uql-orm 0.55.0 → 0.57.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 +1 -1
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +4 -4
- package/dist/bunSql/bunSql.util.js +1 -1
- package/dist/cockroachdb/cockroachDialect.d.ts +5 -2
- package/dist/cockroachdb/cockroachDialect.js +2 -10
- package/dist/d1/d1SqliteDialect.d.ts +1 -0
- package/dist/d1/d1SqliteDialect.js +2 -0
- package/dist/dialect/abstractSqlDialect.d.ts +195 -32
- package/dist/dialect/abstractSqlDialect.js +406 -199
- package/dist/dialect/aliases.d.ts +10 -7
- package/dist/dialect/aliases.js +12 -7
- package/dist/dialect/hydrateColumn.d.ts +8 -2
- package/dist/dialect/hydrateColumn.js +33 -1
- package/dist/dialect/jsonSql.d.ts +13 -5
- package/dist/dialect/jsonSql.js +24 -7
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +30 -2
- package/dist/dialect/mysqlLikeSqlDialect.js +58 -7
- package/dist/dialect/pgLikeSqlDialect.d.ts +20 -20
- package/dist/dialect/pgLikeSqlDialect.js +25 -50
- package/dist/dialect/pgVectorMetrics.d.ts +13 -0
- package/dist/dialect/pgVectorMetrics.js +17 -0
- package/dist/dialect/queryContext.d.ts +3 -7
- package/dist/dialect/queryContext.js +13 -8
- package/dist/dialect/queryJoins.d.ts +8 -4
- package/dist/dialect/queryJoins.js +27 -15
- package/dist/dialect/vectorSqlDialect.d.ts +2 -2
- package/dist/dialect/vectorSqlDialect.js +2 -3
- package/dist/entity/index.d.ts +1 -1
- package/dist/entity/index.js +1 -1
- package/dist/entity/metadata/definition.d.ts +4 -2
- package/dist/entity/metadata/definition.js +27 -29
- package/dist/maria/mariaDialect.d.ts +13 -6
- package/dist/maria/mariaDialect.js +29 -9
- package/dist/migrate/builder/splitSqlStatements.js +2 -2
- package/dist/migrate/cli.d.ts +2 -3
- package/dist/migrate/cli.js +4 -11
- package/dist/migrate/codegen/fieldOptionsSource.js +1 -1
- package/dist/migrate/ddl/index.d.ts +1 -5
- package/dist/migrate/ddl/index.js +14 -25
- package/dist/migrate/ddl/indexDdl.d.ts +11 -2
- package/dist/migrate/ddl/indexDdl.js +17 -1
- package/dist/migrate/ddl/mssqlIndexDdl.d.ts +10 -0
- package/dist/migrate/ddl/mssqlIndexDdl.js +10 -0
- package/dist/migrate/ddl/mysqlIndexDdl.d.ts +10 -17
- package/dist/migrate/ddl/mysqlIndexDdl.js +16 -27
- package/dist/migrate/ddl/pgIndexDdl.d.ts +18 -3
- package/dist/migrate/ddl/pgIndexDdl.js +29 -3
- package/dist/migrate/drift/driftDetector.js +21 -8
- package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
- package/dist/migrate/introspection/baseSqlIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/baseSqlIntrospector.js +65 -76
- package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
- package/dist/migrate/introspection/mysqlIntrospector.js +5 -8
- package/dist/migrate/introspection/sqliteIntrospector.js +2 -5
- package/dist/migrate/migrator.d.ts +7 -4
- package/dist/migrate/migrator.js +9 -14
- package/dist/migrate/schemaGenerator.d.ts +3 -3
- package/dist/migrate/schemaGenerator.js +7 -13
- package/dist/migrate/schemaGeneratorAsync.d.ts +2 -3
- package/dist/mongo/mongoDialect.d.ts +31 -18
- package/dist/mongo/mongoDialect.js +147 -108
- package/dist/mongo/mongodbQuerier.d.ts +10 -17
- package/dist/mongo/mongodbQuerier.js +34 -108
- package/dist/mssql/mssqlDialect.d.ts +16 -0
- package/dist/mssql/mssqlDialect.js +26 -4
- package/dist/mysql/mysqlDialect.d.ts +2 -0
- package/dist/mysql/mysqlDialect.js +4 -0
- package/dist/querier/abstractQuerier.d.ts +20 -36
- package/dist/querier/abstractQuerier.js +44 -143
- package/dist/querier/abstractQuerierPool.d.ts +2 -2
- package/dist/querier/abstractSqlQuerier.d.ts +11 -22
- package/dist/querier/abstractSqlQuerier.js +49 -53
- package/dist/schema/canonicalType.js +4 -6
- package/dist/schema/dependencyGraph.js +2 -4
- package/dist/schema/indexDifferences.js +5 -5
- package/dist/schema/schemaASTBuilder.js +34 -12
- package/dist/schema/schemaASTDiffer.d.ts +10 -2
- package/dist/schema/schemaASTDiffer.js +17 -16
- package/dist/schema/types.d.ts +1 -1
- package/dist/sqlite/sqliteDialect.d.ts +20 -1
- package/dist/sqlite/sqliteDialect.js +40 -8
- package/dist/turso/tursoDialect.d.ts +2 -0
- package/dist/turso/tursoDialect.js +2 -0
- package/dist/type/config.d.ts +2 -2
- package/dist/type/dialect.d.ts +4 -5
- package/dist/type/entity.d.ts +2 -1
- package/dist/type/migratorDialect.d.ts +4 -0
- package/dist/type/querier.d.ts +6 -6
- package/dist/type/query.d.ts +25 -48
- package/dist/type/query.js +10 -5
- package/dist/type/queryAggregate.d.ts +10 -10
- package/dist/type/queryAggregate.js +1 -1
- package/dist/type/universalQuerier.d.ts +4 -4
- package/dist/util/dialect.util.d.ts +8 -2
- package/dist/util/dialect.util.js +19 -0
- package/dist/util/field.util.d.ts +5 -0
- package/dist/util/field.util.js +19 -0
- package/dist/util/logger.d.ts +10 -1
- package/dist/util/logger.js +18 -0
- package/dist/util/object.util.d.ts +4 -0
- package/dist/util/object.util.js +8 -0
- package/dist/util/relationQuery.util.d.ts +15 -68
- package/dist/util/relationQuery.util.js +35 -83
- package/dist/util/rowKey.util.d.ts +1 -11
- package/dist/util/rowKey.util.js +1 -13
- package/package.json +1 -1
- package/dist/querier/relationCount.d.ts +0 -16
- package/dist/querier/relationCount.js +0 -121
|
@@ -10,6 +10,7 @@ import { ddlText } from '../util/ddlExpression.util.js';
|
|
|
10
10
|
import { isInlinedExpression } from '../util/field.util.js';
|
|
11
11
|
import { isSoleIdField } from '../util/field.util.js';
|
|
12
12
|
import { isAutoIncrement } from '../util/field.util.js';
|
|
13
|
+
import { definedEntries } from '../util/object.util.js';
|
|
13
14
|
import { derivedForeignKeyName, derivedIndexName, qualifyName } from '../util/sql.util.js';
|
|
14
15
|
import { fieldOptionsToCanonical } from './canonicalType.js';
|
|
15
16
|
import { createTableNode, SchemaAST } from './schemaAST.js';
|
|
@@ -79,11 +80,7 @@ function addTableFromEntity(ctx, meta) {
|
|
|
79
80
|
const { columns, primaryKey } = table;
|
|
80
81
|
table.checks?.push(...(meta.checks ?? []));
|
|
81
82
|
// Add columns from fields
|
|
82
|
-
const
|
|
83
|
-
for (const key of Object.keys(fields)) {
|
|
84
|
-
const field = fields[key];
|
|
85
|
-
if (!field)
|
|
86
|
-
continue;
|
|
83
|
+
for (const [key, field] of definedEntries(meta.fields)) {
|
|
87
84
|
// An inlined expression has no column; a stored one is a column like any other.
|
|
88
85
|
if (isInlinedExpression(field))
|
|
89
86
|
continue;
|
|
@@ -126,11 +123,7 @@ function addRelationshipsFromEntity(ctx, meta) {
|
|
|
126
123
|
const table = tableOf(ctx, meta);
|
|
127
124
|
if (!table)
|
|
128
125
|
return;
|
|
129
|
-
const
|
|
130
|
-
for (const key of Object.keys(relations)) {
|
|
131
|
-
const relation = relations[key];
|
|
132
|
-
if (!relation)
|
|
133
|
-
continue;
|
|
126
|
+
for (const [key, relation] of definedEntries(meta.relations)) {
|
|
134
127
|
const relatedMeta = getMeta(relation.entity());
|
|
135
128
|
const relatedTable = tableOf(ctx, relatedMeta);
|
|
136
129
|
if (!relatedTable)
|
|
@@ -175,8 +168,8 @@ function addRelationshipsFromEntity(ctx, meta) {
|
|
|
175
168
|
}
|
|
176
169
|
}
|
|
177
170
|
/**
|
|
178
|
-
* Add indexes from field options (`@Field({ index })`)
|
|
179
|
-
*
|
|
171
|
+
* Add indexes from field options (`@Field({ index })`), from `@Index([...])`, and for every foreign
|
|
172
|
+
* key none of those already serves.
|
|
180
173
|
*/
|
|
181
174
|
function addIndexesFromEntity(ctx, meta) {
|
|
182
175
|
const table = tableOf(ctx, meta);
|
|
@@ -201,6 +194,35 @@ function addIndexesFromEntity(ctx, meta) {
|
|
|
201
194
|
for (const idxMeta of meta.indexes ?? []) {
|
|
202
195
|
addCompositeIndex(ctx, table, meta, idxMeta);
|
|
203
196
|
}
|
|
197
|
+
addForeignKeyIndexes(ctx, meta, table);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* An index over each foreign key the table owns, unless one already leads with its columns or a field
|
|
201
|
+
* of it says `index: false`. A relation looks its rows up by these columns, and MySQL alone indexes
|
|
202
|
+
* them on its own. Last, so it sees every index the entity declared.
|
|
203
|
+
*/
|
|
204
|
+
function addForeignKeyIndexes(ctx, meta, table) {
|
|
205
|
+
const optedOut = new Set(definedEntries(meta.fields).flatMap(([key, field]) => field.index === false ? [ctx.resolveColumnName(key, field)] : []));
|
|
206
|
+
for (const { from } of table.outgoingRelations) {
|
|
207
|
+
const columns = from.columns.map((column) => column.name);
|
|
208
|
+
if (columns.some((column) => optedOut.has(column)) || isIndexedBy(table, columns))
|
|
209
|
+
continue;
|
|
210
|
+
ctx.ast.addIndex({
|
|
211
|
+
name: derivedIndexName(table.name, columns),
|
|
212
|
+
table,
|
|
213
|
+
entries: columns.map((column) => ({ column })),
|
|
214
|
+
unique: false,
|
|
215
|
+
source: 'entity',
|
|
216
|
+
syncStatus: 'entity_only',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** Whether the key, a unique column or an index already leads with `columns`, which is all a lookup needs. */
|
|
221
|
+
function isIndexedBy(table, columns) {
|
|
222
|
+
const leads = (indexed) => columns.every((column, at) => indexed[at] === column);
|
|
223
|
+
return (leads(table.primaryKey.map((column) => column.name)) ||
|
|
224
|
+
(columns.length === 1 && table.columns.get(columns[0])?.isUnique === true) ||
|
|
225
|
+
table.indexes.some((index) => leads(index.entries.map((entry) => entry.expression || entry.jsonPath || entry.jsonArray ? undefined : entry.column))));
|
|
204
226
|
}
|
|
205
227
|
/** An `include` column is named like any other, so a naming strategy has to reach it too. */
|
|
206
228
|
function resolveIncludeColumn(ctx, meta, column) {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { type IndexFacet } from './indexDifferences.js';
|
|
11
11
|
import type { SchemaAST } from './schemaAST.js';
|
|
12
12
|
import type { CanonicalType } from './types.js';
|
|
13
|
-
import type { RelationshipDiff, RelationshipNode, SchemaDiffResult, TableDiff, TableNode } from './types.js';
|
|
13
|
+
import type { ColumnDiff, ForeignKeyAction, IndexDiff, RelationshipDiff, RelationshipNode, SchemaDiffResult, TableDiff, TableNode } from './types.js';
|
|
14
14
|
/**
|
|
15
15
|
* Options for schema diffing.
|
|
16
16
|
*/
|
|
@@ -59,7 +59,10 @@ export declare function diffSchemas(source: SchemaAST, target: SchemaAST, option
|
|
|
59
59
|
* against the one the database reported, then projects the result into a `SchemaDiff`. One
|
|
60
60
|
* comparison serves both, so drift and migrations can no longer disagree about what has changed.
|
|
61
61
|
*/
|
|
62
|
-
export declare function diffTable(source: TableNode, target: TableNode, options?: DiffOptions): TableDiff
|
|
62
|
+
export declare function diffTable(source: TableNode, target: TableNode, options?: DiffOptions): (TableDiff & {
|
|
63
|
+
readonly columnDiffs: ColumnDiff[];
|
|
64
|
+
readonly indexDiffs: IndexDiff[];
|
|
65
|
+
}) | undefined;
|
|
63
66
|
/**
|
|
64
67
|
* Compare two lists of relationships.
|
|
65
68
|
*
|
|
@@ -70,3 +73,8 @@ export declare function diffTable(source: TableNode, target: TableNode, options?
|
|
|
70
73
|
* pairing on names would report every hand-named one as a drop and an add.
|
|
71
74
|
*/
|
|
72
75
|
export declare function diffRelationshipNodes(source: readonly RelationshipNode[], target: readonly RelationshipNode[], opts?: DiffOptions): RelationshipDiff[];
|
|
76
|
+
/** A relationship's `ON DELETE` and `ON UPDATE`, an unstated one read as the action the database applies. */
|
|
77
|
+
export declare function referentialActions(rel: RelationshipNode): {
|
|
78
|
+
readonly onDelete: ForeignKeyAction;
|
|
79
|
+
readonly onUpdate: ForeignKeyAction;
|
|
80
|
+
};
|
|
@@ -61,8 +61,8 @@ export function diffSchemas(source, target, options = {}) {
|
|
|
61
61
|
const tablesToAlter = matched
|
|
62
62
|
.map(([sourceTable, targetTable]) => diffTable(sourceTable, targetTable, opts))
|
|
63
63
|
.filter((tableDiff) => tableDiff !== undefined);
|
|
64
|
-
const columnDiffs = tablesToAlter.flatMap((tableDiff) => tableDiff.columnDiffs
|
|
65
|
-
const indexDiffs = tablesToAlter.flatMap((tableDiff) => tableDiff.indexDiffs
|
|
64
|
+
const columnDiffs = tablesToAlter.flatMap((tableDiff) => tableDiff.columnDiffs);
|
|
65
|
+
const indexDiffs = tablesToAlter.flatMap((tableDiff) => tableDiff.indexDiffs);
|
|
66
66
|
const primaryKeyDiffs = tablesToAlter.flatMap((tableDiff) => tableDiff.primaryKeyDiff ?? []);
|
|
67
67
|
// Relationships span tables, so they are compared over the whole schema rather than per table.
|
|
68
68
|
const relationshipDiffs = opts.compareRelationships
|
|
@@ -179,7 +179,7 @@ function diffColumn(tableName, source, target, opts) {
|
|
|
179
179
|
const impliedNotNull = source.isPrimaryKey && target.isPrimaryKey;
|
|
180
180
|
const typeChanged = !generatedType && !areTypesEqual(opts.normalizeType(source.type), opts.normalizeType(target.type));
|
|
181
181
|
if (typeChanged) {
|
|
182
|
-
differences.push(`type: ${formatType(source.type)}
|
|
182
|
+
differences.push(`type: ${formatType(source.type)} -> ${formatType(target.type)}`);
|
|
183
183
|
}
|
|
184
184
|
// Signedness is the one thing compared on a generated key, because it is the one part of the serial
|
|
185
185
|
// spelling that does round trip - and a key left unsigned refuses every foreign key pointing at it,
|
|
@@ -187,14 +187,14 @@ function diffColumn(tableName, source, target, opts) {
|
|
|
187
187
|
// a database created before the serial became signed could never gain a foreign key.
|
|
188
188
|
const signednessChanged = generatedType && !!source.type.unsigned !== !!target.type.unsigned;
|
|
189
189
|
if (signednessChanged) {
|
|
190
|
-
differences.push(`type: ${formatType(source.type)}
|
|
190
|
+
differences.push(`type: ${formatType(source.type)} -> ${formatType(target.type)}`);
|
|
191
191
|
}
|
|
192
192
|
if (!impliedNotNull && source.nullable !== target.nullable) {
|
|
193
|
-
differences.push(`nullable: ${target.nullable}
|
|
193
|
+
differences.push(`nullable: ${target.nullable} -> ${source.nullable}`);
|
|
194
194
|
}
|
|
195
195
|
// Compare unique constraint
|
|
196
196
|
if (source.isUnique !== target.isUnique) {
|
|
197
|
-
differences.push(`unique: ${target.isUnique}
|
|
197
|
+
differences.push(`unique: ${target.isUnique} -> ${source.isUnique}`);
|
|
198
198
|
}
|
|
199
199
|
// Four things are deliberately not compared, all for one reason: a difference here could only be
|
|
200
200
|
// reported, never settled, because no statement this generator emits would change it.
|
|
@@ -206,7 +206,7 @@ function diffColumn(tableName, source, target, opts) {
|
|
|
206
206
|
// The same rule `describeIndexDifferences` follows for what it cannot read.
|
|
207
207
|
// Compare default values (if both defined)
|
|
208
208
|
if (!opts.defaultsEqual(source.defaultValue, target.defaultValue)) {
|
|
209
|
-
differences.push(`default: ${target.defaultValue ?? 'NULL'}
|
|
209
|
+
differences.push(`default: ${target.defaultValue ?? 'NULL'} -> ${source.defaultValue ?? 'NULL'}`);
|
|
210
210
|
}
|
|
211
211
|
if (differences.length === 0) {
|
|
212
212
|
return undefined;
|
|
@@ -265,16 +265,17 @@ export function diffRelationshipNodes(source, target, opts = {}) {
|
|
|
265
265
|
.filter((diff) => diff !== undefined),
|
|
266
266
|
];
|
|
267
267
|
}
|
|
268
|
-
/**
|
|
269
|
-
|
|
270
|
-
|
|
268
|
+
/** A relationship's `ON DELETE` and `ON UPDATE`, an unstated one read as the action the database applies. */
|
|
269
|
+
export function referentialActions(rel) {
|
|
270
|
+
return {
|
|
271
|
+
onDelete: rel.onDelete ?? DEFAULT_FOREIGN_KEY_ACTION,
|
|
272
|
+
onUpdate: rel.onUpdate ?? DEFAULT_FOREIGN_KEY_ACTION,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/** Two relationships over the same columns differ only in what they do when the row they point at goes. */
|
|
271
276
|
function diffRelationship(source, target) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const tDelete = target.onDelete ?? DEFAULT_FOREIGN_KEY_ACTION;
|
|
275
|
-
const sUpdate = source.onUpdate ?? DEFAULT_FOREIGN_KEY_ACTION;
|
|
276
|
-
const tUpdate = target.onUpdate ?? DEFAULT_FOREIGN_KEY_ACTION;
|
|
277
|
-
if (sDelete !== tDelete || sUpdate !== tUpdate) {
|
|
277
|
+
const [expected, actual] = [referentialActions(source), referentialActions(target)];
|
|
278
|
+
if (expected.onDelete !== actual.onDelete || expected.onUpdate !== actual.onUpdate) {
|
|
278
279
|
return { ...relationEnds(source), type: 'alter', expected: source, actual: target };
|
|
279
280
|
}
|
|
280
281
|
return undefined;
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -342,7 +342,7 @@ export type DriftSeverity = 'critical' | 'warning' | 'info';
|
|
|
342
342
|
/**
|
|
343
343
|
* Type of schema drift.
|
|
344
344
|
*/
|
|
345
|
-
export type DriftType = 'missing_table' | 'unexpected_table' | 'missing_column' | 'unexpected_column' | 'type_mismatch' | 'constraint_mismatch' | 'missing_index' | 'unexpected_index' | 'index_mismatch' | 'missing_relationship' | 'unexpected_relationship';
|
|
345
|
+
export type DriftType = 'missing_table' | 'unexpected_table' | 'missing_column' | 'unexpected_column' | 'type_mismatch' | 'constraint_mismatch' | 'missing_index' | 'unexpected_index' | 'index_mismatch' | 'missing_relationship' | 'unexpected_relationship' | 'relationship_mismatch';
|
|
346
346
|
/**
|
|
347
347
|
* A single schema drift issue.
|
|
348
348
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
|
|
1
|
+
import { AbstractSqlDialect, type DerivedRelation, type HydrateKind, type RelationRows } from '../dialect/abstractSqlDialect.js';
|
|
2
2
|
import type { DialectFeatures, EntityMeta, FieldOptions, QueryContext, QueryPager, QuerySizeComparisonOps, QueryTextSearchOptions, Type, VectorDistance, VectorMetric } from '../type/index.js';
|
|
3
3
|
export declare class SqliteDialect extends AbstractSqlDialect {
|
|
4
4
|
/** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
|
|
@@ -16,6 +16,8 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
16
16
|
/** SQLite locks the whole database, not rows, so `$lock` has nothing to map onto. */
|
|
17
17
|
readonly supportsRowLocks = false;
|
|
18
18
|
readonly booleanLiteral = "integer";
|
|
19
|
+
/** A function call takes 127 arguments before SQLite 3.48, as libSQL and Turso embed. */
|
|
20
|
+
readonly maxFunctionArgs: number;
|
|
19
21
|
readonly insertIdSource = "returning";
|
|
20
22
|
/**
|
|
21
23
|
* The [sqlite-vec](https://github.com/asg017/sqlite-vec) functions, which need that extension
|
|
@@ -36,6 +38,23 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
36
38
|
* SQLite's own spelling of "no limit", where the MySQL family uses its largest `BIGINT`.
|
|
37
39
|
*/
|
|
38
40
|
pager(ctx: QueryContext, opts: QueryPager): void;
|
|
41
|
+
/** `json_group_array` of each row's object, ordered by the sort terms carried out beside them. */
|
|
42
|
+
protected appendRelationArray(ctx: QueryContext, rows: RelationRows): void;
|
|
43
|
+
/**
|
|
44
|
+
* `json_object` of each key and its column. A row wider than one call takes inserts the rest into it,
|
|
45
|
+
* addressing each key as one path segment.
|
|
46
|
+
*/
|
|
47
|
+
protected jsonObject(pairs: DerivedRelation['pairs']): string;
|
|
48
|
+
/**
|
|
49
|
+
* An integer crosses JSON as its exact text, which JSON would round past 2^53, and bytes as hex, which
|
|
50
|
+
* `json_object` cannot hold at all. A real stays a number, since SQLite writes one to text with 15 digits.
|
|
51
|
+
*/
|
|
52
|
+
protected readonly carriedFields: {
|
|
53
|
+
numeric: (expr: string, field: FieldOptions) => string;
|
|
54
|
+
blob: (expr: string) => string;
|
|
55
|
+
};
|
|
56
|
+
/** A date reads back as SQLite stored it, a number or text, which JSON carries unchanged. */
|
|
57
|
+
protected hydrateKind(field: FieldOptions | undefined): HydrateKind | undefined;
|
|
39
58
|
/**
|
|
40
59
|
* FTS5 matches the table itself rather than its columns, so this only works when the table *is* an
|
|
41
60
|
* FTS5 virtual table (UQL does not create those; declare it outside your entities).
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { AbstractSqlDialect, } from '../dialect/abstractSqlDialect.js';
|
|
2
|
+
import { JSON_ELEM_ALIAS, JSON_PULL_ALIAS } from '../dialect/aliases.js';
|
|
3
|
+
import { BYTES_PREFIX } from '../dialect/hydrateColumn.js';
|
|
4
|
+
import { chainedCall, groupsPerCall, jsonAssignCall, jsonElemExists, jsonPath, jsonRemoveCall, jsonSetTarget, } from '../dialect/jsonSql.js';
|
|
5
|
+
import { textSearchFields } from '../util/dialect.util.js';
|
|
6
|
+
import { columnFamily, isIntegerColumn } from '../util/field.util.js';
|
|
4
7
|
export class SqliteDialect extends AbstractSqlDialect {
|
|
5
8
|
/** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
|
|
6
9
|
featureDefaults = {
|
|
@@ -36,6 +39,8 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
36
39
|
/** SQLite locks the whole database, not rows, so `$lock` has nothing to map onto. */
|
|
37
40
|
supportsRowLocks = false;
|
|
38
41
|
booleanLiteral = 'integer';
|
|
42
|
+
/** A function call takes 127 arguments before SQLite 3.48, as libSQL and Turso embed. */
|
|
43
|
+
maxFunctionArgs = 127;
|
|
39
44
|
// SQLite supports `RETURNING` (including on `INSERT ... ON CONFLICT`), so IDs are exact per row.
|
|
40
45
|
insertIdSource = 'returning';
|
|
41
46
|
/**
|
|
@@ -81,12 +86,39 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
81
86
|
}
|
|
82
87
|
super.pager(ctx, opts);
|
|
83
88
|
}
|
|
89
|
+
/** `json_group_array` of each row's object, ordered by the sort terms carried out beside them. */
|
|
90
|
+
appendRelationArray(ctx, rows) {
|
|
91
|
+
const { from, pairs, order } = this.derivedRelation(ctx, rows);
|
|
92
|
+
ctx.append(`(SELECT json_group_array(${this.jsonObject(pairs)}${order ? ` ORDER BY ${order}` : ''}) FROM ${from})`);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `json_object` of each key and its column. A row wider than one call takes inserts the rest into it,
|
|
96
|
+
* addressing each key as one path segment.
|
|
97
|
+
*/
|
|
98
|
+
jsonObject(pairs) {
|
|
99
|
+
const perCall = groupsPerCall(this.maxFunctionArgs, 2);
|
|
100
|
+
const object = `json_object(${this.jsonObjectArgs(pairs.slice(0, perCall))})`;
|
|
101
|
+
const inserts = pairs.slice(perCall).map(([key, sql]) => `${this.escape(`$."${key}"`)}, ${sql}`);
|
|
102
|
+
return chainedCall('json_insert', object, inserts, 2, this.maxFunctionArgs);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* An integer crosses JSON as its exact text, which JSON would round past 2^53, and bytes as hex, which
|
|
106
|
+
* `json_object` cannot hold at all. A real stays a number, since SQLite writes one to text with 15 digits.
|
|
107
|
+
*/
|
|
108
|
+
carriedFields = {
|
|
109
|
+
numeric: (expr, field) => (isIntegerColumn(field) ? `CAST(${expr} AS TEXT)` : expr),
|
|
110
|
+
blob: (expr) => `${this.escape(BYTES_PREFIX)} || hex(${expr})`,
|
|
111
|
+
};
|
|
112
|
+
/** A date reads back as SQLite stored it, a number or text, which JSON carries unchanged. */
|
|
113
|
+
hydrateKind(field) {
|
|
114
|
+
return columnFamily(field?.type) === 'date' ? undefined : super.hydrateKind(field);
|
|
115
|
+
}
|
|
84
116
|
/**
|
|
85
117
|
* FTS5 matches the table itself rather than its columns, so this only works when the table *is* an
|
|
86
118
|
* FTS5 virtual table (UQL does not create those; declare it outside your entities).
|
|
87
119
|
*/
|
|
88
120
|
appendTextSearch(ctx, entity, meta, search) {
|
|
89
|
-
const columns = search
|
|
121
|
+
const columns = textSearchFields(meta, search).map((key) => this.escapeId(this.resolveColumnName(key, meta.fields[key])));
|
|
90
122
|
ctx.append(`${this.escapedTableName(meta)} MATCH {${columns.join(' ')}} : `);
|
|
91
123
|
ctx.addValue(search.$value);
|
|
92
124
|
}
|
|
@@ -103,7 +135,7 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
103
135
|
* vs `"a"`), flattens booleans to 0/1, and stringifies objects.
|
|
104
136
|
*/
|
|
105
137
|
jsonAll(ctx, jsonField, value) {
|
|
106
|
-
const alias = ctx.
|
|
138
|
+
const alias = ctx.claimAlias(JSON_ELEM_ALIAS);
|
|
107
139
|
const from = this.jsonElemFrom(jsonField, [], alias);
|
|
108
140
|
const conditions = value.map((val) => jsonElemExists(from, [`${jsonField} -> ${alias}.fullkey = ${this.jsonScalarParam(ctx, val)}`]));
|
|
109
141
|
return `(${conditions.join(' AND ')})`;
|
|
@@ -142,7 +174,7 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
142
174
|
return `JSON_REPLACE(${expr}, ${path}, (${kept}))`;
|
|
143
175
|
}
|
|
144
176
|
jsonSet(ctx, expr, set, field) {
|
|
145
|
-
return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'JSON_SET', jsonSetTarget(expr, field, `'{}'`), set);
|
|
177
|
+
return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'JSON_SET', jsonSetTarget(expr, field, `'{}'`), set, this.maxFunctionArgs);
|
|
146
178
|
}
|
|
147
179
|
/**
|
|
148
180
|
* `[#]` appends, creating the array when the key is absent.
|
|
@@ -152,9 +184,9 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
152
184
|
* so it silently drops the element when the array already exists.
|
|
153
185
|
*/
|
|
154
186
|
jsonPush(ctx, expr, push) {
|
|
155
|
-
return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'JSON_SET', expr, push, '[#]');
|
|
187
|
+
return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'JSON_SET', expr, push, this.maxFunctionArgs, '[#]');
|
|
156
188
|
}
|
|
157
189
|
jsonUnset(_ctx, expr, unset) {
|
|
158
|
-
return jsonRemoveCall('JSON_REMOVE', expr, unset);
|
|
190
|
+
return jsonRemoveCall('JSON_REMOVE', expr, unset, this.maxFunctionArgs);
|
|
159
191
|
}
|
|
160
192
|
}
|
|
@@ -12,4 +12,6 @@ import type { VectorDistance, VectorMetric } from '../type/index.js';
|
|
|
12
12
|
export declare class TursoDialect extends LibsqlDialect {
|
|
13
13
|
/** The Rust engine adds a dot-product distance to libSQL's cosine and L2. */
|
|
14
14
|
readonly vectorMetrics: ReadonlyMap<VectorDistance, VectorMetric>;
|
|
15
|
+
/** The Rust engine takes no `ORDER BY` inside an aggregate. */
|
|
16
|
+
protected readonly orderedAggregates = false;
|
|
15
17
|
}
|
package/dist/type/config.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AbstractDialect } from '../dialect/abstractDialect.js';
|
|
2
1
|
import type { ForeignKeyAction } from '../schema/types.js';
|
|
2
|
+
import type { MigratorDialect } from './migratorDialect.js';
|
|
3
3
|
import type { Querier } from './querier.js';
|
|
4
4
|
import type { QuerierPool } from './querierPool.js';
|
|
5
5
|
import type { Type } from './utility.js';
|
|
@@ -12,7 +12,7 @@ export interface Config {
|
|
|
12
12
|
* This is required for both the application and the migrations CLI.
|
|
13
13
|
* Must expose {@link QuerierPool.dialect}; migrations and the CLI read `pool.dialect.dialectName`.
|
|
14
14
|
*/
|
|
15
|
-
pool: QuerierPool<Querier,
|
|
15
|
+
pool: QuerierPool<Querier, MigratorDialect>;
|
|
16
16
|
/**
|
|
17
17
|
* List of entity classes to be managed by the ORM.
|
|
18
18
|
* If omitted, classes that completed `defineEntity` (including via `@Entity()`) are discovered via `getEntities()`.
|
package/dist/type/dialect.d.ts
CHANGED
|
@@ -33,12 +33,11 @@ export interface QueryContext {
|
|
|
33
33
|
addValue(value: unknown): this;
|
|
34
34
|
pushValue(...values: unknown[]): this;
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* alias would let the inner occurrence shadow the outer one it needs to correlate against.
|
|
36
|
+
* An alias for a table or row source the statement reads: `name`, or `name_2`, `name_3`... - the
|
|
37
|
+
* first no other took, so a nested one never shadows what it correlates against, and never `parent`,
|
|
38
|
+
* the one a correlated subquery compares against. Compared without case, as MySQL does on macOS.
|
|
40
39
|
*/
|
|
41
|
-
|
|
40
|
+
claimAlias(name: string, parent?: string): string;
|
|
42
41
|
/**
|
|
43
42
|
* A context for a fragment of this same statement: it renders its own SQL in isolation while
|
|
44
43
|
* sharing the bound values and the generated aliases, so both stay unique and correctly numbered
|
package/dist/type/entity.d.ts
CHANGED
|
@@ -425,7 +425,8 @@ export type FieldOptions<V = TsTypeOf<FieldType>> = {
|
|
|
425
425
|
*/
|
|
426
426
|
readonly autoIncrement?: boolean;
|
|
427
427
|
/**
|
|
428
|
-
*
|
|
428
|
+
* `true` for an index over the column, a string to name it. A foreign key column is indexed unless
|
|
429
|
+
* this is `false`.
|
|
429
430
|
*/
|
|
430
431
|
readonly index?: boolean | string;
|
|
431
432
|
/**
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import type { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
|
|
2
|
+
import type { MongoDialect } from '../mongo/mongoDialect.js';
|
|
1
3
|
import type { DialectName } from './querier.js';
|
|
4
|
+
/** The dialects the migrator runs on, which `dialectName` tells apart: every SQL engine, and MongoDB. */
|
|
5
|
+
export type MigratorDialect = AbstractSqlDialect | MongoDialect;
|
|
2
6
|
declare const KNOWN_MIGRATOR_DIALECTS: readonly ["postgres", "cockroachdb", "mysql", "mariadb", "sqlite", "mssql", "mongodb"];
|
|
3
7
|
export type KnownMigratorDialect = (typeof KNOWN_MIGRATOR_DIALECTS)[number];
|
|
4
8
|
/**
|
package/dist/type/querier.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { SqlDialectName } from './dialect.js';
|
|
|
4
4
|
import type { FieldKey, HookEvent, RelationKey } from './entity.js';
|
|
5
5
|
import type { LoggingOptions } from './logger.js';
|
|
6
6
|
import type { NamingStrategy } from './namingStrategy.js';
|
|
7
|
-
import type { QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch,
|
|
7
|
+
import type { QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryUpdateResult } from './query.js';
|
|
8
8
|
import type { UniversalQuerier } from './universalQuerier.js';
|
|
9
9
|
import type { Type } from './utility.js';
|
|
10
10
|
/**
|
|
@@ -47,13 +47,13 @@ export interface Querier extends UniversalQuerier {
|
|
|
47
47
|
}, opts?: QueryOptions): Promise<QueryFindResult<E, S, V, X, P, C>[]>;
|
|
48
48
|
findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C>, opts?: QueryOptions): Promise<QueryFindResult<E, S, V, X, P, C>[]>;
|
|
49
49
|
/**
|
|
50
|
-
* Stream records as an async iterable
|
|
51
|
-
*
|
|
50
|
+
* Stream records as an async iterable, in both patterns, each with the relations and counts
|
|
51
|
+
* `findMany` reads. Fires no lifecycle hooks.
|
|
52
52
|
*/
|
|
53
|
-
findManyStream<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(q:
|
|
53
|
+
findManyStream<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(q: QueryProjected<E, S, V, X, P, C> & {
|
|
54
54
|
$entity: Type<E>;
|
|
55
|
-
}, opts?: QueryOptions): AsyncIterable<QueryFindResult<E, S, V, X, P>>;
|
|
56
|
-
findManyStream<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(entity: Type<E>, q:
|
|
55
|
+
}, opts?: QueryOptions): AsyncIterable<QueryFindResult<E, S, V, X, P, C>>;
|
|
56
|
+
findManyStream<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C>, opts?: QueryOptions): AsyncIterable<QueryFindResult<E, S, V, X, P, C>>;
|
|
57
57
|
/**
|
|
58
58
|
* Find many records and count. Supports both patterns.
|
|
59
59
|
*/
|
package/dist/type/query.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FieldKey,
|
|
1
|
+
import type { FieldKey, JsonFieldPaths, RelationKey, RelationTarget, WrittenId } from './entity.js';
|
|
2
2
|
import type { QueryLock } from './queryLock.js';
|
|
3
3
|
import type { QueryRaw } from './queryRaw.js';
|
|
4
4
|
import type { QueryWhere } from './queryWhere.js';
|
|
@@ -25,16 +25,6 @@ export type QueryOptions = {
|
|
|
25
25
|
*/
|
|
26
26
|
autoPrefix?: boolean;
|
|
27
27
|
};
|
|
28
|
-
export type QuerySelectOptions = {
|
|
29
|
-
/**
|
|
30
|
-
* prefix the query with this.
|
|
31
|
-
*/
|
|
32
|
-
prefix?: string;
|
|
33
|
-
/**
|
|
34
|
-
* automatically add the prefix for the alias.
|
|
35
|
-
*/
|
|
36
|
-
autoPrefixAlias?: boolean;
|
|
37
|
-
};
|
|
38
28
|
/**
|
|
39
29
|
* Query field selection - `{ name: true }` whitelists specific fields. Fields only: a relation is a
|
|
40
30
|
* sub-query rather than a projection flag, and a whitelist naming one could not say whether the
|
|
@@ -66,9 +56,8 @@ export type QueryPopulate<E> = {
|
|
|
66
56
|
export declare const COUNT_RESULT_KEY = "_count";
|
|
67
57
|
/**
|
|
68
58
|
* How many rows each named relation holds per parent, `true` for all of them or a filter to narrow
|
|
69
|
-
* which ones count
|
|
70
|
-
*
|
|
71
|
-
* a relation of the same name that `$populate` filled with rows.
|
|
59
|
+
* which ones count: a correlated count in the read's own statement, so no related row is loaded. Comes
|
|
60
|
+
* back under `_count`, which keeps it clear of a relation of the same name `$populate` filled.
|
|
72
61
|
*/
|
|
73
62
|
export type QueryCount<E> = {
|
|
74
63
|
[K in ToManyRelationKey<E>]?: BooleanLike | QueryFilter<RelationTarget<E[K]>>;
|
|
@@ -82,7 +71,7 @@ export type QueryConflictPaths<E> = {
|
|
|
82
71
|
/**
|
|
83
72
|
* Options to populate a relation declared as `V`, by its cardinality.
|
|
84
73
|
*/
|
|
85
|
-
export type QueryPopulateRelationOptions<V> =
|
|
74
|
+
export type QueryPopulateRelationOptions<V> = IsMany<V> extends true ? RelationQuery<RelationTarget<V>> : QueryUnique<RelationTarget<V>> & {
|
|
86
75
|
$required?: boolean;
|
|
87
76
|
};
|
|
88
77
|
/**
|
|
@@ -276,14 +265,13 @@ export type Query<E> = {
|
|
|
276
265
|
* Declared beside the type they describe so the two cannot drift, and `satisfies` fails the build
|
|
277
266
|
* rather than the runtime if a clause is ever renamed.
|
|
278
267
|
*
|
|
279
|
-
* `$lock`
|
|
280
|
-
*
|
|
268
|
+
* `$lock` is only in {@link QUERY_STATEMENT_CLAUSES}: neither a wire query nor a relation's query
|
|
269
|
+
* accepts it.
|
|
281
270
|
*/
|
|
282
271
|
export declare const QUERY_OBJECT_CLAUSES: readonly ["$select", "$populate", "$exclude", "$where", "$sort"];
|
|
283
272
|
/**
|
|
284
|
-
* Object clauses only the statement itself takes
|
|
285
|
-
*
|
|
286
|
-
* populated relation's rows are assembled after that, so there is nothing for a nested one to count.
|
|
273
|
+
* Object clauses only the statement itself takes: a populated relation's rows keep their declared type,
|
|
274
|
+
* so a `$count` inside one would have no `_count` to land in.
|
|
287
275
|
*/
|
|
288
276
|
export declare const QUERY_ROOT_OBJECT_CLAUSES: readonly ["$count"];
|
|
289
277
|
export declare const QUERY_NUMBER_CLAUSES: readonly ["$skip", "$limit"];
|
|
@@ -294,6 +282,16 @@ export declare const QUERY_NUMBER_CLAUSES: readonly ["$skip", "$limit"];
|
|
|
294
282
|
*/
|
|
295
283
|
export declare const QUERY_ROOT_NUMBER_CLAUSES: readonly ["$candidates"];
|
|
296
284
|
export declare const QUERY_BOOLEAN_CLAUSES: readonly ["$distinct"];
|
|
285
|
+
/** The clauses that describe the statement, which a populated relation's own query refuses by name. */
|
|
286
|
+
export declare const QUERY_STATEMENT_CLAUSES: readonly ["$lock", "$count", "$candidates"];
|
|
287
|
+
type RelationClause = (typeof QUERY_OBJECT_CLAUSES | typeof QUERY_NUMBER_CLAUSES | typeof QUERY_BOOLEAN_CLAUSES)[number];
|
|
288
|
+
/**
|
|
289
|
+
* A populated relation's own query: the clause groups its runtime check accepts, so the two cannot
|
|
290
|
+
* drift, and a clause added to {@link Query} stays off it until it joins one of them.
|
|
291
|
+
*/
|
|
292
|
+
export type RelationQuery<E = object> = Pick<Query<E>, RelationClause> & {
|
|
293
|
+
$required?: boolean;
|
|
294
|
+
};
|
|
297
295
|
/**
|
|
298
296
|
* options to get a single record.
|
|
299
297
|
*/
|
|
@@ -315,18 +313,7 @@ export type QueryUnique<E> = Pick<QueryOne<E>, '$select' | '$exclude' | '$popula
|
|
|
315
313
|
* populated relation's own query - stays the concrete {@link Query} it is today.
|
|
316
314
|
* @internal
|
|
317
315
|
*/
|
|
318
|
-
type QueryProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E>> =
|
|
319
|
-
$count?: {
|
|
320
|
-
[K in C & keyof QueryCount<E>]?: QueryCount<E>[K];
|
|
321
|
-
};
|
|
322
|
-
};
|
|
323
|
-
/**
|
|
324
|
-
* {@link QueryProjection} without `$count`, which a stream cannot honor. Split out rather than
|
|
325
|
-
* subtracted afterwards: an optional key is a *known* key even when its value maps over `never`, so
|
|
326
|
-
* a statement that must not take the clause has to be built without it in the first place.
|
|
327
|
-
* @internal
|
|
328
|
-
*/
|
|
329
|
-
type QueryStreamProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>> = {
|
|
316
|
+
type QueryProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E>> = {
|
|
330
317
|
$select?: {
|
|
331
318
|
[K in S]?: V;
|
|
332
319
|
} | readonly QueryRaw[];
|
|
@@ -336,12 +323,10 @@ type QueryStreamProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P
|
|
|
336
323
|
$populate?: {
|
|
337
324
|
[K in P]?: QueryPopulate<E>[K];
|
|
338
325
|
};
|
|
326
|
+
$count?: {
|
|
327
|
+
[K in C & keyof QueryCount<E>]?: QueryCount<E>[K];
|
|
328
|
+
};
|
|
339
329
|
};
|
|
340
|
-
/**
|
|
341
|
-
* A {@link QueryProjected} a stream can honor: no `$count`, which is batched over a result set a
|
|
342
|
-
* stream never holds all of.
|
|
343
|
-
*/
|
|
344
|
-
export type QueryStreamProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>> = Except<Query<E>, '$count'> & QueryStreamProjection<E, S, V, X, P>;
|
|
345
330
|
/**
|
|
346
331
|
* A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.
|
|
347
332
|
*/
|
|
@@ -357,15 +342,7 @@ export type QueryOneProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>
|
|
|
357
342
|
* why `$exclude` is only read on the branch where there is none.
|
|
358
343
|
* @internal
|
|
359
344
|
*/
|
|
360
|
-
type ProjectedKeys<E, S, V, X, P
|
|
361
|
-
/**
|
|
362
|
-
* The id key when it can be named, and nothing when it cannot: {@link IdKey} widens to *every* field
|
|
363
|
-
* for an entity whose id is neither branded nor called `id`/`_id`/`uuid`, and adding that back would
|
|
364
|
-
* hand the caller a row claiming fields the query never fetched. Missing an id costs a `$select`
|
|
365
|
-
* entry; promising absent fields is the bug this type exists to prevent.
|
|
366
|
-
* @internal
|
|
367
|
-
*/
|
|
368
|
-
type NamedIdKey<E> = [FieldKey<E>] extends [IdKey<E>] ? never : IdKey<E>;
|
|
345
|
+
type ProjectedKeys<E, S, V, X, P> = ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S) | P;
|
|
369
346
|
/**
|
|
370
347
|
* Whether every entry of the captured map says the same thing: all selected, or all subtracted.
|
|
371
348
|
* @internal
|
|
@@ -394,8 +371,8 @@ type CountedRelations<C extends PropertyKey> = [C] extends [never] ? unknown : {
|
|
|
394
371
|
};
|
|
395
372
|
};
|
|
396
373
|
/** @internal */
|
|
397
|
-
type QueryProjectedRow<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E>> = [S | X] extends [never] ? E : IsUniform<V> extends true ? [PopulatedToMany<E, P>] extends [never] ? Pick<E, ProjectedKeys<E, S, V, X, P
|
|
398
|
-
Pick<E, Exclude<ProjectedKeys<E, S, V, X, P
|
|
374
|
+
type QueryProjectedRow<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E>> = [S | X] extends [never] ? E : IsUniform<V> extends true ? [PopulatedToMany<E, P>] extends [never] ? Pick<E, ProjectedKeys<E, S, V, X, P> & keyof E> : // A populated to-many is always a list, empty where the parent has no children, so it maps
|
|
375
|
+
Pick<E, Exclude<ProjectedKeys<E, S, V, X, P>, PopulatedToMany<E, P>> & keyof E> & {
|
|
399
376
|
[K in PopulatedToMany<E, P>]-?: NonNullable<E[K]>;
|
|
400
377
|
} : E;
|
|
401
378
|
/** The to-many relations a query populated, which come back as lists rather than as optional ones. */
|
package/dist/type/query.js
CHANGED
|
@@ -9,8 +9,8 @@ export const COUNT_RESULT_KEY = '_count';
|
|
|
9
9
|
* Declared beside the type they describe so the two cannot drift, and `satisfies` fails the build
|
|
10
10
|
* rather than the runtime if a clause is ever renamed.
|
|
11
11
|
*
|
|
12
|
-
* `$lock`
|
|
13
|
-
*
|
|
12
|
+
* `$lock` is only in {@link QUERY_STATEMENT_CLAUSES}: neither a wire query nor a relation's query
|
|
13
|
+
* accepts it.
|
|
14
14
|
*/
|
|
15
15
|
export const QUERY_OBJECT_CLAUSES = [
|
|
16
16
|
'$select',
|
|
@@ -20,9 +20,8 @@ export const QUERY_OBJECT_CLAUSES = [
|
|
|
20
20
|
'$sort',
|
|
21
21
|
];
|
|
22
22
|
/**
|
|
23
|
-
* Object clauses only the statement itself takes
|
|
24
|
-
*
|
|
25
|
-
* populated relation's rows are assembled after that, so there is nothing for a nested one to count.
|
|
23
|
+
* Object clauses only the statement itself takes: a populated relation's rows keep their declared type,
|
|
24
|
+
* so a `$count` inside one would have no `_count` to land in.
|
|
26
25
|
*/
|
|
27
26
|
export const QUERY_ROOT_OBJECT_CLAUSES = ['$count'];
|
|
28
27
|
export const QUERY_NUMBER_CLAUSES = ['$skip', '$limit'];
|
|
@@ -33,3 +32,9 @@ export const QUERY_NUMBER_CLAUSES = ['$skip', '$limit'];
|
|
|
33
32
|
*/
|
|
34
33
|
export const QUERY_ROOT_NUMBER_CLAUSES = ['$candidates'];
|
|
35
34
|
export const QUERY_BOOLEAN_CLAUSES = ['$distinct'];
|
|
35
|
+
/** The clauses that describe the statement, which a populated relation's own query refuses by name. */
|
|
36
|
+
export const QUERY_STATEMENT_CLAUSES = [
|
|
37
|
+
'$lock',
|
|
38
|
+
...QUERY_ROOT_OBJECT_CLAUSES,
|
|
39
|
+
...QUERY_ROOT_NUMBER_CLAUSES,
|
|
40
|
+
];
|