diff --git a/.changeset/effect-3-22-update.md b/.changeset/effect-3-22-update.md
new file mode 100644
index 00000000..dcc21c03
--- /dev/null
+++ b/.changeset/effect-3-22-update.md
@@ -0,0 +1,13 @@
+---
+'@codeforbreakfast/eventsourcing-store-postgres': patch
+'@codeforbreakfast/eventsourcing-transport-websocket': patch
+'@codeforbreakfast/eventsourcing-testing-contracts': patch
+---
+
+Build and test against Effect 3.22.
+
+`eventsourcing-store-postgres` now depends on `@effect/sql` 0.52, `@effect/sql-pg` 0.53 and `@effect/experimental` 0.61. `eventsourcing-transport-websocket` now depends on `@effect/platform` 0.97.
+
+`eventsourcing-store-postgres` fixes lost events on new subscriptions. `subscribe` and `subscribeAll` used to return before Postgres `LISTEN` was active, so an event committed in that gap never reached the subscriber. They now wait until `LISTEN` is active. To detect that, the store sends probe notifications to the channel it is listening on, repeating every 10ms until one comes back. A probe payload starts with `eventstore_listen_probe:`, and every listener on that channel receives it. If your own code listens on the `eventstore_events_*` channels, ignore payloads with that prefix. Older versions of this package log a parse error for each probe they receive.
+
+In `eventsourcing-testing-contracts`, the test helpers fail with Effect's tagged errors instead of a plain `Error`. `expectError` fails with `NoSuchElementException` when the error does not match the predicate. `waitForConnectionState` and `collectMessages` fail with `TimeoutException` when they time out. The declared error type is still `Error`, so existing code compiles unchanged, and you can now match these failures by `_tag`.
diff --git a/bun.lock b/bun.lock
index 27e58822..ecd78180 100644
--- a/bun.lock
+++ b/bun.lock
@@ -11,18 +11,18 @@
"@codeforbreakfast/eslint-effect": "workspace:*",
"@commitlint/cli": "20.5.3",
"@commitlint/config-conventional": "20.5.3",
- "@effect/cluster": "0.55.0",
- "@effect/language-service": "0.60.0",
- "@effect/platform": "0.93.6",
- "@effect/platform-bun": "0.86.0",
- "@effect/rpc": "0.72.2",
- "@effect/workflow": "0.15.0",
+ "@effect/cluster": "0.60.2",
+ "@effect/language-service": "0.87.3",
+ "@effect/platform": "0.97.2",
+ "@effect/platform-bun": "0.91.2",
+ "@effect/rpc": "0.76.2",
+ "@effect/workflow": "0.19.1",
"@eslint-community/eslint-plugin-eslint-comments": "4.8.1",
"@types/bun": "1.4.2",
"@typescript-eslint/eslint-plugin": "8.70.1",
"@typescript-eslint/parser": "8.70.1",
"dependency-cruiser": "17.4.3",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-compat": "6.2.1",
@@ -50,9 +50,9 @@
"@codeforbreakfast/eventsourcing-projections": "workspace:*",
"@codeforbreakfast/eventsourcing-store": "workspace:*",
"@codeforbreakfast/eventsourcing-store-filesystem": "workspace:*",
- "@effect/cli": "0.72.1",
- "@effect/platform-bun": "0.86.0",
- "effect": "3.19.9",
+ "@effect/cli": "0.77.2",
+ "@effect/platform-bun": "0.91.2",
+ "effect": "3.22.2",
},
"devDependencies": {
"@types/bun": "latest",
@@ -62,7 +62,7 @@
"name": "@codeforbreakfast/bun-test-effect",
"version": "0.4.1",
"devDependencies": {
- "effect": "3.19.9",
+ "effect": "3.22.2",
},
"peerDependencies": {
"effect": ">=3.0.0",
@@ -73,7 +73,7 @@
"version": "0.8.5",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "8.70.1",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -93,7 +93,7 @@
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-store-inmemory": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -111,7 +111,7 @@
"devDependencies": {
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -128,7 +128,7 @@
},
"devDependencies": {
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -149,7 +149,7 @@
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-transport-inmemory": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -168,7 +168,7 @@
"@codeforbreakfast/eventsourcing-store-inmemory": "workspace:*",
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -185,7 +185,7 @@
"devDependencies": {
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -203,9 +203,9 @@
"devDependencies": {
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
- "@effect/platform-bun": "0.86.0",
+ "@effect/platform-bun": "0.91.2",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -224,7 +224,7 @@
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -238,17 +238,17 @@
"version": "0.6.14",
"dependencies": {
"@codeforbreakfast/eventsourcing-store": "workspace:*",
- "@effect/experimental": "0.57.11",
- "@effect/sql": "0.48.6",
- "@effect/sql-pg": "0.49.7",
+ "@effect/experimental": "0.61.1",
+ "@effect/sql": "0.52.1",
+ "@effect/sql-pg": "0.53.0",
"type-fest": "5.10.0",
},
"devDependencies": {
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
- "@effect/platform-bun": "0.86.0",
+ "@effect/platform-bun": "0.91.2",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -267,7 +267,7 @@
"devDependencies": {
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -283,7 +283,7 @@
},
"devDependencies": {
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -302,7 +302,7 @@
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -315,7 +315,7 @@
"version": "0.5.7",
"dependencies": {
"@codeforbreakfast/eventsourcing-transport": "workspace:*",
- "@effect/platform": "0.93.6",
+ "@effect/platform": "0.97.2",
"type-fest": "5.10.0",
},
"devDependencies": {
@@ -323,7 +323,7 @@
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
"@types/node": "24.19.0",
"bun-types": "1.4.2",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -341,7 +341,7 @@
},
"devDependencies": {
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3",
},
@@ -467,33 +467,33 @@
"@conventional-changelog/git-client": ["@conventional-changelog/git-client@2.7.0", "", { "dependencies": { "@simple-libs/child-process-utils": "^1.0.0", "@simple-libs/stream-utils": "^1.2.0", "semver": "^7.5.2" }, "peerDependencies": { "conventional-commits-filter": "^5.0.0", "conventional-commits-parser": "^6.4.0" }, "optionalPeers": ["conventional-commits-filter", "conventional-commits-parser"] }, "sha512-j7A8/LBEQ+3rugMzPXoKYzyUPpw/0CBQCyvtTR7Lmu4olG4yRC/Tfkq79Mr3yuPs0SUitlO2HwGP3gitMJnRFw=="],
- "@effect/cli": ["@effect/cli@0.72.1", "", { "dependencies": { "ini": "^4.1.3", "toml": "^3.0.0", "yaml": "^2.5.0" }, "peerDependencies": { "@effect/platform": "^0.93.0", "@effect/printer": "^0.47.0", "@effect/printer-ansi": "^0.47.0", "effect": "^3.19.3" } }, "sha512-HGDMGD23TxFW9tCSX6g+M2u0robikMA0mP0SqeJMj7FWXTdcQ+cQsJE99bxi9iu+5YID7MIrVJMs8TUwXUV2sg=="],
+ "@effect/cli": ["@effect/cli@0.77.2", "", { "dependencies": { "ini": "^4.1.3", "toml": "^4.3.0", "yaml": "^2.5.0" }, "peerDependencies": { "@effect/platform": "^0.97.2", "@effect/printer": "^0.51.0", "@effect/printer-ansi": "^0.51.0", "effect": "^3.22.2" } }, "sha512-1I6rvAtsyATAFF5nBGeou2qKIkIr3WFEoHNd2+UH8inREblwCLKJb7o3EdbMcG5w3212ZanFVOA2/3KHL22NbQ=="],
- "@effect/cluster": ["@effect/cluster@0.55.0", "", { "dependencies": { "kubernetes-types": "^1.30.0" }, "peerDependencies": { "@effect/platform": "^0.93.6", "@effect/rpc": "^0.72.2", "@effect/sql": "^0.48.6", "@effect/workflow": "^0.15.0", "effect": "^3.19.8" } }, "sha512-WeLrGpdtWhr4ap3kcW+G+qYyDqzEa65ypPWsU+sNU2gordENsAkxUQqo8taKr114AOk6WE74q0I7iWABcfGlhQ=="],
+ "@effect/cluster": ["@effect/cluster@0.60.2", "", { "dependencies": { "kubernetes-types": "^1.30.0" }, "peerDependencies": { "@effect/platform": "^0.97.1", "@effect/rpc": "^0.76.2", "@effect/sql": "^0.52.1", "@effect/workflow": "^0.19.1", "effect": "^3.22.1" } }, "sha512-GEODN5kvFJ0L0XSkANcS8s6lfhjrrA8x/ezYUV/lXerqCHf5xo3ZTPUanGMHu5Byvikng0mZDcyNvke2sGjfSQ=="],
- "@effect/experimental": ["@effect/experimental@0.57.11", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/platform": "^0.93.6", "effect": "^3.19.9", "ioredis": "^5", "lmdb": "^3" }, "optionalPeers": ["ioredis", "lmdb"] }, "sha512-M5uug3Drs/gyTHLfA+XzcIZQGUEV/Jn5yi1POki4oZswhpzNmsVTHl4THpxAordRKwa5lFvTSlsRP684YH7pSw=="],
+ "@effect/experimental": ["@effect/experimental@0.61.1", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/platform": "^0.97.1", "effect": "^3.22.1", "ioredis": "^5", "lmdb": "^3" }, "optionalPeers": ["ioredis", "lmdb"] }, "sha512-+P+PgGQeE2fXmsm63YoyENNcQIc7OiCkY4/HLkEdTvq93syfGIpwDfQD8u3xODMHJhZ5ZtRZHH4+UedFO0tLiA=="],
- "@effect/language-service": ["@effect/language-service@0.60.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-elJDWHG5Naq3OkilPt9ZRn56JfSA3MhXUIlDx9RWJeScHm96kZ+HkZ3eFBxqROzXwD6Q2DTtFctFwOM0+QLZEA=="],
+ "@effect/language-service": ["@effect/language-service@0.87.3", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-g1OmRBZF5chbW9QMNoirPo5Bb4zzU2HfP88tR7/VSZEUxHJ1/qVb8AQ0kUh2ggctLH1I715yCwP4gkw56PTjpQ=="],
- "@effect/platform": ["@effect/platform@0.93.6", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.19.8" } }, "sha512-I5lBGQWzWXP4zlIdPs7z7WHmEFVBQhn+74emr/h16GZX96EEJ6I1rjGaKyZF7mtukbMuo9wEckDPssM8vskZ/w=="],
+ "@effect/platform": ["@effect/platform@0.97.2", "", { "dependencies": { "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.10", "multipasta": "^0.2.8" }, "peerDependencies": { "effect": "^3.22.2" } }, "sha512-fof9J+pId374/b1WAt0P1H8QmvulzpHTjS6SUHiG20U/sFjkBUS5vGMy1/XfdTAVKX1IY+Z87dFhi/RFDfgBPQ=="],
- "@effect/platform-bun": ["@effect/platform-bun@0.86.0", "", { "dependencies": { "@effect/platform-node-shared": "^0.56.0", "multipasta": "^0.2.7" }, "peerDependencies": { "@effect/cluster": "^0.55.0", "@effect/platform": "^0.93.6", "@effect/rpc": "^0.72.2", "@effect/sql": "^0.48.6", "effect": "^3.19.8" } }, "sha512-wg6eyzIxHuOd87rHjR18T7DjOtQ0dz8/cQD/pvZo8xFBSZ0AoZXqkHOKuU34DIRA4tp+BdBP6wjDDQYNQK/agQ=="],
+ "@effect/platform-bun": ["@effect/platform-bun@0.91.2", "", { "dependencies": { "@effect/platform-node-shared": "^0.61.1", "multipasta": "^0.2.8" }, "peerDependencies": { "@effect/cluster": "^0.60.2", "@effect/platform": "^0.97.1", "@effect/rpc": "^0.76.2", "@effect/sql": "^0.52.1", "effect": "^3.22.1" } }, "sha512-lISuYdx54uX6BIu0AIwvIqG6mbFkT0UlqGWZm7Jn2V6Ni3nCumnaBfMxYrdGWJzkoGIi1xEkvmRRk5Ff7r6NEA=="],
- "@effect/platform-node-shared": ["@effect/platform-node-shared@0.56.0", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "multipasta": "^0.2.7", "ws": "^8.18.2" }, "peerDependencies": { "@effect/cluster": "^0.55.0", "@effect/platform": "^0.93.6", "@effect/rpc": "^0.72.2", "@effect/sql": "^0.48.6", "effect": "^3.19.8" } }, "sha512-0RawLcUCLHVGs4ch1nY26P4xM+U6R03ZR02MgNHMsL0slh8YYlal5PnwD/852rJ59O9prQX3Kq8zs+cGVoLAJw=="],
+ "@effect/platform-node-shared": ["@effect/platform-node-shared@0.61.1", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "multipasta": "^0.2.8", "ws": "^8.18.2" }, "peerDependencies": { "@effect/cluster": "^0.60.1", "@effect/platform": "^0.97.1", "@effect/rpc": "^0.76.1", "@effect/sql": "^0.52.1", "effect": "^3.22.1" } }, "sha512-MJAJ1Nc43pYJb5ulFtdNID8klNyRLcbQ0zZ7XYRcBAlT/DrCyN4yAD89AzosmUyfOU+LMHdc8LTMt4rKNEMFaA=="],
"@effect/printer": ["@effect/printer@0.40.12", "", { "peerDependencies": { "@effect/typeclass": "^0.31.12", "effect": "^3.12.12" } }, "sha512-B5y/9pzdvN4ntlg74jhFE5nhgdTALdPDfaJ/e4JgIjzHAfcT3nKVyZj1CPoTFkNSFOPID5YBMPSzAG14vwSm5w=="],
"@effect/printer-ansi": ["@effect/printer-ansi@0.40.12", "", { "dependencies": { "@effect/printer": "^0.40.12" }, "peerDependencies": { "@effect/typeclass": "^0.31.12", "effect": "^3.12.12" } }, "sha512-3dtgF0j6HzUQZlnytVqAldBX3mHUcXFrPtYfbo+E/2m0cN0ggfWOXOUYxB/rCWSeYR0bdGxFReEsxNuydzCQHQ=="],
- "@effect/rpc": ["@effect/rpc@0.72.2", "", { "dependencies": { "msgpackr": "^1.11.4" }, "peerDependencies": { "@effect/platform": "^0.93.3", "effect": "^3.19.5" } }, "sha512-BmTXybXCOq96D2r9mvSW/YdiTQs5CStnd4II+lfVKrMr3pMNERKLZ2LG37Tfm4Sy3Q8ire6IVVKO/CN+VR0uQQ=="],
+ "@effect/rpc": ["@effect/rpc@0.76.2", "", { "dependencies": { "msgpackr": "^1.11.10" }, "peerDependencies": { "@effect/platform": "^0.97.1", "effect": "^3.22.1" } }, "sha512-x/4jvjufr1m3QWAace7G5Xh09DDcaeU7ijvS0Qumkan/hM89n19JClYnlV3IW93RACZInbgdyOVA+4kOQJ+JQQ=="],
- "@effect/sql": ["@effect/sql@0.48.6", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/experimental": "^0.57.9", "@effect/platform": "^0.93.6", "effect": "^3.19.8" } }, "sha512-OBIG/DYFxYTA9EXXhqi6sAcX0YLz8Huu8L+wj3a0aOSRPpHm9HkL9a5lacRPPvrVl31rKcEteGa/lO6n26gIFg=="],
+ "@effect/sql": ["@effect/sql@0.52.1", "", { "dependencies": { "uuid": "^11.0.3" }, "peerDependencies": { "@effect/experimental": "^0.61.1", "@effect/platform": "^0.97.1", "effect": "^3.22.1" } }, "sha512-dUVPjlUgpga6sDa/MtYipHAqzHx2St41hxI00nXTBt4IBBJaf2lTGaOvlEoBg/q6PHVAgSoKnxe19pWUvabC9g=="],
- "@effect/sql-pg": ["@effect/sql-pg@0.49.7", "", { "dependencies": { "pg": "^8.16.3", "pg-connection-string": "2.9.1", "pg-cursor": "^2.15.3", "pg-pool": "^3.10.1", "pg-types": "^4.1.0" }, "peerDependencies": { "@effect/experimental": "^0.57.5", "@effect/platform": "^0.93.4", "@effect/sql": "^0.48.1", "effect": "^3.19.6" } }, "sha512-7yGHEcSTj3jo7EZxC4cEXUno+/pgbEjjY5WLrx0Nfta07Vu6CTplcSOQ5KA24HwkclBidNwfX7LWghc04kAgUQ=="],
+ "@effect/sql-pg": ["@effect/sql-pg@0.53.0", "", { "dependencies": { "pg": "^8.16.3", "pg-connection-string": "2.9.1", "pg-cursor": "^2.15.3", "pg-pool": "^3.10.1", "pg-types": "^4.1.0" }, "peerDependencies": { "@effect/experimental": "^0.61.0", "@effect/platform": "^0.97.0", "@effect/sql": "^0.52.0", "effect": "^3.22.0" } }, "sha512-E9LPkP3aB9RNPhAl9R19z6dcmleNyJJGatg0N/zpH3LyIg3dB/4Qhv543rNOGyQlox5ebUADbHUxWk5UzDfSCA=="],
"@effect/typeclass": ["@effect/typeclass@0.31.12", "", { "peerDependencies": { "effect": "^3.12.12" } }, "sha512-iyg8EJgZ7PS4hFnkWFtyeaXIH4544tLprmerRYFjgGHMsqxoorTfHd2N01inXd3stUgcGiHoVy1DDAxWKLomkQ=="],
- "@effect/workflow": ["@effect/workflow@0.15.0", "", { "peerDependencies": { "@effect/experimental": "^0.57.9", "@effect/platform": "^0.93.6", "@effect/rpc": "^0.72.2", "effect": "^3.19.8" } }, "sha512-0u9aO1Uwo5ZdYJL6jygBvNrYt7JKVEI3HKeiSj5c/PP2fXvtdKelADf7FVUZeGEM/cmiYfU/f4LXO+lBJY87HQ=="],
+ "@effect/workflow": ["@effect/workflow@0.19.1", "", { "peerDependencies": { "@effect/experimental": "^0.61.1", "@effect/platform": "^0.97.1", "@effect/rpc": "^0.76.2", "effect": "^3.22.1" } }, "sha512-irU6ddHdIxYOhv0cJiJTk2AUxRmPYjRJ5JdB5UCvpJC4vLCyR2XyKspiCNDiEX1aSGPhVWEXhVMWXR1cErMS3A=="],
"@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="],
@@ -817,7 +817,7 @@
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
- "effect": ["effect@3.19.9", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-taMXnfG/p+j7AmMOHHQaCHvjqwu9QBO3cxuZqL2dMG/yWcEMw0ZHruHe9B49OxtfKH/vKKDDKRhZ+1GJ2p5R5w=="],
+ "effect": ["effect@3.22.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-fm1CQXCs7GTMwpadZxwU5rnAb2bN6mbQpknq/pXBGCzSKkosNYb9k0wsoL0TfB10rSa5xOEZJjo77u/KLXSANg=="],
"electron-to-chromium": ["electron-to-chromium@1.5.223", "", {}, "sha512-qKm55ic6nbEmagFlTFczML33rF90aU+WtrJ9MdTCThrcvDNdUHN4p6QfVN78U06ZmguqXIyMPyYhw2TrbDUwPQ=="],
@@ -1141,11 +1141,11 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
- "msgpackr": ["msgpackr@1.11.5", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA=="],
+ "msgpackr": ["msgpackr@1.12.1", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-4EUH9tQHnMmEgzW/MdAP0KIfa1T9AF+htl0ffe2n5vb2EKn9y2co8ccpgWko6S52Jy1PQZKwRnx5/KkYjtd9MQ=="],
"msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="],
- "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
+ "multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
@@ -1387,7 +1387,7 @@
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
- "toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="],
+ "toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="],
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
@@ -1483,14 +1483,10 @@
"@changesets/write/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="],
- "@codeforbreakfast/eventsourcing-example-todo/@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
-
"@commitlint/config-validator/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
"@commitlint/resolve-extends/global-directory": ["global-directory@5.0.0", "", { "dependencies": { "ini": "6.0.0" } }, "sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w=="],
- "@effect/cli/yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
-
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
@@ -1967,8 +1963,6 @@
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
- "@codeforbreakfast/eventsourcing-example-todo/@types/bun/bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
-
"@commitlint/config-validator/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"@commitlint/resolve-extends/global-directory/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
@@ -2083,8 +2077,6 @@
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
- "@codeforbreakfast/eventsourcing-example-todo/@types/bun/bun-types/@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
-
"@manypkg/find-root/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
@@ -2211,8 +2203,6 @@
"npm/wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
- "@codeforbreakfast/eventsourcing-example-todo/@types/bun/bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
-
"@manypkg/find-root/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"eslint-plugin-expect-type/@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
diff --git a/examples/todo-app/package.json b/examples/todo-app/package.json
index f9c00c17..100f47bf 100644
--- a/examples/todo-app/package.json
+++ b/examples/todo-app/package.json
@@ -21,9 +21,9 @@
"@codeforbreakfast/eventsourcing-projections": "workspace:*",
"@codeforbreakfast/eventsourcing-store": "workspace:*",
"@codeforbreakfast/eventsourcing-store-filesystem": "workspace:*",
- "@effect/cli": "0.72.1",
- "@effect/platform-bun": "0.86.0",
- "effect": "3.19.9"
+ "@effect/cli": "0.77.2",
+ "@effect/platform-bun": "0.91.2",
+ "effect": "3.22.2"
},
"devDependencies": {
"@types/bun": "latest"
diff --git a/examples/todo-app/src/domain/todoAggregate.ts b/examples/todo-app/src/domain/todoAggregate.ts
index ce30ad4d..8da907a8 100644
--- a/examples/todo-app/src/domain/todoAggregate.ts
+++ b/examples/todo-app/src/domain/todoAggregate.ts
@@ -1,4 +1,4 @@
-import { Effect, Match, Option, ParseResult, Schema, pipe } from 'effect';
+import { Data, Effect, Match, Option, ParseResult, Schema, pipe } from 'effect';
import {
makeAggregateRoot,
defineAggregateEventStore,
@@ -91,20 +91,26 @@ const applyEvent =
Match.orElse(() => handleNonCreatedEvent(state, event))
);
+class TodoCommandError extends Data.TaggedError('TodoCommandError')<{
+ readonly message: string;
+}> {}
+
const requireExistingTodo = (
operation: string,
onSome: (state: TodoState) => Effect.Effect
-): ((state: Readonly>) => Effect.Effect) =>
+): ((state: Readonly>) => Effect.Effect) =>
Option.match({
- onNone: () => Effect.fail(new Error(`Cannot ${operation} non-existent TODO`)),
+ onNone: () =>
+ Effect.fail(new TodoCommandError({ message: `Cannot ${operation} non-existent TODO` })),
onSome,
});
const failIfDeletedTodo =
(operation: string) =>
- (state: TodoState): Effect.Effect =>
+ (state: TodoState): Effect.Effect =>
Effect.if(state.deleted, {
- onTrue: () => Effect.fail(new Error(`Cannot ${operation} deleted TODO`)),
+ onTrue: () =>
+ Effect.fail(new TodoCommandError({ message: `Cannot ${operation} deleted TODO` })),
onFalse: () => Effect.succeed(state),
});
diff --git a/examples/todo-app/src/infrastructure/processManager.ts b/examples/todo-app/src/infrastructure/processManager.ts
index 8d6deaea..9ff6b0ef 100644
--- a/examples/todo-app/src/infrastructure/processManager.ts
+++ b/examples/todo-app/src/infrastructure/processManager.ts
@@ -16,12 +16,7 @@ const isTodoCreated = (event: unknown): event is EventRecord =>
typeof event === 'object' && event !== null && 'type' in event && event.type === 'TodoDeleted';
-const parseUserId = (userId: unknown): Effect.Effect =>
- pipe(
- userId,
- Schema.decodeUnknown(UserIdSchema),
- Effect.mapError(() => new Error(`Invalid UserId: ${String(userId)}`))
- );
+const parseUserId = Schema.decodeUnknown(UserIdSchema);
const provideInitiatorFromUserId =
(effect: Effect.Effect) =>
@@ -49,12 +44,7 @@ const executeAndCommit = , TError>(
) =>
pipe(command, withCommandInitiator(event), Effect.flatMap(commitEvents(state.nextEventNumber)));
-const parseTodoId = (todoId: string): Effect.Effect =>
- pipe(
- todoId,
- Schema.decode(TodoIdSchema),
- Effect.mapError(() => new Error(`Invalid TodoId: ${todoId}`))
- );
+const parseTodoId = Schema.decode(TodoIdSchema);
const handleCommand = , TError>(
event: TEvent,
diff --git a/package.json b/package.json
index 1595adac..e646236c 100644
--- a/package.json
+++ b/package.json
@@ -47,17 +47,17 @@
"@changesets/cli": "2.31.1",
"@commitlint/cli": "20.5.3",
"@commitlint/config-conventional": "20.5.3",
- "@effect/cluster": "0.55.0",
- "@effect/language-service": "0.60.0",
- "@effect/platform": "0.93.6",
- "@effect/platform-bun": "0.86.0",
- "@effect/rpc": "0.72.2",
- "@effect/workflow": "0.15.0",
+ "@effect/cluster": "0.60.2",
+ "@effect/language-service": "0.87.3",
+ "@effect/platform": "0.97.2",
+ "@effect/platform-bun": "0.91.2",
+ "@effect/rpc": "0.76.2",
+ "@effect/workflow": "0.19.1",
"@types/bun": "1.4.2",
"@typescript-eslint/eslint-plugin": "8.70.1",
"@typescript-eslint/parser": "8.70.1",
"dependency-cruiser": "17.4.3",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-compat": "6.2.1",
diff --git a/packages/bun-test-effect/package.json b/packages/bun-test-effect/package.json
index c278756a..88d06d06 100644
--- a/packages/bun-test-effect/package.json
+++ b/packages/bun-test-effect/package.json
@@ -44,6 +44,6 @@
"effect": ">=3.0.0"
},
"devDependencies": {
- "effect": "3.19.9"
+ "effect": "3.22.2"
}
}
diff --git a/packages/eslint-effect/package.json b/packages/eslint-effect/package.json
index 78dda6b4..f7cb6622 100644
--- a/packages/eslint-effect/package.json
+++ b/packages/eslint-effect/package.json
@@ -48,6 +48,6 @@
"eslint": "9.39.5",
"@typescript-eslint/eslint-plugin": "8.70.1",
"typescript": "5.9.3",
- "effect": "3.19.9"
+ "effect": "3.22.2"
}
}
diff --git a/packages/eslint-effect/test/no-if-statement.test.ts b/packages/eslint-effect/test/no-if-statement.test.ts
index bff27d84..18ea2a85 100644
--- a/packages/eslint-effect/test/no-if-statement.test.ts
+++ b/packages/eslint-effect/test/no-if-statement.test.ts
@@ -1,4 +1,4 @@
-import { Effect, Either, Option } from 'effect';
+import { Cause, Effect, Either, Option } from 'effect';
const either = Either.right(42);
const option = Option.some(42);
@@ -23,7 +23,7 @@ if (event.type === 'TodoCreated') {
// eslint-disable-next-line effect/no-if-statement
if (state.deleted) {
- const _unused1 = Effect.fail(new Error('Cannot complete deleted TODO'));
+ const _unused1 = Effect.fail(new Cause.RuntimeException('Cannot complete deleted TODO'));
} else {
const _unused2 = Effect.succeed(state);
}
diff --git a/packages/eslint-effect/test/prefer-match-over-ternary.test.ts b/packages/eslint-effect/test/prefer-match-over-ternary.test.ts
index ba6e88a5..6d8d3616 100644
--- a/packages/eslint-effect/test/prefer-match-over-ternary.test.ts
+++ b/packages/eslint-effect/test/prefer-match-over-ternary.test.ts
@@ -1,4 +1,4 @@
-import { pipe, Effect, Match, Option } from 'effect';
+import { pipe, Cause, Effect, Match, Option } from 'effect';
// Should fail - ternary with Effect calls in return statement
const ternaryWithEffect = (condition: boolean) => {
@@ -49,8 +49,10 @@ const ternaryInEffectSucceed = (condition: boolean) => Effect.succeed(condition
// Should fail - ternary inside Effect.fail (complex condition)
const ternaryInEffectFail = (hasError: boolean) =>
- // eslint-disable-next-line effect/prefer-match-over-ternary
- Effect.fail(hasError ? new Error('Critical') : new Error('Warning'));
+ Effect.fail(
+ // eslint-disable-next-line effect/prefer-match-over-ternary
+ hasError ? new Cause.RuntimeException('Critical') : new Cause.RuntimeException('Warning')
+ );
// Should NOT fail - simple literal equality INSIDE Effect.succeed (no duplication)
const simpleLiteralInEffect = (id: string) => Effect.succeed(id === 'user-1' ? 'John' : 'Guest');
diff --git a/packages/eventsourcing-aggregates/package.json b/packages/eventsourcing-aggregates/package.json
index b71c1f5c..e5f5c96c 100644
--- a/packages/eventsourcing-aggregates/package.json
+++ b/packages/eventsourcing-aggregates/package.json
@@ -72,7 +72,7 @@
"@types/node": "24.19.0",
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-store-inmemory": "workspace:*",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"typescript": "5.9.3",
"eslint": "9.39.5"
},
diff --git a/packages/eventsourcing-commands/package.json b/packages/eventsourcing-commands/package.json
index cbf05da7..f6f790c2 100644
--- a/packages/eventsourcing-commands/package.json
+++ b/packages/eventsourcing-commands/package.json
@@ -67,7 +67,7 @@
"devDependencies": {
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"typescript": "5.9.3",
"eslint": "9.39.5"
},
diff --git a/packages/eventsourcing-projections/package.json b/packages/eventsourcing-projections/package.json
index daaa0095..2bc7052e 100644
--- a/packages/eventsourcing-projections/package.json
+++ b/packages/eventsourcing-projections/package.json
@@ -67,7 +67,7 @@
},
"devDependencies": {
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"typescript": "5.9.3",
"eslint": "9.39.5"
},
diff --git a/packages/eventsourcing-protocol/package.json b/packages/eventsourcing-protocol/package.json
index b5419ef0..8b995a8c 100644
--- a/packages/eventsourcing-protocol/package.json
+++ b/packages/eventsourcing-protocol/package.json
@@ -77,7 +77,7 @@
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-transport-inmemory": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"typescript": "5.9.3",
"eslint": "9.39.5"
},
diff --git a/packages/eventsourcing-server/package.json b/packages/eventsourcing-server/package.json
index 020735ce..31e8ded7 100644
--- a/packages/eventsourcing-server/package.json
+++ b/packages/eventsourcing-server/package.json
@@ -68,7 +68,7 @@
"@codeforbreakfast/eventsourcing-store-inmemory": "workspace:*",
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
"@codeforbreakfast/bun-test-effect": "workspace:*",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"typescript": "5.9.3",
"eslint": "9.39.5"
},
diff --git a/packages/eventsourcing-store-filesystem/package.json b/packages/eventsourcing-store-filesystem/package.json
index 5d3ed1cd..fd2f3584 100644
--- a/packages/eventsourcing-store-filesystem/package.json
+++ b/packages/eventsourcing-store-filesystem/package.json
@@ -68,9 +68,9 @@
"devDependencies": {
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
- "@effect/platform-bun": "0.86.0",
+ "@effect/platform-bun": "0.91.2",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3"
},
diff --git a/packages/eventsourcing-store-filesystem/src/lib/FileSystemStore.ts b/packages/eventsourcing-store-filesystem/src/lib/FileSystemStore.ts
index 1f81a18f..27a73e76 100644
--- a/packages/eventsourcing-store-filesystem/src/lib/FileSystemStore.ts
+++ b/packages/eventsourcing-store-filesystem/src/lib/FileSystemStore.ts
@@ -1,4 +1,5 @@
import {
+ Cause,
Chunk,
Effect,
Stream,
@@ -318,11 +319,8 @@ const appendEventsToStream =
);
};
-const parseJsonContent = (content: string): Effect.Effect =>
- Effect.try({
- try: () => JSON.parse(content) as V,
- catch: () => new Error('Failed to parse event'),
- });
+const parseJsonContent = (content: string): Effect.Effect =>
+ Effect.try(() => JSON.parse(content) as V);
const readEventFromFile = (
eventPath: string,
diff --git a/packages/eventsourcing-store-inmemory/package.json b/packages/eventsourcing-store-inmemory/package.json
index b6aa7521..d753b98d 100644
--- a/packages/eventsourcing-store-inmemory/package.json
+++ b/packages/eventsourcing-store-inmemory/package.json
@@ -69,7 +69,7 @@
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"typescript": "5.9.3",
"eslint": "9.39.5"
},
diff --git a/packages/eventsourcing-store-postgres/package.json b/packages/eventsourcing-store-postgres/package.json
index 5ffb5802..f3242f06 100644
--- a/packages/eventsourcing-store-postgres/package.json
+++ b/packages/eventsourcing-store-postgres/package.json
@@ -67,17 +67,17 @@
},
"dependencies": {
"@codeforbreakfast/eventsourcing-store": "workspace:*",
- "@effect/experimental": "0.57.11",
- "@effect/sql": "0.48.6",
- "@effect/sql-pg": "0.49.7",
+ "@effect/experimental": "0.61.1",
+ "@effect/sql": "0.52.1",
+ "@effect/sql-pg": "0.53.0",
"type-fest": "5.10.0"
},
"devDependencies": {
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
- "@effect/platform-bun": "0.86.0",
+ "@effect/platform-bun": "0.91.2",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3"
},
diff --git a/packages/eventsourcing-store-postgres/src/bridge.integration.test.ts b/packages/eventsourcing-store-postgres/src/bridge.integration.test.ts
index 3002cb45..59694908 100644
--- a/packages/eventsourcing-store-postgres/src/bridge.integration.test.ts
+++ b/packages/eventsourcing-store-postgres/src/bridge.integration.test.ts
@@ -42,11 +42,15 @@ const appendEvents = (
events: readonly string[]
) => pipe(events, Stream.fromIterable, Stream.run(store.append({ streamId, eventNumber: 0 })));
-const forkPerStreamSubscription = (store: EventStore, streamId: EventStreamId) => {
+const forkPerStreamSubscription = (
+ store: EventStore,
+ streamId: EventStreamId,
+ count: number
+) => {
const subscription = store.subscribe({ streamId, eventNumber: 0 });
return pipe(
subscription,
- Effect.flatMap((stream) => collectEvents(stream, 2))
+ Effect.flatMap((stream) => collectEvents(stream, count))
);
};
@@ -60,7 +64,7 @@ const forkAllEventsSubscription = (store: EventStore, count: number) =>
const setupDualSubscriptions = (store: EventStore, streamId: EventStreamId) =>
Effect.all({
- perStreamFiber: forkPerStreamSubscription(store, streamId),
+ perStreamFiber: forkPerStreamSubscription(store, streamId, 2),
allEventsFiber: forkAllEventsSubscription(store, 2),
});
@@ -209,6 +213,63 @@ const runMultiStreamTest = (streamId1: EventStreamId, streamId2: EventStreamId)
})
);
+const appendImmediatelyAndJoin = (
+ store: EventStore,
+ streamId: EventStreamId,
+ fiber: Fiber.Fiber, unknown>
+) =>
+ pipe(
+ appendEvents(store, streamId, ['immediate-event']),
+ Effect.andThen(Fiber.join(fiber)),
+ Effect.timeout('2 seconds')
+ );
+
+const runImmediatePerStreamTest = (streamId: EventStreamId) =>
+ pipe(
+ StringEventStore,
+ Effect.flatMap((store) =>
+ Effect.flatMap(forkPerStreamSubscription(store, streamId, 1), (fiber) =>
+ appendImmediatelyAndJoin(store, streamId, fiber)
+ )
+ ),
+ Effect.map((events) => {
+ expect(Array.from(events)).toEqual(['immediate-event']);
+ })
+ );
+
+const runImmediateAllEventsTest = (streamId: EventStreamId) =>
+ pipe(
+ StringEventStore,
+ Effect.flatMap((store) =>
+ Effect.flatMap(forkAllEventsSubscription(store, 1), (fiber) =>
+ appendImmediatelyAndJoin(store, streamId, fiber)
+ )
+ ),
+ Effect.map((events) => {
+ expect(Array.from(Chunk.map(events, (e) => e.event))).toEqual(['immediate-event']);
+ })
+ );
+
+describe('Subscriptions are live as soon as they return', () => {
+ it('should deliver an event appended as soon as subscribe returns', () =>
+ pipe(
+ randomId(),
+ decodeStreamId,
+ Effect.flatMap(runImmediatePerStreamTest),
+ Effect.provide(TestLayer),
+ Effect.runPromise
+ ));
+
+ it('should deliver an event appended as soon as subscribeAll returns', () =>
+ pipe(
+ randomId(),
+ decodeStreamId,
+ Effect.flatMap(runImmediateAllEventsTest),
+ Effect.provide(TestLayer),
+ Effect.runPromise
+ ));
+});
+
describe('Bridge notification publishing', () => {
it('should publish events to BOTH per-stream subscribers AND all-events subscribers', () => {
const streamId = decodeStreamId(randomId());
diff --git a/packages/eventsourcing-store-postgres/src/notificationListener.ts b/packages/eventsourcing-store-postgres/src/notificationListener.ts
index fb6fd9fb..0fff65d9 100644
--- a/packages/eventsourcing-store-postgres/src/notificationListener.ts
+++ b/packages/eventsourcing-store-postgres/src/notificationListener.ts
@@ -1,5 +1,18 @@
+import { SqlError } from '@effect/sql';
import { PgClient } from '@effect/sql-pg';
-import { Effect, Layer, Stream, Ref, Queue, Schema, HashSet, pipe } from 'effect';
+import {
+ Deferred,
+ Effect,
+ Layer,
+ Match,
+ Stream,
+ Ref,
+ Queue,
+ Schedule,
+ Schema,
+ HashSet,
+ pipe,
+} from 'effect';
import {
EventStreamId,
EventStoreError,
@@ -25,6 +38,14 @@ export const makeChannelName = (streamId: EventStreamId): string => `eventstore_
*/
export const ALL_EVENTS_CHANNEL = 'eventstore_events_all';
+/**
+ * Prefix of the payloads a listener sends to its own channel to learn that LISTEN is active.
+ * Every listener on the channel receives them, so every listener drops them.
+ */
+const LISTEN_PROBE_PREFIX = 'eventstore_listen_probe:';
+
+const LISTEN_PROBE_INTERVAL = '10 millis';
+
/**
* Parse notification payload from PostgreSQL trigger JSON
*/
@@ -166,6 +187,73 @@ const processRawNotification =
)
);
+const handleUnlessProbe =
+ (
+ probe: string,
+ active: Deferred.Deferred,
+ handleNotification: (rawPayload: string) => Effect.Effect
+ ) =>
+ (rawPayload: string) =>
+ pipe(
+ rawPayload,
+ Match.value,
+ Match.when(probe, () => Deferred.succeed(active, undefined)),
+ Match.when(
+ (payload) => payload.startsWith(LISTEN_PROBE_PREFIX),
+ () => Effect.void
+ ),
+ Match.orElse(handleNotification),
+ Effect.asVoid
+ );
+
+const sendProbeUntilActive = (
+ client: PgClient.PgClient,
+ channelName: string,
+ probe: string,
+ active: Deferred.Deferred
+) =>
+ pipe(
+ // Not client.notify: @effect/sql-pg 0.53 sends `NOTIFY channel, $1`, which Postgres rejects.
+ client`SELECT pg_notify(${channelName}, ${probe})`,
+ Effect.repeat(Schedule.spaced(LISTEN_PROBE_INTERVAL)),
+ Effect.raceFirst(Deferred.await(active))
+ );
+
+const listenInBackground = (
+ client: PgClient.PgClient,
+ channelName: string,
+ probe: string,
+ active: Deferred.Deferred,
+ handleNotification: (rawPayload: string) => Effect.Effect
+) =>
+ pipe(
+ channelName,
+ client.listen,
+ Stream.runForEach(handleUnlessProbe(probe, active, handleNotification)),
+ Effect.tapError((error) => Deferred.fail(active, error)),
+ Effect.fork
+ );
+
+/**
+ * Starts listening on a channel in the background and waits until LISTEN is active,
+ * so that no notification sent after this returns can be missed.
+ */
+const listenUntilActive = (
+ client: PgClient.PgClient,
+ channelName: string,
+ handleNotification: (rawPayload: string) => Effect.Effect
+) =>
+ pipe(
+ Deferred.make(),
+ Effect.flatMap((active) => {
+ const probe = `${LISTEN_PROBE_PREFIX}${crypto.randomUUID()}`;
+ return Effect.andThen(
+ listenInBackground(client, channelName, probe, active, handleNotification),
+ sendProbeUntilActive(client, channelName, probe, active)
+ );
+ })
+ );
+
const startListeningOnChannel = (
client: PgClient.PgClient,
notificationQueue: Queue.Queue<{
@@ -175,13 +263,10 @@ const startListeningOnChannel = (
}>,
channelName: string
) =>
- pipe(
+ listenUntilActive(
+ client,
channelName,
- client.listen,
- Stream.tap(processRawNotification(notificationQueue, channelName, false)),
- Stream.runDrain,
- Effect.fork,
- Effect.asVoid
+ processRawNotification(notificationQueue, channelName, false)
);
const activateChannelAndStartListening = (
@@ -345,15 +430,7 @@ const startListeningOnAllEventsChannel = (
readonly payload: NotificationPayload;
readonly isAllEvents: boolean;
}>
-) =>
- pipe(
- ALL_EVENTS_CHANNEL,
- client.listen,
- Stream.tap(processAllEventsNotification(notificationQueue)),
- Stream.runDrain,
- Effect.fork,
- Effect.asVoid
- );
+) => listenUntilActive(client, ALL_EVENTS_CHANNEL, processAllEventsNotification(notificationQueue));
const activateAllEventsChannel = (
activeChannels: Ref.Ref>,
diff --git a/packages/eventsourcing-store/package.json b/packages/eventsourcing-store/package.json
index 6f58a79e..65b6c904 100644
--- a/packages/eventsourcing-store/package.json
+++ b/packages/eventsourcing-store/package.json
@@ -69,7 +69,7 @@
"devDependencies": {
"@types/node": "24.19.0",
"@codeforbreakfast/bun-test-effect": "workspace:*",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"typescript": "5.9.3",
"eslint": "9.39.5"
},
diff --git a/packages/eventsourcing-store/src/lib/streaming/connectionManager.ts b/packages/eventsourcing-store/src/lib/streaming/connectionManager.ts
index 1968fa24..21935810 100644
--- a/packages/eventsourcing-store/src/lib/streaming/connectionManager.ts
+++ b/packages/eventsourcing-store/src/lib/streaming/connectionManager.ts
@@ -76,7 +76,11 @@ export const ConnectionConfigLive = Layer.effect(
ConnectionConfigTag,
Effect.mapError(
Config.unwrap(ConnectionConfigSchema),
- (error) => new Error(`Failed to load connection configuration: ${JSON.stringify(error)}`)
+ (error) =>
+ new ConnectionError({
+ message: `Failed to load connection configuration: ${JSON.stringify(error)}`,
+ cause: error,
+ })
)
);
diff --git a/packages/eventsourcing-store/src/lib/testing/eventstore-test-suite.ts b/packages/eventsourcing-store/src/lib/testing/eventstore-test-suite.ts
index fd757399..96da5dfd 100644
--- a/packages/eventsourcing-store/src/lib/testing/eventstore-test-suite.ts
+++ b/packages/eventsourcing-store/src/lib/testing/eventstore-test-suite.ts
@@ -1,4 +1,15 @@
-import { Chunk, Duration, Effect, Layer, ParseResult, Schema, Stream, pipe, Ref } from 'effect';
+import {
+ Cause,
+ Chunk,
+ Duration,
+ Effect,
+ Layer,
+ ParseResult,
+ Schema,
+ Stream,
+ pipe,
+ Ref,
+} from 'effect';
import { beforeAll, beforeEach, describe, expect, it } from 'bun:test';
import { EventStreamId, EventStreamPosition, beginning } from '../streamTypes';
import { type EventStore, ConcurrencyConflictError } from '../eventstore';
@@ -625,7 +636,7 @@ export function runEventStoreTestSuite(
Effect.sleep,
Effect.andThen(
Effect.fail(
- new Error(
+ new Cause.TimeoutException(
`Subscription timed out. Received ${receivedEvents.length} events: ${JSON.stringify(receivedEvents)}`
)
)
diff --git a/packages/eventsourcing-testing-contracts/package.json b/packages/eventsourcing-testing-contracts/package.json
index 5dc4e034..dc3bfaa6 100644
--- a/packages/eventsourcing-testing-contracts/package.json
+++ b/packages/eventsourcing-testing-contracts/package.json
@@ -72,7 +72,7 @@
"devDependencies": {
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"typescript": "5.9.3",
"eslint": "9.39.5"
},
diff --git a/packages/eventsourcing-testing-contracts/src/lib/test-utilities.ts b/packages/eventsourcing-testing-contracts/src/lib/test-utilities.ts
index 1f386fb5..34402228 100644
--- a/packages/eventsourcing-testing-contracts/src/lib/test-utilities.ts
+++ b/packages/eventsourcing-testing-contracts/src/lib/test-utilities.ts
@@ -5,7 +5,7 @@
* and test data generators for transport testing scenarios.
*/
-import { Effect, Stream, pipe, Chunk, Ref, Duration, Scope } from 'effect';
+import { Cause, Effect, Stream, pipe, Chunk, Ref, Duration, Scope } from 'effect';
import { TransportError, makeTransportMessage } from '@codeforbreakfast/eventsourcing-transport';
import type {
TransportMessage,
@@ -238,7 +238,7 @@ const flipAndFilterError = (
Effect.filterOrFail(
Effect.flip(effect),
errorPredicate,
- () => new Error('Error did not match predicate')
+ () => new Cause.NoSuchElementException('Error did not match predicate')
) as Effect.Effect;
/**
@@ -286,7 +286,7 @@ const filterTakeAndDrain = (
),
timeoutMs
),
- () => new Error(`Timeout waiting for connection state: ${expectedState}`)
+ () => new Cause.TimeoutException(`Timeout waiting for connection state: ${expectedState}`)
);
/**
@@ -310,7 +310,7 @@ const takeCollectAndTimeout = (
Effect.map(Stream.runCollect(Stream.take(stream, count)), (chunk) => Array.from(chunk)),
timeoutMs
),
- () => new Error(`Timeout collecting ${count} messages`)
+ () => new Cause.TimeoutException(`Timeout collecting ${count} messages`)
);
/**
diff --git a/packages/eventsourcing-testing-contracts/src/lib/transport/client-server-contract-tests.ts b/packages/eventsourcing-testing-contracts/src/lib/transport/client-server-contract-tests.ts
index db3e82ff..0cff0750 100644
--- a/packages/eventsourcing-testing-contracts/src/lib/transport/client-server-contract-tests.ts
+++ b/packages/eventsourcing-testing-contracts/src/lib/transport/client-server-contract-tests.ts
@@ -9,7 +9,7 @@
*/
import { describe, test, expect, beforeEach, afterEach } from '@codeforbreakfast/bun-test-effect';
-import { Effect, Stream, Scope, pipe, Option, Exit } from 'effect';
+import { Cause, Effect, Stream, Scope, pipe, Option, Exit } from 'effect';
import type { TransportMessage, ConnectionState } from '@codeforbreakfast/eventsourcing-transport';
// =============================================================================
@@ -209,7 +209,9 @@ export const runClientServerContractTests: ClientServerTestRunner = (
if (Option.isSome(clientState)) {
return Effect.sync(() => expect(clientState.value).toBe('connected'));
} else {
- return Effect.fail(new Error('Expected client to reach connected state'));
+ return Effect.fail(
+ new Cause.NoSuchElementException('Expected client to reach connected state')
+ );
}
}
)
@@ -267,7 +269,11 @@ export const runClientServerContractTests: ClientServerTestRunner = (
Stream.runHead(Stream.take(server.connections, 1)),
(serverConnection) => {
if (!Option.isSome(serverConnection)) {
- return Effect.fail(new Error('Expected server connection to be available'));
+ return Effect.fail(
+ new Cause.NoSuchElementException(
+ 'Expected server connection to be available'
+ )
+ );
}
const connection = serverConnection.value;
return Effect.flatMap(connection.transport.subscribe(), (messageStream) =>
@@ -436,7 +442,9 @@ export const runClientServerContractTests: ClientServerTestRunner = (
const handleServerConnection =
(client: ClientTransport) => (serverConnection: Option.Option) => {
if (!Option.isSome(serverConnection)) {
- return Effect.fail(new Error('Expected server connection to be available'));
+ return Effect.fail(
+ new Cause.NoSuchElementException('Expected server connection to be available')
+ );
}
const connection = serverConnection.value;
return subscribeAndCommunicate(client, connection);
@@ -552,7 +560,9 @@ export const runClientServerContractTests: ClientServerTestRunner = (
if (Option.isSome(finalClientState)) {
return Effect.sync(() => expect(finalClientState.value).toBe('disconnected'));
} else {
- return Effect.fail(new Error('Expected final client state to be available'));
+ return Effect.fail(
+ new Cause.NoSuchElementException('Expected final client state to be available')
+ );
}
};
@@ -580,7 +590,9 @@ export const runClientServerContractTests: ClientServerTestRunner = (
(clientScope: Scope.CloseableScope) =>
(serverConnection: Option.Option) => {
if (!Option.isSome(serverConnection)) {
- return Effect.fail(new Error('Expected server connection to be available'));
+ return Effect.fail(
+ new Cause.NoSuchElementException('Expected server connection to be available')
+ );
}
return closeScopeAndVerify(clientScope, serverConnection.value);
};
@@ -646,7 +658,9 @@ export const runClientServerContractTests: ClientServerTestRunner = (
if (Option.isSome(disconnectedState)) {
return Effect.sync(() => expect(disconnectedState.value).toBe('disconnected'));
} else {
- return Effect.fail(new Error('Expected disconnected state to be available'));
+ return Effect.fail(
+ new Cause.NoSuchElementException('Expected disconnected state to be available')
+ );
}
};
@@ -778,7 +792,9 @@ export const runClientServerContractTests: ClientServerTestRunner = (
const handleServerConnection =
(client: ClientTransport) => (serverConnection: Option.Option) => {
if (!Option.isSome(serverConnection)) {
- return Effect.fail(new Error('Expected server connection to be available'));
+ return Effect.fail(
+ new Cause.NoSuchElementException('Expected server connection to be available')
+ );
}
const connection = serverConnection.value;
return subscribeAndPublish(connection, client);
diff --git a/packages/eventsourcing-testing-contracts/src/lib/transport/server-transport-contract-tests.ts b/packages/eventsourcing-testing-contracts/src/lib/transport/server-transport-contract-tests.ts
index 4779c92c..4371d9cf 100644
--- a/packages/eventsourcing-testing-contracts/src/lib/transport/server-transport-contract-tests.ts
+++ b/packages/eventsourcing-testing-contracts/src/lib/transport/server-transport-contract-tests.ts
@@ -9,7 +9,19 @@
*/
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
-import { Effect, Stream, Scope, pipe, Option, Exit, Fiber, Duration, Chunk, Ref } from 'effect';
+import {
+ Cause,
+ Effect,
+ Stream,
+ Scope,
+ pipe,
+ Option,
+ Exit,
+ Fiber,
+ Duration,
+ Chunk,
+ Ref,
+} from 'effect';
import type { TransportMessage, ConnectionState } from '@codeforbreakfast/eventsourcing-transport';
// =============================================================================
@@ -288,7 +300,9 @@ export const runServerTransportContractTests: ServerTestRunner = (
Effect.flatMap((serverConnection) => {
if (!Option.isSome(serverConnection)) {
return Effect.fail(
- new Error('Expected server connection to be available')
+ new Cause.NoSuchElementException(
+ 'Expected server connection to be available'
+ )
);
}
@@ -581,7 +595,9 @@ export const runServerTransportContractTests: ServerTestRunner = (
Effect.flatMap((serverConnection) => {
if (!Option.isSome(serverConnection)) {
return Effect.fail(
- new Error('Expected server connection to be available')
+ new Cause.NoSuchElementException(
+ 'Expected server connection to be available'
+ )
);
}
@@ -796,7 +812,9 @@ export const runServerTransportContractTests: ServerTestRunner = (
Effect.flatMap((serverConnection) => {
if (!Option.isSome(serverConnection)) {
return Effect.fail(
- new Error('Expected server connection to be available')
+ new Cause.NoSuchElementException(
+ 'Expected server connection to be available'
+ )
);
}
diff --git a/packages/eventsourcing-transport-inmemory/package.json b/packages/eventsourcing-transport-inmemory/package.json
index 63fa256e..01b9a30c 100644
--- a/packages/eventsourcing-transport-inmemory/package.json
+++ b/packages/eventsourcing-transport-inmemory/package.json
@@ -70,7 +70,7 @@
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
"@codeforbreakfast/bun-test-effect": "workspace:*",
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3"
},
diff --git a/packages/eventsourcing-transport-websocket/package.json b/packages/eventsourcing-transport-websocket/package.json
index a715bc2a..28b8043f 100644
--- a/packages/eventsourcing-transport-websocket/package.json
+++ b/packages/eventsourcing-transport-websocket/package.json
@@ -63,7 +63,7 @@
},
"dependencies": {
"@codeforbreakfast/eventsourcing-transport": "workspace:*",
- "@effect/platform": "0.93.6",
+ "@effect/platform": "0.97.2",
"type-fest": "5.10.0"
},
"devDependencies": {
@@ -71,7 +71,7 @@
"@codeforbreakfast/eventsourcing-testing-contracts": "workspace:*",
"@types/node": "24.19.0",
"bun-types": "1.4.2",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"eslint": "9.39.5",
"typescript": "5.9.3"
},
diff --git a/packages/eventsourcing-transport/package.json b/packages/eventsourcing-transport/package.json
index 021ad655..88bd6e0b 100644
--- a/packages/eventsourcing-transport/package.json
+++ b/packages/eventsourcing-transport/package.json
@@ -64,7 +64,7 @@
},
"devDependencies": {
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"typescript": "5.9.3",
"eslint": "9.39.5"
},
diff --git a/packages/eventsourcing-websocket/package.json b/packages/eventsourcing-websocket/package.json
index e95c86f6..e360097b 100644
--- a/packages/eventsourcing-websocket/package.json
+++ b/packages/eventsourcing-websocket/package.json
@@ -72,7 +72,7 @@
},
"devDependencies": {
"@types/node": "24.19.0",
- "effect": "3.19.9",
+ "effect": "3.22.2",
"typescript": "5.9.3",
"eslint": "9.39.5"
},
diff --git a/scripts/validate-markdown-examples.ts b/scripts/validate-markdown-examples.ts
index d5564b62..d96f0c1b 100644
--- a/scripts/validate-markdown-examples.ts
+++ b/scripts/validate-markdown-examples.ts
@@ -1,9 +1,13 @@
#!/usr/bin/env bun
-import { Effect, pipe, Array as EffectArray, Console, Option, Match } from 'effect';
+import { Data, Effect, pipe, Array as EffectArray, Console, Option, Match } from 'effect';
import { Path, Command, Terminal, FileSystem } from '@effect/platform';
import { BunContext, BunRuntime } from '@effect/platform-bun';
+class DocsValidationError extends Data.TaggedError('DocsValidationError')<{
+ readonly message: string;
+}> {}
+
interface CodeBlock {
readonly code: string;
readonly file: string;
@@ -263,7 +267,10 @@ const readDirectoryForProcessing = (currentDir: string) =>
pipe(
FileSystem.FileSystem,
Effect.andThen((fs) => fs.readDirectory(currentDir)),
- Effect.mapError((error) => new Error(`Failed to read directory ${currentDir}: ${error}`))
+ Effect.mapError(
+ (error) =>
+ new DocsValidationError({ message: `Failed to read directory ${currentDir}: ${error}` })
+ )
);
const processDirectory = (
@@ -309,7 +316,9 @@ const writeFileStringToPath = (content: string) => (path: string) =>
pipe(
FileSystem.FileSystem,
Effect.andThen((fs) => fs.writeFileString(path, content)),
- Effect.mapError((error) => new Error(`Failed to write tsconfig: ${error}`))
+ Effect.mapError(
+ (error) => new DocsValidationError({ message: `Failed to write tsconfig: ${error}` })
+ )
);
const writeTsConfig = (tempDir: string) =>
@@ -321,7 +330,9 @@ const writeBlockFileContent =
pipe(
FileSystem.FileSystem,
Effect.andThen((fs) => fs.writeFileString(filepath, content)),
- Effect.mapError((error) => new Error(`Failed to write ${filename}: ${error}`)),
+ Effect.mapError(
+ (error) => new DocsValidationError({ message: `Failed to write ${filename}: ${error}` })
+ ),
Effect.as({ filename, block, headerLines })
);
@@ -442,7 +453,9 @@ const cleanupTempDir = (tempDir: string) =>
pipe(
FileSystem.FileSystem,
Effect.andThen((fs) => fs.remove(tempDir, { recursive: true })),
- Effect.mapError(() => new Error(`Failed to cleanup temp dir ${tempDir}`)),
+ Effect.mapError(
+ () => new DocsValidationError({ message: `Failed to cleanup temp dir ${tempDir}` })
+ ),
Effect.orElseSucceed(() => undefined)
);
@@ -475,7 +488,7 @@ const cleanTempDirectory = (tempDir: string) =>
pipe(
FileSystem.FileSystem,
Effect.andThen((fs) => fs.remove(tempDir, { recursive: true })),
- Effect.mapError(() => new Error(`Failed to clean temp dir`)),
+ Effect.mapError(() => new DocsValidationError({ message: `Failed to clean temp dir` })),
Effect.orElseSucceed(() => undefined)
);
@@ -483,7 +496,9 @@ const createTempDirectory = (tempDir: string) =>
pipe(
FileSystem.FileSystem,
Effect.andThen((fs) => fs.makeDirectory(tempDir, { recursive: true })),
- Effect.mapError((error) => new Error(`Failed to create temp dir: ${error}`))
+ Effect.mapError(
+ (error) => new DocsValidationError({ message: `Failed to create temp dir: ${error}` })
+ )
);
const getRelativePath = (from: string, to: string) =>
@@ -496,7 +511,9 @@ const readFileAsString = (file: string) =>
pipe(
FileSystem.FileSystem,
Effect.andThen((fs) => fs.readFileString(file)),
- Effect.mapError((error) => new Error(`Failed to read ${file}: ${error}`))
+ Effect.mapError(
+ (error) => new DocsValidationError({ message: `Failed to read ${file}: ${error}` })
+ )
);
const readAndExtractCodeBlocks =
@@ -534,7 +551,7 @@ const displayErrorsAndFail = (errors: readonly ValidationError[]) => {
Effect.andThen(Console.log(' 1. Update the code examples to match current APIs')),
Effect.andThen(Console.log(' 2. Add missing imports or type annotations')),
Effect.andThen(Console.log(' 3. Verify examples compile with: bun run validate:docs\n')),
- Effect.andThen(Effect.fail(new Error('Validation failed')))
+ Effect.andThen(Effect.fail(new DocsValidationError({ message: 'Validation failed' })))
);
};