@voltro/client 0.37.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +316 -0
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/dist/index.d.ts +301 -4
- package/dist/index.js +539 -383
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -296,8 +296,18 @@ export declare interface BuildApiRuntimeOptions {
|
|
|
296
296
|
readonly name: string;
|
|
297
297
|
/** WebSocket URL of the api (`ws(s)://host:port/ws`). */
|
|
298
298
|
readonly wsUrl: string;
|
|
299
|
-
/**
|
|
300
|
-
|
|
299
|
+
/**
|
|
300
|
+
* The api's rpc group, as the ERASED `RpcGroup.Any`.
|
|
301
|
+
*
|
|
302
|
+
* `RpcGroup` is declared `in out` in @effect/rpc — invariant on purpose — so a
|
|
303
|
+
* CONCRETE group (the `appGroup` codegen emits) is not assignable to
|
|
304
|
+
* `RpcGroup<Rpc.Any>`, and a boundary typed that way rejects the only value
|
|
305
|
+
* anyone ever passes it. This mirrors what `ApiHandle.client` already does:
|
|
306
|
+
* erase at the handle boundary, restore the types at the call site via
|
|
307
|
+
* `createHooks<T>()` / `useAppClient<T>()`. The concrete rpc types are
|
|
308
|
+
* irrelevant to building a transport — they matter to the caller, not here.
|
|
309
|
+
*/
|
|
310
|
+
readonly group: RpcGroup.Any;
|
|
301
311
|
/** Per-connection auth headers (see {@link ResolvableHeaders}). */
|
|
302
312
|
readonly headers?: ResolvableHeaders | undefined;
|
|
303
313
|
/** How to construct a WebSocket. Web wraps `globalThis.WebSocket` with
|
|
@@ -306,6 +316,8 @@ export declare interface BuildApiRuntimeOptions {
|
|
|
306
316
|
readonly webSocketConstructor: (url: string, protocols?: string | ReadonlyArray<string>) => globalThis.WebSocket;
|
|
307
317
|
}
|
|
308
318
|
|
|
319
|
+
export declare type BuildClient = (api: MountedApi, generation: number, onSocketOpen: () => void, onSocketIssue: (kind: SocketIssueKind) => void) => Promise<ResolvedClient>;
|
|
320
|
+
|
|
309
321
|
/** The transport-level pieces of one api's client stack. Combined with the
|
|
310
322
|
* api's descriptor map + inspect base URL, this is an `ApiHandle`. */
|
|
311
323
|
export declare interface BuiltApiRuntime {
|
|
@@ -349,6 +361,11 @@ declare interface CacheEntry {
|
|
|
349
361
|
* server-side subscription is one per cache-entry key, not per
|
|
350
362
|
* subscriber. */
|
|
351
363
|
fetch: (() => Stream.Stream<SubscriptionEvent<unknown>, unknown, never>) | null;
|
|
364
|
+
/** The runtime the saved `fetch` was forked against. Stored so a SINGLE entry
|
|
365
|
+
* can be re-issued without the whole-cache `refreshAll(runtime)` — the
|
|
366
|
+
* confirmed-patch resync needs exactly one, and threading a runtime down
|
|
367
|
+
* from a `setTimeout` is not available to it. */
|
|
368
|
+
runtime: AnyRuntime | null;
|
|
352
369
|
/** W3C trace id of the live stream, captured when the fiber starts.
|
|
353
370
|
* Shared with the server (the subscription span propagates it) and
|
|
354
371
|
* attached to error-bus events so a failure points at the right
|
|
@@ -492,6 +509,20 @@ export declare const clearStoreHistory: () => void;
|
|
|
492
509
|
|
|
493
510
|
export declare const CLIENT_NAME: "framework-client";
|
|
494
511
|
|
|
512
|
+
/**
|
|
513
|
+
* Per-rpc-tag metadata the client uses to route cache updates: what kind of
|
|
514
|
+
* procedure a tag is, which query it reads from, and which tables a mutation
|
|
515
|
+
* targets (driving auto-optimistic updates). Produced by codegen.
|
|
516
|
+
*
|
|
517
|
+
* It is the CANONICAL `ClientDescriptor` from `@voltro/protocol`, not a
|
|
518
|
+
* structural copy of it. The copy that used to live here narrowed `source` to a
|
|
519
|
+
* single string while the real descriptor allows several — so a generated
|
|
520
|
+
* `appDescriptors` did not fit the type the client declared for it. Nothing
|
|
521
|
+
* caught that: the only place the two meet is a GENERATED entry, and no harness
|
|
522
|
+
* typechecks a generated entry.
|
|
523
|
+
*/
|
|
524
|
+
export declare type ClientDescriptorMap = Readonly<Record<string, ClientDescriptor>>;
|
|
525
|
+
|
|
495
526
|
export declare interface ClientErrorEvent {
|
|
496
527
|
/** The thrown value (Error | anything). */
|
|
497
528
|
readonly error: unknown;
|
|
@@ -1047,6 +1078,27 @@ export declare const markFailed: (queue: Outbox, id: string, error: unknown) =>
|
|
|
1047
1078
|
|
|
1048
1079
|
export declare const markSent: (queue: Outbox, id: string) => Outbox;
|
|
1049
1080
|
|
|
1081
|
+
/**
|
|
1082
|
+
* One api the client is mounted against: its name, its typed rpc surface, its
|
|
1083
|
+
* descriptor map and where to connect.
|
|
1084
|
+
*
|
|
1085
|
+
* `headers` are sent in the per-request MESSAGE frame, NOT the WebSocket
|
|
1086
|
+
* upgrade — a browser cannot set upgrade headers — so the server's
|
|
1087
|
+
* AuthMiddleware sees them on every call, which is what lets a cross-origin api
|
|
1088
|
+
* authenticate WS subscriptions. A thunk is resolved fresh on every (re)connect,
|
|
1089
|
+
* so a rotating token is pulled anew per connection instead of frozen at first
|
|
1090
|
+
* mount.
|
|
1091
|
+
*/
|
|
1092
|
+
export declare interface MountedApi {
|
|
1093
|
+
readonly name: string;
|
|
1094
|
+
/** Erased — see `BuildApiRuntimeOptions.group` for why an invariant
|
|
1095
|
+
* `RpcGroup<Rpc.Any>` cannot hold a generated `appGroup`. */
|
|
1096
|
+
readonly group: RpcGroup.Any;
|
|
1097
|
+
readonly descriptors: ClientDescriptorMap;
|
|
1098
|
+
readonly wsUrl: string;
|
|
1099
|
+
readonly headers?: ResolvableHeaders | undefined;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1050
1102
|
export declare interface MutateOptions<Input, Output> {
|
|
1051
1103
|
/** Ran after the server confirms the write. */
|
|
1052
1104
|
readonly onSuccess?: (output: Output, input: Input) => void;
|
|
@@ -1242,6 +1294,31 @@ export declare interface PersistOptions<S> {
|
|
|
1242
1294
|
* `initialSnapshot` are its own concern. */
|
|
1243
1295
|
export declare type PreloadedSubscriptionOptions = Pick<SubscriptionOptions<never>, 'skip'>;
|
|
1244
1296
|
|
|
1297
|
+
/**
|
|
1298
|
+
* What this hook returns: the subscription's state, plus whether the SERVER
|
|
1299
|
+
* tried to preload it and failed.
|
|
1300
|
+
*
|
|
1301
|
+
* `preloadFailed` is the difference between "this page has no data yet" and
|
|
1302
|
+
* "the server could not get this page's data" — two states that were one
|
|
1303
|
+
* observation before it existed, because both arrive as the absence of a seed.
|
|
1304
|
+
* A consumer whose session had expired rendered skeleton titles over empty
|
|
1305
|
+
* tables and found the cause only in a server WARN line.
|
|
1306
|
+
*
|
|
1307
|
+
* It says nothing about WHY, deliberately (`PreloadSeed.failed`), and nothing
|
|
1308
|
+
* about the LIVE subscription — which usually recovers on its own, since the
|
|
1309
|
+
* browser reconnects with a credential the SSR request did not have. So the
|
|
1310
|
+
* honest reading is "the first paint has no server data and that was not for
|
|
1311
|
+
* lack of asking", which is exactly enough to choose a spinner over an
|
|
1312
|
+
* empty-state.
|
|
1313
|
+
*
|
|
1314
|
+
* Widened on THIS hook rather than on `SubscriptionState`, so no existing
|
|
1315
|
+
* `useSubscription` call site is un-narrowed by it — the same scoping rule the
|
|
1316
|
+
* `skip`/`idle` overload follows.
|
|
1317
|
+
*/
|
|
1318
|
+
export declare type PreloadedSubscriptionState<T> = (SubscriptionState<T> | SubscriptionIdle) & {
|
|
1319
|
+
readonly preloadFailed?: true;
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1245
1322
|
/** One preloaded subscription snapshot, as it travels in the hydration payload. */
|
|
1246
1323
|
export declare interface PreloadSeed {
|
|
1247
1324
|
/** The api mount name the subscription reads from (first arg of `useSubscription`). */
|
|
@@ -1249,8 +1326,25 @@ export declare interface PreloadSeed {
|
|
|
1249
1326
|
/** `stableKey([rpcTag, input])` — identical to the SubscriptionCache's own key,
|
|
1250
1327
|
* so the seed and the live subscription address the same entry. */
|
|
1251
1328
|
readonly key: string;
|
|
1252
|
-
/** The server-fetched snapshot value. */
|
|
1329
|
+
/** The server-fetched snapshot value. `undefined` when `failed`. */
|
|
1253
1330
|
readonly value: unknown;
|
|
1331
|
+
/**
|
|
1332
|
+
* The server ATTEMPTED this preload and it failed.
|
|
1333
|
+
*
|
|
1334
|
+
* Reported by a consumer whose SSR seeds all failed at once (an expired
|
|
1335
|
+
* session token — the api answered every preload with a scope error): the
|
|
1336
|
+
* page rendered a skeleton title and an empty table, and the ONLY record of
|
|
1337
|
+
* it was a `preload seed failed` WARN in the server log. The client could not
|
|
1338
|
+
* tell that state from "this page declares no preload", because both arrive
|
|
1339
|
+
* as the absence of a seed. So an app cannot distinguish "still loading" from
|
|
1340
|
+
* "actually empty" without inventing its own convention — which the reporting
|
|
1341
|
+
* consumer then did, page by page.
|
|
1342
|
+
*
|
|
1343
|
+
* NOTHING about the cause travels. A boolean is the whole contract: the
|
|
1344
|
+
* server's failure text is for the server's log, and a reason string here
|
|
1345
|
+
* would be an error message from a refused call rendered into a browser.
|
|
1346
|
+
*/
|
|
1347
|
+
readonly failed?: true;
|
|
1254
1348
|
}
|
|
1255
1349
|
|
|
1256
1350
|
/** Collects preload seeds for ONE request. The server creates one per render. */
|
|
@@ -1389,6 +1483,17 @@ export declare interface QueryFiltersState<Row> {
|
|
|
1389
1483
|
|
|
1390
1484
|
declare type QueryTag<P extends ProcedureTypeMap> = TagsOfKind<P, 'query'>;
|
|
1391
1485
|
|
|
1486
|
+
/**
|
|
1487
|
+
* Did the server attempt this preload, and did it work?
|
|
1488
|
+
*
|
|
1489
|
+
* `'absent'` means the page declared no preload for it (or the server never
|
|
1490
|
+
* rendered this route — a client-side SPA navigation). `'failed'` means the
|
|
1491
|
+
* server tried and the query threw. Those two were the same observation before
|
|
1492
|
+
* this existed, which is why a page could not tell "still loading" from
|
|
1493
|
+
* "genuinely empty".
|
|
1494
|
+
*/
|
|
1495
|
+
export declare const readPreloadedSeedOutcome: (api: string, rpcTag: string, input: Readonly<Record<string, unknown>>) => "seeded" | "failed" | "absent";
|
|
1496
|
+
|
|
1392
1497
|
/**
|
|
1393
1498
|
* The preloaded snapshot for `(api, rpcTag, input)`, or `undefined` when none was
|
|
1394
1499
|
* seeded. On the SERVER it reads the current request's bag directly, so the SSR
|
|
@@ -1433,6 +1538,9 @@ export declare const resetPreloadSeeds: () => void;
|
|
|
1433
1538
|
/** Test helper: forget every defined store. NEVER call this in app code. */
|
|
1434
1539
|
export declare const resetStoreRegistryForTests: () => void;
|
|
1435
1540
|
|
|
1541
|
+
/** Test seam: forget both the installed provider and the used flag. */
|
|
1542
|
+
export declare const resetStoreStorageForTest: () => void;
|
|
1543
|
+
|
|
1436
1544
|
/** Auth headers to attach to every rpc MESSAGE frame, resolved on EVERY
|
|
1437
1545
|
* (re)connect so a rotating token (a `() => Promise<…>` thunk) is fetched anew
|
|
1438
1546
|
* each connection rather than frozen at first build. */
|
|
@@ -1440,6 +1548,15 @@ export declare type ResolvableHeaders = Readonly<Record<string, string>> | (() =
|
|
|
1440
1548
|
|
|
1441
1549
|
export declare const resolveByTag: (client: unknown, tag: string) => unknown;
|
|
1442
1550
|
|
|
1551
|
+
export declare interface ResolvedClient {
|
|
1552
|
+
readonly runtime: AnyRuntime;
|
|
1553
|
+
readonly cache: SubscriptionCache;
|
|
1554
|
+
readonly client: RpcClient.RpcClient<Rpc.Any, RpcClientError.RpcClientError>;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/** Resolve the storage for an area. Internal to the store layer. */
|
|
1558
|
+
export declare const resolveStoreStorage: (area: StoreStorageArea) => StoreStorage | null;
|
|
1559
|
+
|
|
1443
1560
|
/** Resolve one map entry against the props. Pure. */
|
|
1444
1561
|
export declare const resolveTrackingEvent: <P>(entry: TrackingEntry<P> | undefined, props: P) => TrackingEvent | null;
|
|
1445
1562
|
|
|
@@ -1613,6 +1730,16 @@ export declare const schemaToFields: (schema: Schema.Schema.Any) => ReadonlyArra
|
|
|
1613
1730
|
*/
|
|
1614
1731
|
export declare const seedPreloadedSubscription: (api: string, rpcTag: string, input: Readonly<Record<string, unknown>>, value: unknown) => void;
|
|
1615
1732
|
|
|
1733
|
+
/**
|
|
1734
|
+
* Record that this preload was ATTEMPTED and failed, so the client can tell an
|
|
1735
|
+
* empty page from a broken one.
|
|
1736
|
+
*
|
|
1737
|
+
* Same request-scoping and the same key as the success path — a failure the
|
|
1738
|
+
* client cannot address to a subscription is no better than the WARN line it
|
|
1739
|
+
* replaces. It carries no reason: see `PreloadSeed.failed`.
|
|
1740
|
+
*/
|
|
1741
|
+
export declare const seedPreloadedSubscriptionFailure: (api: string, rpcTag: string, input: Readonly<Record<string, unknown>>) => void;
|
|
1742
|
+
|
|
1616
1743
|
/**
|
|
1617
1744
|
* Seed a store for THIS request's hydration. Call it anywhere on the server
|
|
1618
1745
|
* during a render — a loader, a layout loader — and the value reaches the
|
|
@@ -1694,6 +1821,21 @@ export declare const setPreloadSeedResolver: (resolver: (() => PreloadSeedBag |
|
|
|
1694
1821
|
*/
|
|
1695
1822
|
export declare const setStoreSeedResolver: (resolver: (() => StoreSeedBag | null) | null) => void;
|
|
1696
1823
|
|
|
1824
|
+
/**
|
|
1825
|
+
* Install the process-wide backing store for persisted stores.
|
|
1826
|
+
*
|
|
1827
|
+
* Call it BEFORE the first read of a persisted store — on React Native, after
|
|
1828
|
+
* awaiting the adapter's `hydrate()` and before rendering the app. Pass `null`
|
|
1829
|
+
* to restore the browser default.
|
|
1830
|
+
*
|
|
1831
|
+
* Installing after a persisted store has already read logs a warning rather
|
|
1832
|
+
* than throwing: the app is running and the value is merely from the wrong
|
|
1833
|
+
* backend, so failing the boot would be the worse outcome. It is a warning that
|
|
1834
|
+
* means something, though — the store that read first is now inconsistent with
|
|
1835
|
+
* every store defined after it.
|
|
1836
|
+
*/
|
|
1837
|
+
export declare const setStoreStorage: (provider: StoreStorageProvider | null) => void;
|
|
1838
|
+
|
|
1697
1839
|
/**
|
|
1698
1840
|
* Transition a pending mutation to 'success' or 'error'. Updates the
|
|
1699
1841
|
* existing entry in-place (preserves order in the buffer) so the
|
|
@@ -1726,6 +1868,8 @@ export declare const shallowArrayEqual: (a: ReadonlyArray<unknown>, b: ReadonlyA
|
|
|
1726
1868
|
|
|
1727
1869
|
declare type SignalPayload<Messages extends WorkflowClientMessages, Name extends string> = NonNullable<Messages['signals']> extends Readonly<Record<Name, infer Payload>> ? Payload : unknown;
|
|
1728
1870
|
|
|
1871
|
+
export declare type SocketIssueKind = 'close' | 'error' | 'connect-timeout';
|
|
1872
|
+
|
|
1729
1873
|
export declare interface SortState {
|
|
1730
1874
|
readonly column: string;
|
|
1731
1875
|
readonly direction: 'asc' | 'desc';
|
|
@@ -1745,6 +1889,14 @@ export declare const ssrStubHandle: ApiHandle;
|
|
|
1745
1889
|
*/
|
|
1746
1890
|
export declare const stableKey: (key: ReadonlyArray<unknown>) => string;
|
|
1747
1891
|
|
|
1892
|
+
/**
|
|
1893
|
+
* Spawn the per-api connect + reconnect supervisor.
|
|
1894
|
+
*
|
|
1895
|
+
* Returns a handle whose `dispose()` cancels everything pending and
|
|
1896
|
+
* disposes every live ResolvedClient. Calling dispose() twice is safe.
|
|
1897
|
+
*/
|
|
1898
|
+
export declare const startApiSupervisor: (options: SupervisorOptions) => SupervisorHandle;
|
|
1899
|
+
|
|
1748
1900
|
/**
|
|
1749
1901
|
* Publish a NEW (pending) mutation event. Caller is responsible for
|
|
1750
1902
|
* calling `settleMutation(id, ...)` later. Returns the id used to
|
|
@@ -1934,6 +2086,22 @@ export declare interface StoreSeedBag {
|
|
|
1934
2086
|
readonly seeds: StoreSeed[];
|
|
1935
2087
|
}
|
|
1936
2088
|
|
|
2089
|
+
/** The synchronous subset of the DOM `Storage` interface a persisted store
|
|
2090
|
+
* needs. `removeItem` is optional — nothing in the store layer clears keys. */
|
|
2091
|
+
export declare interface StoreStorage {
|
|
2092
|
+
readonly getItem: (key: string) => string | null;
|
|
2093
|
+
readonly setItem: (key: string, value: string) => void;
|
|
2094
|
+
readonly removeItem?: (key: string) => void;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
/** Which area a store asked for. Platforms without a session/local split map
|
|
2098
|
+
* both onto one backing store; that is expected, not a downgrade. */
|
|
2099
|
+
export declare type StoreStorageArea = 'local' | 'session';
|
|
2100
|
+
|
|
2101
|
+
/** A backing store provider: given the requested area, return the storage to
|
|
2102
|
+
* use, or `null` for "there is none here" (the store then does not persist). */
|
|
2103
|
+
export declare type StoreStorageProvider = (area: StoreStorageArea) => StoreStorage | null;
|
|
2104
|
+
|
|
1937
2105
|
export declare interface StoreUseOptions<T> {
|
|
1938
2106
|
/** Which instance to read. Omitted = the global one. */
|
|
1939
2107
|
readonly key?: string;
|
|
@@ -2037,6 +2205,31 @@ export declare class SubscriptionCache {
|
|
|
2037
2205
|
* resolve briefly exposes a stale base before the server delta lands.
|
|
2038
2206
|
*/
|
|
2039
2207
|
confirmByMutation(mutationId: string): void;
|
|
2208
|
+
/**
|
|
2209
|
+
* The confirmed-patch window expired: a committed write never got its
|
|
2210
|
+
* superseding server event. Re-issue the affected subscriptions instead of
|
|
2211
|
+
* discarding the write's preview.
|
|
2212
|
+
*
|
|
2213
|
+
* Three things are load-bearing, and each was a way to reintroduce the bug:
|
|
2214
|
+
*
|
|
2215
|
+
* - **The patch is KEPT across the resync.** `refreshAll` already
|
|
2216
|
+
* establishes that shape ("the new base snapshot will land on top of
|
|
2217
|
+
* them"). Clearing it here would be the revert again, wearing a longer
|
|
2218
|
+
* word.
|
|
2219
|
+
* - **A stub entry — no `fetch`, no `runtime` — is left ALONE.** There is
|
|
2220
|
+
* nothing to re-issue, and the only other option is to show a base the
|
|
2221
|
+
* client knows is stale. Keeping the patch is the honest state.
|
|
2222
|
+
* - **It is LOUD.** The reporter found the jump only by reading minified
|
|
2223
|
+
* source, because in the UI it is indistinguishable from "the server did
|
|
2224
|
+
* not save it" — and that is the false trail they followed first. This
|
|
2225
|
+
* goes to the error bus, so it reaches `voltro logs` rather than only a
|
|
2226
|
+
* console nobody has open.
|
|
2227
|
+
*
|
|
2228
|
+
* No second timer is armed: this runs once per mutation, from the one
|
|
2229
|
+
* `confirmByMutation` scheduled it, so a server that never echoes cannot put
|
|
2230
|
+
* the client into a resync loop.
|
|
2231
|
+
*/
|
|
2232
|
+
private resyncUnechoedMutation;
|
|
2040
2233
|
/**
|
|
2041
2234
|
* Auto-derive optimistic patches from a mutation's declared target(s).
|
|
2042
2235
|
* Iterates every cache entry whose `source` table matches a target's
|
|
@@ -2098,6 +2291,18 @@ export declare class SubscriptionCache {
|
|
|
2098
2291
|
* promote naturally on their next subscriber.
|
|
2099
2292
|
*/
|
|
2100
2293
|
refreshAll(runtime: AnyRuntime): void;
|
|
2294
|
+
/**
|
|
2295
|
+
* Re-issue ONE entry's subscription: interrupt the fiber, reset the base,
|
|
2296
|
+
* re-fork through the saved `fetch`.
|
|
2297
|
+
*
|
|
2298
|
+
* Extracted so `refreshAll` and the confirmed-patch resync cannot disagree
|
|
2299
|
+
* about it — and the thing they must agree on is that **optimistic patches
|
|
2300
|
+
* survive**. For a re-auth they belong to in-flight mutations; for the resync
|
|
2301
|
+
* they describe a write the server has already committed. Dropping them here
|
|
2302
|
+
* would be a revert with a longer name, which is the defect the resync exists
|
|
2303
|
+
* to remove.
|
|
2304
|
+
*/
|
|
2305
|
+
private reissue;
|
|
2101
2306
|
/**
|
|
2102
2307
|
* Seed this (fresh) cache with the last-known-good rows held by the cache it
|
|
2103
2308
|
* REPLACES, so a transport rebuild degrades to STALE DATA rather than to
|
|
@@ -2315,6 +2520,98 @@ export declare interface SubscriptionStateWithFallback<T> extends SubscriptionMe
|
|
|
2315
2520
|
readonly isEmpty: boolean;
|
|
2316
2521
|
}
|
|
2317
2522
|
|
|
2523
|
+
/**
|
|
2524
|
+
* May this server event retire the CONFIRMED optimistic patches on an entry?
|
|
2525
|
+
*
|
|
2526
|
+
* **A rollback happens if and only if the write FAILED.** Nothing else — no
|
|
2527
|
+
* timer, no elapsed window, no "probably fine by now" — may take an optimistic
|
|
2528
|
+
* preview away from a write the server acknowledged. This function is the other
|
|
2529
|
+
* half of that rule: it decides when a patch is *superseded* rather than rolled
|
|
2530
|
+
* back, and superseding is only honest when the authoritative state has actually
|
|
2531
|
+
* arrived and actually says something.
|
|
2532
|
+
*
|
|
2533
|
+
* Three answers, each of which was a way to lose a committed write:
|
|
2534
|
+
*
|
|
2535
|
+
* - **delta → YES.** A delta IS the echo of committed writes. This is the
|
|
2536
|
+
* seamless hand-off: base gains the real row and the placeholder goes in the
|
|
2537
|
+
* same update, so there is no flash and no optimistic+real duplicate.
|
|
2538
|
+
* - **error → NO.** An error does not advance `base`; it sets `baseError` and
|
|
2539
|
+
* leaves the rows where they were. Dropping here would discard the preview
|
|
2540
|
+
* in favour of a base the client knows does not reflect the write.
|
|
2541
|
+
* - **snapshot → only if it MOVED the base.** A resync after a missing echo
|
|
2542
|
+
* re-asks the server, and if the answer comes back byte-identical then the
|
|
2543
|
+
* write is still not reflected in it. Retiring the patch on that would be
|
|
2544
|
+
* the reported bug reached by a longer route: the user watches their saved
|
|
2545
|
+
* change disappear, and the client did it while holding evidence that the
|
|
2546
|
+
* server's answer had not changed.
|
|
2547
|
+
*
|
|
2548
|
+
* The comparison only runs when confirmed patches exist, which is rare and
|
|
2549
|
+
* short-lived, so the stringify is not on any hot path.
|
|
2550
|
+
*/
|
|
2551
|
+
export declare const supersedesConfirmedPatches: (event: SubscriptionEvent<unknown>, baseBefore: unknown, baseAfter: unknown) => boolean;
|
|
2552
|
+
|
|
2553
|
+
export declare interface SupervisorHandle {
|
|
2554
|
+
readonly dispose: () => void;
|
|
2555
|
+
/** Force a fresh ws connect for every api (carrying the browser's current
|
|
2556
|
+
* cookies) so a cookie-based login re-resolves the authenticated Subject
|
|
2557
|
+
* in place, without a page reload. No-op for UI-only (no-api) apps. */
|
|
2558
|
+
readonly reconnect: () => void;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
export declare interface SupervisorOptions {
|
|
2562
|
+
readonly apis: ReadonlyArray<MountedApi>;
|
|
2563
|
+
readonly buildClient: BuildClient;
|
|
2564
|
+
/**
|
|
2565
|
+
* Called whenever the resolved-clients map changes (initial resolve,
|
|
2566
|
+
* reconnect swap, api removed). The React layer mirrors this into
|
|
2567
|
+
* setState so the component tree sees the latest clients.
|
|
2568
|
+
*/
|
|
2569
|
+
readonly onChange: (clients: ReadonlyMap<string, ResolvedClient>, initialized: boolean) => void;
|
|
2570
|
+
/**
|
|
2571
|
+
* Override the retry-delay calculation. Default: 500ms × 2^(attempt-1),
|
|
2572
|
+
* capped at 5s after the 5th attempt. Tests inject a deterministic
|
|
2573
|
+
* function so assertions don't depend on the cap shape.
|
|
2574
|
+
*/
|
|
2575
|
+
readonly retryDelayMs?: (attempt: number) => number;
|
|
2576
|
+
/**
|
|
2577
|
+
* Override the stable-connection window (default 5s). When the
|
|
2578
|
+
* elapsed time since `onSocketOpen` exceeds this AND `failureCount`
|
|
2579
|
+
* hasn't moved, the connection is treated as stable and the count
|
|
2580
|
+
* resets to zero so the next failure starts fresh at attempt=1.
|
|
2581
|
+
*/
|
|
2582
|
+
readonly stableWindowMs?: number;
|
|
2583
|
+
/**
|
|
2584
|
+
* Indirection for the JS scheduler. Production uses globalThis's
|
|
2585
|
+
* `setTimeout`/`clearTimeout`/`queueMicrotask`; tests inject fake
|
|
2586
|
+
* versions to step time deterministically.
|
|
2587
|
+
*/
|
|
2588
|
+
readonly scheduler?: {
|
|
2589
|
+
readonly setTimeout: (cb: () => void, ms: number) => unknown;
|
|
2590
|
+
readonly clearTimeout: (handle: unknown) => void;
|
|
2591
|
+
readonly queueMicrotask: (cb: () => void) => void;
|
|
2592
|
+
};
|
|
2593
|
+
/**
|
|
2594
|
+
* Called when an api enters the reconnecting state, and again — via the
|
|
2595
|
+
* returned dispose — when it reconnects or the supervisor is torn down.
|
|
2596
|
+
* Exactly one entry per api per disconnect cycle: repeated retries within one
|
|
2597
|
+
* cycle do NOT call this again, so a status surface cannot stack duplicates.
|
|
2598
|
+
*
|
|
2599
|
+
* The web passes its devtools status bus. RN omits it.
|
|
2600
|
+
*/
|
|
2601
|
+
readonly onReconnecting?: (apiName: string) => () => void;
|
|
2602
|
+
/**
|
|
2603
|
+
* Recovery for a socket that never re-opens (see the note above `ApiSlot`).
|
|
2604
|
+
* `arm` is called once, for an api that HAD been healthy, after
|
|
2605
|
+
* `afterFailures` consecutive failed reconnects; its returned stop function
|
|
2606
|
+
* runs on the next successful open or on dispose. What arming DOES is the
|
|
2607
|
+
* caller's: the web polls the page origin and hard-reloads when it answers.
|
|
2608
|
+
*/
|
|
2609
|
+
readonly wedgeBackstop?: {
|
|
2610
|
+
readonly afterFailures: number;
|
|
2611
|
+
readonly arm: (apiName: string) => () => void;
|
|
2612
|
+
};
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2318
2615
|
/** The tags in `P` whose procedure is of kind `K`. */
|
|
2319
2616
|
export declare type TagsOfKind<P extends ProcedureTypeMap, K extends string> = {
|
|
2320
2617
|
[T in keyof P]: P[T] extends {
|
|
@@ -2727,7 +3024,7 @@ export declare const usePermissions: () => PermissionState_2;
|
|
|
2727
3024
|
* @param options `{ skip }` — defers the subscription. `initialSnapshot`
|
|
2728
3025
|
* comes from the preload payload, not from here.
|
|
2729
3026
|
*/
|
|
2730
|
-
export declare function usePreloadedSubscription<T = unknown>(apiName: string, rpcTag: string, input?: Readonly<Record<string, unknown>>, options?: PreloadedSubscriptionOptions):
|
|
3027
|
+
export declare function usePreloadedSubscription<T = unknown>(apiName: string, rpcTag: string, input?: Readonly<Record<string, unknown>>, options?: PreloadedSubscriptionOptions): PreloadedSubscriptionState<T>;
|
|
2731
3028
|
|
|
2732
3029
|
export declare const usePreview: <Input = Record<string, unknown>>(apiName: string, previewTag: string) => PreviewState<Input>;
|
|
2733
3030
|
|