@executablemd/durable-streams 0.6.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/types/effect.d.ts CHANGED
@@ -20,6 +20,7 @@
20
20
  * See integration doc §5.1, protocol spec §4.2, §5, §6.
21
21
  */
22
22
  import type { Operation } from "effection";
23
+ import { type LiveDurableOperationCoordinator } from "./live-coordinator.js";
23
24
  import type { DurableEffect, EffectDescription, Json, Result } from "./types.js";
24
25
  /**
25
26
  * Executor function signature for live execution (callback-based).
@@ -56,5 +57,9 @@ export declare function createDurableEffect<T>(desc: EffectDescription, execute:
56
57
  *
57
58
  * @param desc Structured description for the journal and divergence detection
58
59
  * @param execute Returns an Operation to run during live execution
60
+ * @param options.coordinator Selects the live execution/publication boundary;
61
+ * replay never invokes it
59
62
  */
60
- export declare function createDurableOperation<T extends Json>(desc: EffectDescription, execute: () => Operation<T>): DurableEffect<T>;
63
+ export declare function createDurableOperation<T extends Json>(desc: EffectDescription, execute: () => Operation<T>, options?: {
64
+ coordinator?: LiveDurableOperationCoordinator;
65
+ }): DurableEffect<T>;
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 the generator finishes (returns) while the replay index
26
- * still has unconsumed entries for this coroutine. See spec §6.3.
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 EarlyReturnDivergenceError extends Error {
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
  /**
@@ -0,0 +1,76 @@
1
+ /**
2
+ * guardDurableStream — a host-side pre-persistence boundary.
3
+ *
4
+ * A gate runs once per live append, before the event reaches the backend.
5
+ * It receives a copy and returns nothing, so it can inspect or reject but
6
+ * never rewrite. When the gate completes, the original event is handed to
7
+ * the underlying stream exactly once. When the gate fails or is cancelled,
8
+ * the backend is never invoked and the failure propagates to the durable
9
+ * effect that produced the event.
10
+ *
11
+ * The backend append is a statement after the gate rather than a
12
+ * continuation passed to it. A gate therefore has nothing it can invoke
13
+ * twice, which preserves the protocol invariant that one durable yield
14
+ * produces at most one journal event.
15
+ */
16
+ import { type Operation } from "effection";
17
+ import type { DurableStream } from "./stream.js";
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>;
49
+ /**
50
+ * A check that runs before a durable event is persisted.
51
+ *
52
+ * Completing successfully admits the event. Failing or being cancelled
53
+ * rejects it. A gate inspects or rejects, and nothing else: it returns no
54
+ * value, and the event it receives is a copy, so writing to that copy
55
+ * changes nothing about what gets journaled.
56
+ */
57
+ export type DurableEventGate = (event: DurableEvent) => Operation<void>;
58
+ /**
59
+ * Wrap a durable stream so every live append passes through `gate` first.
60
+ *
61
+ * `readAll()` delegates straight to the underlying stream, so replaying a
62
+ * journal restores existing entries without gating them.
63
+ *
64
+ * Wrap the stream before execution begins to cover the complete live
65
+ * journal — root component imports, yields, child closes, and the root
66
+ * close.
67
+ *
68
+ * Rejection is per event. The rejected event never reaches the backend, but
69
+ * the resulting failure may lead the workflow to append a later `Close`
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}.
75
+ */
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,23 +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";
15
+ export { guardDurableStream } from "./guard.js";
16
+ export { establishJournalProvenance, preserveJournalProvenance } from "./guard.js";
17
+ export type { DurableEventGate, JournalProvenance } from "./guard.js";
13
18
  export { useHttpDurableStream } from "./http-stream.js";
14
19
  export type { HttpDurableStreamHandle, HttpDurableStreamOptions } from "./http-stream.js";
15
- export { ContinuePastCloseDivergenceError, DivergenceError, EarlyReturnDivergenceError, StaleInputError, } from "./errors.js";
20
+ export { ContinuePastCloseDivergenceError, DivergenceError, DurablePersistenceError, EarlyReturnDivergenceError, MalformedDurableEventError, StaleInputError, TerminalDivergenceError, } from "./errors.js";
16
21
  export { Divergence } from "./divergence.js";
17
22
  export type { DivergenceDecision, DivergenceInfo, DivergenceKind } from "./divergence.js";
18
23
  export { ReplayGuard } from "./replay-guard.js";
19
- export type { ReplayOutcome } from "./replay-guard.js";
20
- export { DurableCtx } from "./context.js";
21
- export type { DurableContext } from "./context.js";
22
- export { deserializeError, effectionToProtocol, protocolToEffection, serializeError, } from "./serialize.js";
24
+ export type { ReplayOutcome, RetainedHistory } from "./replay-guard.js";
25
+ export { DurableContext } from "./context.js";
26
+ export { deserializeError, effectionToProtocol, protocolToEffection, serializeDurableEvent, serializeError, } from "./serialize.js";
27
+ export { parseDurableEvent } from "./parse.js";
23
28
  export { createDurableEffect, createDurableOperation } from "./effect.js";
24
29
  export type { Executor } from "./effect.js";
30
+ export { defaultLiveDurableOperationCoordinator } from "./live-coordinator.js";
31
+ export type { ActivateDurabilityFailure, LiveDurableOperationCoordinator, } from "./live-coordinator.js";
25
32
  export { durableAction, durableCall, durableSleep, versionCheck } from "./operations.js";
26
33
  export { durableAll, durableRace, durableSpawn } from "./combinators.js";
27
34
  export { durableEach } from "./each.js";
@@ -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>;
@@ -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
- * The API has two phases:
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. **decide** (during replay): Runs synchronously inside
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
  }
@@ -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
- * Return the first non-disabled coroutine with unconsumed yields.
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. On error, appends a Close(err) event.
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
  *
@@ -5,8 +5,29 @@
5
5
  * - Protocol Result ({ status: "ok" | "err" | "cancelled" })
6
6
  * - Effection Result ({ ok: true, value } | { ok: false, error })
7
7
  * - Error ↔ SerializedError
8
+ *
9
+ * `serializeDurableEvent` is the shared NDJSON representation: file
10
+ * persistence writes it, and gates that inspect the persisted form derive
11
+ * it from the same function.
12
+ */
13
+ import type { DurableEvent, EffectionResult, Result, SerializedError } from "./types.js";
14
+ /**
15
+ * Render one durable event as its NDJSON record, terminating newline
16
+ * included.
17
+ *
18
+ * This is ordinary `JSON.stringify(event) + "\n"`. Field order follows the
19
+ * event object's own insertion order; nothing is sorted, normalized, or
20
+ * otherwise canonicalized. The value of sharing it is that file persistence
21
+ * and anything inspecting the persisted form cannot drift apart, not that
22
+ * the output is a canonical form of the event.
23
+ *
24
+ * Serialization fails when `JSON.stringify` throws — a circular structure or
25
+ * a `BigInt` — or when it does not return a string. Values that
26
+ * `JSON.stringify` silently coerces or drops are left alone: `undefined`,
27
+ * function and symbol members are omitted, non-finite numbers become `null`,
28
+ * and non-plain objects serialize through `toJSON`.
8
29
  */
9
- import type { EffectionResult, Result, SerializedError } from "./types.js";
30
+ export declare function serializeDurableEvent(event: DurableEvent): string;
10
31
  /** Serialize an Error to a JSON-safe SerializedError. */
11
32
  export declare function serializeError(error: Error): SerializedError;
12
33
  /** Deserialize a SerializedError back to an Error. */