@voltro/runtime 0.54.0 → 0.56.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 +639 -2
- package/dist/index.d.ts +347 -21
- package/dist/index.js +2283 -2079
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -78,6 +78,7 @@ import { Sampler } from '@opentelemetry/sdk-trace-base';
|
|
|
78
78
|
import { Schedule } from 'effect';
|
|
79
79
|
import { Schema } from 'effect';
|
|
80
80
|
import { ScopeError } from '@voltro/protocol';
|
|
81
|
+
import { Server } from 'node:http';
|
|
81
82
|
import { ServerOnlyColumnWrite } from '@voltro/protocol';
|
|
82
83
|
import { SpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
83
84
|
import { spawn } from 'node:child_process';
|
|
@@ -94,6 +95,7 @@ import { SyncLogger } from '@voltro/logger';
|
|
|
94
95
|
import { TableLike } from '@voltro/database';
|
|
95
96
|
import { TableName } from '@voltro/protocol';
|
|
96
97
|
import { TableValidationFailed } from '@voltro/protocol';
|
|
98
|
+
import { TargetRelation } from '@voltro/protocol';
|
|
97
99
|
import { TenantRowNotFound } from '@voltro/protocol';
|
|
98
100
|
import { TenantScopeViolation } from '@voltro/protocol';
|
|
99
101
|
import { Tracer } from 'effect';
|
|
@@ -1124,7 +1126,14 @@ export declare const applyConnectionCredential: (clientId: number, headers: Reco
|
|
|
1124
1126
|
* - a non-array value is a refusal naming the field — silently ignoring it
|
|
1125
1127
|
* would read as "saved" while the links stayed stale;
|
|
1126
1128
|
* - the row id is `output.id`, else `input.id`; with neither the declaration
|
|
1127
|
-
* is unusable and says so
|
|
1129
|
+
* is unusable and says so;
|
|
1130
|
+
* - the DECLARED junction columns must match the junction's real reference
|
|
1131
|
+
* pair. They are declaration data because the browser drives junction
|
|
1132
|
+
* auto-optimistic from the same block and cannot derive them; this is the
|
|
1133
|
+
* one place both readings meet, so a disagreement is refused here — before
|
|
1134
|
+
* any write — with the correct pair named. Letting it through would mean the
|
|
1135
|
+
* client patched one column while the server wrote another, and the UI would
|
|
1136
|
+
* look right until the delta contradicted it.
|
|
1128
1137
|
*/
|
|
1129
1138
|
export declare const applyDeclaredRelations: (params: {
|
|
1130
1139
|
readonly store: RelationLinksStore;
|
|
@@ -1163,6 +1172,9 @@ export declare const applyInverse: (store: UndoApplyStore, op: InverseOp) => Pro
|
|
|
1163
1172
|
* `synthesizeInverse`), as the caller's transaction wraps them. */
|
|
1164
1173
|
export declare const applyInverses: (store: UndoApplyStore, ops: ReadonlyArray<InverseOp>) => Promise<void>;
|
|
1165
1174
|
|
|
1175
|
+
/** Apply the timeouts to a node HTTP server. */
|
|
1176
|
+
export declare const applyKeepAlive: (server: Server, t: KeepAliveTimeouts) => void;
|
|
1177
|
+
|
|
1166
1178
|
/**
|
|
1167
1179
|
* Apply every rule matching `change`.
|
|
1168
1180
|
*
|
|
@@ -2939,6 +2951,18 @@ export declare const DEFAULT_DELIVERY_CONCURRENCY = 8;
|
|
|
2939
2951
|
* framework default. */
|
|
2940
2952
|
export declare const DEFAULT_HSTS = "max-age=15552000; includeSubDomains";
|
|
2941
2953
|
|
|
2954
|
+
/**
|
|
2955
|
+
* 72 seconds.
|
|
2956
|
+
*
|
|
2957
|
+
* The bound that matters is the PROXY's idle timeout, and the defaults cluster
|
|
2958
|
+
* at 60s (ALB, Envoy) and 75s (nginx). 72s clears the 60s group with margin and
|
|
2959
|
+
* sits just under nginx's — a deployment on nginx should raise this, and the
|
|
2960
|
+
* doc says so. Picking 76s instead would leave every ALB user holding a
|
|
2961
|
+
* connection the load balancer has already dropped, which is the same defect
|
|
2962
|
+
* mirrored; the 60s group is the larger one.
|
|
2963
|
+
*/
|
|
2964
|
+
export declare const DEFAULT_KEEP_ALIVE_MS = 72000;
|
|
2965
|
+
|
|
2942
2966
|
/** Ceiling the idle backoff climbs to. Deliberately short enough to be a
|
|
2943
2967
|
* FLOOR under a missed wake rather than a substitute for one. */
|
|
2944
2968
|
export declare const DEFAULT_MAX_IDLE_INTERVAL_MS = 30000;
|
|
@@ -2947,6 +2971,9 @@ export declare const DEFAULT_MAX_IDLE_INTERVAL_MS = 30000;
|
|
|
2947
2971
|
* small enough to stop a pathological body being buffered into memory. */
|
|
2948
2972
|
export declare const DEFAULT_MAX_RPC_BODY_BYTES: number;
|
|
2949
2973
|
|
|
2974
|
+
/** See {@link DrainDeps.claimLeaseMs}. */
|
|
2975
|
+
export declare const DEFAULT_OUTBOX_CLAIM_LEASE_MS: number;
|
|
2976
|
+
|
|
2950
2977
|
/**
|
|
2951
2978
|
* How many raw-SQL reads one request records for the undeclared-`dependsOn`
|
|
2952
2979
|
* diagnostic. A bound rather than none: the recording lives for as long as the
|
|
@@ -2983,6 +3010,15 @@ export declare const DEFAULT_SOCKET_OVERRUN_AFTER_MS = 10000;
|
|
|
2983
3010
|
|
|
2984
3011
|
export declare const DEFAULT_SOCKET_OVERSIZED_EVENT_BYTES = 262144;
|
|
2985
3012
|
|
|
3013
|
+
/**
|
|
3014
|
+
* How old a row may be before a reader should stop trusting it as current.
|
|
3015
|
+
*
|
|
3016
|
+
* Tunable rather than a constant because the write cadence is: a fleet that
|
|
3017
|
+
* writes every 30s and one that writes every 5m disagree about what "stale"
|
|
3018
|
+
* means, and a single number would be wrong for one of them.
|
|
3019
|
+
*/
|
|
3020
|
+
export declare const DEFAULT_STALE_AFTER_MS = 120000;
|
|
3021
|
+
|
|
2986
3022
|
/**
|
|
2987
3023
|
* The message of an UNDECLARED throw, bounded, with nothing else attached.
|
|
2988
3024
|
*
|
|
@@ -3428,13 +3464,39 @@ export declare interface DispatcherDependencies {
|
|
|
3428
3464
|
export declare const DORMANCY_WAKEUP_TENANT = "default";
|
|
3429
3465
|
|
|
3430
3466
|
export declare interface DrainDeps {
|
|
3431
|
-
readonly store: Pick<DataStore, 'query' | 'update' | 'insert' | 'delete'>;
|
|
3467
|
+
readonly store: Pick<DataStore, 'query' | 'update' | 'updateMany' | 'insert' | 'delete'>;
|
|
3432
3468
|
readonly handlers: ReadonlyMap<string, OutboxHandlerDefinition>;
|
|
3433
3469
|
readonly now?: () => Date;
|
|
3434
3470
|
/** Max rows per drain pass. */
|
|
3435
3471
|
readonly batchSize?: number;
|
|
3436
3472
|
/** Per-entry cap on retained attempt rows. See {@link OUTBOX_ATTEMPT_LOG_CAP}. */
|
|
3437
3473
|
readonly attemptLogCap?: number;
|
|
3474
|
+
/**
|
|
3475
|
+
* Who this process is, stamped on every row it claims.
|
|
3476
|
+
*
|
|
3477
|
+
* REQUIRED, and deliberately so. The first version made it optional "so an
|
|
3478
|
+
* embedder driving a single process is not forced to invent an identity",
|
|
3479
|
+
* which meant a caller who omitted it silently got the old behaviour: every
|
|
3480
|
+
* replica delivers every row. An exactly-once gate that can be switched off by
|
|
3481
|
+
* leaving a field out is not a gate — it is a default, and the wrong one.
|
|
3482
|
+
*
|
|
3483
|
+
* The INSTANCE rather than the replica name: a pod that restarts under a
|
|
3484
|
+
* stable name must not look like it still holds the claim it died inside, or
|
|
3485
|
+
* the lease is reading its own ghost.
|
|
3486
|
+
*/
|
|
3487
|
+
readonly claimedBy: string;
|
|
3488
|
+
/**
|
|
3489
|
+
* How long a claim stands before another replica may take the row.
|
|
3490
|
+
*
|
|
3491
|
+
* It is a LEASE, not a lock: the holder can die, and nothing it left behind
|
|
3492
|
+
* may block delivery forever. It must therefore exceed the slowest handler
|
|
3493
|
+
* you are willing to wait for — a lease shorter than a real delivery hands
|
|
3494
|
+
* the same row to a second replica while the first is still working, which is
|
|
3495
|
+
* the duplicate this whole mechanism removes, reintroduced by a number.
|
|
3496
|
+
*
|
|
3497
|
+
* Default 5 minutes.
|
|
3498
|
+
*/
|
|
3499
|
+
readonly claimLeaseMs?: number;
|
|
3438
3500
|
}
|
|
3439
3501
|
|
|
3440
3502
|
/**
|
|
@@ -3466,14 +3528,6 @@ export declare const drainForShutdown: (options: {
|
|
|
3466
3528
|
}) => void;
|
|
3467
3529
|
}) => void;
|
|
3468
3530
|
|
|
3469
|
-
/**
|
|
3470
|
-
* One drain pass: claim due rows, run their handler, record the outcome.
|
|
3471
|
-
*
|
|
3472
|
-
* A row whose handler is unknown is left PENDING rather than dead-lettered —
|
|
3473
|
-
* the usual cause is a deploy where the enqueuing code shipped before the
|
|
3474
|
-
* handler, and discarding those would turn a rollout ordering detail into
|
|
3475
|
-
* permanent data loss.
|
|
3476
|
-
*/
|
|
3477
3531
|
export declare const drainOutbox: (deps: DrainDeps) => Promise<DrainResult>;
|
|
3478
3532
|
|
|
3479
3533
|
export declare interface DrainResult {
|
|
@@ -4536,6 +4590,38 @@ export declare interface FinopsRunnerHandle {
|
|
|
4536
4590
|
readonly runTick: () => void;
|
|
4537
4591
|
}
|
|
4538
4592
|
|
|
4593
|
+
/** A member of the fleet, as membership sees it. The shape this module needs
|
|
4594
|
+
* from `instanceMembership`, named so the merge can be tested without one. */
|
|
4595
|
+
export declare interface FleetMember {
|
|
4596
|
+
readonly instanceId: string;
|
|
4597
|
+
readonly startedAt: number;
|
|
4598
|
+
}
|
|
4599
|
+
|
|
4600
|
+
export declare interface FleetMergeResult<T> {
|
|
4601
|
+
/** One entry per replica that has written, newest generation only. */
|
|
4602
|
+
readonly byReplica: ReadonlyArray<{
|
|
4603
|
+
readonly replicaId: string;
|
|
4604
|
+
readonly version: string | undefined;
|
|
4605
|
+
readonly ageMs: number;
|
|
4606
|
+
/** Where a peer can reach it, or `undefined`. */
|
|
4607
|
+
readonly reachableAt: string | undefined;
|
|
4608
|
+
readonly payload: T;
|
|
4609
|
+
}>;
|
|
4610
|
+
/** Replicas membership knows about that have written nothing readable. */
|
|
4611
|
+
readonly missing: ReadonlyArray<string>;
|
|
4612
|
+
readonly responded: number;
|
|
4613
|
+
readonly expected: number;
|
|
4614
|
+
/** Rows old enough that a reader should not treat them as current. */
|
|
4615
|
+
readonly stale: ReadonlyArray<{
|
|
4616
|
+
readonly replicaId: string;
|
|
4617
|
+
readonly ageMs: number;
|
|
4618
|
+
}>;
|
|
4619
|
+
/** Framework versions among the responders, counted. Present only when they
|
|
4620
|
+
* DIFFER — a single-version fleet needs no such note, and a note nobody
|
|
4621
|
+
* needs is one everybody learns to skip. */
|
|
4622
|
+
readonly versions: Readonly<Record<string, number>> | undefined;
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4539
4625
|
export declare interface FluentStore extends Omit<MutationStore, 'update' | 'delete' | 'query'> {
|
|
4540
4626
|
/**
|
|
4541
4627
|
* Diff-based many-to-many link writer for a junction table. `anchor` names the
|
|
@@ -4657,6 +4743,17 @@ export declare const forbiddenColumnsForSubject: (tableName: string, subject: Su
|
|
|
4657
4743
|
/** Forget the calling subject's credential. Returns whether a row was removed. */
|
|
4658
4744
|
export declare const forgetCredential: (store: VaultStore, connectionId: string, subjectId: string) => Promise<boolean>;
|
|
4659
4745
|
|
|
4746
|
+
/**
|
|
4747
|
+
* Drop rows for a replica GENERATION that is gone.
|
|
4748
|
+
*
|
|
4749
|
+
* Not a retention sweep — the table has no history to age out. This removes
|
|
4750
|
+
* the row of a process that restarted, because the replica's NEW row is
|
|
4751
|
+
* written under the same `(replicaId, kind)` key and the old generation is
|
|
4752
|
+
* simply overwritten. What this handles is a replica that went away for good
|
|
4753
|
+
* (a scale-down), whose row would otherwise be counted forever.
|
|
4754
|
+
*/
|
|
4755
|
+
export declare const forgetReplicaObservations: (store: DataStore, replicaId: string) => Promise<void>;
|
|
4756
|
+
|
|
4660
4757
|
/** A boot-ready message for a set of cache-scope leaks (empty → `undefined`). */
|
|
4661
4758
|
export declare const formatCacheScopeLeaks: (leaks: ReadonlyArray<CacheScopeLeak>) => string | undefined;
|
|
4662
4759
|
|
|
@@ -4768,6 +4865,9 @@ export declare type HandlerBody = void | Promise<unknown> | Effect.Effect<unknow
|
|
|
4768
4865
|
*/
|
|
4769
4866
|
export declare const hashUnitInterval: (s: string) => number;
|
|
4770
4867
|
|
|
4868
|
+
/** How much longer than `keepAliveTimeout` the headers deadline runs. */
|
|
4869
|
+
export declare const HEADERS_TIMEOUT_MARGIN_MS = 5000;
|
|
4870
|
+
|
|
4771
4871
|
/** Define a histogram. `boundaries` default to the framework duration buckets. */
|
|
4772
4872
|
export declare const histogramMetric: (name: string, boundaries?: MetricBoundaries.MetricBoundaries, description?: string) => Metric.Metric.Histogram<number>;
|
|
4773
4873
|
|
|
@@ -5244,6 +5344,17 @@ export declare const ivmValue: (shape: AggregateShape, g: GroupState) => number
|
|
|
5244
5344
|
* columns is rejected with a message naming what it found.
|
|
5245
5345
|
*/
|
|
5246
5346
|
export declare interface JunctionLinks {
|
|
5347
|
+
/**
|
|
5348
|
+
* The junction's two reference columns as RESOLVED for this anchor — the
|
|
5349
|
+
* source of truth a declared `relations:` is verified against. It is exposed
|
|
5350
|
+
* because the pair also has to be stated in the descriptor (the browser
|
|
5351
|
+
* cannot derive it), and a stated fact that nothing compares against is a
|
|
5352
|
+
* divergence waiting to happen.
|
|
5353
|
+
*/
|
|
5354
|
+
readonly columns: {
|
|
5355
|
+
readonly anchorColumn: string;
|
|
5356
|
+
readonly targetColumn: string;
|
|
5357
|
+
};
|
|
5247
5358
|
/** The current target ids linked to the anchor. */
|
|
5248
5359
|
list(): Promise<ReadonlyArray<string>>;
|
|
5249
5360
|
/** Reconcile the links to EXACTLY `targetIds` — insert the missing, delete the
|
|
@@ -5275,6 +5386,23 @@ export declare interface JunctionLinks {
|
|
|
5275
5386
|
}>;
|
|
5276
5387
|
}
|
|
5277
5388
|
|
|
5389
|
+
/** The `http:` block's keep-alive knobs. */
|
|
5390
|
+
export declare interface KeepAliveConfig {
|
|
5391
|
+
readonly http?: {
|
|
5392
|
+
/** Idle keep-alive lifetime, ms. Default 72000. Raise it above YOUR
|
|
5393
|
+
* proxy's idle timeout — see `DEFAULT_KEEP_ALIVE_MS` for why 72s. */
|
|
5394
|
+
readonly keepAliveTimeoutMs?: number;
|
|
5395
|
+
/** Header-completion deadline, ms. Must exceed `keepAliveTimeoutMs`.
|
|
5396
|
+
* Default `keepAliveTimeoutMs + 5000`. */
|
|
5397
|
+
readonly headersTimeoutMs?: number;
|
|
5398
|
+
};
|
|
5399
|
+
}
|
|
5400
|
+
|
|
5401
|
+
export declare interface KeepAliveTimeouts {
|
|
5402
|
+
readonly keepAliveTimeoutMs: number;
|
|
5403
|
+
readonly headersTimeoutMs: number;
|
|
5404
|
+
}
|
|
5405
|
+
|
|
5278
5406
|
export declare interface KvFacade {
|
|
5279
5407
|
readonly kv: AsyncKv;
|
|
5280
5408
|
/** The resolved Effect-native `Kv` service instance. Its methods close over
|
|
@@ -6010,6 +6138,25 @@ export declare const memoryRywPositionStore: (options?: MemoryRywOptions) => Ryw
|
|
|
6010
6138
|
*/
|
|
6011
6139
|
export declare const mergeCookieHeader: (existing: string | undefined, patch: Readonly<Record<string, string>>) => string;
|
|
6012
6140
|
|
|
6141
|
+
/**
|
|
6142
|
+
* Merge rows into a fleet view, and say what is missing from it.
|
|
6143
|
+
*
|
|
6144
|
+
* The three things that make this honest, each of which a naive `rows.map()`
|
|
6145
|
+
* gets wrong:
|
|
6146
|
+
*
|
|
6147
|
+
* 1. **A replica membership knows about but that has no row is MISSING**, not
|
|
6148
|
+
* absent. Dropping it silently is the unmarked sample this whole feature
|
|
6149
|
+
* exists to end — one level up, and more expensive, because the reader now
|
|
6150
|
+
* believes they asked everybody.
|
|
6151
|
+
* 2. **A stale row is reported WITH its age** rather than filtered out.
|
|
6152
|
+
* Removing it would make a partial answer look complete; keeping it
|
|
6153
|
+
* unmarked would make fiction look current. Neither is available.
|
|
6154
|
+
* 3. **Mixed versions are named.** During a rolling deploy the fleet genuinely
|
|
6155
|
+
* spans two shapes, and an aggregate that averages them silently is wrong
|
|
6156
|
+
* in a way nothing downstream can detect.
|
|
6157
|
+
*/
|
|
6158
|
+
export declare const mergeFleetObservations: <T>(rows: ReadonlyArray<ReplicaObservationRow>, members: ReadonlyArray<FleetMember>, staleAfterMs?: number) => FleetMergeResult<T>;
|
|
6159
|
+
|
|
6013
6160
|
export { MetricBoundaries }
|
|
6014
6161
|
|
|
6015
6162
|
export declare interface MetricBucketPoint {
|
|
@@ -6087,10 +6234,10 @@ export declare interface MutationLike {
|
|
|
6087
6234
|
readonly requiresApproval?: AnyApprovalPolicy | undefined;
|
|
6088
6235
|
readonly target?: {
|
|
6089
6236
|
readonly table: string;
|
|
6090
|
-
readonly relations?: Readonly<Record<string,
|
|
6237
|
+
readonly relations?: Readonly<Record<string, TargetRelation>> | undefined;
|
|
6091
6238
|
} | ReadonlyArray<{
|
|
6092
6239
|
readonly table: string;
|
|
6093
|
-
readonly relations?: Readonly<Record<string,
|
|
6240
|
+
readonly relations?: Readonly<Record<string, TargetRelation>> | undefined;
|
|
6094
6241
|
}> | undefined;
|
|
6095
6242
|
};
|
|
6096
6243
|
executor(input: unknown, ctx: unknown): unknown;
|
|
@@ -6299,6 +6446,16 @@ declare const NoRowFound_base: Schema.TaggedErrorClass<NoRowFound, "NoRowFound",
|
|
|
6299
6446
|
/** Note one read against the in-flight procedure. No scope → nothing happens. */
|
|
6300
6447
|
export declare const noteRead: (descriptor: QueryDescriptor) => void;
|
|
6301
6448
|
|
|
6449
|
+
/**
|
|
6450
|
+
* Record the resume verdict for one subscription setup. `true` means a ring
|
|
6451
|
+
* was recorded; anything else names why it was not.
|
|
6452
|
+
*
|
|
6453
|
+
* Logs at debug ONCE per (label, verdict) — a hot query must not narrate every
|
|
6454
|
+
* subscribe, and the second identical verdict carries no information the first
|
|
6455
|
+
* did not.
|
|
6456
|
+
*/
|
|
6457
|
+
export declare const noteResumeVerdict: (rawLabel: string, verdict: true | ResumeExclusion) => void;
|
|
6458
|
+
|
|
6302
6459
|
export declare interface Notification {
|
|
6303
6460
|
readonly subscriberId: string;
|
|
6304
6461
|
readonly fingerprint: string;
|
|
@@ -7015,10 +7172,21 @@ export declare class ReactionRateLimiter {
|
|
|
7015
7172
|
}
|
|
7016
7173
|
|
|
7017
7174
|
export declare interface ReactionRunDeps {
|
|
7018
|
-
/**
|
|
7175
|
+
/**
|
|
7176
|
+
* Atomically claim ONE logical change. `true` = ours, act. `false` = someone
|
|
7177
|
+
* already has it (or the claim could not be taken — fail-closed, like the
|
|
7178
|
+
* rate slot and the budget beside it).
|
|
7179
|
+
*
|
|
7180
|
+
* ONE operation, not `has` then `add`, and the difference is the whole
|
|
7181
|
+
* guarantee. A read-then-write cannot be won by exactly one caller: two
|
|
7182
|
+
* replicas both read "not seen", both act, and `dedupeKey` — the field
|
|
7183
|
+
* `defineReaction` REQUIRES precisely so an act cannot repeat — delivers N
|
|
7184
|
+
* acts on N replicas. It was worse than a missing gate, because it read as a
|
|
7185
|
+
* present one: the in-memory set behind it was per-process, so every test and
|
|
7186
|
+
* every single-instance run confirmed it worked.
|
|
7187
|
+
*/
|
|
7019
7188
|
readonly dedupe: {
|
|
7020
|
-
readonly
|
|
7021
|
-
readonly add: (key: string) => void | Promise<void>;
|
|
7189
|
+
readonly claim: (key: string) => boolean | Promise<boolean>;
|
|
7022
7190
|
};
|
|
7023
7191
|
/** Resolve + run the reaction's act AS the agent actor. Injected by the serve
|
|
7024
7192
|
* layer (it owns agent/workflow execution). */
|
|
@@ -7137,6 +7305,16 @@ export declare const readPollCeilingMs: (env?: {
|
|
|
7137
7305
|
/** Is anything listening? Call sites skip their work when nothing is. */
|
|
7138
7306
|
export declare const readRecordingActive: () => boolean;
|
|
7139
7307
|
|
|
7308
|
+
/**
|
|
7309
|
+
* Every replica's rows for one kind.
|
|
7310
|
+
*
|
|
7311
|
+
* `now` is injected so a caller can compute ages against one instant rather
|
|
7312
|
+
* than a moving clock — two rows compared against two `Date.now()` calls are
|
|
7313
|
+
* two measurements, and the difference between them is noise a reader would
|
|
7314
|
+
* read as signal.
|
|
7315
|
+
*/
|
|
7316
|
+
export declare const readReplicaObservations: (store: DataStore, kind: ReplicaObservationKind, now?: number) => Promise<ReadonlyArray<ReplicaObservationRow>>;
|
|
7317
|
+
|
|
7140
7318
|
/** `admin:full` (mirrors `@voltro/protocol`'s ADMIN_SCOPE) bypasses ReBAC. */
|
|
7141
7319
|
export declare const REBAC_ADMIN_SCOPE = "admin:full";
|
|
7142
7320
|
|
|
@@ -7531,9 +7709,15 @@ export declare interface RegistryTableLike {
|
|
|
7531
7709
|
* request was. */
|
|
7532
7710
|
export declare const rehydrateGuards: (serialized: ReadonlyArray<SerializedGuard>) => ReadonlyArray<AnyGuardSpec>;
|
|
7533
7711
|
|
|
7534
|
-
/** The store slice `applyDeclaredRelations` needs.
|
|
7712
|
+
/** The store slice `applyDeclaredRelations` needs. `columns` is the junction's
|
|
7713
|
+
* DERIVED reference pair — the truth the descriptor's declaration is checked
|
|
7714
|
+
* against before anything is written. */
|
|
7535
7715
|
declare interface RelationLinksStore {
|
|
7536
7716
|
relationLinks(junctionTable: string, anchorTable: string, anchorId: string): {
|
|
7717
|
+
readonly columns: {
|
|
7718
|
+
readonly anchorColumn: string;
|
|
7719
|
+
readonly targetColumn: string;
|
|
7720
|
+
};
|
|
7537
7721
|
set(targetIds: ReadonlyArray<string>): Promise<unknown>;
|
|
7538
7722
|
};
|
|
7539
7723
|
}
|
|
@@ -7556,6 +7740,8 @@ export declare interface RelationTuple {
|
|
|
7556
7740
|
*/
|
|
7557
7741
|
export declare const releaseWakeup: (store: DataStore, ref: WakeupRef) => Promise<void>;
|
|
7558
7742
|
|
|
7743
|
+
export declare const REPLICA_OBSERVATIONS_TABLE = "_voltro_replica_observations";
|
|
7744
|
+
|
|
7559
7745
|
export declare interface ReplicaHealth {
|
|
7560
7746
|
/** Whether this replica is currently eligible for reads. */
|
|
7561
7747
|
isHealthy(replica: DataStore): boolean;
|
|
@@ -7585,6 +7771,27 @@ export declare interface ReplicaHealthOptions {
|
|
|
7585
7771
|
readonly now?: () => number;
|
|
7586
7772
|
}
|
|
7587
7773
|
|
|
7774
|
+
/** Which observation a row carries. A closed set — a new kind is a deliberate
|
|
7775
|
+
* addition with a reader, not a free-form label. */
|
|
7776
|
+
export declare type ReplicaObservationKind = 'resume' | 'subscriptions';
|
|
7777
|
+
|
|
7778
|
+
/** One replica's row, as a reader gets it back. */
|
|
7779
|
+
export declare interface ReplicaObservationRow {
|
|
7780
|
+
readonly replicaId: string;
|
|
7781
|
+
readonly startedAt: number;
|
|
7782
|
+
readonly version: string | undefined;
|
|
7783
|
+
readonly kind: ReplicaObservationKind;
|
|
7784
|
+
readonly payload: unknown;
|
|
7785
|
+
/** Where a peer can reach it, or `undefined` — never a guess. */
|
|
7786
|
+
readonly reachableAt: string | undefined;
|
|
7787
|
+
readonly writtenAt: number;
|
|
7788
|
+
/** How old this row is, on the READER's clock. Approximate by construction —
|
|
7789
|
+
* it compares two machines' clocks, which is exactly what
|
|
7790
|
+
* `instanceMembership` refuses to do for liveness. It is good enough to
|
|
7791
|
+
* say "this is minutes old", and must never be used to order two rows. */
|
|
7792
|
+
readonly ageMs: number;
|
|
7793
|
+
}
|
|
7794
|
+
|
|
7588
7795
|
/** Captured per-read for observability. Pushed into trace buffers
|
|
7589
7796
|
* by the runtime so `voltro traces` shows the routing decision +
|
|
7590
7797
|
* any RYW wait latency. */
|
|
@@ -7732,6 +7939,9 @@ export declare const resetEventMetricTagCacheForTests: () => void;
|
|
|
7732
7939
|
/** Drop everything recorded. For tests; not called by the runtime. */
|
|
7733
7940
|
export declare const resetObservedGraph: () => void;
|
|
7734
7941
|
|
|
7942
|
+
/** Clear the census. For tests; a running app has no reason to forget. */
|
|
7943
|
+
export declare const resetResumeCensus: () => void;
|
|
7944
|
+
|
|
7735
7945
|
/** Test seam. */
|
|
7736
7946
|
export declare const resetResumeRingsForTest: () => void;
|
|
7737
7947
|
|
|
@@ -7881,6 +8091,8 @@ export declare interface ResolvedWorkflowCallerContext extends WorkflowCallerCon
|
|
|
7881
8091
|
readonly traceId: string;
|
|
7882
8092
|
}
|
|
7883
8093
|
|
|
8094
|
+
export declare const resolveKeepAlive: (config: KeepAliveConfig | null | undefined) => KeepAliveTimeouts;
|
|
8095
|
+
|
|
7884
8096
|
/**
|
|
7885
8097
|
* Resolve the guard's configuration.
|
|
7886
8098
|
*
|
|
@@ -8071,6 +8283,31 @@ export declare type ResumeAnswer =
|
|
|
8071
8283
|
readonly kind: 'snapshot';
|
|
8072
8284
|
};
|
|
8073
8285
|
|
|
8286
|
+
/** The census as of now, one entry per label that has been subscribed. */
|
|
8287
|
+
export declare const resumeCensus: () => ReadonlyArray<ResumeVerdict>;
|
|
8288
|
+
|
|
8289
|
+
/**
|
|
8290
|
+
* Why a subscription carries no resume ring.
|
|
8291
|
+
*
|
|
8292
|
+
* - `computed` — the executor returned a value, not a descriptor. There is no
|
|
8293
|
+
* delta chain to replay: the handler re-runs opaquely and emits snapshots.
|
|
8294
|
+
* Declaring `tables:` on the row filter cannot change this; returning the
|
|
8295
|
+
* query builder instead of a mapped value can.
|
|
8296
|
+
* - `row-filter` — the registered filter may narrow this subscription's source
|
|
8297
|
+
* table, so its row set is re-resolved per delivery. Declaring `tables:`
|
|
8298
|
+
* fixes it for every source the filter does NOT touch; for a source it does
|
|
8299
|
+
* touch, the exclusion is the point.
|
|
8300
|
+
* - `eager-load` — the descriptor carries `.with(...)` while a filter is
|
|
8301
|
+
* registered, and the relation is resolved below the seam that would narrow
|
|
8302
|
+
* it. Reported only when the base table is NOT itself narrowed, so this
|
|
8303
|
+
* reason always means: drop the eager load and the subscription resumes.
|
|
8304
|
+
* - `uncanonical-input` — the input cannot be reduced to a stable ring key, so
|
|
8305
|
+
* two different inputs would collide under one label.
|
|
8306
|
+
* - `not-offered` — the binder was driven without resume input at all (the
|
|
8307
|
+
* lower-level entrypoint used by tests).
|
|
8308
|
+
*/
|
|
8309
|
+
export declare type ResumeExclusion = 'computed' | 'row-filter' | 'eager-load' | 'uncanonical-input' | 'not-offered';
|
|
8310
|
+
|
|
8074
8311
|
/**
|
|
8075
8312
|
* The identity a ring is recorded under — and the identity a resume must
|
|
8076
8313
|
* present to read it back. `subjectId`/`tenantId` are part of the KEY, so a
|
|
@@ -8099,6 +8336,14 @@ export declare interface ResumeTunables {
|
|
|
8099
8336
|
|
|
8100
8337
|
export declare const resumeTunables: () => ResumeTunables;
|
|
8101
8338
|
|
|
8339
|
+
/** One label's tally. `resumable + Σ excluded` is the number of subscriptions
|
|
8340
|
+
* set up on this label since the process started. */
|
|
8341
|
+
export declare interface ResumeVerdict {
|
|
8342
|
+
readonly label: string;
|
|
8343
|
+
readonly resumable: number;
|
|
8344
|
+
readonly excluded: Readonly<Partial<Record<ResumeExclusion, number>>>;
|
|
8345
|
+
}
|
|
8346
|
+
|
|
8102
8347
|
export { RetentionConflict }
|
|
8103
8348
|
|
|
8104
8349
|
export { RetentionSource }
|
|
@@ -8279,9 +8524,19 @@ export declare interface RowFilter<Ctx = unknown> {
|
|
|
8279
8524
|
* must not replay deltas on reconnect — the answer can change while the
|
|
8280
8525
|
* socket is down (a membership ends) and a wrong replay leaks rows. But the
|
|
8281
8526
|
* question the framework could ask was only "is a filter registered at
|
|
8282
|
-
* all?", so ONE registration disabled delta-resume for the whole process
|
|
8283
|
-
*
|
|
8284
|
-
*
|
|
8527
|
+
* all?", so ONE registration disabled delta-resume for the whole process,
|
|
8528
|
+
* including every subscription reading a table the predicate never returns
|
|
8529
|
+
* anything for.
|
|
8530
|
+
*
|
|
8531
|
+
* WHAT IT DOES NOT BUY, and this bounds the whole feature: a query whose
|
|
8532
|
+
* executor returns a VALUE rather than a descriptor — anything that maps the
|
|
8533
|
+
* rows or wraps them in a page envelope — has no delta chain to resume in
|
|
8534
|
+
* the first place. It re-runs an opaque handler and emits snapshots, filter
|
|
8535
|
+
* or no filter. So the number of subscriptions a declaration can give back
|
|
8536
|
+
* is the number that bind a descriptor, not the number of queries in the
|
|
8537
|
+
* app; declare it anyway (it costs nothing and applies the moment a query
|
|
8538
|
+
* returns a builder), but read `resumeCensus()` — or
|
|
8539
|
+
* `/_voltro/inspect/subscriptions` — before expecting a change.
|
|
8285
8540
|
*
|
|
8286
8541
|
* WHY A DECLARATION RATHER THAN A PROBE. Resolving the scope at subscribe
|
|
8287
8542
|
* and treating `predicate(ctx, source) === undefined` as safe is cheaper and
|
|
@@ -8297,9 +8552,14 @@ export declare interface RowFilter<Ctx = unknown> {
|
|
|
8297
8552
|
* filter (this field absent) keeps the conservative behaviour: no resume
|
|
8298
8553
|
* anywhere, no verification.
|
|
8299
8554
|
*
|
|
8555
|
+
* const OWNED = new Set(['documents', 'comments'])
|
|
8300
8556
|
* setRowFilter({
|
|
8301
|
-
* load,
|
|
8302
|
-
*
|
|
8557
|
+
* load,
|
|
8558
|
+
* predicate: (ctx, table) => (OWNED.has(table) ? eq('ownerId', ctx.userId) : undefined),
|
|
8559
|
+
* // Derived from the same set the predicate reads. Two hand-kept
|
|
8560
|
+
* // lists is the shape in which a table lands in exactly one of
|
|
8561
|
+
* // them — and the penalty here is a failing read, not a warning.
|
|
8562
|
+
* tables: [...OWNED],
|
|
8303
8563
|
* })
|
|
8304
8564
|
*/
|
|
8305
8565
|
readonly tables?: ReadonlyArray<string>;
|
|
@@ -9814,6 +10074,61 @@ export declare interface SubscriberDefinition {
|
|
|
9814
10074
|
readonly table: string;
|
|
9815
10075
|
/** Default `'any'`. */
|
|
9816
10076
|
readonly on?: SubscribeOp | ReadonlyArray<Exclude<SubscribeOp, 'any'>>;
|
|
10077
|
+
/**
|
|
10078
|
+
* Run the handler ONCE PER CHANGE across the whole fleet, instead of once on
|
|
10079
|
+
* every replica that sees it.
|
|
10080
|
+
*
|
|
10081
|
+
* **Without it, the handler runs on EVERY api instance.** That is the correct
|
|
10082
|
+
* default and not a gap: a subscriber that refreshes a search index, drops a
|
|
10083
|
+
* process-local cache entry or updates in-memory state has to run everywhere,
|
|
10084
|
+
* and a fleet-wide gate would leave every other replica stale. The default
|
|
10085
|
+
* assumes the handler is IDEMPOTENT.
|
|
10086
|
+
*
|
|
10087
|
+
* It is the wrong default for an EFFECT — a notification, a mail, a webhook, a
|
|
10088
|
+
* payment — because there is nothing to make idempotent: the effect IS the
|
|
10089
|
+
* write, so each run produces another one. Two replicas send two mails.
|
|
10090
|
+
*
|
|
10091
|
+
* `once` returns a deterministic key for the change, and exactly one replica
|
|
10092
|
+
* wins the claim on it (`_voltro_change_claims`, INSERT-wins on a UNIQUE — the
|
|
10093
|
+
* same arbiter the cron scheduler uses for a firing). The key is namespaced by
|
|
10094
|
+
* this subscriber's file id, so two subscribers on one table never lock each
|
|
10095
|
+
* other out.
|
|
10096
|
+
*
|
|
10097
|
+
* ```ts
|
|
10098
|
+
* export default defineSubscriber({
|
|
10099
|
+
* table: 'absence_requests',
|
|
10100
|
+
* on: ['insert'],
|
|
10101
|
+
* once: true,
|
|
10102
|
+
* handler: notifyApprovers,
|
|
10103
|
+
* })
|
|
10104
|
+
* ```
|
|
10105
|
+
*
|
|
10106
|
+
* **`once: true` derives the key for you**, and is the right default choice.
|
|
10107
|
+
* It names the change by its CONTENT plus its position among content-identical
|
|
10108
|
+
* repeats (`changeDigest` + `OccurrenceCounter`, `@voltro/database`) — which is
|
|
10109
|
+
* the only thing two replicas provably agree on, since a fleet change carries
|
|
10110
|
+
* no LSN, no commit id and no `traceId`. `A→B`, then `B→A`, then `A→B` again is
|
|
10111
|
+
* three changes, not two.
|
|
10112
|
+
*
|
|
10113
|
+
* **A function is for when you want to be COARSER than one-per-change.** Two
|
|
10114
|
+
* updates that differ only in a field you do not care about are two changes to
|
|
10115
|
+
* `once: true` and can be one to a key function that ignores that field.
|
|
10116
|
+
*
|
|
10117
|
+
* **If you write one, it must tell two genuine changes apart.** A row id is
|
|
10118
|
+
* enough for `insert` and `delete`, where a row changes state once. It is NOT
|
|
10119
|
+
* enough for `update`: two edits to the same row produce the same id, and the
|
|
10120
|
+
* second would be dropped as a duplicate of the first. Put something that moves
|
|
10121
|
+
* in the key — `` `${row.id}:${row.updatedAt}` `` — or use `once: true`.
|
|
10122
|
+
*
|
|
10123
|
+
* **AT MOST once, not exactly once.** The claim is taken BEFORE the handler
|
|
10124
|
+
* runs, so a replica that wins and then dies takes the event with it, and a
|
|
10125
|
+
* claim that cannot be written (database unreachable) is not taken by anyone.
|
|
10126
|
+
* Both are loud in the log and neither is retried — a subscriber is
|
|
10127
|
+
* best-effort by construction. When the effect must not be lost, the change
|
|
10128
|
+
* stream is the wrong seam for it: run it in the mutation, or start a workflow
|
|
10129
|
+
* from a `*.reaction.tsx`, where the durability is the primitive's job.
|
|
10130
|
+
*/
|
|
10131
|
+
readonly once?: true | ((event: SubscribeEvent) => string);
|
|
9817
10132
|
readonly handler: SubscribeHandler;
|
|
9818
10133
|
}
|
|
9819
10134
|
|
|
@@ -10650,6 +10965,8 @@ export declare const VOLTRO_TENANT_MIXIN_ID: "voltro/tenant";
|
|
|
10650
10965
|
*/
|
|
10651
10966
|
export declare const _voltroApprovalsTable: TableLike;
|
|
10652
10967
|
|
|
10968
|
+
export declare const _voltroChangeClaimsTable: TableLike;
|
|
10969
|
+
|
|
10653
10970
|
/**
|
|
10654
10971
|
* `_voltro_connection_grants` — an IN-FLIGHT oauth2 handshake. Separate from
|
|
10655
10972
|
* the credential table on purpose: a handshake is a short-lived, single-use
|
|
@@ -11403,4 +11720,13 @@ export declare const wrapStoreWithBootCodec: (underlying: DataStore, dialectId:
|
|
|
11403
11720
|
|
|
11404
11721
|
export declare const wrapStoreWithMixinBehaviour: (rawUnderlying: DataStore, ctx: StoreMiddlewareContext) => FluentStore;
|
|
11405
11722
|
|
|
11723
|
+
/**
|
|
11724
|
+
* Write (upsert) this replica's counters for one kind.
|
|
11725
|
+
*
|
|
11726
|
+
* Best-effort throughout: a diagnostic that can fail a request, or a boot, is a
|
|
11727
|
+
* new way to be down. A failure logs at debug and leaves the previous row —
|
|
11728
|
+
* which then reads as stale, which is the truth.
|
|
11729
|
+
*/
|
|
11730
|
+
export declare const writeReplicaObservation: (store: DataStore, kind: ReplicaObservationKind, payload: unknown) => Promise<void>;
|
|
11731
|
+
|
|
11406
11732
|
export { }
|