@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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
- // node_modules/@trpc/client/dist/objectSpread2-BvkFp-_Y.mjs
4
+ // ../../node_modules/@trpc/client/dist/objectSpread2-BvkFp-_Y.mjs
5
5
  var __create = Object.create;
6
6
  var __defProp2 = Object.defineProperty;
7
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -103,7 +103,7 @@ var require_objectSpread2 = __commonJS({ "../../node_modules/.pnpm/@oxc-project+
103
103
  module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
104
104
  } });
105
105
 
106
- // node_modules/@trpc/server/dist/observable-UMO3vUa_.mjs
106
+ // ../../node_modules/@trpc/server/dist/observable-UMO3vUa_.mjs
107
107
  function observable(subscribe) {
108
108
  const self = {
109
109
  subscribe(observer) {
@@ -189,7 +189,7 @@ function observableToPromise(observable$1) {
189
189
  }
190
190
  __name(observableToPromise, "observableToPromise");
191
191
 
192
- // node_modules/@trpc/server/dist/observable-CUiPknO-.mjs
192
+ // ../../node_modules/@trpc/server/dist/observable-CUiPknO-.mjs
193
193
  function share(_opts) {
194
194
  return (source) => {
195
195
  let refCount = 0;
@@ -267,7 +267,7 @@ function behaviorSubject(initialValue) {
267
267
  }
268
268
  __name(behaviorSubject, "behaviorSubject");
269
269
 
270
- // node_modules/@trpc/client/dist/splitLink-B7Cuf2c_.mjs
270
+ // ../../node_modules/@trpc/client/dist/splitLink-B7Cuf2c_.mjs
271
271
  function createChain(opts) {
272
272
  return observable((observer) => {
273
273
  function execute(index = 0, op = opts.op) {
@@ -289,7 +289,7 @@ function createChain(opts) {
289
289
  }
290
290
  __name(createChain, "createChain");
291
291
 
292
- // node_modules/@trpc/server/dist/codes-DagpWZLc.mjs
292
+ // ../../node_modules/@trpc/server/dist/codes-DagpWZLc.mjs
293
293
  function isObject(value) {
294
294
  return !!value && !Array.isArray(value) && typeof value === "object";
295
295
  }
@@ -333,7 +333,7 @@ var retryableRpcCodes = [
333
333
  TRPC_ERROR_CODES_BY_KEY.INTERNAL_SERVER_ERROR
334
334
  ];
335
335
 
336
- // node_modules/@trpc/server/dist/getErrorShape-vC8mUXJD.mjs
336
+ // ../../node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
337
337
  var __create2 = Object.create;
338
338
  var __defProp3 = Object.defineProperty;
339
339
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
@@ -372,6 +372,10 @@ function createInnerProxy(callback, path, memo) {
372
372
  },
373
373
  apply(_1, _2, args) {
374
374
  const lastOfPath = path[path.length - 1];
375
+ if (lastOfPath === "valueOf" || lastOfPath === "toString" || lastOfPath === "toJSON") {
376
+ const debugPath = path.slice(0, -1).join(".");
377
+ return `tRPC.proxy(${debugPath})`;
378
+ }
375
379
  let opts = {
376
380
  args,
377
381
  path
@@ -478,7 +482,7 @@ var require_objectSpread22 = __commonJS2({ "../../node_modules/.pnpm/@oxc-projec
478
482
  } });
479
483
  var import_objectSpread2 = __toESM2(require_objectSpread22(), 1);
480
484
 
481
- // node_modules/@trpc/server/dist/tracked-DiE3uR1B.mjs
485
+ // ../../node_modules/@trpc/server/dist/tracked-DWInO6EQ.mjs
482
486
  var import_defineProperty = __toESM2(require_defineProperty2(), 1);
483
487
  var import_objectSpread2$1 = __toESM2(require_objectSpread22(), 1);
484
488
  function transformResultInner(response, transformer) {
@@ -521,7 +525,7 @@ function transformResult(response, transformer) {
521
525
  __name(transformResult, "transformResult");
522
526
  var import_objectSpread22 = __toESM2(require_objectSpread22(), 1);
523
527
 
524
- // node_modules/@trpc/client/dist/TRPCClientError-apv8gw59.mjs
528
+ // ../../node_modules/@trpc/client/dist/TRPCClientError-apv8gw59.mjs
525
529
  var import_defineProperty2 = __toESM(require_defineProperty(), 1);
526
530
  var import_objectSpread23 = __toESM(require_objectSpread2(), 1);
527
531
  function isTRPCClientError(cause) {
@@ -571,7 +575,7 @@ var TRPCClientError = class TRPCClientError2 extends Error {
571
575
  }
572
576
  };
573
577
 
574
- // node_modules/@trpc/client/dist/unstable-internals-Bg7n9BBj.mjs
578
+ // ../../node_modules/@trpc/client/dist/unstable-internals-Bg7n9BBj.mjs
575
579
  function getTransformer(transformer) {
576
580
  const _transformer = transformer;
577
581
  if (!_transformer) return {
@@ -592,7 +596,7 @@ function getTransformer(transformer) {
592
596
  }
593
597
  __name(getTransformer, "getTransformer");
594
598
 
595
- // node_modules/@trpc/client/dist/httpUtils-BNq9QC3d.mjs
599
+ // ../../node_modules/@trpc/client/dist/httpUtils-pyf5RF99.mjs
596
600
  var isFunction2 = /* @__PURE__ */ __name((fn) => typeof fn === "function", "isFunction");
597
601
  function getFetch(customFetchImpl) {
598
602
  if (customFetchImpl) return customFetchImpl;
@@ -601,7 +605,7 @@ function getFetch(customFetchImpl) {
601
605
  throw new Error("No fetch implementation found");
602
606
  }
603
607
  __name(getFetch, "getFetch");
604
- var import_objectSpread24 = __toESM(require_objectSpread2());
608
+ var import_objectSpread24 = __toESM(require_objectSpread2(), 1);
605
609
  function resolveHTTPLinkOptions(opts) {
606
610
  return {
607
611
  url: opts.url.toString(),
@@ -706,7 +710,7 @@ async function httpRequest(opts) {
706
710
  }
707
711
  __name(httpRequest, "httpRequest");
708
712
 
709
- // node_modules/@trpc/client/dist/httpLink-oiU8eqFi.mjs
713
+ // ../../node_modules/@trpc/client/dist/httpLink-lG_6juPY.mjs
710
714
  function isOctetType(input) {
711
715
  return input instanceof Uint8Array || input instanceof Blob;
712
716
  }
@@ -781,13 +785,13 @@ function httpLink(opts) {
781
785
  }
782
786
  __name(httpLink, "httpLink");
783
787
 
784
- // node_modules/@trpc/client/dist/httpBatchLink-CaWjh1oW.mjs
788
+ // ../../node_modules/@trpc/client/dist/httpBatchLink-LhidKAPw.mjs
785
789
  var import_objectSpread26 = __toESM(require_objectSpread2(), 1);
786
790
 
787
- // node_modules/@trpc/client/dist/loggerLink-ineCN1PO.mjs
791
+ // ../../node_modules/@trpc/client/dist/loggerLink-ineCN1PO.mjs
788
792
  var import_objectSpread27 = __toESM(require_objectSpread2(), 1);
789
793
 
790
- // node_modules/@trpc/client/dist/wsLink-DSf4KOdW.mjs
794
+ // ../../node_modules/@trpc/client/dist/wsLink-DSf4KOdW.mjs
791
795
  var jsonEncoder = {
792
796
  encode: /* @__PURE__ */ __name((data) => JSON.stringify(data), "encode"),
793
797
  decode: /* @__PURE__ */ __name((data) => {
@@ -1427,7 +1431,7 @@ function wsLink(opts) {
1427
1431
  }
1428
1432
  __name(wsLink, "wsLink");
1429
1433
 
1430
- // node_modules/@trpc/client/dist/index.mjs
1434
+ // ../../node_modules/@trpc/client/dist/index.mjs
1431
1435
  var import_defineProperty4 = __toESM(require_defineProperty(), 1);
1432
1436
  var import_objectSpread2$4 = __toESM(require_objectSpread2(), 1);
1433
1437
  var TRPCUntypedClient = class {
@@ -1760,342 +1764,476 @@ var import_awaitAsyncGenerator = __toESM(require_awaitAsyncGenerator(), 1);
1760
1764
  var import_wrapAsyncGenerator = __toESM(require_wrapAsyncGenerator(), 1);
1761
1765
  var import_objectSpread29 = __toESM(require_objectSpread2(), 1);
1762
1766
 
1763
- // src/backend-client.ts
1767
+ // src/system.ts
1764
1768
  import superjson from "superjson";
1765
- var BackendClient = class {
1769
+ import { SystemMirror, createSystemProxy } from "@camstack/types";
1770
+ var WS_KEEP_ALIVE_INTERVAL_MS = 1e4;
1771
+ var WS_KEEP_ALIVE_PONG_TIMEOUT_MS = 3e3;
1772
+ var WS_RECONNECT_RETRY_DELAY_MS = 2e3;
1773
+ var WS_RECONNECT_MAX_RETRY_DELAY_MS = 3e4;
1774
+ var System = class {
1766
1775
  static {
1767
- __name(this, "BackendClient");
1776
+ __name(this, "System");
1768
1777
  }
1769
- /** Raw tRPC client — for advanced usage / direct path access */
1770
- trpc;
1771
1778
  serverUrl;
1779
+ useWs;
1780
+ baseRetryMs;
1781
+ maxRetryMs;
1782
+ onConnectionChange;
1772
1783
  token;
1773
- wsClient = null;
1784
+ _trpcClient;
1785
+ _wsClient = null;
1786
+ // Mirror — owns binding cache + state mirror + lifecycle listeners.
1787
+ // Lazily initialised on `init()`. Subsequent `reconnect()` disposes
1788
+ // the old mirror and rebuilds against the new tRPC client.
1789
+ mirror = null;
1790
+ mirrorInit = null;
1791
+ // Transport-readiness probe state. `connected` flips true the first
1792
+ // time `auth.me` succeeds against the current tRPC client; resets
1793
+ // on every `reconnect()` / `close()` so the next consumer waits for
1794
+ // a fresh handshake before issuing queries.
1795
+ connected = false;
1796
+ connectedPromise = null;
1797
+ // System-cap namespaces — populated in the constructor from
1798
+ // `createSystemProxy(api)`. Each namespace is a `Pick<InferProvider<typeof cap>, …>`
1799
+ // shape generated by `scripts/generate-system-proxy.ts`.
1800
+ _systemProxy;
1801
+ // Connection-version + listener bookkeeping for the admin UI's
1802
+ // reconnect watchdog.
1803
+ _connectionVersion = 0;
1804
+ connectionListeners = /* @__PURE__ */ new Set();
1774
1805
  constructor(config) {
1775
1806
  this.serverUrl = config.serverUrl.replace(/\/+$/, "");
1776
1807
  this.token = config.token;
1777
1808
  const isBrowser = typeof window !== "undefined";
1778
- const useWs = config.useWebSocket ?? isBrowser;
1779
- const headers = /* @__PURE__ */ __name(() => {
1780
- const h = {};
1781
- if (this.token) {
1782
- h["Authorization"] = `Bearer ${this.token}`;
1783
- }
1784
- return h;
1785
- }, "headers");
1786
- if (useWs) {
1787
- const wsUrl = this.serverUrl.replace(/^http/, "ws") + "/trpc";
1788
- this.wsClient = createWSClient({
1789
- url: wsUrl,
1790
- connectionParams: /* @__PURE__ */ __name(() => ({
1791
- token: this.token
1792
- }), "connectionParams"),
1793
- retryDelayMs: /* @__PURE__ */ __name(() => 2e3, "retryDelayMs")
1794
- });
1795
- this.trpc = createTRPCClient({
1796
- links: [
1797
- wsLink({
1798
- client: this.wsClient,
1799
- transformer: superjson
1800
- })
1801
- ]
1802
- });
1803
- } else {
1804
- this.trpc = createTRPCClient({
1805
- links: [
1806
- httpLink({
1807
- url: `${this.serverUrl}/trpc`,
1808
- headers,
1809
- transformer: superjson
1810
- })
1811
- ]
1812
- });
1813
- }
1814
- }
1815
- /** Update the auth token (e.g. after login) */
1816
- setToken(token) {
1817
- this.token = token;
1818
- }
1819
- /** Close the WebSocket connection (if using WS transport) */
1820
- close() {
1821
- this.wsClient?.close();
1809
+ this.useWs = config.useWebSocket ?? isBrowser;
1810
+ this.onConnectionChange = config.onConnectionChange;
1811
+ this.baseRetryMs = config.retryDelayMs ?? WS_RECONNECT_RETRY_DELAY_MS;
1812
+ this.maxRetryMs = config.maxRetryDelayMs ?? WS_RECONNECT_MAX_RETRY_DELAY_MS;
1813
+ this._trpcClient = this.buildTrpcClient();
1814
+ this._systemProxy = createSystemProxy(this._trpcClient);
1822
1815
  }
1823
- // ─── Auth ──────────────────────────────────────────────────────────
1824
- async login(username, password) {
1825
- return this.trpc.auth.login.mutate({
1826
- username,
1827
- password
1828
- });
1829
- }
1830
- async getMe() {
1831
- return this.trpc.auth.me.query();
1832
- }
1833
- async logout() {
1834
- return this.trpc.auth.logout.mutate();
1816
+ // ── Connection state ─────────────────────────────────────────────
1817
+ get connectionVersion() {
1818
+ return this._connectionVersion;
1835
1819
  }
1836
- // ─── System ────────────────────────────────────────────────────────
1837
- async getSystemInfo() {
1838
- return this.trpc.system.info.query();
1839
- }
1840
- async getFeatureFlags() {
1841
- return this.trpc.system.featureFlags.query();
1842
- }
1843
- // ─── Providers ─────────────────────────────────────────────────────
1844
- async listProviders() {
1845
- return this.trpc.providers.list.query();
1820
+ /**
1821
+ * Subscribe to connection-state transitions. Called with `('connected'
1822
+ * | 'disconnected' | 'connecting', version)` whenever the SDK opens,
1823
+ * closes, or starts a manual reconnect. Listener errors are swallowed
1824
+ * so a misbehaving consumer cannot break the SDK's event loop.
1825
+ */
1826
+ subscribeConnectionEvents(cb) {
1827
+ this.connectionListeners.add(cb);
1828
+ return () => {
1829
+ this.connectionListeners.delete(cb);
1830
+ };
1846
1831
  }
1847
- async getProvider(providerId) {
1848
- return this.trpc.providers.get.query({
1849
- id: providerId
1850
- });
1832
+ emitConnectionEvent(state) {
1833
+ try {
1834
+ this.onConnectionChange?.(state);
1835
+ } catch {
1836
+ }
1837
+ for (const l of this.connectionListeners) {
1838
+ try {
1839
+ l(state, this._connectionVersion);
1840
+ } catch {
1841
+ }
1842
+ }
1851
1843
  }
1852
- async startProvider(providerId) {
1853
- return this.trpc.providers.start.mutate({
1854
- id: providerId
1855
- });
1844
+ // ── Lifecycle ────────────────────────────────────────────────────
1845
+ /**
1846
+ * Wait until the underlying tRPC transport is connected AND the
1847
+ * server has responded to a cheap auth round-trip (`auth.me`). This
1848
+ * is the canonical "ready to issue queries" gate.
1849
+ *
1850
+ * Why a probe, not just `ws.readyState === OPEN`?
1851
+ * The WS handshake completes asynchronously: tRPC's `wsLink`
1852
+ * queues outgoing messages and only flushes them after `open()`
1853
+ * resolves (post `connectionParams` send). On the server, the
1854
+ * tRPC context is created lazily once the connectionParams
1855
+ * message is received. A query fired between WS-open and
1856
+ * connection-params-processed is technically queued by tRPC, but
1857
+ * the auth context for that query is only resolved once the
1858
+ * handshake message is decoded server-side. A probe round-trip is
1859
+ * the safest way to confirm both sides have agreed on the auth
1860
+ * identity before the React tree starts firing parallel queries
1861
+ * (which can otherwise land before any addon-side service
1862
+ * discovery has settled, returning empty results that get cached).
1863
+ *
1864
+ * Idempotent — concurrent callers await the same in-flight Promise.
1865
+ * Bounded by `timeoutMs` (default 15s) — beyond which a
1866
+ * `Error('System.awaitConnected: probe timed out after Xms')` is
1867
+ * thrown so the host can render a clear error state instead of
1868
+ * hanging on a bricked socket.
1869
+ */
1870
+ async awaitConnected(timeoutMs) {
1871
+ if (this.connected) return;
1872
+ if (this.connectedPromise) return this.connectedPromise;
1873
+ const effectiveTimeoutMs = timeoutMs ?? 15e3;
1874
+ this.connectedPromise = (async () => {
1875
+ const probe = this._trpcClient.auth.me.query();
1876
+ if (!Number.isFinite(effectiveTimeoutMs)) {
1877
+ await probe;
1878
+ } else {
1879
+ await new Promise((resolve, reject) => {
1880
+ const timer = setTimeout(() => reject(new Error(`System.awaitConnected: probe timed out after ${effectiveTimeoutMs}ms`)), effectiveTimeoutMs);
1881
+ probe.then(() => {
1882
+ clearTimeout(timer);
1883
+ resolve();
1884
+ }, (err) => {
1885
+ clearTimeout(timer);
1886
+ reject(err instanceof Error ? err : new Error(String(err)));
1887
+ });
1888
+ });
1889
+ }
1890
+ this.connected = true;
1891
+ })();
1892
+ try {
1893
+ await this.connectedPromise;
1894
+ } finally {
1895
+ this.connectedPromise = null;
1896
+ }
1856
1897
  }
1857
- async stopProvider(providerId) {
1858
- return this.trpc.providers.stop.mutate({
1859
- id: providerId
1898
+ /**
1899
+ * Warm-boot the device mirror. Awaits the transport probe first
1900
+ * (`awaitConnected`) so the three mirror round-trips
1901
+ * (`getAllBindings` + `getAllSnapshots` + `listAll`) cannot race
1902
+ * against the WS auth handshake. Subsequent `getDevice(id)` calls
1903
+ * are sync; live `device.*` event subscriptions keep the caches
1904
+ * fresh.
1905
+ *
1906
+ * Idempotent — concurrent callers await the same in-flight Promise.
1907
+ */
1908
+ async init(timeoutMs) {
1909
+ if (this.mirror?.isReady()) return;
1910
+ if (this.mirrorInit) return this.mirrorInit;
1911
+ const m = new SystemMirror(this._trpcClient);
1912
+ this.mirror = m;
1913
+ this.mirrorInit = (async () => {
1914
+ await this.awaitConnected(timeoutMs);
1915
+ await m.init(timeoutMs);
1916
+ })().finally(() => {
1917
+ this.mirrorInit = null;
1860
1918
  });
1919
+ return this.mirrorInit;
1861
1920
  }
1862
- async listProviderTypes() {
1863
- return this.trpc.providerConfig.getAvailableTypes.query();
1921
+ /** Promise that resolves once `init()` has completed. */
1922
+ async awaitReady() {
1923
+ if (this.mirror?.isReady()) return;
1924
+ if (this.mirrorInit) return this.mirrorInit;
1925
+ return this.init();
1864
1926
  }
1865
- // ─── Devices ───────────────────────────────────────────────────────
1866
- async listDevices() {
1867
- return this.trpc.devices.list.query();
1927
+ /** True after `init()` resolves. */
1928
+ isReady() {
1929
+ return this.mirror?.isReady() ?? false;
1868
1930
  }
1869
- async getDevice(deviceId) {
1870
- return this.trpc.devices.get.query({
1871
- id: deviceId
1872
- });
1931
+ /** True after the transport probe has succeeded at least once. */
1932
+ isConnected() {
1933
+ return this.connected;
1873
1934
  }
1874
- async discoverDevices(providerId) {
1875
- return this.trpc.devices.discoverDevices.query({
1876
- providerId
1877
- });
1935
+ /**
1936
+ * Force a fresh WebSocket handshake. Tears down the wsClient + tRPC
1937
+ * client + mirror (the mirror captures the tRPC reference at
1938
+ * construction time and would otherwise dispatch through a closed
1939
+ * client) and rebuilds them. No-op for HTTP transport.
1940
+ */
1941
+ reconnect() {
1942
+ if (!this.useWs) return;
1943
+ this.emitConnectionEvent("connecting");
1944
+ this._wsClient?.close();
1945
+ this.disposeMirror();
1946
+ this.connected = false;
1947
+ this.connectedPromise = null;
1948
+ this._trpcClient = this.buildTrpcClient();
1949
+ this._systemProxy = createSystemProxy(this._trpcClient);
1950
+ }
1951
+ /** Tear down WS connection + mirror. The instance is unusable afterwards. */
1952
+ close() {
1953
+ this.disposeMirror();
1954
+ this.connected = false;
1955
+ this.connectedPromise = null;
1956
+ this._wsClient?.close();
1878
1957
  }
1879
- async adoptDevice(providerId, externalId) {
1880
- return this.trpc.devices.adoptDevice.mutate({
1881
- providerId,
1882
- externalId
1883
- });
1958
+ disposeMirror() {
1959
+ this.mirror?.dispose();
1960
+ this.mirror = null;
1961
+ this.mirrorInit = null;
1884
1962
  }
1885
- async createDevice(providerId, config) {
1886
- return this.trpc.devices.createDevice.mutate({
1887
- providerId,
1888
- config
1963
+ // ── Auth ──────────────────────────────────────────────────────────
1964
+ async login(username, password) {
1965
+ const result = await this._trpcClient.auth.login.mutate({
1966
+ username,
1967
+ password
1889
1968
  });
1969
+ if (typeof result === "object" && result !== null && "token" in result) {
1970
+ const token = result.token;
1971
+ if (typeof token === "string") this.setToken(token);
1972
+ }
1973
+ return result;
1890
1974
  }
1891
- /** Returns the URL path for a static asset served by an addon. */
1892
- getAddonAssetUrl(addonId, assetPath) {
1893
- return `/api/addon-assets/${addonId}/${assetPath}`;
1894
- }
1895
- // ─── Addons ────────────────────────────────────────────────────────
1896
- async listAddons() {
1897
- return this.trpc.addons.list.query();
1898
- }
1899
- async getAddonConfigSchema(addonId) {
1900
- return this.trpc.addons.getConfigSchema.query({
1901
- addonId
1902
- });
1975
+ async logout() {
1976
+ await this._trpcClient.auth.logout.mutate();
1903
1977
  }
1904
- async getAddonConfig(addonId) {
1905
- return this.trpc.addons.getConfig.query({
1906
- addonId
1907
- });
1978
+ async getMe() {
1979
+ const me = await this._trpcClient.auth.me.query();
1980
+ return me;
1908
1981
  }
1909
- async updateAddonConfig(addonId, config) {
1910
- return this.trpc.addons.updateConfig.mutate({
1911
- addonId,
1912
- config
1913
- });
1982
+ /** Update the auth token (e.g. after login or token refresh). */
1983
+ setToken(token) {
1984
+ this.token = token;
1914
1985
  }
1915
- async getAddonLogs(addonId, options) {
1916
- return this.trpc.addons.getLogs.query({
1917
- addonId,
1918
- ...options
1919
- });
1986
+ // ── Devices ──────────────────────────────────────────────────────
1987
+ /**
1988
+ * Synchronous snapshot of every device matching the optional filters.
1989
+ * Backed by the `SystemMirror` warm-boot cache — call `init()` first
1990
+ * (or `awaitReady()`) before invoking. Returns an empty array if the
1991
+ * mirror has not yet been booted.
1992
+ *
1993
+ * Each returned proxy has `binding` populated from the mirror's
1994
+ * binding cache (Phase 5 dedup), so consumers no longer need to
1995
+ * make a separate `deviceManager.getBindings` round-trip.
1996
+ */
1997
+ listDevices(filters) {
1998
+ if (!this.mirror) return [];
1999
+ const proxies = filters ? this.mirror.query(filters) : this.mirror.getAllDevices();
2000
+ return proxies.map((p) => this.attachBinding(p));
1920
2001
  }
1921
- // ─── Known Faces ───────────────────────────────────────────────────
1922
- async listKnownFaces() {
1923
- return this.trpc.knownFaces.list.query();
2002
+ /**
2003
+ * Sync lookup by numeric id. `null` if the mirror has not been booted
2004
+ * or the device is unknown.
2005
+ */
2006
+ getDevice(deviceId) {
2007
+ const proxy = this.mirror?.getDeviceById(deviceId) ?? null;
2008
+ return proxy ? this.attachBinding(proxy) : null;
1924
2009
  }
1925
- async registerFace(label, cropBase64, group) {
1926
- return this.trpc.knownFaces.register.mutate({
1927
- label,
1928
- cropBase64,
1929
- group
1930
- });
2010
+ /** Sync lookup by display name (exact match). */
2011
+ getDeviceByName(name) {
2012
+ const proxy = this.mirror?.getDeviceByName(name) ?? null;
2013
+ return proxy ? this.attachBinding(proxy) : null;
1931
2014
  }
1932
- // ─── Pipeline ──────────────────────────────────────────────────────
1933
- async listPipelines() {
1934
- return this.trpc.bridgePipeline.listAddons.query();
2015
+ /** Sync lookup by stableId. */
2016
+ getDeviceByStableId(stableId) {
2017
+ const proxy = this.mirror?.getDeviceByStableId(stableId) ?? null;
2018
+ return proxy ? this.attachBinding(proxy) : null;
1935
2019
  }
1936
- async getPipelineStatus(deviceId) {
1937
- return this.trpc.bridgePipeline.getPipeline.query({
1938
- deviceId
1939
- });
2020
+ /**
2021
+ * Resolve when a device with `deviceId` becomes available. Resolves
2022
+ * immediately if already known; rejects with a timeout error
2023
+ * otherwise (default 30s).
2024
+ */
2025
+ async waitForDevice(deviceId, timeoutMs) {
2026
+ if (!this.mirror) await this.init();
2027
+ const proxy = await this.mirror.waitForDevice(deviceId, timeoutMs ?? 3e4);
2028
+ return this.attachBinding(proxy);
2029
+ }
2030
+ /** Subscribe to `device.registered` events. */
2031
+ onDeviceAdded(cb) {
2032
+ if (!this.mirror) {
2033
+ throw new Error("System.onDeviceAdded: call init() before subscribing");
2034
+ }
2035
+ return this.mirror.onDeviceAdded(cb);
1940
2036
  }
1941
- // ─── Agents ────────────────────────────────────────────────────────
1942
- async listAgents() {
1943
- return this.trpc.agents.listAgents.query();
2037
+ /** Subscribe to `device.unregistered` events. */
2038
+ onDeviceRemoved(cb) {
2039
+ if (!this.mirror) {
2040
+ throw new Error("System.onDeviceRemoved: call init() before subscribing");
2041
+ }
2042
+ return this.mirror.onDeviceRemoved(cb);
1944
2043
  }
1945
- async dispatchTask(agentId, task) {
1946
- return this.trpc.agents.dispatchTask.mutate({
1947
- capability: agentId,
1948
- input: task
1949
- });
2044
+ /**
2045
+ * Patch the proxy's `binding` field from the mirror's cache. The
2046
+ * generated `createDeviceProxy()` already sets `binding` to the
2047
+ * binding it was constructed with — this is a defensive overwrite
2048
+ * that uses the latest cached entry in case a `binding-changed`
2049
+ * event landed between proxy creation and access.
2050
+ */
2051
+ attachBinding(proxy) {
2052
+ const binding = this.lookupBinding(proxy.deviceId);
2053
+ if (binding === proxy.binding) return proxy;
2054
+ const writable = proxy;
2055
+ writable.binding = binding;
2056
+ return proxy;
1950
2057
  }
1951
- async getProcessTree() {
1952
- return this.trpc.agent.processTree.query();
2058
+ /** Fetch the latest cached binding from the mirror, or `null`. */
2059
+ lookupBinding(deviceId) {
2060
+ if (!this.mirror) return null;
2061
+ const proxy = this.mirror.getDeviceById(deviceId);
2062
+ return proxy?.binding ?? null;
1953
2063
  }
1954
- // ─── Bridge Pipeline ───────────────────────────────────────────────
1955
- async bridgeListAddons() {
1956
- return this.trpc.bridgePipeline.listAddons.query();
2064
+ // ── System caps ──────────────────────────────────────────────────
2065
+ //
2066
+ // One getter per `scope: 'system'` capability. The actual surface
2067
+ // comes from `createSystemProxy(api)` (codegen). Adding a new
2068
+ // system cap regenerates the proxy and the new namespace surfaces
2069
+ // automatically via `system.<newCap>.<method>(input)`.
2070
+ get addonPages() {
2071
+ return this._systemProxy.addonPages;
1957
2072
  }
1958
- async bridgeGetPipeline(deviceId) {
1959
- return this.trpc.bridgePipeline.getPipeline.query({
1960
- deviceId
1961
- });
2073
+ get addonSettings() {
2074
+ return this._systemProxy.addonSettings;
1962
2075
  }
1963
- async bridgeSetPipeline(deviceId, config) {
1964
- return this.trpc.bridgePipeline.setPipeline.mutate({
1965
- deviceId,
1966
- config
1967
- });
2076
+ get alerts() {
2077
+ return this._systemProxy.alerts;
1968
2078
  }
1969
- async bridgeValidatePipeline(config) {
1970
- return this.trpc.bridgePipeline.validatePipeline.query(config);
2079
+ get audioAnalyzer() {
2080
+ return this._systemProxy.audioAnalyzer;
1971
2081
  }
1972
- async bridgeGetAddonConfig(addonId) {
1973
- return this.trpc.bridgePipeline.getAddonConfig.query({
1974
- addonId
1975
- });
2082
+ get audioCodec() {
2083
+ return this._systemProxy.audioCodec;
1976
2084
  }
1977
- async bridgeSetAddonConfig(addonId, config) {
1978
- return this.trpc.bridgePipeline.setAddonConfig.mutate({
1979
- addonId,
1980
- config
1981
- });
2085
+ get backup() {
2086
+ return this._systemProxy.backup;
1982
2087
  }
1983
- // ─── Addon Packages ────────────────────────────────────────────────
1984
- async bridgeListPackages() {
1985
- return this.trpc.addons.listPackages.query();
2088
+ get decoder() {
2089
+ return this._systemProxy.decoder;
1986
2090
  }
1987
- async bridgeListAddonsPackages() {
1988
- return this.trpc.addons.list.query();
2091
+ get deviceManager() {
2092
+ return this._systemProxy.deviceManager;
1989
2093
  }
1990
- async bridgeInstallPackage(packageName, version) {
1991
- return this.trpc.addons.installPackage.mutate({
1992
- packageName,
1993
- version
1994
- });
2094
+ get deviceProvider() {
2095
+ return this._systemProxy.deviceProvider;
1995
2096
  }
1996
- async bridgeUninstallPackage(packageName) {
1997
- return this.trpc.addons.uninstallPackage.mutate({
1998
- packageName
1999
- });
2097
+ get deviceState() {
2098
+ return this._systemProxy.deviceState;
2000
2099
  }
2001
- // ─── Recording ─────────────────────────────────────────────────────
2002
- async getRecordingConfig(deviceId) {
2003
- return this.trpc.recording.getConfig.query({
2004
- deviceId
2005
- });
2100
+ get metricsProvider() {
2101
+ return this._systemProxy.metricsProvider;
2006
2102
  }
2007
- async getRecordingPolicy(deviceId) {
2008
- return this.trpc.recording.getPolicy.query({
2009
- deviceId
2010
- });
2103
+ get notificationOutput() {
2104
+ return this._systemProxy.notificationOutput;
2011
2105
  }
2012
- async getRecordingPolicyStatus(deviceId) {
2013
- return this.trpc.recording.getPolicyStatus.query({
2014
- deviceId
2015
- });
2106
+ get pipelineExecutor() {
2107
+ return this._systemProxy.pipelineExecutor;
2016
2108
  }
2017
- async getRecordingSegments(deviceId, streamId, startTime, endTime) {
2018
- return this.trpc.recording.getSegments.query({
2019
- deviceId,
2020
- streamId,
2021
- startTime,
2022
- endTime
2023
- });
2109
+ get pipelineOrchestrator() {
2110
+ return this._systemProxy.pipelineOrchestrator;
2024
2111
  }
2025
- // ─── Events ────────────────────────────────────────────────────────
2026
- async getEvents(deviceId, options) {
2027
- return this.trpc.events.query.query({
2028
- deviceId,
2029
- ...options
2030
- });
2112
+ get pipelineRunner() {
2113
+ return this._systemProxy.pipelineRunner;
2031
2114
  }
2032
- // ─── Logs ──────────────────────────────────────────────────────────
2033
- async getLogs(options) {
2034
- return this.trpc.logs.query.query(options ?? {});
2115
+ get platformProbe() {
2116
+ return this._systemProxy.platformProbe;
2035
2117
  }
2036
- // ─── REPL ──────────────────────────────────────────────────────────
2037
- async replEval(code, scope) {
2038
- return this.trpc.repl.execute.mutate({
2039
- code,
2040
- scope: scope ?? {
2041
- type: "system"
2042
- }
2043
- });
2118
+ get recordingEngine() {
2119
+ return this._systemProxy.recordingEngine;
2044
2120
  }
2045
- // ─── Users ─────────────────────────────────────────────────────────
2046
- async listUsers() {
2047
- return this.trpc.users.list.query();
2121
+ get settingsStore() {
2122
+ return this._systemProxy.settingsStore;
2048
2123
  }
2049
- async createUser(username, password, role) {
2050
- return this.trpc.users.create.mutate({
2051
- username,
2052
- password,
2053
- role
2054
- });
2124
+ get storage() {
2125
+ return this._systemProxy.storage;
2055
2126
  }
2056
- // ─── Session Tracker ───────────────────────────────────────────────
2057
- async getTrackingSessions(deviceId) {
2058
- return this.trpc.session.getActiveTracks.query({
2059
- deviceId
2060
- });
2127
+ get streamBroker() {
2128
+ return this._systemProxy.streamBroker;
2061
2129
  }
2062
- // ─── Processes ─────────────────────────────────────────────────────
2063
- async listProcesses() {
2064
- return this.trpc.processes.listProcesses.query();
2130
+ get turnProvider() {
2131
+ return this._systemProxy.turnProvider;
2065
2132
  }
2066
- async enableProvider(providerId) {
2067
- return this.trpc.processes.enableProvider.mutate({
2068
- id: providerId
2069
- });
2133
+ get userManagement() {
2134
+ return this._systemProxy.userManagement;
2070
2135
  }
2071
- async disableProvider(providerId) {
2072
- return this.trpc.processes.disableProvider.mutate({
2073
- id: providerId
2136
+ // ── Live events ──────────────────────────────────────────────────
2137
+ /**
2138
+ * Subscribe to a single event category. Returns an unsubscribe
2139
+ * handle. Errors thrown by the listener are swallowed so a single
2140
+ * misbehaving consumer cannot tear down the WS subscription.
2141
+ *
2142
+ * Categories should be values from the `EventCategory` enum
2143
+ * (`@camstack/types`) — passing a raw string works for forward-compat
2144
+ * but loses type safety. The SDK forwards the value verbatim to the
2145
+ * server's `live.onEvent` subscription.
2146
+ */
2147
+ subscribeEvent(category, cb) {
2148
+ const sub = this._trpcClient.live.onEvent.subscribe({
2149
+ category
2150
+ }, {
2151
+ onData: /* @__PURE__ */ __name((event) => {
2152
+ try {
2153
+ cb(event);
2154
+ } catch {
2155
+ }
2156
+ }, "onData")
2074
2157
  });
2158
+ return () => {
2159
+ try {
2160
+ sub.unsubscribe();
2161
+ } catch {
2162
+ }
2163
+ };
2075
2164
  }
2076
- // ─── Settings ──────────────────────────────────────────────────────
2077
- /** Fetch the UI schema for a single section, or all sections if omitted. */
2078
- async getSettingsSchema(section) {
2079
- return this.trpc.settings.getSchema.query({
2080
- section
2081
- });
2165
+ // ── Escape hatches ───────────────────────────────────────────────
2166
+ //
2167
+ // Kept public so the ui-library `<TrpcProvider>` can seed React
2168
+ // Query with the same WebSocket connection during Phase 2. Phase 4
2169
+ // is expected to revisit whether either field can be hidden.
2170
+ /** Direct tRPC client. Read once per call; rebuilt on `reconnect()`. */
2171
+ get trpcClient() {
2172
+ return this._trpcClient;
2082
2173
  }
2083
- async getSettings(section) {
2084
- return this.trpc.settings.get.query({
2085
- section
2086
- });
2174
+ /**
2175
+ * Underlying WSClient (or `null` for HTTP transport). Used by
2176
+ * advanced consumers that need direct access to the WebSocket
2177
+ * (e.g. for keep-alive metrics). Rebuilt on `reconnect()`.
2178
+ */
2179
+ get wsClient() {
2180
+ return this._wsClient;
2087
2181
  }
2088
- async updateSettings(section, data) {
2089
- return this.trpc.settings.update.mutate({
2090
- section,
2091
- data
2182
+ // ── Internals ────────────────────────────────────────────────────
2183
+ buildTrpcClient() {
2184
+ const headers = /* @__PURE__ */ __name(() => {
2185
+ const h = {};
2186
+ if (this.token) h["Authorization"] = `Bearer ${this.token}`;
2187
+ return h;
2188
+ }, "headers");
2189
+ if (this.useWs) {
2190
+ const wsUrl = this.serverUrl.replace(/^http/, "ws") + "/trpc";
2191
+ const baseRetryMs = this.baseRetryMs;
2192
+ const maxRetryMs = this.maxRetryMs;
2193
+ this._wsClient = createWSClient({
2194
+ url: wsUrl,
2195
+ connectionParams: /* @__PURE__ */ __name(() => ({
2196
+ token: this.token
2197
+ }), "connectionParams"),
2198
+ retryDelayMs: /* @__PURE__ */ __name((attemptIndex) => Math.min(baseRetryMs * Math.pow(2, attemptIndex), maxRetryMs), "retryDelayMs"),
2199
+ keepAlive: {
2200
+ enabled: true,
2201
+ intervalMs: WS_KEEP_ALIVE_INTERVAL_MS,
2202
+ pongTimeoutMs: WS_KEEP_ALIVE_PONG_TIMEOUT_MS
2203
+ },
2204
+ onOpen: /* @__PURE__ */ __name(() => {
2205
+ this._connectionVersion += 1;
2206
+ this.emitConnectionEvent("connected");
2207
+ }, "onOpen"),
2208
+ onClose: /* @__PURE__ */ __name(() => {
2209
+ this.emitConnectionEvent("disconnected");
2210
+ }, "onClose")
2211
+ });
2212
+ return createTRPCClient({
2213
+ links: [
2214
+ wsLink({
2215
+ client: this._wsClient,
2216
+ transformer: superjson
2217
+ })
2218
+ ]
2219
+ });
2220
+ }
2221
+ this._wsClient = null;
2222
+ return createTRPCClient({
2223
+ links: [
2224
+ httpLink({
2225
+ url: `${this.serverUrl}/trpc`,
2226
+ headers,
2227
+ transformer: superjson
2228
+ })
2229
+ ]
2092
2230
  });
2093
2231
  }
2094
2232
  };
2095
- function createBackendClient(config) {
2096
- return new BackendClient(config);
2233
+ function createSystem(config) {
2234
+ return new System(config);
2097
2235
  }
2098
- __name(createBackendClient, "createBackendClient");
2236
+ __name(createSystem, "createSystem");
2099
2237
 
2100
2238
  // src/detection.ts
2101
2239
  var DetectionClass = /* @__PURE__ */ (function(DetectionClass2) {
@@ -2770,11 +2908,6 @@ function selectOptimalStream(streams, constraints, defaultTransport) {
2770
2908
  });
2771
2909
  scored.sort((a, b) => b.score - a.score);
2772
2910
  const best = scored[0];
2773
- const tierNames = {
2774
- high: "main",
2775
- medium: "sub",
2776
- low: "ext"
2777
- };
2778
2911
  return {
2779
2912
  streamName: best.streamName,
2780
2913
  profile: best.profile,
@@ -2793,7 +2926,6 @@ function getNextEvalInterval(constraints, wasSwitch) {
2793
2926
  }
2794
2927
  __name(getNextEvalInterval, "getNextEvalInterval");
2795
2928
  export {
2796
- BackendClient,
2797
2929
  DEFAULT_ENABLED_CLASSES,
2798
2930
  DetectionClass,
2799
2931
  ELIGIBLE_HA_DOMAINS,
@@ -2804,13 +2936,14 @@ export {
2804
2936
  HA_DOMAIN_TYPE_MAP,
2805
2937
  RAW_TO_CANONICAL,
2806
2938
  SCRYPTED_TYPE_TO_CANONICAL,
2939
+ System,
2807
2940
  TIMELINE_PRESET_ALL,
2808
2941
  TIMELINE_PRESET_CRITICAL,
2809
2942
  TIMELINE_PRESET_IMPORTANT,
2810
2943
  animalClasses,
2811
2944
  audioClasses,
2812
2945
  audioLabelClasses,
2813
- createBackendClient,
2946
+ createSystem,
2814
2947
  defaultDetectionClasses,
2815
2948
  detectionClassesDefaultMap,
2816
2949
  doorbellClasses,
@@ -2844,7 +2977,7 @@ export {
2844
2977
  };
2845
2978
  /*! Bundled license information:
2846
2979
 
2847
- @trpc/client/dist/httpLink-oiU8eqFi.mjs:
2980
+ @trpc/client/dist/httpLink-lG_6juPY.mjs:
2848
2981
  (* istanbul ignore if -- @preserve *)
2849
2982
  */
2850
2983
  //# sourceMappingURL=index.js.map