@restatedev/restate-sdk-gen 1.15.0-rc.1 → 1.15.0-rc.2
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 +4 -2
- package/dist/index.cjs +315 -50
- package/dist/index.d.cts +91 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +91 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +314 -51
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -72,7 +72,8 @@ Imported directly from `@restatedev/restate-sdk-gen`:
|
|
|
72
72
|
- **`sleep(duration)`** — journaled timer.
|
|
73
73
|
- **`awakeable<T>()`** — journaled awakeable; returns `{ id, promise: Future<T> }`.
|
|
74
74
|
- **`channel<T>()`** — single-shot in-memory `Channel<T>`.
|
|
75
|
-
- **`state<T>()` / `sharedState<T>()`** — typed key-value store.
|
|
75
|
+
- **`state<T>()` / `sharedState<T>()`** — typed key-value store (durable, survives across invocations).
|
|
76
|
+
- **`contextLocal<T>(default?)`** — ambient, in-memory storage scoped to the current invocation and shared by every fiber under it. Set once near the top, read anywhere downstream without threading a parameter. Not durable — for state that must outlive the invocation, use `state()`.
|
|
76
77
|
- **`serviceClient` / `objectClient` / `workflowClient`** (+ `*SendClient`) — typed RPC into other Restate services.
|
|
77
78
|
- **`genericCall` / `genericSend`** — untyped RPC.
|
|
78
79
|
- **`cancel(invocationId)`** — cancel another invocation.
|
|
@@ -83,7 +84,8 @@ Imported directly from `@restatedev/restate-sdk-gen`:
|
|
|
83
84
|
- **`all(futures)`** — wait for every future, return their values in order. Heterogeneous-tuple typed (mirrors `Promise.all`).
|
|
84
85
|
- **`race(futures)`** — return the first to settle; the losing routines are abandoned once the main operation settles (under the default `onMainExit: "abandon"`), so their results are discarded. Under `onMainExit: "join"` the losers keep running and a loser parked on a never-settling source keeps the handler alive (see Cancellation).
|
|
85
86
|
- **`select({ tag1: future1, tag2: future2, ... })`** — Tokio/Go-style. Returns `{ tag, future }` of the winning branch; switch on `tag` and unwrap `future`.
|
|
86
|
-
- **`spawn(op)`** — register an `Operation` as a new routine; returns a `Future<T>`
|
|
87
|
+
- **`spawn(op)`** — register an `Operation` as a new routine; returns a `Task<T>` (a `Future<T>` plus `interrupt(err?)`). Under the default `onMainExit: "abandon"`, a spawned routine still running when the main operation settles is abandoned — fire-and-forget spawns are **not** guaranteed to complete. To ensure completion, `yield*` the returned future before returning, or run with `{ onMainExit: "join" }`.
|
|
88
|
+
- **`task.interrupt(err?)`** — throw `err` into a spawned routine at its next yield point (verbatim; a default `InterruptedError` if omitted) and abort its in-flight `run` I/O. **Cascades down the spawn subtree** — every routine the task spawned (transitively) is interrupted too, with the same error, so interrupting a parent winds down the whole tree it rooted; routines spawned elsewhere are untouched. The routine's own try/catch may catch and recover (interrupt is swallowable). Under the default `onMainExit: "abandon"`, interrupt-then-return abandons the routine before its cleanup runs — interrupt then `yield*` the task ("interrupt-then-join") to drive its `catch`/`finally`.
|
|
87
89
|
|
|
88
90
|
Combinators have a fast path: when every input Future is journal-backed, they collapse to a single `RestatePromise.all/race`. Otherwise they fall back to a synthesized fiber. Same semantics either way.
|
|
89
91
|
|
package/dist/index.cjs
CHANGED
|
@@ -172,11 +172,12 @@ const any = (futures) => currentOps().any(futures);
|
|
|
172
172
|
*/
|
|
173
173
|
const allSettled = (futures) => currentOps().allSettled(futures);
|
|
174
174
|
/**
|
|
175
|
-
* Register `op` as a fresh routine and return a `
|
|
176
|
-
* eventual outcome
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
175
|
+
* Register `op` as a fresh routine and return a `Task<T>` for its
|
|
176
|
+
* eventual outcome — a `Future<T>` plus `interrupt(err?)` to throw into
|
|
177
|
+
* the routine at its next yield point. Eager — the child is already in
|
|
178
|
+
* flight by the time `spawn` returns. A spawned routine still running
|
|
179
|
+
* when the main operation settles is abandoned by default (see
|
|
180
|
+
* `OnMainExit`). See `RestateOperations.spawn` for full semantics.
|
|
180
181
|
*/
|
|
181
182
|
const spawn = (op) => currentOps().spawn(op);
|
|
182
183
|
|
|
@@ -216,6 +217,43 @@ function* select(branches) {
|
|
|
216
217
|
};
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/context.ts
|
|
222
|
+
function contextLocal(defaultValue) {
|
|
223
|
+
const key = Symbol("restate.contextLocal");
|
|
224
|
+
return {
|
|
225
|
+
get: () => getCurrent().getLocal(key, defaultValue),
|
|
226
|
+
set: (value) => getCurrent().setLocal(key, value)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region src/task.ts
|
|
232
|
+
/**
|
|
233
|
+
* The default error thrown by `interrupt()` when no explicit error is
|
|
234
|
+
* given. A plain `Error` (not a `TerminalError`) — interrupt imposes no
|
|
235
|
+
* blast radius of its own; if you want an uncaught interrupt to fail the
|
|
236
|
+
* invocation terminally, pass a `TerminalError` to `interrupt(err)`.
|
|
237
|
+
*/
|
|
238
|
+
var InterruptedError = class extends Error {
|
|
239
|
+
constructor(message = "Task interrupted") {
|
|
240
|
+
super(message);
|
|
241
|
+
this.name = "InterruptedError";
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Augment a spawned routine's Future with `interrupt`, producing a Task.
|
|
246
|
+
* Takes a plain `(err) => void` callback (the fiber's `interrupt`) rather
|
|
247
|
+
* than the Fiber itself, so this module stays free of scheduler/fiber
|
|
248
|
+
* imports. `Object.assign` mutates and returns the same Future object —
|
|
249
|
+
* its `[Symbol.iterator]` and symbol-keyed backing are untouched, so the
|
|
250
|
+
* Task remains fully yieldable and composable.
|
|
251
|
+
*/
|
|
252
|
+
function makeTask(future, interruptFiber) {
|
|
253
|
+
const interrupt = (err) => interruptFiber(err === void 0 ? new InterruptedError() : err);
|
|
254
|
+
return Object.assign(future, { interrupt });
|
|
255
|
+
}
|
|
256
|
+
|
|
219
257
|
//#endregion
|
|
220
258
|
//#region src/future.ts
|
|
221
259
|
const futureBacking = Symbol("restateFutureBacking");
|
|
@@ -243,6 +281,29 @@ function isJournalBacked(f) {
|
|
|
243
281
|
return getBacking(f).kind === "journal";
|
|
244
282
|
}
|
|
245
283
|
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/abort.ts
|
|
286
|
+
/**
|
|
287
|
+
* Create a controller whose signal is a child of `parent`: pre-aborted
|
|
288
|
+
* if the parent has already fired, otherwise aborted the moment it does.
|
|
289
|
+
*
|
|
290
|
+
* The subscription uses `{ once, signal: c.signal }` so it self-detaches
|
|
291
|
+
* on either exit — when the parent fires (`once`) or when this controller
|
|
292
|
+
* is aborted by some other path (`signal`). A controller that is retired
|
|
293
|
+
* and replaced therefore drops its listener immediately, so at most one
|
|
294
|
+
* listener per child lives on the parent at a time — no accumulation
|
|
295
|
+
* across cancel/recover or interrupt/recover cycles.
|
|
296
|
+
*/
|
|
297
|
+
function linkAbortController(parent) {
|
|
298
|
+
const c = new AbortController();
|
|
299
|
+
if (parent.aborted) c.abort(parent.reason);
|
|
300
|
+
else parent.addEventListener("abort", () => c.abort(parent.reason), {
|
|
301
|
+
once: true,
|
|
302
|
+
signal: c.signal
|
|
303
|
+
});
|
|
304
|
+
return c;
|
|
305
|
+
}
|
|
306
|
+
|
|
246
307
|
//#endregion
|
|
247
308
|
//#region src/fiber.ts
|
|
248
309
|
var Fiber = class {
|
|
@@ -253,9 +314,45 @@ var Fiber = class {
|
|
|
253
314
|
resume: null
|
|
254
315
|
};
|
|
255
316
|
waiters = [];
|
|
256
|
-
|
|
317
|
+
/**
|
|
318
|
+
* Monotonic park-episode id, bumped on every `wake` (invariant I2).
|
|
319
|
+
* Consumed by `epochGuarded` (waiter validity) and `wokenDuringStep`
|
|
320
|
+
* (self-interrupt detection). Generalizes the old per-AwaitRace `won`
|
|
321
|
+
* flag into a persistent, per-fiber guard.
|
|
322
|
+
*/
|
|
323
|
+
epoch = 0;
|
|
324
|
+
/**
|
|
325
|
+
* Whether the generator has been started (`it.next()` called at least
|
|
326
|
+
* once). `it.throw()` on a not-yet-started generator propagates the
|
|
327
|
+
* error out *without* running any body code, bypassing the routine's
|
|
328
|
+
* own try/catch/finally — so an interrupt delivered before the first
|
|
329
|
+
* advance must prime the generator first (see `stepIterator`).
|
|
330
|
+
*/
|
|
331
|
+
started = false;
|
|
332
|
+
/**
|
|
333
|
+
* Lazily-created AbortController for this fiber's `run` closures. Born
|
|
334
|
+
* a child of the scheduler's current signal (so invocation cancellation
|
|
335
|
+
* / attempt-end abort it) and aborted directly by `interrupt`. Recreated
|
|
336
|
+
* when aborted, so cleanup `run`s after a swallowed interrupt see a
|
|
337
|
+
* fresh, unaborted signal. Null until the fiber first needs a run signal.
|
|
338
|
+
*/
|
|
339
|
+
runController = null;
|
|
340
|
+
/**
|
|
341
|
+
* Spawn-tree links. `parent` is the fiber that was advancing when this
|
|
342
|
+
* one was created; `children` are the fibers it spawned (including the
|
|
343
|
+
* synth fibers behind combinator fallbacks). `interrupt` cascades down
|
|
344
|
+
* `children`. On `finish`, a fiber's still-live children are re-homed
|
|
345
|
+
* onto its `parent` (not dropped) so the subtree stays connected to a
|
|
346
|
+
* live ancestor and a later cascade still reaches them — `parent` is
|
|
347
|
+
* therefore reassignable as ancestors complete.
|
|
348
|
+
*/
|
|
349
|
+
parent;
|
|
350
|
+
children = /* @__PURE__ */ new Set();
|
|
351
|
+
constructor(op, sched, parent = null) {
|
|
257
352
|
this.it = op[Symbol.iterator]();
|
|
258
353
|
this.sched = sched;
|
|
354
|
+
this.parent = parent;
|
|
355
|
+
parent?.children.add(this);
|
|
259
356
|
}
|
|
260
357
|
isDone() {
|
|
261
358
|
return this.state.kind === "done";
|
|
@@ -295,9 +392,13 @@ var Fiber = class {
|
|
|
295
392
|
* notifies the scheduler. May be called from any state except done
|
|
296
393
|
* (waking a done fiber is a programming error and is ignored
|
|
297
394
|
* defensively).
|
|
395
|
+
*
|
|
396
|
+
* Bumps the park epoch so any waiters left registered on the targets
|
|
397
|
+
* of the park we're leaving become stale no-ops (see `epoch`).
|
|
298
398
|
*/
|
|
299
399
|
wake(resume) {
|
|
300
400
|
if (this.state.kind === "done") return;
|
|
401
|
+
this.epoch++;
|
|
301
402
|
this.state = {
|
|
302
403
|
kind: "ready",
|
|
303
404
|
resume
|
|
@@ -305,6 +406,72 @@ var Fiber = class {
|
|
|
305
406
|
this.sched.markReady(this);
|
|
306
407
|
}
|
|
307
408
|
/**
|
|
409
|
+
* Wrap a handler as a waiter that fires only while the fiber is still
|
|
410
|
+
* in the park episode that registered it (see `epoch`, invariant I2).
|
|
411
|
+
* A waiter left on a target after the fiber moved on — a re-park after
|
|
412
|
+
* interrupt, or a losing race source — becomes a no-op. This is the
|
|
413
|
+
* single mechanism behind both the AwaitRace "first source wins" dedup
|
|
414
|
+
* and the cross-episode stale-waiter guard.
|
|
415
|
+
*/
|
|
416
|
+
epochGuarded(handler) {
|
|
417
|
+
const at = this.epoch;
|
|
418
|
+
return (s) => {
|
|
419
|
+
if (this.epoch !== at) return;
|
|
420
|
+
handler(s);
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Whether a re-entrant `wake` fired during the step that began at
|
|
425
|
+
* `epochBefore` — i.e. the fiber interrupted *itself* while advancing
|
|
426
|
+
* (the only wake that can target the currently-advancing fiber). It
|
|
427
|
+
* leaves a fresh resume in `this.state` to deliver at this yield rather
|
|
428
|
+
* than parking on the op the body just produced.
|
|
429
|
+
*/
|
|
430
|
+
wokenDuringStep(epochBefore) {
|
|
431
|
+
return this.epoch !== epochBefore && this.state.kind === "ready";
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* The AbortSignal handed to this fiber's `run` closures. Lazily
|
|
435
|
+
* created as a child of the scheduler's current signal, so invocation
|
|
436
|
+
* cancellation and attempt-end abort it like before; additionally,
|
|
437
|
+
* `interrupt` aborts it directly to stop just this fiber's in-flight
|
|
438
|
+
* I/O. Recreated once aborted, so a `run` issued after a swallowed
|
|
439
|
+
* interrupt (or after cancellation recovery) gets a fresh signal.
|
|
440
|
+
*/
|
|
441
|
+
runSignal() {
|
|
442
|
+
if (this.runController === null || this.runController.signal.aborted) this.runController = linkAbortController(this.sched.abortSignal);
|
|
443
|
+
return this.runController.signal;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Throw `err` into this fiber at its next yield point, and abort its
|
|
447
|
+
* in-flight `run` I/O.
|
|
448
|
+
*
|
|
449
|
+
* Two effects: (1) abort the per-fiber run signal so an in-flight
|
|
450
|
+
* `run(fetch(url, { signal }))` stops promptly — harmless if the fiber
|
|
451
|
+
* has no live controller; the next `runSignal()` makes a fresh one for
|
|
452
|
+
* post-interrupt cleanup. (2) wake the fiber with a failure resume so
|
|
453
|
+
* `stepIterator` delivers `it.throw(err)` at the next advance — the
|
|
454
|
+
* fiber's own try/catch may catch and recover (interrupt is
|
|
455
|
+
* swallowable / non-sticky). A done fiber is left untouched (`wake`
|
|
456
|
+
* no-ops). Same machinery as the cancellation fan-out, scoped to one
|
|
457
|
+
* fiber.
|
|
458
|
+
*
|
|
459
|
+
* Self-interrupt (a fiber interrupting its own task while advancing)
|
|
460
|
+
* is uniform: the re-entrant `wake` fires during the fiber's own
|
|
461
|
+
* step, and `advance` detects the epoch bump and delivers the throw at
|
|
462
|
+
* the fiber's next yield rather than parking (see `advance`). If the
|
|
463
|
+
* body returns before reaching another yield, the self-interrupt is
|
|
464
|
+
* moot — there is no yield point to throw at.
|
|
465
|
+
*/
|
|
466
|
+
interrupt(err) {
|
|
467
|
+
this.runController?.abort(err);
|
|
468
|
+
this.wake({
|
|
469
|
+
ok: false,
|
|
470
|
+
e: err
|
|
471
|
+
});
|
|
472
|
+
for (const child of this.children) child.interrupt(err);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
308
475
|
* Drive the fiber's iterator until it parks (yields a primitive
|
|
309
476
|
* whose dispatch ends with the fiber waiting on a source) or
|
|
310
477
|
* finishes (returns or throws).
|
|
@@ -318,9 +485,10 @@ var Fiber = class {
|
|
|
318
485
|
try {
|
|
319
486
|
let resume = this.state.resume;
|
|
320
487
|
while (true) {
|
|
488
|
+
const epochBefore = this.epoch;
|
|
321
489
|
let next;
|
|
322
490
|
try {
|
|
323
|
-
next = stepIterator(this.it, resume);
|
|
491
|
+
next = stepIterator(this.it, resume, this.started);
|
|
324
492
|
} catch (e) {
|
|
325
493
|
this.finish({
|
|
326
494
|
ok: false,
|
|
@@ -328,6 +496,7 @@ var Fiber = class {
|
|
|
328
496
|
});
|
|
329
497
|
return;
|
|
330
498
|
}
|
|
499
|
+
this.started = true;
|
|
331
500
|
if (next.done) {
|
|
332
501
|
this.finish({
|
|
333
502
|
ok: true,
|
|
@@ -335,6 +504,10 @@ var Fiber = class {
|
|
|
335
504
|
});
|
|
336
505
|
return;
|
|
337
506
|
}
|
|
507
|
+
if (this.wokenDuringStep(epochBefore)) {
|
|
508
|
+
resume = this.state.resume;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
338
511
|
const node = next.value[opTag];
|
|
339
512
|
let outcome;
|
|
340
513
|
switch (node._tag) {
|
|
@@ -371,7 +544,7 @@ var Fiber = class {
|
|
|
371
544
|
};
|
|
372
545
|
return null;
|
|
373
546
|
}
|
|
374
|
-
const settled = backing.target.awaitCompletion((s) => this.wake(s));
|
|
547
|
+
const settled = backing.target.awaitCompletion(this.epochGuarded((s) => this.wake(s)));
|
|
375
548
|
if (settled !== null) return settled;
|
|
376
549
|
this.state = {
|
|
377
550
|
kind: "parked",
|
|
@@ -385,11 +558,13 @@ var Fiber = class {
|
|
|
385
558
|
* (wrapped as Settled) on short-circuit, or null if parked.
|
|
386
559
|
*
|
|
387
560
|
* On the parked path, every source registers a one-shot fire
|
|
388
|
-
* callback that wakes the fiber with `{index, settled}`. The
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
561
|
+
* callback that wakes the fiber with `{index, settled}`. The epoch
|
|
562
|
+
* guard ensures only the first source to settle wakes the fiber
|
|
563
|
+
* (`wake` bumps the epoch, so any later same-tick fire sees a stale
|
|
564
|
+
* epoch and no-ops) and that a waiter surviving an interrupt-and-
|
|
565
|
+
* re-park can't fire onto the moved-on fiber. Local sources (fibers,
|
|
566
|
+
* channels) park on the target's waiter list; journal sources race in
|
|
567
|
+
* the main loop's race promise.
|
|
393
568
|
*/
|
|
394
569
|
parkOnAwaitRace(futures) {
|
|
395
570
|
for (let i = 0; i < futures.length; i++) {
|
|
@@ -402,22 +577,17 @@ var Fiber = class {
|
|
|
402
577
|
}
|
|
403
578
|
};
|
|
404
579
|
}
|
|
405
|
-
let won = false;
|
|
406
580
|
const promises = [];
|
|
407
581
|
for (let i = 0; i < futures.length; i++) {
|
|
408
582
|
const idx = i;
|
|
409
583
|
const b = getBacking(futures[i]);
|
|
410
|
-
const fireOnce = (settled) => {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
settled
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
};
|
|
584
|
+
const fireOnce = this.epochGuarded((settled) => this.wake({
|
|
585
|
+
ok: true,
|
|
586
|
+
v: {
|
|
587
|
+
index: idx,
|
|
588
|
+
settled
|
|
589
|
+
}
|
|
590
|
+
}));
|
|
421
591
|
if (b.kind === "local") b.target.awaitCompletion(fireOnce);
|
|
422
592
|
else promises.push({
|
|
423
593
|
promise: b.promise,
|
|
@@ -439,6 +609,13 @@ var Fiber = class {
|
|
|
439
609
|
kind: "done",
|
|
440
610
|
settled
|
|
441
611
|
};
|
|
612
|
+
const gp = this.parent;
|
|
613
|
+
for (const child of this.children) {
|
|
614
|
+
child.parent = gp;
|
|
615
|
+
gp?.children.add(child);
|
|
616
|
+
}
|
|
617
|
+
this.children.clear();
|
|
618
|
+
gp?.children.delete(this);
|
|
442
619
|
const waiters = this.waiters;
|
|
443
620
|
this.waiters = [];
|
|
444
621
|
for (const w of waiters) w(settled);
|
|
@@ -451,10 +628,23 @@ var Fiber = class {
|
|
|
451
628
|
* very first step; `{ok: true, v}` resumes with a value; `{ok: false,
|
|
452
629
|
* e}` throws into the iterator (or, if the iterator has no `throw`
|
|
453
630
|
* method, rethrows so the fiber fails).
|
|
631
|
+
*
|
|
632
|
+
* `started` is whether the generator has run at least once. Throwing
|
|
633
|
+
* into a *not-yet-started* generator propagates the error straight out
|
|
634
|
+
* without executing any body code (its try/catch/finally never engage).
|
|
635
|
+
* That happens when a spawned routine is interrupted before its first
|
|
636
|
+
* advance. To honor the "delivered at the next yield point" /
|
|
637
|
+
* swallowable contract, we prime the generator with one `next()` first,
|
|
638
|
+
* so the throw lands at the body's first yield. If the body returns
|
|
639
|
+
* before reaching a yield, there is no yield point and the throw is moot.
|
|
454
640
|
*/
|
|
455
|
-
function stepIterator(it, resume) {
|
|
641
|
+
function stepIterator(it, resume, started) {
|
|
456
642
|
if (resume === null) return it.next(void 0);
|
|
457
643
|
if (resume.ok) return it.next(resume.v);
|
|
644
|
+
if (!started) {
|
|
645
|
+
const first = it.next(void 0);
|
|
646
|
+
if (first.done) return first;
|
|
647
|
+
}
|
|
458
648
|
if (it.throw) return it.throw(resume.e);
|
|
459
649
|
throw resume.e;
|
|
460
650
|
}
|
|
@@ -524,6 +714,13 @@ var Scheduler = class {
|
|
|
524
714
|
* production `execute()` always constructs a fresh scheduler.
|
|
525
715
|
*/
|
|
526
716
|
main = null;
|
|
717
|
+
/**
|
|
718
|
+
* The fiber currently being advanced by `drainReady`, or null between
|
|
719
|
+
* advances. `run` reads this (via `currentRunSignal`) to hand a `run`
|
|
720
|
+
* closure the *current fiber's* signal rather than a single global one,
|
|
721
|
+
* so a targeted `interrupt` aborts only that fiber's in-flight I/O.
|
|
722
|
+
*/
|
|
723
|
+
advancingFiber = null;
|
|
527
724
|
abortController;
|
|
528
725
|
/**
|
|
529
726
|
* Parent AbortSignal supplied at construction (production: the SDK's
|
|
@@ -546,6 +743,16 @@ var Scheduler = class {
|
|
|
546
743
|
* keep this module independent of `restate-operations.ts`.
|
|
547
744
|
*/
|
|
548
745
|
contextSlot;
|
|
746
|
+
/**
|
|
747
|
+
* Context-local storage: an ambient key/value bag scoped to this
|
|
748
|
+
* scheduler — i.e. to this one `execute()` call, since production
|
|
749
|
+
* builds a fresh scheduler per invocation (like `fibers` / `ready` /
|
|
750
|
+
* `abortController`, this is per-run state, not shared across runs).
|
|
751
|
+
* Every fiber under this scheduler reaches the same bag (global-per-
|
|
752
|
+
* invocation; see `context.ts`). Keyed by the opaque symbol a
|
|
753
|
+
* `contextLocal()` handle owns. In-memory only — never journaled.
|
|
754
|
+
*/
|
|
755
|
+
localStore = /* @__PURE__ */ new Map();
|
|
549
756
|
constructor(lib, parentSignal, options) {
|
|
550
757
|
this.lib = lib;
|
|
551
758
|
this.onMainExit = options?.onMainExit ?? "abandon";
|
|
@@ -580,13 +787,18 @@ var Scheduler = class {
|
|
|
580
787
|
* attempt itself is over — a parent abort is sticky.
|
|
581
788
|
*/
|
|
582
789
|
newAbortController() {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
790
|
+
return linkAbortController(this.parentSignal);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* The AbortSignal for the currently-advancing fiber's `run` closures —
|
|
794
|
+
* scoped per fiber so a targeted `interrupt(task)` aborts only that
|
|
795
|
+
* task's in-flight I/O, while invocation cancellation / attempt-end
|
|
796
|
+
* still cascade in (the fiber signal is a child of `abortSignal`).
|
|
797
|
+
* Falls back to the scheduler signal when no fiber is advancing (a
|
|
798
|
+
* defensive case — `run` is always called from inside a fiber).
|
|
799
|
+
*/
|
|
800
|
+
currentRunSignal() {
|
|
801
|
+
return this.advancingFiber !== null ? this.advancingFiber.runSignal() : this.abortSignal;
|
|
590
802
|
}
|
|
591
803
|
markReady(f) {
|
|
592
804
|
this.ready.push(f);
|
|
@@ -595,7 +807,7 @@ var Scheduler = class {
|
|
|
595
807
|
this.fibers.delete(f);
|
|
596
808
|
}
|
|
597
809
|
createFiber(op) {
|
|
598
|
-
const f = new Fiber(op, this);
|
|
810
|
+
const f = new Fiber(op, this, this.advancingFiber);
|
|
599
811
|
this.fibers.add(f);
|
|
600
812
|
this.ready.push(f);
|
|
601
813
|
return f;
|
|
@@ -607,24 +819,44 @@ var Scheduler = class {
|
|
|
607
819
|
});
|
|
608
820
|
}
|
|
609
821
|
/**
|
|
610
|
-
* Register an Operation as a fresh fiber
|
|
611
|
-
*
|
|
612
|
-
* the fiber is queued ready and will be advanced on the
|
|
822
|
+
* Register an Operation as a fresh fiber, returning both its
|
|
823
|
+
* local-backed Future and the underlying Fiber. Eager: by the time
|
|
824
|
+
* this returns, the fiber is queued ready and will be advanced on the
|
|
825
|
+
* next drain.
|
|
613
826
|
*
|
|
614
827
|
* Lifetime is bounded by the main fiber under the default
|
|
615
828
|
* `"abandon"` policy: a spawned fiber still running when the main
|
|
616
829
|
* fiber settles is abandoned (see {@link OnMainExit}). Under
|
|
617
830
|
* `"join"` it keeps the scheduler alive until it finishes.
|
|
618
831
|
*
|
|
619
|
-
*
|
|
620
|
-
* `
|
|
621
|
-
|
|
832
|
+
* Internal: combinator fallbacks (race, allSettled, …) use this to get
|
|
833
|
+
* a plain Future. The public `spawn` wraps the same pair into a `Task`.
|
|
834
|
+
*/
|
|
835
|
+
spawnRoutine(op) {
|
|
836
|
+
const fiber = this.createFiber(op);
|
|
837
|
+
return {
|
|
838
|
+
future: makeFuture({
|
|
839
|
+
kind: "local",
|
|
840
|
+
target: fiber
|
|
841
|
+
}),
|
|
842
|
+
fiber
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
/** Spawn returning just the Future — for combinator fallbacks, which
|
|
846
|
+
* never expose `interrupt` on their synthesized fibers. */
|
|
847
|
+
spawnFuture(op) {
|
|
848
|
+
return this.spawnRoutine(op).future;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Register an Operation as a fresh fiber and return a `Task<U>` — a
|
|
852
|
+
* `Future<U>` for its eventual value, plus `interrupt(err?)` to throw
|
|
853
|
+
* into it at its next yield point. Eager: by the time this returns the
|
|
854
|
+
* fiber is queued ready. Reached by `RestateOperations.spawn` and the
|
|
855
|
+
* free `spawn` function.
|
|
622
856
|
*/
|
|
623
857
|
spawn(op) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
target: this.createFiber(op)
|
|
627
|
-
});
|
|
858
|
+
const { future, fiber } = this.spawnRoutine(op);
|
|
859
|
+
return makeTask(future, (err) => fiber.interrupt(err));
|
|
628
860
|
}
|
|
629
861
|
/**
|
|
630
862
|
* Construct a single-shot in-memory channel. Send must be called from
|
|
@@ -635,6 +867,19 @@ var Scheduler = class {
|
|
|
635
867
|
return makeChannel();
|
|
636
868
|
}
|
|
637
869
|
/**
|
|
870
|
+
* Read a context-local value, or `fallback` if `key` was never set in
|
|
871
|
+
* this invocation. `has`-checked so an explicit `setLocal(key,
|
|
872
|
+
* undefined)` reads back as `undefined` rather than falling through to
|
|
873
|
+
* the handle's default.
|
|
874
|
+
*/
|
|
875
|
+
getLocal(key, fallback) {
|
|
876
|
+
return this.localStore.has(key) ? this.localStore.get(key) : fallback;
|
|
877
|
+
}
|
|
878
|
+
/** Write a context-local value for the rest of this invocation. */
|
|
879
|
+
setLocal(key, value) {
|
|
880
|
+
this.localStore.set(key, value);
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
638
883
|
* Combinator over Futures. Fast path when every input is journal-
|
|
639
884
|
* backed: use the lib's all/race for a single combinator entry.
|
|
640
885
|
* Otherwise, fall back to a synthesized fiber that yields each in
|
|
@@ -651,7 +896,7 @@ var Scheduler = class {
|
|
|
651
896
|
const promises = fs.map((f) => f[futureBacking].promise);
|
|
652
897
|
return this.makeJournalFuture(this.lib.all(promises));
|
|
653
898
|
}
|
|
654
|
-
return this.
|
|
899
|
+
return this.spawnFuture(gen(function* () {
|
|
655
900
|
const out = new Array(fs.length);
|
|
656
901
|
for (let i = 0; i < fs.length; i++) out[i] = yield* fs[i];
|
|
657
902
|
return out;
|
|
@@ -659,7 +904,7 @@ var Scheduler = class {
|
|
|
659
904
|
}
|
|
660
905
|
race(futures) {
|
|
661
906
|
const fs = futures;
|
|
662
|
-
return this.
|
|
907
|
+
return this.spawnFuture(gen(function* () {
|
|
663
908
|
const result = yield* awaitRace(fs);
|
|
664
909
|
if (result.settled.ok) return result.settled.v;
|
|
665
910
|
throw result.settled.e;
|
|
@@ -686,7 +931,7 @@ var Scheduler = class {
|
|
|
686
931
|
const promises = fs.map((f) => f[futureBacking].promise);
|
|
687
932
|
return this.makeJournalFuture(this.lib.any(promises));
|
|
688
933
|
}
|
|
689
|
-
return this.
|
|
934
|
+
return this.spawnFuture(gen(function* () {
|
|
690
935
|
const errors = new Array(fs.length);
|
|
691
936
|
const remaining = /* @__PURE__ */ new Set();
|
|
692
937
|
for (let i = 0; i < fs.length; i++) remaining.add(i);
|
|
@@ -720,7 +965,7 @@ var Scheduler = class {
|
|
|
720
965
|
const promises = fs.map((f) => f[futureBacking].promise);
|
|
721
966
|
return this.makeJournalFuture(this.lib.allSettled(promises));
|
|
722
967
|
}
|
|
723
|
-
return this.
|
|
968
|
+
return this.spawnFuture(gen(function* () {
|
|
724
969
|
const out = new Array(fs.length);
|
|
725
970
|
for (let i = 0; i < fs.length; i++) try {
|
|
726
971
|
out[i] = {
|
|
@@ -748,7 +993,16 @@ var Scheduler = class {
|
|
|
748
993
|
return this.onMainExit === "abandon" && this.main !== null && this.main.isDone();
|
|
749
994
|
}
|
|
750
995
|
drainReady() {
|
|
751
|
-
while (this.ready.length > 0 && !this.mainExited())
|
|
996
|
+
while (this.ready.length > 0 && !this.mainExited()) {
|
|
997
|
+
const f = this.ready.shift();
|
|
998
|
+
const prev = this.advancingFiber;
|
|
999
|
+
this.advancingFiber = f;
|
|
1000
|
+
try {
|
|
1001
|
+
f.advance();
|
|
1002
|
+
} finally {
|
|
1003
|
+
this.advancingFiber = prev;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
752
1006
|
}
|
|
753
1007
|
/**
|
|
754
1008
|
* Run an operation to completion. Drain the ready queue, then loop:
|
|
@@ -1032,6 +1286,12 @@ var RestateOperations = class {
|
|
|
1032
1286
|
isProcessing() {
|
|
1033
1287
|
return this.ctx.isProcessing();
|
|
1034
1288
|
}
|
|
1289
|
+
getLocal(key, fallback) {
|
|
1290
|
+
return this.sched.getLocal(key, fallback);
|
|
1291
|
+
}
|
|
1292
|
+
setLocal(key, value) {
|
|
1293
|
+
this.sched.setLocal(key, value);
|
|
1294
|
+
}
|
|
1035
1295
|
/**
|
|
1036
1296
|
* Run a side-effecting closure as a journal entry.
|
|
1037
1297
|
*
|
|
@@ -1073,7 +1333,7 @@ var RestateOperations = class {
|
|
|
1073
1333
|
*/
|
|
1074
1334
|
run(action, opts) {
|
|
1075
1335
|
const name = resolveRunName(action, opts);
|
|
1076
|
-
const wrapped = wrapActionForCancellation(this.sched.
|
|
1336
|
+
const wrapped = wrapActionForCancellation(this.sched.currentRunSignal(), action);
|
|
1077
1337
|
return this.sched.makeJournalFuture(adapt(this.ctx.run(name, wrapped, toSdkRunOptions(opts))));
|
|
1078
1338
|
}
|
|
1079
1339
|
sleep(duration, name) {
|
|
@@ -1172,6 +1432,9 @@ var RestateOperations = class {
|
|
|
1172
1432
|
* A spawned routine still running when the main operation settles is
|
|
1173
1433
|
* abandoned by default (its handler returns without it); pass
|
|
1174
1434
|
* `{ onMainExit: "join" }` to `execute` to wait for it instead.
|
|
1435
|
+
*
|
|
1436
|
+
* Returns a `Task<T>` — a `Future<T>` plus `interrupt(err?)` to throw
|
|
1437
|
+
* into the routine at its next yield point.
|
|
1175
1438
|
*/
|
|
1176
1439
|
spawn(op) {
|
|
1177
1440
|
return this.sched.spawn(op);
|
|
@@ -1611,6 +1874,7 @@ function isProcessing() {
|
|
|
1611
1874
|
}
|
|
1612
1875
|
|
|
1613
1876
|
//#endregion
|
|
1877
|
+
exports.InterruptedError = InterruptedError;
|
|
1614
1878
|
exports.all = all;
|
|
1615
1879
|
exports.allSettled = allSettled;
|
|
1616
1880
|
exports.any = any;
|
|
@@ -1626,6 +1890,7 @@ Object.defineProperty(exports, 'clients', {
|
|
|
1626
1890
|
return ingress_exports;
|
|
1627
1891
|
}
|
|
1628
1892
|
});
|
|
1893
|
+
exports.contextLocal = contextLocal;
|
|
1629
1894
|
exports.date = date;
|
|
1630
1895
|
exports.execute = execute;
|
|
1631
1896
|
exports.gen = gen;
|