@voltro/runtime 0.4.0 → 0.6.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 -0
- package/dist/index.d.ts +1034 -78
- package/dist/index.js +1900 -922
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { AnyCheckSpec } from '@voltro/protocol';
|
|
1
2
|
import { AuthStrategy } from '@voltro/protocol';
|
|
2
3
|
import { CaughtUpVerdict } from '@voltro/database';
|
|
3
4
|
import { ChangeEvent } from '@voltro/database';
|
|
4
5
|
import { clearRetentions } from '@voltro/database';
|
|
5
6
|
import { ConnectionInfo } from '@voltro/protocol';
|
|
7
|
+
import { ConnectionKind } from '@voltro/protocol';
|
|
8
|
+
import { ConnectionState } from '@voltro/protocol';
|
|
6
9
|
import { Context } from 'effect';
|
|
7
10
|
import { createServer } from 'node:http';
|
|
8
11
|
import { Cron } from 'effect';
|
|
@@ -12,7 +15,6 @@ import { DialectReplicationAdapter } from '@voltro/database';
|
|
|
12
15
|
import { Duration } from 'effect';
|
|
13
16
|
import { Effect } from 'effect';
|
|
14
17
|
import { FieldCipher } from '@voltro/database';
|
|
15
|
-
import { GuardCheckSpec } from '@voltro/protocol';
|
|
16
18
|
import * as http from 'node:http';
|
|
17
19
|
import { HttpClient } from '@effect/platform';
|
|
18
20
|
import { HttpRequestInterceptor } from '@voltro/protocol';
|
|
@@ -40,7 +42,7 @@ 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';
|
|
@@ -285,14 +287,14 @@ export declare class AggregateMaintainer {
|
|
|
285
287
|
private readonly groups;
|
|
286
288
|
constructor(shape: AggregateShape);
|
|
287
289
|
/** Seed from the full base row-set (boot / after a rescan-from-scratch). */
|
|
288
|
-
seed(baseRows: ReadonlyArray<
|
|
290
|
+
seed(baseRows: ReadonlyArray<Row_4>): void;
|
|
289
291
|
/**
|
|
290
292
|
* Apply one delta. Returns the groups (if any) whose min/max extreme was
|
|
291
293
|
* deleted and so MUST be rebuilt via `resolveRescan` before the next read.
|
|
292
294
|
*/
|
|
293
295
|
applyChange(delta: CdcDelta): ReadonlyArray<string>;
|
|
294
296
|
/** Rebuild the flagged groups from the current base rows (min/max recovery). */
|
|
295
|
-
resolveRescan(rescanGroups: ReadonlyArray<string>, baseRows: ReadonlyArray<
|
|
297
|
+
resolveRescan(rescanGroups: ReadonlyArray<string>, baseRows: ReadonlyArray<Row_4>): void;
|
|
296
298
|
/** The current materialized result — one entry per non-empty group. */
|
|
297
299
|
materialize(): ReadonlyArray<MaintainedGroup>;
|
|
298
300
|
private groupValues;
|
|
@@ -654,6 +656,8 @@ export declare interface AnalyticsTopEntry {
|
|
|
654
656
|
readonly value: number;
|
|
655
657
|
}
|
|
656
658
|
|
|
659
|
+
declare type AnyRow = Record<string, unknown>;
|
|
660
|
+
|
|
657
661
|
export declare const API_KEYS_TABLE = "_voltro_api_keys";
|
|
658
662
|
|
|
659
663
|
/** Admin-gated management routes (issue / list / revoke) for the built-in keys.
|
|
@@ -678,18 +682,15 @@ export declare interface ApiKeyServiceShape {
|
|
|
678
682
|
/** Mint a new key. Returns the raw token ONCE. */
|
|
679
683
|
readonly issue: (input: IssueInput) => Promise<IssuedApiKey>;
|
|
680
684
|
/** 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
|
-
|
|
685
|
+
* usable (not revoked / expired), and stamps `lastUsedAt`.
|
|
686
|
+
*
|
|
687
|
+
* `createdBy` travels with it because a key always has an accountable
|
|
688
|
+
* human, and downstream needs to know WHICH one: a personal CLI key and a
|
|
689
|
+
* shared CI credential are the same shape on the wire, and telling them
|
|
690
|
+
* apart is what makes per-person accounting possible at all. */
|
|
691
|
+
readonly resolveByHash: (hash: string, now?: number) => Promise<ResolvedApiKey | null>;
|
|
687
692
|
/** 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>;
|
|
693
|
+
readonly verify: (token: string, now?: number) => Promise<ResolvedApiKey | null>;
|
|
693
694
|
/** Revoke a key (irreversible). */
|
|
694
695
|
readonly revoke: (id: string, now?: number) => Promise<boolean>;
|
|
695
696
|
/** Rotate: revoke the old key + issue a replacement with the same name/scopes/tenant. */
|
|
@@ -758,6 +759,36 @@ export declare interface AppContext {
|
|
|
758
759
|
* triggers. `emit(name, payload)` records the event and fans out to
|
|
759
760
|
* matching workflow triggers. */
|
|
760
761
|
readonly events?: EventsAppContext;
|
|
762
|
+
/**
|
|
763
|
+
* Transactional outbox (`ctx.outbox`). Absent when the app declares no
|
|
764
|
+
* `*.outbox.ts` handler — an enqueue with nobody to deliver it would be a
|
|
765
|
+
* side effect the app believes happened and which never will, so the field
|
|
766
|
+
* simply isn't there rather than silently accepting writes.
|
|
767
|
+
*/
|
|
768
|
+
readonly outbox?: OutboxFacade;
|
|
769
|
+
/**
|
|
770
|
+
* Connection vault (`ctx.connections`) — per-subject third-party
|
|
771
|
+
* credentials. Present when the app declares at least one `*.connection.ts`.
|
|
772
|
+
* `ctx.connections.get('jira')` returns the CALLING subject's credential,
|
|
773
|
+
* refreshed if it was near expiry; there is no parameter for "some other
|
|
774
|
+
* subject", by construction.
|
|
775
|
+
*/
|
|
776
|
+
readonly connections?: ConnectionsFacade;
|
|
777
|
+
/**
|
|
778
|
+
* Request-scoped batching (`ctx.load` / `ctx.loadMany`). Coalesces
|
|
779
|
+
* same-tick reads of one table into a single `WHERE id IN (...)`.
|
|
780
|
+
*
|
|
781
|
+
* For assembly whose SHAPE depends on the data — a tree walk where each
|
|
782
|
+
* level's ids come from the level above — which `relations()` + `.with()`
|
|
783
|
+
* cannot express statically. Reach for the relation first; this is the
|
|
784
|
+
* fallback, not the default.
|
|
785
|
+
*
|
|
786
|
+
* Scoped to the request on purpose: a longer-lived cache would serve one
|
|
787
|
+
* subject's rows to another (a data-isolation bug on a tenant-scoped store,
|
|
788
|
+
* not a performance detail) and would go stale across a mutation.
|
|
789
|
+
*/
|
|
790
|
+
readonly load: DataLoader['load'];
|
|
791
|
+
readonly loadMany: DataLoader['loadMany'];
|
|
761
792
|
}
|
|
762
793
|
|
|
763
794
|
/**
|
|
@@ -789,6 +820,17 @@ export declare const applyInverse: (store: UndoApplyStore, op: InverseOp) => Pro
|
|
|
789
820
|
* `synthesizeInverse`), as the caller's transaction wraps them. */
|
|
790
821
|
export declare const applyInverses: (store: UndoApplyStore, ops: ReadonlyArray<InverseOp>) => Promise<void>;
|
|
791
822
|
|
|
823
|
+
/** AND-merge the row filter for `table` onto an existing predicate. */
|
|
824
|
+
export declare const applyRowFilter: (scope: RowFilterScope, table: string, predicate: Predicate | undefined) => Predicate | undefined;
|
|
825
|
+
|
|
826
|
+
/** Apply a resolved scope to a whole descriptor. Used by the dispatcher, which
|
|
827
|
+
* re-derives a subscription's read descriptor from its unfiltered base before
|
|
828
|
+
* every delivery. */
|
|
829
|
+
export declare const applyRowFilterToDescriptor: <D extends {
|
|
830
|
+
readonly table: string;
|
|
831
|
+
readonly predicate?: Predicate | undefined;
|
|
832
|
+
}>(scope: RowFilterScope, descriptor: D) => D;
|
|
833
|
+
|
|
792
834
|
export declare const applySoftDeleteScope: (descriptor: QueryDescriptor, softDeleteScopedTables: ReadonlySet<string>) => QueryDescriptor;
|
|
793
835
|
|
|
794
836
|
export declare const applyTenantScope: (descriptor: QueryDescriptor, tenantScopedTables: ReadonlySet<string>, tenantId: string | null | undefined) => QueryDescriptor;
|
|
@@ -821,6 +863,19 @@ export declare interface AppSupervisor {
|
|
|
821
863
|
* deny so it surfaces to the client typed. */
|
|
822
864
|
export declare const assertCan: (subject: RebacSubject, action: string, resource: RebacResource, deps: CanDeps) => void;
|
|
823
865
|
|
|
866
|
+
/**
|
|
867
|
+
* Refuse to boot an app that declares connections without a field cipher.
|
|
868
|
+
*
|
|
869
|
+
* Without this the first `encryptField` call throws at REQUEST time — which
|
|
870
|
+
* means a deployment looks healthy, accepts a user through a whole OAuth
|
|
871
|
+
* consent screen, and fails only at the moment it would have persisted the
|
|
872
|
+
* token. Moving the check to boot makes a missing key an operator-visible
|
|
873
|
+
* startup failure instead of a per-user mystery. It is deliberately NOT a
|
|
874
|
+
* warning: a warning here degrades to plaintext storage the moment someone
|
|
875
|
+
* "fixes" the throw.
|
|
876
|
+
*/
|
|
877
|
+
export declare const assertConnectionCipherConfigured: (definitions: ReadonlyArray<ConnectionDefinition>) => void;
|
|
878
|
+
|
|
824
879
|
/**
|
|
825
880
|
* Async cache surface exposed as `ctx.cache` to non-Effect handlers — the
|
|
826
881
|
* facade over the Effect-native `Cache` service from `@voltro/cache`
|
|
@@ -911,6 +966,30 @@ export declare interface AttachAnalyticsMirrorOptions {
|
|
|
911
966
|
*/
|
|
912
967
|
export declare const awaitServerListening: (server: ReturnType<typeof createServer>, timeoutMs?: number) => Promise<void>;
|
|
913
968
|
|
|
969
|
+
/** Exponential backoff with a ceiling — 1s, 2s, 4s … capped at 5 minutes. */
|
|
970
|
+
export declare const backoffMs: (attempt: number) => number;
|
|
971
|
+
|
|
972
|
+
/** Mint a single-use handshake grant and build the provider's consent URL. */
|
|
973
|
+
export declare const beginOAuthConnect: (deps: BeginOAuthDeps) => Promise<BeginOAuthResult>;
|
|
974
|
+
|
|
975
|
+
export declare interface BeginOAuthDeps {
|
|
976
|
+
readonly store: VaultStore;
|
|
977
|
+
readonly definition: OAuth2ConnectionDefinition;
|
|
978
|
+
readonly subjectId: string;
|
|
979
|
+
readonly tenantId: string | null;
|
|
980
|
+
readonly redirectTo?: string;
|
|
981
|
+
/** Absolute base URL the provider will redirect back to, e.g.
|
|
982
|
+
* `https://app.example.com`. Used only when the declaration omits an
|
|
983
|
+
* explicit `redirectUri`. */
|
|
984
|
+
readonly publicUrl: string;
|
|
985
|
+
readonly now?: () => Date;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
export declare interface BeginOAuthResult {
|
|
989
|
+
readonly authorizeUrl: string;
|
|
990
|
+
readonly state: string;
|
|
991
|
+
}
|
|
992
|
+
|
|
914
993
|
/**
|
|
915
994
|
* Override the subject for an active connection. Called from the
|
|
916
995
|
* `auth.signin` (or any "I just authenticated this caller") handler
|
|
@@ -949,7 +1028,7 @@ export declare const bindStream: <Element, Err = unknown>(buildStream: (context:
|
|
|
949
1028
|
* the single explicit type-erasure boundary between the framework's
|
|
950
1029
|
* dynamic data layer and the rpc layer's static schemas.
|
|
951
1030
|
*/
|
|
952
|
-
export declare const bindSubscription: <T = ReadonlyArray<
|
|
1031
|
+
export declare const bindSubscription: <T = ReadonlyArray<Row>>(buildDescriptor: (context: RuntimeContext) => QueryDescriptor | ComputedQuery | Effect.Effect<QueryDescriptor | ComputedQuery, unknown, never>, dispatcher: Dispatcher, spanName?: string,
|
|
953
1032
|
/**
|
|
954
1033
|
* Optional resolver: given the materialised descriptor (after any
|
|
955
1034
|
* `.using()` hint), return the `MatcherIndexHint` the matcher should
|
|
@@ -973,16 +1052,32 @@ cache?: {
|
|
|
973
1052
|
readonly swrMs: number | undefined;
|
|
974
1053
|
readonly scope: "subject" | "global";
|
|
975
1054
|
readonly baseKey: string;
|
|
976
|
-
}
|
|
1055
|
+
},
|
|
1056
|
+
/**
|
|
1057
|
+
* Per-subject authorization re-check, built by
|
|
1058
|
+
* `makeQueryReauthorizer(query, input)` in the serve pipeline. Subscribing
|
|
1059
|
+
* passes `guards:` once; this re-runs them before every delivery so a
|
|
1060
|
+
* revoked grant CLOSES the stream rather than continuing to serve it.
|
|
1061
|
+
* Omitted for a query that declares no guards.
|
|
1062
|
+
*/
|
|
1063
|
+
reauthorize?: (subject: Subject) => () => Promise<unknown>,
|
|
1064
|
+
/**
|
|
1065
|
+
* Per-delivery row-visibility resolver. Rarely passed: when omitted, one is
|
|
1066
|
+
* DERIVED from the registered row filter (see `defaultRefilter`), so an
|
|
1067
|
+
* entrypoint cannot end up with unfiltered subscriptions by forgetting to
|
|
1068
|
+
* thread it — which is precisely how a row filter ends up applying to
|
|
1069
|
+
* queries and mutations but not to the live stream.
|
|
1070
|
+
*/
|
|
1071
|
+
refilter?: (subject: Subject) => () => Promise<RowFilterScope>) => Stream.Stream<SubscriptionEvent<T>, never, SubjectService | ConnectionInfo>;
|
|
977
1072
|
|
|
978
|
-
export declare const bindSubscriptionUntyped: (descriptor: QueryDescriptor, dispatcher: Dispatcher, context: RuntimeContext, indexHint: MatcherIndexHint | undefined, label: string, cacheBinding: SnapshotCacheBinding | undefined) => Stream.Stream<SubscriptionEvent<ReadonlyArray<
|
|
1073
|
+
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>;
|
|
979
1074
|
|
|
980
1075
|
export declare interface BrandedScheduleDefinition extends ScheduleDefinition {
|
|
981
1076
|
readonly [SCHEDULE_BRAND]: true;
|
|
982
1077
|
}
|
|
983
1078
|
|
|
984
1079
|
/** Build the full state from a base row-set (seed + per-group rescan). */
|
|
985
|
-
export declare const buildIvmState: (shape: AggregateShape, rows: ReadonlyArray<
|
|
1080
|
+
export declare const buildIvmState: (shape: AggregateShape, rows: ReadonlyArray<Row_3>) => IvmState;
|
|
986
1081
|
|
|
987
1082
|
/** Build the preview diff from a mutation's captured change-set. Pure. */
|
|
988
1083
|
export declare const buildPreview: (changes: ReadonlyArray<CdcChange>) => PreviewDiff;
|
|
@@ -1093,8 +1188,8 @@ export declare interface CdcChange {
|
|
|
1093
1188
|
/** A CDC change as the matcher delivers it. */
|
|
1094
1189
|
export declare interface CdcDelta {
|
|
1095
1190
|
readonly op: 'insert' | 'update' | 'delete';
|
|
1096
|
-
readonly old?:
|
|
1097
|
-
readonly new?:
|
|
1191
|
+
readonly old?: Row_3 | null;
|
|
1192
|
+
readonly new?: Row_3 | null;
|
|
1098
1193
|
}
|
|
1099
1194
|
|
|
1100
1195
|
export { ChangeEvent }
|
|
@@ -1166,6 +1261,10 @@ export declare type CircuitState = 'closed' | 'open' | 'half-open';
|
|
|
1166
1261
|
*/
|
|
1167
1262
|
export declare const classifyShape: (c: CandidateShape) => ShapeClassification;
|
|
1168
1263
|
|
|
1264
|
+
/** Test seam — the in-flight map is module state; a test that asserts
|
|
1265
|
+
* single-flight must be able to start from empty. */
|
|
1266
|
+
export declare const clearConnectionRefreshFlights: () => void;
|
|
1267
|
+
|
|
1169
1268
|
/** Test/boot reset. */
|
|
1170
1269
|
export declare const clearResourcePolicies: () => void;
|
|
1171
1270
|
|
|
@@ -1179,6 +1278,30 @@ export declare const clearSystemStoreHandle: () => void;
|
|
|
1179
1278
|
* branded definition can trust this won't throw. */
|
|
1180
1279
|
export declare const compileCron: (def: ScheduleDefinition) => Cron.Cron;
|
|
1181
1280
|
|
|
1281
|
+
/**
|
|
1282
|
+
* Consume a handshake grant and store the resulting credential.
|
|
1283
|
+
*
|
|
1284
|
+
* The grant row is deleted BEFORE the exchange, not after: a `state` is
|
|
1285
|
+
* single-use, and deleting it up front means a replayed callback (the user
|
|
1286
|
+
* double-clicks, a crawler follows the URL) cannot start a second exchange
|
|
1287
|
+
* even if the first is still in flight.
|
|
1288
|
+
*/
|
|
1289
|
+
export declare const completeOAuthConnect: (deps: CompleteOAuthDeps) => Promise<CompleteOAuthResult>;
|
|
1290
|
+
|
|
1291
|
+
export declare interface CompleteOAuthDeps {
|
|
1292
|
+
readonly store: VaultStore;
|
|
1293
|
+
readonly registry: ConnectionRegistry;
|
|
1294
|
+
readonly state: string;
|
|
1295
|
+
readonly code: string;
|
|
1296
|
+
readonly publicUrl: string;
|
|
1297
|
+
readonly now?: () => Date;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
export declare interface CompleteOAuthResult {
|
|
1301
|
+
readonly connectionId: string;
|
|
1302
|
+
readonly redirectTo: string;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1182
1305
|
/**
|
|
1183
1306
|
* Combine multiple analytics specs into one composite spec at
|
|
1184
1307
|
* app.config.ts time. The framework's boot path materialises each
|
|
@@ -1236,6 +1359,105 @@ declare interface ComputedQuery {
|
|
|
1236
1359
|
readonly recompute: () => Promise<unknown>;
|
|
1237
1360
|
}
|
|
1238
1361
|
|
|
1362
|
+
export declare const CONNECTION_GRANTS_TABLE = "_voltro_connection_grants";
|
|
1363
|
+
|
|
1364
|
+
/** What a declaration can learn about the account it just connected. Optional
|
|
1365
|
+
* — a connection with no `identify` still works, it just has no display name. */
|
|
1366
|
+
export declare interface ConnectionAccount {
|
|
1367
|
+
readonly accountId?: string;
|
|
1368
|
+
readonly accountLabel?: string;
|
|
1369
|
+
readonly metadata?: Record<string, unknown>;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
export declare interface ConnectionBuiltinDeps {
|
|
1373
|
+
readonly registry: ConnectionRegistry;
|
|
1374
|
+
/** Absolute origin the OAuth provider redirects back to. */
|
|
1375
|
+
readonly publicUrl: string;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/** Default callback path for a connection. The `<id>` segment is why
|
|
1379
|
+
* `defineConnection` constrains the id shape. */
|
|
1380
|
+
export declare const connectionCallbackPath: (connectionId: string) => string;
|
|
1381
|
+
|
|
1382
|
+
export declare type ConnectionDefinition = OAuth2ConnectionDefinition | PatConnectionDefinition;
|
|
1383
|
+
|
|
1384
|
+
declare interface ConnectionDefinitionCommon {
|
|
1385
|
+
/** Stable id — the value handlers pass to `ctx.connections.get(...)` and the
|
|
1386
|
+
* path segment of the callback route. */
|
|
1387
|
+
readonly id: string;
|
|
1388
|
+
/** Human label for the connect UI. Defaults to `id`. */
|
|
1389
|
+
readonly label?: string;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/** The slice of AppContext the connection executors need. */
|
|
1393
|
+
export declare interface ConnectionExecutorCtx {
|
|
1394
|
+
readonly store: DataStore;
|
|
1395
|
+
readonly request?: {
|
|
1396
|
+
readonly subject?: {
|
|
1397
|
+
readonly id?: string | null;
|
|
1398
|
+
readonly tenantId?: string | null;
|
|
1399
|
+
} | null;
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/** The subset of `fetch` the vault uses. Structurally satisfied by the global
|
|
1404
|
+
* `fetch` and by `@voltro/integration-http`'s `FetchLike`; declared locally so
|
|
1405
|
+
* the runtime takes no dependency on the http package for a two-field type. */
|
|
1406
|
+
export declare type ConnectionFetch = (url: string, init: {
|
|
1407
|
+
readonly method: string;
|
|
1408
|
+
readonly headers: Record<string, string>;
|
|
1409
|
+
readonly body: string;
|
|
1410
|
+
}) => Promise<{
|
|
1411
|
+
readonly status: number;
|
|
1412
|
+
readonly text: () => Promise<string>;
|
|
1413
|
+
}>;
|
|
1414
|
+
|
|
1415
|
+
/** `label` with the id as fallback. */
|
|
1416
|
+
export declare const connectionLabel: (definition: ConnectionDefinition) => string;
|
|
1417
|
+
|
|
1418
|
+
/** The subject has no credential on file for this connection (or it was
|
|
1419
|
+
* revoked and cleared). The caller's move is to prompt a connect. */
|
|
1420
|
+
export declare class ConnectionNotConnected extends Error {
|
|
1421
|
+
readonly connectionId: string;
|
|
1422
|
+
readonly subjectId: string;
|
|
1423
|
+
readonly _tag = "ConnectionNotConnected";
|
|
1424
|
+
constructor(connectionId: string, subjectId: string);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
export declare interface ConnectionRegistry {
|
|
1428
|
+
readonly get: (connectionId: string) => ConnectionDefinition | undefined;
|
|
1429
|
+
readonly list: () => ReadonlyArray<ConnectionDefinition>;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
/** Resolve `connectionId` for `subject`, refreshed. */
|
|
1433
|
+
export declare type ConnectionResolver = (subject: Pick<Subject, 'id'>, connectionId: string) => Promise<ResolvedConnection>;
|
|
1434
|
+
|
|
1435
|
+
export declare const CONNECTIONS_TABLE = "_voltro_connections";
|
|
1436
|
+
|
|
1437
|
+
export declare interface ConnectionsFacade {
|
|
1438
|
+
/**
|
|
1439
|
+
* The calling subject's credential for `connectionId`, refreshed if it was
|
|
1440
|
+
* near expiry. Throws `ConnectionNotConnected` when there is nothing on file
|
|
1441
|
+
* (prompt a connect) or `ConnectionHandshakeFailed` when a refresh was owed
|
|
1442
|
+
* and failed.
|
|
1443
|
+
*/
|
|
1444
|
+
get(connectionId: string): Promise<ResolvedConnection>;
|
|
1445
|
+
/** Same, but `null` instead of throwing when not connected — for the common
|
|
1446
|
+
* "use it if we have it" branch. A failing REFRESH still throws: silently
|
|
1447
|
+
* degrading a connected-but-broken account to "not connected" would hide a
|
|
1448
|
+
* revoked grant behind a feature quietly doing nothing. */
|
|
1449
|
+
tryGet(connectionId: string): Promise<ResolvedConnection | null>;
|
|
1450
|
+
/** Every declared connection with the caller's status — the same projection
|
|
1451
|
+
* `__voltro.connections.list` serves, for server-side rendering. */
|
|
1452
|
+
list(): Promise<ReadonlyArray<ConnectionState>>;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
export declare interface ConnectionsFacadeDeps {
|
|
1456
|
+
readonly store: DataStore;
|
|
1457
|
+
readonly registry: ConnectionRegistry;
|
|
1458
|
+
readonly subject: Subject;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1239
1461
|
/** Test-only — number of streams currently registered for a client. */
|
|
1240
1462
|
export declare const _connectionStreamCount: (clientId: number) => number;
|
|
1241
1463
|
|
|
@@ -1246,6 +1468,14 @@ export declare const connectionSubjectsSnapshot: () => ReadonlyArray<{
|
|
|
1246
1468
|
tenantId: string | null;
|
|
1247
1469
|
}>;
|
|
1248
1470
|
|
|
1471
|
+
/** A resolved token set. `expiresAt` is an absolute epoch-ms deadline. */
|
|
1472
|
+
export declare interface ConnectionTokens {
|
|
1473
|
+
readonly accessToken: string;
|
|
1474
|
+
readonly refreshToken?: string;
|
|
1475
|
+
readonly expiresAt?: number;
|
|
1476
|
+
readonly scopes: ReadonlyArray<string>;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1249
1479
|
export declare interface CoordinatedScheduleDeps {
|
|
1250
1480
|
/** The exactly-once gate. `singleCoordinator` for one-process
|
|
1251
1481
|
* deployments; `makeAdvisoryLockCoordinator(store, replicaId)` for
|
|
@@ -1295,6 +1525,21 @@ export declare const currentRoutingContext: () => RoutingContext | undefined;
|
|
|
1295
1525
|
/** The trace context active on the current async stack, if any. */
|
|
1296
1526
|
export declare const currentTraceContext: () => LogTraceContext | undefined;
|
|
1297
1527
|
|
|
1528
|
+
export declare interface DataLoader {
|
|
1529
|
+
/**
|
|
1530
|
+
* Load one row by primary key. Calls made in the same tick for the same
|
|
1531
|
+
* table are coalesced into ONE `WHERE id IN (...)`.
|
|
1532
|
+
*
|
|
1533
|
+
* Returns `null` for a missing row rather than throwing — a loader is used
|
|
1534
|
+
* to assemble, and a missing edge in a graph walk is usually data, not an
|
|
1535
|
+
* error. Use `.one()` when absence IS an error.
|
|
1536
|
+
*/
|
|
1537
|
+
load(table: string, id: string): Promise<Row | null>;
|
|
1538
|
+
/** Load many by key, in the order asked. Missing rows come back as `null`,
|
|
1539
|
+
* so the result lines up positionally with the input. */
|
|
1540
|
+
loadMany(table: string, ids: ReadonlyArray<string>): Promise<ReadonlyArray<Row | null>>;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1298
1543
|
export { DataStore }
|
|
1299
1544
|
|
|
1300
1545
|
/** DataStore-backed store over `_voltro_api_keys`. */
|
|
@@ -1316,6 +1561,8 @@ export declare const decryptField: (value: string) => string;
|
|
|
1316
1561
|
* small enough to stop a pathological body being buffered into memory. */
|
|
1317
1562
|
export declare const DEFAULT_MAX_RPC_BODY_BYTES: number;
|
|
1318
1563
|
|
|
1564
|
+
export declare const DEFAULT_REFRESH_SKEW_MS = 60000;
|
|
1565
|
+
|
|
1319
1566
|
/**
|
|
1320
1567
|
* Construct an aggregate definition. The returned object brands itself
|
|
1321
1568
|
* so the cli's file-discovery pass picks it up from default exports.
|
|
@@ -1344,8 +1591,28 @@ export declare const DEFAULT_MAX_RPC_BODY_BYTES: number;
|
|
|
1344
1591
|
*/
|
|
1345
1592
|
export declare const defineAggregate: <Row>(input: AggregateDefinitionInput<Row>) => AggregateDefinition<Row>;
|
|
1346
1593
|
|
|
1594
|
+
/**
|
|
1595
|
+
* Declare a connection. One per `*.connection.ts` file, default-exported.
|
|
1596
|
+
*
|
|
1597
|
+
* This is a pure declaration — no IO, no registration side effect. The CLI's
|
|
1598
|
+
* boot discovery imports the file, reads the default export, and registers it;
|
|
1599
|
+
* that keeps the declaration testable in isolation and keeps a stray import of
|
|
1600
|
+
* a connection file from mutating global state.
|
|
1601
|
+
*/
|
|
1602
|
+
export declare const defineConnection: <D extends ConnectionDefinition>(definition: D) => D;
|
|
1603
|
+
|
|
1347
1604
|
export declare const defineEventTrigger: <EventPayload = unknown, WorkflowPayload = EventPayload>(spec: Omit<WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>, "_tag">) => WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>;
|
|
1348
1605
|
|
|
1606
|
+
/**
|
|
1607
|
+
* Declare who delivers an effect. One per `*.outbox.ts` file.
|
|
1608
|
+
*
|
|
1609
|
+
* The handler runs AFTER the enqueuing transaction committed, outside it, and
|
|
1610
|
+
* may do external I/O — that is the entire point. It must be IDEMPOTENT:
|
|
1611
|
+
* delivery is at-least-once, so a process that dies between "the remote
|
|
1612
|
+
* accepted it" and "we marked it delivered" will retry.
|
|
1613
|
+
*/
|
|
1614
|
+
export declare const defineOutboxHandler: (definition: OutboxHandlerDefinition) => OutboxHandlerDefinition;
|
|
1615
|
+
|
|
1349
1616
|
/** Declare a reaction. Validates that the MANDATORY guard is present — an
|
|
1350
1617
|
* ungated reaction is a spend-storm footgun, so this fails LOUD at boot. */
|
|
1351
1618
|
export declare const defineReaction: (def: ReactionDefinition) => ReactionDefinition;
|
|
@@ -1435,7 +1702,7 @@ export declare class Dispatcher {
|
|
|
1435
1702
|
* Returns an `unsubscribe` function the caller MUST invoke when the
|
|
1436
1703
|
* subscriber goes away (client disconnect, stream finalization, etc.).
|
|
1437
1704
|
*/
|
|
1438
|
-
subscribe(descriptor: QueryDescriptor, emit: (event: SubscriptionEvent<ReadonlyArray<
|
|
1705
|
+
subscribe(descriptor: QueryDescriptor, emit: (event: SubscriptionEvent<ReadonlyArray<Row>>) => void, context: RuntimeContext,
|
|
1439
1706
|
/**
|
|
1440
1707
|
* Optional matcher-side index hint. When set, the registry hashes
|
|
1441
1708
|
* it into the fingerprint AND the matcher uses it for tuple
|
|
@@ -1451,7 +1718,28 @@ export declare class Dispatcher {
|
|
|
1451
1718
|
/** Snapshot-cache binding (Layer 3). When present AND `deps.cache` is
|
|
1452
1719
|
* wired, the initial snapshot is served through the cache, tagged with
|
|
1453
1720
|
* the dependent-table set, and kept warm by the recompute path. */
|
|
1454
|
-
cacheBinding?: SnapshotCacheBinding
|
|
1721
|
+
cacheBinding?: SnapshotCacheBinding,
|
|
1722
|
+
/** Re-run the query's `guards:` before every delivery — see
|
|
1723
|
+
* `ActiveSubscription.reauthorize`. Omitted for unguarded queries. */
|
|
1724
|
+
reauthorize?: () => Promise<unknown>,
|
|
1725
|
+
/** Re-resolve row visibility per delivery — see `ActiveSubscription.refilter`. */
|
|
1726
|
+
refilter?: () => Promise<RowFilterScope>): Promise<() => void>;
|
|
1727
|
+
/**
|
|
1728
|
+
* Tear a subscription down because authorization was WITHDRAWN mid-stream,
|
|
1729
|
+
* and tell the client why.
|
|
1730
|
+
*
|
|
1731
|
+
* A denial is not a transient failure, so it must not be handled like one.
|
|
1732
|
+
* The re-query paths deliberately keep a failing subscriber on its last
|
|
1733
|
+
* good snapshot (a bad predicate shouldn't wedge the stream) — but doing
|
|
1734
|
+
* that for a revoked subject would leave authorized data sitting in a
|
|
1735
|
+
* client that is no longer entitled to it, with no signal that anything
|
|
1736
|
+
* changed. Emitting the typed `ScopeError` and closing is the honest
|
|
1737
|
+
* outcome: the client surfaces the denial and can re-subscribe if the
|
|
1738
|
+
* grant comes back.
|
|
1739
|
+
*/
|
|
1740
|
+
private revokeSubscription;
|
|
1741
|
+
/** `revokeSubscription` for the computed path. Same reasoning. */
|
|
1742
|
+
private revokeComputed;
|
|
1455
1743
|
/**
|
|
1456
1744
|
* Register a COMPUTED-query subscription. The handler already ran once
|
|
1457
1745
|
* (its value is `computed.value`); we emit that as the initial snapshot,
|
|
@@ -1496,6 +1784,33 @@ export declare interface DispatcherDependencies {
|
|
|
1496
1784
|
readonly cache?: SnapshotCache;
|
|
1497
1785
|
}
|
|
1498
1786
|
|
|
1787
|
+
export declare interface DrainDeps {
|
|
1788
|
+
readonly store: Pick<DataStore, 'query' | 'update' | 'insert' | 'delete'>;
|
|
1789
|
+
readonly handlers: ReadonlyMap<string, OutboxHandlerDefinition>;
|
|
1790
|
+
readonly now?: () => Date;
|
|
1791
|
+
/** Max rows per drain pass. */
|
|
1792
|
+
readonly batchSize?: number;
|
|
1793
|
+
/** Per-entry cap on retained attempt rows. See {@link OUTBOX_ATTEMPT_LOG_CAP}. */
|
|
1794
|
+
readonly attemptLogCap?: number;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* One drain pass: claim due rows, run their handler, record the outcome.
|
|
1799
|
+
*
|
|
1800
|
+
* A row whose handler is unknown is left PENDING rather than dead-lettered —
|
|
1801
|
+
* the usual cause is a deploy where the enqueuing code shipped before the
|
|
1802
|
+
* handler, and discarding those would turn a rollout ordering detail into
|
|
1803
|
+
* permanent data loss.
|
|
1804
|
+
*/
|
|
1805
|
+
export declare const drainOutbox: (deps: DrainDeps) => Promise<DrainResult>;
|
|
1806
|
+
|
|
1807
|
+
export declare interface DrainResult {
|
|
1808
|
+
readonly delivered: number;
|
|
1809
|
+
readonly failed: number;
|
|
1810
|
+
readonly dead: number;
|
|
1811
|
+
readonly skipped: number;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1499
1814
|
/**
|
|
1500
1815
|
* Up to `limit` wakeups that are due now (`wakeAt <= now`), earliest
|
|
1501
1816
|
* first. Pending rows are read ordered by `wakeAt`, so the earliest —
|
|
@@ -1514,9 +1829,13 @@ export declare class EffectStore extends EffectStore_base {
|
|
|
1514
1829
|
declare const EffectStore_base: Context.TagClass<EffectStore, "@voltro/EffectStore", EffectStoreOps>;
|
|
1515
1830
|
|
|
1516
1831
|
export declare interface EffectStoreOps {
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1832
|
+
/** Typed the same way `FluentStore.query` is — the row type rides in on the
|
|
1833
|
+
* descriptor, so an Effect-form handler reads real fields instead of
|
|
1834
|
+
* casting off `Record<string, unknown>`. Falls back to `Row` for a
|
|
1835
|
+
* hand-built descriptor. */
|
|
1836
|
+
readonly query: <R = Row>(descriptor: QueryDescriptor<R>) => Effect.Effect<ReadonlyArray<R>, StoreError>;
|
|
1837
|
+
readonly insert: (table: string, row: Row) => Effect.Effect<Row, StoreError>;
|
|
1838
|
+
readonly update: (table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>) => Effect.Effect<Row | null, StoreError>;
|
|
1520
1839
|
readonly delete: (table: string, primaryKey: string) => Effect.Effect<boolean, StoreError>;
|
|
1521
1840
|
/** Bypass soft-delete. See `MutationStore.hardDelete`. */
|
|
1522
1841
|
readonly hardDelete: (table: string, primaryKey: string) => Effect.Effect<boolean, StoreError>;
|
|
@@ -1530,6 +1849,15 @@ export declare const emptySchemaRegistry: SchemaRegistry;
|
|
|
1530
1849
|
* columns use). Throws if no cipher is registered. */
|
|
1531
1850
|
export declare const encryptField: (plaintext: string) => string;
|
|
1532
1851
|
|
|
1852
|
+
export declare interface EnqueueOptions {
|
|
1853
|
+
/** Drop this enqueue if an undelivered row already carries the same key. */
|
|
1854
|
+
readonly idempotencyKey?: string;
|
|
1855
|
+
/** Override the handler's `maxAttempts` for this one effect. */
|
|
1856
|
+
readonly maxAttempts?: number;
|
|
1857
|
+
/** Delay the first attempt (ms from now). */
|
|
1858
|
+
readonly delayMs?: number;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1533
1861
|
/** The always-available default — reads `process.env`. Sync under the hood,
|
|
1534
1862
|
* Promise-wrapped to satisfy the async contract. */
|
|
1535
1863
|
export declare const envSecretsBackend: SecretsBackend;
|
|
@@ -1553,6 +1881,13 @@ export declare interface EventsFacadeOptions {
|
|
|
1553
1881
|
readonly makeId?: (prefix: 'wfe' | 'wfed') => string;
|
|
1554
1882
|
}
|
|
1555
1883
|
|
|
1884
|
+
/** Exchange an authorization code (from the callback) for a token set. */
|
|
1885
|
+
export declare const exchangeAuthorizationCode: (definition: OAuth2ConnectionDefinition, input: {
|
|
1886
|
+
readonly code: string;
|
|
1887
|
+
readonly redirectUri: string;
|
|
1888
|
+
readonly codeVerifier?: string | null;
|
|
1889
|
+
}, now?: () => number) => Promise<ConnectionTokens>;
|
|
1890
|
+
|
|
1556
1891
|
/** Build an Effect `ExternalSpan` parent from a `traceparent` header so
|
|
1557
1892
|
* inbound HTTP work continues the caller's trace. Returns undefined
|
|
1558
1893
|
* when the header is missing/invalid (no parent → a fresh root span). */
|
|
@@ -1575,7 +1910,51 @@ export declare interface FieldChange {
|
|
|
1575
1910
|
*/
|
|
1576
1911
|
export declare const fingerprintFor: (table: string, predicate: Predicate, indexHint?: string) => string;
|
|
1577
1912
|
|
|
1578
|
-
export declare interface FluentStore extends Omit<MutationStore, 'update' | 'delete'> {
|
|
1913
|
+
export declare interface FluentStore extends Omit<MutationStore, 'update' | 'delete' | 'query'> {
|
|
1914
|
+
/**
|
|
1915
|
+
* Execute a query descriptor and return the matching rows — TYPED.
|
|
1916
|
+
*
|
|
1917
|
+
* The row type rides in on the descriptor (`QueryDescriptor<R>`), so
|
|
1918
|
+
* `ctx.store.query(database.notes.where(...).descriptor)` gives you
|
|
1919
|
+
* `ReadonlyArray<Note>`, not `ReadonlyArray<Record<string, unknown>>`. The
|
|
1920
|
+
* builder always knew the shape; it used to be dropped exactly here, which
|
|
1921
|
+
* is why reading a field meant writing `row['title'] as string` — one
|
|
1922
|
+
* downstream app accumulated 2,032 of those casts.
|
|
1923
|
+
*
|
|
1924
|
+
* A hand-built descriptor still resolves to `Row`, i.e. the previous
|
|
1925
|
+
* behaviour. This never types LESS than before.
|
|
1926
|
+
*
|
|
1927
|
+
* The driver-level `DataStore.query` stays untyped on purpose — it is the
|
|
1928
|
+
* SPI every dialect store implements, and it genuinely does return untyped
|
|
1929
|
+
* rows off the wire. The type is re-applied here, at the handler boundary.
|
|
1930
|
+
*/
|
|
1931
|
+
query<R = Row_5>(descriptor: QueryDescriptor<R>): Promise<ReadonlyArray<R>>;
|
|
1932
|
+
/**
|
|
1933
|
+
* Terminal: EXACTLY one row, TYPED. Fails with `NoRowFound` on zero matches
|
|
1934
|
+
* and equally on two or more.
|
|
1935
|
+
*
|
|
1936
|
+
* This exists because the two things we recommend did not compose. The typed
|
|
1937
|
+
* read path lives on the typed builder (`database.users.where(...)`), while
|
|
1938
|
+
* `.one()` lived only on the string-keyed `select('users')` builder, which
|
|
1939
|
+
* yields untyped `Row`. So adopting `.one()` meant re-introducing the very
|
|
1940
|
+
* casts the typed path removes — you could have typed rows OR the terminal,
|
|
1941
|
+
* not both. That is not a "last mile" gap for sophisticated apps; it is two
|
|
1942
|
+
* features from the same release failing to meet.
|
|
1943
|
+
*
|
|
1944
|
+
* Takes the builder itself or its `.descriptor` — both read fine:
|
|
1945
|
+
*
|
|
1946
|
+
* const user = await ctx.store.one(database.users.where(eq('id', id)))
|
|
1947
|
+
*
|
|
1948
|
+
* Scoping is identical to `query()` (tenant + soft-delete apply), because it
|
|
1949
|
+
* IS `query()` underneath — the probe just asks for one row more than it
|
|
1950
|
+
* needs so "the first of several" cannot masquerade as "the one you meant".
|
|
1951
|
+
*/
|
|
1952
|
+
one<R = Row_5>(query: QueryLike<R>): Promise<R>;
|
|
1953
|
+
/** Terminal: the first matching row or `null`, TYPED. Use when "any match" is
|
|
1954
|
+
* genuinely what you mean. */
|
|
1955
|
+
first<R = Row_5>(query: QueryLike<R>): Promise<R | null>;
|
|
1956
|
+
/** Alias of `first` — the first matching row or `null`, TYPED. */
|
|
1957
|
+
maybeOne<R = Row_5>(query: QueryLike<R>): Promise<R | null>;
|
|
1579
1958
|
/** Fluent, scope-applying read builder: `select('notes').where(...).all()`. */
|
|
1580
1959
|
select(table: string): SelectBuilder;
|
|
1581
1960
|
/** Fluent predicate update: `update('notes').where('id', id).set({...})`. */
|
|
@@ -1606,6 +1985,9 @@ export declare interface FluentStoreBackend {
|
|
|
1606
1985
|
readonly delete: DataStore['delete'];
|
|
1607
1986
|
}
|
|
1608
1987
|
|
|
1988
|
+
/** Forget the calling subject's credential. Returns whether a row was removed. */
|
|
1989
|
+
export declare const forgetCredential: (store: VaultStore, connectionId: string, subjectId: string) => Promise<boolean>;
|
|
1990
|
+
|
|
1609
1991
|
/** Serialise a span's context as a `traceparent` header value. */
|
|
1610
1992
|
export declare const formatTraceparent: (ctx: TraceContext) => string;
|
|
1611
1993
|
|
|
@@ -1643,6 +2025,9 @@ export declare const gauge: (name: string, description?: string) => Metric.Metri
|
|
|
1643
2025
|
/** Generate a fresh token `<prefix><40-char-base64url>`. */
|
|
1644
2026
|
export declare const generateApiKeyToken: (prefix?: string) => string;
|
|
1645
2027
|
|
|
2028
|
+
/** The registered resolver, if the app declared any connections. */
|
|
2029
|
+
export declare const getConnectionResolver: () => ConnectionResolver | undefined;
|
|
2030
|
+
|
|
1646
2031
|
/** Look up the override for a connection. Returns `undefined` if no override is set. */
|
|
1647
2032
|
export declare const getConnectionSubject: (clientId: number) => Subject | undefined;
|
|
1648
2033
|
|
|
@@ -1651,12 +2036,22 @@ export declare const getFieldCipher: () => FieldCipher | undefined;
|
|
|
1651
2036
|
|
|
1652
2037
|
export declare const getResourcePolicy: (resourceType: string) => ResourcePolicy | undefined;
|
|
1653
2038
|
|
|
2039
|
+
/** The registered row filter, or `undefined`. */
|
|
2040
|
+
export declare const getRowFilter: () => RowFilter<never> | undefined;
|
|
2041
|
+
|
|
1654
2042
|
/** The per-process recorder (created lazily from `VOLTRO_TIMELINE`). The change
|
|
1655
2043
|
* tap feeds it; the inspect endpoint reads it. */
|
|
1656
2044
|
export declare const getTimelineRecorder: () => TimelineRecorder;
|
|
1657
2045
|
|
|
2046
|
+
export declare const getTupleSource: () => TupleSource | undefined;
|
|
2047
|
+
|
|
2048
|
+
/** How long a started handshake stays claimable. Long enough for a slow
|
|
2049
|
+
* consent screen (and a password-manager detour), short enough that a stolen
|
|
2050
|
+
* state value is worthless by the time it is found. */
|
|
2051
|
+
export declare const GRANT_TTL_MS: number;
|
|
2052
|
+
|
|
1658
2053
|
/** Stable group key from the groupBy column values (JSON to disambiguate types). */
|
|
1659
|
-
export declare const groupKeyOf: (row:
|
|
2054
|
+
export declare const groupKeyOf: (row: Row_3, groupBy: ReadonlyArray<string> | undefined) => string;
|
|
1660
2055
|
|
|
1661
2056
|
/** Per-group accumulator. `count` is always kept (to know when a group empties
|
|
1662
2057
|
* and to compute avg); `sum` for sum/avg; `extreme` for min/max. */
|
|
@@ -1756,7 +2151,7 @@ export declare class IndexedMatcher implements Matcher {
|
|
|
1756
2151
|
|
|
1757
2152
|
export declare class InMemoryDataStore implements DataStore {
|
|
1758
2153
|
#private;
|
|
1759
|
-
constructor(initial?: Record<string, ReadonlyArray<
|
|
2154
|
+
constructor(initial?: Record<string, ReadonlyArray<Row>>, namespace?: string | null);
|
|
1760
2155
|
/**
|
|
1761
2156
|
* Return a namespace-bound view. The view shares THIS store's table
|
|
1762
2157
|
* map, emitter, and id counters (one process, one in-memory database)
|
|
@@ -1765,11 +2160,11 @@ export declare class InMemoryDataStore implements DataStore {
|
|
|
1765
2160
|
* table that never carried the `tenant()` mixin.
|
|
1766
2161
|
*/
|
|
1767
2162
|
withNamespace(namespace: string | null): DataStore;
|
|
1768
|
-
query(descriptor: QueryDescriptor): Promise<ReadonlyArray<
|
|
1769
|
-
insert(table: string, row:
|
|
1770
|
-
insertMany(table: string, rows: ReadonlyArray<
|
|
1771
|
-
patchJson(table: string, primaryKey: string, path: string, value: unknown): Promise<
|
|
1772
|
-
update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<
|
|
2163
|
+
query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
|
|
2164
|
+
insert(table: string, row: Row): Promise<Row>;
|
|
2165
|
+
insertMany(table: string, rows: ReadonlyArray<Row>): Promise<ReadonlyArray<Row>>;
|
|
2166
|
+
patchJson(table: string, primaryKey: string, path: string, value: unknown): Promise<Row | null>;
|
|
2167
|
+
update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<Row | null>;
|
|
1773
2168
|
delete(table: string, primaryKey: string): Promise<boolean>;
|
|
1774
2169
|
updateMany(table: string, patch: Readonly<Record<string, unknown>>, options: {
|
|
1775
2170
|
where: Predicate;
|
|
@@ -1777,13 +2172,13 @@ export declare class InMemoryDataStore implements DataStore {
|
|
|
1777
2172
|
deleteMany(table: string, options: {
|
|
1778
2173
|
where: Predicate;
|
|
1779
2174
|
}): Promise<number>;
|
|
1780
|
-
upsert(table: string, row:
|
|
2175
|
+
upsert(table: string, row: Row, options: {
|
|
1781
2176
|
conflictColumns: ReadonlyArray<string>;
|
|
1782
|
-
update?: ReadonlyArray<string> | ((existing:
|
|
1783
|
-
}): Promise<
|
|
1784
|
-
insertIgnore(table: string, row:
|
|
2177
|
+
update?: ReadonlyArray<string> | ((existing: Row) => Readonly<Record<string, unknown>>);
|
|
2178
|
+
}): Promise<Row>;
|
|
2179
|
+
insertIgnore(table: string, row: Row, options: {
|
|
1785
2180
|
conflictColumns: ReadonlyArray<string>;
|
|
1786
|
-
}): Promise<
|
|
2181
|
+
}): Promise<Row>;
|
|
1787
2182
|
transactional<T>(work: (tx: DataStore) => Promise<T>): Promise<T>;
|
|
1788
2183
|
onChange(listener: (event: ChangeEvent) => void): () => void;
|
|
1789
2184
|
/** Cross-instance reactivity seam — emit an externally-sourced event to
|
|
@@ -1858,6 +2253,25 @@ export declare interface InspectStream {
|
|
|
1858
2253
|
|
|
1859
2254
|
export { inspectWorkflow }
|
|
1860
2255
|
|
|
2256
|
+
/**
|
|
2257
|
+
* Install the resolver that answers `guards: [{ action, resourceType }]`.
|
|
2258
|
+
*
|
|
2259
|
+
* Called once at boot by both entrypoints. Every denial path is explicit
|
|
2260
|
+
* because each one is a place where a plausible implementation would instead
|
|
2261
|
+
* pass:
|
|
2262
|
+
*
|
|
2263
|
+
* - unknown resource type → DENY. A guard naming a policy that was never
|
|
2264
|
+
* registered is a misconfiguration, and a misconfigured authorization
|
|
2265
|
+
* check must not be a permissive one.
|
|
2266
|
+
* - no tuple source → DENY. Nothing can answer the question.
|
|
2267
|
+
* - tuple source throws → DENY, and log it. A database blip must not become
|
|
2268
|
+
* an open door.
|
|
2269
|
+
*
|
|
2270
|
+
* `can()` itself already handles the `admin:full` bypass, anonymous denial and
|
|
2271
|
+
* cross-tenant denial, so this layer does not re-implement them.
|
|
2272
|
+
*/
|
|
2273
|
+
export declare const installPolicyGuardResolver: () => void;
|
|
2274
|
+
|
|
1861
2275
|
/** Fire every registered interrupt for `clientId` (called on disconnect). */
|
|
1862
2276
|
export declare const interruptConnectionStreams: (clientId: number) => void;
|
|
1863
2277
|
|
|
@@ -1989,10 +2403,20 @@ export declare class LinearScanMatcher implements Matcher {
|
|
|
1989
2403
|
stats(): MatcherStats;
|
|
1990
2404
|
}
|
|
1991
2405
|
|
|
2406
|
+
/** Every connection the app declares, projected for ONE subject. */
|
|
2407
|
+
export declare const listConnectionStates: (store: VaultStore, registry: ConnectionRegistry, subjectId: string) => Promise<ReadonlyArray<ConnectionState>>;
|
|
2408
|
+
|
|
1992
2409
|
export declare const listResourcePolicies: () => ReadonlyArray<ResourcePolicy>;
|
|
1993
2410
|
|
|
1994
2411
|
export { listRetentions }
|
|
1995
2412
|
|
|
2413
|
+
export declare interface LoaderDeps {
|
|
2414
|
+
readonly store: Pick<DataStore, 'query'>;
|
|
2415
|
+
/** Coalesce window. Loads issued within the same microtask batch together;
|
|
2416
|
+
* the default (a resolved promise tick) needs no timers. */
|
|
2417
|
+
readonly schedule?: (flush: () => void) => void;
|
|
2418
|
+
}
|
|
2419
|
+
|
|
1996
2420
|
/** Load the tuples for one (subject, resource) — the interceptor's tuple source.
|
|
1997
2421
|
* Returns [] for an anonymous subject (→ `can` denies anyway). */
|
|
1998
2422
|
export declare const loadResourceTuples: (store: DataStore, subjectId: string | null, resourceType: string, resourceId: string) => Promise<ReadonlyArray<RelationTuple>>;
|
|
@@ -2114,6 +2538,20 @@ export declare const makeAsyncKv: ({ store, env }: KvFacadeOptions) => Promise<K
|
|
|
2114
2538
|
*/
|
|
2115
2539
|
export declare const makeBufferingSpanProcessor: (onSpanEnd: (record: TraceSpanRecordLike) => void) => SpanProcessor;
|
|
2116
2540
|
|
|
2541
|
+
/** Build a registry from the discovered declarations. Duplicate ids throw —
|
|
2542
|
+
* two files claiming one id means whichever loaded last silently wins and the
|
|
2543
|
+
* other file looks live but is dead. */
|
|
2544
|
+
export declare const makeConnectionRegistry: (definitions: ReadonlyArray<ConnectionDefinition>) => ConnectionRegistry;
|
|
2545
|
+
|
|
2546
|
+
/** Build the process resolver over a store + registry. The CLI hands the
|
|
2547
|
+
* result to `setConnectionResolver`. */
|
|
2548
|
+
export declare const makeConnectionResolver: (deps: {
|
|
2549
|
+
readonly store: DataStore;
|
|
2550
|
+
readonly registry: ConnectionRegistry;
|
|
2551
|
+
}) => ConnectionResolver;
|
|
2552
|
+
|
|
2553
|
+
export declare const makeConnectionsFacade: (deps: ConnectionsFacadeDeps) => ConnectionsFacade;
|
|
2554
|
+
|
|
2117
2555
|
/**
|
|
2118
2556
|
* Factory the CLI binds into the plugin bind-ctx: closes over the
|
|
2119
2557
|
* process-wide coordinator + replicaId so a plugin calls the ergonomic
|
|
@@ -2123,6 +2561,19 @@ export declare const makeBufferingSpanProcessor: (onSpanEnd: (record: TraceSpanR
|
|
|
2123
2561
|
*/
|
|
2124
2562
|
export declare const makeCoordinatedScheduler: (deps: CoordinatedScheduleDeps) => ((name: string, intervalMs: number, effect: () => void | Promise<void>) => CoordinatedScheduleHandle);
|
|
2125
2563
|
|
|
2564
|
+
/**
|
|
2565
|
+
* Build a request-scoped loader. One instance per AppContext — see the module
|
|
2566
|
+
* header for why this must not be shared across requests.
|
|
2567
|
+
*/
|
|
2568
|
+
export declare const makeDataLoader: (deps: LoaderDeps) => DataLoader;
|
|
2569
|
+
|
|
2570
|
+
/** `__voltro.connections.disconnect` — forget the caller's credential. */
|
|
2571
|
+
export declare const makeDisconnectConnectionExecutor: (deps: ConnectionBuiltinDeps) => (input: {
|
|
2572
|
+
readonly connectionId: string;
|
|
2573
|
+
}, ctx: ConnectionExecutorCtx) => Promise<{
|
|
2574
|
+
readonly ok: boolean;
|
|
2575
|
+
}>;
|
|
2576
|
+
|
|
2126
2577
|
/**
|
|
2127
2578
|
* Build the Layer that provides `EffectStore` from a `MutationStore`.
|
|
2128
2579
|
* dev.ts (CLI) and any standalone test setup uses this to wire up the
|
|
@@ -2153,6 +2604,9 @@ export declare const makeIdleGate: (graceMs: number, now?: () => number) => Idle
|
|
|
2153
2604
|
|
|
2154
2605
|
export declare const makeLazyWorkflowFacade: (resolve: () => WorkflowsAppContext | undefined) => WorkflowsAppContext;
|
|
2155
2606
|
|
|
2607
|
+
/** `__voltro.connections.list` — every declared connection, for the caller. */
|
|
2608
|
+
export declare const makeListConnectionsExecutor: (deps: ConnectionBuiltinDeps) => (_input: Record<string, never>, ctx: ConnectionExecutorCtx) => Promise<ReadonlyArray<ConnectionState>>;
|
|
2609
|
+
|
|
2156
2610
|
/**
|
|
2157
2611
|
* Build the shared mutation runner. Used by BOTH the rpc WS handler and
|
|
2158
2612
|
* the `/_voltro/inspect/invoke` endpoint (and the prod entrypoint) so they
|
|
@@ -2168,6 +2622,8 @@ export declare const makeLazyWorkflowFacade: (resolve: () => WorkflowsAppContext
|
|
|
2168
2622
|
*/
|
|
2169
2623
|
export declare const makeMutationRunner: (deps: MutationRunnerDeps) => (mutation: MutationLike, input: unknown, requestContext: ServeRequestContext) => Promise<unknown>;
|
|
2170
2624
|
|
|
2625
|
+
export declare const makeOutboxFacade: (deps: OutboxFacadeDeps) => OutboxFacade;
|
|
2626
|
+
|
|
2171
2627
|
export declare const makePostCommitWorkflowFacade: (base: WorkflowsAppContext, afterCommit: (work: () => Promise<unknown>) => void) => WorkflowsAppContext;
|
|
2172
2628
|
|
|
2173
2629
|
/**
|
|
@@ -2188,6 +2644,27 @@ export declare const makeProcessAdapter: (supervisor: AppSupervisor) => WakeAdap
|
|
|
2188
2644
|
*/
|
|
2189
2645
|
export declare const makeQueryDescriptorProducer: <D>(deps: QueryProducerDeps<D>) => (query: MutationLike, input: unknown) => (requestContext: ServeRequestContext) => D | ComputedQuery | Effect.Effect<D | ComputedQuery, unknown, never>;
|
|
2190
2646
|
|
|
2647
|
+
/**
|
|
2648
|
+
* Build the per-subscription authorization re-check.
|
|
2649
|
+
*
|
|
2650
|
+
* `guards:` are enforced once when a subscription is opened. That is not
|
|
2651
|
+
* enough on its own: a subscription is a LONG-LIVED grant, and the scopes
|
|
2652
|
+
* that justified it can be withdrawn while it is still open (a role
|
|
2653
|
+
* revoked, a resource un-shared, a membership ended). Without a re-check
|
|
2654
|
+
* the socket keeps delivering rows the subject may no longer read until
|
|
2655
|
+
* the client happens to disconnect.
|
|
2656
|
+
*
|
|
2657
|
+
* So every delivery re-runs the same `checkGuardsEffect` the subscribe-time
|
|
2658
|
+
* gate ran — including the async resource-scope resolver, which is where a
|
|
2659
|
+
* per-row/per-team revocation actually shows up. Returns `null` when the
|
|
2660
|
+
* subject still passes, or the typed `ScopeError` that denied it.
|
|
2661
|
+
*
|
|
2662
|
+
* Returns a closure that always resolves `null` when the descriptor carries
|
|
2663
|
+
* no guards, so the caller needs no branch and an unguarded query pays only
|
|
2664
|
+
* a resolved promise.
|
|
2665
|
+
*/
|
|
2666
|
+
export declare const makeQueryReauthorizer: (query: MutationLike, input: unknown) => (subject: Subject) => () => Promise<unknown>;
|
|
2667
|
+
|
|
2191
2668
|
export declare const makeRouterActivity: () => RouterActivity;
|
|
2192
2669
|
|
|
2193
2670
|
/**
|
|
@@ -2196,6 +2673,23 @@ export declare const makeRouterActivity: () => RouterActivity;
|
|
|
2196
2673
|
*/
|
|
2197
2674
|
export declare const makeSchemaRegistry: (tables: ReadonlyArray<RegistryTableLike>) => SchemaRegistry;
|
|
2198
2675
|
|
|
2676
|
+
/** `__voltro.connections.start` — mint the consent URL for an oauth2 flow. */
|
|
2677
|
+
export declare const makeStartConnectionExecutor: (deps: ConnectionBuiltinDeps) => (input: {
|
|
2678
|
+
readonly connectionId: string;
|
|
2679
|
+
readonly redirectTo?: string;
|
|
2680
|
+
}, ctx: ConnectionExecutorCtx) => Promise<{
|
|
2681
|
+
readonly authorizeUrl: string;
|
|
2682
|
+
readonly state: string;
|
|
2683
|
+
}>;
|
|
2684
|
+
|
|
2685
|
+
/** `__voltro.connections.submitToken` — store a pasted personal access token. */
|
|
2686
|
+
export declare const makeSubmitTokenExecutor: (deps: ConnectionBuiltinDeps) => (input: {
|
|
2687
|
+
readonly connectionId: string;
|
|
2688
|
+
readonly token: string;
|
|
2689
|
+
}, ctx: ConnectionExecutorCtx) => Promise<{
|
|
2690
|
+
readonly ok: boolean;
|
|
2691
|
+
}>;
|
|
2692
|
+
|
|
2199
2693
|
/**
|
|
2200
2694
|
* The factory the serve entrypoints inject into `makeMutationRunner`'s
|
|
2201
2695
|
* `undoCapture` dep. Collects changes from the wrapped tx; `persist` writes ONE
|
|
@@ -2366,7 +2860,7 @@ export declare interface MutationLike {
|
|
|
2366
2860
|
readonly descriptor: {
|
|
2367
2861
|
readonly name: string;
|
|
2368
2862
|
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
2369
|
-
readonly guards?: ReadonlyArray<
|
|
2863
|
+
readonly guards?: ReadonlyArray<AnyCheckSpec> | undefined;
|
|
2370
2864
|
};
|
|
2371
2865
|
executor(input: unknown, ctx: unknown): unknown;
|
|
2372
2866
|
}
|
|
@@ -2427,6 +2921,10 @@ export declare const nextFiring: (def: ScheduleDefinition, after?: Date) => Date
|
|
|
2427
2921
|
*/
|
|
2428
2922
|
export declare const nextWakeup: (store: DataStore) => Promise<Wakeup | null>;
|
|
2429
2923
|
|
|
2924
|
+
/** A scope that constrains nothing — the shape used when no filter is
|
|
2925
|
+
* registered, or for a system subject. */
|
|
2926
|
+
export declare const NO_ROW_FILTER: RowFilterScope;
|
|
2927
|
+
|
|
2430
2928
|
/**
|
|
2431
2929
|
* The real single-node supervisor: `spawn`s the app command and considers
|
|
2432
2930
|
* it healthy once `healthUrl` returns 2xx. Stopping sends `stopSignal`
|
|
@@ -2495,14 +2993,25 @@ export declare const noopAnalyticsSpec: AnalyticsSinkSpec;
|
|
|
2495
2993
|
* misses, writes are dropped. Mirrors the cache's `noopCache`. */
|
|
2496
2994
|
export declare const noopKv: AsyncKv;
|
|
2497
2995
|
|
|
2498
|
-
/**
|
|
2499
|
-
*
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2996
|
+
/**
|
|
2997
|
+
* `.one()` matched a number of rows other than exactly one.
|
|
2998
|
+
*
|
|
2999
|
+
* `found` distinguishes the two failure modes without a second error type:
|
|
3000
|
+
* `0` is "the row you required is missing", `2` is "your filter is not as
|
|
3001
|
+
* unique as you assumed" (the terminal probes with LIMIT 2, so `2` means
|
|
3002
|
+
* "at least two" — it never counts the whole set just to report a number).
|
|
3003
|
+
*/
|
|
3004
|
+
export declare class NoRowFound extends NoRowFound_base {
|
|
3005
|
+
get message(): string;
|
|
2504
3006
|
}
|
|
2505
3007
|
|
|
3008
|
+
declare const NoRowFound_base: Schema.TaggedErrorClass<NoRowFound, "NoRowFound", {
|
|
3009
|
+
readonly _tag: Schema.tag<"NoRowFound">;
|
|
3010
|
+
} & {
|
|
3011
|
+
table: typeof Schema.String;
|
|
3012
|
+
found: typeof Schema.Number;
|
|
3013
|
+
}>;
|
|
3014
|
+
|
|
2506
3015
|
export declare interface Notification {
|
|
2507
3016
|
readonly subscriberId: string;
|
|
2508
3017
|
readonly fingerprint: string;
|
|
@@ -2510,6 +3019,32 @@ export declare interface Notification {
|
|
|
2510
3019
|
readonly newMatched: boolean;
|
|
2511
3020
|
}
|
|
2512
3021
|
|
|
3022
|
+
export declare interface OAuth2ConnectionDefinition extends ConnectionDefinitionCommon {
|
|
3023
|
+
readonly kind: 'oauth2';
|
|
3024
|
+
readonly authorizeUrl: string;
|
|
3025
|
+
readonly tokenUrl: string;
|
|
3026
|
+
readonly clientId: string;
|
|
3027
|
+
/** Runtime secret. Never logged, never sent to the browser, never bundled —
|
|
3028
|
+
* `*.connection.ts` is server-only and is NOT part of the rpcGroup codegen. */
|
|
3029
|
+
readonly clientSecret: string;
|
|
3030
|
+
readonly scopes?: ReadonlyArray<string>;
|
|
3031
|
+
/** Exact redirect URI registered with the provider. Defaults to
|
|
3032
|
+
* `<publicUrl>/_voltro/connections/<id>/callback`. */
|
|
3033
|
+
readonly redirectUri?: string;
|
|
3034
|
+
/** Extra fixed query params on the authorize URL (`audience`, `prompt`, …). */
|
|
3035
|
+
readonly authorizeParams?: Record<string, string>;
|
|
3036
|
+
/** PKCE (S256). On by default — it costs one hash and closes the
|
|
3037
|
+
* authorization-code interception hole for public clients. */
|
|
3038
|
+
readonly pkce?: boolean;
|
|
3039
|
+
/** Renew when the token is within this many ms of expiry. Default 60_000 —
|
|
3040
|
+
* enough headroom that an in-flight request can't race the deadline. */
|
|
3041
|
+
readonly refreshSkewMs?: number;
|
|
3042
|
+
/** Injectable transport (tests, proxies). Defaults to the global `fetch`. */
|
|
3043
|
+
readonly fetchImpl?: ConnectionFetch;
|
|
3044
|
+
/** Resolve who the freshly-minted token belongs to, for display. */
|
|
3045
|
+
readonly identify?: (tokens: ConnectionTokens) => Promise<ConnectionAccount>;
|
|
3046
|
+
}
|
|
3047
|
+
|
|
2513
3048
|
/**
|
|
2514
3049
|
* Listen for re-bind events. Returns an unsubscribe function. Used by
|
|
2515
3050
|
* the dispatcher to re-scope active subscriptions when a connection's
|
|
@@ -2520,13 +3055,17 @@ export declare const onBindConnectionSubject: (listener: (clientId: number, subj
|
|
|
2520
3055
|
|
|
2521
3056
|
/** Thrown by `.expectVersion(n).set(...)` when the optimistic-lock guard
|
|
2522
3057
|
* matched no row (the row was concurrently updated or deleted). */
|
|
2523
|
-
export declare class OptimisticLockError extends
|
|
2524
|
-
|
|
2525
|
-
readonly expected: number;
|
|
2526
|
-
readonly _tag = "OptimisticLockError";
|
|
2527
|
-
constructor(table: string, expected: number);
|
|
3058
|
+
export declare class OptimisticLockError extends OptimisticLockError_base {
|
|
3059
|
+
get message(): string;
|
|
2528
3060
|
}
|
|
2529
3061
|
|
|
3062
|
+
declare const OptimisticLockError_base: Schema.TaggedErrorClass<OptimisticLockError, "OptimisticLockError", {
|
|
3063
|
+
readonly _tag: Schema.tag<"OptimisticLockError">;
|
|
3064
|
+
} & {
|
|
3065
|
+
table: typeof Schema.String;
|
|
3066
|
+
expected: typeof Schema.Number;
|
|
3067
|
+
}>;
|
|
3068
|
+
|
|
2530
3069
|
export declare interface OrchestratorTickDeps {
|
|
2531
3070
|
readonly store: DataStore;
|
|
2532
3071
|
readonly supervisor: AppSupervisor;
|
|
@@ -2537,6 +3076,135 @@ export declare interface OrchestratorTickDeps {
|
|
|
2537
3076
|
readonly log?: WakeOrchestratorLogger;
|
|
2538
3077
|
}
|
|
2539
3078
|
|
|
3079
|
+
/**
|
|
3080
|
+
* Retention, part 1 of 2 — the PER-ENTRY cap.
|
|
3081
|
+
*
|
|
3082
|
+
* The automatic path is already bounded by `maxAttempts` (default 8), so the
|
|
3083
|
+
* only way one entry's history grows without limit is repeated manual resends
|
|
3084
|
+
* of the same row — a support workflow that can genuinely run for years. Once an
|
|
3085
|
+
* entry passes this many attempts, recording a new one trims the oldest, so the
|
|
3086
|
+
* log is a bounded ring per entry: the recent attempts are what a history UI
|
|
3087
|
+
* shows, and the 200th-oldest failure of a chronically broken effect is not
|
|
3088
|
+
* worth a row.
|
|
3089
|
+
*
|
|
3090
|
+
* The check is keyed off the attempt NUMBER, which the drain already knows, so
|
|
3091
|
+
* the normal (never-resent) path pays no extra query at all — it can never
|
|
3092
|
+
* reach the cap.
|
|
3093
|
+
*
|
|
3094
|
+
* Part 2 is the time-based bound: the CLI's boot sweep registers a retention
|
|
3095
|
+
* spec over `startedAt` (default 30 days, `VOLTRO_OUTBOX_ATTEMPTS_TTL_HOURS`).
|
|
3096
|
+
* Both exist because they bound different failure modes — the cap stops ONE hot
|
|
3097
|
+
* entry, the TTL stops the accumulation of MANY cold ones — and only the cap
|
|
3098
|
+
* works on every dialect (the sweep is postgres-gated).
|
|
3099
|
+
*/
|
|
3100
|
+
export declare const OUTBOX_ATTEMPT_LOG_CAP = 50;
|
|
3101
|
+
|
|
3102
|
+
export declare const OUTBOX_ATTEMPTS_TABLE = "_voltro_outbox_attempts";
|
|
3103
|
+
|
|
3104
|
+
export declare const OUTBOX_TABLE = "_voltro_outbox";
|
|
3105
|
+
|
|
3106
|
+
/** Terminal outcome of ONE attempt. `dead` is a failure that also exhausted the
|
|
3107
|
+
* budget — kept separate from `failed` so a listing can render "gave up here"
|
|
3108
|
+
* without cross-referencing the parent row's status. */
|
|
3109
|
+
export declare type OutboxAttemptOutcome = 'delivered' | 'failed' | 'dead';
|
|
3110
|
+
|
|
3111
|
+
/**
|
|
3112
|
+
* One row per delivery ATTEMPT — the queryable history behind `_voltro_outbox`.
|
|
3113
|
+
*
|
|
3114
|
+
* The outbox row alone answers "did this eventually land"; it cannot answer
|
|
3115
|
+
* "what did the remote say on attempt 3", "how long did it take", "who resent
|
|
3116
|
+
* it", which is what a delivery-history UI renders. Columns are chosen from
|
|
3117
|
+
* that render, not from what is convenient to write:
|
|
3118
|
+
*
|
|
3119
|
+
* - a listing row needs: when, which effect, attempt #, outcome, duration,
|
|
3120
|
+
* and (for a failure) the error — all here, no JOIN;
|
|
3121
|
+
* - `effect` / `subjectId` / `tenantId` are DENORMALISED so the history stays
|
|
3122
|
+
* readable (and tenant-scopable) after the parent outbox row is purged by
|
|
3123
|
+
* retention, and so a per-tenant listing needs no join;
|
|
3124
|
+
* - `response` carries the handler's own return value, which is where an
|
|
3125
|
+
* HTTP-shaped handler puts `{ status, body }` — the framework does not
|
|
3126
|
+
* model HTTP, so it snapshots whatever the handler chose to return rather
|
|
3127
|
+
* than inventing status/response columns only some handlers can fill.
|
|
3128
|
+
*
|
|
3129
|
+
* Append-only. Bounded two ways — see `recordAttempt` + the retention
|
|
3130
|
+
* registration in the CLI's boot sweep.
|
|
3131
|
+
*/
|
|
3132
|
+
export declare const outboxAttemptsTable: TableLike;
|
|
3133
|
+
|
|
3134
|
+
/** How an attempt came to be made. `automatic` = the drain picked the row up on
|
|
3135
|
+
* its own schedule; `manual` = a human/API asked for it via `resend()`. The
|
|
3136
|
+
* distinction is the point of the field: without it a support-triggered
|
|
3137
|
+
* redelivery is indistinguishable from a backoff retry, and "did we send this
|
|
3138
|
+
* twice because of us or because of them" becomes unanswerable. */
|
|
3139
|
+
export declare type OutboxAttemptTrigger = 'automatic' | 'manual';
|
|
3140
|
+
|
|
3141
|
+
export declare interface OutboxFacade {
|
|
3142
|
+
/**
|
|
3143
|
+
* Persist the intent to run `effect` after this transaction commits.
|
|
3144
|
+
*
|
|
3145
|
+
* Returns the outbox row id, which doubles as the delivery id a client can
|
|
3146
|
+
* watch to render external-side-effect progress ("saving… syncing… synced").
|
|
3147
|
+
*/
|
|
3148
|
+
enqueue(effect: string, payload: Record<string, unknown>, options?: EnqueueOptions): Promise<string>;
|
|
3149
|
+
/**
|
|
3150
|
+
* Re-arm one entry for delivery NOW, on purpose, attributed to the caller.
|
|
3151
|
+
*
|
|
3152
|
+
* Legitimate because the delivery contract is already at-least-once and
|
|
3153
|
+
* handlers must therefore be idempotent — a manual resend is the SAME hazard
|
|
3154
|
+
* the contract already requires handlers to absorb, not a new one. What it is
|
|
3155
|
+
* not allowed to be is invisible: the next attempt is recorded with
|
|
3156
|
+
* `trigger: 'manual'` plus the requesting subject and reason, so an operator
|
|
3157
|
+
* redelivery can never be mistaken for a backoff retry when someone later
|
|
3158
|
+
* asks why the remote saw the effect twice.
|
|
3159
|
+
*
|
|
3160
|
+
* Three deliberate choices:
|
|
3161
|
+
* - It RE-ARMS the existing row rather than enqueuing a new one. A copy
|
|
3162
|
+
* would carry the same `idempotencyKey` (breaking the dedupe invariant),
|
|
3163
|
+
* duplicate the payload, and split one entry's history across two ids.
|
|
3164
|
+
* - It grants a SMALL fresh budget (`attempts`, default 1) instead of
|
|
3165
|
+
* resetting the counter. A dead row has `attempts >= maxAttempts`, so
|
|
3166
|
+
* without this it would re-die untried; and "try again now" is what the
|
|
3167
|
+
* button means — not "restart the whole eight-attempt backoff schedule".
|
|
3168
|
+
* - It refuses a row that is mid-flight (`delivering`), where re-arming
|
|
3169
|
+
* races the in-progress attempt into a same-process double delivery this
|
|
3170
|
+
* layer CAN prevent (unlike the cross-replica one).
|
|
3171
|
+
*/
|
|
3172
|
+
resend(outboxId: string, options?: ResendOptions): Promise<void>;
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
export declare interface OutboxFacadeDeps {
|
|
3176
|
+
/** The REQUEST's store. Inside a mutation this is the transactional view —
|
|
3177
|
+
* which is what makes the enqueue atomic with the domain write. */
|
|
3178
|
+
readonly store: Pick<DataStore, 'insert' | 'query' | 'update'>;
|
|
3179
|
+
readonly subject: Subject;
|
|
3180
|
+
readonly traceId: string | null;
|
|
3181
|
+
/** Nudge the drain worker once the transaction commits. Optional: without
|
|
3182
|
+
* it the row still delivers on the next poll tick, just later. */
|
|
3183
|
+
readonly afterCommit?: (work: () => Promise<unknown>) => void;
|
|
3184
|
+
/** Wake the drain loop. */
|
|
3185
|
+
readonly nudge?: () => void;
|
|
3186
|
+
readonly now?: () => Date;
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3189
|
+
export declare interface OutboxHandlerContext {
|
|
3190
|
+
readonly payload: Record<string, unknown>;
|
|
3191
|
+
readonly attempt: number;
|
|
3192
|
+
readonly subjectId: string | null;
|
|
3193
|
+
readonly tenantId: string | null;
|
|
3194
|
+
readonly traceId: string | null;
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
export declare interface OutboxHandlerDefinition {
|
|
3198
|
+
readonly effect: string;
|
|
3199
|
+
readonly handler: (ctx: OutboxHandlerContext) => Promise<unknown>;
|
|
3200
|
+
/** Give up after this many attempts, then dead-letter. Default 8. */
|
|
3201
|
+
readonly maxAttempts?: number;
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
export declare type OutboxStatus = 'pending' | 'delivering' | 'delivered' | 'dead';
|
|
3205
|
+
|
|
3206
|
+
export declare const outboxTable: TableLike;
|
|
3207
|
+
|
|
2540
3208
|
declare interface P2COptions {
|
|
2541
3209
|
/**
|
|
2542
3210
|
* Random selector — `Math.random()` by default. Tests inject a
|
|
@@ -2550,6 +3218,18 @@ declare interface P2COptions {
|
|
|
2550
3218
|
* treated as "no parent"). */
|
|
2551
3219
|
export declare const parseTraceparent: (header: string | undefined | null) => TraceContext | null;
|
|
2552
3220
|
|
|
3221
|
+
export declare interface PatConnectionDefinition extends ConnectionDefinitionCommon {
|
|
3222
|
+
readonly kind: 'pat';
|
|
3223
|
+
/** Where to send the user to mint a token — rendered as a help link. */
|
|
3224
|
+
readonly instructionsUrl?: string;
|
|
3225
|
+
/**
|
|
3226
|
+
* Verify a pasted token before storing it. Rejecting here is what turns
|
|
3227
|
+
* "the user typed it wrong" into an immediate error instead of a mysterious
|
|
3228
|
+
* 401 from a background job three hours later. Throwing fails the submit.
|
|
3229
|
+
*/
|
|
3230
|
+
readonly validate?: (token: string) => Promise<ConnectionAccount>;
|
|
3231
|
+
}
|
|
3232
|
+
|
|
2553
3233
|
/** The 3-arg signature a plugin sees on its bind-ctx. */
|
|
2554
3234
|
export declare type PluginScheduleCoordinated = (name: string, intervalMs: number, effect: () => void | Promise<void>) => CoordinatedScheduleHandle;
|
|
2555
3235
|
|
|
@@ -2577,6 +3257,11 @@ export declare interface PreviewStore {
|
|
|
2577
3257
|
transactional: <T>(fn: (tx: unknown) => Promise<T>) => Promise<T>;
|
|
2578
3258
|
}
|
|
2579
3259
|
|
|
3260
|
+
/** Project a stored row into the wire shape. Deliberately total over the
|
|
3261
|
+
* columns the descriptor declares — and deliberately silent about the token
|
|
3262
|
+
* columns, which have no wire representation at all. */
|
|
3263
|
+
export declare const projectConnectionState: (definition: ConnectionDefinition, row: AnyRow | undefined) => ConnectionState;
|
|
3264
|
+
|
|
2580
3265
|
/** Exact-match property filter. Values are AND'd. */
|
|
2581
3266
|
export declare type PropertyFilter = Readonly<Record<string, string | number | boolean | null>>;
|
|
2582
3267
|
|
|
@@ -2653,6 +3338,12 @@ export declare interface PublicApiKey {
|
|
|
2653
3338
|
readonly createdAt: number;
|
|
2654
3339
|
}
|
|
2655
3340
|
|
|
3341
|
+
/** A typed builder or its descriptor — the single-row terminals accept either,
|
|
3342
|
+
* so a call site never has to reach for `.descriptor` just to use them. */
|
|
3343
|
+
export declare type QueryLike<R = Row_5> = QueryDescriptor<R> | {
|
|
3344
|
+
readonly descriptor: QueryDescriptor<R>;
|
|
3345
|
+
};
|
|
3346
|
+
|
|
2656
3347
|
export declare interface QueryProducerDeps<D> {
|
|
2657
3348
|
readonly buildContext: (request: ServeRequestContext) => {
|
|
2658
3349
|
readonly store: unknown;
|
|
@@ -2821,7 +3512,7 @@ export declare const recordTimelineEvent: (change: CdcChange & {
|
|
|
2821
3512
|
}) => void;
|
|
2822
3513
|
|
|
2823
3514
|
/** Blank sensitive-looking columns. Returns a new object; null passes through. */
|
|
2824
|
-
export declare const redactRow: (row:
|
|
3515
|
+
export declare const redactRow: (row: Row_2 | null | undefined) => Row_2 | null;
|
|
2825
3516
|
|
|
2826
3517
|
/**
|
|
2827
3518
|
* Options for `redisRywPositionStore`. The store needs TWO Redis
|
|
@@ -2902,6 +3593,15 @@ export declare const redoUndoInvocation: (input: {
|
|
|
2902
3593
|
readonly ok: boolean;
|
|
2903
3594
|
}>;
|
|
2904
3595
|
|
|
3596
|
+
/** How long a refresh claim is held before another replica may take it. Long
|
|
3597
|
+
* enough for a slow token endpoint, short enough that a crashed replica does
|
|
3598
|
+
* not block renewal for long. */
|
|
3599
|
+
export declare const REFRESH_LEASE_MS = 30000;
|
|
3600
|
+
|
|
3601
|
+
/** Swap a refresh token for a fresh set. Providers that ROTATE return a new
|
|
3602
|
+
* refresh token; the caller persists whatever comes back. */
|
|
3603
|
+
export declare const refreshAccessToken: (definition: OAuth2ConnectionDefinition, refreshToken: string, now?: () => number) => Promise<ConnectionTokens>;
|
|
3604
|
+
|
|
2905
3605
|
/**
|
|
2906
3606
|
* Register an interrupt for a stream bound to `clientId`. Returns a
|
|
2907
3607
|
* deregister fn the stream's scope finalizer must call on normal end (so
|
|
@@ -3065,13 +3765,13 @@ export declare class ReplicatedDataStore implements DataStore {
|
|
|
3065
3765
|
private readonly recordRouting?;
|
|
3066
3766
|
private readonly now;
|
|
3067
3767
|
constructor(options: ReplicatedDataStoreOptions);
|
|
3068
|
-
query(descriptor: QueryDescriptor): Promise<ReadonlyArray<
|
|
3768
|
+
query(descriptor: QueryDescriptor): Promise<ReadonlyArray<Row>>;
|
|
3069
3769
|
private routeToReplica;
|
|
3070
3770
|
private routeWaitMode;
|
|
3071
|
-
insert(table: string, row:
|
|
3072
|
-
insertMany(table: string, rows: ReadonlyArray<
|
|
3073
|
-
patchJson(table: string, primaryKey: string, path: string, value: unknown): Promise<
|
|
3074
|
-
update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<
|
|
3771
|
+
insert(table: string, row: Row): Promise<Row>;
|
|
3772
|
+
insertMany(table: string, rows: ReadonlyArray<Row>): Promise<ReadonlyArray<Row>>;
|
|
3773
|
+
patchJson(table: string, primaryKey: string, path: string, value: unknown): Promise<Row | null>;
|
|
3774
|
+
update(table: string, primaryKey: string, patch: Readonly<Record<string, unknown>>): Promise<Row | null>;
|
|
3075
3775
|
delete(table: string, primaryKey: string): Promise<boolean>;
|
|
3076
3776
|
updateMany(table: string, patch: Readonly<Record<string, unknown>>, options: {
|
|
3077
3777
|
where: Predicate;
|
|
@@ -3079,13 +3779,13 @@ export declare class ReplicatedDataStore implements DataStore {
|
|
|
3079
3779
|
deleteMany(table: string, options: {
|
|
3080
3780
|
where: Predicate;
|
|
3081
3781
|
}): Promise<number>;
|
|
3082
|
-
upsert(table: string, row:
|
|
3782
|
+
upsert(table: string, row: Row, options: {
|
|
3083
3783
|
conflictColumns: ReadonlyArray<string>;
|
|
3084
|
-
update?: ReadonlyArray<string> | ((existing:
|
|
3085
|
-
}): Promise<
|
|
3086
|
-
insertIgnore(table: string, row:
|
|
3784
|
+
update?: ReadonlyArray<string> | ((existing: Row) => Readonly<Record<string, unknown>>);
|
|
3785
|
+
}): Promise<Row>;
|
|
3786
|
+
insertIgnore(table: string, row: Row, options: {
|
|
3087
3787
|
conflictColumns: ReadonlyArray<string>;
|
|
3088
|
-
}): Promise<
|
|
3788
|
+
}): Promise<Row>;
|
|
3089
3789
|
transactional<T>(work: (tx: DataStore) => Promise<T>): Promise<T>;
|
|
3090
3790
|
onChange(listener: (event: ChangeEvent) => void): () => void;
|
|
3091
3791
|
/** Cross-instance reactivity seam — delegates to the primary, where the
|
|
@@ -3149,12 +3849,49 @@ export declare interface ReplicatedDataStoreOptions {
|
|
|
3149
3849
|
readonly now?: () => number;
|
|
3150
3850
|
}
|
|
3151
3851
|
|
|
3852
|
+
export declare interface ResendOptions {
|
|
3853
|
+
/** Operator's note, recorded on the resulting attempt row. */
|
|
3854
|
+
readonly reason?: string;
|
|
3855
|
+
/** How many attempts the resend is worth. Default 1 — see `resend`. */
|
|
3856
|
+
readonly attempts?: number;
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3152
3859
|
/** Test/dev-only — clear ALL overrides. Don't call from app code. */
|
|
3153
3860
|
export declare const _resetConnectionSubjectsForTest: () => void;
|
|
3154
3861
|
|
|
3155
3862
|
/** Reset to the env default (tests). */
|
|
3156
3863
|
export declare const resetSecretsBackend: () => void;
|
|
3157
3864
|
|
|
3865
|
+
/**
|
|
3866
|
+
* Resolve the calling subject's credential for one connection, refreshed.
|
|
3867
|
+
*
|
|
3868
|
+
* This is what a plugin or handler asks for. It never returns an expired
|
|
3869
|
+
* token: either it hands back one that is valid past the skew window, or it
|
|
3870
|
+
* throws. There is no "here's a token, good luck" third case, because the
|
|
3871
|
+
* whole reason apps hand-roll a retry-on-401 is that such a case exists.
|
|
3872
|
+
*/
|
|
3873
|
+
export declare const resolveConnection: (deps: ResolveConnectionDeps) => Promise<ResolvedConnection>;
|
|
3874
|
+
|
|
3875
|
+
export declare interface ResolveConnectionDeps {
|
|
3876
|
+
readonly store: VaultStore;
|
|
3877
|
+
readonly definition: ConnectionDefinition;
|
|
3878
|
+
readonly subjectId: string;
|
|
3879
|
+
readonly now?: () => number;
|
|
3880
|
+
/** Injectable sleep so the wait-for-another-replica path is testable without
|
|
3881
|
+
* real time. */
|
|
3882
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
/**
|
|
3886
|
+
* Resolve one subject's credential from anywhere on the server — a plugin's
|
|
3887
|
+
* `credentialsResolver`, a cron, an outbox handler.
|
|
3888
|
+
*
|
|
3889
|
+
* Throws (rather than returning null) when no vault is registered: an app that
|
|
3890
|
+
* asks for a connection it never declared has a configuration bug, and
|
|
3891
|
+
* returning "not connected" would present it as a user problem.
|
|
3892
|
+
*/
|
|
3893
|
+
export declare const resolveConnectionForSubject: (subject: Pick<Subject, "id">, connectionId: string) => Promise<ResolvedConnection>;
|
|
3894
|
+
|
|
3158
3895
|
/**
|
|
3159
3896
|
* Resolve the current process's region from the environment. Checks,
|
|
3160
3897
|
* in priority order:
|
|
@@ -3169,6 +3906,27 @@ export declare const resetSecretsBackend: () => void;
|
|
|
3169
3906
|
*/
|
|
3170
3907
|
export declare const resolveCurrentRegion: (env?: NodeJS.ProcessEnv) => string | undefined;
|
|
3171
3908
|
|
|
3909
|
+
/** What the auth strategy learns from a presented key. */
|
|
3910
|
+
export declare interface ResolvedApiKey {
|
|
3911
|
+
readonly id: string;
|
|
3912
|
+
readonly tenantId: string;
|
|
3913
|
+
/** The user who created the key, when one is recorded. Null for a key minted
|
|
3914
|
+
* outside a user session (bootstrap / admin tooling). */
|
|
3915
|
+
readonly createdBy: string | null;
|
|
3916
|
+
readonly scopes: ReadonlyArray<string>;
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
/** A usable credential, handed to a plugin / handler. */
|
|
3920
|
+
export declare interface ResolvedConnection {
|
|
3921
|
+
readonly connectionId: string;
|
|
3922
|
+
readonly kind: ConnectionKind;
|
|
3923
|
+
readonly accessToken: string;
|
|
3924
|
+
readonly expiresAt: Date | null;
|
|
3925
|
+
readonly scopes: ReadonlyArray<string>;
|
|
3926
|
+
readonly accountId: string | null;
|
|
3927
|
+
readonly accountLabel: string | null;
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3172
3930
|
/**
|
|
3173
3931
|
* Walk a query descriptor's eager-load tree and return the FULL set
|
|
3174
3932
|
* of tables whose changes could affect the resolved snapshot.
|
|
@@ -3187,10 +3945,26 @@ export declare const resolveCurrentRegion: (env?: NodeJS.ProcessEnv) => string |
|
|
|
3187
3945
|
*/
|
|
3188
3946
|
export declare const resolveDependentTables: (descriptor: QueryDescriptor) => ReadonlySet<string>;
|
|
3189
3947
|
|
|
3948
|
+
export declare const resolveRedirectUri: (definition: OAuth2ConnectionDefinition, publicUrl: string) => string;
|
|
3949
|
+
|
|
3190
3950
|
/** Rebuild the flagged groups from the (already-committed) base rows + merge —
|
|
3191
3951
|
* the caller's resolution of a min/max rescan signal. `baseRows` is the full
|
|
3192
3952
|
* current table (or at least every row in the rescan groups). */
|
|
3193
|
-
export declare const resolveRescan: (shape: AggregateShape, state: IvmState, rescanGroups: ReadonlyArray<string>, baseRows: ReadonlyArray<
|
|
3953
|
+
export declare const resolveRescan: (shape: AggregateShape, state: IvmState, rescanGroups: ReadonlyArray<string>, baseRows: ReadonlyArray<Row_3>) => IvmState;
|
|
3954
|
+
|
|
3955
|
+
/**
|
|
3956
|
+
* Resolve the request-scoped filter for `subject`.
|
|
3957
|
+
*
|
|
3958
|
+
* Fails CLOSED in the one way that matters: if `load` throws, every constrained
|
|
3959
|
+
* read is refused rather than silently unfiltered. A row filter that degrades to
|
|
3960
|
+
* "no filter" under load failure is worse than none, because the system keeps
|
|
3961
|
+
* serving and nothing looks wrong.
|
|
3962
|
+
*
|
|
3963
|
+
* Refusal is expressed as a predicate that matches nothing, not as an error, so
|
|
3964
|
+
* a failure surfaces as an empty result rather than a 500 on every page — and
|
|
3965
|
+
* the failure itself is reported through `onError` so it cannot pass unnoticed.
|
|
3966
|
+
*/
|
|
3967
|
+
export declare const resolveRowFilterScope: (subject: Subject, onError?: (error: unknown) => void) => Effect.Effect<RowFilterScope>;
|
|
3194
3968
|
|
|
3195
3969
|
/** Resolve a secret through the active backend. Falls back to `process.env`
|
|
3196
3970
|
* when the backend returns nothing, so a partially-populated remote backend
|
|
@@ -3233,7 +4007,7 @@ export { retentionTtlMsFromEnv }
|
|
|
3233
4007
|
* - delete → the row was removed, so add `old` back.
|
|
3234
4008
|
* Pure. Applied newest-first to walk from "now" to a past point.
|
|
3235
4009
|
*/
|
|
3236
|
-
export declare const reverseEventOverRows: (rows: ReadonlyArray<
|
|
4010
|
+
export declare const reverseEventOverRows: (rows: ReadonlyArray<Row_2>, e: TimelineEvent) => ReadonlyArray<Row_2>;
|
|
3237
4011
|
|
|
3238
4012
|
/** The ids that were visible before but are not now — what a live-revocation
|
|
3239
4013
|
* delta removes from an open subscription. */
|
|
@@ -3271,12 +4045,14 @@ export declare interface RoutingContext {
|
|
|
3271
4045
|
readonly forcePrimary?: boolean;
|
|
3272
4046
|
}
|
|
3273
4047
|
|
|
3274
|
-
|
|
4048
|
+
export { Row }
|
|
3275
4049
|
|
|
3276
|
-
declare type Row_2 =
|
|
4050
|
+
declare type Row_2 = Record<string, unknown>;
|
|
3277
4051
|
|
|
3278
4052
|
declare type Row_3 = Readonly<Record<string, unknown>>;
|
|
3279
4053
|
|
|
4054
|
+
declare type Row_4 = Readonly<Record<string, unknown>>;
|
|
4055
|
+
|
|
3280
4056
|
declare type Row_5 = Readonly<Record<string, unknown>>;
|
|
3281
4057
|
|
|
3282
4058
|
export declare interface RowDiff {
|
|
@@ -3287,6 +4063,28 @@ export declare interface RowDiff {
|
|
|
3287
4063
|
readonly fields: Readonly<Record<string, FieldChange>>;
|
|
3288
4064
|
}
|
|
3289
4065
|
|
|
4066
|
+
/**
|
|
4067
|
+
* The per-request filter, resolved from a subject.
|
|
4068
|
+
*
|
|
4069
|
+
* `Ctx` is whatever your `load` returns — typically the ids the predicate needs
|
|
4070
|
+
* (team ids, project ids, a role map). The framework never inspects it.
|
|
4071
|
+
*/
|
|
4072
|
+
export declare interface RowFilter<Ctx = unknown> {
|
|
4073
|
+
/** Resolve everything the predicates need, ONCE per request. May read the
|
|
4074
|
+
* store. A failure DENIES (see `resolveRowFilterContext`). */
|
|
4075
|
+
readonly load: (subject: Subject) => Effect.Effect<Ctx, unknown>;
|
|
4076
|
+
/**
|
|
4077
|
+
* The filter for one table, derived purely from the loaded context. Return
|
|
4078
|
+
* `undefined` for a table this filter does not constrain — most tables.
|
|
4079
|
+
*
|
|
4080
|
+
* Must be PURE and SYNC: it runs on every read.
|
|
4081
|
+
*/
|
|
4082
|
+
readonly predicate: (ctx: Ctx, table: string) => Predicate | undefined;
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
/** What a scoped store needs: a sync `table → Predicate?` lookup. */
|
|
4086
|
+
export declare type RowFilterScope = (table: string) => Predicate | undefined;
|
|
4087
|
+
|
|
3290
4088
|
/** One `_voltro_row_history` row, structurally (from @voltro/plugin-versioning). */
|
|
3291
4089
|
export declare interface RowHistoryEntry {
|
|
3292
4090
|
readonly value?: unknown;
|
|
@@ -3481,6 +4279,23 @@ export declare interface RuntimeContext {
|
|
|
3481
4279
|
*/
|
|
3482
4280
|
export declare const runWakeLoop: (deps: WakeOrchestratorDeps, intervalMs?: number) => WakeLoopHandle;
|
|
3483
4281
|
|
|
4282
|
+
/**
|
|
4283
|
+
* Run a full-transaction thunk, replaying it on a transient DB error
|
|
4284
|
+
* (deadlock / lock-wait). Non-transient failures throw on the first try.
|
|
4285
|
+
* Backoff escalates with random jitter so concurrent victims don't
|
|
4286
|
+
* re-collide in lockstep.
|
|
4287
|
+
*
|
|
4288
|
+
* `delay` overrides that backoff. The only caller that does is the unit-test
|
|
4289
|
+
* harness (`@voltro/testing`'s `invoke`), which reproduces the replay against
|
|
4290
|
+
* an in-memory store: there is no lock manager to de-correlate against there,
|
|
4291
|
+
* so a real sleep would buy nothing and would stall a suite using fake timers.
|
|
4292
|
+
* Exported for exactly that — one retry implementation, not a second one that
|
|
4293
|
+
* drifts from this classification.
|
|
4294
|
+
*/
|
|
4295
|
+
export declare const runWithDeadlockRetry: <T>(work: () => Promise<T>, options?: {
|
|
4296
|
+
readonly delay?: (attempt: number) => Promise<void>;
|
|
4297
|
+
}) => Promise<T>;
|
|
4298
|
+
|
|
3484
4299
|
/**
|
|
3485
4300
|
* Run `work` inside a routing scope. Every `query()` call against a
|
|
3486
4301
|
* `ReplicatedDataStore` from inside `work` (or its async children)
|
|
@@ -3531,6 +4346,14 @@ export declare interface RywPositionStore {
|
|
|
3531
4346
|
size(): number;
|
|
3532
4347
|
}
|
|
3533
4348
|
|
|
4349
|
+
/**
|
|
4350
|
+
* Reject anything but a same-origin path. The callback ends in a 302 to this
|
|
4351
|
+
* value, so accepting `https://evil.example` would turn every app that
|
|
4352
|
+
* declares a connection into an open redirector — a phishing primitive handed
|
|
4353
|
+
* out for free with a feature about credentials.
|
|
4354
|
+
*/
|
|
4355
|
+
export declare const sanitizeRedirectTo: (value: string | undefined) => string;
|
|
4356
|
+
|
|
3534
4357
|
/** Brand so the CLI's discovery loop can duck-type a `*.cron.tsx`
|
|
3535
4358
|
* default export without importing the concrete class. */
|
|
3536
4359
|
declare const SCHEDULE_BRAND: unique symbol;
|
|
@@ -3835,6 +4658,8 @@ export declare interface SchemaRegistry {
|
|
|
3835
4658
|
declare interface ScopeCtx {
|
|
3836
4659
|
readonly subject: Subject;
|
|
3837
4660
|
readonly schemaRegistry: SchemaRegistry;
|
|
4661
|
+
/** Row-level security scope for this request — see `./rowFilter`. */
|
|
4662
|
+
readonly rowFilter?: RowFilterScope;
|
|
3838
4663
|
}
|
|
3839
4664
|
|
|
3840
4665
|
export declare interface SecretsBackend {
|
|
@@ -3858,7 +4683,7 @@ export declare class SelectBuilder {
|
|
|
3858
4683
|
private query;
|
|
3859
4684
|
private skipTenant;
|
|
3860
4685
|
private skipSoftDelete;
|
|
3861
|
-
constructor(backend: FluentStoreBackend, scope: ScopeCtx, table: string, query: Query<
|
|
4686
|
+
constructor(backend: FluentStoreBackend, scope: ScopeCtx, table: string, query: Query<Row>, skipTenant?: boolean, skipSoftDelete?: boolean);
|
|
3862
4687
|
private clone;
|
|
3863
4688
|
/** `.where(eq(...))` (predicate AST) OR `.where(col, value)` /
|
|
3864
4689
|
* `.where(col, op, value)` (ergonomic). Multiple calls AND-merge. */
|
|
@@ -3868,7 +4693,7 @@ export declare class SelectBuilder {
|
|
|
3868
4693
|
offset(n: number): SelectBuilder;
|
|
3869
4694
|
select(...columns: ReadonlyArray<string>): SelectBuilder;
|
|
3870
4695
|
using(indexName: string): SelectBuilder;
|
|
3871
|
-
with(spec: Parameters<Query<
|
|
4696
|
+
with(spec: Parameters<Query<Row>['with']>[0]): SelectBuilder;
|
|
3872
4697
|
groupBy(columns: ReadonlyArray<string>): SelectBuilder;
|
|
3873
4698
|
matching(indexName: string, query: string): SelectBuilder;
|
|
3874
4699
|
/** Drop the automatic tenant filter (cross-tenant staff reads). */
|
|
@@ -3877,13 +4702,23 @@ export declare class SelectBuilder {
|
|
|
3877
4702
|
withDeleted(): SelectBuilder;
|
|
3878
4703
|
private scopedQuery;
|
|
3879
4704
|
/** Terminal: all matching rows. */
|
|
3880
|
-
all(): Promise<ReadonlyArray<
|
|
4705
|
+
all(): Promise<ReadonlyArray<Row>>;
|
|
3881
4706
|
/** Terminal: the first matching row or `null`. */
|
|
3882
|
-
maybeOne(): Promise<
|
|
4707
|
+
maybeOne(): Promise<Row | null>;
|
|
3883
4708
|
/** Alias of `maybeOne` — the first row or `null`. */
|
|
3884
|
-
first(): Promise<
|
|
3885
|
-
/**
|
|
3886
|
-
|
|
4709
|
+
first(): Promise<Row | null>;
|
|
4710
|
+
/**
|
|
4711
|
+
* Terminal: EXACTLY one row. Fails with `NoRowFound` on zero matches —
|
|
4712
|
+
* and equally on two or more.
|
|
4713
|
+
*
|
|
4714
|
+
* The over-fetch to LIMIT 2 is deliberate. A `LIMIT 1` probe cannot tell
|
|
4715
|
+
* "the one row you meant" from "the first of several", so a filter that
|
|
4716
|
+
* silently stopped being unique would keep returning an arbitrary row and
|
|
4717
|
+
* the bug would surface far away from its cause. One extra row on the
|
|
4718
|
+
* wire buys a loud failure at the point the assumption breaks. Use
|
|
4719
|
+
* `.first()` / `.maybeOne()` when you genuinely want "any match".
|
|
4720
|
+
*/
|
|
4721
|
+
one(): Promise<Row>;
|
|
3887
4722
|
/** Terminal: COUNT(*) of matching rows (real aggregate, not a fetch). */
|
|
3888
4723
|
count(): Promise<number>;
|
|
3889
4724
|
/** Terminal: does any row match? Uses a `LIMIT 1` probe. */
|
|
@@ -3896,6 +4731,16 @@ export declare interface ServeRequestContext {
|
|
|
3896
4731
|
readonly subject: unknown;
|
|
3897
4732
|
readonly traceId: string;
|
|
3898
4733
|
readonly spanId?: string;
|
|
4734
|
+
/**
|
|
4735
|
+
* Row-level visibility for this request, resolved by `withRowFilter` and read
|
|
4736
|
+
* by the context builder when it wraps the store.
|
|
4737
|
+
*
|
|
4738
|
+
* Carried on the REQUEST because resolving it may read the database while the
|
|
4739
|
+
* context builder is synchronous. Making the builder async would ripple
|
|
4740
|
+
* through every entrypoint; resolving here, already inside an async
|
|
4741
|
+
* boundary, does not.
|
|
4742
|
+
*/
|
|
4743
|
+
readonly rowFilter?: RowFilterScope;
|
|
3899
4744
|
}
|
|
3900
4745
|
|
|
3901
4746
|
/** A plugin interceptor — wraps the base run Effect (Effect-native chain). */
|
|
@@ -3908,9 +4753,19 @@ export declare type ServeRpcInterceptor = (base: Effect.Effect<unknown, unknown,
|
|
|
3908
4753
|
readonly spanId?: string;
|
|
3909
4754
|
}) => Effect.Effect<unknown, unknown, never>;
|
|
3910
4755
|
|
|
4756
|
+
/** Register the process-wide connection resolver (or clear with `undefined`).
|
|
4757
|
+
* Called by the CLI at boot; tests call it directly. */
|
|
4758
|
+
export declare const setConnectionResolver: (resolver: ConnectionResolver | undefined) => void;
|
|
4759
|
+
|
|
3911
4760
|
/** Register the field cipher (or clear with `undefined`). */
|
|
3912
4761
|
export declare const setFieldCipher: (cipher: FieldCipher | undefined) => void;
|
|
3913
4762
|
|
|
4763
|
+
/**
|
|
4764
|
+
* Register (or clear, with `undefined`) the process-global row filter. Call
|
|
4765
|
+
* once at boot. Last write wins.
|
|
4766
|
+
*/
|
|
4767
|
+
export declare const setRowFilter: <Ctx>(filter: RowFilter<Ctx> | undefined) => void;
|
|
4768
|
+
|
|
3914
4769
|
/** Install the process-wide secrets backend (called once at boot). */
|
|
3915
4770
|
export declare const setSecretsBackend: (backend: SecretsBackend) => void;
|
|
3916
4771
|
|
|
@@ -3922,6 +4777,14 @@ export declare const setSystemStoreHandle: (handle: SystemStoreHandle) => void;
|
|
|
3922
4777
|
/** Test seam — swap (or reset with `undefined`) the process recorder. */
|
|
3923
4778
|
export declare const setTimelineRecorderForTest: (recorder: TimelineRecorder | undefined) => void;
|
|
3924
4779
|
|
|
4780
|
+
/**
|
|
4781
|
+
* Register (or clear) the process-global tuple source. Last write wins.
|
|
4782
|
+
*
|
|
4783
|
+
* Registering a source is what ACTIVATES relationship guards. Until then they
|
|
4784
|
+
* deny — see `installPolicyGuardResolver`.
|
|
4785
|
+
*/
|
|
4786
|
+
export declare const setTupleSource: (source: TupleSource | undefined) => void;
|
|
4787
|
+
|
|
3925
4788
|
export declare const sha256Hex: (input: string) => string;
|
|
3926
4789
|
|
|
3927
4790
|
export declare type ShapeClassification = {
|
|
@@ -3978,10 +4841,10 @@ export declare interface SingleNodeOrchestratorOptions {
|
|
|
3978
4841
|
* tables invalidates the entry via the invalidation bus.
|
|
3979
4842
|
*/
|
|
3980
4843
|
export declare interface SnapshotCache {
|
|
3981
|
-
readonly wrap: (binding: SnapshotCacheBinding, tags: ReadonlyArray<string>, compute: () => Promise<ReadonlyArray<
|
|
4844
|
+
readonly wrap: (binding: SnapshotCacheBinding, tags: ReadonlyArray<string>, compute: () => Promise<ReadonlyArray<Row>>) => Promise<ReadonlyArray<Row>>;
|
|
3982
4845
|
/** Write-through from the live recompute, so the cache is never staler
|
|
3983
4846
|
* than the freshest live subscriber. Fire-and-forget. */
|
|
3984
|
-
readonly put: (binding: SnapshotCacheBinding, tags: ReadonlyArray<string>, rows: ReadonlyArray<
|
|
4847
|
+
readonly put: (binding: SnapshotCacheBinding, tags: ReadonlyArray<string>, rows: ReadonlyArray<Row>) => void;
|
|
3985
4848
|
}
|
|
3986
4849
|
|
|
3987
4850
|
/**
|
|
@@ -4037,6 +4900,26 @@ export declare interface StartupContext {
|
|
|
4037
4900
|
/** The default export shape every `*.startup.ts` must provide. */
|
|
4038
4901
|
export declare type StartupFn = (ctx: StartupContext) => void | Promise<void>;
|
|
4039
4902
|
|
|
4903
|
+
/**
|
|
4904
|
+
* Write (or replace) the calling subject's credential, encrypted.
|
|
4905
|
+
*
|
|
4906
|
+
* `upsert` on the `(connectionId, subjectId)` unique keeps re-connecting a
|
|
4907
|
+
* replace rather than an accumulation of stale rows — which matters because a
|
|
4908
|
+
* second row would make "which token is current" a question the resolver would
|
|
4909
|
+
* have to answer with a heuristic.
|
|
4910
|
+
*/
|
|
4911
|
+
export declare const storeCredential: (input: StoreCredentialInput) => Promise<Row>;
|
|
4912
|
+
|
|
4913
|
+
export declare interface StoreCredentialInput {
|
|
4914
|
+
readonly store: VaultStore;
|
|
4915
|
+
readonly definition: ConnectionDefinition;
|
|
4916
|
+
readonly subjectId: string;
|
|
4917
|
+
readonly tenantId: string | null;
|
|
4918
|
+
readonly tokens: ConnectionTokens;
|
|
4919
|
+
readonly account?: ConnectionAccount;
|
|
4920
|
+
readonly now?: () => Date;
|
|
4921
|
+
}
|
|
4922
|
+
|
|
4040
4923
|
/**
|
|
4041
4924
|
* Discriminated union of all framework-owned store errors. Use it on
|
|
4042
4925
|
* a mutation's `error:` schema when you want every kind surfaced
|
|
@@ -4054,6 +4937,14 @@ export declare interface StoreMiddlewareContext {
|
|
|
4054
4937
|
* natively, or there are no array columns to translate).
|
|
4055
4938
|
*/
|
|
4056
4939
|
readonly dialect?: DialectId;
|
|
4940
|
+
/**
|
|
4941
|
+
* Row-level security scope for THIS request's subject, resolved once by the
|
|
4942
|
+
* context builder (`resolveRowFilterScope`). Omitted → no row filtering,
|
|
4943
|
+
* which is right when the app registered none. A filter that failed to load
|
|
4944
|
+
* arrives here as a deny-all scope, never as `undefined` — the difference
|
|
4945
|
+
* between "nothing to filter" and "we could not tell" must not collapse.
|
|
4946
|
+
*/
|
|
4947
|
+
readonly rowFilter?: RowFilterScope;
|
|
4057
4948
|
}
|
|
4058
4949
|
|
|
4059
4950
|
/**
|
|
@@ -4190,6 +5081,11 @@ export declare interface SubscriptionSnapshot {
|
|
|
4190
5081
|
readonly subscriberCount: number;
|
|
4191
5082
|
}
|
|
4192
5083
|
|
|
5084
|
+
/** Drop handshake grants past their TTL. Wired into the boot retention sweep;
|
|
5085
|
+
* a grant is worthless the moment it expires and keeping them turns a hot
|
|
5086
|
+
* table into an audit liability. */
|
|
5087
|
+
export declare const sweepExpiredGrants: (store: VaultStore, now?: Date) => Promise<number>;
|
|
5088
|
+
|
|
4193
5089
|
export { sweepRetention }
|
|
4194
5090
|
|
|
4195
5091
|
/**
|
|
@@ -4289,8 +5185,8 @@ export declare interface TimelineEvent {
|
|
|
4289
5185
|
readonly table: string;
|
|
4290
5186
|
readonly op: 'insert' | 'update' | 'delete';
|
|
4291
5187
|
/** Redacted row images. `old` present on update/delete; `new` on insert/update. */
|
|
4292
|
-
readonly old:
|
|
4293
|
-
readonly new:
|
|
5188
|
+
readonly old: Row_2 | null;
|
|
5189
|
+
readonly new: Row_2 | null;
|
|
4294
5190
|
readonly tenantId: string | null;
|
|
4295
5191
|
}
|
|
4296
5192
|
|
|
@@ -4334,7 +5230,7 @@ export declare class TimelineRecorder {
|
|
|
4334
5230
|
* Read-only. Correct as long as the ring still holds the events after
|
|
4335
5231
|
* `asOfSeq` (bounded by `capacity`).
|
|
4336
5232
|
*/
|
|
4337
|
-
replay(table: string, asOfSeq: number, currentRows: ReadonlyArray<
|
|
5233
|
+
replay(table: string, asOfSeq: number, currentRows: ReadonlyArray<Row_2>): ReadonlyArray<Row_2>;
|
|
4338
5234
|
}
|
|
4339
5235
|
|
|
4340
5236
|
/** Time window. `to` defaults to now. */
|
|
@@ -4492,6 +5388,20 @@ export declare const triggerWorkflow: <EventPayload = unknown, WorkflowPayload =
|
|
|
4492
5388
|
*/
|
|
4493
5389
|
export declare const tryClaimWakeup: (store: DataStore, ref: WakeupRef) => Promise<boolean>;
|
|
4494
5390
|
|
|
5391
|
+
/**
|
|
5392
|
+
* Reads the relationship tuples for one (subject, resource).
|
|
5393
|
+
*
|
|
5394
|
+
* Registered once at boot. The default implementation reads
|
|
5395
|
+
* `_voltro_rebac_tuples`; an app whose relationships live in its own tables
|
|
5396
|
+
* (a `teamMembers` row, say) registers its own instead of copying data into a
|
|
5397
|
+
* framework table.
|
|
5398
|
+
*/
|
|
5399
|
+
export declare type TupleSource = (req: {
|
|
5400
|
+
readonly subjectId: string | null;
|
|
5401
|
+
readonly resourceType: string;
|
|
5402
|
+
readonly resourceId: string;
|
|
5403
|
+
}) => Promise<ReadonlyArray<RelationTuple>> | ReadonlyArray<RelationTuple>;
|
|
5404
|
+
|
|
4495
5405
|
/**
|
|
4496
5406
|
* Remove the override for a connection. Called by the WS-close
|
|
4497
5407
|
* finalizer (or explicit logout flows). Idempotent.
|
|
@@ -4616,6 +5526,8 @@ export declare const useAggregate: <Row>(def: AggregateDefinition<Row>) => Effec
|
|
|
4616
5526
|
*/
|
|
4617
5527
|
export declare const useAnalytics: () => Effect.Effect<AnalyticsSinkImpl, never, AnalyticsSink>;
|
|
4618
5528
|
|
|
5529
|
+
declare type VaultStore = Pick<DataStore, 'query' | 'insert' | 'update' | 'updateMany' | 'delete' | 'upsert'>;
|
|
5530
|
+
|
|
4619
5531
|
/** The pure core of live revocation: given a subject + action + a row-set,
|
|
4620
5532
|
* return ONLY the rows the subject may still see. The reactive matcher calls
|
|
4621
5533
|
* this on a permission-changing write and diffs against the prior visible set;
|
|
@@ -4637,6 +5549,39 @@ export declare const VOLTRO_SOFT_DELETE_MIXIN_ID: "voltro/softDelete";
|
|
|
4637
5549
|
|
|
4638
5550
|
export declare const VOLTRO_TENANT_MIXIN_ID: "voltro/tenant";
|
|
4639
5551
|
|
|
5552
|
+
/**
|
|
5553
|
+
* `_voltro_connection_grants` — an IN-FLIGHT oauth2 handshake. Separate from
|
|
5554
|
+
* the credential table on purpose: a handshake is a short-lived, single-use
|
|
5555
|
+
* anti-CSRF token with a different lifecycle (seconds, deleted on use) from a
|
|
5556
|
+
* credential (months, updated in place). Folding it into a `status:'pending'`
|
|
5557
|
+
* row on the credential table would mean a failed handshake leaves a row that
|
|
5558
|
+
* the resolver + the list projection both have to learn to ignore.
|
|
5559
|
+
*
|
|
5560
|
+
* `state` is what carries identity across the redirect: the browser comes back
|
|
5561
|
+
* from the provider with no session guarantee, so the row — minted for a known
|
|
5562
|
+
* subject before the redirect — is the only trustworthy statement of who the
|
|
5563
|
+
* arriving code belongs to.
|
|
5564
|
+
*/
|
|
5565
|
+
export declare const _voltroConnectionGrantsTable: TableLike;
|
|
5566
|
+
|
|
5567
|
+
/**
|
|
5568
|
+
* `_voltro_connections` — ONE row per (connection, subject). The stored
|
|
5569
|
+
* credential.
|
|
5570
|
+
*
|
|
5571
|
+
* `accessToken` / `refreshToken` hold CIPHERTEXT (`enc:v1:…`), never the raw
|
|
5572
|
+
* token. They are plain `text()` rather than `.encrypted()` columns on purpose:
|
|
5573
|
+
* `.encrypted()` decrypts inside the `ctx.store` mixin middleware, and the
|
|
5574
|
+
* vault must be readable from the resolver path, the OAuth callback route, and
|
|
5575
|
+
* the refresh worker — none of which hold a request-scoped `ctx.store`. Using
|
|
5576
|
+
* `encryptField` / `decryptField` explicitly keeps ONE cipher and one key while
|
|
5577
|
+
* letting every one of those paths read the column. (Same reason the api-key
|
|
5578
|
+
* and session paths use the standalone helpers.)
|
|
5579
|
+
*
|
|
5580
|
+
* Reactive so `__voltro.connections.list` updates an open settings page the
|
|
5581
|
+
* moment a callback lands, with no polling.
|
|
5582
|
+
*/
|
|
5583
|
+
export declare const _voltroConnectionsTable: TableLike;
|
|
5584
|
+
|
|
4640
5585
|
/** `_voltro_rebac_tuples` — the relationship tuples `can()` reads. One row =
|
|
4641
5586
|
* "`subjectId` has `relation` on `<resourceType>:<resourceId>`". Grant by
|
|
4642
5587
|
* inserting; revoke by deleting (a delete is the live-revocation trigger). */
|
|
@@ -4765,6 +5710,17 @@ export declare type WebhooksAppContext = unknown;
|
|
|
4765
5710
|
* builder. */
|
|
4766
5711
|
export declare type WhereOp = '=' | '!=' | '<>' | '>' | '>=' | '<' | '<=' | 'in' | 'like' | 'contains' | 'fts';
|
|
4767
5712
|
|
|
5713
|
+
/**
|
|
5714
|
+
* Resolve this request's row-filter scope. A no-op (same object back) when the
|
|
5715
|
+
* app registered no filter, so an app that uses none pays nothing.
|
|
5716
|
+
*
|
|
5717
|
+
* Every runner calls this before building a context. A read path that skipped
|
|
5718
|
+
* it would have no row-level security and would look exactly like one that
|
|
5719
|
+
* does — which is why it lives in the shared spine rather than in each
|
|
5720
|
+
* entrypoint.
|
|
5721
|
+
*/
|
|
5722
|
+
export declare const withRowFilter: <R extends ServeRequestContext>(request: R) => Promise<R>;
|
|
5723
|
+
|
|
4768
5724
|
export declare interface WorkflowCallerContext {
|
|
4769
5725
|
readonly subject?: unknown;
|
|
4770
5726
|
readonly traceId?: string | null;
|