turbine-orm 0.81.0 → 0.90.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.
- package/README.md +26 -25
- package/dist/checkout.d.ts +27 -2
- package/dist/checkout.js +75 -4
- package/dist/cjs/checkout.d.ts +27 -2
- package/dist/cjs/checkout.js +76 -3
- package/dist/cjs/cli/code-first-tags.d.ts +62 -0
- package/dist/cjs/cli/code-first-tags.js +124 -0
- package/dist/cjs/cli/compile-query.js +9 -0
- package/dist/cjs/cli/config.d.ts +35 -6
- package/dist/cjs/cli/config.js +17 -1
- package/dist/cjs/cli/destructive.d.ts +7 -4
- package/dist/cjs/cli/destructive.js +224 -20
- package/dist/cjs/cli/error-catalog.js +36 -0
- package/dist/cjs/cli/error-text.d.ts +15 -0
- package/dist/cjs/cli/error-text.js +40 -0
- package/dist/cjs/cli/index.d.ts +62 -4
- package/dist/cjs/cli/index.js +568 -108
- package/dist/cjs/cli/local-server-guard.d.ts +53 -0
- package/dist/cjs/cli/local-server-guard.js +119 -0
- package/dist/cjs/cli/local-server-output.d.ts +41 -0
- package/dist/cjs/cli/local-server-output.js +103 -0
- package/dist/cjs/cli/mcp.d.ts +8 -0
- package/dist/cjs/cli/mcp.js +121 -32
- package/dist/cjs/cli/migrate.d.ts +113 -5
- package/dist/cjs/cli/migrate.js +270 -21
- package/dist/cjs/cli/observe.d.ts +6 -1
- package/dist/cjs/cli/observe.js +85 -55
- package/dist/cjs/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cjs/cli/pii-predicate-guard.js +98 -37
- package/dist/cjs/cli/prisma-resolve.js +17 -13
- package/dist/cjs/cli/prisma-schema.js +12 -4
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.d.ts +14 -2
- package/dist/cjs/cli/studio.js +244 -69
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +558 -85
- package/dist/cjs/client.js +1270 -395
- package/dist/cjs/connection-guard.d.ts +13 -0
- package/dist/cjs/connection-guard.js +28 -0
- package/dist/cjs/connection-url.d.ts +51 -16
- package/dist/cjs/connection-url.js +67 -34
- package/dist/cjs/diagnostics.d.ts +81 -0
- package/dist/cjs/diagnostics.js +147 -0
- package/dist/cjs/dialect.d.ts +449 -20
- package/dist/cjs/dialect.js +117 -76
- package/dist/cjs/engine-config.d.ts +7 -6
- package/dist/cjs/errors.d.ts +126 -6
- package/dist/cjs/errors.js +256 -13
- package/dist/cjs/generate.d.ts +57 -7
- package/dist/cjs/generate.js +635 -279
- package/dist/cjs/global-filters-config.d.ts +59 -0
- package/dist/cjs/global-filters-config.js +367 -0
- package/dist/cjs/index.d.ts +9 -4
- package/dist/cjs/index.js +10 -2
- package/dist/cjs/introspect.d.ts +31 -6
- package/dist/cjs/introspect.js +108 -50
- package/dist/cjs/log-codes.d.ts +12 -0
- package/dist/cjs/log-codes.js +77 -0
- package/dist/cjs/logger.d.ts +87 -0
- package/dist/cjs/logger.js +98 -0
- package/dist/cjs/mssql.d.ts +8 -5
- package/dist/cjs/mssql.js +352 -65
- package/dist/cjs/mysql.d.ts +3 -5
- package/dist/cjs/mysql.js +420 -70
- package/dist/cjs/nested-write.d.ts +3 -0
- package/dist/cjs/nested-write.js +179 -114
- package/dist/cjs/pipeline-submittable.d.ts +12 -0
- package/dist/cjs/pipeline-submittable.js +118 -44
- package/dist/cjs/pipeline.js +21 -26
- package/dist/cjs/powdb-introspect.d.ts +5 -2
- package/dist/cjs/powdb-introspect.js +20 -15
- package/dist/cjs/powdb-shared.d.ts +36 -5
- package/dist/cjs/powdb-shared.js +67 -8
- package/dist/cjs/powdb.d.ts +4 -0
- package/dist/cjs/powdb.js +183 -51
- package/dist/cjs/powql.d.ts +394 -51
- package/dist/cjs/powql.js +1829 -284
- package/dist/cjs/prisma-compat.d.ts +23 -15
- package/dist/cjs/prisma-compat.js +507 -372
- package/dist/cjs/query/aggregates.d.ts +17 -1
- package/dist/cjs/query/aggregates.js +261 -29
- package/dist/cjs/query/batched-loader.d.ts +25 -6
- package/dist/cjs/query/batched-loader.js +103 -20
- package/dist/cjs/query/builder.d.ts +325 -184
- package/dist/cjs/query/builder.js +1452 -490
- package/dist/cjs/query/column-ref.d.ts +61 -0
- package/dist/cjs/query/column-ref.js +73 -0
- package/dist/cjs/query/compound-unique.d.ts +21 -0
- package/dist/cjs/query/compound-unique.js +98 -18
- package/dist/cjs/query/deferred.d.ts +53 -16
- package/dist/cjs/query/expr.d.ts +217 -0
- package/dist/cjs/query/expr.js +859 -0
- package/dist/cjs/query/filters.d.ts +155 -53
- package/dist/cjs/query/filters.js +613 -76
- package/dist/cjs/query/index.d.ts +2 -0
- package/dist/cjs/query/index.js +7 -1
- package/dist/cjs/query/json-value.d.ts +43 -0
- package/dist/cjs/query/json-value.js +59 -0
- package/dist/cjs/query/normalize-args.d.ts +87 -0
- package/dist/cjs/query/normalize-args.js +527 -0
- package/dist/cjs/query/option-surface.d.ts +16 -4
- package/dist/cjs/query/option-surface.js +57 -14
- package/dist/cjs/query/relation-names.d.ts +14 -0
- package/dist/cjs/query/relation-names.js +77 -18
- package/dist/cjs/query/relations.d.ts +30 -3
- package/dist/cjs/query/relations.js +219 -63
- package/dist/cjs/query/row-lock.d.ts +49 -0
- package/dist/cjs/query/row-lock.js +138 -0
- package/dist/cjs/query/sql-comment.d.ts +161 -0
- package/dist/cjs/query/sql-comment.js +381 -0
- package/dist/cjs/query/stream-idle.d.ts +132 -0
- package/dist/cjs/query/stream-idle.js +212 -0
- package/dist/cjs/query/table-client.d.ts +101 -0
- package/dist/cjs/query/table-client.js +74 -0
- package/dist/cjs/query/types.d.ts +677 -298
- package/dist/cjs/query/utils.d.ts +105 -6
- package/dist/cjs/query/utils.js +426 -19
- package/dist/cjs/query/warn-registry.d.ts +18 -0
- package/dist/cjs/query/warn-registry.js +18 -0
- package/dist/cjs/query/where-compile.d.ts +138 -14
- package/dist/cjs/query/where-compile.js +513 -29
- package/dist/cjs/query/where.d.ts +159 -59
- package/dist/cjs/query/where.js +716 -246
- package/dist/cjs/query/writes.d.ts +112 -50
- package/dist/cjs/query/writes.js +1498 -200
- package/dist/cjs/query-events.d.ts +4 -2
- package/dist/cjs/query-events.js +3 -1
- package/dist/cjs/realtime.d.ts +7 -3
- package/dist/cjs/realtime.js +13 -2
- package/dist/cjs/schema-builder.d.ts +55 -4
- package/dist/cjs/schema-builder.js +382 -18
- package/dist/cjs/schema-metadata.d.ts +21 -1
- package/dist/cjs/schema-metadata.js +29 -7
- package/dist/cjs/schema-sql.d.ts +75 -5
- package/dist/cjs/schema-sql.js +497 -45
- package/dist/cjs/schema.d.ts +26 -5
- package/dist/cjs/schema.js +110 -13
- package/dist/cjs/sql-placeholders.d.ts +56 -0
- package/dist/cjs/sql-placeholders.js +204 -0
- package/dist/cjs/sqlite.d.ts +75 -20
- package/dist/cjs/sqlite.js +593 -95
- package/dist/cjs/supplied-pool-types.d.ts +48 -0
- package/dist/cjs/supplied-pool-types.js +256 -0
- package/dist/cjs/transaction-control.d.ts +51 -0
- package/dist/cjs/transaction-control.js +316 -0
- package/dist/cjs/transaction-state.d.ts +342 -0
- package/dist/cjs/transaction-state.js +707 -0
- package/dist/cjs/typed-sql.d.ts +37 -3
- package/dist/cjs/typed-sql.js +102 -8
- package/dist/cli/code-first-tags.d.ts +62 -0
- package/dist/cli/code-first-tags.js +120 -0
- package/dist/cli/compile-query.js +9 -0
- package/dist/cli/config.d.ts +35 -6
- package/dist/cli/config.js +16 -1
- package/dist/cli/destructive.d.ts +7 -4
- package/dist/cli/destructive.js +224 -20
- package/dist/cli/error-catalog.js +36 -0
- package/dist/cli/error-text.d.ts +15 -0
- package/dist/cli/error-text.js +37 -0
- package/dist/cli/index.d.ts +62 -4
- package/dist/cli/index.js +569 -112
- package/dist/cli/local-server-guard.d.ts +53 -0
- package/dist/cli/local-server-guard.js +113 -0
- package/dist/cli/local-server-output.d.ts +41 -0
- package/dist/cli/local-server-output.js +98 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +121 -34
- package/dist/cli/migrate.d.ts +113 -5
- package/dist/cli/migrate.js +261 -21
- package/dist/cli/observe.d.ts +6 -1
- package/dist/cli/observe.js +84 -54
- package/dist/cli/pii-predicate-guard.d.ts +43 -23
- package/dist/cli/pii-predicate-guard.js +100 -35
- package/dist/cli/prisma-resolve.js +15 -11
- package/dist/cli/prisma-schema.js +12 -4
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +14 -2
- package/dist/cli/studio.js +246 -71
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +558 -85
- package/dist/client.js +1275 -400
- package/dist/connection-guard.d.ts +13 -0
- package/dist/connection-guard.js +27 -0
- package/dist/connection-url.d.ts +51 -16
- package/dist/connection-url.js +63 -34
- package/dist/diagnostics.d.ts +81 -0
- package/dist/diagnostics.js +144 -0
- package/dist/dialect.d.ts +449 -20
- package/dist/dialect.js +115 -43
- package/dist/engine-config.d.ts +7 -6
- package/dist/errors.d.ts +126 -6
- package/dist/errors.js +252 -12
- package/dist/generate.d.ts +57 -7
- package/dist/generate.js +635 -281
- package/dist/global-filters-config.d.ts +59 -0
- package/dist/global-filters-config.js +361 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.js +4 -2
- package/dist/introspect.d.ts +31 -6
- package/dist/introspect.js +106 -50
- package/dist/log-codes.d.ts +12 -0
- package/dist/log-codes.js +74 -0
- package/dist/logger.d.ts +87 -0
- package/dist/logger.js +93 -0
- package/dist/mssql.d.ts +8 -5
- package/dist/mssql.js +354 -67
- package/dist/mysql.d.ts +3 -5
- package/dist/mysql.js +423 -73
- package/dist/nested-write.d.ts +3 -0
- package/dist/nested-write.js +177 -112
- package/dist/pipeline-submittable.d.ts +12 -0
- package/dist/pipeline-submittable.js +117 -44
- package/dist/pipeline.js +21 -26
- package/dist/powdb-introspect.d.ts +5 -2
- package/dist/powdb-introspect.js +21 -16
- package/dist/powdb-shared.d.ts +36 -5
- package/dist/powdb-shared.js +65 -8
- package/dist/powdb.d.ts +4 -0
- package/dist/powdb.js +183 -51
- package/dist/powql.d.ts +394 -51
- package/dist/powql.js +1836 -291
- package/dist/prisma-compat.d.ts +23 -15
- package/dist/prisma-compat.js +508 -373
- package/dist/query/aggregates.d.ts +17 -1
- package/dist/query/aggregates.js +263 -32
- package/dist/query/batched-loader.d.ts +25 -6
- package/dist/query/batched-loader.js +101 -21
- package/dist/query/builder.d.ts +325 -184
- package/dist/query/builder.js +1458 -495
- package/dist/query/column-ref.d.ts +61 -0
- package/dist/query/column-ref.js +66 -0
- package/dist/query/compound-unique.d.ts +21 -0
- package/dist/query/compound-unique.js +97 -19
- package/dist/query/deferred.d.ts +53 -16
- package/dist/query/expr.d.ts +217 -0
- package/dist/query/expr.js +848 -0
- package/dist/query/filters.d.ts +155 -53
- package/dist/query/filters.js +597 -72
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +4 -0
- package/dist/query/json-value.d.ts +43 -0
- package/dist/query/json-value.js +54 -0
- package/dist/query/normalize-args.d.ts +87 -0
- package/dist/query/normalize-args.js +517 -0
- package/dist/query/option-surface.d.ts +16 -4
- package/dist/query/option-surface.js +56 -13
- package/dist/query/relation-names.d.ts +14 -0
- package/dist/query/relation-names.js +77 -19
- package/dist/query/relations.d.ts +30 -3
- package/dist/query/relations.js +220 -67
- package/dist/query/row-lock.d.ts +49 -0
- package/dist/query/row-lock.js +131 -0
- package/dist/query/sql-comment.d.ts +161 -0
- package/dist/query/sql-comment.js +374 -0
- package/dist/query/stream-idle.d.ts +132 -0
- package/dist/query/stream-idle.js +203 -0
- package/dist/query/table-client.d.ts +101 -0
- package/dist/query/table-client.js +70 -0
- package/dist/query/types.d.ts +677 -298
- package/dist/query/utils.d.ts +105 -6
- package/dist/query/utils.js +414 -18
- package/dist/query/warn-registry.d.ts +18 -0
- package/dist/query/warn-registry.js +18 -0
- package/dist/query/where-compile.d.ts +138 -14
- package/dist/query/where-compile.js +508 -31
- package/dist/query/where.d.ts +159 -59
- package/dist/query/where.js +711 -248
- package/dist/query/writes.d.ts +112 -50
- package/dist/query/writes.js +1496 -202
- package/dist/query-events.d.ts +4 -2
- package/dist/query-events.js +3 -1
- package/dist/realtime.d.ts +7 -3
- package/dist/realtime.js +13 -2
- package/dist/schema-builder.d.ts +55 -4
- package/dist/schema-builder.js +381 -18
- package/dist/schema-metadata.d.ts +21 -1
- package/dist/schema-metadata.js +28 -7
- package/dist/schema-sql.d.ts +75 -5
- package/dist/schema-sql.js +494 -44
- package/dist/schema.d.ts +26 -5
- package/dist/schema.js +109 -13
- package/dist/sql-placeholders.d.ts +56 -0
- package/dist/sql-placeholders.js +200 -0
- package/dist/sqlite.d.ts +75 -20
- package/dist/sqlite.js +597 -99
- package/dist/supplied-pool-types.d.ts +48 -0
- package/dist/supplied-pool-types.js +247 -0
- package/dist/transaction-control.d.ts +51 -0
- package/dist/transaction-control.js +311 -0
- package/dist/transaction-state.d.ts +342 -0
- package/dist/transaction-state.js +696 -0
- package/dist/typed-sql.d.ts +37 -3
- package/dist/typed-sql.js +100 -8
- package/package.json +7 -4
- package/skills/turbine-orm/SKILL.md +56 -6
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ await db.$transaction(async (tx) => {
|
|
|
138
138
|
});
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
-
Nested `$transaction` calls become SAVEPOINTs. Isolation levels, timeouts, and `sessionContext` (transaction-local GUCs for Postgres RLS) are options. `tx` deliberately exposes a smaller surface than `db`; reaching for a client-only member is a compile error, not a production surprise. Reference: [turbineorm.dev/transactions](https://turbineorm.dev/transactions).
|
|
141
|
+
Nested `$transaction` calls become SAVEPOINTs. Isolation levels, timeouts, and `sessionContext` (transaction-local GUCs for Postgres RLS) are options. A transaction PostgreSQL rolled back (a statement failed and the callback caught it) rejects instead of reporting a commit, and a `tx` used after its callback settles throws rather than running outside the transaction. `tx` deliberately exposes a smaller surface than `db`; reaching for a client-only member is a compile error, not a production surprise. Reference: [turbineorm.dev/transactions](https://turbineorm.dev/transactions).
|
|
142
142
|
|
|
143
143
|
### Pipelining, at the protocol level
|
|
144
144
|
|
|
@@ -191,7 +191,7 @@ const db = turbine({
|
|
|
191
191
|
});
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
A global filter is a `WhereClause` that is AND-merged into every query on a table: reads, relation subqueries targeting it, and the predicates of `update` / `delete` / `upsert`. Values are parameterized. Details: [turbineorm.dev/global-filters](https://turbineorm.dev/global-filters).
|
|
194
|
+
A global filter is a `WhereClause` that is AND-merged into every query on a table: reads, relation subqueries targeting it, and the predicates of `update` / `delete` / `upsert`. Values are parameterized. Keys may be table names or client accessors and are checked when the client is built, and a function filter that returns `undefined` throws instead of running unfiltered. Details: [turbineorm.dev/global-filters](https://turbineorm.dev/global-filters).
|
|
195
195
|
|
|
196
196
|
### The UNSAFE symbol
|
|
197
197
|
|
|
@@ -217,7 +217,7 @@ try {
|
|
|
217
217
|
}
|
|
218
218
|
```
|
|
219
219
|
|
|
220
|
-
Every error Turbine raises extends `TurbineError` with a stable code (`TURBINE_E001` through `
|
|
220
|
+
Every error Turbine raises extends `TurbineError` with a stable code (`TURBINE_E001` through `E020`) and a `docsUrl`; a driver error whose SQLSTATE Turbine does not map stays the driver's own error, carrying that SQLSTATE on `.code`. Error messages carry keys, never values: a `NotFoundError` says `where: { id, email }` without printing the email, so errors are safe to forward to a tracker without a scrubbing rule. Retryable failures (`DeadlockError`, `SerializationFailureError` and its subclass `LockNotAvailableError`) expose `isRetryable: true` as a typed const. `err.code` is the check that survives a mixed ESM/CJS module graph, where two copies of the package hold two copies of every class; since 0.78 `instanceof` works across copies too, through a cross-copy brand on every error. Full table: [turbineorm.dev/errors](https://turbineorm.dev/errors).
|
|
221
221
|
|
|
222
222
|
## Built for agents
|
|
223
223
|
|
|
@@ -254,7 +254,7 @@ The rest of the agent story is structural: query args are fully typed, so a wron
|
|
|
254
254
|
|
|
255
255
|
**Destructive migrations need consent.** `migrate up`, `migrate down`, and `push` scan for `DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, unqualified `DELETE` / `UPDATE`, and `ALTER COLUMN ... TYPE`, print an itemized report, and refuse. Interactively you type `destroy my data`, then `yes`; in CI you pass `--allow-destructive`. A refused batch applies nothing. Migrations are SQL files, checksummed with SHA-256, applied under `pg_try_advisory_lock()`.
|
|
256
256
|
|
|
257
|
-
**PII stays in the database.** Tag a column `pii: true` in the schema and it is excluded from every default projection at the SQL level: `RETURNING "id", "name"` instead of `RETURNING *`. It is also refused as a `groupBy` key and a `_min` / `_max` target. Reading it back takes `includePii: UNSAFE`, per query. A schema with no tagged column emits byte-identical SQL.
|
|
257
|
+
**PII stays in the database.** Tag a column `pii: true` in the schema and it is excluded from every default projection at the SQL level: `RETURNING "id", "name"` instead of `RETURNING *`. It is also refused as a `groupBy` key and a `_min` / `_max` target. Reading it back takes `includePii: UNSAFE`, per query, and naming it in `select` without that throws. A schema with no tagged column emits byte-identical SQL.
|
|
258
258
|
|
|
259
259
|
**Studio is read-only by default.** `npx turbine studio` binds loopback, authenticates with a per-process token, runs every read inside `BEGIN READ ONLY`, and has no raw-SQL surface: queries are composed in a builder validated identifier by identifier. Without `--write`, the write endpoints do not exist in the router. PII cells are redacted server-side. Try it with no database: `npx turbine-orm@latest studio --demo` boots a seeded in-memory sample. Docs: [turbineorm.dev/studio](https://turbineorm.dev/studio).
|
|
260
260
|
|
|
@@ -272,7 +272,7 @@ Going deep on one database means the parts other ORMs push to raw SQL are typed
|
|
|
272
272
|
|
|
273
273
|
## Serverless and edge
|
|
274
274
|
|
|
275
|
-
The core is driver-agnostic: hand any pg-compatible pool to `turbineHttp()` and Turbine runs on Vercel Edge, Cloudflare Workers, Deno Deploy, or anywhere else without TCP. The main entry's import graph is held under **
|
|
275
|
+
The core is driver-agnostic: hand any pg-compatible pool to `turbineHttp()` and Turbine runs on Vercel Edge, Cloudflare Workers, Deno Deploy, or anywhere else without TCP. The main entry's import graph is held under **147 kB brotli** (edge entry under **116 kB**) with `pg` external, enforced by `size-limit` in CI at those exact numbers; run `npm run size` for the current figure.
|
|
276
276
|
|
|
277
277
|
```typescript
|
|
278
278
|
import { Pool } from '@neondatabase/serverless';
|
|
@@ -329,7 +329,7 @@ const schema = defineSchema({
|
|
|
329
329
|
|
|
330
330
|
const db = turbineSqlite(':memory:', schemaDefToMetadata(schema));
|
|
331
331
|
for (const stmt of schemaToSQL(schema, { dialect: sqliteDialect })) {
|
|
332
|
-
await db.
|
|
332
|
+
await db.rawUnsafe(stmt);
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
const users = await db
|
|
@@ -355,24 +355,24 @@ It is also built to be extended rather than wrapped. All SQL generation routes t
|
|
|
355
355
|
|
|
356
356
|
## Comparison
|
|
357
357
|
|
|
358
|
-
| | **Turbine** | **Prisma** | **Drizzle** | **Kysely** |
|
|
359
|
-
|
|
360
|
-
| **Engine / runtime** | No engine binary (`pg` only) | Client + TS/WASM query compiler | No engine | No engine |
|
|
361
|
-
| **Runtime deps** | 1 (`pg`) | `@prisma/client` + required driver adapter | 0 | 0 |
|
|
362
|
-
| **Main bundle (brotli)** | under
|
|
363
|
-
| **Studio** | Read-only by default | Full CRUD, cloud-hosted | Full CRUD; [Gateway](https://gateway.drizzle.team/) self-hosted, free | None |
|
|
364
|
-
| **Error PII safety** | Keys only by default | Values in messages | Raw pg errors | Raw pg errors |
|
|
365
|
-
| **Migrations** | SQL-first, SHA-256 checksums | DSL-generated, shadow DB | SQL or Drizzle Kit | None |
|
|
366
|
-
| **Edge runtime** | One import swap, under
|
|
367
|
-
| **Pipeline batching** | Parse/Bind/Execute protocol | Sequential in txn | Sequential | Manual |
|
|
368
|
-
| **Typed errors** | `isRetryable` discriminant | Error codes only | None | None |
|
|
369
|
-
| **Nested relations** | 1 query, deep type inference | 1 query per relation by default; single-query `relationJoins` is Preview | 1 query, `relations()` re-declaration | Manual (`jsonArrayFrom`) |
|
|
370
|
-
| **Index advice** | `turbine doctor`, offline, `--fix` | Optimize retired March 2026 (cloud Query Insights) | None | None |
|
|
371
|
-
| **MCP server for agents** | 11 read-only tools, PII-redacted | Official MCP server | `drizzle-kit mcp` | None |
|
|
372
|
-
| **Vector search** | Built-in `distance` / KNN | Preview / raw | Extension API | Manual |
|
|
373
|
-
| **LISTEN/NOTIFY** | `$listen` / `$notify` | None | None | None |
|
|
374
|
-
|
|
375
|
-
Competitor columns last checked 2026-08-22, against Prisma 7 and Drizzle 0.45 (the current `latest`; 1.0 is at rc.4). Cells that make a claim about someone else's pricing or licensing carry a link to that vendor's own page, because those are the cells that go stale silently: this table said Drizzle's Gateway was paid, which was true of the old Drizzle Studio subscription and is not true of Gateway, and nothing in the repo would have caught it. Features marked Preview may change; bundle sizes move release to release. The longer version of this argument, including what is not a reason to switch: [turbineorm.dev/why-turbine](https://turbineorm.dev/why-turbine).
|
|
358
|
+
| | **Turbine** | **Prisma** | **Drizzle** | **Kysely** | **MikroORM** |
|
|
359
|
+
|---|---|---|---|---|---|
|
|
360
|
+
| **Engine / runtime** | No engine binary (`pg` only) | Client + TS/WASM query compiler | No engine | No engine | Kysely as query runner; SQL built by MikroORM (v7) |
|
|
361
|
+
| **Runtime deps** | 1 (`pg`) | `@prisma/client` + required driver adapter | 0 | 0 | `@mikro-orm/core` (0 deps) + a driver package |
|
|
362
|
+
| **Main bundle (brotli)** | under 147 kB import graph (CI-enforced), `pg` external | ~1.6 MB client (TS/WASM compiler) | ~7 KB core | small | |
|
|
363
|
+
| **Studio** | Read-only by default | Full CRUD, cloud-hosted | Full CRUD; [Gateway](https://gateway.drizzle.team/) self-hosted, free | None | AdminJS integration guide (CRUD panel) |
|
|
364
|
+
| **Error PII safety** | Keys only by default | Values in messages | Raw pg errors | Raw pg errors | |
|
|
365
|
+
| **Migrations** | SQL-first, SHA-256 checksums | DSL-generated, shadow DB | SQL or Drizzle Kit | None | Generated by diffing entities against a snapshot or the database; TS/JS classes |
|
|
366
|
+
| **Edge runtime** | One import swap, under 116 kB brotli (CI-enforced) | Driver adapter + WASM compiler | Native | Native | Cloudflare Workers and Deno (v7) |
|
|
367
|
+
| **Pipeline batching** | Parse/Bind/Execute protocol | Sequential in txn | Sequential | Manual | `flush()` runs queued changes in one transaction |
|
|
368
|
+
| **Typed errors** | `isRetryable` discriminant | Error codes only | None | None | `DriverException` subclasses (`UniqueConstraintViolationException`, ...) |
|
|
369
|
+
| **Nested relations** | 1 query, deep type inference | 1 query per relation by default; single-query `relationJoins` is Preview | 1 query, `relations()` re-declaration | Manual (`jsonArrayFrom`) | `populate`; default `balanced`: joins to-one, one query per to-many relation |
|
|
370
|
+
| **Index advice** | `turbine doctor`, offline, `--fix` | Optimize retired March 2026 (cloud Query Insights) | None | None | |
|
|
371
|
+
| **MCP server for agents** | 11 read-only tools, PII-redacted | Official MCP server | `drizzle-kit mcp` | None | |
|
|
372
|
+
| **Vector search** | Built-in `distance` / KNN | Preview / raw | Extension API | Manual | |
|
|
373
|
+
| **LISTEN/NOTIFY** | `$listen` / `$notify` | None | None | None | |
|
|
374
|
+
|
|
375
|
+
Competitor columns last checked 2026-08-22, against Prisma 7 and Drizzle 0.45 (the current `latest`; 1.0 is at rc.4). The MikroORM column was checked 2026-10-13 against its 7.2 documentation; a blank cell is one that documentation does not settle. Cells that make a claim about someone else's pricing or licensing carry a link to that vendor's own page, because those are the cells that go stale silently: this table said Drizzle's Gateway was paid, which was true of the old Drizzle Studio subscription and is not true of Gateway, and nothing in the repo would have caught it. Features marked Preview may change; bundle sizes move release to release. The longer version of this argument, including what is not a reason to switch: [turbineorm.dev/why-turbine](https://turbineorm.dev/why-turbine).
|
|
376
376
|
|
|
377
377
|
## Limitations
|
|
378
378
|
|
|
@@ -388,13 +388,14 @@ Stated so you do not find out three weeks in:
|
|
|
388
388
|
| Postgres | TypeScript | Notes |
|
|
389
389
|
|---|---|---|
|
|
390
390
|
| `int2`, `int4`, `float4`, `float8` | `number` | |
|
|
391
|
-
| `int8` / `bigint` | `number` | Values above `Number.MAX_SAFE_INTEGER` come back as `string` to avoid precision loss |
|
|
391
|
+
| `int8` / `bigint` | `number` | Values above `Number.MAX_SAFE_INTEGER` come back as `string` to avoid precision loss; `bigint[]` reads as `string[]` |
|
|
392
392
|
| `numeric`, `money` | `string` | Arbitrary precision, kept exact |
|
|
393
393
|
| `text`, `varchar`, `uuid`, `citext` | `string` | |
|
|
394
394
|
| `timestamptz`, `timestamp`, `date` | `Date` | Zone-less columns read and write as UTC by default (`utcTimestamps`); the full temporal semantics, including `temporalInfinity`, are at [turbineorm.dev/schema](https://turbineorm.dev/schema#zone-less-columns-timestamp-and-date-read-as-utc) |
|
|
395
395
|
| `boolean` | `boolean` | |
|
|
396
396
|
| `json`, `jsonb` | `unknown` | |
|
|
397
397
|
| `bytea` | `Buffer` | |
|
|
398
|
+
| `interval`, `point`, `circle` | object | The driver's parsed form, for example `{ x, y }` for a point |
|
|
398
399
|
| Array types | `T[]` | |
|
|
399
400
|
|
|
400
401
|
## Examples
|
package/dist/checkout.d.ts
CHANGED
|
@@ -34,10 +34,35 @@ export interface Checkout {
|
|
|
34
34
|
* `DatabaseError` carrying a SQLSTATE in `.code`, the same property Turbine
|
|
35
35
|
* puts `TURBINE_E0NN` in.
|
|
36
36
|
*
|
|
37
|
-
* Query paths
|
|
38
|
-
* and rejects with the connect error
|
|
37
|
+
* Query paths reach the same errors through `pool.query()`, which checks out
|
|
38
|
+
* internally and rejects with the connect error; the query boundary wraps
|
|
39
|
+
* those, and {@link typeCheckoutTimeouts} types pg-pool's own checkout
|
|
40
|
+
* failures on the pools Turbine owns.
|
|
39
41
|
*/
|
|
40
42
|
export declare function acquireConnection(pool: PgCompatPool): Promise<Checkout>;
|
|
43
|
+
/**
|
|
44
|
+
* A failed checkout as a typed error.
|
|
45
|
+
*
|
|
46
|
+
* pg-pool raises its two checkout failures as plain `Error`s with no code, so
|
|
47
|
+
* `wrapPgError` (keyed on SQLSTATE and socket codes) passes them through and
|
|
48
|
+
* the most common production connection failure, an exhausted pool, reached
|
|
49
|
+
* callers as an untyped error: `instanceof ConnectionError` and
|
|
50
|
+
* `code === 'TURBINE_E004'` both missed it. Everything else goes through
|
|
51
|
+
* `wrapPgError` as before.
|
|
52
|
+
*/
|
|
53
|
+
export declare function checkoutError(err: unknown, pool?: unknown): unknown;
|
|
54
|
+
/**
|
|
55
|
+
* Make every checkout failure of a pool Turbine owns a typed error, for
|
|
56
|
+
* `pool.query` as well as `pool.connect`.
|
|
57
|
+
*
|
|
58
|
+
* {@link acquireConnection} covers the paths that check out explicitly
|
|
59
|
+
* (transactions, nested writes, streams, pipelines). An ordinary read or write
|
|
60
|
+
* goes through `pool.query`, which checks out internally via `this.connect`
|
|
61
|
+
* in callback form, so the conversion has to sit on `connect` itself. Wrapping
|
|
62
|
+
* the error is all it does. For pools Turbine creates only, the same rule as
|
|
63
|
+
* the connection guards: a caller's own pool is not Turbine's to patch.
|
|
64
|
+
*/
|
|
65
|
+
export declare function typeCheckoutTimeouts(pool: unknown): void;
|
|
41
66
|
/**
|
|
42
67
|
* Check a connection out and run `opening` on it, retrying ONCE on a fresh
|
|
43
68
|
* connection when `opening` fails with {@link isStaleConnectionError}.
|
package/dist/checkout.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* write to lose.
|
|
19
19
|
*/
|
|
20
20
|
import { guardCheckout, settleEventLoop } from './connection-guard.js';
|
|
21
|
-
import { explainConnectionLoss, isStaleConnectionError, wrapPgError } from './errors.js';
|
|
21
|
+
import { ConnectionError, explainConnectionLoss, isStaleConnectionError, UnsupportedFeatureError, wrapPgError, } from './errors.js';
|
|
22
22
|
/**
|
|
23
23
|
* Check a connection out, as a typed Turbine error when that fails.
|
|
24
24
|
*
|
|
@@ -29,19 +29,90 @@ import { explainConnectionLoss, isStaleConnectionError, wrapPgError } from './er
|
|
|
29
29
|
* `DatabaseError` carrying a SQLSTATE in `.code`, the same property Turbine
|
|
30
30
|
* puts `TURBINE_E0NN` in.
|
|
31
31
|
*
|
|
32
|
-
* Query paths
|
|
33
|
-
* and rejects with the connect error
|
|
32
|
+
* Query paths reach the same errors through `pool.query()`, which checks out
|
|
33
|
+
* internally and rejects with the connect error; the query boundary wraps
|
|
34
|
+
* those, and {@link typeCheckoutTimeouts} types pg-pool's own checkout
|
|
35
|
+
* failures on the pools Turbine owns.
|
|
34
36
|
*/
|
|
35
37
|
export async function acquireConnection(pool) {
|
|
38
|
+
if (typeof pool.connect !== 'function') {
|
|
39
|
+
const dialect = pool.dialect?.name;
|
|
40
|
+
throw new UnsupportedFeatureError('Checking out a connection', typeof dialect === 'string' ? dialect : 'postgresql', 'A transaction, a cursor stream, a pipeline or a write that takes several statements needs a connection ' +
|
|
41
|
+
'of its own, and this pool has no `connect()` (a stateless HTTP driver). Pass a pool that can check out ' +
|
|
42
|
+
'a connection, or run single statements.');
|
|
43
|
+
}
|
|
36
44
|
let client;
|
|
37
45
|
try {
|
|
38
46
|
client = await pool.connect();
|
|
39
47
|
}
|
|
40
48
|
catch (err) {
|
|
41
|
-
throw
|
|
49
|
+
throw checkoutError(err, pool);
|
|
42
50
|
}
|
|
43
51
|
return { client, checkout: guardCheckout(client) };
|
|
44
52
|
}
|
|
53
|
+
/** pg-pool's own words for a checkout that waited longer than `connectionTimeoutMillis`. */
|
|
54
|
+
const POOL_TIMEOUT_MESSAGE = 'timeout exceeded when trying to connect';
|
|
55
|
+
/** pg-pool's own words for a checkout from a pool that has been ended. */
|
|
56
|
+
const POOL_ENDED_MESSAGE = 'Cannot use a pool after calling end on the pool';
|
|
57
|
+
/**
|
|
58
|
+
* A failed checkout as a typed error.
|
|
59
|
+
*
|
|
60
|
+
* pg-pool raises its two checkout failures as plain `Error`s with no code, so
|
|
61
|
+
* `wrapPgError` (keyed on SQLSTATE and socket codes) passes them through and
|
|
62
|
+
* the most common production connection failure, an exhausted pool, reached
|
|
63
|
+
* callers as an untyped error: `instanceof ConnectionError` and
|
|
64
|
+
* `code === 'TURBINE_E004'` both missed it. Everything else goes through
|
|
65
|
+
* `wrapPgError` as before.
|
|
66
|
+
*/
|
|
67
|
+
export function checkoutError(err, pool) {
|
|
68
|
+
if (!(err instanceof Error) || err.code !== undefined)
|
|
69
|
+
return wrapPgError(err);
|
|
70
|
+
if (err.message === POOL_TIMEOUT_MESSAGE) {
|
|
71
|
+
const sizing = (pool ?? {});
|
|
72
|
+
const max = sizing.options?.max;
|
|
73
|
+
const waitMs = sizing.options?.connectionTimeoutMillis;
|
|
74
|
+
const waiting = sizing.waitingCount;
|
|
75
|
+
const occupancy = max !== undefined
|
|
76
|
+
? ` All ${max} connections of the pool were checked out${waiting ? ` and ${waiting} other callers were waiting` : ''}.`
|
|
77
|
+
: '';
|
|
78
|
+
return new ConnectionError(`Timed out${waitMs ? ` after ${waitMs}ms` : ''} waiting for a free connection from the pool.${occupancy} ` +
|
|
79
|
+
'Something is holding connections for longer than `connectionTimeoutMs`: long transactions or streams, ' +
|
|
80
|
+
'or a query on the outer client inside a `$transaction` callback, which needs a second connection ' +
|
|
81
|
+
'while the transaction holds the first (use `tx` there). Raise `poolSize` if the load is genuine.', { cause: err });
|
|
82
|
+
}
|
|
83
|
+
if (err.message === POOL_ENDED_MESSAGE) {
|
|
84
|
+
return new ConnectionError('The connection pool has been shut down (`disconnect()` or `end()` was called), so no connection can be ' +
|
|
85
|
+
'checked out. Create a new client, or stop issuing queries after shutting this one down.', { cause: err });
|
|
86
|
+
}
|
|
87
|
+
return wrapPgError(err);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Make every checkout failure of a pool Turbine owns a typed error, for
|
|
91
|
+
* `pool.query` as well as `pool.connect`.
|
|
92
|
+
*
|
|
93
|
+
* {@link acquireConnection} covers the paths that check out explicitly
|
|
94
|
+
* (transactions, nested writes, streams, pipelines). An ordinary read or write
|
|
95
|
+
* goes through `pool.query`, which checks out internally via `this.connect`
|
|
96
|
+
* in callback form, so the conversion has to sit on `connect` itself. Wrapping
|
|
97
|
+
* the error is all it does. For pools Turbine creates only, the same rule as
|
|
98
|
+
* the connection guards: a caller's own pool is not Turbine's to patch.
|
|
99
|
+
*/
|
|
100
|
+
export function typeCheckoutTimeouts(pool) {
|
|
101
|
+
const p = pool;
|
|
102
|
+
const connect = p.connect;
|
|
103
|
+
if (typeof connect !== 'function')
|
|
104
|
+
return;
|
|
105
|
+
p.connect = (...args) => {
|
|
106
|
+
const callback = args[0];
|
|
107
|
+
if (typeof callback === 'function') {
|
|
108
|
+
return connect.call(p, (err, ...rest) => callback(err ? checkoutError(err, pool) : err, ...rest));
|
|
109
|
+
}
|
|
110
|
+
const pending = connect.apply(p, args);
|
|
111
|
+
return pending.catch((err) => {
|
|
112
|
+
throw checkoutError(err, pool);
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
}
|
|
45
116
|
/**
|
|
46
117
|
* Check a connection out and run `opening` on it, retrying ONCE on a fresh
|
|
47
118
|
* connection when `opening` fails with {@link isStaleConnectionError}.
|
package/dist/cjs/checkout.d.ts
CHANGED
|
@@ -34,10 +34,35 @@ export interface Checkout {
|
|
|
34
34
|
* `DatabaseError` carrying a SQLSTATE in `.code`, the same property Turbine
|
|
35
35
|
* puts `TURBINE_E0NN` in.
|
|
36
36
|
*
|
|
37
|
-
* Query paths
|
|
38
|
-
* and rejects with the connect error
|
|
37
|
+
* Query paths reach the same errors through `pool.query()`, which checks out
|
|
38
|
+
* internally and rejects with the connect error; the query boundary wraps
|
|
39
|
+
* those, and {@link typeCheckoutTimeouts} types pg-pool's own checkout
|
|
40
|
+
* failures on the pools Turbine owns.
|
|
39
41
|
*/
|
|
40
42
|
export declare function acquireConnection(pool: PgCompatPool): Promise<Checkout>;
|
|
43
|
+
/**
|
|
44
|
+
* A failed checkout as a typed error.
|
|
45
|
+
*
|
|
46
|
+
* pg-pool raises its two checkout failures as plain `Error`s with no code, so
|
|
47
|
+
* `wrapPgError` (keyed on SQLSTATE and socket codes) passes them through and
|
|
48
|
+
* the most common production connection failure, an exhausted pool, reached
|
|
49
|
+
* callers as an untyped error: `instanceof ConnectionError` and
|
|
50
|
+
* `code === 'TURBINE_E004'` both missed it. Everything else goes through
|
|
51
|
+
* `wrapPgError` as before.
|
|
52
|
+
*/
|
|
53
|
+
export declare function checkoutError(err: unknown, pool?: unknown): unknown;
|
|
54
|
+
/**
|
|
55
|
+
* Make every checkout failure of a pool Turbine owns a typed error, for
|
|
56
|
+
* `pool.query` as well as `pool.connect`.
|
|
57
|
+
*
|
|
58
|
+
* {@link acquireConnection} covers the paths that check out explicitly
|
|
59
|
+
* (transactions, nested writes, streams, pipelines). An ordinary read or write
|
|
60
|
+
* goes through `pool.query`, which checks out internally via `this.connect`
|
|
61
|
+
* in callback form, so the conversion has to sit on `connect` itself. Wrapping
|
|
62
|
+
* the error is all it does. For pools Turbine creates only, the same rule as
|
|
63
|
+
* the connection guards: a caller's own pool is not Turbine's to patch.
|
|
64
|
+
*/
|
|
65
|
+
export declare function typeCheckoutTimeouts(pool: unknown): void;
|
|
41
66
|
/**
|
|
42
67
|
* Check a connection out and run `opening` on it, retrying ONCE on a fresh
|
|
43
68
|
* connection when `opening` fails with {@link isStaleConnectionError}.
|
package/dist/cjs/checkout.js
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
*/
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
22
|
exports.acquireConnection = acquireConnection;
|
|
23
|
+
exports.checkoutError = checkoutError;
|
|
24
|
+
exports.typeCheckoutTimeouts = typeCheckoutTimeouts;
|
|
23
25
|
exports.openCheckout = openCheckout;
|
|
24
26
|
const connection_guard_js_1 = require("./connection-guard.js");
|
|
25
27
|
const errors_js_1 = require("./errors.js");
|
|
@@ -33,19 +35,90 @@ const errors_js_1 = require("./errors.js");
|
|
|
33
35
|
* `DatabaseError` carrying a SQLSTATE in `.code`, the same property Turbine
|
|
34
36
|
* puts `TURBINE_E0NN` in.
|
|
35
37
|
*
|
|
36
|
-
* Query paths
|
|
37
|
-
* and rejects with the connect error
|
|
38
|
+
* Query paths reach the same errors through `pool.query()`, which checks out
|
|
39
|
+
* internally and rejects with the connect error; the query boundary wraps
|
|
40
|
+
* those, and {@link typeCheckoutTimeouts} types pg-pool's own checkout
|
|
41
|
+
* failures on the pools Turbine owns.
|
|
38
42
|
*/
|
|
39
43
|
async function acquireConnection(pool) {
|
|
44
|
+
if (typeof pool.connect !== 'function') {
|
|
45
|
+
const dialect = pool.dialect?.name;
|
|
46
|
+
throw new errors_js_1.UnsupportedFeatureError('Checking out a connection', typeof dialect === 'string' ? dialect : 'postgresql', 'A transaction, a cursor stream, a pipeline or a write that takes several statements needs a connection ' +
|
|
47
|
+
'of its own, and this pool has no `connect()` (a stateless HTTP driver). Pass a pool that can check out ' +
|
|
48
|
+
'a connection, or run single statements.');
|
|
49
|
+
}
|
|
40
50
|
let client;
|
|
41
51
|
try {
|
|
42
52
|
client = await pool.connect();
|
|
43
53
|
}
|
|
44
54
|
catch (err) {
|
|
45
|
-
throw (
|
|
55
|
+
throw checkoutError(err, pool);
|
|
46
56
|
}
|
|
47
57
|
return { client, checkout: (0, connection_guard_js_1.guardCheckout)(client) };
|
|
48
58
|
}
|
|
59
|
+
/** pg-pool's own words for a checkout that waited longer than `connectionTimeoutMillis`. */
|
|
60
|
+
const POOL_TIMEOUT_MESSAGE = 'timeout exceeded when trying to connect';
|
|
61
|
+
/** pg-pool's own words for a checkout from a pool that has been ended. */
|
|
62
|
+
const POOL_ENDED_MESSAGE = 'Cannot use a pool after calling end on the pool';
|
|
63
|
+
/**
|
|
64
|
+
* A failed checkout as a typed error.
|
|
65
|
+
*
|
|
66
|
+
* pg-pool raises its two checkout failures as plain `Error`s with no code, so
|
|
67
|
+
* `wrapPgError` (keyed on SQLSTATE and socket codes) passes them through and
|
|
68
|
+
* the most common production connection failure, an exhausted pool, reached
|
|
69
|
+
* callers as an untyped error: `instanceof ConnectionError` and
|
|
70
|
+
* `code === 'TURBINE_E004'` both missed it. Everything else goes through
|
|
71
|
+
* `wrapPgError` as before.
|
|
72
|
+
*/
|
|
73
|
+
function checkoutError(err, pool) {
|
|
74
|
+
if (!(err instanceof Error) || err.code !== undefined)
|
|
75
|
+
return (0, errors_js_1.wrapPgError)(err);
|
|
76
|
+
if (err.message === POOL_TIMEOUT_MESSAGE) {
|
|
77
|
+
const sizing = (pool ?? {});
|
|
78
|
+
const max = sizing.options?.max;
|
|
79
|
+
const waitMs = sizing.options?.connectionTimeoutMillis;
|
|
80
|
+
const waiting = sizing.waitingCount;
|
|
81
|
+
const occupancy = max !== undefined
|
|
82
|
+
? ` All ${max} connections of the pool were checked out${waiting ? ` and ${waiting} other callers were waiting` : ''}.`
|
|
83
|
+
: '';
|
|
84
|
+
return new errors_js_1.ConnectionError(`Timed out${waitMs ? ` after ${waitMs}ms` : ''} waiting for a free connection from the pool.${occupancy} ` +
|
|
85
|
+
'Something is holding connections for longer than `connectionTimeoutMs`: long transactions or streams, ' +
|
|
86
|
+
'or a query on the outer client inside a `$transaction` callback, which needs a second connection ' +
|
|
87
|
+
'while the transaction holds the first (use `tx` there). Raise `poolSize` if the load is genuine.', { cause: err });
|
|
88
|
+
}
|
|
89
|
+
if (err.message === POOL_ENDED_MESSAGE) {
|
|
90
|
+
return new errors_js_1.ConnectionError('The connection pool has been shut down (`disconnect()` or `end()` was called), so no connection can be ' +
|
|
91
|
+
'checked out. Create a new client, or stop issuing queries after shutting this one down.', { cause: err });
|
|
92
|
+
}
|
|
93
|
+
return (0, errors_js_1.wrapPgError)(err);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Make every checkout failure of a pool Turbine owns a typed error, for
|
|
97
|
+
* `pool.query` as well as `pool.connect`.
|
|
98
|
+
*
|
|
99
|
+
* {@link acquireConnection} covers the paths that check out explicitly
|
|
100
|
+
* (transactions, nested writes, streams, pipelines). An ordinary read or write
|
|
101
|
+
* goes through `pool.query`, which checks out internally via `this.connect`
|
|
102
|
+
* in callback form, so the conversion has to sit on `connect` itself. Wrapping
|
|
103
|
+
* the error is all it does. For pools Turbine creates only, the same rule as
|
|
104
|
+
* the connection guards: a caller's own pool is not Turbine's to patch.
|
|
105
|
+
*/
|
|
106
|
+
function typeCheckoutTimeouts(pool) {
|
|
107
|
+
const p = pool;
|
|
108
|
+
const connect = p.connect;
|
|
109
|
+
if (typeof connect !== 'function')
|
|
110
|
+
return;
|
|
111
|
+
p.connect = (...args) => {
|
|
112
|
+
const callback = args[0];
|
|
113
|
+
if (typeof callback === 'function') {
|
|
114
|
+
return connect.call(p, (err, ...rest) => callback(err ? checkoutError(err, pool) : err, ...rest));
|
|
115
|
+
}
|
|
116
|
+
const pending = connect.apply(p, args);
|
|
117
|
+
return pending.catch((err) => {
|
|
118
|
+
throw checkoutError(err, pool);
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
}
|
|
49
122
|
/**
|
|
50
123
|
* Check a connection out and run `opening` on it, retrying ONCE on a fresh
|
|
51
124
|
* connection when `opening` fails with {@link isStaleConnectionError}.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code-first column tags for `turbine generate`.
|
|
3
|
+
*
|
|
4
|
+
* `pii`, `updatedAt`, `maxLength` and `tsType` exist only in the `defineSchema()`
|
|
5
|
+
* file: the database has no column for them, so a client generated from
|
|
6
|
+
* introspection alone returns PII by default, never stamps `updatedAt`, and
|
|
7
|
+
* types a `jsonb` column as `unknown`. `generate` reads the schema file and
|
|
8
|
+
* overlays its tags onto the introspected schema with the two functions here.
|
|
9
|
+
*
|
|
10
|
+
* Lives under `cli/` because only the CLI uses it. `schemaDefToMetadata` (the
|
|
11
|
+
* library path) shares the tag list through `declaredTagsOf`.
|
|
12
|
+
*/
|
|
13
|
+
import { type SchemaMetadata } from '../schema.js';
|
|
14
|
+
import type { SchemaDef } from '../schema-builder.js';
|
|
15
|
+
import { type CodeFirstColumnTags } from '../schema-metadata.js';
|
|
16
|
+
/** Declared tags per table (database name), per column (database name). */
|
|
17
|
+
export type CodeFirstTagMap = Map<string, Map<string, CodeFirstColumnTags>>;
|
|
18
|
+
/**
|
|
19
|
+
* Collect the code-first-only tags a {@link SchemaDef} declares, keyed by the
|
|
20
|
+
* database table and column they land on (the same `camelToSnake(field)` rule
|
|
21
|
+
* {@link schemaDefToMetadata} uses). Tables and columns that declare no tag are
|
|
22
|
+
* absent from the result.
|
|
23
|
+
*
|
|
24
|
+
* Introspection can not see these flags, because the database has no column for
|
|
25
|
+
* them. `turbine generate` overlays them onto the introspected schema with
|
|
26
|
+
* {@link applyCodeFirstColumnTags}.
|
|
27
|
+
*/
|
|
28
|
+
export declare function extractCodeFirstColumnTags(def: SchemaDef): CodeFirstTagMap;
|
|
29
|
+
/** What {@link applyCodeFirstColumnTags} changed. */
|
|
30
|
+
export interface AppliedColumnTags {
|
|
31
|
+
/** Tables that received at least one tag. */
|
|
32
|
+
tables: number;
|
|
33
|
+
pii: number;
|
|
34
|
+
updatedAt: number;
|
|
35
|
+
maxLength: number;
|
|
36
|
+
/**
|
|
37
|
+
* Declared `tsType`s, as generator `columnTypes` entries
|
|
38
|
+
* (`'<table>.<column>'` by database name). Returned rather than written onto
|
|
39
|
+
* the schema so they go through the generator's own key and type-text
|
|
40
|
+
* validation, the same as a `columnTypes` entry in the config file.
|
|
41
|
+
*/
|
|
42
|
+
columnTypes: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Overlay declared tags onto an introspected schema, in place, matching tables
|
|
46
|
+
* and columns by their database names.
|
|
47
|
+
*
|
|
48
|
+
* `pii` and `updatedAt` are set wherever declared. `maxLength` only fills a gap:
|
|
49
|
+
* the live column's own length wins when introspection read one.
|
|
50
|
+
*
|
|
51
|
+
* A `pii` or `updatedAt` tag that names a table or column the database does not
|
|
52
|
+
* have is an error, never a skip: a typo or an un-pushed schema would otherwise
|
|
53
|
+
* generate a client that silently returns the column it was tagged to hide.
|
|
54
|
+
* Every problem is reported at once. `maxLength` is also in the DDL, so a
|
|
55
|
+
* missing table or column only costs that fill-in. `inScope` lets the caller say
|
|
56
|
+
* that a missing table was deliberately left out of the client (an `exclude`
|
|
57
|
+
* filter), which is not an error either.
|
|
58
|
+
*/
|
|
59
|
+
export declare function applyCodeFirstColumnTags(schema: SchemaMetadata, tags: CodeFirstTagMap, options?: {
|
|
60
|
+
source?: string;
|
|
61
|
+
inScope?: (table: string) => boolean;
|
|
62
|
+
}): AppliedColumnTags;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Code-first column tags for `turbine generate`.
|
|
4
|
+
*
|
|
5
|
+
* `pii`, `updatedAt`, `maxLength` and `tsType` exist only in the `defineSchema()`
|
|
6
|
+
* file: the database has no column for them, so a client generated from
|
|
7
|
+
* introspection alone returns PII by default, never stamps `updatedAt`, and
|
|
8
|
+
* types a `jsonb` column as `unknown`. `generate` reads the schema file and
|
|
9
|
+
* overlays its tags onto the introspected schema with the two functions here.
|
|
10
|
+
*
|
|
11
|
+
* Lives under `cli/` because only the CLI uses it. `schemaDefToMetadata` (the
|
|
12
|
+
* library path) shares the tag list through `declaredTagsOf`.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.extractCodeFirstColumnTags = extractCodeFirstColumnTags;
|
|
16
|
+
exports.applyCodeFirstColumnTags = applyCodeFirstColumnTags;
|
|
17
|
+
const errors_js_1 = require("../errors.js");
|
|
18
|
+
const introspect_js_1 = require("../introspect.js");
|
|
19
|
+
const utils_js_1 = require("../query/utils.js");
|
|
20
|
+
const schema_js_1 = require("../schema.js");
|
|
21
|
+
const schema_metadata_js_1 = require("../schema-metadata.js");
|
|
22
|
+
/**
|
|
23
|
+
* Collect the code-first-only tags a {@link SchemaDef} declares, keyed by the
|
|
24
|
+
* database table and column they land on (the same `camelToSnake(field)` rule
|
|
25
|
+
* {@link schemaDefToMetadata} uses). Tables and columns that declare no tag are
|
|
26
|
+
* absent from the result.
|
|
27
|
+
*
|
|
28
|
+
* Introspection can not see these flags, because the database has no column for
|
|
29
|
+
* them. `turbine generate` overlays them onto the introspected schema with
|
|
30
|
+
* {@link applyCodeFirstColumnTags}.
|
|
31
|
+
*/
|
|
32
|
+
function extractCodeFirstColumnTags(def) {
|
|
33
|
+
const out = new Map();
|
|
34
|
+
for (const tableDef of Object.values(def.tables)) {
|
|
35
|
+
for (const [field, config] of Object.entries(tableDef.columns)) {
|
|
36
|
+
const tags = (0, schema_metadata_js_1.declaredTagsOf)(config);
|
|
37
|
+
if (Object.keys(tags).length === 0)
|
|
38
|
+
continue;
|
|
39
|
+
let columns = out.get(tableDef.name);
|
|
40
|
+
if (!columns) {
|
|
41
|
+
columns = new Map();
|
|
42
|
+
out.set(tableDef.name, columns);
|
|
43
|
+
}
|
|
44
|
+
columns.set((0, schema_js_1.camelToSnake)(field), tags);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
/** A tag the database has no column for, so only the schema file can supply it. */
|
|
50
|
+
function isCodeFirstOnly(tags) {
|
|
51
|
+
return tags.pii === true || tags.updatedAt === true;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Overlay declared tags onto an introspected schema, in place, matching tables
|
|
55
|
+
* and columns by their database names.
|
|
56
|
+
*
|
|
57
|
+
* `pii` and `updatedAt` are set wherever declared. `maxLength` only fills a gap:
|
|
58
|
+
* the live column's own length wins when introspection read one.
|
|
59
|
+
*
|
|
60
|
+
* A `pii` or `updatedAt` tag that names a table or column the database does not
|
|
61
|
+
* have is an error, never a skip: a typo or an un-pushed schema would otherwise
|
|
62
|
+
* generate a client that silently returns the column it was tagged to hide.
|
|
63
|
+
* Every problem is reported at once. `maxLength` is also in the DDL, so a
|
|
64
|
+
* missing table or column only costs that fill-in. `inScope` lets the caller say
|
|
65
|
+
* that a missing table was deliberately left out of the client (an `exclude`
|
|
66
|
+
* filter), which is not an error either.
|
|
67
|
+
*/
|
|
68
|
+
function applyCodeFirstColumnTags(schema, tags, options = {}) {
|
|
69
|
+
const applied = { tables: 0, pii: 0, updatedAt: 0, maxLength: 0, columnTypes: {} };
|
|
70
|
+
const problems = [];
|
|
71
|
+
for (const [tableName, columns] of tags) {
|
|
72
|
+
const shown = (0, introspect_js_1.printableCatalogName)(tableName);
|
|
73
|
+
if (!Object.hasOwn(schema.tables, tableName)) {
|
|
74
|
+
if (options.inScope && !options.inScope(tableName))
|
|
75
|
+
continue;
|
|
76
|
+
if (![...columns.values()].some(isCodeFirstOnly))
|
|
77
|
+
continue;
|
|
78
|
+
const near = (0, utils_js_1.closestName)(tableName, Object.keys(schema.tables));
|
|
79
|
+
problems.push(`table "${shown}" is not in the database${near ? ` (did you mean "${(0, introspect_js_1.printableCatalogName)(near)}"?)` : ''}`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const table = schema.tables[tableName];
|
|
83
|
+
let touched = false;
|
|
84
|
+
for (const [columnName, declared] of columns) {
|
|
85
|
+
const column = table.columns.find((c) => c.name === columnName);
|
|
86
|
+
if (!column) {
|
|
87
|
+
if (!isCodeFirstOnly(declared))
|
|
88
|
+
continue;
|
|
89
|
+
const near = (0, utils_js_1.closestName)(columnName, table.columns.map((c) => c.name));
|
|
90
|
+
problems.push(`column "${(0, introspect_js_1.printableCatalogName)(columnName)}" on table "${shown}" is not in the database` +
|
|
91
|
+
`${near ? ` (did you mean "${(0, introspect_js_1.printableCatalogName)(near)}"?)` : ''}`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (declared.pii) {
|
|
95
|
+
column.pii = true;
|
|
96
|
+
applied.pii++;
|
|
97
|
+
touched = true;
|
|
98
|
+
}
|
|
99
|
+
if (declared.updatedAt) {
|
|
100
|
+
column.updatedAt = true;
|
|
101
|
+
applied.updatedAt++;
|
|
102
|
+
touched = true;
|
|
103
|
+
}
|
|
104
|
+
if (declared.maxLength !== undefined && column.maxLength === undefined) {
|
|
105
|
+
column.maxLength = declared.maxLength;
|
|
106
|
+
applied.maxLength++;
|
|
107
|
+
touched = true;
|
|
108
|
+
}
|
|
109
|
+
if (declared.tsType !== undefined) {
|
|
110
|
+
applied.columnTypes[`${tableName}.${columnName}`] = declared.tsType;
|
|
111
|
+
touched = true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (touched)
|
|
115
|
+
applied.tables++;
|
|
116
|
+
}
|
|
117
|
+
if (problems.length > 0) {
|
|
118
|
+
const from = options.source ? ` in ${options.source}` : '';
|
|
119
|
+
throw new errors_js_1.ValidationError(`Cannot apply the column tags declared${from}: ${problems.join('; ')}. ` +
|
|
120
|
+
'Run `turbine push` so the database has them, or fix the name in the schema file. ' +
|
|
121
|
+
'Generating without the tags would return the columns they mark as PII.');
|
|
122
|
+
}
|
|
123
|
+
return applied;
|
|
124
|
+
}
|
|
@@ -108,6 +108,9 @@ exports.COLUMN_NAMING_ARG_KEYS = [
|
|
|
108
108
|
'_avg',
|
|
109
109
|
'_min',
|
|
110
110
|
'_max',
|
|
111
|
+
// Keys are column names and the result is a stored value (or one equal to it),
|
|
112
|
+
// the same reason `_min` / `_max` are listed.
|
|
113
|
+
'_percentile',
|
|
111
114
|
];
|
|
112
115
|
/**
|
|
113
116
|
* Does this args object carry a key that names a column, AT ANY DEPTH?
|
|
@@ -154,6 +157,12 @@ function carriesColumnNamingArg(args, depth = 0) {
|
|
|
154
157
|
// ---------------------------------------------------------------------------
|
|
155
158
|
// Compile
|
|
156
159
|
// ---------------------------------------------------------------------------
|
|
160
|
+
/**
|
|
161
|
+
* Any non-array object, class instances included. Deliberately NOT the shared
|
|
162
|
+
* `isPlainRecord`: the builder walks a `with` (and a `where`) by its own keys
|
|
163
|
+
* whatever its prototype, so a report that skipped a class-instance `with`
|
|
164
|
+
* would list no relation while the SQL loads one.
|
|
165
|
+
*/
|
|
157
166
|
function isPlainObject(value) {
|
|
158
167
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
159
168
|
}
|