@camstack/sdk 0.1.36 → 0.1.37

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/system.d.ts CHANGED
@@ -57,7 +57,10 @@ export type SystemLiveEventListener<TData = unknown> = (event: SystemLiveEvent<T
57
57
  type TrpcClient = TRPCClient<BackendAppRouter>;
58
58
  type TrpcWsClient = ReturnType<typeof createWSClient>;
59
59
  export declare class System {
60
- readonly serverUrl: string;
60
+ /** Active server base URL. Mutable via `switchServerUrl()` so the
61
+ * endpoint-race helper can pivot the transport onto a LAN candidate
62
+ * after the initial public-URL bootstrap. */
63
+ private _serverUrl;
61
64
  private readonly useWs;
62
65
  private readonly baseRetryMs;
63
66
  private readonly maxRetryMs;
@@ -73,6 +76,8 @@ export declare class System {
73
76
  private _connectionVersion;
74
77
  private readonly connectionListeners;
75
78
  constructor(config: SystemConfig);
79
+ /** Active server base URL (no trailing slash). */
80
+ get serverUrl(): string;
76
81
  get connectionVersion(): number;
77
82
  /**
78
83
  * Subscribe to connection-state transitions. Called with `('connected'
@@ -132,6 +137,47 @@ export declare class System {
132
137
  * client) and rebuilds them. No-op for HTTP transport.
133
138
  */
134
139
  reconnect(): void;
140
+ /**
141
+ * Pivot the underlying tRPC transport onto a different base URL.
142
+ * Used by the endpoint-race flow: the SDK opens against the public
143
+ * URL the operator provided, calls `localNetwork.getConnectionEndpoints`
144
+ * to discover LAN candidates, races them, and (when a faster one wins)
145
+ * calls `switchServerUrl(winner)` to migrate every subsequent query
146
+ * onto the LAN path without losing auth state.
147
+ *
148
+ * Keeps the auth token. Tears down the WS + mirror and rebuilds them
149
+ * against the new URL — same machinery as `reconnect()` but with a
150
+ * different target.
151
+ */
152
+ switchServerUrl(nextUrl: string): void;
153
+ /**
154
+ * Race the candidate base URLs reported by the hub's `local-network`
155
+ * cap, pick the fastest one that responds, and (if it's different
156
+ * from the current URL) pivot the transport onto it.
157
+ *
158
+ * Flow:
159
+ * 1. Query `localNetwork.getConnectionEndpoints({ port })` over the
160
+ * already-authenticated tRPC channel — the cap is auth-gated,
161
+ * so the LAN IPs never leak to anonymous callers.
162
+ * 2. For each candidate, fire a HEAD on `{baseUrl}/trpc/health`
163
+ * with a short timeout (default 1500ms). The first 2xx wins.
164
+ * 3. If the winner differs from `this.serverUrl`, call
165
+ * `switchServerUrl(winner)` and return it. Otherwise return
166
+ * the current URL unchanged.
167
+ *
168
+ * Bounded — if every candidate times out we keep the current URL.
169
+ * Idempotent — safe to call on every connect / reconnect / network
170
+ * change event.
171
+ */
172
+ raceConnectionEndpoints(options?: {
173
+ /** Per-candidate probe timeout. Default 1500ms. */
174
+ readonly perCandidateTimeoutMs?: number;
175
+ /** Skip IPv6 candidates. Default `false`. */
176
+ readonly ipv4Only?: boolean;
177
+ }): Promise<{
178
+ winner: string;
179
+ switched: boolean;
180
+ }>;
135
181
  /** Tear down WS connection + mirror. The instance is unusable afterwards. */
136
182
  close(): void;
137
183
  private disposeMirror;
@@ -227,4 +273,25 @@ export declare class System {
227
273
  }
228
274
  /** Create a `System` instance. Convenience factory. */
229
275
  export declare function createSystem(config: SystemConfig): System;
276
+ /**
277
+ * Race a list of candidate base URLs and return the first one that
278
+ * responds to `GET {baseUrl}/trpc/health` with a 2xx within
279
+ * `timeoutMs`. Returns `null` if every candidate fails / times out.
280
+ *
281
+ * Implementation notes:
282
+ * - Uses `fetch` with `AbortController` for per-candidate cutoff so a
283
+ * stalled candidate doesn't pin the wallclock for the whole race.
284
+ * - Cancels every still-pending probe as soon as the first one
285
+ * succeeds — no wasted bandwidth on the loser candidates.
286
+ * - `/trpc/health` is the only endpoint guaranteed to respond on every
287
+ * CamStack deployment (registered alongside the tRPC plugin). It is
288
+ * intentionally NOT auth-gated since it's used by load balancers /
289
+ * uptime probes — same surface every reverse proxy already monitors.
290
+ * - HEAD would be nicer but Cloudflare Tunnel + some browsers
291
+ * mishandle HEAD on the ingress path; GET is safer.
292
+ *
293
+ * Exported so non-System callers (CLI helpers, tests) can race their
294
+ * own candidate lists without instantiating a full System.
295
+ */
296
+ export declare function raceFastestEndpoint(candidates: ReadonlyArray<string>, timeoutMs: number): Promise<string | null>;
230
297
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/sdk",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -16,7 +16,7 @@
16
16
  "dist"
17
17
  ],
18
18
  "scripts": {
19
- "build": "tsup && tsc -p tsconfig.build.json",
19
+ "build": "vite build && tsc -p tsconfig.build.json",
20
20
  "typecheck": "tsc --noEmit",
21
21
  "publish": "npm publish --access public"
22
22
  },