|
| 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 file — plan + 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. |
0 commit comments