uql-orm 0.42.1 → 0.44.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 (54) hide show
  1. package/dist/browser/querier/httpQuerier.d.ts +6 -0
  2. package/dist/browser/querier/httpQuerier.js +1 -1
  3. package/dist/browser/uql-browser.min.js +2 -2
  4. package/dist/browser/uql-browser.min.js.map +5 -5
  5. package/dist/dialect/abstractDialect.js +5 -6
  6. package/dist/dialect/abstractSqlDialect.d.ts +10 -12
  7. package/dist/dialect/abstractSqlDialect.js +34 -34
  8. package/dist/dialect/jsonSql.d.ts +3 -2
  9. package/dist/dialect/jsonSql.js +7 -5
  10. package/dist/dialect/vectorCast.d.ts +0 -6
  11. package/dist/dialect/vectorCast.js +0 -8
  12. package/dist/entity/decorator/bag.d.ts +3 -0
  13. package/dist/entity/decorator/members.d.ts +5 -2
  14. package/dist/entity/index.d.ts +1 -1
  15. package/dist/entity/index.js +1 -1
  16. package/dist/entity/metadata/definition.d.ts +15 -13
  17. package/dist/entity/metadata/definition.js +73 -25
  18. package/dist/http/handler.d.ts +8 -0
  19. package/dist/http/handler.js +5 -5
  20. package/dist/maria/mariaDialect.js +2 -2
  21. package/dist/migrate/cli.d.ts +5 -0
  22. package/dist/migrate/cli.js +33 -16
  23. package/dist/migrate/codegen/entityTypes.d.ts +7 -0
  24. package/dist/migrate/codegen/entityTypes.js +69 -0
  25. package/dist/migrate/codegen/index.d.ts +1 -0
  26. package/dist/migrate/codegen/index.js +1 -0
  27. package/dist/migrate/drift/driftDetector.js +5 -1
  28. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
  29. package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
  30. package/dist/migrate/index.d.ts +1 -1
  31. package/dist/migrate/migrator.d.ts +34 -20
  32. package/dist/migrate/migrator.js +77 -34
  33. package/dist/migrate/schemaGenerator.d.ts +1 -5
  34. package/dist/migrate/schemaGenerator.js +11 -15
  35. package/dist/schema/canonicalType.d.ts +19 -4
  36. package/dist/schema/canonicalType.js +114 -164
  37. package/dist/schema/schemaASTBuilder.d.ts +23 -2
  38. package/dist/schema/schemaASTBuilder.js +2 -2
  39. package/dist/schema/schemaASTDiffer.js +6 -2
  40. package/dist/type/entity.d.ts +36 -6
  41. package/dist/type/migration.d.ts +14 -1
  42. package/dist/type/query.d.ts +2 -6
  43. package/dist/type/queryWhere.d.ts +13 -3
  44. package/dist/util/field.util.d.ts +19 -8
  45. package/dist/util/field.util.js +47 -51
  46. package/dist/util/fieldOption.util.d.ts +79 -0
  47. package/dist/util/fieldOption.util.js +84 -0
  48. package/dist/util/index.d.ts +1 -0
  49. package/dist/util/index.js +1 -0
  50. package/dist/util/object.util.d.ts +3 -3
  51. package/dist/util/object.util.js +3 -3
  52. package/dist/util/sql.util.d.ts +4 -0
  53. package/dist/util/sql.util.js +4 -0
  54. package/package.json +3 -3
@@ -1,56 +1,53 @@
1
- const NUMERIC_COLUMN_TYPES = {
2
- int: true,
3
- integer: true,
4
- tinyint: true,
5
- smallint: true,
6
- bigint: true,
7
- float: true,
8
- float4: true,
9
- float8: true,
10
- double: true,
11
- 'double precision': true,
12
- decimal: true,
13
- numeric: true,
14
- real: true,
15
- serial: true,
16
- smallserial: true,
17
- bigserial: true,
18
- };
19
- const JSON_COLUMN_TYPES = {
20
- json: true,
21
- jsonb: true,
22
- };
23
- /**
24
- * Checks if a field type is numeric (Number, BigInt, or explicit numeric logical types)
25
- */
26
- export function isNumericType(type) {
27
- if (type === Number || type === BigInt)
28
- return true;
29
- if (typeof type === 'string') {
30
- return type.toLowerCase() in NUMERIC_COLUMN_TYPES;
31
- }
32
- return false;
33
- }
1
+ import { getKeys } from './object.util.js';
34
2
  /**
35
- * Checks if a field type is boolean (Boolean, or an explicit boolean logical type)
3
+ * The runtime half of the column-type unions in `type/entity.ts`, which TypeScript erases. Each list
4
+ * is checked against its own union, so a type cannot be filed under the wrong family, and
5
+ * {@link UnplacedColumnType} refuses to compile if a new one is filed under none. Nothing here
6
+ * restates the unions: the compile-time side of the same question reads them directly.
36
7
  */
37
- export function isBooleanType(type) {
38
- if (type === Boolean)
39
- return true;
40
- if (typeof type === 'string') {
41
- const lowered = type.toLowerCase();
42
- return lowered === 'bool' || lowered === 'boolean';
8
+ export const COLUMN_TYPES_BY_FAMILY = {
9
+ numeric: [
10
+ 'int',
11
+ 'integer',
12
+ 'tinyint',
13
+ 'smallint',
14
+ 'bigint',
15
+ 'float',
16
+ 'float4',
17
+ 'float8',
18
+ 'double',
19
+ 'double precision',
20
+ 'decimal',
21
+ 'numeric',
22
+ 'real',
23
+ 'serial',
24
+ 'smallserial',
25
+ 'bigserial',
26
+ ],
27
+ string: ['char', 'varchar', 'text', 'uuid'],
28
+ date: ['date', 'time', 'datetime', 'timestamp', 'timestamptz'],
29
+ json: ['json', 'jsonb'],
30
+ blob: ['blob', 'bytea'],
31
+ boolean: ['bool', 'boolean'],
32
+ vector: ['vector', 'halfvec', 'sparsevec'],
33
+ };
34
+ // Constructors and type strings in one map: a logical type is either, and every caller asks the same
35
+ // question of both.
36
+ const FAMILY_OF = new Map([
37
+ [String, 'string'],
38
+ [Number, 'numeric'],
39
+ [BigInt, 'numeric'],
40
+ [Boolean, 'boolean'],
41
+ [Date, 'date'],
42
+ ]);
43
+ for (const family of getKeys(COLUMN_TYPES_BY_FAMILY)) {
44
+ for (const columnType of COLUMN_TYPES_BY_FAMILY[family]) {
45
+ FAMILY_OF.set(columnType, family);
43
46
  }
44
- return false;
45
47
  }
46
- /**
47
- * Checks if a field type is JSON
48
- */
49
- export function isJsonType(type) {
50
- if (typeof type === 'string') {
51
- return type.toLowerCase() in JSON_COLUMN_TYPES;
52
- }
53
- return false;
48
+ /** The family of a logical field type, or `undefined` where it names none. */
49
+ export function columnFamily(type) {
50
+ return FAMILY_OF.get(typeof type === 'string' ? type.toLowerCase() : type);
54
51
  }
55
52
  /**
56
53
  * Whether the field is the entity's *whole* primary key - the only kind a serial can stand in for,
@@ -75,6 +72,5 @@ export function isAutoIncrement(field, isPrimaryKey) {
75
72
  const colType = field.columnType?.toLowerCase();
76
73
  if (colType === 'serial' || colType === 'smallserial' || colType === 'bigserial')
77
74
  return true;
78
- const isNumeric = isNumericType(field.type);
79
- return isPrimaryKey && isNumeric && !field.onInsert && !field.columnType;
75
+ return isPrimaryKey && columnFamily(field.type) === 'numeric' && !field.onInsert && !field.columnType;
80
76
  }
@@ -0,0 +1,79 @@
1
+ import type { BlobColumnType, BooleanColumnType, DateColumnType, FieldOptions, JsonColumnType, NumericColumnType, QueryRaw, StringColumnType, VectorColumnType } from '../type/index.js';
2
+ import { type ColumnFamily } from './field.util.js';
3
+ /**
4
+ * The column family each field option means anything on, or `'*'` where it applies to every column.
5
+ * Exhaustive over {@link FieldOptions}, so a new option cannot be added without placing it - the
6
+ * discipline `INDEX_FEATURE_LABELS` uses for index features.
7
+ */
8
+ declare const FIELD_OPTION_FAMILY: {
9
+ readonly name: '*';
10
+ readonly isId: '*';
11
+ readonly type: '*';
12
+ readonly dimensions: 'vector';
13
+ readonly distance: 'vector';
14
+ readonly references: '*';
15
+ readonly onDelete: '*';
16
+ readonly enum: '*';
17
+ readonly virtual: '*';
18
+ readonly updatable: '*';
19
+ readonly eager: '*';
20
+ readonly onInsert: '*';
21
+ readonly onUpdate: '*';
22
+ readonly softDelete: '*';
23
+ readonly columnType: '*';
24
+ readonly length: 'string';
25
+ readonly precision: 'numeric';
26
+ readonly scale: 'numeric';
27
+ readonly nullable: '*';
28
+ readonly unique: '*';
29
+ readonly defaultValue: '*';
30
+ readonly autoIncrement: 'numeric';
31
+ readonly index: '*';
32
+ readonly comment: '*';
33
+ };
34
+ /**
35
+ * The only options a `virtual` field reaches: it is skipped in DDL and dropped from every insert and
36
+ * update, so the whole persistence half of the options above is dead on one. Stated as what survives
37
+ * rather than on each option that dies, because it is one fact rather than nineteen - and because an
38
+ * option added without a thought then lands on the safe side of it.
39
+ */
40
+ declare const VIRTUAL_READS: readonly ["type", "virtual", "enum", "eager", "distance"];
41
+ type VirtualRead = (typeof VIRTUAL_READS)[number];
42
+ /**
43
+ * The first option `opts` cannot use, phrased as the tail of `'Entity.field' ...`, or `undefined`
44
+ * where every option applies. The runtime half of the decorators' check, so the imperative API and
45
+ * plain JavaScript reach the same answer.
46
+ */
47
+ export declare function fieldOptionConflict(opts: FieldOptions): string | undefined;
48
+ /** The family the options put the column in; every family where they name no type to put it in. */
49
+ type FamilyOf<O> = O extends {
50
+ readonly columnType: infer C;
51
+ } ? FamilyOfType<C> : O extends {
52
+ readonly type: infer T;
53
+ } ? FamilyOfType<T> : ColumnFamily;
54
+ /** Read off the column-type unions themselves, which is what `COLUMN_TYPES_BY_FAMILY` is checked against. */
55
+ type FamilyOfType<T> = T extends NumericColumnType | NumberConstructor | BigIntConstructor ? 'numeric' : T extends StringColumnType | StringConstructor ? 'string' : T extends VectorColumnType ? 'vector' : T extends JsonColumnType ? 'json' : T extends DateColumnType | DateConstructor ? 'date' : T extends BooleanColumnType | BooleanConstructor ? 'boolean' : T extends BlobColumnType ? 'blob' : ColumnFamily;
56
+ /** What the field's own values leave unread, matching {@link deadOn} line for line. */
57
+ type DeadOptions<O> = (O extends {
58
+ readonly virtual: QueryRaw;
59
+ } ? Exclude<keyof FieldOptions, VirtualRead> : never) | (O extends {
60
+ readonly isId: true;
61
+ readonly nullable: true;
62
+ } ? 'nullable' : never) | (O extends {
63
+ readonly updatable: false;
64
+ } ? 'onUpdate' : never);
65
+ type Given<O> = Extract<keyof O, keyof FieldOptions>;
66
+ type Offending<O> = {
67
+ [K in Given<O>]: (typeof FIELD_OPTION_FAMILY)[K] extends FamilyOf<O> | '*' ? K extends DeadOptions<O> ? K : never : K;
68
+ }[Given<O>];
69
+ /**
70
+ * Maps every option `O` states but cannot use to `never`, the way `RejectUnknown` maps a typo'd one,
71
+ * so an option that would be silently ignored reads as the same compile error. Resolves to `unknown`
72
+ * - an inert intersection member - when there are none.
73
+ *
74
+ * `@Id` adds `{ nullable?: false }` of its own rather than passing the `isId` it stamps on, which
75
+ * would have to reach `O` as an intersection - and a non-naked `O` in its own constraint stops it
76
+ * inferring from the options at all.
77
+ */
78
+ export type RejectIncompatible<O> = [Offending<O>] extends [never] ? unknown : Record<Offending<O> & string, never>;
79
+ export {};
@@ -0,0 +1,84 @@
1
+ import { columnFamily } from './field.util.js';
2
+ import { getKeys } from './object.util.js';
3
+ /**
4
+ * The column family each field option means anything on, or `'*'` where it applies to every column.
5
+ * Exhaustive over {@link FieldOptions}, so a new option cannot be added without placing it - the
6
+ * discipline `INDEX_FEATURE_LABELS` uses for index features.
7
+ */
8
+ const FIELD_OPTION_FAMILY = {
9
+ name: '*',
10
+ isId: '*',
11
+ type: '*',
12
+ dimensions: 'vector',
13
+ distance: 'vector',
14
+ references: '*',
15
+ onDelete: '*',
16
+ enum: '*',
17
+ virtual: '*',
18
+ updatable: '*',
19
+ eager: '*',
20
+ onInsert: '*',
21
+ onUpdate: '*',
22
+ softDelete: '*',
23
+ columnType: '*',
24
+ length: 'string',
25
+ precision: 'numeric',
26
+ scale: 'numeric',
27
+ nullable: '*',
28
+ unique: '*',
29
+ defaultValue: '*',
30
+ autoIncrement: 'numeric',
31
+ index: '*',
32
+ comment: '*',
33
+ };
34
+ /**
35
+ * The only options a `virtual` field reaches: it is skipped in DDL and dropped from every insert and
36
+ * update, so the whole persistence half of the options above is dead on one. Stated as what survives
37
+ * rather than on each option that dies, because it is one fact rather than nineteen - and because an
38
+ * option added without a thought then lands on the safe side of it.
39
+ */
40
+ const VIRTUAL_READS = [
41
+ 'type',
42
+ 'virtual',
43
+ 'enum',
44
+ 'eager',
45
+ 'distance',
46
+ ];
47
+ /**
48
+ * Whatever leaves `key` unread, named for the message, or `undefined` where the field reads it. Only
49
+ * `nullable: true` contradicts a key: `nullable: false` says what the key already is, and rejecting
50
+ * an accurate statement teaches an author to distrust the check.
51
+ */
52
+ function deadOn(opts, key) {
53
+ if (opts.virtual !== undefined && !VIRTUAL_READS.some((read) => read === key))
54
+ return 'a virtual field';
55
+ if (opts.isId === true && key === 'nullable' && opts.nullable === true)
56
+ return 'a primary key';
57
+ if (opts.updatable === false && key === 'onUpdate')
58
+ return "a field declared 'updatable: false'";
59
+ return undefined;
60
+ }
61
+ /**
62
+ * The first option `opts` cannot use, phrased as the tail of `'Entity.field' ...`, or `undefined`
63
+ * where every option applies. The runtime half of the decorators' check, so the imperative API and
64
+ * plain JavaScript reach the same answer.
65
+ */
66
+ export function fieldOptionConflict(opts) {
67
+ const family = columnFamily(opts.columnType ?? opts.type);
68
+ // Walked in table order, not in the order the field happened to be written, so a field with two
69
+ // conflicts always reports the same one. An option no rule knows is a typo, which `RejectUnknown`
70
+ // reports where it can still be spelled right.
71
+ for (const key of getKeys(FIELD_OPTION_FAMILY)) {
72
+ const applies = FIELD_OPTION_FAMILY[key];
73
+ if (opts[key] === undefined)
74
+ continue;
75
+ if (family && applies !== '*' && applies !== family) {
76
+ return `cannot use '${key}': it applies to a ${applies} column, not to a ${family} one`;
77
+ }
78
+ const dead = deadOn(opts, key);
79
+ if (dead) {
80
+ return `cannot use '${key}': it is ignored on ${dead}`;
81
+ }
82
+ }
83
+ return undefined;
84
+ }
@@ -1,5 +1,6 @@
1
1
  export * from './dialect.util.js';
2
2
  export * from './field.util.js';
3
+ export * from './fieldOption.util.js';
3
4
  export * from './filters.util.js';
4
5
  export * from './hook.util.js';
5
6
  export * from './ddlExpression.util.js';
@@ -1,5 +1,6 @@
1
1
  export * from './dialect.util.js';
2
2
  export * from './field.util.js';
3
+ export * from './fieldOption.util.js';
3
4
  export * from './filters.util.js';
4
5
  export * from './hook.util.js';
5
6
  export * from './ddlExpression.util.js';
@@ -21,9 +21,9 @@ export declare function isOperatorObject(value: unknown): value is Record<string
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
23
  /**
24
- * The entity's own name for a message to carry, declared or its class's. `defineEntity` always sets
25
- * one, so the fallback is for a meta a decorator is still building - which is why the sites spelling
26
- * this out reached for three different fallbacks, `?? ''` among them, and named nothing at all.
24
+ * The entity's own name, declared or its class's. `meta.name` holds only what the author wrote, so
25
+ * the fallback is what an entity that named no table is called - which is why the sites spelling this
26
+ * out reached for three different fallbacks, `?? ''` among them, and named nothing at all.
27
27
  */
28
28
  export declare function entityName<E>(meta: EntityMeta<E>): string;
29
29
  export declare function getFieldKeys<E>(fields: {
@@ -53,9 +53,9 @@ export function getKeys(obj) {
53
53
  return obj ? Object.keys(obj) : [];
54
54
  }
55
55
  /**
56
- * The entity's own name for a message to carry, declared or its class's. `defineEntity` always sets
57
- * one, so the fallback is for a meta a decorator is still building - which is why the sites spelling
58
- * this out reached for three different fallbacks, `?? ''` among them, and named nothing at all.
56
+ * The entity's own name, declared or its class's. `meta.name` holds only what the author wrote, so
57
+ * the fallback is what an entity that named no table is called - which is why the sites spelling this
58
+ * out reached for three different fallbacks, `?? ''` among them, and named nothing at all.
59
59
  */
60
60
  export function entityName(meta) {
61
61
  return meta.name ?? meta.entity.name;
@@ -25,6 +25,10 @@ export declare function qualifyName(name: string, schema?: string): string;
25
25
  *
26
26
  * The kind goes last, as Postgres spells its own (`users_pkey`, `users_email_idx`), so a table's
27
27
  * constraints sort together under the table they belong to.
28
+ *
29
+ * Not overridable, deliberately: a `NamingStrategy` hook would have to reach the eight call sites
30
+ * these have, an introspector and two builders among them, to replace a name any declaration can
31
+ * already set outright with `name:`. Worth revisiting only for a case that option cannot express.
28
32
  */
29
33
  export declare function derivedConstraintName(table: string, parts: readonly (string | number)[], kind: ConstraintKind): string;
30
34
  /** The kinds of derived name, which is also what `indexNameStem` strips to compare them. */
@@ -74,6 +74,10 @@ const NAME_HASH_LENGTH = 6;
74
74
  *
75
75
  * The kind goes last, as Postgres spells its own (`users_pkey`, `users_email_idx`), so a table's
76
76
  * constraints sort together under the table they belong to.
77
+ *
78
+ * Not overridable, deliberately: a `NamingStrategy` hook would have to reach the eight call sites
79
+ * these have, an introspector and two builders among them, to replace a name any declaration can
80
+ * already set outright with `name:`. Worth revisiting only for a case that option cannot express.
77
81
  */
78
82
  export function derivedConstraintName(table, parts, kind) {
79
83
  const body = parts.length ? `${table}${TABLE_SEPARATOR}${parts.join('_')}` : table;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "uql-orm",
3
3
  "homepage": "https://uql-orm.dev",
4
- "description": "JSON-native ORM for Node.js, Bun and Deno. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
4
+ "description": "JSON-native TypeScript ORM for Node.js, Bun and Deno. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
5
5
  "license": "MIT",
6
- "version": "0.42.1",
6
+ "version": "0.44.0",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"
@@ -145,7 +145,7 @@
145
145
  "express": "^5.2.1",
146
146
  "mariadb": "^3.5.4",
147
147
  "mongodb": "^7.6.0",
148
- "mysql2": "^3.24.3",
148
+ "mysql2": "^3.24.4",
149
149
  "pg": "^8.23.0",
150
150
  "pg-query-stream": "^4.17.0",
151
151
  "rxjs": "^7.8.2",