@voltro/runtime 0.5.0 → 0.7.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 +68 -1
- package/dist/index.d.ts +841 -69
- package/dist/index.js +1859 -1120
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { CaughtUpVerdict } from '@voltro/database';
|
|
|
4
4
|
import { ChangeEvent } from '@voltro/database';
|
|
5
5
|
import { clearRetentions } from '@voltro/database';
|
|
6
6
|
import { ConnectionInfo } from '@voltro/protocol';
|
|
7
|
+
import { ConnectionKind } from '@voltro/protocol';
|
|
8
|
+
import { ConnectionState } from '@voltro/protocol';
|
|
7
9
|
import { Context } from 'effect';
|
|
8
10
|
import { createServer } from 'node:http';
|
|
9
11
|
import { Cron } from 'effect';
|
|
@@ -40,11 +42,12 @@ import { registerRetention } from '@voltro/database';
|
|
|
40
42
|
import { RestRouteDescriptor } from '@voltro/protocol/rest';
|
|
41
43
|
import { RetentionSpec } from '@voltro/database';
|
|
42
44
|
import { retentionTtlMsFromEnv } from '@voltro/database';
|
|
43
|
-
import { Row
|
|
45
|
+
import { Row } from '@voltro/database';
|
|
44
46
|
import { Rpc } from '@effect/rpc';
|
|
45
47
|
import { RpcGroup } from '@effect/rpc';
|
|
46
48
|
import { RpcInterceptor } from '@voltro/protocol';
|
|
47
49
|
import { Sampler } from '@opentelemetry/sdk-trace-base';
|
|
50
|
+
import { Schedule } from 'effect';
|
|
48
51
|
import { Schema } from 'effect';
|
|
49
52
|
import { ScopeError } from '@voltro/protocol';
|
|
50
53
|
import { SpanProcessor } from '@opentelemetry/sdk-trace-base';
|
|
@@ -285,14 +288,14 @@ export declare class AggregateMaintainer {
|
|
|
285
288
|
private readonly groups;
|
|
286
289
|
constructor(shape: AggregateShape);
|
|
287
290
|
/** Seed from the full base row-set (boot / after a rescan-from-scratch). */
|
|
288
|
-
seed(baseRows: ReadonlyArray<
|
|
291
|
+
seed(baseRows: ReadonlyArray<Row_4>): void;
|
|
289
292
|
/**
|
|
290
293
|
* Apply one delta. Returns the groups (if any) whose min/max extreme was
|
|
291
294
|
* deleted and so MUST be rebuilt via `resolveRescan` before the next read.
|
|
292
295
|
*/
|
|
293
296
|
applyChange(delta: CdcDelta): ReadonlyArray<string>;
|
|
294
297
|
/** Rebuild the flagged groups from the current base rows (min/max recovery). */
|
|
295
|
-
resolveRescan(rescanGroups: ReadonlyArray<string>, baseRows: ReadonlyArray<
|
|
298
|
+
resolveRescan(rescanGroups: ReadonlyArray<string>, baseRows: ReadonlyArray<Row_4>): void;
|
|
296
299
|
/** The current materialized result — one entry per non-empty group. */
|
|
297
300
|
materialize(): ReadonlyArray<MaintainedGroup>;
|
|
298
301
|
private groupValues;
|
|
@@ -654,6 +657,8 @@ export declare interface AnalyticsTopEntry {
|
|
|
654
657
|
readonly value: number;
|
|
655
658
|
}
|
|
656
659
|
|
|
660
|
+
declare type AnyRow = Record<string, unknown>;
|
|
661
|
+
|
|
657
662
|
export declare const API_KEYS_TABLE = "_voltro_api_keys";
|
|
658
663
|
|
|
659
664
|
/** Admin-gated management routes (issue / list / revoke) for the built-in keys.
|
|
@@ -678,18 +683,15 @@ export declare interface ApiKeyServiceShape {
|
|
|
678
683
|
/** Mint a new key. Returns the raw token ONCE. */
|
|
679
684
|
readonly issue: (input: IssueInput) => Promise<IssuedApiKey>;
|
|
680
685
|
/** Hash-lookup used by the auth strategy: returns the key's identity if
|
|
681
|
-
* usable (not revoked / expired), and stamps `lastUsedAt`.
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
686
|
+
* usable (not revoked / expired), and stamps `lastUsedAt`.
|
|
687
|
+
*
|
|
688
|
+
* `createdBy` travels with it because a key always has an accountable
|
|
689
|
+
* human, and downstream needs to know WHICH one: a personal CLI key and a
|
|
690
|
+
* shared CI credential are the same shape on the wire, and telling them
|
|
691
|
+
* apart is what makes per-person accounting possible at all. */
|
|
692
|
+
readonly resolveByHash: (hash: string, now?: number) => Promise<ResolvedApiKey | null>;
|
|
687
693
|
/** Verify a raw token (hashes then resolves). */
|
|
688
|
-
readonly verify: (token: string, now?: number) => Promise<
|
|
689
|
-
id: string;
|
|
690
|
-
tenantId: string;
|
|
691
|
-
scopes: ReadonlyArray<string>;
|
|
692
|
-
} | null>;
|
|
694
|
+
readonly verify: (token: string, now?: number) => Promise<ResolvedApiKey | null>;
|
|
693
695
|
/** Revoke a key (irreversible). */
|
|
694
696
|
readonly revoke: (id: string, now?: number) => Promise<boolean>;
|
|
695
697
|
/** Rotate: revoke the old key + issue a replacement with the same name/scopes/tenant. */
|
|
@@ -765,6 +767,14 @@ export declare interface AppContext {
|
|
|
765
767
|
* simply isn't there rather than silently accepting writes.
|
|
766
768
|
*/
|
|
767
769
|
readonly outbox?: OutboxFacade;
|
|
770
|
+
/**
|
|
771
|
+
* Connection vault (`ctx.connections`) — per-subject third-party
|
|
772
|
+
* credentials. Present when the app declares at least one `*.connection.ts`.
|
|
773
|
+
* `ctx.connections.get('jira')` returns the CALLING subject's credential,
|
|
774
|
+
* refreshed if it was near expiry; there is no parameter for "some other
|
|
775
|
+
* subject", by construction.
|
|
776
|
+
*/
|
|
777
|
+
readonly connections?: ConnectionsFacade;
|
|
768
778
|
/**
|
|
769
779
|
* Request-scoped batching (`ctx.load` / `ctx.loadMany`). Coalesces
|
|
770
780
|
* same-tick reads of one table into a single `WHERE id IN (...)`.
|
|
@@ -811,6 +821,17 @@ export declare const applyInverse: (store: UndoApplyStore, op: InverseOp) => Pro
|
|
|
811
821
|
* `synthesizeInverse`), as the caller's transaction wraps them. */
|
|
812
822
|
export declare const applyInverses: (store: UndoApplyStore, ops: ReadonlyArray<InverseOp>) => Promise<void>;
|
|
813
823
|
|
|
824
|
+
/** AND-merge the row filter for `table` onto an existing predicate. */
|
|
825
|
+
export declare const applyRowFilter: (scope: RowFilterScope, table: string, predicate: Predicate | undefined) => Predicate | undefined;
|
|
826
|
+
|
|
827
|
+
/** Apply a resolved scope to a whole descriptor. Used by the dispatcher, which
|
|
828
|
+
* re-derives a subscription's read descriptor from its unfiltered base before
|
|
829
|
+
* every delivery. */
|
|
830
|
+
export declare const applyRowFilterToDescriptor: <D extends {
|
|
831
|
+
readonly table: string;
|
|
832
|
+
readonly predicate?: Predicate | undefined;
|
|
833
|
+
}>(scope: RowFilterScope, descriptor: D) => D;
|
|
834
|
+
|
|
814
835
|
export declare const applySoftDeleteScope: (descriptor: QueryDescriptor, softDeleteScopedTables: ReadonlySet<string>) => QueryDescriptor;
|
|
815
836
|
|
|
816
837
|
export declare const applyTenantScope: (descriptor: QueryDescriptor, tenantScopedTables: ReadonlySet<string>, tenantId: string | null | undefined) => QueryDescriptor;
|
|
@@ -843,6 +864,19 @@ export declare interface AppSupervisor {
|
|
|
843
864
|
* deny so it surfaces to the client typed. */
|
|
844
865
|
export declare const assertCan: (subject: RebacSubject, action: string, resource: RebacResource, deps: CanDeps) => void;
|
|
845
866
|
|
|
867
|
+
/**
|
|
868
|
+
* Refuse to boot an app that declares connections without a field cipher.
|
|
869
|
+
*
|
|
870
|
+
* Without this the first `encryptField` call throws at REQUEST time — which
|
|
871
|
+
* means a deployment looks healthy, accepts a user through a whole OAuth
|
|
872
|
+
* consent screen, and fails only at the moment it would have persisted the
|
|
873
|
+
* token. Moving the check to boot makes a missing key an operator-visible
|
|
874
|
+
* startup failure instead of a per-user mystery. It is deliberately NOT a
|
|
875
|
+
* warning: a warning here degrades to plaintext storage the moment someone
|
|
876
|
+
* "fixes" the throw.
|
|
877
|
+
*/
|
|
878
|
+
export declare const assertConnectionCipherConfigured: (definitions: ReadonlyArray<ConnectionDefinition>) => void;
|
|
879
|
+
|
|
846
880
|
/**
|
|
847
881
|
* Async cache surface exposed as `ctx.cache` to non-Effect handlers — the
|
|
848
882
|
* facade over the Effect-native `Cache` service from `@voltro/cache`
|
|
@@ -936,6 +970,27 @@ export declare const awaitServerListening: (server: ReturnType<typeof createServ
|
|
|
936
970
|
/** Exponential backoff with a ceiling — 1s, 2s, 4s … capped at 5 minutes. */
|
|
937
971
|
export declare const backoffMs: (attempt: number) => number;
|
|
938
972
|
|
|
973
|
+
/** Mint a single-use handshake grant and build the provider's consent URL. */
|
|
974
|
+
export declare const beginOAuthConnect: (deps: BeginOAuthDeps) => Promise<BeginOAuthResult>;
|
|
975
|
+
|
|
976
|
+
export declare interface BeginOAuthDeps {
|
|
977
|
+
readonly store: VaultStore;
|
|
978
|
+
readonly definition: OAuth2ConnectionDefinition;
|
|
979
|
+
readonly subjectId: string;
|
|
980
|
+
readonly tenantId: string | null;
|
|
981
|
+
readonly redirectTo?: string;
|
|
982
|
+
/** Absolute base URL the provider will redirect back to, e.g.
|
|
983
|
+
* `https://app.example.com`. Used only when the declaration omits an
|
|
984
|
+
* explicit `redirectUri`. */
|
|
985
|
+
readonly publicUrl: string;
|
|
986
|
+
readonly now?: () => Date;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export declare interface BeginOAuthResult {
|
|
990
|
+
readonly authorizeUrl: string;
|
|
991
|
+
readonly state: string;
|
|
992
|
+
}
|
|
993
|
+
|
|
939
994
|
/**
|
|
940
995
|
* Override the subject for an active connection. Called from the
|
|
941
996
|
* `auth.signin` (or any "I just authenticated this caller") handler
|
|
@@ -974,7 +1029,7 @@ export declare const bindStream: <Element, Err = unknown>(buildStream: (context:
|
|
|
974
1029
|
* the single explicit type-erasure boundary between the framework's
|
|
975
1030
|
* dynamic data layer and the rpc layer's static schemas.
|
|
976
1031
|
*/
|
|
977
|
-
export declare const bindSubscription: <T = ReadonlyArray<
|
|
1032
|
+
export declare const bindSubscription: <T = ReadonlyArray<Row>>(buildDescriptor: (context: RuntimeContext) => QueryDescriptor | ComputedQuery | Effect.Effect<QueryDescriptor | ComputedQuery, unknown, never>, dispatcher: Dispatcher, spanName?: string,
|
|
978
1033
|
/**
|
|
979
1034
|
* Optional resolver: given the materialised descriptor (after any
|
|
980
1035
|
* `.using()` hint), return the `MatcherIndexHint` the matcher should
|
|
@@ -1006,16 +1061,24 @@ cache?: {
|
|
|
1006
1061
|
* revoked grant CLOSES the stream rather than continuing to serve it.
|
|
1007
1062
|
* Omitted for a query that declares no guards.
|
|
1008
1063
|
*/
|
|
1009
|
-
reauthorize?: (subject: Subject) => () => Promise<unknown
|
|
1064
|
+
reauthorize?: (subject: Subject) => () => Promise<unknown>,
|
|
1065
|
+
/**
|
|
1066
|
+
* Per-delivery row-visibility resolver. Rarely passed: when omitted, one is
|
|
1067
|
+
* DERIVED from the registered row filter (see `defaultRefilter`), so an
|
|
1068
|
+
* entrypoint cannot end up with unfiltered subscriptions by forgetting to
|
|
1069
|
+
* thread it — which is precisely how a row filter ends up applying to
|
|
1070
|
+
* queries and mutations but not to the live stream.
|
|
1071
|
+
*/
|
|
1072
|
+
refilter?: (subject: Subject) => () => Promise<RowFilterScope>) => Stream.Stream<SubscriptionEvent<T>, never, SubjectService | ConnectionInfo>;
|
|
1010
1073
|
|
|
1011
|
-
export declare const bindSubscriptionUntyped: (descriptor: QueryDescriptor, dispatcher: Dispatcher, context: RuntimeContext, indexHint: MatcherIndexHint | undefined, label: string, cacheBinding: SnapshotCacheBinding | undefined, reauthorize: (() => Promise<unknown>) | undefined) => Stream.Stream<SubscriptionEvent<ReadonlyArray<
|
|
1074
|
+
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>;
|
|
1012
1075
|
|
|
1013
1076
|
export declare interface BrandedScheduleDefinition extends ScheduleDefinition {
|
|
1014
1077
|
readonly [SCHEDULE_BRAND]: true;
|
|
1015
1078
|
}
|
|
1016
1079
|
|
|
1017
1080
|
/** Build the full state from a base row-set (seed + per-group rescan). */
|
|
1018
|
-
export declare const buildIvmState: (shape: AggregateShape, rows: ReadonlyArray<
|
|
1081
|
+
export declare const buildIvmState: (shape: AggregateShape, rows: ReadonlyArray<Row_3>) => IvmState;
|
|
1019
1082
|
|
|
1020
1083
|
/** Build the preview diff from a mutation's captured change-set. Pure. */
|
|
1021
1084
|
export declare const buildPreview: (changes: ReadonlyArray<CdcChange>) => PreviewDiff;
|
|
@@ -1126,8 +1189,8 @@ export declare interface CdcChange {
|
|
|
1126
1189
|
/** A CDC change as the matcher delivers it. */
|
|
1127
1190
|
export declare interface CdcDelta {
|
|
1128
1191
|
readonly op: 'insert' | 'update' | 'delete';
|
|
1129
|
-
readonly old?:
|
|
1130
|
-
readonly new?:
|
|
1192
|
+
readonly old?: Row_3 | null;
|
|
1193
|
+
readonly new?: Row_3 | null;
|
|
1131
1194
|
}
|
|
1132
1195
|
|
|
1133
1196
|
export { ChangeEvent }
|
|
@@ -1199,6 +1262,10 @@ export declare type CircuitState = 'closed' | 'open' | 'half-open';
|
|
|
1199
1262
|
*/
|
|
1200
1263
|
export declare const classifyShape: (c: CandidateShape) => ShapeClassification;
|
|
1201
1264
|
|
|
1265
|
+
/** Test seam — the in-flight map is module state; a test that asserts
|
|
1266
|
+
* single-flight must be able to start from empty. */
|
|
1267
|
+
export declare const clearConnectionRefreshFlights: () => void;
|
|
1268
|
+
|
|
1202
1269
|
/** Test/boot reset. */
|
|
1203
1270
|
export declare const clearResourcePolicies: () => void;
|
|
1204
1271
|
|
|
@@ -1212,6 +1279,30 @@ export declare const clearSystemStoreHandle: () => void;
|
|
|
1212
1279
|
* branded definition can trust this won't throw. */
|
|
1213
1280
|
export declare const compileCron: (def: ScheduleDefinition) => Cron.Cron;
|
|
1214
1281
|
|
|
1282
|
+
/**
|
|
1283
|
+
* Consume a handshake grant and store the resulting credential.
|
|
1284
|
+
*
|
|
1285
|
+
* The grant row is deleted BEFORE the exchange, not after: a `state` is
|
|
1286
|
+
* single-use, and deleting it up front means a replayed callback (the user
|
|
1287
|
+
* double-clicks, a crawler follows the URL) cannot start a second exchange
|
|
1288
|
+
* even if the first is still in flight.
|
|
1289
|
+
*/
|
|
1290
|
+
export declare const completeOAuthConnect: (deps: CompleteOAuthDeps) => Promise<CompleteOAuthResult>;
|
|
1291
|
+
|
|
1292
|
+
export declare interface CompleteOAuthDeps {
|
|
1293
|
+
readonly store: VaultStore;
|
|
1294
|
+
readonly registry: ConnectionRegistry;
|
|
1295
|
+
readonly state: string;
|
|
1296
|
+
readonly code: string;
|
|
1297
|
+
readonly publicUrl: string;
|
|
1298
|
+
readonly now?: () => Date;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
export declare interface CompleteOAuthResult {
|
|
1302
|
+
readonly connectionId: string;
|
|
1303
|
+
readonly redirectTo: string;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1215
1306
|
/**
|
|
1216
1307
|
* Combine multiple analytics specs into one composite spec at
|
|
1217
1308
|
* app.config.ts time. The framework's boot path materialises each
|
|
@@ -1269,6 +1360,105 @@ declare interface ComputedQuery {
|
|
|
1269
1360
|
readonly recompute: () => Promise<unknown>;
|
|
1270
1361
|
}
|
|
1271
1362
|
|
|
1363
|
+
export declare const CONNECTION_GRANTS_TABLE = "_voltro_connection_grants";
|
|
1364
|
+
|
|
1365
|
+
/** What a declaration can learn about the account it just connected. Optional
|
|
1366
|
+
* — a connection with no `identify` still works, it just has no display name. */
|
|
1367
|
+
export declare interface ConnectionAccount {
|
|
1368
|
+
readonly accountId?: string;
|
|
1369
|
+
readonly accountLabel?: string;
|
|
1370
|
+
readonly metadata?: Record<string, unknown>;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
export declare interface ConnectionBuiltinDeps {
|
|
1374
|
+
readonly registry: ConnectionRegistry;
|
|
1375
|
+
/** Absolute origin the OAuth provider redirects back to. */
|
|
1376
|
+
readonly publicUrl: string;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/** Default callback path for a connection. The `<id>` segment is why
|
|
1380
|
+
* `defineConnection` constrains the id shape. */
|
|
1381
|
+
export declare const connectionCallbackPath: (connectionId: string) => string;
|
|
1382
|
+
|
|
1383
|
+
export declare type ConnectionDefinition = OAuth2ConnectionDefinition | PatConnectionDefinition;
|
|
1384
|
+
|
|
1385
|
+
declare interface ConnectionDefinitionCommon {
|
|
1386
|
+
/** Stable id — the value handlers pass to `ctx.connections.get(...)` and the
|
|
1387
|
+
* path segment of the callback route. */
|
|
1388
|
+
readonly id: string;
|
|
1389
|
+
/** Human label for the connect UI. Defaults to `id`. */
|
|
1390
|
+
readonly label?: string;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/** The slice of AppContext the connection executors need. */
|
|
1394
|
+
export declare interface ConnectionExecutorCtx {
|
|
1395
|
+
readonly store: DataStore;
|
|
1396
|
+
readonly request?: {
|
|
1397
|
+
readonly subject?: {
|
|
1398
|
+
readonly id?: string | null;
|
|
1399
|
+
readonly tenantId?: string | null;
|
|
1400
|
+
} | null;
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/** The subset of `fetch` the vault uses. Structurally satisfied by the global
|
|
1405
|
+
* `fetch` and by `@voltro/integration-http`'s `FetchLike`; declared locally so
|
|
1406
|
+
* the runtime takes no dependency on the http package for a two-field type. */
|
|
1407
|
+
export declare type ConnectionFetch = (url: string, init: {
|
|
1408
|
+
readonly method: string;
|
|
1409
|
+
readonly headers: Record<string, string>;
|
|
1410
|
+
readonly body: string;
|
|
1411
|
+
}) => Promise<{
|
|
1412
|
+
readonly status: number;
|
|
1413
|
+
readonly text: () => Promise<string>;
|
|
1414
|
+
}>;
|
|
1415
|
+
|
|
1416
|
+
/** `label` with the id as fallback. */
|
|
1417
|
+
export declare const connectionLabel: (definition: ConnectionDefinition) => string;
|
|
1418
|
+
|
|
1419
|
+
/** The subject has no credential on file for this connection (or it was
|
|
1420
|
+
* revoked and cleared). The caller's move is to prompt a connect. */
|
|
1421
|
+
export declare class ConnectionNotConnected extends Error {
|
|
1422
|
+
readonly connectionId: string;
|
|
1423
|
+
readonly subjectId: string;
|
|
1424
|
+
readonly _tag = "ConnectionNotConnected";
|
|
1425
|
+
constructor(connectionId: string, subjectId: string);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
export declare interface ConnectionRegistry {
|
|
1429
|
+
readonly get: (connectionId: string) => ConnectionDefinition | undefined;
|
|
1430
|
+
readonly list: () => ReadonlyArray<ConnectionDefinition>;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
/** Resolve `connectionId` for `subject`, refreshed. */
|
|
1434
|
+
export declare type ConnectionResolver = (subject: Pick<Subject, 'id'>, connectionId: string) => Promise<ResolvedConnection>;
|
|
1435
|
+
|
|
1436
|
+
export declare const CONNECTIONS_TABLE = "_voltro_connections";
|
|
1437
|
+
|
|
1438
|
+
export declare interface ConnectionsFacade {
|
|
1439
|
+
/**
|
|
1440
|
+
* The calling subject's credential for `connectionId`, refreshed if it was
|
|
1441
|
+
* near expiry. Throws `ConnectionNotConnected` when there is nothing on file
|
|
1442
|
+
* (prompt a connect) or `ConnectionHandshakeFailed` when a refresh was owed
|
|
1443
|
+
* and failed.
|
|
1444
|
+
*/
|
|
1445
|
+
get(connectionId: string): Promise<ResolvedConnection>;
|
|
1446
|
+
/** Same, but `null` instead of throwing when not connected — for the common
|
|
1447
|
+
* "use it if we have it" branch. A failing REFRESH still throws: silently
|
|
1448
|
+
* degrading a connected-but-broken account to "not connected" would hide a
|
|
1449
|
+
* revoked grant behind a feature quietly doing nothing. */
|
|
1450
|
+
tryGet(connectionId: string): Promise<ResolvedConnection | null>;
|
|
1451
|
+
/** Every declared connection with the caller's status — the same projection
|
|
1452
|
+
* `__voltro.connections.list` serves, for server-side rendering. */
|
|
1453
|
+
list(): Promise<ReadonlyArray<ConnectionState>>;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
export declare interface ConnectionsFacadeDeps {
|
|
1457
|
+
readonly store: DataStore;
|
|
1458
|
+
readonly registry: ConnectionRegistry;
|
|
1459
|
+
readonly subject: Subject;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1272
1462
|
/** Test-only — number of streams currently registered for a client. */
|
|
1273
1463
|
export declare const _connectionStreamCount: (clientId: number) => number;
|
|
1274
1464
|
|
|
@@ -1279,6 +1469,14 @@ export declare const connectionSubjectsSnapshot: () => ReadonlyArray<{
|
|
|
1279
1469
|
tenantId: string | null;
|
|
1280
1470
|
}>;
|
|
1281
1471
|
|
|
1472
|
+
/** A resolved token set. `expiresAt` is an absolute epoch-ms deadline. */
|
|
1473
|
+
export declare interface ConnectionTokens {
|
|
1474
|
+
readonly accessToken: string;
|
|
1475
|
+
readonly refreshToken?: string;
|
|
1476
|
+
readonly expiresAt?: number;
|
|
1477
|
+
readonly scopes: ReadonlyArray<string>;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1282
1480
|
export declare interface CoordinatedScheduleDeps {
|
|
1283
1481
|
/** The exactly-once gate. `singleCoordinator` for one-process
|
|
1284
1482
|
* deployments; `makeAdvisoryLockCoordinator(store, replicaId)` for
|
|
@@ -1337,10 +1535,10 @@ export declare interface DataLoader {
|
|
|
1337
1535
|
* to assemble, and a missing edge in a graph walk is usually data, not an
|
|
1338
1536
|
* error. Use `.one()` when absence IS an error.
|
|
1339
1537
|
*/
|
|
1340
|
-
load(table: string, id: string): Promise<
|
|
1538
|
+
load(table: string, id: string): Promise<Row | null>;
|
|
1341
1539
|
/** Load many by key, in the order asked. Missing rows come back as `null`,
|
|
1342
1540
|
* so the result lines up positionally with the input. */
|
|
1343
|
-
loadMany(table: string, ids: ReadonlyArray<string>): Promise<ReadonlyArray<
|
|
1541
|
+
loadMany(table: string, ids: ReadonlyArray<string>): Promise<ReadonlyArray<Row | null>>;
|
|
1344
1542
|
}
|
|
1345
1543
|
|
|
1346
1544
|
export { DataStore }
|
|
@@ -1364,6 +1562,20 @@ export declare const decryptField: (value: string) => string;
|
|
|
1364
1562
|
* small enough to stop a pathological body being buffered into memory. */
|
|
1365
1563
|
export declare const DEFAULT_MAX_RPC_BODY_BYTES: number;
|
|
1366
1564
|
|
|
1565
|
+
export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
|
|
1566
|
+
|
|
1567
|
+
/**
|
|
1568
|
+
* Retry policy applied to `load` when a filter does not specify one: three
|
|
1569
|
+
* attempts total (the initial call plus two retries), backing off exponentially
|
|
1570
|
+
* from 20ms — so ~60ms of added latency in the worst case.
|
|
1571
|
+
*
|
|
1572
|
+
* Sized for a transient blip (a connection reaped from the pool, a failover
|
|
1573
|
+
* flap), not for an outage. A `load` that is still failing after this is not
|
|
1574
|
+
* having a bad moment, and stretching the schedule only converts a fast honest
|
|
1575
|
+
* error into a slow one while holding the request open.
|
|
1576
|
+
*/
|
|
1577
|
+
export declare const DEFAULT_ROW_FILTER_RETRY: Schedule.Schedule<unknown, unknown>;
|
|
1578
|
+
|
|
1367
1579
|
/**
|
|
1368
1580
|
* Construct an aggregate definition. The returned object brands itself
|
|
1369
1581
|
* so the cli's file-discovery pass picks it up from default exports.
|
|
@@ -1392,6 +1604,16 @@ export declare const DEFAULT_MAX_RPC_BODY_BYTES: number;
|
|
|
1392
1604
|
*/
|
|
1393
1605
|
export declare const defineAggregate: <Row>(input: AggregateDefinitionInput<Row>) => AggregateDefinition<Row>;
|
|
1394
1606
|
|
|
1607
|
+
/**
|
|
1608
|
+
* Declare a connection. One per `*.connection.ts` file, default-exported.
|
|
1609
|
+
*
|
|
1610
|
+
* This is a pure declaration — no IO, no registration side effect. The CLI's
|
|
1611
|
+
* boot discovery imports the file, reads the default export, and registers it;
|
|
1612
|
+
* that keeps the declaration testable in isolation and keeps a stray import of
|
|
1613
|
+
* a connection file from mutating global state.
|
|
1614
|
+
*/
|
|
1615
|
+
export declare const defineConnection: <D extends ConnectionDefinition>(definition: D) => D;
|
|
1616
|
+
|
|
1395
1617
|
export declare const defineEventTrigger: <EventPayload = unknown, WorkflowPayload = EventPayload>(spec: Omit<WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>, "_tag">) => WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>;
|
|
1396
1618
|
|
|
1397
1619
|
/**
|
|
@@ -1493,7 +1715,7 @@ export declare class Dispatcher {
|
|
|
1493
1715
|
* Returns an `unsubscribe` function the caller MUST invoke when the
|
|
1494
1716
|
* subscriber goes away (client disconnect, stream finalization, etc.).
|
|
1495
1717
|
*/
|
|
1496
|
-
subscribe(descriptor: QueryDescriptor, emit: (event: SubscriptionEvent<ReadonlyArray<
|
|
1718
|
+
subscribe(descriptor: QueryDescriptor, emit: (event: SubscriptionEvent<ReadonlyArray<Row>>) => void, context: RuntimeContext,
|
|
1497
1719
|
/**
|
|
1498
1720
|
* Optional matcher-side index hint. When set, the registry hashes
|
|
1499
1721
|
* it into the fingerprint AND the matcher uses it for tuple
|
|
@@ -1512,10 +1734,13 @@ export declare class Dispatcher {
|
|
|
1512
1734
|
cacheBinding?: SnapshotCacheBinding,
|
|
1513
1735
|
/** Re-run the query's `guards:` before every delivery — see
|
|
1514
1736
|
* `ActiveSubscription.reauthorize`. Omitted for unguarded queries. */
|
|
1515
|
-
reauthorize?: () => Promise<unknown
|
|
1737
|
+
reauthorize?: () => Promise<unknown>,
|
|
1738
|
+
/** Re-resolve row visibility per delivery — see `ActiveSubscription.refilter`. */
|
|
1739
|
+
refilter?: () => Promise<RowFilterScope>): Promise<() => void>;
|
|
1516
1740
|
/**
|
|
1517
|
-
* Tear a subscription down because
|
|
1518
|
-
*
|
|
1741
|
+
* Tear a subscription down because it can no longer be served CORRECTLY
|
|
1742
|
+
* mid-stream — authorization was withdrawn, or row visibility became
|
|
1743
|
+
* unresolvable — and tell the client why.
|
|
1519
1744
|
*
|
|
1520
1745
|
* A denial is not a transient failure, so it must not be handled like one.
|
|
1521
1746
|
* The re-query paths deliberately keep a failing subscriber on its last
|
|
@@ -1574,11 +1799,13 @@ export declare interface DispatcherDependencies {
|
|
|
1574
1799
|
}
|
|
1575
1800
|
|
|
1576
1801
|
export declare interface DrainDeps {
|
|
1577
|
-
readonly store: Pick<DataStore, 'query' | 'update'>;
|
|
1802
|
+
readonly store: Pick<DataStore, 'query' | 'update' | 'insert' | 'delete'>;
|
|
1578
1803
|
readonly handlers: ReadonlyMap<string, OutboxHandlerDefinition>;
|
|
1579
1804
|
readonly now?: () => Date;
|
|
1580
1805
|
/** Max rows per drain pass. */
|
|
1581
1806
|
readonly batchSize?: number;
|
|
1807
|
+
/** Per-entry cap on retained attempt rows. See {@link OUTBOX_ATTEMPT_LOG_CAP}. */
|
|
1808
|
+
readonly attemptLogCap?: number;
|
|
1582
1809
|
}
|
|
1583
1810
|
|
|
1584
1811
|
/**
|
|
@@ -1620,9 +1847,9 @@ export declare interface EffectStoreOps {
|
|
|
1620
1847
|
* descriptor, so an Effect-form handler reads real fields instead of
|
|
1621
1848
|
* casting off `Record<string, unknown>`. Falls back to `Row` for a
|
|
1622
1849
|
* hand-built descriptor. */
|
|
1623
|
-
readonly query: <R =
|
|
1624
|
-
readonly insert: (table: string, row:
|
|
1625
|
-
readonly update: (table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>) => Effect.Effect<
|
|
1850
|
+
readonly query: <R = Row>(descriptor: QueryDescriptor<R>) => Effect.Effect<ReadonlyArray<R>, StoreError>;
|
|
1851
|
+
readonly insert: (table: string, row: Row) => Effect.Effect<Row, StoreError>;
|
|
1852
|
+
readonly update: (table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>) => Effect.Effect<Row | null, StoreError>;
|
|
1626
1853
|
readonly delete: (table: string, primaryKey: string) => Effect.Effect<boolean, StoreError>;
|
|
1627
1854
|
/** Bypass soft-delete. See `MutationStore.hardDelete`. */
|
|
1628
1855
|
readonly hardDelete: (table: string, primaryKey: string) => Effect.Effect<boolean, StoreError>;
|
|
@@ -1668,6 +1895,13 @@ export declare interface EventsFacadeOptions {
|
|
|
1668
1895
|
readonly makeId?: (prefix: 'wfe' | 'wfed') => string;
|
|
1669
1896
|
}
|
|
1670
1897
|
|
|
1898
|
+
/** Exchange an authorization code (from the callback) for a token set. */
|
|
1899
|
+
export declare const exchangeAuthorizationCode: (definition: OAuth2ConnectionDefinition, input: {
|
|
1900
|
+
readonly code: string;
|
|
1901
|
+
readonly redirectUri: string;
|
|
1902
|
+
readonly codeVerifier?: string | null;
|
|
1903
|
+
}, now?: () => number) => Promise<ConnectionTokens>;
|
|
1904
|
+
|
|
1671
1905
|
/** Build an Effect `ExternalSpan` parent from a `traceparent` header so
|
|
1672
1906
|
* inbound HTTP work continues the caller's trace. Returns undefined
|
|
1673
1907
|
* when the header is missing/invalid (no parent → a fresh root span). */
|
|
@@ -1709,6 +1943,32 @@ export declare interface FluentStore extends Omit<MutationStore, 'update' | 'del
|
|
|
1709
1943
|
* rows off the wire. The type is re-applied here, at the handler boundary.
|
|
1710
1944
|
*/
|
|
1711
1945
|
query<R = Row_5>(descriptor: QueryDescriptor<R>): Promise<ReadonlyArray<R>>;
|
|
1946
|
+
/**
|
|
1947
|
+
* Terminal: EXACTLY one row, TYPED. Fails with `NoRowFound` on zero matches
|
|
1948
|
+
* and equally on two or more.
|
|
1949
|
+
*
|
|
1950
|
+
* This exists because the two things we recommend did not compose. The typed
|
|
1951
|
+
* read path lives on the typed builder (`database.users.where(...)`), while
|
|
1952
|
+
* `.one()` lived only on the string-keyed `select('users')` builder, which
|
|
1953
|
+
* yields untyped `Row`. So adopting `.one()` meant re-introducing the very
|
|
1954
|
+
* casts the typed path removes — you could have typed rows OR the terminal,
|
|
1955
|
+
* not both. That is not a "last mile" gap for sophisticated apps; it is two
|
|
1956
|
+
* features from the same release failing to meet.
|
|
1957
|
+
*
|
|
1958
|
+
* Takes the builder itself or its `.descriptor` — both read fine:
|
|
1959
|
+
*
|
|
1960
|
+
* const user = await ctx.store.one(database.users.where(eq('id', id)))
|
|
1961
|
+
*
|
|
1962
|
+
* Scoping is identical to `query()` (tenant + soft-delete apply), because it
|
|
1963
|
+
* IS `query()` underneath — the probe just asks for one row more than it
|
|
1964
|
+
* needs so "the first of several" cannot masquerade as "the one you meant".
|
|
1965
|
+
*/
|
|
1966
|
+
one<R = Row_5>(query: QueryLike<R>): Promise<R>;
|
|
1967
|
+
/** Terminal: the first matching row or `null`, TYPED. Use when "any match" is
|
|
1968
|
+
* genuinely what you mean. */
|
|
1969
|
+
first<R = Row_5>(query: QueryLike<R>): Promise<R | null>;
|
|
1970
|
+
/** Alias of `first` — the first matching row or `null`, TYPED. */
|
|
1971
|
+
maybeOne<R = Row_5>(query: QueryLike<R>): Promise<R | null>;
|
|
1712
1972
|
/** Fluent, scope-applying read builder: `select('notes').where(...).all()`. */
|
|
1713
1973
|
select(table: string): SelectBuilder;
|
|
1714
1974
|
/** Fluent predicate update: `update('notes').where('id', id).set({...})`. */
|
|
@@ -1739,6 +1999,9 @@ export declare interface FluentStoreBackend {
|
|
|
1739
1999
|
readonly delete: DataStore['delete'];
|
|
1740
2000
|
}
|
|
1741
2001
|
|
|
2002
|
+
/** Forget the calling subject's credential. Returns whether a row was removed. */
|
|
2003
|
+
export declare const forgetCredential: (store: VaultStore, connectionId: string, subjectId: string) => Promise<boolean>;
|
|
2004
|
+
|
|
1742
2005
|
/** Serialise a span's context as a `traceparent` header value. */
|
|
1743
2006
|
export declare const formatTraceparent: (ctx: TraceContext) => string;
|
|
1744
2007
|
|
|
@@ -1776,6 +2039,9 @@ export declare const gauge: (name: string, description?: string) => Metric.Metri
|
|
|
1776
2039
|
/** Generate a fresh token `<prefix><40-char-base64url>`. */
|
|
1777
2040
|
export declare const generateApiKeyToken: (prefix?: string) => string;
|
|
1778
2041
|
|
|
2042
|
+
/** The registered resolver, if the app declared any connections. */
|
|
2043
|
+
export declare const getConnectionResolver: () => ConnectionResolver | undefined;
|
|
2044
|
+
|
|
1779
2045
|
/** Look up the override for a connection. Returns `undefined` if no override is set. */
|
|
1780
2046
|
export declare const getConnectionSubject: (clientId: number) => Subject | undefined;
|
|
1781
2047
|
|
|
@@ -1784,14 +2050,22 @@ export declare const getFieldCipher: () => FieldCipher | undefined;
|
|
|
1784
2050
|
|
|
1785
2051
|
export declare const getResourcePolicy: (resourceType: string) => ResourcePolicy | undefined;
|
|
1786
2052
|
|
|
2053
|
+
/** The registered row filter, or `undefined`. */
|
|
2054
|
+
export declare const getRowFilter: () => RowFilter<never> | undefined;
|
|
2055
|
+
|
|
1787
2056
|
/** The per-process recorder (created lazily from `VOLTRO_TIMELINE`). The change
|
|
1788
2057
|
* tap feeds it; the inspect endpoint reads it. */
|
|
1789
2058
|
export declare const getTimelineRecorder: () => TimelineRecorder;
|
|
1790
2059
|
|
|
1791
2060
|
export declare const getTupleSource: () => TupleSource | undefined;
|
|
1792
2061
|
|
|
2062
|
+
/** How long a started handshake stays claimable. Long enough for a slow
|
|
2063
|
+
* consent screen (and a password-manager detour), short enough that a stolen
|
|
2064
|
+
* state value is worthless by the time it is found. */
|
|
2065
|
+
export declare const GRANT_TTL_MS: number;
|
|
2066
|
+
|
|
1793
2067
|
/** Stable group key from the groupBy column values (JSON to disambiguate types). */
|
|
1794
|
-
export declare const groupKeyOf: (row:
|
|
2068
|
+
export declare const groupKeyOf: (row: Row_3, groupBy: ReadonlyArray<string> | undefined) => string;
|
|
1795
2069
|
|
|
1796
2070
|
/** Per-group accumulator. `count` is always kept (to know when a group empties
|
|
1797
2071
|
* and to compute avg); `sum` for sum/avg; `extreme` for min/max. */
|
|
@@ -1891,7 +2165,7 @@ export declare class IndexedMatcher implements Matcher {
|
|
|
1891
2165
|
|
|
1892
2166
|
export declare class InMemoryDataStore implements DataStore {
|
|
1893
2167
|
#private;
|
|
1894
|
-
constructor(initial?: Record<string, ReadonlyArray<
|
|
2168
|
+
constructor(initial?: Record<string, ReadonlyArray<Row>>, namespace?: string | null);
|
|
1895
2169
|
/**
|
|
1896
2170
|
* Return a namespace-bound view. The view shares THIS store's table
|
|
1897
2171
|
* map, emitter, and id counters (one process, one in-memory database)
|
|
@@ -1900,11 +2174,11 @@ export declare class InMemoryDataStore implements DataStore {
|
|
|
1900
2174
|
* table that never carried the `tenant()` mixin.
|
|
1901
2175
|
*/
|
|
1902
2176
|
withNamespace(namespace: string | null): DataStore;
|
|
1903
|
-
query(descriptor: QueryDescriptor): Promise<ReadonlyArray<
|
|
1904
|
-
insert(table: string, row:
|
|
1905
|
-
insertMany(table: string, rows: ReadonlyArray<
|
|
1906
|
-
patchJson(table: string, primaryKey: string, path: string, value: unknown): Promise<
|
|
1907
|
-
update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<
|
|
2177
|
+
query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
|
|
2178
|
+
insert(table: string, row: Row): Promise<Row>;
|
|
2179
|
+
insertMany(table: string, rows: ReadonlyArray<Row>): Promise<ReadonlyArray<Row>>;
|
|
2180
|
+
patchJson(table: string, primaryKey: string, path: string, value: unknown): Promise<Row | null>;
|
|
2181
|
+
update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<Row | null>;
|
|
1908
2182
|
delete(table: string, primaryKey: string): Promise<boolean>;
|
|
1909
2183
|
updateMany(table: string, patch: Readonly<Record<string, unknown>>, options: {
|
|
1910
2184
|
where: Predicate;
|
|
@@ -1912,13 +2186,13 @@ export declare class InMemoryDataStore implements DataStore {
|
|
|
1912
2186
|
deleteMany(table: string, options: {
|
|
1913
2187
|
where: Predicate;
|
|
1914
2188
|
}): Promise<number>;
|
|
1915
|
-
upsert(table: string, row:
|
|
2189
|
+
upsert(table: string, row: Row, options: {
|
|
1916
2190
|
conflictColumns: ReadonlyArray<string>;
|
|
1917
|
-
update?: ReadonlyArray<string> | ((existing:
|
|
1918
|
-
}): Promise<
|
|
1919
|
-
insertIgnore(table: string, row:
|
|
2191
|
+
update?: ReadonlyArray<string> | ((existing: Row) => Readonly<Record<string, unknown>>);
|
|
2192
|
+
}): Promise<Row>;
|
|
2193
|
+
insertIgnore(table: string, row: Row, options: {
|
|
1920
2194
|
conflictColumns: ReadonlyArray<string>;
|
|
1921
|
-
}): Promise<
|
|
2195
|
+
}): Promise<Row>;
|
|
1922
2196
|
transactional<T>(work: (tx: DataStore) => Promise<T>): Promise<T>;
|
|
1923
2197
|
onChange(listener: (event: ChangeEvent) => void): () => void;
|
|
1924
2198
|
/** Cross-instance reactivity seam — emit an externally-sourced event to
|
|
@@ -2143,6 +2417,9 @@ export declare class LinearScanMatcher implements Matcher {
|
|
|
2143
2417
|
stats(): MatcherStats;
|
|
2144
2418
|
}
|
|
2145
2419
|
|
|
2420
|
+
/** Every connection the app declares, projected for ONE subject. */
|
|
2421
|
+
export declare const listConnectionStates: (store: VaultStore, registry: ConnectionRegistry, subjectId: string) => Promise<ReadonlyArray<ConnectionState>>;
|
|
2422
|
+
|
|
2146
2423
|
export declare const listResourcePolicies: () => ReadonlyArray<ResourcePolicy>;
|
|
2147
2424
|
|
|
2148
2425
|
export { listRetentions }
|
|
@@ -2275,6 +2552,20 @@ export declare const makeAsyncKv: ({ store, env }: KvFacadeOptions) => Promise<K
|
|
|
2275
2552
|
*/
|
|
2276
2553
|
export declare const makeBufferingSpanProcessor: (onSpanEnd: (record: TraceSpanRecordLike) => void) => SpanProcessor;
|
|
2277
2554
|
|
|
2555
|
+
/** Build a registry from the discovered declarations. Duplicate ids throw —
|
|
2556
|
+
* two files claiming one id means whichever loaded last silently wins and the
|
|
2557
|
+
* other file looks live but is dead. */
|
|
2558
|
+
export declare const makeConnectionRegistry: (definitions: ReadonlyArray<ConnectionDefinition>) => ConnectionRegistry;
|
|
2559
|
+
|
|
2560
|
+
/** Build the process resolver over a store + registry. The CLI hands the
|
|
2561
|
+
* result to `setConnectionResolver`. */
|
|
2562
|
+
export declare const makeConnectionResolver: (deps: {
|
|
2563
|
+
readonly store: DataStore;
|
|
2564
|
+
readonly registry: ConnectionRegistry;
|
|
2565
|
+
}) => ConnectionResolver;
|
|
2566
|
+
|
|
2567
|
+
export declare const makeConnectionsFacade: (deps: ConnectionsFacadeDeps) => ConnectionsFacade;
|
|
2568
|
+
|
|
2278
2569
|
/**
|
|
2279
2570
|
* Factory the CLI binds into the plugin bind-ctx: closes over the
|
|
2280
2571
|
* process-wide coordinator + replicaId so a plugin calls the ergonomic
|
|
@@ -2290,6 +2581,13 @@ export declare const makeCoordinatedScheduler: (deps: CoordinatedScheduleDeps) =
|
|
|
2290
2581
|
*/
|
|
2291
2582
|
export declare const makeDataLoader: (deps: LoaderDeps) => DataLoader;
|
|
2292
2583
|
|
|
2584
|
+
/** `__voltro.connections.disconnect` — forget the caller's credential. */
|
|
2585
|
+
export declare const makeDisconnectConnectionExecutor: (deps: ConnectionBuiltinDeps) => (input: {
|
|
2586
|
+
readonly connectionId: string;
|
|
2587
|
+
}, ctx: ConnectionExecutorCtx) => Promise<{
|
|
2588
|
+
readonly ok: boolean;
|
|
2589
|
+
}>;
|
|
2590
|
+
|
|
2293
2591
|
/**
|
|
2294
2592
|
* Build the Layer that provides `EffectStore` from a `MutationStore`.
|
|
2295
2593
|
* dev.ts (CLI) and any standalone test setup uses this to wire up the
|
|
@@ -2320,6 +2618,9 @@ export declare const makeIdleGate: (graceMs: number, now?: () => number) => Idle
|
|
|
2320
2618
|
|
|
2321
2619
|
export declare const makeLazyWorkflowFacade: (resolve: () => WorkflowsAppContext | undefined) => WorkflowsAppContext;
|
|
2322
2620
|
|
|
2621
|
+
/** `__voltro.connections.list` — every declared connection, for the caller. */
|
|
2622
|
+
export declare const makeListConnectionsExecutor: (deps: ConnectionBuiltinDeps) => (_input: Record<string, never>, ctx: ConnectionExecutorCtx) => Promise<ReadonlyArray<ConnectionState>>;
|
|
2623
|
+
|
|
2323
2624
|
/**
|
|
2324
2625
|
* Build the shared mutation runner. Used by BOTH the rpc WS handler and
|
|
2325
2626
|
* the `/_voltro/inspect/invoke` endpoint (and the prod entrypoint) so they
|
|
@@ -2386,6 +2687,23 @@ export declare const makeRouterActivity: () => RouterActivity;
|
|
|
2386
2687
|
*/
|
|
2387
2688
|
export declare const makeSchemaRegistry: (tables: ReadonlyArray<RegistryTableLike>) => SchemaRegistry;
|
|
2388
2689
|
|
|
2690
|
+
/** `__voltro.connections.start` — mint the consent URL for an oauth2 flow. */
|
|
2691
|
+
export declare const makeStartConnectionExecutor: (deps: ConnectionBuiltinDeps) => (input: {
|
|
2692
|
+
readonly connectionId: string;
|
|
2693
|
+
readonly redirectTo?: string;
|
|
2694
|
+
}, ctx: ConnectionExecutorCtx) => Promise<{
|
|
2695
|
+
readonly authorizeUrl: string;
|
|
2696
|
+
readonly state: string;
|
|
2697
|
+
}>;
|
|
2698
|
+
|
|
2699
|
+
/** `__voltro.connections.submitToken` — store a pasted personal access token. */
|
|
2700
|
+
export declare const makeSubmitTokenExecutor: (deps: ConnectionBuiltinDeps) => (input: {
|
|
2701
|
+
readonly connectionId: string;
|
|
2702
|
+
readonly token: string;
|
|
2703
|
+
}, ctx: ConnectionExecutorCtx) => Promise<{
|
|
2704
|
+
readonly ok: boolean;
|
|
2705
|
+
}>;
|
|
2706
|
+
|
|
2389
2707
|
/**
|
|
2390
2708
|
* The factory the serve entrypoints inject into `makeMutationRunner`'s
|
|
2391
2709
|
* `undoCapture` dep. Collects changes from the wrapped tx; `persist` writes ONE
|
|
@@ -2617,6 +2935,10 @@ export declare const nextFiring: (def: ScheduleDefinition, after?: Date) => Date
|
|
|
2617
2935
|
*/
|
|
2618
2936
|
export declare const nextWakeup: (store: DataStore) => Promise<Wakeup | null>;
|
|
2619
2937
|
|
|
2938
|
+
/** A scope that constrains nothing — the shape used when no filter is
|
|
2939
|
+
* registered, or for a system subject. */
|
|
2940
|
+
export declare const NO_ROW_FILTER: RowFilterScope;
|
|
2941
|
+
|
|
2620
2942
|
/**
|
|
2621
2943
|
* The real single-node supervisor: `spawn`s the app command and considers
|
|
2622
2944
|
* it healthy once `healthUrl` returns 2xx. Stopping sends `stopSignal`
|
|
@@ -2711,6 +3033,32 @@ export declare interface Notification {
|
|
|
2711
3033
|
readonly newMatched: boolean;
|
|
2712
3034
|
}
|
|
2713
3035
|
|
|
3036
|
+
export declare interface OAuth2ConnectionDefinition extends ConnectionDefinitionCommon {
|
|
3037
|
+
readonly kind: 'oauth2';
|
|
3038
|
+
readonly authorizeUrl: string;
|
|
3039
|
+
readonly tokenUrl: string;
|
|
3040
|
+
readonly clientId: string;
|
|
3041
|
+
/** Runtime secret. Never logged, never sent to the browser, never bundled —
|
|
3042
|
+
* `*.connection.ts` is server-only and is NOT part of the rpcGroup codegen. */
|
|
3043
|
+
readonly clientSecret: string;
|
|
3044
|
+
readonly scopes?: ReadonlyArray<string>;
|
|
3045
|
+
/** Exact redirect URI registered with the provider. Defaults to
|
|
3046
|
+
* `<publicUrl>/_voltro/connections/<id>/callback`. */
|
|
3047
|
+
readonly redirectUri?: string;
|
|
3048
|
+
/** Extra fixed query params on the authorize URL (`audience`, `prompt`, …). */
|
|
3049
|
+
readonly authorizeParams?: Record<string, string>;
|
|
3050
|
+
/** PKCE (S256). On by default — it costs one hash and closes the
|
|
3051
|
+
* authorization-code interception hole for public clients. */
|
|
3052
|
+
readonly pkce?: boolean;
|
|
3053
|
+
/** Renew when the token is within this many ms of expiry. Default 60_000 —
|
|
3054
|
+
* enough headroom that an in-flight request can't race the deadline. */
|
|
3055
|
+
readonly refreshSkewMs?: number;
|
|
3056
|
+
/** Injectable transport (tests, proxies). Defaults to the global `fetch`. */
|
|
3057
|
+
readonly fetchImpl?: ConnectionFetch;
|
|
3058
|
+
/** Resolve who the freshly-minted token belongs to, for display. */
|
|
3059
|
+
readonly identify?: (tokens: ConnectionTokens) => Promise<ConnectionAccount>;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
2714
3062
|
/**
|
|
2715
3063
|
* Listen for re-bind events. Returns an unsubscribe function. Used by
|
|
2716
3064
|
* the dispatcher to re-scope active subscriptions when a connection's
|
|
@@ -2742,8 +3090,68 @@ export declare interface OrchestratorTickDeps {
|
|
|
2742
3090
|
readonly log?: WakeOrchestratorLogger;
|
|
2743
3091
|
}
|
|
2744
3092
|
|
|
3093
|
+
/**
|
|
3094
|
+
* Retention, part 1 of 2 — the PER-ENTRY cap.
|
|
3095
|
+
*
|
|
3096
|
+
* The automatic path is already bounded by `maxAttempts` (default 8), so the
|
|
3097
|
+
* only way one entry's history grows without limit is repeated manual resends
|
|
3098
|
+
* of the same row — a support workflow that can genuinely run for years. Once an
|
|
3099
|
+
* entry passes this many attempts, recording a new one trims the oldest, so the
|
|
3100
|
+
* log is a bounded ring per entry: the recent attempts are what a history UI
|
|
3101
|
+
* shows, and the 200th-oldest failure of a chronically broken effect is not
|
|
3102
|
+
* worth a row.
|
|
3103
|
+
*
|
|
3104
|
+
* The check is keyed off the attempt NUMBER, which the drain already knows, so
|
|
3105
|
+
* the normal (never-resent) path pays no extra query at all — it can never
|
|
3106
|
+
* reach the cap.
|
|
3107
|
+
*
|
|
3108
|
+
* Part 2 is the time-based bound: the CLI's boot sweep registers a retention
|
|
3109
|
+
* spec over `startedAt` (default 30 days, `VOLTRO_OUTBOX_ATTEMPTS_TTL_HOURS`).
|
|
3110
|
+
* Both exist because they bound different failure modes — the cap stops ONE hot
|
|
3111
|
+
* entry, the TTL stops the accumulation of MANY cold ones — and only the cap
|
|
3112
|
+
* works on every dialect (the sweep is postgres-gated).
|
|
3113
|
+
*/
|
|
3114
|
+
export declare const OUTBOX_ATTEMPT_LOG_CAP = 50;
|
|
3115
|
+
|
|
3116
|
+
export declare const OUTBOX_ATTEMPTS_TABLE = "_voltro_outbox_attempts";
|
|
3117
|
+
|
|
2745
3118
|
export declare const OUTBOX_TABLE = "_voltro_outbox";
|
|
2746
3119
|
|
|
3120
|
+
/** Terminal outcome of ONE attempt. `dead` is a failure that also exhausted the
|
|
3121
|
+
* budget — kept separate from `failed` so a listing can render "gave up here"
|
|
3122
|
+
* without cross-referencing the parent row's status. */
|
|
3123
|
+
export declare type OutboxAttemptOutcome = 'delivered' | 'failed' | 'dead';
|
|
3124
|
+
|
|
3125
|
+
/**
|
|
3126
|
+
* One row per delivery ATTEMPT — the queryable history behind `_voltro_outbox`.
|
|
3127
|
+
*
|
|
3128
|
+
* The outbox row alone answers "did this eventually land"; it cannot answer
|
|
3129
|
+
* "what did the remote say on attempt 3", "how long did it take", "who resent
|
|
3130
|
+
* it", which is what a delivery-history UI renders. Columns are chosen from
|
|
3131
|
+
* that render, not from what is convenient to write:
|
|
3132
|
+
*
|
|
3133
|
+
* - a listing row needs: when, which effect, attempt #, outcome, duration,
|
|
3134
|
+
* and (for a failure) the error — all here, no JOIN;
|
|
3135
|
+
* - `effect` / `subjectId` / `tenantId` are DENORMALISED so the history stays
|
|
3136
|
+
* readable (and tenant-scopable) after the parent outbox row is purged by
|
|
3137
|
+
* retention, and so a per-tenant listing needs no join;
|
|
3138
|
+
* - `response` carries the handler's own return value, which is where an
|
|
3139
|
+
* HTTP-shaped handler puts `{ status, body }` — the framework does not
|
|
3140
|
+
* model HTTP, so it snapshots whatever the handler chose to return rather
|
|
3141
|
+
* than inventing status/response columns only some handlers can fill.
|
|
3142
|
+
*
|
|
3143
|
+
* Append-only. Bounded two ways — see `recordAttempt` + the retention
|
|
3144
|
+
* registration in the CLI's boot sweep.
|
|
3145
|
+
*/
|
|
3146
|
+
export declare const outboxAttemptsTable: TableLike;
|
|
3147
|
+
|
|
3148
|
+
/** How an attempt came to be made. `automatic` = the drain picked the row up on
|
|
3149
|
+
* its own schedule; `manual` = a human/API asked for it via `resend()`. The
|
|
3150
|
+
* distinction is the point of the field: without it a support-triggered
|
|
3151
|
+
* redelivery is indistinguishable from a backoff retry, and "did we send this
|
|
3152
|
+
* twice because of us or because of them" becomes unanswerable. */
|
|
3153
|
+
export declare type OutboxAttemptTrigger = 'automatic' | 'manual';
|
|
3154
|
+
|
|
2747
3155
|
export declare interface OutboxFacade {
|
|
2748
3156
|
/**
|
|
2749
3157
|
* Persist the intent to run `effect` after this transaction commits.
|
|
@@ -2752,12 +3160,36 @@ export declare interface OutboxFacade {
|
|
|
2752
3160
|
* watch to render external-side-effect progress ("saving… syncing… synced").
|
|
2753
3161
|
*/
|
|
2754
3162
|
enqueue(effect: string, payload: Record<string, unknown>, options?: EnqueueOptions): Promise<string>;
|
|
3163
|
+
/**
|
|
3164
|
+
* Re-arm one entry for delivery NOW, on purpose, attributed to the caller.
|
|
3165
|
+
*
|
|
3166
|
+
* Legitimate because the delivery contract is already at-least-once and
|
|
3167
|
+
* handlers must therefore be idempotent — a manual resend is the SAME hazard
|
|
3168
|
+
* the contract already requires handlers to absorb, not a new one. What it is
|
|
3169
|
+
* not allowed to be is invisible: the next attempt is recorded with
|
|
3170
|
+
* `trigger: 'manual'` plus the requesting subject and reason, so an operator
|
|
3171
|
+
* redelivery can never be mistaken for a backoff retry when someone later
|
|
3172
|
+
* asks why the remote saw the effect twice.
|
|
3173
|
+
*
|
|
3174
|
+
* Three deliberate choices:
|
|
3175
|
+
* - It RE-ARMS the existing row rather than enqueuing a new one. A copy
|
|
3176
|
+
* would carry the same `idempotencyKey` (breaking the dedupe invariant),
|
|
3177
|
+
* duplicate the payload, and split one entry's history across two ids.
|
|
3178
|
+
* - It grants a SMALL fresh budget (`attempts`, default 1) instead of
|
|
3179
|
+
* resetting the counter. A dead row has `attempts >= maxAttempts`, so
|
|
3180
|
+
* without this it would re-die untried; and "try again now" is what the
|
|
3181
|
+
* button means — not "restart the whole eight-attempt backoff schedule".
|
|
3182
|
+
* - It refuses a row that is mid-flight (`delivering`), where re-arming
|
|
3183
|
+
* races the in-progress attempt into a same-process double delivery this
|
|
3184
|
+
* layer CAN prevent (unlike the cross-replica one).
|
|
3185
|
+
*/
|
|
3186
|
+
resend(outboxId: string, options?: ResendOptions): Promise<void>;
|
|
2755
3187
|
}
|
|
2756
3188
|
|
|
2757
3189
|
export declare interface OutboxFacadeDeps {
|
|
2758
3190
|
/** The REQUEST's store. Inside a mutation this is the transactional view —
|
|
2759
3191
|
* which is what makes the enqueue atomic with the domain write. */
|
|
2760
|
-
readonly store: Pick<DataStore, 'insert' | 'query'>;
|
|
3192
|
+
readonly store: Pick<DataStore, 'insert' | 'query' | 'update'>;
|
|
2761
3193
|
readonly subject: Subject;
|
|
2762
3194
|
readonly traceId: string | null;
|
|
2763
3195
|
/** Nudge the drain worker once the transaction commits. Optional: without
|
|
@@ -2800,6 +3232,18 @@ declare interface P2COptions {
|
|
|
2800
3232
|
* treated as "no parent"). */
|
|
2801
3233
|
export declare const parseTraceparent: (header: string | undefined | null) => TraceContext | null;
|
|
2802
3234
|
|
|
3235
|
+
export declare interface PatConnectionDefinition extends ConnectionDefinitionCommon {
|
|
3236
|
+
readonly kind: 'pat';
|
|
3237
|
+
/** Where to send the user to mint a token — rendered as a help link. */
|
|
3238
|
+
readonly instructionsUrl?: string;
|
|
3239
|
+
/**
|
|
3240
|
+
* Verify a pasted token before storing it. Rejecting here is what turns
|
|
3241
|
+
* "the user typed it wrong" into an immediate error instead of a mysterious
|
|
3242
|
+
* 401 from a background job three hours later. Throwing fails the submit.
|
|
3243
|
+
*/
|
|
3244
|
+
readonly validate?: (token: string) => Promise<ConnectionAccount>;
|
|
3245
|
+
}
|
|
3246
|
+
|
|
2803
3247
|
/** The 3-arg signature a plugin sees on its bind-ctx. */
|
|
2804
3248
|
export declare type PluginScheduleCoordinated = (name: string, intervalMs: number, effect: () => void | Promise<void>) => CoordinatedScheduleHandle;
|
|
2805
3249
|
|
|
@@ -2827,6 +3271,11 @@ export declare interface PreviewStore {
|
|
|
2827
3271
|
transactional: <T>(fn: (tx: unknown) => Promise<T>) => Promise<T>;
|
|
2828
3272
|
}
|
|
2829
3273
|
|
|
3274
|
+
/** Project a stored row into the wire shape. Deliberately total over the
|
|
3275
|
+
* columns the descriptor declares — and deliberately silent about the token
|
|
3276
|
+
* columns, which have no wire representation at all. */
|
|
3277
|
+
export declare const projectConnectionState: (definition: ConnectionDefinition, row: AnyRow | undefined) => ConnectionState;
|
|
3278
|
+
|
|
2830
3279
|
/** Exact-match property filter. Values are AND'd. */
|
|
2831
3280
|
export declare type PropertyFilter = Readonly<Record<string, string | number | boolean | null>>;
|
|
2832
3281
|
|
|
@@ -2903,6 +3352,12 @@ export declare interface PublicApiKey {
|
|
|
2903
3352
|
readonly createdAt: number;
|
|
2904
3353
|
}
|
|
2905
3354
|
|
|
3355
|
+
/** A typed builder or its descriptor — the single-row terminals accept either,
|
|
3356
|
+
* so a call site never has to reach for `.descriptor` just to use them. */
|
|
3357
|
+
export declare type QueryLike<R = Row_5> = QueryDescriptor<R> | {
|
|
3358
|
+
readonly descriptor: QueryDescriptor<R>;
|
|
3359
|
+
};
|
|
3360
|
+
|
|
2906
3361
|
export declare interface QueryProducerDeps<D> {
|
|
2907
3362
|
readonly buildContext: (request: ServeRequestContext) => {
|
|
2908
3363
|
readonly store: unknown;
|
|
@@ -3071,7 +3526,7 @@ export declare const recordTimelineEvent: (change: CdcChange & {
|
|
|
3071
3526
|
}) => void;
|
|
3072
3527
|
|
|
3073
3528
|
/** Blank sensitive-looking columns. Returns a new object; null passes through. */
|
|
3074
|
-
export declare const redactRow: (row:
|
|
3529
|
+
export declare const redactRow: (row: Row_2 | null | undefined) => Row_2 | null;
|
|
3075
3530
|
|
|
3076
3531
|
/**
|
|
3077
3532
|
* Options for `redisRywPositionStore`. The store needs TWO Redis
|
|
@@ -3152,6 +3607,15 @@ export declare const redoUndoInvocation: (input: {
|
|
|
3152
3607
|
readonly ok: boolean;
|
|
3153
3608
|
}>;
|
|
3154
3609
|
|
|
3610
|
+
/** How long a refresh claim is held before another replica may take it. Long
|
|
3611
|
+
* enough for a slow token endpoint, short enough that a crashed replica does
|
|
3612
|
+
* not block renewal for long. */
|
|
3613
|
+
export declare const REFRESH_LEASE_MS = 30000;
|
|
3614
|
+
|
|
3615
|
+
/** Swap a refresh token for a fresh set. Providers that ROTATE return a new
|
|
3616
|
+
* refresh token; the caller persists whatever comes back. */
|
|
3617
|
+
export declare const refreshAccessToken: (definition: OAuth2ConnectionDefinition, refreshToken: string, now?: () => number) => Promise<ConnectionTokens>;
|
|
3618
|
+
|
|
3155
3619
|
/**
|
|
3156
3620
|
* Register an interrupt for a stream bound to `clientId`. Returns a
|
|
3157
3621
|
* deregister fn the stream's scope finalizer must call on normal end (so
|
|
@@ -3315,13 +3779,13 @@ export declare class ReplicatedDataStore implements DataStore {
|
|
|
3315
3779
|
private readonly recordRouting?;
|
|
3316
3780
|
private readonly now;
|
|
3317
3781
|
constructor(options: ReplicatedDataStoreOptions);
|
|
3318
|
-
query(descriptor: QueryDescriptor): Promise<ReadonlyArray<
|
|
3782
|
+
query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
|
|
3319
3783
|
private routeToReplica;
|
|
3320
3784
|
private routeWaitMode;
|
|
3321
|
-
insert(table: string, row:
|
|
3322
|
-
insertMany(table: string, rows: ReadonlyArray<
|
|
3323
|
-
patchJson(table: string, primaryKey: string, path: string, value: unknown): Promise<
|
|
3324
|
-
update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<
|
|
3785
|
+
insert(table: string, row: Row): Promise<Row>;
|
|
3786
|
+
insertMany(table: string, rows: ReadonlyArray<Row>): Promise<ReadonlyArray<Row>>;
|
|
3787
|
+
patchJson(table: string, primaryKey: string, path: string, value: unknown): Promise<Row | null>;
|
|
3788
|
+
update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<Row | null>;
|
|
3325
3789
|
delete(table: string, primaryKey: string): Promise<boolean>;
|
|
3326
3790
|
updateMany(table: string, patch: Readonly<Record<string, unknown>>, options: {
|
|
3327
3791
|
where: Predicate;
|
|
@@ -3329,13 +3793,13 @@ export declare class ReplicatedDataStore implements DataStore {
|
|
|
3329
3793
|
deleteMany(table: string, options: {
|
|
3330
3794
|
where: Predicate;
|
|
3331
3795
|
}): Promise<number>;
|
|
3332
|
-
upsert(table: string, row:
|
|
3796
|
+
upsert(table: string, row: Row, options: {
|
|
3333
3797
|
conflictColumns: ReadonlyArray<string>;
|
|
3334
|
-
update?: ReadonlyArray<string> | ((existing:
|
|
3335
|
-
}): Promise<
|
|
3336
|
-
insertIgnore(table: string, row:
|
|
3798
|
+
update?: ReadonlyArray<string> | ((existing: Row) => Readonly<Record<string, unknown>>);
|
|
3799
|
+
}): Promise<Row>;
|
|
3800
|
+
insertIgnore(table: string, row: Row, options: {
|
|
3337
3801
|
conflictColumns: ReadonlyArray<string>;
|
|
3338
|
-
}): Promise<
|
|
3802
|
+
}): Promise<Row>;
|
|
3339
3803
|
transactional<T>(work: (tx: DataStore) => Promise<T>): Promise<T>;
|
|
3340
3804
|
onChange(listener: (event: ChangeEvent) => void): () => void;
|
|
3341
3805
|
/** Cross-instance reactivity seam — delegates to the primary, where the
|
|
@@ -3399,12 +3863,49 @@ export declare interface ReplicatedDataStoreOptions {
|
|
|
3399
3863
|
readonly now?: () => number;
|
|
3400
3864
|
}
|
|
3401
3865
|
|
|
3866
|
+
export declare interface ResendOptions {
|
|
3867
|
+
/** Operator's note, recorded on the resulting attempt row. */
|
|
3868
|
+
readonly reason?: string;
|
|
3869
|
+
/** How many attempts the resend is worth. Default 1 — see `resend`. */
|
|
3870
|
+
readonly attempts?: number;
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3402
3873
|
/** Test/dev-only — clear ALL overrides. Don't call from app code. */
|
|
3403
3874
|
export declare const _resetConnectionSubjectsForTest: () => void;
|
|
3404
3875
|
|
|
3405
3876
|
/** Reset to the env default (tests). */
|
|
3406
3877
|
export declare const resetSecretsBackend: () => void;
|
|
3407
3878
|
|
|
3879
|
+
/**
|
|
3880
|
+
* Resolve the calling subject's credential for one connection, refreshed.
|
|
3881
|
+
*
|
|
3882
|
+
* This is what a plugin or handler asks for. It never returns an expired
|
|
3883
|
+
* token: either it hands back one that is valid past the skew window, or it
|
|
3884
|
+
* throws. There is no "here's a token, good luck" third case, because the
|
|
3885
|
+
* whole reason apps hand-roll a retry-on-401 is that such a case exists.
|
|
3886
|
+
*/
|
|
3887
|
+
export declare const resolveConnection: (deps: ResolveConnectionDeps) => Promise<ResolvedConnection>;
|
|
3888
|
+
|
|
3889
|
+
export declare interface ResolveConnectionDeps {
|
|
3890
|
+
readonly store: VaultStore;
|
|
3891
|
+
readonly definition: ConnectionDefinition;
|
|
3892
|
+
readonly subjectId: string;
|
|
3893
|
+
readonly now?: () => number;
|
|
3894
|
+
/** Injectable sleep so the wait-for-another-replica path is testable without
|
|
3895
|
+
* real time. */
|
|
3896
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
/**
|
|
3900
|
+
* Resolve one subject's credential from anywhere on the server — a plugin's
|
|
3901
|
+
* `credentialsResolver`, a cron, an outbox handler.
|
|
3902
|
+
*
|
|
3903
|
+
* Throws (rather than returning null) when no vault is registered: an app that
|
|
3904
|
+
* asks for a connection it never declared has a configuration bug, and
|
|
3905
|
+
* returning "not connected" would present it as a user problem.
|
|
3906
|
+
*/
|
|
3907
|
+
export declare const resolveConnectionForSubject: (subject: Pick<Subject, "id">, connectionId: string) => Promise<ResolvedConnection>;
|
|
3908
|
+
|
|
3408
3909
|
/**
|
|
3409
3910
|
* Resolve the current process's region from the environment. Checks,
|
|
3410
3911
|
* in priority order:
|
|
@@ -3419,6 +3920,27 @@ export declare const resetSecretsBackend: () => void;
|
|
|
3419
3920
|
*/
|
|
3420
3921
|
export declare const resolveCurrentRegion: (env?: NodeJS.ProcessEnv) => string | undefined;
|
|
3421
3922
|
|
|
3923
|
+
/** What the auth strategy learns from a presented key. */
|
|
3924
|
+
export declare interface ResolvedApiKey {
|
|
3925
|
+
readonly id: string;
|
|
3926
|
+
readonly tenantId: string;
|
|
3927
|
+
/** The user who created the key, when one is recorded. Null for a key minted
|
|
3928
|
+
* outside a user session (bootstrap / admin tooling). */
|
|
3929
|
+
readonly createdBy: string | null;
|
|
3930
|
+
readonly scopes: ReadonlyArray<string>;
|
|
3931
|
+
}
|
|
3932
|
+
|
|
3933
|
+
/** A usable credential, handed to a plugin / handler. */
|
|
3934
|
+
export declare interface ResolvedConnection {
|
|
3935
|
+
readonly connectionId: string;
|
|
3936
|
+
readonly kind: ConnectionKind;
|
|
3937
|
+
readonly accessToken: string;
|
|
3938
|
+
readonly expiresAt: Date | null;
|
|
3939
|
+
readonly scopes: ReadonlyArray<string>;
|
|
3940
|
+
readonly accountId: string | null;
|
|
3941
|
+
readonly accountLabel: string | null;
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3422
3944
|
/**
|
|
3423
3945
|
* Walk a query descriptor's eager-load tree and return the FULL set
|
|
3424
3946
|
* of tables whose changes could affect the resolved snapshot.
|
|
@@ -3437,10 +3959,54 @@ export declare const resolveCurrentRegion: (env?: NodeJS.ProcessEnv) => string |
|
|
|
3437
3959
|
*/
|
|
3438
3960
|
export declare const resolveDependentTables: (descriptor: QueryDescriptor) => ReadonlySet<string>;
|
|
3439
3961
|
|
|
3962
|
+
export declare const resolveRedirectUri: (definition: OAuth2ConnectionDefinition, publicUrl: string) => string;
|
|
3963
|
+
|
|
3440
3964
|
/** Rebuild the flagged groups from the (already-committed) base rows + merge —
|
|
3441
3965
|
* the caller's resolution of a min/max rescan signal. `baseRows` is the full
|
|
3442
3966
|
* current table (or at least every row in the rescan groups). */
|
|
3443
|
-
export declare const resolveRescan: (shape: AggregateShape, state: IvmState, rescanGroups: ReadonlyArray<string>, baseRows: ReadonlyArray<
|
|
3967
|
+
export declare const resolveRescan: (shape: AggregateShape, state: IvmState, rescanGroups: ReadonlyArray<string>, baseRows: ReadonlyArray<Row_3>) => IvmState;
|
|
3968
|
+
|
|
3969
|
+
/**
|
|
3970
|
+
* Resolve the request-scoped filter for `subject`.
|
|
3971
|
+
*
|
|
3972
|
+
* Three stages, in order, and the order is the design:
|
|
3973
|
+
*
|
|
3974
|
+
* 1. **Bypass** — no registered filter, or a `system` subject: `NO_ROW_FILTER`,
|
|
3975
|
+
* zero cost, no `load` call.
|
|
3976
|
+
* 2. **Retry** — `load` runs under the filter's `retry` schedule
|
|
3977
|
+
* (`DEFAULT_ROW_FILTER_RETRY` unless overridden). A transient failure must
|
|
3978
|
+
* never reach stage 3, because at stage 3 it is indistinguishable from an
|
|
3979
|
+
* authorization answer.
|
|
3980
|
+
* 3. **Refuse** — a failure that survived retry is reported through `onError`
|
|
3981
|
+
* and then, per `onLoadError`, either FAILS with `RowFilterUnavailable`
|
|
3982
|
+
* (default) or degrades to `DENY_ALL`.
|
|
3983
|
+
*
|
|
3984
|
+
* Fails CLOSED, never open: a `load` failure never yields an unfiltered read.
|
|
3985
|
+
* A row filter that degrades to "no filter" under load failure is worse than
|
|
3986
|
+
* none, because the system keeps serving and nothing looks wrong.
|
|
3987
|
+
*
|
|
3988
|
+
* And refusal is an ERROR by default, not an empty result. An empty result for
|
|
3989
|
+
* an infrastructure failure is the most misleading outcome available: it is
|
|
3990
|
+
* byte-identical to legitimate emptiness, so the user reads "you have no
|
|
3991
|
+
* tickets" and the operator reads a healthy 200. Every constrained page IS
|
|
3992
|
+
* broken when this happens; the honest thing is to say so.
|
|
3993
|
+
*/
|
|
3994
|
+
export declare const resolveRowFilterScope: (subject: Subject, onError?: (error: unknown) => void) => Effect.Effect<RowFilterScope, RowFilterUnavailable>;
|
|
3995
|
+
|
|
3996
|
+
/**
|
|
3997
|
+
* The same resolution, for a filter held DIRECTLY rather than registered —
|
|
3998
|
+
* `resolveRowFilterScope` is exactly this applied to the process-global filter.
|
|
3999
|
+
*
|
|
4000
|
+
* It exists for a caller that has a filter in hand and no business writing it to
|
|
4001
|
+
* a process global: the `@voltro/testing` harness takes a `rowFilter:` option so
|
|
4002
|
+
* a test can assert row-level security without mutating state shared by every
|
|
4003
|
+
* other test in the worker. Splitting the resolution out — rather than letting
|
|
4004
|
+
* that caller re-derive "a system subject bypasses, a load failure refuses" —
|
|
4005
|
+
* keeps ONE definition of the policy, so the retry schedule and the
|
|
4006
|
+
* `onLoadError` decision above reach every caller at once instead of drifting
|
|
4007
|
+
* into a second, quieter copy.
|
|
4008
|
+
*/
|
|
4009
|
+
export declare const resolveRowFilterScopeFor: <Ctx>(filter: RowFilter<Ctx> | undefined, subject: Subject, onError?: (error: unknown) => void) => Effect.Effect<RowFilterScope, RowFilterUnavailable>;
|
|
3444
4010
|
|
|
3445
4011
|
/** Resolve a secret through the active backend. Falls back to `process.env`
|
|
3446
4012
|
* when the backend returns nothing, so a partially-populated remote backend
|
|
@@ -3483,7 +4049,7 @@ export { retentionTtlMsFromEnv }
|
|
|
3483
4049
|
* - delete → the row was removed, so add `old` back.
|
|
3484
4050
|
* Pure. Applied newest-first to walk from "now" to a past point.
|
|
3485
4051
|
*/
|
|
3486
|
-
export declare const reverseEventOverRows: (rows: ReadonlyArray<
|
|
4052
|
+
export declare const reverseEventOverRows: (rows: ReadonlyArray<Row_2>, e: TimelineEvent) => ReadonlyArray<Row_2>;
|
|
3487
4053
|
|
|
3488
4054
|
/** The ids that were visible before but are not now — what a live-revocation
|
|
3489
4055
|
* delta removes from an open subscription. */
|
|
@@ -3521,12 +4087,14 @@ export declare interface RoutingContext {
|
|
|
3521
4087
|
readonly forcePrimary?: boolean;
|
|
3522
4088
|
}
|
|
3523
4089
|
|
|
3524
|
-
|
|
4090
|
+
export { Row }
|
|
3525
4091
|
|
|
3526
|
-
declare type Row_2 =
|
|
4092
|
+
declare type Row_2 = Record<string, unknown>;
|
|
3527
4093
|
|
|
3528
4094
|
declare type Row_3 = Readonly<Record<string, unknown>>;
|
|
3529
4095
|
|
|
4096
|
+
declare type Row_4 = Readonly<Record<string, unknown>>;
|
|
4097
|
+
|
|
3530
4098
|
declare type Row_5 = Readonly<Record<string, unknown>>;
|
|
3531
4099
|
|
|
3532
4100
|
export declare interface RowDiff {
|
|
@@ -3537,6 +4105,71 @@ export declare interface RowDiff {
|
|
|
3537
4105
|
readonly fields: Readonly<Record<string, FieldChange>>;
|
|
3538
4106
|
}
|
|
3539
4107
|
|
|
4108
|
+
/**
|
|
4109
|
+
* The per-request filter, resolved from a subject.
|
|
4110
|
+
*
|
|
4111
|
+
* `Ctx` is whatever your `load` returns — typically the ids the predicate needs
|
|
4112
|
+
* (team ids, project ids, a role map). The framework never inspects it.
|
|
4113
|
+
*/
|
|
4114
|
+
export declare interface RowFilter<Ctx = unknown> {
|
|
4115
|
+
/** Resolve everything the predicates need, ONCE per request. May read the
|
|
4116
|
+
* store. A failure that survives `retry` is handled per `onLoadError`. */
|
|
4117
|
+
readonly load: (subject: Subject) => Effect.Effect<Ctx, unknown>;
|
|
4118
|
+
/**
|
|
4119
|
+
* The filter for one table, derived purely from the loaded context. Return
|
|
4120
|
+
* `undefined` for a table this filter does not constrain — most tables.
|
|
4121
|
+
*
|
|
4122
|
+
* Must be PURE and SYNC: it runs on every read.
|
|
4123
|
+
*/
|
|
4124
|
+
readonly predicate: (ctx: Ctx, table: string) => Predicate | undefined;
|
|
4125
|
+
/**
|
|
4126
|
+
* Retry schedule for `load`. Defaults to `DEFAULT_ROW_FILTER_RETRY` (3
|
|
4127
|
+
* attempts, exponential from 20ms). Pass your own `Schedule` to widen or
|
|
4128
|
+
* narrow it, or `false` to call `load` exactly once.
|
|
4129
|
+
*
|
|
4130
|
+
* This exists so a transient failure never REACHES the decision below. Without
|
|
4131
|
+
* it, one reaped connection is indistinguishable from "this subject may see
|
|
4132
|
+
* nothing", and a `load` that reads the store — which is the whole point of
|
|
4133
|
+
* relational visibility — is exactly the kind that blips.
|
|
4134
|
+
*/
|
|
4135
|
+
readonly retry?: Schedule.Schedule<unknown, unknown> | false;
|
|
4136
|
+
/**
|
|
4137
|
+
* What a `load` failure that survived `retry` means. Default `'fail'`.
|
|
4138
|
+
*
|
|
4139
|
+
* - `'fail'` — the request fails with `RowFilterUnavailable`. Correct by
|
|
4140
|
+
* default: every constrained page IS broken, and saying so is the only
|
|
4141
|
+
* outcome the user and the operator can act on.
|
|
4142
|
+
* - `'deny'` — degrade to a scope matching zero rows. For an app that would
|
|
4143
|
+
* rather render an empty list than an error state. `onError` still fires.
|
|
4144
|
+
*
|
|
4145
|
+
* There is deliberately no fail-OPEN option. A filter that evaporates under
|
|
4146
|
+
* DB stress leaks precisely when nobody is watching, and it is only safe if
|
|
4147
|
+
* the handler still carries the check the filter was meant to replace.
|
|
4148
|
+
*/
|
|
4149
|
+
readonly onLoadError?: 'fail' | 'deny';
|
|
4150
|
+
}
|
|
4151
|
+
|
|
4152
|
+
/** What a scoped store needs: a sync `table → Predicate?` lookup. */
|
|
4153
|
+
export declare type RowFilterScope = (table: string) => Predicate | undefined;
|
|
4154
|
+
|
|
4155
|
+
/**
|
|
4156
|
+
* `load` could not answer, so this request has NO trustworthy row visibility.
|
|
4157
|
+
*
|
|
4158
|
+
* Raised (by default) instead of quietly serving zero rows. See the module
|
|
4159
|
+
* header for why an empty result is the wrong shape for an infrastructure
|
|
4160
|
+
* failure. `cause` is `String(originalError)` — round-trip-safe across the rpc
|
|
4161
|
+
* boundary, the same convention as `StoreOperationFailed`.
|
|
4162
|
+
*/
|
|
4163
|
+
export declare class RowFilterUnavailable extends RowFilterUnavailable_base {
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
declare const RowFilterUnavailable_base: Schema.TaggedErrorClass<RowFilterUnavailable, "RowFilterUnavailable", {
|
|
4167
|
+
readonly _tag: Schema.tag<"RowFilterUnavailable">;
|
|
4168
|
+
} & {
|
|
4169
|
+
/** `String(originalError)` — the last failure after retries were exhausted. */
|
|
4170
|
+
cause: typeof Schema.String;
|
|
4171
|
+
}>;
|
|
4172
|
+
|
|
3540
4173
|
/** One `_voltro_row_history` row, structurally (from @voltro/plugin-versioning). */
|
|
3541
4174
|
export declare interface RowHistoryEntry {
|
|
3542
4175
|
readonly value?: unknown;
|
|
@@ -3731,6 +4364,23 @@ export declare interface RuntimeContext {
|
|
|
3731
4364
|
*/
|
|
3732
4365
|
export declare const runWakeLoop: (deps: WakeOrchestratorDeps, intervalMs?: number) => WakeLoopHandle;
|
|
3733
4366
|
|
|
4367
|
+
/**
|
|
4368
|
+
* Run a full-transaction thunk, replaying it on a transient DB error
|
|
4369
|
+
* (deadlock / lock-wait). Non-transient failures throw on the first try.
|
|
4370
|
+
* Backoff escalates with random jitter so concurrent victims don't
|
|
4371
|
+
* re-collide in lockstep.
|
|
4372
|
+
*
|
|
4373
|
+
* `delay` overrides that backoff. The only caller that does is the unit-test
|
|
4374
|
+
* harness (`@voltro/testing`'s `invoke`), which reproduces the replay against
|
|
4375
|
+
* an in-memory store: there is no lock manager to de-correlate against there,
|
|
4376
|
+
* so a real sleep would buy nothing and would stall a suite using fake timers.
|
|
4377
|
+
* Exported for exactly that — one retry implementation, not a second one that
|
|
4378
|
+
* drifts from this classification.
|
|
4379
|
+
*/
|
|
4380
|
+
export declare const runWithDeadlockRetry: <T>(work: () => Promise<T>, options?: {
|
|
4381
|
+
readonly delay?: (attempt: number) => Promise<void>;
|
|
4382
|
+
}) => Promise<T>;
|
|
4383
|
+
|
|
3734
4384
|
/**
|
|
3735
4385
|
* Run `work` inside a routing scope. Every `query()` call against a
|
|
3736
4386
|
* `ReplicatedDataStore` from inside `work` (or its async children)
|
|
@@ -3781,6 +4431,14 @@ export declare interface RywPositionStore {
|
|
|
3781
4431
|
size(): number;
|
|
3782
4432
|
}
|
|
3783
4433
|
|
|
4434
|
+
/**
|
|
4435
|
+
* Reject anything but a same-origin path. The callback ends in a 302 to this
|
|
4436
|
+
* value, so accepting `https://evil.example` would turn every app that
|
|
4437
|
+
* declares a connection into an open redirector — a phishing primitive handed
|
|
4438
|
+
* out for free with a feature about credentials.
|
|
4439
|
+
*/
|
|
4440
|
+
export declare const sanitizeRedirectTo: (value: string | undefined) => string;
|
|
4441
|
+
|
|
3784
4442
|
/** Brand so the CLI's discovery loop can duck-type a `*.cron.tsx`
|
|
3785
4443
|
* default export without importing the concrete class. */
|
|
3786
4444
|
declare const SCHEDULE_BRAND: unique symbol;
|
|
@@ -4085,6 +4743,8 @@ export declare interface SchemaRegistry {
|
|
|
4085
4743
|
declare interface ScopeCtx {
|
|
4086
4744
|
readonly subject: Subject;
|
|
4087
4745
|
readonly schemaRegistry: SchemaRegistry;
|
|
4746
|
+
/** Row-level security scope for this request — see `./rowFilter`. */
|
|
4747
|
+
readonly rowFilter?: RowFilterScope;
|
|
4088
4748
|
}
|
|
4089
4749
|
|
|
4090
4750
|
export declare interface SecretsBackend {
|
|
@@ -4108,7 +4768,7 @@ export declare class SelectBuilder {
|
|
|
4108
4768
|
private query;
|
|
4109
4769
|
private skipTenant;
|
|
4110
4770
|
private skipSoftDelete;
|
|
4111
|
-
constructor(backend: FluentStoreBackend, scope: ScopeCtx, table: string, query: Query<
|
|
4771
|
+
constructor(backend: FluentStoreBackend, scope: ScopeCtx, table: string, query: Query<Row>, skipTenant?: boolean, skipSoftDelete?: boolean);
|
|
4112
4772
|
private clone;
|
|
4113
4773
|
/** `.where(eq(...))` (predicate AST) OR `.where(col, value)` /
|
|
4114
4774
|
* `.where(col, op, value)` (ergonomic). Multiple calls AND-merge. */
|
|
@@ -4118,7 +4778,7 @@ export declare class SelectBuilder {
|
|
|
4118
4778
|
offset(n: number): SelectBuilder;
|
|
4119
4779
|
select(...columns: ReadonlyArray<string>): SelectBuilder;
|
|
4120
4780
|
using(indexName: string): SelectBuilder;
|
|
4121
|
-
with(spec: Parameters<Query<
|
|
4781
|
+
with(spec: Parameters<Query<Row>['with']>[0]): SelectBuilder;
|
|
4122
4782
|
groupBy(columns: ReadonlyArray<string>): SelectBuilder;
|
|
4123
4783
|
matching(indexName: string, query: string): SelectBuilder;
|
|
4124
4784
|
/** Drop the automatic tenant filter (cross-tenant staff reads). */
|
|
@@ -4127,11 +4787,11 @@ export declare class SelectBuilder {
|
|
|
4127
4787
|
withDeleted(): SelectBuilder;
|
|
4128
4788
|
private scopedQuery;
|
|
4129
4789
|
/** Terminal: all matching rows. */
|
|
4130
|
-
all(): Promise<ReadonlyArray<
|
|
4790
|
+
all(): Promise<ReadonlyArray<Row>>;
|
|
4131
4791
|
/** Terminal: the first matching row or `null`. */
|
|
4132
|
-
maybeOne(): Promise<
|
|
4792
|
+
maybeOne(): Promise<Row | null>;
|
|
4133
4793
|
/** Alias of `maybeOne` — the first row or `null`. */
|
|
4134
|
-
first(): Promise<
|
|
4794
|
+
first(): Promise<Row | null>;
|
|
4135
4795
|
/**
|
|
4136
4796
|
* Terminal: EXACTLY one row. Fails with `NoRowFound` on zero matches —
|
|
4137
4797
|
* and equally on two or more.
|
|
@@ -4143,7 +4803,7 @@ export declare class SelectBuilder {
|
|
|
4143
4803
|
* wire buys a loud failure at the point the assumption breaks. Use
|
|
4144
4804
|
* `.first()` / `.maybeOne()` when you genuinely want "any match".
|
|
4145
4805
|
*/
|
|
4146
|
-
one(): Promise<
|
|
4806
|
+
one(): Promise<Row>;
|
|
4147
4807
|
/** Terminal: COUNT(*) of matching rows (real aggregate, not a fetch). */
|
|
4148
4808
|
count(): Promise<number>;
|
|
4149
4809
|
/** Terminal: does any row match? Uses a `LIMIT 1` probe. */
|
|
@@ -4156,6 +4816,16 @@ export declare interface ServeRequestContext {
|
|
|
4156
4816
|
readonly subject: unknown;
|
|
4157
4817
|
readonly traceId: string;
|
|
4158
4818
|
readonly spanId?: string;
|
|
4819
|
+
/**
|
|
4820
|
+
* Row-level visibility for this request, resolved by `withRowFilter` and read
|
|
4821
|
+
* by the context builder when it wraps the store.
|
|
4822
|
+
*
|
|
4823
|
+
* Carried on the REQUEST because resolving it may read the database while the
|
|
4824
|
+
* context builder is synchronous. Making the builder async would ripple
|
|
4825
|
+
* through every entrypoint; resolving here, already inside an async
|
|
4826
|
+
* boundary, does not.
|
|
4827
|
+
*/
|
|
4828
|
+
readonly rowFilter?: RowFilterScope;
|
|
4159
4829
|
}
|
|
4160
4830
|
|
|
4161
4831
|
/** A plugin interceptor — wraps the base run Effect (Effect-native chain). */
|
|
@@ -4168,9 +4838,19 @@ export declare type ServeRpcInterceptor = (base: Effect.Effect<unknown, unknown,
|
|
|
4168
4838
|
readonly spanId?: string;
|
|
4169
4839
|
}) => Effect.Effect<unknown, unknown, never>;
|
|
4170
4840
|
|
|
4841
|
+
/** Register the process-wide connection resolver (or clear with `undefined`).
|
|
4842
|
+
* Called by the CLI at boot; tests call it directly. */
|
|
4843
|
+
export declare const setConnectionResolver: (resolver: ConnectionResolver | undefined) => void;
|
|
4844
|
+
|
|
4171
4845
|
/** Register the field cipher (or clear with `undefined`). */
|
|
4172
4846
|
export declare const setFieldCipher: (cipher: FieldCipher | undefined) => void;
|
|
4173
4847
|
|
|
4848
|
+
/**
|
|
4849
|
+
* Register (or clear, with `undefined`) the process-global row filter. Call
|
|
4850
|
+
* once at boot. Last write wins.
|
|
4851
|
+
*/
|
|
4852
|
+
export declare const setRowFilter: <Ctx>(filter: RowFilter<Ctx> | undefined) => void;
|
|
4853
|
+
|
|
4174
4854
|
/** Install the process-wide secrets backend (called once at boot). */
|
|
4175
4855
|
export declare const setSecretsBackend: (backend: SecretsBackend) => void;
|
|
4176
4856
|
|
|
@@ -4246,10 +4926,10 @@ export declare interface SingleNodeOrchestratorOptions {
|
|
|
4246
4926
|
* tables invalidates the entry via the invalidation bus.
|
|
4247
4927
|
*/
|
|
4248
4928
|
export declare interface SnapshotCache {
|
|
4249
|
-
readonly wrap: (binding: SnapshotCacheBinding, tags: ReadonlyArray<string>, compute: () => Promise<ReadonlyArray<
|
|
4929
|
+
readonly wrap: (binding: SnapshotCacheBinding, tags: ReadonlyArray<string>, compute: () => Promise<ReadonlyArray<Row>>) => Promise<ReadonlyArray<Row>>;
|
|
4250
4930
|
/** Write-through from the live recompute, so the cache is never staler
|
|
4251
4931
|
* than the freshest live subscriber. Fire-and-forget. */
|
|
4252
|
-
readonly put: (binding: SnapshotCacheBinding, tags: ReadonlyArray<string>, rows: ReadonlyArray<
|
|
4932
|
+
readonly put: (binding: SnapshotCacheBinding, tags: ReadonlyArray<string>, rows: ReadonlyArray<Row>) => void;
|
|
4253
4933
|
}
|
|
4254
4934
|
|
|
4255
4935
|
/**
|
|
@@ -4305,6 +4985,26 @@ export declare interface StartupContext {
|
|
|
4305
4985
|
/** The default export shape every `*.startup.ts` must provide. */
|
|
4306
4986
|
export declare type StartupFn = (ctx: StartupContext) => void | Promise<void>;
|
|
4307
4987
|
|
|
4988
|
+
/**
|
|
4989
|
+
* Write (or replace) the calling subject's credential, encrypted.
|
|
4990
|
+
*
|
|
4991
|
+
* `upsert` on the `(connectionId, subjectId)` unique keeps re-connecting a
|
|
4992
|
+
* replace rather than an accumulation of stale rows — which matters because a
|
|
4993
|
+
* second row would make "which token is current" a question the resolver would
|
|
4994
|
+
* have to answer with a heuristic.
|
|
4995
|
+
*/
|
|
4996
|
+
export declare const storeCredential: (input: StoreCredentialInput) => Promise<Row>;
|
|
4997
|
+
|
|
4998
|
+
export declare interface StoreCredentialInput {
|
|
4999
|
+
readonly store: VaultStore;
|
|
5000
|
+
readonly definition: ConnectionDefinition;
|
|
5001
|
+
readonly subjectId: string;
|
|
5002
|
+
readonly tenantId: string | null;
|
|
5003
|
+
readonly tokens: ConnectionTokens;
|
|
5004
|
+
readonly account?: ConnectionAccount;
|
|
5005
|
+
readonly now?: () => Date;
|
|
5006
|
+
}
|
|
5007
|
+
|
|
4308
5008
|
/**
|
|
4309
5009
|
* Discriminated union of all framework-owned store errors. Use it on
|
|
4310
5010
|
* a mutation's `error:` schema when you want every kind surfaced
|
|
@@ -4322,6 +5022,18 @@ export declare interface StoreMiddlewareContext {
|
|
|
4322
5022
|
* natively, or there are no array columns to translate).
|
|
4323
5023
|
*/
|
|
4324
5024
|
readonly dialect?: DialectId;
|
|
5025
|
+
/**
|
|
5026
|
+
* Row-level security scope for THIS request's subject, resolved once by the
|
|
5027
|
+
* context builder (`resolveRowFilterScope`). Omitted → no row filtering,
|
|
5028
|
+
* which is right when the app registered none.
|
|
5029
|
+
*
|
|
5030
|
+
* A filter that failed to load NEVER arrives here as `undefined`: by default
|
|
5031
|
+
* the request fails with `RowFilterUnavailable` before a context is built, and
|
|
5032
|
+
* under `onLoadError: 'deny'` a deny-all scope arrives instead. The difference
|
|
5033
|
+
* between "nothing to filter" and "we could not tell" must not collapse —
|
|
5034
|
+
* `undefined` means the former and only the former.
|
|
5035
|
+
*/
|
|
5036
|
+
readonly rowFilter?: RowFilterScope;
|
|
4325
5037
|
}
|
|
4326
5038
|
|
|
4327
5039
|
/**
|
|
@@ -4458,6 +5170,11 @@ export declare interface SubscriptionSnapshot {
|
|
|
4458
5170
|
readonly subscriberCount: number;
|
|
4459
5171
|
}
|
|
4460
5172
|
|
|
5173
|
+
/** Drop handshake grants past their TTL. Wired into the boot retention sweep;
|
|
5174
|
+
* a grant is worthless the moment it expires and keeping them turns a hot
|
|
5175
|
+
* table into an audit liability. */
|
|
5176
|
+
export declare const sweepExpiredGrants: (store: VaultStore, now?: Date) => Promise<number>;
|
|
5177
|
+
|
|
4461
5178
|
export { sweepRetention }
|
|
4462
5179
|
|
|
4463
5180
|
/**
|
|
@@ -4557,8 +5274,8 @@ export declare interface TimelineEvent {
|
|
|
4557
5274
|
readonly table: string;
|
|
4558
5275
|
readonly op: 'insert' | 'update' | 'delete';
|
|
4559
5276
|
/** Redacted row images. `old` present on update/delete; `new` on insert/update. */
|
|
4560
|
-
readonly old:
|
|
4561
|
-
readonly new:
|
|
5277
|
+
readonly old: Row_2 | null;
|
|
5278
|
+
readonly new: Row_2 | null;
|
|
4562
5279
|
readonly tenantId: string | null;
|
|
4563
5280
|
}
|
|
4564
5281
|
|
|
@@ -4602,7 +5319,7 @@ export declare class TimelineRecorder {
|
|
|
4602
5319
|
* Read-only. Correct as long as the ring still holds the events after
|
|
4603
5320
|
* `asOfSeq` (bounded by `capacity`).
|
|
4604
5321
|
*/
|
|
4605
|
-
replay(table: string, asOfSeq: number, currentRows: ReadonlyArray<
|
|
5322
|
+
replay(table: string, asOfSeq: number, currentRows: ReadonlyArray<Row_2>): ReadonlyArray<Row_2>;
|
|
4606
5323
|
}
|
|
4607
5324
|
|
|
4608
5325
|
/** Time window. `to` defaults to now. */
|
|
@@ -4898,6 +5615,8 @@ export declare const useAggregate: <Row>(def: AggregateDefinition<Row>) => Effec
|
|
|
4898
5615
|
*/
|
|
4899
5616
|
export declare const useAnalytics: () => Effect.Effect<AnalyticsSinkImpl, never, AnalyticsSink>;
|
|
4900
5617
|
|
|
5618
|
+
declare type VaultStore = Pick<DataStore, 'query' | 'insert' | 'update' | 'updateMany' | 'delete' | 'upsert'>;
|
|
5619
|
+
|
|
4901
5620
|
/** The pure core of live revocation: given a subject + action + a row-set,
|
|
4902
5621
|
* return ONLY the rows the subject may still see. The reactive matcher calls
|
|
4903
5622
|
* this on a permission-changing write and diffs against the prior visible set;
|
|
@@ -4919,6 +5638,39 @@ export declare const VOLTRO_SOFT_DELETE_MIXIN_ID: "voltro/softDelete";
|
|
|
4919
5638
|
|
|
4920
5639
|
export declare const VOLTRO_TENANT_MIXIN_ID: "voltro/tenant";
|
|
4921
5640
|
|
|
5641
|
+
/**
|
|
5642
|
+
* `_voltro_connection_grants` — an IN-FLIGHT oauth2 handshake. Separate from
|
|
5643
|
+
* the credential table on purpose: a handshake is a short-lived, single-use
|
|
5644
|
+
* anti-CSRF token with a different lifecycle (seconds, deleted on use) from a
|
|
5645
|
+
* credential (months, updated in place). Folding it into a `status:'pending'`
|
|
5646
|
+
* row on the credential table would mean a failed handshake leaves a row that
|
|
5647
|
+
* the resolver + the list projection both have to learn to ignore.
|
|
5648
|
+
*
|
|
5649
|
+
* `state` is what carries identity across the redirect: the browser comes back
|
|
5650
|
+
* from the provider with no session guarantee, so the row — minted for a known
|
|
5651
|
+
* subject before the redirect — is the only trustworthy statement of who the
|
|
5652
|
+
* arriving code belongs to.
|
|
5653
|
+
*/
|
|
5654
|
+
export declare const _voltroConnectionGrantsTable: TableLike;
|
|
5655
|
+
|
|
5656
|
+
/**
|
|
5657
|
+
* `_voltro_connections` — ONE row per (connection, subject). The stored
|
|
5658
|
+
* credential.
|
|
5659
|
+
*
|
|
5660
|
+
* `accessToken` / `refreshToken` hold CIPHERTEXT (`enc:v1:…`), never the raw
|
|
5661
|
+
* token. They are plain `text()` rather than `.encrypted()` columns on purpose:
|
|
5662
|
+
* `.encrypted()` decrypts inside the `ctx.store` mixin middleware, and the
|
|
5663
|
+
* vault must be readable from the resolver path, the OAuth callback route, and
|
|
5664
|
+
* the refresh worker — none of which hold a request-scoped `ctx.store`. Using
|
|
5665
|
+
* `encryptField` / `decryptField` explicitly keeps ONE cipher and one key while
|
|
5666
|
+
* letting every one of those paths read the column. (Same reason the api-key
|
|
5667
|
+
* and session paths use the standalone helpers.)
|
|
5668
|
+
*
|
|
5669
|
+
* Reactive so `__voltro.connections.list` updates an open settings page the
|
|
5670
|
+
* moment a callback lands, with no polling.
|
|
5671
|
+
*/
|
|
5672
|
+
export declare const _voltroConnectionsTable: TableLike;
|
|
5673
|
+
|
|
4922
5674
|
/** `_voltro_rebac_tuples` — the relationship tuples `can()` reads. One row =
|
|
4923
5675
|
* "`subjectId` has `relation` on `<resourceType>:<resourceId>`". Grant by
|
|
4924
5676
|
* inserting; revoke by deleting (a delete is the live-revocation trigger). */
|
|
@@ -5047,6 +5799,26 @@ export declare type WebhooksAppContext = unknown;
|
|
|
5047
5799
|
* builder. */
|
|
5048
5800
|
export declare type WhereOp = '=' | '!=' | '<>' | '>' | '>=' | '<' | '<=' | 'in' | 'like' | 'contains' | 'fts';
|
|
5049
5801
|
|
|
5802
|
+
/**
|
|
5803
|
+
* Resolve this request's row-filter scope. A no-op (same object back) when the
|
|
5804
|
+
* app registered no filter, so an app that uses none pays nothing.
|
|
5805
|
+
*
|
|
5806
|
+
* Every runner calls this before building a context. A read path that skipped
|
|
5807
|
+
* it would have no row-level security and would look exactly like one that
|
|
5808
|
+
* does — which is why it lives in the shared spine rather than in each
|
|
5809
|
+
* entrypoint.
|
|
5810
|
+
*
|
|
5811
|
+
* REJECTS with the raw `RowFilterUnavailable` (via `runProvidedEffect`, not
|
|
5812
|
+
* `Effect.runPromise`) when the filter's `onLoadError` is the default `'fail'`
|
|
5813
|
+
* and `load` failed every retry. Raw, so the rpc encoder matches it against the
|
|
5814
|
+
* descriptor's `error:` schema and the client gets the tag instead of an opaque
|
|
5815
|
+
* defect — the same treatment `enforceGuards` gives a `ScopeError`. Proceeding
|
|
5816
|
+
* with an unresolved filter is not an option here: the context builder would
|
|
5817
|
+
* receive `rowFilter: undefined`, which means "this app registered no filter"
|
|
5818
|
+
* and would silently serve the request UNFILTERED.
|
|
5819
|
+
*/
|
|
5820
|
+
export declare const withRowFilter: <R extends ServeRequestContext>(request: R) => Promise<R>;
|
|
5821
|
+
|
|
5050
5822
|
export declare interface WorkflowCallerContext {
|
|
5051
5823
|
readonly subject?: unknown;
|
|
5052
5824
|
readonly traceId?: string | null;
|