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.
- package/dist/browser/querier/httpQuerier.d.ts +6 -0
- package/dist/browser/querier/httpQuerier.js +1 -1
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +5 -5
- package/dist/dialect/abstractDialect.js +5 -6
- 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/bag.d.ts +3 -0
- package/dist/entity/decorator/members.d.ts +5 -2
- package/dist/entity/index.d.ts +1 -1
- package/dist/entity/index.js +1 -1
- package/dist/entity/metadata/definition.d.ts +15 -13
- package/dist/entity/metadata/definition.js +73 -25
- package/dist/http/handler.d.ts +8 -0
- package/dist/http/handler.js +5 -5
- package/dist/maria/mariaDialect.js +2 -2
- package/dist/migrate/cli.d.ts +5 -0
- package/dist/migrate/cli.js +33 -16
- package/dist/migrate/codegen/entityTypes.d.ts +7 -0
- package/dist/migrate/codegen/entityTypes.js +69 -0
- package/dist/migrate/codegen/index.d.ts +1 -0
- package/dist/migrate/codegen/index.js +1 -0
- 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/index.d.ts +1 -1
- package/dist/migrate/migrator.d.ts +34 -20
- package/dist/migrate/migrator.js +77 -34
- 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.d.ts +23 -2
- package/dist/schema/schemaASTBuilder.js +2 -2
- package/dist/schema/schemaASTDiffer.js +6 -2
- package/dist/type/entity.d.ts +36 -6
- package/dist/type/migration.d.ts +14 -1
- package/dist/type/query.d.ts +2 -6
- package/dist/type/queryWhere.d.ts +13 -3
- 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/object.util.d.ts +3 -3
- package/dist/util/object.util.js +3 -3
- package/dist/util/sql.util.d.ts +4 -0
- package/dist/util/sql.util.js +4 -0
- package/package.json +3 -3
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - Canonical types (dialect-agnostic)
|
|
7
7
|
* - TypeScript types (for entity generation)
|
|
8
8
|
*/
|
|
9
|
+
import { columnFamily } from '../util/field.util.js';
|
|
9
10
|
/** Whether a category is one of the vector types, narrowing it to the cast pgvector names use. */
|
|
10
11
|
export function isVectorCategory(category) {
|
|
11
12
|
return category === 'vector' || category === 'halfvec' || category === 'sparsevec';
|
|
@@ -108,66 +109,57 @@ const PG_TYPE_MAP = {
|
|
|
108
109
|
halfvec: 'HALFVEC',
|
|
109
110
|
sparsevec: 'SPARSEVEC',
|
|
110
111
|
};
|
|
112
|
+
const MYSQL_SCALAR_MAP = {
|
|
113
|
+
integer: 'INT',
|
|
114
|
+
float: 'FLOAT',
|
|
115
|
+
decimal: 'DECIMAL',
|
|
116
|
+
string: 'VARCHAR',
|
|
117
|
+
boolean: 'TINYINT(1)',
|
|
118
|
+
date: 'DATE',
|
|
119
|
+
time: 'TIME',
|
|
120
|
+
timestamp: 'DATETIME',
|
|
121
|
+
json: 'JSON',
|
|
122
|
+
uuid: 'CHAR(36)',
|
|
123
|
+
blob: 'BLOB',
|
|
124
|
+
};
|
|
125
|
+
const SQLITE_SCALAR_MAP = {
|
|
126
|
+
integer: 'INTEGER',
|
|
127
|
+
float: 'REAL',
|
|
128
|
+
decimal: 'REAL',
|
|
129
|
+
string: 'TEXT',
|
|
130
|
+
boolean: 'INTEGER',
|
|
131
|
+
date: 'TEXT',
|
|
132
|
+
time: 'TEXT',
|
|
133
|
+
timestamp: 'TEXT',
|
|
134
|
+
json: 'TEXT',
|
|
135
|
+
uuid: 'TEXT',
|
|
136
|
+
blob: 'BLOB',
|
|
137
|
+
};
|
|
138
|
+
/** MongoDB uses BSON types, not SQL types. These are placeholders for compatibility. */
|
|
139
|
+
const MONGO_SCALAR_MAP = {
|
|
140
|
+
integer: 'int',
|
|
141
|
+
float: 'double',
|
|
142
|
+
decimal: 'decimal128',
|
|
143
|
+
string: 'string',
|
|
144
|
+
boolean: 'bool',
|
|
145
|
+
date: 'date',
|
|
146
|
+
time: 'string',
|
|
147
|
+
timestamp: 'timestamp',
|
|
148
|
+
json: 'object',
|
|
149
|
+
uuid: 'binData',
|
|
150
|
+
blob: 'binData',
|
|
151
|
+
};
|
|
152
|
+
/** Every engine's scalars, and the one spelling it gives all three vector widths. */
|
|
111
153
|
const CANONICAL_TO_SQL = {
|
|
112
154
|
postgres: PG_TYPE_MAP,
|
|
113
155
|
// CockroachDB's VECTOR is native, no extension needed.
|
|
114
156
|
cockroachdb: withVectorType(PG_SCALAR_MAP, 'VECTOR'),
|
|
115
|
-
mysql: withVectorType({
|
|
116
|
-
integer: 'INT',
|
|
117
|
-
float: 'FLOAT',
|
|
118
|
-
decimal: 'DECIMAL',
|
|
119
|
-
string: 'VARCHAR',
|
|
120
|
-
boolean: 'TINYINT(1)',
|
|
121
|
-
date: 'DATE',
|
|
122
|
-
time: 'TIME',
|
|
123
|
-
timestamp: 'DATETIME',
|
|
124
|
-
json: 'JSON',
|
|
125
|
-
uuid: 'CHAR(36)',
|
|
126
|
-
blob: 'BLOB',
|
|
127
|
-
},
|
|
128
157
|
// MySQL does have a `VECTOR` type (26.7), but no distance function outside HeatWave and no vector
|
|
129
158
|
// index, so JSON keeps the column queryable with the JSON operators and needs no conversion.
|
|
130
|
-
'JSON'),
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
decimal: 'REAL',
|
|
135
|
-
string: 'TEXT',
|
|
136
|
-
boolean: 'INTEGER',
|
|
137
|
-
date: 'TEXT',
|
|
138
|
-
time: 'TEXT',
|
|
139
|
-
timestamp: 'TEXT',
|
|
140
|
-
json: 'TEXT',
|
|
141
|
-
uuid: 'TEXT',
|
|
142
|
-
blob: 'BLOB',
|
|
143
|
-
}, 'TEXT'),
|
|
144
|
-
mariadb: withVectorType({
|
|
145
|
-
integer: 'INT',
|
|
146
|
-
float: 'FLOAT',
|
|
147
|
-
decimal: 'DECIMAL',
|
|
148
|
-
string: 'VARCHAR',
|
|
149
|
-
boolean: 'TINYINT(1)',
|
|
150
|
-
date: 'DATE',
|
|
151
|
-
time: 'TIME',
|
|
152
|
-
timestamp: 'DATETIME',
|
|
153
|
-
json: 'JSON',
|
|
154
|
-
uuid: 'CHAR(36)',
|
|
155
|
-
blob: 'BLOB',
|
|
156
|
-
}, 'VECTOR'),
|
|
157
|
-
// MongoDB uses BSON types, not SQL types. These are placeholders for compatibility.
|
|
158
|
-
mongodb: withVectorType({
|
|
159
|
-
integer: 'int',
|
|
160
|
-
float: 'double',
|
|
161
|
-
decimal: 'decimal128',
|
|
162
|
-
string: 'string',
|
|
163
|
-
boolean: 'bool',
|
|
164
|
-
date: 'date',
|
|
165
|
-
time: 'string',
|
|
166
|
-
timestamp: 'timestamp',
|
|
167
|
-
json: 'object',
|
|
168
|
-
uuid: 'binData',
|
|
169
|
-
blob: 'binData',
|
|
170
|
-
}, 'array'),
|
|
159
|
+
mysql: withVectorType(MYSQL_SCALAR_MAP, 'JSON'),
|
|
160
|
+
mariadb: withVectorType(MYSQL_SCALAR_MAP, 'VECTOR'),
|
|
161
|
+
sqlite: withVectorType(SQLITE_SCALAR_MAP, 'TEXT'),
|
|
162
|
+
mongodb: withVectorType(MONGO_SCALAR_MAP, 'array'),
|
|
171
163
|
};
|
|
172
164
|
/**
|
|
173
165
|
* Size variant modifiers for SQL types.
|
|
@@ -239,7 +231,7 @@ const CANONICAL_TO_TS = {
|
|
|
239
231
|
timestamp: 'Date',
|
|
240
232
|
json: 'unknown',
|
|
241
233
|
uuid: 'string',
|
|
242
|
-
blob: '
|
|
234
|
+
blob: 'Uint8Array',
|
|
243
235
|
vector: 'number[]',
|
|
244
236
|
halfvec: 'number[]',
|
|
245
237
|
sparsevec: 'number[]',
|
|
@@ -387,136 +379,94 @@ function formatDecimalSqlType(type, dialect, baseType) {
|
|
|
387
379
|
export function canonicalToTypeScript(type) {
|
|
388
380
|
return CANONICAL_TO_TS[type.category];
|
|
389
381
|
}
|
|
382
|
+
/**
|
|
383
|
+
* A type as `dialect` would actually store it: rendered to that engine's SQL and read back.
|
|
384
|
+
*
|
|
385
|
+
* Several canonical types share one storage type per engine - a `boolean` is `TINYINT(1)` on MySQL and
|
|
386
|
+
* `INTEGER` on SQLite - and only the engine settles an unstated bound, since `VARCHAR` is 255 on MySQL
|
|
387
|
+
* and `TEXT` on Postgres. Both paths that diff a schema compare through this, so a migration and a
|
|
388
|
+
* drift report cannot disagree about what changed.
|
|
389
|
+
*/
|
|
390
|
+
export function engineType(dialect) {
|
|
391
|
+
return (type) => sqlToCanonical(canonicalToSql(type, dialect));
|
|
392
|
+
}
|
|
390
393
|
/**
|
|
391
394
|
* Convert UQL FieldOptions to a canonical type.
|
|
392
395
|
*/
|
|
393
|
-
export function fieldOptionsToCanonical(options
|
|
394
|
-
//
|
|
396
|
+
export function fieldOptionsToCanonical(options) {
|
|
397
|
+
// An explicit column type is read exactly as an introspected one is: the SQL type, plus whatever
|
|
398
|
+
// bounds are stated beside it.
|
|
395
399
|
if (options.columnType) {
|
|
396
|
-
|
|
397
|
-
return {
|
|
398
|
-
...base,
|
|
399
|
-
length: options.length ?? base.length,
|
|
400
|
-
precision: options.precision ?? base.precision,
|
|
401
|
-
scale: options.scale ?? base.scale,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
// Infer from type (could be constructor or string)
|
|
405
|
-
const type = options.type || tsType;
|
|
406
|
-
if (type === String) {
|
|
407
|
-
return {
|
|
408
|
-
category: 'string',
|
|
409
|
-
length: options.length,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
if (type === Number) {
|
|
413
|
-
if (options.precision || options.scale) {
|
|
414
|
-
return {
|
|
415
|
-
category: 'decimal',
|
|
416
|
-
precision: options.precision,
|
|
417
|
-
scale: options.scale,
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
// BIGINT for every `Number`, key or not: a 32-bit column is a migration waiting to happen, and
|
|
421
|
-
// the pools decode it back to a JS number at the wire (see `pgNumericTypes`).
|
|
422
|
-
return { category: 'integer', size: 'big' };
|
|
423
|
-
}
|
|
424
|
-
if (type === Boolean) {
|
|
425
|
-
return { category: 'boolean' };
|
|
426
|
-
}
|
|
427
|
-
if (type === Date) {
|
|
428
|
-
return { category: 'timestamp' };
|
|
429
|
-
}
|
|
430
|
-
if (type === BigInt) {
|
|
431
|
-
return { category: 'integer', size: 'big' };
|
|
400
|
+
return canonicalColumnType(options.columnType, options);
|
|
432
401
|
}
|
|
402
|
+
// Infer from type, which is a SQL type string or one of the constructors.
|
|
403
|
+
const { type } = options;
|
|
433
404
|
if (typeof type === 'string') {
|
|
434
405
|
const canonical = sqlToCanonical(type);
|
|
435
406
|
// Propagate explicit dimensions into CanonicalType.length for vector types
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
407
|
+
return options.dimensions && isVectorCategory(canonical.category)
|
|
408
|
+
? { ...canonical, length: options.dimensions }
|
|
409
|
+
: canonical;
|
|
410
|
+
}
|
|
411
|
+
switch (columnFamily(type)) {
|
|
412
|
+
case 'numeric':
|
|
413
|
+
// BIGINT for every `Number` without a scale, key or not: a 32-bit column is a migration waiting
|
|
414
|
+
// to happen, and the pools decode it back to a JS number at the wire (see `pgNumericTypes`).
|
|
415
|
+
return type === Number && (options.precision || options.scale)
|
|
416
|
+
? { category: 'decimal', precision: options.precision, scale: options.scale }
|
|
417
|
+
: { category: 'integer', size: 'big' };
|
|
418
|
+
case 'boolean':
|
|
419
|
+
return { category: 'boolean' };
|
|
420
|
+
case 'date':
|
|
421
|
+
return { category: 'timestamp' };
|
|
422
|
+
// `String`, and anything a reflected type left unrecognised.
|
|
423
|
+
default:
|
|
424
|
+
return { category: 'string', length: options.length };
|
|
440
425
|
}
|
|
441
|
-
// Default to string
|
|
442
|
-
return { category: 'string', length: options.length };
|
|
443
426
|
}
|
|
444
427
|
/**
|
|
445
|
-
* Compare two canonical types for equality.
|
|
446
|
-
*
|
|
428
|
+
* Compare two canonical types for equality. Used for schema diffing.
|
|
429
|
+
*
|
|
430
|
+
* Nothing here guesses an engine's own default for an unstated bound: `VARCHAR` is 255 on MySQL and
|
|
431
|
+
* `TEXT` on Postgres, and a comparison that assumed either was blind to that difference on the other.
|
|
432
|
+
* `DiffOptions.normalizeType` is what settles it, by putting both sides through the engine first.
|
|
447
433
|
*/
|
|
448
434
|
export function areTypesEqual(a, b) {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
// For strings, compare length (allowing for default 255)
|
|
458
|
-
if (a.category === 'string') {
|
|
459
|
-
const lengthA = a.length ?? 255;
|
|
460
|
-
const lengthB = b.length ?? 255;
|
|
461
|
-
if (lengthA !== lengthB) {
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
// For decimals, compare precision and scale (allowing for default 10, 2)
|
|
466
|
-
if (a.category === 'decimal') {
|
|
467
|
-
const pA = a.precision ?? 10;
|
|
468
|
-
const pB = b.precision ?? 10;
|
|
469
|
-
const sA = a.scale ?? 2;
|
|
470
|
-
const sB = b.scale ?? 2;
|
|
471
|
-
if (pA !== pB || sA !== sB) {
|
|
472
|
-
return false;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
// For timestamps, compare timezone
|
|
476
|
-
if (a.category === 'timestamp') {
|
|
477
|
-
if (!!a.withTimezone !== !!b.withTimezone) {
|
|
478
|
-
return false;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
// Unsigned must match
|
|
482
|
-
if (!!a.unsigned !== !!b.unsigned) {
|
|
483
|
-
return false;
|
|
484
|
-
}
|
|
485
|
-
return true;
|
|
435
|
+
return (a.category === b.category &&
|
|
436
|
+
a.size === b.size &&
|
|
437
|
+
a.length === b.length &&
|
|
438
|
+
a.precision === b.precision &&
|
|
439
|
+
a.scale === b.scale &&
|
|
440
|
+
!!a.withTimezone === !!b.withTimezone &&
|
|
441
|
+
!!a.unsigned === !!b.unsigned);
|
|
486
442
|
}
|
|
487
443
|
/**
|
|
488
|
-
*
|
|
444
|
+
* Whether changing a column from one type to the other can lose what it holds.
|
|
445
|
+
*
|
|
446
|
+
* An unstated bound is the engine's widest, so stating one for the first time narrows the column just
|
|
447
|
+
* as lowering one does: `TEXT` to `VARCHAR(50)` truncates, and `NUMERIC` to `NUMERIC(5,2)` rounds.
|
|
489
448
|
*/
|
|
490
449
|
export function isBreakingTypeChange(from, to) {
|
|
491
|
-
// Changing category is always potentially breaking
|
|
492
450
|
if (from.category !== to.category) {
|
|
493
451
|
return true;
|
|
494
452
|
}
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
if (from.size && to.size) {
|
|
498
|
-
|
|
499
|
-
const toIndex = sizeOrder.indexOf(to.size);
|
|
500
|
-
if (toIndex < fromIndex) {
|
|
501
|
-
return true;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
// Reducing string length is breaking
|
|
505
|
-
if (from.category === 'string' && from.length && to.length) {
|
|
506
|
-
if (to.length < from.length) {
|
|
507
|
-
return true;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
// Reducing precision/scale is breaking
|
|
511
|
-
if (from.category === 'decimal') {
|
|
512
|
-
if (from.precision && to.precision && to.precision < from.precision) {
|
|
513
|
-
return true;
|
|
514
|
-
}
|
|
515
|
-
if (from.scale && to.scale && to.scale < from.scale) {
|
|
516
|
-
return true;
|
|
517
|
-
}
|
|
453
|
+
// Only where both state a size: an unsized member of a family is the engine's base type, wider than
|
|
454
|
+
// `tiny` and narrower than `big`, and which of those it is depends on a family this does not know.
|
|
455
|
+
if (from.size && to.size && SIZE_ORDER.indexOf(to.size) < SIZE_ORDER.indexOf(from.size)) {
|
|
456
|
+
return true;
|
|
518
457
|
}
|
|
519
|
-
return
|
|
458
|
+
return (narrows(from.length, to.length) ||
|
|
459
|
+
narrows(from.precision, to.precision) ||
|
|
460
|
+
narrows(from.scale, to.scale) ||
|
|
461
|
+
// Either direction drops half the range: signed loses the top bit, unsigned the negatives.
|
|
462
|
+
!!from.unsigned !== !!to.unsigned ||
|
|
463
|
+
// An offset the column stops keeping cannot be recovered from what is left.
|
|
464
|
+
(!!from.withTimezone && !to.withTimezone));
|
|
465
|
+
}
|
|
466
|
+
const SIZE_ORDER = ['tiny', 'small', 'medium', 'big'];
|
|
467
|
+
/** A bound narrowed, where an unstated one is unbounded. */
|
|
468
|
+
function narrows(from, to) {
|
|
469
|
+
return to !== undefined && (from === undefined || to < from);
|
|
520
470
|
}
|
|
521
471
|
/**
|
|
522
472
|
* Get the UQL ColumnType that best matches a canonical type.
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* - Entity metadata (decorator-based entities)
|
|
6
6
|
* - Database introspection results (TableSchema[])
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
8
|
+
import type { EntityGetter } from '../type/entity.js';
|
|
9
|
+
import type { EntityMeta, FieldMeta, FieldOptions, Type } from '../type/index.js';
|
|
9
10
|
import type { NamingStrategy } from '../type/namingStrategy.js';
|
|
10
11
|
import { SchemaAST } from './schemaAST.js';
|
|
11
|
-
import { type ForeignKeyAction } from './types.js';
|
|
12
|
+
import { type CanonicalType, type ForeignKeyAction } from './types.js';
|
|
12
13
|
/**
|
|
13
14
|
* Options for building SchemaAST from entities.
|
|
14
15
|
*/
|
|
@@ -31,3 +32,23 @@ export interface BuildSchemaASTOptions {
|
|
|
31
32
|
* resolves against a table another entity declares, and an index against the columns of its own.
|
|
32
33
|
*/
|
|
33
34
|
export declare function buildSchemaAST(entities: readonly Type<unknown>[], options?: BuildSchemaASTOptions): SchemaAST;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the canonical type for a field, inheriting from the referenced
|
|
37
|
+
* entity's primary key when the field is a foreign-key reference
|
|
38
|
+
* (`@Field({ references: () => SomeEntity })`) with no explicit type of its
|
|
39
|
+
* own.
|
|
40
|
+
*
|
|
41
|
+
* Without this, a field like `creatorId?: UUID` (a bare TypeScript alias for
|
|
42
|
+
* `string`, erased at runtime) falls back to the generic string inference in
|
|
43
|
+
* {@link fieldOptionsToCanonical} and gets typed as TEXT/VARCHAR - producing a
|
|
44
|
+
* foreign key column whose type doesn't match the UUID primary key it
|
|
45
|
+
* references, which Postgres (and most databases) reject outright.
|
|
46
|
+
*
|
|
47
|
+
* `field.typeFromReference` (set by `defineField`, see entity/metadata/definition.ts)
|
|
48
|
+
* is what distinguishes "no type was given" from "the decorator explicitly set
|
|
49
|
+
* a type" - including explicit constructor overrides like `type: BigInt`, which
|
|
50
|
+
* a value-based check (e.g. `typeof field.type === 'string'`) would miss since
|
|
51
|
+
* reflection also produces constructor values like `String`/`Number`.
|
|
52
|
+
* `columnType` remains the unambiguous, always-respected explicit override.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveColumnCanonicalType(field: FieldMeta, seen?: Set<EntityGetter>): CanonicalType;
|
|
@@ -53,7 +53,7 @@ export function buildSchemaAST(entities, options = {}) {
|
|
|
53
53
|
* reflection also produces constructor values like `String`/`Number`.
|
|
54
54
|
* `columnType` remains the unambiguous, always-respected explicit override.
|
|
55
55
|
*/
|
|
56
|
-
function resolveColumnCanonicalType(field, seen = new Set()) {
|
|
56
|
+
export function resolveColumnCanonicalType(field, seen = new Set()) {
|
|
57
57
|
const hasExplicitType = !!field.columnType || !field.typeFromReference;
|
|
58
58
|
if (!hasExplicitType && field.references && !seen.has(field.references)) {
|
|
59
59
|
seen.add(field.references);
|
|
@@ -65,7 +65,7 @@ function resolveColumnCanonicalType(field, seen = new Set()) {
|
|
|
65
65
|
return resolveColumnCanonicalType(referencedIdField, seen);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
return fieldOptionsToCanonical(field
|
|
68
|
+
return fieldOptionsToCanonical(field);
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Add a table from entity metadata.
|
|
@@ -175,7 +175,8 @@ function diffColumn(tableName, source, target, opts) {
|
|
|
175
175
|
// key column" rule these two replace used to hide.
|
|
176
176
|
const generatedType = source.isAutoIncrement && target.isAutoIncrement;
|
|
177
177
|
const impliedNotNull = source.isPrimaryKey && target.isPrimaryKey;
|
|
178
|
-
|
|
178
|
+
const typeChanged = !generatedType && !areTypesEqual(opts.normalizeType(source.type), opts.normalizeType(target.type));
|
|
179
|
+
if (typeChanged) {
|
|
179
180
|
differences.push(`type: ${formatType(source.type)} → ${formatType(target.type)}`);
|
|
180
181
|
}
|
|
181
182
|
if (!impliedNotNull && source.nullable !== target.nullable) {
|
|
@@ -202,7 +203,10 @@ function diffColumn(tableName, source, target, opts) {
|
|
|
202
203
|
type: 'alter',
|
|
203
204
|
expected: source,
|
|
204
205
|
actual: target,
|
|
205
|
-
|
|
206
|
+
// Only the type this diff actually reports: a column altered for its default carries no data loss,
|
|
207
|
+
// and a generated key's type - never compared above - reads as unsigned against an entity that
|
|
208
|
+
// cannot say so.
|
|
209
|
+
isBreaking: typeChanged && isBreakingTypeChange(target.type, source.type),
|
|
206
210
|
description: differences.join(', '),
|
|
207
211
|
};
|
|
208
212
|
}
|
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}.
|
|
@@ -730,7 +741,10 @@ export type EntityIndexMeta = {
|
|
|
730
741
|
} & VectorIndexOptions & IndexTypeOptions;
|
|
731
742
|
export type EntityMeta<E> = {
|
|
732
743
|
readonly entity: Type<E>;
|
|
744
|
+
/** The table, which is the class's own name where the entity named none - see {@link derivedName}. */
|
|
733
745
|
name?: string;
|
|
746
|
+
/** Whether {@link name} came from the class rather than from the author, so a naming strategy applies. */
|
|
747
|
+
derivedName?: boolean;
|
|
734
748
|
/** Set only when the entity named one; unset defers to the pool where it is used. See `AbstractDialect.resolveSchema`. */
|
|
735
749
|
schema?: string;
|
|
736
750
|
/**
|
|
@@ -760,7 +774,13 @@ export type EntityMeta<E> = {
|
|
|
760
774
|
checks?: CheckSchema[];
|
|
761
775
|
/** Lifecycle hooks registered via @BeforeInsert, @AfterUpdate, etc. */
|
|
762
776
|
hooks?: Partial<Record<HookEvent, HookRegistration[]>>;
|
|
763
|
-
|
|
777
|
+
/**
|
|
778
|
+
* Bumped by every `define*` call, so anything derived from this metadata can tell that it changed.
|
|
779
|
+
* A content type registered at runtime keeps adding to an entity that has already been read.
|
|
780
|
+
*/
|
|
781
|
+
revision: number;
|
|
782
|
+
/** The revision `getMeta` last finalized, which is what makes finalizing idempotent and re-entrant. */
|
|
783
|
+
processedAt?: number;
|
|
764
784
|
};
|
|
765
785
|
/**
|
|
766
786
|
* Configurable options for an entity (`@Entity()` / `defineEntity`).
|
|
@@ -777,6 +797,16 @@ export type CheckOptions = {
|
|
|
777
797
|
readonly name?: string;
|
|
778
798
|
readonly expression: QueryRaw;
|
|
779
799
|
};
|
|
800
|
+
/**
|
|
801
|
+
* An entity's members as the registry takes them, keyed by plain strings - what a decorator bag, an
|
|
802
|
+
* {@link EntityOptions} and a decorator bag both reduce to before anything is registered: a member
|
|
803
|
+
* decorator has no class to key against, so by then the keys are plain strings either way.
|
|
804
|
+
*/
|
|
805
|
+
export type EntityMembers = {
|
|
806
|
+
readonly fields?: Readonly<Record<string, FieldOptions | undefined>>;
|
|
807
|
+
readonly relations?: Readonly<Record<string, RelationOptions | undefined>>;
|
|
808
|
+
readonly hooks?: Readonly<Partial<Record<HookEvent, readonly string[]>>>;
|
|
809
|
+
};
|
|
780
810
|
export type EntityOptions<E = unknown> = {
|
|
781
811
|
readonly name?: string;
|
|
782
812
|
/**
|
package/dist/type/migration.d.ts
CHANGED
|
@@ -206,6 +206,19 @@ export interface SchemaDiff {
|
|
|
206
206
|
readonly foreignKeysToAdd?: ForeignKeySchema[];
|
|
207
207
|
readonly foreignKeysToDrop?: string[];
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* What every sync entry point takes: `safe` keeps it additive, `drop` lets it remove a column, and
|
|
211
|
+
* `logging` reports each statement. A plan ignores `logging`, having nothing to run.
|
|
212
|
+
*/
|
|
213
|
+
export interface SyncOptions {
|
|
214
|
+
readonly safe?: boolean;
|
|
215
|
+
readonly drop?: boolean;
|
|
216
|
+
readonly logging?: boolean;
|
|
217
|
+
/** One entity instead of every registered one, for a schema that grows while the process runs. */
|
|
218
|
+
readonly entity?: Type<unknown>;
|
|
219
|
+
/** Drop every table and recreate it. Development only: it is the one option that loses data. */
|
|
220
|
+
readonly force?: boolean;
|
|
221
|
+
}
|
|
209
222
|
export interface CreateSchemaOptions {
|
|
210
223
|
readonly ifNotExists?: boolean;
|
|
211
224
|
/**
|
|
@@ -262,7 +275,7 @@ export interface SchemaGenerator {
|
|
|
262
275
|
/**
|
|
263
276
|
* Get the SQL type for a field based on its options
|
|
264
277
|
*/
|
|
265
|
-
getSqlType(fieldOptions: FieldOptions
|
|
278
|
+
getSqlType(fieldOptions: FieldOptions): string;
|
|
266
279
|
/**
|
|
267
280
|
* Compare an entity with a database table node and return the differences.
|
|
268
281
|
*/
|
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. */
|
|
@@ -296,10 +296,20 @@ type QueryCommonOp = Exclude<keyof QueryWhereFieldOperatorMap<unknown>, QueryStr
|
|
|
296
296
|
type QueryAllowedOp<T> = QueryCommonOp | ([NonNullable<T>] extends [QueryComparableScalar] ? QueryOrderedOp : never) | ([NonNullable<T>] extends [string] ? QueryStringOp : never) | ([NonNullable<T>] extends [readonly number[] | Uint8Array] ? QueryVectorOp : never) | (IsMany<T> extends true ? QueryArrayOp : never);
|
|
297
297
|
/**
|
|
298
298
|
* Operators applicable to a field of type `T`: string operators require string fields, ordering
|
|
299
|
-
* operators comparable fields, array operators array fields.
|
|
300
|
-
*
|
|
299
|
+
* operators comparable fields, array operators array fields.
|
|
300
|
+
*
|
|
301
|
+
* Two shapes stay fully permissive, because neither says anything to check against: `unknown`
|
|
302
|
+
* (untyped JSON dot-paths, erased dialect shapes), and a field typed as every scalar at once - the
|
|
303
|
+
* column of a content type defined at runtime. Narrowing to what they share would leave a dynamic
|
|
304
|
+
* row with equality alone, since no operator applies to a boolean and a blob both.
|
|
305
|
+
*/
|
|
306
|
+
export type QueryWhereFieldOperators<T> = unknown extends T ? QueryWhereFieldOperatorMap<T> : IsUntypedColumn<T> extends true ? QueryWhereFieldOperatorMap<T> : Pick<QueryWhereFieldOperatorMap<T>, QueryAllowedOp<T>>;
|
|
307
|
+
/**
|
|
308
|
+
* Whether a column admits every scalar at once, which is what an entity keyed by an index signature
|
|
309
|
+
* says about all of its columns. `Scalar` is the yardstick rather than a parameter: the question is
|
|
310
|
+
* whether `T` is at least that wide, and nothing narrower than the whole union answers it.
|
|
301
311
|
*/
|
|
302
|
-
|
|
312
|
+
type IsUntypedColumn<T> = [Scalar] extends [NonNullable<T>] ? true : false;
|
|
303
313
|
/**
|
|
304
314
|
* Value for a field comparison. A bare array is an implicit `$in` for scalar fields only:
|
|
305
315
|
* on array-typed fields (e.g. a vector `number[]`) an array of arrays is ambiguous, so
|
|
@@ -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.
|