@voltro/runtime 0.51.0 → 0.53.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
@@ -69,6 +69,7 @@ import { RetentionSource } from '@voltro/database';
69
69
  import { RetentionSpec } from '@voltro/database';
70
70
  import { retentionTtlMsFromEnv } from '@voltro/database';
71
71
  import { Row } from '@voltro/database';
72
+ import { RowPatch } from '@voltro/protocol';
72
73
  import { Rpc } from '@effect/rpc';
73
74
  import { RpcGroup } from '@effect/rpc';
74
75
  import { RpcInterceptor } from '@voltro/protocol';
@@ -97,6 +98,9 @@ import { TenantRowNotFound } from '@voltro/protocol';
97
98
  import { TenantScopeViolation } from '@voltro/protocol';
98
99
  import { Tracer } from 'effect';
99
100
  import { Unauthenticated } from '@voltro/protocol';
101
+ import { ValidationError } from '@voltro/protocol';
102
+ import { ValidationErrors } from '@voltro/protocol';
103
+ import { WebSocketGatewayRoute } from '@voltro/protocol';
100
104
  import { WorkflowParentClosePolicy } from '@voltro/protocol';
101
105
  import { WorkflowRunHandle } from '@voltro/protocol';
102
106
  import { WorkflowUpdateResult } from '@voltro/protocol';
@@ -150,6 +154,28 @@ export declare interface ActivitySignals {
150
154
  readonly activeWorkflowExecutions: () => number;
151
155
  }
152
156
 
157
+ /**
158
+ * Adopt a detached subscription for a resuming client at `fromRevision`.
159
+ * Undefined — identity unknown, entry dead (an error/revocation while
160
+ * offline), or the revision does not chain — means: subscribe fresh, send a
161
+ * snapshot. A failed adoption EXPIRES the entry: its ring cannot serve this
162
+ * client and keeping a half-dead entry around only risks a second caller
163
+ * adopting state the first one invalidated.
164
+ */
165
+ export declare const adoptDetached: (identity: ResumeIdentity, fromRevision: number, now?: number, tunables?: ResumeTunables) => AdoptedSubscription | undefined;
166
+
167
+ export declare interface AdoptedSubscription {
168
+ /** The deltas the client missed, in order — replay before going live. */
169
+ readonly deltas: ReadonlyArray<{
170
+ readonly revision: number;
171
+ readonly emittedAt: number;
172
+ readonly patch: RowPatch;
173
+ }>;
174
+ /** Redirect this to the new stream to go live on the SAME revision line. */
175
+ readonly sink: ResumeSink;
176
+ readonly unsubscribe: () => void;
177
+ }
178
+
153
179
  export declare interface AggregateContext {
154
180
  /** Read-side store for source queries. Note: the build function
155
181
  * runs OUTSIDE any per-request subject, so the auto-stamping a
@@ -807,6 +833,15 @@ export declare interface AnalyticsTopEntry {
807
833
  readonly value: number;
808
834
  }
809
835
 
836
+ /**
837
+ * Answer a `resumeFrom` for this identity. `fromRevision` is the last
838
+ * revision the CLIENT materialised. Only revisions inside
839
+ * [baseRevision, headRevision] chain; everything else — unknown key, aged
840
+ * ring, a revision the ring no longer covers, a revision AHEAD of the head
841
+ * (a client from a different life) — is a snapshot.
842
+ */
843
+ export declare const answerResume: (identity: ResumeIdentity, fromRevision: number, now?: number, tunables?: ResumeTunables) => ResumeAnswer;
844
+
810
845
  declare type AnyRow = Record<string, unknown>;
811
846
 
812
847
  declare type AnyRow_2 = Record<string, unknown>;
@@ -989,6 +1024,10 @@ export declare interface AppContext {
989
1024
  * `ctx.access.has(scope)` / `yield* ctx.access.require(scope)` instead of
990
1025
  * reaching into `ctx.request.subject.scopes`. Always present. */
991
1026
  readonly access: AppAccess;
1027
+ /** Typed server-side field validation — `yield* ctx.validation.fail('email',
1028
+ * 'validation.emailTaken')` routes to the form field that submitted it.
1029
+ * Always present. */
1030
+ readonly validation: AppValidation;
992
1031
  /** Cache — always present (memory backend by default). Use
993
1032
  * `ctx.cache.wrap(key, { ttlMs, tags }, () => expensive())` to cache
994
1033
  * derived work; tagged entries auto-invalidate when a mutation writes a
@@ -1073,6 +1112,28 @@ export declare interface AppContext {
1073
1112
  */
1074
1113
  export declare const applyConnectionCredential: (clientId: number, headers: Record<string, string | undefined>) => Record<string, string | undefined>;
1075
1114
 
1115
+ /**
1116
+ * Reconcile a mutation target's declared junction `relations:` against the
1117
+ * input — INSIDE the mutation's transaction, through `ctx.store`, so the
1118
+ * links ride the same undo capture, rule evaluation and rollback as the
1119
+ * mutation's own writes.
1120
+ *
1121
+ * Semantics that must not drift:
1122
+ * - an ABSENT input field touches nothing (absent ≠ empty);
1123
+ * - an empty array is the explicit clear;
1124
+ * - a non-array value is a refusal naming the field — silently ignoring it
1125
+ * would read as "saved" while the links stayed stale;
1126
+ * - the row id is `output.id`, else `input.id`; with neither the declaration
1127
+ * is unusable and says so.
1128
+ */
1129
+ export declare const applyDeclaredRelations: (params: {
1130
+ readonly store: RelationLinksStore;
1131
+ readonly target: MutationLike["descriptor"]["target"];
1132
+ readonly procedure: string;
1133
+ readonly input: unknown;
1134
+ readonly output: unknown;
1135
+ }) => Promise<void>;
1136
+
1076
1137
  /**
1077
1138
  * Pure helper backing `ctx.store.applyDefined`. Picks the listed keys from
1078
1139
  * `input` whose value is not `undefined`, so a PATCH sets exactly the fields
@@ -1219,6 +1280,35 @@ export declare interface AppSupervisor {
1219
1280
  stop: () => Promise<void>;
1220
1281
  }
1221
1282
 
1283
+ /** Typed server-side FIELD validation — the executor half of form error
1284
+ * routing. Each helper fails the procedure with a wire `ValidationError`
1285
+ * (auto-merged into every mutation's and action's error union), which the
1286
+ * form binding routes to `errors[field]` instead of `submitError`. */
1287
+ export declare interface AppValidation {
1288
+ /** Fail with ONE field error. `message` is a message id
1289
+ * (`'validation.emailTaken'`) resolved by the client's validation catalog.
1290
+ *
1291
+ * if (await emailTaken(input.email)) {
1292
+ * return yield* ctx.validation.fail('email', 'validation.emailTaken')
1293
+ * }
1294
+ */
1295
+ readonly fail: (field: string, message: string, params?: Record<string, unknown>) => Effect.Effect<never, ValidationError>;
1296
+ /** Fail with several field errors in one round trip. Refuses an empty
1297
+ * list — a failure with nothing to show is a bug at the call site. */
1298
+ readonly failAll: (issues: ReadonlyArray<{
1299
+ readonly field: string;
1300
+ readonly message: string;
1301
+ readonly params?: Record<string, unknown>;
1302
+ }>) => Effect.Effect<never, ValidationErrors>;
1303
+ /** Guard form: succeed when `condition` holds, else fail the field. */
1304
+ readonly require: (condition: boolean, field: string, message: string, params?: Record<string, unknown>) => Effect.Effect<void, ValidationError>;
1305
+ }
1306
+
1307
+ /** The ONE `ctx.validation` value. Pure and subject-free, so a single shared
1308
+ * instance serves every context — request, schedule, workflow, test — and
1309
+ * the builders cannot each assemble a drifting copy. */
1310
+ export declare const appValidation: AppValidation;
1311
+
1222
1312
  /** Effect-style guard for handlers — throws the typed {@link AccessDenied} on a
1223
1313
  * deny so it surfaces to the client typed. */
1224
1314
  export declare const assertCan: (subject: RebacSubject, action: string, resource: RebacResource, deps: CanDeps) => void;
@@ -1622,7 +1712,14 @@ reauthorize?: (subject: Subject) => () => Promise<unknown>,
1622
1712
  */
1623
1713
  refilter?: (subject: Subject) => () => Promise<RowFilterScope>) => Stream.Stream<SubscriptionEvent<T>, never, SubjectService | ConnectionInfo>;
1624
1714
 
1625
- export declare const bindSubscriptionUntyped: (descriptor: QueryDescriptor, dispatcher: Dispatcher, context: RuntimeContext, indexHint: MatcherIndexHint | undefined, label: string, cacheBinding: SnapshotCacheBinding | undefined, reauthorize: (() => Promise<unknown>) | undefined, refilter?: (() => Promise<RowFilterScope>) | undefined) => Stream.Stream<SubscriptionEvent<ReadonlyArray<Row>>, never, never>;
1715
+ export declare const bindSubscriptionUntyped: (descriptor: QueryDescriptor, dispatcher: Dispatcher, context: RuntimeContext, indexHint: MatcherIndexHint | undefined, label: string, cacheBinding: SnapshotCacheBinding | undefined, reauthorize: (() => Promise<unknown>) | undefined, refilter?: (() => Promise<RowFilterScope>) | undefined, backpressure?: {
1716
+ readonly maxBufferedBytes: number;
1717
+ readonly overrunAfterMs: number;
1718
+ readonly oversizedEventBytes: number;
1719
+ }, resume?: {
1720
+ readonly input: unknown;
1721
+ readonly fromRevision?: number;
1722
+ }) => Stream.Stream<SubscriptionEvent<ReadonlyArray<Row>>, never, never>;
1626
1723
 
1627
1724
  export declare interface BrandedScheduleDefinition extends ScheduleDefinition {
1628
1725
  readonly [SCHEDULE_BRAND]: true;
@@ -1759,6 +1856,8 @@ export declare interface CandidateShape {
1759
1856
  readonly windowed?: boolean;
1760
1857
  }
1761
1858
 
1859
+ export declare const canonicalInputKey: (input: unknown) => string;
1860
+
1762
1861
  /* Excluded from this release type: canonicalize */
1763
1862
 
1764
1863
  /**
@@ -2032,6 +2131,22 @@ export declare const composeAnalytics: (specs: ReadonlyArray<AnalyticsSinkSpec>)
2032
2131
  */
2033
2132
  export declare const composeAnalyticsSinks: (sinks: ReadonlyArray<AnalyticsSinkImpl>) => AnalyticsSinkImpl;
2034
2133
 
2134
+ export declare interface CompressedResult {
2135
+ readonly bytes: Uint8Array;
2136
+ /** Headers to MERGE onto the response. Always carries Vary for
2137
+ * compressible types; carries Content-Encoding only when compressed. */
2138
+ readonly headers: Readonly<Record<string, string>>;
2139
+ readonly compressed: boolean;
2140
+ }
2141
+
2142
+ export declare interface CompressionOptions {
2143
+ /** Master switch. Default true. */
2144
+ readonly enabled?: boolean;
2145
+ /** Bodies smaller than this are never compressed (the frame overhead can
2146
+ * exceed the saving). Default 1024. */
2147
+ readonly minBytes?: number;
2148
+ }
2149
+
2035
2150
  declare interface ComputedQuery {
2036
2151
  readonly __voltroComputed: true;
2037
2152
  /** The initial computed value — emitted verbatim as the first snapshot. */
@@ -2800,6 +2915,8 @@ export declare const DEFAULT_APPROVAL_EXPIRY_MS: number;
2800
2915
  * engine's finest useful cadence. */
2801
2916
  export declare const DEFAULT_CLAIM_BUCKET_MS = 60000;
2802
2917
 
2918
+ export declare const DEFAULT_COMPRESSION_MIN_BYTES = 1024;
2919
+
2803
2920
  /**
2804
2921
  * How many subscribers one change event is delivered to CONCURRENTLY.
2805
2922
  *
@@ -2838,6 +2955,10 @@ export declare const DEFAULT_RAW_READ_TRACKING_LIMIT = 32;
2838
2955
 
2839
2956
  export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
2840
2957
 
2958
+ export declare const DEFAULT_RESUME_MAX_DELTAS = 256;
2959
+
2960
+ export declare const DEFAULT_RESUME_WINDOW_MS = 60000;
2961
+
2841
2962
  /**
2842
2963
  * Retry policy applied to `load` when a filter does not specify one: three
2843
2964
  * attempts total (the initial call plus two retries), backing off exponentially
@@ -2850,6 +2971,14 @@ export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
2850
2971
  */
2851
2972
  export declare const DEFAULT_ROW_FILTER_RETRY: Schedule.Schedule<unknown, unknown>;
2852
2973
 
2974
+ /** Defaults, overridable via `app.config.ts` `reactive.socket.*` (+ env) —
2975
+ * resolved by the boot paths' tunables resolver. */
2976
+ export declare const DEFAULT_SOCKET_MAX_BUFFERED_BYTES = 1048576;
2977
+
2978
+ export declare const DEFAULT_SOCKET_OVERRUN_AFTER_MS = 10000;
2979
+
2980
+ export declare const DEFAULT_SOCKET_OVERSIZED_EVENT_BYTES = 262144;
2981
+
2853
2982
  /**
2854
2983
  * The message of an UNDECLARED throw, bounded, with nothing else attached.
2855
2984
  *
@@ -3055,6 +3184,17 @@ export declare const deliveredRowCount: (value: unknown) => number;
3055
3184
  */
3056
3185
  export declare const deriveKey: (passphrase: string) => Buffer;
3057
3186
 
3187
+ /**
3188
+ * Detach a live subscription for possible resume. The sink is redirected to
3189
+ * ring-recording only; after the window the subscription is truly
3190
+ * unsubscribed. If an entry already sits under this identity (an older
3191
+ * incarnation), it is expired first — one identity, one detached entry.
3192
+ */
3193
+ export declare const detachForResume: (identity: ResumeIdentity, handle: {
3194
+ readonly sink: ResumeSink;
3195
+ readonly unsubscribe: () => void;
3196
+ }, tunables?: ResumeTunables, now?: () => number) => void;
3197
+
3058
3198
  export { DialectReplicationAdapter }
3059
3199
 
3060
3200
  /** Field-level diff for one change. Insert → every field `undefined→value`;
@@ -3235,6 +3375,10 @@ export declare class Dispatcher {
3235
3375
 
3236
3376
  export declare interface DispatcherDependencies {
3237
3377
  readonly store: DataStore;
3378
+ /** CRDT columns per table (schemaRegistry.crdtColumns). Feeds the
3379
+ * downstream lane: crdt cells diff as incremental mergeCells ops.
3380
+ * Absent → no crdt tables (tests, embedders) — plain full-value diffs. */
3381
+ readonly crdtColumnsOf?: (table: string) => ReadonlySet<string>;
3238
3382
  /** Optional sink for per-delivery timing. `voltro dev` wires this into
3239
3383
  * the trace buffer so the dashboards show each data transfer's latency.
3240
3384
  * No-op when absent (tests, embedders that don't trace). */
@@ -3470,6 +3614,8 @@ export declare const evaluateTouchedRules: (params: {
3470
3614
  readonly subject: unknown;
3471
3615
  }) => Promise<void>;
3472
3616
 
3617
+ declare type Event_2 = SubscriptionEvent<ReadonlyArray<Row>>;
3618
+
3473
3619
  /**
3474
3620
  * How many deliveries one client may fall behind before the oldest are dropped.
3475
3621
  *
@@ -4392,6 +4538,14 @@ export declare interface FluentStore extends Omit<MutationStore, 'update' | 'del
4392
4538
  * await ctx.store.links('post_tags', { postId: post.id }).set(tagIds)
4393
4539
  */
4394
4540
  links(junctionTable: string, anchor: Readonly<Record<string, string>>): JunctionLinks;
4541
+ /**
4542
+ * `links(...)` with the anchor COLUMN derived from the anchored TABLE: the
4543
+ * junction's reference column that points at `anchorTable` becomes the
4544
+ * source. Exactly one column may qualify — a self-junction (both columns
4545
+ * referencing the same table) is refused with the fix, never guessed.
4546
+ * This is what a mutation target's declared `relations:` resolves through.
4547
+ */
4548
+ relationLinks(junctionTable: string, anchorTable: string, anchorId: string): JunctionLinks;
4395
4549
  /**
4396
4550
  * Execute a query descriptor and return the matching rows — TYPED.
4397
4551
  *
@@ -4923,6 +5077,8 @@ export declare const ipToBytes: (ip: string) => Uint8Array | undefined;
4923
5077
 
4924
5078
  export declare const isAggregateDefinition: (value: unknown) => value is AggregateDefinition;
4925
5079
 
5080
+ export declare const isCompressibleContentType: (contentType: string | undefined) => boolean;
5081
+
4926
5082
  export declare const isCostBudgetDefinition: (value: unknown) => value is CostBudgetDefinition;
4927
5083
 
4928
5084
  export declare const isEmptyTenantScopedRead: (descriptor: QueryDescriptor, tenantScopedTables: ReadonlySet<string>, tenantId: string | null | undefined) => boolean;
@@ -5605,6 +5761,8 @@ export declare const makeSubmitTokenExecutor: (deps: ConnectionBuiltinDeps) => (
5605
5761
  readonly ok: boolean;
5606
5762
  }>;
5607
5763
 
5764
+ export declare const makeSubscriptionOutbox: (options: SubscriptionBackpressureOptions) => SubscriptionOutbox;
5765
+
5608
5766
  /**
5609
5767
  * The factory the serve entrypoints inject into `makeMutationRunner`'s
5610
5768
  * `undoCapture` dep. Collects changes from the wrapped tx; `persist` writes ONE
@@ -5612,7 +5770,7 @@ export declare const makeSubmitTokenExecutor: (deps: ConnectionBuiltinDeps) => (
5612
5770
  * mutation's transaction. The changes are JSON-serialized into a TEXT column —
5613
5771
  * dialect-uniform, no json codec on the path.
5614
5772
  */
5615
- export declare const makeUndoCapture: (rawTx: DataStore) => {
5773
+ export declare const makeUndoCapture: (rawTx: DataStore, crdtColumnsOf?: (table: string) => ReadonlySet<string>) => {
5616
5774
  tx: DataStore;
5617
5775
  persist: (store: unknown, meta: {
5618
5776
  readonly tag: string;
@@ -5687,6 +5845,18 @@ export declare const materializeIvm: (shape: AggregateShape, state: IvmState) =>
5687
5845
  readonly value: number | null;
5688
5846
  }>;
5689
5847
 
5848
+ /**
5849
+ * Compress a buffered response body if (enabled ∧ type compressible ∧ size ≥
5850
+ * threshold ∧ the client accepts an encoding). Total — never throws; the
5851
+ * uncompressed body with correct Vary is always a valid answer.
5852
+ */
5853
+ export declare const maybeCompress: (input: {
5854
+ readonly body: Uint8Array;
5855
+ readonly contentType: string | undefined;
5856
+ readonly acceptEncoding: string | undefined;
5857
+ readonly options?: CompressionOptions | undefined;
5858
+ }) => CompressedResult;
5859
+
5690
5860
  /** Told when the membership changes. `restarted` is a `left` + `joined` pair
5691
5861
  * for one id, reported as such so a deployment drops the old state rather than
5692
5862
  * resuming it. */
@@ -5889,8 +6059,10 @@ export declare interface MutationLike {
5889
6059
  readonly requiresApproval?: AnyApprovalPolicy | undefined;
5890
6060
  readonly target?: {
5891
6061
  readonly table: string;
6062
+ readonly relations?: Readonly<Record<string, string>> | undefined;
5892
6063
  } | ReadonlyArray<{
5893
6064
  readonly table: string;
6065
+ readonly relations?: Readonly<Record<string, string>> | undefined;
5894
6066
  }> | undefined;
5895
6067
  };
5896
6068
  executor(input: unknown, ctx: unknown): unknown;
@@ -5964,6 +6136,13 @@ export declare interface MutationStore extends DataStore {
5964
6136
  hardDelete(table: string, primaryKey: string): Promise<boolean>;
5965
6137
  }
5966
6138
 
6139
+ export declare type NegotiatedEncoding = 'br' | 'gzip';
6140
+
6141
+ /** Pick the encoding the client accepts — brotli preferred, gzip second,
6142
+ * nothing otherwise. Honours q=0 refusals; ignores q-ordering beyond that
6143
+ * (a client sending both accepts both). */
6144
+ export declare const negotiateEncoding: (acceptEncoding: string | undefined) => NegotiatedEncoding | undefined;
6145
+
5967
6146
  /**
5968
6147
  * The backoff curve, extracted so it can be asserted directly — a schedule
5969
6148
  * that backs off wrongly is otherwise only visible as a latency an integration
@@ -6829,6 +7008,25 @@ export declare interface ReactiveConfigInput {
6829
7008
  * warning to name a later one.
6830
7009
  */
6831
7010
  readonly rawReadTrackingLimit?: number;
7011
+ /**
7012
+ * Subscription socket backpressure (plan 01 P1). A slow or dead consumer no
7013
+ * longer retains every event: updates COALESCE onto the newest state while
7014
+ * the socket is blocked, and a consumer persistently over the buffer
7015
+ * threshold is closed with a typed `SubscriptionOverrun` (it re-subscribes
7016
+ * for a fresh snapshot). Resolved by the CLI's `reactiveTunables.ts`; env
7017
+ * overrides win: `VOLTRO_REACTIVE_MAX_BUFFERED_BYTES` /
7018
+ * `VOLTRO_REACTIVE_OVERRUN_AFTER_MS` / `VOLTRO_REACTIVE_OVERSIZED_EVENT_BYTES`.
7019
+ */
7020
+ readonly socket?: {
7021
+ /** Estimated pending bytes per subscription above which a persistently
7022
+ * blocked consumer is overrun-closed. Default 1 MiB. */
7023
+ readonly maxBufferedBytes?: number;
7024
+ /** How long the pending state must stay over the threshold before the
7025
+ * terminal close. Default 10_000 ms. */
7026
+ readonly overrunAfterMs?: number;
7027
+ /** Per-event WARN threshold (telemetry, NOT a cap). Default 256 KiB. */
7028
+ readonly oversizedEventBytes?: number;
7029
+ };
6832
7030
  }
6833
7031
 
6834
7032
  export declare interface ReactiveEnv {
@@ -6844,6 +7042,16 @@ export declare type ReactiveReturn<D extends ExecutorDescriptor> = D extends {
6844
7042
  readonly descriptor: QueryDescriptor;
6845
7043
  } : never;
6846
7044
 
7045
+ export declare interface ReactiveSocketTunables {
7046
+ readonly maxBufferedBytes: number;
7047
+ readonly overrunAfterMs: number;
7048
+ readonly oversizedEventBytes: number;
7049
+ }
7050
+
7051
+ /** The resolved tunables, or the defaults when no boot path has set any
7052
+ * (unit tests, a bare library use). */
7053
+ export declare const reactiveSocketTunables: () => ReactiveSocketTunables;
7054
+
6847
7055
  export declare interface ReadClassification {
6848
7056
  /** Tables whose ROWS can reach the caller: the queried table and its joins. */
6849
7057
  readonly composed: ReadonlySet<string>;
@@ -6930,6 +7138,8 @@ export declare interface RebacSubject {
6930
7138
  readonly scopes?: ReadonlyArray<string>;
6931
7139
  }
6932
7140
 
7141
+ export declare const recordCoalesced: (label: string, collapsed: number) => void;
7142
+
6933
7143
  export declare const recordCredentialExpiry: (clientId: number, exp: number | undefined) => void;
6934
7144
 
6935
7145
  /** What the framework emit-seams (servePipeline / rpcServer / plugin wrappers)
@@ -6956,6 +7166,15 @@ export declare const recordEventPublished: (event: string) => void;
6956
7166
  /** Subscriber attached (+1) or detached (-1). */
6957
7167
  export declare const recordEventSubscribers: (event: string, delta: number) => void;
6958
7168
 
7169
+ /**
7170
+ * Record one emitted event into the identity's ring. Snapshots RESET the ring
7171
+ * (the snapshot's revision becomes the new replay base — a client holding an
7172
+ * older revision is outside the chain); deltas append. An error event drops
7173
+ * the ring: whatever state the client holds, the safe answer after an error
7174
+ * is a fresh snapshot.
7175
+ */
7176
+ export declare const recordForResume: (identity: ResumeIdentity, event: SubscriptionEvent<unknown>, now?: number, tunables?: ResumeTunables) => void;
7177
+
6959
7178
  /**
6960
7179
  * A store that notes every read and changes nothing else.
6961
7180
  *
@@ -6966,6 +7185,10 @@ export declare const recordEventSubscribers: (event: string, delta: number) => v
6966
7185
  */
6967
7186
  export declare const recordingStore: (store: DataStore) => DataStore;
6968
7187
 
7188
+ export declare const recordOverrun: (label: string) => void;
7189
+
7190
+ export declare const recordOversized: (label: string) => void;
7191
+
6969
7192
  /**
6970
7193
  * Record a raw read, opening a scope for the rest of this execution context if
6971
7194
  * none is open yet.
@@ -7256,6 +7479,13 @@ export declare interface RegistryTableLike {
7256
7479
  * request was. */
7257
7480
  export declare const rehydrateGuards: (serialized: ReadonlyArray<SerializedGuard>) => ReadonlyArray<AnyGuardSpec>;
7258
7481
 
7482
+ /** The store slice `applyDeclaredRelations` needs. */
7483
+ declare interface RelationLinksStore {
7484
+ relationLinks(junctionTable: string, anchorTable: string, anchorId: string): {
7485
+ set(targetIds: ReadonlyArray<string>): Promise<unknown>;
7486
+ };
7487
+ }
7488
+
7259
7489
  /** A relationship tuple — `subjectId` has `relation` on `<resourceType>:<resourceId>`.
7260
7490
  * In practice these are rows in a relation table; the engine takes them as data. */
7261
7491
  export declare interface RelationTuple {
@@ -7441,15 +7671,24 @@ export declare const resetComputedQueryCacheWarnings: () => void;
7441
7671
  /** Test/dev-only — clear ALL per-connection state. Don't call from app code. */
7442
7672
  export declare const _resetConnectionCredentialsForTest: () => void;
7443
7673
 
7674
+ /** Test seam — expire everything immediately. */
7675
+ export declare const resetDetachedForTest: () => void;
7676
+
7444
7677
  /** Test seams for the tag-cache guard — the cache is module-private otherwise. */
7445
7678
  export declare const resetEventMetricTagCacheForTests: () => void;
7446
7679
 
7447
7680
  /** Drop everything recorded. For tests; not called by the runtime. */
7448
7681
  export declare const resetObservedGraph: () => void;
7449
7682
 
7683
+ /** Test seam. */
7684
+ export declare const resetResumeRingsForTest: () => void;
7685
+
7450
7686
  /** Reset to the env default (tests). */
7451
7687
  export declare const resetSecretsBackend: () => void;
7452
7688
 
7689
+ /** Test seam. */
7690
+ export declare const resetSubscriptionSocketMetrics: () => void;
7691
+
7453
7692
  /**
7454
7693
  * The client address to rate-limit, geo-block and audit by.
7455
7694
  *
@@ -7768,6 +8007,46 @@ export declare const restrictingReadsEffect: <A, E, R>(effect: Effect.Effect<A,
7768
8007
  /** The full ordered list of result variants for a definition, holdout last. */
7769
8008
  export declare const resultVariantNames: (def: ExperimentDefinition) => ReadonlyArray<string>;
7770
8009
 
8010
+ export declare type ResumeAnswer =
8011
+ /** The client's revision chains: replay these deltas in order. */
8012
+ {
8013
+ readonly kind: 'deltas';
8014
+ readonly deltas: ReadonlyArray<RingDelta>;
8015
+ }
8016
+ /** Outside the window / unknown identity / gap — send a snapshot,
8017
+ * `resumed: false`. */
8018
+ | {
8019
+ readonly kind: 'snapshot';
8020
+ };
8021
+
8022
+ /**
8023
+ * The identity a ring is recorded under — and the identity a resume must
8024
+ * present to read it back. `subjectId`/`tenantId` are part of the KEY, so a
8025
+ * login/logout/tenant-switch between disconnect and resume simply misses the
8026
+ * ring and falls back to a snapshot (`resumed: false`) — the blank-on-auth
8027
+ * doctrine, enforced structurally rather than checked.
8028
+ */
8029
+ export declare interface ResumeIdentity {
8030
+ readonly label: string;
8031
+ /** Canonicalised query input (JSON with sorted keys). */
8032
+ readonly inputKey: string;
8033
+ readonly subjectId: string;
8034
+ readonly tenantId: string;
8035
+ }
8036
+
8037
+ export declare const resumeKeyOf: (identity: ResumeIdentity) => string;
8038
+
8039
+ export declare interface ResumeSink {
8040
+ current: (event: SubscriptionEvent<unknown>) => void;
8041
+ }
8042
+
8043
+ export declare interface ResumeTunables {
8044
+ readonly windowMs: number;
8045
+ readonly maxDeltas: number;
8046
+ }
8047
+
8048
+ export declare const resumeTunables: () => ResumeTunables;
8049
+
7771
8050
  export { RetentionConflict }
7772
8051
 
7773
8052
  export { RetentionSource }
@@ -7776,6 +8055,55 @@ export { RetentionSpec }
7776
8055
 
7777
8056
  export { retentionTtlMsFromEnv }
7778
8057
 
8058
+ /**
8059
+ * Drop the ISR cache entries for a path on every `voltro start` replica.
8060
+ *
8061
+ * `path` is a concrete path (`/pricing`) or a declared route pattern
8062
+ * (`/blog/[slug]` = every cached instance). All tenant+locale variants of the
8063
+ * key fall; pass `{ tenant }` to narrow to one tenant's variants.
8064
+ *
8065
+ * Only `renderMode: 'isr'` routes have cache entries to drop — calling this
8066
+ * for a `static` route is reported as an error on the web process (static
8067
+ * HTML is a build artifact `voltro start` never re-renders). Under
8068
+ * `voltro dev` there is no ISR cache and the call is a debug-logged no-op.
8069
+ */
8070
+ export declare const revalidatePath: (path: string, options?: {
8071
+ readonly tenant?: string;
8072
+ }) => Promise<void>;
8073
+
8074
+ /**
8075
+ * Drop the ISR cache entries of every route whose `cacheInvalidatesOn` lists
8076
+ * `table` — the same sink CDC feeds on postgres, callable imperatively. This
8077
+ * is THE invalidation path for dialects without CDC (sqlite, mysql, memory).
8078
+ */
8079
+ export declare const revalidateTable: (table: string) => Promise<void>;
8080
+
8081
+ /**
8082
+ * Drop the ISR cache entries of every route whose `cacheInvalidatesOn` lists
8083
+ * `tag`. `cacheInvalidatesOn` accepts free strings, so a tag shares one
8084
+ * mechanism (and one entry point) with table invalidation — declare
8085
+ * `cacheInvalidatesOn: ['pricing']` on any number of routes and
8086
+ * `revalidateTag('pricing')` drops them all.
8087
+ */
8088
+ export declare const revalidateTag: (tag: string) => Promise<void>;
8089
+
8090
+ export declare interface RevalidationMessage {
8091
+ /** What to drop:
8092
+ * - `path`: a concrete path (`/pricing`) or a declared route pattern
8093
+ * (`/blog/[slug]` — every cached instance of the route). All
8094
+ * tenant+locale key variants fall unless `tenant` narrows it.
8095
+ * - `table`: every route whose `cacheInvalidatesOn` lists the table —
8096
+ * the same sink CDC feeds, for dialects (or moments) without CDC.
8097
+ * - `tag`: identical mechanics to `table` — `cacheInvalidatesOn` accepts
8098
+ * free strings, so a tag is simply a name that never was a table. */
8099
+ readonly kind: 'path' | 'table' | 'tag';
8100
+ readonly value: string;
8101
+ /** For `kind: 'path'`: drop only this tenant's cached variants. */
8102
+ readonly tenant?: string;
8103
+ }
8104
+
8105
+ export declare type RevalidationPublisher = (message: RevalidationMessage) => Promise<void>;
8106
+
7779
8107
  /**
7780
8108
  * Reverse ONE recorded event over an in-memory row-set (to step BACK across it):
7781
8109
  * - insert → the row was added, so remove it (by new.id).
@@ -7793,6 +8121,12 @@ export declare const revokedIds: (before: ReadonlyArray<{
7793
8121
  readonly id: string;
7794
8122
  }>) => ReadonlyArray<string>;
7795
8123
 
8124
+ declare interface RingDelta {
8125
+ readonly revision: number;
8126
+ readonly emittedAt: number;
8127
+ readonly patch: RowPatch;
8128
+ }
8129
+
7796
8130
  /**
7797
8131
  * Live traffic the router fronts — read by idle detection (the orchestrator
7798
8132
  * can't see the app's HTTP/WS load directly when it's scaled to zero, but
@@ -7926,7 +8260,7 @@ declare const RowFilterUnavailable_base: Schema.TaggedErrorClass<RowFilterUnavai
7926
8260
  cause: typeof Schema.String;
7927
8261
  }>;
7928
8262
 
7929
- /** One `_voltro_row_history` row, structurally (from @voltro/plugin-versioning). */
8263
+ /** One `_voltro_row_history` row, structurally (from @voltro/plugin-row-history). */
7930
8264
  export declare interface RowHistoryEntry {
7931
8265
  readonly value?: unknown;
7932
8266
  readonly newValue?: unknown;
@@ -8056,6 +8390,45 @@ export declare interface RpcServerOptions<Rpcs extends Rpc.Any> {
8056
8390
  * with a `Content-Length` got a clean 413. See `rpcBodyCap.ts`.
8057
8391
  */
8058
8392
  readonly maxRpcBodyBytes?: number;
8393
+ /**
8394
+ * Max body size (bytes) for EVERY other buffered surface on this listener:
8395
+ * plugin HTTP routes (which read `req.arrayBuffer`) and incoming webhook
8396
+ * receivers. The rpc endpoint keeps its own `maxRpcBodyBytes` (a JSON
8397
+ * envelope is never legitimately large; these surfaces legitimately can
8398
+ * be — a webhook provider posting a fat payload is the normal case, which
8399
+ * is why a PluginHttpRoute / webhook handler may override PER ROUTE via
8400
+ * its own `maxBodyBytes`). Same two-shape 413 contract as the rpc cap
8401
+ * (declared Content-Length refused first; the byte counter enforces).
8402
+ * Default {@link DEFAULT_MAX_RPC_BODY_BYTES}; env override
8403
+ * `VOLTRO_MAX_BODY_BYTES`.
8404
+ */
8405
+ readonly maxBodyBytes?: number;
8406
+ /**
8407
+ * Negotiated response compression for the buffered non-rpc surfaces
8408
+ * (plugin routes, webhooks). `POST /rpc` is NEVER compressed (the BREACH
8409
+ * position — see httpCompression.ts) and streams are never compressed.
8410
+ * Default enabled with a 1 KiB threshold; `http.compression` in
8411
+ * app.config.ts feeds this via resolveHttpTunables.
8412
+ */
8413
+ readonly compression?: CompressionOptions;
8414
+ /**
8415
+ * Raw WebSocket gateways (plan 08 §3) — `defineWebSocket` routes discovered
8416
+ * from `*.ws.ts`. Each mounts its own upgrade path beside the rpc socket;
8417
+ * every gateway path joins the upgrade origin guard's set, and
8418
+ * `auth: 'subject'` routes resolve their subject through
8419
+ * {@link RpcServerOptions.resolveGatewaySubject} BEFORE the upgrade.
8420
+ */
8421
+ readonly webSocketGateways?: ReadonlyArray< WebSocketGatewayRoute>;
8422
+ /**
8423
+ * Subject resolution for `auth: 'subject'` gateways — the SAME chain the
8424
+ * boot path hands its REST bindings (cookie/bearer). `null` → 401 before
8425
+ * any socket exists. `credentialExpiresAt` binds the connection's lifetime
8426
+ * exactly like an rpc connection's (SEC-16).
8427
+ */
8428
+ readonly resolveGatewaySubject?: (headers: Readonly<Record<string, string>>) => Promise<{
8429
+ readonly subject: Subject;
8430
+ readonly credentialExpiresAt?: number | undefined;
8431
+ } | null>;
8059
8432
  /**
8060
8433
  * Transport-level security policy for this listener — see
8061
8434
  * {@link TransportSecurityOptions}. `undefined` → the safe defaults.
@@ -8840,6 +9213,12 @@ export declare interface ServeRequestContext {
8840
9213
  readonly subject: unknown;
8841
9214
  readonly traceId: string;
8842
9215
  readonly spanId?: string;
9216
+ /**
9217
+ * Caller-side cancellation (the gRPC deadline / client-cancel path).
9218
+ * Honoured at the ONE place the executor effect runs to a promise, so an
9219
+ * exceeded deadline INTERRUPTS the server work — not merely the response.
9220
+ */
9221
+ readonly signal?: AbortSignal;
8843
9222
  /**
8844
9223
  * Row-level visibility for this request, resolved by `withRowFilter` and read
8845
9224
  * by the context builder when it wraps the store.
@@ -8897,6 +9276,8 @@ export declare type ServeRpcInterceptor = (base: Effect.Effect<unknown, unknown,
8897
9276
  readonly target?: ReadonlyArray<string>;
8898
9277
  }) => Effect.Effect<unknown, unknown, never>;
8899
9278
 
9279
+ export declare const setBufferedBytes: (label: string, bytes: number) => void;
9280
+
8900
9281
  /** Register the process-wide connection resolver (or clear with `undefined`).
8901
9282
  * Called by the CLI at boot; tests call it directly. */
8902
9283
  export declare const setConnectionResolver: (resolver: ConnectionResolver | undefined) => void;
@@ -8904,6 +9285,17 @@ export declare const setConnectionResolver: (resolver: ConnectionResolver | unde
8904
9285
  /** Register the field cipher (or clear with `undefined`). */
8905
9286
  export declare const setFieldCipher: (cipher: FieldCipher | undefined) => void;
8906
9287
 
9288
+ export declare const setReactiveSocketTunables: (tunables: ReactiveSocketTunables | null) => void;
9289
+
9290
+ export declare const setResumeTunables: (tunables: ResumeTunables | null) => void;
9291
+
9292
+ /**
9293
+ * Boot-path wiring. `null` clears (shutdown). Registering `null` vs never
9294
+ * registering are deliberately the SAME state — a call before boot wiring and
9295
+ * a call after teardown both deserve the loud error below, not a quiet drop.
9296
+ */
9297
+ export declare const setRevalidationPublisher: (publisher: RevalidationPublisher | null) => void;
9298
+
8907
9299
  /**
8908
9300
  * Register (or clear, with `undefined`) the process-global row filter. Call
8909
9301
  * once at boot. Last write wins.
@@ -9025,7 +9417,10 @@ export declare interface SnapshotCacheBinding {
9025
9417
  readonly swrMs: number | undefined;
9026
9418
  }
9027
9419
 
9028
- /** Snapshot the whole registry, normalized to `MetricSample[]`. */
9420
+ /** Snapshot the whole registry, normalized to `MetricSample[]` — plus the
9421
+ * socket-backpressure counters (`subscriptionSocketMetrics.ts`), appended
9422
+ * HERE so every reader (Prometheus exporter, inspect Metrics panel) sees
9423
+ * one derivation and the two cannot diverge. */
9029
9424
  export declare const snapshotMetrics: Effect.Effect<ReadonlyArray<MetricSample>>;
9030
9425
 
9031
9426
  /** Eager snapshot for sync callers (the inspect endpoint). */
@@ -9290,6 +9685,22 @@ export declare interface SubscriberDefinition {
9290
9685
  readonly handler: SubscribeHandler;
9291
9686
  }
9292
9687
 
9688
+ export declare interface SubscriptionBackpressureOptions {
9689
+ /** Estimated pending bytes above which a persistently-blocked consumer is
9690
+ * overrun-closed. `reactive.socket.maxBufferedBytes` / env override. */
9691
+ readonly maxBufferedBytes: number;
9692
+ /** How long the pending state must stay over the threshold (with the pump
9693
+ * blocked) before the terminal close. */
9694
+ readonly overrunAfterMs: number;
9695
+ /** Per-event WARN threshold — telemetry for oversized snapshots/deltas.
9696
+ * Deliberately NOT a cap: measurability is the goal. */
9697
+ readonly oversizedEventBytes: number;
9698
+ /** Query tag for logs + metrics. */
9699
+ readonly label: string;
9700
+ readonly now?: () => number;
9701
+ readonly warn?: (message: string, fields: Record<string, unknown>) => void;
9702
+ }
9703
+
9293
9704
  /**
9294
9705
  * Timing of ONE subscription delivery (the initial snapshot, or a delta
9295
9706
  * pushed on a change). The duration is the real produce→push latency:
@@ -9325,6 +9736,29 @@ export declare interface SubscriptionDeliveryRecord {
9325
9736
  readonly rowCount: number;
9326
9737
  }
9327
9738
 
9739
+ export declare interface SubscriptionOutbox {
9740
+ /** SYNC push from the dispatcher's emit callback. */
9741
+ readonly push: (event: Event_2) => void;
9742
+ /** Pump side: resolves with the next event to hand into the stream —
9743
+ * coalesced when more than one push landed since the last take — or null
9744
+ * once the outbox is closed and drained. */
9745
+ readonly take: () => Promise<Event_2 | null>;
9746
+ /** True once the overrun policy fired (the error event has been queued;
9747
+ * the stream ends after it drains). */
9748
+ readonly overrun: () => boolean;
9749
+ readonly close: () => void;
9750
+ }
9751
+
9752
+ /** The wire shape of the terminal overrun error — documented in
9753
+ * wire-protocol.md. The client surfaces it as the entry's `error` and
9754
+ * re-subscribes for a fresh snapshot. */
9755
+ export declare interface SubscriptionOverrunError {
9756
+ readonly _tag: 'SubscriptionOverrun';
9757
+ readonly message: string;
9758
+ readonly bufferedBytes: number;
9759
+ readonly maxBufferedBytes: number;
9760
+ }
9761
+
9328
9762
  export declare type SubscriptionRecord = {
9329
9763
  readonly id: string;
9330
9764
  readonly table: string;
@@ -9385,6 +9819,35 @@ export declare interface SubscriptionSnapshot {
9385
9819
  readonly subscriberCount: number;
9386
9820
  }
9387
9821
 
9822
+ /**
9823
+ * The counters as `MetricSample`s — appended by `snapshotMetrics`
9824
+ * (metrics.ts), which is the ONE derivation both the Prometheus exporter and
9825
+ * the inspect Metrics panel read. Same input → the two cannot diverge.
9826
+ */
9827
+ export declare const subscriptionSocketMetricSamples: () => ReadonlyArray< MetricSample>;
9828
+
9829
+ export declare interface SubscriptionSocketMetricsSnapshot {
9830
+ /** Sum + per-label current pending bytes across live subscriptions. */
9831
+ readonly bufferedBytes: {
9832
+ readonly total: number;
9833
+ readonly byLabel: Readonly<Record<string, number>>;
9834
+ };
9835
+ readonly coalescedTotal: {
9836
+ readonly total: number;
9837
+ readonly byLabel: Readonly<Record<string, number>>;
9838
+ };
9839
+ readonly overrunTotal: {
9840
+ readonly total: number;
9841
+ readonly byLabel: Readonly<Record<string, number>>;
9842
+ };
9843
+ readonly oversizedTotal: {
9844
+ readonly total: number;
9845
+ readonly byLabel: Readonly<Record<string, number>>;
9846
+ };
9847
+ }
9848
+
9849
+ export declare const subscriptionSocketMetricsSnapshot: () => SubscriptionSocketMetricsSnapshot;
9850
+
9388
9851
  /** Drop handshake grants past their TTL. Wired into the boot retention sweep;
9389
9852
  * a grant is worthless the moment it expires and keeping them turns a hot
9390
9853
  * table into an audit liability. */
@@ -10208,6 +10671,10 @@ export declare const WEBHOOK_VERIFICATION_PROPERTY: "voltroWebhookVerification";
10208
10671
  * public POST that runs application code, so "nothing verifies it" has to
10209
10672
  * be a decision somebody wrote down, not the default. */
10210
10673
  export declare interface WebhookRouteHandler {
10674
+ /** Per-route body cap override (bytes) — wins over the listener's
10675
+ * `maxBodyBytes`. A provider that legitimately posts fat payloads is the
10676
+ * reason this exists; the DEFAULT stays the shared cap. */
10677
+ readonly maxBodyBytes?: number;
10211
10678
  readonly handle: (request: {
10212
10679
  readonly method: string;
10213
10680
  readonly path: string;
@@ -10251,24 +10718,6 @@ export declare const webhookVerificationOf: (handle: unknown) => WebhookVerifica
10251
10718
  */
10252
10719
  export declare type WhereOp = '=' | '!=' | '<>' | '>' | '>=' | '<' | '<=' | 'in' | 'contains' | 'startsWith' | 'fts';
10253
10720
 
10254
- /**
10255
- * Resolve this request's row-filter scope. A no-op (same object back) when the
10256
- * app registered no filter, so an app that uses none pays nothing.
10257
- *
10258
- * Every runner calls this before building a context. A read path that skipped
10259
- * it would have no row-level security and would look exactly like one that
10260
- * does — which is why it lives in the shared spine rather than in each
10261
- * entrypoint.
10262
- *
10263
- * REJECTS with the raw `RowFilterUnavailable` (via `runProvidedEffect`, not
10264
- * `Effect.runPromise`) when the filter's `onLoadError` is the default `'fail'`
10265
- * and `load` failed every retry. Raw, so the rpc encoder matches it against the
10266
- * descriptor's `error:` schema and the client gets the tag instead of an opaque
10267
- * defect — the same treatment `enforceGuards` gives a `ScopeError`. Proceeding
10268
- * with an unresolved filter is not an option here: the context builder would
10269
- * receive `rowFilter: undefined`, which means "this app registered no filter"
10270
- * and would silently serve the request UNFILTERED.
10271
- */
10272
10721
  export declare const withRowFilter: <R extends ServeRequestContext>(request: R) => Promise<R>;
10273
10722
 
10274
10723
  /**
@@ -10798,7 +11247,7 @@ export declare interface WorkflowWaitOptions {
10798
11247
  * calls (incl. multi-table within one mutation) are. A mutation that writes via
10799
11248
  * a bulk helper simply produces no undo entry (documented).
10800
11249
  */
10801
- export declare const wrapCaptureStore: (tx: DataStore, collect: (c: ForwardChange) => void) => DataStore;
11250
+ export declare const wrapCaptureStore: (tx: DataStore, collect: (c: ForwardChange) => void, crdtColumnsOf?: (table: string) => ReadonlySet<string>) => DataStore;
10802
11251
 
10803
11252
  /**
10804
11253
  * Wrap a raw `DataStore` with the storage codec only — `.encrypted()` columns