@camera.ui/sdk 0.0.2 → 0.0.4

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.
Files changed (76) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +3 -3
  3. package/dist/camera/events.js +2 -0
  4. package/dist/camera/index.js +3 -1
  5. package/dist/external.js +7 -0
  6. package/dist/index.d.ts +7132 -4248
  7. package/dist/index.js +3 -11
  8. package/dist/internal/contract-validators.js +21 -0
  9. package/dist/internal/index.d.ts +915 -0
  10. package/dist/internal/index.js +9 -0
  11. package/dist/internal/sensor-triggers.js +2 -0
  12. package/dist/internal/shared-utils.js +86 -0
  13. package/dist/internal/streaming-internal.js +1 -0
  14. package/dist/manager/index.js +1 -1
  15. package/dist/observable/index.js +419 -0
  16. package/dist/plugin/api.js +21 -0
  17. package/dist/plugin/contract.js +101 -114
  18. package/dist/plugin/helper.js +277 -0
  19. package/dist/plugin/index.js +4 -1
  20. package/dist/plugin/interfaces.js +51 -1
  21. package/dist/plugin/notifier.js +23 -0
  22. package/dist/plugin/oauth.js +1 -0
  23. package/dist/sensor/audio.js +103 -81
  24. package/dist/sensor/base.js +350 -318
  25. package/dist/sensor/battery.js +73 -59
  26. package/dist/sensor/classifier.js +117 -0
  27. package/dist/sensor/clip.js +30 -0
  28. package/dist/sensor/contact.js +37 -18
  29. package/dist/sensor/detection.js +4 -0
  30. package/dist/sensor/doorbell.js +52 -38
  31. package/dist/sensor/face.js +71 -86
  32. package/dist/sensor/garage.js +121 -0
  33. package/dist/sensor/humidity.js +52 -0
  34. package/dist/sensor/index.js +17 -11
  35. package/dist/sensor/leak.js +52 -0
  36. package/dist/sensor/licensePlate.js +70 -79
  37. package/dist/sensor/light.js +82 -38
  38. package/dist/sensor/lock.js +99 -0
  39. package/dist/sensor/motion.js +85 -70
  40. package/dist/sensor/object.js +73 -94
  41. package/dist/sensor/occupancy.js +52 -0
  42. package/dist/sensor/ptz.js +114 -100
  43. package/dist/sensor/securitySystem.js +98 -0
  44. package/dist/sensor/siren.js +75 -43
  45. package/dist/sensor/smoke.js +52 -0
  46. package/dist/sensor/spec.js +1 -0
  47. package/dist/sensor/switch.js +72 -0
  48. package/dist/sensor/temperature.js +52 -0
  49. package/dist/storage/index.js +1 -2
  50. package/dist/types.js +1 -0
  51. package/docs/.vitepress/config.ts +77 -0
  52. package/docs/.vitepress/theme/index.ts +5 -0
  53. package/docs/.vitepress/theme/style.css +117 -0
  54. package/docs/index.md +16 -0
  55. package/docs/logo.png +0 -0
  56. package/docs/public/apple-touch-icon.png +0 -0
  57. package/docs/public/favicon-16.ico +0 -0
  58. package/docs/public/favicon.ico +0 -0
  59. package/docs/public/logo.svg +1 -0
  60. package/examples/README.md +7 -0
  61. package/examples/getting-started.md +535 -0
  62. package/package.json +36 -23
  63. package/scripts/build-example-docs.mjs +62 -0
  64. package/tsconfig.node.json +3 -2
  65. package/typedoc.json +42 -0
  66. package/dist/sensor/guards.js +0 -133
  67. package/dist/sensor/types.js +0 -46
  68. package/dist/service/base.js +0 -96
  69. package/dist/service/index.js +0 -3
  70. /package/dist/camera/{types.js → enums.js} +0 -0
  71. /package/dist/{manager/types.js → camera/frames.js} +0 -0
  72. /package/dist/{plugin/types.js → internal/camera-config-internal.js} +0 -0
  73. /package/dist/{service/services.js → internal/camera-enums.js} +0 -0
  74. /package/dist/{service/types.js → internal/camera-wire.js} +0 -0
  75. /package/dist/{storage/schema.js → internal/manager-rpc.js} +0 -0
  76. /package/dist/{storage/storages.js → internal/sensor-rpc.js} +0 -0
@@ -0,0 +1,9 @@
1
+ export * from './contract-validators.js';
2
+ export * from './manager-rpc.js';
3
+ export * from './shared-utils.js';
4
+ export * from './sensor-rpc.js';
5
+ export * from './camera-wire.js';
6
+ export * from './sensor-triggers.js';
7
+ export * from './streaming-internal.js';
8
+ export * from './camera-enums.js';
9
+ export * from './camera-config-internal.js';
@@ -0,0 +1,2 @@
1
+ /** Sensor trigger types — the subset of trigger types that originate from configurable sensors (excludes motion/audio). */
2
+ export const SENSOR_TRIGGER_TYPES = ['contact', 'doorbell', 'switch', 'light', 'siren', 'security_system'];
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Deep equality check for arbitrary values.
3
+ *
4
+ * Recursively compares primitives, arrays, and plain objects. Object
5
+ * comparison ignores property declaration order (only key/value pairs
6
+ * matter). Set `ignoreOrder` to `true` to compare arrays as multisets,
7
+ * i.e. ignoring element order.
8
+ *
9
+ * Typically used for property-change detection on sensors: a property
10
+ * update is only emitted when the new value is not deeply equal to the
11
+ * previous value, which avoids redundant events for unchanged data.
12
+ *
13
+ * @param first - First value to compare.
14
+ *
15
+ * @param second - Second value to compare.
16
+ *
17
+ * @param ignoreOrder - If `true`, arrays are compared ignoring element order.
18
+ *
19
+ * @returns `true` if the values are deeply equal.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { isEqual } from '@camera.ui/sdk/internal';
24
+ *
25
+ * isEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); // true
26
+ * isEqual([1, 2, 3], [3, 2, 1], true); // true (ignoreOrder)
27
+ * ```
28
+ */
29
+ export function isEqual(first, second, ignoreOrder = false) {
30
+ // Same reference or both primitive and equal
31
+ if (first === second) {
32
+ return true;
33
+ }
34
+ // Handle null/undefined
35
+ if (first === null || first === undefined || second === null || second === undefined) {
36
+ return first === second;
37
+ }
38
+ // Different types
39
+ const firstType = first.constructor?.name;
40
+ const secondType = second.constructor?.name;
41
+ if (firstType !== secondType) {
42
+ return false;
43
+ }
44
+ // Array comparison
45
+ if (Array.isArray(first) && Array.isArray(second)) {
46
+ if (first.length !== second.length) {
47
+ return false;
48
+ }
49
+ if (ignoreOrder) {
50
+ const secondCopy = [...second];
51
+ return first.every((item) => {
52
+ const index = secondCopy.findIndex((secondItem) => isEqual(item, secondItem, ignoreOrder));
53
+ if (index === -1)
54
+ return false;
55
+ secondCopy.splice(index, 1);
56
+ return true;
57
+ });
58
+ }
59
+ else {
60
+ for (let i = 0; i < first.length; i++) {
61
+ if (!isEqual(first[i], second[i], ignoreOrder)) {
62
+ return false;
63
+ }
64
+ }
65
+ return true;
66
+ }
67
+ }
68
+ // Object comparison
69
+ if (firstType === 'Object' && secondType === 'Object') {
70
+ const firstObj = first;
71
+ const secondObj = second;
72
+ const fKeys = Object.keys(firstObj);
73
+ const sKeys = Object.keys(secondObj);
74
+ if (fKeys.length !== sKeys.length) {
75
+ return false;
76
+ }
77
+ for (const key of fKeys) {
78
+ if (!isEqual(firstObj[key], secondObj[key], ignoreOrder)) {
79
+ return false;
80
+ }
81
+ }
82
+ return true;
83
+ }
84
+ // Primitive comparison (fallback)
85
+ return first === second;
86
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1 @@
1
- export * from './types.js';
1
+ export {};
@@ -0,0 +1,419 @@
1
+ /**
2
+ * Lightweight reactive primitives for camera.ui.
3
+ *
4
+ * Provides cold Observables, multicast Subjects (Subject, BehaviorSubject,
5
+ * ReplaySubject) and a small set of composable operators for building
6
+ * property-change notifications and event streams throughout the SDK.
7
+ */
8
+ // ── Disposable ───────────────────────────────────────────────────────
9
+ /**
10
+ * Subscription handle returned by `subscribe()`.
11
+ *
12
+ * Call `dispose()` (or its alias `unsubscribe()`) to detach the listener
13
+ * and run any teardown logic registered by the producer. Disposing twice
14
+ * is a no-op.
15
+ */
16
+ export class Disposable {
17
+ #closed = false;
18
+ #teardown;
19
+ constructor(teardown) {
20
+ this.#teardown = teardown;
21
+ }
22
+ get closed() {
23
+ return this.#closed;
24
+ }
25
+ dispose() {
26
+ if (this.#closed)
27
+ return;
28
+ this.#closed = true;
29
+ this.#teardown();
30
+ }
31
+ unsubscribe() {
32
+ this.dispose();
33
+ }
34
+ }
35
+ // ── Observable ───────────────────────────────────────────────────────
36
+ /**
37
+ * Cold producer of a push-based value stream.
38
+ *
39
+ * The producer function passed to the constructor is executed once per
40
+ * `subscribe()` call, so each subscriber gets its own independent run.
41
+ * `subscribe()` returns a {@link Disposable} that stops the stream and
42
+ * triggers any teardown registered by the producer.
43
+ */
44
+ export class Observable {
45
+ /** @internal */
46
+ _subscribe;
47
+ constructor(subscribe) {
48
+ this._subscribe = subscribe;
49
+ }
50
+ /**
51
+ * Start the producer for this subscriber and route emitted values to `callback`.
52
+ *
53
+ * @param callback - Receiver invoked once per emitted value.
54
+ *
55
+ * @returns Disposable that stops the stream and runs producer teardown.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const sub = sensor.onPropertyChanged.subscribe((change) => {
60
+ * console.log(change.property, change.value);
61
+ * });
62
+ * // later
63
+ * sub.dispose();
64
+ * ```
65
+ */
66
+ subscribe(callback) {
67
+ return this._subscribe(callback);
68
+ }
69
+ pipe(...operators) {
70
+ let result = this;
71
+ for (const op of operators) {
72
+ result = op(result);
73
+ }
74
+ return result;
75
+ }
76
+ }
77
+ // ── Subject ──────────────────────────────────────────────────────────
78
+ /**
79
+ * Multicast value source.
80
+ *
81
+ * Calls to `next(value)` are dispatched synchronously to every active
82
+ * subscriber. `complete()` releases all subscribers and locks the
83
+ * Subject so further `next()` calls become no-ops. `subscribe()`
84
+ * returns a {@link Disposable} for individual cleanup.
85
+ */
86
+ export class Subject {
87
+ #subscribers = new Set();
88
+ #completed = false;
89
+ get closed() {
90
+ return this.#completed;
91
+ }
92
+ next(value) {
93
+ if (this.#completed)
94
+ return;
95
+ for (const cb of this.#subscribers) {
96
+ cb(value);
97
+ }
98
+ }
99
+ complete() {
100
+ if (this.#completed)
101
+ return;
102
+ this.#completed = true;
103
+ this.#subscribers.clear();
104
+ }
105
+ subscribe(callback) {
106
+ if (this.#completed) {
107
+ return new Disposable(() => { });
108
+ }
109
+ this.#subscribers.add(callback);
110
+ return new Disposable(() => {
111
+ this.#subscribers.delete(callback);
112
+ });
113
+ }
114
+ pipe(...operators) {
115
+ let result = this.asObservable();
116
+ for (const op of operators) {
117
+ result = op(result);
118
+ }
119
+ return result;
120
+ }
121
+ /**
122
+ * Returns a read-only Observable that mirrors this Subject without
123
+ * exposing `next()` or `complete()`. Useful for handing out a public
124
+ * stream while keeping write access internal.
125
+ *
126
+ * @returns Read-only Observable view of this Subject.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * const subject = new Subject<number>();
131
+ * export const events$ = subject.asObservable();
132
+ * ```
133
+ */
134
+ asObservable() {
135
+ return new Observable((cb) => this.subscribe(cb));
136
+ }
137
+ }
138
+ // ── BehaviorSubject ──────────────────────────────────────────────────
139
+ /**
140
+ * Subject seeded with an initial value that always remembers the latest
141
+ * emission. New subscribers receive the current value immediately on
142
+ * `subscribe()` and then all subsequent values. The current value is
143
+ * also accessible synchronously via `value` and `getValue()`.
144
+ */
145
+ export class BehaviorSubject extends Subject {
146
+ #value;
147
+ constructor(initialValue) {
148
+ super();
149
+ this.#value = initialValue;
150
+ }
151
+ next(value) {
152
+ this.#value = value;
153
+ super.next(value);
154
+ }
155
+ getValue() {
156
+ return this.#value;
157
+ }
158
+ get value() {
159
+ return this.#value;
160
+ }
161
+ subscribe(callback) {
162
+ const disposable = super.subscribe(callback);
163
+ if (!this.closed) {
164
+ callback(this.#value);
165
+ }
166
+ return disposable;
167
+ }
168
+ }
169
+ // ── ReplaySubject ────────────────────────────────────────────────────
170
+ /**
171
+ * Subject that buffers up to the last `bufferSize` values (configurable,
172
+ * defaults to unbounded). New subscribers immediately receive every
173
+ * buffered value in order before continuing with live emissions.
174
+ */
175
+ export class ReplaySubject extends Subject {
176
+ #buffer = [];
177
+ #bufferSize;
178
+ constructor(bufferSize = Infinity) {
179
+ super();
180
+ this.#bufferSize = bufferSize;
181
+ }
182
+ next(value) {
183
+ if (this.closed)
184
+ return;
185
+ this.#buffer.push(value);
186
+ if (this.#buffer.length > this.#bufferSize) {
187
+ this.#buffer.shift();
188
+ }
189
+ super.next(value);
190
+ }
191
+ subscribe(callback) {
192
+ // Replay buffered values before subscribing to live values
193
+ for (const value of this.#buffer) {
194
+ callback(value);
195
+ }
196
+ return super.subscribe(callback);
197
+ }
198
+ }
199
+ // ── Operators ────────────────────────────────────────────────────────
200
+ /**
201
+ * Emit only the values for which `predicate` returns `true`.
202
+ *
203
+ * @param predicate - Test invoked for each value; truthy passes downstream.
204
+ *
205
+ * @returns Operator that filters the source stream.
206
+ *
207
+ * @example
208
+ * ```ts
209
+ * import { filter } from '@camera.ui/sdk';
210
+ *
211
+ * sensor.onPropertyChanged
212
+ * .pipe(filter((c) => c.property === 'detected'))
213
+ * .subscribe((c) => console.log('motion:', c.value));
214
+ * ```
215
+ */
216
+ export function filter(predicate) {
217
+ return (source) => new Observable((cb) => source.subscribe((value) => {
218
+ if (predicate(value))
219
+ cb(value);
220
+ }));
221
+ }
222
+ /**
223
+ * Apply `transform` to each emitted value and emit the result.
224
+ *
225
+ * @param transform - Projection invoked for each upstream value.
226
+ *
227
+ * @returns Operator that maps every value into a new shape.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * import { map } from '@camera.ui/sdk';
232
+ *
233
+ * sensor.onPropertyChanged
234
+ * .pipe(map((c) => c.value as number))
235
+ * .subscribe((n) => console.log('battery %:', n));
236
+ * ```
237
+ */
238
+ export function map(transform) {
239
+ return (source) => new Observable((cb) => source.subscribe((value) => {
240
+ cb(transform(value));
241
+ }));
242
+ }
243
+ /**
244
+ * Emit a value only when it differs from the previous one. Uses `===` by
245
+ * default, or an optional custom comparator (e.g. for deep equality).
246
+ *
247
+ * @param comparator - Equality function; return `true` to suppress duplicates.
248
+ *
249
+ * @returns Operator that drops consecutive equal values.
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * import { distinctUntilChanged } from '@camera.ui/sdk';
254
+ *
255
+ * sensor.onPropertyChanged
256
+ * .pipe(distinctUntilChanged((a, b) => a.value === b.value))
257
+ * .subscribe((c) => console.log('changed:', c));
258
+ * ```
259
+ */
260
+ export function distinctUntilChanged(comparator) {
261
+ return (source) => new Observable((cb) => {
262
+ let hasValue = false;
263
+ let lastValue;
264
+ const compare = comparator ?? ((a, b) => a === b);
265
+ return source.subscribe((value) => {
266
+ if (!hasValue || !compare(lastValue, value)) {
267
+ hasValue = true;
268
+ lastValue = value;
269
+ cb(value);
270
+ }
271
+ });
272
+ });
273
+ }
274
+ /**
275
+ * Emit `[previous, current]` pairs for every value after the first.
276
+ *
277
+ * @returns Operator that yields adjacent value pairs.
278
+ *
279
+ * @example
280
+ * ```ts
281
+ * import { pairwise } from '@camera.ui/sdk';
282
+ *
283
+ * sensor.onPropertyChanged
284
+ * .pipe(pairwise())
285
+ * .subscribe(([prev, curr]) => console.log(prev.value, '->', curr.value));
286
+ * ```
287
+ */
288
+ export function pairwise() {
289
+ return (source) => new Observable((cb) => {
290
+ let hasValue = false;
291
+ let prev;
292
+ return source.subscribe((value) => {
293
+ if (hasValue) {
294
+ cb([prev, value]);
295
+ }
296
+ hasValue = true;
297
+ prev = value;
298
+ });
299
+ });
300
+ }
301
+ /**
302
+ * Project each source value to a list and flatten the results into the
303
+ * output stream.
304
+ *
305
+ * @param project - Function returning an array of values for each input.
306
+ *
307
+ * @returns Operator that flattens projected arrays into a single stream.
308
+ *
309
+ * @example
310
+ * ```ts
311
+ * import { mergeMap } from '@camera.ui/sdk';
312
+ *
313
+ * motionSensor.onPropertyChanged
314
+ * .pipe(mergeMap((c) => (c.property === 'detections' ? c.value as any[] : [])))
315
+ * .subscribe((det) => console.log('detection:', det));
316
+ * ```
317
+ */
318
+ export function mergeMap(project) {
319
+ return (source) => new Observable((cb) => {
320
+ let index = 0;
321
+ return source.subscribe((value) => {
322
+ const results = project(value, index++);
323
+ for (const r of results) {
324
+ cb(r);
325
+ }
326
+ });
327
+ });
328
+ }
329
+ /**
330
+ * Multicast a cold Observable through a Subject, sharing a single upstream
331
+ * subscription among all subscribers (reference-counted). Supply a custom
332
+ * connector (e.g. `() => new ReplaySubject(1)`) to change buffering.
333
+ *
334
+ * @param config - Optional config with a `connector` factory for the Subject.
335
+ *
336
+ * @param config.connector - Factory returning the multicast Subject to use.
337
+ *
338
+ * @returns Operator that multicasts the source.
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * import { share, ReplaySubject } from '@camera.ui/sdk';
343
+ *
344
+ * const events$ = source$.pipe(share({ connector: () => new ReplaySubject(1) }));
345
+ * events$.subscribe((v) => console.log('a', v));
346
+ * events$.subscribe((v) => console.log('b', v));
347
+ * ```
348
+ */
349
+ export function share(config) {
350
+ return (source) => {
351
+ let subject = null;
352
+ let sourceDisposable = null;
353
+ let refCount = 0;
354
+ return new Observable((cb) => {
355
+ if (!subject) {
356
+ subject = config?.connector ? config.connector() : new Subject();
357
+ sourceDisposable = source.subscribe((value) => {
358
+ subject.next(value);
359
+ });
360
+ }
361
+ refCount++;
362
+ const sub = subject.subscribe(cb);
363
+ return new Disposable(() => {
364
+ sub.dispose();
365
+ refCount--;
366
+ if (refCount === 0) {
367
+ sourceDisposable?.dispose();
368
+ sourceDisposable = null;
369
+ subject = null;
370
+ }
371
+ });
372
+ });
373
+ };
374
+ }
375
+ // ── Utilities ────────────────────────────────────────────────────────
376
+ /**
377
+ * Subscribe to the source and return a Promise that resolves with its first
378
+ * emitted value, then disposes the subscription. Rejects if the source has
379
+ * already completed without emitting.
380
+ *
381
+ * @param observable - Source stream to await.
382
+ *
383
+ * @returns Promise that resolves with the first emitted value.
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * import { firstValueFrom } from '@camera.ui/sdk';
388
+ *
389
+ * const change = await firstValueFrom(sensor.onPropertyChanged);
390
+ * console.log('first change:', change.property);
391
+ * ```
392
+ */
393
+ export function firstValueFrom(observable) {
394
+ return new Promise((resolve, reject) => {
395
+ let resolved = false;
396
+ // The callback may fire synchronously during subscribe() (BehaviorSubject/
397
+ // ReplaySubject), so declare sub up front to avoid the temporal dead zone.
398
+ let sub = undefined;
399
+ sub = observable.subscribe((value) => {
400
+ if (!resolved) {
401
+ resolved = true;
402
+ // sub may not be assigned yet if value is emitted synchronously (BehaviorSubject/ReplaySubject)
403
+ if (sub) {
404
+ sub.dispose();
405
+ }
406
+ resolve(value);
407
+ }
408
+ });
409
+ // Already resolved synchronously — clean up immediately
410
+ if (resolved) {
411
+ sub.dispose();
412
+ return;
413
+ }
414
+ // If the observable completed without emitting (closed subject)
415
+ if ('closed' in observable && observable.closed && !resolved) {
416
+ reject(new Error('Observable completed without emitting a value'));
417
+ }
418
+ });
419
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Lifecycle events emitted on the PluginAPI EventEmitter. Plugins subscribe
3
+ * with `api.on(API_EVENT.X, handler)` to react to host-driven phase changes.
4
+ */
5
+ export var API_EVENT;
6
+ (function (API_EVENT) {
7
+ /**
8
+ * Emitted exactly once after the plugin has been constructed, all assigned
9
+ * cameras have been wired up, and `configureCameras()` has returned. Use
10
+ * this to start background work that must wait until the camera set is
11
+ * stable (timers, model warm-up, outbound connections).
12
+ */
13
+ API_EVENT["FINISH_LAUNCHING"] = "finishLaunching";
14
+ /**
15
+ * Emitted when the host is tearing the plugin down (graceful stop, reload
16
+ * or process exit). Listeners must release resources synchronously enough
17
+ * to finish before the host kills the process — open files, sockets,
18
+ * timers, child processes.
19
+ */
20
+ API_EVENT["SHUTDOWN"] = "shutdown";
21
+ })(API_EVENT || (API_EVENT = {}));