@voltro/client 0.33.0 → 0.35.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/dist/index.d.ts CHANGED
@@ -24,6 +24,10 @@ import { WorkflowRunStatus } from '@voltro/protocol';
24
24
  import { WorkflowRunStepRow } from '@voltro/protocol';
25
25
  import { WorkflowUpdateResult } from '@voltro/protocol';
26
26
 
27
+ /** Can the current caller invoke something? `unknown` = only the server knows
28
+ * (a per-resource guard, or an undeclared decision). */
29
+ export declare type AccessDecision = 'allowed' | 'denied' | 'unknown';
30
+
27
31
  export declare interface ActionState<Input, Output> {
28
32
  /** Invoke the action.
29
33
  *
@@ -54,6 +58,8 @@ export declare interface ActionState<Input, Output> {
54
58
  readonly lastResult: Output | undefined;
55
59
  }
56
60
 
61
+ declare type ActionTag<P extends ProcedureTypeMap> = TagsOfKind<P, 'action'>;
62
+
57
63
  /** Mirrors `@voltro/protocol/scopes` ADMIN_SCOPE — an admin satisfies any
58
64
  * required scope. Inlined so the client stays decoupled from server code. */
59
65
  export declare const ADMIN_SCOPE = "admin:full";
@@ -179,6 +185,39 @@ export declare interface ApiHandle {
179
185
  readonly errorBus: RpcErrorBus;
180
186
  }
181
187
 
188
+ /**
189
+ * The typed hooks for one api. Overload ORDER mirrors the untyped
190
+ * `useSubscription` exactly, because that order is the design: a `fallback` /
191
+ * `initialSnapshot` call gets the non-union result and never narrows, a literal
192
+ * `skip: false` keeps the two-state union, and only a DYNAMIC `skip` is handed
193
+ * the third {@link SubscriptionIdle} state. Re-deriving that here rather than
194
+ * widening it would un-narrow every migrated call site.
195
+ */
196
+ export declare interface ApiHooks<P extends ProcedureTypeMap> {
197
+ readonly useSubscription: {
198
+ /** No input needed — the procedure declares no required input field. */
199
+ <Tag extends QueryTag<P> & OptionalInputTag<P>>(tag: Tag): SubscriptionState<QueryData<P, Tag>>;
200
+ /** SSR seed: `data` is present from the first paint, `loading` is false. */
201
+ <Tag extends QueryTag<P>>(tag: Tag, input: ProcedureInput<P[Tag]>, options: SubscriptionOptions<QueryData<P, Tag>> & {
202
+ readonly initialSnapshot: QueryData<P, Tag>;
203
+ }): SubscriptionStateWithFallback<QueryData<P, Tag>>;
204
+ /** `fallback`: `data` is always present, `loading` still tells the truth. */
205
+ <Tag extends QueryTag<P>>(tag: Tag, input: ProcedureInput<P[Tag]>, options: SubscriptionOptions<QueryData<P, Tag>> & {
206
+ readonly fallback: QueryData<P, Tag>;
207
+ }): SubscriptionStateWithFallback<QueryData<P, Tag>>;
208
+ /** The two-state union — `!loading` proves `data` is present. */
209
+ <Tag extends QueryTag<P>>(tag: Tag, input: ProcedureInput<P[Tag]>, options?: SubscriptionOptions<QueryData<P, Tag>> & {
210
+ readonly skip?: false;
211
+ }): SubscriptionState<QueryData<P, Tag>>;
212
+ /** Dynamic `skip` — "deliberately not asking" becomes a real outcome. */
213
+ <Tag extends QueryTag<P>>(tag: Tag, input: ProcedureInput<P[Tag]>, options: SubscriptionOptions<QueryData<P, Tag>> & {
214
+ readonly skip?: boolean;
215
+ }): SubscriptionState<QueryData<P, Tag>> | SubscriptionIdle;
216
+ };
217
+ readonly useMutation: <Tag extends MutationTag<P>>(tag: Tag) => MutationBuilder<ProcedureInput<P[Tag]>, ProcedureOutput<P[Tag]>>;
218
+ readonly useAction: <Tag extends ActionTag<P>>(tag: Tag) => ActionState<ProcedureInput<P[Tag]>, ProcedureOutput<P[Tag]>>;
219
+ }
220
+
182
221
  /**
183
222
  * Apply preload seeds from the hydration payload, BEFORE the first client
184
223
  * render. Idempotent (a Map upsert) so a re-hydration or a repeated key is
@@ -243,7 +282,9 @@ export declare interface AutoApplyTarget {
243
282
  * The returned `runtime` owns the socket; dispose it (`runtime.dispose()`, after
244
283
  * `cache.destroy(runtime)`) to tear the connection down. A fresh cache is bound
245
284
  * per resolved runtime — cross-runtime cache reuse is unsafe (its fibers
246
- * reference the old runtime).
285
+ * reference the old runtime). Carrying the previous cache's DATA forward is a
286
+ * different thing and is safe: see `SubscriptionCache.seedStaleFrom`, which
287
+ * copies rows (never fibers) and is gated by the caller to same-subject swaps.
247
288
  *
248
289
  * Rejects if the runtime fails to resolve the client (e.g. the socket never
249
290
  * opens); the caller's reconnect logic decides what to do with that.
@@ -352,8 +393,8 @@ export declare const canCall: (subjectScopes: ReadonlyArray<string>, required: s
352
393
  * An empty requirement is allowed (nothing is being demanded). Pure. */
353
394
  export declare const canCallAny: (subjectScopes: ReadonlyArray<string>, required: ReadonlyArray<string>) => boolean;
354
395
 
355
- /** Structural mirror of @voltro/cli's `CapabilityManifestTable` column — the
356
- * client stays decoupled from the (node-only) cli package. */
396
+ /** Structural mirror of @voltro/cli's manifest column — the client stays
397
+ * decoupled from the (node-only) cli package. */
357
398
  export declare interface CapabilityColumn {
358
399
  readonly name: string;
359
400
  readonly type: string;
@@ -362,6 +403,26 @@ export declare interface CapabilityColumn {
362
403
  readonly refersTo?: string;
363
404
  /** Closed value set (`text().oneOf([...])`) → render a select. */
364
405
  readonly enum?: ReadonlyArray<string>;
406
+ /**
407
+ * `.serverOnly()` — this column never crosses ANY wire, and the runtime
408
+ * REFUSES a mutation input that sets it (`assertNoServerOnlyInput`). The one
409
+ * axis of the three that is a statement about wire exposure, and therefore
410
+ * the only one a schema-driven UI may act on by omitting a field.
411
+ */
412
+ readonly serverOnly?: boolean;
413
+ /**
414
+ * `.encrypted()` — ciphertext at rest. Ordinary wire data to the app's own
415
+ * procedures, which read it decrypted. NEVER treat this as a reason to hide
416
+ * a field: encryption-at-rest and wire exposure are different questions, and
417
+ * conflating them hides columns the user is meant to edit.
418
+ */
419
+ readonly encrypted?: boolean;
420
+ /**
421
+ * `.sensitive(class)` — the declared class. The EXPORT-masking axis: the
422
+ * value is real wire data the app renders normally; what has to mask it is a
423
+ * bulk export / CSV / copy-out affordance.
424
+ */
425
+ readonly sensitive?: string;
365
426
  }
366
427
 
367
428
  /** Structural mirror of @voltro/cli's `CapabilityManifest` (served at
@@ -374,6 +435,9 @@ export declare interface CapabilityManifest {
374
435
  }>;
375
436
  readonly widgets: ReadonlyArray<string>;
376
437
  readonly tables: ReadonlyArray<CapabilityTable>;
438
+ /** Every scope the installed plugins DECLARE. Empty means "not declared" —
439
+ * never "no scopes exist". */
440
+ readonly scopes?: ReadonlyArray<string>;
377
441
  }
378
442
 
379
443
  export declare interface CapabilityManifestState {
@@ -394,6 +458,14 @@ export declare interface CapabilityProcedure {
394
458
  readonly table: string;
395
459
  readonly op: string;
396
460
  }>;
461
+ /**
462
+ * The authorization this procedure declares. `undefined` means the descriptor
463
+ * declared NEITHER `guards:` nor `openAccess:` — undecided, which is not the
464
+ * same as open. Under `security.defaultDeny` such a procedure cannot boot, so
465
+ * it is a narrow case in a running app; it is still reported honestly rather
466
+ * than guessed at.
467
+ */
468
+ readonly guards?: ReadonlyArray<ManifestGuard>;
397
469
  }
398
470
 
399
471
  /** A user table in the manifest (framework `_voltro_*` tables are excluded). */
@@ -402,6 +474,11 @@ export declare interface CapabilityTable {
402
474
  readonly columns: ReadonlyArray<CapabilityColumn>;
403
475
  readonly reactive: boolean;
404
476
  readonly framework: boolean;
477
+ /** The single primary-key column, when the table has one. */
478
+ readonly pkColumn?: string;
479
+ /** False when no single primary key exists — a row-keyed update/delete
480
+ * cannot address a row, so the entity is list-only. */
481
+ readonly editable?: boolean;
405
482
  }
406
483
 
407
484
  /** Clear the buffer (devtools "Clear" button). */
@@ -562,6 +639,19 @@ export declare type CopilotAnswer<Row = Record<string, unknown>> = {
562
639
  */
563
640
  export declare const createAnalytics: <const E extends ReadonlyArray<EventDescriptor<string, unknown>>>(events: E, sink: TrackingSink) => Analytics<E>;
564
641
 
642
+ /**
643
+ * Bind the typed hook surface of ONE api.
644
+ *
645
+ * `Procedures` is the generated `AppProcedures` map from that api's
646
+ * `rpcGroup.generated.ts`; `apiName` is the key this web app registered it under
647
+ * in `app.config.ts`.
648
+ *
649
+ * Call it ONCE, at module scope, and destructure the result — see the file
650
+ * header for why the destructured form is the one that keeps the React lint
651
+ * rules working.
652
+ */
653
+ export declare const createHooks: <Procedures extends ProcedureTypeMap>(apiName: string) => ApiHooks<Procedures>;
654
+
565
655
  export declare interface DataCopilotState<Row = Record<string, unknown>> {
566
656
  /** Ask a natural-language question. Resolves the typed answer (also stored
567
657
  * on `.answer`); rejects only on a transport / execution error. */
@@ -595,6 +685,19 @@ export declare interface DataTableState<Row> {
595
685
  readonly hasMore: boolean;
596
686
  }
597
687
 
688
+ /**
689
+ * Decide, from the DECLARED access and the subject's global scopes, whether the
690
+ * caller may invoke a procedure. Pure — the matcher core, mirroring the
691
+ * server's `checkGuards`: entries are ANDed, `mode` controls AND/OR within one
692
+ * entry, and `admin:full` satisfies anything.
693
+ *
694
+ * `guards === undefined` (neither `guards:` nor `openAccess:`) is `unknown`,
695
+ * not `allowed`: an undeclared procedure is refused outright under
696
+ * `security.defaultDeny`, so calling it callable would be a guess in the
697
+ * dangerous direction.
698
+ */
699
+ export declare const decideAccess: (guards: ReadonlyArray<ManifestGuard> | undefined, subjectScopes: ReadonlyArray<string>) => AccessDecision;
700
+
598
701
  /** All defined stores, in definition order. */
599
702
  export declare const definedStores: () => ReadonlyArray<StoreHandle<never>>;
600
703
 
@@ -621,38 +724,76 @@ export declare const defineTracking: <P = Record<string, unknown>>(name: string,
621
724
  /**
622
725
  * Pure projection: turn a capability manifest into one `EntityAdminSpec` per
623
726
  * user table, each joined to the query that lists it and the mutations that
624
- * create/update/delete it. The auto-admin template maps over the result to
625
- * render a live, permission-gated back-office; everything here is derived from
626
- * what the app actually exposes, so an entity with no list query simply renders
627
- * no table rather than binding to a tag that 404s.
727
+ * create/update/delete it, and each carrying the access those procedures
728
+ * DECLARE.
729
+ *
730
+ * Everything here is derived from what the app actually exposes: an entity with
731
+ * no list query renders no table rather than binding to a tag that does not
732
+ * resolve, and an action's gate comes from the procedure's own declaration
733
+ * rather than a scope string this function invented.
734
+ *
735
+ * The invented ones were the bug worth naming. Gating on `<table>:create` hides
736
+ * the create form from every caller of every app that did not happen to name
737
+ * its scope that way — a total outage of the affordance, wearing a permission
738
+ * check's clothes, and invisible in any demo that seeds `admin:full`.
628
739
  */
629
740
  export declare const deriveEntityAdmins: (manifest: CapabilityManifest) => ReadonlyArray<EntityAdminSpec>;
630
741
 
631
742
  export declare const enqueueEntry: (queue: Outbox, id: string, tag: string, input: unknown) => Outbox;
632
743
 
744
+ /**
745
+ * One operation of an entity's admin surface.
746
+ *
747
+ * `tag` is `undefined` when the app exposes NO procedure for that op — the
748
+ * affordance does not exist and must not be rendered. When it does exist,
749
+ * `guards` is that procedure's own declaration, so the UI gate and the server
750
+ * check read the same data.
751
+ */
752
+ export declare interface EntityAction {
753
+ /** The procedure to invoke, when one exists. */
754
+ readonly tag?: string;
755
+ /** The access that procedure DECLARES. `undefined` = undeclared (or no
756
+ * procedure at all). Feed to `useAccessDecision` / `decideAccess`. */
757
+ readonly guards?: ReadonlyArray<ManifestGuard>;
758
+ }
759
+
633
760
  /** One entity's admin surface: the table + the procedures that read/write it,
634
- * plus the conventional scope strings the UI gates writes on. A tag is
635
- * `undefined` when the app exposes no procedure for that op (the admin then
636
- * renders that affordance read-only / hidden). */
761
+ * each carrying the access the SERVER declares for it. */
637
762
  export declare interface EntityAdminSpec {
638
763
  readonly table: string;
764
+ /**
765
+ * The columns an admin may render. `.serverOnly()` columns are EXCLUDED —
766
+ * they never cross the wire, so a column header for one is always blank and a
767
+ * form field for one is refused by the runtime. `.sensitive()` and
768
+ * `.encrypted()` columns ARE present: neither axis is about the wire, and
769
+ * dropping them would hide data the operator is meant to see and edit.
770
+ */
639
771
  readonly columns: ReadonlyArray<CapabilityColumn>;
772
+ /**
773
+ * The `.serverOnly()` column names, reported so a UI can say WHY a column is
774
+ * absent — and so a hand-written form never binds one. The runtime's
775
+ * `assertNoServerOnlyInput` refuses them at the mutation boundary; this is
776
+ * the same rule one layer earlier, where it can be a design affordance
777
+ * instead of a runtime error.
778
+ */
779
+ readonly serverOnlyColumns: ReadonlyArray<string>;
780
+ /** Columns carrying `.sensitive(class)` — real wire data, but a bulk-export
781
+ * affordance has to mask them. */
782
+ readonly sensitiveColumns: ReadonlyArray<string>;
640
783
  readonly reactive: boolean;
784
+ /** The primary-key column row-keyed actions target. `undefined` → no single
785
+ * pk, so the entity is list-only. */
786
+ readonly pkColumn?: string;
787
+ /** Can a row be addressed for update/delete at all? */
788
+ readonly editable: boolean;
641
789
  /** Query whose `source` is this table → drives <DataTable>. */
642
- readonly listTag?: string;
643
- /** Mutation targeting `{table, op:'insert'}` → drives the create <AutoForm>. */
644
- readonly createTag?: string;
790
+ readonly list: EntityAction;
791
+ /** Mutation targeting `{table, op:'insert'}` → the create <AutoForm>. */
792
+ readonly create: EntityAction;
645
793
  /** Mutation targeting `{table, op:'update'}` → row edit. */
646
- readonly updateTag?: string;
794
+ readonly update: EntityAction;
647
795
  /** Mutation targeting `{table, op:'delete'}` → row delete. */
648
- readonly deleteTag?: string;
649
- /** Conventional scope strings the admin gates on via `useCan`. The app maps
650
- * these to its real RBAC scopes; absent a scope registry in the manifest
651
- * this convention is the seam (gate at the action level — round-2/07 adds
652
- * row-level authority). */
653
- readonly createScope: string;
654
- readonly writeScope: string;
655
- readonly deleteScope: string;
796
+ readonly delete: EntityAction;
656
797
  }
657
798
 
658
799
  declare interface EntryState<T> {
@@ -872,6 +1013,31 @@ export declare const makePreloadSeedBag: () => PreloadSeedBag;
872
1013
 
873
1014
  export declare const makeStoreSeedBag: () => StoreSeedBag;
874
1015
 
1016
+ /**
1017
+ * The access a procedure DECLARES, as data — the same declaration the server
1018
+ * enforces, mirrored from @voltro/cli's `SerialisedGuard`.
1019
+ *
1020
+ * Three variants for the three states an access decision has. `open` is the
1021
+ * erased form of `openAccess: '<why>'`; it is a DECISION, and distinguishing it
1022
+ * from "nobody declared anything" is the whole reason it travels.
1023
+ */
1024
+ export declare type ManifestGuard = {
1025
+ readonly kind: 'scope';
1026
+ readonly scope: ReadonlyArray<string>;
1027
+ /** `all` — every scope; `any` — at least one. */
1028
+ readonly mode: 'all' | 'any';
1029
+ /** The guard carries a `resource` extractor, so the REAL check is per-row
1030
+ * and a global scope answer is incomplete by construction. */
1031
+ readonly resourceScoped: boolean;
1032
+ } | {
1033
+ readonly kind: 'policy';
1034
+ readonly action: string;
1035
+ readonly resourceType: string;
1036
+ } | {
1037
+ readonly kind: 'open';
1038
+ readonly reason: string;
1039
+ };
1040
+
875
1041
  /** Exported for tests: the inspect URL this hook fetches. */
876
1042
  export declare const manifestUrl: (base: string) => string;
877
1043
 
@@ -957,6 +1123,18 @@ export declare interface MutationState<Input, Output> {
957
1123
 
958
1124
  export declare type MutationStatus = 'pending' | 'success' | 'error';
959
1125
 
1126
+ declare type MutationTag<P extends ProcedureTypeMap> = TagsOfKind<P, 'mutation'>;
1127
+
1128
+ /**
1129
+ * The reason a procedure declared itself open, or `undefined` if it did not.
1130
+ *
1131
+ * A helper rather than a `.find(...)` at each call site because `Array.find`
1132
+ * does NOT narrow a discriminated union — it hands back `ManifestGuard`, so
1133
+ * reading `.reason` off it is a type error, and the obvious workaround
1134
+ * (`as`-casting) would read a field that may not be there.
1135
+ */
1136
+ export declare const openAccessReason: (guards: ReadonlyArray<ManifestGuard> | undefined) => string | undefined;
1137
+
960
1138
  export declare interface OptimisticContext {
961
1139
  /** Stage an optimistic patch keyed by `queryKey` (same convention as
962
1140
  * useSubscription's: `[rpcTag, input]`). The patch is automatically
@@ -988,6 +1166,15 @@ declare interface OptimisticPatch<T = unknown> {
988
1166
  confirmed?: boolean;
989
1167
  }
990
1168
 
1169
+ /**
1170
+ * The tags whose input has no REQUIRED field, so the input argument may be
1171
+ * omitted. Everything else must pass one — which is what makes a forgotten
1172
+ * required input a compile error rather than a runtime `undefined`.
1173
+ */
1174
+ export declare type OptionalInputTag<P extends ProcedureTypeMap> = {
1175
+ [T in keyof P]: Record<string, never> extends ProcedureInput<P[T]> ? T : never;
1176
+ }[keyof P] & keyof P;
1177
+
991
1178
  export declare type Outbox = ReadonlyArray<OutboxEntry>;
992
1179
 
993
1180
  export declare interface OutboxControls {
@@ -1100,6 +1287,43 @@ export declare interface PreviewState<Input> {
1100
1287
  readonly error: unknown | undefined;
1101
1288
  }
1102
1289
 
1290
+ /** The value a procedure's `input` schema decodes to on the client. */
1291
+ export declare type ProcedureInput<D> = D extends {
1292
+ readonly input: infer I extends Schema.Schema.Any;
1293
+ } ? Schema.Schema.Type<I> : never;
1294
+
1295
+ /**
1296
+ * The value a procedure resolves to on the client.
1297
+ *
1298
+ * `output` for a query / mutation / action; `element` for a stream, whose
1299
+ * per-element schema is named differently in its descriptor.
1300
+ */
1301
+ export declare type ProcedureOutput<D> = D extends {
1302
+ readonly output: infer O extends Schema.Schema.Any;
1303
+ } ? Schema.Schema.Type<O> : D extends {
1304
+ readonly element: infer E extends Schema.Schema.Any;
1305
+ } ? Schema.Schema.Type<E> : never;
1306
+
1307
+ /**
1308
+ * The shape `createHooks` needs from ONE generated procedure entry: enough to
1309
+ * tell queries from mutations from actions. Deliberately structural — the
1310
+ * generated map hands over the real descriptor types (`QueryProcedureDescriptor`
1311
+ * & co.), and this package must not import them value-level to say so.
1312
+ */
1313
+ export declare interface ProcedureTypeLike {
1314
+ readonly kind: string;
1315
+ }
1316
+
1317
+ /**
1318
+ * The generated tag → descriptor-type map. Emitted by the codegen as
1319
+ * `AppProcedures` in `rpcGroup.generated.ts`.
1320
+ *
1321
+ * It is emitted as a type ALIAS, not an interface, on purpose: an interface has
1322
+ * no implicit index signature, so `interface AppProcedures { … }` would not
1323
+ * satisfy this constraint and every binding would fail to compile.
1324
+ */
1325
+ export declare type ProcedureTypeMap = Readonly<Record<string, ProcedureTypeLike>>;
1326
+
1103
1327
  export declare interface ProvenanceResult {
1104
1328
  readonly table: string;
1105
1329
  readonly id: string;
@@ -1140,6 +1364,9 @@ export declare const publishClientError: (event: ClientErrorEvent) => void;
1140
1364
  /** Publish a started client trace. Cheap + sync; safe on the hot path. */
1141
1365
  export declare const publishClientTrace: (event: ClientTraceEvent) => void;
1142
1366
 
1367
+ /** What `useSubscription` hands back as `data` for one tag. */
1368
+ declare type QueryData<P extends ProcedureTypeMap, Tag extends keyof P> = WithOptimistic<ProcedureOutput<P[Tag]>>;
1369
+
1143
1370
  export declare interface QueryFieldState {
1144
1371
  readonly options: ReadonlyArray<FieldOption>;
1145
1372
  readonly loading: boolean;
@@ -1160,6 +1387,8 @@ export declare interface QueryFiltersState<Row> {
1160
1387
  readonly error: unknown | undefined;
1161
1388
  }
1162
1389
 
1390
+ declare type QueryTag<P extends ProcedureTypeMap> = TagsOfKind<P, 'query'>;
1391
+
1163
1392
  /**
1164
1393
  * The preloaded snapshot for `(api, rpcTag, input)`, or `undefined` when none was
1165
1394
  * seeded. On the SERVER it reads the current request's bag directly, so the SSR
@@ -1190,6 +1419,10 @@ export declare const replayable: (queue: Outbox) => Outbox;
1190
1419
  * the route boundary won't catch the throw. */
1191
1420
  export declare const reportClientError: (error: unknown, context?: Record<string, unknown>) => void;
1192
1421
 
1422
+ /** The scope strings a procedure's guards require, flattened for display.
1423
+ * Empty for an open / policy-only / undeclared procedure. */
1424
+ export declare const requiredScopes: (guards: ReadonlyArray<ManifestGuard> | undefined) => ReadonlyArray<string>;
1425
+
1193
1426
  /** Test-only: reset the one-time no-provider dev-warning state (the
1194
1427
  * module-global flags + any pending grace-period timer). */
1195
1428
  export declare const _resetFrameworkRuntimesWarning: () => void;
@@ -1865,6 +2098,45 @@ export declare class SubscriptionCache {
1865
2098
  * promote naturally on their next subscriber.
1866
2099
  */
1867
2100
  refreshAll(runtime: AnyRuntime): void;
2101
+ /**
2102
+ * Seed this (fresh) cache with the last-known-good rows held by the cache it
2103
+ * REPLACES, so a transport rebuild degrades to STALE DATA rather than to
2104
+ * skeletons.
2105
+ *
2106
+ * A reconnect builds an entirely new client stack — new runtime, new socket,
2107
+ * new cache — and the new cache starts empty, so every live `useSubscription`
2108
+ * went `data: undefined` → `loading: true` and the whole app blanked until
2109
+ * each stream's first snapshot round-tripped. Carrying the previous `base`
2110
+ * over is the same trick `initialSnapshot` plays for SSR: render real (if
2111
+ * momentarily old) data now, let the first server snapshot replace it.
2112
+ *
2113
+ * What is carried and what is NOT:
2114
+ *
2115
+ * - Only entries that actually HOLD server data (`base !== undefined`).
2116
+ * A pending or cold-start-failed entry has nothing better than the
2117
+ * loading state it is already in.
2118
+ * - `baseError` is dropped: the new transport gets to re-establish the
2119
+ * truth, and a stale error banner over live data is worse than neither.
2120
+ * - Optimistic patches are dropped. They belong to mutations that were in
2121
+ * flight on the transport that just died; their success/failure — and so
2122
+ * their revert — is resolved against the OLD cache. Carrying them here
2123
+ * would strand a preview no mutation can ever retract.
2124
+ * - Seeded entries carry no fiber and no fetch, exactly like the stub
2125
+ * entries a pre-mount optimistic patch creates. The first `subscribe()`
2126
+ * promotes one: it saves the fetch and forks the stream, with the seeded
2127
+ * `base` still in place, so the component never sees `undefined`.
2128
+ *
2129
+ * **Caller contract — this must only be used for a SAME-SUBJECT swap.** The
2130
+ * caller (the api supervisor) gates it on the rebuild being transport-driven.
2131
+ * A rebuild that exists to re-resolve WHO the connection is (login, logout,
2132
+ * tenant switch) must NOT seed: the next subject may be entitled to strictly
2133
+ * less, and painting the previous subject's rows into their screens is a data
2134
+ * exposure, not a UX win. That is also why `refreshAll` above clears `base`
2135
+ * outright — same reasoning, same boundary.
2136
+ *
2137
+ * Returns the number of entries seeded (diagnostics / tests).
2138
+ */
2139
+ seedStaleFrom(previous: SubscriptionCache): number;
1868
2140
  private create;
1869
2141
  private createStub;
1870
2142
  private freshState;
@@ -1872,6 +2144,10 @@ export declare class SubscriptionCache {
1872
2144
  private invalidate;
1873
2145
  private getSnapshot;
1874
2146
  private unsubscribe;
2147
+ /** Start (or restart) the inactive-TTL countdown for `entry`: unless
2148
+ * something subscribes first, interrupt its fiber and drop it. Shared by
2149
+ * the last-subscriber-out path and by `seedStaleFrom`'s unclaimed seeds. */
2150
+ private scheduleEviction;
1875
2151
  }
1876
2152
 
1877
2153
  export declare interface SubscriptionCacheOptions {
@@ -2039,6 +2315,13 @@ export declare interface SubscriptionStateWithFallback<T> extends SubscriptionMe
2039
2315
  readonly isEmpty: boolean;
2040
2316
  }
2041
2317
 
2318
+ /** The tags in `P` whose procedure is of kind `K`. */
2319
+ export declare type TagsOfKind<P extends ProcedureTypeMap, K extends string> = {
2320
+ [T in keyof P]: P[T] extends {
2321
+ readonly kind: K;
2322
+ } ? T : never;
2323
+ }[keyof P] & keyof P;
2324
+
2042
2325
  /** A map entry: a bare event name, or a function of the component's props.
2043
2326
  *
2044
2327
  * `P` is the component's prop type. It defaults to an untyped bag so a
@@ -2238,6 +2521,10 @@ export declare interface UploadResult {
2238
2521
 
2239
2522
  export declare type UploadStatus = 'idle' | 'uploading' | 'success' | 'error';
2240
2523
 
2524
+ /** Reactively decide access for one declaration, against the scopes fed to
2525
+ * `<PermissionProvider>`. The hook form of `decideAccess`. */
2526
+ export declare const useAccessDecision: (guards: ReadonlyArray<ManifestGuard> | undefined) => AccessDecision;
2527
+
2241
2528
  export declare const useAction: <Input = unknown, Output = unknown>(apiName: string, rpcTag: string) => ActionState<Input, Output>;
2242
2529
 
2243
2530
  export declare const useAgent: (apiName: string, rpcTag: string) => AgentControls;
@@ -2702,16 +2989,31 @@ export declare interface WindowSpec {
2702
2989
  * them. The server mutation funnel reads it to dedupe a retried mutation. */
2703
2990
  export declare const withIdempotencyKey: <A, E, R>(call: Effect.Effect<A, E, R>, key: string) => Effect.Effect<A, E, R>;
2704
2991
 
2992
+ /**
2993
+ * What a subscription's `data` actually holds: the query's output, with the
2994
+ * auto-optimistic marker the CLIENT adds.
2995
+ *
2996
+ * `SubscriptionCache` stamps `optimistic: true` on every row it stages on a
2997
+ * mutation's behalf (insert prepends one, update merges one), so a live list
2998
+ * carries a field the SERVER schema does not declare and never will. That is
2999
+ * precisely why every hand-annotated call site wrote its own row mirror with
3000
+ * `readonly optimistic?: boolean` bolted on — inferring the output alone would
3001
+ * have made `row.optimistic` a type error at the exact sites the framework's
3002
+ * headline feature is used.
3003
+ *
3004
+ * Only ARRAY outputs are widened, because only rows are staged. A scalar or
3005
+ * object output is returned as the server declared it.
3006
+ */
3007
+ export declare type WithOptimistic<T> = T extends ReadonlyArray<infer Element> ? ReadonlyArray<Element extends object ? Element & {
3008
+ readonly optimistic?: boolean;
3009
+ } : Element> : T;
3010
+
2705
3011
  export declare interface WorkflowClientMessages {
2706
3012
  readonly signals?: Readonly<Record<string, unknown>>;
2707
3013
  readonly updates?: Readonly<Record<string, {
2708
3014
  readonly payload: unknown;
2709
3015
  readonly result: unknown;
2710
3016
  }>>;
2711
- readonly queries?: Readonly<Record<string, {
2712
- readonly payload: unknown;
2713
- readonly result: unknown;
2714
- }>>;
2715
3017
  }
2716
3018
 
2717
3019
  export declare type WorkflowDomainEventsState = (SubscriptionState<ReadonlyArray<WorkflowDomainEventRow>> | SubscriptionIdle) & {