turbine-orm 0.50.0 → 0.51.0

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.
Files changed (186) hide show
  1. package/README.md +66 -66
  2. package/dist/adapters/cockroachdb.d.ts +5 -5
  3. package/dist/adapters/cockroachdb.js +10 -10
  4. package/dist/adapters/index.d.ts +5 -5
  5. package/dist/adapters/index.js +7 -7
  6. package/dist/adapters/yugabytedb.d.ts +7 -7
  7. package/dist/adapters/yugabytedb.js +10 -10
  8. package/dist/cjs/adapters/cockroachdb.d.ts +5 -5
  9. package/dist/cjs/adapters/cockroachdb.js +10 -10
  10. package/dist/cjs/adapters/index.d.ts +5 -5
  11. package/dist/cjs/adapters/index.js +7 -7
  12. package/dist/cjs/adapters/yugabytedb.d.ts +7 -7
  13. package/dist/cjs/adapters/yugabytedb.js +10 -10
  14. package/dist/cjs/cli/config.d.ts +13 -2
  15. package/dist/cjs/cli/config.js +3 -2
  16. package/dist/cjs/cli/destructive.d.ts +1 -1
  17. package/dist/cjs/cli/destructive.js +1 -1
  18. package/dist/cjs/cli/index.d.ts +10 -10
  19. package/dist/cjs/cli/index.js +49 -45
  20. package/dist/cjs/cli/loader.d.ts +7 -7
  21. package/dist/cjs/cli/loader.js +9 -9
  22. package/dist/cjs/cli/mcp.js +4 -4
  23. package/dist/cjs/cli/migrate.d.ts +5 -5
  24. package/dist/cjs/cli/migrate.js +11 -11
  25. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  26. package/dist/cjs/cli/ui.d.ts +2 -2
  27. package/dist/cjs/cli/ui.js +2 -2
  28. package/dist/cjs/client.d.ts +49 -38
  29. package/dist/cjs/client.js +57 -56
  30. package/dist/cjs/dialect.d.ts +62 -18
  31. package/dist/cjs/dialect.js +40 -2
  32. package/dist/cjs/errors.d.ts +5 -5
  33. package/dist/cjs/errors.js +11 -11
  34. package/dist/cjs/generate.d.ts +6 -6
  35. package/dist/cjs/generate.js +31 -29
  36. package/dist/cjs/index-advisor.d.ts +5 -5
  37. package/dist/cjs/index-advisor.js +0 -0
  38. package/dist/cjs/index.d.ts +1 -1
  39. package/dist/cjs/index.js +7 -7
  40. package/dist/cjs/introspect.d.ts +35 -9
  41. package/dist/cjs/introspect.js +83 -32
  42. package/dist/cjs/mssql.d.ts +11 -11
  43. package/dist/cjs/mssql.js +64 -29
  44. package/dist/cjs/mysql.d.ts +8 -8
  45. package/dist/cjs/mysql.js +61 -23
  46. package/dist/cjs/nested-write.d.ts +21 -2
  47. package/dist/cjs/nested-write.js +51 -14
  48. package/dist/cjs/optional-peer-import.cjs +7 -7
  49. package/dist/cjs/optional-peer-import.d.cts +7 -7
  50. package/dist/cjs/pipeline-submittable.d.ts +2 -2
  51. package/dist/cjs/pipeline-submittable.js +6 -6
  52. package/dist/cjs/pipeline.d.ts +1 -1
  53. package/dist/cjs/pipeline.js +4 -4
  54. package/dist/cjs/powdb-introspect.d.ts +1 -1
  55. package/dist/cjs/powdb-introspect.js +1 -1
  56. package/dist/cjs/powdb.d.ts +28 -28
  57. package/dist/cjs/powdb.js +66 -66
  58. package/dist/cjs/powql.d.ts +27 -27
  59. package/dist/cjs/powql.js +73 -52
  60. package/dist/cjs/query/aggregates.d.ts +1 -1
  61. package/dist/cjs/query/aggregates.js +5 -5
  62. package/dist/cjs/query/batched-loader.d.ts +11 -11
  63. package/dist/cjs/query/batched-loader.js +24 -24
  64. package/dist/cjs/query/builder.d.ts +39 -21
  65. package/dist/cjs/query/builder.js +99 -57
  66. package/dist/cjs/query/compound-unique.d.ts +1 -1
  67. package/dist/cjs/query/compound-unique.js +0 -0
  68. package/dist/cjs/query/deferred.d.ts +12 -6
  69. package/dist/cjs/query/deferred.js +1 -1
  70. package/dist/cjs/query/filters.d.ts +31 -11
  71. package/dist/cjs/query/filters.js +67 -14
  72. package/dist/cjs/query/index.d.ts +1 -1
  73. package/dist/cjs/query/index.js +1 -1
  74. package/dist/cjs/query/relations.d.ts +9 -9
  75. package/dist/cjs/query/relations.js +164 -57
  76. package/dist/cjs/query/types.d.ts +86 -35
  77. package/dist/cjs/query/types.js +1 -1
  78. package/dist/cjs/query/utils.d.ts +27 -10
  79. package/dist/cjs/query/utils.js +86 -14
  80. package/dist/cjs/query/where.d.ts +47 -28
  81. package/dist/cjs/query/where.js +130 -31
  82. package/dist/cjs/query/writes.d.ts +24 -5
  83. package/dist/cjs/query/writes.js +102 -13
  84. package/dist/cjs/realtime.d.ts +7 -7
  85. package/dist/cjs/realtime.js +9 -9
  86. package/dist/cjs/schema-builder.d.ts +18 -7
  87. package/dist/cjs/schema-builder.js +17 -10
  88. package/dist/cjs/schema-metadata.d.ts +3 -3
  89. package/dist/cjs/schema-metadata.js +9 -9
  90. package/dist/cjs/schema-sql.d.ts +9 -9
  91. package/dist/cjs/schema-sql.js +20 -20
  92. package/dist/cjs/schema.d.ts +19 -9
  93. package/dist/cjs/schema.js +6 -6
  94. package/dist/cjs/serverless.d.ts +15 -15
  95. package/dist/cjs/serverless.js +16 -16
  96. package/dist/cjs/sqlite.d.ts +8 -8
  97. package/dist/cjs/sqlite.js +53 -22
  98. package/dist/cjs/typed-sql.d.ts +4 -4
  99. package/dist/cjs/typed-sql.js +5 -5
  100. package/dist/cli/config.d.ts +13 -2
  101. package/dist/cli/config.js +3 -2
  102. package/dist/cli/destructive.d.ts +1 -1
  103. package/dist/cli/destructive.js +1 -1
  104. package/dist/cli/index.d.ts +10 -10
  105. package/dist/cli/index.js +49 -45
  106. package/dist/cli/loader.d.ts +7 -7
  107. package/dist/cli/loader.js +9 -9
  108. package/dist/cli/mcp.js +4 -4
  109. package/dist/cli/migrate.d.ts +5 -5
  110. package/dist/cli/migrate.js +11 -11
  111. package/dist/cli/studio-ui.generated.js +1 -1
  112. package/dist/cli/ui.d.ts +2 -2
  113. package/dist/cli/ui.js +2 -2
  114. package/dist/client.d.ts +49 -38
  115. package/dist/client.js +57 -56
  116. package/dist/dialect.d.ts +62 -18
  117. package/dist/dialect.js +40 -2
  118. package/dist/errors.d.ts +5 -5
  119. package/dist/errors.js +11 -11
  120. package/dist/generate.d.ts +6 -6
  121. package/dist/generate.js +31 -29
  122. package/dist/index-advisor.d.ts +5 -5
  123. package/dist/index-advisor.js +0 -0
  124. package/dist/index.d.ts +1 -1
  125. package/dist/index.js +7 -7
  126. package/dist/introspect.d.ts +35 -9
  127. package/dist/introspect.js +82 -32
  128. package/dist/mssql.d.ts +11 -11
  129. package/dist/mssql.js +64 -29
  130. package/dist/mysql.d.ts +8 -8
  131. package/dist/mysql.js +61 -23
  132. package/dist/nested-write.d.ts +21 -2
  133. package/dist/nested-write.js +51 -14
  134. package/dist/optional-peer-import.cjs +7 -7
  135. package/dist/optional-peer-import.d.cts +7 -7
  136. package/dist/pipeline-submittable.d.ts +2 -2
  137. package/dist/pipeline-submittable.js +6 -6
  138. package/dist/pipeline.d.ts +1 -1
  139. package/dist/pipeline.js +4 -4
  140. package/dist/powdb-introspect.d.ts +1 -1
  141. package/dist/powdb-introspect.js +1 -1
  142. package/dist/powdb.d.ts +28 -28
  143. package/dist/powdb.js +66 -66
  144. package/dist/powql.d.ts +27 -27
  145. package/dist/powql.js +73 -52
  146. package/dist/query/aggregates.d.ts +1 -1
  147. package/dist/query/aggregates.js +5 -5
  148. package/dist/query/batched-loader.d.ts +11 -11
  149. package/dist/query/batched-loader.js +24 -24
  150. package/dist/query/builder.d.ts +39 -21
  151. package/dist/query/builder.js +100 -58
  152. package/dist/query/compound-unique.d.ts +1 -1
  153. package/dist/query/compound-unique.js +0 -0
  154. package/dist/query/deferred.d.ts +12 -6
  155. package/dist/query/deferred.js +1 -1
  156. package/dist/query/filters.d.ts +31 -11
  157. package/dist/query/filters.js +66 -13
  158. package/dist/query/index.d.ts +1 -1
  159. package/dist/query/index.js +1 -1
  160. package/dist/query/relations.d.ts +9 -9
  161. package/dist/query/relations.js +165 -58
  162. package/dist/query/types.d.ts +86 -35
  163. package/dist/query/types.js +1 -1
  164. package/dist/query/utils.d.ts +27 -10
  165. package/dist/query/utils.js +84 -14
  166. package/dist/query/where.d.ts +47 -28
  167. package/dist/query/where.js +129 -32
  168. package/dist/query/writes.d.ts +24 -5
  169. package/dist/query/writes.js +101 -13
  170. package/dist/realtime.d.ts +7 -7
  171. package/dist/realtime.js +9 -9
  172. package/dist/schema-builder.d.ts +18 -7
  173. package/dist/schema-builder.js +17 -10
  174. package/dist/schema-metadata.d.ts +3 -3
  175. package/dist/schema-metadata.js +9 -9
  176. package/dist/schema-sql.d.ts +9 -9
  177. package/dist/schema-sql.js +20 -20
  178. package/dist/schema.d.ts +19 -9
  179. package/dist/schema.js +6 -6
  180. package/dist/serverless.d.ts +15 -15
  181. package/dist/serverless.js +16 -16
  182. package/dist/sqlite.d.ts +8 -8
  183. package/dist/sqlite.js +53 -22
  184. package/dist/typed-sql.d.ts +4 -4
  185. package/dist/typed-sql.js +5 -5
  186. package/package.json +2 -2
package/README.md CHANGED
@@ -8,7 +8,7 @@ Most query layers are designed for the shape of a laptop database: empty, dispos
8
8
  npm install turbine-orm
9
9
  ```
10
10
 
11
- **Full docs: [turbineorm.dev](https://turbineorm.dev)** [Quick Start](https://turbineorm.dev/quickstart) · [API Reference](https://turbineorm.dev/queries) · [Relations](https://turbineorm.dev/relations) · [Transactions & Pipelines](https://turbineorm.dev/transactions) · [Serverless & Edge](https://turbineorm.dev/serverless) · [Typed Errors](https://turbineorm.dev/errors) · [Benchmarks](https://turbineorm.dev/benchmarks)
11
+ **Full docs: [turbineorm.dev](https://turbineorm.dev)**, [Quick Start](https://turbineorm.dev/quickstart) · [API Reference](https://turbineorm.dev/queries) · [Relations](https://turbineorm.dev/relations) · [Transactions & Pipelines](https://turbineorm.dev/transactions) · [Serverless & Edge](https://turbineorm.dev/serverless) · [Typed Errors](https://turbineorm.dev/errors) · [Benchmarks](https://turbineorm.dev/benchmarks)
12
12
 
13
13
  ## Why Turbine?
14
14
 
@@ -151,7 +151,7 @@ const user = await db.users.findUnique({
151
151
 
152
152
  ### Many-to-many relations
153
153
 
154
- Turbine auto-detects pure junction tables during `generate` a table whose primary key is exactly two single-column foreign keys and which carries no other columns (e.g. `posts_tags(post_id, tag_id)`). Both endpoints gain a many-to-many relation you can load like any other:
154
+ Turbine auto-detects pure junction tables during `generate`, a table whose primary key is exactly two single-column foreign keys and which carries no other columns (e.g. `posts_tags(post_id, tag_id)`). Both endpoints gain a many-to-many relation you can load like any other:
155
155
 
156
156
  ```typescript
157
157
  const posts = await db.posts.findMany({
@@ -165,7 +165,7 @@ const post = await db.posts.findFirst({
165
165
  });
166
166
  ```
167
167
 
168
- A junction table that carries extra columns (a "payload") is treated as a first-class entity, so it stays an ordinary `hasMany` — that's by design. For those, or for any junction you want to wire up by hand, declare the relation explicitly in your code-first schema:
168
+ A junction table that carries extra columns (a "payload") is treated as a first-class entity, so it stays an ordinary `hasMany`. That's by design. For those, or for any junction you want to wire up by hand, declare the relation explicitly in your code-first schema:
169
169
 
170
170
  ```typescript
171
171
  import { defineSchema } from 'turbine-orm';
@@ -205,7 +205,7 @@ The other nested operations (`create`, `connectOrCreate`, `update`, `upsert`, `d
205
205
 
206
206
  ### Self-relations
207
207
 
208
- A self-referencing foreign key (e.g. `categories.parent_id → categories.id`) introspects to a `belongsTo` *and* a `hasMany` on the same table, so parent and child queries just work including nested trees:
208
+ A self-referencing foreign key (e.g. `categories.parent_id → categories.id`) introspects to a `belongsTo` *and* a `hasMany` on the same table, so parent and child queries just work, including nested trees:
209
209
 
210
210
  ```typescript
211
211
  // A category with its parent and its children
@@ -267,7 +267,7 @@ const deleted = await db.users.delete({
267
267
  For race-free counter updates, pass an operator object instead of a literal. Turbine generates `col = col + $n` style SQL so concurrent updates are safe.
268
268
 
269
269
  ```typescript
270
- // Atomic increment no read-modify-write race
270
+ // Atomic increment, no read-modify-write race
271
271
  await db.posts.update({
272
272
  where: { id: 1 },
273
273
  data: { viewCount: { increment: 1 } },
@@ -325,7 +325,7 @@ const stats = await db.raw<{ day: Date; count: number }>`
325
325
 
326
326
  ### Typed raw SQL (`db.sql<T>`)
327
327
 
328
- `db.sql<T>` is the typed escape hatch: you supply the row shape and get a thenable query with `.one()` and `.scalar()` helpers. Every `${value}` is bound as a `$N` parameter never interpolated so injection isn't possible even with hostile input.
328
+ `db.sql<T>` is the typed escape hatch: you supply the row shape and get a thenable query with `.one()` and `.scalar()` helpers. Every `${value}` is bound as a `$N` parameter, never interpolated, so injection isn't possible even with hostile input.
329
329
 
330
330
  ```typescript
331
331
  // Awaiting the query returns T[]
@@ -360,7 +360,7 @@ const users = await db.users.findMany({
360
360
  ### Streaming large result sets
361
361
 
362
362
  ```typescript
363
- // Stream rows using PostgreSQL cursors constant memory, no matter how many rows
363
+ // Stream rows using PostgreSQL cursors, constant memory, no matter how many rows
364
364
  for await (const user of db.users.findManyStream({
365
365
  where: { orgId: 1 },
366
366
  batchSize: 500, // internal FETCH batch size (default: 1000)
@@ -371,7 +371,7 @@ for await (const user of db.users.findManyStream({
371
371
  }
372
372
  ```
373
373
 
374
- Uses `DECLARE CURSOR` under the hood rows are fetched in batches on a dedicated connection, parsed individually, and yielded via `AsyncGenerator`. Safe to `break` early; the cursor and connection are cleaned up automatically.
374
+ Uses `DECLARE CURSOR` under the hood, rows are fetched in batches on a dedicated connection, parsed individually, and yielded via `AsyncGenerator`. Safe to `break` early; the cursor and connection are cleaned up automatically.
375
375
 
376
376
  ### Query timeout
377
377
 
@@ -406,17 +406,17 @@ const db = turbine({
406
406
  // relation). Override per query on findMany/findFirst/findUnique.
407
407
  relationLoadStrategy: 'auto',
408
408
  // 'positional' (Postgres-only) drops repeated JSON keys from relation
409
- // subqueries ~39% fewer wire bytes on wide relations, byte-identical output.
409
+ // subqueries, ~39% fewer wire bytes on wide relations, byte-identical output.
410
410
  // Default 'object'.
411
411
  jsonEncoding: 'object',
412
- // Parse `timestamp` (without time zone) as UTC the Prisma/Rails/Django
413
- // convention so results don't shift with the server's local zone.
412
+ // Parse `timestamp` (without time zone) as UTC, the Prisma/Rails/Django
413
+ // convention, so results don't shift with the server's local zone.
414
414
  // Default true; set false for the legacy local-time interpretation.
415
415
  utcTimestamps: true,
416
416
  });
417
417
  ```
418
418
 
419
- Run `npx turbine doctor` to catch relations whose child-side FK lacks a covering index the correlated-subquery strategy probes the child once per parent row, so a missing FK index costs a full scan per parent.
419
+ Run `npx turbine doctor` to catch relations whose child-side FK lacks a covering index, the correlated-subquery strategy probes the child once per parent row, so a missing FK index costs a full scan per parent.
420
420
 
421
421
  ### Pool and statement configuration
422
422
 
@@ -467,7 +467,7 @@ Use `db.pipeline(...)` when the queries are independent and you want one round-t
467
467
 
468
468
  ### Middleware
469
469
 
470
- Middleware wraps every query. It runs **after SQL generation**, so it can observe what's about to execute (`params.model`, `params.action`, `params.args`), measure timing, and transform the result returned by `next()` but it cannot change the query itself.
470
+ Middleware wraps every query. It runs **after SQL generation**, so it can observe what's about to execute (`params.model`, `params.action`, `params.args`), measure timing, and transform the result returned by `next()`, but it cannot change the query itself.
471
471
 
472
472
  ```typescript
473
473
  // Query timing
@@ -478,7 +478,7 @@ db.$use(async (params, next) => {
478
478
  return result;
479
479
  });
480
480
 
481
- // Result transformation redact a field on the way out
481
+ // Result transformation, redact a field on the way out
482
482
  db.$use(async (params, next) => {
483
483
  const result = await next(params);
484
484
  if (params.model === 'users' && Array.isArray(result)) {
@@ -488,7 +488,7 @@ db.$use(async (params, next) => {
488
488
  });
489
489
  ```
490
490
 
491
- > **Warning:** `params.args` is a read-only snapshot mutating it does not change the executed SQL. The query is fully built and parameterized before middleware runs.
491
+ > **Warning:** `params.args` is a read-only snapshot, mutating it does not change the executed SQL. The query is fully built and parameterized before middleware runs.
492
492
 
493
493
  Middleware can't rewrite queries, so cross-cutting predicates like **soft deletes** and **multi-tenancy** belong to [global filters](https://turbineorm.dev/global-filters) instead. A global filter is a `WhereClause` that Turbine `AND`-merges into the compiled `WHERE` of every query on a table: reads, the relation subqueries that target it, and the predicate of `update` / `delete` / `upsert`. `create` and `createMany` are never filtered, since a new row has nothing to scope.
494
494
 
@@ -598,13 +598,13 @@ await db.$notify('order_created', JSON.stringify({ id: 1 }));
598
598
  await sub.unsubscribe();
599
599
  ```
600
600
 
601
- `$listen` holds a dedicated connection open for the lifetime of the subscription, so it requires a real persistent pool it is not available over serverless HTTP drivers. `$notify` is a single round-trip and works everywhere. Channel names are validated as plain identifiers; the payload is always bound as a parameter.
601
+ `$listen` holds a dedicated connection open for the lifetime of the subscription, so it requires a real persistent pool. It is not available over serverless HTTP drivers. `$notify` is a single round-trip and works everywhere. Channel names are validated as plain identifiers; the payload is always bound as a parameter.
602
602
 
603
603
  ## Vector search (pgvector)
604
604
 
605
605
  Query a `vector` column for nearest neighbors. Requires the [pgvector](https://github.com/pgvector/pgvector) extension and a `vector` column on your table.
606
606
 
607
- **KNN ranking** order by distance to a query vector and take the closest rows:
607
+ **KNN ranking**, order by distance to a query vector and take the closest rows:
608
608
 
609
609
  ```typescript
610
610
  const similar = await db.items.findMany({
@@ -614,7 +614,7 @@ const similar = await db.items.findMany({
614
614
  // queryVector is a number[]; nearest-first by default (direction: 'desc' to invert)
615
615
  ```
616
616
 
617
- **Distance filter** keep only rows within a distance threshold:
617
+ **Distance filter**, keep only rows within a distance threshold:
618
618
 
619
619
  ```typescript
620
620
  const close = await db.items.findMany({
@@ -622,7 +622,7 @@ const close = await db.items.findMany({
622
622
  });
623
623
  ```
624
624
 
625
- `metric` selects the pgvector operator: `'l2'` → `<->` (Euclidean), `'cosine'` → `<=>` (cosine distance), `'ip'` → `<#>` (negative inner product). Distance filters accept `lt`, `lte`, `gt`, and `gte`. The query vector is always bound as `$n::vector` never interpolated.
625
+ `metric` selects the pgvector operator: `'l2'` → `<->` (Euclidean), `'cosine'` → `<=>` (cosine distance), `'ip'` → `<#>` (negative inner product). Distance filters accept `lt`, `lte`, `gt`, and `gte`. The query vector is always bound as `$n::vector`, never interpolated.
626
626
 
627
627
  > **Note:** pg has no built-in parser for the `vector` type, so a fetched `vector` column comes back as a string literal like `'[1,2,3]'` unless you register a parser (e.g. via pgvector's own client helpers). Querying by distance works regardless.
628
628
 
@@ -663,7 +663,7 @@ Every operator supported by the `where` clause. Operators compose freely with `A
663
663
  | `endsWith` | Suffix match (`LIKE %v`) | `where: { email: { endsWith: '@acme.com' } }` |
664
664
  | `mode: 'insensitive'` | Switch any string operator to `ILIKE` | `where: { title: { contains: 'SQL', mode: 'insensitive' } }` |
665
665
 
666
- LIKE wildcards in user input are escaped automatically `%`, `_`, and `\` are treated as literals.
666
+ LIKE wildcards in user input are escaped automatically, `%`, `_`, and `\` are treated as literals.
667
667
 
668
668
  ### Relation filters
669
669
 
@@ -780,8 +780,8 @@ npx turbine migrate status
780
780
  ```
781
781
 
782
782
  **Destructive migrations require explicit confirmation.** If a pending migration (or a DOWN
783
- section being rolled back) contains data-destroying SQL `DROP TABLE`, `DROP COLUMN`,
784
- `TRUNCATE`, `DELETE FROM`, `UPDATE` without `WHERE`, `ALTER COLUMN … TYPE` Turbine refuses
783
+ section being rolled back) contains data-destroying SQL, `DROP TABLE`, `DROP COLUMN`,
784
+ `TRUNCATE`, `DELETE FROM`, `UPDATE` without `WHERE`, `ALTER COLUMN … TYPE`, Turbine refuses
785
785
  to run it and prints an itemized report. Interactively you must type `destroy my data` and
786
786
  then `yes`; in CI you must pass `--allow-destructive`. A refused batch applies nothing.
787
787
 
@@ -800,8 +800,8 @@ npx turbine studio --port 5173 --host 127.0.0.1 --no-open
800
800
  **Features**
801
801
 
802
802
  - **Query / Data / Schema tabs.** Compose queries visually, browse rows, and inspect tables and relations.
803
- - **ORM-native query composer.** The Query tab builds a real `findMany` drill into relations (`with`) to any depth, pick fields (`select`/`omit`), add filters (`where`), `orderBy`, and `limit` at every level with a live TypeScript preview of the exact call to copy into your codebase.
804
- - **Saved queries.** Named builder queries persisted to `.turbine/studio-queries.json` share them across runs without committing them.
803
+ - **ORM-native query composer.** The Query tab builds a real `findMany`, drill into relations (`with`) to any depth, pick fields (`select`/`omit`), add filters (`where`), `orderBy`, and `limit` at every level, with a live TypeScript preview of the exact call to copy into your codebase.
804
+ - **Saved queries.** Named builder queries persisted to `.turbine/studio-queries.json`, share them across runs without committing them.
805
805
  - **Cmd+K command palette.** Jump to any table, tab, or saved query in one keystroke.
806
806
  - **Full-text search across rows.** The Data tab supports substring search across every text column of the current table.
807
807
  - **PII redaction.** Columns tagged `pii: true` in the schema render as a redaction placeholder in every tab. `--show-pii` reveals them, with a loud startup warning. Tags are a code-first declaration (introspection never infers one), so Studio reads them from the generated metadata in your `out` directory; if it finds none it says so at startup rather than implying a protection it cannot apply.
@@ -817,22 +817,22 @@ npx turbine studio --port 5173 --host 127.0.0.1 --no-open
817
817
 
818
818
  ## Observability
819
819
 
820
- Built-in query metrics with zero new dependencies. `$observe` buffers per-query timings in memory and flushes **per-minute aggregates** count, avg, p50, p95, p99, and error count per `model:action` to a `_turbine_metrics` table in a **separate database**, over its own 1-connection pool so metrics writes never contend with your application pool.
820
+ Built-in query metrics with zero new dependencies. `$observe` buffers per-query timings in memory and flushes **per-minute aggregates**, count, avg, p50, p95, p99, and error count per `model:action`, to a `_turbine_metrics` table in a **separate database**, over its own 1-connection pool so metrics writes never contend with your application pool.
821
821
 
822
822
  ```typescript
823
823
  const handle = await db.$observe({
824
824
  connectionString: process.env.TURBINE_OBSERVE_URL!, // metrics DB (not your app DB)
825
825
  flushIntervalMs: 60_000, // default: 60s
826
- retentionDays: 30, // default: 30 older buckets are pruned on flush
826
+ retentionDays: 30, // default: 30, older buckets are pruned on flush
827
827
  });
828
828
 
829
829
  // Later, to flush remaining metrics and close the metrics pool
830
830
  await handle.stop();
831
831
  ```
832
832
 
833
- `$observe` creates the `_turbine_metrics` table if it doesn't exist. Flushes are fire-and-forget (`INSERT ... ON CONFLICT` additive merge) and never throw into your application. If the `TURBINE_OBSERVE_URL` environment variable is set, the client starts observing automatically on construction no code needed.
833
+ `$observe` creates the `_turbine_metrics` table if it doesn't exist. Flushes are fire-and-forget (`INSERT ... ON CONFLICT` additive merge) and never throw into your application. If the `TURBINE_OBSERVE_URL` environment variable is set, the client starts observing automatically on construction, no code needed.
834
834
 
835
- For your own instrumentation, subscribe to query events with `$on('query')` each event carries `sql`, `params`, `duration` (ms), `model`, `action`, `rows`, `timestamp`, and `error` (if the query failed):
835
+ For your own instrumentation, subscribe to query events with `$on('query')`, each event carries `sql`, `params`, `duration` (ms), `model`, `action`, `rows`, `timestamp`, and `error` (if the query failed):
836
836
 
837
837
  ```typescript
838
838
  db.$on('query', (e) => {
@@ -853,7 +853,7 @@ Same security model as Studio: loopback by default, non-loopback refused without
853
853
 
854
854
  ## Serverless / Edge
855
855
 
856
- Turbine's core is driver-agnostic: pass any pg-compatible pool to `TurbineConfig.pool` (or use the `turbineHttp()` factory) and Turbine runs on **Vercel Edge**, **Cloudflare Workers**, **Deno Deploy**, **Netlify Edge**, or any other environment where a direct TCP connection is unavailable. No new dependencies install whichever driver you already use.
856
+ Turbine's core is driver-agnostic: pass any pg-compatible pool to `TurbineConfig.pool` (or use the `turbineHttp()` factory) and Turbine runs on **Vercel Edge**, **Cloudflare Workers**, **Deno Deploy**, **Netlify Edge**, or any other environment where a direct TCP connection is unavailable. No new dependencies, install whichever driver you already use.
857
857
 
858
858
  ### Neon Serverless (HTTP / WebSocket)
859
859
 
@@ -888,7 +888,7 @@ const pool = createPool({ connectionString: process.env.POSTGRES_URL });
888
888
  const db = turbineHttp(pool, SCHEMA);
889
889
  ```
890
890
 
891
- ### Supabase (direct Postgres no HTTP proxy needed)
891
+ ### Supabase (direct Postgres, no HTTP proxy needed)
892
892
 
893
893
  ```ts
894
894
  import { TurbineClient } from 'turbine-orm';
@@ -921,27 +921,27 @@ export default {
921
921
 
922
922
  - **Streaming cursors** (`findManyStream`) require `DECLARE CURSOR`, which most HTTP drivers don't support. Use `findMany` with `limit` + pagination instead.
923
923
  - **LISTEN/NOTIFY** is not available over HTTP.
924
- - Transactions work but hold an HTTP connection for their duration keep them short.
924
+ - Transactions work but hold an HTTP connection for their duration, keep them short.
925
925
 
926
926
  When Turbine receives an external pool, `db.disconnect()` is a no-op: the caller owns the pool's lifecycle.
927
927
 
928
928
  ## Database engines
929
929
 
930
- Turbine is **Postgres-first** `import { TurbineClient } from 'turbine-orm'` targets PostgreSQL, and the safety bundle above is built around it. When you need another database, the same typed API runs on **SQLite**, **MySQL 8**, and **SQL Server** through subpath exports plus **PowDB**, a single-node embedded database with its own query language (PowQL). Multi-engine is *additive*, not a pivot: pick the engine that fits, keep the same `findMany` / `with` / `where` API.
930
+ Turbine is **Postgres-first**, `import { TurbineClient } from 'turbine-orm'` targets PostgreSQL, and the safety bundle above is built around it. When you need another database, the same typed API runs on **SQLite**, **MySQL 8**, and **SQL Server** through subpath exports, plus **PowDB**, a single-node embedded database with its own query language (PowQL). Multi-engine is *additive*, not a pivot: pick the engine that fits, keep the same `findMany` / `with` / `where` API.
931
931
 
932
- Two engines run **in-process** (no server): **SQLite** (always there is no SQLite wire protocol) and **PowDB**, which uniquely runs *both* in-process (embedded) *and* over a network client against the same data. The root install stays one dependency (`pg`). Each engine's driver is its own concern: SQLite needs nothing (Node's built-in `node:sqlite`), while MySQL, SQL Server, and PowDB use **optional peer dependencies** you install only if you use them.
932
+ Two engines run **in-process** (no server): **SQLite** (always. There is no SQLite wire protocol) and **PowDB**, which uniquely runs *both* in-process (embedded) *and* over a network client against the same data. The root install stays one dependency (`pg`). Each engine's driver is its own concern: SQLite needs nothing (Node's built-in `node:sqlite`), while MySQL, SQL Server, and PowDB use **optional peer dependencies** you install only if you use them.
933
933
 
934
934
  ```bash
935
- # SQLite zero extra deps (Node >= 22.5, built-in node:sqlite)
935
+ # SQLite, zero extra deps (Node >= 22.5, built-in node:sqlite)
936
936
  npm install turbine-orm
937
937
 
938
- # MySQL 8 optional peer
938
+ # MySQL 8, optional peer
939
939
  npm install turbine-orm mysql2
940
940
 
941
- # SQL Server 2016+ optional peer
941
+ # SQL Server 2016+, optional peer
942
942
  npm install turbine-orm mssql
943
943
 
944
- # PowDB optional peer; embedded (in-process) or networked transport
944
+ # PowDB, optional peer; embedded (in-process) or networked transport
945
945
  npm install turbine-orm @zvndev/powdb-embedded # in-process
946
946
  npm install turbine-orm @zvndev/powdb-client # networked
947
947
  ```
@@ -949,7 +949,7 @@ npm install turbine-orm @zvndev/powdb-client # networked
949
949
  Each engine ships a factory that returns the same `TurbineClient`:
950
950
 
951
951
  ```ts
952
- // SQLite synchronous; pass a file path, ':memory:', or an open DatabaseSync
952
+ // SQLite, synchronous; pass a file path, ':memory:', or an open DatabaseSync
953
953
  import { turbineSqlite } from 'turbine-orm/sqlite';
954
954
  import { SCHEMA } from './generated/turbine/metadata.js';
955
955
 
@@ -958,7 +958,7 @@ const users = await db.users.findMany({ with: { posts: true }, limit: 10 });
958
958
  ```
959
959
 
960
960
  ```ts
961
- // MySQL 8 async; connection string, mysql2 config, or an existing mysql2 pool
961
+ // MySQL 8, async; connection string, mysql2 config, or an existing mysql2 pool
962
962
  import { turbineMysql } from 'turbine-orm/mysql';
963
963
  import { SCHEMA } from './generated/turbine/metadata.js';
964
964
 
@@ -966,7 +966,7 @@ const db = await turbineMysql('mysql://user:pass@localhost:3306/app', SCHEMA);
966
966
  ```
967
967
 
968
968
  ```ts
969
- // SQL Server 2016+ async; connection string, mssql config, or an existing pool
969
+ // SQL Server 2016+, async; connection string, mssql config, or an existing pool
970
970
  import { turbineMssql } from 'turbine-orm/mssql';
971
971
  import { SCHEMA } from './generated/turbine/metadata.js';
972
972
 
@@ -974,7 +974,7 @@ const db = await turbineMssql('mssql://sa:Passw0rd!@localhost:1433/app', SCHEMA)
974
974
  ```
975
975
 
976
976
  ```ts
977
- // PowDB async; embedded (in-process) or networked. Schema is code-defined.
977
+ // PowDB, async; embedded (in-process) or networked. Schema is code-defined.
978
978
  import { turbinePowDB } from 'turbine-orm/powdb';
979
979
  import { schemaDefToMetadata } from 'turbine-orm';
980
980
  import { schema } from './schema.js'; // defineSchema({...})
@@ -1021,9 +1021,9 @@ Everything is honest about what ports and what doesn't. Features marked **PG-onl
1021
1021
 
1022
1022
  ✗ E017 = throws `UnsupportedFeatureError`. The full matrix (atomic updates, introspection, optimistic locking, per-cell mechanics) is on [turbineorm.dev/engines](https://turbineorm.dev/engines).
1023
1023
 
1024
- **Engine notes:** SQLite uses `RETURNING` (≥ 3.35) just like Postgres. MySQL has no `RETURNING`, so writes re-`SELECT` the affected row and **`createMany` returns `[]`** (the rows ARE inserted re-query if you need them). SQL Server returns rows via `OUTPUT`/`MERGE`; `DISTINCT ON` is Postgres-only. Only Postgres streams via a true cursor (constant memory); the other engines' `findManyStream` materializes the result then yields it in batches. Optimistic locking throws `OptimisticLockError` on all engines (on MySQL the conflict is detected from the version-checked UPDATE's affected-row count). The `turbine` CLI (`generate`, `migrate`) is currently PostgreSQL-only point the engine factories at a hand-written or programmatically introspected `SCHEMA`.
1024
+ **Engine notes:** SQLite uses `RETURNING` (≥ 3.35) just like Postgres. MySQL has no `RETURNING`, so writes re-`SELECT` the affected row and **`createMany` returns `[]`** (the rows ARE inserted, re-query if you need them). SQL Server returns rows via `OUTPUT`/`MERGE`; `DISTINCT ON` is Postgres-only. Only Postgres streams via a true cursor (constant memory); the other engines' `findManyStream` materializes the result then yields it in batches. Optimistic locking throws `OptimisticLockError` on all engines (on MySQL the conflict is detected from the version-checked UPDATE's affected-row count). The `turbine` CLI (`generate`, `migrate`) is currently PostgreSQL-only, point the engine factories at a hand-written or programmatically introspected `SCHEMA`.
1025
1025
 
1026
- **PowDB** speaks its own non-SQL query language (PowQL), so it sits outside the SQL matrix above. Writes use a trailing **`returning`** keyword (upsert reselects by PK). PKs are server-assigned `auto` ints **or** client UUIDs. Nested relations run as **one statement** on engine 0.18+ (PowQL nested projections per-parent order/limit, childless parents kept, the same single-query shape as Postgres `json_agg`); older engines and ineligible shapes (many-to-many via the junction) load client-side with identical output. Nested writes cover hasMany/hasOne/belongsTo, and route through the same shared nested-write engine as the SQL engines, so the many-to-many `connect` / `disconnect` / `set` junction writes added in 0.50 apply here too (they are ordinary reads and writes on the junction table); the remaining many-to-many operations throw `ValidationError`. Transactions are single-writer: concurrent `$transaction` calls queue FIFO (bounded by `transactionQueueTimeoutMs`); nested/re-entrant transactions throw typed errors (no savepoints). Schema is code-first via `defineSchema` `schemaDefToMetadata()` bridges it to any engine that needs runtime metadata, and a programmatic `describe`-based introspector exists since 0.34 (relations excluded). JSON documents are first-class on engine 0.12+: `JsonFilter` where-filters, JSON-path `orderBy`/`groupBy`, doc-field expression indexes, and a lossless native wire (0.13+) that keeps JSON `null`, missing fields, and the string `"null"` distinct. Embedded `syncMode: 'normal'` moves fsync off the commit path; the networked transport runs the same data over a socket. Cursor streaming and the Postgres-only trio (pgvector / LISTEN/NOTIFY / RLS session GUCs) throw `UnsupportedFeatureError`. Full details: **[turbineorm.dev/engines#powdb](https://turbineorm.dev/engines#powdb)**.
1026
+ **PowDB** speaks its own non-SQL query language (PowQL), so it sits outside the SQL matrix above. Writes use a trailing **`returning`** keyword (upsert reselects by PK). PKs are server-assigned `auto` ints **or** client UUIDs. Nested relations run as **one statement** on engine 0.18+ (PowQL nested projections, per-parent order/limit, childless parents kept, the same single-query shape as Postgres `json_agg`); older engines and ineligible shapes (many-to-many via the junction) load client-side with identical output. Nested writes cover hasMany/hasOne/belongsTo, and route through the same shared nested-write engine as the SQL engines, so the many-to-many `connect` / `disconnect` / `set` junction writes added in 0.50 apply here too (they are ordinary reads and writes on the junction table); the remaining many-to-many operations throw `ValidationError`. Transactions are single-writer: concurrent `$transaction` calls queue FIFO (bounded by `transactionQueueTimeoutMs`); nested/re-entrant transactions throw typed errors (no savepoints). Schema is code-first via `defineSchema`, `schemaDefToMetadata()` bridges it to any engine that needs runtime metadata, and a programmatic `describe`-based introspector exists since 0.34 (relations excluded). JSON documents are first-class on engine 0.12+: `JsonFilter` where-filters, JSON-path `orderBy`/`groupBy`, doc-field expression indexes, and a lossless native wire (0.13+) that keeps JSON `null`, missing fields, and the string `"null"` distinct. Embedded `syncMode: 'normal'` moves fsync off the commit path; the networked transport runs the same data over a socket. Cursor streaming and the Postgres-only trio (pgvector / LISTEN/NOTIFY / RLS session GUCs) throw `UnsupportedFeatureError`. Full details: **[turbineorm.dev/engines#powdb](https://turbineorm.dev/engines#powdb)**.
1027
1027
 
1028
1028
  Full setup, signatures, and the complete support matrix: **[turbineorm.dev/engines](https://turbineorm.dev/engines)**.
1029
1029
 
@@ -1050,7 +1050,7 @@ Priority order: CLI flags > environment variables (`DATABASE_URL`) > config file
1050
1050
 
1051
1051
  ## How It Works
1052
1052
 
1053
- Turbine resolves nested relations the same way Prisma 7 and Drizzle do: correlated subqueries with `json_agg` + `json_build_object`, evaluated by PostgreSQL in a single round-trip. No N+1, no client-side stitching, no separate queries per relation. The `with` clause is fully type-inferred end-to-end write `db.users.findMany({ with: { posts: { with: { comments: { with: { author: true } } } } } })` and `users[0].posts[0].comments[0].author.name` autocompletes with zero manual annotation.
1053
+ Turbine resolves nested relations the same way Prisma 7 and Drizzle do: correlated subqueries with `json_agg` + `json_build_object`, evaluated by PostgreSQL in a single round-trip. No N+1, no client-side stitching, no separate queries per relation. The `with` clause is fully type-inferred end-to-end, write `db.users.findMany({ with: { posts: { with: { comments: { with: { author: true } } } } } })` and `users[0].posts[0].comments[0].author.name` autocompletes with zero manual annotation.
1054
1054
 
1055
1055
  The query strategy is table stakes now. What isn't table stakes: the one-dependency, no-WASM footprint, the read-only Studio your DBA will approve, the error messages that never leak PII, and the SQL-first migrations with SHA-256 drift detection. See [Why Turbine?](#why-turbine) for the full breakdown.
1056
1056
 
@@ -1062,7 +1062,7 @@ Turbine maps Postgres types to TypeScript:
1062
1062
  |---|---|---|
1063
1063
  | `int2`, `int4`, `float4`, `float8` | `number` | Standard numeric types |
1064
1064
  | `int8` / `bigint` | `number` | Values > `Number.MAX_SAFE_INTEGER` (2^53 - 1) are returned as `string` at runtime to avoid precision loss. This affects < 0.01% of use cases (auto-increment IDs, counts, etc. are all safe). |
1065
- | `numeric`, `money` | `string` | Arbitrary precision kept as string to avoid JS float issues |
1065
+ | `numeric`, `money` | `string` | Arbitrary precision, kept as string to avoid JS float issues |
1066
1066
  | `text`, `varchar`, `uuid`, `citext` | `string` | |
1067
1067
  | `timestamptz`, `timestamp`, `date` | `Date` | `timestamp` (without time zone) is parsed as UTC by default (Prisma/Rails/Django convention), so the same row yields the same instant in every region. Opt out with `utcTimestamps: false`. |
1068
1068
  | `boolean` | `boolean` | |
@@ -1093,43 +1093,43 @@ Reading the table: no engine binary and no WASM, just one runtime dependency (`p
1093
1093
 
1094
1094
  *Competitor columns are re-verified against competitor releases on a fixed schedule. Last checked July 2026, against Prisma 7 and Drizzle 0.45. Features marked Preview or beta may change, and bundle sizes move release to release.*
1095
1095
 
1096
- **A note on Kysely.** Kysely's [`jsonArrayFrom` / `jsonObjectFrom`](https://kysely.dev/docs/recipes/relations) relations recipe builds nested results with the same correlated-subquery-plus-JSON approach Turbine uses good evidence the pattern is the right one. The gap is in what the driver can no longer see once rows are aggregated into JSON: nested fields lose their column types, so a `Date` inside a `jsonArrayFrom` result is typed `Date` but arrives as a **string** at runtime ([kysely-org/kysely#482](https://github.com/kysely-org/kysely/issues/482)), and the nesting isn't type-checked at depth. Turbine's `WithResult` inference types the whole tree, and `parseNestedRow` re-applies date coercion (and snake→camel mapping) to every nested row so `users[0].posts[0].createdAt` is an actual `Date`, at any depth, with no plugin to wire up.
1096
+ **A note on Kysely.** Kysely's [`jsonArrayFrom` / `jsonObjectFrom`](https://kysely.dev/docs/recipes/relations) relations recipe builds nested results with the same correlated-subquery-plus-JSON approach Turbine uses, good evidence the pattern is the right one. The gap is in what the driver can no longer see once rows are aggregated into JSON: nested fields lose their column types, so a `Date` inside a `jsonArrayFrom` result is typed `Date` but arrives as a **string** at runtime ([kysely-org/kysely#482](https://github.com/kysely-org/kysely/issues/482)), and the nesting isn't type-checked at depth. Turbine's `WithResult` inference types the whole tree, and `parseNestedRow` re-applies date coercion (and snake→camel mapping) to every nested row, so `users[0].posts[0].createdAt` is an actual `Date`, at any depth, with no plugin to wire up.
1097
1097
 
1098
1098
  ## Limitations
1099
1099
 
1100
1100
  Turbine is focused and opinionated. Here's what it doesn't do:
1101
1101
 
1102
- - **Postgres-first.** PostgreSQL is the default and primary target going deep on one database is what enables the safety bundle and the edge-runtime story. SQLite, MySQL 8, and SQL Server engines are available as additive subpath exports (see [Database engines](#database-engines)), but several flagship features (pgvector, LISTEN/NOTIFY, RLS `sessionContext`, full-text `search`, array-column filters, `groupBy({ distinctOn })`) are Postgres-only and throw `UnsupportedFeatureError` elsewhere.
1103
- - **Full-text search** is available via a `search` filter `where: { title: { search: 'hello & world', config: 'english' } }` compiles to a parameterized `to_tsvector(...) @@ to_tsquery(...)`. PostgreSQL only: the other engines throw `UnsupportedFeatureError` (`TURBINE_E017`) rather than degrade to a `LIKE`. For advanced ranking (`ts_rank`, weighted vectors) use `db.raw`.
1104
- - **Large nested result sets.** Nested results are materialized server-side in PostgreSQL memory. For relations with 10K+ rows, always use `limit` in your `with` clause or stream the parents with `findManyStream` and resolve children per-row.
1102
+ - **Postgres-first.** PostgreSQL is the default and primary target, going deep on one database is what enables the safety bundle and the edge-runtime story. SQLite, MySQL 8, and SQL Server engines are available as additive subpath exports (see [Database engines](#database-engines)), but several flagship features (pgvector, LISTEN/NOTIFY, RLS `sessionContext`, full-text `search`, array-column filters, `groupBy({ distinctOn })`) are Postgres-only and throw `UnsupportedFeatureError` elsewhere.
1103
+ - **Full-text search** is available via a `search` filter, `where: { title: { search: 'hello & world', config: 'english' } }` compiles to a parameterized `to_tsvector(...) @@ to_tsquery(...)`. PostgreSQL only: the other engines throw `UnsupportedFeatureError` (`TURBINE_E017`) rather than degrade to a `LIKE`. For advanced ranking (`ts_rank`, weighted vectors) use `db.raw`.
1104
+ - **Large nested result sets.** Nested results are materialized server-side in PostgreSQL memory. For relations with 10K+ rows, always use `limit` in your `with` clause, or stream the parents with `findManyStream` and resolve children per-row.
1105
1105
 
1106
1106
  ## Examples
1107
1107
 
1108
1108
  **Feature demos**
1109
1109
 
1110
- - **[Thread Machine](./examples/thread-machine/)** HN clone rendered from a single `findMany`. 4-level object graph (stories → comments → replies → author), every property autocompletes through the chain
1111
- - **[Streaming CSV](./examples/streaming-csv/)** Export 100K orders + line items to CSV with constant memory. PostgreSQL cursors, live heap meter, nested `with` inside `findManyStream`
1112
- - **[Clickstorm](./examples/clickstorm/)** Side-by-side atomic-increment vs read-modify-write load test. 10K concurrent clicks. The atomic path wins every time
1110
+ - **[Thread Machine](./examples/thread-machine/)**, HN clone rendered from a single `findMany`. 4-level object graph (stories → comments → replies → author), every property autocompletes through the chain
1111
+ - **[Streaming CSV](./examples/streaming-csv/)**, Export 100K orders + line items to CSV with constant memory. PostgreSQL cursors, live heap meter, nested `with` inside `findManyStream`
1112
+ - **[Clickstorm](./examples/clickstorm/)**, Side-by-side atomic-increment vs read-modify-write load test. 10K concurrent clicks. The atomic path wins every time
1113
1113
 
1114
1114
  **Runtime targets**
1115
1115
 
1116
- - **[Next.js](./examples/nextjs/)** Server-rendered app with nested relations, streaming, and live code demos
1117
- - **[Neon Edge](./examples/neon-edge/)** Vercel Edge route handler talking to Neon over HTTP via `@neondatabase/serverless`
1118
- - **[Vercel Postgres](./examples/vercel-postgres/)** Next.js app router route handler on `@vercel/postgres`
1119
- - **[Cloudflare Worker](./examples/cloudflare-worker/)** Worker `fetch` handler with `pg` over Cloudflare Hyperdrive
1120
- - **[Supabase](./examples/supabase/)** Standalone script over the standard `pg` driver against Supabase
1116
+ - **[Next.js](./examples/nextjs/)**, Server-rendered app with nested relations, streaming, and live code demos
1117
+ - **[Neon Edge](./examples/neon-edge/)**, Vercel Edge route handler talking to Neon over HTTP via `@neondatabase/serverless`
1118
+ - **[Vercel Postgres](./examples/vercel-postgres/)**, Next.js app router route handler on `@vercel/postgres`
1119
+ - **[Cloudflare Worker](./examples/cloudflare-worker/)**, Worker `fetch` handler with `pg` over Cloudflare Hyperdrive
1120
+ - **[Supabase](./examples/supabase/)**, Standalone script over the standard `pg` driver against Supabase
1121
1121
 
1122
1122
  ## Guides
1123
1123
 
1124
- - **[Quick Start](https://turbineorm.dev/quickstart)** zero-to-first-query in five minutes
1125
- - **[API Reference](https://turbineorm.dev/queries)** every `findMany` / `findUnique` / `create` / `update` / `delete` option, the full operator table, and `pipeline()` semantics
1126
- - **[Relations](https://turbineorm.dev/relations)** deep `with` clause, nested options, relation filters (`some` / `every` / `none`), payload-size guidance
1127
- - **[Transactions & Pipelines](https://turbineorm.dev/transactions)** isolation levels, nested SAVEPOINTs, retry loops for `DeadlockError` and `SerializationFailureError`
1128
- - **[Schema & Migrations](https://turbineorm.dev/schema)** `defineSchema()`, auto-diff migrations, checksum validation
1129
- - **[Serverless & Edge](https://turbineorm.dev/serverless)** Neon, Vercel Postgres, Cloudflare Hyperdrive, Supabase walkthroughs
1130
- - **[CLI](https://turbineorm.dev/cli)** every command, flag, and config option
1131
- - **[Typed Errors](https://turbineorm.dev/errors)** error code reference, `wrapPgError()` translation, retry patterns
1132
- - **[Migrating from Prisma](https://turbineorm.dev/migrate-from-prisma)** API mapping table, side-by-side `findMany`, and notes on the differences
1124
+ - **[Quick Start](https://turbineorm.dev/quickstart)**, zero-to-first-query in five minutes
1125
+ - **[API Reference](https://turbineorm.dev/queries)**, every `findMany` / `findUnique` / `create` / `update` / `delete` option, the full operator table, and `pipeline()` semantics
1126
+ - **[Relations](https://turbineorm.dev/relations)**, deep `with` clause, nested options, relation filters (`some` / `every` / `none`), payload-size guidance
1127
+ - **[Transactions & Pipelines](https://turbineorm.dev/transactions)**, isolation levels, nested SAVEPOINTs, retry loops for `DeadlockError` and `SerializationFailureError`
1128
+ - **[Schema & Migrations](https://turbineorm.dev/schema)**, `defineSchema()`, auto-diff migrations, checksum validation
1129
+ - **[Serverless & Edge](https://turbineorm.dev/serverless)**, Neon, Vercel Postgres, Cloudflare Hyperdrive, Supabase walkthroughs
1130
+ - **[CLI](https://turbineorm.dev/cli)**, every command, flag, and config option
1131
+ - **[Typed Errors](https://turbineorm.dev/errors)**, error code reference, `wrapPgError()` translation, retry patterns
1132
+ - **[Migrating from Prisma](https://turbineorm.dev/migrate-from-prisma)**, API mapping table, side-by-side `findMany`, and notes on the differences
1133
1133
 
1134
1134
  ## Requirements
1135
1135
 
@@ -1,20 +1,20 @@
1
1
  /**
2
- * turbine-orm CockroachDB adapter
2
+ * turbine-orm, CockroachDB adapter
3
3
  *
4
4
  * CockroachDB speaks the PostgreSQL wire protocol but has key differences:
5
5
  *
6
- * 1. **No advisory locks** `pg_try_advisory_lock()` is not supported.
6
+ * 1. **No advisory locks**, `pg_try_advisory_lock()` is not supported.
7
7
  * This adapter uses a `_turbine_lock` table with `SELECT FOR UPDATE NOWAIT`
8
8
  * as a concurrency mechanism for migrations.
9
9
  *
10
- * 2. **No `SET LOCAL statement_timeout`** CockroachDB uses
10
+ * 2. **No `SET LOCAL statement_timeout`**, CockroachDB uses
11
11
  * `SET transaction_timeout` (v23.1+) for per-transaction time limits.
12
12
  *
13
- * 3. **`pg_indexes` view** CockroachDB supports `pg_indexes` since v22.1
13
+ * 3. **`pg_indexes` view**, CockroachDB supports `pg_indexes` since v22.1
14
14
  * but the `indexdef` column may not match Postgres exactly. We use
15
15
  * `SHOW INDEXES` as a more reliable alternative.
16
16
  *
17
- * 4. **`pg_class.reltuples`** Not reliable in CockroachDB. We use
17
+ * 4. **`pg_class.reltuples`**, Not reliable in CockroachDB. We use
18
18
  * `crdb_internal.table_row_statistics` for row estimates.
19
19
  *
20
20
  * Known limitations with Turbine on CockroachDB:
@@ -1,20 +1,20 @@
1
1
  /**
2
- * turbine-orm CockroachDB adapter
2
+ * turbine-orm, CockroachDB adapter
3
3
  *
4
4
  * CockroachDB speaks the PostgreSQL wire protocol but has key differences:
5
5
  *
6
- * 1. **No advisory locks** `pg_try_advisory_lock()` is not supported.
6
+ * 1. **No advisory locks**, `pg_try_advisory_lock()` is not supported.
7
7
  * This adapter uses a `_turbine_lock` table with `SELECT FOR UPDATE NOWAIT`
8
8
  * as a concurrency mechanism for migrations.
9
9
  *
10
- * 2. **No `SET LOCAL statement_timeout`** CockroachDB uses
10
+ * 2. **No `SET LOCAL statement_timeout`**, CockroachDB uses
11
11
  * `SET transaction_timeout` (v23.1+) for per-transaction time limits.
12
12
  *
13
- * 3. **`pg_indexes` view** CockroachDB supports `pg_indexes` since v22.1
13
+ * 3. **`pg_indexes` view**, CockroachDB supports `pg_indexes` since v22.1
14
14
  * but the `indexdef` column may not match Postgres exactly. We use
15
15
  * `SHOW INDEXES` as a more reliable alternative.
16
16
  *
17
- * 4. **`pg_class.reltuples`** Not reliable in CockroachDB. We use
17
+ * 4. **`pg_class.reltuples`**, Not reliable in CockroachDB. We use
18
18
  * `crdb_internal.table_row_statistics` for row estimates.
19
19
  *
20
20
  * Known limitations with Turbine on CockroachDB:
@@ -69,7 +69,7 @@ const SQL_INDEXES_CRDB = `
69
69
  `;
70
70
  /**
71
71
  * Row estimates using crdb_internal. Falls back gracefully if the view
72
- * doesn't exist (permissions issue) returns 0 rows in that case.
72
+ * doesn't exist (permissions issue), returns 0 rows in that case.
73
73
  */
74
74
  const SQL_ROW_ESTIMATES_CRDB = `
75
75
  SELECT
@@ -82,7 +82,7 @@ const SQL_ROW_ESTIMATES_CRDB = `
82
82
  AND t.database_name = current_database()
83
83
  `;
84
84
  /**
85
- * Enum introspection CockroachDB supports pg_type/pg_enum since v20.2.
85
+ * Enum introspection, CockroachDB supports pg_type/pg_enum since v20.2.
86
86
  * The standard query works, but we include it explicitly so the override
87
87
  * mechanism is complete.
88
88
  */
@@ -107,13 +107,13 @@ export const cockroachdb = {
107
107
  await client.query(CREATE_LOCK_TABLE_SQL);
108
108
  // Insert the lock row if it doesn't exist (idempotent)
109
109
  await client.query(`INSERT INTO "${LOCK_TABLE}" (lock_id) VALUES ($1) ON CONFLICT (lock_id) DO NOTHING`, [lockId]);
110
- // Try to acquire the row lock with NOWAIT fails immediately if held
110
+ // Try to acquire the row lock with NOWAIT, fails immediately if held
111
111
  try {
112
112
  await client.query('BEGIN');
113
113
  await client.query(`SELECT lock_id FROM "${LOCK_TABLE}" WHERE lock_id = $1 FOR UPDATE NOWAIT`, [lockId]);
114
114
  // Update the acquired metadata
115
115
  await client.query(`UPDATE "${LOCK_TABLE}" SET acquired_at = now(), acquired_by = current_user WHERE lock_id = $1`, [lockId]);
116
- // Note: we leave the transaction OPEN the lock is held until
116
+ // Note: we leave the transaction OPEN, the lock is held until
117
117
  // releaseLock() commits or rolls back.
118
118
  return true;
119
119
  }
@@ -129,7 +129,7 @@ export const cockroachdb = {
129
129
  }
130
130
  return false;
131
131
  }
132
- // Any other error rollback and re-throw
132
+ // Any other error, rollback and re-throw
133
133
  try {
134
134
  await client.query('ROLLBACK');
135
135
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
- * turbine-orm Database adapter interface
2
+ * turbine-orm, Database adapter interface
3
3
  *
4
4
  * Adapters allow Turbine to work with PostgreSQL-compatible databases that
5
5
  * have subtle differences (e.g. CockroachDB, YugabyteDB). The default
6
- * behavior remains standard PostgreSQL adapters only override specific
6
+ * behavior remains standard PostgreSQL, adapters only override specific
7
7
  * operations where compatibility gaps exist.
8
8
  *
9
9
  * @example
@@ -57,9 +57,9 @@ export interface DatabaseAdapter {
57
57
  * Generate the SQL to set a statement timeout within a transaction.
58
58
  * PostgreSQL uses `SELECT set_config('statement_timeout', $1, true)`.
59
59
  * CockroachDB uses `SELECT set_config('transaction_timeout', $1, true)` (v23.1+).
60
- * (`SET LOCAL ... = $1` is a syntax error SET takes no bind params.)
60
+ * (`SET LOCAL ... = $1` is a syntax error, SET takes no bind params.)
61
61
  *
62
- * @param seconds timeout in seconds
62
+ * @param seconds, timeout in seconds
63
63
  * @returns an object with the parameterized SQL and its bound values
64
64
  */
65
65
  statementTimeout?(seconds: number): {
@@ -79,7 +79,7 @@ export interface DatabaseAdapter {
79
79
  export declare const postgresql: DatabaseAdapter;
80
80
  /**
81
81
  * Google AlloyDB adapter. AlloyDB is PostgreSQL with Google's columnar storage
82
- * engine. It is wire-protocol and catalog-compatible no adapter overrides
82
+ * engine. It is wire-protocol and catalog-compatible, no adapter overrides
83
83
  * are needed. All Turbine features (json_agg, advisory locks, introspection,
84
84
  * migrations) work identically to standard PostgreSQL.
85
85
  *
@@ -1,9 +1,9 @@
1
1
  /**
2
- * turbine-orm Database adapter interface
2
+ * turbine-orm, Database adapter interface
3
3
  *
4
4
  * Adapters allow Turbine to work with PostgreSQL-compatible databases that
5
5
  * have subtle differences (e.g. CockroachDB, YugabyteDB). The default
6
- * behavior remains standard PostgreSQL adapters only override specific
6
+ * behavior remains standard PostgreSQL, adapters only override specific
7
7
  * operations where compatibility gaps exist.
8
8
  *
9
9
  * @example
@@ -15,7 +15,7 @@
15
15
  * ```
16
16
  */
17
17
  // ---------------------------------------------------------------------------
18
- // Default PostgreSQL adapter (no-op standard behavior)
18
+ // Default PostgreSQL adapter (no-op, standard behavior)
19
19
  // ---------------------------------------------------------------------------
20
20
  /**
21
21
  * The default PostgreSQL adapter. Uses pg_try_advisory_lock, standard
@@ -31,18 +31,18 @@ export const postgresql = {
31
31
  await client.query(`SELECT pg_advisory_unlock($1)`, [lockId]);
32
32
  },
33
33
  statementTimeout(seconds) {
34
- // `SET LOCAL ... = $1` is a Postgres syntax error SET does not accept
34
+ // `SET LOCAL ... = $1` is a Postgres syntax error, SET does not accept
35
35
  // bind parameters. `set_config(name, value, is_local=true)` is the
36
36
  // parameterizable, transaction-local equivalent.
37
37
  return { sql: `SELECT set_config('statement_timeout', $1, true)`, params: [`${seconds}s`] };
38
38
  },
39
39
  };
40
40
  // ---------------------------------------------------------------------------
41
- // AlloyDB fully PostgreSQL-compatible, no adapter logic needed
41
+ // AlloyDB, fully PostgreSQL-compatible, no adapter logic needed
42
42
  // ---------------------------------------------------------------------------
43
43
  /**
44
44
  * Google AlloyDB adapter. AlloyDB is PostgreSQL with Google's columnar storage
45
- * engine. It is wire-protocol and catalog-compatible no adapter overrides
45
+ * engine. It is wire-protocol and catalog-compatible, no adapter overrides
46
46
  * are needed. All Turbine features (json_agg, advisory locks, introspection,
47
47
  * migrations) work identically to standard PostgreSQL.
48
48
  *
@@ -58,7 +58,7 @@ export const alloydb = {
58
58
  name: 'alloydb',
59
59
  };
60
60
  // ---------------------------------------------------------------------------
61
- // TimescaleDB PostgreSQL extension, fully compatible
61
+ // TimescaleDB, PostgreSQL extension, fully compatible
62
62
  // ---------------------------------------------------------------------------
63
63
  /**
64
64
  * TimescaleDB adapter. Timescale is a PostgreSQL extension that adds