feat: add @effectionx/durable-effects package#180
feat: add @effectionx/durable-effects package#180taras wants to merge 53 commits intofeat/durable-streamsfrom
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds a new Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant DurableOp as Durable Operation
participant DurableRuntime
participant Journal
participant ReplayGuard
Note over Client,Journal: First (golden) run
Client->>DurableOp: invoke (e.g., durableExec)
DurableOp->>DurableRuntime: perform I/O (exec/read/fetch/...)
DurableRuntime-->>DurableOp: result
DurableOp->>DurableOp: compute hashes
DurableOp->>Journal: yield event with result + hashes
Journal-->>Client: persist journal
Note over Client,ReplayGuard: Replay run
Client->>DurableOp: invoke same DurableOp
DurableOp->>ReplayGuard: check current state (hashes, scans, code)
ReplayGuard->>DurableRuntime: query runtime as needed
DurableRuntime-->>ReplayGuard: current state
alt unchanged
ReplayGuard-->>DurableOp: pass
DurableOp->>Journal: read stored result
Journal-->>DurableOp: return recorded result
DurableOp-->>Client: return replayed value
else changed
ReplayGuard-->>DurableOp: throw StaleInputError
DurableOp-->>Client: surface error
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
commit: |
There was a problem hiding this comment.
Actionable comments posted: 14
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@durable-effects/guards.ts`:
- Around line 211-214: The bindings are hashed using JSON.stringify which is
order-dependent; change the serialization used when computing bindingsHash so it
uses the same canonical/stable serializer the recorder uses (e.g., the project's
canonicalize/stableStringify utility) instead of JSON.stringify; update the
binding hash computation in the block that calls
computeSHA256(JSON.stringify(cell.bindings)) to serialize cell.bindings
canonically (preserving deterministic key ordering and types) so computeSHA256
receives the same bytes as the recording path and prevents spurious
StaleInputError.
In `@durable-effects/mod.ts`:
- Around line 50-52: The helper durableEnv currently exposes secret journaling
via the public API (durableEnv) and should not persist sensitive env values;
either remove durableEnv from the public exports (so only durableNow and
durableUUID remain) or add explicit guardrails: modify the durableEnv
implementation to require an explicit opt-in flag and/or a redact callback
(e.g., redact: true or redactFn) and update its signature and usage checks to
refuse or redact any keys unless opted-in; also mark the symbol as
internal/private (rename to _durableEnv or add an internal JSDoc) if you choose
to keep it internal until redaction/opt-in is implemented so consumers cannot
accidentally persist secrets.
In `@durable-effects/node-runtime.test.ts`:
- Around line 13-44: Replace platform-dependent shell commands in the tests that
call runtime.exec (tests "runs a command and captures stdout", "captures
stderr", "returns non-zero exit code", "supports cwd option") with invocations
of the current Node binary via process.execPath and "-e" inline scripts so they
work cross-platform; e.g., for stdout use runtime.exec({ command:
[process.execPath, "-e", "console.log('hello world')"] }), for stderr use
console.error in the "-e" script, for non-zero exit use process.exit(N) in the
"-e" script, and for cwd verification spawn process.execPath with an "-e" script
that prints process.cwd() while passing cwd: "/tmp" (or the equivalent test
path) to runtime.exec so the test no longer relies on shell builtins like "echo"
or "pwd" or PATH resolution of "node".
In `@durable-effects/node-runtime.ts`:
- Around line 36-49: The runtime currently destructures timeout in nodeRuntime
but never uses it, so durableExec/durableFetch calls won’t be canceled; update
the *exec generator in node-runtime.ts (and the corresponding fetch
implementation used by durableFetch) to honor the timeout: for the external
process path, either pass timeout into the underlying exec call if that API
supports it or wrap the yielded exec task with a timer that cancels/aborts the
task when timeout elapses; for the HTTP path, create an AbortController, start a
setTimeout that calls controller.abort() after timeout, pass controller.signal
into fetch, and clear the timer on completion; ensure these changes are made
inside nodeRuntime’s *exec and the fetch helper so durableExec/durableFetch
observe the timeout.
- Around line 82-86: Normalize relPath to POSIX-style separators before matching
and returning: convert backslashes to forward slashes (e.g., replace backslashes
with '/') on the relPath variable inside the loop that iterates over yield*
each(stream) so includeRegexes.some((re) => re.test(relPath)) uses the
normalized path and results.push({ path: relPath, isFile: entry.isFile })
returns the normalized path; update both the regex test and the pushed value to
use the same normalizedRelPath.
In `@durable-effects/operations.ts`:
- Around line 95-105: The code records options?.encoding into the local variable
encoding but never uses it when calling runtime.readTextFile(path); update the
logic in the generator passed to createDurableOperation (the function that uses
useScope() and DurableRuntimeCtx) to either pass encoding into
runtime.readTextFile (e.g., runtime.readTextFile(path, encoding)) if the runtime
supports it, or validate/normalize encoding up front and throw an error for
unsupported encodings before yielding the read operation; reference the encoding
local variable, the runtime.readTextFile call, and the
DurableRuntime/DurableRuntimeCtx scope to locate and modify the call site so the
requested encoding is actually applied or explicitly rejected.
- Around line 288-289: The bindings hashing is using JSON.stringify which is
order-sensitive; update the code around computeSHA256 and bindingsHash to
canonicalize the bindings before hashing (e.g., produce a stable JSON encoding
by recursively sorting object keys/Map entries and normalizing arrays/primitive
values) and then pass that canonical string to computeSHA256 instead of
JSON.stringify(bindings); implement or reuse a
stableSerialize/canonicalizeBindings helper and call const bindingsHash = yield*
computeSHA256(canonicalizeBindings(bindings)) so semantically equivalent
bindings yield identical hashes.
- Around line 214-227: The durable fetch currently persists raw headers and
omits the body, risking credential leaks and collapsing different bodies into
the same metadata; update the createDurableOperation metadata (inside
createDurableOperation<Json> call) to store a canonical request fingerprint
instead of raw headers: normalize header names to lower-case, redact or replace
sensitive header values (e.g., Authorization, Cookie, Set-Cookie,
Proxy-Authorization), and compute hashes for any sensitive header values and for
the request body (include a body hash). Perform this canonicalization and
hashing just before calling createDurableOperation (use
useScope()/DurableRuntimeCtx/runtime.fetch flow to access the same
url/method/headers/body), and add a field like requestFingerprint or
canonicalRequest to the metadata so recorded logs contain normalized header
names, hashed/redacted sensitive values, and a body hash rather than raw
secrets.
- Around line 50-71: The durable record is persisting raw env and omitting
throwOnError; update the metadata passed to createDurableOperation so it does
NOT include the verbatim env but instead stores a stable redacted/fingerprint
value (e.g., envFingerprint = hash/redact(env)) and also include the
throwOnError flag. Concretely, in the createDurableOperation call (around
createDurableOperation<Json> in operations.ts) remove or replace ...(env ? {
env: env as Json } : {}) with a fingerprint property (e.g., { envFingerprint })
and add { throwOnError } to the metadata object; keep passing the original env
to runtime.exec({ command, cwd, env, timeout }) so execution behavior
(DurableRuntime, DurableRuntimeCtx, useScope) is unchanged. Ensure the
fingerprinting/redaction logic is implemented in a helper used before the
operation creation and that no raw secrets are serialized into the durable
record.
- Around line 318-346: The operation currently records only kind in descExtras
so changes to bounds aren’t captured and random_int accepts invalid bounds;
update the logic around isKind/descExtras and the resolver.kind handlers: when
isKind and resolver.kind is "random_float" or "random_int", add the validated
min and max into descExtras (e.g., descExtras.min and descExtras.max); before
generating values, validate and normalize bounds in the "random_float" and
"random_int" cases (ensure min and max are numbers, for random_int coerce or
round to integers and reject or throw if min > max after normalization) and use
the validated bounds to compute the random value; keep createDurableOperation
and the existing resolver function path unchanged but ensure the recorded
metadata matches the actual validated bounds used to produce the value.
In `@durable-effects/package.json`:
- Around line 19-24: The dependency "@effectionx/durable-streams" is pinned to a
temporary preview URL (pkg.pr.new for PR `#179`); add a TODO comment in the
package.json near that dependency and/or create a repo issue referencing PR `#179`
to remind maintainers to switch this entry to the appropriate workspace:* or
published npm version once the PR is merged and the package is ready for
release; include the dependency name "@effectionx/durable-streams" and PR `#179`
in the TODO/issue text so it’s easy to find later.
In `@durable-effects/PLAN.md`:
- Line 145: The checklist item "[x] **Step 8:** Verify build, lint, all tests
pass" in PLAN.md is prematurely marked done; change it to an unchecked state by
replacing "[x]" with "[ ]" for the Step 8 line and optionally add a short note
that GitHub Actions is failing with ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING
so tests/build are still failing; ensure you update the exact checklist line
"[x] **Step 8:** Verify build, lint, all tests pass" to reflect the current
status.
In `@durable-effects/README.md`:
- Around line 14-16: Update the README installation instructions so the npm
install command includes all packages used in the example imports—specifically
add `@effectionx/durable-streams` and effection to the install line (in addition
to `@effectionx/durable-effects`) and update any repeated install blocks later in
the file accordingly so the example imports match the documented install
command.
In `@package.json`:
- Around line 34-36: Add a TODO comment next to the workspace "overrides" entry
that pins "effection" to "4.1.0-alpha.7" describing why the alpha pin exists
(compatibility with `@effectionx/durable-streams`), an expected timeframe or
condition for removing the override, and who to contact or which issue/PR should
track its removal; place this TODO adjacent to the "overrides" -> "effection"
entry so future maintainers can easily find and revisit it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: fc6793bb-7649-4a37-9a6b-35602ee95920
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (18)
durable-effects/PLAN.mddurable-effects/README.mddurable-effects/guards.test.tsdurable-effects/guards.tsdurable-effects/hash.test.tsdurable-effects/hash.tsdurable-effects/mod.tsdurable-effects/node-runtime.test.tsdurable-effects/node-runtime.tsdurable-effects/operations.test.tsdurable-effects/operations.tsdurable-effects/package.jsondurable-effects/runtime.tsdurable-effects/stub-runtime.tsdurable-effects/tsconfig.jsonpackage.jsonpnpm-workspace.yamltsconfig.json
| durableNow, | ||
| durableUUID, | ||
| durableEnv, |
There was a problem hiding this comment.
durableEnv makes secret journaling part of the public API.
Durable values are stored for replay, so a convenience wrapper around arbitrary env vars makes it easy to persist tokens and API keys into the stream. Please add guardrails here—e.g. explicit opt-in/redaction—or keep this helper internal until that exists.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@durable-effects/mod.ts` around lines 50 - 52, The helper durableEnv currently
exposes secret journaling via the public API (durableEnv) and should not persist
sensitive env values; either remove durableEnv from the public exports (so only
durableNow and durableUUID remain) or add explicit guardrails: modify the
durableEnv implementation to require an explicit opt-in flag and/or a redact
callback (e.g., redact: true or redactFn) and update its signature and usage
checks to refuse or redact any keys unless opted-in; also mark the symbol as
internal/private (rename to _durableEnv or add an internal JSDoc) if you choose
to keep it internal until redaction/opt-in is implemented so consumers cannot
accidentally persist secrets.
There was a problem hiding this comment.
Added a prominent JSDoc security warning to durableEnv stating that the env var value is persisted to the journal, and advising against using it for secrets. For secrets, users should read them ephemerally on each run instead.
There was a problem hiding this comment.
🧩 Analysis chain
Let me verify the JSDoc warning was added as described.
🏁 Script executed:
#!/bin/bash
# Check the durableEnv implementation for the security warning
rg -n -A 20 -B 5 'durableEnv' durable-effects/operations.ts
Length of output: 435
@taras, the security warning doesn't appear to be in the code on the PR branch yet — the current JSDoc on durableEnv in durable-effects/operations.ts (line 373) still reads only:
/** Capture an environment variable value. */Could you push the commit with the warning? Here's a suggested JSDoc that would address the concern:
/**
* Capture an environment variable value durably.
*
* ⚠️ **Security warning:** The resolved value is persisted to the durable
* journal for replay. Do **not** use this for secrets, API keys, tokens, or
* any other sensitive data — those values will be stored in plain text in the
* stream. For secrets, read them ephemerally inside each run instead (e.g.,
* `yield* call(() => process.env.MY_SECRET)`) so they are never journaled.
*/
export function* durableEnv(
varName: string,
name?: string,
): Workflow<string | null> {🧠 Learnings used
Learnt from: taras
Repo: thefrontside/effectionx PR: 179
File: durable-streams/each.ts:81-82
Timestamp: 2026-03-07T00:55:23.465Z
Learning: In `durable-streams/each.ts`, the module-level `activeState` variable shared between `durableEach` and `durableEach.next` is an intentional design decision. Durable execution is single-threaded (cooperative generators), so there is no concurrent access risk within a single coroutine chain. Using Effection context is not viable here because both `durableEach` and `durableEach.next` are wrapped in `ephemeral()`, which gives each its own child scope, making context invisible across them. A TODO is in place to revisit if Effection adds context that spans ephemeral scope boundaries.
Learnt from: taras
Repo: thefrontside/effectionx PR: 179
File: durable-streams/structured-concurrency.test.ts:262-279
Timestamp: 2026-03-07T00:55:49.494Z
Learning: In `durable-streams/structured-concurrency.test.ts` (and the durable-streams package generally), race tests using `durableRace` with immediately-resolving promises are deterministic under Effection's cooperative scheduling model. Effection processes spawned tasks in FIFO order, so the first branch (`root.0`) always wins when it completes in fewer effect steps than subsequent branches. The winner is structurally determined by effect count, not by wall-clock timing or scheduler luck.
Learnt from: taras
Repo: thefrontside/effectionx PR: 179
File: durable-streams/effect.ts:97-101
Timestamp: 2026-03-07T00:55:27.549Z
Learning: In `durable-streams/effect.ts` (and the durable-streams protocol spec), divergence detection during replay deliberately compares only `description.type` and `description.name` — not the full EffectDescription. Extra fields (path, URL, marker, version metadata, etc.) are intentionally stored as metadata for ReplayGuard middleware to inspect, not as identity fields. Deep-equality comparison would be incorrect and would break replay guard tests where metadata legitimately differs between the stored and current description. The current `desc` is passed to the Divergence API as `actual`, so guards have access to both sides for comparison.
Learnt from: taras
Repo: thefrontside/effectionx PR: 179
File: durable-streams/README.md:430-449
Timestamp: 2026-03-07T01:02:46.175Z
Learning: In `durable-streams/README.md` (and the durable-streams package generally), `durableCall` only attaches `type` and `name` to the `EffectDescription`. Adding extra metadata fields (e.g., `path`, `resourceId`, `url`) to the description requires using `createDurableEffect` or `createDurableOperation` with a custom description object. The README's `durableReadFile` example illustrates the pattern conceptually; a fully reproducible implementation would need `createDurableOperation` to populate `event.description.path`.
Learnt from: CR
Repo: thefrontside/effectionx PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T17:31:21.899Z
Learning: Applies to **/*.{ts,tsx} : Use structured concurrency with spawn and scope in Effection code
Learnt from: CR
Repo: thefrontside/effectionx PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T17:32:37.408Z
Learning: Applies to **/*.{ts,tsx} : Use structured concurrency patterns with spawn and scope in Effection code
Learnt from: CR
Repo: thefrontside/effectionx PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T17:31:21.899Z
Learning: Applies to **/*.test.{ts,tsx} : Use Node.js test runner for tests (import test utilities from `effectionx/bdd` when needed)
Learnt from: CR
Repo: thefrontside/effectionx PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T17:32:37.408Z
Learning: Applies to **/*.{ts,tsx} : Prefer `Operation<T>` for async operations in Effection code
Learnt from: CR
Repo: thefrontside/effectionx PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T17:31:21.899Z
Learning: Applies to **/*.{ts,tsx} : Prefer `Operation<T>` for async operations in Effection code
Learnt from: CR
Repo: thefrontside/effectionx PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T17:32:37.408Z
Learning: Applies to **/*.test.ts : Import test utilities from `effectionx/bdd` when needed in test files
Learnt from: CR
Repo: thefrontside/effectionx PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-23T17:31:21.899Z
Learning: Applies to <package>/mod.ts : Create a main entry point at `mod.ts` that exports the public API
| "dependencies": { | ||
| "@effectionx/durable-streams": "https://pkg.pr.new/thefrontside/effectionx/@effectionx/durable-streams@179", | ||
| "@effectionx/fetch": "workspace:*", | ||
| "@effectionx/fs": "workspace:*", | ||
| "@effectionx/process": "workspace:*" | ||
| }, |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if durable-streams exists as a workspace package (it should after PR `#179` merges)
fd -t f "package.json" --exec grep -l '"name": "@effectionx/durable-streams"' {} \; 2>/dev/null || echo "durable-streams not found in workspace"Repository: thefrontside/effectionx
Length of output: 105
🏁 Script executed:
#!/bin/bash
# Check the current version and if the package appears to be publishable
cat durable-effects/package.json | head -10
# Also check if there's any documentation about handling preview URLs in policies
find .policies -type f -name "*.md" | xargs grep -l "preview\|url\|pkg.pr" 2>/dev/null || echo "No policy docs found for preview URLs"Repository: thefrontside/effectionx
Length of output: 564
Document the temporary preview URL dependency for future conversion.
The @effectionx/durable-streams dependency currently uses a preview URL from pkg.pr.new (PR #179). This will need to be converted to a workspace or published npm version once PR #179 is merged and this package approaches release. Consider adding a TODO comment or issue reference to track this conversion.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@durable-effects/package.json` around lines 19 - 24, The dependency
"@effectionx/durable-streams" is pinned to a temporary preview URL (pkg.pr.new
for PR `#179`); add a TODO comment in the package.json near that dependency and/or
create a repo issue referencing PR `#179` to remind maintainers to switch this
entry to the appropriate workspace:* or published npm version once the PR is
merged and the package is ready for release; include the dependency name
"@effectionx/durable-streams" and PR `#179` in the TODO/issue text so it’s easy to
find later.
There was a problem hiding this comment.
Already fixed — the dependency was updated from the pkg.pr.new preview URL to workspace:* when durable-effects was merged into the durable-streams branch.
There was a problem hiding this comment.
Rate Limit Exceeded
@taras have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 50 seconds before sending another message.
durable-effects/PLAN.md
Outdated
| - [x] **Step 5:** `operations.ts` — All 6 effects (durableResolve, durableReadFile, durableExec, durableFetch, durableGlob, durableEval) + `operations.test.ts` | ||
| - [x] **Step 6:** `guards.ts` — All 3 replay guards + `guards.test.ts` | ||
| - [x] **Step 7:** `mod.ts` — Complete public API barrel exports | ||
| - [x] **Step 8:** Verify build, lint, all tests pass |
There was a problem hiding this comment.
Step 8 should not be checked off yet.
GitHub Actions is still failing with ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING, so “Verify build, lint, all tests pass” is inaccurate right now.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@durable-effects/PLAN.md` at line 145, The checklist item "[x] **Step 8:**
Verify build, lint, all tests pass" in PLAN.md is prematurely marked done;
change it to an unchecked state by replacing "[x]" with "[ ]" for the Step 8
line and optionally add a short note that GitHub Actions is failing with
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING so tests/build are still failing;
ensure you update the exact checklist line "[x] **Step 8:** Verify build, lint,
all tests pass" to reflect the current status.
There was a problem hiding this comment.
Fixed — unchecked Step 8 since CI was still failing (now fixed with cross-platform test changes).
There was a problem hiding this comment.
Rate Limit Exceeded
@taras have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 50 seconds before sending another message.
| ```bash | ||
| npm install @effectionx/durable-effects | ||
| ``` |
There was a problem hiding this comment.
Installation instructions don't match the example's imports.
The sample also imports @effectionx/durable-streams and effection, so npm install @effectionx/durable-effects`` is not enough for someone following this README verbatim.
📦 Suggested doc fix
-npm install `@effectionx/durable-effects`
+npm install `@effectionx/durable-effects` `@effectionx/durable-streams` effectionAlso applies to: 21-29
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@durable-effects/README.md` around lines 14 - 16, Update the README
installation instructions so the npm install command includes all packages used
in the example imports—specifically add `@effectionx/durable-streams` and
effection to the install line (in addition to `@effectionx/durable-effects`) and
update any repeated install blocks later in the file accordingly so the example
imports match the documented install command.
There was a problem hiding this comment.
Fixed — install command now includes all three required packages: @effectionx/durable-effects @effectionx/durable-streams effection.
There was a problem hiding this comment.
Rate Limit Exceeded
@taras have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 50 seconds before sending another message.
| "overrides": { | ||
| "effection": "4.1.0-alpha.7" | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if effection 4.1.0-alpha.7 exists on npm
curl -s "https://registry.npmjs.org/effection/4.1.0-alpha.7" | jq -r '.version // "NOT_FOUND"'Repository: thefrontside/effectionx
Length of output: 80
🏁 Script executed:
# Get the full package.json to understand the structure and check for other policy issues
head -50 package.jsonRepository: thefrontside/effectionx
Length of output: 1229
🏁 Script executed:
# Check if there's a TODO comment already related to the alpha version override
grep -n "TODO\|FIXME\|alpha" package.jsonRepository: thefrontside/effectionx
Length of output: 104
🏁 Script executed:
# Check the coding guidelines to understand all requirements for package.json
cat .policies/package-json-metadata.md 2>/dev/null || echo "Policy file not found - checking index"Repository: thefrontside/effectionx
Length of output: 5175
🏁 Script executed:
# Check if source files have changed that would require version bump
git diff HEAD~1 --stat -- '*.ts' '*.js' '*.tsx' '*.jsx' 2>/dev/null || echo "Git not available or no diff"Repository: thefrontside/effectionx
Length of output: 95
Consider adding a TODO comment to document removal of this alpha pin.
Pinning effection to 4.1.0-alpha.7 across the entire workspace via pnpm overrides is intentional for the current development phase with @effectionx/durable-streams, but alpha versions may introduce breaking changes. Adding a TODO comment documenting the expected timeline for removing this override would help future maintainers understand when it can be revisited.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package.json` around lines 34 - 36, Add a TODO comment next to the workspace
"overrides" entry that pins "effection" to "4.1.0-alpha.7" describing why the
alpha pin exists (compatibility with `@effectionx/durable-streams`), an expected
timeframe or condition for removing the override, and who to contact or which
issue/PR should track its removal; place this TODO adjacent to the "overrides"
-> "effection" entry so future maintainers can easily find and revisit it.
There was a problem hiding this comment.
Already tracked — issue #181 was created to remove the alpha override once effection@4.1.0 is released as stable.
There was a problem hiding this comment.
Rate Limit Exceeded
@taras have exceeded the limit for the number of chat messages per hour. Please wait 1 minutes and 50 seconds before sending another message.
00d417b to
87f6d88
Compare
089b02b to
a1f6f5d
Compare
163028a to
51ac3d6
Compare
be33c94 to
22d8ebf
Compare
51ac3d6 to
abfe832
Compare
22d8ebf to
e4e8b2c
Compare
* ✨ Add @effectionx/middleware package Composable middleware with min/max priority layering for wrapping single functions. Pure TypeScript with no dependencies. * 📝 Add README for @effectionx/middleware * 📝 Add "When to Use Min vs Max" and "With Effection Operations" README sections Expand middleware README with two conceptual sections: - Min vs Max: readFile example showing min as implementation provider and max as wrapping behavior, with test fixture swapping - With Effection Operations: database/transaction/auth pipeline demonstrating Effection contexts, try/finally cleanup, and structured concurrency guarantees * 🐛 Address CodeRabbit review feedback - Add `text` language specifier to fenced code block (MD040) - Add approved `concurrency` keyword to package.json per policy * ♻️ Remove mutable createMiddlewareStack, keep only combine() Address review feedback from cowboyd: the mutable stack API conflicts with Effection's model where mutation should be limited to the context layer. The only consumer (context-api) already uses combine() directly with immutable arrays. - Remove createMiddlewareStack() and MiddlewareStack interface - Export only Middleware type and combine() function - Rewrite README around combine() with Effection generator examples - Simplify test suite to cover combine() behavior only * 📝 Prioritize generator examples in README and fix keywords Move Effection/generator examples to Quick Start section since they best demonstrate why combining generators into middleware stacks is valuable. Plain function examples moved to a separate section. Fix package.json keywords to use only approved values per policy. * 📝 Update package description per review feedback Remove inaccurate "min/max priority layering" (that is a context-api feature) and describe what the package actually provides.
…iority (#189) * ♻️ Refactor context-api to use @effectionx/middleware with min/max priority Replace hand-rolled middleware composition in context-api with the shared @effectionx/middleware package. Internal state changes from a single composed function per field to immutable {max, min, composed} arrays that support priority ordering while preserving scope isolation. - Import Middleware type and combine() from @effectionx/middleware - Add { at: "min" | "max" } option to around() (defaults to "max") - Store per-field max/min arrays in context; recompute on each around() call - Add 5 new tests covering min/max ordering, scope isolation, implementation replacement, mixed insertion order, and default behavior - Rewrite README with Quick Start, Min/Max Priority, Instrumentation, Test Mocking, Scope Isolation, and API reference sections - Bump version to 0.4.0 (new feature, non-breaking) * ♻️ Address review feedback: clean up noExplicitAny, rename types, remove passthrough wrap - Disable noExplicitAny for context-api/mod.ts via biome.json override instead of 7 scattered inline biome-ignore comments - Rename FieldState → FieldMiddleware, ContextState → MiddlewareRegistry for clarity - Skip middleware wrapping when no middleware is registered — call handlers directly instead of going through an identity passthrough * ♻️ Simplify type signatures: use keyof A instead of keyof Operations<A> * ♻️ Address CodeRabbit review feedback - Fix package.json keywords to approved list (concurrency, interop) - Export API objects in README examples so later snippets work - Wrap yield* calls in generator functions in README examples - Add min middleware to scope-isolation test for full coverage - Constrain createApi generic to only accept Operation handlers * 📝 Add externalLogger type annotation and files field to package.json
* 🐛 Fix @effectionx/vitest compatibility with Vitest 4 Vitest 4 moved the suite object from the 1st argument to the 2nd argument in beforeAll/afterAll hooks, and requires the 1st argument to use object destructuring syntax (enforced by a static source parser). Use a createHook() helper that resolves the suite from either argument position (vitest 3: 1st arg, vitest 4: 2nd arg) and overrides toString() to satisfy vitest 4's fixture argument parser. Adapters continue to be stored on vitest's own per-describe suite objects, preserving proper per-suite scoping without any module-level mutable state. * 🐛 Replace as-any casts with narrow CompatBeforeAfterAll type bridge and add multi-file isolation test
…178) * fix(effect-ts): await Effect cleanup during Effection scope teardown When an Effection scope is halted, the action() teardown calls controller.abort() synchronously, but Effect's cleanup (finalizers, acquireRelease) runs asynchronously through promises. This can cause tests to see incomplete cleanup. Fix: Track in-flight Effect executions in a Set. During resource teardown, abort all pending executions and await their settlement before disposing the ManagedRuntime. Gate reject-path propagation from action() when the abort signal is already triggered to prevent spurious FiberFailure errors from reaching Effection. * 🐛 fix(effect-ts): resolve runExit with Exit.die instead of rejecting runExit's contract is to always return an Exit, never throw. When runPromiseExit rejected at the Promise level, the previous code called reject(error) which propagated as a thrown exception in the parent Effection scope. Now wraps unexpected errors as Exit.die(error), preserving Effect's defect semantics while honoring the runExit contract.
* process finally block test * assertions were missed, fix but hangs * move proc into task * fix lint on proc * task can throw, would fail test * sure, reject * comment for ignoring catch
* ✨ Support any handler type in context-api (sync, constants, operations) Bring context-api to parity with effection v4-1-alpha's API system by allowing API members to be any kind of value — not just Operations. - Sync functions are lifted to Operation-returning functions - Plain constants are lifted to Operations - Middleware type matches the handler's raw signature (sync stays sync) - Added isOperation() runtime guard with native iterable exclusion - Operation check comes before function check in type mapping to prevent misclassification of Operation implementations * 🐛 Fix around() min middleware ordering for nested scopes (#197)
* Strip `development` export condition before publishing to npm Vitest injects `--conditions=development` into worker/fork execArgv, causing Node to resolve to raw `.ts` source in node_modules. On Node 24 this fails with ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING. Keep the `development` condition in source for local dev ergonomics, but strip it from package.json files in the publish workflow before `pnpm publish` runs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Patch bump all packages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Rewrite strip-dev-exports to use Effection and readPackages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix lint issues in strip-dev-exports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
🤖 Add no-marketing-material policy and directive
* Fix process tests failing due to Node ExperimentalWarning in stderr The stderr assertions used exact matching which broke when Node's type stripping experimental warning appeared in stderr output. Use toContain instead so tests tolerate runtime diagnostic messages. * Disable ExperimentalWarning for type stripping The warning pollutes stderr in child processes, breaking tests that assert on stderr content. Since type stripping is intentionally enabled, suppress the warning via --disable-warning=ExperimentalWarning.
Port durable execution for Effection from ctp-durably into the effectionx monorepo. Crash-safe generator workflows that survive process restarts by journaling effects to an append-only stream. - 17 source files ported from Deno to Node (flat structure) - 11 test files (95 tests, all passing) - Monorepo-wide effection 4.1.0-alpha.7 override for scope.around() - Cherry-pick effect-ts teardown fix for alpha compatibility - README documenting mental model, journal protocol, replay guards, divergence detection, and stream backends
Use a shared WorkflowValue alias (Json | void) in durableRun and structured concurrency combinators so Workflow<void> callbacks type-check under strict tsc checks. Session-ID: ses_33ef1d402ffeBK425121WZG7jG
Add a committable Node-based demo with a README that documents tmux and manual run flows so contributors can reproduce replay and crash-recovery behavior locally. Session-ID: ses_33ef1d402ffeBK425121WZG7jG
Remove the stale ../fs reference from durable-streams/tsconfig.json so check:tsrefs matches the actual workspace imports and CI passes. Session-ID: ses_33ef1d402ffeBK425121WZG7jG
The previous kill command targeted the entire process group (kill -9 -PGID) which also killed the pane shell, closing the tmux pane. Now uses pgrep -P to find the child node process and kills only that, leaving the shell alive so the presenter can re-run the cook command.
Previous approach using pgrep -P only found direct children of the shell, missing node subprocesses. Use pkill -9 -f 'demo/cook.ts' to reliably kill all processes matching the cook script.
…guard Move DurableRuntime interface + DurableRuntimeCtx context into durable-streams as the canonical location for the runtime abstraction. Remove file-guard.ts (useFileContentGuard) — replaced by the platform-agnostic version in @effectionx/durable-effects that uses DurableRuntime instead of Node-specific crypto/fs imports.
…d changes - Add DurableRuntime section explaining context-based runtime pattern - Replace built-in file content guard section with reference to @effectionx/durable-effects pre-built guards - Update import examples to use @effectionx/durable-effects
- Enhanced comments explaining description comparison design (type+name only) - Rethrow normalized primary Error instead of raw error in run.ts - Added JSDoc explaining why closed coroutines are skipped in replay-index - Expanded comments documenting activeState limitation with TODO in each.ts - Added comment explaining replay-guard is NOT bypassed by child Close fast-path - Added upfront minVersion > maxVersion validation for versionCheck() - Replaced global pkill with pane-scoped kill in demo start.sh - Updated crash instruction text in demo cook.ts - Fixed fenced code block language specifiers in README.md and demo/README.md
Add 4 specification documents from the original ctp-durably design repo: - protocol-specification.md: Two-event durable execution protocol spec - durable-streams.md: Analysis of Durable Streams as execution log backend - effection-integration.md: Architecture research mapping protocol to Effection - DECISIONS.md: 34 architectural decisions (DEC-001 through DEC-034) References updated for the effectionx monorepo context: - DEC-001 (Deno) and DEC-004 (@std/assert) marked as superseded - DEC-002 updated with alpha.7 pin and tracking issue #181 - DEC-003 updated to reflect two-package split - Project-local lib/ and test/ paths updated to package-root layout
…me interface
Adds a stat(path) method that returns file metadata without reading
contents. Returns { exists: false, isFile: false, isDirectory: false }
for missing paths instead of throwing — existence checks should not
require try/catch. Permission errors still propagate.
Create new package with PLAN.md, package.json, tsconfig.json, README.md, and placeholder mod.ts. Integrate into monorepo workspace and add effection alpha override for experimental API compatibility. Consumes @effectionx/durable-streams via pkg-pr-new preview URL from PR #179.
Define platform-agnostic DurableRuntime interface with Operation-returning methods for exec, readTextFile, glob, fetch, plus synchronous env/platform. Uses minimal ResponseHeaders interface to avoid DOM lib dependency.
Web Crypto API based SHA-256 hashing that returns Operation<string>. Portable across Node 22+, Deno, and browsers. Returns sha256:<hex> format.
All I/O methods throw by default, proving no live execution during replay. Override per-test to inject specific behavior.
Delegates to @effectionx/process for exec, @effectionx/fetch for HTTP, and @effectionx/fs for filesystem operations (readTextFile, walk, glob). All operations integrate with Effection structured concurrency.
…teDurableOperation<Json> pattern Named interfaces (ExecResult, ReadFileResult, etc.) don't satisfy Json's index signature constraint. Fix by using createDurableOperation<Json> for all 6 effects, casting return values as unknown as Json inside callbacks, and casting the outer yield to the concrete result type.
…nience wrappers 16 tests covering golden run, full replay, and error propagation for durableExec, durableReadFile, durableGlob, durableFetch, durableEval, durableResolve, and convenience wrappers (durableNow, durableUUID, durableEnv).
useFileContentGuard — detects file content changes via hash comparison useGlobContentGuard — detects directory scan changes (add/remove/modify) useCodeFreshnessGuard — detects source/bindings changes for eval cells All guards use DurableRuntime for I/O and computeSHA256 for hashing. Guards compose via Effection's scope.around(ReplayGuard, ...) middleware.
12 tests covering: - useFileContentGuard: file unchanged/changed/no-path/cache-dedup - useGlobContentGuard: files unchanged/added/non-glob pass-through - useCodeFreshnessGuard: source+bindings unchanged/source changed/ bindings changed/unknown cell/non-eval pass-through
Exports: runtime (DurableRuntimeCtx, DurableRuntime), nodeRuntime, stubRuntime, computeSHA256, all 6 effects + 3 convenience wrappers, all 3 replay guards, and all supporting types.
…-metadata policy Replace custom keywords (durable, effects, workflow) with approved categories: concurrency, io, process.
… from durable-streams Replace preview URL dependency with workspace:* for durable-streams. Replace runtime.ts implementation with re-exports from durable-streams. Add durable-streams to tsconfig references for type resolution.
Adds the stat(path) implementation using @effectionx/fs, matching the interface added to DurableRuntime. Re-exports StatResult from durable-effects/runtime.ts and mod.ts. Adds throwing stub and 3 tests covering file, directory, and missing path cases.
5d599cb to
6b63d3d
Compare
Motivation
durable-streamsgives youdurableCall— a generic "call this function and journal the result" primitive. But real workflows need structured effects with typed results, content hashing for staleness detection, and replay guards that know what changed. Writingyield* durableCall("read-file", () => fs.readFile(path))works but loses the structure: there's no content hash in the result for replay guards to compare, no path in the description for guards to inspect, and no typedReadFileResultfor the caller.This package provides effects where the description and result shapes are designed together with their corresponding replay guards.
durableReadFilestores the path in the description and a content hash in the result.useFileContentGuardknows to read that path, hash the current file, and compare against that stored hash. The effect and guard are two halves of the same design.Approach
New package providing six durable effects and three replay guards for Effection workflows. This is the "leaf effects" layer that sits on top of
@effectionx/durable-streams— wheredurable-streamsprovides the protocol (journal, replay index, divergence detection,durableRun,createDurableOperation), this package provides the concrete operations that workflows actually use.DurableRuntime— the platform abstractionEffects don't call Node or Deno APIs directly. A
DurableRuntimeinterface providesexec(),readTextFile(),glob(),fetch(),env(), andplatform()— all returningOperation<T>, notPromise<T>. Cancellation flows through Effection's structured concurrency. NoAbortSignalcrosses the interface.nodeRuntime()is the concrete implementation using@effectionx/process,@effectionx/fs, and@effectionx/fetch.stubRuntime()is the test double — every I/O method throws by default, proving that replay tests never hit live execution.The runtime is installed as an Effection context before
durableRun:Effects
durableExec"exec"durableReadFile"read_file"durableGlob"glob"durableFetch"fetch"durableEval"eval"durableResolve"resolve"All use
createDurableOperationfromdurable-streams. All are fully replay-safe — during replay, the stored result is returned without executing anything.Replay Guards
useFileContentGuarddurableReadFileuseGlobContentGuarddurableGlobuseCodeFreshnessGuarddurableEvalGuards hook into the existing
ReplayGuardmiddleware fromdurable-streams. During the check phase (before replay), they gather current state through the runtime. During the decide phase (during replay), they compare against recorded hashes. Mismatch →StaleInputError.Shared utilities
computeSHA256returnsOperation<string>using the Web Crypto API. Used by effects (to hash content into results) and guards (to hash current state for comparison). Returns"sha256:<hex>"format.Testing
Tests use
@effectionx/bdd(describe/it) andstubRuntime()for isolation. Each effect has golden-run, full-replay, and error-propagation tests. Guards have unchanged/changed/non-applicable tests.nodeRuntimehas integration tests against real Node APIs.