uql-orm 0.42.1 → 0.43.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 +2 -2
- package/dist/dialect/abstractSqlDialect.d.ts +10 -12
- package/dist/dialect/abstractSqlDialect.js +34 -34
- package/dist/dialect/jsonSql.d.ts +3 -2
- package/dist/dialect/jsonSql.js +7 -5
- package/dist/dialect/vectorCast.d.ts +0 -6
- package/dist/dialect/vectorCast.js +0 -8
- package/dist/entity/decorator/members.d.ts +5 -2
- package/dist/entity/metadata/definition.js +29 -12
- package/dist/maria/mariaDialect.js +2 -2
- package/dist/migrate/drift/driftDetector.js +5 -1
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
- package/dist/migrate/schemaGenerator.d.ts +1 -5
- package/dist/migrate/schemaGenerator.js +11 -15
- package/dist/schema/canonicalType.d.ts +19 -4
- package/dist/schema/canonicalType.js +114 -164
- package/dist/schema/schemaASTBuilder.js +1 -1
- package/dist/schema/schemaASTDiffer.js +6 -2
- package/dist/type/entity.d.ts +23 -6
- package/dist/type/migration.d.ts +1 -1
- package/dist/type/query.d.ts +2 -6
- package/dist/util/field.util.d.ts +19 -8
- package/dist/util/field.util.js +47 -51
- package/dist/util/fieldOption.util.d.ts +79 -0
- package/dist/util/fieldOption.util.js +84 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/sql.util.d.ts +4 -0
- package/dist/util/sql.util.js +4 -0
- package/package.json +2 -2
package/dist/type/entity.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export type RelationKey<E> = Exclude<Key<E>, FieldKey<E> | MethodKey<E>>;
|
|
|
49
49
|
* inferring junk like `T = string`), while the marker key only exists on branded types.
|
|
50
50
|
*/
|
|
51
51
|
type IsJson<T> = '__json' extends keyof T ? true : false;
|
|
52
|
+
/** Whether `T` is what a JSON column holds: the branded payload, or an array of them. */
|
|
53
|
+
type IsJsonColumn<T> = IsJson<T> extends true ? true : IsJson<NonNullable<Unpacked<T>>>;
|
|
52
54
|
/** The payload `P` of a branded `Json<P>`, or `never` for any non-JSON type. */
|
|
53
55
|
type UnwrapJson<T> = IsJson<T> extends true ? (T extends Json<infer P> ? P : never) : never;
|
|
54
56
|
/**
|
|
@@ -276,7 +278,7 @@ export type FieldType = StringConstructor | NumberConstructor | BooleanConstruct
|
|
|
276
278
|
* Both `Json<T>` and `Json<T>[]` have to be recognised, and the array check has to precede the scalar
|
|
277
279
|
* arms so a `number[]` vector is not read as a `number`.
|
|
278
280
|
*/
|
|
279
|
-
export type TypeFor<V, T = NonNullable<V>> =
|
|
281
|
+
export type TypeFor<V, T = NonNullable<V>> = IsJsonColumn<T> extends true ? JsonColumnType : T extends readonly number[] ? VectorColumnType : T extends string ? StringConstructor | StringColumnType : T extends number ? NumberConstructor | NumericColumnType : T extends bigint ? BigIntConstructor | NumericColumnType : T extends boolean ? BooleanConstructor | BooleanColumnType : T extends Date ? DateConstructor | DateColumnType : T extends Uint8Array ? BlobColumnType : FieldType;
|
|
280
282
|
/**
|
|
281
283
|
* A field as the registry holds it: what the user authored, plus what registration worked out.
|
|
282
284
|
*
|
|
@@ -384,11 +386,9 @@ export type FieldOptions<V = TsTypeOf<FieldType>> = {
|
|
|
384
386
|
*/
|
|
385
387
|
readonly unique?: boolean;
|
|
386
388
|
/**
|
|
387
|
-
* The column's DDL default, rendered into `CREATE TABLE` by `formatDefaultValue
|
|
388
|
-
* the generators above. A JSONB column defaults with the SQL literal it stores, `defaultValue: '{}'`,
|
|
389
|
-
* which is a string whatever the field's TypeScript type is.
|
|
389
|
+
* The column's DDL default, rendered into `CREATE TABLE` by `formatDefaultValue`.
|
|
390
390
|
*/
|
|
391
|
-
readonly defaultValue?:
|
|
391
|
+
readonly defaultValue?: DdlDefault<V>;
|
|
392
392
|
/**
|
|
393
393
|
* Whether the column is auto-incrementing (for integer IDs).
|
|
394
394
|
*/
|
|
@@ -403,6 +403,17 @@ export type FieldOptions<V = TsTypeOf<FieldType>> = {
|
|
|
403
403
|
readonly comment?: string;
|
|
404
404
|
};
|
|
405
405
|
export type OnFieldCallback<V = TsTypeOf<FieldType>> = V | QueryRaw | (() => V | QueryRaw);
|
|
406
|
+
/**
|
|
407
|
+
* What a column may default to: the value it holds, except on a JSON column, which defaults with the
|
|
408
|
+
* SQL literal it stores (`defaultValue: '{}'`) whatever the property's TypeScript type is. Opening
|
|
409
|
+
* that exception to every field is what let `@Field({ type: Number, defaultValue: 'hello' })` compile.
|
|
410
|
+
*
|
|
411
|
+
* The erased shape - `FieldOptions` with no field in mind - admits every column's default at once, or
|
|
412
|
+
* no `FieldOptions<V>` would be assignable to the one the registry and the dialects read.
|
|
413
|
+
*/
|
|
414
|
+
type DdlDefault<V, T = NonNullable<V>> = IsJsonColumn<T> extends true ? JsonDdlDefault : [TsTypeOf<FieldType>] extends [T] ? JsonDdlDefault | T : T;
|
|
415
|
+
/** What a JSON column, and the field-less `FieldOptions`, may default to. */
|
|
416
|
+
type JsonDdlDefault = Scalar | Record<string, unknown>;
|
|
406
417
|
/**
|
|
407
418
|
* The TypeScript types a field may be declared as, given the `type` it registers: the inverse of
|
|
408
419
|
* {@link TypeFor}.
|
|
@@ -760,7 +771,13 @@ export type EntityMeta<E> = {
|
|
|
760
771
|
checks?: CheckSchema[];
|
|
761
772
|
/** Lifecycle hooks registered via @BeforeInsert, @AfterUpdate, etc. */
|
|
762
773
|
hooks?: Partial<Record<HookEvent, HookRegistration[]>>;
|
|
763
|
-
|
|
774
|
+
/**
|
|
775
|
+
* Bumped by every `define*` call, so anything derived from this metadata can tell that it changed.
|
|
776
|
+
* A content type registered at runtime keeps adding to an entity that has already been read.
|
|
777
|
+
*/
|
|
778
|
+
revision: number;
|
|
779
|
+
/** The revision `getMeta` last finalized, which is what makes finalizing idempotent and re-entrant. */
|
|
780
|
+
processedAt?: number;
|
|
764
781
|
};
|
|
765
782
|
/**
|
|
766
783
|
* Configurable options for an entity (`@Entity()` / `defineEntity`).
|
package/dist/type/migration.d.ts
CHANGED
|
@@ -262,7 +262,7 @@ export interface SchemaGenerator {
|
|
|
262
262
|
/**
|
|
263
263
|
* Get the SQL type for a field based on its options
|
|
264
264
|
*/
|
|
265
|
-
getSqlType(fieldOptions: FieldOptions
|
|
265
|
+
getSqlType(fieldOptions: FieldOptions): string;
|
|
266
266
|
/**
|
|
267
267
|
* Compare an entity with a database table node and return the differences.
|
|
268
268
|
*/
|
package/dist/type/query.d.ts
CHANGED
|
@@ -394,12 +394,8 @@ type CountedRelations<C extends PropertyKey> = [C] extends [never] ? unknown : {
|
|
|
394
394
|
};
|
|
395
395
|
};
|
|
396
396
|
/** @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] ?
|
|
398
|
-
|
|
399
|
-
} : // A populated to-many is always a list, empty where the parent has no children, so it maps
|
|
400
|
-
{
|
|
401
|
-
[K in keyof E as K extends Exclude<ProjectedKeys<E, S, V, X, P, C>, PopulatedToMany<E, P>> ? K : never]: E[K];
|
|
402
|
-
} & {
|
|
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, C> & keyof E> : // A populated to-many is always a list, empty where the parent has no children, so it maps
|
|
398
|
+
Pick<E, Exclude<ProjectedKeys<E, S, V, X, P, C>, PopulatedToMany<E, P>> & keyof E> & {
|
|
403
399
|
[K in PopulatedToMany<E, P>]-?: NonNullable<E[K]>;
|
|
404
400
|
} : E;
|
|
405
401
|
/** The to-many relations a query populated, which come back as lists rather than as optional ones. */
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import type { EntityMeta, FieldOptions } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* The kind of column a field lands on, which is what decides whether an option means anything on it:
|
|
4
|
+
* `length` is a string's, `precision` a number's, `dimensions` a vector's. Named in the words an
|
|
5
|
+
* error reports it in, so there is no second table of labels to keep in step.
|
|
4
6
|
*/
|
|
5
|
-
export
|
|
7
|
+
export type ColumnFamily = 'string' | 'numeric' | 'boolean' | 'date' | 'json' | 'blob' | 'vector';
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
9
|
+
* The runtime half of the column-type unions in `type/entity.ts`, which TypeScript erases. Each list
|
|
10
|
+
* is checked against its own union, so a type cannot be filed under the wrong family, and
|
|
11
|
+
* {@link UnplacedColumnType} refuses to compile if a new one is filed under none. Nothing here
|
|
12
|
+
* restates the unions: the compile-time side of the same question reads them directly.
|
|
8
13
|
*/
|
|
9
|
-
export declare
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
export declare const COLUMN_TYPES_BY_FAMILY: {
|
|
15
|
+
readonly numeric: readonly ["int", "integer", "tinyint", "smallint", "bigint", "float", "float4", "float8", "double", "double precision", "decimal", "numeric", "real", "serial", "smallserial", "bigserial"];
|
|
16
|
+
readonly string: readonly ["char", "varchar", "text", "uuid"];
|
|
17
|
+
readonly date: readonly ["date", "time", "datetime", "timestamp", "timestamptz"];
|
|
18
|
+
readonly json: readonly ["json", "jsonb"];
|
|
19
|
+
readonly blob: readonly ["blob", "bytea"];
|
|
20
|
+
readonly boolean: readonly ["bool", "boolean"];
|
|
21
|
+
readonly vector: readonly ["vector", "halfvec", "sparsevec"];
|
|
22
|
+
};
|
|
23
|
+
/** The family of a logical field type, or `undefined` where it names none. */
|
|
24
|
+
export declare function columnFamily(type: unknown): ColumnFamily | undefined;
|
|
14
25
|
/**
|
|
15
26
|
* Whether the field is the entity's *whole* primary key - the only kind a serial can stand in for,
|
|
16
27
|
* and the only one that may state `PRIMARY KEY` in its own column definition.
|
package/dist/util/field.util.js
CHANGED
|
@@ -1,56 +1,53 @@
|
|
|
1
|
-
|
|
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
|
-
*
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/dist/util/index.d.ts
CHANGED
package/dist/util/index.js
CHANGED
package/dist/util/sql.util.d.ts
CHANGED
|
@@ -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. */
|
package/dist/util/sql.util.js
CHANGED
|
@@ -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.
|
|
6
|
+
"version": "0.43.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|