@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/CHANGELOG.md +1968 -0
- package/dist/index.d.ts +328 -26
- package/dist/index.js +646 -579
- package/package.json +2 -2
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
|
|
356
|
-
*
|
|
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
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
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
|
-
*
|
|
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
|
|
643
|
-
/** Mutation targeting `{table, op:'insert'}` →
|
|
644
|
-
readonly
|
|
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
|
|
794
|
+
readonly update: EntityAction;
|
|
647
795
|
/** Mutation targeting `{table, op:'delete'}` → row delete. */
|
|
648
|
-
readonly
|
|
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) & {
|