@camstack/sdk 0.1.55 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,759 +1,640 @@
1
- import { createWSClient, createTRPCClient, wsLink, httpLink } from "@trpc/client";
2
- import { createSystemProxy, SystemMirror } from "@camstack/types";
3
- class DoubleIndexedKV {
4
- constructor() {
5
- this.keyToValue = /* @__PURE__ */ new Map();
6
- this.valueToKey = /* @__PURE__ */ new Map();
7
- }
8
- set(key, value) {
9
- this.keyToValue.set(key, value);
10
- this.valueToKey.set(value, key);
11
- }
12
- getByKey(key) {
13
- return this.keyToValue.get(key);
14
- }
15
- getByValue(value) {
16
- return this.valueToKey.get(value);
17
- }
18
- clear() {
19
- this.keyToValue.clear();
20
- this.valueToKey.clear();
21
- }
22
- }
23
- class Registry {
24
- constructor(generateIdentifier) {
25
- this.generateIdentifier = generateIdentifier;
26
- this.kv = new DoubleIndexedKV();
27
- }
28
- register(value, identifier) {
29
- if (this.kv.getByValue(value)) {
30
- return;
31
- }
32
- if (!identifier) {
33
- identifier = this.generateIdentifier(value);
34
- }
35
- this.kv.set(identifier, value);
36
- }
37
- clear() {
38
- this.kv.clear();
39
- }
40
- getIdentifier(value) {
41
- return this.kv.getByValue(value);
42
- }
43
- getValue(identifier) {
44
- return this.kv.getByKey(identifier);
45
- }
46
- }
47
- class ClassRegistry extends Registry {
48
- constructor() {
49
- super((c) => c.name);
50
- this.classToAllowedProps = /* @__PURE__ */ new Map();
51
- }
52
- register(value, options) {
53
- if (typeof options === "object") {
54
- if (options.allowProps) {
55
- this.classToAllowedProps.set(value, options.allowProps);
56
- }
57
- super.register(value, options.identifier);
58
- } else {
59
- super.register(value, options);
60
- }
61
- }
62
- getAllowedProps(value) {
63
- return this.classToAllowedProps.get(value);
64
- }
65
- }
1
+ import { createTRPCClient, createWSClient, httpLink, wsLink } from "@trpc/client";
2
+ import { SystemMirror, createSystemProxy } from "@camstack/types";
3
+ //#region ../../node_modules/superjson/dist/double-indexed-kv.js
4
+ var DoubleIndexedKV = class {
5
+ constructor() {
6
+ this.keyToValue = /* @__PURE__ */ new Map();
7
+ this.valueToKey = /* @__PURE__ */ new Map();
8
+ }
9
+ set(key, value) {
10
+ this.keyToValue.set(key, value);
11
+ this.valueToKey.set(value, key);
12
+ }
13
+ getByKey(key) {
14
+ return this.keyToValue.get(key);
15
+ }
16
+ getByValue(value) {
17
+ return this.valueToKey.get(value);
18
+ }
19
+ clear() {
20
+ this.keyToValue.clear();
21
+ this.valueToKey.clear();
22
+ }
23
+ };
24
+ //#endregion
25
+ //#region ../../node_modules/superjson/dist/registry.js
26
+ var Registry = class {
27
+ constructor(generateIdentifier) {
28
+ this.generateIdentifier = generateIdentifier;
29
+ this.kv = new DoubleIndexedKV();
30
+ }
31
+ register(value, identifier) {
32
+ if (this.kv.getByValue(value)) return;
33
+ if (!identifier) identifier = this.generateIdentifier(value);
34
+ this.kv.set(identifier, value);
35
+ }
36
+ clear() {
37
+ this.kv.clear();
38
+ }
39
+ getIdentifier(value) {
40
+ return this.kv.getByValue(value);
41
+ }
42
+ getValue(identifier) {
43
+ return this.kv.getByKey(identifier);
44
+ }
45
+ };
46
+ //#endregion
47
+ //#region ../../node_modules/superjson/dist/class-registry.js
48
+ var ClassRegistry = class extends Registry {
49
+ constructor() {
50
+ super((c) => c.name);
51
+ this.classToAllowedProps = /* @__PURE__ */ new Map();
52
+ }
53
+ register(value, options) {
54
+ if (typeof options === "object") {
55
+ if (options.allowProps) this.classToAllowedProps.set(value, options.allowProps);
56
+ super.register(value, options.identifier);
57
+ } else super.register(value, options);
58
+ }
59
+ getAllowedProps(value) {
60
+ return this.classToAllowedProps.get(value);
61
+ }
62
+ };
63
+ //#endregion
64
+ //#region ../../node_modules/superjson/dist/util.js
66
65
  function valuesOfObj(record) {
67
- if ("values" in Object) {
68
- return Object.values(record);
69
- }
70
- const values = [];
71
- for (const key in record) {
72
- if (record.hasOwnProperty(key)) {
73
- values.push(record[key]);
74
- }
75
- }
76
- return values;
66
+ if ("values" in Object) return Object.values(record);
67
+ const values = [];
68
+ for (const key in record) if (record.hasOwnProperty(key)) values.push(record[key]);
69
+ return values;
77
70
  }
78
71
  function find(record, predicate) {
79
- const values = valuesOfObj(record);
80
- if ("find" in values) {
81
- return values.find(predicate);
82
- }
83
- const valuesNotNever = values;
84
- for (let i = 0; i < valuesNotNever.length; i++) {
85
- const value = valuesNotNever[i];
86
- if (predicate(value)) {
87
- return value;
88
- }
89
- }
90
- return void 0;
72
+ const values = valuesOfObj(record);
73
+ if ("find" in values) return values.find(predicate);
74
+ const valuesNotNever = values;
75
+ for (let i = 0; i < valuesNotNever.length; i++) {
76
+ const value = valuesNotNever[i];
77
+ if (predicate(value)) return value;
78
+ }
91
79
  }
92
80
  function forEach(record, run) {
93
- Object.entries(record).forEach(([key, value]) => run(value, key));
81
+ Object.entries(record).forEach(([key, value]) => run(value, key));
94
82
  }
95
83
  function includes(arr, value) {
96
- return arr.indexOf(value) !== -1;
84
+ return arr.indexOf(value) !== -1;
97
85
  }
98
86
  function findArr(record, predicate) {
99
- for (let i = 0; i < record.length; i++) {
100
- const value = record[i];
101
- if (predicate(value)) {
102
- return value;
103
- }
104
- }
105
- return void 0;
106
- }
107
- class CustomTransformerRegistry {
108
- constructor() {
109
- this.transfomers = {};
110
- }
111
- register(transformer) {
112
- this.transfomers[transformer.name] = transformer;
113
- }
114
- findApplicable(v) {
115
- return find(this.transfomers, (transformer) => transformer.isApplicable(v));
116
- }
117
- findByName(name) {
118
- return this.transfomers[name];
119
- }
87
+ for (let i = 0; i < record.length; i++) {
88
+ const value = record[i];
89
+ if (predicate(value)) return value;
90
+ }
120
91
  }
121
- const getType$1 = (payload) => Object.prototype.toString.call(payload).slice(8, -1);
122
- const isUndefined = (payload) => typeof payload === "undefined";
123
- const isNull = (payload) => payload === null;
124
- const isPlainObject$1 = (payload) => {
125
- if (typeof payload !== "object" || payload === null)
126
- return false;
127
- if (payload === Object.prototype)
128
- return false;
129
- if (Object.getPrototypeOf(payload) === null)
130
- return true;
131
- return Object.getPrototypeOf(payload) === Object.prototype;
92
+ //#endregion
93
+ //#region ../../node_modules/superjson/dist/custom-transformer-registry.js
94
+ var CustomTransformerRegistry = class {
95
+ constructor() {
96
+ this.transfomers = {};
97
+ }
98
+ register(transformer) {
99
+ this.transfomers[transformer.name] = transformer;
100
+ }
101
+ findApplicable(v) {
102
+ return find(this.transfomers, (transformer) => transformer.isApplicable(v));
103
+ }
104
+ findByName(name) {
105
+ return this.transfomers[name];
106
+ }
132
107
  };
133
- const isEmptyObject = (payload) => isPlainObject$1(payload) && Object.keys(payload).length === 0;
134
- const isArray$1 = (payload) => Array.isArray(payload);
135
- const isString = (payload) => typeof payload === "string";
136
- const isNumber = (payload) => typeof payload === "number" && !isNaN(payload);
137
- const isBoolean = (payload) => typeof payload === "boolean";
138
- const isRegExp = (payload) => payload instanceof RegExp;
139
- const isMap = (payload) => payload instanceof Map;
140
- const isSet = (payload) => payload instanceof Set;
141
- const isSymbol = (payload) => getType$1(payload) === "Symbol";
142
- const isDate = (payload) => payload instanceof Date && !isNaN(payload.valueOf());
143
- const isError = (payload) => payload instanceof Error;
144
- const isNaNValue = (payload) => typeof payload === "number" && isNaN(payload);
145
- const isPrimitive = (payload) => isBoolean(payload) || isNull(payload) || isUndefined(payload) || isNumber(payload) || isString(payload) || isSymbol(payload);
146
- const isBigint = (payload) => typeof payload === "bigint";
147
- const isInfinite = (payload) => payload === Infinity || payload === -Infinity;
148
- const isTypedArray = (payload) => ArrayBuffer.isView(payload) && !(payload instanceof DataView);
149
- const isURL = (payload) => payload instanceof URL;
150
- const escapeKey = (key) => key.replace(/\\/g, "\\\\").replace(/\./g, "\\.");
151
- const stringifyPath = (path) => path.map(String).map(escapeKey).join(".");
152
- const parsePath = (string, legacyPaths) => {
153
- const result = [];
154
- let segment = "";
155
- for (let i = 0; i < string.length; i++) {
156
- let char = string.charAt(i);
157
- if (!legacyPaths && char === "\\") {
158
- const escaped = string.charAt(i + 1);
159
- if (escaped === "\\") {
160
- segment += "\\";
161
- i++;
162
- continue;
163
- } else if (escaped !== ".") {
164
- throw Error("invalid path");
165
- }
166
- }
167
- const isEscapedDot = char === "\\" && string.charAt(i + 1) === ".";
168
- if (isEscapedDot) {
169
- segment += ".";
170
- i++;
171
- continue;
172
- }
173
- const isEndOfSegment = char === ".";
174
- if (isEndOfSegment) {
175
- result.push(segment);
176
- segment = "";
177
- continue;
178
- }
179
- segment += char;
180
- }
181
- const lastSegment = segment;
182
- result.push(lastSegment);
183
- return result;
108
+ //#endregion
109
+ //#region ../../node_modules/superjson/dist/is.js
110
+ var getType$1 = (payload) => Object.prototype.toString.call(payload).slice(8, -1);
111
+ var isUndefined = (payload) => typeof payload === "undefined";
112
+ var isNull = (payload) => payload === null;
113
+ var isPlainObject$1 = (payload) => {
114
+ if (typeof payload !== "object" || payload === null) return false;
115
+ if (payload === Object.prototype) return false;
116
+ if (Object.getPrototypeOf(payload) === null) return true;
117
+ return Object.getPrototypeOf(payload) === Object.prototype;
184
118
  };
119
+ var isEmptyObject = (payload) => isPlainObject$1(payload) && Object.keys(payload).length === 0;
120
+ var isArray$1 = (payload) => Array.isArray(payload);
121
+ var isString = (payload) => typeof payload === "string";
122
+ var isNumber = (payload) => typeof payload === "number" && !isNaN(payload);
123
+ var isBoolean = (payload) => typeof payload === "boolean";
124
+ var isRegExp = (payload) => payload instanceof RegExp;
125
+ var isMap = (payload) => payload instanceof Map;
126
+ var isSet = (payload) => payload instanceof Set;
127
+ var isSymbol = (payload) => getType$1(payload) === "Symbol";
128
+ var isDate = (payload) => payload instanceof Date && !isNaN(payload.valueOf());
129
+ var isError = (payload) => payload instanceof Error;
130
+ var isNaNValue = (payload) => typeof payload === "number" && isNaN(payload);
131
+ var isPrimitive = (payload) => isBoolean(payload) || isNull(payload) || isUndefined(payload) || isNumber(payload) || isString(payload) || isSymbol(payload);
132
+ var isBigint = (payload) => typeof payload === "bigint";
133
+ var isInfinite = (payload) => payload === Infinity || payload === -Infinity;
134
+ var isTypedArray = (payload) => ArrayBuffer.isView(payload) && !(payload instanceof DataView);
135
+ var isURL = (payload) => payload instanceof URL;
136
+ //#endregion
137
+ //#region ../../node_modules/superjson/dist/pathstringifier.js
138
+ var escapeKey = (key) => key.replace(/\\/g, "\\\\").replace(/\./g, "\\.");
139
+ var stringifyPath = (path) => path.map(String).map(escapeKey).join(".");
140
+ var parsePath = (string, legacyPaths) => {
141
+ const result = [];
142
+ let segment = "";
143
+ for (let i = 0; i < string.length; i++) {
144
+ let char = string.charAt(i);
145
+ if (!legacyPaths && char === "\\") {
146
+ const escaped = string.charAt(i + 1);
147
+ if (escaped === "\\") {
148
+ segment += "\\";
149
+ i++;
150
+ continue;
151
+ } else if (escaped !== ".") throw Error("invalid path");
152
+ }
153
+ if (char === "\\" && string.charAt(i + 1) === ".") {
154
+ segment += ".";
155
+ i++;
156
+ continue;
157
+ }
158
+ if (char === ".") {
159
+ result.push(segment);
160
+ segment = "";
161
+ continue;
162
+ }
163
+ segment += char;
164
+ }
165
+ const lastSegment = segment;
166
+ result.push(lastSegment);
167
+ return result;
168
+ };
169
+ //#endregion
170
+ //#region ../../node_modules/superjson/dist/transformer.js
185
171
  function simpleTransformation(isApplicable, annotation, transform, untransform) {
186
- return {
187
- isApplicable,
188
- annotation,
189
- transform,
190
- untransform
191
- };
172
+ return {
173
+ isApplicable,
174
+ annotation,
175
+ transform,
176
+ untransform
177
+ };
192
178
  }
193
- const simpleRules = [
194
- simpleTransformation(isUndefined, "undefined", () => null, () => void 0),
195
- simpleTransformation(isBigint, "bigint", (v) => v.toString(), (v) => {
196
- if (typeof BigInt !== "undefined") {
197
- return BigInt(v);
198
- }
199
- console.error("Please add a BigInt polyfill.");
200
- return v;
201
- }),
202
- simpleTransformation(isDate, "Date", (v) => v.toISOString(), (v) => new Date(v)),
203
- simpleTransformation(isError, "Error", (v, superJson) => {
204
- const baseError = {
205
- name: v.name,
206
- message: v.message
207
- };
208
- if ("cause" in v) {
209
- baseError.cause = v.cause;
210
- }
211
- superJson.allowedErrorProps.forEach((prop) => {
212
- baseError[prop] = v[prop];
213
- });
214
- return baseError;
215
- }, (v, superJson) => {
216
- const e = new Error(v.message, { cause: v.cause });
217
- e.name = v.name;
218
- e.stack = v.stack;
219
- superJson.allowedErrorProps.forEach((prop) => {
220
- e[prop] = v[prop];
221
- });
222
- return e;
223
- }),
224
- simpleTransformation(isRegExp, "regexp", (v) => "" + v, (regex) => {
225
- const body = regex.slice(1, regex.lastIndexOf("/"));
226
- const flags = regex.slice(regex.lastIndexOf("/") + 1);
227
- return new RegExp(body, flags);
228
- }),
229
- simpleTransformation(
230
- isSet,
231
- "set",
232
- // (sets only exist in es6+)
233
- // eslint-disable-next-line es5/no-es6-methods
234
- (v) => [...v.values()],
235
- (v) => new Set(v)
236
- ),
237
- simpleTransformation(isMap, "map", (v) => [...v.entries()], (v) => new Map(v)),
238
- simpleTransformation((v) => isNaNValue(v) || isInfinite(v), "number", (v) => {
239
- if (isNaNValue(v)) {
240
- return "NaN";
241
- }
242
- if (v > 0) {
243
- return "Infinity";
244
- } else {
245
- return "-Infinity";
246
- }
247
- }, Number),
248
- simpleTransformation((v) => v === 0 && 1 / v === -Infinity, "number", () => {
249
- return "-0";
250
- }, Number),
251
- simpleTransformation(isURL, "URL", (v) => v.toString(), (v) => new URL(v))
179
+ var simpleRules = [
180
+ simpleTransformation(isUndefined, "undefined", () => null, () => void 0),
181
+ simpleTransformation(isBigint, "bigint", (v) => v.toString(), (v) => {
182
+ if (typeof BigInt !== "undefined") return BigInt(v);
183
+ console.error("Please add a BigInt polyfill.");
184
+ return v;
185
+ }),
186
+ simpleTransformation(isDate, "Date", (v) => v.toISOString(), (v) => new Date(v)),
187
+ simpleTransformation(isError, "Error", (v, superJson) => {
188
+ const baseError = {
189
+ name: v.name,
190
+ message: v.message
191
+ };
192
+ if ("cause" in v) baseError.cause = v.cause;
193
+ superJson.allowedErrorProps.forEach((prop) => {
194
+ baseError[prop] = v[prop];
195
+ });
196
+ return baseError;
197
+ }, (v, superJson) => {
198
+ const e = new Error(v.message, { cause: v.cause });
199
+ e.name = v.name;
200
+ e.stack = v.stack;
201
+ superJson.allowedErrorProps.forEach((prop) => {
202
+ e[prop] = v[prop];
203
+ });
204
+ return e;
205
+ }),
206
+ simpleTransformation(isRegExp, "regexp", (v) => "" + v, (regex) => {
207
+ const body = regex.slice(1, regex.lastIndexOf("/"));
208
+ const flags = regex.slice(regex.lastIndexOf("/") + 1);
209
+ return new RegExp(body, flags);
210
+ }),
211
+ simpleTransformation(isSet, "set", (v) => [...v.values()], (v) => new Set(v)),
212
+ simpleTransformation(isMap, "map", (v) => [...v.entries()], (v) => new Map(v)),
213
+ simpleTransformation((v) => isNaNValue(v) || isInfinite(v), "number", (v) => {
214
+ if (isNaNValue(v)) return "NaN";
215
+ if (v > 0) return "Infinity";
216
+ else return "-Infinity";
217
+ }, Number),
218
+ simpleTransformation((v) => v === 0 && 1 / v === -Infinity, "number", () => {
219
+ return "-0";
220
+ }, Number),
221
+ simpleTransformation(isURL, "URL", (v) => v.toString(), (v) => new URL(v))
252
222
  ];
253
223
  function compositeTransformation(isApplicable, annotation, transform, untransform) {
254
- return {
255
- isApplicable,
256
- annotation,
257
- transform,
258
- untransform
259
- };
224
+ return {
225
+ isApplicable,
226
+ annotation,
227
+ transform,
228
+ untransform
229
+ };
260
230
  }
261
- const symbolRule = compositeTransformation((s, superJson) => {
262
- if (isSymbol(s)) {
263
- const isRegistered = !!superJson.symbolRegistry.getIdentifier(s);
264
- return isRegistered;
265
- }
266
- return false;
231
+ var symbolRule = compositeTransformation((s, superJson) => {
232
+ if (isSymbol(s)) return !!superJson.symbolRegistry.getIdentifier(s);
233
+ return false;
267
234
  }, (s, superJson) => {
268
- const identifier = superJson.symbolRegistry.getIdentifier(s);
269
- return ["symbol", identifier];
235
+ return ["symbol", superJson.symbolRegistry.getIdentifier(s)];
270
236
  }, (v) => v.description, (_, a, superJson) => {
271
- const value = superJson.symbolRegistry.getValue(a[1]);
272
- if (!value) {
273
- throw new Error("Trying to deserialize unknown symbol");
274
- }
275
- return value;
237
+ const value = superJson.symbolRegistry.getValue(a[1]);
238
+ if (!value) throw new Error("Trying to deserialize unknown symbol");
239
+ return value;
276
240
  });
277
- const constructorToName = [
278
- Int8Array,
279
- Uint8Array,
280
- Int16Array,
281
- Uint16Array,
282
- Int32Array,
283
- Uint32Array,
284
- Float32Array,
285
- Float64Array,
286
- Uint8ClampedArray
241
+ var constructorToName = [
242
+ Int8Array,
243
+ Uint8Array,
244
+ Int16Array,
245
+ Uint16Array,
246
+ Int32Array,
247
+ Uint32Array,
248
+ Float32Array,
249
+ Float64Array,
250
+ Uint8ClampedArray
287
251
  ].reduce((obj, ctor) => {
288
- obj[ctor.name] = ctor;
289
- return obj;
252
+ obj[ctor.name] = ctor;
253
+ return obj;
290
254
  }, {});
291
- const typedArrayRule = compositeTransformation(isTypedArray, (v) => ["typed-array", v.constructor.name], (v) => [...v], (v, a) => {
292
- const ctor = constructorToName[a[1]];
293
- if (!ctor) {
294
- throw new Error("Trying to deserialize unknown typed array");
295
- }
296
- return new ctor(v);
255
+ var typedArrayRule = compositeTransformation(isTypedArray, (v) => ["typed-array", v.constructor.name], (v) => [...v], (v, a) => {
256
+ const ctor = constructorToName[a[1]];
257
+ if (!ctor) throw new Error("Trying to deserialize unknown typed array");
258
+ return new ctor(v);
297
259
  });
298
260
  function isInstanceOfRegisteredClass(potentialClass, superJson) {
299
- if (potentialClass?.constructor) {
300
- const isRegistered = !!superJson.classRegistry.getIdentifier(potentialClass.constructor);
301
- return isRegistered;
302
- }
303
- return false;
261
+ if (potentialClass?.constructor) return !!superJson.classRegistry.getIdentifier(potentialClass.constructor);
262
+ return false;
304
263
  }
305
- const classRule = compositeTransformation(isInstanceOfRegisteredClass, (clazz, superJson) => {
306
- const identifier = superJson.classRegistry.getIdentifier(clazz.constructor);
307
- return ["class", identifier];
264
+ var classRule = compositeTransformation(isInstanceOfRegisteredClass, (clazz, superJson) => {
265
+ return ["class", superJson.classRegistry.getIdentifier(clazz.constructor)];
308
266
  }, (clazz, superJson) => {
309
- const allowedProps = superJson.classRegistry.getAllowedProps(clazz.constructor);
310
- if (!allowedProps) {
311
- return { ...clazz };
312
- }
313
- const result = {};
314
- allowedProps.forEach((prop) => {
315
- result[prop] = clazz[prop];
316
- });
317
- return result;
267
+ const allowedProps = superJson.classRegistry.getAllowedProps(clazz.constructor);
268
+ if (!allowedProps) return { ...clazz };
269
+ const result = {};
270
+ allowedProps.forEach((prop) => {
271
+ result[prop] = clazz[prop];
272
+ });
273
+ return result;
318
274
  }, (v, a, superJson) => {
319
- const clazz = superJson.classRegistry.getValue(a[1]);
320
- if (!clazz) {
321
- throw new Error(`Trying to deserialize unknown class '${a[1]}' - check https://github.com/blitz-js/superjson/issues/116#issuecomment-773996564`);
322
- }
323
- return Object.assign(Object.create(clazz.prototype), v);
275
+ const clazz = superJson.classRegistry.getValue(a[1]);
276
+ if (!clazz) throw new Error(`Trying to deserialize unknown class '${a[1]}' - check https://github.com/blitz-js/superjson/issues/116#issuecomment-773996564`);
277
+ return Object.assign(Object.create(clazz.prototype), v);
324
278
  });
325
- const customRule = compositeTransformation((value, superJson) => {
326
- return !!superJson.customTransformerRegistry.findApplicable(value);
279
+ var customRule = compositeTransformation((value, superJson) => {
280
+ return !!superJson.customTransformerRegistry.findApplicable(value);
327
281
  }, (value, superJson) => {
328
- const transformer = superJson.customTransformerRegistry.findApplicable(value);
329
- return ["custom", transformer.name];
282
+ return ["custom", superJson.customTransformerRegistry.findApplicable(value).name];
330
283
  }, (value, superJson) => {
331
- const transformer = superJson.customTransformerRegistry.findApplicable(value);
332
- return transformer.serialize(value);
284
+ return superJson.customTransformerRegistry.findApplicable(value).serialize(value);
333
285
  }, (v, a, superJson) => {
334
- const transformer = superJson.customTransformerRegistry.findByName(a[1]);
335
- if (!transformer) {
336
- throw new Error("Trying to deserialize unknown custom value");
337
- }
338
- return transformer.deserialize(v);
286
+ const transformer = superJson.customTransformerRegistry.findByName(a[1]);
287
+ if (!transformer) throw new Error("Trying to deserialize unknown custom value");
288
+ return transformer.deserialize(v);
339
289
  });
340
- const compositeRules = [classRule, symbolRule, customRule, typedArrayRule];
341
- const transformValue = (value, superJson) => {
342
- const applicableCompositeRule = findArr(compositeRules, (rule) => rule.isApplicable(value, superJson));
343
- if (applicableCompositeRule) {
344
- return {
345
- value: applicableCompositeRule.transform(value, superJson),
346
- type: applicableCompositeRule.annotation(value, superJson)
347
- };
348
- }
349
- const applicableSimpleRule = findArr(simpleRules, (rule) => rule.isApplicable(value, superJson));
350
- if (applicableSimpleRule) {
351
- return {
352
- value: applicableSimpleRule.transform(value, superJson),
353
- type: applicableSimpleRule.annotation
354
- };
355
- }
356
- return void 0;
290
+ var compositeRules = [
291
+ classRule,
292
+ symbolRule,
293
+ customRule,
294
+ typedArrayRule
295
+ ];
296
+ var transformValue = (value, superJson) => {
297
+ const applicableCompositeRule = findArr(compositeRules, (rule) => rule.isApplicable(value, superJson));
298
+ if (applicableCompositeRule) return {
299
+ value: applicableCompositeRule.transform(value, superJson),
300
+ type: applicableCompositeRule.annotation(value, superJson)
301
+ };
302
+ const applicableSimpleRule = findArr(simpleRules, (rule) => rule.isApplicable(value, superJson));
303
+ if (applicableSimpleRule) return {
304
+ value: applicableSimpleRule.transform(value, superJson),
305
+ type: applicableSimpleRule.annotation
306
+ };
357
307
  };
358
- const simpleRulesByAnnotation = {};
308
+ var simpleRulesByAnnotation = {};
359
309
  simpleRules.forEach((rule) => {
360
- simpleRulesByAnnotation[rule.annotation] = rule;
310
+ simpleRulesByAnnotation[rule.annotation] = rule;
361
311
  });
362
- const untransformValue = (json, type, superJson) => {
363
- if (isArray$1(type)) {
364
- switch (type[0]) {
365
- case "symbol":
366
- return symbolRule.untransform(json, type, superJson);
367
- case "class":
368
- return classRule.untransform(json, type, superJson);
369
- case "custom":
370
- return customRule.untransform(json, type, superJson);
371
- case "typed-array":
372
- return typedArrayRule.untransform(json, type, superJson);
373
- default:
374
- throw new Error("Unknown transformation: " + type);
375
- }
376
- } else {
377
- const transformation = simpleRulesByAnnotation[type];
378
- if (!transformation) {
379
- throw new Error("Unknown transformation: " + type);
380
- }
381
- return transformation.untransform(json, superJson);
382
- }
312
+ var untransformValue = (json, type, superJson) => {
313
+ if (isArray$1(type)) switch (type[0]) {
314
+ case "symbol": return symbolRule.untransform(json, type, superJson);
315
+ case "class": return classRule.untransform(json, type, superJson);
316
+ case "custom": return customRule.untransform(json, type, superJson);
317
+ case "typed-array": return typedArrayRule.untransform(json, type, superJson);
318
+ default: throw new Error("Unknown transformation: " + type);
319
+ }
320
+ else {
321
+ const transformation = simpleRulesByAnnotation[type];
322
+ if (!transformation) throw new Error("Unknown transformation: " + type);
323
+ return transformation.untransform(json, superJson);
324
+ }
383
325
  };
384
- const getNthKey = (value, n) => {
385
- if (n > value.size)
386
- throw new Error("index out of bounds");
387
- const keys = value.keys();
388
- while (n > 0) {
389
- keys.next();
390
- n--;
391
- }
392
- return keys.next().value;
326
+ //#endregion
327
+ //#region ../../node_modules/superjson/dist/accessDeep.js
328
+ var getNthKey = (value, n) => {
329
+ if (n > value.size) throw new Error("index out of bounds");
330
+ const keys = value.keys();
331
+ while (n > 0) {
332
+ keys.next();
333
+ n--;
334
+ }
335
+ return keys.next().value;
393
336
  };
394
337
  function validatePath(path) {
395
- if (includes(path, "__proto__")) {
396
- throw new Error("__proto__ is not allowed as a property");
397
- }
398
- if (includes(path, "prototype")) {
399
- throw new Error("prototype is not allowed as a property");
400
- }
401
- if (includes(path, "constructor")) {
402
- throw new Error("constructor is not allowed as a property");
403
- }
338
+ if (includes(path, "__proto__")) throw new Error("__proto__ is not allowed as a property");
339
+ if (includes(path, "prototype")) throw new Error("prototype is not allowed as a property");
340
+ if (includes(path, "constructor")) throw new Error("constructor is not allowed as a property");
404
341
  }
405
- const getDeep = (object, path) => {
406
- validatePath(path);
407
- for (let i = 0; i < path.length; i++) {
408
- const key = path[i];
409
- if (isSet(object)) {
410
- object = getNthKey(object, +key);
411
- } else if (isMap(object)) {
412
- const row = +key;
413
- const type = +path[++i] === 0 ? "key" : "value";
414
- const keyOfRow = getNthKey(object, row);
415
- switch (type) {
416
- case "key":
417
- object = keyOfRow;
418
- break;
419
- case "value":
420
- object = object.get(keyOfRow);
421
- break;
422
- }
423
- } else {
424
- object = object[key];
425
- }
426
- }
427
- return object;
342
+ var getDeep = (object, path) => {
343
+ validatePath(path);
344
+ for (let i = 0; i < path.length; i++) {
345
+ const key = path[i];
346
+ if (isSet(object)) object = getNthKey(object, +key);
347
+ else if (isMap(object)) {
348
+ const row = +key;
349
+ const type = +path[++i] === 0 ? "key" : "value";
350
+ const keyOfRow = getNthKey(object, row);
351
+ switch (type) {
352
+ case "key":
353
+ object = keyOfRow;
354
+ break;
355
+ case "value":
356
+ object = object.get(keyOfRow);
357
+ break;
358
+ }
359
+ } else object = object[key];
360
+ }
361
+ return object;
428
362
  };
429
- const setDeep = (object, path, mapper) => {
430
- validatePath(path);
431
- if (path.length === 0) {
432
- return mapper(object);
433
- }
434
- let parent = object;
435
- for (let i = 0; i < path.length - 1; i++) {
436
- const key = path[i];
437
- if (isArray$1(parent)) {
438
- const index = +key;
439
- parent = parent[index];
440
- } else if (isPlainObject$1(parent)) {
441
- parent = parent[key];
442
- } else if (isSet(parent)) {
443
- const row = +key;
444
- parent = getNthKey(parent, row);
445
- } else if (isMap(parent)) {
446
- const isEnd = i === path.length - 2;
447
- if (isEnd) {
448
- break;
449
- }
450
- const row = +key;
451
- const type = +path[++i] === 0 ? "key" : "value";
452
- const keyOfRow = getNthKey(parent, row);
453
- switch (type) {
454
- case "key":
455
- parent = keyOfRow;
456
- break;
457
- case "value":
458
- parent = parent.get(keyOfRow);
459
- break;
460
- }
461
- }
462
- }
463
- const lastKey = path[path.length - 1];
464
- if (isArray$1(parent)) {
465
- parent[+lastKey] = mapper(parent[+lastKey]);
466
- } else if (isPlainObject$1(parent)) {
467
- parent[lastKey] = mapper(parent[lastKey]);
468
- }
469
- if (isSet(parent)) {
470
- const oldValue = getNthKey(parent, +lastKey);
471
- const newValue = mapper(oldValue);
472
- if (oldValue !== newValue) {
473
- parent.delete(oldValue);
474
- parent.add(newValue);
475
- }
476
- }
477
- if (isMap(parent)) {
478
- const row = +path[path.length - 2];
479
- const keyToRow = getNthKey(parent, row);
480
- const type = +lastKey === 0 ? "key" : "value";
481
- switch (type) {
482
- case "key": {
483
- const newKey = mapper(keyToRow);
484
- parent.set(newKey, parent.get(keyToRow));
485
- if (newKey !== keyToRow) {
486
- parent.delete(keyToRow);
487
- }
488
- break;
489
- }
490
- case "value": {
491
- parent.set(keyToRow, mapper(parent.get(keyToRow)));
492
- break;
493
- }
494
- }
495
- }
496
- return object;
363
+ var setDeep = (object, path, mapper) => {
364
+ validatePath(path);
365
+ if (path.length === 0) return mapper(object);
366
+ let parent = object;
367
+ for (let i = 0; i < path.length - 1; i++) {
368
+ const key = path[i];
369
+ if (isArray$1(parent)) {
370
+ const index = +key;
371
+ parent = parent[index];
372
+ } else if (isPlainObject$1(parent)) parent = parent[key];
373
+ else if (isSet(parent)) {
374
+ const row = +key;
375
+ parent = getNthKey(parent, row);
376
+ } else if (isMap(parent)) {
377
+ if (i === path.length - 2) break;
378
+ const row = +key;
379
+ const type = +path[++i] === 0 ? "key" : "value";
380
+ const keyOfRow = getNthKey(parent, row);
381
+ switch (type) {
382
+ case "key":
383
+ parent = keyOfRow;
384
+ break;
385
+ case "value":
386
+ parent = parent.get(keyOfRow);
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ const lastKey = path[path.length - 1];
392
+ if (isArray$1(parent)) parent[+lastKey] = mapper(parent[+lastKey]);
393
+ else if (isPlainObject$1(parent)) parent[lastKey] = mapper(parent[lastKey]);
394
+ if (isSet(parent)) {
395
+ const oldValue = getNthKey(parent, +lastKey);
396
+ const newValue = mapper(oldValue);
397
+ if (oldValue !== newValue) {
398
+ parent.delete(oldValue);
399
+ parent.add(newValue);
400
+ }
401
+ }
402
+ if (isMap(parent)) {
403
+ const row = +path[path.length - 2];
404
+ const keyToRow = getNthKey(parent, row);
405
+ switch (+lastKey === 0 ? "key" : "value") {
406
+ case "key": {
407
+ const newKey = mapper(keyToRow);
408
+ parent.set(newKey, parent.get(keyToRow));
409
+ if (newKey !== keyToRow) parent.delete(keyToRow);
410
+ break;
411
+ }
412
+ case "value":
413
+ parent.set(keyToRow, mapper(parent.get(keyToRow)));
414
+ break;
415
+ }
416
+ }
417
+ return object;
497
418
  };
498
- const enableLegacyPaths = (version) => version < 1;
499
- function traverse(tree, walker2, version, origin = []) {
500
- if (!tree) {
501
- return;
502
- }
503
- const legacyPaths = enableLegacyPaths(version);
504
- if (!isArray$1(tree)) {
505
- forEach(tree, (subtree, key) => traverse(subtree, walker2, version, [
506
- ...origin,
507
- ...parsePath(key, legacyPaths)
508
- ]));
509
- return;
510
- }
511
- const [nodeValue, children] = tree;
512
- if (children) {
513
- forEach(children, (child, key) => {
514
- traverse(child, walker2, version, [
515
- ...origin,
516
- ...parsePath(key, legacyPaths)
517
- ]);
518
- });
519
- }
520
- walker2(nodeValue, origin);
419
+ //#endregion
420
+ //#region ../../node_modules/superjson/dist/plainer.js
421
+ var enableLegacyPaths = (version) => version < 1;
422
+ function traverse(tree, walker, version, origin = []) {
423
+ if (!tree) return;
424
+ const legacyPaths = enableLegacyPaths(version);
425
+ if (!isArray$1(tree)) {
426
+ forEach(tree, (subtree, key) => traverse(subtree, walker, version, [...origin, ...parsePath(key, legacyPaths)]));
427
+ return;
428
+ }
429
+ const [nodeValue, children] = tree;
430
+ if (children) forEach(children, (child, key) => {
431
+ traverse(child, walker, version, [...origin, ...parsePath(key, legacyPaths)]);
432
+ });
433
+ walker(nodeValue, origin);
521
434
  }
522
435
  function applyValueAnnotations(plain, annotations, version, superJson) {
523
- traverse(annotations, (type, path) => {
524
- plain = setDeep(plain, path, (v) => untransformValue(v, type, superJson));
525
- }, version);
526
- return plain;
436
+ traverse(annotations, (type, path) => {
437
+ plain = setDeep(plain, path, (v) => untransformValue(v, type, superJson));
438
+ }, version);
439
+ return plain;
527
440
  }
528
441
  function applyReferentialEqualityAnnotations(plain, annotations, version) {
529
- const legacyPaths = enableLegacyPaths(version);
530
- function apply(identicalPaths, path) {
531
- const object = getDeep(plain, parsePath(path, legacyPaths));
532
- identicalPaths.map((path2) => parsePath(path2, legacyPaths)).forEach((identicalObjectPath) => {
533
- plain = setDeep(plain, identicalObjectPath, () => object);
534
- });
535
- }
536
- if (isArray$1(annotations)) {
537
- const [root, other] = annotations;
538
- root.forEach((identicalPath) => {
539
- plain = setDeep(plain, parsePath(identicalPath, legacyPaths), () => plain);
540
- });
541
- if (other) {
542
- forEach(other, apply);
543
- }
544
- } else {
545
- forEach(annotations, apply);
546
- }
547
- return plain;
442
+ const legacyPaths = enableLegacyPaths(version);
443
+ function apply(identicalPaths, path) {
444
+ const object = getDeep(plain, parsePath(path, legacyPaths));
445
+ identicalPaths.map((path) => parsePath(path, legacyPaths)).forEach((identicalObjectPath) => {
446
+ plain = setDeep(plain, identicalObjectPath, () => object);
447
+ });
448
+ }
449
+ if (isArray$1(annotations)) {
450
+ const [root, other] = annotations;
451
+ root.forEach((identicalPath) => {
452
+ plain = setDeep(plain, parsePath(identicalPath, legacyPaths), () => plain);
453
+ });
454
+ if (other) forEach(other, apply);
455
+ } else forEach(annotations, apply);
456
+ return plain;
548
457
  }
549
- const isDeep = (object, superJson) => isPlainObject$1(object) || isArray$1(object) || isMap(object) || isSet(object) || isError(object) || isInstanceOfRegisteredClass(object, superJson);
458
+ var isDeep = (object, superJson) => isPlainObject$1(object) || isArray$1(object) || isMap(object) || isSet(object) || isError(object) || isInstanceOfRegisteredClass(object, superJson);
550
459
  function addIdentity(object, path, identities) {
551
- const existingSet = identities.get(object);
552
- if (existingSet) {
553
- existingSet.push(path);
554
- } else {
555
- identities.set(object, [path]);
556
- }
460
+ const existingSet = identities.get(object);
461
+ if (existingSet) existingSet.push(path);
462
+ else identities.set(object, [path]);
557
463
  }
558
464
  function generateReferentialEqualityAnnotations(identitites, dedupe) {
559
- const result = {};
560
- let rootEqualityPaths = void 0;
561
- identitites.forEach((paths) => {
562
- if (paths.length <= 1) {
563
- return;
564
- }
565
- if (!dedupe) {
566
- paths = paths.map((path) => path.map(String)).sort((a, b) => a.length - b.length);
567
- }
568
- const [representativePath, ...identicalPaths] = paths;
569
- if (representativePath.length === 0) {
570
- rootEqualityPaths = identicalPaths.map(stringifyPath);
571
- } else {
572
- result[stringifyPath(representativePath)] = identicalPaths.map(stringifyPath);
573
- }
574
- });
575
- if (rootEqualityPaths) {
576
- if (isEmptyObject(result)) {
577
- return [rootEqualityPaths];
578
- } else {
579
- return [rootEqualityPaths, result];
580
- }
581
- } else {
582
- return isEmptyObject(result) ? void 0 : result;
583
- }
465
+ const result = {};
466
+ let rootEqualityPaths = void 0;
467
+ identitites.forEach((paths) => {
468
+ if (paths.length <= 1) return;
469
+ if (!dedupe) paths = paths.map((path) => path.map(String)).sort((a, b) => a.length - b.length);
470
+ const [representativePath, ...identicalPaths] = paths;
471
+ if (representativePath.length === 0) rootEqualityPaths = identicalPaths.map(stringifyPath);
472
+ else result[stringifyPath(representativePath)] = identicalPaths.map(stringifyPath);
473
+ });
474
+ if (rootEqualityPaths) if (isEmptyObject(result)) return [rootEqualityPaths];
475
+ else return [rootEqualityPaths, result];
476
+ else return isEmptyObject(result) ? void 0 : result;
584
477
  }
585
- const walker = (object, identities, superJson, dedupe, path = [], objectsInThisPath = [], seenObjects = /* @__PURE__ */ new Map()) => {
586
- const primitive = isPrimitive(object);
587
- if (!primitive) {
588
- addIdentity(object, path, identities);
589
- const seen = seenObjects.get(object);
590
- if (seen) {
591
- return dedupe ? {
592
- transformedValue: null
593
- } : seen;
594
- }
595
- }
596
- if (!isDeep(object, superJson)) {
597
- const transformed2 = transformValue(object, superJson);
598
- const result2 = transformed2 ? {
599
- transformedValue: transformed2.value,
600
- annotations: [transformed2.type]
601
- } : {
602
- transformedValue: object
603
- };
604
- if (!primitive) {
605
- seenObjects.set(object, result2);
606
- }
607
- return result2;
608
- }
609
- if (includes(objectsInThisPath, object)) {
610
- return {
611
- transformedValue: null
612
- };
613
- }
614
- const transformationResult = transformValue(object, superJson);
615
- const transformed = transformationResult?.value ?? object;
616
- const transformedValue = isArray$1(transformed) ? [] : {};
617
- const innerAnnotations = {};
618
- forEach(transformed, (value, index) => {
619
- if (index === "__proto__" || index === "constructor" || index === "prototype") {
620
- throw new Error(`Detected property ${index}. This is a prototype pollution risk, please remove it from your object.`);
621
- }
622
- const recursiveResult = walker(value, identities, superJson, dedupe, [...path, index], [...objectsInThisPath, object], seenObjects);
623
- transformedValue[index] = recursiveResult.transformedValue;
624
- if (isArray$1(recursiveResult.annotations)) {
625
- innerAnnotations[escapeKey(index)] = recursiveResult.annotations;
626
- } else if (isPlainObject$1(recursiveResult.annotations)) {
627
- forEach(recursiveResult.annotations, (tree, key) => {
628
- innerAnnotations[escapeKey(index) + "." + key] = tree;
629
- });
630
- }
631
- });
632
- const result = isEmptyObject(innerAnnotations) ? {
633
- transformedValue,
634
- annotations: !!transformationResult ? [transformationResult.type] : void 0
635
- } : {
636
- transformedValue,
637
- annotations: !!transformationResult ? [transformationResult.type, innerAnnotations] : innerAnnotations
638
- };
639
- if (!primitive) {
640
- seenObjects.set(object, result);
641
- }
642
- return result;
478
+ var walker = (object, identities, superJson, dedupe, path = [], objectsInThisPath = [], seenObjects = /* @__PURE__ */ new Map()) => {
479
+ const primitive = isPrimitive(object);
480
+ if (!primitive) {
481
+ addIdentity(object, path, identities);
482
+ const seen = seenObjects.get(object);
483
+ if (seen) return dedupe ? { transformedValue: null } : seen;
484
+ }
485
+ if (!isDeep(object, superJson)) {
486
+ const transformed = transformValue(object, superJson);
487
+ const result = transformed ? {
488
+ transformedValue: transformed.value,
489
+ annotations: [transformed.type]
490
+ } : { transformedValue: object };
491
+ if (!primitive) seenObjects.set(object, result);
492
+ return result;
493
+ }
494
+ if (includes(objectsInThisPath, object)) return { transformedValue: null };
495
+ const transformationResult = transformValue(object, superJson);
496
+ const transformed = transformationResult?.value ?? object;
497
+ const transformedValue = isArray$1(transformed) ? [] : {};
498
+ const innerAnnotations = {};
499
+ forEach(transformed, (value, index) => {
500
+ if (index === "__proto__" || index === "constructor" || index === "prototype") throw new Error(`Detected property ${index}. This is a prototype pollution risk, please remove it from your object.`);
501
+ const recursiveResult = walker(value, identities, superJson, dedupe, [...path, index], [...objectsInThisPath, object], seenObjects);
502
+ transformedValue[index] = recursiveResult.transformedValue;
503
+ if (isArray$1(recursiveResult.annotations)) innerAnnotations[escapeKey(index)] = recursiveResult.annotations;
504
+ else if (isPlainObject$1(recursiveResult.annotations)) forEach(recursiveResult.annotations, (tree, key) => {
505
+ innerAnnotations[escapeKey(index) + "." + key] = tree;
506
+ });
507
+ });
508
+ const result = isEmptyObject(innerAnnotations) ? {
509
+ transformedValue,
510
+ annotations: !!transformationResult ? [transformationResult.type] : void 0
511
+ } : {
512
+ transformedValue,
513
+ annotations: !!transformationResult ? [transformationResult.type, innerAnnotations] : innerAnnotations
514
+ };
515
+ if (!primitive) seenObjects.set(object, result);
516
+ return result;
643
517
  };
518
+ //#endregion
519
+ //#region ../../node_modules/is-what/dist/getType.js
520
+ /** Returns the object type of the given payload */
644
521
  function getType(payload) {
645
- return Object.prototype.toString.call(payload).slice(8, -1);
522
+ return Object.prototype.toString.call(payload).slice(8, -1);
646
523
  }
524
+ //#endregion
525
+ //#region ../../node_modules/is-what/dist/isArray.js
526
+ /** Returns whether the payload is an array */
647
527
  function isArray(payload) {
648
- return getType(payload) === "Array";
528
+ return getType(payload) === "Array";
649
529
  }
530
+ //#endregion
531
+ //#region ../../node_modules/is-what/dist/isPlainObject.js
532
+ /**
533
+ * Returns whether the payload is a plain JavaScript object (excluding special classes or objects
534
+ * with other prototypes)
535
+ */
650
536
  function isPlainObject(payload) {
651
- if (getType(payload) !== "Object")
652
- return false;
653
- const prototype = Object.getPrototypeOf(payload);
654
- return !!prototype && prototype.constructor === Object && prototype === Object.prototype;
537
+ if (getType(payload) !== "Object") return false;
538
+ const prototype = Object.getPrototypeOf(payload);
539
+ return !!prototype && prototype.constructor === Object && prototype === Object.prototype;
655
540
  }
541
+ //#endregion
542
+ //#region ../../node_modules/copy-anything/dist/index.js
656
543
  function assignProp(carry, key, newVal, originalObject, includeNonenumerable) {
657
- const propType = {}.propertyIsEnumerable.call(originalObject, key) ? "enumerable" : "nonenumerable";
658
- if (propType === "enumerable")
659
- carry[key] = newVal;
660
- if (includeNonenumerable && propType === "nonenumerable") {
661
- Object.defineProperty(carry, key, {
662
- value: newVal,
663
- enumerable: false,
664
- writable: true,
665
- configurable: true
666
- });
667
- }
544
+ const propType = {}.propertyIsEnumerable.call(originalObject, key) ? "enumerable" : "nonenumerable";
545
+ if (propType === "enumerable") carry[key] = newVal;
546
+ if (includeNonenumerable && propType === "nonenumerable") Object.defineProperty(carry, key, {
547
+ value: newVal,
548
+ enumerable: false,
549
+ writable: true,
550
+ configurable: true
551
+ });
668
552
  }
553
+ /**
554
+ * Copy (clone) an object and all its props recursively to get rid of any prop referenced of the
555
+ * original object. Arrays are also cloned, however objects inside arrays are still linked.
556
+ *
557
+ * @param target Target can be anything
558
+ * @param [options={}] See type {@link Options} for more details.
559
+ *
560
+ * - `{ props: ['key1'] }` will only copy the `key1` property. When using this you will need to cast
561
+ * the return type manually (in order to keep the TS implementation in here simple I didn't
562
+ * built a complex auto resolved type for those few cases people want to use this option)
563
+ * - `{ nonenumerable: true }` will copy all non-enumerable properties. Default is `{}`
564
+ *
565
+ * @returns The target with replaced values
566
+ */
669
567
  function copy(target, options = {}) {
670
- if (isArray(target)) {
671
- return target.map((item) => copy(item, options));
672
- }
673
- if (!isPlainObject(target)) {
674
- return target;
675
- }
676
- const props = Object.getOwnPropertyNames(target);
677
- const symbols = Object.getOwnPropertySymbols(target);
678
- return [...props, ...symbols].reduce((carry, key) => {
679
- if (key === "__proto__")
680
- return carry;
681
- if (isArray(options.props) && !options.props.includes(key)) {
682
- return carry;
683
- }
684
- const val = target[key];
685
- const newVal = copy(val, options);
686
- assignProp(carry, key, newVal, target, options.nonenumerable);
687
- return carry;
688
- }, {});
689
- }
690
- class SuperJSON {
691
- /**
692
- * @param dedupeReferentialEqualities If true, SuperJSON will make sure only one instance of referentially equal objects are serialized and the rest are replaced with `null`.
693
- */
694
- constructor({ dedupe = false } = {}) {
695
- this.classRegistry = new ClassRegistry();
696
- this.symbolRegistry = new Registry((s) => s.description ?? "");
697
- this.customTransformerRegistry = new CustomTransformerRegistry();
698
- this.allowedErrorProps = [];
699
- this.dedupe = dedupe;
700
- }
701
- serialize(object) {
702
- const identities = /* @__PURE__ */ new Map();
703
- const output = walker(object, identities, this, this.dedupe);
704
- const res = {
705
- json: output.transformedValue
706
- };
707
- if (output.annotations) {
708
- res.meta = {
709
- ...res.meta,
710
- values: output.annotations
711
- };
712
- }
713
- const equalityAnnotations = generateReferentialEqualityAnnotations(identities, this.dedupe);
714
- if (equalityAnnotations) {
715
- res.meta = {
716
- ...res.meta,
717
- referentialEqualities: equalityAnnotations
718
- };
719
- }
720
- if (res.meta)
721
- res.meta.v = 1;
722
- return res;
723
- }
724
- deserialize(payload, options) {
725
- const { json, meta } = payload;
726
- let result = options?.inPlace ? json : copy(json);
727
- if (meta?.values) {
728
- result = applyValueAnnotations(result, meta.values, meta.v ?? 0, this);
729
- }
730
- if (meta?.referentialEqualities) {
731
- result = applyReferentialEqualityAnnotations(result, meta.referentialEqualities, meta.v ?? 0);
732
- }
733
- return result;
734
- }
735
- stringify(object) {
736
- return JSON.stringify(this.serialize(object));
737
- }
738
- parse(string) {
739
- return this.deserialize(JSON.parse(string), { inPlace: true });
740
- }
741
- registerClass(v, options) {
742
- this.classRegistry.register(v, options);
743
- }
744
- registerSymbol(v, identifier) {
745
- this.symbolRegistry.register(v, identifier);
746
- }
747
- registerCustom(transformer, name) {
748
- this.customTransformerRegistry.register({
749
- name,
750
- ...transformer
751
- });
752
- }
753
- allowErrorProps(...props) {
754
- this.allowedErrorProps.push(...props);
755
- }
568
+ if (isArray(target)) return target.map((item) => copy(item, options));
569
+ if (!isPlainObject(target)) return target;
570
+ const props = Object.getOwnPropertyNames(target);
571
+ const symbols = Object.getOwnPropertySymbols(target);
572
+ return [...props, ...symbols].reduce((carry, key) => {
573
+ if (key === "__proto__") return carry;
574
+ if (isArray(options.props) && !options.props.includes(key)) return carry;
575
+ const val = target[key];
576
+ assignProp(carry, key, copy(val, options), target, options.nonenumerable);
577
+ return carry;
578
+ }, {});
756
579
  }
580
+ //#endregion
581
+ //#region ../../node_modules/superjson/dist/index.js
582
+ var SuperJSON = class {
583
+ /**
584
+ * @param dedupeReferentialEqualities If true, SuperJSON will make sure only one instance of referentially equal objects are serialized and the rest are replaced with `null`.
585
+ */
586
+ constructor({ dedupe = false } = {}) {
587
+ this.classRegistry = new ClassRegistry();
588
+ this.symbolRegistry = new Registry((s) => s.description ?? "");
589
+ this.customTransformerRegistry = new CustomTransformerRegistry();
590
+ this.allowedErrorProps = [];
591
+ this.dedupe = dedupe;
592
+ }
593
+ serialize(object) {
594
+ const identities = /* @__PURE__ */ new Map();
595
+ const output = walker(object, identities, this, this.dedupe);
596
+ const res = { json: output.transformedValue };
597
+ if (output.annotations) res.meta = {
598
+ ...res.meta,
599
+ values: output.annotations
600
+ };
601
+ const equalityAnnotations = generateReferentialEqualityAnnotations(identities, this.dedupe);
602
+ if (equalityAnnotations) res.meta = {
603
+ ...res.meta,
604
+ referentialEqualities: equalityAnnotations
605
+ };
606
+ if (res.meta) res.meta.v = 1;
607
+ return res;
608
+ }
609
+ deserialize(payload, options) {
610
+ const { json, meta } = payload;
611
+ let result = options?.inPlace ? json : copy(json);
612
+ if (meta?.values) result = applyValueAnnotations(result, meta.values, meta.v ?? 0, this);
613
+ if (meta?.referentialEqualities) result = applyReferentialEqualityAnnotations(result, meta.referentialEqualities, meta.v ?? 0);
614
+ return result;
615
+ }
616
+ stringify(object) {
617
+ return JSON.stringify(this.serialize(object));
618
+ }
619
+ parse(string) {
620
+ return this.deserialize(JSON.parse(string), { inPlace: true });
621
+ }
622
+ registerClass(v, options) {
623
+ this.classRegistry.register(v, options);
624
+ }
625
+ registerSymbol(v, identifier) {
626
+ this.symbolRegistry.register(v, identifier);
627
+ }
628
+ registerCustom(transformer, name) {
629
+ this.customTransformerRegistry.register({
630
+ name,
631
+ ...transformer
632
+ });
633
+ }
634
+ allowErrorProps(...props) {
635
+ this.allowedErrorProps.push(...props);
636
+ }
637
+ };
757
638
  SuperJSON.defaultInstance = new SuperJSON();
758
639
  SuperJSON.serialize = SuperJSON.defaultInstance.serialize.bind(SuperJSON.defaultInstance);
759
640
  SuperJSON.deserialize = SuperJSON.defaultInstance.deserialize.bind(SuperJSON.defaultInstance);
@@ -771,1245 +652,1248 @@ SuperJSON.registerClass;
771
652
  SuperJSON.registerCustom;
772
653
  SuperJSON.registerSymbol;
773
654
  SuperJSON.allowErrorProps;
774
- const WS_KEEP_ALIVE_INTERVAL_MS = 1e4;
775
- const WS_KEEP_ALIVE_PONG_TIMEOUT_MS = 6e3;
776
- const WS_RECONNECT_RETRY_DELAY_MS = 2e3;
777
- const WS_RECONNECT_MAX_RETRY_DELAY_MS = 3e4;
778
- class System {
779
- /** Active server base URL. Mutable via `switchServerUrl()` so the
780
- * endpoint-race helper can pivot the transport onto a LAN candidate
781
- * after the initial public-URL bootstrap. */
782
- _serverUrl;
783
- useWs;
784
- baseRetryMs;
785
- maxRetryMs;
786
- onConnectionChange;
787
- token;
788
- _trpcClient;
789
- _wsClient = null;
790
- // Mirror owns binding cache + state mirror + lifecycle listeners.
791
- // Lazily initialised on `init()`. Subsequent `reconnect()` disposes
792
- // the old mirror and rebuilds against the new tRPC client.
793
- mirror = null;
794
- mirrorInit = null;
795
- // Transport-readiness probe state. `connected` flips true the first
796
- // time `auth.me` succeeds against the current tRPC client; resets
797
- // on every `reconnect()` / `close()` so the next consumer waits for
798
- // a fresh handshake before issuing queries.
799
- connected = false;
800
- connectedPromise = null;
801
- // System-cap namespaces — populated in the constructor from
802
- // `createSystemProxy(api)`. Each namespace is a `Pick<InferProvider<typeof cap>, …>`
803
- // shape generated by `scripts/generate-system-proxy.ts`.
804
- _systemProxy;
805
- // Connection-version + listener bookkeeping for the admin UI's
806
- // reconnect watchdog.
807
- _connectionVersion = 0;
808
- connectionListeners = /* @__PURE__ */ new Set();
809
- constructor(config) {
810
- this._serverUrl = config.serverUrl.replace(/\/+$/, "");
811
- this.token = config.token;
812
- const isBrowser = typeof window !== "undefined";
813
- this.useWs = config.useWebSocket ?? isBrowser;
814
- this.onConnectionChange = config.onConnectionChange;
815
- this.baseRetryMs = config.retryDelayMs ?? WS_RECONNECT_RETRY_DELAY_MS;
816
- this.maxRetryMs = config.maxRetryDelayMs ?? WS_RECONNECT_MAX_RETRY_DELAY_MS;
817
- this._trpcClient = this.buildTrpcClient();
818
- this._systemProxy = createSystemProxy(this._trpcClient);
819
- }
820
- // ── Connection state ─────────────────────────────────────────────
821
- /** Active server base URL (no trailing slash). */
822
- get serverUrl() {
823
- return this._serverUrl;
824
- }
825
- get connectionVersion() {
826
- return this._connectionVersion;
827
- }
828
- /**
829
- * Subscribe to connection-state transitions. Called with `('connected'
830
- * | 'disconnected' | 'connecting', version)` whenever the SDK opens,
831
- * closes, or starts a manual reconnect. Listener errors are swallowed
832
- * so a misbehaving consumer cannot break the SDK's event loop.
833
- */
834
- subscribeConnectionEvents(cb) {
835
- this.connectionListeners.add(cb);
836
- return () => {
837
- this.connectionListeners.delete(cb);
838
- };
839
- }
840
- emitConnectionEvent(state) {
841
- try {
842
- this.onConnectionChange?.(state);
843
- } catch {
844
- }
845
- for (const l of this.connectionListeners) {
846
- try {
847
- l(state, this._connectionVersion);
848
- } catch {
849
- }
850
- }
851
- }
852
- // ── Lifecycle ────────────────────────────────────────────────────
853
- /**
854
- * Wait until the underlying tRPC transport is connected AND the
855
- * server has responded to a cheap auth round-trip (`auth.me`). This
856
- * is the canonical "ready to issue queries" gate.
857
- *
858
- * Why a probe, not just `ws.readyState === OPEN`?
859
- * The WS handshake completes asynchronously: tRPC's `wsLink`
860
- * queues outgoing messages and only flushes them after `open()`
861
- * resolves (post `connectionParams` send). On the server, the
862
- * tRPC context is created lazily once the connectionParams
863
- * message is received. A query fired between WS-open and
864
- * connection-params-processed is technically queued by tRPC, but
865
- * the auth context for that query is only resolved once the
866
- * handshake message is decoded server-side. A probe round-trip is
867
- * the safest way to confirm both sides have agreed on the auth
868
- * identity before the React tree starts firing parallel queries
869
- * (which can otherwise land before any addon-side service
870
- * discovery has settled, returning empty results that get cached).
871
- *
872
- * Idempotent concurrent callers await the same in-flight Promise.
873
- * Bounded by `timeoutMs` (default 15s) — beyond which a
874
- * `Error('System.awaitConnected: probe timed out after Xms')` is
875
- * thrown so the host can render a clear error state instead of
876
- * hanging on a bricked socket.
877
- */
878
- async awaitConnected(timeoutMs) {
879
- if (this.connected) return;
880
- if (this.connectedPromise) return this.connectedPromise;
881
- const effectiveTimeoutMs = timeoutMs ?? 15e3;
882
- this.connectedPromise = (async () => {
883
- const probe = this._trpcClient.auth.me.query();
884
- if (!Number.isFinite(effectiveTimeoutMs)) {
885
- await probe;
886
- } else {
887
- await new Promise((resolve, reject) => {
888
- const timer = setTimeout(
889
- () => reject(new Error(`System.awaitConnected: probe timed out after ${effectiveTimeoutMs}ms`)),
890
- effectiveTimeoutMs
891
- );
892
- probe.then(
893
- () => {
894
- clearTimeout(timer);
895
- resolve();
896
- },
897
- (err) => {
898
- clearTimeout(timer);
899
- reject(err instanceof Error ? err : new Error(String(err)));
900
- }
901
- );
902
- });
903
- }
904
- this.connected = true;
905
- })();
906
- try {
907
- await this.connectedPromise;
908
- } finally {
909
- this.connectedPromise = null;
910
- }
911
- }
912
- /**
913
- * Warm-boot the device mirror. Awaits the transport probe first
914
- * (`awaitConnected`) so the three mirror round-trips
915
- * (`getAllBindings` + `getAllSnapshots` + `listAll`) cannot race
916
- * against the WS auth handshake. Subsequent `getDevice(id)` calls
917
- * are sync; live `device.*` event subscriptions keep the caches
918
- * fresh.
919
- *
920
- * Idempotent — concurrent callers await the same in-flight Promise.
921
- */
922
- async init(timeoutMs) {
923
- if (this.mirror?.isReady()) return;
924
- if (this.mirrorInit) return this.mirrorInit;
925
- const m = new SystemMirror(this._trpcClient);
926
- this.mirror = m;
927
- this.mirrorInit = (async () => {
928
- await this.awaitConnected(timeoutMs);
929
- await m.init(timeoutMs);
930
- })().finally(() => {
931
- this.mirrorInit = null;
932
- });
933
- return this.mirrorInit;
934
- }
935
- /** Promise that resolves once `init()` has completed. */
936
- async awaitReady() {
937
- if (this.mirror?.isReady()) return;
938
- if (this.mirrorInit) return this.mirrorInit;
939
- return this.init();
940
- }
941
- /** True after `init()` resolves. */
942
- isReady() {
943
- return this.mirror?.isReady() ?? false;
944
- }
945
- /** True after the transport probe has succeeded at least once. */
946
- isConnected() {
947
- return this.connected;
948
- }
949
- /**
950
- * Force a fresh WebSocket handshake. Tears down the wsClient + tRPC
951
- * client + mirror (the mirror captures the tRPC reference at
952
- * construction time and would otherwise dispatch through a closed
953
- * client) and rebuilds them. No-op for HTTP transport.
954
- */
955
- reconnect() {
956
- if (!this.useWs) return;
957
- this.emitConnectionEvent("connecting");
958
- this._wsClient?.close();
959
- this.disposeMirror();
960
- this.connected = false;
961
- this.connectedPromise = null;
962
- this._trpcClient = this.buildTrpcClient();
963
- this._systemProxy = createSystemProxy(this._trpcClient);
964
- }
965
- /**
966
- * Pivot the underlying tRPC transport onto a different base URL.
967
- * Used by the endpoint-race flow: the SDK opens against the public
968
- * URL the operator provided, calls `localNetwork.getConnectionEndpoints`
969
- * to discover LAN candidates, races them, and (when a faster one wins)
970
- * calls `switchServerUrl(winner)` to migrate every subsequent query
971
- * onto the LAN path without losing auth state.
972
- *
973
- * Keeps the auth token. Tears down the WS + mirror and rebuilds them
974
- * against the new URL — same machinery as `reconnect()` but with a
975
- * different target.
976
- */
977
- switchServerUrl(nextUrl) {
978
- const normalized = nextUrl.replace(/\/+$/, "");
979
- if (normalized === this._serverUrl) return;
980
- this._serverUrl = normalized;
981
- this.reconnect();
982
- }
983
- /**
984
- * Race the candidate base URLs reported by the hub's `local-network`
985
- * cap, pick the fastest one that responds, and (if it's different
986
- * from the current URL) pivot the transport onto it.
987
- *
988
- * Flow:
989
- * 1. Query `localNetwork.getConnectionEndpoints({ port })` over the
990
- * already-authenticated tRPC channel the cap is auth-gated,
991
- * so the LAN IPs never leak to anonymous callers.
992
- * 2. For each candidate, fire a HEAD on `{baseUrl}/trpc/health`
993
- * with a short timeout (default 1500ms). The first 2xx wins.
994
- * 3. If the winner differs from `this.serverUrl`, call
995
- * `switchServerUrl(winner)` and return it. Otherwise return
996
- * the current URL unchanged.
997
- *
998
- * Bounded if every candidate times out we keep the current URL.
999
- * Idempotent safe to call on every connect / reconnect / network
1000
- * change event.
1001
- */
1002
- async raceConnectionEndpoints(options) {
1003
- const timeoutMs = options?.perCandidateTimeoutMs ?? 1500;
1004
- const url = (() => {
1005
- try {
1006
- return new URL(this._serverUrl);
1007
- } catch {
1008
- return null;
1009
- }
1010
- })();
1011
- const port = url?.port ? Number(url.port) : url?.protocol === "https:" ? 443 : 80;
1012
- const scheme = url?.protocol === "https:" ? "https" : "http";
1013
- const proxy = this._trpcClient;
1014
- const cap = proxy.localNetwork?.getConnectionEndpoints;
1015
- if (!cap) {
1016
- return { winner: this._serverUrl, switched: false };
1017
- }
1018
- let endpoints;
1019
- try {
1020
- const res = await cap.query({ port, ipv4Only: options?.ipv4Only, scheme });
1021
- endpoints = res.endpoints;
1022
- } catch {
1023
- return { winner: this._serverUrl, switched: false };
1024
- }
1025
- if (endpoints.length === 0) {
1026
- return { winner: this._serverUrl, switched: false };
1027
- }
1028
- const winner = await raceFastestEndpoint(endpoints.map((e) => e.baseUrl), timeoutMs);
1029
- if (!winner || winner === this._serverUrl) {
1030
- return { winner: winner ?? this._serverUrl, switched: false };
1031
- }
1032
- this.switchServerUrl(winner);
1033
- return { winner, switched: true };
1034
- }
1035
- /** Tear down WS connection + mirror. The instance is unusable afterwards. */
1036
- close() {
1037
- this.disposeMirror();
1038
- this.connected = false;
1039
- this.connectedPromise = null;
1040
- this._wsClient?.close();
1041
- }
1042
- disposeMirror() {
1043
- this.mirror?.dispose();
1044
- this.mirror = null;
1045
- this.mirrorInit = null;
1046
- }
1047
- // ── Auth ──────────────────────────────────────────────────────────
1048
- async login(username, password) {
1049
- const result = await this._trpcClient.auth.login.mutate({ username, password });
1050
- if (typeof result === "object" && result !== null && "token" in result) {
1051
- const r = result;
1052
- const token = r.token;
1053
- if (typeof token === "string" && r.requiresTotp !== true) this.setToken(token);
1054
- }
1055
- return result;
1056
- }
1057
- /**
1058
- * Second leg of the 2FA login. The caller passes the challenge
1059
- * token returned by `login` (when `requiresTotp: true`) plus the
1060
- * 6-digit code from the user's authenticator app. On success the
1061
- * server mints the real session JWT, which we set as the active
1062
- * token on this client.
1063
- */
1064
- async loginVerifyTotp(challengeToken, code) {
1065
- const result = await this._trpcClient.auth.loginVerifyTotp.mutate({ challengeToken, code });
1066
- if (typeof result === "object" && result !== null && "token" in result) {
1067
- const token = result.token;
1068
- if (typeof token === "string") this.setToken(token);
1069
- }
1070
- return result;
1071
- }
1072
- async logout() {
1073
- await this._trpcClient.auth.logout.mutate();
1074
- }
1075
- async getMe() {
1076
- const me = await this._trpcClient.auth.me.query();
1077
- return me;
1078
- }
1079
- // ── Self-service auth (the signed-in user acting on themselves) ─────
1080
- //
1081
- // The hand-written `auth.*` router exposes 5 procedures that operate
1082
- // on `ctx.user` (the JWT-bound identity), distinct from the admin
1083
- // `user-management` cap that operates on arbitrary userIds. They live
1084
- // on `System` rather than as codegen'd hooks because `auth.*` is a
1085
- // legacy core router, not a `.cap.ts` — see the no-restricted-imports
1086
- // rule in `eslint.config.mjs` for the canonical entry-point design.
1087
- async changeOwnPassword(input) {
1088
- const result = await this._trpcClient.auth.changeOwnPassword.mutate(input);
1089
- return result;
1090
- }
1091
- async setupOwnTotp() {
1092
- const result = await this._trpcClient.auth.setupOwnTotp.mutate();
1093
- return result;
1094
- }
1095
- async confirmOwnTotp(input) {
1096
- const result = await this._trpcClient.auth.confirmOwnTotp.mutate(input);
1097
- return result;
1098
- }
1099
- async disableOwnTotp() {
1100
- const result = await this._trpcClient.auth.disableOwnTotp.mutate();
1101
- return result;
1102
- }
1103
- async getOwnTotpStatus() {
1104
- const result = await this._trpcClient.auth.getOwnTotpStatus.query();
1105
- return result;
1106
- }
1107
- /** Update the auth token (e.g. after login or token refresh). */
1108
- setToken(token) {
1109
- this.token = token;
1110
- }
1111
- // ── Devices ──────────────────────────────────────────────────────
1112
- /**
1113
- * Synchronous snapshot of every device matching the optional filters.
1114
- * Backed by the `SystemMirror` warm-boot cache — call `init()` first
1115
- * (or `awaitReady()`) before invoking. Returns an empty array if the
1116
- * mirror has not yet been booted.
1117
- *
1118
- * Each returned proxy has `binding` populated from the mirror's
1119
- * binding cache (Phase 5 dedup), so consumers no longer need to
1120
- * make a separate `deviceManager.getBindings` round-trip.
1121
- */
1122
- listDevices(filters) {
1123
- if (!this.mirror) return [];
1124
- const proxies = filters ? this.mirror.query(filters) : this.mirror.getAllDevices();
1125
- return proxies.map((p) => this.attachBinding(p));
1126
- }
1127
- /**
1128
- * Sync `DeviceInfo` snapshot (name, canonical type, online, …) for one device
1129
- * from the warm-boot mirror `null` if the mirror isn't booted or the device
1130
- * is unknown. Unlike a `DeviceProxy` (cap accessors only), this carries the
1131
- * display identity the UI needs to render a device on first paint.
1132
- */
1133
- getDeviceInfo(deviceId) {
1134
- return this.mirror?.getDeviceInfo(deviceId) ?? null;
1135
- }
1136
- /**
1137
- * The `DeviceInfo` snapshots for the current device set (the warm-boot cache),
1138
- * honouring the same `filters` as {@link listDevices}. Lets a UI render a named
1139
- * device list immediately without waiting for per-device lifecycle events.
1140
- */
1141
- listDeviceInfos(filters) {
1142
- const mirror = this.mirror;
1143
- if (!mirror) return [];
1144
- const proxies = filters ? mirror.query(filters) : mirror.getAllDevices();
1145
- const out = [];
1146
- for (const p of proxies) {
1147
- const info = mirror.getDeviceInfo(p.deviceId);
1148
- if (info) out.push(info);
1149
- }
1150
- return out;
1151
- }
1152
- /**
1153
- * Sync lookup by numeric id. `null` if the mirror has not been booted
1154
- * or the device is unknown.
1155
- */
1156
- getDevice(deviceId) {
1157
- const proxy = this.mirror?.getDeviceById(deviceId) ?? null;
1158
- return proxy ? this.attachBinding(proxy) : null;
1159
- }
1160
- /** Sync lookup by display name (exact match). */
1161
- getDeviceByName(name) {
1162
- const proxy = this.mirror?.getDeviceByName(name) ?? null;
1163
- return proxy ? this.attachBinding(proxy) : null;
1164
- }
1165
- /** Sync lookup by stableId. */
1166
- getDeviceByStableId(stableId) {
1167
- const proxy = this.mirror?.getDeviceByStableId(stableId) ?? null;
1168
- return proxy ? this.attachBinding(proxy) : null;
1169
- }
1170
- /**
1171
- * Resolve when a device with `deviceId` becomes available. Resolves
1172
- * immediately if already known; rejects with a timeout error
1173
- * otherwise (default 30s).
1174
- */
1175
- async waitForDevice(deviceId, timeoutMs) {
1176
- if (!this.mirror) await this.init();
1177
- const proxy = await this.mirror.waitForDevice(deviceId, timeoutMs ?? 3e4);
1178
- return this.attachBinding(proxy);
1179
- }
1180
- /** Subscribe to `device.registered` events. */
1181
- onDeviceAdded(cb) {
1182
- if (!this.mirror) {
1183
- throw new Error("System.onDeviceAdded: call init() before subscribing");
1184
- }
1185
- return this.mirror.onDeviceAdded(cb);
1186
- }
1187
- /** Subscribe to `device.unregistered` events. */
1188
- onDeviceRemoved(cb) {
1189
- if (!this.mirror) {
1190
- throw new Error("System.onDeviceRemoved: call init() before subscribing");
1191
- }
1192
- return this.mirror.onDeviceRemoved(cb);
1193
- }
1194
- /**
1195
- * Patch the proxy's `binding` field from the mirror's cache. The
1196
- * generated `createDeviceProxy()` already sets `binding` to the
1197
- * binding it was constructed with — this is a defensive overwrite
1198
- * that uses the latest cached entry in case a `binding-changed`
1199
- * event landed between proxy creation and access.
1200
- */
1201
- attachBinding(proxy) {
1202
- const binding = this.lookupBinding(proxy.deviceId);
1203
- if (binding === proxy.binding) return proxy;
1204
- const writable = proxy;
1205
- writable.binding = binding;
1206
- return proxy;
1207
- }
1208
- /** Fetch the latest cached binding from the mirror, or `null`. */
1209
- lookupBinding(deviceId) {
1210
- if (!this.mirror) return null;
1211
- const proxy = this.mirror.getDeviceById(deviceId);
1212
- return proxy?.binding ?? null;
1213
- }
1214
- // ── System caps ──────────────────────────────────────────────────
1215
- //
1216
- // One getter per `scope: 'system'` capability. The actual surface
1217
- // comes from `createSystemProxy(api)` (codegen). Adding a new
1218
- // system cap regenerates the proxy and the new namespace surfaces
1219
- // automatically via `system.<newCap>.<method>(input)`.
1220
- get addonPages() {
1221
- return this._systemProxy.addonPages;
1222
- }
1223
- get addonSettings() {
1224
- return this._systemProxy.addonSettings;
1225
- }
1226
- get alerts() {
1227
- return this._systemProxy.alerts;
1228
- }
1229
- get audioAnalyzer() {
1230
- return this._systemProxy.audioAnalyzer;
1231
- }
1232
- get audioCodec() {
1233
- return this._systemProxy.audioCodec;
1234
- }
1235
- get backup() {
1236
- return this._systemProxy.backup;
1237
- }
1238
- get decoder() {
1239
- return this._systemProxy.decoder;
1240
- }
1241
- get deviceManager() {
1242
- return this._systemProxy.deviceManager;
1243
- }
1244
- get deviceProvider() {
1245
- return this._systemProxy.deviceProvider;
1246
- }
1247
- get deviceState() {
1248
- return this._systemProxy.deviceState;
1249
- }
1250
- get metricsProvider() {
1251
- return this._systemProxy.metricsProvider;
1252
- }
1253
- get notificationOutput() {
1254
- return this._systemProxy.notificationOutput;
1255
- }
1256
- get pipelineExecutor() {
1257
- return this._systemProxy.pipelineExecutor;
1258
- }
1259
- get pipelineOrchestrator() {
1260
- return this._systemProxy.pipelineOrchestrator;
1261
- }
1262
- get pipelineRunner() {
1263
- return this._systemProxy.pipelineRunner;
1264
- }
1265
- // `platform-probe` is infra (addon-to-addon via `api.platformProbe`), not a
1266
- // client-facing system cap — it isn't on SystemProxy, so no getter here.
1267
- get settingsStore() {
1268
- return this._systemProxy.settingsStore;
1269
- }
1270
- get storage() {
1271
- return this._systemProxy.storage;
1272
- }
1273
- get streamBroker() {
1274
- return this._systemProxy.streamBroker;
1275
- }
1276
- get turnProvider() {
1277
- return this._systemProxy.turnProvider;
1278
- }
1279
- get userManagement() {
1280
- return this._systemProxy.userManagement;
1281
- }
1282
- // ── Live events ──────────────────────────────────────────────────
1283
- /**
1284
- * Subscribe to a single event category. Returns an unsubscribe
1285
- * handle. Errors thrown by the listener are swallowed so a single
1286
- * misbehaving consumer cannot tear down the WS subscription.
1287
- *
1288
- * Categories should be values from the `EventCategory` enum
1289
- * (`@camstack/types`) — passing a raw string works for forward-compat
1290
- * but loses type safety. The SDK forwards the value verbatim to the
1291
- * server's `live.onEvent` subscription.
1292
- */
1293
- subscribeEvent(category, cb) {
1294
- const sub = this._trpcClient.live.onEvent.subscribe(
1295
- { category },
1296
- {
1297
- onData: (event) => {
1298
- try {
1299
- cb(event);
1300
- } catch {
1301
- }
1302
- }
1303
- }
1304
- );
1305
- return () => {
1306
- try {
1307
- sub.unsubscribe();
1308
- } catch {
1309
- }
1310
- };
1311
- }
1312
- // ── Escape hatches ───────────────────────────────────────────────
1313
- //
1314
- // Kept public so the ui-library `<TrpcProvider>` can seed React
1315
- // Query with the same WebSocket connection during Phase 2. Phase 4
1316
- // is expected to revisit whether either field can be hidden.
1317
- /** Direct tRPC client. Read once per call; rebuilt on `reconnect()`. */
1318
- get trpcClient() {
1319
- return this._trpcClient;
1320
- }
1321
- /**
1322
- * Underlying WSClient (or `null` for HTTP transport). Used by
1323
- * advanced consumers that need direct access to the WebSocket
1324
- * (e.g. for keep-alive metrics). Rebuilt on `reconnect()`.
1325
- */
1326
- get wsClient() {
1327
- return this._wsClient;
1328
- }
1329
- // ── Internals ────────────────────────────────────────────────────
1330
- buildTrpcClient() {
1331
- const headers = () => {
1332
- const h = {};
1333
- if (this.token) h["Authorization"] = `Bearer ${this.token}`;
1334
- return h;
1335
- };
1336
- if (this.useWs) {
1337
- const wsUrl = this._serverUrl.replace(/^http/, "ws") + "/trpc";
1338
- const baseRetryMs = this.baseRetryMs;
1339
- const maxRetryMs = this.maxRetryMs;
1340
- this._wsClient = createWSClient({
1341
- url: wsUrl,
1342
- connectionParams: () => ({ token: this.token }),
1343
- retryDelayMs: (attemptIndex) => Math.min(baseRetryMs * Math.pow(2, attemptIndex), maxRetryMs),
1344
- keepAlive: {
1345
- enabled: true,
1346
- intervalMs: WS_KEEP_ALIVE_INTERVAL_MS,
1347
- pongTimeoutMs: WS_KEEP_ALIVE_PONG_TIMEOUT_MS
1348
- },
1349
- onOpen: () => {
1350
- this._connectionVersion += 1;
1351
- this.emitConnectionEvent("connected");
1352
- },
1353
- onClose: () => {
1354
- this.emitConnectionEvent("disconnected");
1355
- }
1356
- });
1357
- return createTRPCClient({
1358
- links: [wsLink({ client: this._wsClient, transformer: SuperJSON })]
1359
- });
1360
- }
1361
- this._wsClient = null;
1362
- return createTRPCClient({
1363
- links: [httpLink({ url: `${this._serverUrl}/trpc`, headers, transformer: SuperJSON })]
1364
- });
1365
- }
1366
- }
655
+ //#endregion
656
+ //#region src/system.ts
657
+ /**
658
+ * System single, unified entry point for the camstack client API.
659
+ *
660
+ * Devices are returned as typed `DeviceProxy` objects (auto-injecting
661
+ * `deviceId` + `nodeId`), system caps are exposed as typed namespaces
662
+ * (`system.userManagement`, `system.storage`, etc.), and live events
663
+ * flow through a single `subscribeEvent` helper.
664
+ *
665
+ * The escape hatches (`trpcClient`, `wsClient`) are still public so the
666
+ * ui-library Provider can seed React Query with the same WS connection
667
+ * — a future phase will narrow the surface further.
668
+ */
669
+ var WS_KEEP_ALIVE_INTERVAL_MS = 1e4;
670
+ var WS_KEEP_ALIVE_PONG_TIMEOUT_MS = 6e3;
671
+ var WS_RECONNECT_RETRY_DELAY_MS = 2e3;
672
+ var WS_RECONNECT_MAX_RETRY_DELAY_MS = 3e4;
673
+ var System = class {
674
+ /** Active server base URL. Mutable via `switchServerUrl()` so the
675
+ * endpoint-race helper can pivot the transport onto a LAN candidate
676
+ * after the initial public-URL bootstrap. */
677
+ _serverUrl;
678
+ useWs;
679
+ baseRetryMs;
680
+ maxRetryMs;
681
+ onConnectionChange;
682
+ token;
683
+ _trpcClient;
684
+ _wsClient = null;
685
+ mirror = null;
686
+ mirrorInit = null;
687
+ connected = false;
688
+ connectedPromise = null;
689
+ _systemProxy;
690
+ _connectionVersion = 0;
691
+ connectionListeners = /* @__PURE__ */ new Set();
692
+ constructor(config) {
693
+ this._serverUrl = config.serverUrl.replace(/\/+$/, "");
694
+ this.token = config.token;
695
+ const isBrowser = typeof window !== "undefined";
696
+ this.useWs = config.useWebSocket ?? isBrowser;
697
+ this.onConnectionChange = config.onConnectionChange;
698
+ this.baseRetryMs = config.retryDelayMs ?? WS_RECONNECT_RETRY_DELAY_MS;
699
+ this.maxRetryMs = config.maxRetryDelayMs ?? WS_RECONNECT_MAX_RETRY_DELAY_MS;
700
+ this._trpcClient = this.buildTrpcClient();
701
+ this._systemProxy = createSystemProxy(this._trpcClient);
702
+ }
703
+ /** Active server base URL (no trailing slash). */
704
+ get serverUrl() {
705
+ return this._serverUrl;
706
+ }
707
+ get connectionVersion() {
708
+ return this._connectionVersion;
709
+ }
710
+ /**
711
+ * Subscribe to connection-state transitions. Called with `('connected'
712
+ * | 'disconnected' | 'connecting', version)` whenever the SDK opens,
713
+ * closes, or starts a manual reconnect. Listener errors are swallowed
714
+ * so a misbehaving consumer cannot break the SDK's event loop.
715
+ */
716
+ subscribeConnectionEvents(cb) {
717
+ this.connectionListeners.add(cb);
718
+ return () => {
719
+ this.connectionListeners.delete(cb);
720
+ };
721
+ }
722
+ emitConnectionEvent(state) {
723
+ try {
724
+ this.onConnectionChange?.(state);
725
+ } catch {}
726
+ for (const l of this.connectionListeners) try {
727
+ l(state, this._connectionVersion);
728
+ } catch {}
729
+ }
730
+ /**
731
+ * Wait until the underlying tRPC transport is connected AND the
732
+ * server has responded to a cheap auth round-trip (`auth.me`). This
733
+ * is the canonical "ready to issue queries" gate.
734
+ *
735
+ * Why a probe, not just `ws.readyState === OPEN`?
736
+ * The WS handshake completes asynchronously: tRPC's `wsLink`
737
+ * queues outgoing messages and only flushes them after `open()`
738
+ * resolves (post `connectionParams` send). On the server, the
739
+ * tRPC context is created lazily once the connectionParams
740
+ * message is received. A query fired between WS-open and
741
+ * connection-params-processed is technically queued by tRPC, but
742
+ * the auth context for that query is only resolved once the
743
+ * handshake message is decoded server-side. A probe round-trip is
744
+ * the safest way to confirm both sides have agreed on the auth
745
+ * identity before the React tree starts firing parallel queries
746
+ * (which can otherwise land before any addon-side service
747
+ * discovery has settled, returning empty results that get cached).
748
+ *
749
+ * Idempotent concurrent callers await the same in-flight Promise.
750
+ * Bounded by `timeoutMs` (default 15s) beyond which a
751
+ * `Error('System.awaitConnected: probe timed out after Xms')` is
752
+ * thrown so the host can render a clear error state instead of
753
+ * hanging on a bricked socket.
754
+ */
755
+ async awaitConnected(timeoutMs) {
756
+ if (this.connected) return;
757
+ if (this.connectedPromise) return this.connectedPromise;
758
+ const effectiveTimeoutMs = timeoutMs ?? 15e3;
759
+ this.connectedPromise = (async () => {
760
+ const probe = this._trpcClient.auth.me.query();
761
+ if (!Number.isFinite(effectiveTimeoutMs)) await probe;
762
+ else await new Promise((resolve, reject) => {
763
+ const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`System.awaitConnected: probe timed out after ${effectiveTimeoutMs}ms`)), effectiveTimeoutMs);
764
+ probe.then(() => {
765
+ clearTimeout(timer);
766
+ resolve();
767
+ }, (err) => {
768
+ clearTimeout(timer);
769
+ reject(err instanceof Error ? err : new Error(String(err)));
770
+ });
771
+ });
772
+ this.connected = true;
773
+ })();
774
+ try {
775
+ await this.connectedPromise;
776
+ } finally {
777
+ this.connectedPromise = null;
778
+ }
779
+ }
780
+ /**
781
+ * Warm-boot the device mirror. Awaits the transport probe first
782
+ * (`awaitConnected`) so the three mirror round-trips
783
+ * (`getAllBindings` + `getAllSnapshots` + `listAll`) cannot race
784
+ * against the WS auth handshake. Subsequent `getDevice(id)` calls
785
+ * are sync; live `device.*` event subscriptions keep the caches
786
+ * fresh.
787
+ *
788
+ * Idempotent — concurrent callers await the same in-flight Promise.
789
+ */
790
+ async init(timeoutMs) {
791
+ if (this.mirror?.isReady()) return;
792
+ if (this.mirrorInit) return this.mirrorInit;
793
+ const m = new SystemMirror(this._trpcClient);
794
+ this.mirror = m;
795
+ this.mirrorInit = (async () => {
796
+ await this.awaitConnected(timeoutMs);
797
+ await m.init(timeoutMs);
798
+ })().finally(() => {
799
+ this.mirrorInit = null;
800
+ });
801
+ return this.mirrorInit;
802
+ }
803
+ /** Promise that resolves once `init()` has completed. */
804
+ async awaitReady() {
805
+ if (this.mirror?.isReady()) return;
806
+ if (this.mirrorInit) return this.mirrorInit;
807
+ return this.init();
808
+ }
809
+ /** True after `init()` resolves. */
810
+ isReady() {
811
+ return this.mirror?.isReady() ?? false;
812
+ }
813
+ /** True after the transport probe has succeeded at least once. */
814
+ isConnected() {
815
+ return this.connected;
816
+ }
817
+ /**
818
+ * Force a fresh WebSocket handshake. Tears down the wsClient + tRPC
819
+ * client + mirror (the mirror captures the tRPC reference at
820
+ * construction time and would otherwise dispatch through a closed
821
+ * client) and rebuilds them. No-op for HTTP transport.
822
+ */
823
+ reconnect() {
824
+ if (!this.useWs) return;
825
+ this.emitConnectionEvent("connecting");
826
+ this._wsClient?.close();
827
+ this.disposeMirror();
828
+ this.connected = false;
829
+ this.connectedPromise = null;
830
+ this._trpcClient = this.buildTrpcClient();
831
+ this._systemProxy = createSystemProxy(this._trpcClient);
832
+ }
833
+ /**
834
+ * Pivot the underlying tRPC transport onto a different base URL.
835
+ * Used by the endpoint-race flow: the SDK opens against the public
836
+ * URL the operator provided, calls `localNetwork.getConnectionEndpoints`
837
+ * to discover LAN candidates, races them, and (when a faster one wins)
838
+ * calls `switchServerUrl(winner)` to migrate every subsequent query
839
+ * onto the LAN path without losing auth state.
840
+ *
841
+ * Keeps the auth token. Tears down the WS + mirror and rebuilds them
842
+ * against the new URL — same machinery as `reconnect()` but with a
843
+ * different target.
844
+ */
845
+ switchServerUrl(nextUrl) {
846
+ const normalized = nextUrl.replace(/\/+$/, "");
847
+ if (normalized === this._serverUrl) return;
848
+ this._serverUrl = normalized;
849
+ this.reconnect();
850
+ }
851
+ /**
852
+ * Race the candidate base URLs reported by the hub's `local-network`
853
+ * cap, pick the fastest one that responds, and (if it's different
854
+ * from the current URL) pivot the transport onto it.
855
+ *
856
+ * Flow:
857
+ * 1. Query `localNetwork.getConnectionEndpoints({ port })` over the
858
+ * already-authenticated tRPC channel — the cap is auth-gated,
859
+ * so the LAN IPs never leak to anonymous callers.
860
+ * 2. For each candidate, fire a HEAD on `{baseUrl}/trpc/health`
861
+ * with a short timeout (default 1500ms). The first 2xx wins.
862
+ * 3. If the winner differs from `this.serverUrl`, call
863
+ * `switchServerUrl(winner)` and return it. Otherwise return
864
+ * the current URL unchanged.
865
+ *
866
+ * Bounded if every candidate times out we keep the current URL.
867
+ * Idempotent safe to call on every connect / reconnect / network
868
+ * change event.
869
+ */
870
+ async raceConnectionEndpoints(options) {
871
+ const timeoutMs = options?.perCandidateTimeoutMs ?? 1500;
872
+ const url = (() => {
873
+ try {
874
+ return new URL(this._serverUrl);
875
+ } catch {
876
+ return null;
877
+ }
878
+ })();
879
+ const port = url?.port ? Number(url.port) : url?.protocol === "https:" ? 443 : 80;
880
+ const scheme = url?.protocol === "https:" ? "https" : "http";
881
+ const cap = this._trpcClient.localNetwork?.getConnectionEndpoints;
882
+ if (!cap) return {
883
+ winner: this._serverUrl,
884
+ switched: false
885
+ };
886
+ let endpoints;
887
+ try {
888
+ endpoints = (await cap.query({
889
+ port,
890
+ ipv4Only: options?.ipv4Only,
891
+ scheme
892
+ })).endpoints;
893
+ } catch {
894
+ return {
895
+ winner: this._serverUrl,
896
+ switched: false
897
+ };
898
+ }
899
+ if (endpoints.length === 0) return {
900
+ winner: this._serverUrl,
901
+ switched: false
902
+ };
903
+ const winner = await raceFastestEndpoint(endpoints.map((e) => e.baseUrl), timeoutMs);
904
+ if (!winner || winner === this._serverUrl) return {
905
+ winner: winner ?? this._serverUrl,
906
+ switched: false
907
+ };
908
+ this.switchServerUrl(winner);
909
+ return {
910
+ winner,
911
+ switched: true
912
+ };
913
+ }
914
+ /** Tear down WS connection + mirror. The instance is unusable afterwards. */
915
+ close() {
916
+ this.disposeMirror();
917
+ this.connected = false;
918
+ this.connectedPromise = null;
919
+ this._wsClient?.close();
920
+ }
921
+ disposeMirror() {
922
+ this.mirror?.dispose();
923
+ this.mirror = null;
924
+ this.mirrorInit = null;
925
+ }
926
+ async login(username, password) {
927
+ const result = await this._trpcClient.auth.login.mutate({
928
+ username,
929
+ password
930
+ });
931
+ if (typeof result === "object" && result !== null && "token" in result) {
932
+ const r = result;
933
+ const token = r.token;
934
+ if (typeof token === "string" && r.requiresTotp !== true) this.setToken(token);
935
+ }
936
+ return result;
937
+ }
938
+ /**
939
+ * Second leg of the 2FA login. The caller passes the challenge
940
+ * token returned by `login` (when `requiresTotp: true`) plus the
941
+ * 6-digit code from the user's authenticator app. On success the
942
+ * server mints the real session JWT, which we set as the active
943
+ * token on this client.
944
+ */
945
+ async loginVerifyTotp(challengeToken, code) {
946
+ const result = await this._trpcClient.auth.loginVerifyTotp.mutate({
947
+ challengeToken,
948
+ code
949
+ });
950
+ if (typeof result === "object" && result !== null && "token" in result) {
951
+ const token = result.token;
952
+ if (typeof token === "string") this.setToken(token);
953
+ }
954
+ return result;
955
+ }
956
+ async logout() {
957
+ await this._trpcClient.auth.logout.mutate();
958
+ }
959
+ async getMe() {
960
+ return await this._trpcClient.auth.me.query();
961
+ }
962
+ async changeOwnPassword(input) {
963
+ return await this._trpcClient.auth.changeOwnPassword.mutate(input);
964
+ }
965
+ async setupOwnTotp() {
966
+ return await this._trpcClient.auth.setupOwnTotp.mutate();
967
+ }
968
+ async confirmOwnTotp(input) {
969
+ return await this._trpcClient.auth.confirmOwnTotp.mutate(input);
970
+ }
971
+ async disableOwnTotp() {
972
+ return await this._trpcClient.auth.disableOwnTotp.mutate();
973
+ }
974
+ async getOwnTotpStatus() {
975
+ return await this._trpcClient.auth.getOwnTotpStatus.query();
976
+ }
977
+ /** Update the auth token (e.g. after login or token refresh). */
978
+ setToken(token) {
979
+ this.token = token;
980
+ }
981
+ /**
982
+ * Synchronous snapshot of every device matching the optional filters.
983
+ * Backed by the `SystemMirror` warm-boot cache — call `init()` first
984
+ * (or `awaitReady()`) before invoking. Returns an empty array if the
985
+ * mirror has not yet been booted.
986
+ *
987
+ * Each returned proxy has `binding` populated from the mirror's
988
+ * binding cache (Phase 5 dedup), so consumers no longer need to
989
+ * make a separate `deviceManager.getBindings` round-trip.
990
+ */
991
+ listDevices(filters) {
992
+ if (!this.mirror) return [];
993
+ return (filters ? this.mirror.query(filters) : this.mirror.getAllDevices()).map((p) => this.attachBinding(p));
994
+ }
995
+ /**
996
+ * Sync `DeviceInfo` snapshot (name, canonical type, online, …) for one device
997
+ * from the warm-boot mirror `null` if the mirror isn't booted or the device
998
+ * is unknown. Unlike a `DeviceProxy` (cap accessors only), this carries the
999
+ * display identity the UI needs to render a device on first paint.
1000
+ */
1001
+ getDeviceInfo(deviceId) {
1002
+ return this.mirror?.getDeviceInfo(deviceId) ?? null;
1003
+ }
1004
+ /**
1005
+ * The `DeviceInfo` snapshots for the current device set (the warm-boot cache),
1006
+ * honouring the same `filters` as {@link listDevices}. Lets a UI render a named
1007
+ * device list immediately without waiting for per-device lifecycle events.
1008
+ */
1009
+ listDeviceInfos(filters) {
1010
+ const mirror = this.mirror;
1011
+ if (!mirror) return [];
1012
+ const proxies = filters ? mirror.query(filters) : mirror.getAllDevices();
1013
+ const out = [];
1014
+ for (const p of proxies) {
1015
+ const info = mirror.getDeviceInfo(p.deviceId);
1016
+ if (info) out.push(info);
1017
+ }
1018
+ return out;
1019
+ }
1020
+ /**
1021
+ * Sync lookup by numeric id. `null` if the mirror has not been booted
1022
+ * or the device is unknown.
1023
+ */
1024
+ getDevice(deviceId) {
1025
+ const proxy = this.mirror?.getDeviceById(deviceId) ?? null;
1026
+ return proxy ? this.attachBinding(proxy) : null;
1027
+ }
1028
+ /** Sync lookup by display name (exact match). */
1029
+ getDeviceByName(name) {
1030
+ const proxy = this.mirror?.getDeviceByName(name) ?? null;
1031
+ return proxy ? this.attachBinding(proxy) : null;
1032
+ }
1033
+ /** Sync lookup by stableId. */
1034
+ getDeviceByStableId(stableId) {
1035
+ const proxy = this.mirror?.getDeviceByStableId(stableId) ?? null;
1036
+ return proxy ? this.attachBinding(proxy) : null;
1037
+ }
1038
+ /**
1039
+ * Resolve when a device with `deviceId` becomes available. Resolves
1040
+ * immediately if already known; rejects with a timeout error
1041
+ * otherwise (default 30s).
1042
+ */
1043
+ async waitForDevice(deviceId, timeoutMs) {
1044
+ if (!this.mirror) await this.init();
1045
+ const proxy = await this.mirror.waitForDevice(deviceId, timeoutMs ?? 3e4);
1046
+ return this.attachBinding(proxy);
1047
+ }
1048
+ /** Subscribe to `device.registered` events. */
1049
+ onDeviceAdded(cb) {
1050
+ if (!this.mirror) throw new Error("System.onDeviceAdded: call init() before subscribing");
1051
+ return this.mirror.onDeviceAdded(cb);
1052
+ }
1053
+ /** Subscribe to `device.unregistered` events. */
1054
+ onDeviceRemoved(cb) {
1055
+ if (!this.mirror) throw new Error("System.onDeviceRemoved: call init() before subscribing");
1056
+ return this.mirror.onDeviceRemoved(cb);
1057
+ }
1058
+ /**
1059
+ * Patch the proxy's `binding` field from the mirror's cache. The
1060
+ * generated `createDeviceProxy()` already sets `binding` to the
1061
+ * binding it was constructed with — this is a defensive overwrite
1062
+ * that uses the latest cached entry in case a `binding-changed`
1063
+ * event landed between proxy creation and access.
1064
+ */
1065
+ attachBinding(proxy) {
1066
+ const binding = this.lookupBinding(proxy.deviceId);
1067
+ if (binding === proxy.binding) return proxy;
1068
+ const writable = proxy;
1069
+ writable.binding = binding;
1070
+ return proxy;
1071
+ }
1072
+ /** Fetch the latest cached binding from the mirror, or `null`. */
1073
+ lookupBinding(deviceId) {
1074
+ if (!this.mirror) return null;
1075
+ return this.mirror.getDeviceById(deviceId)?.binding ?? null;
1076
+ }
1077
+ get addonPages() {
1078
+ return this._systemProxy.addonPages;
1079
+ }
1080
+ get addonSettings() {
1081
+ return this._systemProxy.addonSettings;
1082
+ }
1083
+ get alerts() {
1084
+ return this._systemProxy.alerts;
1085
+ }
1086
+ get audioAnalyzer() {
1087
+ return this._systemProxy.audioAnalyzer;
1088
+ }
1089
+ get audioCodec() {
1090
+ return this._systemProxy.audioCodec;
1091
+ }
1092
+ get backup() {
1093
+ return this._systemProxy.backup;
1094
+ }
1095
+ get decoder() {
1096
+ return this._systemProxy.decoder;
1097
+ }
1098
+ get deviceManager() {
1099
+ return this._systemProxy.deviceManager;
1100
+ }
1101
+ get deviceProvider() {
1102
+ return this._systemProxy.deviceProvider;
1103
+ }
1104
+ get deviceState() {
1105
+ return this._systemProxy.deviceState;
1106
+ }
1107
+ get metricsProvider() {
1108
+ return this._systemProxy.metricsProvider;
1109
+ }
1110
+ get notificationOutput() {
1111
+ return this._systemProxy.notificationOutput;
1112
+ }
1113
+ get pipelineExecutor() {
1114
+ return this._systemProxy.pipelineExecutor;
1115
+ }
1116
+ get pipelineOrchestrator() {
1117
+ return this._systemProxy.pipelineOrchestrator;
1118
+ }
1119
+ get pipelineRunner() {
1120
+ return this._systemProxy.pipelineRunner;
1121
+ }
1122
+ get settingsStore() {
1123
+ return this._systemProxy.settingsStore;
1124
+ }
1125
+ get storage() {
1126
+ return this._systemProxy.storage;
1127
+ }
1128
+ get streamBroker() {
1129
+ return this._systemProxy.streamBroker;
1130
+ }
1131
+ get turnProvider() {
1132
+ return this._systemProxy.turnProvider;
1133
+ }
1134
+ get userManagement() {
1135
+ return this._systemProxy.userManagement;
1136
+ }
1137
+ /**
1138
+ * Subscribe to a single event category. Returns an unsubscribe
1139
+ * handle. Errors thrown by the listener are swallowed so a single
1140
+ * misbehaving consumer cannot tear down the WS subscription.
1141
+ *
1142
+ * Categories should be values from the `EventCategory` enum
1143
+ * (`@camstack/types`) — passing a raw string works for forward-compat
1144
+ * but loses type safety. The SDK forwards the value verbatim to the
1145
+ * server's `live.onEvent` subscription.
1146
+ */
1147
+ subscribeEvent(category, cb) {
1148
+ const sub = this._trpcClient.live.onEvent.subscribe({ category }, { onData: (event) => {
1149
+ try {
1150
+ cb(event);
1151
+ } catch {}
1152
+ } });
1153
+ return () => {
1154
+ try {
1155
+ sub.unsubscribe();
1156
+ } catch {}
1157
+ };
1158
+ }
1159
+ /** Direct tRPC client. Read once per call; rebuilt on `reconnect()`. */
1160
+ get trpcClient() {
1161
+ return this._trpcClient;
1162
+ }
1163
+ /**
1164
+ * Underlying WSClient (or `null` for HTTP transport). Used by
1165
+ * advanced consumers that need direct access to the WebSocket
1166
+ * (e.g. for keep-alive metrics). Rebuilt on `reconnect()`.
1167
+ */
1168
+ get wsClient() {
1169
+ return this._wsClient;
1170
+ }
1171
+ buildTrpcClient() {
1172
+ const headers = () => {
1173
+ const h = {};
1174
+ if (this.token) h["Authorization"] = `Bearer ${this.token}`;
1175
+ return h;
1176
+ };
1177
+ if (this.useWs) {
1178
+ const wsUrl = this._serverUrl.replace(/^http/, "ws") + "/trpc";
1179
+ const baseRetryMs = this.baseRetryMs;
1180
+ const maxRetryMs = this.maxRetryMs;
1181
+ this._wsClient = createWSClient({
1182
+ url: wsUrl,
1183
+ connectionParams: () => ({ token: this.token }),
1184
+ retryDelayMs: (attemptIndex) => Math.min(baseRetryMs * Math.pow(2, attemptIndex), maxRetryMs),
1185
+ keepAlive: {
1186
+ enabled: true,
1187
+ intervalMs: WS_KEEP_ALIVE_INTERVAL_MS,
1188
+ pongTimeoutMs: WS_KEEP_ALIVE_PONG_TIMEOUT_MS
1189
+ },
1190
+ onOpen: () => {
1191
+ this._connectionVersion += 1;
1192
+ this.emitConnectionEvent("connected");
1193
+ },
1194
+ onClose: () => {
1195
+ this.emitConnectionEvent("disconnected");
1196
+ }
1197
+ });
1198
+ return createTRPCClient({ links: [wsLink({
1199
+ client: this._wsClient,
1200
+ transformer: SuperJSON
1201
+ })] });
1202
+ }
1203
+ this._wsClient = null;
1204
+ return createTRPCClient({ links: [httpLink({
1205
+ url: `${this._serverUrl}/trpc`,
1206
+ headers,
1207
+ transformer: SuperJSON
1208
+ })] });
1209
+ }
1210
+ };
1211
+ /** Create a `System` instance. Convenience factory. */
1367
1212
  function createSystem(config) {
1368
- return new System(config);
1213
+ return new System(config);
1369
1214
  }
1215
+ /**
1216
+ * Race a list of candidate base URLs and return the first one that
1217
+ * responds to `GET {baseUrl}/trpc/health` with a 2xx within
1218
+ * `timeoutMs`. Returns `null` if every candidate fails / times out.
1219
+ *
1220
+ * Implementation notes:
1221
+ * - Uses `fetch` with `AbortController` for per-candidate cutoff so a
1222
+ * stalled candidate doesn't pin the wallclock for the whole race.
1223
+ * - Cancels every still-pending probe as soon as the first one
1224
+ * succeeds — no wasted bandwidth on the loser candidates.
1225
+ * - `/trpc/health` is the only endpoint guaranteed to respond on every
1226
+ * CamStack deployment (registered alongside the tRPC plugin). It is
1227
+ * intentionally NOT auth-gated since it's used by load balancers /
1228
+ * uptime probes — same surface every reverse proxy already monitors.
1229
+ * - HEAD would be nicer but Cloudflare Tunnel + some browsers
1230
+ * mishandle HEAD on the ingress path; GET is safer.
1231
+ *
1232
+ * Exported so non-System callers (CLI helpers, tests) can race their
1233
+ * own candidate lists without instantiating a full System.
1234
+ */
1370
1235
  async function raceFastestEndpoint(candidates, timeoutMs) {
1371
- if (candidates.length === 0) return null;
1372
- if (typeof fetch !== "function") return candidates[0] ?? null;
1373
- const pageIsHttps = typeof window !== "undefined" && typeof window.location !== "undefined" && window.location.protocol === "https:";
1374
- const reachable = pageIsHttps ? candidates.filter((u) => u.toLowerCase().startsWith("https://")) : candidates;
1375
- if (reachable.length === 0) return null;
1376
- const controllers = reachable.map(() => new AbortController());
1377
- const probes = reachable.map(async (baseUrl, i) => {
1378
- const ctrl = controllers[i];
1379
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
1380
- try {
1381
- const res = await fetch(`${baseUrl.replace(/\/+$/, "")}/trpc/health`, {
1382
- method: "GET",
1383
- signal: ctrl.signal,
1384
- // Cross-origin probes from a browser sandbox are fine — the
1385
- // /trpc/health response is open-CORS by the Fastify default.
1386
- // No credentials needed (this is a transport-quality probe,
1387
- // not an auth round-trip).
1388
- credentials: "omit"
1389
- });
1390
- if (!res.ok) throw new Error(`${res.status}`);
1391
- return baseUrl;
1392
- } finally {
1393
- clearTimeout(timer);
1394
- }
1395
- });
1396
- try {
1397
- const winner = await Promise.any(probes);
1398
- for (const c of controllers) {
1399
- try {
1400
- c.abort();
1401
- } catch {
1402
- }
1403
- }
1404
- return winner;
1405
- } catch {
1406
- return null;
1407
- }
1236
+ if (candidates.length === 0) return null;
1237
+ if (typeof fetch !== "function") return candidates[0] ?? null;
1238
+ const reachable = typeof window !== "undefined" && typeof window.location !== "undefined" && window.location.protocol === "https:" ? candidates.filter((u) => u.toLowerCase().startsWith("https://")) : candidates;
1239
+ if (reachable.length === 0) return null;
1240
+ const controllers = reachable.map(() => new AbortController());
1241
+ const probes = reachable.map(async (baseUrl, i) => {
1242
+ const ctrl = controllers[i];
1243
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
1244
+ try {
1245
+ const res = await fetch(`${baseUrl.replace(/\/+$/, "")}/trpc/health`, {
1246
+ method: "GET",
1247
+ signal: ctrl.signal,
1248
+ credentials: "omit"
1249
+ });
1250
+ if (!res.ok) throw new Error(`${res.status}`);
1251
+ return baseUrl;
1252
+ } finally {
1253
+ clearTimeout(timer);
1254
+ }
1255
+ });
1256
+ try {
1257
+ const winner = await Promise.any(probes);
1258
+ for (const c of controllers) try {
1259
+ c.abort();
1260
+ } catch {}
1261
+ return winner;
1262
+ } catch {
1263
+ return null;
1264
+ }
1408
1265
  }
1409
- var DetectionClass = /* @__PURE__ */ ((DetectionClass2) => {
1410
- DetectionClass2["Motion"] = "motion";
1411
- DetectionClass2["Person"] = "person";
1412
- DetectionClass2["Vehicle"] = "vehicle";
1413
- DetectionClass2["Animal"] = "animal";
1414
- DetectionClass2["Audio"] = "audio";
1415
- DetectionClass2["Face"] = "face";
1416
- DetectionClass2["Plate"] = "plate";
1417
- DetectionClass2["Package"] = "package";
1418
- DetectionClass2["Doorbell"] = "doorbell";
1419
- DetectionClass2["Sensor"] = "sensor";
1420
- return DetectionClass2;
1421
- })(DetectionClass || {});
1422
- const animalClasses = [
1423
- "animal",
1424
- "dog_cat",
1425
- "dog",
1426
- "cat",
1427
- "horse",
1428
- "sheep",
1429
- "cow",
1430
- "elephant",
1431
- "bear",
1432
- "zebra",
1433
- "giraffe",
1434
- "mouse",
1435
- "rabbit",
1436
- "deer",
1437
- "lion",
1438
- "tiger",
1439
- "bird",
1440
- "eagle",
1441
- "owl",
1442
- "pigeon",
1443
- "fish",
1444
- "whale",
1445
- "dolphin",
1446
- "snake",
1447
- "turtle",
1448
- "lizard"
1266
+ //#endregion
1267
+ //#region src/detection.ts
1268
+ /**
1269
+ * Detection classes — shared between CamStack app and proxy.
1270
+ * Aligned with scrypted-advanced-notifier/src/detectionClasses.ts.
1271
+ */
1272
+ var DetectionClass = /* @__PURE__ */ function(DetectionClass) {
1273
+ DetectionClass["Motion"] = "motion";
1274
+ DetectionClass["Person"] = "person";
1275
+ DetectionClass["Vehicle"] = "vehicle";
1276
+ DetectionClass["Animal"] = "animal";
1277
+ DetectionClass["Audio"] = "audio";
1278
+ DetectionClass["Face"] = "face";
1279
+ DetectionClass["Plate"] = "plate";
1280
+ DetectionClass["Package"] = "package";
1281
+ DetectionClass["Doorbell"] = "doorbell";
1282
+ DetectionClass["Sensor"] = "sensor";
1283
+ return DetectionClass;
1284
+ }({});
1285
+ var animalClasses = [
1286
+ "animal",
1287
+ "dog_cat",
1288
+ "dog",
1289
+ "cat",
1290
+ "horse",
1291
+ "sheep",
1292
+ "cow",
1293
+ "elephant",
1294
+ "bear",
1295
+ "zebra",
1296
+ "giraffe",
1297
+ "mouse",
1298
+ "rabbit",
1299
+ "deer",
1300
+ "lion",
1301
+ "tiger",
1302
+ "bird",
1303
+ "eagle",
1304
+ "owl",
1305
+ "pigeon",
1306
+ "fish",
1307
+ "whale",
1308
+ "dolphin",
1309
+ "snake",
1310
+ "turtle",
1311
+ "lizard"
1449
1312
  ];
1450
- const personClasses = [
1451
- "person",
1452
- "people",
1453
- "pedestrian",
1454
- "rider",
1455
- "driver",
1456
- "cyclist",
1457
- "skier",
1458
- "skateboarder",
1459
- "face",
1460
- "hand",
1461
- "head",
1462
- "body"
1313
+ var personClasses = [
1314
+ "person",
1315
+ "people",
1316
+ "pedestrian",
1317
+ "rider",
1318
+ "driver",
1319
+ "cyclist",
1320
+ "skier",
1321
+ "skateboarder",
1322
+ "face",
1323
+ "hand",
1324
+ "head",
1325
+ "body"
1463
1326
  ];
1464
- const vehicleClasses = [
1465
- "vehicle",
1466
- "car",
1467
- "truck",
1468
- "bus",
1469
- "motorcycle",
1470
- "bicycle",
1471
- "van",
1472
- "ambulance",
1473
- "police_car",
1474
- "fire_truck",
1475
- "train",
1476
- "subway",
1477
- "tram",
1478
- "airplane",
1479
- "boat",
1480
- "ship",
1481
- "helicopter"
1327
+ var vehicleClasses = [
1328
+ "vehicle",
1329
+ "car",
1330
+ "truck",
1331
+ "bus",
1332
+ "motorcycle",
1333
+ "bicycle",
1334
+ "van",
1335
+ "ambulance",
1336
+ "police_car",
1337
+ "fire_truck",
1338
+ "train",
1339
+ "subway",
1340
+ "tram",
1341
+ "airplane",
1342
+ "boat",
1343
+ "ship",
1344
+ "helicopter"
1482
1345
  ];
1483
- const faceClasses = [
1484
- "face",
1485
- "eyes",
1486
- "nose",
1487
- "mouth",
1488
- "ears",
1489
- "eyebrows",
1490
- "left_eye",
1491
- "right_eye",
1492
- "pupil",
1493
- "iris",
1494
- "eyelid",
1495
- "eye_corner",
1496
- "upper_lip",
1497
- "lower_lip",
1498
- "teeth",
1499
- "chin",
1500
- "cheek",
1501
- "forehead",
1502
- "jaw",
1503
- "glasses",
1504
- "sunglasses",
1505
- "facial_hair",
1506
- "beard",
1507
- "mustache",
1508
- "facial_landmark",
1509
- "facial_keypoint"
1346
+ var faceClasses = [
1347
+ "face",
1348
+ "eyes",
1349
+ "nose",
1350
+ "mouth",
1351
+ "ears",
1352
+ "eyebrows",
1353
+ "left_eye",
1354
+ "right_eye",
1355
+ "pupil",
1356
+ "iris",
1357
+ "eyelid",
1358
+ "eye_corner",
1359
+ "upper_lip",
1360
+ "lower_lip",
1361
+ "teeth",
1362
+ "chin",
1363
+ "cheek",
1364
+ "forehead",
1365
+ "jaw",
1366
+ "glasses",
1367
+ "sunglasses",
1368
+ "facial_hair",
1369
+ "beard",
1370
+ "mustache",
1371
+ "facial_landmark",
1372
+ "facial_keypoint"
1510
1373
  ];
1511
- const licensePlateClasses = [
1512
- "plate",
1513
- "license_plate",
1514
- "front_plate",
1515
- "rear_plate",
1516
- "motorcycle_plate",
1517
- "temporary_plate",
1518
- "dealer_plate",
1519
- "licensePlate",
1520
- "plate_number",
1521
- "plate_character",
1522
- "plate_digit",
1523
- "plate_letter",
1524
- "plate_symbol",
1525
- "plate_region",
1526
- "plate_country_identifier",
1527
- "plate_frame",
1528
- "plate_bolt",
1529
- "plate_sticker",
1530
- "plate_validation_tag",
1531
- "damaged_plate",
1532
- "obscured_plate",
1533
- "dirty_plate"
1374
+ var licensePlateClasses = [
1375
+ "plate",
1376
+ "license_plate",
1377
+ "front_plate",
1378
+ "rear_plate",
1379
+ "motorcycle_plate",
1380
+ "temporary_plate",
1381
+ "dealer_plate",
1382
+ "licensePlate",
1383
+ "plate_number",
1384
+ "plate_character",
1385
+ "plate_digit",
1386
+ "plate_letter",
1387
+ "plate_symbol",
1388
+ "plate_region",
1389
+ "plate_country_identifier",
1390
+ "plate_frame",
1391
+ "plate_bolt",
1392
+ "plate_sticker",
1393
+ "plate_validation_tag",
1394
+ "damaged_plate",
1395
+ "obscured_plate",
1396
+ "dirty_plate"
1534
1397
  ];
1535
- const motionClasses = ["motion", "movement", "other"];
1536
- const packageClasses = ["package", "packet"];
1537
- const audioClasses = [
1538
- "audio"
1539
- /* Audio */
1398
+ var motionClasses = [
1399
+ "motion",
1400
+ "movement",
1401
+ "other"
1540
1402
  ];
1541
- const audioLabelClasses = [
1542
- "speech",
1543
- "scream",
1544
- "babbling",
1545
- "yell",
1546
- "bellow",
1547
- "whoop",
1548
- "whispering",
1549
- "laughter",
1550
- "snicker",
1551
- "crying",
1552
- "cry",
1553
- "sigh",
1554
- "singing",
1555
- "choir",
1556
- "chant",
1557
- "mantra",
1558
- "child_singing",
1559
- "rapping",
1560
- "humming",
1561
- "groan",
1562
- "grunt",
1563
- "whistling",
1564
- "breathing",
1565
- "wheeze",
1566
- "snoring",
1567
- "gasp",
1568
- "pant",
1569
- "snort",
1570
- "cough",
1571
- "throat_clearing",
1572
- "sneeze",
1573
- "sniff",
1574
- "cheering",
1575
- "applause",
1576
- "chatter",
1577
- "crowd",
1578
- "children_playing",
1579
- "bark",
1580
- "yip",
1581
- "howl",
1582
- "bow-wow",
1583
- "growling",
1584
- "whimper_dog",
1585
- "purr",
1586
- "meow",
1587
- "hiss",
1588
- "caterwaul",
1589
- "pets",
1590
- "livestock",
1591
- "doorbell",
1592
- "ding-dong",
1593
- "door",
1594
- "slam",
1595
- "knock",
1596
- "alarm",
1597
- "telephone",
1598
- "music",
1599
- "dog",
1600
- "dogs"
1403
+ var packageClasses = ["package", "packet"];
1404
+ var audioClasses = ["audio"];
1405
+ /** Common YAMNet audio labels (advanced-notifier). Parent: Audio. */
1406
+ var audioLabelClasses = [
1407
+ "speech",
1408
+ "scream",
1409
+ "babbling",
1410
+ "yell",
1411
+ "bellow",
1412
+ "whoop",
1413
+ "whispering",
1414
+ "laughter",
1415
+ "snicker",
1416
+ "crying",
1417
+ "cry",
1418
+ "sigh",
1419
+ "singing",
1420
+ "choir",
1421
+ "chant",
1422
+ "mantra",
1423
+ "child_singing",
1424
+ "rapping",
1425
+ "humming",
1426
+ "groan",
1427
+ "grunt",
1428
+ "whistling",
1429
+ "breathing",
1430
+ "wheeze",
1431
+ "snoring",
1432
+ "gasp",
1433
+ "pant",
1434
+ "snort",
1435
+ "cough",
1436
+ "throat_clearing",
1437
+ "sneeze",
1438
+ "sniff",
1439
+ "cheering",
1440
+ "applause",
1441
+ "chatter",
1442
+ "crowd",
1443
+ "children_playing",
1444
+ "bark",
1445
+ "yip",
1446
+ "howl",
1447
+ "bow-wow",
1448
+ "growling",
1449
+ "whimper_dog",
1450
+ "purr",
1451
+ "meow",
1452
+ "hiss",
1453
+ "caterwaul",
1454
+ "pets",
1455
+ "livestock",
1456
+ "doorbell",
1457
+ "ding-dong",
1458
+ "door",
1459
+ "slam",
1460
+ "knock",
1461
+ "alarm",
1462
+ "telephone",
1463
+ "music",
1464
+ "dog",
1465
+ "dogs"
1601
1466
  ];
1602
- const doorbellClasses = ["doorbell", "ring"];
1603
- const sensorLabelClasses = [
1604
- "lock",
1605
- "binary",
1606
- "flood",
1607
- "entry",
1608
- "door",
1609
- "leak",
1610
- "door_open",
1611
- "flooded",
1612
- "entry_open"
1467
+ var doorbellClasses = ["doorbell", "ring"];
1468
+ /** Sensor types (advanced-notifier SupportedSensorType). Parent: Sensor. */
1469
+ var sensorLabelClasses = [
1470
+ "lock",
1471
+ "binary",
1472
+ "flood",
1473
+ "entry",
1474
+ "door",
1475
+ "leak",
1476
+ "door_open",
1477
+ "flooded",
1478
+ "entry_open"
1613
1479
  ];
1614
- const detectionClassesDefaultMap = {
1615
- ...animalClasses.reduce((tot, curr) => ({
1616
- ...tot,
1617
- [curr]: "animal"
1618
- /* Animal */
1619
- }), {}),
1620
- ...personClasses.reduce((tot, curr) => ({
1621
- ...tot,
1622
- [curr]: "person"
1623
- /* Person */
1624
- }), {}),
1625
- ...vehicleClasses.reduce((tot, curr) => ({
1626
- ...tot,
1627
- [curr]: "vehicle"
1628
- /* Vehicle */
1629
- }), {}),
1630
- ...motionClasses.reduce((tot, curr) => ({
1631
- ...tot,
1632
- [curr]: "motion"
1633
- /* Motion */
1634
- }), {}),
1635
- ...packageClasses.reduce((tot, curr) => ({
1636
- ...tot,
1637
- [curr]: "package"
1638
- /* Package */
1639
- }), {}),
1640
- ...faceClasses.reduce((tot, curr) => ({
1641
- ...tot,
1642
- [curr]: "face"
1643
- /* Face */
1644
- }), {}),
1645
- ...licensePlateClasses.reduce((tot, curr) => ({
1646
- ...tot,
1647
- [curr]: "plate"
1648
- /* Plate */
1649
- }), {}),
1650
- ...audioClasses.reduce((tot, curr) => ({
1651
- ...tot,
1652
- [curr]: "audio"
1653
- /* Audio */
1654
- }), {}),
1655
- ...audioLabelClasses.reduce((tot, curr) => ({
1656
- ...tot,
1657
- [curr]: "audio"
1658
- /* Audio */
1659
- }), {}),
1660
- ...doorbellClasses.reduce((tot, curr) => ({
1661
- ...tot,
1662
- [curr]: "doorbell"
1663
- /* Doorbell */
1664
- }), {}),
1665
- ...sensorLabelClasses.reduce((tot, curr) => ({
1666
- ...tot,
1667
- [curr]: "sensor"
1668
- /* Sensor */
1669
- }), {})
1480
+ var detectionClassesDefaultMap = {
1481
+ ...animalClasses.reduce((tot, curr) => ({
1482
+ ...tot,
1483
+ [curr]: "animal"
1484
+ }), {}),
1485
+ ...personClasses.reduce((tot, curr) => ({
1486
+ ...tot,
1487
+ [curr]: "person"
1488
+ }), {}),
1489
+ ...vehicleClasses.reduce((tot, curr) => ({
1490
+ ...tot,
1491
+ [curr]: "vehicle"
1492
+ }), {}),
1493
+ ...motionClasses.reduce((tot, curr) => ({
1494
+ ...tot,
1495
+ [curr]: "motion"
1496
+ }), {}),
1497
+ ...packageClasses.reduce((tot, curr) => ({
1498
+ ...tot,
1499
+ [curr]: "package"
1500
+ }), {}),
1501
+ ...faceClasses.reduce((tot, curr) => ({
1502
+ ...tot,
1503
+ [curr]: "face"
1504
+ }), {}),
1505
+ ...licensePlateClasses.reduce((tot, curr) => ({
1506
+ ...tot,
1507
+ [curr]: "plate"
1508
+ }), {}),
1509
+ ...audioClasses.reduce((tot, curr) => ({
1510
+ ...tot,
1511
+ [curr]: "audio"
1512
+ }), {}),
1513
+ ...audioLabelClasses.reduce((tot, curr) => ({
1514
+ ...tot,
1515
+ [curr]: "audio"
1516
+ }), {}),
1517
+ ...doorbellClasses.reduce((tot, curr) => ({
1518
+ ...tot,
1519
+ [curr]: "doorbell"
1520
+ }), {}),
1521
+ ...sensorLabelClasses.reduce((tot, curr) => ({
1522
+ ...tot,
1523
+ [curr]: "sensor"
1524
+ }), {})
1670
1525
  };
1671
- const isFaceClassname = (c) => faceClasses.includes(c);
1672
- const isPlateClassname = (c) => licensePlateClasses.includes(c);
1673
- const isAnimalClassname = (c) => animalClasses.includes(c);
1674
- const isPersonClassname = (c) => personClasses.includes(c);
1675
- const isVehicleClassname = (c) => vehicleClasses.includes(c);
1676
- const isMotionClassname = (c) => motionClasses.includes(c);
1677
- const isDoorbellClassname = (c) => doorbellClasses.includes(c);
1678
- const isPackageClassname = (c) => packageClasses.includes(c);
1679
- const isAudioClassname = (c) => audioClasses.includes(c) || audioLabelClasses.includes(c);
1680
- const isSensorLabelClassname = (c) => sensorLabelClasses.includes(c);
1681
- const isLabelDetection = (c) => isFaceClassname(c) || isPlateClassname(c);
1682
- const getParentClass = (className) => detectionClassesDefaultMap[className];
1683
- const getParentDetectionClass = (det) => {
1684
- const { className } = det;
1685
- const baseMap = {
1686
- [
1687
- "face"
1688
- /* Face */
1689
- ]: "person",
1690
- [
1691
- "plate"
1692
- /* Plate */
1693
- ]: "vehicle"
1694
- /* Vehicle */
1695
- };
1696
- const parentGroup = detectionClassesDefaultMap[className];
1697
- if (parentGroup && parentGroup !== className) return parentGroup;
1698
- return baseMap[className];
1526
+ var isFaceClassname = (c) => faceClasses.includes(c);
1527
+ var isPlateClassname = (c) => licensePlateClasses.includes(c);
1528
+ var isAnimalClassname = (c) => animalClasses.includes(c);
1529
+ var isPersonClassname = (c) => personClasses.includes(c);
1530
+ var isVehicleClassname = (c) => vehicleClasses.includes(c);
1531
+ var isMotionClassname = (c) => motionClasses.includes(c);
1532
+ var isDoorbellClassname = (c) => doorbellClasses.includes(c);
1533
+ var isPackageClassname = (c) => packageClasses.includes(c);
1534
+ var isAudioClassname = (c) => audioClasses.includes(c) || audioLabelClasses.includes(c);
1535
+ var isSensorLabelClassname = (c) => sensorLabelClasses.includes(c);
1536
+ var isLabelDetection = (c) => isFaceClassname(c) || isPlateClassname(c);
1537
+ var getParentClass = (className) => detectionClassesDefaultMap[className];
1538
+ var getParentDetectionClass = (det) => {
1539
+ const { className } = det;
1540
+ const baseMap = {
1541
+ ["face"]: "person",
1542
+ ["plate"]: "vehicle"
1543
+ };
1544
+ const parentGroup = detectionClassesDefaultMap[className];
1545
+ if (parentGroup && parentGroup !== className) return parentGroup;
1546
+ return baseMap[className];
1699
1547
  };
1700
- const defaultDetectionClasses = Object.values(DetectionClass);
1701
- const DEFAULT_ENABLED_CLASSES = defaultDetectionClasses.filter(
1702
- (c) => c !== "motion"
1703
- /* Motion */
1704
- );
1705
- const TIMELINE_PRESET_CRITICAL = [
1706
- "person",
1707
- "doorbell",
1708
- "package"
1709
- /* Package */
1548
+ var defaultDetectionClasses = Object.values(DetectionClass);
1549
+ /** Default enabled classes: all except Motion. */
1550
+ var DEFAULT_ENABLED_CLASSES = defaultDetectionClasses.filter((c) => c !== "motion");
1551
+ /** Classes for "critical" preset: security-relevant only (person, doorbell, package). */
1552
+ var TIMELINE_PRESET_CRITICAL = [
1553
+ "person",
1554
+ "doorbell",
1555
+ "package"
1710
1556
  ];
1711
- const TIMELINE_PRESET_IMPORTANT = [
1712
- ...TIMELINE_PRESET_CRITICAL,
1713
- "vehicle",
1714
- "animal",
1715
- "audio",
1716
- "face",
1717
- "plate"
1718
- /* Plate */
1557
+ /** Classes for "important" preset: critical + vehicle, animal, audio, face, plate. */
1558
+ var TIMELINE_PRESET_IMPORTANT = [
1559
+ ...TIMELINE_PRESET_CRITICAL,
1560
+ "vehicle",
1561
+ "animal",
1562
+ "audio",
1563
+ "face",
1564
+ "plate"
1719
1565
  ];
1720
- const TIMELINE_PRESET_ALL = [...DEFAULT_ENABLED_CLASSES];
1566
+ /** Classes for "all" preset: same as DEFAULT_ENABLED_CLASSES. */
1567
+ var TIMELINE_PRESET_ALL = [...DEFAULT_ENABLED_CLASSES];
1568
+ /** Get enabled classes for a preset. For "custom", pass customClasses. */
1721
1569
  function getClassesForTimelinePreset(preset, customClasses) {
1722
- switch (preset) {
1723
- case "critical":
1724
- return TIMELINE_PRESET_CRITICAL;
1725
- case "important":
1726
- return TIMELINE_PRESET_IMPORTANT;
1727
- case "all":
1728
- return TIMELINE_PRESET_ALL;
1729
- case "custom":
1730
- return customClasses?.length ? customClasses : DEFAULT_ENABLED_CLASSES;
1731
- default:
1732
- return DEFAULT_ENABLED_CLASSES;
1733
- }
1570
+ switch (preset) {
1571
+ case "critical": return TIMELINE_PRESET_CRITICAL;
1572
+ case "important": return TIMELINE_PRESET_IMPORTANT;
1573
+ case "all": return TIMELINE_PRESET_ALL;
1574
+ case "custom": return customClasses?.length ? customClasses : DEFAULT_ENABLED_CLASSES;
1575
+ default: return DEFAULT_ENABLED_CLASSES;
1576
+ }
1734
1577
  }
1735
- const RAW_TO_CANONICAL = {
1736
- // Scrypted PascalCase
1737
- Light: "light",
1738
- Switch: "switch",
1739
- WindowCovering: "cover",
1740
- Lock: "lock",
1741
- SecuritySystem: "alarm",
1742
- Buttons: "button",
1743
- Select: "select",
1744
- Siren: "siren",
1745
- Sensor: "sensor",
1746
- Entry: "entry",
1747
- Program: "script",
1748
- MediaPlayer: "media_player",
1749
- Outlet: "switch",
1750
- // Home Assistant lowercase domains
1751
- light: "light",
1752
- switch: "switch",
1753
- input_boolean: "switch",
1754
- cover: "cover",
1755
- lock: "lock",
1756
- alarm_control_panel: "alarm",
1757
- input_button: "button",
1758
- button: "button",
1759
- input_select: "select",
1760
- select: "select",
1761
- siren: "siren",
1762
- sensor: "sensor",
1763
- media_player: "media_player",
1764
- script: "script"
1578
+ //#endregion
1579
+ //#region src/devices.ts
1580
+ /**
1581
+ * Maps raw device types from Scrypted (PascalCase) and Home Assistant (lowercase domains)
1582
+ * to canonical CamStack device types.
1583
+ */
1584
+ var RAW_TO_CANONICAL = {
1585
+ Light: "light",
1586
+ Switch: "switch",
1587
+ WindowCovering: "cover",
1588
+ Lock: "lock",
1589
+ SecuritySystem: "alarm",
1590
+ Buttons: "button",
1591
+ Select: "select",
1592
+ Siren: "siren",
1593
+ Sensor: "sensor",
1594
+ Entry: "entry",
1595
+ Program: "script",
1596
+ MediaPlayer: "media_player",
1597
+ Outlet: "switch",
1598
+ light: "light",
1599
+ switch: "switch",
1600
+ input_boolean: "switch",
1601
+ cover: "cover",
1602
+ lock: "lock",
1603
+ alarm_control_panel: "alarm",
1604
+ input_button: "button",
1605
+ button: "button",
1606
+ input_select: "select",
1607
+ select: "select",
1608
+ siren: "siren",
1609
+ sensor: "sensor",
1610
+ media_player: "media_player",
1611
+ script: "script"
1765
1612
  };
1766
- const HA_DOMAIN_TYPE_MAP = {
1767
- light: "light",
1768
- switch: "switch",
1769
- input_boolean: "switch",
1770
- cover: "cover",
1771
- lock: "lock",
1772
- alarm_control_panel: "alarm",
1773
- input_button: "button",
1774
- button: "button",
1775
- input_select: "select",
1776
- select: "select",
1777
- siren: "siren",
1778
- sensor: "sensor",
1779
- binary_sensor: "sensor",
1780
- media_player: "media_player",
1781
- script: "script",
1782
- climate: "climate",
1783
- camera: "camera",
1784
- fan: "fan",
1785
- vacuum: "vacuum",
1786
- automation: "automation",
1787
- scene: "scene",
1788
- input_number: "sensor",
1789
- person: "person",
1790
- device_tracker: "tracker",
1791
- weather: "weather",
1792
- water_heater: "climate"
1613
+ /**
1614
+ * Extended HA domain → type map (includes non-eligible domains for display/categorization).
1615
+ * Superset of RAW_TO_CANONICAL for HA-specific domains.
1616
+ */
1617
+ var HA_DOMAIN_TYPE_MAP = {
1618
+ light: "light",
1619
+ switch: "switch",
1620
+ input_boolean: "switch",
1621
+ cover: "cover",
1622
+ lock: "lock",
1623
+ alarm_control_panel: "alarm",
1624
+ input_button: "button",
1625
+ button: "button",
1626
+ input_select: "select",
1627
+ select: "select",
1628
+ siren: "siren",
1629
+ sensor: "sensor",
1630
+ binary_sensor: "sensor",
1631
+ media_player: "media_player",
1632
+ script: "script",
1633
+ climate: "climate",
1634
+ camera: "camera",
1635
+ fan: "fan",
1636
+ vacuum: "vacuum",
1637
+ automation: "automation",
1638
+ scene: "scene",
1639
+ input_number: "sensor",
1640
+ person: "person",
1641
+ device_tracker: "tracker",
1642
+ weather: "weather",
1643
+ water_heater: "climate"
1793
1644
  };
1794
- const SCRYPTED_TYPE_TO_CANONICAL = {
1795
- Light: "light",
1796
- Switch: "switch",
1797
- WindowCovering: "cover",
1798
- Lock: "lock",
1799
- SecuritySystem: "alarm",
1800
- Buttons: "button",
1801
- Select: "select",
1802
- Siren: "siren",
1803
- Sensor: "sensor",
1804
- Entry: "entry",
1805
- Program: "script",
1806
- MediaPlayer: "media_player",
1807
- Camera: "camera",
1808
- Doorbell: "doorbell",
1809
- Fan: "fan",
1810
- Outlet: "switch"
1645
+ /**
1646
+ * Extended Scrypted type → canonical map (includes non-eligible types for display).
1647
+ * Superset of RAW_TO_CANONICAL for Scrypted-specific types.
1648
+ */
1649
+ var SCRYPTED_TYPE_TO_CANONICAL = {
1650
+ Light: "light",
1651
+ Switch: "switch",
1652
+ WindowCovering: "cover",
1653
+ Lock: "lock",
1654
+ SecuritySystem: "alarm",
1655
+ Buttons: "button",
1656
+ Select: "select",
1657
+ Siren: "siren",
1658
+ Sensor: "sensor",
1659
+ Entry: "entry",
1660
+ Program: "script",
1661
+ MediaPlayer: "media_player",
1662
+ Camera: "camera",
1663
+ Doorbell: "doorbell",
1664
+ Fan: "fan",
1665
+ Outlet: "switch"
1811
1666
  };
1667
+ /** Normalize raw type (Scrypted or HA) to canonical key for filtering and display. */
1812
1668
  function getCanonicalDeviceType(rawType) {
1813
- const canonical = RAW_TO_CANONICAL[rawType];
1814
- if (canonical) return canonical;
1815
- const lower = rawType.toLowerCase();
1816
- return RAW_TO_CANONICAL[lower] ?? null;
1669
+ const canonical = RAW_TO_CANONICAL[rawType];
1670
+ if (canonical) return canonical;
1671
+ return RAW_TO_CANONICAL[rawType.toLowerCase()] ?? null;
1817
1672
  }
1818
- const ELIGIBLE_SCRYPTED_DEVICE_TYPES = [
1819
- "Entry",
1820
- "Light",
1821
- "Switch",
1822
- "Lock",
1823
- "SecuritySystem",
1824
- "Buttons",
1825
- "WindowCovering",
1826
- "Siren",
1827
- "Sensor",
1828
- "Select",
1829
- "Program"
1673
+ /** Scrypted device types eligible for device control in CamStack. PascalCase. */
1674
+ var ELIGIBLE_SCRYPTED_DEVICE_TYPES = [
1675
+ "Entry",
1676
+ "Light",
1677
+ "Switch",
1678
+ "Lock",
1679
+ "SecuritySystem",
1680
+ "Buttons",
1681
+ "WindowCovering",
1682
+ "Siren",
1683
+ "Sensor",
1684
+ "Select",
1685
+ "Program"
1830
1686
  ];
1831
- const ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET = new Set(ELIGIBLE_SCRYPTED_DEVICE_TYPES);
1832
- const ELIGIBLE_HA_DOMAINS = [
1833
- "light",
1834
- "switch",
1835
- "input_boolean",
1836
- "cover",
1837
- "lock",
1838
- "alarm_control_panel",
1839
- "input_button",
1840
- "button",
1841
- "input_select",
1842
- "select",
1843
- "siren",
1844
- "media_player",
1845
- "script"
1687
+ /** Set version for O(1) lookup. */
1688
+ var ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET = new Set(ELIGIBLE_SCRYPTED_DEVICE_TYPES);
1689
+ /** Home Assistant domains eligible for device control in CamStack. */
1690
+ var ELIGIBLE_HA_DOMAINS = [
1691
+ "light",
1692
+ "switch",
1693
+ "input_boolean",
1694
+ "cover",
1695
+ "lock",
1696
+ "alarm_control_panel",
1697
+ "input_button",
1698
+ "button",
1699
+ "input_select",
1700
+ "select",
1701
+ "siren",
1702
+ "media_player",
1703
+ "script"
1846
1704
  ];
1847
- const ELIGIBLE_HA_DOMAINS_SET = new Set(ELIGIBLE_HA_DOMAINS);
1848
- const FEATURE_MATRIX = [
1849
- {
1850
- id: "liveStream",
1851
- label: "Live Stream",
1852
- sources: { frigate: true, scrypted: true, rtsp: true },
1853
- adapterMethod: "getLiveStream"
1854
- },
1855
- {
1856
- id: "multiResolution",
1857
- label: "Multi-Resolution",
1858
- sources: { frigate: true, scrypted: true, rtsp: false },
1859
- adapterMethod: "getResolutions"
1860
- },
1861
- {
1862
- id: "motion",
1863
- label: "Motion Detection",
1864
- sources: { frigate: false, scrypted: true, rtsp: false },
1865
- adapterMethod: "getMotion"
1866
- },
1867
- {
1868
- id: "objectDetection",
1869
- label: "Object Detection",
1870
- sources: { frigate: false, scrypted: true, rtsp: false },
1871
- adapterMethod: "getObjectDetections"
1872
- },
1873
- {
1874
- id: "audioVolume",
1875
- label: "Audio Level",
1876
- sources: { frigate: false, scrypted: true, rtsp: false },
1877
- adapterMethod: "getAudioVolume"
1878
- },
1879
- {
1880
- id: "audioVolumes",
1881
- label: "Audio Volumes (dBFS)",
1882
- sources: { frigate: false, scrypted: true, rtsp: false },
1883
- adapterMethod: "getAudioVolumes"
1884
- },
1885
- {
1886
- id: "ptz",
1887
- label: "PTZ Control",
1888
- sources: { frigate: false, scrypted: true, rtsp: false },
1889
- adapterMethod: "getPTZ"
1890
- },
1891
- {
1892
- id: "intercom",
1893
- label: "Intercom (Mic)",
1894
- sources: { frigate: false, scrypted: true, rtsp: false },
1895
- adapterMethod: "getIntercomSupport"
1896
- },
1897
- {
1898
- id: "deviceStatus",
1899
- label: "Device Status",
1900
- sources: { frigate: false, scrypted: true, rtsp: false },
1901
- adapterMethod: "getStatus"
1902
- },
1903
- {
1904
- id: "timeline",
1905
- label: "Detection Timeline",
1906
- sources: { frigate: true, scrypted: true, rtsp: false },
1907
- adapterMethod: "getCameraDayData"
1908
- },
1909
- {
1910
- id: "clusteredTimeline",
1911
- label: "Clustered Timeline",
1912
- sources: { frigate: true, scrypted: true, rtsp: false },
1913
- requiresBackend: true,
1914
- adapterMethod: "getClusteredDayData"
1915
- },
1916
- {
1917
- id: "detectionClasses",
1918
- label: "Detection Classes",
1919
- sources: { frigate: true, scrypted: true, rtsp: false },
1920
- adapterMethod: "getDetectionClasses"
1921
- },
1922
- {
1923
- id: "videoClips",
1924
- label: "Video Clips",
1925
- sources: { frigate: true, scrypted: true, rtsp: false },
1926
- adapterMethod: "getVideoClips"
1927
- },
1928
- {
1929
- id: "nvrPlayback",
1930
- label: "NVR Playback",
1931
- sources: { frigate: true, scrypted: true, rtsp: false },
1932
- adapterMethod: "getNvrPlaybackSupported"
1933
- },
1934
- {
1935
- id: "nvrScrub",
1936
- label: "NVR Scrub/Seek",
1937
- sources: { frigate: false, scrypted: true, rtsp: false },
1938
- adapterMethod: "seekRecordingStream"
1939
- },
1940
- {
1941
- id: "recordingThumbnail",
1942
- label: "Recording Thumbnails",
1943
- sources: { frigate: true, scrypted: true, rtsp: false },
1944
- adapterMethod: "getRecordingStreamThumbnail"
1945
- },
1946
- {
1947
- id: "nvrSeekToLive",
1948
- label: "Seek to Live",
1949
- sources: { frigate: false, scrypted: true, rtsp: false },
1950
- adapterMethod: "seekNvrToLive"
1951
- }
1705
+ /** Set version for O(1) lookup. */
1706
+ var ELIGIBLE_HA_DOMAINS_SET = new Set(ELIGIBLE_HA_DOMAINS);
1707
+ //#endregion
1708
+ //#region src/features.ts
1709
+ var FEATURE_MATRIX = [
1710
+ {
1711
+ id: "liveStream",
1712
+ label: "Live Stream",
1713
+ sources: {
1714
+ frigate: true,
1715
+ scrypted: true,
1716
+ rtsp: true
1717
+ },
1718
+ adapterMethod: "getLiveStream"
1719
+ },
1720
+ {
1721
+ id: "multiResolution",
1722
+ label: "Multi-Resolution",
1723
+ sources: {
1724
+ frigate: true,
1725
+ scrypted: true,
1726
+ rtsp: false
1727
+ },
1728
+ adapterMethod: "getResolutions"
1729
+ },
1730
+ {
1731
+ id: "motion",
1732
+ label: "Motion Detection",
1733
+ sources: {
1734
+ frigate: false,
1735
+ scrypted: true,
1736
+ rtsp: false
1737
+ },
1738
+ adapterMethod: "getMotion"
1739
+ },
1740
+ {
1741
+ id: "objectDetection",
1742
+ label: "Object Detection",
1743
+ sources: {
1744
+ frigate: false,
1745
+ scrypted: true,
1746
+ rtsp: false
1747
+ },
1748
+ adapterMethod: "getObjectDetections"
1749
+ },
1750
+ {
1751
+ id: "audioVolume",
1752
+ label: "Audio Level",
1753
+ sources: {
1754
+ frigate: false,
1755
+ scrypted: true,
1756
+ rtsp: false
1757
+ },
1758
+ adapterMethod: "getAudioVolume"
1759
+ },
1760
+ {
1761
+ id: "audioVolumes",
1762
+ label: "Audio Volumes (dBFS)",
1763
+ sources: {
1764
+ frigate: false,
1765
+ scrypted: true,
1766
+ rtsp: false
1767
+ },
1768
+ adapterMethod: "getAudioVolumes"
1769
+ },
1770
+ {
1771
+ id: "ptz",
1772
+ label: "PTZ Control",
1773
+ sources: {
1774
+ frigate: false,
1775
+ scrypted: true,
1776
+ rtsp: false
1777
+ },
1778
+ adapterMethod: "getPTZ"
1779
+ },
1780
+ {
1781
+ id: "intercom",
1782
+ label: "Intercom (Mic)",
1783
+ sources: {
1784
+ frigate: false,
1785
+ scrypted: true,
1786
+ rtsp: false
1787
+ },
1788
+ adapterMethod: "getIntercomSupport"
1789
+ },
1790
+ {
1791
+ id: "deviceStatus",
1792
+ label: "Device Status",
1793
+ sources: {
1794
+ frigate: false,
1795
+ scrypted: true,
1796
+ rtsp: false
1797
+ },
1798
+ adapterMethod: "getStatus"
1799
+ },
1800
+ {
1801
+ id: "timeline",
1802
+ label: "Detection Timeline",
1803
+ sources: {
1804
+ frigate: true,
1805
+ scrypted: true,
1806
+ rtsp: false
1807
+ },
1808
+ adapterMethod: "getCameraDayData"
1809
+ },
1810
+ {
1811
+ id: "clusteredTimeline",
1812
+ label: "Clustered Timeline",
1813
+ sources: {
1814
+ frigate: true,
1815
+ scrypted: true,
1816
+ rtsp: false
1817
+ },
1818
+ requiresBackend: true,
1819
+ adapterMethod: "getClusteredDayData"
1820
+ },
1821
+ {
1822
+ id: "detectionClasses",
1823
+ label: "Detection Classes",
1824
+ sources: {
1825
+ frigate: true,
1826
+ scrypted: true,
1827
+ rtsp: false
1828
+ },
1829
+ adapterMethod: "getDetectionClasses"
1830
+ },
1831
+ {
1832
+ id: "videoClips",
1833
+ label: "Video Clips",
1834
+ sources: {
1835
+ frigate: true,
1836
+ scrypted: true,
1837
+ rtsp: false
1838
+ },
1839
+ adapterMethod: "getVideoClips"
1840
+ },
1841
+ {
1842
+ id: "nvrPlayback",
1843
+ label: "NVR Playback",
1844
+ sources: {
1845
+ frigate: true,
1846
+ scrypted: true,
1847
+ rtsp: false
1848
+ },
1849
+ adapterMethod: "getNvrPlaybackSupported"
1850
+ },
1851
+ {
1852
+ id: "nvrScrub",
1853
+ label: "NVR Scrub/Seek",
1854
+ sources: {
1855
+ frigate: false,
1856
+ scrypted: true,
1857
+ rtsp: false
1858
+ },
1859
+ adapterMethod: "seekRecordingStream"
1860
+ },
1861
+ {
1862
+ id: "recordingThumbnail",
1863
+ label: "Recording Thumbnails",
1864
+ sources: {
1865
+ frigate: true,
1866
+ scrypted: true,
1867
+ rtsp: false
1868
+ },
1869
+ adapterMethod: "getRecordingStreamThumbnail"
1870
+ },
1871
+ {
1872
+ id: "nvrSeekToLive",
1873
+ label: "Seek to Live",
1874
+ sources: {
1875
+ frigate: false,
1876
+ scrypted: true,
1877
+ rtsp: false
1878
+ },
1879
+ adapterMethod: "seekNvrToLive"
1880
+ }
1952
1881
  ];
1882
+ /** Check if a feature is available for a given source type and platform. */
1953
1883
  function isFeatureAvailable(featureId, source, platform) {
1954
- const entry = FEATURE_MATRIX.find((f) => f.id === featureId);
1955
- if (!entry) return false;
1956
- if (!entry.sources[source]) return false;
1957
- if (entry.platforms && entry.platforms[platform] === false) return false;
1958
- return true;
1884
+ const entry = FEATURE_MATRIX.find((f) => f.id === featureId);
1885
+ if (!entry) return false;
1886
+ if (!entry.sources[source]) return false;
1887
+ if (entry.platforms && entry.platforms[platform] === false) return false;
1888
+ return true;
1959
1889
  }
1890
+ /** Get all features supported by a source type. */
1960
1891
  function getSourceFeatures(source) {
1961
- return FEATURE_MATRIX.filter((f) => f.sources[source]);
1892
+ return FEATURE_MATRIX.filter((f) => f.sources[source]);
1962
1893
  }
1894
+ /** Get all features that require the backend proxy. */
1963
1895
  function getBackendRequiredFeatures() {
1964
- return FEATURE_MATRIX.filter((f) => f.requiresBackend);
1896
+ return FEATURE_MATRIX.filter((f) => f.requiresBackend);
1965
1897
  }
1966
- export {
1967
- DEFAULT_ENABLED_CLASSES,
1968
- DetectionClass,
1969
- ELIGIBLE_HA_DOMAINS,
1970
- ELIGIBLE_HA_DOMAINS_SET,
1971
- ELIGIBLE_SCRYPTED_DEVICE_TYPES,
1972
- ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET,
1973
- FEATURE_MATRIX,
1974
- HA_DOMAIN_TYPE_MAP,
1975
- RAW_TO_CANONICAL,
1976
- SCRYPTED_TYPE_TO_CANONICAL,
1977
- System,
1978
- TIMELINE_PRESET_ALL,
1979
- TIMELINE_PRESET_CRITICAL,
1980
- TIMELINE_PRESET_IMPORTANT,
1981
- animalClasses,
1982
- audioClasses,
1983
- audioLabelClasses,
1984
- createSystem,
1985
- defaultDetectionClasses,
1986
- detectionClassesDefaultMap,
1987
- doorbellClasses,
1988
- faceClasses,
1989
- getBackendRequiredFeatures,
1990
- getCanonicalDeviceType,
1991
- getClassesForTimelinePreset,
1992
- getParentClass,
1993
- getParentDetectionClass,
1994
- getSourceFeatures,
1995
- isAnimalClassname,
1996
- isAudioClassname,
1997
- isDoorbellClassname,
1998
- isFaceClassname,
1999
- isFeatureAvailable,
2000
- isLabelDetection,
2001
- isMotionClassname,
2002
- isPackageClassname,
2003
- isPersonClassname,
2004
- isPlateClassname,
2005
- isSensorLabelClassname,
2006
- isVehicleClassname,
2007
- licensePlateClasses,
2008
- motionClasses,
2009
- packageClasses,
2010
- personClasses,
2011
- raceFastestEndpoint,
2012
- sensorLabelClasses,
2013
- vehicleClasses
2014
- };
2015
- //# sourceMappingURL=index.js.map
1898
+ //#endregion
1899
+ export { DEFAULT_ENABLED_CLASSES, DetectionClass, ELIGIBLE_HA_DOMAINS, ELIGIBLE_HA_DOMAINS_SET, ELIGIBLE_SCRYPTED_DEVICE_TYPES, ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET, FEATURE_MATRIX, HA_DOMAIN_TYPE_MAP, RAW_TO_CANONICAL, SCRYPTED_TYPE_TO_CANONICAL, System, TIMELINE_PRESET_ALL, TIMELINE_PRESET_CRITICAL, TIMELINE_PRESET_IMPORTANT, animalClasses, audioClasses, audioLabelClasses, createSystem, defaultDetectionClasses, detectionClassesDefaultMap, doorbellClasses, faceClasses, getBackendRequiredFeatures, getCanonicalDeviceType, getClassesForTimelinePreset, getParentClass, getParentDetectionClass, getSourceFeatures, isAnimalClassname, isAudioClassname, isDoorbellClassname, isFaceClassname, isFeatureAvailable, isLabelDetection, isMotionClassname, isPackageClassname, isPersonClassname, isPlateClassname, isSensorLabelClassname, isVehicleClassname, licensePlateClasses, motionClasses, packageClasses, personClasses, raceFastestEndpoint, sensorLabelClasses, vehicleClasses };