uql-orm 0.55.0 → 0.56.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/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +3 -3
- package/dist/bunSql/bunSql.util.js +1 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +2 -3
- package/dist/dialect/pgLikeSqlDialect.js +2 -2
- package/dist/dialect/queryJoins.js +2 -4
- 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 +24 -26
- package/dist/migrate/builder/splitSqlStatements.js +2 -2
- package/dist/migrate/cli.js +2 -9
- package/dist/migrate/codegen/fieldOptionsSource.js +1 -1
- package/dist/migrate/ddl/pgIndexDdl.d.ts +5 -0
- package/dist/migrate/ddl/pgIndexDdl.js +9 -0
- 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 +5 -2
- package/dist/migrate/migrator.js +9 -14
- package/dist/migrate/schemaGenerator.js +2 -6
- package/dist/mongo/mongoDialect.d.ts +1 -1
- package/dist/mongo/mongoDialect.js +4 -7
- package/dist/mongo/mongodbQuerier.js +6 -6
- package/dist/querier/abstractQuerier.js +17 -22
- package/dist/querier/abstractSqlQuerier.d.ts +10 -8
- package/dist/querier/abstractSqlQuerier.js +33 -27
- package/dist/schema/canonicalType.js +0 -2
- package/dist/schema/dependencyGraph.js +2 -4
- package/dist/schema/indexDifferences.js +2 -2
- package/dist/schema/schemaASTBuilder.js +3 -10
- package/dist/schema/schemaASTDiffer.d.ts +10 -2
- package/dist/schema/schemaASTDiffer.js +12 -11
- package/dist/schema/types.d.ts +1 -1
- package/dist/sqlite/sqliteDialect.js +2 -1
- package/dist/util/dialect.util.d.ts +7 -1
- package/dist/util/dialect.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 +2 -0
- package/dist/util/object.util.js +4 -0
- package/dist/util/relationQuery.util.d.ts +3 -3
- package/dist/util/relationQuery.util.js +8 -2
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { COUNT_ALIAS, TOTAL_ALIAS } from '../dialect/aliases.js';
|
|
2
2
|
import { decodeColumn } from '../dialect/hydrateColumn.js';
|
|
3
3
|
import { getMeta, idOf, namesKey } from '../entity/index.js';
|
|
4
|
-
import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, insertShapeOf, getRelationRequestSummary,
|
|
4
|
+
import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, insertShapeOf, getRelationRequestSummary, idOnlyQuery, isAutoIncrement, isPagedQuery, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
5
5
|
import { AbstractQuerier } from './abstractQuerier.js';
|
|
6
6
|
import { enrichError } from './queryError.js';
|
|
7
7
|
/**
|
|
@@ -223,8 +223,11 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
223
223
|
return this.all(ctx.sql, ctx.values);
|
|
224
224
|
}
|
|
225
225
|
async hydrateRows(entity, q, rows) {
|
|
226
|
-
const founds = unflatObjects(rows)
|
|
227
|
-
|
|
226
|
+
const founds = unflatObjects(rows);
|
|
227
|
+
this.hydrateAll(entity, founds);
|
|
228
|
+
if (q.$populate) {
|
|
229
|
+
await this.fillToManyRelations(entity, founds, q.$populate);
|
|
230
|
+
}
|
|
228
231
|
return founds;
|
|
229
232
|
}
|
|
230
233
|
async *internalFindManyStream(entity, q, opts) {
|
|
@@ -245,11 +248,14 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
245
248
|
// context holds was normalized as it was bound.
|
|
246
249
|
const ctx = this.dialect.createContext();
|
|
247
250
|
this.dialect.find(ctx, entity, q, opts);
|
|
251
|
+
const fields = this.dialect.hydratableFields(entity);
|
|
248
252
|
let attrsPaths;
|
|
249
253
|
try {
|
|
250
254
|
for await (const row of this.internalStream(ctx.sql, ctx.values)) {
|
|
251
255
|
attrsPaths ??= obtainAttrsPaths(row);
|
|
252
|
-
|
|
256
|
+
const found = unflatObject(row, attrsPaths);
|
|
257
|
+
this.hydrateFields(meta, fields, found);
|
|
258
|
+
yield found;
|
|
253
259
|
}
|
|
254
260
|
}
|
|
255
261
|
catch (err) {
|
|
@@ -265,33 +271,35 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
265
271
|
yield* await this.internalAll(query, values);
|
|
266
272
|
}
|
|
267
273
|
/**
|
|
268
|
-
* Turn what a driver returned back into the types the entity declares, for
|
|
269
|
-
* populated under
|
|
270
|
-
* `decodeColumn`. Both live with the dialect, because a `sparsevec` is
|
|
271
|
-
*
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
* Turn what a driver returned back into the types the entity declares, for every row and everything
|
|
275
|
+
* populated under them. Which columns, and as what, is `hydratableFields`, resolved once for all the
|
|
276
|
+
* rows; the per-cell decode is `decodeColumn`. Both live with the dialect, because a `sparsevec` is
|
|
277
|
+
* only sparse on Postgres.
|
|
278
|
+
*/
|
|
279
|
+
hydrateAll(entity, dtos, visited) {
|
|
280
|
+
const meta = getMeta(entity);
|
|
281
|
+
const fields = this.dialect.hydratableFields(entity);
|
|
282
|
+
for (const dto of dtos) {
|
|
283
|
+
this.hydrateFields(meta, fields, dto, visited);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* One row of {@link hydrateAll}. `visited` guards a populated graph that points back at itself, and
|
|
288
|
+
* makes a node two paths reach decode once. Only a populated relation can lead the walk back, so the
|
|
289
|
+
* guard is created at the first one a row carries: rows that populated nothing never allocate one.
|
|
276
290
|
*/
|
|
277
|
-
hydrateFields(
|
|
291
|
+
hydrateFields(meta, fields, dto, visited) {
|
|
278
292
|
if (!dto || typeof dto !== 'object' || visited?.has(dto)) {
|
|
279
|
-
return
|
|
293
|
+
return;
|
|
280
294
|
}
|
|
281
|
-
|
|
295
|
+
visited?.add(dto);
|
|
282
296
|
const row = dto;
|
|
283
|
-
for (const [key, kind] of
|
|
297
|
+
for (const [key, kind] of fields) {
|
|
284
298
|
const value = row[key];
|
|
285
299
|
if (value != null) {
|
|
286
300
|
row[key] = decodeColumn(value, kind);
|
|
287
301
|
}
|
|
288
302
|
}
|
|
289
|
-
// Allocated only where the walk can continue: an entity declaring no relation cannot lead back
|
|
290
|
-
// to a node already decoded, and the loop below is a no-op for it anyway.
|
|
291
|
-
if (hasKeys(meta.relations)) {
|
|
292
|
-
visited ??= new WeakSet();
|
|
293
|
-
}
|
|
294
|
-
visited?.add(dto);
|
|
295
303
|
// The value is read before the relation's target is resolved: a query that populated nothing
|
|
296
304
|
// still walks every relation the entity declares, and `rel.entity()` is a call per row per
|
|
297
305
|
// relation that only the populated ones need.
|
|
@@ -302,16 +310,14 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
302
310
|
const rel = meta.relations[key];
|
|
303
311
|
if (!rel)
|
|
304
312
|
continue;
|
|
313
|
+
visited ??= new WeakSet([dto]);
|
|
305
314
|
const relEntity = rel.entity();
|
|
306
315
|
if (Array.isArray(value)) {
|
|
307
|
-
|
|
308
|
-
this.hydrateFields(relEntity, it, visited);
|
|
309
|
-
}
|
|
316
|
+
this.hydrateAll(relEntity, value, visited);
|
|
310
317
|
continue;
|
|
311
318
|
}
|
|
312
|
-
this.hydrateFields(relEntity, value, visited);
|
|
319
|
+
this.hydrateFields(getMeta(relEntity), this.dialect.hydratableFields(relEntity), value, visited);
|
|
313
320
|
}
|
|
314
|
-
return dto;
|
|
315
321
|
}
|
|
316
322
|
/**
|
|
317
323
|
* Runs a statement whose one row carries a {@link COUNT_ALIAS} column. `Number` because `COUNT(*)` is BIGINT and
|
|
@@ -38,11 +38,9 @@ export function findCycles(nodes, dependenciesOf) {
|
|
|
38
38
|
const visited = new Set();
|
|
39
39
|
const onPath = new Set();
|
|
40
40
|
const visit = (node, path) => {
|
|
41
|
+
// A node on the walk was handed down in `path` too, so the cycle closes where it first appears.
|
|
41
42
|
if (onPath.has(node)) {
|
|
42
|
-
|
|
43
|
-
if (start !== -1) {
|
|
44
|
-
cycles.push(path.slice(start));
|
|
45
|
-
}
|
|
43
|
+
cycles.push(path.slice(path.indexOf(node)));
|
|
46
44
|
return;
|
|
47
45
|
}
|
|
48
46
|
if (visited.has(node)) {
|
|
@@ -64,8 +64,8 @@ export function describeIndexDifferences(source, target, facets) {
|
|
|
64
64
|
differences.push(`columns: (${targetColumns}) → (${sourceColumns})`);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
if (
|
|
68
|
-
differences.push(`unique: ${target.unique
|
|
67
|
+
if (source.unique !== target.unique) {
|
|
68
|
+
differences.push(`unique: ${target.unique} → ${source.unique}`);
|
|
69
69
|
}
|
|
70
70
|
if (facets.has('accessMethod') && (source.type ?? 'btree') !== (target.type ?? 'btree')) {
|
|
71
71
|
differences.push(`type: ${target.type ?? 'btree'} → ${source.type ?? 'btree'}`);
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -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,6 +1,7 @@
|
|
|
1
1
|
import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
|
|
2
2
|
import { JSON_ELEM_ALIAS_PREFIX, JSON_PULL_ALIAS } from '../dialect/aliases.js';
|
|
3
3
|
import { jsonAssignCall, jsonElemExists, jsonPath, jsonRemoveCall, jsonSetTarget } from '../dialect/jsonSql.js';
|
|
4
|
+
import { textSearchFields } from '../util/dialect.util.js';
|
|
4
5
|
export class SqliteDialect extends AbstractSqlDialect {
|
|
5
6
|
/** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
|
|
6
7
|
featureDefaults = {
|
|
@@ -86,7 +87,7 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
86
87
|
* FTS5 virtual table (UQL does not create those; declare it outside your entities).
|
|
87
88
|
*/
|
|
88
89
|
appendTextSearch(ctx, entity, meta, search) {
|
|
89
|
-
const columns = search
|
|
90
|
+
const columns = textSearchFields(meta, search).map((key) => this.escapeId(this.resolveColumnName(key, meta.fields[key])));
|
|
90
91
|
ctx.append(`${this.escapedTableName(meta)} MATCH {${columns.join(' ')}} : `);
|
|
91
92
|
ctx.addValue(search.$value);
|
|
92
93
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CascadeType, type EntityData, type EntityId, type EntityIndexMeta, type EntityMeta, type FieldKey, type FieldOptions, type JsonUpdateOp, type OnFieldCallback, type Query, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySearch, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryVectorSearch, type QueryWhere, type RelationKey, type UpdatePayload } from '../type/index.js';
|
|
1
|
+
import { type CascadeType, type EntityData, type EntityId, type EntityIndexMeta, type EntityMeta, type FieldKey, type FieldOptions, type JsonUpdateOp, type OnFieldCallback, type Query, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySearch, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorSearch, type QueryWhere, type RelationKey, type UpdatePayload } from '../type/index.js';
|
|
2
2
|
export type CallbackKey = keyof Pick<FieldOptions, 'onInsert' | 'onUpdate'>;
|
|
3
3
|
export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: EntityData<E>, callbackKey: CallbackKey): FieldKey<E>[];
|
|
4
4
|
/** Appends `record`'s not-yet-`seen` insertable keys (real, caller-written, defined value) to `keys`. */
|
|
@@ -171,3 +171,9 @@ export declare function assertNonNegativeInteger(value: number, clause: string):
|
|
|
171
171
|
export declare function throwUnknownAggregateColumn(key: string, clause: string): never;
|
|
172
172
|
/** {@link throwUnknownAggregateColumn} over every key of a clause, for backends that check up front. */
|
|
173
173
|
export declare function assertAggregateColumns(clauseMap: object, emitted: ReadonlySet<string>, clause: string): void;
|
|
174
|
+
/**
|
|
175
|
+
* The fields a `$text` searches: those it names, or else the columns of the entity's fulltext index,
|
|
176
|
+
* the declaration MySQL's `MATCH` has to name exactly and a MongoDB text index already is. Refused
|
|
177
|
+
* where neither says, rather than guessed: every engine answers a guess with an error of its own.
|
|
178
|
+
*/
|
|
179
|
+
export declare function textSearchFields<E>(meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): readonly string[];
|
|
@@ -424,3 +424,22 @@ export function assertAggregateColumns(clauseMap, emitted, clause) {
|
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* The fields a `$text` searches: those it names, or else the columns of the entity's fulltext index,
|
|
429
|
+
* the declaration MySQL's `MATCH` has to name exactly and a MongoDB text index already is. Refused
|
|
430
|
+
* where neither says, rather than guessed: every engine answers a guess with an error of its own.
|
|
431
|
+
*/
|
|
432
|
+
export function textSearchFields(meta, search) {
|
|
433
|
+
if (search.$fields?.length) {
|
|
434
|
+
return search.$fields;
|
|
435
|
+
}
|
|
436
|
+
const fulltext = (meta.indexes ?? []).filter((index) => index.type === 'fulltext');
|
|
437
|
+
if (fulltext.length === 1) {
|
|
438
|
+
return fulltext[0].columns.map((entry) => entry.column);
|
|
439
|
+
}
|
|
440
|
+
const name = entityName(meta);
|
|
441
|
+
const declared = fulltext.length
|
|
442
|
+
? `${fulltext.length} fulltext indexes to choose from`
|
|
443
|
+
: 'no fulltext index to search';
|
|
444
|
+
throw new TypeError(`$text on '${name}' names no $fields, and '${name}' declares ${declared}. Name them with $fields.`);
|
|
445
|
+
}
|
package/dist/util/logger.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export declare class LoggerWrapper implements Logger {
|
|
|
34
34
|
private readonly loggerFunction?;
|
|
35
35
|
private readonly logValues;
|
|
36
36
|
private readonly slowQuery?;
|
|
37
|
-
constructor(options
|
|
37
|
+
constructor(options?: LoggingOptions, config?: LoggerWrapperConfig);
|
|
38
38
|
/** Whether `logQuery` would ever actually surface bound values, given the configured levels/slowQuery/logValues. */
|
|
39
39
|
willLogValues(): boolean;
|
|
40
40
|
logQuery(query: string, values?: unknown[], duration?: number): void;
|
|
@@ -46,6 +46,15 @@ export declare class LoggerWrapper implements Logger {
|
|
|
46
46
|
logSkippedMigration(message: string): void;
|
|
47
47
|
private log;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The wrapper a querier logs through, one per options object and read once: a pool builds a querier
|
|
51
|
+
* for every statement, and all of them log by the same options.
|
|
52
|
+
*/
|
|
53
|
+
export declare function queryLoggerFor(extra?: {
|
|
54
|
+
readonly logger?: LoggingOptions;
|
|
55
|
+
readonly logValues?: boolean;
|
|
56
|
+
readonly slowQuery?: number;
|
|
57
|
+
}): LoggerWrapper;
|
|
49
58
|
/**
|
|
50
59
|
* Structural type for any EventEmitter-like connection pool that emits an
|
|
51
60
|
* `'error'` event on a dropped connection (node-postgres, `mariadb`, etc.).
|
package/dist/util/logger.js
CHANGED
|
@@ -136,6 +136,24 @@ export class LoggerWrapper {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
+
const wrappersByOptions = new WeakMap();
|
|
140
|
+
let unconfigured;
|
|
141
|
+
/**
|
|
142
|
+
* The wrapper a querier logs through, one per options object and read once: a pool builds a querier
|
|
143
|
+
* for every statement, and all of them log by the same options.
|
|
144
|
+
*/
|
|
145
|
+
export function queryLoggerFor(extra) {
|
|
146
|
+
if (!extra) {
|
|
147
|
+
unconfigured ??= new LoggerWrapper();
|
|
148
|
+
return unconfigured;
|
|
149
|
+
}
|
|
150
|
+
let wrapper = wrappersByOptions.get(extra);
|
|
151
|
+
if (!wrapper) {
|
|
152
|
+
wrapper = new LoggerWrapper(extra.logger, { logValues: extra.logValues, slowQuery: extra.slowQuery });
|
|
153
|
+
wrappersByOptions.set(extra, wrapper);
|
|
154
|
+
}
|
|
155
|
+
return wrapper;
|
|
156
|
+
}
|
|
139
157
|
/**
|
|
140
158
|
* Attaches an error listener to a connection pool so a dropped connection is logged instead of left
|
|
141
159
|
* unhandled - which crashes the process for drivers that don't guard against it themselves
|
|
@@ -20,6 +20,8 @@ export declare function isOperatorObject(value: unknown): value is Record<string
|
|
|
20
20
|
/** Whether every key of the non-empty object `value` is an operator (no plain field names mixed in). */
|
|
21
21
|
export declare function isOperatorOnlyObject(value: unknown): value is Record<string, unknown>;
|
|
22
22
|
export declare function getKeys<T extends object>(obj: T): (keyof T & string)[];
|
|
23
|
+
/** The entries of `record` holding a value: a key declared but left `undefined` is no entry at all. */
|
|
24
|
+
export declare function definedEntries<K extends string, V>(record: Partial<Record<K, V>>): [K, V][];
|
|
23
25
|
/**
|
|
24
26
|
* The entity's own name, declared or its class's. `meta.name` holds only what the author wrote, so
|
|
25
27
|
* the fallback is what an entity that named no table is called - which is why the sites spelling this
|
package/dist/util/object.util.js
CHANGED
|
@@ -52,6 +52,10 @@ export function isOperatorOnlyObject(value) {
|
|
|
52
52
|
export function getKeys(obj) {
|
|
53
53
|
return obj ? Object.keys(obj) : [];
|
|
54
54
|
}
|
|
55
|
+
/** The entries of `record` holding a value: a key declared but left `undefined` is no entry at all. */
|
|
56
|
+
export function definedEntries(record) {
|
|
57
|
+
return Object.entries(record).filter((entry) => entry[1] !== undefined);
|
|
58
|
+
}
|
|
55
59
|
/**
|
|
56
60
|
* The entity's own name, declared or its class's. `meta.name` holds only what the author wrote, so
|
|
57
61
|
* the fallback is what an entity that named no table is called - which is why the sites spelling this
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { EntityMeta, FieldMeta, Except, Query, QueryPopulate, RelationKey, RelationMeta } from '../type/index.js';
|
|
2
2
|
export type RelationRequestSummary<E> = {
|
|
3
|
-
readonly requestedKeys: RelationKey<E>[];
|
|
4
|
-
readonly joinableKeys: RelationKey<E>[];
|
|
5
|
-
readonly toManyKeys: RelationKey<E>[];
|
|
3
|
+
readonly requestedKeys: readonly RelationKey<E>[];
|
|
4
|
+
readonly joinableKeys: readonly RelationKey<E>[];
|
|
5
|
+
readonly toManyKeys: readonly RelationKey<E>[];
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
8
|
* Whether a relation holds many rows per parent, so it cannot be joined into the parent's row. Takes
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { QUERY_BOOLEAN_CLAUSES, QUERY_NUMBER_CLAUSES, QUERY_OBJECT_CLAUSES, QUERY_ROOT_NUMBER_CLAUSES, } from '../type/query.js';
|
|
2
2
|
import { getKeys, someKey } from './object.util.js';
|
|
3
|
+
/** What a query populating nothing requests, shared: most reads populate nothing, and ask on every one. */
|
|
4
|
+
const NOTHING_REQUESTED = Object.freeze({
|
|
5
|
+
requestedKeys: Object.freeze([]),
|
|
6
|
+
joinableKeys: Object.freeze([]),
|
|
7
|
+
toManyKeys: Object.freeze([]),
|
|
8
|
+
});
|
|
3
9
|
/**
|
|
4
10
|
* Whether a relation holds many rows per parent, so it cannot be joined into the parent's row. Takes
|
|
5
11
|
* the one field it reads, so it answers for a relation being declared as well as for a resolved one.
|
|
@@ -142,11 +148,11 @@ function assertJoinableRelationQuery(relKey, value) {
|
|
|
142
148
|
}
|
|
143
149
|
}
|
|
144
150
|
export function getRelationRequestSummary(meta, populate) {
|
|
151
|
+
if (!populate)
|
|
152
|
+
return NOTHING_REQUESTED;
|
|
145
153
|
const requestedKeys = [];
|
|
146
154
|
const joinableKeys = [];
|
|
147
155
|
const toManyKeys = [];
|
|
148
|
-
if (!populate)
|
|
149
|
-
return { requestedKeys, joinableKeys, toManyKeys };
|
|
150
156
|
for (const key of getKeys(populate)) {
|
|
151
157
|
if (!populate[key])
|
|
152
158
|
continue;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.56.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|