Skip to content

Commit 17d00a3

Browse files
committed
feat(durable-effects): scaffold @effectionx/durable-effects package
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.
1 parent 51ac3d6 commit 17d00a3

7 files changed

Lines changed: 293 additions & 0 deletions

File tree

durable-effects/PLAN.md

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# @effectionx/durable-effects — Implementation Plan
2+
3+
**Status:** In progress
4+
**Branch:** `feat/durable-effects` (based on `main`)
5+
**Worktree:** `~/dev/worktrees/effectionx/durable-effects`
6+
**Spec:** `~/Downloads/effect-type-specs-3.md`
7+
8+
---
9+
10+
## Agent Recovery Prompt
11+
12+
> You are implementing the `@effectionx/durable-effects` package in the effectionx
13+
> monorepo. This is a NEW package (not modifications to `durable-streams`).
14+
>
15+
> **What this package does:** Provides 6 durable effects (`durableExec`,
16+
> `durableReadFile`, `durableGlob`, `durableFetch`, `durableEval`, `durableResolve`)
17+
> plus 3 replay guards (`useFileContentGuard`, `useGlobContentGuard`,
18+
> `useCodeFreshnessGuard`) for use in Effection durable workflows.
19+
>
20+
> **Key architecture decisions:**
21+
>
22+
> 1. **Branch from `main`**, NOT from `feat/durable-streams`.
23+
> 2. **Consume `@effectionx/durable-streams` via preview package URL:**
24+
> `https://pkg.pr.new/thefrontside/effectionx/@effectionx/durable-streams@179`
25+
> (from PR #179). It is NOT a workspace sibling.
26+
> 3. **Node runtime only**`nodeRuntime()` using Node.js APIs. No Deno.
27+
> 4. **Separate context**`DurableRuntimeCtx` is its own Effection context,
28+
> NOT a field on `DurableContext`.
29+
> 5. **`useFileContentGuard` moves entirely** from `durable-streams` to this
30+
> package (in a follow-up PR #2; this PR creates the new version here).
31+
> 6. **Web Crypto API** for `computeSHA256` (portable, Node 22+).
32+
> 7. **Do NOT modify `durableRun`** — callers install runtime context manually
33+
> via `scope.set(DurableRuntimeCtx, nodeRuntime())` before calling `durableRun`.
34+
> 8. **Colocated test files**`*.test.ts` at package root (effectionx convention).
35+
> 9. **Effection alpha override** — needs `"effection": "4.1.0-alpha.7"` in
36+
> `pnpm.overrides` for `effection/experimental` compatibility.
37+
> 10. **Commit after every step** — each implementation step gets its own commit.
38+
>
39+
> **What to import from `@effectionx/durable-streams`:**
40+
> `createDurableOperation`, `DurableEffect`, `Workflow`, `Json`, `WorkflowValue`,
41+
> `EffectDescription`, `Result`, `ReplayGuard`, `ReplayOutcome`, `StaleInputError`,
42+
> `durableRun`, `InMemoryStream`, `DurableEvent`, `Yield`.
43+
>
44+
> **What to import from `effection`:**
45+
> `createContext`, `useScope`, `call`, `action`, `Operation`, `Context`, `Scope`.
46+
>
47+
> **Test pattern:** Use `@effectionx/bdd` (`describe`, `it`), `expect` from
48+
> `expect`, `InMemoryStream` from `@effectionx/durable-streams`, and
49+
> `stubRuntime()` for isolation. Install runtime context manually in tests:
50+
> ```typescript
51+
> const scope = yield* useScope();
52+
> scope.set(DurableRuntimeCtx, stubRuntime({ /* overrides */ }));
53+
> yield* durableRun(workflow, { stream });
54+
> ```
55+
>
56+
> **Inside `createDurableOperation` callbacks**, access runtime via:
57+
> ```typescript
58+
> function* () {
59+
> const scope = yield* useScope();
60+
> const runtime = scope.expect<DurableRuntime>(DurableRuntimeCtx);
61+
> // ... use runtime methods ...
62+
> }
63+
> ```
64+
>
65+
> **Spec file** is at `~/Downloads/effect-type-specs-3.md`. Read it for
66+
> detailed signatures, implementation code, and test requirements.
67+
>
68+
> **This file (PLAN.md)** tracks progress. Check the checklist below to see
69+
> what's done and what's next. Mark items as you complete them.
70+
71+
---
72+
73+
## Architecture
74+
75+
```
76+
durable-effects/ # NEW package at repo root
77+
├── PLAN.md # This fileplan + progress tracker
78+
├── mod.ts # Public API barrel
79+
├── package.json # @effectionx/durable-effects
80+
├── tsconfig.json # Extends root, references bdd
81+
├── README.md # Package documentation
82+
83+
├── runtime.ts # DurableRuntime interface + DurableRuntimeCtx
84+
├── node-runtime.ts # nodeRuntime() — Node.js implementation
85+
├── stub-runtime.ts # stubRuntime() — test helper
86+
├── hash.ts # computeSHA256() — Web Crypto API
87+
88+
├── operations.ts # 6 durable effects
89+
├── guards.ts # 3 replay guards
90+
91+
├── hash.test.ts # Tests for computeSHA256
92+
├── operations.test.ts # Tests for all 6 effects
93+
├── guards.test.ts # Tests for all 3 guards
94+
└── node-runtime.test.ts # Tests for nodeRuntime()
95+
```
96+
97+
### Dependency Graph
98+
99+
```
100+
@effectionx/durable-effects
101+
├── dependencies
102+
│ └── @effectionx/durable-streams (preview URL from PR #179)
103+
├── peerDependencies
104+
│ └── effection: "^3 || ^4"
105+
└── devDependencies
106+
├── @effectionx/bdd: "workspace:*"
107+
├── effection: "^4"
108+
└── expect: "^29"
109+
```
110+
111+
### Effects Summary
112+
113+
| Effect | Type | Runtime methods used |
114+
|------------------|---------------|-----------------------------------------------|
115+
| `durableExec` | `"exec"` | `runtime.exec()` |
116+
| `durableReadFile`| `"read_file"` | `runtime.readTextFile()` |
117+
| `durableGlob` | `"glob"` | `runtime.glob()`, `runtime.readTextFile()` |
118+
| `durableFetch` | `"fetch"` | `runtime.fetch()` |
119+
| `durableEval` | `"eval"` | none (caller-provided evaluator) |
120+
| `durableResolve` | `"resolve"` | `runtime.env()`, `runtime.platform()` |
121+
122+
### Guards Summary
123+
124+
| Guard | Works with | Detects |
125+
|--------------------------|------------------|--------------------------------------------|
126+
| `useFileContentGuard` | `durableReadFile`| File content changed since journal recorded|
127+
| `useGlobContentGuard` | `durableGlob` | Files added/removed/modified in scan |
128+
| `useCodeFreshnessGuard` | `durableEval` | Source or bindings changed for eval cell |
129+
130+
---
131+
132+
## Implementation Checklist
133+
134+
Mark each step done as you complete it. Commit after every step.
135+
136+
- [x] **Step 0:** Create worktree + branch from main
137+
- [ ] **Step 0.5:** Write PLAN.md, scaffold package, integrate into monorepo, pnpm install
138+
- [ ] **Step 1:** `runtime.ts` — DurableRuntime interface + DurableRuntimeCtx
139+
- [ ] **Step 2:** `hash.ts` — computeSHA256 using Web Crypto + `hash.test.ts`
140+
- [ ] **Step 3:** `stub-runtime.ts` — test stub runtime
141+
- [ ] **Step 4:** `node-runtime.ts` — nodeRuntime() + `node-runtime.test.ts`
142+
- [ ] **Step 5:** `operations.ts` — All 6 effects (durableResolve, durableReadFile, durableExec, durableFetch, durableGlob, durableEval) + `operations.test.ts`
143+
- [ ] **Step 6:** `guards.ts` — All 3 replay guards + `guards.test.ts`
144+
- [ ] **Step 7:** `mod.ts` — Complete public API barrel exports
145+
- [ ] **Step 8:** Verify build, lint, all tests pass
146+
- [ ] **Step 9:** Create PR
147+
148+
---
149+
150+
## PR Sequence
151+
152+
**PR 1 (this work):** `feat/durable-effects`
153+
- New `@effectionx/durable-effects` package with all effects, guards, runtime
154+
- Consumes `@effectionx/durable-streams` via preview URL
155+
- Self-contained, no changes to `durable-streams`
156+
157+
**PR 2 (follow-up, after durable-streams merges):**
158+
- Move `DurableRuntime` interface + `DurableRuntimeCtx` into `durable-streams`
159+
- Add `runtime?: DurableRuntime` to `DurableRunOptions`
160+
- Wire runtime context installation into `durableRun()`
161+
- Remove `file-guard.ts` and `useFileContentGuard` from `durable-streams`
162+
- Switch dependency from preview URL to `workspace:*`
163+
164+
---
165+
166+
## Notes
167+
168+
- The spec file (`~/Downloads/effect-type-specs-3.md`) was written for Deno.
169+
Adapt all implementations to Node.js APIs.
170+
- `effection/experimental` requires alpha (`4.1.0-alpha.7`). The root
171+
`package.json` needs `pnpm.overrides` for this.
172+
- `DurableRuntime.fetch()` returns an object with a minimal headers interface
173+
(`{ get(key: string): string | null }`) to avoid requiring DOM lib types.
174+
- `durableResolve` uses `as T` casts — pragmatic, safe at runtime given
175+
`T extends Json` constraint.

durable-effects/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# @effectionx/durable-effects
2+
3+
Durable effects and replay guards for Effection workflows.
4+
5+
Provides a collection of durable effects (`durableExec`, `durableReadFile`,
6+
`durableGlob`, `durableFetch`, `durableEval`, `durableResolve`) and replay
7+
guards (`useFileContentGuard`, `useGlobContentGuard`, `useCodeFreshnessGuard`)
8+
for use with `@effectionx/durable-streams`.
9+
10+
---
11+
12+
## Installation
13+
14+
```bash
15+
npm install @effectionx/durable-effects
16+
```
17+
18+
## Usage
19+
20+
```typescript
21+
import { durableRun, InMemoryStream } from "@effectionx/durable-streams";
22+
import {
23+
nodeRuntime,
24+
DurableRuntimeCtx,
25+
durableExec,
26+
durableReadFile,
27+
useFileContentGuard,
28+
} from "@effectionx/durable-effects";
29+
import { run, useScope } from "effection";
30+
31+
await run(function* () {
32+
const scope = yield* useScope();
33+
34+
// Install the runtime
35+
scope.set(DurableRuntimeCtx, nodeRuntime());
36+
37+
// Optionally install replay guards
38+
yield* useFileContentGuard();
39+
40+
// Run a durable workflow
41+
const stream = new InMemoryStream();
42+
yield* durableRun(function* () {
43+
const result = yield* durableExec("build", {
44+
command: ["npm", "run", "build"],
45+
});
46+
const config = yield* durableReadFile("config", "./config.json");
47+
return { result, config };
48+
}, { stream });
49+
});
50+
```

durable-effects/mod.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* @module
3+
* Durable effects and replay guards for Effection workflows.
4+
*
5+
* Provides platform-agnostic durable effects (exec, readFile, glob, fetch,
6+
* eval, resolve) and replay guards for staleness detection, built on
7+
* @effectionx/durable-streams.
8+
*/
9+
10+
// TODO: exports will be added as each module is implemented

durable-effects/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "@effectionx/durable-effects",
3+
"description": "Durable effects and replay guards for Effection workflows",
4+
"version": "0.1.0",
5+
"type": "module",
6+
"main": "./dist/mod.js",
7+
"types": "./dist/mod.d.ts",
8+
"exports": {
9+
".": {
10+
"types": "./dist/mod.d.ts",
11+
"development": "./mod.ts",
12+
"import": "./dist/mod.js",
13+
"default": "./dist/mod.js"
14+
}
15+
},
16+
"peerDependencies": {
17+
"effection": "^3 || ^4"
18+
},
19+
"dependencies": {
20+
"@effectionx/durable-streams": "https://pkg.pr.new/thefrontside/effectionx/@effectionx/durable-streams@179"
21+
},
22+
"license": "MIT",
23+
"author": "engineering@frontside.com",
24+
"repository": {
25+
"type": "git",
26+
"url": "git+https://github.com/thefrontside/effectionx.git"
27+
},
28+
"bugs": {
29+
"url": "https://github.com/thefrontside/effectionx/issues"
30+
},
31+
"engines": {
32+
"node": ">= 22"
33+
},
34+
"sideEffects": false,
35+
"keywords": ["durable", "effects", "concurrency", "workflow"],
36+
"files": ["dist", "*.ts", "README.md"],
37+
"devDependencies": {
38+
"@effectionx/bdd": "workspace:*",
39+
"effection": "^4",
40+
"expect": "^29"
41+
}
42+
}

durable-effects/tsconfig.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "dist",
5+
"rootDir": "."
6+
},
7+
"include": ["**/*.ts"],
8+
"exclude": ["**/*.test.ts", "dist"],
9+
"references": [
10+
{
11+
"path": "../bdd"
12+
}
13+
]
14+
}

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ packages:
55
- "context-api"
66
- "converge"
77
# deno-deploy excluded - deprecated Deno-only package
8+
- "durable-effects"
89
- "durable-streams"
910
- "effect-ts"
1011
- "fetch"

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
{ "path": "chain" },
1818
{ "path": "context-api" },
1919
{ "path": "converge" },
20+
{ "path": "durable-effects" },
2021
{ "path": "durable-streams" },
2122
{ "path": "effect-ts" },
2223
{ "path": "fetch" },

0 commit comments

Comments
 (0)