@lostgradient/weft 0.9.0 → 0.11.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/README.md +12 -4
- package/dist/alerting/alert-manager.d.ts +3 -1
- package/dist/alerting/alert-manager.js +5 -2
- package/dist/cli-main.js +63 -63
- package/dist/core/checkpoint/serialization.js +2 -2
- package/dist/core/context/index.d.ts +2 -1
- package/dist/core/context/index.js +8 -0
- package/dist/core/context/operation-request.d.ts +2 -0
- package/dist/core/context/parallel-cache-entry.d.ts +1 -1
- package/dist/core/context/parallel-cache-entry.js +12 -8
- package/dist/core/context/parallel-operations.d.ts +1 -1
- package/dist/core/context/parallel-operations.js +50 -8
- package/dist/core/engine/async-activity-completion.d.ts +24 -54
- package/dist/core/engine/async-activity-completion.js +25 -90
- package/dist/core/engine/async-activity-records.d.ts +106 -0
- package/dist/core/engine/async-activity-records.js +124 -0
- package/dist/core/engine/bulk-operations-purge.js +2 -1
- package/dist/core/engine/callback-creators-core.js +1 -0
- package/dist/core/engine/construction.d.ts +1 -3
- package/dist/core/engine/construction.js +2 -5
- package/dist/core/engine/deferred-consume-envelope.d.ts +10 -1
- package/dist/core/engine/deferred-consume-envelope.js +9 -1
- package/dist/core/engine/disposal.js +4 -0
- package/dist/core/engine/engine-internal-types.d.ts +1 -0
- package/dist/core/engine/engine-leak-warnings.d.ts +2 -0
- package/dist/core/engine/engine-leak-warnings.js +7 -1
- package/dist/core/engine/engine-runtime-helpers.d.ts +8 -0
- package/dist/core/engine/engine-runtime-helpers.js +9 -0
- package/dist/core/engine/errors.js +1 -1
- package/dist/core/engine/index.d.ts +18 -5
- package/dist/core/engine/index.js +41 -10
- package/dist/core/engine/inline-parking.js +14 -1
- package/dist/core/engine/internals.d.ts +23 -2
- package/dist/core/engine/lifecycle/recovered-services.d.ts +4 -2
- package/dist/core/engine/lifecycle/recovered-services.js +37 -7
- package/dist/core/engine/lifecycle/resume.d.ts +2 -2
- package/dist/core/engine/lifecycle/resume.js +39 -7
- package/dist/core/engine/lifecycle/shared.d.ts +34 -0
- package/dist/core/engine/lifecycle/transition.d.ts +1 -1
- package/dist/core/engine/lifecycle/transition.js +3 -3
- package/dist/core/engine/lifecycle.d.ts +1 -1
- package/dist/core/engine/memo-durable-activity.js +7 -9
- package/dist/core/engine/operations-activity.js +1 -1
- package/dist/core/engine/operations-coordination.d.ts +15 -1
- package/dist/core/engine/operations-coordination.js +43 -10
- package/dist/core/engine/operations-time.js +8 -28
- package/dist/core/engine/ownership-options.d.ts +1 -0
- package/dist/core/engine/ownership-options.js +14 -0
- package/dist/core/engine/retention.js +4 -0
- package/dist/core/engine/schedule-run-metadata.d.ts +3 -0
- package/dist/core/engine/schedule-run-metadata.js +29 -0
- package/dist/core/engine/schedule-run.js +19 -4
- package/dist/core/engine/schedules.js +4 -3
- package/dist/core/engine/sleep-timer-acknowledgements.d.ts +15 -0
- package/dist/core/engine/sleep-timer-acknowledgements.js +144 -0
- package/dist/core/engine/sub-operation.js +6 -13
- package/dist/core/engine/termination/cleanup.js +6 -1
- package/dist/core/types/options.d.ts +12 -0
- package/dist/core/types/services-resolution.d.ts +8 -3
- package/dist/core/types/workflow-builder.d.ts +2 -2
- package/dist/core/types/workflow-context.d.ts +27 -0
- package/dist/index.d.ts +2 -2
- package/dist/mcp/cli.js +17 -17
- package/dist/server/authorization.d.ts +3 -3
- package/dist/server/fault-to-json-rpc.d.ts +2 -1
- package/dist/server/handler.js +22 -22
- package/dist/server/index.js +17 -17
- package/dist/server/json-rpc-dispatch.d.ts +2 -2
- package/dist/server/json-rpc-parse.d.ts +1 -1
- package/dist/server/json-rpc-protocol.d.ts +2 -2
- package/dist/server/json-rpc-websocket.d.ts +1 -1
- package/dist/server/operation-fault.d.ts +3 -1
- package/dist/server/operations/async-activity.js +2 -2
- package/dist/server/principal.d.ts +2 -2
- package/dist/server/stdio-session.d.ts +4 -4
- package/dist/service-worker/index.js +13 -13
- package/dist/service-worker/scheduler.js +1 -0
- package/dist/storage/compressed-storage.js +1 -1
- package/dist/storage/http.js +2 -2
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/indexeddb.js +1 -1
- package/dist/storage/interface.d.ts +1 -0
- package/dist/storage/interface.js +1 -1
- package/dist/storage/lazy-postgres-pool.d.ts +38 -0
- package/dist/storage/lazy-postgres-pool.js +38 -0
- package/dist/storage/lmdb.js +1 -1
- package/dist/storage/memory.js +1 -1
- package/dist/storage/neon-batch.d.ts +8 -8
- package/dist/storage/neon.d.ts +26 -79
- package/dist/storage/neon.js +3 -3
- package/dist/storage/postgres-key-value-queries.d.ts +1 -1
- package/dist/storage/postgres-key-value-queries.js +1 -1
- package/dist/storage/postgres-key-value-storage.d.ts +110 -0
- package/dist/storage/postgres-key-value-storage.js +204 -0
- package/dist/storage/postgres.d.ts +59 -0
- package/dist/storage/postgres.js +15 -0
- package/dist/storage/resolve.js +1 -1
- package/dist/storage/scoped-storage.js +1 -1
- package/dist/storage/testing.js +1 -1
- package/dist/storage/turso.js +1 -1
- package/dist/storage/typed-storage.js +1 -1
- package/dist/storage/web-extension.js +1 -1
- package/dist/testing/index.js +17 -17
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +17 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Weft
|
|
2
2
|
|
|
3
|
-
A Bun-native durable execution engine. Current release: `0.
|
|
3
|
+
A Bun-native durable execution engine. Current release: `0.11.0`.
|
|
4
4
|
|
|
5
5
|
Install the library from npm as `@lostgradient/weft`:
|
|
6
6
|
|
|
@@ -63,7 +63,7 @@ Weft's durability promise is checkpoint-level and explicit:
|
|
|
63
63
|
- Every `yield*` boundary is persisted before the workflow advances to the next durable step.
|
|
64
64
|
- `Engine.create()` recovers by default after registering workflow definitions, so fresh processes resume persisted running workflows without a separate boot hook.
|
|
65
65
|
- Recovery resumes from the last checkpoint position instead of replaying the workflow from the beginning.
|
|
66
|
-
- External activity side effects still need idempotency keys, provider lookup, or
|
|
66
|
+
- External activity side effects still need idempotency keys, provider lookup, verifier logic, or external write fencing. Without that, a crash after the external side effect but before Weft commits the activity result can dispatch the activity again. Weft exposes `workflowExecutionToken` and per-attempt `activityAttemptToken` values so databases you control can reject stale writes from older attempts or replaced runs.
|
|
67
67
|
|
|
68
68
|
The full [Durability Guarantee](documentation/architecture/durability-guarantee.md) separates what is guaranteed today from the Tier-0 activity-reconciliation work that narrows the remaining crash window.
|
|
69
69
|
|
|
@@ -105,6 +105,8 @@ That's the core loop: `workflow({ name })` is a **chained builder** that co-loca
|
|
|
105
105
|
|
|
106
106
|
Passing an explicitly empty workflow map is the same default-registry boot shape as omitting `workflows`: `Engine.create({ workflows: {} })` recovers after registration and returns an engine whose TypeScript type is compatible with default-registry consumers such as `serve({ engine })`. Use a non-empty `workflows` map when you want TypeScript to narrow `engine.start(...)` to the registered names.
|
|
107
107
|
|
|
108
|
+
Serverless hosts that cannot keep process-local intervals alive can set `backgroundTasks: 'manual'` and call `await engine.runMaintenance()` from an alarm or Cron wake-up to drive timers, scheduled starts, update-response cleanup, retention, and alert evaluation. Await each maintenance cycle before starting another one so host-driven ticks do not overlap.
|
|
109
|
+
|
|
108
110
|
If you'd rather wire things up by hand — useful for tests, isolating engines onto separate storage scopes via `ScopedStorage`, or adding new workflows after the engine starts up — `new Engine({ storage })`, `engine.register(workflow)` or `engine.registerWorkflows({ ... })`, and `await engine.recoverAll()` are the underlying primitives. Each `engine.register(workflow)` call returns the engine with that workflow's name and types baked in, so `engine.start('welcome', ...)` autocompletes immediately.
|
|
109
111
|
|
|
110
112
|
When a workflow needs a live host capability that cannot be checkpointed, pass it as per-run `services`:
|
|
@@ -113,7 +115,9 @@ When a workflow needs a live host capability that cannot be checkpointed, pass i
|
|
|
113
115
|
const handle = await engine.start('welcome', { name: 'Steve' }, { services: { crmClient } });
|
|
114
116
|
```
|
|
115
117
|
|
|
116
|
-
Inside inline workflows, read that value from `ctx.services` and narrow it to your application type. Weft never writes the service object into checkpoints; it persists only a presence marker so `Engine.create({ resolveWorkflowServices })` can rebuild the service value during fresh-process recovery before the generator advances. Do not use `services` for durable data, and do not pass it in Worker execution mode — non-serializable values cannot cross to a Worker.
|
|
118
|
+
Inside inline workflows, read that value from `ctx.services` and narrow it to your application type. Weft never writes the service object into checkpoints; it persists only a presence marker so `Engine.create({ resolveWorkflowServices })` can rebuild the service value during fresh-process recovery before the generator advances. Scheduled runs also carry `schedule.id` and, when the grid timestamp is retained, `schedule.occurrence` into the resolver across recovery, so host services can branch on schedule origin without duplicating that identity in workflow input. Do not use `services` for durable data, and do not pass it in Worker execution mode — non-serializable values cannot cross to a Worker.
|
|
119
|
+
|
|
120
|
+
When recovery also needs to rebuild live host surfaces such as progress emitters or adapters, opt out of automatic recovery first with `Engine.create({ recover: false, ... })`, then call `await engine.recoverAll({ onRecoveredWorkflow })` after any host surfaces are ready. Weft awaits the hook after services are re-provided and before the recovered generator advances, and a hook failure fails only that recovered run with a system failure category.
|
|
117
121
|
|
|
118
122
|
> [!NOTE]
|
|
119
123
|
> The chained builder also accepts `.signals({...})`, `.updates({...})`, `.queries({...})`, and `.searchAttributes({...})`. Each can be called at most once before `.execute(fn)`; the type system flips a phantom flag so a duplicate call fails to typecheck, and the runtime mirrors the same invariant. These maps don't introduce new runtime gating — they're type hints that thread into `ctx.run()`, `ctx.waitForSignal()`, `ctx.waitForUpdate()`, and friends so your editor autocompletes and your code typechecks. The underlying dispatch paths are unchanged.
|
|
@@ -147,7 +151,9 @@ Because recovery never re-executes the workflow from the beginning, your workflo
|
|
|
147
151
|
|
|
148
152
|
### Durable Workflows
|
|
149
153
|
|
|
150
|
-
Generator functions with automatic checkpointing at every `yield*` boundary. Activities, sleeps, signals, condition gates with `ctx.waitUntil()`, queries, updates, structured logs with `ctx.log`, parallel execution via `ctx.all()`, race semantics via `ctx.race()`, memoization via `ctx.memo()`, sagas via `ctx.saga()`, child workflows, and forks. Plain async helpers called from inline `ctx.memo()` callbacks can use `durableActivity()` for activity-level retry, heartbeat, reconciliation, and observability without converting the helper stack to generators. `ctx.sleep()` uses replay-stable durable timer keys, so a workflow that crashes while parked on a sleep resumes the same timer instead of orphaning the old one. `ctx.all()` and `ctx.
|
|
154
|
+
Generator functions with automatic checkpointing at every `yield*` boundary. Activities, sleeps, signals, condition gates with `ctx.waitUntil()`, queries, updates, structured logs with `ctx.log`, parallel execution via `ctx.all()`, race semantics via `ctx.race()` and `ctx.raceKeyed()`, memoization via `ctx.memo()`, sagas via `ctx.saga()`, child workflows, and forks. Plain async helpers called from inline `ctx.memo()` callbacks can use `durableActivity()` for activity-level retry, heartbeat, reconciliation, and observability without converting the helper stack to generators. `ctx.sleep()` uses replay-stable durable timer keys, so a workflow that crashes while parked on a sleep resumes the same timer instead of orphaning the old one. Fired sleep timers are acknowledged only after the awakened inline workflow reaches its next durable checkpoint or terminal state, which lets Service Worker schedulers retry a timer instead of deleting the only wake-up record during an eviction window. `ctx.all()`, `ctx.race()`, and `ctx.raceKeyed()` can branch over activities, sleeps, and signal waits; use `ctx.race([ctx.waitForSignal(name), ctx.sleep(timeout)])` for signal timeouts instead of placing an unbounded signal wait directly in `ctx.all()`.
|
|
155
|
+
|
|
156
|
+
Use `ctx.raceKeyed()` when the workflow needs to know which branch won without encoding branch identity into the payload. The result is a discriminated `{ key, value }` pair, so checking `winner.key` narrows `winner.value`. For non-blocking signal drains, race a direct `ctx.waitForSignal(name)` branch against a literal `ctx.sleep(0)`: Weft checks the durable signal buffer first, consumes one already-buffered signal if present, and otherwise continues immediately. The stronger ordering is specific to zero-duration sleeps; positive timeouts keep ordinary race behavior.
|
|
151
157
|
|
|
152
158
|
Every workflow context exposes `ctx.workflowId` and `ctx.workflowType`. `workflowType` is the registered name from `workflow({ name })`, so shared workflow code can log, tag, or branch on the current workflow type without closing over definition-site state.
|
|
153
159
|
|
|
@@ -250,6 +256,8 @@ The idempotency mapping intentionally outlives terminal cleanup. If retention re
|
|
|
250
256
|
|
|
251
257
|
For stable-id re-sync flows, `engine.startOrSignal()` can replace a terminal prior run with `{ id, onTerminalConflict: 'start-new' }` when the initial signal also carries a deterministic `signalId`. Non-terminal runs are still signalled, not replaced, and restart-capable calls reject `idempotencyKey` because idempotency keys are permanent at-most-once mappings. Signal identifiers are treated as opaque user identifiers before storage-key construction, so caller-provided values that contain separator-looking text such as `anonymous:` stay explicit signal IDs instead of colliding with Weft's generated anonymous-signal sequence.
|
|
252
258
|
|
|
259
|
+
When a workflow drains signals with `ctx.race([ctx.waitForSignal(name), ctx.sleep(0)])` and then returns, use that stable `id`, a deterministic per-event `signalId`, and `onTerminalConflict: 'start-new'` for the corresponding `startOrSignal` calls. Delivery is serialized against terminal completion, so a signal arriving across the completion boundary is consumed by the current run or handed to its successor; a positive drain window is not needed for correctness.
|
|
260
|
+
|
|
253
261
|
### Search Attributes
|
|
254
262
|
|
|
255
263
|
Attach indexed metadata to a workflow at runtime, then list and filter on it.
|
|
@@ -35,7 +35,9 @@ import type { AlertState, AlertingOptions } from './types';
|
|
|
35
35
|
*/
|
|
36
36
|
export declare class AlertManager implements Disposable {
|
|
37
37
|
#private;
|
|
38
|
-
constructor(target: EventTarget, options: AlertingOptions, getNow?: () => number);
|
|
38
|
+
constructor(target: EventTarget, options: AlertingOptions, getNow?: () => number, startBackgroundTick?: boolean);
|
|
39
|
+
/** Re-evaluate every rule once, for hosts that drive maintenance explicitly. */
|
|
40
|
+
tick(): void;
|
|
39
41
|
/** Get current state of all alert rules (for debugging/testing). */
|
|
40
42
|
get states(): readonly AlertState[];
|
|
41
43
|
[Symbol.dispose](): void;
|
|
@@ -18,7 +18,7 @@ export class AlertManager {
|
|
|
18
18
|
#pendingWebhooks;
|
|
19
19
|
#getNow;
|
|
20
20
|
#tickInterval;
|
|
21
|
-
constructor(target, options, getNow = Date.now) {
|
|
21
|
+
constructor(target, options, getNow = Date.now, startBackgroundTick = !0) {
|
|
22
22
|
this.#target = target;
|
|
23
23
|
this.#options = options;
|
|
24
24
|
this.#getNow = getNow;
|
|
@@ -41,7 +41,10 @@ export class AlertManager {
|
|
|
41
41
|
this.#windows.set(i, new HistogramWindow(windowMs));
|
|
42
42
|
}
|
|
43
43
|
this.#subscribeToEvents();
|
|
44
|
-
this.#tickInterval = setInterval(this.#evaluateAll.bind(this), TICK_INTERVAL_MS);
|
|
44
|
+
this.#tickInterval = startBackgroundTick ? setInterval(this.#evaluateAll.bind(this), TICK_INTERVAL_MS) : null;
|
|
45
|
+
}
|
|
46
|
+
tick() {
|
|
47
|
+
this.#evaluateAll();
|
|
45
48
|
}
|
|
46
49
|
#evaluateAll() {
|
|
47
50
|
for (let i = 0;i < this.#options.rules.length; i++)
|