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.
Files changed (64) hide show
  1. package/dist/browser/uql-browser.min.js +2 -2
  2. package/dist/browser/uql-browser.min.js.map +5 -5
  3. package/dist/cockroachdb/cockroachDialect.d.ts +2 -5
  4. package/dist/cockroachdb/cockroachDialect.js +2 -5
  5. package/dist/dialect/abstractDialect.d.ts +5 -5
  6. package/dist/dialect/abstractDialect.js +7 -6
  7. package/dist/dialect/abstractSqlDialect.d.ts +8 -3
  8. package/dist/dialect/abstractSqlDialect.js +14 -11
  9. package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -4
  10. package/dist/dialect/mysqlLikeSqlDialect.js +0 -6
  11. package/dist/dialect/pgLikeSqlDialect.js +0 -2
  12. package/dist/dialect/vectorSqlDialect.js +2 -1
  13. package/dist/entity/decorator/members.d.ts +3 -10
  14. package/dist/entity/metadata/definition.js +0 -4
  15. package/dist/http/handler.js +3 -12
  16. package/dist/http/query.js +4 -1
  17. package/dist/migrate/builder/migrationBuilder.js +0 -4
  18. package/dist/migrate/ddl/index.d.ts +8 -0
  19. package/dist/migrate/ddl/index.js +11 -0
  20. package/dist/migrate/ddl/mssqlTableDdl.d.ts +23 -0
  21. package/dist/migrate/ddl/mssqlTableDdl.js +61 -0
  22. package/dist/migrate/ddl/tableDdl.d.ts +34 -0
  23. package/dist/migrate/ddl/tableDdl.js +71 -0
  24. package/dist/migrate/introspection/mssqlIntrospector.d.ts +6 -9
  25. package/dist/migrate/introspection/mssqlIntrospector.js +40 -33
  26. package/dist/migrate/migrator.d.ts +3 -7
  27. package/dist/migrate/migrator.js +3 -7
  28. package/dist/migrate/schemaGenerator.d.ts +8 -23
  29. package/dist/migrate/schemaGenerator.js +29 -87
  30. package/dist/migrate/storage/databaseStorage.d.ts +2 -2
  31. package/dist/migrate/storage/databaseStorage.js +2 -2
  32. package/dist/mongo/mongoDialect.js +9 -14
  33. package/dist/mongo/mongodbQuerier.d.ts +3 -5
  34. package/dist/mongo/mongodbQuerier.js +18 -19
  35. package/dist/mssql/mssqlDialect.d.ts +14 -1
  36. package/dist/mssql/mssqlDialect.js +22 -7
  37. package/dist/querier/abstractQuerier.d.ts +17 -22
  38. package/dist/querier/abstractQuerier.js +92 -67
  39. package/dist/querier/abstractSqlQuerier.d.ts +9 -9
  40. package/dist/querier/abstractSqlQuerier.js +71 -87
  41. package/dist/schema/canonicalType.js +2 -2
  42. package/dist/schema/schemaASTBuilder.js +7 -7
  43. package/dist/sqlite/sqliteDialect.js +0 -2
  44. package/dist/type/dialect.d.ts +0 -8
  45. package/dist/type/entity.d.ts +6 -11
  46. package/dist/type/migration.d.ts +0 -3
  47. package/dist/type/query.d.ts +13 -13
  48. package/dist/type/query.js +0 -6
  49. package/dist/type/queryWhere.d.ts +7 -19
  50. package/dist/type/universalQuerier.d.ts +3 -3
  51. package/dist/type/vector.d.ts +3 -1
  52. package/dist/util/dialect.util.d.ts +12 -8
  53. package/dist/util/dialect.util.js +17 -29
  54. package/dist/util/field.util.d.ts +4 -16
  55. package/dist/util/field.util.js +6 -19
  56. package/dist/util/fieldOption.util.d.ts +1 -4
  57. package/dist/util/fieldOption.util.js +0 -2
  58. package/dist/util/logger.d.ts +3 -3
  59. package/dist/util/logger.js +3 -0
  60. package/dist/util/object.util.d.ts +2 -0
  61. package/dist/util/object.util.js +4 -0
  62. package/dist/util/raw.d.ts +3 -10
  63. package/dist/util/sql.util.js +2 -2
  64. 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
- * Normalizes any `$where` shape (id, id[], raw, or map) to a `QueryWhereMap`. Read-only: for a map
247
- * input it returns that same object by reference (no copy), so callers must not mutate the result -
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 buildQueryWhereAsMap(meta, filter = {}) {
251
- if (filter instanceof QueryRaw) {
252
- return { $and: [filter] };
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
- if (isScalarId(filter)) {
262
- // A scalar can only name one column, so on a composite it would address every row agreeing on
263
- // that one. A composite is addressed by a map, which falls through below as the `$where` it
264
- // already is - an id object and a where map are the same shape by design.
265
- return { [soleIdOf(meta, 'addressing by a bare id value')]: filter };
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 = buildQueryWhereAsMap(meta, condition);
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
- * One of the two questions `virtual` used to answer alone. Every read site asks this - the DDL skip,
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
  /**
@@ -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
- * One of the two questions `virtual` used to answer alone. Every read site asks this - the DDL skip,
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 computedExpression(field) !== undefined && field.stored !== true;
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 computedExpression(field) !== undefined;
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", "virtual", "computed", "stored", "enum", "eager", "distance"];
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',
@@ -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?;
@@ -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>;
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- import { QueryRaw, type QueryRawFn, type Scalar } from '../type/index.js';
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; the other two forms are not. `raw('SQL')` emits
23
- * its argument verbatim and a callback emits whatever it writes, so build neither from user input.
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
@@ -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.upsertMany` strips `ids`/`firstId`/`created` back down
204
- // to just `changes` for a multi-row `firstId`-dialect upsert after calling this.
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.52.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": "^11",
154
+ "mssql": "^12",
155
155
  "mysql2": "^3.24.4",
156
156
  "pg": "^8.23.0",
157
157
  "pg-query-stream": "^4.17.0",