uql-orm 0.79.0 → 0.80.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.map +2 -2
- package/dist/cockroachdb/cockroachDialect.js +5 -1
- package/dist/dialect/abstractDialect.d.ts +1 -31
- package/dist/dialect/abstractDialect.js +3 -27
- package/dist/dialect/abstractSqlDialect.d.ts +25 -56
- package/dist/dialect/abstractSqlDialect.js +78 -145
- package/dist/dialect/aliases.d.ts +5 -0
- package/dist/dialect/aliases.js +5 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +4 -2
- package/dist/dialect/mysqlLikeSqlDialect.js +14 -1
- package/dist/dialect/operators.d.ts +66 -0
- package/dist/dialect/operators.js +129 -0
- package/dist/dialect/pgLikeSqlDialect.d.ts +4 -1
- package/dist/dialect/pgLikeSqlDialect.js +16 -3
- package/dist/entity/decorator/entity.d.ts +6 -1
- package/dist/entity/decorator/entity.js +12 -1
- package/dist/entity/index.d.ts +1 -1
- package/dist/entity/index.js +1 -1
- package/dist/entity/metadata/definition.d.ts +6 -1
- package/dist/entity/metadata/definition.js +19 -0
- package/dist/migrate/codegen/entityTypes.js +1 -2
- package/dist/migrate/ddl/mssqlIndexDdl.d.ts +5 -0
- package/dist/migrate/ddl/mssqlIndexDdl.js +10 -0
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +2 -0
- package/dist/migrate/ddl/mssqlTableDdl.js +5 -0
- package/dist/migrate/ddl/tableDdl.d.ts +2 -0
- package/dist/migrate/ddl/tableDdl.js +4 -0
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +4 -0
- package/dist/migrate/generator/mongoSchemaGenerator.js +10 -0
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +13 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +21 -0
- package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -1
- package/dist/migrate/introspection/mongoIntrospector.js +4 -0
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/mssqlIntrospector.js +8 -0
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/mysqlIntrospector.js +9 -0
- package/dist/migrate/introspection/postgresIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/postgresIntrospector.js +10 -0
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +3 -0
- package/dist/migrate/migrator.d.ts +28 -1
- package/dist/migrate/migrator.js +88 -9
- package/dist/migrate/schemaGenerator.d.ts +12 -1
- package/dist/migrate/schemaGenerator.js +47 -5
- package/dist/migrate/storage/databaseStorage.d.ts +4 -0
- package/dist/migrate/storage/databaseStorage.js +14 -8
- package/dist/migrate/triggerSql.d.ts +24 -0
- package/dist/migrate/triggerSql.js +229 -0
- package/dist/mongo/mongoDialect.d.ts +0 -21
- package/dist/mongo/mongoDialect.js +105 -100
- package/dist/mongo/mongodbQuerier.js +17 -1
- package/dist/mssql/mssqlDialect.d.ts +18 -7
- package/dist/mssql/mssqlDialect.js +77 -33
- package/dist/mssql/mssqlQuerier.js +2 -2
- package/dist/schema/canonicalType.d.ts +6 -1
- package/dist/schema/canonicalType.js +14 -0
- package/dist/schema/schemaASTBuilder.d.ts +2 -8
- package/dist/schema/schemaASTBuilder.js +6 -20
- package/dist/sqlite/sqliteDialect.d.ts +1 -1
- package/dist/sqlite/sqliteDialect.js +12 -3
- package/dist/type/dialect.d.ts +69 -9
- package/dist/type/entity.d.ts +96 -3
- package/dist/type/migration.d.ts +17 -0
- package/dist/type/query.d.ts +13 -4
- package/dist/type/queryWhere.d.ts +4 -2
- package/dist/util/field.util.d.ts +9 -1
- package/dist/util/field.util.js +14 -2
- package/dist/util/fieldOption.util.d.ts +2 -2
- package/dist/util/fieldOption.util.js +2 -2
- package/dist/util/raw.d.ts +9 -1
- package/dist/util/raw.js +36 -11
- package/dist/util/sql.util.d.ts +12 -0
- package/dist/util/sql.util.js +24 -3
- package/dist/util/uqlError.d.ts +2 -0
- package/dist/util/uqlError.js +4 -0
- package/package.json +4 -4
- package/skills/uql-orm/SKILL.md +11 -6
package/dist/type/migration.d.ts
CHANGED
|
@@ -248,6 +248,8 @@ export interface DropSchemaOptions {
|
|
|
248
248
|
readonly ifExists?: boolean;
|
|
249
249
|
readonly cascade?: boolean;
|
|
250
250
|
}
|
|
251
|
+
/** The triggers uql installed on one table, by name, each with the statements that recreate it as it stands. */
|
|
252
|
+
export type InstalledTriggers = ReadonlyMap<string, readonly string[]>;
|
|
251
253
|
/**
|
|
252
254
|
* Interface for generating DDL statements from entity metadata
|
|
253
255
|
*/
|
|
@@ -262,6 +264,15 @@ export interface SchemaGenerator {
|
|
|
262
264
|
generateDropSchema(entities: readonly Type<object>[], options?: DropSchemaOptions): string[];
|
|
263
265
|
/** Generate DROP TABLE statement. */
|
|
264
266
|
generateDropTable(tableName: string, options?: DropSchemaOptions): string;
|
|
267
|
+
/**
|
|
268
|
+
* What takes the triggers on `entity`'s table from `installed` - each by name, with the statements that
|
|
269
|
+
* recreate it - to what it declares: nothing where the two agree, which is always on MongoDB.
|
|
270
|
+
*/
|
|
271
|
+
generateTriggers(entity: Type<object>, installed?: InstalledTriggers): string[];
|
|
272
|
+
/** The inverse of {@link generateTriggers} from the same `installed`: its triggers dropped, and the ones it dropped restored. */
|
|
273
|
+
generateTriggersDown(entity: Type<object>, installed?: InstalledTriggers): string[];
|
|
274
|
+
/** A `DROP` for each trigger uql owns among `names` on `entity`'s table, whatever the entity declares. */
|
|
275
|
+
generateTriggerDrops(entity: Type<object>, names: readonly string[]): string[];
|
|
265
276
|
/**
|
|
266
277
|
* Generate ALTER TABLE statements based on schema diff
|
|
267
278
|
*/
|
|
@@ -321,6 +332,12 @@ export interface SchemaGenerator {
|
|
|
321
332
|
* Interface for introspecting the current database schema
|
|
322
333
|
*/
|
|
323
334
|
export interface SchemaIntrospector {
|
|
335
|
+
/**
|
|
336
|
+
* Every trigger uql installed in this schema, by table and then by name, each with the statements that
|
|
337
|
+
* recreate it as it stands. The names say which to drop once an entity no longer declares them; the
|
|
338
|
+
* statements are what a rollback puts back, read off the engine rather than recorded anywhere by uql.
|
|
339
|
+
*/
|
|
340
|
+
ownedTriggers(): Promise<Map<string, InstalledTriggers>>;
|
|
324
341
|
/**
|
|
325
342
|
* What this introspector can read back about an index, and so all that diffing may compare.
|
|
326
343
|
* Comparing a feature it cannot read reports the same drift forever: the entity side declares it,
|
package/dist/type/query.d.ts
CHANGED
|
@@ -22,13 +22,22 @@ export type QueryOptions = {
|
|
|
22
22
|
* table look alike. The entity's own filters never count as naming one.
|
|
23
23
|
*/
|
|
24
24
|
unfiltered?: boolean;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* What a statement is rendered with, on top of the options its caller passed. Kept apart from
|
|
28
|
+
* {@link QueryOptions} because that one is public - it is the third argument of every querier method -
|
|
29
|
+
* and none of this is a caller's to set: an alias is the dialect's to choose and to spell.
|
|
30
|
+
*/
|
|
31
|
+
export type QueryRenderOptions = QueryOptions & {
|
|
32
|
+
/** The alias columns are read off, escaped by the dialect unless {@link escapedPrefix} spells it. */
|
|
28
33
|
prefix?: string;
|
|
29
34
|
/**
|
|
30
|
-
*
|
|
35
|
+
* The prefix already written out, for the one caller whose row is not an identifier: a trigger reads
|
|
36
|
+
* `NEW."col"`, where `NEW` is a record the engine declares, and quoting it names a table that is not
|
|
37
|
+
* in scope. Defaults to {@link prefix} escaped.
|
|
31
38
|
*/
|
|
39
|
+
escapedPrefix?: string;
|
|
40
|
+
/** Whether to infer the alias where none is given. */
|
|
32
41
|
autoPrefix?: boolean;
|
|
33
42
|
};
|
|
34
43
|
/**
|
|
@@ -223,6 +223,8 @@ export type QueryWhereElemMatch<U, Raw = QueryRaw> = unknown extends U ? {
|
|
|
223
223
|
} : NonNullable<U> extends Scalar ? QueryWhereFieldOperators<NonNullable<U>, Raw> : {
|
|
224
224
|
[K in keyof NonNullable<U>]?: QueryWhereFieldValue<NonNullable<U>[K], Raw>;
|
|
225
225
|
};
|
|
226
|
+
/** Every operator a field condition takes, which is what a key of one is once checked. */
|
|
227
|
+
export type QueryWhereFieldOp = keyof QueryWhereFieldOperatorMap<unknown>;
|
|
226
228
|
/**
|
|
227
229
|
* Simple relational comparison operators. `Pick`'s constraint ties this back to
|
|
228
230
|
* {@link QueryWhereFieldOperatorMap} so a rename there breaks this union at compile time.
|
|
@@ -251,7 +253,7 @@ type QueryArrayOp = keyof Pick<QueryWhereFieldOperatorMap<unknown>, '$all' | '$s
|
|
|
251
253
|
/**
|
|
252
254
|
* Ordering operators: {@link QueryCompareOp} plus `$between`.
|
|
253
255
|
*/
|
|
254
|
-
type QueryOrderedOp = QueryCompareOp | keyof Pick<QueryWhereFieldOperatorMap<unknown>, '$between'>;
|
|
256
|
+
export type QueryOrderedOp = QueryCompareOp | keyof Pick<QueryWhereFieldOperatorMap<unknown>, '$between'>;
|
|
255
257
|
/**
|
|
256
258
|
* Vector-only operators. `Pick`'s constraint ties this back to {@link QueryWhereFieldOperatorMap}
|
|
257
259
|
* so a rename there breaks this union at compile time.
|
|
@@ -261,7 +263,7 @@ type QueryVectorOp = keyof Pick<QueryWhereFieldOperatorMap<unknown>, '$near'>;
|
|
|
261
263
|
* The operators every field takes. A subtraction, so an operator added to the map without being
|
|
262
264
|
* classified above is offered on every field: classify it first.
|
|
263
265
|
*/
|
|
264
|
-
type QueryCommonOp = Exclude<
|
|
266
|
+
type QueryCommonOp = Exclude<QueryWhereFieldOp, QueryStringOp | QueryArrayOp | QueryOrderedOp | QueryVectorOp>;
|
|
265
267
|
/**
|
|
266
268
|
* Operator keys applicable to a field of type `T`. Brackets prevent union distribution so an
|
|
267
269
|
* optional field (`string | undefined`) or a literal union (`'a' | 'b'`) gates as one type.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ColumnFamily, type EntityMeta, type FieldKey, type FieldOptions, type RelationAggregateSpec } from '../type/index.js';
|
|
1
|
+
import { type ColumnFamily, type EntityMeta, type FieldKey, type FieldOptions, type StampEvent, type RelationAggregateSpec } from '../type/index.js';
|
|
2
2
|
/** The family of a logical field type, or `undefined` where it names none. */
|
|
3
3
|
export declare function columnFamily(type: unknown): ColumnFamily | undefined;
|
|
4
4
|
/**
|
|
@@ -12,6 +12,14 @@ export declare function isIntegerColumn(field: Pick<FieldOptions, 'type' | 'colu
|
|
|
12
12
|
* because an inlined field has no column to name, while a stored one is read like any other.
|
|
13
13
|
*/
|
|
14
14
|
export declare function isInlinedExpression<F extends FieldOptions>(field: F): field is F & Required<Pick<F, 'computed'>>;
|
|
15
|
+
/** Whether the entity puts anything on its table the database runs: an authored trigger, or a stamp. */
|
|
16
|
+
export declare function hasTriggers<E>(meta: EntityMeta<E>): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The events the database writes this field on, or `undefined` where it is not a stamp. A stamp is a
|
|
19
|
+
* real column the engine fills on each event, which is how an expression too volatile for a generated
|
|
20
|
+
* column - `now()` - is still kept by the database rather than by whoever happens to write the row.
|
|
21
|
+
*/
|
|
22
|
+
export declare function stampEvents(field: FieldOptions): readonly StampEvent[] | undefined;
|
|
15
23
|
/**
|
|
16
24
|
* The relation aggregate a field computes, where it computes one rather than writing SQL: what it
|
|
17
25
|
* reads, off which relation, narrowed and capped how. Every engine renders it from this - a correlated
|
package/dist/util/field.util.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { COLUMN_TYPES, RelationAggregate, } from '../type/index.js';
|
|
2
|
-
import { getKeys } from './object.util.js';
|
|
2
|
+
import { definedEntries, getKeys } from './object.util.js';
|
|
3
3
|
// Constructors and type strings in one map: a logical type is either, and every caller asks the same
|
|
4
4
|
// question of both.
|
|
5
5
|
const FAMILY_OF = new Map([
|
|
@@ -39,7 +39,19 @@ export function isIntegerColumn(field) {
|
|
|
39
39
|
* because an inlined field has no column to name, while a stored one is read like any other.
|
|
40
40
|
*/
|
|
41
41
|
export function isInlinedExpression(field) {
|
|
42
|
-
return field.computed !== undefined && field.stored
|
|
42
|
+
return field.computed !== undefined && !field.stored;
|
|
43
|
+
}
|
|
44
|
+
/** Whether the entity puts anything on its table the database runs: an authored trigger, or a stamp. */
|
|
45
|
+
export function hasTriggers(meta) {
|
|
46
|
+
return Boolean(meta.triggers?.length) || definedEntries(meta.fields).some(([, field]) => stampEvents(field));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The events the database writes this field on, or `undefined` where it is not a stamp. A stamp is a
|
|
50
|
+
* real column the engine fills on each event, which is how an expression too volatile for a generated
|
|
51
|
+
* column - `now()` - is still kept by the database rather than by whoever happens to write the row.
|
|
52
|
+
*/
|
|
53
|
+
export function stampEvents(field) {
|
|
54
|
+
return Array.isArray(field.stored) ? field.stored : undefined;
|
|
43
55
|
}
|
|
44
56
|
/**
|
|
45
57
|
* The relation aggregate a field computes, where it computes one rather than writing SQL: what it
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ColumnFamily, type FamilyOf, type FieldOptions, QueryRaw } from '../type/index.js';
|
|
1
|
+
import { type ColumnFamily, type FamilyOf, type FieldOptions, QueryRaw, type StampEvent } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* The column family each field option means anything on, or `'*'` where it applies to every column.
|
|
4
4
|
* Exhaustive over {@link FieldOptions}, so a new option cannot be added without placing it - the
|
|
@@ -74,7 +74,7 @@ type OptionsFamily<O> = O extends {
|
|
|
74
74
|
} ? FamilyOf<T> : ColumnFamily;
|
|
75
75
|
/** What the field's own values leave unread, matching {@link deadOn} line for line. */
|
|
76
76
|
type DeadOptions<O> = (O extends {
|
|
77
|
-
readonly stored: true;
|
|
77
|
+
readonly stored: true | readonly StampEvent[];
|
|
78
78
|
} ? GeneratedWrite : O extends {
|
|
79
79
|
readonly computed: QueryRaw;
|
|
80
80
|
} ? Exclude<keyof FieldOptions, InlineRead> : never) | (O extends {
|
|
@@ -86,8 +86,8 @@ function contradictsNotNull(opts, key) {
|
|
|
86
86
|
function deadOn(opts, key) {
|
|
87
87
|
if (isInlinedExpression(opts) && !INLINE_READS.some((read) => read === key))
|
|
88
88
|
return 'an inlined computed field';
|
|
89
|
-
if (opts.stored
|
|
90
|
-
return 'a
|
|
89
|
+
if (opts.stored && GENERATED_WRITES.some((write) => write === key))
|
|
90
|
+
return 'a column the database writes';
|
|
91
91
|
if (opts.isId === true && contradictsNotNull(opts, key))
|
|
92
92
|
return 'a primary key';
|
|
93
93
|
if (opts.updatable === false && key === 'onUpdate')
|
package/dist/util/raw.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EntitySql, type EntityWhere, type EntityWhereMeta, QueryRaw, type QueryRawFn, type ComputedRefs, type RefMap, type Type } from '../type/index.js';
|
|
1
|
+
import { ColumnRef, type EntitySql, type EntityWhere, type EntityWhereMeta, QueryRaw, type QueryRawFn, type ComputedRefs, type RefMap, type TriggerRowName, type Type } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Raw SQL, where an interpolated value binds, a `refs` field renders its column, and a `raw` renders
|
|
4
4
|
* in place: `raw`GREATEST(0, ${user.credits} - ${amount})``. A callback writes whatever it writes, so
|
|
@@ -26,3 +26,11 @@ export declare function memberRefs<E>(): ComputedRefs<E>;
|
|
|
26
26
|
export declare function entitySql<E>(sql: EntitySql<E>): QueryRaw;
|
|
27
27
|
/** A definition's predicate, its callback resolved the way {@link entitySql} resolves one. */
|
|
28
28
|
export declare function entityWhere<E>(where: EntityWhere<E>): EntityWhereMeta<E>;
|
|
29
|
+
/**
|
|
30
|
+
* The fields of `E` as the row a trigger body reads them off, qualified by the side it names: `NEW."col"`
|
|
31
|
+
* against the incoming row, `OLD."col"` against the outgoing one. Columns only, never a relation's
|
|
32
|
+
* aggregate: that is a subquery, and a trigger fires on one row rather than over a table to correlate to.
|
|
33
|
+
*/
|
|
34
|
+
export declare function rowRefs<E>(qualifier: TriggerRowName): RefMap<E>;
|
|
35
|
+
/** One field of a trigger's row, for code that names it by its key rather than off {@link rowRefs}. */
|
|
36
|
+
export declare function rowColumn(qualifier: TriggerRowName, key: string): ColumnRef;
|
package/dist/util/raw.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getMeta } from '../entity/metadata/definition.js';
|
|
2
2
|
import { ColumnRef, QueryRaw, RAW_TEXT, RelationAggregate, } from '../type/index.js';
|
|
3
|
-
import { isInlinedExpression } from './field.util.js';
|
|
4
|
-
import { hasKeys } from './object.util.js';
|
|
3
|
+
import { aggregateOf, isInlinedExpression } from './field.util.js';
|
|
4
|
+
import { entityName, hasKeys } from './object.util.js';
|
|
5
5
|
export function raw(value, ...rest) {
|
|
6
6
|
if (!isTemplateStrings(value)) {
|
|
7
7
|
return new QueryRaw(value);
|
|
@@ -83,26 +83,51 @@ function relationAggregate(spec) {
|
|
|
83
83
|
opts.dialect.appendRelationAggregate(opts.ctx, opts.entity, spec, opts.prefix);
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
|
-
/**
|
|
87
|
-
|
|
86
|
+
/**
|
|
87
|
+
* The fields of `E` as the row a trigger body reads them off, qualified by the side it names: `NEW."col"`
|
|
88
|
+
* against the incoming row, `OLD."col"` against the outgoing one. Columns only, never a relation's
|
|
89
|
+
* aggregate: that is a subquery, and a trigger fires on one row rather than over a table to correlate to.
|
|
90
|
+
*/
|
|
91
|
+
export function rowRefs(qualifier) {
|
|
92
|
+
return new Proxy({}, { get: (_, key) => rowColumn(qualifier, String(key)) });
|
|
93
|
+
}
|
|
94
|
+
/** One field of a trigger's row, for code that names it by its key rather than off {@link rowRefs}. */
|
|
95
|
+
export function rowColumn(qualifier, key) {
|
|
96
|
+
return columnRef(undefined, key, qualifier);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* One field as SQL, against its own entity or, read off a definition, the entity rendering it. A
|
|
100
|
+
* `qualifier` names the row it reads from, `NEW` or `OLD`, instead of the alias in scope.
|
|
101
|
+
*/
|
|
102
|
+
function columnRef(entity, key, qualifier) {
|
|
88
103
|
return new ColumnRef(key, (opts) => {
|
|
89
104
|
const owner = entity ?? opts.entity;
|
|
90
105
|
if (!owner) {
|
|
91
106
|
throw new TypeError(`'${key}' was read off a definition's refs, so it renders only inside its entity's SQL`);
|
|
92
107
|
}
|
|
93
|
-
renderColumn(getMeta(owner), key, { ...opts, entity: owner });
|
|
108
|
+
renderColumn(getMeta(owner), key, { ...opts, entity: owner }, qualifier);
|
|
94
109
|
});
|
|
95
110
|
}
|
|
96
|
-
/**
|
|
97
|
-
|
|
111
|
+
/**
|
|
112
|
+
* A field's column, or the expression an inlined computed one stands for, as a `$where` on it reads it.
|
|
113
|
+
* Under a `qualifier` the column is read off that row rather than off the alias in scope, and the row is
|
|
114
|
+
* written verbatim: it is a record the engine declares, not an identifier to quote and case-fold.
|
|
115
|
+
*/
|
|
116
|
+
function renderColumn(meta, key, opts, qualifier) {
|
|
117
|
+
const scope = qualifier === undefined ? opts : { ...opts, escapedPrefix: `${qualifier}.` };
|
|
98
118
|
const field = meta.fields[key];
|
|
99
119
|
if (field && isInlinedExpression(field)) {
|
|
100
|
-
|
|
101
|
-
field
|
|
102
|
-
|
|
120
|
+
// A relation aggregate is a subquery correlated to a table in scope, and a trigger's row is not one.
|
|
121
|
+
if (qualifier !== undefined && aggregateOf(field)) {
|
|
122
|
+
throw new TypeError(`'${entityName(meta)}.${key}' reads a relation, which a trigger's row cannot: it fires on one row, ` +
|
|
123
|
+
'with no table in scope to correlate a subquery to. Name the columns it is derived from instead.');
|
|
124
|
+
}
|
|
125
|
+
scope.ctx.append('(');
|
|
126
|
+
field.computed.render(scope);
|
|
127
|
+
scope.ctx.append(')');
|
|
103
128
|
return;
|
|
104
129
|
}
|
|
105
|
-
|
|
130
|
+
scope.ctx.append(scope.escapedPrefix + scope.dialect.escapeId(scope.dialect.columnOf(meta, key), true));
|
|
106
131
|
}
|
|
107
132
|
/** A tag call passes the frozen strings array, which carries its own `raw` counterpart. */
|
|
108
133
|
function isTemplateStrings(value) {
|
package/dist/util/sql.util.d.ts
CHANGED
|
@@ -23,6 +23,18 @@ export declare function qualifyName(name: string, schema?: string): string;
|
|
|
23
23
|
export declare function derivedConstraintName(table: string, parts: readonly (string | number)[], kind: ConstraintKind): string;
|
|
24
24
|
/** The kinds of derived name, which is also what `indexNameStem` strips to compare them. */
|
|
25
25
|
export type ConstraintKind = 'pk' | 'fk' | 'idx' | 'ck' | 'uk';
|
|
26
|
+
/**
|
|
27
|
+
* Whether uql installed the object called `name`. Ownership is the prefix and nothing else, since no
|
|
28
|
+
* engine records who created one - so this is the only thing standing between a hand-written trigger and
|
|
29
|
+
* a `DROP`, and it is asked on both sides: when reading the catalogue, and again before emitting.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isOwnedName(name: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The identifier uql installs a schema object under: its own prefix, the table it hangs off, the label
|
|
34
|
+
* the author gave it, and last a hash of the object's `content`, which no clamping cuts. The table keeps
|
|
35
|
+
* two entities sharing a label apart where an engine scopes such names to the schema.
|
|
36
|
+
*/
|
|
37
|
+
export declare function ownedName(table: string, label: string, content: string): string;
|
|
26
38
|
/**
|
|
27
39
|
* The name a derived index gets when nothing named it: `Order__total_idx`, or `Order__total_uk` for a
|
|
28
40
|
* unique one - which the builder has always spelled apart, and which reads as what it enforces.
|
package/dist/util/sql.util.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OWNED_PREFIX } from '../dialect/aliases.js';
|
|
1
2
|
import { hasKeys } from './object.util.js';
|
|
2
3
|
/** Pre-computed regex for each SQL identifier escape character to avoid per-call allocation. */
|
|
3
4
|
const escapeIdRegexCache = { '`': /`/g, '"': /"/g };
|
|
@@ -75,13 +76,33 @@ export function derivedConstraintName(table, parts, kind) {
|
|
|
75
76
|
}
|
|
76
77
|
/** Between table and columns, doubled: index names share one namespace per database, where `a` + `b_c` and `a_b` + `c` would collide. */
|
|
77
78
|
const TABLE_SEPARATOR = '__';
|
|
79
|
+
/** What every name uql installs begins with, so the two ends asking about one cannot spell it apart. */
|
|
80
|
+
const OWNED_START = `${OWNED_PREFIX}_`;
|
|
81
|
+
/**
|
|
82
|
+
* Whether uql installed the object called `name`. Ownership is the prefix and nothing else, since no
|
|
83
|
+
* engine records who created one - so this is the only thing standing between a hand-written trigger and
|
|
84
|
+
* a `DROP`, and it is asked on both sides: when reading the catalogue, and again before emitting.
|
|
85
|
+
*/
|
|
86
|
+
export function isOwnedName(name) {
|
|
87
|
+
return name.startsWith(OWNED_START);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The identifier uql installs a schema object under: its own prefix, the table it hangs off, the label
|
|
91
|
+
* the author gave it, and last a hash of the object's `content`, which no clamping cuts. The table keeps
|
|
92
|
+
* two entities sharing a label apart where an engine scopes such names to the schema.
|
|
93
|
+
*/
|
|
94
|
+
export function ownedName(table, label, content) {
|
|
95
|
+
const version = `_${hashIdentifier(content)}`;
|
|
96
|
+
return clampIdentifier(`${OWNED_START}${table}${TABLE_SEPARATOR}${label}`, version.length) + version;
|
|
97
|
+
}
|
|
78
98
|
/** A name the engine stores whole, shortened around a hash of the full one, which stays stable across runs. */
|
|
79
|
-
function clampIdentifier(name) {
|
|
80
|
-
|
|
99
|
+
function clampIdentifier(name, reserved = 0) {
|
|
100
|
+
const max = MAX_IDENTIFIER_LENGTH - reserved;
|
|
101
|
+
if (name.length <= max) {
|
|
81
102
|
return name;
|
|
82
103
|
}
|
|
83
104
|
const suffix = `_${hashIdentifier(name)}`;
|
|
84
|
-
return name.slice(0,
|
|
105
|
+
return name.slice(0, max - suffix.length) + suffix;
|
|
85
106
|
}
|
|
86
107
|
/**
|
|
87
108
|
* FNV-1a, by hand: the package ships zero runtime dependencies, and `node:crypto` is not reachable
|
package/dist/util/uqlError.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export declare class UqlUsageError extends TypeError {
|
|
|
18
18
|
/** What an HTTP transport answers with. */
|
|
19
19
|
readonly status = 400;
|
|
20
20
|
}
|
|
21
|
+
/** What a value is, for a refusal naming what `/http` handed over instead of what the types require. */
|
|
22
|
+
export declare function kindOf(value: unknown): string;
|
|
21
23
|
/**
|
|
22
24
|
* @deprecated since 0.77.1 - use {@link UqlUsageError}, which every misuse throws, lock or not. The
|
|
23
25
|
* same class under both names, so an existing `instanceof` keeps working.
|
package/dist/util/uqlError.js
CHANGED
|
@@ -11,6 +11,10 @@ export class UqlUsageError extends TypeError {
|
|
|
11
11
|
/** What an HTTP transport answers with. */
|
|
12
12
|
status = 400;
|
|
13
13
|
}
|
|
14
|
+
/** What a value is, for a refusal naming what `/http` handed over instead of what the types require. */
|
|
15
|
+
export function kindOf(value) {
|
|
16
|
+
return value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value;
|
|
17
|
+
}
|
|
14
18
|
/**
|
|
15
19
|
* @deprecated since 0.77.1 - use {@link UqlUsageError}, which every misuse throws, lock or not. The
|
|
16
20
|
* same class under both names, so an existing `instanceof` keeps working.
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.80.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
|
@@ -136,9 +136,9 @@
|
|
|
136
136
|
"@electric-sql/pglite-pgvector": "0.0.9",
|
|
137
137
|
"@libsql/client": "^0.18.0",
|
|
138
138
|
"@neondatabase/serverless": "^1.1.0",
|
|
139
|
-
"@nestjs/common": "^12.0
|
|
140
|
-
"@nestjs/core": "^12.0
|
|
141
|
-
"@nestjs/testing": "^12.0
|
|
139
|
+
"@nestjs/common": "^12.1.0",
|
|
140
|
+
"@nestjs/core": "^12.1.0",
|
|
141
|
+
"@nestjs/testing": "^12.1.0",
|
|
142
142
|
"@tursodatabase/database": "^0.7.2",
|
|
143
143
|
"@tursodatabase/serverless": "^1.4.0",
|
|
144
144
|
"@types/better-sqlite3": "^9.6.0",
|
package/skills/uql-orm/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: uql-orm
|
|
|
3
3
|
description: >
|
|
4
4
|
Write code with UQL (the uql-orm package), the TypeScript ORM whose queries are plain JSON objects,
|
|
5
5
|
on PostgreSQL, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, MongoDB, Turso, Neon, D1 and PGlite.
|
|
6
|
-
Use when a project imports uql-orm, or when defining entities, querying, populating relations,
|
|
6
|
+
Use when a project imports uql-orm, or when defining entities or triggers, querying, populating relations,
|
|
7
7
|
writing transactions, raw SQL or migrations with it. UQL is not Prisma, Drizzle, TypeORM or MikroORM:
|
|
8
8
|
their APIs do not carry over.
|
|
9
9
|
---
|
|
@@ -76,14 +76,15 @@ export class Post {
|
|
|
76
76
|
|
|
77
77
|
- Every `@Field` states its `type` (`String`, `Number`, `Boolean`, `Date`, `BigInt`, or a column type such as `'uuid'`, `'text'`, `'jsonb'`), except a foreign key, which takes `references` and inherits the target key's type.
|
|
78
78
|
- A column is nullable unless it says `nullable: false`, and its property must admit `null` to match: `title?: string | null`. A property typed without `| null` on a nullable column is a compile error.
|
|
79
|
-
- An engine's own column type is a `raw` constant,
|
|
79
|
+
- An engine's own column type is a `raw` constant, ``columnType: raw`tsvector` ``, rendered verbatim and carrying its own `length`/`precision`: never a bare string.
|
|
80
80
|
- Members are named by callbacks, never by strings: `mappedBy: (post) => post.author`, `references: (post) => post.authorId`.
|
|
81
81
|
- `@ManyToMany({ entity: () => Tag, through: () => PostTag })` names its junction entity.
|
|
82
82
|
- `@Index((post) => [post.authorId], { where: { archived: { $ne: true } } })` states a partial index's filter as the predicate the query passes, never as `raw`: a planner matches the two by shape, so `raw` that means the same thing leaves the index unused.
|
|
83
|
-
- `@Field({ type: Number, version: true })`, with `[versionKey]?: 'version'` on the class,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`
|
|
83
|
+
- `@Field({ type: Number, version: true })`, with `[versionKey]?: 'version'` on the class, is an optimistic lock:
|
|
84
|
+
an update must carry the version it read (a compile error otherwise), and one against a row someone else moved on throws `UqlOptimisticLockError` (kind `optimisticLock`, HTTP 409). Its updates name one row by its id; save and upsert are refused.
|
|
85
|
+
- `@Field({ computed })` is a value the database produces, on a `readonly` property: SQL over the row, ``(u) => raw`${u.first} || ' ' || ${u.last}` ``, or a relation aggregate, `(order) => order.items.count()`.
|
|
86
|
+
`stored: true` makes the SQL a generated column; `stored: ['insert', 'update']` makes it a stamp, a trigger writing it on those events whoever writes the row (``computed: raw`CURRENT_TIMESTAMP` ``), where `onUpdate` covers only uql's own writes.
|
|
87
|
+
- `@Trigger({ on: 'afterUpdate', of: (post) => [post.status], where: { $old: { status: 'draft' } }, run })` is a trigger the database fires; `where` holds a `$where` predicate per row it names, or SQL off the rows. `run` reads `{ newRow }` on insert, `{ newRow, oldRow }` on update, `{ oldRow }` on delete, and returns the engine's own SQL, one body for every engine or `{ postgres, mssql, ... }` where they differ (SQL Server fires per statement, reading `inserted`/`deleted` as tables, with no `before*` and no `where`). MongoDB has none, and refuses a write to an entity declaring one.
|
|
87
88
|
- `defineEntity` defines the same entity without decorators: https://uql-orm.dev/entities/imperative.md
|
|
88
89
|
|
|
89
90
|
## Queries
|
|
@@ -111,6 +112,7 @@ const users = await pool.findMany(User, {
|
|
|
111
112
|
- `$where` takes a value for equality or an operator map: `$eq`, `$ne`, `$lt`, `$lte`, `$gt`, `$gte`, `$in`,
|
|
112
113
|
`$nin`, `$between`, `$like`, `$ilike`, `$regex`, `$startsWith`, `$endsWith`, `$includes`, `$isNull`,
|
|
113
114
|
`$isNotNull`. `$and`, `$or`, `$not` and `$nor` combine clauses.
|
|
115
|
+
- NULL compares the way the engine compares it: on SQL, `$ne`, `$nin`, `$not` and `$nor` leave out a NULL row, where MongoDB keeps it. Name NULL where you want it, `{ $or: [{ col: { $ne: 'a' } }, { col: null }] }`; ask for NULL with `{ col: null }` and its absence with `{ col: { $ne: null } }`.
|
|
114
116
|
- `$text: { $value }` in `$where` searches text on every engine with full-text search, through the entity's
|
|
115
117
|
`@Index(..., { type: 'fulltext', config })`, whose columns may carry a `weight`. `$sort: { $text: 'desc' }` ranks by
|
|
116
118
|
relevance, and `{ $text: { $project: 'score' } }` also returns it, typed with `WithProjection<E, 'score'>`.
|
|
@@ -153,11 +155,14 @@ transaction. A querier from `pool.getQuerier()` is yours to release: bind it wit
|
|
|
153
155
|
`npx uql-migrate` reads `uql.config.ts`. `sync` creates what the entities imply (development only);
|
|
154
156
|
`generate:entities` writes the diff as a migration file to review; `up` applies migrations; `generate:from-db`
|
|
155
157
|
writes entity classes from an existing database; `drift:check` fails when the database no longer matches.
|
|
158
|
+
Triggers are part of the diff: uql installs its own under `_uql_`-prefixed names and never touches another.
|
|
156
159
|
|
|
157
160
|
## Where to read more
|
|
158
161
|
|
|
159
162
|
- Operators, per-dialect SQL: https://uql-orm.dev/querying/comparison-operators.md
|
|
160
163
|
- Relations and deep `$populate`: https://uql-orm.dev/querying/relations.md
|
|
164
|
+
- Computed fields and stamps: https://uql-orm.dev/entities/computed-fields.md
|
|
165
|
+
- Triggers: https://uql-orm.dev/entities/triggers.md
|
|
161
166
|
- Every method's signature: https://uql-orm.dev/querying/methods.md
|
|
162
167
|
- Coming from Prisma, Drizzle, TypeORM or MikroORM: https://uql-orm.dev/switching-to-uql.md
|
|
163
168
|
- Breaking changes by version: https://uql-orm.dev/upgrade-guide.md
|