turbine-orm 0.49.0 → 0.50.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 +122 -39
- package/dist/cjs/adapters/cockroachdb.d.ts +39 -0
- package/dist/cjs/adapters/index.d.ts +110 -0
- package/dist/cjs/adapters/yugabytedb.d.ts +51 -0
- package/dist/cjs/cli/config.d.ts +181 -0
- package/dist/cjs/cli/config.js +32 -6
- package/dist/cjs/cli/destructive.d.ts +38 -0
- package/dist/cjs/cli/index.d.ts +359 -0
- package/dist/cjs/cli/index.js +228 -56
- package/dist/cjs/cli/loader.d.ts +61 -0
- package/dist/cjs/cli/mcp.d.ts +42 -0
- package/dist/cjs/cli/migrate.d.ts +356 -0
- package/dist/cjs/cli/migrate.js +131 -40
- package/dist/cjs/cli/observe-ui.d.ts +1 -0
- package/dist/cjs/cli/observe-ui.js +14 -5
- package/dist/cjs/cli/observe.d.ts +25 -0
- package/dist/cjs/cli/observe.js +49 -12
- package/dist/cjs/cli/pii-tags.d.ts +53 -0
- package/dist/cjs/cli/prisma-report.d.ts +33 -0
- package/dist/cjs/cli/prisma-report.js +73 -0
- package/dist/cjs/cli/prisma-resolve.d.ts +106 -0
- package/dist/cjs/cli/prisma-resolve.js +1 -0
- package/dist/cjs/cli/prisma-schema.d.ts +176 -0
- package/dist/cjs/cli/prisma-schema.js +82 -4
- package/dist/cjs/cli/rate-limit.d.ts +32 -0
- package/dist/cjs/cli/rate-limit.js +45 -0
- package/dist/cjs/cli/studio-demo.d.ts +43 -0
- package/dist/cjs/cli/studio-ui.generated.d.ts +1 -0
- package/dist/cjs/cli/studio.d.ts +207 -0
- package/dist/cjs/cli/studio.js +136 -71
- package/dist/cjs/cli/ui.d.ts +73 -0
- package/dist/cjs/cli/ui.js +51 -9
- package/dist/cjs/client.d.ts +837 -0
- package/dist/cjs/client.js +3 -0
- package/dist/cjs/dialect.d.ts +516 -0
- package/dist/cjs/dialect.js +37 -12
- package/dist/cjs/errors.d.ts +370 -0
- package/dist/cjs/generate.d.ts +137 -0
- package/dist/cjs/generate.js +39 -6
- package/dist/cjs/index-advisor.d.ts +153 -0
- package/dist/cjs/index-stats.d.ts +384 -0
- package/dist/cjs/index.d.ts +55 -0
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/introspect.d.ts +269 -0
- package/dist/cjs/mssql.d.ts +232 -0
- package/dist/cjs/mssql.js +6 -0
- package/dist/cjs/mysql.d.ts +173 -0
- package/dist/cjs/mysql.js +16 -0
- package/dist/cjs/nested-write.d.ts +96 -0
- package/dist/cjs/nested-write.js +414 -24
- package/dist/cjs/observe.d.ts +115 -0
- package/dist/cjs/optional-peer-import.d.cts +72 -0
- package/dist/cjs/pipeline-submittable.d.ts +93 -0
- package/dist/cjs/pipeline.d.ts +71 -0
- package/dist/cjs/powdb-introspect.d.ts +84 -0
- package/dist/cjs/powdb.d.ts +931 -0
- package/dist/cjs/powdb.js +106 -21
- package/dist/cjs/powql.d.ts +592 -0
- package/dist/cjs/powql.js +42 -6
- package/dist/cjs/prisma-compat.d.ts +283 -0
- package/dist/cjs/prisma-compat.js +167 -9
- package/dist/cjs/query/aggregates.d.ts +92 -0
- package/dist/cjs/query/aggregates.js +7 -3
- package/dist/cjs/query/batched-loader.d.ts +193 -0
- package/dist/cjs/query/builder.d.ts +849 -0
- package/dist/cjs/query/builder.js +571 -65
- package/dist/cjs/query/compound-unique.d.ts +51 -0
- package/dist/cjs/query/deferred.d.ts +223 -0
- package/dist/cjs/query/filters.d.ts +201 -0
- package/dist/cjs/query/index.d.ts +14 -0
- package/dist/cjs/query/index.js +6 -1
- package/dist/cjs/query/relations.d.ts +609 -0
- package/dist/cjs/query/relations.js +693 -46
- package/dist/cjs/query/types.d.ts +1300 -0
- package/dist/cjs/query/utils.d.ts +209 -0
- package/dist/cjs/query/utils.js +208 -1
- package/dist/cjs/query/warn-registry.d.ts +68 -0
- package/dist/cjs/query/warn-registry.js +9 -0
- package/dist/cjs/query/where-compile.d.ts +139 -0
- package/dist/cjs/query/where.d.ts +548 -0
- package/dist/cjs/query/where.js +58 -22
- package/dist/cjs/query/writes.d.ts +172 -0
- package/dist/cjs/query/writes.js +105 -12
- package/dist/cjs/realtime.d.ts +70 -0
- package/dist/cjs/schema-builder.d.ts +354 -0
- package/dist/cjs/schema-metadata.d.ts +83 -0
- package/dist/cjs/schema-sql.d.ts +217 -0
- package/dist/cjs/schema-sql.js +23 -5
- package/dist/cjs/schema.d.ts +356 -0
- package/dist/cjs/schema.js +125 -0
- package/dist/cjs/seed.d.ts +15 -0
- package/dist/cjs/serverless.d.ts +142 -0
- package/dist/cjs/sqlite.d.ts +143 -0
- package/dist/cjs/sqlite.js +4 -0
- package/dist/cjs/typed-sql.d.ts +102 -0
- package/dist/cli/config.d.ts +18 -4
- package/dist/cli/config.js +31 -6
- package/dist/cli/index.d.ts +123 -0
- package/dist/cli/index.js +223 -58
- package/dist/cli/migrate.d.ts +59 -10
- package/dist/cli/migrate.js +128 -41
- package/dist/cli/observe-ui.d.ts +1 -1
- package/dist/cli/observe-ui.js +14 -5
- package/dist/cli/observe.d.ts +7 -1
- package/dist/cli/observe.js +48 -12
- package/dist/cli/prisma-report.d.ts +14 -0
- package/dist/cli/prisma-report.js +72 -0
- package/dist/cli/prisma-resolve.d.ts +6 -0
- package/dist/cli/prisma-resolve.js +1 -0
- package/dist/cli/prisma-schema.d.ts +62 -2
- package/dist/cli/prisma-schema.js +81 -4
- package/dist/cli/rate-limit.d.ts +32 -0
- package/dist/cli/rate-limit.js +40 -0
- package/dist/cli/studio.d.ts +5 -5
- package/dist/cli/studio.js +135 -70
- package/dist/cli/ui.d.ts +1 -1
- package/dist/cli/ui.js +51 -9
- package/dist/client.d.ts +40 -0
- package/dist/client.js +3 -0
- package/dist/dialect.d.ts +17 -1
- package/dist/dialect.js +37 -12
- package/dist/generate.js +40 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mssql.js +6 -0
- package/dist/mysql.js +16 -0
- package/dist/nested-write.d.ts +2 -0
- package/dist/nested-write.js +415 -25
- package/dist/powdb.d.ts +4 -2
- package/dist/powdb.js +106 -21
- package/dist/powql.d.ts +5 -0
- package/dist/powql.js +42 -6
- package/dist/prisma-compat.d.ts +2 -0
- package/dist/prisma-compat.js +166 -8
- package/dist/query/aggregates.js +7 -3
- package/dist/query/builder.d.ts +292 -21
- package/dist/query/builder.js +570 -64
- package/dist/query/deferred.d.ts +39 -0
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +173 -5
- package/dist/query/relations.js +688 -47
- package/dist/query/types.d.ts +123 -39
- package/dist/query/utils.d.ts +116 -0
- package/dist/query/utils.js +198 -0
- package/dist/query/warn-registry.d.ts +9 -0
- package/dist/query/warn-registry.js +9 -0
- package/dist/query/where.d.ts +38 -1
- package/dist/query/where.js +58 -23
- package/dist/query/writes.d.ts +42 -1
- package/dist/query/writes.js +104 -13
- package/dist/schema-sql.d.ts +14 -0
- package/dist/schema-sql.js +23 -5
- package/dist/schema.d.ts +38 -0
- package/dist/schema.js +123 -0
- package/dist/sqlite.js +4 -0
- package/package.json +77 -28
package/dist/query/types.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export type OrderDirection = 'asc' | 'desc';
|
|
|
38
38
|
* PowDB's own existing default (loaders / nested projections); it never selects
|
|
39
39
|
* a distinct PowQL code path.
|
|
40
40
|
*/
|
|
41
|
-
export type RelationLoadStrategy = 'join' | 'batched' | 'auto';
|
|
41
|
+
export type RelationLoadStrategy = 'join' | 'batched' | 'auto' | 'flatten';
|
|
42
42
|
/**
|
|
43
43
|
* Reference to ANOTHER COLUMN of the same table inside a where operator,
|
|
44
44
|
* enabling column-to-column comparison:
|
|
@@ -100,34 +100,79 @@ export interface WhereOperator<V = unknown, F extends string = string> {
|
|
|
100
100
|
* - A vector distance filter object ({ distance: { to, metric, lt } }) for pgvector columns
|
|
101
101
|
*/
|
|
102
102
|
export type WhereValue<V = unknown, F extends string = string> = (V extends Array<infer U> ? TypedRelationFilter<U> : V extends Date ? V : V extends object ? V | TypedToOneFilter<V> | WhereClause<V> : V) | WhereOperator<V, F> | JsonFilter | ArrayFilter | TextSearchFilter | VectorFilter | null;
|
|
103
|
-
/**
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Relation filter on a to-many relation property.
|
|
105
|
+
*
|
|
106
|
+
* `UR` is the target entity's own relations map, so a nested relation filter
|
|
107
|
+
* (`posts: { some: { comments: { some: ... } } }`) stays key-checked at depth.
|
|
108
|
+
* It defaults to `{}` so the legacy single-generic spelling still works.
|
|
109
|
+
*/
|
|
110
|
+
export interface TypedRelationFilter<U, UR extends object = {}> {
|
|
111
|
+
some?: WhereClause<U, UR>;
|
|
112
|
+
every?: WhereClause<U, UR>;
|
|
113
|
+
none?: WhereClause<U, UR>;
|
|
108
114
|
}
|
|
109
115
|
/**
|
|
110
116
|
* Relation filter on a to-one relation property. A bare object on a to-one
|
|
111
117
|
* relation key is also accepted at runtime (implicit `is`, Prisma-compatible).
|
|
118
|
+
*
|
|
119
|
+
* `VR` is the target entity's own relations map (see {@link TypedRelationFilter}).
|
|
112
120
|
*/
|
|
113
|
-
export interface TypedToOneFilter<V> {
|
|
114
|
-
is?: WhereClause<V>;
|
|
115
|
-
isNot?: WhereClause<V>;
|
|
121
|
+
export interface TypedToOneFilter<V, VR extends object = {}> {
|
|
122
|
+
is?: WhereClause<V, VR>;
|
|
123
|
+
isNot?: WhereClause<V, VR>;
|
|
116
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* The where-value accepted on a RELATION key, derived from the generated
|
|
127
|
+
* {@link RelationDescriptor} brand: to-many relations take `some`/`every`/`none`,
|
|
128
|
+
* to-one relations take `is`/`isNot` or a bare sub-where (implicit `is`).
|
|
129
|
+
*
|
|
130
|
+
* Legacy generated shapes (`posts: Post[]`, `profile: Profile | null`) carry no
|
|
131
|
+
* brand, so they degrade to `unknown` — the key is still recognised (no typo
|
|
132
|
+
* false-positive) but its value is not checked.
|
|
133
|
+
*/
|
|
134
|
+
type RelationWhereValue<Rel> = Rel extends RelationDescriptor<infer Target, infer Cardinality, infer TR> ? Cardinality extends 'many' ? TypedRelationFilter<Target, TR & object> : TypedToOneFilter<Target, TR & object> | WhereClause<Target, TR & object> : unknown;
|
|
117
135
|
/**
|
|
118
136
|
* Where clause type: each field can be a plain value, null, or operator object.
|
|
119
|
-
* Special keys: OR
|
|
120
|
-
*
|
|
137
|
+
* Special keys: OR / AND / NOT.
|
|
138
|
+
*
|
|
139
|
+
* **Key checking.** Historically this type carried a
|
|
140
|
+
* `[relationName: string]: unknown` index signature so relation filters
|
|
141
|
+
* (`where: { posts: { some: ... } }`) would typecheck — the relation names are
|
|
142
|
+
* NOT keys of the entity `T`. That index signature also annihilated
|
|
143
|
+
* excess-property checking, so `where: { emial: 'x' }` compiled silently.
|
|
144
|
+
*
|
|
145
|
+
* Passing `R` (the generated `*Relations` map, which every typed client
|
|
146
|
+
* already threads through `QueryInterface<T, R>`) removes the need for the
|
|
147
|
+
* index signature: the relation keys become real, explicitly enumerated
|
|
148
|
+
* properties, so a misspelled column OR relation is a compile error at the
|
|
149
|
+
* offending key while every legitimate filter shape still typechecks.
|
|
150
|
+
*
|
|
151
|
+
* When `R` is omitted / `{}` (the untyped escape hatch, `defineSchema`-only
|
|
152
|
+
* clients, and any call site that does not thread the relations map) the type
|
|
153
|
+
* degrades to exactly its historical permissive form.
|
|
121
154
|
*/
|
|
122
|
-
export type WhereClause<T> =
|
|
155
|
+
export type WhereClause<T, R extends object = {}> = [keyof R] extends [never] ? LooseWhereClause<T> : StrictWhereClause<T, R>;
|
|
156
|
+
/** The historical, index-signature-carrying where clause. See {@link WhereClause}. */
|
|
157
|
+
export type LooseWhereClause<T> = {
|
|
123
158
|
[K in keyof T]?: WhereValue<T[K], Extract<keyof T, string>>;
|
|
124
159
|
} & {
|
|
125
|
-
OR?:
|
|
126
|
-
AND?:
|
|
127
|
-
NOT?:
|
|
160
|
+
OR?: LooseWhereClause<T>[];
|
|
161
|
+
AND?: LooseWhereClause<T>[];
|
|
162
|
+
NOT?: LooseWhereClause<T>;
|
|
128
163
|
/** Relation filters — keyed by relation name, value is { some, every, none } */
|
|
129
164
|
[relationName: string]: unknown;
|
|
130
165
|
};
|
|
166
|
+
/** Key-checked where clause: column keys from `T`, relation keys from `R`, no index signature. */
|
|
167
|
+
type StrictWhereClause<T, R extends object> = {
|
|
168
|
+
[K in keyof T]?: WhereValue<T[K], Extract<keyof T, string>>;
|
|
169
|
+
} & {
|
|
170
|
+
[K in keyof R]?: RelationWhereValue<R[K]>;
|
|
171
|
+
} & {
|
|
172
|
+
OR?: WhereClause<T, R>[];
|
|
173
|
+
AND?: WhereClause<T, R>[];
|
|
174
|
+
NOT?: WhereClause<T, R>;
|
|
175
|
+
};
|
|
131
176
|
/**
|
|
132
177
|
* Client-level automatic WHERE filters, keyed by table accessor (the name used
|
|
133
178
|
* in `db[name]` / `client.table(name)`). Each value is AND-merged into the
|
|
@@ -178,7 +223,7 @@ export interface WithClause {
|
|
|
178
223
|
* `WithResult` inference.
|
|
179
224
|
*/
|
|
180
225
|
export type TypedWithClause<R extends object = {}> = [keyof R] extends [never] ? WithClause : {
|
|
181
|
-
[K in keyof R]?: true | WithOptions<RelationRelations<R[K]> & object
|
|
226
|
+
[K in keyof R]?: true | WithOptions<RelationRelations<R[K]> & object, RelationTarget<R[K]>>;
|
|
182
227
|
} & {
|
|
183
228
|
/** Reserved: correlated relation counts. `true` counts all to-many relations. */
|
|
184
229
|
_count?: true | {
|
|
@@ -200,9 +245,19 @@ export type TypedWithClause<R extends object = {}> = [keyof R] extends [never] ?
|
|
|
200
245
|
* {@link WithOptions.orderBy} is `WithOrderByObject[]`.
|
|
201
246
|
*/
|
|
202
247
|
export type WithOrderByObject = Record<string, OrderDirection | OrderBySpec | JsonPathOrderBy | RelationOrderBy>;
|
|
203
|
-
|
|
248
|
+
/**
|
|
249
|
+
* The `where` accepted inside a relation `with` block. When the relation
|
|
250
|
+
* target entity is known (a typed client, via the {@link RelationDescriptor}
|
|
251
|
+
* brand the generator emits) this is a key-checked {@link WhereClause} over the
|
|
252
|
+
* TARGET entity and its own relations, so `with: { posts: { where: { titel } } }`
|
|
253
|
+
* is a compile error. When the target is unknown (the untyped
|
|
254
|
+
* {@link WithClause} escape hatch) it degrades to the historical open record.
|
|
255
|
+
*/
|
|
256
|
+
export type WithWhere<NestedT, NestedR extends object> = [unknown] extends [NestedT] ? Record<string, unknown> : WhereClause<NestedT & object, NestedR>;
|
|
257
|
+
export interface WithOptions<NestedR extends object = {}, NestedT = unknown> {
|
|
204
258
|
with?: TypedWithClause<NestedR>;
|
|
205
|
-
|
|
259
|
+
/** Filter the related rows. Keys are checked against the relation target when it is known (see {@link WithWhere}). */
|
|
260
|
+
where?: WithWhere<NestedT, NestedR>;
|
|
206
261
|
/**
|
|
207
262
|
* Order the related rows. Accepts a single object (`{ a: 'asc', b: 'desc' }`)
|
|
208
263
|
* or a Prisma-style array of objects (`[{ a: 'asc' }, { b: 'desc' }]`, whose
|
|
@@ -316,6 +371,28 @@ type WithRelationAdditions<T, R extends object, W> = [keyof W & keyof R] extends
|
|
|
316
371
|
type CountResult<C> = C extends true ? Record<string, number> : C extends object ? {
|
|
317
372
|
[K in keyof C]: number;
|
|
318
373
|
} : Record<string, number>;
|
|
374
|
+
/**
|
|
375
|
+
* Key-validating companion for an inferred `select` / `omit` flag map.
|
|
376
|
+
*
|
|
377
|
+
* `select?: S` alone can never reject a typo: `S` is inferred FROM the object
|
|
378
|
+
* literal, so `{ emial: true }` simply becomes the inferred type and a
|
|
379
|
+
* misspelled column silently narrows the result to `Pick<T, never>`. Writing
|
|
380
|
+
* the property as `S & FieldFlags<T, S>` keeps `S` inferred exactly as before
|
|
381
|
+
* (the naked `S` member is still the inference site, so literal `true` values
|
|
382
|
+
* and the `TrueKeys` result narrowing are unchanged) while the mapped member
|
|
383
|
+
* maps every key that is NOT a field of `T` to `never`, which makes the
|
|
384
|
+
* supplied `true` unassignable and reports the typo at the offending key.
|
|
385
|
+
*
|
|
386
|
+
* A key that IS a field of `T` maps to plain `boolean`, so every legitimate
|
|
387
|
+
* flag map (`{ id: true }`, `{ id: true, email: false }`, `{}`) is unaffected.
|
|
388
|
+
*
|
|
389
|
+
* An OPEN flag map (`Record<string, boolean>`, which is what the internal
|
|
390
|
+
* pass-through signatures instantiate `S` with) has nothing to validate: it is
|
|
391
|
+
* returned unchanged so those instantiations stay assignable to each other.
|
|
392
|
+
*/
|
|
393
|
+
export type FieldFlags<T, S> = S extends object ? string extends keyof S ? S : {
|
|
394
|
+
[K in keyof S]: K extends keyof T ? boolean : never;
|
|
395
|
+
} : S;
|
|
319
396
|
/** Extract keys from a boolean record where the value is `true`. */
|
|
320
397
|
type TrueKeys<S extends Record<string, boolean>> = {
|
|
321
398
|
[K in keyof S]: S[K] extends true ? K : never;
|
|
@@ -339,9 +416,12 @@ export type FieldResult<T, S extends Record<string, boolean> | undefined, O exte
|
|
|
339
416
|
*/
|
|
340
417
|
export type QueryResult<T, R extends object, W, S extends Record<string, boolean> | undefined, O extends Record<string, boolean> | undefined> = S extends undefined ? O extends undefined ? WithResult<T, R, W> : O extends Record<string, boolean> ? Omit<WithResult<T, R, W>, Exclude<Extract<keyof T, TrueKeys<O>>, keyof W>> : WithResult<T, R, W> : S extends Record<string, boolean> ? Pick<WithResult<T, R, W>, Extract<keyof T, TrueKeys<S>> | Exclude<keyof WithResult<T, R, W>, keyof T> | Extract<keyof W, keyof WithResult<T, R, W>>> : WithResult<T, R, W>;
|
|
341
418
|
export interface FindUniqueArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
419
|
+
/** Row selector. Keys are checked against `T` and `R` (see {@link WhereClause}). */
|
|
420
|
+
where: WhereClause<T, R>;
|
|
421
|
+
/** Only return these fields. Keys are checked against `T` (see {@link FieldFlags}). */
|
|
422
|
+
select?: S & FieldFlags<T, S>;
|
|
423
|
+
/** Exclude these fields. Keys are checked against `T` (see {@link FieldFlags}). */
|
|
424
|
+
omit?: O & FieldFlags<T, O>;
|
|
345
425
|
with?: W;
|
|
346
426
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
347
427
|
timeout?: number;
|
|
@@ -355,9 +435,12 @@ export interface FindUniqueArgs<T, R extends object = {}, W extends TypedWithCla
|
|
|
355
435
|
includePii?: boolean;
|
|
356
436
|
}
|
|
357
437
|
export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined> {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
438
|
+
/** Row filter. Keys are checked against `T` and `R` (see {@link WhereClause}). */
|
|
439
|
+
where?: WhereClause<T, R>;
|
|
440
|
+
/** Only return these fields. Keys are checked against `T` (see {@link FieldFlags}). */
|
|
441
|
+
select?: S & FieldFlags<T, S>;
|
|
442
|
+
/** Exclude these fields. Keys are checked against `T` (see {@link FieldFlags}). */
|
|
443
|
+
omit?: O & FieldFlags<T, O>;
|
|
361
444
|
orderBy?: OrderByClause;
|
|
362
445
|
limit?: number;
|
|
363
446
|
offset?: number;
|
|
@@ -465,7 +548,8 @@ export type UpdateInput<T> = {
|
|
|
465
548
|
[K in keyof T]?: T[K] | UpdateOperatorInput<T[K]>;
|
|
466
549
|
};
|
|
467
550
|
export interface UpdateArgs<T, R extends object = {}> {
|
|
468
|
-
|
|
551
|
+
/** Row selector. Keys are checked against `T` and `R` (see {@link WhereClause}). */
|
|
552
|
+
where: WhereClause<T, R>;
|
|
469
553
|
/**
|
|
470
554
|
* Update data. On typed clients, relation names additionally accept nested
|
|
471
555
|
* write ops ({@link NestedUpdateOp}): `create` / `connect` / `connectOrCreate`
|
|
@@ -506,8 +590,8 @@ export interface UpdateArgs<T, R extends object = {}> {
|
|
|
506
590
|
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
507
591
|
skipGlobalFilters?: SkipGlobalFilters;
|
|
508
592
|
}
|
|
509
|
-
export interface UpdateManyArgs<T> {
|
|
510
|
-
where: WhereClause<T>;
|
|
593
|
+
export interface UpdateManyArgs<T, R extends object = {}> {
|
|
594
|
+
where: WhereClause<T, R>;
|
|
511
595
|
data: UpdateInput<T>;
|
|
512
596
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
513
597
|
timeout?: number;
|
|
@@ -516,8 +600,8 @@ export interface UpdateManyArgs<T> {
|
|
|
516
600
|
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
517
601
|
skipGlobalFilters?: SkipGlobalFilters;
|
|
518
602
|
}
|
|
519
|
-
export interface DeleteArgs<T> {
|
|
520
|
-
where: WhereClause<T>;
|
|
603
|
+
export interface DeleteArgs<T, R extends object = {}> {
|
|
604
|
+
where: WhereClause<T, R>;
|
|
521
605
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
522
606
|
timeout?: number;
|
|
523
607
|
/** See {@link UpdateArgs.allowFullTableScan}. */
|
|
@@ -525,8 +609,8 @@ export interface DeleteArgs<T> {
|
|
|
525
609
|
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
526
610
|
skipGlobalFilters?: SkipGlobalFilters;
|
|
527
611
|
}
|
|
528
|
-
export interface DeleteManyArgs<T> {
|
|
529
|
-
where: WhereClause<T>;
|
|
612
|
+
export interface DeleteManyArgs<T, R extends object = {}> {
|
|
613
|
+
where: WhereClause<T, R>;
|
|
530
614
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
531
615
|
timeout?: number;
|
|
532
616
|
/** See {@link UpdateArgs.allowFullTableScan}. */
|
|
@@ -534,8 +618,8 @@ export interface DeleteManyArgs<T> {
|
|
|
534
618
|
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
535
619
|
skipGlobalFilters?: SkipGlobalFilters;
|
|
536
620
|
}
|
|
537
|
-
export interface UpsertArgs<T> {
|
|
538
|
-
where: WhereClause<T>;
|
|
621
|
+
export interface UpsertArgs<T, R extends object = {}> {
|
|
622
|
+
where: WhereClause<T, R>;
|
|
539
623
|
create: Partial<T>;
|
|
540
624
|
update: Partial<T>;
|
|
541
625
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
@@ -594,8 +678,8 @@ export type CreateDataInput<T, R extends object = {}> = [keyof R] extends [never
|
|
|
594
678
|
export type UpdateDataInput<T, R extends object = {}> = [keyof R] extends [never] ? UpdateInput<T> : UpdateInput<T> & {
|
|
595
679
|
[K in keyof R]?: NestedUpdateOp<RelationTarget<R[K]> & object, RelationRelations<R[K]> & object>;
|
|
596
680
|
};
|
|
597
|
-
export interface CountArgs<T> {
|
|
598
|
-
where?: WhereClause<T>;
|
|
681
|
+
export interface CountArgs<T, R extends object = {}> {
|
|
682
|
+
where?: WhereClause<T, R>;
|
|
599
683
|
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
600
684
|
timeout?: number;
|
|
601
685
|
/** Opt out of configured {@link GlobalFilters}. See {@link SkipGlobalFilters}. */
|
|
@@ -751,10 +835,10 @@ export interface GroupByOrderBy {
|
|
|
751
835
|
/** A by-column field name or JSON group-key alias → direction or sort spec. */
|
|
752
836
|
[key: string]: OrderDirection | OrderBySpec | GroupByAggregateOrderBy | undefined;
|
|
753
837
|
}
|
|
754
|
-
export interface GroupByArgs<T> {
|
|
838
|
+
export interface GroupByArgs<T, R extends object = {}> {
|
|
755
839
|
/** Group keys: plain column field names and/or JSON-path keys ({@link JsonPathGroupKey}). */
|
|
756
840
|
by: ((keyof T & string) | JsonPathGroupKey)[];
|
|
757
|
-
where?: WhereClause<T>;
|
|
841
|
+
where?: WhereClause<T, R>;
|
|
758
842
|
/**
|
|
759
843
|
* PostgreSQL only: group over one representative row per column combination
|
|
760
844
|
* (`SELECT DISTINCT ON … ORDER BY …` row source). See {@link GroupByDistinctOn}.
|
|
@@ -883,8 +967,8 @@ type GroupByCountPart<A> = A extends {
|
|
|
883
967
|
_count: number;
|
|
884
968
|
};
|
|
885
969
|
/** Arguments for the standalone aggregate method */
|
|
886
|
-
export interface AggregateArgs<T> {
|
|
887
|
-
where?: WhereClause<T>;
|
|
970
|
+
export interface AggregateArgs<T, R extends object = {}> {
|
|
971
|
+
where?: WhereClause<T, R>;
|
|
888
972
|
/**
|
|
889
973
|
* Count rows. `true` → `_count: number` (COUNT(*)). The record form counts per
|
|
890
974
|
* selection: the reserved `_all: true` key → COUNT(*), and each entity field
|
package/dist/query/utils.d.ts
CHANGED
|
@@ -75,6 +75,61 @@ export declare const OPERATOR_KEYS: Set<string>;
|
|
|
75
75
|
* For multi-column: `"alias"."col_a" = "parent"."ref_a" AND "alias"."col_b" = "parent"."ref_b"`
|
|
76
76
|
*/
|
|
77
77
|
export declare function buildCorrelation(leftRef: string, leftColumns: string | string[], rightRef: string, rightColumns: string | string[]): string;
|
|
78
|
+
/**
|
|
79
|
+
* Render a JS `Date` as a TIME-OF-DAY literal for a `time` / `timetz` column.
|
|
80
|
+
*
|
|
81
|
+
* Which time of day? The **UTC** components of the Date, never the process
|
|
82
|
+
* local zone. That is what Prisma does (`new Date('1970-01-01T09:00:00Z')`
|
|
83
|
+
* written to a `@db.Time(6)` column stores `09:00:00`), and the affected
|
|
84
|
+
* consumers are porting from Prisma, so Prisma is the contract. It is also the
|
|
85
|
+
* only choice that round-trips: the same Date produces the same literal no
|
|
86
|
+
* matter where the process runs.
|
|
87
|
+
*
|
|
88
|
+
* `timetz` gets an explicit `+00:00`, because the value's zone IS UTC and
|
|
89
|
+
* omitting it would let Postgres attach the session's `TimeZone` instead.
|
|
90
|
+
* Fractional seconds are emitted only when non-zero, so an even-second Date
|
|
91
|
+
* binds the plain `HH:MM:SS` form.
|
|
92
|
+
*/
|
|
93
|
+
export declare function toTimeOfDayLiteral(value: Date, kind: 'time' | 'timetz'): string;
|
|
94
|
+
/** The temporal column shapes that need a bound Date rewritten to a literal. */
|
|
95
|
+
export type TemporalBindKind = 'time' | 'timetz' | 'date' | 'timestamp';
|
|
96
|
+
/**
|
|
97
|
+
* Render a JS `Date` as a literal for a zone-less `date` / `timestamp` column,
|
|
98
|
+
* using the value's **UTC** components.
|
|
99
|
+
*
|
|
100
|
+
* This is the write-side mirror of `parseDbDate`, which reads an offset-less
|
|
101
|
+
* database value back as UTC. Without it the driver serializes the Date with
|
|
102
|
+
* the PROCESS's offset (`prepareValue` → `dateToString`), so a `timestamp`
|
|
103
|
+
* column is not round-trip stable outside a UTC process: writing
|
|
104
|
+
* `2026-07-25T00:00Z` from `America/Los_Angeles` stores
|
|
105
|
+
* `2026-07-24 17:00:00` and reads back as `2026-07-24T17:00Z`. It also matches
|
|
106
|
+
* the choice {@link toTimeOfDayLiteral} already makes for `time` columns, and
|
|
107
|
+
* Prisma, which writes UTC components to zone-less columns.
|
|
108
|
+
*
|
|
109
|
+
* `timestamptz` is NOT handled here (and must not be): it stores a real
|
|
110
|
+
* instant, so the driver's local-offset string is already correct.
|
|
111
|
+
*/
|
|
112
|
+
export declare function toLocalDateTimeLiteral(value: Date, kind: 'date' | 'timestamp'): string;
|
|
113
|
+
/**
|
|
114
|
+
* Classify a column's database type for temporal bind rewriting.
|
|
115
|
+
*
|
|
116
|
+
* `utcDateTimes: false` restricts the classification to the time-of-day types,
|
|
117
|
+
* whose rewrite is a hard-error fix (Postgres rejects an ISO timestamp for a
|
|
118
|
+
* `time` column outright) rather than a value correction.
|
|
119
|
+
*/
|
|
120
|
+
export declare function temporalBindKind(dbType: string | undefined, utcDateTimes?: boolean): TemporalBindKind | null;
|
|
121
|
+
/**
|
|
122
|
+
* Rewrite one bound value for a temporal column: a JS `Date` on a `time` /
|
|
123
|
+
* `timetz` / `date` / `timestamp` column becomes the corresponding UTC literal,
|
|
124
|
+
* and an array of Dates on such a column is rewritten element-wise (the
|
|
125
|
+
* per-element rewrite is what a `time[]` column needs, and matches the scalar
|
|
126
|
+
* case rather than silently binding an ISO timestamp).
|
|
127
|
+
*
|
|
128
|
+
* Everything else — every non-Date, every non-temporal column, and every
|
|
129
|
+
* `timestamptz` column — is returned by IDENTITY, so this is a byte-for-byte
|
|
130
|
+
* no-op outside the shapes above.
|
|
131
|
+
*/
|
|
132
|
+
export declare function coerceTemporalValue(dbType: string | undefined, value: unknown, utcDateTimes?: boolean): unknown;
|
|
78
133
|
/**
|
|
79
134
|
* Parse a database date-time string deterministically.
|
|
80
135
|
*
|
|
@@ -91,3 +146,64 @@ export declare function buildCorrelation(leftRef: string, leftColumns: string |
|
|
|
91
146
|
* as-is.
|
|
92
147
|
*/
|
|
93
148
|
export declare function parseDbDate(value: string): Date;
|
|
149
|
+
/**
|
|
150
|
+
* Postgres type name → OID, for every type family whose `json_build_object`
|
|
151
|
+
* rendering is NOT the value the pg driver produces for the same column.
|
|
152
|
+
*
|
|
153
|
+
* Why this table exists: the `'join'` strategy reads a relation through
|
|
154
|
+
* `json_agg(json_build_object(...))`, so its values are whatever
|
|
155
|
+
* `JSON.parse` makes of Postgres's JSON rendering. Every other read path in
|
|
156
|
+
* the library — a top-level row, `'batched'`, `'flatten'` — reads the column
|
|
157
|
+
* through the driver and gets the driver's representation. Measured against
|
|
158
|
+
* PostgreSQL 17, those two disagree for exactly the families below, which
|
|
159
|
+
* made the SAME query return a different JS type depending on which plan ran
|
|
160
|
+
* (and `'auto'` picks the plan from a row-count heuristic, so it could differ
|
|
161
|
+
* between two runs of one query). Three of these are lossy, not merely
|
|
162
|
+
* different:
|
|
163
|
+
*
|
|
164
|
+
* type driver (target) json_build_object
|
|
165
|
+
* ──────────── ──────────────────────────── ─────────────────────────────
|
|
166
|
+
* numeric '1000.50' (string) 1000.5 (number, LOSSY)
|
|
167
|
+
* int8 '9007199254740993' 9007199254740992 (LOSSY)
|
|
168
|
+
* bytea Buffer '\xdeadbeef' (string)
|
|
169
|
+
* date Date (local midnight) Date (UTC midnight, off by tz)
|
|
170
|
+
* interval { days, hours, … } '1 day 02:03:04' (string)
|
|
171
|
+
* point { x, y } '(1,2)' (string)
|
|
172
|
+
* circle { x, y, radius } '<(1,2),3>' (string)
|
|
173
|
+
*
|
|
174
|
+
* The array forms diverge the same way, plus `_timestamp`/`_timestamptz`
|
|
175
|
+
* (driver: `Date[]`; JSON: `string[]`) — the scalar `timestamp` /
|
|
176
|
+
* `timestamptz` are deliberately ABSENT because the existing `dateColumns`
|
|
177
|
+
* coercion in `parseRow` already lands them on the driver's value, and they
|
|
178
|
+
* are the hottest column type in a typical schema (no reason to add a cast to
|
|
179
|
+
* every `created_at`).
|
|
180
|
+
*
|
|
181
|
+
* The fix these OIDs drive: emit the column as `col::text` inside
|
|
182
|
+
* `json_build_object` so the JSON carries the same wire text the driver would
|
|
183
|
+
* receive, then run the DRIVER'S OWN parser for that OID over it. Parity is
|
|
184
|
+
* then by construction rather than by coincidence, and it automatically
|
|
185
|
+
* honours a caller's `pg.types.setTypeParser` (including the int8 parser
|
|
186
|
+
* TurbineClient itself registers) instead of second-guessing it.
|
|
187
|
+
*
|
|
188
|
+
* Postgres-only: the JSON functions and the divergence set are both
|
|
189
|
+
* engine-specific, so callers gate this on the postgres dialect.
|
|
190
|
+
*/
|
|
191
|
+
export declare const JSON_WIRE_COERCION_OIDS: Readonly<Record<string, number>>;
|
|
192
|
+
/**
|
|
193
|
+
* The OID whose driver parser reproduces `pgType`'s driver representation from
|
|
194
|
+
* its text rendering, or `undefined` when the type's JSON rendering already
|
|
195
|
+
* matches the driver (the common case: text, uuid, bool, int4, float8, json,
|
|
196
|
+
* jsonb, arrays of those, …).
|
|
197
|
+
*/
|
|
198
|
+
export declare function jsonWireCoercionOid(pgType: string | undefined): number | undefined;
|
|
199
|
+
/**
|
|
200
|
+
* Apply the driver's text parser for `oid` to a JSON-sourced wire string.
|
|
201
|
+
*
|
|
202
|
+
* Resolved through `pg.types.getTypeParser` on every call rather than
|
|
203
|
+
* memoized: parser registration is process-global and happens in the
|
|
204
|
+
* TurbineClient constructor (int8, and `timestamp` under `utcTimestamps`), and
|
|
205
|
+
* a caller may register their own at any point. A stale memo would silently
|
|
206
|
+
* reintroduce the very divergence this exists to remove. The lookup is a plain
|
|
207
|
+
* object index in pg-types, so it is not worth caching.
|
|
208
|
+
*/
|
|
209
|
+
export declare function coerceJsonWireValue(oid: number, value: unknown): unknown;
|
package/dist/query/utils.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Standalone utility functions and classes used by the query builder.
|
|
5
5
|
*/
|
|
6
|
+
import pg from 'pg';
|
|
7
|
+
import { localDateTimeKind, timeOfDayKind } from '../schema.js';
|
|
6
8
|
// ---------------------------------------------------------------------------
|
|
7
9
|
// Identifier quoting — prevents SQL injection via table/column names
|
|
8
10
|
// ---------------------------------------------------------------------------
|
|
@@ -141,6 +143,114 @@ export function buildCorrelation(leftRef, leftColumns, rightRef, rightColumns) {
|
|
|
141
143
|
.map((col, i) => `${leftRef}.${quoteIdent(col)} = ${rightRef}.${quoteIdent(rightCols[i])}`)
|
|
142
144
|
.join(' AND ');
|
|
143
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Render a JS `Date` as a TIME-OF-DAY literal for a `time` / `timetz` column.
|
|
148
|
+
*
|
|
149
|
+
* Which time of day? The **UTC** components of the Date, never the process
|
|
150
|
+
* local zone. That is what Prisma does (`new Date('1970-01-01T09:00:00Z')`
|
|
151
|
+
* written to a `@db.Time(6)` column stores `09:00:00`), and the affected
|
|
152
|
+
* consumers are porting from Prisma, so Prisma is the contract. It is also the
|
|
153
|
+
* only choice that round-trips: the same Date produces the same literal no
|
|
154
|
+
* matter where the process runs.
|
|
155
|
+
*
|
|
156
|
+
* `timetz` gets an explicit `+00:00`, because the value's zone IS UTC and
|
|
157
|
+
* omitting it would let Postgres attach the session's `TimeZone` instead.
|
|
158
|
+
* Fractional seconds are emitted only when non-zero, so an even-second Date
|
|
159
|
+
* binds the plain `HH:MM:SS` form.
|
|
160
|
+
*/
|
|
161
|
+
export function toTimeOfDayLiteral(value, kind) {
|
|
162
|
+
const pad = (n, width = 2) => String(n).padStart(width, '0');
|
|
163
|
+
const ms = value.getUTCMilliseconds();
|
|
164
|
+
const literal = `${pad(value.getUTCHours())}:${pad(value.getUTCMinutes())}:${pad(value.getUTCSeconds())}` +
|
|
165
|
+
(ms === 0 ? '' : `.${pad(ms, 3)}`);
|
|
166
|
+
return kind === 'timetz' ? `${literal}+00:00` : literal;
|
|
167
|
+
}
|
|
168
|
+
/** Render the UTC calendar date of a `Date` as `YYYY-MM-DD`. */
|
|
169
|
+
function utcDatePart(value) {
|
|
170
|
+
const year = value.getUTCFullYear();
|
|
171
|
+
const y = year < 0 ? `-${String(-year).padStart(4, '0')}` : String(year).padStart(4, '0');
|
|
172
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
173
|
+
return `${y}-${pad(value.getUTCMonth() + 1)}-${pad(value.getUTCDate())}`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Render a JS `Date` as a literal for a zone-less `date` / `timestamp` column,
|
|
177
|
+
* using the value's **UTC** components.
|
|
178
|
+
*
|
|
179
|
+
* This is the write-side mirror of `parseDbDate`, which reads an offset-less
|
|
180
|
+
* database value back as UTC. Without it the driver serializes the Date with
|
|
181
|
+
* the PROCESS's offset (`prepareValue` → `dateToString`), so a `timestamp`
|
|
182
|
+
* column is not round-trip stable outside a UTC process: writing
|
|
183
|
+
* `2026-07-25T00:00Z` from `America/Los_Angeles` stores
|
|
184
|
+
* `2026-07-24 17:00:00` and reads back as `2026-07-24T17:00Z`. It also matches
|
|
185
|
+
* the choice {@link toTimeOfDayLiteral} already makes for `time` columns, and
|
|
186
|
+
* Prisma, which writes UTC components to zone-less columns.
|
|
187
|
+
*
|
|
188
|
+
* `timestamptz` is NOT handled here (and must not be): it stores a real
|
|
189
|
+
* instant, so the driver's local-offset string is already correct.
|
|
190
|
+
*/
|
|
191
|
+
export function toLocalDateTimeLiteral(value, kind) {
|
|
192
|
+
const datePart = utcDatePart(value);
|
|
193
|
+
if (kind === 'date')
|
|
194
|
+
return datePart;
|
|
195
|
+
return `${datePart} ${toTimeOfDayLiteral(value, 'time')}`;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Classify a column's database type for temporal bind rewriting.
|
|
199
|
+
*
|
|
200
|
+
* `utcDateTimes: false` restricts the classification to the time-of-day types,
|
|
201
|
+
* whose rewrite is a hard-error fix (Postgres rejects an ISO timestamp for a
|
|
202
|
+
* `time` column outright) rather than a value correction.
|
|
203
|
+
*/
|
|
204
|
+
export function temporalBindKind(dbType, utcDateTimes = true) {
|
|
205
|
+
const timeKind = timeOfDayKind(dbType);
|
|
206
|
+
if (timeKind)
|
|
207
|
+
return timeKind;
|
|
208
|
+
return utcDateTimes ? localDateTimeKind(dbType) : null;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Rewrite one bound value for a temporal column: a JS `Date` on a `time` /
|
|
212
|
+
* `timetz` / `date` / `timestamp` column becomes the corresponding UTC literal,
|
|
213
|
+
* and an array of Dates on such a column is rewritten element-wise (the
|
|
214
|
+
* per-element rewrite is what a `time[]` column needs, and matches the scalar
|
|
215
|
+
* case rather than silently binding an ISO timestamp).
|
|
216
|
+
*
|
|
217
|
+
* Everything else — every non-Date, every non-temporal column, and every
|
|
218
|
+
* `timestamptz` column — is returned by IDENTITY, so this is a byte-for-byte
|
|
219
|
+
* no-op outside the shapes above.
|
|
220
|
+
*/
|
|
221
|
+
export function coerceTemporalValue(dbType, value, utcDateTimes = true) {
|
|
222
|
+
const isDate = value instanceof Date;
|
|
223
|
+
if (!isDate && !Array.isArray(value))
|
|
224
|
+
return value;
|
|
225
|
+
if (isDate && Number.isNaN(value.getTime()))
|
|
226
|
+
return value;
|
|
227
|
+
// An array value is either an `in`/`notIn` list on a scalar temporal column
|
|
228
|
+
// (type already the element type) or the value of an array column, whose
|
|
229
|
+
// introspected type is the `_time` / `_timestamp` array spelling.
|
|
230
|
+
const kind = temporalBindKind(isDate ? dbType : arrayElementDbType(dbType), utcDateTimes);
|
|
231
|
+
if (!kind)
|
|
232
|
+
return value;
|
|
233
|
+
if (isDate)
|
|
234
|
+
return renderTemporal(value, kind);
|
|
235
|
+
// Rewrite only if the list actually holds a Date, so a string list stays
|
|
236
|
+
// byte-identical (and the same array instance is returned).
|
|
237
|
+
if (!value.some((v) => v instanceof Date))
|
|
238
|
+
return value;
|
|
239
|
+
return value.map((v) => (v instanceof Date && !Number.isNaN(v.getTime()) ? renderTemporal(v, kind) : v));
|
|
240
|
+
}
|
|
241
|
+
/** `_time` → `time`, `time[]` → `time`, anything else unchanged. */
|
|
242
|
+
function arrayElementDbType(dbType) {
|
|
243
|
+
if (!dbType)
|
|
244
|
+
return dbType;
|
|
245
|
+
if (dbType.startsWith('_'))
|
|
246
|
+
return dbType.slice(1);
|
|
247
|
+
return dbType.endsWith('[]') ? dbType.slice(0, -2) : dbType;
|
|
248
|
+
}
|
|
249
|
+
function renderTemporal(value, kind) {
|
|
250
|
+
return kind === 'date' || kind === 'timestamp'
|
|
251
|
+
? toLocalDateTimeLiteral(value, kind)
|
|
252
|
+
: toTimeOfDayLiteral(value, kind);
|
|
253
|
+
}
|
|
144
254
|
/**
|
|
145
255
|
* Matches an explicit timezone suffix on a date-time string: a trailing `Z`
|
|
146
256
|
* or a `±HH`, `±HHMM`, `±HH:MM` offset.
|
|
@@ -176,3 +286,91 @@ export function parseDbDate(value) {
|
|
|
176
286
|
// normalize `YYYY-MM-DD HH:MM:SS` (driver form) to ISO before pinning UTC
|
|
177
287
|
return new Date(`${value.replace(' ', 'T')}Z`);
|
|
178
288
|
}
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
// JSON-wire value coercion (relationLoadStrategy: 'join')
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
/**
|
|
293
|
+
* Postgres type name → OID, for every type family whose `json_build_object`
|
|
294
|
+
* rendering is NOT the value the pg driver produces for the same column.
|
|
295
|
+
*
|
|
296
|
+
* Why this table exists: the `'join'` strategy reads a relation through
|
|
297
|
+
* `json_agg(json_build_object(...))`, so its values are whatever
|
|
298
|
+
* `JSON.parse` makes of Postgres's JSON rendering. Every other read path in
|
|
299
|
+
* the library — a top-level row, `'batched'`, `'flatten'` — reads the column
|
|
300
|
+
* through the driver and gets the driver's representation. Measured against
|
|
301
|
+
* PostgreSQL 17, those two disagree for exactly the families below, which
|
|
302
|
+
* made the SAME query return a different JS type depending on which plan ran
|
|
303
|
+
* (and `'auto'` picks the plan from a row-count heuristic, so it could differ
|
|
304
|
+
* between two runs of one query). Three of these are lossy, not merely
|
|
305
|
+
* different:
|
|
306
|
+
*
|
|
307
|
+
* type driver (target) json_build_object
|
|
308
|
+
* ──────────── ──────────────────────────── ─────────────────────────────
|
|
309
|
+
* numeric '1000.50' (string) 1000.5 (number, LOSSY)
|
|
310
|
+
* int8 '9007199254740993' 9007199254740992 (LOSSY)
|
|
311
|
+
* bytea Buffer '\xdeadbeef' (string)
|
|
312
|
+
* date Date (local midnight) Date (UTC midnight, off by tz)
|
|
313
|
+
* interval { days, hours, … } '1 day 02:03:04' (string)
|
|
314
|
+
* point { x, y } '(1,2)' (string)
|
|
315
|
+
* circle { x, y, radius } '<(1,2),3>' (string)
|
|
316
|
+
*
|
|
317
|
+
* The array forms diverge the same way, plus `_timestamp`/`_timestamptz`
|
|
318
|
+
* (driver: `Date[]`; JSON: `string[]`) — the scalar `timestamp` /
|
|
319
|
+
* `timestamptz` are deliberately ABSENT because the existing `dateColumns`
|
|
320
|
+
* coercion in `parseRow` already lands them on the driver's value, and they
|
|
321
|
+
* are the hottest column type in a typical schema (no reason to add a cast to
|
|
322
|
+
* every `created_at`).
|
|
323
|
+
*
|
|
324
|
+
* The fix these OIDs drive: emit the column as `col::text` inside
|
|
325
|
+
* `json_build_object` so the JSON carries the same wire text the driver would
|
|
326
|
+
* receive, then run the DRIVER'S OWN parser for that OID over it. Parity is
|
|
327
|
+
* then by construction rather than by coincidence, and it automatically
|
|
328
|
+
* honours a caller's `pg.types.setTypeParser` (including the int8 parser
|
|
329
|
+
* TurbineClient itself registers) instead of second-guessing it.
|
|
330
|
+
*
|
|
331
|
+
* Postgres-only: the JSON functions and the divergence set are both
|
|
332
|
+
* engine-specific, so callers gate this on the postgres dialect.
|
|
333
|
+
*/
|
|
334
|
+
export const JSON_WIRE_COERCION_OIDS = {
|
|
335
|
+
numeric: 1700,
|
|
336
|
+
int8: 20,
|
|
337
|
+
bytea: 17,
|
|
338
|
+
date: 1082,
|
|
339
|
+
interval: 1186,
|
|
340
|
+
point: 600,
|
|
341
|
+
circle: 718,
|
|
342
|
+
_numeric: 1231,
|
|
343
|
+
_int8: 1016,
|
|
344
|
+
_bytea: 1001,
|
|
345
|
+
_date: 1182,
|
|
346
|
+
_interval: 1187,
|
|
347
|
+
_point: 1017,
|
|
348
|
+
_timestamp: 1115,
|
|
349
|
+
_timestamptz: 1185,
|
|
350
|
+
};
|
|
351
|
+
/**
|
|
352
|
+
* The OID whose driver parser reproduces `pgType`'s driver representation from
|
|
353
|
+
* its text rendering, or `undefined` when the type's JSON rendering already
|
|
354
|
+
* matches the driver (the common case: text, uuid, bool, int4, float8, json,
|
|
355
|
+
* jsonb, arrays of those, …).
|
|
356
|
+
*/
|
|
357
|
+
export function jsonWireCoercionOid(pgType) {
|
|
358
|
+
if (!pgType)
|
|
359
|
+
return undefined;
|
|
360
|
+
return JSON_WIRE_COERCION_OIDS[pgType];
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Apply the driver's text parser for `oid` to a JSON-sourced wire string.
|
|
364
|
+
*
|
|
365
|
+
* Resolved through `pg.types.getTypeParser` on every call rather than
|
|
366
|
+
* memoized: parser registration is process-global and happens in the
|
|
367
|
+
* TurbineClient constructor (int8, and `timestamp` under `utcTimestamps`), and
|
|
368
|
+
* a caller may register their own at any point. A stale memo would silently
|
|
369
|
+
* reintroduce the very divergence this exists to remove. The lookup is a plain
|
|
370
|
+
* object index in pg-types, so it is not worth caching.
|
|
371
|
+
*/
|
|
372
|
+
export function coerceJsonWireValue(oid, value) {
|
|
373
|
+
if (typeof value !== 'string')
|
|
374
|
+
return value;
|
|
375
|
+
return pg.types.getTypeParser(oid, 'text')(value);
|
|
376
|
+
}
|
|
@@ -54,6 +54,15 @@ export declare const WARN_NS: {
|
|
|
54
54
|
readonly autoStrategy: "autoStrategy";
|
|
55
55
|
/** Deep-`with` (depth > 5) advisory (builder.ts `findMany`). */
|
|
56
56
|
readonly deepWith: "deepWith";
|
|
57
|
+
/** Non-deterministic page advisory: paginating findMany with no orderBy. */
|
|
58
|
+
readonly unorderedPage: "unorderedPage";
|
|
57
59
|
/** PowDB `emitLinks` DDL skips (name/column collision, endpoint drift). */
|
|
58
60
|
readonly powdbLinks: "powdbLinks";
|
|
61
|
+
/**
|
|
62
|
+
* `relationLoadStrategy: 'flatten'` was asked for but a relation stayed on the
|
|
63
|
+
* correlated-subquery path (relations.ts `planFlattenWith`, builder.ts
|
|
64
|
+
* `planFlatten`). An explicitly requested strategy that quietly does not
|
|
65
|
+
* engage is indistinguishable from one that does nothing, so say so once.
|
|
66
|
+
*/
|
|
67
|
+
readonly flattenFallback: "flattenFallback";
|
|
59
68
|
};
|
|
@@ -89,6 +89,15 @@ export const WARN_NS = {
|
|
|
89
89
|
autoStrategy: 'autoStrategy',
|
|
90
90
|
/** Deep-`with` (depth > 5) advisory (builder.ts `findMany`). */
|
|
91
91
|
deepWith: 'deepWith',
|
|
92
|
+
/** Non-deterministic page advisory: paginating findMany with no orderBy. */
|
|
93
|
+
unorderedPage: 'unorderedPage',
|
|
92
94
|
/** PowDB `emitLinks` DDL skips (name/column collision, endpoint drift). */
|
|
93
95
|
powdbLinks: 'powdbLinks',
|
|
96
|
+
/**
|
|
97
|
+
* `relationLoadStrategy: 'flatten'` was asked for but a relation stayed on the
|
|
98
|
+
* correlated-subquery path (relations.ts `planFlattenWith`, builder.ts
|
|
99
|
+
* `planFlatten`). An explicitly requested strategy that quietly does not
|
|
100
|
+
* engage is indistinguishable from one that does nothing, so say so once.
|
|
101
|
+
*/
|
|
102
|
+
flattenFallback: 'flattenFallback',
|
|
94
103
|
};
|