@voltro/runtime 0.52.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,8 @@ 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';
100
103
  import { WebSocketGatewayRoute } from '@voltro/protocol';
101
104
  import { WorkflowParentClosePolicy } from '@voltro/protocol';
102
105
  import { WorkflowRunHandle } from '@voltro/protocol';
@@ -151,6 +154,28 @@ export declare interface ActivitySignals {
151
154
  readonly activeWorkflowExecutions: () => number;
152
155
  }
153
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
+
154
179
  export declare interface AggregateContext {
155
180
  /** Read-side store for source queries. Note: the build function
156
181
  * runs OUTSIDE any per-request subject, so the auto-stamping a
@@ -808,6 +833,15 @@ export declare interface AnalyticsTopEntry {
808
833
  readonly value: number;
809
834
  }
810
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
+
811
845
  declare type AnyRow = Record<string, unknown>;
812
846
 
813
847
  declare type AnyRow_2 = Record<string, unknown>;
@@ -990,6 +1024,10 @@ export declare interface AppContext {
990
1024
  * `ctx.access.has(scope)` / `yield* ctx.access.require(scope)` instead of
991
1025
  * reaching into `ctx.request.subject.scopes`. Always present. */
992
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;
993
1031
  /** Cache — always present (memory backend by default). Use
994
1032
  * `ctx.cache.wrap(key, { ttlMs, tags }, () => expensive())` to cache
995
1033
  * derived work; tagged entries auto-invalidate when a mutation writes a
@@ -1074,6 +1112,28 @@ export declare interface AppContext {
1074
1112
  */
1075
1113
  export declare const applyConnectionCredential: (clientId: number, headers: Record<string, string | undefined>) => Record<string, string | undefined>;
1076
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
+
1077
1137
  /**
1078
1138
  * Pure helper backing `ctx.store.applyDefined`. Picks the listed keys from
1079
1139
  * `input` whose value is not `undefined`, so a PATCH sets exactly the fields
@@ -1220,6 +1280,35 @@ export declare interface AppSupervisor {
1220
1280
  stop: () => Promise<void>;
1221
1281
  }
1222
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
+
1223
1312
  /** Effect-style guard for handlers — throws the typed {@link AccessDenied} on a
1224
1313
  * deny so it surfaces to the client typed. */
1225
1314
  export declare const assertCan: (subject: RebacSubject, action: string, resource: RebacResource, deps: CanDeps) => void;
@@ -1623,7 +1712,14 @@ reauthorize?: (subject: Subject) => () => Promise<unknown>,
1623
1712
  */
1624
1713
  refilter?: (subject: Subject) => () => Promise<RowFilterScope>) => Stream.Stream<SubscriptionEvent<T>, never, SubjectService | ConnectionInfo>;
1625
1714
 
1626
- 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>;
1627
1723
 
1628
1724
  export declare interface BrandedScheduleDefinition extends ScheduleDefinition {
1629
1725
  readonly [SCHEDULE_BRAND]: true;
@@ -1760,6 +1856,8 @@ export declare interface CandidateShape {
1760
1856
  readonly windowed?: boolean;
1761
1857
  }
1762
1858
 
1859
+ export declare const canonicalInputKey: (input: unknown) => string;
1860
+
1763
1861
  /* Excluded from this release type: canonicalize */
1764
1862
 
1765
1863
  /**
@@ -2857,6 +2955,10 @@ export declare const DEFAULT_RAW_READ_TRACKING_LIMIT = 32;
2857
2955
 
2858
2956
  export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
2859
2957
 
2958
+ export declare const DEFAULT_RESUME_MAX_DELTAS = 256;
2959
+
2960
+ export declare const DEFAULT_RESUME_WINDOW_MS = 60000;
2961
+
2860
2962
  /**
2861
2963
  * Retry policy applied to `load` when a filter does not specify one: three
2862
2964
  * attempts total (the initial call plus two retries), backing off exponentially
@@ -2869,6 +2971,14 @@ export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
2869
2971
  */
2870
2972
  export declare const DEFAULT_ROW_FILTER_RETRY: Schedule.Schedule<unknown, unknown>;
2871
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
+
2872
2982
  /**
2873
2983
  * The message of an UNDECLARED throw, bounded, with nothing else attached.
2874
2984
  *
@@ -3074,6 +3184,17 @@ export declare const deliveredRowCount: (value: unknown) => number;
3074
3184
  */
3075
3185
  export declare const deriveKey: (passphrase: string) => Buffer;
3076
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
+
3077
3198
  export { DialectReplicationAdapter }
3078
3199
 
3079
3200
  /** Field-level diff for one change. Insert → every field `undefined→value`;
@@ -3254,6 +3375,10 @@ export declare class Dispatcher {
3254
3375
 
3255
3376
  export declare interface DispatcherDependencies {
3256
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>;
3257
3382
  /** Optional sink for per-delivery timing. `voltro dev` wires this into
3258
3383
  * the trace buffer so the dashboards show each data transfer's latency.
3259
3384
  * No-op when absent (tests, embedders that don't trace). */
@@ -3489,6 +3614,8 @@ export declare const evaluateTouchedRules: (params: {
3489
3614
  readonly subject: unknown;
3490
3615
  }) => Promise<void>;
3491
3616
 
3617
+ declare type Event_2 = SubscriptionEvent<ReadonlyArray<Row>>;
3618
+
3492
3619
  /**
3493
3620
  * How many deliveries one client may fall behind before the oldest are dropped.
3494
3621
  *
@@ -4411,6 +4538,14 @@ export declare interface FluentStore extends Omit<MutationStore, 'update' | 'del
4411
4538
  * await ctx.store.links('post_tags', { postId: post.id }).set(tagIds)
4412
4539
  */
4413
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;
4414
4549
  /**
4415
4550
  * Execute a query descriptor and return the matching rows — TYPED.
4416
4551
  *
@@ -5626,6 +5761,8 @@ export declare const makeSubmitTokenExecutor: (deps: ConnectionBuiltinDeps) => (
5626
5761
  readonly ok: boolean;
5627
5762
  }>;
5628
5763
 
5764
+ export declare const makeSubscriptionOutbox: (options: SubscriptionBackpressureOptions) => SubscriptionOutbox;
5765
+
5629
5766
  /**
5630
5767
  * The factory the serve entrypoints inject into `makeMutationRunner`'s
5631
5768
  * `undoCapture` dep. Collects changes from the wrapped tx; `persist` writes ONE
@@ -5633,7 +5770,7 @@ export declare const makeSubmitTokenExecutor: (deps: ConnectionBuiltinDeps) => (
5633
5770
  * mutation's transaction. The changes are JSON-serialized into a TEXT column —
5634
5771
  * dialect-uniform, no json codec on the path.
5635
5772
  */
5636
- export declare const makeUndoCapture: (rawTx: DataStore) => {
5773
+ export declare const makeUndoCapture: (rawTx: DataStore, crdtColumnsOf?: (table: string) => ReadonlySet<string>) => {
5637
5774
  tx: DataStore;
5638
5775
  persist: (store: unknown, meta: {
5639
5776
  readonly tag: string;
@@ -5922,8 +6059,10 @@ export declare interface MutationLike {
5922
6059
  readonly requiresApproval?: AnyApprovalPolicy | undefined;
5923
6060
  readonly target?: {
5924
6061
  readonly table: string;
6062
+ readonly relations?: Readonly<Record<string, string>> | undefined;
5925
6063
  } | ReadonlyArray<{
5926
6064
  readonly table: string;
6065
+ readonly relations?: Readonly<Record<string, string>> | undefined;
5927
6066
  }> | undefined;
5928
6067
  };
5929
6068
  executor(input: unknown, ctx: unknown): unknown;
@@ -6869,6 +7008,25 @@ export declare interface ReactiveConfigInput {
6869
7008
  * warning to name a later one.
6870
7009
  */
6871
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
+ };
6872
7030
  }
6873
7031
 
6874
7032
  export declare interface ReactiveEnv {
@@ -6884,6 +7042,16 @@ export declare type ReactiveReturn<D extends ExecutorDescriptor> = D extends {
6884
7042
  readonly descriptor: QueryDescriptor;
6885
7043
  } : never;
6886
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
+
6887
7055
  export declare interface ReadClassification {
6888
7056
  /** Tables whose ROWS can reach the caller: the queried table and its joins. */
6889
7057
  readonly composed: ReadonlySet<string>;
@@ -6970,6 +7138,8 @@ export declare interface RebacSubject {
6970
7138
  readonly scopes?: ReadonlyArray<string>;
6971
7139
  }
6972
7140
 
7141
+ export declare const recordCoalesced: (label: string, collapsed: number) => void;
7142
+
6973
7143
  export declare const recordCredentialExpiry: (clientId: number, exp: number | undefined) => void;
6974
7144
 
6975
7145
  /** What the framework emit-seams (servePipeline / rpcServer / plugin wrappers)
@@ -6996,6 +7166,15 @@ export declare const recordEventPublished: (event: string) => void;
6996
7166
  /** Subscriber attached (+1) or detached (-1). */
6997
7167
  export declare const recordEventSubscribers: (event: string, delta: number) => void;
6998
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
+
6999
7178
  /**
7000
7179
  * A store that notes every read and changes nothing else.
7001
7180
  *
@@ -7006,6 +7185,10 @@ export declare const recordEventSubscribers: (event: string, delta: number) => v
7006
7185
  */
7007
7186
  export declare const recordingStore: (store: DataStore) => DataStore;
7008
7187
 
7188
+ export declare const recordOverrun: (label: string) => void;
7189
+
7190
+ export declare const recordOversized: (label: string) => void;
7191
+
7009
7192
  /**
7010
7193
  * Record a raw read, opening a scope for the rest of this execution context if
7011
7194
  * none is open yet.
@@ -7296,6 +7479,13 @@ export declare interface RegistryTableLike {
7296
7479
  * request was. */
7297
7480
  export declare const rehydrateGuards: (serialized: ReadonlyArray<SerializedGuard>) => ReadonlyArray<AnyGuardSpec>;
7298
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
+
7299
7489
  /** A relationship tuple — `subjectId` has `relation` on `<resourceType>:<resourceId>`.
7300
7490
  * In practice these are rows in a relation table; the engine takes them as data. */
7301
7491
  export declare interface RelationTuple {
@@ -7481,15 +7671,24 @@ export declare const resetComputedQueryCacheWarnings: () => void;
7481
7671
  /** Test/dev-only — clear ALL per-connection state. Don't call from app code. */
7482
7672
  export declare const _resetConnectionCredentialsForTest: () => void;
7483
7673
 
7674
+ /** Test seam — expire everything immediately. */
7675
+ export declare const resetDetachedForTest: () => void;
7676
+
7484
7677
  /** Test seams for the tag-cache guard — the cache is module-private otherwise. */
7485
7678
  export declare const resetEventMetricTagCacheForTests: () => void;
7486
7679
 
7487
7680
  /** Drop everything recorded. For tests; not called by the runtime. */
7488
7681
  export declare const resetObservedGraph: () => void;
7489
7682
 
7683
+ /** Test seam. */
7684
+ export declare const resetResumeRingsForTest: () => void;
7685
+
7490
7686
  /** Reset to the env default (tests). */
7491
7687
  export declare const resetSecretsBackend: () => void;
7492
7688
 
7689
+ /** Test seam. */
7690
+ export declare const resetSubscriptionSocketMetrics: () => void;
7691
+
7493
7692
  /**
7494
7693
  * The client address to rate-limit, geo-block and audit by.
7495
7694
  *
@@ -7808,6 +8007,46 @@ export declare const restrictingReadsEffect: <A, E, R>(effect: Effect.Effect<A,
7808
8007
  /** The full ordered list of result variants for a definition, holdout last. */
7809
8008
  export declare const resultVariantNames: (def: ExperimentDefinition) => ReadonlyArray<string>;
7810
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
+
7811
8050
  export { RetentionConflict }
7812
8051
 
7813
8052
  export { RetentionSource }
@@ -7882,6 +8121,12 @@ export declare const revokedIds: (before: ReadonlyArray<{
7882
8121
  readonly id: string;
7883
8122
  }>) => ReadonlyArray<string>;
7884
8123
 
8124
+ declare interface RingDelta {
8125
+ readonly revision: number;
8126
+ readonly emittedAt: number;
8127
+ readonly patch: RowPatch;
8128
+ }
8129
+
7885
8130
  /**
7886
8131
  * Live traffic the router fronts — read by idle detection (the orchestrator
7887
8132
  * can't see the app's HTTP/WS load directly when it's scaled to zero, but
@@ -8968,6 +9213,12 @@ export declare interface ServeRequestContext {
8968
9213
  readonly subject: unknown;
8969
9214
  readonly traceId: string;
8970
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;
8971
9222
  /**
8972
9223
  * Row-level visibility for this request, resolved by `withRowFilter` and read
8973
9224
  * by the context builder when it wraps the store.
@@ -9025,6 +9276,8 @@ export declare type ServeRpcInterceptor = (base: Effect.Effect<unknown, unknown,
9025
9276
  readonly target?: ReadonlyArray<string>;
9026
9277
  }) => Effect.Effect<unknown, unknown, never>;
9027
9278
 
9279
+ export declare const setBufferedBytes: (label: string, bytes: number) => void;
9280
+
9028
9281
  /** Register the process-wide connection resolver (or clear with `undefined`).
9029
9282
  * Called by the CLI at boot; tests call it directly. */
9030
9283
  export declare const setConnectionResolver: (resolver: ConnectionResolver | undefined) => void;
@@ -9032,6 +9285,10 @@ export declare const setConnectionResolver: (resolver: ConnectionResolver | unde
9032
9285
  /** Register the field cipher (or clear with `undefined`). */
9033
9286
  export declare const setFieldCipher: (cipher: FieldCipher | undefined) => void;
9034
9287
 
9288
+ export declare const setReactiveSocketTunables: (tunables: ReactiveSocketTunables | null) => void;
9289
+
9290
+ export declare const setResumeTunables: (tunables: ResumeTunables | null) => void;
9291
+
9035
9292
  /**
9036
9293
  * Boot-path wiring. `null` clears (shutdown). Registering `null` vs never
9037
9294
  * registering are deliberately the SAME state — a call before boot wiring and
@@ -9160,7 +9417,10 @@ export declare interface SnapshotCacheBinding {
9160
9417
  readonly swrMs: number | undefined;
9161
9418
  }
9162
9419
 
9163
- /** 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. */
9164
9424
  export declare const snapshotMetrics: Effect.Effect<ReadonlyArray<MetricSample>>;
9165
9425
 
9166
9426
  /** Eager snapshot for sync callers (the inspect endpoint). */
@@ -9425,6 +9685,22 @@ export declare interface SubscriberDefinition {
9425
9685
  readonly handler: SubscribeHandler;
9426
9686
  }
9427
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
+
9428
9704
  /**
9429
9705
  * Timing of ONE subscription delivery (the initial snapshot, or a delta
9430
9706
  * pushed on a change). The duration is the real produce→push latency:
@@ -9460,6 +9736,29 @@ export declare interface SubscriptionDeliveryRecord {
9460
9736
  readonly rowCount: number;
9461
9737
  }
9462
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
+
9463
9762
  export declare type SubscriptionRecord = {
9464
9763
  readonly id: string;
9465
9764
  readonly table: string;
@@ -9520,6 +9819,35 @@ export declare interface SubscriptionSnapshot {
9520
9819
  readonly subscriberCount: number;
9521
9820
  }
9522
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
+
9523
9851
  /** Drop handshake grants past their TTL. Wired into the boot retention sweep;
9524
9852
  * a grant is worthless the moment it expires and keeping them turns a hot
9525
9853
  * table into an audit liability. */
@@ -10390,24 +10718,6 @@ export declare const webhookVerificationOf: (handle: unknown) => WebhookVerifica
10390
10718
  */
10391
10719
  export declare type WhereOp = '=' | '!=' | '<>' | '>' | '>=' | '<' | '<=' | 'in' | 'contains' | 'startsWith' | 'fts';
10392
10720
 
10393
- /**
10394
- * Resolve this request's row-filter scope. A no-op (same object back) when the
10395
- * app registered no filter, so an app that uses none pays nothing.
10396
- *
10397
- * Every runner calls this before building a context. A read path that skipped
10398
- * it would have no row-level security and would look exactly like one that
10399
- * does — which is why it lives in the shared spine rather than in each
10400
- * entrypoint.
10401
- *
10402
- * REJECTS with the raw `RowFilterUnavailable` (via `runProvidedEffect`, not
10403
- * `Effect.runPromise`) when the filter's `onLoadError` is the default `'fail'`
10404
- * and `load` failed every retry. Raw, so the rpc encoder matches it against the
10405
- * descriptor's `error:` schema and the client gets the tag instead of an opaque
10406
- * defect — the same treatment `enforceGuards` gives a `ScopeError`. Proceeding
10407
- * with an unresolved filter is not an option here: the context builder would
10408
- * receive `rowFilter: undefined`, which means "this app registered no filter"
10409
- * and would silently serve the request UNFILTERED.
10410
- */
10411
10721
  export declare const withRowFilter: <R extends ServeRequestContext>(request: R) => Promise<R>;
10412
10722
 
10413
10723
  /**
@@ -10937,7 +11247,7 @@ export declare interface WorkflowWaitOptions {
10937
11247
  * calls (incl. multi-table within one mutation) are. A mutation that writes via
10938
11248
  * a bulk helper simply produces no undo entry (documented).
10939
11249
  */
10940
- 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;
10941
11251
 
10942
11252
  /**
10943
11253
  * Wrap a raw `DataStore` with the storage codec only — `.encrypted()` columns