@voltro/runtime 0.24.0 → 0.26.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 +670 -0
- package/dist/index.d.ts +849 -8
- package/dist/index.js +2063 -1472
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -14,8 +14,16 @@ import { DialectId } from '@voltro/database';
|
|
|
14
14
|
import { DialectReplicationAdapter } from '@voltro/database';
|
|
15
15
|
import { Duration } from 'effect';
|
|
16
16
|
import { Effect } from 'effect';
|
|
17
|
+
import { EventDeliverySemantics } from '@voltro/protocol';
|
|
18
|
+
import { EventDescriptor } from '@voltro/protocol';
|
|
19
|
+
import { EventKeyInvalid } from '@voltro/protocol';
|
|
20
|
+
import { EventPayloadInvalid } from '@voltro/protocol';
|
|
21
|
+
import { EventPayloadTooLarge } from '@voltro/protocol';
|
|
22
|
+
import { EventResumePoint } from '@voltro/protocol';
|
|
23
|
+
import { EventStreamEvent } from '@voltro/protocol';
|
|
17
24
|
import { Fiber } from 'effect';
|
|
18
25
|
import { FieldCipher } from '@voltro/database';
|
|
26
|
+
import { Guards } from '@voltro/protocol';
|
|
19
27
|
import * as http from 'node:http';
|
|
20
28
|
import { HttpClient } from '@effect/platform';
|
|
21
29
|
import { HttpRequestInterceptor } from '@voltro/protocol';
|
|
@@ -35,6 +43,7 @@ import { Metric } from 'effect';
|
|
|
35
43
|
import { MetricBoundaries } from 'effect';
|
|
36
44
|
import { MetricReader } from '@opentelemetry/sdk-metrics';
|
|
37
45
|
import { PluginHttpRoute } from '@voltro/protocol';
|
|
46
|
+
import { PluginRefOrphanPolicy } from '@voltro/database';
|
|
38
47
|
import { Predicate } from '@voltro/database';
|
|
39
48
|
import { Query } from '@voltro/database';
|
|
40
49
|
import { QueryDescriptor } from '@voltro/database';
|
|
@@ -855,7 +864,7 @@ export declare interface AppContext {
|
|
|
855
864
|
* recompute. */
|
|
856
865
|
readonly kv: AsyncKv;
|
|
857
866
|
/** Webhooks service — present when `@voltro/plugin-webhooks` is in
|
|
858
|
-
* the app's plugin list.
|
|
867
|
+
* the app's plugin list. Declare the event with `defineEvent({ …, webhook })` in a
|
|
859
868
|
* `*.webhook.tsx` file to declare an event, then `ctx.webhooks.emit(
|
|
860
869
|
* 'eventId', payload)` from any handler. */
|
|
861
870
|
readonly webhooks?: WebhooksAppContext;
|
|
@@ -863,10 +872,23 @@ export declare interface AppContext {
|
|
|
863
872
|
* `*.workflow.tsx` files. Starts return a run handle immediately;
|
|
864
873
|
* `wait`/`poll`/`signal`/`cancel` are explicit operations. */
|
|
865
874
|
readonly workflows?: WorkflowsAppContext;
|
|
866
|
-
/**
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
|
|
875
|
+
/**
|
|
876
|
+
* `ctx.events.publish(descriptor, key, payload)` — present when the app
|
|
877
|
+
* declares `*.event.ts` files.
|
|
878
|
+
*
|
|
879
|
+
* This was typed as the OLD string-emitter facade (`emit(name, data)`) long
|
|
880
|
+
* after that facade was deleted, so the documented call was a `tsc` error
|
|
881
|
+
* while the runtime carried only `publish` — a consumer measured
|
|
882
|
+
* `eventKeys: ["publish"], emitType: undefined` with a cron probe because the
|
|
883
|
+
* type and the docs disagreed and they could not tell which was lying.
|
|
884
|
+
*
|
|
885
|
+
* What let it drift is worth naming: the builder installed the publisher with
|
|
886
|
+
* `as never`, so the compiler had the answer the whole time and was told not
|
|
887
|
+
* to give it. `appContext.ts` even carried a comment stating `emit` is gone —
|
|
888
|
+
* beside the cast that hid it. A context field is the one place this repo
|
|
889
|
+
* treats such a cast as a defect in its own right.
|
|
890
|
+
*/
|
|
891
|
+
readonly events?: EventPublisher;
|
|
870
892
|
/**
|
|
871
893
|
* Transactional outbox (`ctx.outbox`). Absent when the app declares no
|
|
872
894
|
* `*.outbox.ts` handler — an enqueue with nobody to deliver it would be a
|
|
@@ -928,6 +950,18 @@ export declare const applyInverse: (store: UndoApplyStore, op: InverseOp) => Pro
|
|
|
928
950
|
* `synthesizeInverse`), as the caller's transaction wraps them. */
|
|
929
951
|
export declare const applyInverses: (store: UndoApplyStore, ops: ReadonlyArray<InverseOp>) => Promise<void>;
|
|
930
952
|
|
|
953
|
+
/**
|
|
954
|
+
* Apply every rule matching `change`.
|
|
955
|
+
*
|
|
956
|
+
* Returns what it did, so a caller can log it. Never throws for a rule that
|
|
957
|
+
* finds nothing — an orphan rule firing on a row nobody references is the
|
|
958
|
+
* normal case, not an error.
|
|
959
|
+
*/
|
|
960
|
+
export declare const applyPluginRefRules: (store: PluginRefStore, rules: ReadonlyArray<PluginRefRule>, change: PluginRefChange) => Promise<ReadonlyArray<{
|
|
961
|
+
readonly rule: PluginRefRule;
|
|
962
|
+
readonly affected: number;
|
|
963
|
+
}>>;
|
|
964
|
+
|
|
931
965
|
/** AND-merge the row filter for `table` onto an existing predicate. */
|
|
932
966
|
export declare const applyRowFilter: (scope: RowFilterScope, table: string, predicate: Predicate | undefined) => Predicate | undefined;
|
|
933
967
|
|
|
@@ -1069,6 +1103,17 @@ export declare interface AuditableQuery {
|
|
|
1069
1103
|
readonly source?: string | ReadonlyArray<string> | undefined;
|
|
1070
1104
|
}
|
|
1071
1105
|
|
|
1106
|
+
/**
|
|
1107
|
+
* An Effect that is ALSO awaitable.
|
|
1108
|
+
*
|
|
1109
|
+
* The intersection is the type-level half of the fix: without `PromiseLike`,
|
|
1110
|
+
* `await ctx.events.publish(…)` type-checks (awaiting a non-Promise is legal)
|
|
1111
|
+
* and hands back the EFFECT rather than the result — so even after the runtime
|
|
1112
|
+
* behaviour was fixed, the value an async handler got would have been typed
|
|
1113
|
+
* wrong. Both halves are needed, and both are pinned by tests.
|
|
1114
|
+
*/
|
|
1115
|
+
export declare type AwaitableEffect<A, E> = Effect.Effect<A, E> & PromiseLike<A>;
|
|
1116
|
+
|
|
1072
1117
|
/**
|
|
1073
1118
|
* Resolve once a server returned by {@link startRpcServer} has actually
|
|
1074
1119
|
* bound its port (node `'listening'` event). REJECTS on `'error'`
|
|
@@ -1117,6 +1162,37 @@ export declare interface BeginOAuthResult {
|
|
|
1117
1162
|
*/
|
|
1118
1163
|
export declare const bindConnectionSubject: (clientId: number, subject: Subject) => void;
|
|
1119
1164
|
|
|
1165
|
+
/**
|
|
1166
|
+
* Bind one client's subscription to a declared event.
|
|
1167
|
+
*
|
|
1168
|
+
* The TENANT comes from the resolved subject and from nowhere else, so a
|
|
1169
|
+
* cross-tenant subscription is not something a caller can ask for. `key` has
|
|
1170
|
+
* already been decoded by the rpc layer against the descriptor's schema.
|
|
1171
|
+
*
|
|
1172
|
+
* The stream ends when the client disconnects, through the same per-connection
|
|
1173
|
+
* interrupt registry subscriptions use — so one reconnect policy, one devtools
|
|
1174
|
+
* view, one connection lifecycle for both primitives.
|
|
1175
|
+
*/
|
|
1176
|
+
export declare const bindEvent: (input: BindEventInput) => Stream.Stream<EventStreamEvent<unknown>, ScopeError, SubjectService | ConnectionInfo>;
|
|
1177
|
+
|
|
1178
|
+
export declare interface BindEventInput {
|
|
1179
|
+
readonly bus: EventBus;
|
|
1180
|
+
readonly event: string;
|
|
1181
|
+
/** Decoded routing key, exactly as the descriptor declares it. */
|
|
1182
|
+
readonly key: unknown;
|
|
1183
|
+
readonly options?: EventSubscribeOptions | undefined;
|
|
1184
|
+
/** Queue depth override — tests use a tiny one to force the drop path. */
|
|
1185
|
+
readonly queueSize?: number;
|
|
1186
|
+
/**
|
|
1187
|
+
* The descriptor's `guards:` — WHO MAY LISTEN.
|
|
1188
|
+
*
|
|
1189
|
+
* Passed in rather than read from a registry so there is exactly one way for a
|
|
1190
|
+
* subscription to be authorised, and it is the same list the manifest, the
|
|
1191
|
+
* doctor and the dashboard report on.
|
|
1192
|
+
*/
|
|
1193
|
+
readonly guards?: Guards<unknown> | undefined;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1120
1196
|
export declare const bindMutation: <Input, Output, E = never>(execute: (input: Input, context: RuntimeContext) => Output | Promise<Output> | Effect.Effect<Output, E, never>, input: Input, spanName?: string, codec?: MutationOutputCodec<unknown>, idempotency?: IdempotencyBinding) => Effect.Effect<Output, E, SubjectService | ConnectionInfo>;
|
|
1121
1197
|
|
|
1122
1198
|
/**
|
|
@@ -1128,7 +1204,25 @@ export declare const bindMutation: <Input, Output, E = never>(execute: (input: I
|
|
|
1128
1204
|
* finalizers run → the provider request aborts), reusing the same
|
|
1129
1205
|
* per-connection interrupt registry subscriptions use.
|
|
1130
1206
|
*/
|
|
1131
|
-
export declare const bindStream: <Element, Err = unknown>(buildStream: (context: RuntimeContext) => Stream.Stream<Element, Err, never> | Effect.Effect<Stream.Stream<Element, Err, never>, unknown, never>, spanName?: string
|
|
1207
|
+
export declare const bindStream: <Element, Err = unknown>(buildStream: (context: RuntimeContext) => Stream.Stream<Element, Err, never> | Effect.Effect<Stream.Stream<Element, Err, never>, unknown, never>, spanName?: string,
|
|
1208
|
+
/**
|
|
1209
|
+
* The stream's declared `guards:`, when it has any.
|
|
1210
|
+
*
|
|
1211
|
+
* Checked once at subscribe AND before every element, exactly as a query's
|
|
1212
|
+
* are. A stream was the one realtime primitive that could not express
|
|
1213
|
+
* authorization at all, so whatever protection existed was hand-written in an
|
|
1214
|
+
* executor where nothing could verify it was there.
|
|
1215
|
+
*
|
|
1216
|
+
* A denial ENDS the stream rather than dropping the element: a skipped
|
|
1217
|
+
* element is indistinguishable from "nothing to send", and the client must
|
|
1218
|
+
* learn it lost access instead of inferring it from silence.
|
|
1219
|
+
*/
|
|
1220
|
+
guards?: ReadonlyArray<unknown> | undefined,
|
|
1221
|
+
/** The call's decoded input — the guard INPUT, so a resource-scoped guard
|
|
1222
|
+
* (`{ scope: 'x:read', from: 'id' }`) can see which resource was asked for.
|
|
1223
|
+
* Passed explicitly rather than read off RuntimeContext, which does not
|
|
1224
|
+
* carry it. */
|
|
1225
|
+
guardInput?: unknown) => Stream.Stream<Element, Err, SubjectService | ConnectionInfo>;
|
|
1132
1226
|
|
|
1133
1227
|
/**
|
|
1134
1228
|
* Build a `Stream<SubscriptionEvent<T>>` that an @effect/rpc streaming
|
|
@@ -1323,6 +1417,7 @@ export declare interface CandidateShape {
|
|
|
1323
1417
|
readonly windowed?: boolean;
|
|
1324
1418
|
}
|
|
1325
1419
|
|
|
1420
|
+
/* Excluded from this release type: canonicalize */
|
|
1326
1421
|
export { CaughtUpVerdict }
|
|
1327
1422
|
|
|
1328
1423
|
/** A CDC change event, structurally — what the reactive engine already emits
|
|
@@ -1422,6 +1517,28 @@ export { clearRetentions }
|
|
|
1422
1517
|
/** Clear the process-wide handle (test teardown). */
|
|
1423
1518
|
export declare const clearSystemStoreHandle: () => void;
|
|
1424
1519
|
|
|
1520
|
+
/**
|
|
1521
|
+
* Collect the declared rules from the discovered tables.
|
|
1522
|
+
*
|
|
1523
|
+
* `tables` is the boot's table set. Each column carrying a `pluginRef` spec
|
|
1524
|
+
* contributes one rule, with the target resolved through the plugin's exported
|
|
1525
|
+
* handle — so a rename carries the rule.
|
|
1526
|
+
*
|
|
1527
|
+
* Throws when a `pluginRef` names a table nothing registered: a declared rule
|
|
1528
|
+
* against an absent plugin would sit there looking enforced and never fire,
|
|
1529
|
+
* which is the exact failure this framework has shipped too often. The message
|
|
1530
|
+
* names both sides so the fix is obvious (install the plugin, or drop the
|
|
1531
|
+
* column).
|
|
1532
|
+
*/
|
|
1533
|
+
export declare const collectPluginRefRules: (tables: ReadonlyArray<{
|
|
1534
|
+
readonly name: string;
|
|
1535
|
+
readonly columns: Record<string, unknown>;
|
|
1536
|
+
}>, specOf: (column: unknown) => {
|
|
1537
|
+
target: () => unknown;
|
|
1538
|
+
orphanPolicy: PluginRefOrphanPolicy;
|
|
1539
|
+
onSoftDelete: boolean;
|
|
1540
|
+
} | undefined, registered: ReadonlySet<string>) => ReadonlyArray<PluginRefRule>;
|
|
1541
|
+
|
|
1425
1542
|
/** Parse + return the compiled `Cron` for a definition. Throws the
|
|
1426
1543
|
* same way `defineSchedule` validated — callers that already hold a
|
|
1427
1544
|
* branded definition can trust this won't throw. */
|
|
@@ -1929,7 +2046,13 @@ export declare const defineAggregate: <Row>(input: AggregateDefinitionInput<Row>
|
|
|
1929
2046
|
*/
|
|
1930
2047
|
export declare const defineConnection: <D extends ConnectionDefinition>(definition: D) => D;
|
|
1931
2048
|
|
|
1932
|
-
export declare const defineEventTrigger: <EventPayload = unknown, WorkflowPayload = EventPayload>(spec: Omit<WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>, "_tag">
|
|
2049
|
+
export declare const defineEventTrigger: <EventPayload = unknown, WorkflowPayload = EventPayload>(spec: Omit<WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>, "_tag"> | (Omit<WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>, "_tag" | "event"> & {
|
|
2050
|
+
/** A declared event. Its `name` becomes the matched event. */
|
|
2051
|
+
readonly on: {
|
|
2052
|
+
readonly kind: "event";
|
|
2053
|
+
readonly name: string;
|
|
2054
|
+
};
|
|
2055
|
+
})) => WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>;
|
|
1933
2056
|
|
|
1934
2057
|
/**
|
|
1935
2058
|
* Type a procedure executor against its descriptor's `output` (and `input`).
|
|
@@ -2116,6 +2239,27 @@ export declare class Dispatcher {
|
|
|
2116
2239
|
onSubscriptionsChange(listener: () => void): () => void;
|
|
2117
2240
|
/** Tear down the change-event listener. Call on server shutdown. */
|
|
2118
2241
|
close(): void;
|
|
2242
|
+
/**
|
|
2243
|
+
* Re-run EVERY live subscription, because this replica proved it missed
|
|
2244
|
+
* changes it can never recover.
|
|
2245
|
+
*
|
|
2246
|
+
* The broadcast bus detects a serial gap from a peer; pub/sub keeps no log,
|
|
2247
|
+
* so the lost ChangeEvents are gone. That does not matter, and the reason it
|
|
2248
|
+
* does not matter is the whole design: a live query is IDEMPOTENT. Re-running
|
|
2249
|
+
* one is always safe and always lands on the truth, so a proven loss is
|
|
2250
|
+
* repaired by refreshing rather than by replaying.
|
|
2251
|
+
*
|
|
2252
|
+
* Without this a dropped message leaves a client stale FOREVER — its socket
|
|
2253
|
+
* never breaks, so the client-side reconnect never fires, and its query does
|
|
2254
|
+
* not re-run until something else happens to touch the same table. On a quiet
|
|
2255
|
+
* table that is never, and the only symptom is a user saying the page did not
|
|
2256
|
+
* update.
|
|
2257
|
+
*
|
|
2258
|
+
* Deliberately blunt. It re-queries every subscription rather than reasoning
|
|
2259
|
+
* about which tables the lost changes touched — we do not know, and guessing
|
|
2260
|
+
* narrower would reintroduce exactly the silent staleness this repairs.
|
|
2261
|
+
*/
|
|
2262
|
+
refreshAll(reason: string): Promise<void>;
|
|
2119
2263
|
private handleChange;
|
|
2120
2264
|
}
|
|
2121
2265
|
|
|
@@ -2261,6 +2405,320 @@ export declare const enterRequestLoader: (loader: DataLoader) => void;
|
|
|
2261
2405
|
* Promise-wrapped to satisfy the async contract. */
|
|
2262
2406
|
export declare const envSecretsBackend: SecretsBackend;
|
|
2263
2407
|
|
|
2408
|
+
/**
|
|
2409
|
+
* How many deliveries one client may fall behind before the oldest are dropped.
|
|
2410
|
+
*
|
|
2411
|
+
* Sized to match the bus's ring: a client that falls further behind than the
|
|
2412
|
+
* server retains could not be made whole on reconnect anyway, so a deeper queue
|
|
2413
|
+
* would only delay the same honest answer.
|
|
2414
|
+
*/
|
|
2415
|
+
export declare const EVENT_CLIENT_QUEUE_SIZE = 64;
|
|
2416
|
+
|
|
2417
|
+
/**
|
|
2418
|
+
* The queue depth for a `latest` event: exactly one.
|
|
2419
|
+
*
|
|
2420
|
+
* Not a smaller buffer — a DIFFERENT contract. A depth of one on a sliding queue
|
|
2421
|
+
* means a delivery arriving while another is pending REPLACES it, which is what
|
|
2422
|
+
* "only the current value matters" means when the consumer is slow. Anything
|
|
2423
|
+
* deeper would hand a 60Hz display a backlog to work through before it could
|
|
2424
|
+
* reach the state it actually wanted to show.
|
|
2425
|
+
*/
|
|
2426
|
+
export declare const EVENT_LATEST_QUEUE_SIZE = 1;
|
|
2427
|
+
|
|
2428
|
+
export declare class EventBus {
|
|
2429
|
+
private readonly routes;
|
|
2430
|
+
private readonly origin;
|
|
2431
|
+
private readonly ringSize;
|
|
2432
|
+
private readonly ringTtlMs;
|
|
2433
|
+
private readonly maxRoutes;
|
|
2434
|
+
private readonly publishRemote;
|
|
2435
|
+
private readonly deliveryOf;
|
|
2436
|
+
/** Live local subscriber count per EVENT — the basis of channel interest. */
|
|
2437
|
+
private readonly interest;
|
|
2438
|
+
private readonly interestListeners;
|
|
2439
|
+
private readonly now;
|
|
2440
|
+
constructor(options: EventBusOptions);
|
|
2441
|
+
/** This instance's id — the `origin` on everything it publishes. */
|
|
2442
|
+
get instanceOrigin(): string;
|
|
2443
|
+
/**
|
|
2444
|
+
* Be told when this instance gains its FIRST or loses its LAST local
|
|
2445
|
+
* subscriber for an event.
|
|
2446
|
+
*
|
|
2447
|
+
* The transport uses it to hold a cross-instance subscription only while
|
|
2448
|
+
* somebody here is listening. Without it every replica receives, decodes and
|
|
2449
|
+
* materialises a route for every event of every peer — including the ones it
|
|
2450
|
+
* serves no clients for, which for a high-rate event is the bulk of the work
|
|
2451
|
+
* it does and none of the work it needs.
|
|
2452
|
+
*
|
|
2453
|
+
* Registering REPLAYS the currently-active events synchronously, because the
|
|
2454
|
+
* transport is attached after the bus exists: a subscriber that arrived in
|
|
2455
|
+
* between would otherwise be invisible until it left and came back.
|
|
2456
|
+
*/
|
|
2457
|
+
onInterestChange(listener: (event: string, active: boolean) => void): () => void;
|
|
2458
|
+
private shiftInterest;
|
|
2459
|
+
private notifyInterest;
|
|
2460
|
+
/** Events this instance currently has at least one local subscriber for. */
|
|
2461
|
+
activeEvents(): ReadonlyArray<string>;
|
|
2462
|
+
/**
|
|
2463
|
+
* One event's declared delivery semantics — what the bridge sizes its queue
|
|
2464
|
+
* from, so the two cannot disagree about whether a drop is a loss.
|
|
2465
|
+
*/
|
|
2466
|
+
deliveryFor(event: string): EventDeliverySemantics;
|
|
2467
|
+
private state;
|
|
2468
|
+
/**
|
|
2469
|
+
* Drop the coldest IDLE routes when over the ceiling.
|
|
2470
|
+
*
|
|
2471
|
+
* A route with live listeners is never evicted regardless of age: evicting it
|
|
2472
|
+
* would silently stop delivering to a connected subscriber, which is the exact
|
|
2473
|
+
* class of failure this bus exists to make impossible. Only retention is
|
|
2474
|
+
* sacrificed under pressure, never delivery.
|
|
2475
|
+
*/
|
|
2476
|
+
private evictIfOverCapacity;
|
|
2477
|
+
/**
|
|
2478
|
+
* Drop what the ring may no longer replay.
|
|
2479
|
+
*
|
|
2480
|
+
* IN PLACE. The obvious version — `ring = ring.slice(drop)` — allocates and
|
|
2481
|
+
* copies the whole ring on EVERY publish once it is full, which measured as
|
|
2482
|
+
* the single largest cost in the publish path (5.8µs with a full ring against
|
|
2483
|
+
* 0.35µs for everything else combined: the key encoding, the route, and the
|
|
2484
|
+
* size gate's JSON put together). `splice` mutates, so a saturated route costs
|
|
2485
|
+
* the same as an empty one.
|
|
2486
|
+
*
|
|
2487
|
+
* Worth keeping in mind if this is ever rewritten as a true circular buffer:
|
|
2488
|
+
* the win here was removing the ALLOCATION, not the copy. A 64-element
|
|
2489
|
+
* `splice(0, 1)` is cheap; a 64-element fresh array every 5 microseconds is
|
|
2490
|
+
* what the garbage collector notices.
|
|
2491
|
+
*/
|
|
2492
|
+
private pruneRing;
|
|
2493
|
+
/**
|
|
2494
|
+
* Publish an already-validated, already-size-checked envelope body.
|
|
2495
|
+
*
|
|
2496
|
+
* Validation lives at the `ctx.events.publish` seam rather than here because
|
|
2497
|
+
* that is where the descriptor is — the bus deals in routes and bytes.
|
|
2498
|
+
*/
|
|
2499
|
+
publish(input: {
|
|
2500
|
+
readonly tenantId: string | null;
|
|
2501
|
+
readonly event: string;
|
|
2502
|
+
readonly key: unknown;
|
|
2503
|
+
readonly payload: unknown;
|
|
2504
|
+
}): EventEnvelope;
|
|
2505
|
+
/**
|
|
2506
|
+
* Take a delivery published by ANOTHER instance.
|
|
2507
|
+
*
|
|
2508
|
+
* Ignores our own origin echoed back: the local subscribers were already
|
|
2509
|
+
* served synchronously at publish time, so accepting it here would deliver
|
|
2510
|
+
* twice and break the serial contract (`n` would repeat).
|
|
2511
|
+
*/
|
|
2512
|
+
injectRemote(envelope: EventEnvelope): void;
|
|
2513
|
+
private accept;
|
|
2514
|
+
/**
|
|
2515
|
+
* Subscribe to one route. Returns the unsubscribe.
|
|
2516
|
+
*
|
|
2517
|
+
* The listener is invoked synchronously from `publish`, in registration order.
|
|
2518
|
+
* Anything the subscriber needs to do slowly belongs behind its own queue —
|
|
2519
|
+
* see the bridge.
|
|
2520
|
+
*/
|
|
2521
|
+
subscribe(input: {
|
|
2522
|
+
readonly tenantId: string | null;
|
|
2523
|
+
readonly event: string;
|
|
2524
|
+
readonly key: unknown;
|
|
2525
|
+
readonly listener: EventListener_2;
|
|
2526
|
+
readonly options?: EventSubscribeOptions | undefined;
|
|
2527
|
+
/**
|
|
2528
|
+
* Called SYNCHRONOUSLY at attach with the route's current watermarks.
|
|
2529
|
+
*
|
|
2530
|
+
* It is how a consumer learns where the stream stood the instant it joined,
|
|
2531
|
+
* with no window for a publish to slip in between reading and subscribing.
|
|
2532
|
+
* The bridge needs it because a delivery dropped BEFORE the client's first
|
|
2533
|
+
* read would otherwise be invisible: with no baseline, the first serial it
|
|
2534
|
+
* sees looks like a starting point rather than evidence of what came before.
|
|
2535
|
+
*/
|
|
2536
|
+
readonly onAttach?: ((watermark: ReadonlyMap<string, number>) => void) | undefined;
|
|
2537
|
+
}): () => void;
|
|
2538
|
+
/**
|
|
2539
|
+
* Serve a re-attach: replay what the ring still holds, and report — exactly —
|
|
2540
|
+
* what it does not.
|
|
2541
|
+
*
|
|
2542
|
+
* The subtraction is the point. `owed` is what the client should have received
|
|
2543
|
+
* since its last serial (from the watermark, which survives ring eviction);
|
|
2544
|
+
* `replayed` is what we can actually hand over. An origin the client never
|
|
2545
|
+
* mentioned is one that appeared while it was away, so everything retained
|
|
2546
|
+
* from it is owed. A `gap` is emitted BEFORE the replay so a consumer reading
|
|
2547
|
+
* in order learns it is behind before it starts processing.
|
|
2548
|
+
*/
|
|
2549
|
+
private replayForResume;
|
|
2550
|
+
stats(): EventBusStats;
|
|
2551
|
+
/** Per-route live numbers for the inspect surface + dashboard. */
|
|
2552
|
+
snapshot(): ReadonlyArray<EventRouteSnapshot>;
|
|
2553
|
+
/** Drop every route, listener and buffer. For tests and shutdown. */
|
|
2554
|
+
clear(): void;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
export declare interface EventBusOptions {
|
|
2558
|
+
/** This instance's id. Two processes must never share one. */
|
|
2559
|
+
readonly origin: string;
|
|
2560
|
+
/**
|
|
2561
|
+
* Ring depth per route. 64 by default — sized from what comparable products
|
|
2562
|
+
* allow (Ably caps replay at 100 messages / 2 minutes; Supabase at 25), i.e.
|
|
2563
|
+
* tens of messages and minutes. Anything materially larger is a durable queue
|
|
2564
|
+
* with extra steps, and the framework already has one (`ctx.outbox`).
|
|
2565
|
+
*/
|
|
2566
|
+
readonly ringSize?: number;
|
|
2567
|
+
/** How long a ring entry stays eligible for replay. 5 minutes by default. */
|
|
2568
|
+
readonly ringTtlMs?: number;
|
|
2569
|
+
/**
|
|
2570
|
+
* Hard ceiling on how many routes retain a ring at once. A route is an
|
|
2571
|
+
* app-controlled cardinality (`{userId}` in a key is one route per user), so
|
|
2572
|
+
* without a ceiling an unbounded key space is an unbounded memory leak in a
|
|
2573
|
+
* long-lived process — the same failure shape as an append-only table with no
|
|
2574
|
+
* sweep, one layer up. Oldest-touched routes are evicted first.
|
|
2575
|
+
*/
|
|
2576
|
+
readonly maxRoutes?: number;
|
|
2577
|
+
/** Hand-off to the cross-instance transport. Absent ⇒ single-instance. */
|
|
2578
|
+
readonly publishRemote?: (envelope: EventEnvelope) => void;
|
|
2579
|
+
/**
|
|
2580
|
+
* What a declared event's name means for retention and loss reporting.
|
|
2581
|
+
*
|
|
2582
|
+
* A lookup rather than a field on `subscribe`, because the answer must be the
|
|
2583
|
+
* SAME for every subscriber of one event and for the publisher — a per-call
|
|
2584
|
+
* argument is a per-call opportunity to disagree, and the disagreement would
|
|
2585
|
+
* show up as one client being told it missed messages another was told it did
|
|
2586
|
+
* not. Wired once from the declared events at boot.
|
|
2587
|
+
*/
|
|
2588
|
+
readonly deliveryOf?: (event: string) => EventDeliverySemantics | undefined;
|
|
2589
|
+
readonly now?: () => number;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
export declare interface EventBusStats {
|
|
2593
|
+
readonly routes: number;
|
|
2594
|
+
readonly subscribers: number;
|
|
2595
|
+
readonly buffered: number;
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
/** What a subscriber is told. `gap` is only ever emitted with a PROVEN count. */
|
|
2599
|
+
export declare type EventDelivery = {
|
|
2600
|
+
readonly kind: 'event';
|
|
2601
|
+
readonly envelope: EventEnvelope;
|
|
2602
|
+
/**
|
|
2603
|
+
* This route's watermark for `envelope.origin` IMMEDIATELY BEFORE this
|
|
2604
|
+
* envelope was accepted — the one number a subscriber cannot infer, and
|
|
2605
|
+
* without which it cannot tell two opposite situations apart.
|
|
2606
|
+
*
|
|
2607
|
+
* Both look identical from a subscriber's seat: an origin absent from the
|
|
2608
|
+
* attach watermark, and a first delivery carrying a high serial.
|
|
2609
|
+
*
|
|
2610
|
+
* prior > 0 this instance HAD been receiving that origin, so the
|
|
2611
|
+
* serials between what we forwarded and this one existed
|
|
2612
|
+
* here and were lost on the way to this subscriber.
|
|
2613
|
+
* prior = 0 this instance had accepted nothing from that origin on
|
|
2614
|
+
* this route, so nothing local could have been dropped. A
|
|
2615
|
+
* replica that started — or joined this event's channel —
|
|
2616
|
+
* while a peer was already at serial 5000 is in this case.
|
|
2617
|
+
*
|
|
2618
|
+
* Reading the second as the first is what made a freshly-started replica
|
|
2619
|
+
* announce a 5000-message loss to every client on its first delivery.
|
|
2620
|
+
*/
|
|
2621
|
+
readonly prior: number;
|
|
2622
|
+
} | {
|
|
2623
|
+
readonly kind: 'gap';
|
|
2624
|
+
readonly missed: number;
|
|
2625
|
+
readonly reason: 'buffer' | 'resume';
|
|
2626
|
+
};
|
|
2627
|
+
|
|
2628
|
+
/** One delivery, as it travels locally and across instances. */
|
|
2629
|
+
export declare interface EventEnvelope {
|
|
2630
|
+
/** `tenant · event · key`, NUL-separated — built by `eventRoute`, read with
|
|
2631
|
+
* `parseEventRoute`. Never construct or split one by hand. */
|
|
2632
|
+
readonly route: string;
|
|
2633
|
+
/** Declared event name, carried separately so a consumer of the raw envelope
|
|
2634
|
+
* (broadcast bridge, inspect, metrics) needn't re-parse the route. */
|
|
2635
|
+
readonly event: string;
|
|
2636
|
+
/** Publishing instance. Serials are only comparable WITHIN one origin. */
|
|
2637
|
+
readonly origin: string;
|
|
2638
|
+
/** Monotonic per (origin, route). */
|
|
2639
|
+
readonly n: number;
|
|
2640
|
+
readonly emittedAt: number;
|
|
2641
|
+
readonly payload: unknown;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
declare type EventListener_2 = (delivery: EventDelivery) => void;
|
|
2645
|
+
export { EventListener_2 as EventListener }
|
|
2646
|
+
|
|
2647
|
+
export declare const eventMetricTagCacheSizeForTests: () => number;
|
|
2648
|
+
|
|
2649
|
+
/** What `publish` needs to know about the caller. */
|
|
2650
|
+
export declare interface EventPublishContext {
|
|
2651
|
+
readonly bus: EventBus;
|
|
2652
|
+
/** From the SUBJECT, never from a caller argument. */
|
|
2653
|
+
readonly tenantId: string | null;
|
|
2654
|
+
/**
|
|
2655
|
+
* Defer until commit, when a transaction is open.
|
|
2656
|
+
*
|
|
2657
|
+
* Present ⇒ the caller is inside `transactional()` and the delivery is queued
|
|
2658
|
+
* with the ChangeEvents the transactional view already buffers, so it is
|
|
2659
|
+
* flushed by the same `commitEvents()` that publishes row changes — and
|
|
2660
|
+
* discarded entirely on rollback. Absent ⇒ deliver now.
|
|
2661
|
+
*
|
|
2662
|
+
* A display reacting to a game start the database rolled back is not a race
|
|
2663
|
+
* that is unlikely, it is one that happens on every constraint violation.
|
|
2664
|
+
*/
|
|
2665
|
+
readonly deferToCommit?: ((deliver: () => void) => void) | undefined;
|
|
2666
|
+
/**
|
|
2667
|
+
* The DURABLE audience — the workflow-trigger path.
|
|
2668
|
+
*
|
|
2669
|
+
* `publish` is one declaration reaching several audiences, and this is the
|
|
2670
|
+
* seam for the audience that outlives the process: it records the event and
|
|
2671
|
+
* starts whatever workflows are triggered by it. Absent when the app declares
|
|
2672
|
+
* no triggers, so an app that only fans out to clients pays nothing.
|
|
2673
|
+
*
|
|
2674
|
+
* Deliberately fire-and-forget from `publish`'s point of view: a trigger that
|
|
2675
|
+
* fails must not fail the mutation that published, for the same reason a
|
|
2676
|
+
* broker outage does not. The trigger's own delivery row records the failure.
|
|
2677
|
+
*/
|
|
2678
|
+
readonly onPublished?: ((eventName: string, payload: unknown, descriptor: unknown) => void) | undefined;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
export declare interface EventPublisher {
|
|
2682
|
+
publish<Name extends string, Key extends Schema.Schema.Any, Payload extends Schema.Schema.Any>(descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, payload: Schema.Schema.Type<Payload>): AwaitableEffect<EventPublishResult, EventPublishError>;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
export declare type EventPublishError = EventPayloadInvalid | EventKeyInvalid | EventPayloadTooLarge;
|
|
2686
|
+
|
|
2687
|
+
/**
|
|
2688
|
+
* The `ctx.events.publish` facade, bound to one request's tenant + transaction.
|
|
2689
|
+
*
|
|
2690
|
+
* Typed at the call site from the descriptor alone: `key` and `payload` are both
|
|
2691
|
+
* inferred, so a wrong field name is a `tsc` error where it is written rather
|
|
2692
|
+
* than a handler that never fires.
|
|
2693
|
+
*/
|
|
2694
|
+
/** What a publish resolves to, in either spelling. */
|
|
2695
|
+
export declare interface EventPublishResult {
|
|
2696
|
+
readonly n: number;
|
|
2697
|
+
readonly deferred: boolean;
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
/** A declared event's live numbers, for the inspect surface. */
|
|
2701
|
+
export declare interface EventRouteSnapshot {
|
|
2702
|
+
readonly route: string;
|
|
2703
|
+
readonly event: string;
|
|
2704
|
+
readonly subscribers: number;
|
|
2705
|
+
readonly buffered: number;
|
|
2706
|
+
readonly lastEmittedAt: number | undefined;
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
/**
|
|
2710
|
+
* The INTERNAL durable-event facade.
|
|
2711
|
+
*
|
|
2712
|
+
* No longer `ctx.events` — an app cannot reach this. `ctx.events.publish` is
|
|
2713
|
+
* the only emitter, and it drives this one as its durable audience, so a
|
|
2714
|
+
* workflow trigger and a client subscriber can never disagree about whether the
|
|
2715
|
+
* event happened.
|
|
2716
|
+
*
|
|
2717
|
+
* The string form survives here, one layer down, because the durable path
|
|
2718
|
+
* matches triggers BY NAME and the name is what a `defineEvent` descriptor
|
|
2719
|
+
* supplies. What was removed is the ability for an app to invent a name that
|
|
2720
|
+
* nothing declared — which is the whole defect.
|
|
2721
|
+
*/
|
|
2264
2722
|
export declare interface EventsAppContext {
|
|
2265
2723
|
emit<Payload = unknown>(eventName: string, data: Payload, options?: WorkflowEventEmitOptions): Promise<WorkflowEventEmitResult>;
|
|
2266
2724
|
}
|
|
@@ -2280,6 +2738,20 @@ export declare interface EventsFacadeOptions {
|
|
|
2280
2738
|
readonly makeId?: (prefix: 'wfe' | 'wfed') => string;
|
|
2281
2739
|
}
|
|
2282
2740
|
|
|
2741
|
+
export declare interface EventSubscribeOptions {
|
|
2742
|
+
/**
|
|
2743
|
+
* Where this client left off, per origin. PRESENT means re-attach and the
|
|
2744
|
+
* client is owed continuity; ABSENT means a first attach, which starts empty.
|
|
2745
|
+
*
|
|
2746
|
+
* The distinction cannot be inferred server-side — a reconnecting socket and a
|
|
2747
|
+
* freshly mounted component look identical from here — so the client states
|
|
2748
|
+
* it, and the framework's client does that automatically.
|
|
2749
|
+
*/
|
|
2750
|
+
readonly resume?: ReadonlyArray<EventResumePoint> | undefined;
|
|
2751
|
+
/** Descriptor opt-in: deliver what the ring holds even on a FIRST attach. */
|
|
2752
|
+
readonly rewind?: boolean | undefined;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2283
2755
|
/** Exchange an authorization code (from the callback) for a token set. */
|
|
2284
2756
|
export declare const exchangeAuthorizationCode: (definition: OAuth2ConnectionDefinition, input: {
|
|
2285
2757
|
readonly code: string;
|
|
@@ -2428,6 +2900,9 @@ export declare const formatServerOnlyLeaks: (leaks: ReadonlyArray<ServerOnlyLeak
|
|
|
2428
2900
|
/** Serialise a span's context as a `traceparent` header value. */
|
|
2429
2901
|
export declare const formatTraceparent: (ctx: TraceContext) => string;
|
|
2430
2902
|
|
|
2903
|
+
/** The boot warning for unresolved sources, or `undefined` when there are none. */
|
|
2904
|
+
export declare const formatUnresolvedSources: (found: ReadonlyArray<UnresolvedSource>) => string | undefined;
|
|
2905
|
+
|
|
2431
2906
|
/** One captured row change within a mutation invocation. */
|
|
2432
2907
|
export declare interface ForwardChange {
|
|
2433
2908
|
readonly table: string;
|
|
@@ -2740,6 +3215,47 @@ export { inspectWorkflow }
|
|
|
2740
3215
|
*/
|
|
2741
3216
|
export declare const installPolicyGuardResolver: () => void;
|
|
2742
3217
|
|
|
3218
|
+
/**
|
|
3219
|
+
* The live membership of this deployment, as THIS instance can observe it.
|
|
3220
|
+
*
|
|
3221
|
+
* Transport-agnostic, like the event bus: `announce` is injected and remote
|
|
3222
|
+
* heartbeats arrive through `receive`. That keeps it testable with no broker and
|
|
3223
|
+
* makes the cross-instance wiring one place rather than a concern spread through
|
|
3224
|
+
* the registry.
|
|
3225
|
+
*/
|
|
3226
|
+
export declare class InstanceMembership {
|
|
3227
|
+
private readonly members;
|
|
3228
|
+
private readonly listeners;
|
|
3229
|
+
private readonly instanceId;
|
|
3230
|
+
private readonly startedAt;
|
|
3231
|
+
private readonly meta;
|
|
3232
|
+
private readonly heartbeatMs;
|
|
3233
|
+
private readonly downAfterMs;
|
|
3234
|
+
private readonly announce;
|
|
3235
|
+
private readonly now;
|
|
3236
|
+
private timer;
|
|
3237
|
+
constructor(options: MembershipOptions);
|
|
3238
|
+
get id(): string;
|
|
3239
|
+
/** Begin announcing, and start expiring the silent. */
|
|
3240
|
+
start(): void;
|
|
3241
|
+
stop(): void;
|
|
3242
|
+
private beat;
|
|
3243
|
+
/**
|
|
3244
|
+
* Take a heartbeat from another instance.
|
|
3245
|
+
*
|
|
3246
|
+
* Our own id echoed back by the broker is ignored: self-liveness is local, and
|
|
3247
|
+
* accepting the echo would let a broker outage look like a self-death.
|
|
3248
|
+
*/
|
|
3249
|
+
receive(heartbeat: MembershipHeartbeat): void;
|
|
3250
|
+
/** Drop instances that have gone silent past the threshold. */
|
|
3251
|
+
sweep(): void;
|
|
3252
|
+
/** Everyone this instance currently believes is alive, self included. */
|
|
3253
|
+
list(): ReadonlyArray<MemberSnapshot>;
|
|
3254
|
+
/** Subscribe to membership changes. Returns the unsubscribe. */
|
|
3255
|
+
onChange(listener: MembershipListener): () => void;
|
|
3256
|
+
private emit;
|
|
3257
|
+
}
|
|
3258
|
+
|
|
2743
3259
|
/** Fire every registered interrupt for `clientId` (called on disconnect). */
|
|
2744
3260
|
export declare const interruptConnectionStreams: (clientId: number) => void;
|
|
2745
3261
|
|
|
@@ -2820,6 +3336,20 @@ export declare const isSafeToApply: (current: Record<string, unknown> | null | u
|
|
|
2820
3336
|
|
|
2821
3337
|
export declare const isScheduleDefinition: (v: unknown) => v is BrandedScheduleDefinition;
|
|
2822
3338
|
|
|
3339
|
+
/**
|
|
3340
|
+
* Did this change soft-delete the row?
|
|
3341
|
+
*
|
|
3342
|
+
* A soft delete is NOT a `delete` event — it is an `update` that sets
|
|
3343
|
+
* `deletedAt`. That distinction is why the first version of `onSoftDelete` was
|
|
3344
|
+
* inert in every case: the matcher only looked at `op === 'delete'`, which a
|
|
3345
|
+
* soft delete never is, so the option existed and could not fire. Exactly the
|
|
3346
|
+
* defect this whole round was about, one level down.
|
|
3347
|
+
*
|
|
3348
|
+
* The transition matters, not the value: a row that was already deleted and is
|
|
3349
|
+
* updated again must not re-fire the rule.
|
|
3350
|
+
*/
|
|
3351
|
+
export declare const isSoftDelete: (change: PluginRefChange) => boolean;
|
|
3352
|
+
|
|
2823
3353
|
/** A freshly issued key — `token` is shown ONCE and never stored in clear. */
|
|
2824
3354
|
export declare interface IssuedApiKey {
|
|
2825
3355
|
readonly id: string;
|
|
@@ -3101,6 +3631,17 @@ export declare const makeAppAccess: (subject: Subject) => AppAccess;
|
|
|
3101
3631
|
* fall back to `database`. */
|
|
3102
3632
|
export declare const makeAsyncKv: ({ store, env }: KvFacadeOptions) => Promise<KvFacade>;
|
|
3103
3633
|
|
|
3634
|
+
/**
|
|
3635
|
+
* Detect a serial jump per origin, and report it as a proven count.
|
|
3636
|
+
*
|
|
3637
|
+
* Stateful across one client's stream, which is why it is built per
|
|
3638
|
+
* subscription rather than shared.
|
|
3639
|
+
*/
|
|
3640
|
+
export declare const makeBufferGapDetector: (attachWatermark?: ReadonlyMap<string, number>) => (event: {
|
|
3641
|
+
readonly origin: string;
|
|
3642
|
+
readonly n: number;
|
|
3643
|
+
}, prior?: number) => number;
|
|
3644
|
+
|
|
3104
3645
|
/**
|
|
3105
3646
|
* Build a SpanProcessor that maps every finished span to a flat record
|
|
3106
3647
|
* and hands it to `onSpanEnd`. The callback must not throw (a thrown
|
|
@@ -3151,6 +3692,8 @@ export declare const makeDisconnectConnectionExecutor: (deps: ConnectionBuiltinD
|
|
|
3151
3692
|
*/
|
|
3152
3693
|
export declare const makeEffectStoreLayer: (store: MutationStore) => Layer.Layer<EffectStore>;
|
|
3153
3694
|
|
|
3695
|
+
export declare const makeEventPublisher: (ctx: EventPublishContext) => EventPublisher;
|
|
3696
|
+
|
|
3154
3697
|
export declare const makeEventsFacade: (options: EventsFacadeOptions) => EventsAppContext;
|
|
3155
3698
|
|
|
3156
3699
|
/** Convenience: derive a key from a passphrase and build the cipher. */
|
|
@@ -3387,6 +3930,77 @@ export declare const materializeIvm: (shape: AggregateShape, state: IvmState) =>
|
|
|
3387
3930
|
readonly value: number | null;
|
|
3388
3931
|
}>;
|
|
3389
3932
|
|
|
3933
|
+
/** Told when the membership changes. `restarted` is a `left` + `joined` pair
|
|
3934
|
+
* for one id, reported as such so a consumer drops the old state rather than
|
|
3935
|
+
* resuming it. */
|
|
3936
|
+
export declare type MembershipEvent = {
|
|
3937
|
+
readonly kind: 'joined';
|
|
3938
|
+
readonly instanceId: string;
|
|
3939
|
+
readonly startedAt: number;
|
|
3940
|
+
} | {
|
|
3941
|
+
readonly kind: 'left';
|
|
3942
|
+
readonly instanceId: string;
|
|
3943
|
+
readonly reason: 'silent';
|
|
3944
|
+
} | {
|
|
3945
|
+
readonly kind: 'restarted';
|
|
3946
|
+
readonly instanceId: string;
|
|
3947
|
+
readonly startedAt: number;
|
|
3948
|
+
};
|
|
3949
|
+
|
|
3950
|
+
/** What one instance broadcasts about itself. */
|
|
3951
|
+
export declare interface MembershipHeartbeat {
|
|
3952
|
+
readonly instanceId: string;
|
|
3953
|
+
/**
|
|
3954
|
+
* When this PROCESS started, as the sender's own epoch ms.
|
|
3955
|
+
*
|
|
3956
|
+
* Carried as an IDENTITY, never compared to our clock. Its job is to make a
|
|
3957
|
+
* restart distinguishable from a hiccup: an instance that comes back with a
|
|
3958
|
+
* NEW `startedAt` is a fresh process whose owned state is gone, so a consumer
|
|
3959
|
+
* must drop what it held for the old one rather than resume it. A returning
|
|
3960
|
+
* instance with the SAME value only ever missed a few beats.
|
|
3961
|
+
*/
|
|
3962
|
+
readonly startedAt: number;
|
|
3963
|
+
/** Free-form, for a dashboard: region, version, pod name. */
|
|
3964
|
+
readonly meta?: Readonly<Record<string, string | number>> | undefined;
|
|
3965
|
+
}
|
|
3966
|
+
|
|
3967
|
+
export declare type MembershipListener = (event: MembershipEvent) => void;
|
|
3968
|
+
|
|
3969
|
+
export declare interface MembershipOptions {
|
|
3970
|
+
readonly instanceId: string;
|
|
3971
|
+
/** This process's start time. Defaults to now. */
|
|
3972
|
+
readonly startedAt?: number;
|
|
3973
|
+
readonly meta?: Readonly<Record<string, string | number>>;
|
|
3974
|
+
/**
|
|
3975
|
+
* How often to announce. 5 s by default — frequent enough that a death is
|
|
3976
|
+
* noticed inside a page load, cheap enough that a hundred replicas produce
|
|
3977
|
+
* twenty messages a second between them.
|
|
3978
|
+
*/
|
|
3979
|
+
readonly heartbeatMs?: number;
|
|
3980
|
+
/**
|
|
3981
|
+
* Silence after which an instance is PRESUMED down. Default 3× the heartbeat.
|
|
3982
|
+
*
|
|
3983
|
+
* Three missed beats rather than one, because a single missed beat is a GC
|
|
3984
|
+
* pause or a broker hiccup, and evicting on it would make a healthy cluster
|
|
3985
|
+
* flap — every flap dropping and re-adding that instance's owned state, which
|
|
3986
|
+
* a presence roster shows as everyone briefly leaving and coming back.
|
|
3987
|
+
*/
|
|
3988
|
+
readonly downAfterMs?: number;
|
|
3989
|
+
/** Send a heartbeat onto the transport. Absent ⇒ single-instance. */
|
|
3990
|
+
readonly announce?: (heartbeat: MembershipHeartbeat) => void;
|
|
3991
|
+
readonly now?: () => number;
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
export declare interface MemberSnapshot {
|
|
3995
|
+
readonly instanceId: string;
|
|
3996
|
+
readonly startedAt: number;
|
|
3997
|
+
readonly meta: Readonly<Record<string, string | number>> | undefined;
|
|
3998
|
+
/** Our clock, when we last heard from it. Never the sender's. */
|
|
3999
|
+
readonly lastHeardAt: number;
|
|
4000
|
+
/** True for this process. */
|
|
4001
|
+
readonly self: boolean;
|
|
4002
|
+
}
|
|
4003
|
+
|
|
3390
4004
|
/** In-memory store (tests + single-process dev). */
|
|
3391
4005
|
export declare const memoryApiKeyStore: () => ApiKeyStore;
|
|
3392
4006
|
|
|
@@ -3943,6 +4557,34 @@ export declare const payloadPropertyNames: (schema: Schema.Schema.Any) => {
|
|
|
3943
4557
|
readonly optional: ReadonlyArray<string>;
|
|
3944
4558
|
};
|
|
3945
4559
|
|
|
4560
|
+
/** A change as the post-commit channel reports it. */
|
|
4561
|
+
export declare interface PluginRefChange {
|
|
4562
|
+
readonly table: string;
|
|
4563
|
+
readonly op: 'insert' | 'update' | 'delete';
|
|
4564
|
+
readonly rowId: string;
|
|
4565
|
+
readonly tenantId?: string | null;
|
|
4566
|
+
/** The row before, when there was one — used to spot a soft delete. */
|
|
4567
|
+
readonly old?: Record<string, unknown> | null;
|
|
4568
|
+
/** The row after, when there is one. */
|
|
4569
|
+
readonly new?: Record<string, unknown> | null;
|
|
4570
|
+
}
|
|
4571
|
+
|
|
4572
|
+
/** One resolved rule: "when <targetTable> loses a row, do <policy> to <table.column>". */
|
|
4573
|
+
export declare interface PluginRefRule {
|
|
4574
|
+
readonly table: string;
|
|
4575
|
+
readonly column: string;
|
|
4576
|
+
readonly targetTable: string;
|
|
4577
|
+
readonly policy: PluginRefOrphanPolicy;
|
|
4578
|
+
readonly onSoftDelete: boolean;
|
|
4579
|
+
}
|
|
4580
|
+
|
|
4581
|
+
/** The store surface the enforcement needs. */
|
|
4582
|
+
export declare interface PluginRefStore {
|
|
4583
|
+
query: (descriptor: unknown) => Promise<ReadonlyArray<Record<string, unknown>>>;
|
|
4584
|
+
update: (table: string, id: string, patch: Record<string, unknown>) => Promise<unknown>;
|
|
4585
|
+
delete: (table: string, id: string) => Promise<unknown>;
|
|
4586
|
+
}
|
|
4587
|
+
|
|
3946
4588
|
/** The 3-arg signature a plugin sees on its bind-ctx. */
|
|
3947
4589
|
export declare type PluginScheduleCoordinated = (name: string, intervalMs: number, effect: () => void | Promise<void>) => CoordinatedScheduleHandle;
|
|
3948
4590
|
|
|
@@ -4063,6 +4705,17 @@ export declare interface PublicApiKey {
|
|
|
4063
4705
|
readonly metadata?: Readonly<Record<string, unknown>> | null;
|
|
4064
4706
|
}
|
|
4065
4707
|
|
|
4708
|
+
/**
|
|
4709
|
+
* Validate, size-check and publish one event.
|
|
4710
|
+
*
|
|
4711
|
+
* Returns the assigned serial so a caller (and the tests) can observe ordering
|
|
4712
|
+
* without reaching into the bus.
|
|
4713
|
+
*/
|
|
4714
|
+
export declare const publishEvent: <Name extends string, Key extends Schema.Schema.Any, Payload extends Schema.Schema.Any>(ctx: EventPublishContext, descriptor: EventDescriptor<Name, Key, Payload>, key: Schema.Schema.Type<Key>, payload: Schema.Schema.Type<Payload>) => Effect.Effect<{
|
|
4715
|
+
readonly n: number;
|
|
4716
|
+
readonly deferred: boolean;
|
|
4717
|
+
}, EventPublishError>;
|
|
4718
|
+
|
|
4066
4719
|
/** A typed builder or its descriptor — the single-row terminals accept either,
|
|
4067
4720
|
* so a call site never has to reach for `.descriptor` just to use them. */
|
|
4068
4721
|
export declare type QueryLike<R = Row_5> = QueryDescriptor<R> | {
|
|
@@ -4231,6 +4884,19 @@ export declare interface RecordedSample {
|
|
|
4231
4884
|
readonly status: 'ok' | 'error';
|
|
4232
4885
|
}
|
|
4233
4886
|
|
|
4887
|
+
/** `count` deliveries handed to subscribers of `event`. */
|
|
4888
|
+
export declare const recordEventDelivered: (event: string, count?: number) => void;
|
|
4889
|
+
|
|
4890
|
+
/** `count` deliveries PROVABLY lost — `buffer` (a slow consumer) or `resume`
|
|
4891
|
+
* (a re-attach older than the ring). Never called with a guess. */
|
|
4892
|
+
export declare const recordEventDropped: (event: string, reason: "buffer" | "resume", count?: number) => void;
|
|
4893
|
+
|
|
4894
|
+
/** One publish of `event`. */
|
|
4895
|
+
export declare const recordEventPublished: (event: string) => void;
|
|
4896
|
+
|
|
4897
|
+
/** Subscriber attached (+1) or detached (-1). */
|
|
4898
|
+
export declare const recordEventSubscribers: (event: string, delta: number) => void;
|
|
4899
|
+
|
|
4234
4900
|
/**
|
|
4235
4901
|
* Record one framework sample into the Effect metric registry. Synchronous —
|
|
4236
4902
|
* metric updates are pure, so `runSync` is cheap and safe to call from the
|
|
@@ -4238,6 +4904,15 @@ export declare interface RecordedSample {
|
|
|
4238
4904
|
*/
|
|
4239
4905
|
export declare const recordSample: (s: RecordedSample) => void;
|
|
4240
4906
|
|
|
4907
|
+
/**
|
|
4908
|
+
* Record one schedule firing into the registry: the run counter (by name +
|
|
4909
|
+
* status), the duration histogram, and — on success — the last-success gauge.
|
|
4910
|
+
* Synchronous, like the other recorders (metric updates are pure). Called from
|
|
4911
|
+
* the scheduler's success + failure arms so every cron in every app is covered
|
|
4912
|
+
* without per-handler wiring.
|
|
4913
|
+
*/
|
|
4914
|
+
export declare const recordScheduleRun: (r: ScheduleRunRecord) => void;
|
|
4915
|
+
|
|
4241
4916
|
/** Move the live-subscriptions gauge for `label`'s tag by `delta` (+1 open, -1 close). */
|
|
4242
4917
|
export declare const recordSubscriptionActive: (label: string, delta: number) => void;
|
|
4243
4918
|
|
|
@@ -4259,6 +4934,14 @@ export declare const recordTimelineEvent: (change: CdcChange & {
|
|
|
4259
4934
|
readonly tenantId?: string | null;
|
|
4260
4935
|
}) => void;
|
|
4261
4936
|
|
|
4937
|
+
/**
|
|
4938
|
+
* Record one workflow run's terminal outcome: the run counter (by name + status),
|
|
4939
|
+
* the duration histogram, and — on success — the last-success gauge. Synchronous,
|
|
4940
|
+
* like the other recorders. Called from the workflow run-recording seam through an
|
|
4941
|
+
* injected hook so `@voltro/workflow` keeps its no-`@voltro/runtime`-dep boundary.
|
|
4942
|
+
*/
|
|
4943
|
+
export declare const recordWorkflowRun: (r: WorkflowRunRecord) => void;
|
|
4944
|
+
|
|
4262
4945
|
/**
|
|
4263
4946
|
* Strip `redact` columns from a set of rows. Exposed on its own so a hand-written
|
|
4264
4947
|
* handler that isn't a plain CRUD read can still redact declaratively and be
|
|
@@ -4406,6 +5089,7 @@ export declare interface RegistryTableLike {
|
|
|
4406
5089
|
* it. MariaDB/Postgres reject an explicit value, so the MutationStore
|
|
4407
5090
|
* strips any the caller supplied before the INSERT reaches the dialect.
|
|
4408
5091
|
*/
|
|
5092
|
+
readonly versionColumn?: boolean;
|
|
4409
5093
|
readonly generatedAs?: {
|
|
4410
5094
|
readonly expr: string;
|
|
4411
5095
|
readonly stored: boolean;
|
|
@@ -4626,6 +5310,9 @@ export declare const resetComputedQueryCacheWarnings: () => void;
|
|
|
4626
5310
|
/** Test/dev-only — clear ALL overrides. Don't call from app code. */
|
|
4627
5311
|
export declare const _resetConnectionSubjectsForTest: () => void;
|
|
4628
5312
|
|
|
5313
|
+
/** Test seams for the tag-cache guard — the cache is module-private otherwise. */
|
|
5314
|
+
export declare const resetEventMetricTagCacheForTests: () => void;
|
|
5315
|
+
|
|
4629
5316
|
/** Drop everything recorded. For tests; not called by the runtime. */
|
|
4630
5317
|
export declare const resetObservedGraph: () => void;
|
|
4631
5318
|
|
|
@@ -5077,6 +5764,14 @@ export declare interface RpcServerOptions<Rpcs extends Rpc.Any> {
|
|
|
5077
5764
|
readonly maxRpcBodyBytes?: number;
|
|
5078
5765
|
}
|
|
5079
5766
|
|
|
5767
|
+
/**
|
|
5768
|
+
* Rules that apply to one change, or an empty list.
|
|
5769
|
+
*
|
|
5770
|
+
* Exported separately from the execution so the matching is testable without a
|
|
5771
|
+
* store: which rules fire is the part with the edge cases.
|
|
5772
|
+
*/
|
|
5773
|
+
export declare const rulesFor: (rules: ReadonlyArray<PluginRefRule>, change: PluginRefChange) => ReadonlyArray<PluginRefRule>;
|
|
5774
|
+
|
|
5080
5775
|
/**
|
|
5081
5776
|
* Run `fn` with `SubjectService` bound to a `system` subject and a
|
|
5082
5777
|
* system-scoped fluent `ctx.store`. Returns whatever `fn` returns.
|
|
@@ -5502,6 +6197,16 @@ export declare interface SchedulerLogger {
|
|
|
5502
6197
|
debug?: (msg: string, fields?: Record<string, unknown>) => void;
|
|
5503
6198
|
}
|
|
5504
6199
|
|
|
6200
|
+
export declare interface ScheduleRunRecord {
|
|
6201
|
+
readonly name: string;
|
|
6202
|
+
readonly status: 'succeeded' | 'failed' | 'skipped' | 'missed';
|
|
6203
|
+
readonly durationMs: number;
|
|
6204
|
+
/** UNIX SECONDS of completion — set only on a success (drives the last-success
|
|
6205
|
+
* gauge). Omit for a non-success; the gauge is deliberately not moved then, so
|
|
6206
|
+
* it keeps pointing at the last time the job actually worked. */
|
|
6207
|
+
readonly completedAtSec?: number;
|
|
6208
|
+
}
|
|
6209
|
+
|
|
5505
6210
|
/** Trigger driver — `self` runs an in-app supervised timer; `external`
|
|
5506
6211
|
* exposes an HTTP endpoint that an outside scheduler (k8s CronJob,
|
|
5507
6212
|
* AWS EventBridge, GCP Cloud Scheduler, …) invokes. */
|
|
@@ -5530,6 +6235,8 @@ export declare interface SchemaInfo {
|
|
|
5530
6235
|
* on every UPDATE against the merged post-update row.
|
|
5531
6236
|
*/
|
|
5532
6237
|
readonly computedFields: ReadonlyMap<string, (row: Readonly<Record<string, unknown>>) => unknown>;
|
|
6238
|
+
/** The column marked `.version()`, when the table declares one. */
|
|
6239
|
+
readonly versionColumn?: string;
|
|
5533
6240
|
/**
|
|
5534
6241
|
* DB-generated columns (`generatedAs`). The MutationStore strips any
|
|
5535
6242
|
* caller-supplied value for these before the INSERT — the engine computes
|
|
@@ -5579,6 +6286,10 @@ export declare interface SchemaRegistry {
|
|
|
5579
6286
|
/** Convenience: `hasMixin(table, VOLTRO_AUDIT_MIXIN_ID)`. */
|
|
5580
6287
|
hasAudit(table: string): boolean;
|
|
5581
6288
|
hasSoftDelete(table: string): boolean;
|
|
6289
|
+
/** True when the table carries `expires()`. Reads filter expired rows on
|
|
6290
|
+
* EVERY dialect from this flag — the postgres-only sweep is a separate,
|
|
6291
|
+
* optional half. */
|
|
6292
|
+
hasExpires(table: string): boolean;
|
|
5582
6293
|
hasTenant(table: string): boolean;
|
|
5583
6294
|
/** Field-existence check — used to skip stamping a column that the
|
|
5584
6295
|
* table doesn't actually declare (extra defensive). */
|
|
@@ -5606,6 +6317,14 @@ export declare interface SchemaRegistry {
|
|
|
5606
6317
|
* overwrites whatever the caller passed for that column.
|
|
5607
6318
|
*/
|
|
5608
6319
|
computedFields(table: string): ReadonlyMap<string, (row: Readonly<Record<string, unknown>>) => unknown>;
|
|
6320
|
+
/**
|
|
6321
|
+
* The table's optimistic-concurrency column, or `undefined`.
|
|
6322
|
+
*
|
|
6323
|
+
* Resolved ONCE at registration rather than scanned per write: an update is
|
|
6324
|
+
* the hot path, and a per-write `Object.entries` over every column of every
|
|
6325
|
+
* table is the kind of cost that only shows up under load.
|
|
6326
|
+
*/
|
|
6327
|
+
versionColumn(table: string): string | undefined;
|
|
5609
6328
|
/**
|
|
5610
6329
|
* DB-generated columns (`generatedAs`) for a table. Empty set if none.
|
|
5611
6330
|
* Read by MutationStore.stampedForInsert to STRIP any caller-supplied
|
|
@@ -6056,6 +6775,26 @@ export declare interface SubscribeContext {
|
|
|
6056
6775
|
* workflow" belongs in a `*.reaction.tsx`, whose `act` is crash-safe.
|
|
6057
6776
|
*/
|
|
6058
6777
|
readonly store: FluentStore;
|
|
6778
|
+
/**
|
|
6779
|
+
* Publish a declared event from a table change — the bridge between the two
|
|
6780
|
+
* axes.
|
|
6781
|
+
*
|
|
6782
|
+
* A row changing and a thing happening are not the same statement, and most
|
|
6783
|
+
* of the time only one of them is what a client cares about: nobody watches
|
|
6784
|
+
* `attendance` rows, they watch "attendance changed". Without this, an app
|
|
6785
|
+
* with a table-derived event has to publish from every mutation that touches
|
|
6786
|
+
* the table and remember to do it in the next one too — which is the
|
|
6787
|
+
* fail-open-by-omission shape a declaration exists to remove.
|
|
6788
|
+
*
|
|
6789
|
+
* Present only when the app declares at least one `defineEvent`, so reaching
|
|
6790
|
+
* for it in an app with none is a type error rather than a runtime undefined.
|
|
6791
|
+
*
|
|
6792
|
+
* BEST-EFFORT, like everything else in a subscriber: this fires AFTER the
|
|
6793
|
+
* commit, so there is no transaction to couple to and a failed publish cannot
|
|
6794
|
+
* roll anything back. If the event must not be lost, publish it from the
|
|
6795
|
+
* mutation instead — there it rides the commit.
|
|
6796
|
+
*/
|
|
6797
|
+
readonly publish?: EventPublisher['publish'];
|
|
6059
6798
|
}
|
|
6060
6799
|
|
|
6061
6800
|
/** Event shape passed to a subscriber handler. Same as the
|
|
@@ -6483,7 +7222,36 @@ export declare interface TransactionalStore {
|
|
|
6483
7222
|
transactional<T>(work: (tx: unknown) => Promise<T>): Promise<T>;
|
|
6484
7223
|
}
|
|
6485
7224
|
|
|
6486
|
-
|
|
7225
|
+
/**
|
|
7226
|
+
* Declare a workflow trigger.
|
|
7227
|
+
*
|
|
7228
|
+
* TWO forms, and the descriptor one is the one to use:
|
|
7229
|
+
*
|
|
7230
|
+
* ```ts
|
|
7231
|
+
* triggerWorkflow({ on: gameStarted, workflow: 'postGameReport' }) // typed
|
|
7232
|
+
* triggerWorkflow({ event: 'games.started', workflow: 'postGameReport' }) // string
|
|
7233
|
+
* ```
|
|
7234
|
+
*
|
|
7235
|
+
* `on:` takes a `defineEvent` descriptor and reads its NAME, so the trigger and
|
|
7236
|
+
* the producer cannot drift: rename the event and this call site moves with it,
|
|
7237
|
+
* where the string form silently stops matching and the workflow simply never
|
|
7238
|
+
* runs again. That failure is exactly what a consumer reported having with their
|
|
7239
|
+
* own string channels — `on-game-scores` beside `games/evo5/on-game-scores`,
|
|
7240
|
+
* both live, one dead since the day it was written — and it is the reason the
|
|
7241
|
+
* string form is going away rather than being kept as an alternative.
|
|
7242
|
+
*
|
|
7243
|
+
* It is ADDITIVE today on purpose. Shipping the descriptor form with the event
|
|
7244
|
+
* primitive means a third string namespace never comes into existence even
|
|
7245
|
+
* briefly; removing the string form is a separate, breaking change with its own
|
|
7246
|
+
* codemod, and nothing here has to be undone for it.
|
|
7247
|
+
*/
|
|
7248
|
+
export declare const triggerWorkflow: <EventPayload = unknown, WorkflowPayload = EventPayload>(spec: Omit<WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>, "_tag"> | (Omit<WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>, "_tag" | "event"> & {
|
|
7249
|
+
/** A declared event. Its `name` becomes the matched event. */
|
|
7250
|
+
readonly on: {
|
|
7251
|
+
readonly kind: "event";
|
|
7252
|
+
readonly name: string;
|
|
7253
|
+
};
|
|
7254
|
+
})) => WorkflowEventTriggerDefinition<EventPayload, WorkflowPayload>;
|
|
6487
7255
|
|
|
6488
7256
|
/**
|
|
6489
7257
|
* Atomically claim a pending wakeup for waking — CAS `pending → fired`.
|
|
@@ -6610,6 +7378,27 @@ export declare class UndoStack<E> {
|
|
|
6610
7378
|
};
|
|
6611
7379
|
}
|
|
6612
7380
|
|
|
7381
|
+
/** One `source:` entry that resolves to no declared table. */
|
|
7382
|
+
export declare interface UnresolvedSource {
|
|
7383
|
+
/** The procedure that declares it. */
|
|
7384
|
+
readonly procedure: string;
|
|
7385
|
+
/** The name as written. */
|
|
7386
|
+
readonly source: string;
|
|
7387
|
+
/** A declared table whose name is close — the rename case, usually. */
|
|
7388
|
+
readonly didYouMean: string | undefined;
|
|
7389
|
+
}
|
|
7390
|
+
|
|
7391
|
+
/**
|
|
7392
|
+
* Every `source:` across `procedures` that names no declared table.
|
|
7393
|
+
*
|
|
7394
|
+
* `declared` is the table-name set the boot already built. Procedures with no
|
|
7395
|
+
* `source` are skipped — reactivity is opt-in and its absence is not a defect.
|
|
7396
|
+
*/
|
|
7397
|
+
export declare const unresolvedSources: (procedures: ReadonlyArray<{
|
|
7398
|
+
readonly name: string;
|
|
7399
|
+
readonly source: string | ReadonlyArray<string> | undefined;
|
|
7400
|
+
}>, declared: ReadonlySet<string>) => ReadonlyArray<UnresolvedSource>;
|
|
7401
|
+
|
|
6613
7402
|
export declare class UpdateBuilder {
|
|
6614
7403
|
private readonly backend;
|
|
6615
7404
|
private readonly scope;
|
|
@@ -6672,6 +7461,11 @@ export declare const visibleRows: <Row extends {
|
|
|
6672
7461
|
*/
|
|
6673
7462
|
export declare const VOLTRO_AUDIT_MIXIN_ID: "voltro/audit";
|
|
6674
7463
|
|
|
7464
|
+
/** Re-declared here for the same reason the soft-delete id is: the registry
|
|
7465
|
+
* must not import the mixin (which pulls its column builders into every
|
|
7466
|
+
* consumer). Kept in step by `expiresMixinId.test.ts`. */
|
|
7467
|
+
export declare const VOLTRO_EXPIRES_MIXIN_ID: "voltro/expires";
|
|
7468
|
+
|
|
6675
7469
|
export declare const VOLTRO_SOFT_DELETE_MIXIN_ID: "voltro/softDelete";
|
|
6676
7470
|
|
|
6677
7471
|
export declare const VOLTRO_TENANT_MIXIN_ID: "voltro/tenant";
|
|
@@ -6955,6 +7749,17 @@ export declare interface WorkflowFacadeOptions {
|
|
|
6955
7749
|
* Inspect HTTP + the `voltro workflows retry` CLI delegate to the same
|
|
6956
7750
|
* implementation. */
|
|
6957
7751
|
readonly retry?: (runId: string, options: WorkflowRetryOptions | undefined) => Promise<WorkflowRetryResult>;
|
|
7752
|
+
/** Backs {@link WorkflowsAppContext.redrive}. Supplied by the CLI's facade
|
|
7753
|
+
* builder, which owns the runs/steps store query + the cluster journal
|
|
7754
|
+
* adapter. Inspect HTTP + `voltro workflows redrive` delegate to the same
|
|
7755
|
+
* implementation. */
|
|
7756
|
+
readonly redrive?: (runId: string) => Promise<WorkflowRedriveResult>;
|
|
7757
|
+
/** Fired (fire-and-forget) right after a workflow is ENQUEUED — a start,
|
|
7758
|
+
* child, or run. The CLI wires this to a cross-replica "wake" broadcast so
|
|
7759
|
+
* the replica that OWNS the new run's shard polls immediately instead of
|
|
7760
|
+
* waiting for its next storage tick. No-op when unset (single replica / no
|
|
7761
|
+
* broker → the poll interval covers it). */
|
|
7762
|
+
readonly onEnqueue?: () => void;
|
|
6958
7763
|
}
|
|
6959
7764
|
|
|
6960
7765
|
export declare interface WorkflowLayerExecutionContext {
|
|
@@ -6995,6 +7800,16 @@ export declare class WorkflowPayloadError extends Error {
|
|
|
6995
7800
|
missingFields: ReadonlyArray<string>, detail: string);
|
|
6996
7801
|
}
|
|
6997
7802
|
|
|
7803
|
+
/** Result of {@link WorkflowsAppContext.redrive} — whether the failed run's
|
|
7804
|
+
* durable journal was re-driven, how many failed step attempts were reset so
|
|
7805
|
+
* they re-execute, and a `reason` when it declined (no journal / still
|
|
7806
|
+
* running / already succeeded). */
|
|
7807
|
+
export declare interface WorkflowRedriveResult {
|
|
7808
|
+
readonly redriven: boolean;
|
|
7809
|
+
readonly activitiesReset: number;
|
|
7810
|
+
readonly reason: string | null;
|
|
7811
|
+
}
|
|
7812
|
+
|
|
6998
7813
|
/** Options for {@link WorkflowsAppContext.retry}. */
|
|
6999
7814
|
export declare interface WorkflowRetryOptions {
|
|
7000
7815
|
/** Re-run the workflow against this payload instead of the original
|
|
@@ -7016,6 +7831,11 @@ export declare interface WorkflowRunListFilter {
|
|
|
7016
7831
|
readonly workflowName?: string;
|
|
7017
7832
|
readonly tag?: string;
|
|
7018
7833
|
readonly status?: WorkflowRunRecordStatus;
|
|
7834
|
+
/** The DEAD-LETTER view: failed runs an operator has NOT yet discarded
|
|
7835
|
+
* (`status = 'failed' AND discardedAt IS NULL`). Since the framework applies no
|
|
7836
|
+
* retry, a `failed` run is terminal — this is the queue of unhandled failures.
|
|
7837
|
+
* Combines with the other filters (e.g. by `workflowName`). */
|
|
7838
|
+
readonly deadLettered?: boolean;
|
|
7019
7839
|
readonly limit?: number;
|
|
7020
7840
|
readonly offset?: number;
|
|
7021
7841
|
}
|
|
@@ -7024,6 +7844,16 @@ export declare interface WorkflowRunOptions {
|
|
|
7024
7844
|
/* Excluded from this release type: callerContext */
|
|
7025
7845
|
}
|
|
7026
7846
|
|
|
7847
|
+
export declare interface WorkflowRunRecord {
|
|
7848
|
+
readonly name: string;
|
|
7849
|
+
/** Terminal outcome only. `failed` is the dead-letter state (no framework retry). */
|
|
7850
|
+
readonly status: 'succeeded' | 'failed';
|
|
7851
|
+
readonly durationMs: number;
|
|
7852
|
+
/** UNIX SECONDS of completion — set only on success (drives the last-success
|
|
7853
|
+
* gauge). Omit on failure so the gauge keeps pointing at the last real success. */
|
|
7854
|
+
readonly completedAtSec?: number;
|
|
7855
|
+
}
|
|
7856
|
+
|
|
7027
7857
|
/** Persisted run lifecycle states recorded in `_voltro_workflow_runs`.
|
|
7028
7858
|
* Distinct from {@link WorkflowRunStatus} (which is derived from a live
|
|
7029
7859
|
* engine poll and carries `succeeded`/`unknown`); this enum matches the
|
|
@@ -7063,6 +7893,9 @@ export declare interface WorkflowRunSummary<Payload = unknown, Output = unknown>
|
|
|
7063
7893
|
readonly durationMs: number | null;
|
|
7064
7894
|
readonly traceId: string | null;
|
|
7065
7895
|
readonly parentExecutionId: string | null;
|
|
7896
|
+
/** When set, an operator has acknowledged this (failed) run — it is off the
|
|
7897
|
+
* dead-letter view. Null = unacknowledged. See `WorkflowRunListFilter.deadLettered`. */
|
|
7898
|
+
readonly discardedAt: Date | null;
|
|
7066
7899
|
}
|
|
7067
7900
|
|
|
7068
7901
|
export declare interface WorkflowsAppContext {
|
|
@@ -7088,6 +7921,14 @@ export declare interface WorkflowsAppContext {
|
|
|
7088
7921
|
* workflow by tag and re-executes it; pass `payloadOverride` to replay
|
|
7089
7922
|
* against a corrected input. */
|
|
7090
7923
|
retry(runId: string, options?: WorkflowRetryOptions): Promise<WorkflowRetryResult>;
|
|
7924
|
+
/** Re-drive a terminally-`failed` run from the step it died on, reusing
|
|
7925
|
+
* its durable journal — the counterpart to {@link retry} (fresh execution,
|
|
7926
|
+
* empty journal) and to {@link resume} (which only re-drives a *suspended*
|
|
7927
|
+
* run). Addressed by run id (`wfrun_…`). Completed steps replay from the
|
|
7928
|
+
* journal; only the failed steps re-execute. Fix the downstream cause
|
|
7929
|
+
* first, then redrive. Refuses a run that is not a not-yet-discarded
|
|
7930
|
+
* failure. */
|
|
7931
|
+
redrive(runId: string): Promise<WorkflowRedriveResult>;
|
|
7091
7932
|
}
|
|
7092
7933
|
|
|
7093
7934
|
export declare interface WorkflowSignalTarget {
|