diff --git a/.changeset/effect-4-0-6.md b/.changeset/effect-4-0-6.md new file mode 100644 index 0000000..0bedb06 --- /dev/null +++ b/.changeset/effect-4-0-6.md @@ -0,0 +1,8 @@ +--- +"@maple-dev/effect-orm": minor +--- + +Require Effect 4.0.6 (`effect ^4.0.6` peer). Effect now fails a transaction whose COMMIT failed +with a `SqlError` instead of dying; `transaction` still reports it as `TransactionCommitFailed`. +`ClickhouseHttpClient` names its driver `"clickhouse-http"` (`SqlClient.driver`), and Effect's own +statement span is now named after the database (`db.namespace`) rather than `sql.execute`. diff --git a/README.md b/README.md index 0e9299e..a4d6121 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,12 @@ Built on [Effect](https://effect.website) (peer dependency). Install the package with its Effect 4 peer: ```bash -bun add @maple-dev/effect-orm "effect@^4.0.0" +bun add @maple-dev/effect-orm "effect@^4.0.6" ``` See [Getting started](./docs/getting-started.md) for source builds and examples. -`effect` is a peer dependency. The recommended range `^4.0.0` allows +`effect` is a peer dependency. The recommended range `^4.0.6` allows newer Effect 4 releases without opting into Effect 5. Effect 3 and the Effect 4 prereleases are incompatible. diff --git a/bun.lock b/bun.lock index a8a9d5a..0693db5 100644 --- a/bun.lock +++ b/bun.lock @@ -8,15 +8,15 @@ "@changesets/cli": "3.0.3", "@clickhouse/client": "1.21.0", "@effect/language-service": "^0.87.3", - "@effect/platform-bun": "4.0.0", - "@effect/sql-clickhouse": "4.0.0", - "@effect/sql-d1": "4.0.0", - "@effect/sql-pglite": "4.0.0", - "@effect/sql-sqlite-node": "4.0.0", - "@effect/vitest": "4.0.0", + "@effect/platform-bun": "4.0.6", + "@effect/sql-clickhouse": "4.0.6", + "@effect/sql-d1": "4.0.6", + "@effect/sql-pglite": "4.0.6", + "@effect/sql-sqlite-node": "4.0.6", + "@effect/vitest": "4.0.6", "@electric-sql/pglite": "0.5.8", "@types/node": "^22.10.2", - "effect": "4.0.0", + "effect": "4.0.6", "expect-type": "^1.3.0", "fast-check": "^4.9.0", "miniflare": "4.20260730.0", @@ -25,12 +25,12 @@ "vitest": "^4.1.9", }, "peerDependencies": { - "effect": "^4.0.0", + "effect": "^4.0.6", }, }, }, "patchedDependencies": { - "@effect/vitest@4.0.0": "patches/@effect%2Fvitest@4.0.0.patch", + "@effect/vitest@4.0.6": "patches/@effect%2Fvitest@4.0.6.patch", }, "packages": { "@changesets/apply-release-plan": ["@changesets/apply-release-plan@8.1.1", "", { "dependencies": { "@changesets/config": "^4.0.1", "@changesets/format": "^0.1.2", "@changesets/git": "^4.0.1", "@changesets/should-skip-package": "^1.0.0", "@changesets/types": "^7.0.0", "import-meta-resolve": "^4.2.0", "jsonc-parser": "^3.3.1", "semver": "^7.8.1" } }, "sha512-nk2Hbq3M9NeHMdaIRBDvawKrQJzXB9sGyBUE5yWnIKtvdcNCPj75zBekJYr0gTT2ecJH3eFeTrc8eisPImcv0Q=="], @@ -87,21 +87,21 @@ "@effect/language-service": ["@effect/language-service@0.87.3", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-g1OmRBZF5chbW9QMNoirPo5Bb4zzU2HfP88tR7/VSZEUxHJ1/qVb8AQ0kUh2ggctLH1I715yCwP4gkw56PTjpQ=="], - "@effect/platform-bun": ["@effect/platform-bun@4.0.0", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0" }, "peerDependencies": { "effect": "^4.0.0" } }, "sha512-g9O41Sub/+JKfLjvBNEoqR1Pg6wtc2bbYzv7Kw8YsMihbt/ypCn7ZoydEXaHvTTUvbVrXOQbVFlfplyo84Clug=="], + "@effect/platform-bun": ["@effect/platform-bun@4.0.6", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.6" }, "peerDependencies": { "effect": "^4.0.6" } }, "sha512-RAsGcIVcqhV4aBiM07VnMeNWDyewp8WDwTdIUtiklUDBRtddDllYzPsTlsn+0gLdpdz2lGnMEOFzoNaNrcMfSA=="], "@effect/platform-node": ["@effect/platform-node@4.0.0", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0", "undici": "^8.11.2" }, "peerDependencies": { "effect": "^4.0.0", "redis": ">=5.0.0 <7.0.0" } }, "sha512-/L+4MUbl8FejPKLHLROYSOebvHnfixkP8KiblSD/DE5uH5xENZBHaux4nH6XMnRP+lr8ajpEJFS8sUrmmxFJeQ=="], - "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.22.0" }, "peerDependencies": { "effect": "^4.0.0" } }, "sha512-VBXHJU9UXVZPdQGGDRjGXpo+TpOzRwZuMrdKAtIxi1qwcRftmacZ1zTgIrzBa0Z2RA2KVkoOecQUsTlt/nJZhw=="], + "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.6", "", { "dependencies": { "@types/ws": "^8.18.2", "ws": "^8.22.0" }, "peerDependencies": { "effect": "^4.0.6" } }, "sha512-0PhyC4uG30xGtsajnxjkcBWfTjNRzUh4ZKl1Kvo/MNyCTb+W2O+fBzf7wCRCoqt+nLudiq4V7vX40Q8XIBuMaQ=="], - "@effect/sql-clickhouse": ["@effect/sql-clickhouse@4.0.0", "", { "dependencies": { "@clickhouse/client": "^1.23.1" }, "peerDependencies": { "@effect/platform-node": "^4.0.0", "effect": "^4.0.0" } }, "sha512-KthAxSJAW4VR9YyFsgOwwWX2ym6aYMFEJkzyxJExzcGFiUbFMK7hsbwzagiBKJAr/N1gGWQYiZP2fZgkgruiuA=="], + "@effect/sql-clickhouse": ["@effect/sql-clickhouse@4.0.6", "", { "dependencies": { "@clickhouse/client": "^1.24.1" }, "peerDependencies": { "@effect/platform-node": "^4.0.6", "effect": "^4.0.6" } }, "sha512-JQ1qNjEi9tRt4j9/qwisqqiuX4EV/wCWnw/ywEhTc95bEuO7r7g3wGa/Mog9gvPaHRoXAd7Su1AzduWNxWnD9A=="], - "@effect/sql-d1": ["@effect/sql-d1@4.0.0", "", { "dependencies": { "@cloudflare/workers-types": "^5.20260926.1" }, "peerDependencies": { "effect": "^4.0.0" } }, "sha512-pmkzncZCGD6CLyvc89keEBU85h+gHVCu/LKQnThd+ruYSUbG0R9jYA+lnk3R8Djp0wQW6SbsbmHQuhopIYyo3Q=="], + "@effect/sql-d1": ["@effect/sql-d1@4.0.6", "", { "dependencies": { "@cloudflare/workers-types": "^5.20261016.1" }, "peerDependencies": { "effect": "^4.0.6" } }, "sha512-W149Svo5pR+7eoKtTmMX3aFCJKQmvMdypKhMlFekKvy2UjY6h5Af8d+9YeBeOXkYtDQjxNs4l6iv7EqbF6XBbg=="], - "@effect/sql-pglite": ["@effect/sql-pglite@4.0.0", "", { "dependencies": { "@electric-sql/pglite": "^0.5.8" }, "peerDependencies": { "effect": "^4.0.0" } }, "sha512-BTlCPSipKMHZJEoJ94rR4f8Gx8cv8tLpZeHc3sajg2ko/2GY8s6CyycSra3Z6GLFUhcivRTYQ9Zc5LThz64qfQ=="], + "@effect/sql-pglite": ["@effect/sql-pglite@4.0.6", "", { "dependencies": { "@electric-sql/pglite": "^0.5.10" }, "peerDependencies": { "effect": "^4.0.6" } }, "sha512-pAKT7ZyY4Y6Nud3MlHXyFuLlH4pFUYpUeywtGWIcHOJtuA/8A8UvXijxrCkZ1dmLYThOqr7tWx5/mqN+lb8/EA=="], - "@effect/sql-sqlite-node": ["@effect/sql-sqlite-node@4.0.0", "", { "peerDependencies": { "effect": "^4.0.0" } }, "sha512-xyUSLY7v55iMgGxrjFrTo5ngixgQ3jLmOQ+AX8CCazO/caxuMM0oq8zb974FAcvQHRrpMEWU7Vnoo+rxct7PAQ=="], + "@effect/sql-sqlite-node": ["@effect/sql-sqlite-node@4.0.6", "", { "peerDependencies": { "effect": "^4.0.6" } }, "sha512-mYWr1Ppoz3/XrY+ZBnYfdvZvdcAYJTO6VcWoNetBX5n2c0VKHqg9S8WO6EpgtV5WiR8/yHHmu3qkfAGHJmalcA=="], - "@effect/vitest": ["@effect/vitest@4.0.0", "", { "peerDependencies": { "effect": "^4.0.0", "vitest": ">=5.0.0 <6.0.0" } }, "sha512-kGElCPFGtP+CgMCwV/HjJWqtE3zYo6I1+8MSA54l+EI24lPU9p2Efa+TBp1jfkg9j711KKzEqgSAy3VzTsb76Q=="], + "@effect/vitest": ["@effect/vitest@4.0.6", "", { "peerDependencies": { "effect": "^4.0.6", "vitest": ">=5.0.0 <6.0.0" } }, "sha512-oKNbtyUHK5+0o730CXwojULEu7f3+c235pYFD4U5T//o+hHrJbYbZU4bRkcv936tPr5IOnmujeyQiC/LuJJjHw=="], "@electric-sql/pglite": ["@electric-sql/pglite@0.5.8", "", {}, "sha512-n9tsbUOhwx2epK1V0ZG9Ar4SHWUju04dhmzZXiSBXwBoleOvIfals33NAaWgagQVAL4Rbvx/Ptsu3P+pA09f6Q=="], @@ -365,7 +365,7 @@ "dts-resolver": ["dts-resolver@3.0.0", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q=="], - "effect": ["effect@4.0.0", "", {}, "sha512-ooc1TG5t+FfzgYnFz2ff6BBKyZ7EwBRVXC7c4RhQUAD6/TZ2gTXXMeb4WX7a19ozQo4J73/QW+S00YAIresoMQ=="], + "effect": ["effect@4.0.6", "", {}, "sha512-Tc3mkzuLR6t6P9dhLt0D2GrxYtmU5td4XEJEiXCmILz53IRIfJyHsKZWjTA3xEjiwpNTtXBgI4iqYPxMztBDrw=="], "empathic": ["empathic@2.1.0", "", {}, "sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw=="], @@ -525,10 +525,16 @@ "yuku-parser": ["yuku-parser@0.8.7", "", { "dependencies": { "@yuku-toolchain/types": "^0.8.7", "yuku-ast": "^0.8.7" }, "optionalDependencies": { "@yuku-parser/binding-android-arm64": "0.8.7", "@yuku-parser/binding-darwin-arm64": "0.8.7", "@yuku-parser/binding-darwin-x64": "0.8.7", "@yuku-parser/binding-freebsd-x64": "0.8.7", "@yuku-parser/binding-linux-arm-gnu": "0.8.7", "@yuku-parser/binding-linux-arm-musl": "0.8.7", "@yuku-parser/binding-linux-arm64-gnu": "0.8.7", "@yuku-parser/binding-linux-arm64-musl": "0.8.7", "@yuku-parser/binding-linux-x64-gnu": "0.8.7", "@yuku-parser/binding-linux-x64-musl": "0.8.7", "@yuku-parser/binding-win32-arm64": "0.8.7", "@yuku-parser/binding-win32-x64": "0.8.7" } }, "sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ=="], + "@effect/platform-node/@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.22.0" }, "peerDependencies": { "effect": "^4.0.0" } }, "sha512-VBXHJU9UXVZPdQGGDRjGXpo+TpOzRwZuMrdKAtIxi1qwcRftmacZ1zTgIrzBa0Z2RA2KVkoOecQUsTlt/nJZhw=="], + "@effect/platform-node/undici": ["undici@8.11.2", "", {}, "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ=="], "@effect/platform-node-shared/ws": ["ws@8.22.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg=="], - "@effect/sql-clickhouse/@clickhouse/client": ["@clickhouse/client@1.23.1", "", {}, "sha512-vs3/Zc1dHvT171btW5nMoPsPCJ6QVJ5pp7obxzO5sjqwFx/jjz9wwCAqcFOdc2DhprugDBaVn+4dVY8hG3A9nw=="], + "@effect/sql-clickhouse/@clickhouse/client": ["@clickhouse/client@1.24.1", "", {}, "sha512-1kjKJ4uuD4J9JlRU4CpwEGfecstvtoGreBorhh7CxgB7r7PL54DCLD6gajKZhLCri84XlyD3CUxEm6rYkKC+dA=="], + + "@effect/sql-pglite/@electric-sql/pglite": ["@electric-sql/pglite@0.5.11", "", {}, "sha512-SWuqsTjA4DDWqgZ05PC1RJaq8e00p5KACxbqaikDFQ8dIGA0SLRzuPniky7IW+q/c5RDNMdEXwv7mFBWIx9Hxw=="], + + "@effect/platform-node/@effect/platform-node-shared/ws": ["ws@8.22.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg=="], } } diff --git a/design/transactions.md b/design/transactions.md index b8ffda5..854ce61 100644 --- a/design/transactions.md +++ b/design/transactions.md @@ -390,6 +390,10 @@ This is a translation of Effect 4.0.0 behavior. The right fix is upstream: COMMI that is part of phase 2; when it lands, steps 1 and 2 become a no-op. Decided: ship the translation and file upstream in parallel, not one after the other. +Update (effect 4.0.6): COMMIT failure now arrives as a `SqlError` in the error channel (still a +die when the cleanup after it fails too). Step 2 also accepts that failure when the body +succeeded. ROLLBACK still dies and still replaces the body's exit. + **Retry.** `Database.retryContention(effect, options?)` re-runs `effect` when it fails with `DatabaseError` or `TransactionCommitFailed` whose reason is `SerializationError` or `DeadlockError`, three times with exponential backoff from 50 ms by default, matching diff --git a/docs/database.md b/docs/database.md index 2e0cccd..9208a6b 100644 --- a/docs/database.md +++ b/docs/database.md @@ -5,13 +5,13 @@ connections: a `Database` is a small object over your client. ```sh -npm install @effect/sql-pg@4.0.0 # or @effect/sql-pglite, @effect/sql-clickhouse +npm install @effect/sql-pg@4.0.6 # or @effect/sql-pglite, @effect/sql-clickhouse ``` Transactions are Effect's own `SqlClient.withTransaction`, with four additions: - **Settings**: isolation level, access mode, deferrable. -- **Typed COMMIT and ROLLBACK failures**: Effect 4.0.0 turns them into defects. +- **Typed COMMIT and ROLLBACK failures**: Effect fails with a bare `SqlError` or dies. - **A guard** against statements that outlive their transaction. - **Contention retry** for SQLSTATE 40001 and 40P01. @@ -220,8 +220,8 @@ again. `read uncommitted` is not offered because Postgres runs it as `read commi | `TransactionOptionsRejected` | A setting the dialect does not support, or settings on a nested transaction | | `TransactionUnsupported` | The dialect has no transactions (ClickHouse) | -Effect 4.0.0 turns a failed COMMIT or ROLLBACK into a defect and, when ROLLBACK fails, drops -the body's own failure. `transaction` turns both back into the typed errors above. Defects +Effect fails with a bare `SqlError` on a failed COMMIT, dies on a failed ROLLBACK and, when +ROLLBACK fails, drops the body's own failure. `transaction` turns these into the typed errors above. Defects from `body` stay defects. `TransactionClosed` is a defect, not an error to handle. A fiber forked inside a transaction diff --git a/docs/getting-started.md b/docs/getting-started.md index 3b41e16..f9ab928 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -10,13 +10,13 @@ Effect application: `Effect.runPromise` lets you use it from ordinary async code CommonJS code can `require()` every entry point too, on Node 20.19+ or 22.12+, which load ES modules synchronously (`require(esm)`). That is the same way such code loads Effect 4, which is also ESM-only, so the library and its caller share one Effect. -These examples are checked against Effect `4.0.0` and the builder in this repository. -The package declares `effect ^4.0.0` as a peer dependency; Effect 3 is incompatible. +These examples are checked against Effect `4.0.6` and the builder in this repository. +The package declares `effect ^4.0.6` as a peer dependency; Effect 3 is incompatible. Install from npm with its Effect 4 peer dependency: ```sh -npm install @maple-dev/effect-orm "effect@^4.0.0" +npm install @maple-dev/effect-orm "effect@^4.0.6" ``` The recommended range accepts stable Effect 4 releases. It excludes Effect 3, the @@ -36,7 +36,7 @@ bun pm pack Install the resulting `.tgz` into your own project with its peer dependency: ```sh -npm install /absolute/path/to/the-generated-package.tgz "effect@^4.0.0" +npm install /absolute/path/to/the-generated-package.tgz "effect@^4.0.6" ``` Keep the Effect 4 range explicit when installing. Use an ESM project diff --git a/docs/running-queries.md b/docs/running-queries.md index 717fde4..2ea4335 100644 --- a/docs/running-queries.md +++ b/docs/running-queries.md @@ -8,10 +8,10 @@ column types already assume. ## A complete client example For Node.js or Bun, install Effect's ClickHouse client separately. Use the same Effect 4 -release as your `effect` dependency; this example is checked against `4.0.0`: +release as your `effect` dependency; this example is checked against `4.0.6`: ```sh -npm install effect@4.0.0 @effect/sql-clickhouse@4.0.0 +npm install effect@4.0.6 @effect/sql-clickhouse@4.0.6 ``` This example reads five rows from ClickHouse's built-in `system.numbers` table, declared diff --git a/package.json b/package.json index 5238372..68234a7 100644 --- a/package.json +++ b/package.json @@ -126,15 +126,15 @@ "@changesets/cli": "3.0.3", "@clickhouse/client": "1.21.0", "@effect/language-service": "^0.87.3", - "@effect/platform-bun": "4.0.0", - "@effect/sql-clickhouse": "4.0.0", - "@effect/sql-d1": "4.0.0", - "@effect/sql-pglite": "4.0.0", - "@effect/sql-sqlite-node": "4.0.0", - "@effect/vitest": "4.0.0", + "@effect/platform-bun": "4.0.6", + "@effect/sql-clickhouse": "4.0.6", + "@effect/sql-d1": "4.0.6", + "@effect/sql-pglite": "4.0.6", + "@effect/sql-sqlite-node": "4.0.6", + "@effect/vitest": "4.0.6", "@electric-sql/pglite": "0.5.8", "@types/node": "^22.10.2", - "effect": "4.0.0", + "effect": "4.0.6", "expect-type": "^1.3.0", "fast-check": "^4.9.0", "miniflare": "4.20260730.0", @@ -143,10 +143,10 @@ "vitest": "^4.1.9" }, "peerDependencies": { - "effect": "^4.0.0" + "effect": "^4.0.6" }, "packageManager": "bun@1.4.0", "patchedDependencies": { - "@effect/vitest@4.0.0": "patches/@effect%2Fvitest@4.0.0.patch" + "@effect/vitest@4.0.6": "patches/@effect%2Fvitest@4.0.6.patch" } } diff --git a/patches/@effect%2Fvitest@4.0.0.patch b/patches/@effect%2Fvitest@4.0.6.patch similarity index 63% rename from patches/@effect%2Fvitest@4.0.0.patch rename to patches/@effect%2Fvitest@4.0.6.patch index 01095fb..3d56231 100644 --- a/patches/@effect%2Fvitest@4.0.0.patch +++ b/patches/@effect%2Fvitest@4.0.6.patch @@ -1,8 +1,8 @@ diff --git a/dist/internal/internal.js b/dist/internal/internal.js -index 7138319..0fdd563 100644 +index ba15b512471edc067d9cb27ed7838ab42bba6c44..8e80dd30505e6414a4bd130cd17a72f87184c27f 100644 --- a/dist/internal/internal.js +++ b/dist/internal/internal.js -@@ -13,7 +13,8 @@ import * as Scope from "effect/Scope"; +@@ -14,7 +14,8 @@ import * as Scope from "effect/Scope"; import * as TestClock from "effect/testing/TestClock"; import * as TestConsole from "effect/testing/TestConsole"; import * as V from "vitest"; @@ -12,21 +12,21 @@ index 7138319..0fdd563 100644 const runPromise = /*#__PURE__*/Effect.fnUntraced(function* (effect, _ctx) { const exit = yield* Effect.exit(effect); if (Exit.isFailure(exit)) { -@@ -29,7 +30,8 @@ const runPromise = /*#__PURE__*/Effect.fnUntraced(function* (effect, _ctx) { +@@ -44,7 +45,8 @@ const settleOnAbort = (ctx, promise) => { /** @internal */ const runTest = ctx => effect => { const promise = runPromise(effect, ctx); -- if (ctx) { +- return ctx ? settleOnAbort(ctx, promise) : promise; + // Patched (maple): Bun's `bun test` context has no `signal`. -+ if (ctx?.signal) { - // Vitest stops awaiting the test promise once the signal aborts (timeout or - // cancellation), so only then add a hook that waits for the finalizers. - // Registering it unconditionally would change the teardown of every test. ++ return ctx?.signal ? settleOnAbort(ctx, promise) : promise; + }; + const TestEnv = /*#__PURE__*/Layer.mergeAll(TestConsole.layer, /*#__PURE__*/TestClock.layer()); + /** @internal */ diff --git a/src/internal/internal.ts b/src/internal/internal.ts -index b43385b..e3974e6 100644 +index 356c99ebf8d15603ce67bbfd05c05735bf43cd4b..e647e98bdbd8d88ff9ef96d36bfad657e3cb54db 100644 --- a/src/internal/internal.ts +++ b/src/internal/internal.ts -@@ -17,7 +17,8 @@ import * as TestConsole from "effect/testing/TestConsole" +@@ -18,7 +18,8 @@ import * as TestConsole from "effect/testing/TestConsole" import * as V from "vitest" import type * as Vitest from "../index.ts" @@ -36,13 +36,13 @@ index b43385b..e3974e6 100644 const runPromise: ( _: Effect.Effect, -@@ -39,7 +40,8 @@ const runPromise: ( +@@ -53,7 +54,8 @@ const settleOnAbort = (ctx: Vitest.TestContext, promise: Promise) => { /** @internal */ const runTest = (ctx?: Vitest.TestContext) => (effect: Effect.Effect) => { const promise = runPromise(effect, ctx) -- if (ctx) { +- return ctx ? settleOnAbort(ctx, promise) : promise + // Patched (maple): Bun's `bun test` context has no `signal`. -+ if (ctx?.signal) { - // Vitest stops awaiting the test promise once the signal aborts (timeout or - // cancellation), so only then add a hook that waits for the finalizers. - // Registering it unconditionally would change the teardown of every test. ++ return ctx?.signal ? settleOnAbort(ctx, promise) : promise + } + + /** @internal */ diff --git a/src/clickhouse-http/client.test.ts b/src/clickhouse-http/client.test.ts index b94f8e5..e7fc8b9 100644 --- a/src/clickhouse-http/client.test.ts +++ b/src/clickhouse-http/client.test.ts @@ -259,13 +259,14 @@ describe("ClickhouseHttpClient", () => { return Effect.gen(function* () { const sql = yield* SqlClient.SqlClient yield* sql.unsafe("SELECT 1") - const span = spans.find((s) => s.name === "sql.execute") + const span = spans.find((s) => s.attributes.has("effect.sql.method")) assert(span !== undefined) - return Object.fromEntries(span.attributes) + return { name: span.name, ...Object.fromEntries(span.attributes) } }).pipe(Effect.provide(live), Effect.withTracer(recording)) } return Effect.gen(function* () { expect(yield* run({})).toMatchObject({ + name: "analytics", "db.system.name": "clickhouse", "db.namespace": "analytics", "server.address": "clickhouse.example", diff --git a/src/clickhouse-http/client.ts b/src/clickhouse-http/client.ts index d771e33..525be27 100644 --- a/src/clickhouse-http/client.ts +++ b/src/clickhouse-http/client.ts @@ -272,6 +272,7 @@ export const make = ( const client = yield* Client.make({ acquirer: Effect.succeed(connection), + driver: "clickhouse-http", compiler, // The peer, for service maps: with the HTTP span off, nothing else names it. // The caller's attributes come last, so they can rename the peer. diff --git a/src/database/database.ts b/src/database/database.ts index aaf89d4..116e582 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -427,9 +427,10 @@ export const fromSqlClient = (sql: SqlClient.SqlClient, options: FromSqlClientOp /** * Turn what came out of `withTransaction` back into what the caller should see. - * Effect dies on a failed COMMIT or ROLLBACK, and a dying ROLLBACK replaces the - * body's own exit. Body defects were carried through as `BodyDefect`, so any - * other defect that is a `SqlError` came from transaction control. + * Effect fails with the `SqlError` of a failed COMMIT (or dies, when cleanup + * after it failed too), and dies on a failed ROLLBACK, which replaces the body's + * own exit. Body defects were carried through as `BodyDefect`, so any other + * defect that is a `SqlError` came from transaction control. */ const restoreCause = ( cause: Cause.Cause, @@ -441,19 +442,27 @@ const restoreCause = ( return Effect.failCause(failure.success.cause as Cause.Cause) } const defect = Cause.findDefect(cause) + // The body succeeded, so a `SqlError` here came from COMMIT or RELEASE. + if (bodyExit !== undefined && Exit.isSuccess(bodyExit)) { + const error = + failure._tag === "Success" && isSqlError(failure.success) + ? failure.success + : defect._tag === "Success" && isSqlError(defect.success) + ? defect.success + : undefined + if (error === undefined) return Effect.failCause(cause as Cause.Cause) + const sqlState = sqlStateOf(error) + return Effect.fail( + new TransactionCommitFailed({ + message: `${depth === 0 ? "COMMIT" : "releasing the savepoint"} failed: ${firstLine(error)}`, + reason: reasonOf(error), + ...(sqlState === undefined ? undefined : { sqlState }), + cause: error, + }), + ) + } if (defect._tag === "Success" && isSqlError(defect.success)) { const error = defect.success - if (bodyExit !== undefined && Exit.isSuccess(bodyExit)) { - const sqlState = sqlStateOf(error) - return Effect.fail( - new TransactionCommitFailed({ - message: `${depth === 0 ? "COMMIT" : "releasing the savepoint"} failed: ${firstLine(error)}`, - reason: reasonOf(error), - ...(sqlState === undefined ? undefined : { sqlState }), - cause: error, - }), - ) - } // The body never ran: BEGIN or SAVEPOINT died, not ROLLBACK. if (bodyExit === undefined) return Effect.fail(toDatabaseError(depth === 0 ? "BEGIN" : "SAVEPOINT")(error)) // An interrupted transaction whose ROLLBACK died stays interrupted. diff --git a/src/database/tracing.test.ts b/src/database/tracing.test.ts index 9bc2fa3..d878538 100644 --- a/src/database/tracing.test.ts +++ b/src/database/tracing.test.ts @@ -35,7 +35,7 @@ const traced = (effect: Effect.Effect) => }) const dbSpan = (recorded: ReadonlyArray) => { - const span = recorded.find((s) => s.attributes.has("db.system.name") && s.name !== "sql.execute") + const span = recorded.find((s) => s.attributes.has("db.system.name") && !s.attributes.has("effect.sql.method")) assert(span !== undefined, `no statement span among ${recorded.map((s) => s.name).join(", ")}`) return span } @@ -62,7 +62,7 @@ layer(Live)("Database statement spans", (it) => { "db.response.returned_rows": 0, }) // Effect's own span runs inside it, carrying the query text. - const execute = spans.find((s) => s.name === "sql.execute") + const execute = spans.find((s) => s.attributes.has("effect.sql.method")) assert(execute !== undefined) expect(Option.getOrUndefined(execute.parent)).toBe(span) }), diff --git a/src/database/tracing.ts b/src/database/tracing.ts index 97237ac..5372ae8 100644 --- a/src/database/tracing.ts +++ b/src/database/tracing.ts @@ -2,9 +2,9 @@ // database semantic conventions describe: // https://opentelemetry.io/docs/specs/semconv/database/database-spans/ // -// Effect's own `sql.execute` span still runs inside it with the query text, but -// it cannot carry these: it names the operation after the client method -// (`execute`), and only a compiled query knows its table. +// Effect's own span still runs inside it with the query text, but it cannot +// carry these: it is named after the database, not the operation, and only a +// compiled query knows its table. import { Context, Effect } from "effect" import type { CompiledQuery } from "../ch/compile" import type { Dialect } from "../ch/dialect"