turbine-orm 0.92.0-next.8724ade → 0.92.0-next.b95bea3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -16
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -21,36 +21,36 @@ Six reasons, each with the mechanism that makes it true:
|
|
|
21
21
|
1. **One dependency.** `dependencies` is `{ "pg": "^8.15.0" }`. No engine binary, no WASM compiler, no adapter packages in lockstep. The optional engines (SQLite, MySQL, SQL Server, PowDB, all tiered Experimental in [STABILITY.md](STABILITY.md)) are peer dependencies or Node builtins you install only if you use them.
|
|
22
22
|
2. **Written from scratch.** Turbine is not a layer over Knex or a query-builder library. Query compilation is plain string building with an FNV-1a shape fingerprint into a bounded LRU of SQL templates, so there is no plan cache to size and no compiler running on your event loop.
|
|
23
23
|
3. **Nested relations in one statement.** A `with` clause compiles to correlated `json_agg` subqueries, so users with posts with comments is one round trip, typed end to end: `users[0].posts[0].comments[0].author.name` autocompletes with no annotation.
|
|
24
|
-
4. **Faster than Drizzle and Prisma.** In the 0.
|
|
24
|
+
4. **Faster than Drizzle and Prisma.** In the 0.92.0 run Drizzle took 1.5x as long as Turbine and Prisma 2.1x, by geometric mean, on a dedicated benchmark machine. The table is below; the losses are stated with the wins.
|
|
25
25
|
5. **Agents get a skill and typed tools, not a SQL prompt.** `npx turbine skill` installs a query-writing skill whose every claim is executed against a live database before release; `npx turbine mcp` exposes eleven read-only MCP tools, including a relation graph and a join-path finder that returns the `with` clause to write. Every tool runs inside `BEGIN READ ONLY`, and PII-tagged columns are redacted before rows reach a model.
|
|
26
26
|
6. **The dangerous operations ask first.** Destructive migration statements refuse to run without typed consent. `update`/`delete` with an empty `where` throws. Columns tagged `pii: true` are excluded from the emitted SQL's projections. `turbine doctor` reports missing FK indexes offline, no account, no telemetry.
|
|
27
27
|
|
|
28
28
|
## Benchmarks
|
|
29
29
|
|
|
30
|
-
Measured **2026-10-15 against turbine-orm 0.
|
|
30
|
+
Measured **2026-10-15 against turbine-orm 0.92.0**, versus **Drizzle 0.45.3** (relational queries) and **Prisma 7.10.0** (`@prisma/adapter-pg`, `relationJoins` on), with a hand-written `pg` control arm. Run by the repository's [`benchmarks.yml`](.github/workflows/benchmarks.yml) workflow on a dedicated machine: AMD Ryzen 9 7950X host, 4-vCPU 16 GB Firecracker VM, client and PostgreSQL 17 (Docker) on the same VM over a Unix socket. PostgreSQL 17.11, Node v24.18.0, `pg` 8.23.1 under every arm. Same schema, same data (1K users, 10K posts, 50K comments), same pool config. Each figure is the median over 200 rounds with arm order rotated per round, taken as the median of 3 full runs.
|
|
31
31
|
|
|
32
|
-
| Scenario | Turbine 0.
|
|
32
|
+
| Scenario | Turbine 0.92.0 | Drizzle 0.45.3 | Prisma 7.10.0 | raw pg |
|
|
33
33
|
|---|---|---|---|---|
|
|
34
|
-
| findMany, 100 users | **0.
|
|
35
|
-
| findMany, 50 users + posts
|
|
36
|
-
| findMany, 10 users + posts + comments | **0.
|
|
37
|
-
| findUnique by primary key | **0.090 ms** | 0.
|
|
38
|
-
| findUnique, user + posts + comments | **0.
|
|
39
|
-
| count, all users | **0.
|
|
40
|
-
| stream 50,000 comments, per row *(tie)* | 55.
|
|
41
|
-
| atomic increment | **0.
|
|
42
|
-
| pipeline, 5-query batch | **0.
|
|
43
|
-
| hot findUnique, rotating ids | **0.078 ms** | 0.
|
|
34
|
+
| findMany, 100 users | **0.208 ms** | 0.310 ms | 0.352 ms | 0.201 ms |
|
|
35
|
+
| findMany, 50 users + posts | **1.075 ms** | 1.485 ms | 2.955 ms | 0.854 ms |
|
|
36
|
+
| findMany, 10 users + posts + comments | **0.620 ms** | 0.884 ms | 2.270 ms | n/a |
|
|
37
|
+
| findUnique by primary key | **0.090 ms** | 0.149 ms | 0.179 ms | 0.075 ms |
|
|
38
|
+
| findUnique, user + posts + comments | **0.246 ms** | 0.460 ms | 0.599 ms | n/a |
|
|
39
|
+
| count, all users | **0.079 ms** | 0.114 ms | 0.169 ms | 0.078 ms |
|
|
40
|
+
| stream 50,000 comments, per row *(tie)* | 55.02 ms | 55.13 ms | 82.84 ms | 57.03 ms |
|
|
41
|
+
| atomic increment | **0.164 ms** | 0.182 ms | 0.259 ms | 0.135 ms |
|
|
42
|
+
| pipeline, 5-query batch | **0.343 ms** | 0.701 ms | 0.784 ms | 0.360 ms |
|
|
43
|
+
| hot findUnique, rotating ids | **0.078 ms** | 0.145 ms | 0.158 ms | 0.070 ms |
|
|
44
44
|
|
|
45
45
|
What that run says:
|
|
46
46
|
|
|
47
|
-
- **Across all ten scenarios Drizzle took 1.
|
|
48
|
-
- **Against hand-written `pg`, Turbine ran at 1.
|
|
47
|
+
- **Across all ten scenarios Drizzle took 1.49x as long as Turbine and Prisma 2.13x**, by geometric mean. Turbine won or tied every one against both (1 tie with Drizzle), and won or tied all 14 extra scenarios too.
|
|
48
|
+
- **Against hand-written `pg`, Turbine ran at 1.09x, Drizzle at 1.59x and Prisma at 2.13x** (geometric mean over the 8 scenarios with a control). Where a query returns nested rows, the control decodes them into objects with `Date` timestamps as the ORMs do. The nested read with posts is 1.26x the control, down from 1.86x in 0.91.0, because relation rows now decode in one pass.
|
|
49
49
|
- **Pipelining is the clearest win over a network**: five dashboard queries 25 ms from the database take 25 ms as one batch against 171 ms as a transaction.
|
|
50
50
|
- **Where Turbine loses:** deep `OFFSET` pagination unless `forceCustomPlan` is set, and per-row streaming against the Drizzle 1.0 release candidate (`findManyStreamBatches` ties it).
|
|
51
51
|
- Figures under 0.15 ms carry roughly one third uncertainty in their absolute values. Orderings held across runs; those margins should not be quoted.
|
|
52
52
|
|
|
53
|
-
Full method, the extra scenarios, streaming, latency and the losses: [`benchmarks/RESULTS-0.
|
|
53
|
+
Full method, the extra scenarios, streaming, latency and the losses: [`benchmarks/RESULTS-0.92.0.md`](https://github.com/zvndev/turbine-orm/blob/main/benchmarks/RESULTS-0.92.0.md), or the charts at [turbineorm.dev/benchmarks](https://turbineorm.dev/benchmarks).
|
|
54
54
|
|
|
55
55
|
Reproduce it on a fresh machine with `gh workflow run benchmarks.yml`, or locally with `benchmarks/run-round.sh` (see [`benchmarks/README.md`](benchmarks/README.md)).
|
|
56
56
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turbine-orm",
|
|
3
|
-
"version": "0.92.0-next.
|
|
3
|
+
"version": "0.92.0-next.b95bea3",
|
|
4
4
|
"description": "Postgres-native TypeScript ORM, runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. One dependency, no WASM engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"//exports": "Each subpath declares its types PER CONDITION. A single shared top-level \"types\" resolves to the ESM declarations for `require` too, which is TS1479 (\"is an ES module ... cannot be require()d\") for any CJS consumer on moduleResolution node16/nodenext. The require condition points at dist/cjs, which ships its own {\"type\":\"commonjs\"} package.json, so those declarations are CJS declarations. Gated in CI by publint + @arethetypeswrong/cli + a real .cts consumer typecheck (see the package-types job in ci.yml).",
|
|
@@ -165,6 +165,7 @@
|
|
|
165
165
|
"lint:fix": "biome check --write src/",
|
|
166
166
|
"format": "biome format --write src/",
|
|
167
167
|
"check:error-codes": "tsx scripts/check-error-codes.ts",
|
|
168
|
+
"gen:capabilities": "tsx scripts/gen-capability-table.ts",
|
|
168
169
|
"//check:error-prefix": "No message reaching a TurbineError may hand-write a `[turbine] ` prefix. formatErrorMessage prepends `[TURBINE_EXXX] ` unless the message already opens with that exact tag, and a hand-written `[turbine] ` never matches one, so the two stacked on roughly 355 of the package's throw sites. A prefix rule looks unnecessary because formatErrorMessage guarantees the [CODE] tag: true, and not the failure mode, which is the INTERACTION between the automatic tag and a manual one. Diagnostics that no code tag ever touches (console calls, stream writes, Studio HTTP bodies) keep the prefix and are named in the script's DIAGNOSTIC_SINKS.",
|
|
169
170
|
"check:error-prefix": "node scripts/check-error-prefix.mjs",
|
|
170
171
|
"check:changelog": "node scripts/check-changelog-headings.mjs",
|