@voltro/runtime 0.29.0 → 0.30.1
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 +448 -0
- package/dist/index.d.ts +215 -5
- package/dist/index.js +1237 -1136
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -1274,7 +1274,30 @@ export declare interface BindEventInput {
|
|
|
1274
1274
|
readonly guards?: Guards<unknown> | undefined;
|
|
1275
1275
|
}
|
|
1276
1276
|
|
|
1277
|
-
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
|
|
1277
|
+
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,
|
|
1278
|
+
/**
|
|
1279
|
+
* This procedure's DECLARED error union (`descriptor.error`).
|
|
1280
|
+
*
|
|
1281
|
+
* The third category of failure, and the one two guards below could not see.
|
|
1282
|
+
* A value that is TAGGED but not representable by this union is undeclared by
|
|
1283
|
+
* construction: the untagged catch skips it (it has a `_tag`), the infra list
|
|
1284
|
+
* skips it (it is not on a curated list), and the rpc encoder then cannot
|
|
1285
|
+
* match it and ships the whole `ExitEncoded<…>` decode tree to the browser —
|
|
1286
|
+
* ~2 KB for a one-line cause, with the message at the END so every tool that
|
|
1287
|
+
* truncates shows the useless half.
|
|
1288
|
+
*
|
|
1289
|
+
* A consumer met it with `TenantScopeViolation`. Adding that tag to
|
|
1290
|
+
* `INFRA_ERROR_TAGS` would have been wrong: `effectStore.ts` documents
|
|
1291
|
+
* `error: Schema.Union(TenantScopeViolation, StoreOperationFailed, …)` as a
|
|
1292
|
+
* supported declaration, so an app that DECLARES it must still receive it
|
|
1293
|
+
* typed. The question is therefore not "is this tag infra" but "can THIS
|
|
1294
|
+
* descriptor represent it" — which only the descriptor can answer.
|
|
1295
|
+
*
|
|
1296
|
+
* Omitted ⇒ the check is skipped entirely. A call site that cannot supply a
|
|
1297
|
+
* schema keeps exactly the old behaviour rather than collapsing errors it
|
|
1298
|
+
* cannot classify.
|
|
1299
|
+
*/
|
|
1300
|
+
declaredError?: Schema.Schema.All) => Effect.Effect<Output, E, SubjectService | ConnectionInfo>;
|
|
1278
1301
|
|
|
1279
1302
|
/**
|
|
1280
1303
|
* Bind a non-reactive server→client stream (A3). The executor builds a
|
|
@@ -5723,6 +5746,26 @@ export declare type ReactionAct = {
|
|
|
5723
5746
|
} | {
|
|
5724
5747
|
readonly kind: 'workflow';
|
|
5725
5748
|
readonly workflow: string;
|
|
5749
|
+
/**
|
|
5750
|
+
* Shape the workflow's payload from the change. Omit and the CHANGED ROW is
|
|
5751
|
+
* the payload, as before.
|
|
5752
|
+
*
|
|
5753
|
+
* Without this the workflow's payload schema is dictated by the watched
|
|
5754
|
+
* TABLE's row shape rather than by what the workflow needs, which drags two
|
|
5755
|
+
* problems into every reaction-started workflow:
|
|
5756
|
+
*
|
|
5757
|
+
* • every column travels, including ones the workflow has no business
|
|
5758
|
+
* seeing — a reaction on a users table hands the whole row to a
|
|
5759
|
+
* narration workflow;
|
|
5760
|
+
* • the row shape is DIALECT-dependent at the edges. A `timestamp()`
|
|
5761
|
+
* column arrives as a `Date` on MariaDB and a number elsewhere, so an
|
|
5762
|
+
* app normalising on both sides of an idempotency key is doing the
|
|
5763
|
+
* framework's job.
|
|
5764
|
+
*
|
|
5765
|
+
* A mapper fixes both at the declaration: the workflow declares the payload
|
|
5766
|
+
* it wants and the reaction adapts.
|
|
5767
|
+
*/
|
|
5768
|
+
readonly payload?: (event: ReactionEvent) => unknown;
|
|
5726
5769
|
};
|
|
5727
5770
|
|
|
5728
5771
|
export declare interface ReactionDefinition {
|
|
@@ -5749,10 +5792,35 @@ export declare interface ReactionGuards {
|
|
|
5749
5792
|
/** Per-tenant AI spend ceiling (USD). Checked via the injected `checkBudget`
|
|
5750
5793
|
* (the app wires `requireAiBudget`). Over budget → the reaction refuses. */
|
|
5751
5794
|
readonly costBudgetUsd?: number;
|
|
5752
|
-
/**
|
|
5795
|
+
/**
|
|
5796
|
+
* Rate cap — at most `limit` firings per `windowMs`, PER KEY.
|
|
5797
|
+
*
|
|
5798
|
+
* ── Two defects this shape replaces, both reported from production ────────
|
|
5799
|
+
*
|
|
5800
|
+
* It read as a per-key cap and was neither. The runner keyed the limiter on
|
|
5801
|
+
* the REACTION NAME, so one cap covered every row and every tenant the
|
|
5802
|
+
* reaction watched: an app with a hundred tenants got a hundredth of the
|
|
5803
|
+
* throughput it declared, and the busiest tenant starved the rest.
|
|
5804
|
+
*
|
|
5805
|
+
* And the limiter was in-memory, per process. With three replicas the
|
|
5806
|
+
* effective cap was 3×, and nothing about the declaration said so — the same
|
|
5807
|
+
* config produced a different limit depending on how many pods happened to be
|
|
5808
|
+
* running.
|
|
5809
|
+
*
|
|
5810
|
+
* `key` is now REQUIRED to get per-entity behaviour, and the limiter is
|
|
5811
|
+
* DURABLE (a claim in the shared store, the same INSERT-wins arbiter the cron
|
|
5812
|
+
* scheduler uses), so the cap is the cap regardless of replica count.
|
|
5813
|
+
*
|
|
5814
|
+
* Omitting `key` keeps the old GLOBAL meaning — which is a legitimate thing to
|
|
5815
|
+
* want (a cap on a scarce downstream), just not what the old field appeared to
|
|
5816
|
+
* offer.
|
|
5817
|
+
*/
|
|
5753
5818
|
readonly rateLimit?: {
|
|
5754
5819
|
readonly limit: number;
|
|
5755
5820
|
readonly windowMs: number;
|
|
5821
|
+
/** Partitions the cap. `(event) => event.new.tenantId` for per-tenant,
|
|
5822
|
+
* `(event) => String(event.new.id)` for per-row. */
|
|
5823
|
+
readonly key?: (event: ReactionEvent) => string;
|
|
5756
5824
|
};
|
|
5757
5825
|
}
|
|
5758
5826
|
|
|
@@ -5760,7 +5828,17 @@ export declare type ReactionOp = 'insert' | 'update' | 'delete';
|
|
|
5760
5828
|
|
|
5761
5829
|
export declare type ReactionOutcome = 'acted' | 'skipped-op' | 'skipped-when' | 'skipped-dedupe' | 'skipped-ratelimit' | 'skipped-budget';
|
|
5762
5830
|
|
|
5763
|
-
/**
|
|
5831
|
+
/**
|
|
5832
|
+
* A per-key sliding-window rate limiter — IN-MEMORY, PER PROCESS.
|
|
5833
|
+
*
|
|
5834
|
+
* Correct for a single-process deployment and wrong for every other one: with N
|
|
5835
|
+
* replicas the effective cap is N×, silently. It stays as the FALLBACK the
|
|
5836
|
+
* runner uses when no durable claimer is wired (dev on the memory store), and
|
|
5837
|
+
* the runner logs once when it falls back, because "my limit is 3× what I
|
|
5838
|
+
* declared" is not something anyone discovers by reading config.
|
|
5839
|
+
*
|
|
5840
|
+
* Prefer {@link ReactionRunDeps.claimRateSlot}.
|
|
5841
|
+
*/
|
|
5764
5842
|
export declare class ReactionRateLimiter {
|
|
5765
5843
|
private readonly hits;
|
|
5766
5844
|
allow(key: string, limit: number, windowMs: number, now: number): boolean;
|
|
@@ -5781,6 +5859,14 @@ export declare interface ReactionRunDeps {
|
|
|
5781
5859
|
/** Per-tenant AI budget check — true = within budget. Injected (wraps
|
|
5782
5860
|
* requireAiBudget). Only consulted when `guards.costBudgetUsd` is set. */
|
|
5783
5861
|
readonly checkBudget?: (tenantId: string | null, budgetUsd: number) => boolean | Promise<boolean>;
|
|
5862
|
+
/**
|
|
5863
|
+
* Atomically claim ONE rate slot. `true` = we got it and may fire.
|
|
5864
|
+
*
|
|
5865
|
+
* Injected by the serve layer over the shared store, so the cap holds across
|
|
5866
|
+
* replicas. Absent → the runner falls back to {@link ReactionRateLimiter},
|
|
5867
|
+
* which is per-process and therefore N× on N replicas.
|
|
5868
|
+
*/
|
|
5869
|
+
readonly claimRateSlot?: (key: string) => Promise<boolean>;
|
|
5784
5870
|
readonly rateLimiter?: ReactionRateLimiter;
|
|
5785
5871
|
readonly now?: () => number;
|
|
5786
5872
|
}
|
|
@@ -8757,6 +8843,44 @@ export declare type WhereOp = '=' | '!=' | '<>' | '>' | '>=' | '<' | '<=' | 'in'
|
|
|
8757
8843
|
*/
|
|
8758
8844
|
export declare const withRowFilter: <R extends ServeRequestContext>(request: R) => Promise<R>;
|
|
8759
8845
|
|
|
8846
|
+
/**
|
|
8847
|
+
* What the gate decided. `passthrough` is the common case — no controls
|
|
8848
|
+
* declared — and is distinct from `start` so the facade can skip the commit
|
|
8849
|
+
* bookkeeping entirely rather than calling a no-op.
|
|
8850
|
+
*/
|
|
8851
|
+
export declare type WorkflowAdmissionOutcome = {
|
|
8852
|
+
readonly kind: 'passthrough';
|
|
8853
|
+
} | {
|
|
8854
|
+
readonly kind: 'start';
|
|
8855
|
+
/** What to start WITH. Differs from the caller's payload only for a batch
|
|
8856
|
+
* that filled up on this arrival. */
|
|
8857
|
+
readonly payload: unknown;
|
|
8858
|
+
/** Called once the engine returns an execution id: links the ledger row,
|
|
8859
|
+
* consumes any batched intents, evicts a singleton incumbent. */
|
|
8860
|
+
readonly commit: (executionId: string) => Promise<void>;
|
|
8861
|
+
} | {
|
|
8862
|
+
readonly kind: 'queued';
|
|
8863
|
+
readonly intentId: string;
|
|
8864
|
+
readonly mode: string;
|
|
8865
|
+
readonly dueAt: number;
|
|
8866
|
+
} | {
|
|
8867
|
+
readonly kind: 'dropped';
|
|
8868
|
+
readonly retryAfterMs: number;
|
|
8869
|
+
} | {
|
|
8870
|
+
readonly kind: 'skipped';
|
|
8871
|
+
readonly executionId: string;
|
|
8872
|
+
};
|
|
8873
|
+
|
|
8874
|
+
export declare interface WorkflowAdmissionRequest {
|
|
8875
|
+
readonly workflowName: string;
|
|
8876
|
+
readonly payload: unknown;
|
|
8877
|
+
readonly callerContext: WorkflowCallerContext | undefined;
|
|
8878
|
+
/** `true` for `run(...)` and `start(..., { wait: true })` — a caller blocking
|
|
8879
|
+
* for the RESULT. A deferring control has no coherent answer for one, so the
|
|
8880
|
+
* gate refuses it by name rather than doing something surprising. */
|
|
8881
|
+
readonly waiting: boolean;
|
|
8882
|
+
}
|
|
8883
|
+
|
|
8760
8884
|
export declare interface WorkflowCallerContext {
|
|
8761
8885
|
readonly subject?: unknown;
|
|
8762
8886
|
readonly traceId?: string | null;
|
|
@@ -8771,6 +8895,19 @@ export declare interface WorkflowChildOptions {
|
|
|
8771
8895
|
/* Excluded from this release type: callerContext */
|
|
8772
8896
|
}
|
|
8773
8897
|
|
|
8898
|
+
/**
|
|
8899
|
+
* Reject a blocking caller on a workflow whose flow control can DEFER.
|
|
8900
|
+
*
|
|
8901
|
+
* `run(...)` and `start({ wait: true })` block for the run's RESULT, and there
|
|
8902
|
+
* is no result to return for a start that was collapsed into a future run.
|
|
8903
|
+
* Silently starting it anyway would break the declared limit; silently
|
|
8904
|
+
* returning nothing would break the caller's type. So it is an error that names
|
|
8905
|
+
* both halves.
|
|
8906
|
+
*/
|
|
8907
|
+
export declare class WorkflowDeferredResultError extends Error {
|
|
8908
|
+
constructor(workflowName: string, method: string, mode: string);
|
|
8909
|
+
}
|
|
8910
|
+
|
|
8774
8911
|
export declare interface WorkflowDefinitionLike {
|
|
8775
8912
|
readonly name: string;
|
|
8776
8913
|
readonly payloadSchema: Schema.Schema.Any;
|
|
@@ -8858,6 +8995,32 @@ export declare interface WorkflowFacadeOptions {
|
|
|
8858
8995
|
* waiting for its next storage tick. No-op when unset (single replica / no
|
|
8859
8996
|
* broker → the poll interval covers it). */
|
|
8860
8997
|
readonly onEnqueue?: () => void;
|
|
8998
|
+
/**
|
|
8999
|
+
* Declarative flow control — the admission boundary every start passes
|
|
9000
|
+
* through when the workflow declares `debounce` / `singleton` / `concurrency`
|
|
9001
|
+
* / `throttle` / `rateLimit` / `batch`, or when an operator paused it.
|
|
9002
|
+
*
|
|
9003
|
+
* Injected, and absent by default, for the same reason `listRuns` is: the
|
|
9004
|
+
* runtime stays storage-agnostic and the CLI owns the tables. Absent ⇒ every
|
|
9005
|
+
* start takes exactly the path it took before flow control existed, which is
|
|
9006
|
+
* what makes this feature free for a workflow that declares none.
|
|
9007
|
+
*/
|
|
9008
|
+
readonly admitStart?: (input: WorkflowAdmissionRequest) => Promise<WorkflowAdmissionOutcome>;
|
|
9009
|
+
/**
|
|
9010
|
+
* What an ARRIVING start's payload is validated against, when that differs
|
|
9011
|
+
* from the workflow's own `payload` schema.
|
|
9012
|
+
*
|
|
9013
|
+
* Exactly one control needs this: `batch`. A batching workflow's own payload
|
|
9014
|
+
* is the accumulated `{ items: [...] }` — that is the declaration contract
|
|
9015
|
+
* `validateFlowControl` enforces — while callers start it with a SINGLE item.
|
|
9016
|
+
* Validating the caller's item against the batch shape rejects every start
|
|
9017
|
+
* before the gate is reached, which is what shipped.
|
|
9018
|
+
*
|
|
9019
|
+
* Injected rather than derived, for the same reason `admitStart` is: the
|
|
9020
|
+
* runtime does not know flow control exists. Returns `undefined` for every
|
|
9021
|
+
* workflow that does not batch, which is the common case and costs nothing.
|
|
9022
|
+
*/
|
|
9023
|
+
readonly startPayloadSchema?: (workflowName: string) => unknown | undefined;
|
|
8861
9024
|
}
|
|
8862
9025
|
|
|
8863
9026
|
export declare interface WorkflowLayerExecutionContext {
|
|
@@ -8898,6 +9061,20 @@ export declare class WorkflowPayloadError extends Error {
|
|
|
8898
9061
|
missingFields: ReadonlyArray<string>, detail: string);
|
|
8899
9062
|
}
|
|
8900
9063
|
|
|
9064
|
+
/**
|
|
9065
|
+
* A blocking caller hit a `rateLimit` cap.
|
|
9066
|
+
*
|
|
9067
|
+
* `start()` reports a drop through the handle, because a fire-and-forget caller
|
|
9068
|
+
* has somewhere to put it. `run()` has no such place — its return type is the
|
|
9069
|
+
* workflow's success value — so the drop has to be an error, or it would look
|
|
9070
|
+
* like a run that returned `undefined`.
|
|
9071
|
+
*/
|
|
9072
|
+
export declare class WorkflowRateLimitedError extends Error {
|
|
9073
|
+
readonly workflowName: string;
|
|
9074
|
+
readonly retryAfterMs: number;
|
|
9075
|
+
constructor(workflowName: string, retryAfterMs: number);
|
|
9076
|
+
}
|
|
9077
|
+
|
|
8901
9078
|
/** Result of {@link WorkflowsAppContext.redrive} — whether the failed run's
|
|
8902
9079
|
* durable journal was re-driven, how many failed step attempts were reset so
|
|
8903
9080
|
* they re-execute, and a `reason` when it declined (no journal / still
|
|
@@ -8929,6 +9106,24 @@ export declare interface WorkflowRunListFilter {
|
|
|
8929
9106
|
readonly workflowName?: string;
|
|
8930
9107
|
readonly tag?: string;
|
|
8931
9108
|
readonly status?: WorkflowRunRecordStatus;
|
|
9109
|
+
/** Several statuses at once (`['failed', 'cancelled']`). Ignored when the
|
|
9110
|
+
* single `status` is also set — one of them has to win, and the singular,
|
|
9111
|
+
* older spelling is the one existing callers already rely on. */
|
|
9112
|
+
readonly statuses?: ReadonlyArray<WorkflowRunRecordStatus>;
|
|
9113
|
+
/** Case-insensitive substring match on the workflow tag — the search-box
|
|
9114
|
+
* semantic, where `tag`/`workflowName` are exact. */
|
|
9115
|
+
readonly tagContains?: string;
|
|
9116
|
+
/** Exact match on the run's recorded `source` (`workflow-rpc`,
|
|
9117
|
+
* `app-context`, `inspect`, `schedule:<name>`, …). */
|
|
9118
|
+
readonly source?: string;
|
|
9119
|
+
/** Prefix match against the run id OR the execution id — what a human
|
|
9120
|
+
* pastes from a log line. Case-sensitive, because ids are. */
|
|
9121
|
+
readonly idPrefix?: string;
|
|
9122
|
+
/** Only runs started at/after this instant. With `startedBefore` this is
|
|
9123
|
+
* the time-range view; each bound works alone too. */
|
|
9124
|
+
readonly startedAfter?: Date;
|
|
9125
|
+
/** Only runs started strictly before this instant. */
|
|
9126
|
+
readonly startedBefore?: Date;
|
|
8932
9127
|
/** The DEAD-LETTER view: failed runs an operator has NOT yet discarded
|
|
8933
9128
|
* (`status = 'failed' AND discardedAt IS NULL`). Since the framework applies no
|
|
8934
9129
|
* retry, a `failed` run is terminal — this is the queue of unhandled failures.
|
|
@@ -9035,16 +9230,31 @@ export declare interface WorkflowSignalTarget {
|
|
|
9035
9230
|
readonly workflowName?: string;
|
|
9036
9231
|
}
|
|
9037
9232
|
|
|
9233
|
+
/** A blocking caller lost a `singleton: { mode: 'skip' }` race. Carries the
|
|
9234
|
+
* incumbent's execution id, so the caller can wait on THAT run instead. */
|
|
9235
|
+
export declare class WorkflowSingletonHeldError extends Error {
|
|
9236
|
+
readonly workflowName: string;
|
|
9237
|
+
readonly executionId: string;
|
|
9238
|
+
constructor(workflowName: string, executionId: string);
|
|
9239
|
+
}
|
|
9240
|
+
|
|
9038
9241
|
export declare interface WorkflowStartOptions {
|
|
9039
9242
|
readonly wait?: boolean;
|
|
9040
9243
|
/* Excluded from this release type: callerContext */
|
|
9244
|
+
/* Excluded from this release type: admitted */
|
|
9041
9245
|
}
|
|
9042
9246
|
|
|
9043
9247
|
export declare const workflowToRpc: (workflow: WorkflowDefinitionLike) => Rpc.Rpc<string, Schema.Schema.Any, Schema.Struct<{
|
|
9044
9248
|
id: typeof Schema.String;
|
|
9045
9249
|
workflowName: typeof Schema.String;
|
|
9046
|
-
executionId: typeof Schema.String
|
|
9047
|
-
status: Schema.Literal<["running"]>;
|
|
9250
|
+
executionId: Schema.NullOr<typeof Schema.String>;
|
|
9251
|
+
status: Schema.Literal<["running", "queued", "dropped", "skipped"]>;
|
|
9252
|
+
deferral: Schema.optional<Schema.Struct<{
|
|
9253
|
+
mode: typeof Schema.String;
|
|
9254
|
+
dueAt: Schema.NullOr<typeof Schema.Number>;
|
|
9255
|
+
retryAfterMs: Schema.NullOr<typeof Schema.Number>;
|
|
9256
|
+
intentId: Schema.NullOr<typeof Schema.String>;
|
|
9257
|
+
}>>;
|
|
9048
9258
|
}>, Schema.Schema.All, never>;
|
|
9049
9259
|
|
|
9050
9260
|
export declare interface WorkflowUpdateOptions {
|