uql-orm 0.52.0 → 0.54.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 +5 -5
- package/dist/cockroachdb/cockroachDialect.d.ts +2 -5
- package/dist/cockroachdb/cockroachDialect.js +2 -5
- package/dist/dialect/abstractDialect.d.ts +5 -5
- package/dist/dialect/abstractDialect.js +7 -6
- package/dist/dialect/abstractSqlDialect.d.ts +8 -3
- package/dist/dialect/abstractSqlDialect.js +14 -11
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -4
- package/dist/dialect/mysqlLikeSqlDialect.js +0 -6
- package/dist/dialect/pgLikeSqlDialect.js +0 -2
- package/dist/dialect/vectorSqlDialect.js +2 -1
- package/dist/entity/decorator/members.d.ts +3 -10
- package/dist/entity/metadata/definition.js +0 -4
- package/dist/http/handler.js +3 -12
- package/dist/http/query.js +4 -1
- package/dist/migrate/builder/migrationBuilder.js +0 -4
- package/dist/migrate/ddl/index.d.ts +8 -0
- package/dist/migrate/ddl/index.js +11 -0
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +23 -0
- package/dist/migrate/ddl/mssqlTableDdl.js +61 -0
- package/dist/migrate/ddl/tableDdl.d.ts +34 -0
- package/dist/migrate/ddl/tableDdl.js +71 -0
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +6 -9
- package/dist/migrate/introspection/mssqlIntrospector.js +40 -33
- package/dist/migrate/migrator.d.ts +3 -7
- package/dist/migrate/migrator.js +3 -7
- package/dist/migrate/schemaGenerator.d.ts +8 -23
- package/dist/migrate/schemaGenerator.js +29 -87
- package/dist/migrate/storage/databaseStorage.d.ts +2 -2
- package/dist/migrate/storage/databaseStorage.js +2 -2
- package/dist/mongo/mongoDialect.js +9 -14
- package/dist/mongo/mongodbQuerier.d.ts +3 -5
- package/dist/mongo/mongodbQuerier.js +18 -19
- package/dist/mssql/mssqlDialect.d.ts +14 -1
- package/dist/mssql/mssqlDialect.js +22 -7
- package/dist/querier/abstractQuerier.d.ts +17 -22
- package/dist/querier/abstractQuerier.js +92 -67
- package/dist/querier/abstractSqlQuerier.d.ts +9 -9
- package/dist/querier/abstractSqlQuerier.js +71 -87
- package/dist/schema/canonicalType.js +2 -2
- package/dist/schema/schemaASTBuilder.js +7 -7
- package/dist/sqlite/sqliteDialect.js +0 -2
- package/dist/type/dialect.d.ts +0 -8
- package/dist/type/entity.d.ts +6 -11
- package/dist/type/migration.d.ts +0 -3
- package/dist/type/query.d.ts +13 -13
- package/dist/type/query.js +0 -6
- package/dist/type/queryWhere.d.ts +7 -19
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/type/vector.d.ts +3 -1
- package/dist/util/dialect.util.d.ts +12 -8
- package/dist/util/dialect.util.js +17 -29
- package/dist/util/field.util.d.ts +4 -16
- package/dist/util/field.util.js +6 -19
- package/dist/util/fieldOption.util.d.ts +1 -4
- package/dist/util/fieldOption.util.js +0 -2
- package/dist/util/logger.d.ts +3 -3
- package/dist/util/logger.js +3 -0
- package/dist/util/object.util.d.ts +2 -0
- package/dist/util/object.util.js +4 -0
- package/dist/util/raw.d.ts +3 -10
- package/dist/util/sql.util.js +2 -2
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@ import { soleIdOf } from '../entity/metadata/definition.js';
|
|
|
3
3
|
import { QueryRaw, resolveAggregateOp, SOFT_DELETE_FILTER, } from '../type/index.js';
|
|
4
4
|
import { VECTOR_INDEX_TYPES } from '../type/vector.js';
|
|
5
5
|
import { isDatabaseWritten } from './field.util.js';
|
|
6
|
-
import { entityName, getFieldKeys, getKeys, hasKeys, isScalarId, someKey } from './object.util.js';
|
|
6
|
+
import { entityName, getFieldKeys, getKeys, hasKeys, isScalarId, isWhereMap, someKey } from './object.util.js';
|
|
7
7
|
export function filterFieldKeys(meta, payload, callbackKey) {
|
|
8
8
|
return getKeys(payload).filter((key) => {
|
|
9
9
|
const fieldOpts = meta.fields[key];
|
|
@@ -73,6 +73,7 @@ export function getFieldCallbackValue(val) {
|
|
|
73
73
|
export function getSoftDeleteValue(field) {
|
|
74
74
|
return field.softDelete === true ? new Date() : getFieldCallbackValue(field.softDelete);
|
|
75
75
|
}
|
|
76
|
+
/** Fills each field `callbackKey` generates on `payload` in place, where the caller left it unset. */
|
|
76
77
|
export function fillOnFields(meta, payload, callbackKey) {
|
|
77
78
|
const payloads = Array.isArray(payload) ? payload : [payload];
|
|
78
79
|
const keys = getKeys(meta.fields).filter((key) => meta.fields[key][callbackKey]);
|
|
@@ -234,37 +235,24 @@ const JSON_UPDATE_OPS = [
|
|
|
234
235
|
export function isJsonUpdateOp(value) {
|
|
235
236
|
return value !== null && typeof value === 'object' && someKey(value, (key) => JSON_UPDATE_OPS.includes(key));
|
|
236
237
|
}
|
|
237
|
-
export function augmentWhere(meta, target = {}, source = {}) {
|
|
238
|
-
const targetComparison = buildQueryWhereAsMap(meta, target);
|
|
239
|
-
const sourceComparison = buildQueryWhereAsMap(meta, source);
|
|
240
|
-
return {
|
|
241
|
-
...targetComparison,
|
|
242
|
-
...sourceComparison,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
238
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* {@link applyFilters} and {@link augmentWhere} return new objects instead.
|
|
239
|
+
* The `$where` naming rows by key: a bare value names the one key column (refused on a composite), a
|
|
240
|
+
* composite's key map is a `$where` already, and a list is an `IN` of bare values or an OR of maps.
|
|
249
241
|
*/
|
|
250
|
-
export function
|
|
251
|
-
if (
|
|
252
|
-
return {
|
|
253
|
-
}
|
|
254
|
-
if (Array.isArray(filter)) {
|
|
255
|
-
// A list of bare ids is an `IN` over the one key column; a list of anything else is a list of
|
|
256
|
-
// `$where`s, which is an OR - and that is how a composite's id objects name a settled set of rows.
|
|
257
|
-
return filter.every(isScalarId)
|
|
258
|
-
? { [soleIdOf(meta, 'addressing by a bare id value')]: filter }
|
|
259
|
-
: { $or: filter };
|
|
242
|
+
export function whereIds(meta, ids) {
|
|
243
|
+
if (Array.isArray(ids) ? ids.every(isScalarId) : isScalarId(ids)) {
|
|
244
|
+
return { [soleIdOf(meta, 'addressing by a bare id value')]: ids };
|
|
260
245
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
246
|
+
return (Array.isArray(ids) ? { $or: ids } : ids);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Refuses a `$where` that is not a map. Untyped JS and parsed JSON can still pass an id or a list of
|
|
250
|
+
* them, and a scalar read as a map has no keys: the statement would address every row.
|
|
251
|
+
*/
|
|
252
|
+
export function assertWhere(meta, where) {
|
|
253
|
+
if (!isWhereMap(where)) {
|
|
254
|
+
throw new TypeError(`$where on '${entityName(meta)}' must be a map of conditions, such as { id: 1 }`);
|
|
266
255
|
}
|
|
267
|
-
return filter;
|
|
268
256
|
}
|
|
269
257
|
/** Returns a `QueryOptions.filters` value with the built-in soft-delete filter disabled (used by hard delete). */
|
|
270
258
|
export function withoutSoftDeleteFilter(filters) {
|
|
@@ -314,7 +302,7 @@ export function applyFilters(meta, whereMap, opts) {
|
|
|
314
302
|
}
|
|
315
303
|
continue;
|
|
316
304
|
}
|
|
317
|
-
const conditionMap =
|
|
305
|
+
const conditionMap = condition;
|
|
318
306
|
if (!hasKeys(conditionMap)) {
|
|
319
307
|
continue; // resolved to "no restriction" (e.g. a trusted system context) - nothing to merge
|
|
320
308
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { EntityMeta, FieldOptions } from '../type/index.js';
|
|
2
|
-
import type { QueryRaw } from '../type/queryRaw.js';
|
|
3
2
|
/**
|
|
4
3
|
* The kind of column a field lands on, which is what decides whether an option means anything on it:
|
|
5
4
|
* `length` is a string's, `precision` a number's, `dimensions` a vector's. Named in the words an
|
|
@@ -23,26 +22,15 @@ export declare const COLUMN_TYPES_BY_FAMILY: {
|
|
|
23
22
|
};
|
|
24
23
|
/** The family of a logical field type, or `undefined` where it names none. */
|
|
25
24
|
export declare function columnFamily(type: unknown): ColumnFamily | undefined;
|
|
26
|
-
/**
|
|
27
|
-
* The expression the database computes for this field, whichever key declared it.
|
|
28
|
-
*
|
|
29
|
-
* `virtual` is `computed` under its old name and is read here so both spell one behaviour. Giving
|
|
30
|
-
* both is refused at registration rather than resolved, since only the author knows which was meant.
|
|
31
|
-
*/
|
|
32
|
-
export declare function computedExpression(field: FieldOptions): QueryRaw | undefined;
|
|
33
25
|
/**
|
|
34
26
|
* Whether the field's expression is spliced into each statement that reads it, rather than stored.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* the projection, the `$where` operand, the `ORDER BY` operand - because an inlined field has no
|
|
38
|
-
* column to name, while a stored one is read exactly like any other.
|
|
27
|
+
* Every read site asks this - the DDL skip, the projection, the `$where` and `ORDER BY` operands -
|
|
28
|
+
* because an inlined field has no column to name, while a stored one is read like any other.
|
|
39
29
|
*/
|
|
40
30
|
export declare function isInlinedExpression(field: FieldOptions): boolean;
|
|
41
31
|
/**
|
|
42
|
-
* Whether the database supplies this field's value, so no insert or update may write it
|
|
43
|
-
*
|
|
44
|
-
* The other question, and the one that makes `stored` more than a rename: a stored computed column
|
|
45
|
-
* *is* a real column, so it is read like one - but writing to it is an error on every engine.
|
|
32
|
+
* Whether the database supplies this field's value, so no insert or update may write it: a stored
|
|
33
|
+
* computed column *is* a real column, read like one, but writing to it is an error on every engine.
|
|
46
34
|
*/
|
|
47
35
|
export declare function isDatabaseWritten(field: FieldOptions): boolean;
|
|
48
36
|
/**
|
package/dist/util/field.util.js
CHANGED
|
@@ -46,33 +46,20 @@ for (const family of getKeys(COLUMN_TYPES_BY_FAMILY)) {
|
|
|
46
46
|
export function columnFamily(type) {
|
|
47
47
|
return FAMILY_OF.get(typeof type === 'string' ? type.toLowerCase() : type);
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
50
|
-
* The expression the database computes for this field, whichever key declared it.
|
|
51
|
-
*
|
|
52
|
-
* `virtual` is `computed` under its old name and is read here so both spell one behaviour. Giving
|
|
53
|
-
* both is refused at registration rather than resolved, since only the author knows which was meant.
|
|
54
|
-
*/
|
|
55
|
-
export function computedExpression(field) {
|
|
56
|
-
return field.computed ?? field.virtual;
|
|
57
|
-
}
|
|
58
49
|
/**
|
|
59
50
|
* Whether the field's expression is spliced into each statement that reads it, rather than stored.
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* the projection, the `$where` operand, the `ORDER BY` operand - because an inlined field has no
|
|
63
|
-
* column to name, while a stored one is read exactly like any other.
|
|
51
|
+
* Every read site asks this - the DDL skip, the projection, the `$where` and `ORDER BY` operands -
|
|
52
|
+
* because an inlined field has no column to name, while a stored one is read like any other.
|
|
64
53
|
*/
|
|
65
54
|
export function isInlinedExpression(field) {
|
|
66
|
-
return
|
|
55
|
+
return field.computed !== undefined && field.stored !== true;
|
|
67
56
|
}
|
|
68
57
|
/**
|
|
69
|
-
* Whether the database supplies this field's value, so no insert or update may write it
|
|
70
|
-
*
|
|
71
|
-
* The other question, and the one that makes `stored` more than a rename: a stored computed column
|
|
72
|
-
* *is* a real column, so it is read like one - but writing to it is an error on every engine.
|
|
58
|
+
* Whether the database supplies this field's value, so no insert or update may write it: a stored
|
|
59
|
+
* computed column *is* a real column, read like one, but writing to it is an error on every engine.
|
|
73
60
|
*/
|
|
74
61
|
export function isDatabaseWritten(field) {
|
|
75
|
-
return
|
|
62
|
+
return field.computed !== undefined;
|
|
76
63
|
}
|
|
77
64
|
/**
|
|
78
65
|
* Whether the field is the entity's *whole* primary key - the only kind a serial can stand in for,
|
|
@@ -14,7 +14,6 @@ declare const FIELD_OPTION_FAMILY: {
|
|
|
14
14
|
readonly references: '*';
|
|
15
15
|
readonly onDelete: '*';
|
|
16
16
|
readonly enum: '*';
|
|
17
|
-
readonly virtual: '*';
|
|
18
17
|
readonly computed: '*';
|
|
19
18
|
readonly stored: '*';
|
|
20
19
|
readonly updatable: '*';
|
|
@@ -39,7 +38,7 @@ declare const FIELD_OPTION_FAMILY: {
|
|
|
39
38
|
* rather than on each option that dies, because it is one fact rather than nineteen - and because an
|
|
40
39
|
* option added without a thought then lands on the safe side of it.
|
|
41
40
|
*/
|
|
42
|
-
declare const INLINE_READS: readonly ["type", "
|
|
41
|
+
declare const INLINE_READS: readonly ["type", "computed", "stored", "enum", "eager", "distance"];
|
|
43
42
|
type InlineRead = (typeof INLINE_READS)[number];
|
|
44
43
|
/**
|
|
45
44
|
* What a column the *database* writes cannot use. A stored computed column is a real column - it has
|
|
@@ -66,8 +65,6 @@ type FamilyOfType<T> = T extends NumericColumnType | NumberConstructor | BigIntC
|
|
|
66
65
|
type DeadOptions<O> = (O extends {
|
|
67
66
|
readonly stored: true;
|
|
68
67
|
} ? GeneratedWrite : O extends {
|
|
69
|
-
readonly virtual: QueryRaw;
|
|
70
|
-
} | {
|
|
71
68
|
readonly computed: QueryRaw;
|
|
72
69
|
} ? Exclude<keyof FieldOptions, InlineRead> : never) | (O extends {
|
|
73
70
|
readonly isId: true;
|
|
@@ -14,7 +14,6 @@ const FIELD_OPTION_FAMILY = {
|
|
|
14
14
|
references: '*',
|
|
15
15
|
onDelete: '*',
|
|
16
16
|
enum: '*',
|
|
17
|
-
virtual: '*',
|
|
18
17
|
computed: '*',
|
|
19
18
|
stored: '*',
|
|
20
19
|
updatable: '*',
|
|
@@ -41,7 +40,6 @@ const FIELD_OPTION_FAMILY = {
|
|
|
41
40
|
*/
|
|
42
41
|
const INLINE_READS = [
|
|
43
42
|
'type',
|
|
44
|
-
'virtual',
|
|
45
43
|
'computed',
|
|
46
44
|
'stored',
|
|
47
45
|
'enum',
|
package/dist/util/logger.d.ts
CHANGED
|
@@ -12,9 +12,6 @@ export declare class DefaultLogger implements Logger {
|
|
|
12
12
|
logMigration(message: string): void;
|
|
13
13
|
logSkippedMigration(message: string): void;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* A wrapper class that implements the Logger interface and handles different logging options.
|
|
17
|
-
*/
|
|
18
15
|
/**
|
|
19
16
|
* Secondary {@link LoggerWrapper} settings, alongside the primary `options: LoggingOptions`
|
|
20
17
|
* constructor argument.
|
|
@@ -28,6 +25,9 @@ export interface LoggerWrapperConfig {
|
|
|
28
25
|
/** Threshold in milliseconds - queries exceeding this are logged as slow. */
|
|
29
26
|
slowQuery?: number;
|
|
30
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* A wrapper class that implements the Logger interface and handles different logging options.
|
|
30
|
+
*/
|
|
31
31
|
export declare class LoggerWrapper implements Logger {
|
|
32
32
|
private readonly levels;
|
|
33
33
|
private readonly logger?;
|
package/dist/util/logger.js
CHANGED
|
@@ -40,6 +40,9 @@ export class DefaultLogger {
|
|
|
40
40
|
console.info(`\x1b[33mskipped migration:\x1b[0m ${message}`);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* A wrapper class that implements the Logger interface and handles different logging options.
|
|
45
|
+
*/
|
|
43
46
|
export class LoggerWrapper {
|
|
44
47
|
levels;
|
|
45
48
|
logger;
|
|
@@ -35,3 +35,5 @@ export declare function getFieldKeys<E>(fields: {
|
|
|
35
35
|
* is what a `$where` map and a composite key's id object both are; an array is a list of either.
|
|
36
36
|
*/
|
|
37
37
|
export declare function isScalarId(value: unknown): boolean;
|
|
38
|
+
/** Whether `value` is a plain object naming columns, the one shape a `$where` takes. */
|
|
39
|
+
export declare function isWhereMap(value: unknown): value is Record<string, unknown>;
|
package/dist/util/object.util.js
CHANGED
|
@@ -81,3 +81,7 @@ export function isScalarId(value) {
|
|
|
81
81
|
const proto = Object.getPrototypeOf(value);
|
|
82
82
|
return proto !== Object.prototype && proto !== null;
|
|
83
83
|
}
|
|
84
|
+
/** Whether `value` is a plain object naming columns, the one shape a `$where` takes. */
|
|
85
|
+
export function isWhereMap(value) {
|
|
86
|
+
return !Array.isArray(value) && !isScalarId(value);
|
|
87
|
+
}
|
package/dist/util/raw.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { QueryRaw, type QueryRawFn
|
|
1
|
+
import { QueryRaw, type QueryRawFn } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Create a raw SQL expression.
|
|
4
4
|
*
|
|
@@ -19,18 +19,11 @@ import { QueryRaw, type QueryRawFn, type Scalar } from '../type/index.js';
|
|
|
19
19
|
* The callback form remains for SQL a template cannot express, such as a sub-query generated through
|
|
20
20
|
* `dialect.find(...)`. See {@link col} for a context-aware column reference.
|
|
21
21
|
*
|
|
22
|
-
* **⚠️ Security:** the tag is safe because it binds;
|
|
23
|
-
*
|
|
24
|
-
* Inside a callback, bind with `ctx.addValue()`.
|
|
22
|
+
* **⚠️ Security:** the tag is safe because it binds; a callback is not, since it emits whatever it
|
|
23
|
+
* writes, so never build one from user input. Inside a callback, bind with `ctx.addValue()`.
|
|
25
24
|
*/
|
|
26
25
|
export declare function raw(strings: TemplateStringsArray, ...values: readonly unknown[]): QueryRaw;
|
|
27
26
|
export declare function raw(value: QueryRawFn, alias?: string): QueryRaw;
|
|
28
|
-
/**
|
|
29
|
-
* @deprecated Emits its argument verbatim, so it cannot bind a value. Use the tagged template:
|
|
30
|
-
* `raw('"a" > 1')` becomes `` raw`"a" > 1` ``, and `raw('LOG10(x)', 'score')` becomes
|
|
31
|
-
* `` raw`LOG10(x)`.as('score') ``. `npx uql-codemod` rewrites both.
|
|
32
|
-
*/
|
|
33
|
-
export declare function raw(value: Scalar, alias?: string): QueryRaw;
|
|
34
27
|
/**
|
|
35
28
|
* A column of the entity being queried, alias-qualified and escaped for the dialect. This is what a
|
|
36
29
|
* template cannot know on its own: the alias is decided while the statement is built, not where the
|
package/dist/util/sql.util.js
CHANGED
|
@@ -200,8 +200,8 @@ export function buildUpdateResult(payload) {
|
|
|
200
200
|
// UPDATE` convention makes `changes` a per-row weighted sum (1=insert, 2=update, 0=no-op), so a
|
|
201
201
|
// batch mixing an insert and an update would fabricate ids for rows that were never touched. This
|
|
202
202
|
// function has no way to tell the two call sites apart (`internalRun` reports the same header
|
|
203
|
-
// shape either way), so `AbstractSqlQuerier
|
|
204
|
-
//
|
|
203
|
+
// shape either way), so `AbstractSqlQuerier`'s `runUpsert` discards them for a multi-row `firstId`
|
|
204
|
+
// upsert and reads the ids back by the conflict columns instead.
|
|
205
205
|
let ids = [];
|
|
206
206
|
if (rows?.length) {
|
|
207
207
|
ids = rows.map((r) => r['id']);
|
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.54.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
"express": "^5.2.1",
|
|
152
152
|
"mariadb": "^3.5.4",
|
|
153
153
|
"mongodb": "^7.6.0",
|
|
154
|
-
"mssql": "^
|
|
154
|
+
"mssql": "^12",
|
|
155
155
|
"mysql2": "^3.24.4",
|
|
156
156
|
"pg": "^8.23.0",
|
|
157
157
|
"pg-query-stream": "^4.17.0",
|