uql-orm 0.24.6 → 0.25.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/README.md +9 -8
- package/dist/cockroachdb/crdbQuerierPool.d.ts +1 -1
- package/dist/cockroachdb/crdbQuerierPool.js +5 -4
- package/dist/dialect/abstractSqlDialect.d.ts +78 -27
- package/dist/dialect/abstractSqlDialect.js +194 -130
- package/dist/dialect/hydrateColumn.d.ts +16 -0
- package/dist/dialect/hydrateColumn.js +66 -0
- package/dist/dialect/jsonSql.d.ts +24 -0
- package/dist/dialect/jsonSql.js +39 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +5 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +10 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +3 -7
- package/dist/dialect/pgLikeSqlDialect.js +2 -14
- package/dist/dialect/vectorCast.d.ts +15 -0
- package/dist/dialect/vectorCast.js +58 -0
- package/dist/entity/metadata/definition.d.ts +0 -1
- package/dist/entity/metadata/definition.js +1 -1
- package/dist/maria/mariaDialect.d.ts +3 -2
- package/dist/maria/mariaDialect.js +3 -18
- package/dist/maria/mariadbQuerierPool.js +6 -1
- package/dist/migrate/builder/migrationBuilder.d.ts +12 -16
- package/dist/migrate/builder/migrationBuilder.js +24 -59
- package/dist/migrate/builder/tableBuilder.js +0 -12
- package/dist/migrate/cli.d.ts +0 -1
- package/dist/migrate/cli.js +1 -1
- package/dist/migrate/codegen/entityCodeGenerator.js +0 -3
- package/dist/migrate/drift/driftDetector.js +17 -15
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +9 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +18 -0
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +8 -2
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +10 -9
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +0 -3
- package/dist/migrate/introspection/mysqlIntrospector.js +0 -9
- package/dist/migrate/introspection/postgresIntrospector.d.ts +0 -3
- package/dist/migrate/introspection/postgresIntrospector.js +0 -12
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +1 -9
- package/dist/migrate/migrator.d.ts +17 -0
- package/dist/migrate/migrator.js +51 -50
- package/dist/migrate/schemaGenerator.d.ts +19 -8
- package/dist/migrate/schemaGenerator.js +48 -17
- package/dist/neon/neonQuerierPool.d.ts +1 -1
- package/dist/neon/neonQuerierPool.js +6 -4
- package/dist/postgres/abstractPgQuerierPool.d.ts +6 -0
- package/dist/postgres/abstractPgQuerierPool.js +3 -0
- package/dist/postgres/pgNumericTypes.d.ts +41 -0
- package/dist/postgres/pgNumericTypes.js +35 -0
- package/dist/postgres/pgQuerierPool.d.ts +1 -1
- package/dist/postgres/pgQuerierPool.js +5 -4
- package/dist/querier/abstractSqlQuerier.d.ts +9 -2
- package/dist/querier/abstractSqlQuerier.js +34 -25
- package/dist/schema/canonicalType.js +2 -12
- package/dist/schema/schemaAST.js +0 -24
- package/dist/schema/schemaASTBuilder.d.ts +1 -1
- package/dist/schema/schemaASTBuilder.js +4 -5
- package/dist/sqlite/nodeSqliteQuerierPool.js +1 -0
- package/dist/sqlite/sqliteQuerierPool.d.ts +5 -0
- package/dist/sqlite/sqliteQuerierPool.js +7 -0
- package/dist/turso/tursoLocalQuerierPool.js +1 -0
- package/dist/type/entity.d.ts +16 -2
- package/dist/type/migration.d.ts +30 -9
- package/dist/type/queryAggregate.d.ts +3 -0
- package/dist/util/field.util.d.ts +4 -0
- package/dist/util/field.util.js +12 -0
- package/dist/util/sqlLiteral.js +18 -15
- package/package.json +6 -6
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeColumn } from '../dialect/hydrateColumn.js';
|
|
1
2
|
import { getMeta } from '../entity/index.js';
|
|
2
3
|
import { buildUpdateResult, clone, getInsertFieldKeys, getRelationRequestSummary, isAutoIncrement, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
3
4
|
import { AbstractQuerier } from './abstractQuerier.js';
|
|
@@ -54,7 +55,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
54
55
|
const ctx = this.dialect.createContext();
|
|
55
56
|
this.dialect.find(ctx, entity, q, opts);
|
|
56
57
|
const res = await this.all(ctx.sql, ctx.values);
|
|
57
|
-
const founds = unflatObjects(res).map((row) => this.
|
|
58
|
+
const founds = unflatObjects(res).map((row) => this.hydrateFields(entity, row));
|
|
58
59
|
await this.fillToManyRelations(entity, founds, q.$populate);
|
|
59
60
|
return founds;
|
|
60
61
|
}
|
|
@@ -74,7 +75,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
74
75
|
try {
|
|
75
76
|
for await (const row of this.internalStream(ctx.sql, normalizedParams)) {
|
|
76
77
|
attrsPaths ??= obtainAttrsPaths(row);
|
|
77
|
-
yield this.
|
|
78
|
+
yield this.hydrateFields(entity, unflatObject(row, attrsPaths));
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
catch (err) {
|
|
@@ -90,31 +91,25 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
90
91
|
const rows = await this.internalAll(query, this.dialect.normalizeValues(values));
|
|
91
92
|
yield* rows;
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Turn what a driver returned back into the types the entity declares, for the row and everything
|
|
96
|
+
* populated under it. Which columns, and as what, is `hydratableFields`; the per-cell decode is
|
|
97
|
+
* `decodeColumn`. Both live with the dialect, because a `sparsevec` is only sparse on Postgres.
|
|
98
|
+
*
|
|
99
|
+
* `visited` guards a populated graph that points back at itself; it defaults rather than living in
|
|
100
|
+
* a separate entry-point wrapper, because the wrapper's whole body was seeding it.
|
|
101
|
+
*/
|
|
102
|
+
hydrateFields(entity, dto, visited = new WeakSet()) {
|
|
98
103
|
if (!dto || typeof dto !== 'object' || visited.has(dto)) {
|
|
99
|
-
return;
|
|
104
|
+
return dto;
|
|
100
105
|
}
|
|
101
106
|
visited.add(dto);
|
|
102
107
|
const meta = getMeta(entity);
|
|
103
108
|
const row = dto;
|
|
104
|
-
for (const key
|
|
105
|
-
const field = meta.fields[key];
|
|
106
|
-
if (!field || (field.type !== 'json' && field.type !== 'jsonb')) {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
+
for (const [key, kind] of this.dialect.hydratableFields(entity)) {
|
|
109
110
|
const value = row[key];
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
row[key] = JSON.parse(value);
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// Keep the original value when the driver returns non-JSON text.
|
|
111
|
+
if (value != null) {
|
|
112
|
+
row[key] = decodeColumn(value, kind);
|
|
118
113
|
}
|
|
119
114
|
}
|
|
120
115
|
for (const key in meta.relations) {
|
|
@@ -125,26 +120,38 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
125
120
|
const value = row[key];
|
|
126
121
|
if (Array.isArray(value)) {
|
|
127
122
|
for (const it of value) {
|
|
128
|
-
this.
|
|
123
|
+
this.hydrateFields(relEntity, it, visited);
|
|
129
124
|
}
|
|
130
125
|
continue;
|
|
131
126
|
}
|
|
132
127
|
if (value && typeof value === 'object') {
|
|
133
|
-
this.
|
|
128
|
+
this.hydrateFields(relEntity, value, visited);
|
|
134
129
|
}
|
|
135
130
|
}
|
|
131
|
+
return dto;
|
|
136
132
|
}
|
|
137
133
|
async internalCount(entity, q = {}, opts) {
|
|
138
134
|
const ctx = this.dialect.createContext();
|
|
139
135
|
this.dialect.count(ctx, entity, q, opts);
|
|
140
136
|
const res = await this.all(ctx.sql, ctx.values);
|
|
137
|
+
// `COUNT(*)` is BIGINT, which the pools decode at the wire - but a caller who supplies their own
|
|
138
|
+
// `types` replaces that, and the signature promises a number here regardless.
|
|
141
139
|
return Number(res[0].count);
|
|
142
140
|
}
|
|
143
141
|
async internalAggregate(entity, q, opts) {
|
|
144
142
|
const ctx = this.dialect.createContext();
|
|
145
143
|
this.dialect.aggregate(ctx, entity, q, opts);
|
|
146
144
|
// biome-ignore lint/suspicious/noExplicitAny: raw DB rows satisfy QueryAggregateResult at runtime but TS can't verify
|
|
147
|
-
|
|
145
|
+
const res = await this.all(ctx.sql, ctx.values);
|
|
146
|
+
const hydratable = this.dialect.hydratableAggregates(entity, q);
|
|
147
|
+
for (const row of res) {
|
|
148
|
+
for (const [alias, kind] of hydratable) {
|
|
149
|
+
if (row[alias] != null) {
|
|
150
|
+
row[alias] = decodeColumn(row[alias], kind);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return res;
|
|
148
155
|
}
|
|
149
156
|
async internalInsertMany(entity, payload) {
|
|
150
157
|
if (!payload?.length) {
|
|
@@ -224,10 +231,12 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
224
231
|
return 0;
|
|
225
232
|
}
|
|
226
233
|
const ids = founds.map((it) => it[meta.id]);
|
|
234
|
+
// Children first: they hold the foreign key, so deleting the parent ahead of them is rejected
|
|
235
|
+
// outright by any schema that declares the constraint without `ON DELETE CASCADE`.
|
|
236
|
+
await this.deleteRelations(entity, ids, opts);
|
|
227
237
|
const deleteCtx = this.dialect.createContext();
|
|
228
238
|
this.dialect.delete(deleteCtx, entity, { $where: ids }, opts);
|
|
229
239
|
const { changes = 0 } = await this.run(deleteCtx.sql, deleteCtx.values);
|
|
230
|
-
await this.deleteRelations(entity, ids, opts);
|
|
231
240
|
return changes;
|
|
232
241
|
}
|
|
233
242
|
get hasOpenTransaction() {
|
|
@@ -6,15 +6,10 @@
|
|
|
6
6
|
* - Canonical types (dialect-agnostic)
|
|
7
7
|
* - TypeScript types (for entity generation)
|
|
8
8
|
*/
|
|
9
|
-
// ============================================================================
|
|
10
|
-
// Vector Category Helpers
|
|
11
|
-
// ============================================================================
|
|
12
9
|
/** Whether a category is one of the vector types, narrowing it to the cast pgvector names use. */
|
|
13
10
|
export function isVectorCategory(category) {
|
|
14
11
|
return category === 'vector' || category === 'halfvec' || category === 'sparsevec';
|
|
15
12
|
}
|
|
16
|
-
// Type Mapping Tables
|
|
17
|
-
// ============================================================================
|
|
18
13
|
/**
|
|
19
14
|
* Maps SQL type strings to canonical type categories.
|
|
20
15
|
* Handles variations across dialects (PostgreSQL, MySQL, SQLite).
|
|
@@ -250,9 +245,6 @@ const CANONICAL_TO_TS = {
|
|
|
250
245
|
halfvec: 'number[]',
|
|
251
246
|
sparsevec: 'number[]',
|
|
252
247
|
};
|
|
253
|
-
// ============================================================================
|
|
254
|
-
// Type Conversion Functions
|
|
255
|
-
// ============================================================================
|
|
256
248
|
/**
|
|
257
249
|
* Parse a SQL type string into a canonical type.
|
|
258
250
|
* Handles complex types like VARCHAR(255), DECIMAL(10,2), etc.
|
|
@@ -426,10 +418,8 @@ export function fieldOptionsToCanonical(options, tsType) {
|
|
|
426
418
|
scale: options.scale,
|
|
427
419
|
};
|
|
428
420
|
}
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
return { category: 'integer', size: 'big' };
|
|
432
|
-
}
|
|
421
|
+
// BIGINT for every `Number`, key or not: a 32-bit column is a migration waiting to happen, and
|
|
422
|
+
// the pools decode it back to a JS number at the wire (see `pgNumericTypes`).
|
|
433
423
|
return { category: 'integer', size: 'big' };
|
|
434
424
|
}
|
|
435
425
|
if (type === Boolean) {
|
package/dist/schema/schemaAST.js
CHANGED
|
@@ -18,9 +18,6 @@ export class SchemaAST {
|
|
|
18
18
|
tables = new Map();
|
|
19
19
|
relationships = [];
|
|
20
20
|
indexes = [];
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// Table Operations
|
|
23
|
-
// ============================================================================
|
|
24
21
|
/**
|
|
25
22
|
* Get a table by name.
|
|
26
23
|
*/
|
|
@@ -68,9 +65,6 @@ export class SchemaAST {
|
|
|
68
65
|
getTableNames() {
|
|
69
66
|
return Array.from(this.tables.keys());
|
|
70
67
|
}
|
|
71
|
-
// ============================================================================
|
|
72
|
-
// Graph Navigation
|
|
73
|
-
// ============================================================================
|
|
74
68
|
/**
|
|
75
69
|
* Get all tables that depend on this table (have FKs pointing to it).
|
|
76
70
|
* These are tables that reference this table's primary key.
|
|
@@ -103,9 +97,6 @@ export class SchemaAST {
|
|
|
103
97
|
getReferencedColumn(fkColumn) {
|
|
104
98
|
return fkColumn.references?.to.columns[0];
|
|
105
99
|
}
|
|
106
|
-
// ============================================================================
|
|
107
|
-
// Graph Analysis
|
|
108
|
-
// ============================================================================
|
|
109
100
|
/**
|
|
110
101
|
* Detect circular foreign key dependencies.
|
|
111
102
|
* Returns arrays of tables that form cycles.
|
|
@@ -179,9 +170,6 @@ export class SchemaAST {
|
|
|
179
170
|
}
|
|
180
171
|
return result;
|
|
181
172
|
}
|
|
182
|
-
// ============================================================================
|
|
183
|
-
// Validation
|
|
184
|
-
// ============================================================================
|
|
185
173
|
/**
|
|
186
174
|
* Validate schema integrity.
|
|
187
175
|
* Checks for:
|
|
@@ -233,9 +221,6 @@ export class SchemaAST {
|
|
|
233
221
|
isValid() {
|
|
234
222
|
return this.validate().length === 0;
|
|
235
223
|
}
|
|
236
|
-
// ============================================================================
|
|
237
|
-
// Smart Relation Detection
|
|
238
|
-
// ============================================================================
|
|
239
224
|
/**
|
|
240
225
|
* Check if a table looks like a junction table (ManyToMany through).
|
|
241
226
|
* Junction tables typically have:
|
|
@@ -292,9 +277,6 @@ export class SchemaAST {
|
|
|
292
277
|
return 'ManyToMany';
|
|
293
278
|
}
|
|
294
279
|
}
|
|
295
|
-
// ============================================================================
|
|
296
|
-
// Index Operations
|
|
297
|
-
// ============================================================================
|
|
298
280
|
/**
|
|
299
281
|
* Add an index to the schema.
|
|
300
282
|
*/
|
|
@@ -317,9 +299,6 @@ export class SchemaAST {
|
|
|
317
299
|
getIndex(name) {
|
|
318
300
|
return this.indexes.find((i) => i.name === name);
|
|
319
301
|
}
|
|
320
|
-
// ============================================================================
|
|
321
|
-
// Relationship Operations
|
|
322
|
-
// ============================================================================
|
|
323
302
|
/**
|
|
324
303
|
* Add a relationship to the schema.
|
|
325
304
|
*/
|
|
@@ -365,9 +344,6 @@ export class SchemaAST {
|
|
|
365
344
|
this.relationships.splice(index, 1);
|
|
366
345
|
return true;
|
|
367
346
|
}
|
|
368
|
-
// ============================================================================
|
|
369
|
-
// Utility Methods
|
|
370
|
-
// ============================================================================
|
|
371
347
|
/**
|
|
372
348
|
* Create a deep clone of this schema.
|
|
373
349
|
*/
|
|
@@ -41,7 +41,7 @@ export declare class SchemaASTBuilder {
|
|
|
41
41
|
/**
|
|
42
42
|
* Build AST from entity classes (decorated with @Entity, @Field, etc.)
|
|
43
43
|
*/
|
|
44
|
-
fromEntities(entities: Type<unknown>[], options?: BuildFromEntitiesOptions): SchemaAST;
|
|
44
|
+
fromEntities(entities: readonly Type<unknown>[], options?: BuildFromEntitiesOptions): SchemaAST;
|
|
45
45
|
/**
|
|
46
46
|
* Resolve the canonical type for a field, inheriting from the referenced
|
|
47
47
|
* entity's primary key when the field is a foreign-key reference
|
|
@@ -34,9 +34,6 @@ export class SchemaASTBuilder {
|
|
|
34
34
|
getAST() {
|
|
35
35
|
return this.ast;
|
|
36
36
|
}
|
|
37
|
-
// ============================================================================
|
|
38
|
-
// Build from Entities
|
|
39
|
-
// ============================================================================
|
|
40
37
|
/**
|
|
41
38
|
* Build AST from entity classes (decorated with @Entity, @Field, etc.)
|
|
42
39
|
*/
|
|
@@ -184,8 +181,10 @@ export class SchemaASTBuilder {
|
|
|
184
181
|
type: relation.cardinality === 'm1' ? 'ManyToOne' : 'OneToOne',
|
|
185
182
|
from: { table, columns: [localColumn] },
|
|
186
183
|
to: { table: relatedTable, columns: [foreignColumn] },
|
|
187
|
-
|
|
188
|
-
|
|
184
|
+
// The relation's own action wins over the global default, so one relation can cascade in the
|
|
185
|
+
// database while the rest stay on `NO ACTION`.
|
|
186
|
+
onDelete: relation.onDelete ?? options.defaultForeignKeyAction ?? this.defaultForeignKeyAction,
|
|
187
|
+
onUpdate: relation.onUpdate ?? options.defaultForeignKeyAction ?? this.defaultForeignKeyAction,
|
|
189
188
|
confidence: 1.0,
|
|
190
189
|
inferredFrom: 'entity_decorator',
|
|
191
190
|
};
|
|
@@ -24,6 +24,7 @@ export class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool {
|
|
|
24
24
|
...(extensions?.length ? { allowExtension: true } : undefined),
|
|
25
25
|
});
|
|
26
26
|
nodeDb.exec('PRAGMA journal_mode = WAL');
|
|
27
|
+
nodeDb.exec('PRAGMA foreign_keys = ON');
|
|
27
28
|
return adaptNodeSqlite(nodeDb);
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -11,5 +11,10 @@ export type Sqlite3PoolOptions = Options & LocalSqlitePoolOptions;
|
|
|
11
11
|
export declare class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool<Sqlite3PoolOptions> {
|
|
12
12
|
readonly filename: string | Buffer;
|
|
13
13
|
constructor(filename?: string | Buffer, opts?: Sqlite3PoolOptions, extra?: ExtraOptions);
|
|
14
|
+
/**
|
|
15
|
+
* SQLite ships with foreign keys unenforced, per connection, for backward compatibility. UQL emits the
|
|
16
|
+
* constraints in its DDL, so leaving them off means a declared `onDelete: 'CASCADE'` silently does
|
|
17
|
+
* nothing and a dangling reference is accepted. Enabled here on every driver, as TypeORM also does.
|
|
18
|
+
*/
|
|
14
19
|
protected createDb(): Promise<SqliteDatabase>;
|
|
15
20
|
}
|
|
@@ -9,6 +9,11 @@ export class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool {
|
|
|
9
9
|
super(opts, extra);
|
|
10
10
|
this.filename = filename;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* SQLite ships with foreign keys unenforced, per connection, for backward compatibility. UQL emits the
|
|
14
|
+
* constraints in its DDL, so leaving them off means a declared `onDelete: 'CASCADE'` silently does
|
|
15
|
+
* nothing and a dangling reference is accepted. Enabled here on every driver, as TypeORM also does.
|
|
16
|
+
*/
|
|
12
17
|
async createDb() {
|
|
13
18
|
// `bun:sqlite` rejects option keys it does not know, and rejects an options object carrying no
|
|
14
19
|
// open flags, so `extensions` is stripped out and what remains of it collapses back to nothing.
|
|
@@ -19,11 +24,13 @@ export class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool {
|
|
|
19
24
|
const { adaptBunSqlite } = await import('./bunSqliteAdapter.bun.js');
|
|
20
25
|
const bunDb = new BunDatabase(this.filename, opts);
|
|
21
26
|
bunDb.run('PRAGMA journal_mode = WAL');
|
|
27
|
+
bunDb.run('PRAGMA foreign_keys = ON');
|
|
22
28
|
return adaptBunSqlite(bunDb);
|
|
23
29
|
}
|
|
24
30
|
const { default: BetterSqlite3 } = await import('better-sqlite3');
|
|
25
31
|
const db = new BetterSqlite3(this.filename, opts);
|
|
26
32
|
db.pragma('journal_mode = WAL');
|
|
33
|
+
db.pragma('foreign_keys = ON');
|
|
27
34
|
return db;
|
|
28
35
|
}
|
|
29
36
|
}
|
|
@@ -30,6 +30,7 @@ export class TursoLocalQuerierPool extends AbstractSqlQuerierPool {
|
|
|
30
30
|
// Annotated rather than cast, so the structural contract is checked against the real driver.
|
|
31
31
|
const db = await connect(this.filename, this.opts);
|
|
32
32
|
await db.pragma('journal_mode = WAL');
|
|
33
|
+
await db.pragma('foreign_keys = ON');
|
|
33
34
|
return db;
|
|
34
35
|
}
|
|
35
36
|
async end() {
|
package/dist/type/entity.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IndexType } from '../schema/types.js';
|
|
1
|
+
import type { ForeignKeyAction, IndexType } from '../schema/types.js';
|
|
2
2
|
import type { FilterOptions } from './query.js';
|
|
3
3
|
import type { QueryRaw } from './queryRaw.js';
|
|
4
4
|
import type { DistributiveOmit, Json, Scalar, Type, Unpacked } from './utility.js';
|
|
@@ -362,6 +362,20 @@ export type RelationOptions<E = any> = {
|
|
|
362
362
|
entity: EntityGetter<E>;
|
|
363
363
|
cardinality: RelationCardinality;
|
|
364
364
|
readonly cascade?: boolean | CascadeType;
|
|
365
|
+
/**
|
|
366
|
+
* Referential actions for the generated foreign key, letting the database do the work instead of the
|
|
367
|
+
* ORM: `onDelete: 'CASCADE'` makes deleting the parent a single statement rather than the graph walk
|
|
368
|
+
* `cascade` performs. Defaults to the migrator's `defaultForeignKeyAction`.
|
|
369
|
+
*
|
|
370
|
+
* Read only from the side that owns the key (`@ManyToOne`, or a `@OneToOne` without `mappedBy`),
|
|
371
|
+
* matching where TypeORM, Prisma and Drizzle put it. Not available on a bare `@Field({ references })`
|
|
372
|
+
* with no relation, because `FieldOptions.onUpdate` already means a value callback there.
|
|
373
|
+
*
|
|
374
|
+
* This overlaps `cascade` rather than conflicting with it: declaring both deletes the children in JS
|
|
375
|
+
* and then leaves the foreign key nothing to cascade, so pick one.
|
|
376
|
+
*/
|
|
377
|
+
readonly onDelete?: ForeignKeyAction;
|
|
378
|
+
readonly onUpdate?: ForeignKeyAction;
|
|
365
379
|
mappedBy?: RelationMappedBy<E>;
|
|
366
380
|
/**
|
|
367
381
|
* The pivot entity of a many-to-many. Unconstrained by `E`: a pivot holds foreign keys to both
|
|
@@ -392,7 +406,7 @@ type RelationOwnerJoin<E> = Required<Pick<RelationOptions<E>, 'through'>> | Requ
|
|
|
392
406
|
* without one of the three, resolution has no columns to join on and throws.
|
|
393
407
|
*/
|
|
394
408
|
type RelationJoin<E> = RelationOwnerJoin<E> | Required<Pick<RelationOptions<E>, 'mappedBy'>>;
|
|
395
|
-
type RelationOptionsOwner<E> = Pick<RelationOptions<E>, 'entity' | 'references' | 'cascade'>;
|
|
409
|
+
type RelationOptionsOwner<E> = Pick<RelationOptions<E>, 'entity' | 'references' | 'cascade' | 'onDelete' | 'onUpdate'>;
|
|
396
410
|
type RelationOptionsInverseSide<E> = Pick<RelationOptions<E>, 'entity' | 'cascade'> & Required<Pick<RelationOptions<E>, 'mappedBy'>>;
|
|
397
411
|
type RelationOptionsThroughOwner<E> = Pick<RelationOptions<E>, 'entity' | 'cascade'> & RelationOwnerJoin<E>;
|
|
398
412
|
/**
|
package/dist/type/migration.d.ts
CHANGED
|
@@ -174,22 +174,43 @@ export interface SchemaDiff {
|
|
|
174
174
|
readonly foreignKeysToAdd?: ForeignKeySchema[];
|
|
175
175
|
readonly foreignKeysToDrop?: string[];
|
|
176
176
|
}
|
|
177
|
+
export interface CreateSchemaOptions {
|
|
178
|
+
readonly ifNotExists?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Restrict which tables are created, for an incremental migration adding one table to a schema that
|
|
181
|
+
* already exists. Constraints still resolve against the full entity graph.
|
|
182
|
+
*/
|
|
183
|
+
readonly only?: readonly string[];
|
|
184
|
+
/**
|
|
185
|
+
* Emit the tables without their foreign keys. Only the integration fixtures want this, and only until
|
|
186
|
+
* their data stops relying on dangling references; a migration always wants the constraints.
|
|
187
|
+
*/
|
|
188
|
+
readonly foreignKeys?: boolean;
|
|
189
|
+
}
|
|
190
|
+
export interface DropSchemaOptions {
|
|
191
|
+
readonly ifExists?: boolean;
|
|
192
|
+
readonly cascade?: boolean;
|
|
193
|
+
}
|
|
177
194
|
/**
|
|
178
195
|
* Interface for generating DDL statements from entity metadata
|
|
179
196
|
*/
|
|
180
197
|
export interface SchemaGenerator {
|
|
181
198
|
/**
|
|
182
|
-
*
|
|
183
|
-
*
|
|
199
|
+
* The whole schema for `entities`: every table, then the foreign keys between them.
|
|
200
|
+
*
|
|
201
|
+
* There is deliberately no per-entity counterpart. One entity means an AST holding one table, so every
|
|
202
|
+
* cross-entity foreign key has nothing to resolve against and is dropped: all three call sites that
|
|
203
|
+
* used to work that way emitted schemas with no referential integrity.
|
|
184
204
|
*/
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
205
|
+
generateCreateSchema(entities: readonly Type<unknown>[], options?: CreateSchemaOptions): string[];
|
|
206
|
+
/**
|
|
207
|
+
* Every `DROP TABLE` for `entities`, dependents first. The inverse of {@link generateCreateSchema},
|
|
208
|
+
* and the reason it takes the whole set: dropping in any order that ignores the relation graph is
|
|
209
|
+
* rejected once the foreign keys are really there.
|
|
210
|
+
*/
|
|
211
|
+
generateDropSchema(entities: readonly Type<unknown>[], options?: DropSchemaOptions): string[];
|
|
188
212
|
/** Generate DROP TABLE statement. */
|
|
189
|
-
generateDropTable(tableName: string, options?:
|
|
190
|
-
ifExists?: boolean;
|
|
191
|
-
cascade?: boolean;
|
|
192
|
-
}): string;
|
|
213
|
+
generateDropTable(tableName: string, options?: DropSchemaOptions): string;
|
|
193
214
|
/**
|
|
194
215
|
* Generate ALTER TABLE statements based on schema diff
|
|
195
216
|
*/
|
|
@@ -107,6 +107,9 @@ type FieldValueType<E, F> = F extends keyof E ? E[F] : unknown;
|
|
|
107
107
|
/**
|
|
108
108
|
* Resolves a single computed column's type from its aggregate function: `$count`/`$sum`/`$avg` are
|
|
109
109
|
* always `number`; `$min`/`$max` keep the aggregated field's own type.
|
|
110
|
+
*
|
|
111
|
+
* `$sum`/`$avg` are exact to 2^53: Postgres widens a sum over BIGINT to NUMERIC, and decoding that
|
|
112
|
+
* text to satisfy this `number` drops the digits past that bound. Use `raw()` for a wider total.
|
|
110
113
|
* @internal
|
|
111
114
|
*/
|
|
112
115
|
type QueryAggregateFnResult<E, Fn> = Fn extends QueryAggregateNumericFn ? number : Fn extends {
|
|
@@ -3,6 +3,10 @@ import type { FieldOptions } from '../type/index.js';
|
|
|
3
3
|
* Checks if a field type is numeric (Number, BigInt, or explicit numeric logical types)
|
|
4
4
|
*/
|
|
5
5
|
export declare function isNumericType(type: unknown): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Checks if a field type is boolean (Boolean, or an explicit boolean logical type)
|
|
8
|
+
*/
|
|
9
|
+
export declare function isBooleanType(type: unknown): boolean;
|
|
6
10
|
/**
|
|
7
11
|
* Checks if a field type is JSON
|
|
8
12
|
*/
|
package/dist/util/field.util.js
CHANGED
|
@@ -31,6 +31,18 @@ export function isNumericType(type) {
|
|
|
31
31
|
}
|
|
32
32
|
return false;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Checks if a field type is boolean (Boolean, or an explicit boolean logical type)
|
|
36
|
+
*/
|
|
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';
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
34
46
|
/**
|
|
35
47
|
* Checks if a field type is JSON
|
|
36
48
|
*/
|
package/dist/util/sqlLiteral.js
CHANGED
|
@@ -64,6 +64,22 @@ function createEscaper(escapeString) {
|
|
|
64
64
|
}
|
|
65
65
|
return sql;
|
|
66
66
|
};
|
|
67
|
+
/** Split out so the `typeof` switch below stays a flat one-line-per-type dispatch. */
|
|
68
|
+
const escapeObject = (value) => {
|
|
69
|
+
if (value instanceof Date) {
|
|
70
|
+
return Number.isNaN(value.getTime()) ? 'NULL' : dateLiteral(value);
|
|
71
|
+
}
|
|
72
|
+
if (Array.isArray(value)) {
|
|
73
|
+
return sqlList(value);
|
|
74
|
+
}
|
|
75
|
+
if (isByteSource(value)) {
|
|
76
|
+
return bytesToHexLiteral(value);
|
|
77
|
+
}
|
|
78
|
+
if ('toSqlString' in value && typeof value.toSqlString === 'function') {
|
|
79
|
+
return String(value.toSqlString());
|
|
80
|
+
}
|
|
81
|
+
throw new TypeError('escapeSqlLiteral: plain objects are not supported; use bound parameters or JSON.stringify + a string column.');
|
|
82
|
+
};
|
|
67
83
|
const escapeValue = (value) => {
|
|
68
84
|
if (value === undefined || value === null) {
|
|
69
85
|
return 'NULL';
|
|
@@ -77,24 +93,11 @@ function createEscaper(escapeString) {
|
|
|
77
93
|
return String(value);
|
|
78
94
|
case 'string':
|
|
79
95
|
return escapeString(value);
|
|
96
|
+
case 'object':
|
|
97
|
+
return escapeObject(value);
|
|
80
98
|
case 'symbol':
|
|
81
99
|
case 'function':
|
|
82
100
|
throw new TypeError('escapeSqlLiteral: symbol and function values are not supported; use bound parameters.');
|
|
83
|
-
case 'object': {
|
|
84
|
-
if (value instanceof Date) {
|
|
85
|
-
return Number.isNaN(value.getTime()) ? 'NULL' : dateLiteral(value);
|
|
86
|
-
}
|
|
87
|
-
if (Array.isArray(value)) {
|
|
88
|
-
return sqlList(value);
|
|
89
|
-
}
|
|
90
|
-
if (isByteSource(value)) {
|
|
91
|
-
return bytesToHexLiteral(value);
|
|
92
|
-
}
|
|
93
|
-
if ('toSqlString' in value && typeof value.toSqlString === 'function') {
|
|
94
|
-
return String(value.toSqlString());
|
|
95
|
-
}
|
|
96
|
-
throw new TypeError('escapeSqlLiteral: plain objects are not supported; use bound parameters or JSON.stringify + a string column.');
|
|
97
|
-
}
|
|
98
101
|
default:
|
|
99
102
|
// Unreachable today; throwing keeps a future JS type from silently becoming SQL.
|
|
100
103
|
throw new TypeError(`escapeSqlLiteral: unsupported value type '${typeof value}'; use bound parameters.`);
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "Extremely fast, type-safe TypeScript ORM - one API for every database",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.25.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
|
@@ -134,18 +134,18 @@
|
|
|
134
134
|
"@tursodatabase/serverless": "^1.4.0",
|
|
135
135
|
"@types/better-sqlite3": "^9.6.0",
|
|
136
136
|
"@types/express": "^5.0.6",
|
|
137
|
-
"@types/pg": "^8.
|
|
137
|
+
"@types/pg": "^8.21.0",
|
|
138
138
|
"@types/ws": "^8.18.1",
|
|
139
139
|
"better-sqlite3": "^13.0.3",
|
|
140
140
|
"express": "^5.2.1",
|
|
141
141
|
"mariadb": "^3.5.3",
|
|
142
142
|
"mongodb": "^7.5.0",
|
|
143
143
|
"mysql2": "^3.23.2",
|
|
144
|
-
"pg": "^8.
|
|
145
|
-
"pg-query-stream": "^4.
|
|
144
|
+
"pg": "^8.23.0",
|
|
145
|
+
"pg-query-stream": "^4.17.0",
|
|
146
146
|
"rxjs": "^7.8.2",
|
|
147
147
|
"sqlite-vec": "^0.1.9",
|
|
148
|
-
"ws": "^8.21.
|
|
148
|
+
"ws": "^8.21.3"
|
|
149
149
|
},
|
|
150
150
|
"author": "Roger Padilla",
|
|
151
151
|
"repository": {
|
|
@@ -198,5 +198,5 @@
|
|
|
198
198
|
"publishConfig": {
|
|
199
199
|
"access": "public"
|
|
200
200
|
},
|
|
201
|
-
"gitHead": "
|
|
201
|
+
"gitHead": "cfe2b005308b808d2e6e3d6e062c13791c883d13"
|
|
202
202
|
}
|