uql-orm 0.67.1 → 0.68.1
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/dist/browser/http/http.js +5 -8
- package/dist/browser/querier/httpQuerier.d.ts +9 -9
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +9 -8
- package/dist/cockroachdb/cockroachDialect.d.ts +6 -4
- package/dist/cockroachdb/cockroachDialect.js +7 -3
- package/dist/d1/d1Querier.d.ts +5 -5
- package/dist/d1/d1QuerierPool.d.ts +3 -3
- package/dist/d1/d1SqliteDialect.d.ts +1 -1
- package/dist/d1/d1SqliteDialect.js +1 -1
- package/dist/dialect/abstractSqlDialect.d.ts +107 -97
- package/dist/dialect/abstractSqlDialect.js +250 -293
- package/dist/dialect/hydrateColumn.js +33 -30
- package/dist/dialect/jsonSql.d.ts +32 -23
- package/dist/dialect/jsonSql.js +42 -31
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +19 -23
- package/dist/dialect/mysqlLikeSqlDialect.js +34 -51
- package/dist/dialect/pgLikeSqlDialect.d.ts +24 -17
- package/dist/dialect/pgLikeSqlDialect.js +53 -26
- package/dist/dialect/vectorCast.d.ts +2 -0
- package/dist/dialect/vectorCast.js +25 -5
- package/dist/dialect/vectorSqlDialect.d.ts +8 -8
- package/dist/dialect/vectorSqlDialect.js +11 -11
- package/dist/http/query.d.ts +10 -2
- package/dist/http/query.js +26 -1
- package/dist/maria/mariaDialect.d.ts +16 -11
- package/dist/maria/mariaDialect.js +23 -19
- package/dist/migrate/ddl/indexDdl.d.ts +3 -1
- package/dist/migrate/ddl/indexDdl.js +5 -1
- package/dist/migrate/ddl/mysqlIndexDdl.d.ts +7 -2
- package/dist/migrate/ddl/mysqlIndexDdl.js +28 -6
- package/dist/migrate/ddl/pgIndexDdl.d.ts +0 -9
- package/dist/migrate/ddl/pgIndexDdl.js +2 -6
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +12 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +13 -3
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +2 -9
- package/dist/migrate/introspection/mssqlIntrospector.js +2 -9
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +2 -9
- package/dist/migrate/introspection/mysqlIntrospector.js +2 -8
- package/dist/mongo/mongoDialect.d.ts +22 -10
- package/dist/mongo/mongoDialect.js +86 -38
- package/dist/mssql/mssqlDialect.d.ts +22 -18
- package/dist/mssql/mssqlDialect.js +54 -43
- package/dist/mysql/mysqlDialect.d.ts +16 -1
- package/dist/mysql/mysqlDialect.js +18 -2
- package/dist/sqlite/sqliteDialect.d.ts +17 -16
- package/dist/sqlite/sqliteDialect.js +35 -39
- package/dist/type/dialect.d.ts +0 -4
- package/dist/type/entity.d.ts +12 -7
- package/dist/type/query.d.ts +29 -24
- package/dist/type/queryWhere.d.ts +20 -20
- package/dist/type/universalQuerier.d.ts +10 -10
- package/dist/type/vector.d.ts +5 -7
- package/dist/type/wire.d.ts +9 -0
- package/dist/util/dialect.util.d.ts +2 -0
- package/dist/util/dialect.util.js +6 -2
- package/dist/util/object.util.d.ts +2 -4
- package/dist/util/object.util.js +4 -9
- package/package.json +2 -2
- package/dist/dialect/jsonArrayElemMatchUtils.d.ts +0 -2
- package/dist/dialect/jsonArrayElemMatchUtils.js +0 -7
- package/dist/dialect/pgVectorMetrics.d.ts +0 -13
- package/dist/dialect/pgVectorMetrics.js +0 -17
- package/dist/maria/mariaVectorMetrics.d.ts +0 -8
- package/dist/maria/mariaVectorMetrics.js +0 -10
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { relationTermKey } from '../dialect/abstractSqlDialect.js';
|
|
2
|
-
import { COUNT_ALIAS,
|
|
2
|
+
import { COUNT_ALIAS, JSON_PULL_ALIAS } from '../dialect/aliases.js';
|
|
3
3
|
import { BYTES_PREFIX } from '../dialect/hydrateColumn.js';
|
|
4
|
-
import { jsonPath } from '../dialect/jsonSql.js';
|
|
4
|
+
import { jsonArraySlotArgs, jsonPath, jsonSlotArgs } from '../dialect/jsonSql.js';
|
|
5
5
|
import { MergeSqlDialect } from '../dialect/mergeSqlDialect.js';
|
|
6
6
|
import { getMeta } from '../entity/index.js';
|
|
7
7
|
import { fieldOptionsToCanonical } from '../schema/canonicalType.js';
|
|
@@ -34,12 +34,17 @@ const MSSQL_FEATURES = {
|
|
|
34
34
|
rowLockOf: true,
|
|
35
35
|
orderedUpsertReturning: false,
|
|
36
36
|
orderedJsonAggregates: true,
|
|
37
|
-
partialJsonContainment: false,
|
|
38
|
-
typedJsonElements: false,
|
|
39
37
|
narrowVectorTypes: false,
|
|
40
38
|
vectorTuningNeedsTransaction: false,
|
|
41
39
|
serialDeclaresPrimaryKey: false,
|
|
42
40
|
};
|
|
41
|
+
/** The `type` `OPENJSON` reports for the JSON scalar an element is compared with; anything else binds as a string. */
|
|
42
|
+
function openJsonType(value) {
|
|
43
|
+
if (typeof value === 'number') {
|
|
44
|
+
return 2;
|
|
45
|
+
}
|
|
46
|
+
return typeof value === 'boolean' ? 3 : 1;
|
|
47
|
+
}
|
|
43
48
|
/** Microsoft SQL Server 2017 and up. Identifiers are `"`-quoted, the ANSI spelling `tedious` enables. */
|
|
44
49
|
export class MsSqlDialect extends MergeSqlDialect {
|
|
45
50
|
features = MSSQL_FEATURES;
|
|
@@ -234,25 +239,19 @@ export class MsSqlDialect extends MergeSqlDialect {
|
|
|
234
239
|
return `TRY_CAST(${expr} AS FLOAT)`;
|
|
235
240
|
}
|
|
236
241
|
/**
|
|
237
|
-
* `
|
|
238
|
-
*
|
|
239
|
-
*
|
|
242
|
+
* `OPENJSON` at the path's parent, matching its last segment as a key, in either reading: `JSON_VALUE`
|
|
243
|
+
* answers NULL for text past 4000 characters, and `JSON_QUERY` for a scalar. A value reads back as
|
|
244
|
+
* text, which {@link jsonScalarParam} binds its operand as, and an array or object as its own JSON.
|
|
240
245
|
*/
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
246
|
+
jsonPathReading(escapedColumn, path) {
|
|
247
|
+
if (!path) {
|
|
248
|
+
return escapedColumn;
|
|
249
|
+
}
|
|
250
|
+
const dot = path.lastIndexOf('.');
|
|
251
|
+
const parent = dot === -1 ? '$' : `$.${path.slice(0, dot).split('.').map(escapeSingleQuotes).join('.')}`;
|
|
252
|
+
const leaf = escapeSingleQuotes(path.slice(dot + 1));
|
|
245
253
|
return `(SELECT ${this.#elem.value} FROM OPENJSON(${escapedColumn}, '${parent}') WHERE ${this.#elem.key} = N'${leaf}')`;
|
|
246
254
|
}
|
|
247
|
-
/**
|
|
248
|
-
* The same read as the scalar one. `JSON_QUERY` answers NULL for anything that is not an object or
|
|
249
|
-
* an array, so it cannot serve the JSON access mode a boolean or a number operand asks for -
|
|
250
|
-
* `OPENJSON` returns both as text, and {@link jsonScalarParam} binds the operand as the matching
|
|
251
|
-
* text. An array or object comes back as its own JSON text, which is what `OPENJSON` takes next.
|
|
252
|
-
*/
|
|
253
|
-
getJsonPathJsonbExpr(escapedColumn, jsonPathStr) {
|
|
254
|
-
return this.getJsonPathScalarExpr(escapedColumn, jsonPathStr);
|
|
255
|
-
}
|
|
256
255
|
/**
|
|
257
256
|
* A value compared against a JSON path, which reads back as text, so only a boolean needs spelling as
|
|
258
257
|
* `'true'`; SQL Server has no cast that parses text as JSON.
|
|
@@ -286,24 +285,37 @@ export class MsSqlDialect extends MergeSqlDialect {
|
|
|
286
285
|
}
|
|
287
286
|
return `JSON_QUERY(${this.addValue(ctx, JSON.stringify(value))})`;
|
|
288
287
|
}
|
|
289
|
-
jsonElemFrom(
|
|
290
|
-
return `OPENJSON(${
|
|
288
|
+
jsonElemFrom(slot, alias) {
|
|
289
|
+
return `OPENJSON(${jsonArraySlotArgs(slot, this.jsonIsArray(slot))}) ${alias}`;
|
|
290
|
+
}
|
|
291
|
+
/** `JSON_QUERY` answers an array or an object as written, and a scalar as NULL. */
|
|
292
|
+
jsonIsArray(slot) {
|
|
293
|
+
return `LEFT(JSON_QUERY(${jsonSlotArgs(slot)}), 1) = '['`;
|
|
291
294
|
}
|
|
292
|
-
/**
|
|
293
|
-
|
|
294
|
-
return
|
|
295
|
-
? `${alias}.${this.#elem.value}`
|
|
296
|
-
: `JSON_VALUE(${alias}.${this.#elem.value}, ${jsonPath(field)})`;
|
|
295
|
+
/** An object element's `value` is its JSON text, which its fields are paths into. */
|
|
296
|
+
jsonElemDoc(alias) {
|
|
297
|
+
return `${alias}.${this.#elem.value}`;
|
|
297
298
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
299
|
+
/**
|
|
300
|
+
* An element of the value's own JSON type: `OPENJSON` reads a string and a number back as the same text,
|
|
301
|
+
* so the `type` it reports is what tells `'5'` from `5`. A number compares by value, cast on both sides:
|
|
302
|
+
* text against a numeric parameter converts implicitly, which throws on an element that is no number.
|
|
303
|
+
*/
|
|
304
|
+
jsonElemEquals(ctx, _slot, alias, value) {
|
|
305
|
+
const type = `${alias}.${this.#elem.type}`;
|
|
306
|
+
if (value === null) {
|
|
307
|
+
return `${type} = 0`;
|
|
308
|
+
}
|
|
309
|
+
const elem = this.jsonElemDoc(alias);
|
|
310
|
+
const param = this.jsonScalarParam(ctx, value);
|
|
311
|
+
const equal = typeof value === 'number' ? `${this.numericCast(elem)} = ${this.numericCast(param)}` : `${elem} = ${param}`;
|
|
312
|
+
return `${equal} AND ${type} = ${openJsonType(value)}`;
|
|
302
313
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return this.buildFragment(ctx, (fragmentCtx) => this.buildSizeComparison(fragmentCtx, () => fragmentCtx.append(`(SELECT COUNT(*) FROM OPENJSON(${jsonField}) ${alias})`), value));
|
|
314
|
+
jsonLength(slot) {
|
|
315
|
+
return `(SELECT COUNT(*) FROM OPENJSON(${jsonArraySlotArgs(slot, this.jsonIsArray(slot))}))`;
|
|
306
316
|
}
|
|
317
|
+
/** SQL Server orders JSON as the text `OPENJSON` reads, so a number sorts by its value first. */
|
|
318
|
+
jsonSortModes = ['numeric', 'text'];
|
|
307
319
|
/** `JSON_MODIFY` takes one path per call, so several keys chain into one expression. */
|
|
308
320
|
jsonSet(ctx, expr, set, _field) {
|
|
309
321
|
for (const [key, value] of Object.entries(set)) {
|
|
@@ -323,25 +335,24 @@ export class MsSqlDialect extends MergeSqlDialect {
|
|
|
323
335
|
}
|
|
324
336
|
/**
|
|
325
337
|
* The surviving elements are re-aggregated into an array and written back whole - there is no
|
|
326
|
-
* remove-by-value. `JSON_QUERY` is what marks the rebuilt text as JSON rather than a string.
|
|
338
|
+
* remove-by-value. `JSON_QUERY` is what marks the rebuilt text as JSON rather than a string. Only an
|
|
339
|
+
* array is rewritten: `JSON_MODIFY` would create an absent key, and any other value stays as it is.
|
|
327
340
|
*/
|
|
328
341
|
jsonPullKey(ctx, expr, escapedCol, key, value) {
|
|
329
|
-
const
|
|
330
|
-
const val =
|
|
342
|
+
const slot = { base: escapedCol, path: key };
|
|
343
|
+
const val = this.jsonElemDoc(JSON_PULL_ALIAS);
|
|
331
344
|
// `OPENJSON` hands back a string element unquoted and a null one as SQL NULL, so each survivor is
|
|
332
345
|
// re-encoded from its reported `type` before the array is put back together - concatenated raw,
|
|
333
346
|
// the result is text the engine then refuses to parse as JSON.
|
|
334
|
-
const encoded = `CASE ${
|
|
347
|
+
const encoded = `CASE ${JSON_PULL_ALIAS}.${this.#elem.type}` +
|
|
335
348
|
` WHEN 0 THEN 'null'` +
|
|
336
349
|
` WHEN 1 THEN '"' + STRING_ESCAPE(${val}, 'json') + '"'` +
|
|
337
350
|
` ELSE ${val} END`;
|
|
338
351
|
// `IS NULL OR` because a JSON null element reads back as SQL NULL, and `<>` against one is
|
|
339
352
|
// unknown rather than true - which silently dropped every null from the array it rebuilt.
|
|
340
|
-
const kept = `SELECT '[' + STRING_AGG(${encoded}, ',') + ']' FROM
|
|
353
|
+
const kept = `SELECT '[' + STRING_AGG(${encoded}, ',') + ']' FROM ${this.jsonElemFrom(slot, JSON_PULL_ALIAS)}` +
|
|
341
354
|
` WHERE ${val} IS NULL OR ${val} <> ${this.jsonScalarParam(ctx, value)}`;
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const path = jsonPath(key);
|
|
345
|
-
return `CASE WHEN JSON_QUERY(${escapedCol}, ${path}) IS NULL THEN ${expr} ELSE JSON_MODIFY(${expr}, ${path}, JSON_QUERY(COALESCE((${kept}), '[]'))) END`;
|
|
355
|
+
const pulled = `JSON_MODIFY(${expr}, ${jsonPath(key)}, JSON_QUERY(COALESCE((${kept}), '[]')))`;
|
|
356
|
+
return `CASE WHEN ${this.jsonIsArray(slot)} THEN ${pulled} ELSE ${expr} END`;
|
|
346
357
|
}
|
|
347
358
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { JsonSlot } from '../dialect/jsonSql.js';
|
|
1
2
|
import { MysqlLikeSqlDialect } from '../dialect/mysqlLikeSqlDialect.js';
|
|
3
|
+
import type { QueryContext } from '../type/index.js';
|
|
2
4
|
export declare class MySqlDialect extends MysqlLikeSqlDialect {
|
|
3
5
|
readonly dialectName = "mysql";
|
|
4
6
|
/**
|
|
@@ -6,6 +8,19 @@ export declare class MySqlDialect extends MysqlLikeSqlDialect {
|
|
|
6
8
|
* "subject to removal in a future version"; aliasing the inserted row (8.0.19+) is its replacement.
|
|
7
9
|
*/
|
|
8
10
|
protected readonly upsertNewRowAlias = "_uql_new";
|
|
9
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* 26.7 may plan the correlated `JSON_TABLE` as a semijoin materialized once for the whole table, which
|
|
13
|
+
* answers every row with one row's elements. Verified in `mysqlJsonElemMatch.test.ts`.
|
|
14
|
+
*/
|
|
15
|
+
protected readonly jsonElemHint = "/*+ NO_SEMIJOIN() */";
|
|
16
|
+
/**
|
|
17
|
+
* `JSON_OVERLAPS`, which a multi-valued index serves: verified in `mysqlJsonArrayIndex.test.ts`. It reads
|
|
18
|
+
* a scalar as an array of one, so the value must be an array.
|
|
19
|
+
*/
|
|
20
|
+
protected jsonAny(ctx: QueryContext, slot: JsonSlot, values: readonly unknown[]): string;
|
|
21
|
+
/**
|
|
22
|
+
* A `SET_VAR` hint, which MySQL reads only right after the statement's own `SELECT` keyword - before
|
|
23
|
+
* `DISTINCT`, and never in a subquery - so it is anchored to the start rather than found.
|
|
24
|
+
*/
|
|
10
25
|
protected applySettings(sql: string, settings: readonly string[]): string;
|
|
11
26
|
}
|
|
@@ -7,8 +7,24 @@ export class MySqlDialect extends MysqlLikeSqlDialect {
|
|
|
7
7
|
* "subject to removal in a future version"; aliasing the inserted row (8.0.19+) is its replacement.
|
|
8
8
|
*/
|
|
9
9
|
upsertNewRowAlias = UPSERT_NEW_ROW_ALIAS;
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* 26.7 may plan the correlated `JSON_TABLE` as a semijoin materialized once for the whole table, which
|
|
12
|
+
* answers every row with one row's elements. Verified in `mysqlJsonElemMatch.test.ts`.
|
|
13
|
+
*/
|
|
14
|
+
jsonElemHint = '/*+ NO_SEMIJOIN() */';
|
|
15
|
+
/**
|
|
16
|
+
* `JSON_OVERLAPS`, which a multi-valued index serves: verified in `mysqlJsonArrayIndex.test.ts`. It reads
|
|
17
|
+
* a scalar as an array of one, so the value must be an array.
|
|
18
|
+
*/
|
|
19
|
+
jsonAny(ctx, slot, values) {
|
|
20
|
+
const overlaps = `JSON_OVERLAPS(${this.jsonValue(slot)}, ${this.addValue(ctx, JSON.stringify(values))})`;
|
|
21
|
+
return `(${this.jsonIsArray(slot)} AND ${overlaps})`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A `SET_VAR` hint, which MySQL reads only right after the statement's own `SELECT` keyword - before
|
|
25
|
+
* `DISTINCT`, and never in a subquery - so it is anchored to the start rather than found.
|
|
26
|
+
*/
|
|
11
27
|
applySettings(sql, settings) {
|
|
12
|
-
return sql.replace(
|
|
28
|
+
return sql.replace(/^SELECT /, `SELECT /*+ ${settings.map((setting) => `SET_VAR(${setting})`).join(' ')} */ `);
|
|
13
29
|
}
|
|
14
30
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AbstractSqlDialect, type DerivedRelation, type HydrateKind, type RelationRows } from '../dialect/abstractSqlDialect.js';
|
|
2
|
-
import
|
|
2
|
+
import { type JsonAccessMode, type JsonSlot } from '../dialect/jsonSql.js';
|
|
3
|
+
import type { EntityMeta, FieldOptions, QueryContext, QueryPager, QueryTextSearchOptions, SqlDialectFeatures, Type, VectorDistance, VectorMetric } from '../type/index.js';
|
|
3
4
|
/** What SQLite and the engines derived from it have. */
|
|
4
5
|
export declare const SQLITE_FEATURES: SqlDialectFeatures;
|
|
5
6
|
export declare class SqliteDialect extends AbstractSqlDialect {
|
|
@@ -58,25 +59,25 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
58
59
|
* FTS5 virtual table (UQL does not create those; declare it outside your entities).
|
|
59
60
|
*/
|
|
60
61
|
protected appendTextSearch<E>(ctx: QueryContext, entity: Type<E>, meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): void;
|
|
62
|
+
protected jsonLength(slot: JsonSlot): string;
|
|
63
|
+
/** `JSON_EACH` walks the array at the path itself, each element's `fullkey` naming it from the column. */
|
|
64
|
+
protected jsonElemFrom(slot: JsonSlot, alias: string): string;
|
|
65
|
+
protected jsonIsArray(slot: JsonSlot): string;
|
|
66
|
+
/** An object element's `value` is its JSON text, which its fields are paths into. */
|
|
67
|
+
protected jsonElemDoc(alias: string): string;
|
|
61
68
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* vs `"a"`), flattens booleans to 0/1, and stringifies objects.
|
|
69
|
+
* A scalar element's `value` is already typed, so a number and a string compare as such. Its JSON form
|
|
70
|
+
* is read back from the column at the element's own `fullkey`, since `value` flattens a boolean to 0/1.
|
|
65
71
|
*/
|
|
66
|
-
protected
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
protected
|
|
71
|
-
protected getJsonPathScalarExpr(escapedColumn: string, jsonPathStr: string): string;
|
|
72
|
+
protected jsonElemValue(slot: JsonSlot, alias: string, mode: JsonAccessMode): string;
|
|
73
|
+
/** `JSON_EXTRACT` already answers a number as one, and orders it by value. */
|
|
74
|
+
protected readonly jsonSortModes: readonly JsonAccessMode[];
|
|
75
|
+
/** `->` for the JSON value, and `JSON_EXTRACT` for SQLite's own: a number as a number, a string as text. */
|
|
76
|
+
protected jsonPathReading(escapedColumn: string, path: string, mode: 'json' | 'text'): string;
|
|
72
77
|
protected numericCast(expr: string): string;
|
|
73
78
|
protected jsonCast(operand: string): string;
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
* through `->` at their own `fullkey` so each keeps its JSON type - `JSON_EACH`'s `value` would
|
|
77
|
-
* flatten booleans to 0/1 and stringify objects.
|
|
78
|
-
*/
|
|
79
|
-
protected jsonPullKey(ctx: QueryContext, expr: string, escapedCol: string, key: string, value: unknown): string;
|
|
79
|
+
/** `JSON` keeps each element's JSON type in the array, which answers `[]` for no rows. */
|
|
80
|
+
protected jsonArrayOf(elem: string): string;
|
|
80
81
|
protected jsonSet(ctx: QueryContext, expr: string, set: Record<string, unknown>, field?: FieldOptions): string;
|
|
81
82
|
/** `[#]` appends, creating the array where it is absent: `JSON_SET`, since Turso's `JSON_INSERT` will not touch an existing array. */
|
|
82
83
|
protected jsonPush(ctx: QueryContext, expr: string, push: Record<string, unknown>): string;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AbstractSqlDialect, } from '../dialect/abstractSqlDialect.js';
|
|
2
|
-
import { JSON_ELEM_ALIAS, JSON_PULL_ALIAS } from '../dialect/aliases.js';
|
|
3
2
|
import { BYTES_PREFIX } from '../dialect/hydrateColumn.js';
|
|
4
|
-
import { chainedCall, groupsPerCall,
|
|
3
|
+
import { chainedCall, groupsPerCall, jsonSetCall, jsonPath, jsonArraySlotArgs, jsonSlotArgs, jsonRemoveCall, jsonSetTarget, } from '../dialect/jsonSql.js';
|
|
5
4
|
import { textSearchFields } from '../util/dialect.util.js';
|
|
6
5
|
import { columnFamily, isIntegerColumn } from '../util/field.util.js';
|
|
7
6
|
/** What SQLite and the engines derived from it have. */
|
|
@@ -25,8 +24,6 @@ export const SQLITE_FEATURES = {
|
|
|
25
24
|
rowLockOf: true,
|
|
26
25
|
orderedUpsertReturning: true,
|
|
27
26
|
orderedJsonAggregates: true,
|
|
28
|
-
partialJsonContainment: false,
|
|
29
|
-
typedJsonElements: true,
|
|
30
27
|
narrowVectorTypes: false,
|
|
31
28
|
vectorTuningNeedsTransaction: false,
|
|
32
29
|
serialDeclaresPrimaryKey: true,
|
|
@@ -126,32 +123,38 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
126
123
|
ctx.append(`${this.escapedTableName(meta)} MATCH {${columns.join(' ')}} : `);
|
|
127
124
|
ctx.addValue(search.$value);
|
|
128
125
|
}
|
|
126
|
+
jsonLength(slot) {
|
|
127
|
+
return `JSON_ARRAY_LENGTH(${jsonArraySlotArgs(slot, this.jsonIsArray(slot))})`;
|
|
128
|
+
}
|
|
129
|
+
/** `JSON_EACH` walks the array at the path itself, each element's `fullkey` naming it from the column. */
|
|
130
|
+
jsonElemFrom(slot, alias) {
|
|
131
|
+
return `JSON_EACH(${jsonArraySlotArgs(slot, this.jsonIsArray(slot))}) ${alias}`;
|
|
132
|
+
}
|
|
133
|
+
jsonIsArray(slot) {
|
|
134
|
+
return `JSON_TYPE(${jsonSlotArgs(slot)}) = 'array'`;
|
|
135
|
+
}
|
|
136
|
+
/** An object element's `value` is its JSON text, which its fields are paths into. */
|
|
137
|
+
jsonElemDoc(alias) {
|
|
138
|
+
return `${alias}.value`;
|
|
139
|
+
}
|
|
129
140
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* vs `"a"`), flattens booleans to 0/1, and stringifies objects.
|
|
141
|
+
* A scalar element's `value` is already typed, so a number and a string compare as such. Its JSON form
|
|
142
|
+
* is read back from the column at the element's own `fullkey`, since `value` flattens a boolean to 0/1.
|
|
133
143
|
*/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const conditions = value.map((val) => jsonElemExists(from, [`${jsonField} -> ${alias}.fullkey = ${this.jsonScalarParam(ctx, val)}`]));
|
|
138
|
-
return `(${conditions.join(' AND ')})`;
|
|
139
|
-
}
|
|
140
|
-
jsonSize(ctx, jsonField, value) {
|
|
141
|
-
return this.buildFragment(ctx, (fragmentCtx) => this.buildSizeComparison(fragmentCtx, () => fragmentCtx.append(`JSON_ARRAY_LENGTH(${jsonField})`), value));
|
|
142
|
-
}
|
|
143
|
-
/** `JSON_EACH` yields both scalar and object elements, so one form covers each case. */
|
|
144
|
-
jsonElemFrom(jsonField, _fields, alias) {
|
|
145
|
-
return `JSON_EACH(${jsonField}) ${alias}`;
|
|
146
|
-
}
|
|
147
|
-
jsonElemRef(alias, field, asJson = false) {
|
|
148
|
-
if (field === undefined) {
|
|
149
|
-
return `${alias}.value`;
|
|
144
|
+
jsonElemValue(slot, alias, mode) {
|
|
145
|
+
if (mode === 'json') {
|
|
146
|
+
return `${slot.base} -> ${alias}.fullkey`;
|
|
150
147
|
}
|
|
151
|
-
|
|
148
|
+
const value = this.jsonElemDoc(alias);
|
|
149
|
+
return mode === 'numeric' ? this.numericCast(value) : value;
|
|
152
150
|
}
|
|
153
|
-
|
|
154
|
-
|
|
151
|
+
/** `JSON_EXTRACT` already answers a number as one, and orders it by value. */
|
|
152
|
+
jsonSortModes = ['text'];
|
|
153
|
+
/** `->` for the JSON value, and `JSON_EXTRACT` for SQLite's own: a number as a number, a string as text. */
|
|
154
|
+
jsonPathReading(escapedColumn, path, mode) {
|
|
155
|
+
return mode === 'json'
|
|
156
|
+
? `(${escapedColumn} -> ${jsonPath(path)})`
|
|
157
|
+
: `JSON_EXTRACT(${escapedColumn}, ${jsonPath(path)})`;
|
|
155
158
|
}
|
|
156
159
|
numericCast(expr) {
|
|
157
160
|
return `CAST(${expr} AS REAL)`;
|
|
@@ -159,25 +162,18 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
159
162
|
jsonCast(operand) {
|
|
160
163
|
return `JSON(${operand})`;
|
|
161
164
|
}
|
|
162
|
-
/**
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
* flatten booleans to 0/1 and stringify objects.
|
|
166
|
-
*/
|
|
167
|
-
jsonPullKey(ctx, expr, escapedCol, key, value) {
|
|
168
|
-
const path = jsonPath(key);
|
|
169
|
-
const elem = `${escapedCol} -> ${JSON_PULL_ALIAS}.fullkey`;
|
|
170
|
-
const kept = `SELECT JSON_GROUP_ARRAY(JSON(${elem})) FROM JSON_EACH(${escapedCol}, ${path}) ${JSON_PULL_ALIAS} WHERE ${elem} <> ${this.jsonScalarParam(ctx, value)}`;
|
|
171
|
-
return `JSON_REPLACE(${expr}, ${path}, (${kept}))`;
|
|
165
|
+
/** `JSON` keeps each element's JSON type in the array, which answers `[]` for no rows. */
|
|
166
|
+
jsonArrayOf(elem) {
|
|
167
|
+
return `JSON_GROUP_ARRAY(JSON(${elem}))`;
|
|
172
168
|
}
|
|
173
169
|
jsonSet(ctx, expr, set, field) {
|
|
174
|
-
return
|
|
170
|
+
return jsonSetCall((value) => this.jsonScalarParam(ctx, value), jsonSetTarget(expr, field, `'{}'`), set, this.maxFunctionArgs);
|
|
175
171
|
}
|
|
176
172
|
/** `[#]` appends, creating the array where it is absent: `JSON_SET`, since Turso's `JSON_INSERT` will not touch an existing array. */
|
|
177
173
|
jsonPush(ctx, expr, push) {
|
|
178
|
-
return
|
|
174
|
+
return jsonSetCall((value) => this.jsonScalarParam(ctx, value), expr, push, this.maxFunctionArgs, '[#]');
|
|
179
175
|
}
|
|
180
176
|
jsonUnset(_ctx, expr, unset) {
|
|
181
|
-
return jsonRemoveCall(
|
|
177
|
+
return jsonRemoveCall(expr, unset, this.maxFunctionArgs);
|
|
182
178
|
}
|
|
183
179
|
}
|
package/dist/type/dialect.d.ts
CHANGED
|
@@ -135,10 +135,6 @@ export interface SqlDialectFeatures extends DialectFeatures {
|
|
|
135
135
|
readonly orderedUpsertReturning: boolean;
|
|
136
136
|
/** Whether a JSON aggregate takes an `ORDER BY` of its own; where not, a relation's rows keep their derived table's order. */
|
|
137
137
|
readonly orderedJsonAggregates: boolean;
|
|
138
|
-
/** Whether JSON array containment matches an object element that merely includes the given keys, as `@>` does. */
|
|
139
|
-
readonly partialJsonContainment: boolean;
|
|
140
|
-
/** Whether an exploded scalar JSON element keeps its SQL type, as SQLite's `JSON_EACH` does. */
|
|
141
|
-
readonly typedJsonElements: boolean;
|
|
142
138
|
/** Whether the engine has pgvector's `halfvec` and `sparsevec`; elsewhere both map onto `vector`. */
|
|
143
139
|
readonly narrowVectorTypes: boolean;
|
|
144
140
|
/** Whether an ANN index's tuning `SET` applies only inside a transaction, as `SET LOCAL` does. */
|
package/dist/type/entity.d.ts
CHANGED
|
@@ -84,7 +84,7 @@ export type JsonUpdateOp<T = unknown> = {
|
|
|
84
84
|
*/
|
|
85
85
|
type JsonUpdateOpFor<V, T = UnwrapJson<NonNullable<V>>> = [T] extends [never] ? never : IsMany<T> extends true ? never : JsonUpdateOp<T>;
|
|
86
86
|
/** What an update takes beyond the value: `null` to clear an optional member, `raw` SQL, and JSON operators. */
|
|
87
|
-
type UpdateExtra<V> = (undefined extends V ? null : never) |
|
|
87
|
+
type UpdateExtra<V, Raw> = (undefined extends V ? null : never) | Raw | JsonUpdateOpFor<V>;
|
|
88
88
|
/**
|
|
89
89
|
* What a whole-record write persists: the fields and relations with their declared optionality, a
|
|
90
90
|
* related row's alike, and no methods. Two mapped types, since asking each key costs a conditional.
|
|
@@ -97,10 +97,10 @@ export type EntityData<E, F extends keyof E = FieldKey<E>, R extends keyof E = R
|
|
|
97
97
|
/** A relation's value as its rows' {@link EntityData}. */
|
|
98
98
|
type RelationData<V> = V extends readonly (infer T)[] ? EntityData<T>[] : V extends object ? EntityData<V> : never;
|
|
99
99
|
/** {@link EntityData} made partial, each member also taking its {@link UpdateExtra}. */
|
|
100
|
-
export type UpdatePayload<E, F extends keyof E = FieldKey<E>, R extends keyof E = RelationKey<E>> = {
|
|
101
|
-
[P in F]?: E[P] | UpdateExtra<E[P]>;
|
|
100
|
+
export type UpdatePayload<E, Raw = QueryRaw, F extends keyof E = FieldKey<E>, R extends keyof E = RelationKey<E>> = {
|
|
101
|
+
[P in F]?: E[P] | UpdateExtra<E[P], Raw>;
|
|
102
102
|
} & {
|
|
103
|
-
[P in R]?: E[P] | RelationData<E[P]> | UpdateExtra<E[P]>;
|
|
103
|
+
[P in R]?: E[P] | RelationData<E[P]> | UpdateExtra<E[P], Raw>;
|
|
104
104
|
};
|
|
105
105
|
/** The key's name where the entity states it, by the `idKey` brand or a conventional name; `never` otherwise. */
|
|
106
106
|
export type NamedIdKey<E> = E extends {
|
|
@@ -181,7 +181,10 @@ export type FieldOptions<V = TsTypeOf<FieldType>, E = unknown> = {
|
|
|
181
181
|
readonly type?: FieldType;
|
|
182
182
|
/** A vector column's dimensions: `@Field({ type: 'vector', dimensions: 1536 })`. */
|
|
183
183
|
readonly dimensions?: number;
|
|
184
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* The metric a vector search on this field uses unless it names its own `$distance`; by default its
|
|
186
|
+
* vector index's metric, else `'cosine'`.
|
|
187
|
+
*/
|
|
185
188
|
readonly distance?: VectorDistance;
|
|
186
189
|
/** The entity this column is a foreign key to. */
|
|
187
190
|
readonly references?: EntityGetter;
|
|
@@ -445,10 +448,12 @@ export type IndexJsonPath = {
|
|
|
445
448
|
readonly path: string;
|
|
446
449
|
/** How the value is read, matching what the queries over it compare against. */
|
|
447
450
|
readonly type: FieldType;
|
|
451
|
+
/** Length of a string value, which MySQL keys as `CHAR(n)` and so requires. */
|
|
452
|
+
readonly length?: number;
|
|
448
453
|
};
|
|
449
454
|
/**
|
|
450
|
-
* MySQL's multi-valued index, one key per element of the JSON array at `path`,
|
|
451
|
-
* `$elemMatch`
|
|
455
|
+
* MySQL's multi-valued index, one key per element of the JSON array at `path`, which `$all` and an
|
|
456
|
+
* `$elemMatch` on one value or several use; refused on any other engine.
|
|
452
457
|
* @example `@Index((user) => [{ column: user.tags, jsonArray: { type: String, length: 64 } }])`
|
|
453
458
|
*/
|
|
454
459
|
export type IndexJsonArray = {
|
package/dist/type/query.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export type QuerySelect<E, F extends keyof E = FieldKey<E>, V = BooleanLike> = {
|
|
|
37
37
|
* Accepted `$select` value: a field map, or raw SQL projections built with `raw()`
|
|
38
38
|
* (e.g. ``[raw`*`, raw`LOG10(points)`.as('score')]``). The raw form is SQL-only.
|
|
39
39
|
*/
|
|
40
|
-
export type QuerySelectValue<E> = QuerySelect<E> | readonly
|
|
40
|
+
export type QuerySelectValue<E, Raw = QueryRaw> = QuerySelect<E> | readonly Raw[];
|
|
41
41
|
/**
|
|
42
42
|
* Fields to exclude from the query result - `{ name: true }` blacklists fields.
|
|
43
43
|
* Mutually exclusive with positive field selections in `$select`.
|
|
@@ -46,8 +46,8 @@ export type QueryExclude<E> = QuerySelect<E>;
|
|
|
46
46
|
/**
|
|
47
47
|
* relation population map.
|
|
48
48
|
*/
|
|
49
|
-
export type QueryPopulate<E, R extends keyof E = RelationKey<E>> = {
|
|
50
|
-
[K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K]>;
|
|
49
|
+
export type QueryPopulate<E, Raw = QueryRaw, R extends keyof E = RelationKey<E>> = {
|
|
50
|
+
[K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K], Raw>;
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
53
|
* The key a read carries its relation tallies under. One spelling for the type and the runtime that
|
|
@@ -59,8 +59,8 @@ export declare const COUNT_RESULT_KEY = "_count";
|
|
|
59
59
|
* which ones count: a correlated count in the read's own statement, so no related row is loaded. Comes
|
|
60
60
|
* back under `_count`, which keeps it clear of a relation of the same name `$populate` filled.
|
|
61
61
|
*/
|
|
62
|
-
export type QueryCount<E, R extends keyof E = ToManyRelationKey<E>> = {
|
|
63
|
-
[K in R]?: BooleanLike | QueryFilter<RelationTarget<E[K]
|
|
62
|
+
export type QueryCount<E, Raw = QueryRaw, R extends keyof E = ToManyRelationKey<E>> = {
|
|
63
|
+
[K in R]?: BooleanLike | QueryFilter<RelationTarget<E[K]>, Raw>;
|
|
64
64
|
};
|
|
65
65
|
/**
|
|
66
66
|
* query conflict paths - subset of field keys used to detect upsert conflicts.
|
|
@@ -69,7 +69,7 @@ export type QueryConflictPaths<E> = QuerySelect<E, FieldKey<E>, true>;
|
|
|
69
69
|
/**
|
|
70
70
|
* Options to populate a relation declared as `V`, by its cardinality.
|
|
71
71
|
*/
|
|
72
|
-
export type QueryPopulateRelationOptions<V> = IsMany<V> extends true ? RelationQuery<RelationTarget<V
|
|
72
|
+
export type QueryPopulateRelationOptions<V, Raw = QueryRaw> = IsMany<V> extends true ? RelationQuery<RelationTarget<V>, Raw> : QueryUnique<RelationTarget<V>, Raw> & {
|
|
73
73
|
$required?: boolean;
|
|
74
74
|
};
|
|
75
75
|
/**
|
|
@@ -158,24 +158,24 @@ export type QueryPager = {
|
|
|
158
158
|
/**
|
|
159
159
|
* Which rows a statement addresses.
|
|
160
160
|
*/
|
|
161
|
-
export type QueryFilter<E> = {
|
|
161
|
+
export type QueryFilter<E, Raw = QueryRaw> = {
|
|
162
162
|
/**
|
|
163
163
|
* filtering options.
|
|
164
164
|
*/
|
|
165
|
-
$where?: QueryWhere<E>;
|
|
165
|
+
$where?: QueryWhere<E, Raw>;
|
|
166
166
|
};
|
|
167
167
|
/**
|
|
168
168
|
* A filter plus the page `count` takes. No `$sort`: ordering picks *which* rows a page holds, never
|
|
169
169
|
* how many, so a count that accepted one would promise an influence it cannot have.
|
|
170
170
|
*/
|
|
171
|
-
export type QueryPage<E> = QueryFilter<E> & QueryPager;
|
|
171
|
+
export type QueryPage<E, Raw = QueryRaw> = QueryFilter<E, Raw> & QueryPager;
|
|
172
172
|
/**
|
|
173
173
|
* A filter plus the ordering and page `updateMany`/`deleteMany` take. Both settle the
|
|
174
174
|
* rows they address with a SELECT first, so the page is portable rather than MySQL-only, and a
|
|
175
175
|
* vector `$sort` is as valid here as on a read: it ranks the settle query's rows, which has the
|
|
176
176
|
* projection list to hold the distance. `$lock` stays off these, declared on {@link Query} instead.
|
|
177
177
|
*/
|
|
178
|
-
export type QuerySearch<E> = QueryPage<E> & {
|
|
178
|
+
export type QuerySearch<E, Raw = QueryRaw> = QueryPage<E, Raw> & {
|
|
179
179
|
/**
|
|
180
180
|
* sorting options.
|
|
181
181
|
*/
|
|
@@ -184,21 +184,21 @@ export type QuerySearch<E> = QueryPage<E> & {
|
|
|
184
184
|
/**
|
|
185
185
|
* query options.
|
|
186
186
|
*/
|
|
187
|
-
export type Query<E> = {
|
|
187
|
+
export type Query<E, Raw = QueryRaw> = {
|
|
188
188
|
/**
|
|
189
189
|
* field selection - `{ name: true }` whitelists fields, or raw SQL projections
|
|
190
190
|
* (``[raw`LOG10(points)`.as('score')]``, SQL dialects only - MongoDB rejects the raw-array form).
|
|
191
191
|
* Mutually exclusive with `$exclude`.
|
|
192
192
|
*/
|
|
193
|
-
$select?: QuerySelectValue<E>;
|
|
193
|
+
$select?: QuerySelectValue<E, Raw>;
|
|
194
194
|
/**
|
|
195
195
|
* relation population options.
|
|
196
196
|
*/
|
|
197
|
-
$populate?: QueryPopulate<E>;
|
|
197
|
+
$populate?: QueryPopulate<E, Raw>;
|
|
198
198
|
/**
|
|
199
199
|
* how many rows each named relation holds, under `_count` on every row. See {@link QueryCount}.
|
|
200
200
|
*/
|
|
201
|
-
$count?: QueryCount<E>;
|
|
201
|
+
$count?: QueryCount<E, Raw>;
|
|
202
202
|
/**
|
|
203
203
|
* field exclusion - `{ name: true }` blacklists fields. Mutually exclusive with positive `$select`.
|
|
204
204
|
* Keys a relation is assembled from (a joined row's primary key, a to-many's foreign key) are kept
|
|
@@ -227,7 +227,7 @@ export type Query<E> = {
|
|
|
227
227
|
/**
|
|
228
228
|
* filtering options.
|
|
229
229
|
*/
|
|
230
|
-
$where?: QueryWhere<E>;
|
|
230
|
+
$where?: QueryWhere<E, Raw>;
|
|
231
231
|
/**
|
|
232
232
|
* Index from where start the search
|
|
233
233
|
*/
|
|
@@ -237,6 +237,11 @@ export type Query<E> = {
|
|
|
237
237
|
*/
|
|
238
238
|
$limit?: number;
|
|
239
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* A {@link Query} as it travels as JSON, which a `raw` SQL fragment cannot: what the browser client takes,
|
|
242
|
+
* and what an RPC contract (tRPC, oRPC, TanStack Start) declares as its input.
|
|
243
|
+
*/
|
|
244
|
+
export type WireQuery<E> = Query<E, never>;
|
|
240
245
|
/**
|
|
241
246
|
* `Query`'s clauses grouped by the shape of their value, for the wire parser and the relation query
|
|
242
247
|
* check alike; `satisfies` keeps them in step with `Query`.
|
|
@@ -262,36 +267,36 @@ type RelationClause = (typeof QUERY_OBJECT_CLAUSES | typeof QUERY_NUMBER_CLAUSES
|
|
|
262
267
|
* A populated relation's own query: the clause groups its runtime check accepts, so the two cannot
|
|
263
268
|
* drift, and a clause added to {@link Query} stays off it until it joins one of them.
|
|
264
269
|
*/
|
|
265
|
-
export type RelationQuery<E = object> = Pick<Query<E>, RelationClause> & {
|
|
270
|
+
export type RelationQuery<E = object, Raw = QueryRaw> = Pick<Query<E, Raw>, RelationClause> & {
|
|
266
271
|
$required?: boolean;
|
|
267
272
|
};
|
|
268
273
|
/**
|
|
269
274
|
* options to get a single record.
|
|
270
275
|
*/
|
|
271
|
-
export type QueryOne<E> = Except<Query<E>, '$limit'>;
|
|
276
|
+
export type QueryOne<E, Raw = QueryRaw> = Except<Query<E, Raw>, '$limit'>;
|
|
272
277
|
/**
|
|
273
278
|
* options to get an unique record.
|
|
274
279
|
*/
|
|
275
|
-
export type QueryUnique<E> = Pick<QueryOne<E>, '$select' | '$exclude' | '$populate' | '$where'>;
|
|
280
|
+
export type QueryUnique<E, Raw = QueryRaw> = Pick<QueryOne<E, Raw>, '$select' | '$exclude' | '$populate' | '$where'>;
|
|
276
281
|
/**
|
|
277
282
|
* The clauses that shape a row, captured as key sets rather than maps: a naked type parameter skips
|
|
278
283
|
* excess-property checks, while a key set fails its own constraint on a typo.
|
|
279
284
|
* @internal
|
|
280
285
|
*/
|
|
281
|
-
type QueryProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E
|
|
282
|
-
$select?: QuerySelect<E, S, V> | readonly
|
|
286
|
+
type QueryProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E>, Raw = QueryRaw> = {
|
|
287
|
+
$select?: QuerySelect<E, S, V> | readonly Raw[];
|
|
283
288
|
$exclude?: QuerySelect<E, X, V>;
|
|
284
|
-
$populate?: QueryPopulate<E, P>;
|
|
285
|
-
$count?: QueryCount<E, C & ToManyRelationKey<E>>;
|
|
289
|
+
$populate?: QueryPopulate<E, Raw, P>;
|
|
290
|
+
$count?: QueryCount<E, Raw, C & ToManyRelationKey<E>>;
|
|
286
291
|
};
|
|
287
292
|
/**
|
|
288
293
|
* A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.
|
|
289
294
|
*/
|
|
290
|
-
export type QueryProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E> = never> = Query<E> & QueryProjection<E, S, V, X, P, C>;
|
|
295
|
+
export type QueryProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E> = never, Raw = QueryRaw> = Query<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;
|
|
291
296
|
/**
|
|
292
297
|
* A {@link QueryOne} whose projection is captured, so {@link QueryFindResult} can shape the row.
|
|
293
298
|
*/
|
|
294
|
-
export type QueryOneProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E> = never> = QueryOne<E> & QueryProjection<E, S, V, X, P, C>;
|
|
299
|
+
export type QueryOneProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E> = never, Raw = QueryRaw> = QueryOne<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;
|
|
295
300
|
/**
|
|
296
301
|
* The keys a query comes back with, as the runtime projects them: a positive `$select`'s, or every
|
|
297
302
|
* field minus what `$select` or `$exclude` subtracts, plus the populated relations.
|