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/dist/typed-sql.d.ts
CHANGED
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
* impossible to string-concatenate a value into the query through this API.
|
|
20
20
|
* This is the whole point of the tagged-template shape: the literal segments
|
|
21
21
|
* are frozen by the compiler (`TemplateStringsArray`), and the only way to
|
|
22
|
-
* get a runtime value into the query is via `${...}`, which we bind.
|
|
22
|
+
* get a runtime value into the query is via `${...}`, which we bind. A
|
|
23
|
+
* plain array passed where the template belongs is refused at runtime
|
|
24
|
+
* ({@link assertTemplateStrings}).
|
|
23
25
|
*
|
|
24
26
|
* 3. **Rows are returned as-is (no snake→camel mapping).** This matches the
|
|
25
27
|
* existing `client.raw()` behavior: a typed raw query is a literal escape
|
|
@@ -49,6 +51,7 @@
|
|
|
49
51
|
*/
|
|
50
52
|
import type { PgCompatPool } from './client.js';
|
|
51
53
|
import { type Dialect } from './dialect.js';
|
|
54
|
+
import { type TurbineLogger } from './logger.js';
|
|
52
55
|
import type { QueryEvent } from './query/deferred.js';
|
|
53
56
|
/**
|
|
54
57
|
* Build a `(sql, params)` pair from a tagged-template invocation.
|
|
@@ -61,10 +64,37 @@ import type { QueryEvent } from './query/deferred.js';
|
|
|
61
64
|
*
|
|
62
65
|
* Exported for unit testing the parameterization invariant without a database.
|
|
63
66
|
*/
|
|
64
|
-
export declare function buildTypedSql(strings: TemplateStringsArray, values: readonly unknown[], dialect?: Pick<Dialect, 'paramPlaceholder'
|
|
67
|
+
export declare function buildTypedSql(strings: TemplateStringsArray, values: readonly unknown[], dialect?: Pick<Dialect, 'paramPlaceholder'>, api?: string): {
|
|
65
68
|
sql: string;
|
|
66
69
|
params: unknown[];
|
|
67
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* Refuse a "template" that did not come from a tagged-template call.
|
|
73
|
+
*
|
|
74
|
+
* Only the static segments of a template reach the SQL text, and that is safe
|
|
75
|
+
* because the compiler wrote them. A plain array passed in their place (a
|
|
76
|
+
* JSON-parsed request field, a string split by hand) puts its text into the
|
|
77
|
+
* SQL with nothing escaped. A real `TemplateStringsArray` is frozen and carries
|
|
78
|
+
* a frozen `raw` array of the same length; `JSON.parse` and array literals
|
|
79
|
+
* produce neither.
|
|
80
|
+
*
|
|
81
|
+
* `unsafeApi` names the method that takes statement text instead, for the
|
|
82
|
+
* error message.
|
|
83
|
+
*/
|
|
84
|
+
export declare function assertTemplateStrings(strings: unknown, api: string, unsafeApi?: string): asserts strings is TemplateStringsArray;
|
|
85
|
+
/**
|
|
86
|
+
* Raw-statement params with every `Date` (in arrays too) bound as UTC text,
|
|
87
|
+
* for a client reading zone-less temporal types as UTC (`utcTimestamps`).
|
|
88
|
+
*
|
|
89
|
+
* node-postgres renders a Date in the process's zone with its offset. A
|
|
90
|
+
* `timestamptz` keeps the instant either way, but a `timestamp` or `date`
|
|
91
|
+
* drops the offset and stores the local wall time, which then reads back
|
|
92
|
+
* shifted by the process offset. UTC text stores the instant's UTC fields,
|
|
93
|
+
* which is what a UTC read returns. Returns `params` itself when it holds no
|
|
94
|
+
* Date. An Invalid Date is refused (E003) instead of reaching the server as
|
|
95
|
+
* unparseable text.
|
|
96
|
+
*/
|
|
97
|
+
export declare function utcDateParams(params: unknown[], api: string): unknown[];
|
|
68
98
|
/**
|
|
69
99
|
* A pending typed raw SQL query. Implements the thenable contract, so it can be
|
|
70
100
|
* `await`ed directly to get `T[]`, or refined via `.one()` / `.scalar()` first.
|
|
@@ -100,6 +130,8 @@ export declare class TypedSqlQuery<T extends Record<string, unknown>> implements
|
|
|
100
130
|
* built outside a client, which then emits nothing, as before.
|
|
101
131
|
*/
|
|
102
132
|
private readonly onQuery?;
|
|
133
|
+
/** Where the `logging` preview line goes. Defaults to the console logger. */
|
|
134
|
+
private readonly logger;
|
|
103
135
|
constructor(pool: PgCompatPool, sql: string, params: unknown[], logging: boolean,
|
|
104
136
|
/**
|
|
105
137
|
* Runs the execution under the owning client's `errorMessages` mode. Passed
|
|
@@ -120,7 +152,9 @@ export declare class TypedSqlQuery<T extends Record<string, unknown>> implements
|
|
|
120
152
|
* with model `'$raw'` and action `'sql'`. Absent for a `TypedSqlQuery`
|
|
121
153
|
* built outside a client, which then emits nothing, as before.
|
|
122
154
|
*/
|
|
123
|
-
onQuery?: ((event: QueryEvent) => void) | undefined
|
|
155
|
+
onQuery?: ((event: QueryEvent) => void) | undefined,
|
|
156
|
+
/** Where the `logging` preview line goes. Defaults to the console logger. */
|
|
157
|
+
logger?: TurbineLogger);
|
|
124
158
|
/** Execute and return all rows. Internal; powers `then`, `one`, and `scalar`. */
|
|
125
159
|
private run;
|
|
126
160
|
/**
|
package/dist/typed-sql.js
CHANGED
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
* impossible to string-concatenate a value into the query through this API.
|
|
20
20
|
* This is the whole point of the tagged-template shape: the literal segments
|
|
21
21
|
* are frozen by the compiler (`TemplateStringsArray`), and the only way to
|
|
22
|
-
* get a runtime value into the query is via `${...}`, which we bind.
|
|
22
|
+
* get a runtime value into the query is via `${...}`, which we bind. A
|
|
23
|
+
* plain array passed where the template belongs is refused at runtime
|
|
24
|
+
* ({@link assertTemplateStrings}).
|
|
23
25
|
*
|
|
24
26
|
* 3. **Rows are returned as-is (no snake→camel mapping).** This matches the
|
|
25
27
|
* existing `client.raw()` behavior: a typed raw query is a literal escape
|
|
@@ -49,6 +51,7 @@
|
|
|
49
51
|
*/
|
|
50
52
|
import { postgresDialect } from './dialect.js';
|
|
51
53
|
import { ValidationError, wrapPgError } from './errors.js';
|
|
54
|
+
import { consoleLogger, logTo } from './logger.js';
|
|
52
55
|
import { rawIdentity, runReportedStatement } from './query-events.js';
|
|
53
56
|
/**
|
|
54
57
|
* Build a `(sql, params)` pair from a tagged-template invocation.
|
|
@@ -61,11 +64,10 @@ import { rawIdentity, runReportedStatement } from './query-events.js';
|
|
|
61
64
|
*
|
|
62
65
|
* Exported for unit testing the parameterization invariant without a database.
|
|
63
66
|
*/
|
|
64
|
-
export function buildTypedSql(strings, values, dialect = postgresDialect) {
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// loudly here instead).
|
|
67
|
+
export function buildTypedSql(strings, values, dialect = postgresDialect, api = 'sql') {
|
|
68
|
+
assertTemplateStrings(strings, api);
|
|
69
|
+
// A tagged template always has `strings.length === values.length + 1`; a
|
|
70
|
+
// hand-built frozen one may not, and would desync placeholders from params.
|
|
69
71
|
if (strings.length !== values.length + 1) {
|
|
70
72
|
throw new ValidationError(`sql template segment/value count mismatch: ${strings.length} segments, ${values.length} values.`);
|
|
71
73
|
}
|
|
@@ -78,6 +80,92 @@ export function buildTypedSql(strings, values, dialect = postgresDialect) {
|
|
|
78
80
|
}
|
|
79
81
|
return { sql, params: values.slice() };
|
|
80
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Refuse a "template" that did not come from a tagged-template call.
|
|
85
|
+
*
|
|
86
|
+
* Only the static segments of a template reach the SQL text, and that is safe
|
|
87
|
+
* because the compiler wrote them. A plain array passed in their place (a
|
|
88
|
+
* JSON-parsed request field, a string split by hand) puts its text into the
|
|
89
|
+
* SQL with nothing escaped. A real `TemplateStringsArray` is frozen and carries
|
|
90
|
+
* a frozen `raw` array of the same length; `JSON.parse` and array literals
|
|
91
|
+
* produce neither.
|
|
92
|
+
*
|
|
93
|
+
* `unsafeApi` names the method that takes statement text instead, for the
|
|
94
|
+
* error message.
|
|
95
|
+
*/
|
|
96
|
+
export function assertTemplateStrings(strings, api, unsafeApi = 'rawUnsafe') {
|
|
97
|
+
if (Array.isArray(strings) && Object.isFrozen(strings)) {
|
|
98
|
+
const raw = strings.raw;
|
|
99
|
+
if (Array.isArray(raw) && Object.isFrozen(raw) && raw.length === strings.length) {
|
|
100
|
+
if (strings.every((s) => typeof s === 'string'))
|
|
101
|
+
return;
|
|
102
|
+
throw new ValidationError(`${api}: a segment of the template contains an invalid escape sequence, so it has no text. ` +
|
|
103
|
+
'Fix the escape (write a literal backslash as `\\\\`).');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const received = Array.isArray(strings) ? 'a plain array' : strings === null ? 'null' : typeof strings;
|
|
107
|
+
throw new ValidationError(`${api} must be called as a tagged template, for example ${api}\`SELECT * FROM users WHERE id = \${id}\`, ` +
|
|
108
|
+
`so that every interpolated value is sent as a bound parameter. It received ${received} instead, whose ` +
|
|
109
|
+
'text would reach the SQL unescaped. To run statement text built at runtime (DDL, a migration file), use ' +
|
|
110
|
+
`\`${unsafeApi}(text, ...values)\` and keep untrusted input out of the text.`);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A `Date` in the form node-postgres sends under `parseInputDatesAsUTC`:
|
|
114
|
+
* `YYYY-MM-DDTHH:MM:SS.mmm+00:00`, with ` BC` for years before 1.
|
|
115
|
+
*/
|
|
116
|
+
function utcDateText(date) {
|
|
117
|
+
const pad = (n, width = 2) => String(n).padStart(width, '0');
|
|
118
|
+
let year = date.getUTCFullYear();
|
|
119
|
+
const bc = year < 1;
|
|
120
|
+
if (bc)
|
|
121
|
+
year = 1 - year;
|
|
122
|
+
return (`${pad(year, 4)}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}` +
|
|
123
|
+
`T${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())}` +
|
|
124
|
+
`.${pad(date.getUTCMilliseconds(), 3)}+00:00${bc ? ' BC' : ''}`);
|
|
125
|
+
}
|
|
126
|
+
function utcDateValue(value, api, position, nested) {
|
|
127
|
+
if (value instanceof Date) {
|
|
128
|
+
if (Number.isNaN(value.getTime())) {
|
|
129
|
+
throw new ValidationError(`${api}: parameter $${position} ${nested ? 'contains' : 'is'} an Invalid Date, which has no SQL value. ` +
|
|
130
|
+
'Pass a valid Date, or null for SQL NULL.');
|
|
131
|
+
}
|
|
132
|
+
return utcDateText(value);
|
|
133
|
+
}
|
|
134
|
+
if (!Array.isArray(value))
|
|
135
|
+
return value;
|
|
136
|
+
let out;
|
|
137
|
+
for (let i = 0; i < value.length; i++) {
|
|
138
|
+
const converted = utcDateValue(value[i], api, position, true);
|
|
139
|
+
if (converted !== value[i]) {
|
|
140
|
+
out ??= value.slice();
|
|
141
|
+
out[i] = converted;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return out ?? value;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Raw-statement params with every `Date` (in arrays too) bound as UTC text,
|
|
148
|
+
* for a client reading zone-less temporal types as UTC (`utcTimestamps`).
|
|
149
|
+
*
|
|
150
|
+
* node-postgres renders a Date in the process's zone with its offset. A
|
|
151
|
+
* `timestamptz` keeps the instant either way, but a `timestamp` or `date`
|
|
152
|
+
* drops the offset and stores the local wall time, which then reads back
|
|
153
|
+
* shifted by the process offset. UTC text stores the instant's UTC fields,
|
|
154
|
+
* which is what a UTC read returns. Returns `params` itself when it holds no
|
|
155
|
+
* Date. An Invalid Date is refused (E003) instead of reaching the server as
|
|
156
|
+
* unparseable text.
|
|
157
|
+
*/
|
|
158
|
+
export function utcDateParams(params, api) {
|
|
159
|
+
let out;
|
|
160
|
+
for (let i = 0; i < params.length; i++) {
|
|
161
|
+
const converted = utcDateValue(params[i], api, i + 1, false);
|
|
162
|
+
if (converted !== params[i]) {
|
|
163
|
+
out ??= params.slice();
|
|
164
|
+
out[i] = converted;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out ?? params;
|
|
168
|
+
}
|
|
81
169
|
/**
|
|
82
170
|
* A pending typed raw SQL query. Implements the thenable contract, so it can be
|
|
83
171
|
* `await`ed directly to get `T[]`, or refined via `.one()` / `.scalar()` first.
|
|
@@ -95,6 +183,7 @@ export class TypedSqlQuery {
|
|
|
95
183
|
logging;
|
|
96
184
|
runScoped;
|
|
97
185
|
onQuery;
|
|
186
|
+
logger;
|
|
98
187
|
constructor(pool, sql, params, logging,
|
|
99
188
|
/**
|
|
100
189
|
* Runs the execution under the owning client's `errorMessages` mode. Passed
|
|
@@ -115,19 +204,22 @@ export class TypedSqlQuery {
|
|
|
115
204
|
* with model `'$raw'` and action `'sql'`. Absent for a `TypedSqlQuery`
|
|
116
205
|
* built outside a client, which then emits nothing, as before.
|
|
117
206
|
*/
|
|
118
|
-
onQuery
|
|
207
|
+
onQuery,
|
|
208
|
+
/** Where the `logging` preview line goes. Defaults to the console logger. */
|
|
209
|
+
logger = consoleLogger) {
|
|
119
210
|
this.pool = pool;
|
|
120
211
|
this.sql = sql;
|
|
121
212
|
this.params = params;
|
|
122
213
|
this.logging = logging;
|
|
123
214
|
this.runScoped = runScoped;
|
|
124
215
|
this.onQuery = onQuery;
|
|
216
|
+
this.logger = logger;
|
|
125
217
|
}
|
|
126
218
|
/** Execute and return all rows. Internal; powers `then`, `one`, and `scalar`. */
|
|
127
219
|
run() {
|
|
128
220
|
return this.runScoped(async () => {
|
|
129
221
|
if (this.logging) {
|
|
130
|
-
|
|
222
|
+
logTo(this.logger, 'debug', `Typed SQL: ${this.sql.trim().substring(0, 120)}...`, { code: 'typed-sql' });
|
|
131
223
|
}
|
|
132
224
|
const result = await runReportedStatement(this.onQuery, rawIdentity('sql'), this.sql, this.params, () => this.pool.query(this.sql, this.params), wrapPgError);
|
|
133
225
|
return result.rows;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turbine-orm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.90.0",
|
|
4
4
|
"description": "Postgres-native TypeScript ORM, runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. One dependency, no WASM engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"//exports": "Each subpath declares its types PER CONDITION. A single shared top-level \"types\" resolves to the ESM declarations for `require` too, which is TS1479 (\"is an ES module ... cannot be require()d\") for any CJS consumer on moduleResolution node16/nodenext. The require condition points at dist/cjs, which ships its own {\"type\":\"commonjs\"} package.json, so those declarations are CJS declarations. Gated in CI by publint + @arethetypeswrong/cli + a real .cts consumer typecheck (see the package-types job in ci.yml).",
|
|
@@ -139,9 +139,9 @@
|
|
|
139
139
|
"//check:coverage-sync": "The three-way coupling between a coverage collection and the floors that hold it. A file can be added to coverage:cli:collect and then be held by NOTHING, which is not a missing gate but a slower lane enforcing a floor of zero, and it has happened twice (mcp.ts and error-catalog.ts each spent a release that way). The invariant is that every file in the collection is named by a per-file gate OR by the aggregate, NOT that the collection and the aggregate are equal: cli/index.ts is deliberately outside the aggregate, see //coverage:cli:gate:aggregate. It also refuses a gate that names a file nobody collects, which measures nothing and passes, and it fails closed on an empty parse. It runs FIRST in test:coverage:cli, ahead of the collection, because it needs no coverage data and its failure names the missing wiring instead of a number.",
|
|
140
140
|
"check:coverage-sync": "node scripts/check-coverage-gate-sync.mjs",
|
|
141
141
|
"//coverage:cli": "The CLI coverage gate, split into ONE collection run plus a per-file threshold check for every file in it, and an aggregate. c8 enforces a single threshold set per invocation and its --per-file applies the SAME numbers to every file, neither of which can express 'destructive.ts holds 100 while migrate.ts holds 70'. An aggregate-only floor lets the least-covered file spend the whole slack the best-covered file earned: at the measured 3623/2951 lines, migrate.ts could fall from 70.3% to 66.6% with the aggregate still green. So each file gets its OWN floor, checked by re-reporting the coverage already on disk (c8 report re-reads ./coverage/tmp, so this costs no extra test run). The aggregate check is kept as well: it catches all three sagging together inside their individual margins. Per-file gates run FIRST because their failure names the file. DATABASE_URL is neutralized on the collection run: these test files include live migration tests that create and drop tables, and this script runs from prepublishOnly.",
|
|
142
|
-
"test:coverage:cli": "npm run check:coverage-sync && npm run coverage:cli:collect && npm run coverage:cli:gate:destructive && npm run coverage:cli:gate:sql-statements && npm run coverage:cli:gate:pii-guard && npm run coverage:cli:gate:error-catalog && npm run coverage:cli:gate:mcp && npm run coverage:cli:gate:compile-query && npm run coverage:cli:gate:studio && npm run coverage:cli:gate:migrate && npm run coverage:cli:gate:index && npm run coverage:cli:gate:aggregate",
|
|
142
|
+
"test:coverage:cli": "npm run check:coverage-sync && npm run coverage:cli:collect && npm run coverage:cli:gate:destructive && npm run coverage:cli:gate:sql-statements && npm run coverage:cli:gate:pii-guard && npm run coverage:cli:gate:error-catalog && npm run coverage:cli:gate:mcp && npm run coverage:cli:gate:compile-query && npm run coverage:cli:gate:local-server-guard && npm run coverage:cli:gate:observe && npm run coverage:cli:gate:studio && npm run coverage:cli:gate:migrate && npm run coverage:cli:gate:index && npm run coverage:cli:gate:aggregate",
|
|
143
143
|
"//coverage:cli:collect": "--check-coverage=false is load-bearing, not tidying. c8 reads .c8rc.json for defaults, so this COLLECTION step was silently enforcing the MAIN gate's global thresholds against a src/cli-only file set. That is a gate nobody wrote and nobody wanted, and it went unnoticed only because the main floor was low enough (75) for the CLI aggregate (89.29) to clear it by accident. When the main floors were re-baselined to 93/89/78 (see //merge-bug in .c8rc.json) it started failing here, several steps before the per-file gates that are supposed to decide. Thresholds for these files belong to the coverage:cli:gate:* scripts, which pass their own explicitly.",
|
|
144
|
-
"coverage:cli:collect": "DATABASE_URL= c8 --all --check-coverage=false --reporter text --exclude 'src/test/**' --include src/cli/studio.ts --include src/cli/migrate.ts --include src/cli/destructive.ts --include src/cli/sql-statements.ts --include src/cli/pii-predicate-guard.ts --include src/cli/mcp.ts --include src/cli/compile-query.ts --include src/cli/error-catalog.ts --include src/cli/index.ts tsx --test src/test/studio-write.test.ts src/test/pii-guard-symmetry.test.ts src/test/studio-demo.test.ts src/test/studio.test.ts src/test/studio-security.test.ts src/test/migrate.test.ts src/test/migrate-deploy.test.ts src/test/migrate-smoke-fixes.test.ts src/test/tracking-table-race.test.ts src/test/destructive-dynamic-assembly.test.ts src/test/destructive-migrations.test.ts src/test/backfill-recipe.test.ts src/test/cli.test.ts src/test/cli-diff-migration.test.ts src/test/cli-flags.test.ts src/test/cli-first-run.test.ts src/test/mcp.test.ts src/test/mcp-relations.test.ts src/test/mcp-pii.test.ts src/test/mcp-pii-round2.test.ts src/test/mcp-agent-tools.test.ts src/test/mcp-compile-query.test.ts src/test/mcp-perimeter-bounds.test.ts src/test/destructive-obfuscation.test.ts src/test/cli-init-schema.test.ts src/test/cli-introspect-options.test.ts src/test/mcp-transport-bounds.test.ts src/test/seed-schema.test.ts src/test/migrate-preview-and-report.test.ts src/test/destructive-quoting-forms.test.ts",
|
|
144
|
+
"coverage:cli:collect": "DATABASE_URL= c8 --all --check-coverage=false --reporter text --exclude 'src/test/**' --include src/cli/studio.ts --include src/cli/migrate.ts --include src/cli/destructive.ts --include src/cli/sql-statements.ts --include src/cli/pii-predicate-guard.ts --include src/cli/mcp.ts --include src/cli/compile-query.ts --include src/cli/error-catalog.ts --include src/cli/index.ts --include src/cli/local-server-guard.ts --include src/cli/observe.ts tsx --test src/test/studio-write.test.ts src/test/pii-guard-symmetry.test.ts src/test/studio-demo.test.ts src/test/studio.test.ts src/test/studio-security.test.ts src/test/migrate.test.ts src/test/migrations-pure.test.ts src/test/migrate-deploy.test.ts src/test/migrate-smoke-fixes.test.ts src/test/tracking-table-race.test.ts src/test/destructive-dynamic-assembly.test.ts src/test/destructive-migrations.test.ts src/test/backfill-recipe.test.ts src/test/cli.test.ts src/test/cli-diff-migration.test.ts src/test/cli-flags.test.ts src/test/cli-first-run.test.ts src/test/mcp.test.ts src/test/mcp-relations.test.ts src/test/mcp-pii.test.ts src/test/mcp-pii-round2.test.ts src/test/mcp-agent-tools.test.ts src/test/mcp-compile-query.test.ts src/test/mcp-perimeter-bounds.test.ts src/test/destructive-obfuscation.test.ts src/test/cli-init-schema.test.ts src/test/cli-introspect-options.test.ts src/test/mcp-transport-bounds.test.ts src/test/seed-schema.test.ts src/test/migrate-preview-and-report.test.ts src/test/destructive-quoting-forms.test.ts src/test/codegen-cli-destructive.test.ts src/test/codegen-cli-config.test.ts src/test/security-perimeter-local-servers.test.ts src/test/security-perimeter-mcp-secret-names.test.ts src/test/observe-cli.test.ts",
|
|
145
145
|
"coverage:cli:gate": "c8 report --all --exclude 'src/test/**' --reporter text --check-coverage",
|
|
146
146
|
"coverage:cli:gate:destructive": "npm run coverage:cli:gate -- --include src/cli/destructive.ts --lines 98 --statements 98 --branches 84 --functions 98",
|
|
147
147
|
"coverage:cli:gate:sql-statements": "npm run coverage:cli:gate -- --include src/cli/sql-statements.ts --lines 100 --statements 100 --branches 98 --functions 100",
|
|
@@ -153,6 +153,9 @@
|
|
|
153
153
|
"coverage:cli:gate:error-catalog": "npm run coverage:cli:gate -- --include src/cli/error-catalog.ts --lines 100 --statements 100 --branches 90 --functions 100",
|
|
154
154
|
"//coverage:cli:gate:index": "cli/index.ts is the largest file in the repo (5,945 lines) and was in no coverage gate at all. The .c8rc.json //cli-gate argument for the other CLI files applies verbatim: coverage could fall to zero without anything noticing. The floors are LOW on purpose and are not a target: this file is argument parsing and command dispatch whose `process.exit` paths dominate any figure set over it, and the point of the gate is that the number cannot fall silently, not that it is high. MEASURED 2026-08-23 by `npm run coverage:cli:collect` (no database), read from the json-summary reporter rather than the text table, per the //cli-gate-measurements note: 32.62 lines / 32.62 statements / 75.62 branches / 20 functions, identical in isolation and in the nine-file collection. Floors sit about two points under. Raise as coverage improves; never lower one without recording why. It is deliberately NOT in coverage:cli:gate:aggregate, see that key's note.",
|
|
155
155
|
"coverage:cli:gate:index": "npm run coverage:cli:gate -- --include src/cli/index.ts --lines 30 --statements 30 --branches 73 --functions 18",
|
|
156
|
+
"coverage:cli:gate:local-server-guard": "npm run coverage:cli:gate -- --include src/cli/local-server-guard.ts --lines 100 --statements 100 --branches 96 --functions 100",
|
|
157
|
+
"//coverage:cli:gate:observe": "cli/observe.ts is the read-only metrics dashboard behind `turbine observe`: a loopback HTTP server with its own session token, one-time login ticket, Host allowlist, Origin check, rate limit and CSP, in front of two queries that run in read-only transactions. It was in no coverage gate at all (about 48 percent of lines were executed by the suites that happened to touch it), while Studio, which shares its perimeter, was. Floors MEASURED 2026-10-13 by `npm run coverage:cli:collect`, read from the json-summary reporter: 100 lines / 100 statements / 99.13 branches / 100 functions. THE COLLECTION BLANKS DATABASE_URL, so the figures are the no-database ones: the live-Postgres tests in observe-cli.test.ts skip here and are not counted, and with a database the figure is 99.14 branches and otherwise identical. That is by design: the fake pool and the fake Postgres wire server in that file reach every line without a database. The one uncovered branch is the non-Error arm of the server's 500 handler (`String(err)`), which a real pg pool never produces. Floors sit 2 points under. Held by this per-file gate only and NOT listed in coverage:cli:gate:aggregate, the same arrangement as local-server-guard.ts: that aggregate was baselined over the larger files and adding a 100 percent file would only move it. Raise as coverage improves; never lower one without recording why.",
|
|
158
|
+
"coverage:cli:gate:observe": "npm run coverage:cli:gate -- --include src/cli/observe.ts --lines 98 --statements 98 --branches 97 --functions 98",
|
|
156
159
|
"coverage:cli:gate:studio": "npm run coverage:cli:gate -- --include src/cli/studio.ts --lines 82 --statements 82 --branches 81 --functions 86",
|
|
157
160
|
"coverage:cli:gate:migrate": "npm run coverage:cli:gate -- --include src/cli/migrate.ts --lines 69 --statements 69 --branches 91 --functions 73",
|
|
158
161
|
"//coverage:cli:gate:aggregate": "Re-baselined 2026-08-15 when compile-query.ts joined the --include list: measured 89.28 lines / 89.28 statements / 86.77 branches / 89.18 functions, up from a 87/87/85/86 floor set over seven files (itself up from 78/78/84/82 over five). Raise as coverage improves, never lower one without recording why. WHY cli/index.ts IS NOT HERE, although it is in coverage:cli:collect and has its own per-file gate: it is 5,945 lines at 32.62%, so folding it in drops this aggregate from a measured 89.63 lines / 87.15 branches / 89.18 functions to 66.32 / 85.76 / 63.38, which would mean LOWERING two floors by about 25 points. That is the rule above broken, and it also empties the aggregate of its purpose. This check exists only as the backstop that catches every file sagging together inside its individual margin; with one 5,945-line file at 32% dominating the denominator, the other eight could collectively shed well over a thousand covered lines with this still green. The invariant the collection actually needs is that every collected file is held by SOME floor, and index.ts is held by coverage:cli:gate:index. That is what check:coverage-sync asserts.",
|
|
@@ -209,7 +212,7 @@
|
|
|
209
212
|
"@size-limit/esbuild": "^12.1.0",
|
|
210
213
|
"@size-limit/file": "^12.1.0",
|
|
211
214
|
"@swc/core": "1.16.2",
|
|
212
|
-
"@types/node": "^
|
|
215
|
+
"@types/node": "^22.20.4",
|
|
213
216
|
"@types/pg": "^8.11.11",
|
|
214
217
|
"@zvndev/powdb-client": "^0.20.0",
|
|
215
218
|
"@zvndev/powdb-embedded": "^0.20.0",
|
|
@@ -101,6 +101,16 @@ Operators: `equals`, `not`, `gt`, `gte`, `lt`, `lte`, `in`, `notIn`, `contains`,
|
|
|
101
101
|
`startsWith`, `endsWith`, plus `mode: 'insensitive'`. `AND`, `OR` and `NOT` nest
|
|
102
102
|
at any depth; `AND` and `OR` take arrays.
|
|
103
103
|
|
|
104
|
+
`not` also takes an operator object: `givenName: { not: { contains: 'Roux' } }`.
|
|
105
|
+
To compare two columns, wrap the other one in `col()`, imported from
|
|
106
|
+
`turbine-orm`: `rindScore: { gt: col('aromaScore') }`. A plain `{ col: 'x' }`
|
|
107
|
+
object is refused with `TURBINE_E003`, and so is an operand that does not fit
|
|
108
|
+
its operator, such as an array for `equals` or `null` for `gt`.
|
|
109
|
+
|
|
110
|
+
A key whose value is `undefined` is left out, as in Prisma. When that empties
|
|
111
|
+
the whole `where`, `findFirst` throws `TURBINE_E003` instead of returning an
|
|
112
|
+
arbitrary row.
|
|
113
|
+
|
|
104
114
|
Values are always bound as parameters, and `contains` / `startsWith` /
|
|
105
115
|
`endsWith` escape LIKE wildcards for you. There is no code path in the typed API
|
|
106
116
|
that concatenates a value into SQL.
|
|
@@ -160,7 +170,10 @@ them. Passing both spellings of one bound with different values throws.
|
|
|
160
170
|
{ take: 20, skip: 40 }
|
|
161
171
|
```
|
|
162
172
|
|
|
163
|
-
For deep pages, prefer a `cursor` over a large `offset`.
|
|
173
|
+
For deep pages, prefer a `cursor` over a large `offset`. A cursor names the
|
|
174
|
+
last row you have by a unique key, `cursor: { id: 41 }`; the page excludes that
|
|
175
|
+
row and continues in the query's `orderBy`, with the primary key added as a
|
|
176
|
+
tiebreaker.
|
|
164
177
|
|
|
165
178
|
## JSON columns
|
|
166
179
|
|
|
@@ -174,8 +187,11 @@ where: { credentialBlob: { path: ['tier'], equals: 'master' } }
|
|
|
174
187
|
A path takes a DIFFERENT operator set from a plain column: `equals`, `gt`,
|
|
175
188
|
`gte`, `lt`, `lte`, `hasKey`, `contains`, `stringContains`, `stringStartsWith`,
|
|
176
189
|
`stringEndsWith`, plus `mode` (which applies to the three `string*` operators).
|
|
177
|
-
`
|
|
178
|
-
`
|
|
190
|
+
`equals` at a path compares JSON values, so `equals: null` matches a JSON
|
|
191
|
+
`null` and not the string `"null"`. Without a `path`, `equals` compares the
|
|
192
|
+
whole document exactly. `contains` on a JSON column is containment (`@>`, a
|
|
193
|
+
whole sub-document such as `{ contains: { panel: { seats: 4 } } }`), NOT a
|
|
194
|
+
substring test; the substring
|
|
179
195
|
operators on a path are `stringContains`, `stringStartsWith` and
|
|
180
196
|
`stringEndsWith`. `not`, `in`, `notIn`, `startsWith` and `endsWith` are refused
|
|
181
197
|
with `TURBINE_E003` listing the accepted set (the last two point at their
|
|
@@ -189,8 +205,9 @@ comes back as `_sum.score`. `_sum` / `_avg` cast the value to numeric; `_min` /
|
|
|
189
205
|
`_max` compare as text unless `type: 'numeric'`. **`aggregate()` has no JSON-path
|
|
190
206
|
form**: every key there must be a column (anything else is `TURBINE_E003`) and
|
|
191
207
|
every value `true`; an object value is read as `true`, so the whole column
|
|
192
|
-
reaches the database as `avg(jsonb)
|
|
193
|
-
|
|
208
|
+
reaches the database as `avg(jsonb)`, which the database refuses. That surfaces
|
|
209
|
+
as `TURBINE_E003` with `sqlstate` `42883`, not as a Turbine check naming the
|
|
210
|
+
path. **`_count` never takes a path**, in `groupBy` or `aggregate()`:
|
|
194
211
|
an object under a `_count` key is read as `true` (a count of that column's
|
|
195
212
|
non-null values) and any path in it is ignored. To count per JSON value, group
|
|
196
213
|
by the path and read `_count`.
|
|
@@ -211,6 +228,11 @@ PostgreSQL's exact text as a **string** (every value, not only large ones; `AVG`
|
|
|
211
228
|
is not float-cast there); over `int4` / `float` columns and over a JSON path they
|
|
212
229
|
return a number. `_min` / `_max` return the column's own type.
|
|
213
230
|
|
|
231
|
+
`_percentile: { aromaScore: { p: 0.9 } }` returns the 90th percentile, on
|
|
232
|
+
PostgreSQL only. `p` may be an array of fractions, and the result is then an
|
|
233
|
+
array in the same order. The default `method: 'cont'` interpolates and is
|
|
234
|
+
refused on a date column with `TURBINE_E003`; use `method: 'disc'` there.
|
|
235
|
+
|
|
214
236
|
## groupBy and having
|
|
215
237
|
|
|
216
238
|
`by` lists the grouping columns. The `having` shape is **column first, aggregate
|
|
@@ -247,7 +269,35 @@ the `ON CONFLICT` target when `create` carries the same values, so a non-unique
|
|
|
247
269
|
predicate is refused rather than sent. When `create` does not carry them (a
|
|
248
270
|
`create` that leaves `id` to its sequence, say), `upsert` looks the row up by
|
|
249
271
|
`where` first, inside a transaction. Use `updateMany` and `deleteMany` for
|
|
250
|
-
"every row matching a filter".
|
|
272
|
+
"every row matching a filter". They take no `limit`, `take`, `offset` or `skip`
|
|
273
|
+
(`TURBINE_E003`): to write a bounded batch, `findMany` the ids first, then pass
|
|
274
|
+
`where: { id: { in: ids } }`.
|
|
275
|
+
|
|
276
|
+
## Computed updates and bulk upserts
|
|
277
|
+
|
|
278
|
+
To set a column from the row's own values, build the value with `expr` and
|
|
279
|
+
`col()`, both imported from `turbine-orm`:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
await db.cheeseWheels.update({
|
|
283
|
+
where: { id: 7 },
|
|
284
|
+
data: { wheelCount: expr.add(col('wheelCount'), 1) },
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
A plain object shaped like an expression (`{ kind: 'add', args: [1, 2] }`) is
|
|
289
|
+
refused with `TURBINE_E003`, and so is an expression in `create` data.
|
|
290
|
+
|
|
291
|
+
`createMany` with `onConflict: { target: ['guildId', 'batchRef'], update:
|
|
292
|
+
['status'] }` inserts the new keys and updates the rows that already exist. The
|
|
293
|
+
target must be the primary key or one whole unique key; any other column list
|
|
294
|
+
is refused with `TURBINE_E003`.
|
|
295
|
+
|
|
296
|
+
## Locking rows
|
|
297
|
+
|
|
298
|
+
`lock: 'update'` on `findMany`, `findFirst` or `findUnique` locks the rows it
|
|
299
|
+
returns until the transaction ends. It must run on the `tx` inside
|
|
300
|
+
`db.$transaction(async (tx) => ...)`; on `db` it throws `TURBINE_E003`.
|
|
251
301
|
|
|
252
302
|
## `distinct`
|
|
253
303
|
|