mirror of
https://github.com/koush/scrypted.git
synced 2026-02-03 14:13:28 +00:00
sdk: Add strict types to sdk (#1308)
* Enable strict mode * Add @types/node Remove @types/rimraf * Fix `include` path to be actual `src` * Add strict to `sdk` * Assert `getItem` * Fix types in SDK * Refactor SDK function to be type safe * parseValue handle value null or undefined * Fix types tsconfig * Make getDeviceConsole required * Add build-sdk workflow * Set working directory * Assert not undefined * Remove optionals * Undo addScryptedInterfaceProperties, revert to self executing function * Use different type * Make _deviceState private and add ts-ignore * Remove unused function * Remove non-null asserts * Add tsconfig for sdk/types/src * Get property isOptional from schema Use typedoc types * Type fixes * Fix type * Fix type * Revert change
This commit is contained in:
25
.github/workflows/build-sdk.yml
vendored
Normal file
25
.github/workflows/build-sdk.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Build SDK
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths: ["sdk/**"]
|
||||
pull_request:
|
||||
paths: ["sdk/**"]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./sdk
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
@@ -6,10 +6,10 @@ import { DeviceBase, ScryptedInterfaceDescriptors, ScryptedInterfaceProperty, TY
|
||||
* @category Core Reference
|
||||
*/
|
||||
export class ScryptedDeviceBase extends DeviceBase {
|
||||
private _storage: Storage;
|
||||
private _log: Logger;
|
||||
private _console: Console;
|
||||
private _deviceState: DeviceState;
|
||||
private _storage: Storage | undefined;
|
||||
private _log: Logger | undefined;
|
||||
private _console: Console | undefined;
|
||||
private _deviceState: DeviceState | undefined;
|
||||
|
||||
constructor(public readonly nativeId?: string) {
|
||||
super();
|
||||
@@ -43,7 +43,7 @@ export class ScryptedDeviceBase extends DeviceBase {
|
||||
});
|
||||
}
|
||||
|
||||
getMediaObjectConsole(mediaObject: MediaObject): Console {
|
||||
getMediaObjectConsole(mediaObject: MediaObject): Console | undefined {
|
||||
if (typeof mediaObject.sourceId !== 'string')
|
||||
return this.console;
|
||||
return deviceManager.getMixinConsole(mediaObject.sourceId, this.nativeId);
|
||||
@@ -86,17 +86,17 @@ export interface MixinDeviceOptions<T> {
|
||||
mixinProviderNativeId: ScryptedNativeId;
|
||||
mixinDevice: T;
|
||||
mixinDeviceInterfaces: ScryptedInterface[];
|
||||
private _storage: Storage;
|
||||
private mixinStorageSuffix: string;
|
||||
private _log: Logger;
|
||||
private _console: Console;
|
||||
private _storage: Storage | undefined;
|
||||
private mixinStorageSuffix: string | undefined;
|
||||
private _log: Logger | undefined;
|
||||
private _console: Console | undefined;
|
||||
private _deviceState: WritableDeviceState;
|
||||
private _listeners = new Set<EventListenerRegister>();
|
||||
|
||||
constructor(options: MixinDeviceOptions<T>) {
|
||||
super();
|
||||
|
||||
this.nativeId = systemManager.getDeviceById(this.id)?.nativeId;
|
||||
this.nativeId = systemManager.getDeviceById(this.id).nativeId;
|
||||
this.mixinDevice = options.mixinDevice;
|
||||
this.mixinDeviceInterfaces = options.mixinDeviceInterfaces;
|
||||
this.mixinStorageSuffix = options.mixinStorageSuffix;
|
||||
@@ -164,22 +164,24 @@ export interface MixinDeviceOptions<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(function () {
|
||||
function _createGetState(state: any) {
|
||||
function _createGetState<T>(deviceBase: ScryptedDeviceBase | MixinDeviceBase<T>, state: ScryptedInterfaceProperty) {
|
||||
return function () {
|
||||
this._lazyLoadDeviceState();
|
||||
return this._deviceState?.[state];
|
||||
deviceBase._lazyLoadDeviceState();
|
||||
// @ts-ignore: accessing private property
|
||||
return deviceBase._deviceState?.[state];
|
||||
};
|
||||
}
|
||||
|
||||
function _createSetState(state: any) {
|
||||
function _createSetState<T>(deviceBase: ScryptedDeviceBase | MixinDeviceBase<T>, state: ScryptedInterfaceProperty) {
|
||||
return function (value: any) {
|
||||
this._lazyLoadDeviceState();
|
||||
if (!this._deviceState)
|
||||
deviceBase._lazyLoadDeviceState();
|
||||
// @ts-ignore: accessing private property
|
||||
if (!deviceBase._deviceState)
|
||||
console.warn('device state is unavailable. the device must be discovered with deviceManager.onDeviceDiscovered or deviceManager.onDevicesChanged before the state can be set.');
|
||||
else
|
||||
this._deviceState[state] = value;
|
||||
// @ts-ignore: accessing private property
|
||||
deviceBase._deviceState[state] = value;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -187,17 +189,16 @@ export interface MixinDeviceOptions<T> {
|
||||
if (field === ScryptedInterfaceProperty.nativeId)
|
||||
continue;
|
||||
Object.defineProperty(ScryptedDeviceBase.prototype, field, {
|
||||
set: _createSetState(field),
|
||||
get: _createGetState(field),
|
||||
set: _createSetState(ScryptedDeviceBase.prototype, field),
|
||||
get: _createGetState(ScryptedDeviceBase.prototype, field),
|
||||
});
|
||||
Object.defineProperty(MixinDeviceBase.prototype, field, {
|
||||
set: _createSetState(field),
|
||||
get: _createGetState(field),
|
||||
set: _createSetState(MixinDeviceBase.prototype, field),
|
||||
get: _createGetState(MixinDeviceBase.prototype, field),
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
export const sdk: ScryptedStatic = {} as any;
|
||||
declare const deviceManager: DeviceManager;
|
||||
declare const endpointManager: EndpointManager;
|
||||
|
||||
@@ -2,7 +2,11 @@ import sdk, { ScryptedInterface, Setting, Settings, SettingValue } from ".";
|
||||
|
||||
const { systemManager } = sdk;
|
||||
|
||||
function parseValue(value: string, setting: StorageSetting, readDefaultValue: () => any, rawDevice?: boolean) {
|
||||
function parseValue(value: string | null | undefined, setting: StorageSetting, readDefaultValue: () => any, rawDevice?: boolean) {
|
||||
if (value === null || value === undefined) {
|
||||
return readDefaultValue();
|
||||
}
|
||||
|
||||
const type = setting.multiple ? 'array' : setting.type;
|
||||
|
||||
if (type === 'boolean') {
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
|
||||
76
sdk/types/package-lock.json
generated
76
sdk/types/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "0.3.11",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/node": "^18.19.15",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^10.9.1"
|
||||
}
|
||||
@@ -75,36 +75,13 @@
|
||||
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/glob": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.0.0.tgz",
|
||||
"integrity": "sha512-l6NQsDDyQUVeoTynNpC9uRvCUint/gSUXQA2euwmTuWGvPY5LSDUu6tkCtJB2SvGQlJQzLaKqcGZP4//7EDveA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/minimatch": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/minimatch": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
|
||||
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.7.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.20.tgz",
|
||||
"integrity": "sha512-adzY4vLLr5Uivmx8+zfSJ5fbdgKxX8UMtjtl+17n0B1q1Nz8JEmE151vefMdpD+1gyh+77weN4qEhej/O7budQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==",
|
||||
"version": "18.19.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz",
|
||||
"integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/glob": "*",
|
||||
"@types/node": "*"
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
@@ -321,6 +298,12 @@
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
@@ -399,36 +382,13 @@
|
||||
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.0.0.tgz",
|
||||
"integrity": "sha512-l6NQsDDyQUVeoTynNpC9uRvCUint/gSUXQA2euwmTuWGvPY5LSDUu6tkCtJB2SvGQlJQzLaKqcGZP4//7EDveA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/minimatch": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
|
||||
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.7.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.20.tgz",
|
||||
"integrity": "sha512-adzY4vLLr5Uivmx8+zfSJ5fbdgKxX8UMtjtl+17n0B1q1Nz8JEmE151vefMdpD+1gyh+77weN4qEhej/O7budQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==",
|
||||
"version": "18.19.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.15.tgz",
|
||||
"integrity": "sha512-AMZ2UWx+woHNfM11PyAEQmfSxi05jm9OlkxczuHeEqmvwPkYj6MWv44gbzDPefYOLysTOFyI3ziiy2ONmUZfpA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/glob": "*",
|
||||
"@types/node": "*"
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
@@ -586,6 +546,12 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm run build",
|
||||
"build": "rimraf dist gen && typedoc && ts-node ./src/build.ts && tsc"
|
||||
"build": "tsc --project src && rimraf dist gen && typedoc && ts-node ./src/build.ts && tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/node": "^18.19.15",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
|
||||
@@ -22,8 +22,8 @@ function toTypescriptType(type: any): string {
|
||||
}
|
||||
|
||||
for (const name of Object.values(ScryptedInterface)) {
|
||||
const td = schema.children.find((child) => child.name === name);
|
||||
const children = td.children || [];
|
||||
const td = schema.children?.find((child) => child.name === name);
|
||||
const children = td?.children || [];
|
||||
const properties = children.filter((child) => child.kindString === 'Property').map((child) => child.name);
|
||||
const methods = children.filter((child) => child.kindString === 'Method').map((child) => child.name);
|
||||
ScryptedInterfaceDescriptors[name] = {
|
||||
@@ -46,7 +46,7 @@ ${Object.entries(allProperties).map(([property, {type, flags}]) => ` ${property
|
||||
}
|
||||
|
||||
export class DeviceBase implements DeviceState {
|
||||
${Object.entries(allProperties).map(([property, {type, flags}]) => ` ${property}${flags.isOptional ? '?' : ''}: ${toTypescriptType(type)}`).join('\n')};
|
||||
${Object.entries(allProperties).map(([property, {type, flags}]) => ` ${property}${flags.isOptional ? '?' : '!'}: ${toTypescriptType(type)}`).join('\n')};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -138,12 +138,15 @@ function selfSignature(method: any) {
|
||||
return params.join(', ');
|
||||
}
|
||||
|
||||
const enums = schema.children.filter((child: any) => child.kindString === 'Enumeration');
|
||||
const interfaces = schema.children.filter((child: any) => Object.values(ScryptedInterface).includes(child.name));
|
||||
const enums = schema.children?.filter((child: any) => child.kindString === 'Enumeration') ?? [];
|
||||
const interfaces = schema.children?.filter((child: any) => Object.values(ScryptedInterface).includes(child.name)) ?? [];
|
||||
let python = '';
|
||||
|
||||
for (const iface of ['Logger', 'DeviceManager', 'SystemManager', 'MediaManager', 'EndpointManager']) {
|
||||
interfaces.push(schema.children.find((child: any) => child.name === iface));
|
||||
const child = schema.children?.find((child: any) => child.name === iface);
|
||||
|
||||
if (child)
|
||||
interfaces.push(child);
|
||||
}
|
||||
|
||||
let seen = new Set<string>();
|
||||
@@ -226,14 +229,16 @@ for (const td of interfaces) {
|
||||
|
||||
let pythonEnums = ''
|
||||
for (const e of enums) {
|
||||
pythonEnums += `
|
||||
if (e.children) {
|
||||
pythonEnums += `
|
||||
class ${e.name}(str, Enum):
|
||||
${toDocstring(e)}
|
||||
`
|
||||
for (const val of e.children) {
|
||||
if ('type' in val && 'value' in val.type)
|
||||
if (val.type && 'value' in val.type)
|
||||
pythonEnums += ` ${val.name} = "${val.type.value}"
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +287,7 @@ ScryptedInterfaceDescriptors = ${JSON.stringify(ScryptedInterfaceDescriptors, nu
|
||||
`
|
||||
|
||||
while (discoveredTypes.size) {
|
||||
const unknowns = schema.children.filter((child: any) => discoveredTypes.has(child.name) && !enums.find((e: any) => e.name === child.name));
|
||||
const unknowns = schema.children?.filter((child: any) => discoveredTypes.has(child.name) && !enums.find((e: any) => e.name === child.name)) ?? [];
|
||||
|
||||
const newSeen = new Set([...seen, ...discoveredTypes]);
|
||||
discoveredTypes.clear();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true
|
||||
"noEmit": true,
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
@@ -1201,7 +1201,7 @@ export interface AmbientLightSensor {
|
||||
/**
|
||||
* The ambient light in lux.
|
||||
*/
|
||||
ambientLight: number;
|
||||
ambientLight?: number;
|
||||
}
|
||||
export interface OccupancySensor {
|
||||
occupied?: boolean;
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist"
|
||||
"outDir": "dist",
|
||||
"strict": true
|
||||
},
|
||||
"include": [
|
||||
"gen/**/*"
|
||||
|
||||
Reference in New Issue
Block a user