feat(durable): add @effectionx/durable durable-native runtime#174
Draft
feat(durable): add @effectionx/durable durable-native runtime#174
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Add protocol decisions from coroutine-transport-protocol exploration: - Single flat stream per workflow with correlation IDs - 4 event types (yield/next/close/spawn) replacing 8-type schema - DurableOperation.id as coroutineId in stream events - Three-way terminal state for cancellation (ok/err/cancelled) - Updated runtime invariants to reference new event types
- Add @effectionx/durable package scaffold (package.json, tsconfig.json) - Wire into monorepo (pnpm-workspace.yaml, root tsconfig references) - Implement DurableOperation<T> branded type with private symbol - Define 4-event schema (Yield, Next, Close, Spawn) - Define DurableStream interface and StreamEntry type - Add DivergenceError and serialization helpers - Implement InMemoryDurableStream with static from() factory
Clean-room implementation of the durable reducer engine using the new 4-event schema (yield, next, close, spawn). Uses api.Scope.around() middleware from effection/experimental to intercept scope lifecycle. Key components: - ReplayIndex: indexes events per-coroutine for deterministic replay - Scope middleware: emits spawn events before child execution, close events with 3-way status (ok/err/cancelled) on teardown - Effect interception: records yield/next pairs on live path, feeds stored values during replay without calling effect.enter() - Divergence detection: throws DivergenceError on replay mismatch - Serialization: toJson with cycle detection and LiveOnly sentinels
Wires DurableReducer into an Effection scope via createScope/global and ReducerContext injection. Accepts a DurableOperation and optional DurableStream for persistence. Falls back to InMemoryDurableStream for ephemeral execution.
…rce, scoped) Branded wrappers around Effection primitives that enforce DurableOperation<T> at the type level. The actual durable semantics (event recording/replay) come from the reducer middleware — these are thin type-safe entry points.
Export all public types, runtime, stream, and primitives from mod.ts. Add test helpers for filtering events (allEvents, lifecycleEvents, userFacingEvents, userEffectPairs).
- stream-recording: close events, yield/next pairs, spawn events - replay: basic replay, multi-step replay, error replay, partial replay from cutoff, divergence detection - concurrency: spawn/close lifecycle, all branches, race winner/losers, nested scope hierarchy - resource: acquire/release lifecycle, value recording, error handling Fix: capture parent ID before unregistering in destroy middleware (close events for root were being lost because parent mapping was deleted before being read).
CI resolves effection differently than local development. Add a scoped pnpm override for @effectionx/durable to ensure it always uses the preview build with experimental API exports (ReducerContext, InstructionQueue, DelimiterContext).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Introduce
@effectionx/durable, a durable-native structured concurrency runtime for Effection. Unlike@effectionx/durably(which wraps plain operations with compatibility prefixes), this package makes durability the default — every primitive (spawn,all,race,resource,scoped) is durable by design, with a brandedDurableOperation<T>type that enforces durable boundaries at compile time.The package uses a simplified 4-event stream protocol (
yield,next,close,spawn) replacing durably's 8-event schema, and a clean-room reducer implementation built oneffection@4.1.0-alpha.5experimental APIs (api.Scope,ReducerContext).Approach
Type Model
DurableOperation<T>: BrandedOperation<T>via privateunique symbol. Structurally compatible withyield*but prevents plain operations from entering durable APIs. Only the durable runtime can mint branded operations.4-Event Stream Protocol
yieldnextclosespawnEliminates
scope:set,scope:delete,workflow:return(informational events never consumed during replay). RenamesscopeId→coroutineIdto align with coroutine protocol terminology.DurableReducer (clean-room)
Written from scratch using the same
api.Scope.around()middleware pattern as durably but targeting the new 4-event schema. Key capabilities:spawnbefore child execution,closewith 3-way status on teardownyield/nexton live path, feeds stored values during replayDivergenceErroron replay mismatchRuntime Invariants (7)
close(cancelled)before teardown)effect.enter()for recorded events)Test Coverage (24 tests)