@camstack/sdk 0.1.34 → 0.1.36

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.cjs CHANGED
@@ -31,7 +31,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
- BackendClient: () => BackendClient,
35
34
  DEFAULT_ENABLED_CLASSES: () => DEFAULT_ENABLED_CLASSES,
36
35
  DetectionClass: () => DetectionClass,
37
36
  ELIGIBLE_HA_DOMAINS: () => ELIGIBLE_HA_DOMAINS,
@@ -42,13 +41,14 @@ __export(index_exports, {
42
41
  HA_DOMAIN_TYPE_MAP: () => HA_DOMAIN_TYPE_MAP,
43
42
  RAW_TO_CANONICAL: () => RAW_TO_CANONICAL,
44
43
  SCRYPTED_TYPE_TO_CANONICAL: () => SCRYPTED_TYPE_TO_CANONICAL,
44
+ System: () => System,
45
45
  TIMELINE_PRESET_ALL: () => TIMELINE_PRESET_ALL,
46
46
  TIMELINE_PRESET_CRITICAL: () => TIMELINE_PRESET_CRITICAL,
47
47
  TIMELINE_PRESET_IMPORTANT: () => TIMELINE_PRESET_IMPORTANT,
48
48
  animalClasses: () => animalClasses,
49
49
  audioClasses: () => audioClasses,
50
50
  audioLabelClasses: () => audioLabelClasses,
51
- createBackendClient: () => createBackendClient,
51
+ createSystem: () => createSystem,
52
52
  defaultDetectionClasses: () => defaultDetectionClasses,
53
53
  detectionClassesDefaultMap: () => detectionClassesDefaultMap,
54
54
  doorbellClasses: () => doorbellClasses,
@@ -82,7 +82,7 @@ __export(index_exports, {
82
82
  });
83
83
  module.exports = __toCommonJS(index_exports);
84
84
 
85
- // node_modules/@trpc/client/dist/objectSpread2-BvkFp-_Y.mjs
85
+ // ../../node_modules/@trpc/client/dist/objectSpread2-BvkFp-_Y.mjs
86
86
  var __create2 = Object.create;
87
87
  var __defProp2 = Object.defineProperty;
88
88
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
@@ -184,7 +184,7 @@ var require_objectSpread2 = __commonJS({ "../../node_modules/.pnpm/@oxc-project+
184
184
  module2.exports = _objectSpread2, module2.exports.__esModule = true, module2.exports["default"] = module2.exports;
185
185
  } });
186
186
 
187
- // node_modules/@trpc/server/dist/observable-UMO3vUa_.mjs
187
+ // ../../node_modules/@trpc/server/dist/observable-UMO3vUa_.mjs
188
188
  function observable(subscribe) {
189
189
  const self = {
190
190
  subscribe(observer) {
@@ -270,7 +270,7 @@ function observableToPromise(observable$1) {
270
270
  }
271
271
  __name(observableToPromise, "observableToPromise");
272
272
 
273
- // node_modules/@trpc/server/dist/observable-CUiPknO-.mjs
273
+ // ../../node_modules/@trpc/server/dist/observable-CUiPknO-.mjs
274
274
  function share(_opts) {
275
275
  return (source) => {
276
276
  let refCount = 0;
@@ -348,7 +348,7 @@ function behaviorSubject(initialValue) {
348
348
  }
349
349
  __name(behaviorSubject, "behaviorSubject");
350
350
 
351
- // node_modules/@trpc/client/dist/splitLink-B7Cuf2c_.mjs
351
+ // ../../node_modules/@trpc/client/dist/splitLink-B7Cuf2c_.mjs
352
352
  function createChain(opts) {
353
353
  return observable((observer) => {
354
354
  function execute(index = 0, op = opts.op) {
@@ -370,7 +370,7 @@ function createChain(opts) {
370
370
  }
371
371
  __name(createChain, "createChain");
372
372
 
373
- // node_modules/@trpc/server/dist/codes-DagpWZLc.mjs
373
+ // ../../node_modules/@trpc/server/dist/codes-DagpWZLc.mjs
374
374
  function isObject(value) {
375
375
  return !!value && !Array.isArray(value) && typeof value === "object";
376
376
  }
@@ -414,7 +414,7 @@ var retryableRpcCodes = [
414
414
  TRPC_ERROR_CODES_BY_KEY.INTERNAL_SERVER_ERROR
415
415
  ];
416
416
 
417
- // node_modules/@trpc/server/dist/getErrorShape-vC8mUXJD.mjs
417
+ // ../../node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
418
418
  var __create3 = Object.create;
419
419
  var __defProp3 = Object.defineProperty;
420
420
  var __getOwnPropDesc3 = Object.getOwnPropertyDescriptor;
@@ -453,6 +453,10 @@ function createInnerProxy(callback, path, memo) {
453
453
  },
454
454
  apply(_1, _2, args) {
455
455
  const lastOfPath = path[path.length - 1];
456
+ if (lastOfPath === "valueOf" || lastOfPath === "toString" || lastOfPath === "toJSON") {
457
+ const debugPath = path.slice(0, -1).join(".");
458
+ return `tRPC.proxy(${debugPath})`;
459
+ }
456
460
  let opts = {
457
461
  args,
458
462
  path
@@ -559,7 +563,7 @@ var require_objectSpread22 = __commonJS2({ "../../node_modules/.pnpm/@oxc-projec
559
563
  } });
560
564
  var import_objectSpread2 = __toESM3(require_objectSpread22(), 1);
561
565
 
562
- // node_modules/@trpc/server/dist/tracked-DiE3uR1B.mjs
566
+ // ../../node_modules/@trpc/server/dist/tracked-DWInO6EQ.mjs
563
567
  var import_defineProperty = __toESM3(require_defineProperty2(), 1);
564
568
  var import_objectSpread2$1 = __toESM3(require_objectSpread22(), 1);
565
569
  function transformResultInner(response, transformer) {
@@ -602,7 +606,7 @@ function transformResult(response, transformer) {
602
606
  __name(transformResult, "transformResult");
603
607
  var import_objectSpread22 = __toESM3(require_objectSpread22(), 1);
604
608
 
605
- // node_modules/@trpc/client/dist/TRPCClientError-apv8gw59.mjs
609
+ // ../../node_modules/@trpc/client/dist/TRPCClientError-apv8gw59.mjs
606
610
  var import_defineProperty2 = __toESM2(require_defineProperty(), 1);
607
611
  var import_objectSpread23 = __toESM2(require_objectSpread2(), 1);
608
612
  function isTRPCClientError(cause) {
@@ -652,7 +656,7 @@ var TRPCClientError = class TRPCClientError2 extends Error {
652
656
  }
653
657
  };
654
658
 
655
- // node_modules/@trpc/client/dist/unstable-internals-Bg7n9BBj.mjs
659
+ // ../../node_modules/@trpc/client/dist/unstable-internals-Bg7n9BBj.mjs
656
660
  function getTransformer(transformer) {
657
661
  const _transformer = transformer;
658
662
  if (!_transformer) return {
@@ -673,7 +677,7 @@ function getTransformer(transformer) {
673
677
  }
674
678
  __name(getTransformer, "getTransformer");
675
679
 
676
- // node_modules/@trpc/client/dist/httpUtils-BNq9QC3d.mjs
680
+ // ../../node_modules/@trpc/client/dist/httpUtils-pyf5RF99.mjs
677
681
  var isFunction2 = /* @__PURE__ */ __name((fn) => typeof fn === "function", "isFunction");
678
682
  function getFetch(customFetchImpl) {
679
683
  if (customFetchImpl) return customFetchImpl;
@@ -682,7 +686,7 @@ function getFetch(customFetchImpl) {
682
686
  throw new Error("No fetch implementation found");
683
687
  }
684
688
  __name(getFetch, "getFetch");
685
- var import_objectSpread24 = __toESM2(require_objectSpread2());
689
+ var import_objectSpread24 = __toESM2(require_objectSpread2(), 1);
686
690
  function resolveHTTPLinkOptions(opts) {
687
691
  return {
688
692
  url: opts.url.toString(),
@@ -787,7 +791,7 @@ async function httpRequest(opts) {
787
791
  }
788
792
  __name(httpRequest, "httpRequest");
789
793
 
790
- // node_modules/@trpc/client/dist/httpLink-oiU8eqFi.mjs
794
+ // ../../node_modules/@trpc/client/dist/httpLink-lG_6juPY.mjs
791
795
  function isOctetType(input) {
792
796
  return input instanceof Uint8Array || input instanceof Blob;
793
797
  }
@@ -862,13 +866,13 @@ function httpLink(opts) {
862
866
  }
863
867
  __name(httpLink, "httpLink");
864
868
 
865
- // node_modules/@trpc/client/dist/httpBatchLink-CaWjh1oW.mjs
869
+ // ../../node_modules/@trpc/client/dist/httpBatchLink-LhidKAPw.mjs
866
870
  var import_objectSpread26 = __toESM2(require_objectSpread2(), 1);
867
871
 
868
- // node_modules/@trpc/client/dist/loggerLink-ineCN1PO.mjs
872
+ // ../../node_modules/@trpc/client/dist/loggerLink-ineCN1PO.mjs
869
873
  var import_objectSpread27 = __toESM2(require_objectSpread2(), 1);
870
874
 
871
- // node_modules/@trpc/client/dist/wsLink-DSf4KOdW.mjs
875
+ // ../../node_modules/@trpc/client/dist/wsLink-DSf4KOdW.mjs
872
876
  var jsonEncoder = {
873
877
  encode: /* @__PURE__ */ __name((data) => JSON.stringify(data), "encode"),
874
878
  decode: /* @__PURE__ */ __name((data) => {
@@ -1508,7 +1512,7 @@ function wsLink(opts) {
1508
1512
  }
1509
1513
  __name(wsLink, "wsLink");
1510
1514
 
1511
- // node_modules/@trpc/client/dist/index.mjs
1515
+ // ../../node_modules/@trpc/client/dist/index.mjs
1512
1516
  var import_defineProperty4 = __toESM2(require_defineProperty(), 1);
1513
1517
  var import_objectSpread2$4 = __toESM2(require_objectSpread2(), 1);
1514
1518
  var TRPCUntypedClient = class {
@@ -1841,342 +1845,476 @@ var import_awaitAsyncGenerator = __toESM2(require_awaitAsyncGenerator(), 1);
1841
1845
  var import_wrapAsyncGenerator = __toESM2(require_wrapAsyncGenerator(), 1);
1842
1846
  var import_objectSpread29 = __toESM2(require_objectSpread2(), 1);
1843
1847
 
1844
- // src/backend-client.ts
1848
+ // src/system.ts
1845
1849
  var import_superjson = __toESM(require("superjson"), 1);
1846
- var BackendClient = class {
1850
+ var import_types = require("@camstack/types");
1851
+ var WS_KEEP_ALIVE_INTERVAL_MS = 1e4;
1852
+ var WS_KEEP_ALIVE_PONG_TIMEOUT_MS = 3e3;
1853
+ var WS_RECONNECT_RETRY_DELAY_MS = 2e3;
1854
+ var WS_RECONNECT_MAX_RETRY_DELAY_MS = 3e4;
1855
+ var System = class {
1847
1856
  static {
1848
- __name(this, "BackendClient");
1857
+ __name(this, "System");
1849
1858
  }
1850
- /** Raw tRPC client — for advanced usage / direct path access */
1851
- trpc;
1852
1859
  serverUrl;
1860
+ useWs;
1861
+ baseRetryMs;
1862
+ maxRetryMs;
1863
+ onConnectionChange;
1853
1864
  token;
1854
- wsClient = null;
1865
+ _trpcClient;
1866
+ _wsClient = null;
1867
+ // Mirror — owns binding cache + state mirror + lifecycle listeners.
1868
+ // Lazily initialised on `init()`. Subsequent `reconnect()` disposes
1869
+ // the old mirror and rebuilds against the new tRPC client.
1870
+ mirror = null;
1871
+ mirrorInit = null;
1872
+ // Transport-readiness probe state. `connected` flips true the first
1873
+ // time `auth.me` succeeds against the current tRPC client; resets
1874
+ // on every `reconnect()` / `close()` so the next consumer waits for
1875
+ // a fresh handshake before issuing queries.
1876
+ connected = false;
1877
+ connectedPromise = null;
1878
+ // System-cap namespaces — populated in the constructor from
1879
+ // `createSystemProxy(api)`. Each namespace is a `Pick<InferProvider<typeof cap>, …>`
1880
+ // shape generated by `scripts/generate-system-proxy.ts`.
1881
+ _systemProxy;
1882
+ // Connection-version + listener bookkeeping for the admin UI's
1883
+ // reconnect watchdog.
1884
+ _connectionVersion = 0;
1885
+ connectionListeners = /* @__PURE__ */ new Set();
1855
1886
  constructor(config) {
1856
1887
  this.serverUrl = config.serverUrl.replace(/\/+$/, "");
1857
1888
  this.token = config.token;
1858
1889
  const isBrowser = typeof window !== "undefined";
1859
- const useWs = config.useWebSocket ?? isBrowser;
1860
- const headers = /* @__PURE__ */ __name(() => {
1861
- const h = {};
1862
- if (this.token) {
1863
- h["Authorization"] = `Bearer ${this.token}`;
1864
- }
1865
- return h;
1866
- }, "headers");
1867
- if (useWs) {
1868
- const wsUrl = this.serverUrl.replace(/^http/, "ws") + "/trpc";
1869
- this.wsClient = createWSClient({
1870
- url: wsUrl,
1871
- connectionParams: /* @__PURE__ */ __name(() => ({
1872
- token: this.token
1873
- }), "connectionParams"),
1874
- retryDelayMs: /* @__PURE__ */ __name(() => 2e3, "retryDelayMs")
1875
- });
1876
- this.trpc = createTRPCClient({
1877
- links: [
1878
- wsLink({
1879
- client: this.wsClient,
1880
- transformer: import_superjson.default
1881
- })
1882
- ]
1883
- });
1884
- } else {
1885
- this.trpc = createTRPCClient({
1886
- links: [
1887
- httpLink({
1888
- url: `${this.serverUrl}/trpc`,
1889
- headers,
1890
- transformer: import_superjson.default
1891
- })
1892
- ]
1893
- });
1894
- }
1895
- }
1896
- /** Update the auth token (e.g. after login) */
1897
- setToken(token) {
1898
- this.token = token;
1899
- }
1900
- /** Close the WebSocket connection (if using WS transport) */
1901
- close() {
1902
- this.wsClient?.close();
1890
+ this.useWs = config.useWebSocket ?? isBrowser;
1891
+ this.onConnectionChange = config.onConnectionChange;
1892
+ this.baseRetryMs = config.retryDelayMs ?? WS_RECONNECT_RETRY_DELAY_MS;
1893
+ this.maxRetryMs = config.maxRetryDelayMs ?? WS_RECONNECT_MAX_RETRY_DELAY_MS;
1894
+ this._trpcClient = this.buildTrpcClient();
1895
+ this._systemProxy = (0, import_types.createSystemProxy)(this._trpcClient);
1903
1896
  }
1904
- // ─── Auth ──────────────────────────────────────────────────────────
1905
- async login(username, password) {
1906
- return this.trpc.auth.login.mutate({
1907
- username,
1908
- password
1909
- });
1910
- }
1911
- async getMe() {
1912
- return this.trpc.auth.me.query();
1913
- }
1914
- async logout() {
1915
- return this.trpc.auth.logout.mutate();
1897
+ // ── Connection state ─────────────────────────────────────────────
1898
+ get connectionVersion() {
1899
+ return this._connectionVersion;
1916
1900
  }
1917
- // ─── System ────────────────────────────────────────────────────────
1918
- async getSystemInfo() {
1919
- return this.trpc.system.info.query();
1920
- }
1921
- async getFeatureFlags() {
1922
- return this.trpc.system.featureFlags.query();
1923
- }
1924
- // ─── Providers ─────────────────────────────────────────────────────
1925
- async listProviders() {
1926
- return this.trpc.providers.list.query();
1901
+ /**
1902
+ * Subscribe to connection-state transitions. Called with `('connected'
1903
+ * | 'disconnected' | 'connecting', version)` whenever the SDK opens,
1904
+ * closes, or starts a manual reconnect. Listener errors are swallowed
1905
+ * so a misbehaving consumer cannot break the SDK's event loop.
1906
+ */
1907
+ subscribeConnectionEvents(cb) {
1908
+ this.connectionListeners.add(cb);
1909
+ return () => {
1910
+ this.connectionListeners.delete(cb);
1911
+ };
1927
1912
  }
1928
- async getProvider(providerId) {
1929
- return this.trpc.providers.get.query({
1930
- id: providerId
1931
- });
1913
+ emitConnectionEvent(state) {
1914
+ try {
1915
+ this.onConnectionChange?.(state);
1916
+ } catch {
1917
+ }
1918
+ for (const l of this.connectionListeners) {
1919
+ try {
1920
+ l(state, this._connectionVersion);
1921
+ } catch {
1922
+ }
1923
+ }
1932
1924
  }
1933
- async startProvider(providerId) {
1934
- return this.trpc.providers.start.mutate({
1935
- id: providerId
1936
- });
1925
+ // ── Lifecycle ────────────────────────────────────────────────────
1926
+ /**
1927
+ * Wait until the underlying tRPC transport is connected AND the
1928
+ * server has responded to a cheap auth round-trip (`auth.me`). This
1929
+ * is the canonical "ready to issue queries" gate.
1930
+ *
1931
+ * Why a probe, not just `ws.readyState === OPEN`?
1932
+ * The WS handshake completes asynchronously: tRPC's `wsLink`
1933
+ * queues outgoing messages and only flushes them after `open()`
1934
+ * resolves (post `connectionParams` send). On the server, the
1935
+ * tRPC context is created lazily once the connectionParams
1936
+ * message is received. A query fired between WS-open and
1937
+ * connection-params-processed is technically queued by tRPC, but
1938
+ * the auth context for that query is only resolved once the
1939
+ * handshake message is decoded server-side. A probe round-trip is
1940
+ * the safest way to confirm both sides have agreed on the auth
1941
+ * identity before the React tree starts firing parallel queries
1942
+ * (which can otherwise land before any addon-side service
1943
+ * discovery has settled, returning empty results that get cached).
1944
+ *
1945
+ * Idempotent — concurrent callers await the same in-flight Promise.
1946
+ * Bounded by `timeoutMs` (default 15s) — beyond which a
1947
+ * `Error('System.awaitConnected: probe timed out after Xms')` is
1948
+ * thrown so the host can render a clear error state instead of
1949
+ * hanging on a bricked socket.
1950
+ */
1951
+ async awaitConnected(timeoutMs) {
1952
+ if (this.connected) return;
1953
+ if (this.connectedPromise) return this.connectedPromise;
1954
+ const effectiveTimeoutMs = timeoutMs ?? 15e3;
1955
+ this.connectedPromise = (async () => {
1956
+ const probe = this._trpcClient.auth.me.query();
1957
+ if (!Number.isFinite(effectiveTimeoutMs)) {
1958
+ await probe;
1959
+ } else {
1960
+ await new Promise((resolve, reject) => {
1961
+ const timer = setTimeout(() => reject(new Error(`System.awaitConnected: probe timed out after ${effectiveTimeoutMs}ms`)), effectiveTimeoutMs);
1962
+ probe.then(() => {
1963
+ clearTimeout(timer);
1964
+ resolve();
1965
+ }, (err) => {
1966
+ clearTimeout(timer);
1967
+ reject(err instanceof Error ? err : new Error(String(err)));
1968
+ });
1969
+ });
1970
+ }
1971
+ this.connected = true;
1972
+ })();
1973
+ try {
1974
+ await this.connectedPromise;
1975
+ } finally {
1976
+ this.connectedPromise = null;
1977
+ }
1937
1978
  }
1938
- async stopProvider(providerId) {
1939
- return this.trpc.providers.stop.mutate({
1940
- id: providerId
1979
+ /**
1980
+ * Warm-boot the device mirror. Awaits the transport probe first
1981
+ * (`awaitConnected`) so the three mirror round-trips
1982
+ * (`getAllBindings` + `getAllSnapshots` + `listAll`) cannot race
1983
+ * against the WS auth handshake. Subsequent `getDevice(id)` calls
1984
+ * are sync; live `device.*` event subscriptions keep the caches
1985
+ * fresh.
1986
+ *
1987
+ * Idempotent — concurrent callers await the same in-flight Promise.
1988
+ */
1989
+ async init(timeoutMs) {
1990
+ if (this.mirror?.isReady()) return;
1991
+ if (this.mirrorInit) return this.mirrorInit;
1992
+ const m = new import_types.SystemMirror(this._trpcClient);
1993
+ this.mirror = m;
1994
+ this.mirrorInit = (async () => {
1995
+ await this.awaitConnected(timeoutMs);
1996
+ await m.init(timeoutMs);
1997
+ })().finally(() => {
1998
+ this.mirrorInit = null;
1941
1999
  });
2000
+ return this.mirrorInit;
1942
2001
  }
1943
- async listProviderTypes() {
1944
- return this.trpc.providerConfig.getAvailableTypes.query();
2002
+ /** Promise that resolves once `init()` has completed. */
2003
+ async awaitReady() {
2004
+ if (this.mirror?.isReady()) return;
2005
+ if (this.mirrorInit) return this.mirrorInit;
2006
+ return this.init();
1945
2007
  }
1946
- // ─── Devices ───────────────────────────────────────────────────────
1947
- async listDevices() {
1948
- return this.trpc.devices.list.query();
2008
+ /** True after `init()` resolves. */
2009
+ isReady() {
2010
+ return this.mirror?.isReady() ?? false;
1949
2011
  }
1950
- async getDevice(deviceId) {
1951
- return this.trpc.devices.get.query({
1952
- id: deviceId
1953
- });
2012
+ /** True after the transport probe has succeeded at least once. */
2013
+ isConnected() {
2014
+ return this.connected;
1954
2015
  }
1955
- async discoverDevices(providerId) {
1956
- return this.trpc.devices.discoverDevices.query({
1957
- providerId
1958
- });
2016
+ /**
2017
+ * Force a fresh WebSocket handshake. Tears down the wsClient + tRPC
2018
+ * client + mirror (the mirror captures the tRPC reference at
2019
+ * construction time and would otherwise dispatch through a closed
2020
+ * client) and rebuilds them. No-op for HTTP transport.
2021
+ */
2022
+ reconnect() {
2023
+ if (!this.useWs) return;
2024
+ this.emitConnectionEvent("connecting");
2025
+ this._wsClient?.close();
2026
+ this.disposeMirror();
2027
+ this.connected = false;
2028
+ this.connectedPromise = null;
2029
+ this._trpcClient = this.buildTrpcClient();
2030
+ this._systemProxy = (0, import_types.createSystemProxy)(this._trpcClient);
2031
+ }
2032
+ /** Tear down WS connection + mirror. The instance is unusable afterwards. */
2033
+ close() {
2034
+ this.disposeMirror();
2035
+ this.connected = false;
2036
+ this.connectedPromise = null;
2037
+ this._wsClient?.close();
1959
2038
  }
1960
- async adoptDevice(providerId, externalId) {
1961
- return this.trpc.devices.adoptDevice.mutate({
1962
- providerId,
1963
- externalId
1964
- });
2039
+ disposeMirror() {
2040
+ this.mirror?.dispose();
2041
+ this.mirror = null;
2042
+ this.mirrorInit = null;
1965
2043
  }
1966
- async createDevice(providerId, config) {
1967
- return this.trpc.devices.createDevice.mutate({
1968
- providerId,
1969
- config
2044
+ // ── Auth ──────────────────────────────────────────────────────────
2045
+ async login(username, password) {
2046
+ const result = await this._trpcClient.auth.login.mutate({
2047
+ username,
2048
+ password
1970
2049
  });
2050
+ if (typeof result === "object" && result !== null && "token" in result) {
2051
+ const token = result.token;
2052
+ if (typeof token === "string") this.setToken(token);
2053
+ }
2054
+ return result;
1971
2055
  }
1972
- /** Returns the URL path for a static asset served by an addon. */
1973
- getAddonAssetUrl(addonId, assetPath) {
1974
- return `/api/addon-assets/${addonId}/${assetPath}`;
1975
- }
1976
- // ─── Addons ────────────────────────────────────────────────────────
1977
- async listAddons() {
1978
- return this.trpc.addons.list.query();
1979
- }
1980
- async getAddonConfigSchema(addonId) {
1981
- return this.trpc.addons.getConfigSchema.query({
1982
- addonId
1983
- });
2056
+ async logout() {
2057
+ await this._trpcClient.auth.logout.mutate();
1984
2058
  }
1985
- async getAddonConfig(addonId) {
1986
- return this.trpc.addons.getConfig.query({
1987
- addonId
1988
- });
2059
+ async getMe() {
2060
+ const me = await this._trpcClient.auth.me.query();
2061
+ return me;
1989
2062
  }
1990
- async updateAddonConfig(addonId, config) {
1991
- return this.trpc.addons.updateConfig.mutate({
1992
- addonId,
1993
- config
1994
- });
2063
+ /** Update the auth token (e.g. after login or token refresh). */
2064
+ setToken(token) {
2065
+ this.token = token;
1995
2066
  }
1996
- async getAddonLogs(addonId, options) {
1997
- return this.trpc.addons.getLogs.query({
1998
- addonId,
1999
- ...options
2000
- });
2067
+ // ── Devices ──────────────────────────────────────────────────────
2068
+ /**
2069
+ * Synchronous snapshot of every device matching the optional filters.
2070
+ * Backed by the `SystemMirror` warm-boot cache — call `init()` first
2071
+ * (or `awaitReady()`) before invoking. Returns an empty array if the
2072
+ * mirror has not yet been booted.
2073
+ *
2074
+ * Each returned proxy has `binding` populated from the mirror's
2075
+ * binding cache (Phase 5 dedup), so consumers no longer need to
2076
+ * make a separate `deviceManager.getBindings` round-trip.
2077
+ */
2078
+ listDevices(filters) {
2079
+ if (!this.mirror) return [];
2080
+ const proxies = filters ? this.mirror.query(filters) : this.mirror.getAllDevices();
2081
+ return proxies.map((p) => this.attachBinding(p));
2001
2082
  }
2002
- // ─── Known Faces ───────────────────────────────────────────────────
2003
- async listKnownFaces() {
2004
- return this.trpc.knownFaces.list.query();
2083
+ /**
2084
+ * Sync lookup by numeric id. `null` if the mirror has not been booted
2085
+ * or the device is unknown.
2086
+ */
2087
+ getDevice(deviceId) {
2088
+ const proxy = this.mirror?.getDeviceById(deviceId) ?? null;
2089
+ return proxy ? this.attachBinding(proxy) : null;
2005
2090
  }
2006
- async registerFace(label, cropBase64, group) {
2007
- return this.trpc.knownFaces.register.mutate({
2008
- label,
2009
- cropBase64,
2010
- group
2011
- });
2091
+ /** Sync lookup by display name (exact match). */
2092
+ getDeviceByName(name) {
2093
+ const proxy = this.mirror?.getDeviceByName(name) ?? null;
2094
+ return proxy ? this.attachBinding(proxy) : null;
2012
2095
  }
2013
- // ─── Pipeline ──────────────────────────────────────────────────────
2014
- async listPipelines() {
2015
- return this.trpc.bridgePipeline.listAddons.query();
2096
+ /** Sync lookup by stableId. */
2097
+ getDeviceByStableId(stableId) {
2098
+ const proxy = this.mirror?.getDeviceByStableId(stableId) ?? null;
2099
+ return proxy ? this.attachBinding(proxy) : null;
2016
2100
  }
2017
- async getPipelineStatus(deviceId) {
2018
- return this.trpc.bridgePipeline.getPipeline.query({
2019
- deviceId
2020
- });
2101
+ /**
2102
+ * Resolve when a device with `deviceId` becomes available. Resolves
2103
+ * immediately if already known; rejects with a timeout error
2104
+ * otherwise (default 30s).
2105
+ */
2106
+ async waitForDevice(deviceId, timeoutMs) {
2107
+ if (!this.mirror) await this.init();
2108
+ const proxy = await this.mirror.waitForDevice(deviceId, timeoutMs ?? 3e4);
2109
+ return this.attachBinding(proxy);
2110
+ }
2111
+ /** Subscribe to `device.registered` events. */
2112
+ onDeviceAdded(cb) {
2113
+ if (!this.mirror) {
2114
+ throw new Error("System.onDeviceAdded: call init() before subscribing");
2115
+ }
2116
+ return this.mirror.onDeviceAdded(cb);
2021
2117
  }
2022
- // ─── Agents ────────────────────────────────────────────────────────
2023
- async listAgents() {
2024
- return this.trpc.agents.listAgents.query();
2118
+ /** Subscribe to `device.unregistered` events. */
2119
+ onDeviceRemoved(cb) {
2120
+ if (!this.mirror) {
2121
+ throw new Error("System.onDeviceRemoved: call init() before subscribing");
2122
+ }
2123
+ return this.mirror.onDeviceRemoved(cb);
2025
2124
  }
2026
- async dispatchTask(agentId, task) {
2027
- return this.trpc.agents.dispatchTask.mutate({
2028
- capability: agentId,
2029
- input: task
2030
- });
2125
+ /**
2126
+ * Patch the proxy's `binding` field from the mirror's cache. The
2127
+ * generated `createDeviceProxy()` already sets `binding` to the
2128
+ * binding it was constructed with — this is a defensive overwrite
2129
+ * that uses the latest cached entry in case a `binding-changed`
2130
+ * event landed between proxy creation and access.
2131
+ */
2132
+ attachBinding(proxy) {
2133
+ const binding = this.lookupBinding(proxy.deviceId);
2134
+ if (binding === proxy.binding) return proxy;
2135
+ const writable = proxy;
2136
+ writable.binding = binding;
2137
+ return proxy;
2031
2138
  }
2032
- async getProcessTree() {
2033
- return this.trpc.agent.processTree.query();
2139
+ /** Fetch the latest cached binding from the mirror, or `null`. */
2140
+ lookupBinding(deviceId) {
2141
+ if (!this.mirror) return null;
2142
+ const proxy = this.mirror.getDeviceById(deviceId);
2143
+ return proxy?.binding ?? null;
2034
2144
  }
2035
- // ─── Bridge Pipeline ───────────────────────────────────────────────
2036
- async bridgeListAddons() {
2037
- return this.trpc.bridgePipeline.listAddons.query();
2145
+ // ── System caps ──────────────────────────────────────────────────
2146
+ //
2147
+ // One getter per `scope: 'system'` capability. The actual surface
2148
+ // comes from `createSystemProxy(api)` (codegen). Adding a new
2149
+ // system cap regenerates the proxy and the new namespace surfaces
2150
+ // automatically via `system.<newCap>.<method>(input)`.
2151
+ get addonPages() {
2152
+ return this._systemProxy.addonPages;
2038
2153
  }
2039
- async bridgeGetPipeline(deviceId) {
2040
- return this.trpc.bridgePipeline.getPipeline.query({
2041
- deviceId
2042
- });
2154
+ get addonSettings() {
2155
+ return this._systemProxy.addonSettings;
2043
2156
  }
2044
- async bridgeSetPipeline(deviceId, config) {
2045
- return this.trpc.bridgePipeline.setPipeline.mutate({
2046
- deviceId,
2047
- config
2048
- });
2157
+ get alerts() {
2158
+ return this._systemProxy.alerts;
2049
2159
  }
2050
- async bridgeValidatePipeline(config) {
2051
- return this.trpc.bridgePipeline.validatePipeline.query(config);
2160
+ get audioAnalyzer() {
2161
+ return this._systemProxy.audioAnalyzer;
2052
2162
  }
2053
- async bridgeGetAddonConfig(addonId) {
2054
- return this.trpc.bridgePipeline.getAddonConfig.query({
2055
- addonId
2056
- });
2163
+ get audioCodec() {
2164
+ return this._systemProxy.audioCodec;
2057
2165
  }
2058
- async bridgeSetAddonConfig(addonId, config) {
2059
- return this.trpc.bridgePipeline.setAddonConfig.mutate({
2060
- addonId,
2061
- config
2062
- });
2166
+ get backup() {
2167
+ return this._systemProxy.backup;
2063
2168
  }
2064
- // ─── Addon Packages ────────────────────────────────────────────────
2065
- async bridgeListPackages() {
2066
- return this.trpc.addons.listPackages.query();
2169
+ get decoder() {
2170
+ return this._systemProxy.decoder;
2067
2171
  }
2068
- async bridgeListAddonsPackages() {
2069
- return this.trpc.addons.list.query();
2172
+ get deviceManager() {
2173
+ return this._systemProxy.deviceManager;
2070
2174
  }
2071
- async bridgeInstallPackage(packageName, version) {
2072
- return this.trpc.addons.installPackage.mutate({
2073
- packageName,
2074
- version
2075
- });
2175
+ get deviceProvider() {
2176
+ return this._systemProxy.deviceProvider;
2076
2177
  }
2077
- async bridgeUninstallPackage(packageName) {
2078
- return this.trpc.addons.uninstallPackage.mutate({
2079
- packageName
2080
- });
2178
+ get deviceState() {
2179
+ return this._systemProxy.deviceState;
2081
2180
  }
2082
- // ─── Recording ─────────────────────────────────────────────────────
2083
- async getRecordingConfig(deviceId) {
2084
- return this.trpc.recording.getConfig.query({
2085
- deviceId
2086
- });
2181
+ get metricsProvider() {
2182
+ return this._systemProxy.metricsProvider;
2087
2183
  }
2088
- async getRecordingPolicy(deviceId) {
2089
- return this.trpc.recording.getPolicy.query({
2090
- deviceId
2091
- });
2184
+ get notificationOutput() {
2185
+ return this._systemProxy.notificationOutput;
2092
2186
  }
2093
- async getRecordingPolicyStatus(deviceId) {
2094
- return this.trpc.recording.getPolicyStatus.query({
2095
- deviceId
2096
- });
2187
+ get pipelineExecutor() {
2188
+ return this._systemProxy.pipelineExecutor;
2097
2189
  }
2098
- async getRecordingSegments(deviceId, streamId, startTime, endTime) {
2099
- return this.trpc.recording.getSegments.query({
2100
- deviceId,
2101
- streamId,
2102
- startTime,
2103
- endTime
2104
- });
2190
+ get pipelineOrchestrator() {
2191
+ return this._systemProxy.pipelineOrchestrator;
2105
2192
  }
2106
- // ─── Events ────────────────────────────────────────────────────────
2107
- async getEvents(deviceId, options) {
2108
- return this.trpc.events.query.query({
2109
- deviceId,
2110
- ...options
2111
- });
2193
+ get pipelineRunner() {
2194
+ return this._systemProxy.pipelineRunner;
2112
2195
  }
2113
- // ─── Logs ──────────────────────────────────────────────────────────
2114
- async getLogs(options) {
2115
- return this.trpc.logs.query.query(options ?? {});
2196
+ get platformProbe() {
2197
+ return this._systemProxy.platformProbe;
2116
2198
  }
2117
- // ─── REPL ──────────────────────────────────────────────────────────
2118
- async replEval(code, scope) {
2119
- return this.trpc.repl.execute.mutate({
2120
- code,
2121
- scope: scope ?? {
2122
- type: "system"
2123
- }
2124
- });
2199
+ get recordingEngine() {
2200
+ return this._systemProxy.recordingEngine;
2125
2201
  }
2126
- // ─── Users ─────────────────────────────────────────────────────────
2127
- async listUsers() {
2128
- return this.trpc.users.list.query();
2202
+ get settingsStore() {
2203
+ return this._systemProxy.settingsStore;
2129
2204
  }
2130
- async createUser(username, password, role) {
2131
- return this.trpc.users.create.mutate({
2132
- username,
2133
- password,
2134
- role
2135
- });
2205
+ get storage() {
2206
+ return this._systemProxy.storage;
2136
2207
  }
2137
- // ─── Session Tracker ───────────────────────────────────────────────
2138
- async getTrackingSessions(deviceId) {
2139
- return this.trpc.session.getActiveTracks.query({
2140
- deviceId
2141
- });
2208
+ get streamBroker() {
2209
+ return this._systemProxy.streamBroker;
2142
2210
  }
2143
- // ─── Processes ─────────────────────────────────────────────────────
2144
- async listProcesses() {
2145
- return this.trpc.processes.listProcesses.query();
2211
+ get turnProvider() {
2212
+ return this._systemProxy.turnProvider;
2146
2213
  }
2147
- async enableProvider(providerId) {
2148
- return this.trpc.processes.enableProvider.mutate({
2149
- id: providerId
2150
- });
2214
+ get userManagement() {
2215
+ return this._systemProxy.userManagement;
2151
2216
  }
2152
- async disableProvider(providerId) {
2153
- return this.trpc.processes.disableProvider.mutate({
2154
- id: providerId
2217
+ // ── Live events ──────────────────────────────────────────────────
2218
+ /**
2219
+ * Subscribe to a single event category. Returns an unsubscribe
2220
+ * handle. Errors thrown by the listener are swallowed so a single
2221
+ * misbehaving consumer cannot tear down the WS subscription.
2222
+ *
2223
+ * Categories should be values from the `EventCategory` enum
2224
+ * (`@camstack/types`) — passing a raw string works for forward-compat
2225
+ * but loses type safety. The SDK forwards the value verbatim to the
2226
+ * server's `live.onEvent` subscription.
2227
+ */
2228
+ subscribeEvent(category, cb) {
2229
+ const sub = this._trpcClient.live.onEvent.subscribe({
2230
+ category
2231
+ }, {
2232
+ onData: /* @__PURE__ */ __name((event) => {
2233
+ try {
2234
+ cb(event);
2235
+ } catch {
2236
+ }
2237
+ }, "onData")
2155
2238
  });
2239
+ return () => {
2240
+ try {
2241
+ sub.unsubscribe();
2242
+ } catch {
2243
+ }
2244
+ };
2156
2245
  }
2157
- // ─── Settings ──────────────────────────────────────────────────────
2158
- /** Fetch the UI schema for a single section, or all sections if omitted. */
2159
- async getSettingsSchema(section) {
2160
- return this.trpc.settings.getSchema.query({
2161
- section
2162
- });
2246
+ // ── Escape hatches ───────────────────────────────────────────────
2247
+ //
2248
+ // Kept public so the ui-library `<TrpcProvider>` can seed React
2249
+ // Query with the same WebSocket connection during Phase 2. Phase 4
2250
+ // is expected to revisit whether either field can be hidden.
2251
+ /** Direct tRPC client. Read once per call; rebuilt on `reconnect()`. */
2252
+ get trpcClient() {
2253
+ return this._trpcClient;
2163
2254
  }
2164
- async getSettings(section) {
2165
- return this.trpc.settings.get.query({
2166
- section
2167
- });
2255
+ /**
2256
+ * Underlying WSClient (or `null` for HTTP transport). Used by
2257
+ * advanced consumers that need direct access to the WebSocket
2258
+ * (e.g. for keep-alive metrics). Rebuilt on `reconnect()`.
2259
+ */
2260
+ get wsClient() {
2261
+ return this._wsClient;
2168
2262
  }
2169
- async updateSettings(section, data) {
2170
- return this.trpc.settings.update.mutate({
2171
- section,
2172
- data
2263
+ // ── Internals ────────────────────────────────────────────────────
2264
+ buildTrpcClient() {
2265
+ const headers = /* @__PURE__ */ __name(() => {
2266
+ const h = {};
2267
+ if (this.token) h["Authorization"] = `Bearer ${this.token}`;
2268
+ return h;
2269
+ }, "headers");
2270
+ if (this.useWs) {
2271
+ const wsUrl = this.serverUrl.replace(/^http/, "ws") + "/trpc";
2272
+ const baseRetryMs = this.baseRetryMs;
2273
+ const maxRetryMs = this.maxRetryMs;
2274
+ this._wsClient = createWSClient({
2275
+ url: wsUrl,
2276
+ connectionParams: /* @__PURE__ */ __name(() => ({
2277
+ token: this.token
2278
+ }), "connectionParams"),
2279
+ retryDelayMs: /* @__PURE__ */ __name((attemptIndex) => Math.min(baseRetryMs * Math.pow(2, attemptIndex), maxRetryMs), "retryDelayMs"),
2280
+ keepAlive: {
2281
+ enabled: true,
2282
+ intervalMs: WS_KEEP_ALIVE_INTERVAL_MS,
2283
+ pongTimeoutMs: WS_KEEP_ALIVE_PONG_TIMEOUT_MS
2284
+ },
2285
+ onOpen: /* @__PURE__ */ __name(() => {
2286
+ this._connectionVersion += 1;
2287
+ this.emitConnectionEvent("connected");
2288
+ }, "onOpen"),
2289
+ onClose: /* @__PURE__ */ __name(() => {
2290
+ this.emitConnectionEvent("disconnected");
2291
+ }, "onClose")
2292
+ });
2293
+ return createTRPCClient({
2294
+ links: [
2295
+ wsLink({
2296
+ client: this._wsClient,
2297
+ transformer: import_superjson.default
2298
+ })
2299
+ ]
2300
+ });
2301
+ }
2302
+ this._wsClient = null;
2303
+ return createTRPCClient({
2304
+ links: [
2305
+ httpLink({
2306
+ url: `${this.serverUrl}/trpc`,
2307
+ headers,
2308
+ transformer: import_superjson.default
2309
+ })
2310
+ ]
2173
2311
  });
2174
2312
  }
2175
2313
  };
2176
- function createBackendClient(config) {
2177
- return new BackendClient(config);
2314
+ function createSystem(config) {
2315
+ return new System(config);
2178
2316
  }
2179
- __name(createBackendClient, "createBackendClient");
2317
+ __name(createSystem, "createSystem");
2180
2318
 
2181
2319
  // src/detection.ts
2182
2320
  var DetectionClass = /* @__PURE__ */ (function(DetectionClass2) {
@@ -2851,11 +2989,6 @@ function selectOptimalStream(streams, constraints, defaultTransport) {
2851
2989
  });
2852
2990
  scored.sort((a, b) => b.score - a.score);
2853
2991
  const best = scored[0];
2854
- const tierNames = {
2855
- high: "main",
2856
- medium: "sub",
2857
- low: "ext"
2858
- };
2859
2992
  return {
2860
2993
  streamName: best.streamName,
2861
2994
  profile: best.profile,
@@ -2875,7 +3008,6 @@ function getNextEvalInterval(constraints, wasSwitch) {
2875
3008
  __name(getNextEvalInterval, "getNextEvalInterval");
2876
3009
  // Annotate the CommonJS export names for ESM import in node:
2877
3010
  0 && (module.exports = {
2878
- BackendClient,
2879
3011
  DEFAULT_ENABLED_CLASSES,
2880
3012
  DetectionClass,
2881
3013
  ELIGIBLE_HA_DOMAINS,
@@ -2886,13 +3018,14 @@ __name(getNextEvalInterval, "getNextEvalInterval");
2886
3018
  HA_DOMAIN_TYPE_MAP,
2887
3019
  RAW_TO_CANONICAL,
2888
3020
  SCRYPTED_TYPE_TO_CANONICAL,
3021
+ System,
2889
3022
  TIMELINE_PRESET_ALL,
2890
3023
  TIMELINE_PRESET_CRITICAL,
2891
3024
  TIMELINE_PRESET_IMPORTANT,
2892
3025
  animalClasses,
2893
3026
  audioClasses,
2894
3027
  audioLabelClasses,
2895
- createBackendClient,
3028
+ createSystem,
2896
3029
  defaultDetectionClasses,
2897
3030
  detectionClassesDefaultMap,
2898
3031
  doorbellClasses,
@@ -2926,7 +3059,7 @@ __name(getNextEvalInterval, "getNextEvalInterval");
2926
3059
  });
2927
3060
  /*! Bundled license information:
2928
3061
 
2929
- @trpc/client/dist/httpLink-oiU8eqFi.mjs:
3062
+ @trpc/client/dist/httpLink-lG_6juPY.mjs:
2930
3063
  (* istanbul ignore if -- @preserve *)
2931
3064
  */
2932
3065
  //# sourceMappingURL=index.cjs.map