turbine-orm 0.50.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -66
- package/dist/adapters/cockroachdb.d.ts +5 -5
- package/dist/adapters/cockroachdb.js +10 -10
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/yugabytedb.d.ts +7 -7
- package/dist/adapters/yugabytedb.js +10 -10
- package/dist/cjs/adapters/cockroachdb.d.ts +5 -5
- package/dist/cjs/adapters/cockroachdb.js +10 -10
- package/dist/cjs/adapters/index.d.ts +5 -5
- package/dist/cjs/adapters/index.js +7 -7
- package/dist/cjs/adapters/yugabytedb.d.ts +7 -7
- package/dist/cjs/adapters/yugabytedb.js +10 -10
- package/dist/cjs/cli/config.d.ts +13 -2
- package/dist/cjs/cli/config.js +3 -2
- package/dist/cjs/cli/destructive.d.ts +1 -1
- package/dist/cjs/cli/destructive.js +1 -1
- package/dist/cjs/cli/index.d.ts +10 -10
- package/dist/cjs/cli/index.js +49 -45
- package/dist/cjs/cli/loader.d.ts +7 -7
- package/dist/cjs/cli/loader.js +9 -9
- package/dist/cjs/cli/mcp.js +4 -4
- package/dist/cjs/cli/migrate.d.ts +5 -5
- package/dist/cjs/cli/migrate.js +11 -11
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/ui.d.ts +2 -2
- package/dist/cjs/cli/ui.js +2 -2
- package/dist/cjs/client.d.ts +49 -38
- package/dist/cjs/client.js +57 -56
- package/dist/cjs/dialect.d.ts +62 -18
- package/dist/cjs/dialect.js +40 -2
- package/dist/cjs/errors.d.ts +5 -5
- package/dist/cjs/errors.js +11 -11
- package/dist/cjs/generate.d.ts +6 -6
- package/dist/cjs/generate.js +31 -29
- package/dist/cjs/index-advisor.d.ts +5 -5
- package/dist/cjs/index-advisor.js +0 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +7 -7
- package/dist/cjs/introspect.d.ts +35 -9
- package/dist/cjs/introspect.js +83 -32
- package/dist/cjs/mssql.d.ts +11 -11
- package/dist/cjs/mssql.js +64 -29
- package/dist/cjs/mysql.d.ts +8 -8
- package/dist/cjs/mysql.js +61 -23
- package/dist/cjs/nested-write.d.ts +21 -2
- package/dist/cjs/nested-write.js +51 -14
- package/dist/cjs/optional-peer-import.cjs +7 -7
- package/dist/cjs/optional-peer-import.d.cts +7 -7
- package/dist/cjs/pipeline-submittable.d.ts +2 -2
- package/dist/cjs/pipeline-submittable.js +6 -6
- package/dist/cjs/pipeline.d.ts +1 -1
- package/dist/cjs/pipeline.js +4 -4
- package/dist/cjs/powdb-introspect.d.ts +1 -1
- package/dist/cjs/powdb-introspect.js +1 -1
- package/dist/cjs/powdb.d.ts +28 -28
- package/dist/cjs/powdb.js +66 -66
- package/dist/cjs/powql.d.ts +27 -27
- package/dist/cjs/powql.js +73 -52
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +5 -5
- package/dist/cjs/query/batched-loader.d.ts +11 -11
- package/dist/cjs/query/batched-loader.js +24 -24
- package/dist/cjs/query/builder.d.ts +39 -21
- package/dist/cjs/query/builder.js +99 -57
- package/dist/cjs/query/compound-unique.d.ts +1 -1
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/deferred.d.ts +12 -6
- package/dist/cjs/query/deferred.js +1 -1
- package/dist/cjs/query/filters.d.ts +31 -11
- package/dist/cjs/query/filters.js +67 -14
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/index.js +1 -1
- package/dist/cjs/query/relations.d.ts +9 -9
- package/dist/cjs/query/relations.js +164 -57
- package/dist/cjs/query/types.d.ts +86 -35
- package/dist/cjs/query/types.js +1 -1
- package/dist/cjs/query/utils.d.ts +27 -10
- package/dist/cjs/query/utils.js +86 -14
- package/dist/cjs/query/where.d.ts +47 -28
- package/dist/cjs/query/where.js +130 -31
- package/dist/cjs/query/writes.d.ts +24 -5
- package/dist/cjs/query/writes.js +102 -13
- package/dist/cjs/realtime.d.ts +7 -7
- package/dist/cjs/realtime.js +9 -9
- package/dist/cjs/schema-builder.d.ts +18 -7
- package/dist/cjs/schema-builder.js +17 -10
- package/dist/cjs/schema-metadata.d.ts +3 -3
- package/dist/cjs/schema-metadata.js +9 -9
- package/dist/cjs/schema-sql.d.ts +9 -9
- package/dist/cjs/schema-sql.js +20 -20
- package/dist/cjs/schema.d.ts +19 -9
- package/dist/cjs/schema.js +6 -6
- package/dist/cjs/serverless.d.ts +15 -15
- package/dist/cjs/serverless.js +16 -16
- package/dist/cjs/sqlite.d.ts +8 -8
- package/dist/cjs/sqlite.js +53 -22
- package/dist/cjs/typed-sql.d.ts +4 -4
- package/dist/cjs/typed-sql.js +5 -5
- package/dist/cli/config.d.ts +13 -2
- package/dist/cli/config.js +3 -2
- package/dist/cli/destructive.d.ts +1 -1
- package/dist/cli/destructive.js +1 -1
- package/dist/cli/index.d.ts +10 -10
- package/dist/cli/index.js +49 -45
- package/dist/cli/loader.d.ts +7 -7
- package/dist/cli/loader.js +9 -9
- package/dist/cli/mcp.js +4 -4
- package/dist/cli/migrate.d.ts +5 -5
- package/dist/cli/migrate.js +11 -11
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/ui.d.ts +2 -2
- package/dist/cli/ui.js +2 -2
- package/dist/client.d.ts +49 -38
- package/dist/client.js +57 -56
- package/dist/dialect.d.ts +62 -18
- package/dist/dialect.js +40 -2
- package/dist/errors.d.ts +5 -5
- package/dist/errors.js +11 -11
- package/dist/generate.d.ts +6 -6
- package/dist/generate.js +31 -29
- package/dist/index-advisor.d.ts +5 -5
- package/dist/index-advisor.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/introspect.d.ts +35 -9
- package/dist/introspect.js +82 -32
- package/dist/mssql.d.ts +11 -11
- package/dist/mssql.js +64 -29
- package/dist/mysql.d.ts +8 -8
- package/dist/mysql.js +61 -23
- package/dist/nested-write.d.ts +21 -2
- package/dist/nested-write.js +51 -14
- package/dist/optional-peer-import.cjs +7 -7
- package/dist/optional-peer-import.d.cts +7 -7
- package/dist/pipeline-submittable.d.ts +2 -2
- package/dist/pipeline-submittable.js +6 -6
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +4 -4
- package/dist/powdb-introspect.d.ts +1 -1
- package/dist/powdb-introspect.js +1 -1
- package/dist/powdb.d.ts +28 -28
- package/dist/powdb.js +66 -66
- package/dist/powql.d.ts +27 -27
- package/dist/powql.js +73 -52
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +5 -5
- package/dist/query/batched-loader.d.ts +11 -11
- package/dist/query/batched-loader.js +24 -24
- package/dist/query/builder.d.ts +39 -21
- package/dist/query/builder.js +100 -58
- package/dist/query/compound-unique.d.ts +1 -1
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +12 -6
- package/dist/query/deferred.js +1 -1
- package/dist/query/filters.d.ts +31 -11
- package/dist/query/filters.js +66 -13
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +9 -9
- package/dist/query/relations.js +165 -58
- package/dist/query/types.d.ts +86 -35
- package/dist/query/types.js +1 -1
- package/dist/query/utils.d.ts +27 -10
- package/dist/query/utils.js +84 -14
- package/dist/query/where.d.ts +47 -28
- package/dist/query/where.js +129 -32
- package/dist/query/writes.d.ts +24 -5
- package/dist/query/writes.js +101 -13
- package/dist/realtime.d.ts +7 -7
- package/dist/realtime.js +9 -9
- package/dist/schema-builder.d.ts +18 -7
- package/dist/schema-builder.js +17 -10
- package/dist/schema-metadata.d.ts +3 -3
- package/dist/schema-metadata.js +9 -9
- package/dist/schema-sql.d.ts +9 -9
- package/dist/schema-sql.js +20 -20
- package/dist/schema.d.ts +19 -9
- package/dist/schema.js +6 -6
- package/dist/serverless.d.ts +15 -15
- package/dist/serverless.js +16 -16
- package/dist/sqlite.d.ts +8 -8
- package/dist/sqlite.js +53 -22
- package/dist/typed-sql.d.ts +4 -4
- package/dist/typed-sql.js +5 -5
- package/package.json +2 -2
package/dist/query/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Query builder types
|
|
3
3
|
*
|
|
4
4
|
* All exported type and interface definitions for the query builder module.
|
|
5
5
|
*/
|
|
@@ -128,7 +128,7 @@ export interface TypedToOneFilter<V, VR extends object = {}> {
|
|
|
128
128
|
* to-one relations take `is`/`isNot` or a bare sub-where (implicit `is`).
|
|
129
129
|
*
|
|
130
130
|
* Legacy generated shapes (`posts: Post[]`, `profile: Profile | null`) carry no
|
|
131
|
-
* brand, so they degrade to `unknown
|
|
131
|
+
* brand, so they degrade to `unknown`, the key is still recognised (no typo
|
|
132
132
|
* false-positive) but its value is not checked.
|
|
133
133
|
*/
|
|
134
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;
|
|
@@ -138,7 +138,7 @@ type RelationWhereValue<Rel> = Rel extends RelationDescriptor<infer Target, infe
|
|
|
138
138
|
*
|
|
139
139
|
* **Key checking.** Historically this type carried a
|
|
140
140
|
* `[relationName: string]: unknown` index signature so relation filters
|
|
141
|
-
* (`where: { posts: { some: ... } }`) would typecheck
|
|
141
|
+
* (`where: { posts: { some: ... } }`) would typecheck, the relation names are
|
|
142
142
|
* NOT keys of the entity `T`. That index signature also annihilated
|
|
143
143
|
* excess-property checking, so `where: { emial: 'x' }` compiled silently.
|
|
144
144
|
*
|
|
@@ -160,7 +160,7 @@ export type LooseWhereClause<T> = {
|
|
|
160
160
|
OR?: LooseWhereClause<T>[];
|
|
161
161
|
AND?: LooseWhereClause<T>[];
|
|
162
162
|
NOT?: LooseWhereClause<T>;
|
|
163
|
-
/** Relation filters
|
|
163
|
+
/** Relation filters, keyed by relation name, value is { some, every, none } */
|
|
164
164
|
[relationName: string]: unknown;
|
|
165
165
|
};
|
|
166
166
|
/** Key-checked where clause: column keys from `T`, relation keys from `R`, no index signature. */
|
|
@@ -177,7 +177,7 @@ type StrictWhereClause<T, R extends object> = {
|
|
|
177
177
|
* Client-level automatic WHERE filters, keyed by table accessor (the name used
|
|
178
178
|
* in `db[name]` / `client.table(name)`). Each value is AND-merged into the
|
|
179
179
|
* compiled WHERE of every read and mutation on that table, and into every
|
|
180
|
-
* relation subquery that targets it
|
|
180
|
+
* relation subquery that targets it, the mechanism behind soft-delete and
|
|
181
181
|
* multi-tenancy. A function value is evaluated at query-build time (per query),
|
|
182
182
|
* so a closure over per-request state (e.g. the current tenant id) enables
|
|
183
183
|
* request-scoped filters. `create`/`createMany` are never filtered.
|
|
@@ -190,7 +190,7 @@ export type GlobalFilters = {
|
|
|
190
190
|
* global filter on the query's own table AND on every relation target it
|
|
191
191
|
* touches; an array skips only the named table accessors (own table and/or
|
|
192
192
|
* relation targets). Global filters never satisfy the empty-`where` guard for
|
|
193
|
-
* `update`/`delete
|
|
193
|
+
* `update`/`delete`, that guard always checks the user-supplied `where`.
|
|
194
194
|
*/
|
|
195
195
|
export type SkipGlobalFilters = true | readonly string[];
|
|
196
196
|
/**
|
|
@@ -201,7 +201,7 @@ export type SkipGlobalFilters = true | readonly string[];
|
|
|
201
201
|
*/
|
|
202
202
|
export type WithCount = true | Record<string, true>;
|
|
203
203
|
/**
|
|
204
|
-
* Unparameterized with clause
|
|
204
|
+
* Unparameterized with clause, accepts any relation name.
|
|
205
205
|
* Used internally by the query builder at runtime.
|
|
206
206
|
*
|
|
207
207
|
* The reserved `_count` key (see {@link WithCount}) is also accepted at runtime;
|
|
@@ -219,7 +219,7 @@ export interface WithClause {
|
|
|
219
219
|
*
|
|
220
220
|
* For typed maps, each relation accepts either `true` (default include) or a
|
|
221
221
|
* {@link WithOptions} object whose nested `with` is keyed against the relation
|
|
222
|
-
* target's own relations interface
|
|
222
|
+
* target's own relations interface, this is what enables deep
|
|
223
223
|
* `WithResult` inference.
|
|
224
224
|
*/
|
|
225
225
|
export type TypedWithClause<R extends object = {}> = [keyof R] extends [never] ? WithClause : {
|
|
@@ -233,7 +233,7 @@ export type TypedWithClause<R extends object = {}> = [keyof R] extends [never] ?
|
|
|
233
233
|
/**
|
|
234
234
|
* Options for an included relation.
|
|
235
235
|
*
|
|
236
|
-
* Generic over `NestedR
|
|
236
|
+
* Generic over `NestedR`, the relations interface of the *target* entity -
|
|
237
237
|
* so the nested `with` clause is autocompleted with the correct relation keys
|
|
238
238
|
* and so {@link WithResult} can recursively infer the return type. Defaults to
|
|
239
239
|
* `{}` (no relation suggestions) for callers that use the unparameterized
|
|
@@ -254,6 +254,38 @@ export type WithOrderByObject = Record<string, OrderDirection | OrderBySpec | Js
|
|
|
254
254
|
* {@link WithClause} escape hatch) it degrades to the historical open record.
|
|
255
255
|
*/
|
|
256
256
|
export type WithWhere<NestedT, NestedR extends object> = [unknown] extends [NestedT] ? Record<string, unknown> : WhereClause<NestedT & object, NestedR>;
|
|
257
|
+
/**
|
|
258
|
+
* A key-checked orderBy object over an entity and its relations.
|
|
259
|
+
*
|
|
260
|
+
* A column key takes a direction, an {@link OrderBySpec}, a JSON-path ordering
|
|
261
|
+
* or a vector KNN ordering; a RELATION key takes a {@link RelationOrderBy}
|
|
262
|
+
* (`_count`, or a target column) or a pick-row ordering. Written as an
|
|
263
|
+
* intersection of two mapped types so an object literal with a key in neither
|
|
264
|
+
* set is an excess property, which is what turns `orderBy: { titel: 'asc' }`
|
|
265
|
+
* into a compile error instead of a runtime E003.
|
|
266
|
+
*
|
|
267
|
+
* Degrades to the open {@link OrderByObject} when the entity is untyped, so
|
|
268
|
+
* `db.table(name)` and dynamically built clauses keep working. A value typed
|
|
269
|
+
* `Record<string, OrderDirection>` still assigns, because an index signature
|
|
270
|
+
* satisfies each optional target key.
|
|
271
|
+
*/
|
|
272
|
+
export type TypedOrderByObject<T, R extends object> = [unknown] extends [T] ? OrderByObject : [
|
|
273
|
+
keyof R
|
|
274
|
+
] extends [never] ? OrderByObject : {
|
|
275
|
+
[K in keyof T]?: OrderDirection | OrderBySpec | JsonPathOrderBy | VectorOrderBy;
|
|
276
|
+
} & {
|
|
277
|
+
[K in keyof R]?: RelationOrderBy | RelationPickOrderBy;
|
|
278
|
+
};
|
|
279
|
+
/** {@link TypedOrderByObject}, single or Prisma-style array. */
|
|
280
|
+
export type TypedOrderByClause<T, R extends object> = TypedOrderByObject<T, R> | TypedOrderByObject<T, R>[];
|
|
281
|
+
/**
|
|
282
|
+
* `select` / `omit` inside a relation `with` block, key-checked against the
|
|
283
|
+
* relation TARGET when it is known, and the historical open record when it is
|
|
284
|
+
* not (the untyped {@link WithClause} escape hatch).
|
|
285
|
+
*/
|
|
286
|
+
export type WithFieldFlags<NestedT> = [unknown] extends [NestedT] ? Record<string, boolean> : {
|
|
287
|
+
[K in keyof NestedT]?: boolean;
|
|
288
|
+
};
|
|
257
289
|
export interface WithOptions<NestedR extends object = {}, NestedT = unknown> {
|
|
258
290
|
with?: TypedWithClause<NestedR>;
|
|
259
291
|
/** Filter the related rows. Keys are checked against the relation target when it is known (see {@link WithWhere}). */
|
|
@@ -263,21 +295,21 @@ export interface WithOptions<NestedR extends object = {}, NestedT = unknown> {
|
|
|
263
295
|
* or a Prisma-style array of objects (`[{ a: 'asc' }, { b: 'desc' }]`, whose
|
|
264
296
|
* element order is the authoritative multi-key sort precedence).
|
|
265
297
|
*/
|
|
266
|
-
orderBy?:
|
|
298
|
+
orderBy?: TypedOrderByClause<NestedT, NestedR>;
|
|
267
299
|
limit?: number;
|
|
268
|
-
/** Only include these fields from the relation */
|
|
269
|
-
select?:
|
|
270
|
-
/** Exclude these fields from the relation */
|
|
271
|
-
omit?:
|
|
300
|
+
/** Only include these fields from the relation. Key-checked against the target when it is known. */
|
|
301
|
+
select?: WithFieldFlags<NestedT>;
|
|
302
|
+
/** Exclude these fields from the relation. Key-checked against the target when it is known. */
|
|
303
|
+
omit?: WithFieldFlags<NestedT>;
|
|
272
304
|
}
|
|
273
305
|
/**
|
|
274
306
|
* A relation descriptor used by generated `*Relations` interfaces to make deep
|
|
275
307
|
* `with` clause inference work. It bundles three pieces of information that
|
|
276
308
|
* `WithResult` needs to recurse through nested relations:
|
|
277
309
|
*
|
|
278
|
-
* - `__target`
|
|
279
|
-
* - `__cardinality
|
|
280
|
-
* - `__relations`
|
|
310
|
+
* - `__target` , the target entity type (e.g. `Post`)
|
|
311
|
+
* - `__cardinality`, `'many'` for hasMany, `'one'` for belongsTo / hasOne
|
|
312
|
+
* - `__relations` , the target entity's relations interface (for further recursion)
|
|
281
313
|
*
|
|
282
314
|
* **Generator contract (Track 3):** the code generator emits `*Relations`
|
|
283
315
|
* interfaces in the following shape so that `WithResult` can walk arbitrary
|
|
@@ -290,14 +322,14 @@ export interface WithOptions<NestedR extends object = {}, NestedT = unknown> {
|
|
|
290
322
|
* }
|
|
291
323
|
* ```
|
|
292
324
|
*
|
|
293
|
-
* The brand fields are phantom
|
|
325
|
+
* The brand fields are phantom, they exist only for type inference and have
|
|
294
326
|
* no runtime representation. The runtime always sees the parsed entity values
|
|
295
|
-
* (arrays for hasMany, single object or null for belongsTo / hasOne)
|
|
327
|
+
* (arrays for hasMany, single object or null for belongsTo / hasOne), see the
|
|
296
328
|
* cardinality projection inside {@link WithResult}.
|
|
297
329
|
*
|
|
298
330
|
* **Backward compatibility:** legacy generated code emitted bare types
|
|
299
331
|
* (`posts: Post[]`, `profile: Profile | null`). `WithResult` still accepts that
|
|
300
|
-
* shape via a fallback branch
|
|
332
|
+
* shape via a fallback branch, it just cannot recurse into nested `with` for
|
|
301
333
|
* those relations until the generator is updated.
|
|
302
334
|
*
|
|
303
335
|
* @typeParam Target - The entity type the relation points at.
|
|
@@ -331,7 +363,7 @@ type ApplyCardinality<Rel, Resolved> = Rel extends RelationDescriptor<infer _T,
|
|
|
331
363
|
* deep nesting.
|
|
332
364
|
*
|
|
333
365
|
* **When `R` is `{}` (the default):** the recursion short-circuits and the
|
|
334
|
-
* function returns plain `T
|
|
366
|
+
* function returns plain `T`, preserving the existing untyped escape hatch
|
|
335
367
|
* for callers that have not generated typed clients.
|
|
336
368
|
*
|
|
337
369
|
* **When `R` does not contain the requested relations:** the unknown keys are
|
|
@@ -348,7 +380,7 @@ type ApplyCardinality<Rel, Resolved> = Rel extends RelationDescriptor<infer _T,
|
|
|
348
380
|
*/
|
|
349
381
|
export type WithResult<T, R extends object, W> = [keyof R] extends [never] ? T : W extends object ? W extends {
|
|
350
382
|
_count: infer C;
|
|
351
|
-
} ? // `_count` requested
|
|
383
|
+
} ? // `_count` requested, add the typed count object alongside any relations.
|
|
352
384
|
WithRelationAdditions<T, R, W> & {
|
|
353
385
|
_count: CountResult<C>;
|
|
354
386
|
} : WithRelationAdditions<T, R, W> : T;
|
|
@@ -402,7 +434,7 @@ export type SelectResult<T, S extends Record<string, boolean> | undefined> = S e
|
|
|
402
434
|
/** Omit the fields from T that are marked (value = true) in O. */
|
|
403
435
|
export type OmitResult<T, O extends Record<string, boolean> | undefined> = O extends Record<string, boolean> ? Omit<T, Extract<keyof T, TrueKeys<O>>> : T;
|
|
404
436
|
/**
|
|
405
|
-
* Apply select or omit field narrowing to a base type. Select takes priority
|
|
437
|
+
* Apply select or omit field narrowing to a base type. Select takes priority -
|
|
406
438
|
* when both are provided, only select is applied (matching runtime behavior).
|
|
407
439
|
*/
|
|
408
440
|
export type FieldResult<T, S extends Record<string, boolean> | undefined, O extends Record<string, boolean> | undefined> = S extends Record<string, boolean> ? SelectResult<T, S> : OmitResult<T, O>;
|
|
@@ -441,7 +473,7 @@ export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClaus
|
|
|
441
473
|
select?: S & FieldFlags<T, S>;
|
|
442
474
|
/** Exclude these fields. Keys are checked against `T` (see {@link FieldFlags}). */
|
|
443
475
|
omit?: O & FieldFlags<T, O>;
|
|
444
|
-
orderBy?:
|
|
476
|
+
orderBy?: TypedOrderByClause<T, R>;
|
|
445
477
|
limit?: number;
|
|
446
478
|
offset?: number;
|
|
447
479
|
with?: W;
|
|
@@ -523,7 +555,7 @@ export interface CreateManyArgs<T> {
|
|
|
523
555
|
* `set` works on any type; `increment`, `decrement`, `multiply`, and `divide`
|
|
524
556
|
* are only valid on numeric fields. They generate SQL like
|
|
525
557
|
* `col = col + $n` (and the corresponding `-`, `*`, `/` variants) instead of
|
|
526
|
-
* plain absolute assignments, so they are safe against concurrent writers
|
|
558
|
+
* plain absolute assignments, so they are safe against concurrent writers -
|
|
527
559
|
* the database performs the math atomically.
|
|
528
560
|
*
|
|
529
561
|
* @example
|
|
@@ -541,7 +573,7 @@ export type UpdateOperatorInput<V> = {
|
|
|
541
573
|
divide: number;
|
|
542
574
|
} : never);
|
|
543
575
|
/**
|
|
544
|
-
* Update data
|
|
576
|
+
* Update data, each field can be a plain value or an atomic operator object.
|
|
545
577
|
* Back-compatible with `Partial<T>`: plain values still typecheck unchanged.
|
|
546
578
|
*/
|
|
547
579
|
export type UpdateInput<T> = {
|
|
@@ -560,14 +592,14 @@ export interface UpdateArgs<T, R extends object = {}> {
|
|
|
560
592
|
timeout?: number;
|
|
561
593
|
/**
|
|
562
594
|
* Opt in to running this mutation when `where` resolves to an empty
|
|
563
|
-
* predicate (e.g. `{}` or `{ id: undefined }`). Default `false
|
|
595
|
+
* predicate (e.g. `{}` or `{ id: undefined }`). Default `false`, an
|
|
564
596
|
* empty predicate throws `ValidationError` to catch the common case of
|
|
565
597
|
* a filter value accidentally being `undefined`. Set this to `true` only
|
|
566
598
|
* when an unconditional mutation is the intended behaviour.
|
|
567
599
|
*/
|
|
568
600
|
allowFullTableScan?: boolean;
|
|
569
601
|
/**
|
|
570
|
-
* Optimistic locking
|
|
602
|
+
* Optimistic locking, prevents lost updates in concurrent scenarios.
|
|
571
603
|
* Specify the version field and its expected value. The update adds a
|
|
572
604
|
* WHERE check on the version and auto-increments it. If the row was
|
|
573
605
|
* modified by another transaction, throws `OptimisticLockError`.
|
|
@@ -663,7 +695,7 @@ export interface NestedUpdateOp<T, TR extends object = {}> {
|
|
|
663
695
|
/**
|
|
664
696
|
* `create()` data input. When the relations map `R` is known (typed clients),
|
|
665
697
|
* each relation name additionally accepts a {@link NestedCreateOp} for the
|
|
666
|
-
* relation's target entity
|
|
698
|
+
* relation's target entity, recursively, via the target's own relations map.
|
|
667
699
|
* When `R` is `{}` (untyped escape hatch) this collapses to plain `Partial<T>`.
|
|
668
700
|
*/
|
|
669
701
|
export type CreateDataInput<T, R extends object = {}> = [keyof R] extends [never] ? Partial<T> : Partial<T> & {
|
|
@@ -718,14 +750,14 @@ export interface HavingAggregateFilter {
|
|
|
718
750
|
_count?: HavingFilter;
|
|
719
751
|
}
|
|
720
752
|
/**
|
|
721
|
-
* HAVING clause for `groupBy
|
|
753
|
+
* HAVING clause for `groupBy`, filters whole groups by their aggregate values
|
|
722
754
|
* (the SQL `HAVING` clause). Follows Prisma's shape: each aggregable field maps
|
|
723
755
|
* to a {@link HavingAggregateFilter} (`field → aggregate → operator → value`),
|
|
724
756
|
* and the special top-level `_count` key (no field) filters on `COUNT(*)`.
|
|
725
757
|
*
|
|
726
758
|
* Implemented as a mapped type so the special `_count` key can carry a
|
|
727
759
|
* {@link HavingFilter} while every entity field carries a
|
|
728
|
-
* {@link HavingAggregateFilter}
|
|
760
|
+
* {@link HavingAggregateFilter}, without the index-signature conflict an
|
|
729
761
|
* intersection type would produce when `T` is a broad `Record<string, unknown>`.
|
|
730
762
|
*
|
|
731
763
|
* @example
|
|
@@ -1050,7 +1082,7 @@ export interface JsonFilter {
|
|
|
1050
1082
|
hasKey?: string;
|
|
1051
1083
|
/**
|
|
1052
1084
|
* Greater-than comparison of the value at `path` (required). Numbers cast
|
|
1053
|
-
* the extracted text to numeric
|
|
1085
|
+
* the extracted text to numeric, `(col #>> path)::numeric > $n`, while
|
|
1054
1086
|
* strings compare as text.
|
|
1055
1087
|
*/
|
|
1056
1088
|
gt?: number | string;
|
|
@@ -1060,6 +1092,25 @@ export interface JsonFilter {
|
|
|
1060
1092
|
lt?: number | string;
|
|
1061
1093
|
/** Less-than-or-equal comparison of the value at `path` (required). See {@link JsonFilter.gt}. */
|
|
1062
1094
|
lte?: number | string;
|
|
1095
|
+
/**
|
|
1096
|
+
* Substring match against the TEXT at `path` (required): `col #>> path LIKE
|
|
1097
|
+
* '%value%'`. This is Prisma's `string_contains`.
|
|
1098
|
+
*
|
|
1099
|
+
* Deliberately not spelled `contains`, which on a JSON column already means
|
|
1100
|
+
* whole-document containment (`@>`) and means something entirely different.
|
|
1101
|
+
* The operand is LIKE-escaped, so `%` and `_` match literally.
|
|
1102
|
+
*/
|
|
1103
|
+
stringContains?: string;
|
|
1104
|
+
/** Prefix match against the text at `path` (required). Prisma's `string_starts_with`. See {@link JsonFilter.stringContains}. */
|
|
1105
|
+
stringStartsWith?: string;
|
|
1106
|
+
/** Suffix match against the text at `path` (required). Prisma's `string_ends_with`. See {@link JsonFilter.stringContains}. */
|
|
1107
|
+
stringEndsWith?: string;
|
|
1108
|
+
/**
|
|
1109
|
+
* Case-insensitive matching for the substring operators above (ILIKE on
|
|
1110
|
+
* PostgreSQL, the dialect's equivalent elsewhere). Has no effect on
|
|
1111
|
+
* `equals` / `contains` / `hasKey` / the range operators.
|
|
1112
|
+
*/
|
|
1113
|
+
mode?: 'default' | 'insensitive';
|
|
1063
1114
|
}
|
|
1064
1115
|
/** Array query operators for where clauses */
|
|
1065
1116
|
export interface ArrayFilter {
|
|
@@ -1086,7 +1137,7 @@ export interface TextSearchFilter {
|
|
|
1086
1137
|
* - `'cosine'` → `<=>` (cosine distance)
|
|
1087
1138
|
* - `'ip'` → `<#>` (negative inner product)
|
|
1088
1139
|
*
|
|
1089
|
-
* This is a fixed allow-list
|
|
1140
|
+
* This is a fixed allow-list, a value outside it is rejected with a
|
|
1090
1141
|
* `ValidationError` so a user-supplied string can never become a SQL operator.
|
|
1091
1142
|
*/
|
|
1092
1143
|
export type VectorMetric = 'l2' | 'cosine' | 'ip';
|
|
@@ -1189,9 +1240,9 @@ export interface JsonPathOrderBy {
|
|
|
1189
1240
|
/**
|
|
1190
1241
|
* Ordering by a relation, keyed by the relation name in an {@link OrderByClause}:
|
|
1191
1242
|
*
|
|
1192
|
-
* - to-many (hasMany / manyToMany): `{ posts: { _count: 'desc' } }
|
|
1243
|
+
* - to-many (hasMany / manyToMany): `{ posts: { _count: 'desc' } }`, orders by
|
|
1193
1244
|
* a correlated `COUNT(*)` of the related rows.
|
|
1194
|
-
* - to-one (belongsTo / hasOne): `{ author: { name: 'asc' } }
|
|
1245
|
+
* - to-one (belongsTo / hasOne): `{ author: { name: 'asc' } }`, orders by a
|
|
1195
1246
|
* correlated scalar subquery on the target column (an {@link OrderBySpec} with
|
|
1196
1247
|
* `nulls` is accepted too).
|
|
1197
1248
|
*/
|
package/dist/query/types.js
CHANGED
package/dist/query/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Query builder utilities
|
|
3
3
|
*
|
|
4
4
|
* Standalone utility functions and classes used by the query builder.
|
|
5
5
|
*/
|
|
@@ -18,7 +18,7 @@ export declare function quoteIdent(name: string): string;
|
|
|
18
18
|
* relations, reverseColumnMap). These are constructed as plain objects, so a
|
|
19
19
|
* bare `map[key]` for a user-supplied field name like "constructor",
|
|
20
20
|
* "toString", or "__proto__" returns an inherited member from
|
|
21
|
-
* `Object.prototype
|
|
21
|
+
* `Object.prototype`, a truthy value that slips past validation and produces a
|
|
22
22
|
* cryptic `TypeError` instead of a clean `ValidationError`. Returns `undefined`
|
|
23
23
|
* unless `key` is an OWN enumerable/non-enumerable property.
|
|
24
24
|
*/
|
|
@@ -60,12 +60,12 @@ export interface SqlCacheEntry {
|
|
|
60
60
|
export declare function fnv1a64Hex(s: string): string;
|
|
61
61
|
/**
|
|
62
62
|
* Derive a prepared-statement name from a SQL string.
|
|
63
|
-
* Format: `t_<16hex
|
|
63
|
+
* Format: `t_<16hex>`, always 18 chars, well under NAMEDATALEN (63).
|
|
64
64
|
*
|
|
65
65
|
* @internal Exported for testing only.
|
|
66
66
|
*/
|
|
67
67
|
export declare function sqlToPreparedName(sql: string): string;
|
|
68
|
-
/** Known operator keys
|
|
68
|
+
/** Known operator keys, used to detect operator objects vs plain values */
|
|
69
69
|
export declare const OPERATOR_KEYS: Set<string>;
|
|
70
70
|
/**
|
|
71
71
|
* Build a correlation clause joining columns between two table references.
|
|
@@ -125,21 +125,21 @@ export declare function temporalBindKind(dbType: string | undefined, utcDateTime
|
|
|
125
125
|
* per-element rewrite is what a `time[]` column needs, and matches the scalar
|
|
126
126
|
* case rather than silently binding an ISO timestamp).
|
|
127
127
|
*
|
|
128
|
-
* Everything else
|
|
129
|
-
* `timestamptz` column
|
|
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
130
|
* no-op outside the shapes above.
|
|
131
131
|
*/
|
|
132
132
|
export declare function coerceTemporalValue(dbType: string | undefined, value: unknown, utcDateTimes?: boolean): unknown;
|
|
133
133
|
/**
|
|
134
134
|
* Parse a database date-time string deterministically.
|
|
135
135
|
*
|
|
136
|
-
* Postgres `timestamp` (without time zone) values arrive with no offset
|
|
136
|
+
* Postgres `timestamp` (without time zone) values arrive with no offset -
|
|
137
137
|
* both from the driver and from `json_agg`/`json_build_object` subquery JSON
|
|
138
138
|
* (`2026-07-07T17:15:41.896`). JavaScript's `new Date()` interprets such
|
|
139
139
|
* strings in the SERVER'S LOCAL TIME ZONE, so the same row parses to a
|
|
140
140
|
* different instant depending on where the code runs. The universal ORM
|
|
141
141
|
* convention (Prisma, Rails, Django) is to treat offset-less timestamps as
|
|
142
|
-
* UTC
|
|
142
|
+
* UTC, that is also the only interpretation that round-trips: Postgres
|
|
143
143
|
* stores exactly the wall-clock fields you sent.
|
|
144
144
|
*
|
|
145
145
|
* Strings that carry an explicit offset (`timestamptz` output) are parsed
|
|
@@ -153,7 +153,7 @@ export declare function parseDbDate(value: string): Date;
|
|
|
153
153
|
* Why this table exists: the `'join'` strategy reads a relation through
|
|
154
154
|
* `json_agg(json_build_object(...))`, so its values are whatever
|
|
155
155
|
* `JSON.parse` makes of Postgres's JSON rendering. Every other read path in
|
|
156
|
-
* the library
|
|
156
|
+
* the library, a top-level row, `'batched'`, `'flatten'`, reads the column
|
|
157
157
|
* through the driver and gets the driver's representation. Measured against
|
|
158
158
|
* PostgreSQL 17, those two disagree for exactly the families below, which
|
|
159
159
|
* made the SAME query return a different JS type depending on which plan ran
|
|
@@ -172,7 +172,7 @@ export declare function parseDbDate(value: string): Date;
|
|
|
172
172
|
* circle { x, y, radius } '<(1,2),3>' (string)
|
|
173
173
|
*
|
|
174
174
|
* The array forms diverge the same way, plus `_timestamp`/`_timestamptz`
|
|
175
|
-
* (driver: `Date[]`; JSON: `string[]`)
|
|
175
|
+
* (driver: `Date[]`; JSON: `string[]`), the scalar `timestamp` /
|
|
176
176
|
* `timestamptz` are deliberately ABSENT because the existing `dateColumns`
|
|
177
177
|
* coercion in `parseRow` already lands them on the driver's value, and they
|
|
178
178
|
* are the hottest column type in a typical schema (no reason to add a cast to
|
|
@@ -207,3 +207,20 @@ export declare function jsonWireCoercionOid(pgType: string | undefined): number
|
|
|
207
207
|
* object index in pg-types, so it is not worth caching.
|
|
208
208
|
*/
|
|
209
209
|
export declare function coerceJsonWireValue(oid: number, value: unknown): unknown;
|
|
210
|
+
/** The closest name in `candidates` to `input`, or null when none is close. */
|
|
211
|
+
export declare function closestName(input: string, candidates: Iterable<string>): string | null;
|
|
212
|
+
/**
|
|
213
|
+
* The "unknown field" error text, listing RELATIONS as well as columns.
|
|
214
|
+
*
|
|
215
|
+
* The message used to read `Known fields: id, name.` and nothing else. That is
|
|
216
|
+
* actively misleading when the key was a relation name: relation filters ARE
|
|
217
|
+
* valid in a `where`, so a user who guessed the wrong relation name concluded
|
|
218
|
+
* from this message that turbine cannot filter by relations at all. Relation
|
|
219
|
+
* names are frequently guessed wrong because introspection derives them, and
|
|
220
|
+
* two foreign keys to one table produce names (`msgsBySender`) that no one
|
|
221
|
+
* would predict.
|
|
222
|
+
*/
|
|
223
|
+
export declare function unknownFieldMessage(table: string, field: string, meta: {
|
|
224
|
+
columnMap: Record<string, string>;
|
|
225
|
+
relations?: Record<string, unknown>;
|
|
226
|
+
}): string;
|
package/dist/query/utils.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Query builder utilities
|
|
3
3
|
*
|
|
4
4
|
* Standalone utility functions and classes used by the query builder.
|
|
5
5
|
*/
|
|
6
6
|
import pg from 'pg';
|
|
7
7
|
import { localDateTimeKind, timeOfDayKind } from '../schema.js';
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
|
-
// Identifier quoting
|
|
9
|
+
// Identifier quoting, prevents SQL injection via table/column names
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
/**
|
|
12
12
|
* Quote a SQL identifier (table name, column name) using Postgres double-quote
|
|
@@ -25,7 +25,7 @@ export function quoteIdent(name) {
|
|
|
25
25
|
* relations, reverseColumnMap). These are constructed as plain objects, so a
|
|
26
26
|
* bare `map[key]` for a user-supplied field name like "constructor",
|
|
27
27
|
* "toString", or "__proto__" returns an inherited member from
|
|
28
|
-
* `Object.prototype
|
|
28
|
+
* `Object.prototype`, a truthy value that slips past validation and produces a
|
|
29
29
|
* cryptic `TypeError` instead of a clean `ValidationError`. Returns `undefined`
|
|
30
30
|
* unless `key` is an OWN enumerable/non-enumerable property.
|
|
31
31
|
*/
|
|
@@ -47,7 +47,7 @@ export function escapeLike(value) {
|
|
|
47
47
|
return value.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
|
|
48
48
|
}
|
|
49
49
|
// ---------------------------------------------------------------------------
|
|
50
|
-
// LRU cache
|
|
50
|
+
// LRU cache, bounded SQL template cache to prevent memory leaks
|
|
51
51
|
// ---------------------------------------------------------------------------
|
|
52
52
|
/**
|
|
53
53
|
* Simple LRU (Least Recently Used) cache with a fixed maximum size.
|
|
@@ -104,14 +104,14 @@ export function fnv1a64Hex(s) {
|
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
106
|
* Derive a prepared-statement name from a SQL string.
|
|
107
|
-
* Format: `t_<16hex
|
|
107
|
+
* Format: `t_<16hex>`, always 18 chars, well under NAMEDATALEN (63).
|
|
108
108
|
*
|
|
109
109
|
* @internal Exported for testing only.
|
|
110
110
|
*/
|
|
111
111
|
export function sqlToPreparedName(sql) {
|
|
112
112
|
return `t_${fnv1a64Hex(sql)}`;
|
|
113
113
|
}
|
|
114
|
-
/** Known operator keys
|
|
114
|
+
/** Known operator keys, used to detect operator objects vs plain values */
|
|
115
115
|
export const OPERATOR_KEYS = new Set([
|
|
116
116
|
'equals',
|
|
117
117
|
'gt',
|
|
@@ -214,8 +214,8 @@ export function temporalBindKind(dbType, utcDateTimes = true) {
|
|
|
214
214
|
* per-element rewrite is what a `time[]` column needs, and matches the scalar
|
|
215
215
|
* case rather than silently binding an ISO timestamp).
|
|
216
216
|
*
|
|
217
|
-
* Everything else
|
|
218
|
-
* `timestamptz` column
|
|
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
219
|
* no-op outside the shapes above.
|
|
220
220
|
*/
|
|
221
221
|
export function coerceTemporalValue(dbType, value, utcDateTimes = true) {
|
|
@@ -259,13 +259,13 @@ const TZ_SUFFIX_RE = /(?:Z|[+-]\d{2}(?::?\d{2})?)$/;
|
|
|
259
259
|
/**
|
|
260
260
|
* Parse a database date-time string deterministically.
|
|
261
261
|
*
|
|
262
|
-
* Postgres `timestamp` (without time zone) values arrive with no offset
|
|
262
|
+
* Postgres `timestamp` (without time zone) values arrive with no offset -
|
|
263
263
|
* both from the driver and from `json_agg`/`json_build_object` subquery JSON
|
|
264
264
|
* (`2026-07-07T17:15:41.896`). JavaScript's `new Date()` interprets such
|
|
265
265
|
* strings in the SERVER'S LOCAL TIME ZONE, so the same row parses to a
|
|
266
266
|
* different instant depending on where the code runs. The universal ORM
|
|
267
267
|
* convention (Prisma, Rails, Django) is to treat offset-less timestamps as
|
|
268
|
-
* UTC
|
|
268
|
+
* UTC, that is also the only interpretation that round-trips: Postgres
|
|
269
269
|
* stores exactly the wall-clock fields you sent.
|
|
270
270
|
*
|
|
271
271
|
* Strings that carry an explicit offset (`timestamptz` output) are parsed
|
|
@@ -273,12 +273,12 @@ const TZ_SUFFIX_RE = /(?:Z|[+-]\d{2}(?::?\d{2})?)$/;
|
|
|
273
273
|
*/
|
|
274
274
|
export function parseDbDate(value) {
|
|
275
275
|
// Date-only values (`2026-07-07`, from `date` columns in json_agg output)
|
|
276
|
-
// have no time to zone-pin
|
|
276
|
+
// have no time to zone-pin, and their `-07` tail must not be read as an
|
|
277
277
|
// offset. JS parses bare ISO dates as UTC midnight already.
|
|
278
278
|
if (!value.includes(':'))
|
|
279
279
|
return new Date(value);
|
|
280
280
|
if (TZ_SUFFIX_RE.test(value)) {
|
|
281
|
-
// JS Date can't parse colon-less (`-0430`) or bare-hour (`+02`) offsets
|
|
281
|
+
// JS Date can't parse colon-less (`-0430`) or bare-hour (`+02`) offsets -
|
|
282
282
|
// normalize both to `±HH:MM`. Postgres emits the bare-hour form for
|
|
283
283
|
// whole-hour zones in some text outputs.
|
|
284
284
|
return new Date(value.replace(/([+-]\d{2})(\d{2})$/, '$1:$2').replace(/([+-]\d{2})$/, '$1:00'));
|
|
@@ -296,7 +296,7 @@ export function parseDbDate(value) {
|
|
|
296
296
|
* Why this table exists: the `'join'` strategy reads a relation through
|
|
297
297
|
* `json_agg(json_build_object(...))`, so its values are whatever
|
|
298
298
|
* `JSON.parse` makes of Postgres's JSON rendering. Every other read path in
|
|
299
|
-
* the library
|
|
299
|
+
* the library, a top-level row, `'batched'`, `'flatten'`, reads the column
|
|
300
300
|
* through the driver and gets the driver's representation. Measured against
|
|
301
301
|
* PostgreSQL 17, those two disagree for exactly the families below, which
|
|
302
302
|
* made the SAME query return a different JS type depending on which plan ran
|
|
@@ -315,7 +315,7 @@ export function parseDbDate(value) {
|
|
|
315
315
|
* circle { x, y, radius } '<(1,2),3>' (string)
|
|
316
316
|
*
|
|
317
317
|
* The array forms diverge the same way, plus `_timestamp`/`_timestamptz`
|
|
318
|
-
* (driver: `Date[]`; JSON: `string[]`)
|
|
318
|
+
* (driver: `Date[]`; JSON: `string[]`), the scalar `timestamp` /
|
|
319
319
|
* `timestamptz` are deliberately ABSENT because the existing `dateColumns`
|
|
320
320
|
* coercion in `parseRow` already lands them on the driver's value, and they
|
|
321
321
|
* are the hottest column type in a typical schema (no reason to add a cast to
|
|
@@ -374,3 +374,73 @@ export function coerceJsonWireValue(oid, value) {
|
|
|
374
374
|
return value;
|
|
375
375
|
return pg.types.getTypeParser(oid, 'text')(value);
|
|
376
376
|
}
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
// Unknown-field diagnostics
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
/**
|
|
381
|
+
* Case-insensitive closeness of `candidate` to `input`, higher is better,
|
|
382
|
+
* 0 meaning "not worth suggesting". Deliberately tiny: this runs only on the
|
|
383
|
+
* error path, and its whole job is to turn a guessed name into the real one.
|
|
384
|
+
*
|
|
385
|
+
* Substring containment is scored ABOVE edit distance because the real-world
|
|
386
|
+
* miss is a longer, more descriptive guess than the actual name (`modelVersions`
|
|
387
|
+
* for a relation turbine derived as `versions`), where the edit distance is
|
|
388
|
+
* large but the containment is exact.
|
|
389
|
+
*/
|
|
390
|
+
function nameCloseness(input, candidate) {
|
|
391
|
+
const a = input.toLowerCase();
|
|
392
|
+
const b = candidate.toLowerCase();
|
|
393
|
+
if (a === b)
|
|
394
|
+
return 1000;
|
|
395
|
+
if (a.includes(b) || b.includes(a))
|
|
396
|
+
return 500 + Math.min(a.length, b.length);
|
|
397
|
+
// Levenshtein, bounded: only near-misses are worth suggesting.
|
|
398
|
+
const rows = a.length + 1;
|
|
399
|
+
const cols = b.length + 1;
|
|
400
|
+
let prev = Array.from({ length: cols }, (_, j) => j);
|
|
401
|
+
for (let i = 1; i < rows; i++) {
|
|
402
|
+
const cur = [i];
|
|
403
|
+
for (let j = 1; j < cols; j++) {
|
|
404
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
405
|
+
}
|
|
406
|
+
prev = cur;
|
|
407
|
+
}
|
|
408
|
+
const distance = prev[cols - 1];
|
|
409
|
+
const limit = Math.max(2, Math.floor(Math.max(a.length, b.length) / 3));
|
|
410
|
+
return distance <= limit ? 100 - distance : 0;
|
|
411
|
+
}
|
|
412
|
+
/** The closest name in `candidates` to `input`, or null when none is close. */
|
|
413
|
+
export function closestName(input, candidates) {
|
|
414
|
+
let best = null;
|
|
415
|
+
let bestScore = 0;
|
|
416
|
+
for (const c of candidates) {
|
|
417
|
+
const score = nameCloseness(input, c);
|
|
418
|
+
if (score > bestScore) {
|
|
419
|
+
bestScore = score;
|
|
420
|
+
best = c;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return best;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* The "unknown field" error text, listing RELATIONS as well as columns.
|
|
427
|
+
*
|
|
428
|
+
* The message used to read `Known fields: id, name.` and nothing else. That is
|
|
429
|
+
* actively misleading when the key was a relation name: relation filters ARE
|
|
430
|
+
* valid in a `where`, so a user who guessed the wrong relation name concluded
|
|
431
|
+
* from this message that turbine cannot filter by relations at all. Relation
|
|
432
|
+
* names are frequently guessed wrong because introspection derives them, and
|
|
433
|
+
* two foreign keys to one table produce names (`msgsBySender`) that no one
|
|
434
|
+
* would predict.
|
|
435
|
+
*/
|
|
436
|
+
export function unknownFieldMessage(table, field, meta) {
|
|
437
|
+
const columns = Object.keys(meta.columnMap);
|
|
438
|
+
const relations = Object.keys(meta.relations ?? {});
|
|
439
|
+
const suggestion = closestName(field, [...columns, ...relations]);
|
|
440
|
+
const didYouMean = suggestion
|
|
441
|
+
? ` Did you mean "${suggestion}"${relations.includes(suggestion) ? ' (a relation)' : ''}?`
|
|
442
|
+
: '';
|
|
443
|
+
return (`[turbine] Unknown field "${field}" on table "${table}".${didYouMean}` +
|
|
444
|
+
` Known columns: ${columns.join(', ') || '(none)'}.` +
|
|
445
|
+
(relations.length ? ` Known relations (valid in \`where\` and \`with\`): ${relations.join(', ')}.` : ''));
|
|
446
|
+
}
|