@executablemd/durable-streams 0.7.0 → 0.8.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/esm/_dnt.polyfills.js +1 -0
- package/esm/combinators.js +54 -11
- package/esm/context.js +1 -1
- package/esm/durability.js +118 -0
- package/esm/effect.js +64 -24
- package/esm/errors.js +54 -7
- package/esm/guard.js +67 -1
- package/esm/live-coordinator.js +16 -0
- package/esm/mod.js +14 -2
- package/esm/parse.js +206 -0
- package/esm/replay-guard.js +21 -3
- package/esm/replay-index.js +53 -17
- package/esm/retained.js +390 -0
- package/esm/run.js +74 -29
- package/package.json +2 -2
- package/types/_dnt.polyfills.d.ts +6 -0
- package/types/context.d.ts +11 -2
- package/types/durability.d.ts +7 -0
- package/types/effect.d.ts +6 -1
- package/types/errors.d.ts +42 -3
- package/types/guard.d.ts +35 -1
- package/types/live-coordinator.d.ts +11 -0
- package/types/mod.d.ts +10 -5
- package/types/parse.d.ts +23 -0
- package/types/replay-guard.d.ts +39 -3
- package/types/replay-index.d.ts +24 -12
- package/types/retained.d.ts +83 -0
- package/types/run.d.ts +2 -1
package/types/errors.d.ts
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
* Error types for the durable execution protocol.
|
|
3
3
|
*/
|
|
4
4
|
import type { CoroutineId, EffectDescription } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Raised when a durable event cannot be persisted.
|
|
7
|
+
*
|
|
8
|
+
* Persistence failures are protocol failures, not workflow outcomes. The
|
|
9
|
+
* adapter error remains available as the cause, and no compensating Close is
|
|
10
|
+
* written over the unpersisted event.
|
|
11
|
+
*/
|
|
12
|
+
export declare class DurablePersistenceError extends Error {
|
|
13
|
+
name: string;
|
|
14
|
+
constructor(eventType: "yield" | "close", cause: unknown);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Raised when a persisted record does not describe a `DurableEvent`.
|
|
18
|
+
*
|
|
19
|
+
* `path` locates the offending member within the record, such as
|
|
20
|
+
* `$.result.error.message`. Members the protocol does not name appear as `*`,
|
|
21
|
+
* because a record's own member names are as much retained content as its
|
|
22
|
+
* values. Neither the path nor the message repeats anything from the record: a
|
|
23
|
+
* journal is retained, filtered history, and a parse failure is not a reason to
|
|
24
|
+
* copy its contents into an error that travels to logs and terminals.
|
|
25
|
+
*/
|
|
26
|
+
export declare class MalformedDurableEventError extends Error {
|
|
27
|
+
name: string;
|
|
28
|
+
/** Location of the offending member within the record. */
|
|
29
|
+
path: string;
|
|
30
|
+
constructor(reason: string, path: string);
|
|
31
|
+
}
|
|
5
32
|
/**
|
|
6
33
|
* Raised when the replay index entry at the current cursor position
|
|
7
34
|
* does not match the effect yielded by the generator. See spec §6.2.
|
|
@@ -22,14 +49,26 @@ export declare class DivergenceError extends Error {
|
|
|
22
49
|
constructor(coroutineId: CoroutineId, position: number, expected: EffectDescription, actual: EffectDescription, message?: string);
|
|
23
50
|
}
|
|
24
51
|
/**
|
|
25
|
-
* Raised when
|
|
26
|
-
*
|
|
52
|
+
* Raised when a workflow terminates while replay still has unconsumed entries.
|
|
53
|
+
* The retained journal describes effects that the current execution did not
|
|
54
|
+
* reach, so no terminal Close may be appended over that history.
|
|
27
55
|
*/
|
|
28
|
-
export declare class
|
|
56
|
+
export declare class TerminalDivergenceError extends Error {
|
|
29
57
|
name: string;
|
|
30
58
|
coroutineId: CoroutineId;
|
|
31
59
|
consumedCount: number;
|
|
32
60
|
totalCount: number;
|
|
61
|
+
constructor(coroutineId: CoroutineId, consumedCount: number, totalCount: number, options?: {
|
|
62
|
+
cause?: unknown;
|
|
63
|
+
message?: string;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Raised when the generator finishes (returns) while the replay index
|
|
68
|
+
* still has unconsumed entries for this coroutine. See spec §6.3.
|
|
69
|
+
*/
|
|
70
|
+
export declare class EarlyReturnDivergenceError extends TerminalDivergenceError {
|
|
71
|
+
name: string;
|
|
33
72
|
constructor(coroutineId: CoroutineId, consumedCount: number, totalCount: number);
|
|
34
73
|
}
|
|
35
74
|
/**
|
package/types/guard.d.ts
CHANGED
|
@@ -13,9 +13,39 @@
|
|
|
13
13
|
* twice, which preserves the protocol invariant that one durable yield
|
|
14
14
|
* produces at most one journal event.
|
|
15
15
|
*/
|
|
16
|
-
import type
|
|
16
|
+
import { type Operation } from "effection";
|
|
17
17
|
import type { DurableStream } from "./stream.js";
|
|
18
18
|
import type { DurableEvent } from "./types.js";
|
|
19
|
+
declare class JournalProvenance {
|
|
20
|
+
#private;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A non-operational, equality-only witness that a stream descends from the
|
|
24
|
+
* exact journal backend a provider selected.
|
|
25
|
+
*
|
|
26
|
+
* It grants no append, read, execution, publication or reconciliation
|
|
27
|
+
* capability. It is meaningful only because the provider retains the witness
|
|
28
|
+
* it established and later requires exact equality.
|
|
29
|
+
*/
|
|
30
|
+
export type { JournalProvenance };
|
|
31
|
+
/** Establish the provenance a provider retains for one exact journal backend. */
|
|
32
|
+
export declare function establishJournalProvenance(stream: DurableStream): JournalProvenance;
|
|
33
|
+
/**
|
|
34
|
+
* Carry an exact source stream's provenance onto a trusted wrapper of it.
|
|
35
|
+
*
|
|
36
|
+
* Preservation is visible composition rather than new authority: it transfers
|
|
37
|
+
* only the witness already associated with that exact source, so an unproven
|
|
38
|
+
* source leaves the target unproven. The target is returned so the wrapping
|
|
39
|
+
* site reads as one expression.
|
|
40
|
+
*/
|
|
41
|
+
export declare function preserveJournalProvenance(source: DurableStream, target: DurableStream): DurableStream;
|
|
42
|
+
/** @internal The live durable path reads provenance without receiving stream authority. */
|
|
43
|
+
export declare function getJournalProvenance(stream: DurableStream): JournalProvenance | undefined;
|
|
44
|
+
export interface DurableEventRejectionOccurrence {
|
|
45
|
+
rejected: boolean;
|
|
46
|
+
error?: unknown;
|
|
47
|
+
}
|
|
48
|
+
export declare function withDurableEventRejectionOccurrence(occurrence: DurableEventRejectionOccurrence, operation: () => Operation<void>): Operation<void>;
|
|
19
49
|
/**
|
|
20
50
|
* A check that runs before a durable event is persisted.
|
|
21
51
|
*
|
|
@@ -38,5 +68,9 @@ export type DurableEventGate = (event: DurableEvent) => Operation<void>;
|
|
|
38
68
|
* Rejection is per event. The rejected event never reaches the backend, but
|
|
39
69
|
* the resulting failure may lead the workflow to append a later `Close`
|
|
40
70
|
* event with an `err` result, and that close crosses the gate on its own.
|
|
71
|
+
*
|
|
72
|
+
* The guard is policy-neutral, so the wrapper it returns is unproven. An
|
|
73
|
+
* authorized wrapping site preserves journal provenance explicitly through
|
|
74
|
+
* {@link preserveJournalProvenance}.
|
|
41
75
|
*/
|
|
42
76
|
export declare function guardDurableStream(stream: DurableStream, gate: DurableEventGate): DurableStream;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Operation } from "effection";
|
|
2
|
+
import type { JournalProvenance } from "./guard.js";
|
|
3
|
+
import type { Json, Result } from "./types.js";
|
|
4
|
+
/** Activates the first infrastructure failure for the enclosing durable run. */
|
|
5
|
+
export type ActivateDurabilityFailure = (failure: unknown) => Error;
|
|
6
|
+
/** Coordinates one live structured durable operation with its publication. */
|
|
7
|
+
export interface LiveDurableOperationCoordinator {
|
|
8
|
+
run<T extends Json>(execute: () => Operation<T>, publish: (result: Result) => Operation<void>, activateFailure: ActivateDurabilityFailure, journalProvenance: JournalProvenance | undefined): Operation<Result>;
|
|
9
|
+
}
|
|
10
|
+
/** The ordinary live path: execute once, publish once, then return the same result. */
|
|
11
|
+
export declare const defaultLiveDurableOperationCoordinator: LiveDurableOperationCoordinator;
|
package/types/mod.d.ts
CHANGED
|
@@ -5,25 +5,30 @@
|
|
|
5
5
|
* Implements the two-event durable execution protocol for generator-based
|
|
6
6
|
* structured concurrency, with Durable Streams as the persistence backend.
|
|
7
7
|
*/
|
|
8
|
+
import "./_dnt.polyfills.js";
|
|
8
9
|
export type { Close, CoroutineId, CoroutineView, DurableEffect, DurableEvent, EffectDescription, EffectionResult, Json, Resolve, Result, SerializedError, Workflow, Yield, } from "./types.js";
|
|
9
10
|
export { ReplayIndex } from "./replay-index.js";
|
|
11
|
+
export { retainEvents } from "./retained.js";
|
|
10
12
|
export type { YieldEntry } from "./replay-index.js";
|
|
11
13
|
export type { DurableStream } from "./stream.js";
|
|
12
14
|
export { InMemoryStream } from "./stream.js";
|
|
13
15
|
export { guardDurableStream } from "./guard.js";
|
|
14
|
-
export
|
|
16
|
+
export { establishJournalProvenance, preserveJournalProvenance } from "./guard.js";
|
|
17
|
+
export type { DurableEventGate, JournalProvenance } from "./guard.js";
|
|
15
18
|
export { useHttpDurableStream } from "./http-stream.js";
|
|
16
19
|
export type { HttpDurableStreamHandle, HttpDurableStreamOptions } from "./http-stream.js";
|
|
17
|
-
export { ContinuePastCloseDivergenceError, DivergenceError, EarlyReturnDivergenceError, StaleInputError, } from "./errors.js";
|
|
20
|
+
export { ContinuePastCloseDivergenceError, DivergenceError, DurablePersistenceError, EarlyReturnDivergenceError, MalformedDurableEventError, StaleInputError, TerminalDivergenceError, } from "./errors.js";
|
|
18
21
|
export { Divergence } from "./divergence.js";
|
|
19
22
|
export type { DivergenceDecision, DivergenceInfo, DivergenceKind } from "./divergence.js";
|
|
20
23
|
export { ReplayGuard } from "./replay-guard.js";
|
|
21
|
-
export type { ReplayOutcome } from "./replay-guard.js";
|
|
22
|
-
export {
|
|
23
|
-
export type { DurableContext } from "./context.js";
|
|
24
|
+
export type { ReplayOutcome, RetainedHistory } from "./replay-guard.js";
|
|
25
|
+
export { DurableContext } from "./context.js";
|
|
24
26
|
export { deserializeError, effectionToProtocol, protocolToEffection, serializeDurableEvent, serializeError, } from "./serialize.js";
|
|
27
|
+
export { parseDurableEvent } from "./parse.js";
|
|
25
28
|
export { createDurableEffect, createDurableOperation } from "./effect.js";
|
|
26
29
|
export type { Executor } from "./effect.js";
|
|
30
|
+
export { defaultLiveDurableOperationCoordinator } from "./live-coordinator.js";
|
|
31
|
+
export type { ActivateDurabilityFailure, LiveDurableOperationCoordinator, } from "./live-coordinator.js";
|
|
27
32
|
export { durableAction, durableCall, durableSleep, versionCheck } from "./operations.js";
|
|
28
33
|
export { durableAll, durableRace, durableSpawn } from "./combinators.js";
|
|
29
34
|
export { durableEach } from "./each.js";
|
package/types/parse.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `parseDurableEvent` is the typed inverse of `serializeDurableEvent`.
|
|
3
|
+
*
|
|
4
|
+
* A backend that retains the NDJSON record — a journal file, a SQLite
|
|
5
|
+
* column — reads it back through this function. The record is parsed,
|
|
6
|
+
* never trusted: every member is checked against the protocol types and
|
|
7
|
+
* the returned event is rebuilt from the checked members, so nothing
|
|
8
|
+
* reaches replay because it merely looked plausible.
|
|
9
|
+
*
|
|
10
|
+
* The closed shapes — the event envelope, a protocol `Result`, a
|
|
11
|
+
* `SerializedError` — reject members they do not declare. An
|
|
12
|
+
* `EffectDescription` declares an index signature, so its extra members
|
|
13
|
+
* are admitted as `Json`; those are the input fields replay guards read.
|
|
14
|
+
*/
|
|
15
|
+
import type { DurableEvent, EffectionResult } from "./types.js";
|
|
16
|
+
/**
|
|
17
|
+
* Parse one NDJSON record produced by `serializeDurableEvent`.
|
|
18
|
+
*
|
|
19
|
+
* The record's terminating newline is optional — `JSON.parse` ignores
|
|
20
|
+
* trailing whitespace — so a stored record and a line split out of a
|
|
21
|
+
* journal file both parse.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseDurableEvent(record: string): EffectionResult<DurableEvent>;
|
package/types/replay-guard.d.ts
CHANGED
|
@@ -12,14 +12,26 @@
|
|
|
12
12
|
* (e.g., content hash, status code). There is no separate metadata field —
|
|
13
13
|
* inputs belong in the effect description, outputs belong in the result.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* A guard is **composable policy, not authority**. Guards compose through
|
|
16
|
+
* `Api.around`, and a handler installed further out may decline to call `next`.
|
|
17
|
+
* That is what composition is for, and it is why an invariant that must not be
|
|
18
|
+
* negotiable — durable identity above all — belongs somewhere a caller cannot
|
|
19
|
+
* replace, such as inside the `DurableStream` a consumer hands to `durableRun`.
|
|
20
|
+
*
|
|
21
|
+
* The API has three stages:
|
|
16
22
|
*
|
|
17
23
|
* 1. **check** (before replay begins): Runs in generator context inside
|
|
18
24
|
* `durableRun`, after the journal is loaded but before the workflow starts.
|
|
19
25
|
* I/O is allowed — this is where file hashing, network checks, and other
|
|
20
26
|
* observation-gathering happens. Results are cached in middleware closures.
|
|
21
27
|
*
|
|
22
|
-
* 2. **
|
|
28
|
+
* 2. **admit** (after every check, before terminal reuse): Runs once with the
|
|
29
|
+
* retained history as a whole. A guard that requires something of the
|
|
30
|
+
* history rather than of one event — that an event it validates is present,
|
|
31
|
+
* and present once — refuses here, because a per-event check has nothing to
|
|
32
|
+
* object to in a journal that omits the event. Default is a no-op.
|
|
33
|
+
*
|
|
34
|
+
* 3. **decide** (during replay): Runs synchronously inside
|
|
23
35
|
* `DurableEffect.enter()`, after identity matching succeeds but before
|
|
24
36
|
* the stored result is fed to the generator. Must be pure and side-effect-
|
|
25
37
|
* free. Reads from the cache populated during the check phase.
|
|
@@ -31,7 +43,24 @@
|
|
|
31
43
|
* See replay-guard-spec.md for the full design.
|
|
32
44
|
*/
|
|
33
45
|
import type { Api, Operation } from "effection";
|
|
34
|
-
import type { Yield } from "./types.js";
|
|
46
|
+
import type { CoroutineId, Yield } from "./types.js";
|
|
47
|
+
/**
|
|
48
|
+
* The retained history a run is about to replay, offered once.
|
|
49
|
+
*
|
|
50
|
+
* A guard's per-event check can only speak about events a journal contains. A
|
|
51
|
+
* journal missing something a guard requires offers it nothing to object to,
|
|
52
|
+
* and the recorded terminal result is then reused on the strength of history
|
|
53
|
+
* that was never validated. This is where a guard says whether the history as a
|
|
54
|
+
* whole may be replayed at all.
|
|
55
|
+
*/
|
|
56
|
+
export interface RetainedHistory {
|
|
57
|
+
/** The coroutine whose recorded terminal result is about to be reused. */
|
|
58
|
+
readonly coroutineId: CoroutineId;
|
|
59
|
+
/** Every retained Yield, each owning the one cell for what it settled to. */
|
|
60
|
+
readonly yields: readonly Yield[];
|
|
61
|
+
/** Whether a recorded terminal result exists for that coroutine. */
|
|
62
|
+
readonly terminal: boolean;
|
|
63
|
+
}
|
|
35
64
|
/**
|
|
36
65
|
* The outcome of a replay guard's decision.
|
|
37
66
|
*
|
|
@@ -62,6 +91,13 @@ export type ReplayOutcome = {
|
|
|
62
91
|
interface ReplayGuardApi {
|
|
63
92
|
/** Phase 1: Check — gather observations before replay (I/O allowed). */
|
|
64
93
|
check(event: Yield): Operation<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Phase 1b: Admit — the retained history has been offered in full, and a
|
|
96
|
+
* recorded terminal result has not been reused yet. A guard that requires
|
|
97
|
+
* something of the history as a whole — that an event it validates is
|
|
98
|
+
* present at all, and present once — refuses here by throwing.
|
|
99
|
+
*/
|
|
100
|
+
admit(history: RetainedHistory): Operation<void>;
|
|
65
101
|
/** Phase 2: Decide — return replay outcome (synchronous, pure). */
|
|
66
102
|
decide(event: Yield): ReplayOutcome;
|
|
67
103
|
}
|
package/types/replay-index.d.ts
CHANGED
|
@@ -4,18 +4,37 @@
|
|
|
4
4
|
* Provides per-coroutine cursored access to Yield events and keyed access
|
|
5
5
|
* to Close events. See spec §4.1.
|
|
6
6
|
*/
|
|
7
|
-
import type { Close, CoroutineId, DurableEvent, EffectDescription, Result } from "./types.js";
|
|
7
|
+
import type { Close, CoroutineId, DurableEvent, EffectDescription, Result, Yield } from "./types.js";
|
|
8
8
|
export interface YieldEntry {
|
|
9
9
|
description: EffectDescription;
|
|
10
10
|
result: Result;
|
|
11
11
|
}
|
|
12
12
|
export declare class ReplayIndex {
|
|
13
13
|
private yields;
|
|
14
|
+
/** Every retained Yield in stream order, each owning its own settled cells. */
|
|
15
|
+
private retained;
|
|
14
16
|
private cursors;
|
|
15
17
|
private closes;
|
|
16
18
|
/** Coroutines where replay has been disabled (run-live mode). */
|
|
17
19
|
private disabled;
|
|
20
|
+
/** Retained coroutine identities reached by the current definition. */
|
|
21
|
+
private claimed;
|
|
22
|
+
/**
|
|
23
|
+
* Index a journal's events by identity, without reading what they settled to.
|
|
24
|
+
*
|
|
25
|
+
* The events are retained first — idempotently, so a caller that already
|
|
26
|
+
* produced the stable history hands the same objects on rather than a second
|
|
27
|
+
* wrapping of them, and every phase then observes one identity and one
|
|
28
|
+
* settlement per event.
|
|
29
|
+
*/
|
|
18
30
|
constructor(events: DurableEvent[]);
|
|
31
|
+
/**
|
|
32
|
+
* Every retained Yield in stream order, as the events a check phase sees.
|
|
33
|
+
*
|
|
34
|
+
* The same objects the replay path consumes, so a guard and a later consumer
|
|
35
|
+
* observe one settled result rather than two reads of the stream.
|
|
36
|
+
*/
|
|
37
|
+
retainedYields(): Yield[];
|
|
19
38
|
/**
|
|
20
39
|
* Disable replay for a coroutine (run-live mode).
|
|
21
40
|
*
|
|
@@ -26,6 +45,8 @@ export declare class ReplayIndex {
|
|
|
26
45
|
disableReplay(coroutineId: CoroutineId): void;
|
|
27
46
|
/** Returns true if replay has been disabled for this coroutine. */
|
|
28
47
|
isReplayDisabled(coroutineId: CoroutineId): boolean;
|
|
48
|
+
/** Mark a retained coroutine identity as reached by the current run. */
|
|
49
|
+
claim(coroutineId: CoroutineId): void;
|
|
29
50
|
/**
|
|
30
51
|
* Returns the next unconsumed yield for this coroutine,
|
|
31
52
|
* or undefined if the cursor is past the end or replay is disabled.
|
|
@@ -46,17 +67,8 @@ export declare class ReplayIndex {
|
|
|
46
67
|
* unconsumed entries belong to child coroutines rather than the root.
|
|
47
68
|
*/
|
|
48
69
|
hasAnyUnconsumedYields(): boolean;
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
* NOTE: Closed coroutines are skipped because their yields were consumed
|
|
53
|
-
* by the child's own replay path (via runDurableChild). This means
|
|
54
|
-
* orphaned children (recorded in the journal but never spawned in the
|
|
55
|
-
* current run) are not detected here. Orphan detection requires tracking
|
|
56
|
-
* which coroutine IDs were visited during the current run, which is a
|
|
57
|
-
* future enhancement.
|
|
58
|
-
*/
|
|
59
|
-
firstUnconsumed(): {
|
|
70
|
+
/** Return the first retained coroutine not aligned with the current subtree. */
|
|
71
|
+
firstUnaligned(subtreeId: CoroutineId): {
|
|
60
72
|
coroutineId: CoroutineId;
|
|
61
73
|
cursor: number;
|
|
62
74
|
totalYields: number;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retained events — what a run reads a journal as.
|
|
3
|
+
*
|
|
4
|
+
* A journal is data supplied by a backend, and every phase of a replay reads
|
|
5
|
+
* the same events: a private authority gate, the replay index, public guard
|
|
6
|
+
* policy, and the replay path itself. If those are separate reads of the
|
|
7
|
+
* backend's own objects, a source that answers differently between them decides
|
|
8
|
+
* one thing for validation and another for execution, and nothing downstream
|
|
9
|
+
* can detect the substitution.
|
|
10
|
+
*
|
|
11
|
+
* A retained event is therefore read once and detached. **Every** event that
|
|
12
|
+
* participates in admission, indexing, or terminal reuse is retained, Close as
|
|
13
|
+
* well as Yield: a Close decides whether a coroutine has a terminal result to
|
|
14
|
+
* reuse, so leaving it as the backend's own object lets it belong to a child
|
|
15
|
+
* coroutine while one phase asks and to the root while the next does.
|
|
16
|
+
*
|
|
17
|
+
* The discriminator is settled once, by the classification that chooses a
|
|
18
|
+
* retained event's kind, and never read from the source again. Identity — the
|
|
19
|
+
* coroutine an event belongs to, and a Yield's complete effect description — is
|
|
20
|
+
* settled once too, so no phase can be shown a different event than the phase
|
|
21
|
+
* before it.
|
|
22
|
+
*
|
|
23
|
+
* A Yield's *settlement* stays lazy and separate: the index is built before
|
|
24
|
+
* guards run, and a guard that would refuse an event has to get that chance
|
|
25
|
+
* before the stream is asked to produce its result. A Close keeps its own cell,
|
|
26
|
+
* memoized the same way, so every later read receives the same detached answer.
|
|
27
|
+
*/
|
|
28
|
+
import type { DurableEvent, Json, Result } from "./types.js";
|
|
29
|
+
/**
|
|
30
|
+
* A detached copy of one retained JSON value.
|
|
31
|
+
*
|
|
32
|
+
* Every property is read once and rebuilt, so nothing the stream still owns
|
|
33
|
+
* remains reachable: a nested accessor cannot answer one thing to one phase and
|
|
34
|
+
* another to the next, and no later mutation of the source changes what replay
|
|
35
|
+
* used.
|
|
36
|
+
*
|
|
37
|
+
* The copy is ordinary JSON. Detaching is the claim against the *stream*;
|
|
38
|
+
* making the copy immutable would be a claim against the *consumer*, and
|
|
39
|
+
* replayed values are legitimately mutable — an eval binding restored from a
|
|
40
|
+
* journal is pushed to by the iteration that resumes on it. Members are
|
|
41
|
+
* therefore writable and configurable like any other JSON.
|
|
42
|
+
*
|
|
43
|
+
* Keys are defined rather than assigned all the same, because `__proto__`
|
|
44
|
+
* reaches an inherited setter on some runtimes and would rewrite the copy's
|
|
45
|
+
* prototype instead of becoming a member of it.
|
|
46
|
+
*
|
|
47
|
+
* A cycle is refused. `Json` has none, and a value that does is not something
|
|
48
|
+
* this can detach — refusing is remembered like any other refusal.
|
|
49
|
+
*/
|
|
50
|
+
export declare function detachJson(value: Json, seen?: Set<object>): Json;
|
|
51
|
+
/**
|
|
52
|
+
* The retained form of a journal's events.
|
|
53
|
+
*
|
|
54
|
+
* Idempotent: retaining an already-retained event returns it, so a caller that
|
|
55
|
+
* has produced the stable history hands the same objects onward rather than a
|
|
56
|
+
* second wrapping of them. That is what lets one snapshot serve every phase.
|
|
57
|
+
*
|
|
58
|
+
* Only the event type is read here, which is the least a caller can read and
|
|
59
|
+
* still tell a Yield from a Close. Everything else is the retained event's own.
|
|
60
|
+
*/
|
|
61
|
+
export declare function retainEvents(events: readonly DurableEvent[]): DurableEvent[];
|
|
62
|
+
/**
|
|
63
|
+
* An isolated observation of a retained event, for public policy to read.
|
|
64
|
+
*
|
|
65
|
+
* A replay guard is composable policy, and composition means handlers read,
|
|
66
|
+
* annotate, and pass along. What it must never mean is that a handler edits the
|
|
67
|
+
* history the execution already validated: the authoritative graph is what
|
|
68
|
+
* admission accepted and what replay consumes, and a guard that could rewrite a
|
|
69
|
+
* root selection or an effect description after admission would hold exactly
|
|
70
|
+
* the authority the private gate exists to keep out of public hands.
|
|
71
|
+
*
|
|
72
|
+
* So policy reads a copy. It is deep and mutable, so middleware may compose over
|
|
73
|
+
* it as freely as it likes, and nothing it does reaches replay.
|
|
74
|
+
*/
|
|
75
|
+
export declare function observeEvent(event: DurableEvent): DurableEvent;
|
|
76
|
+
/**
|
|
77
|
+
* A retained result as a consumer may hold it: ordinary mutable JSON.
|
|
78
|
+
*
|
|
79
|
+
* The authoritative copy is frozen so policy cannot rewrite it. A document
|
|
80
|
+
* that resumes on a restored binding writes to it, so what a workflow receives
|
|
81
|
+
* is a fresh copy taken from that authority rather than the authority itself.
|
|
82
|
+
*/
|
|
83
|
+
export declare function consumable(result: Result): Result;
|
package/types/run.d.ts
CHANGED
|
@@ -32,7 +32,8 @@ export interface DurableRunOptions {
|
|
|
32
32
|
* 3. Runs the workflow — replayed effects resolve synchronously from
|
|
33
33
|
* the index; live effects execute and persist before resuming.
|
|
34
34
|
* 4. On completion, appends a Close event to the stream.
|
|
35
|
-
* 5.
|
|
35
|
+
* 5. Before any Close, rejects durability failures and retained coroutine
|
|
36
|
+
* history the current definition did not align with.
|
|
36
37
|
*
|
|
37
38
|
* Returns the workflow's result value.
|
|
38
39
|
*
|