uql-orm 0.81.0 → 0.82.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 +3 -3
- package/dist/browser/querier/httpQuerier.d.ts +2 -2
- package/dist/browser/querier/httpQuerier.js +2 -1
- package/dist/browser/type/clientQuerier.d.ts +2 -2
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +9 -8
- package/dist/bunSql/bunSql.util.js +2 -1
- package/dist/dialect/abstractSqlDialect.d.ts +15 -5
- package/dist/dialect/abstractSqlDialect.js +109 -40
- package/dist/dialect/operators.d.ts +7 -1
- package/dist/dialect/operators.js +13 -1
- package/dist/entity/metadata/definition.d.ts +1 -2
- package/dist/entity/metadata/definition.js +37 -39
- package/dist/http/handler.js +5 -4
- package/dist/http/query.d.ts +1 -1
- package/dist/http/query.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/migrate/acquireQuerierForMigrations.js +2 -1
- package/dist/migrate/assertCliConfig.js +7 -6
- package/dist/migrate/bin.js +0 -0
- package/dist/migrate/builder/expressions.js +2 -1
- package/dist/migrate/cli-config.js +5 -4
- package/dist/migrate/ddl/indexDdl.js +4 -3
- package/dist/migrate/ddl/mysqlIndexDdl.js +5 -4
- package/dist/migrate/ddl/pgIndexDdl.js +2 -1
- package/dist/migrate/ddl/sqliteIndexDdl.js +2 -1
- package/dist/migrate/ddl/tableDdl.js +2 -1
- package/dist/migrate/generator/mongoCommand.js +2 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +6 -5
- package/dist/migrate/indexPredicate.js +2 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +2 -1
- package/dist/migrate/introspection/mongoIntrospector.js +2 -1
- package/dist/migrate/migrationTarget.js +2 -1
- package/dist/migrate/migrator.js +2 -1
- package/dist/migrate/schemaGenerator.js +5 -4
- package/dist/migrate/triggerSql.d.ts +1 -1
- package/dist/migrate/triggerSql.js +77 -61
- package/dist/mongo/mongoDialect.d.ts +1 -3
- package/dist/mongo/mongoDialect.js +9 -14
- package/dist/mongo/mongodbQuerier.js +3 -7
- package/dist/querier/abstractQuerier.d.ts +9 -4
- package/dist/querier/abstractQuerier.js +26 -19
- package/dist/querier/abstractQuerierPool.d.ts +3 -3
- package/dist/querier/abstractSqlQuerier.d.ts +2 -2
- package/dist/querier/abstractSqlQuerier.js +1 -1
- package/dist/querier/abstractSqlQuerierPool.d.ts +2 -2
- package/dist/querier/queryError.d.ts +2 -2
- package/dist/schema/schemaASTBuilder.js +2 -1
- package/dist/type/dialect.d.ts +23 -1
- package/dist/type/entity.d.ts +15 -11
- package/dist/type/logger.d.ts +2 -2
- package/dist/type/querier.d.ts +3 -3
- package/dist/type/query.d.ts +3 -13
- package/dist/type/queryAggregate.d.ts +4 -10
- package/dist/type/queryRaw.d.ts +17 -3
- package/dist/type/queryRaw.js +2 -1
- package/dist/type/queryWhere.d.ts +7 -7
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/type/vector.d.ts +2 -1
- package/dist/type/vector.js +2 -1
- package/dist/util/dialect.util.d.ts +13 -5
- package/dist/util/dialect.util.js +28 -20
- package/dist/util/field.util.d.ts +4 -4
- package/dist/util/field.util.js +10 -2
- package/dist/util/hook.util.d.ts +1 -1
- package/dist/util/hook.util.js +8 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/logger.d.ts +3 -3
- package/dist/util/object.util.js +3 -2
- package/dist/util/raw.d.ts +6 -7
- package/dist/util/raw.js +10 -12
- package/dist/util/sqlLiteral.js +3 -2
- package/dist/util/triggerWrite.d.ts +15 -0
- package/dist/util/triggerWrite.js +20 -0
- package/package.json +1 -1
- package/skills/uql-orm/SKILL.md +3 -3
package/dist/type/dialect.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { EntityMeta, UpdatePayload } from './entity.js';
|
|
1
|
+
import type { EntityMeta, EntityPredicate, UpdatePayload } from './entity.js';
|
|
2
2
|
import type { Query, QueryConflictPaths, QueryPage, QueryRenderOptions, QuerySearch, RelationQuery } from './query.js';
|
|
3
3
|
import type { QueryAggMap, QueryAggregate, QueryAggregateOp, QueryGroupMap } from './queryAggregate.js';
|
|
4
|
+
import type { QueryRawRenderOptions } from './queryRaw.js';
|
|
4
5
|
import type { QueryWhere } from './queryWhere.js';
|
|
5
6
|
import type { Type } from './utility.js';
|
|
6
7
|
import type { QueryVectorQuery } from './vector.js';
|
|
@@ -231,6 +232,25 @@ export interface TriggerFeatures {
|
|
|
231
232
|
*/
|
|
232
233
|
readonly before: boolean;
|
|
233
234
|
}
|
|
235
|
+
/** Where DDL's SQL sits: the row a trigger's predicate reads, as its prefix, and a set-based body's rows. */
|
|
236
|
+
export type DdlRenderOptions = Pick<QueryComparisonOptions, 'escapedPrefix' | 'operand'> & Pick<QueryRawRenderOptions, 'rows'>;
|
|
237
|
+
/**
|
|
238
|
+
* A write a trigger's body runs, as `insertInto`, `updateTable` and `deleteFrom` state it. Held untyped
|
|
239
|
+
* here, past those helpers' typing, since the dialect renders it by the entity's metadata alone.
|
|
240
|
+
*/
|
|
241
|
+
export type TriggerWrite = {
|
|
242
|
+
readonly entity: Type<object>;
|
|
243
|
+
} & ({
|
|
244
|
+
readonly kind: 'insert';
|
|
245
|
+
readonly row: Readonly<Record<string, unknown>>;
|
|
246
|
+
} | {
|
|
247
|
+
readonly kind: 'update';
|
|
248
|
+
readonly set: Readonly<Record<string, unknown>>;
|
|
249
|
+
readonly where: EntityPredicate<object>;
|
|
250
|
+
} | {
|
|
251
|
+
readonly kind: 'delete';
|
|
252
|
+
readonly where: EntityPredicate<object>;
|
|
253
|
+
});
|
|
234
254
|
/**
|
|
235
255
|
* What a SQL statement is rendered through, as a `raw` callback and a query context see it:
|
|
236
256
|
* `AbstractSqlDialect` is the one implementation.
|
|
@@ -262,6 +282,8 @@ export interface SqlQueryDialect {
|
|
|
262
282
|
update<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryRenderOptions): void;
|
|
263
283
|
/** An upsert of one record or many by their conflict paths. */
|
|
264
284
|
upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
|
|
285
|
+
/** A write in a trigger's body; `rows` is where a set-based engine's body reads its rows from. */
|
|
286
|
+
triggerWrite(ctx: QueryContext, write: TriggerWrite, rows?: string): void;
|
|
265
287
|
/** A delete of the records the query matches, a soft delete where the entity has one. */
|
|
266
288
|
delete<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, opts?: QueryRenderOptions): void;
|
|
267
289
|
/**
|
package/dist/type/entity.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EnumValues, ForeignKeyAction, IndexType } from '../schema/types.js';
|
|
2
2
|
import type { SqlDialectName } from './dialect.js';
|
|
3
3
|
import type { FilterOptions, RelationQuery } from './query.js';
|
|
4
|
-
import type { ColumnRef, QueryRaw, RelationAggregate } from './queryRaw.js';
|
|
4
|
+
import type { ColumnRef, QueryRaw, RawFor, RelationAggregate } from './queryRaw.js';
|
|
5
5
|
import type { QueryWhere } from './queryWhere.js';
|
|
6
6
|
import type { AtLeastOne, Except, ExactlyOne, IsEqual, IsMany, Json, Scalar, Type, Unpacked, Writable } from './utility.js';
|
|
7
7
|
import type { VectorDistance, VectorIndexOptions, VectorIndexType } from './vector.js';
|
|
@@ -37,6 +37,10 @@ export type WritableKey<E> = {
|
|
|
37
37
|
}[FieldKey<E>];
|
|
38
38
|
/** A whole-record write as a caller supplies one: {@link EntityData} without the fields it cannot write. */
|
|
39
39
|
export type EntityWrite<E> = EntityData<E, WritableKey<E>>;
|
|
40
|
+
/** A row a trigger's body writes: each writable field its value, or SQL - a row's ref most often. */
|
|
41
|
+
export type WriteRow<E, F extends keyof E = WritableKey<E>> = {
|
|
42
|
+
readonly [K in F]?: E[K] | RawFor<QueryRaw, E[K]>;
|
|
43
|
+
};
|
|
40
44
|
/**
|
|
41
45
|
* The property an entity brands with {@link versionKey} as its optimistic lock, `never` where it
|
|
42
46
|
* brands none. The brand is what carries `@Field({ version: true })` to the type level, since a
|
|
@@ -114,7 +118,7 @@ export type JsonArrayFields<T> = {
|
|
|
114
118
|
*/
|
|
115
119
|
export type JsonUpdateOp<T = unknown> = {
|
|
116
120
|
readonly $set?: Partial<T>;
|
|
117
|
-
readonly $unset?: unknown extends T ? string[] : (keyof T & string)[];
|
|
121
|
+
readonly $unset?: unknown extends T ? readonly string[] : readonly (keyof T & string)[];
|
|
118
122
|
readonly $push?: JsonArrayFields<T>;
|
|
119
123
|
readonly $pull?: JsonArrayFields<T>;
|
|
120
124
|
};
|
|
@@ -133,7 +137,7 @@ export type FieldUpdateOp<T extends number | bigint = number | bigint> = Exactly
|
|
|
133
137
|
/** The {@link FieldUpdateOp} a field takes: `never` on one it has no operator for, which is any but a number. */
|
|
134
138
|
type FieldUpdateOpFor<V> = [NonNullable<V>] extends [number] ? FieldUpdateOp<number> : [NonNullable<V>] extends [bigint] ? FieldUpdateOp<bigint> : never;
|
|
135
139
|
/** What an update takes beyond the value: `null` to clear an optional member, `raw` SQL, and update operators. */
|
|
136
|
-
type UpdateExtra<V, Raw> = (undefined extends V ? null : never) | Raw | JsonUpdateOpFor<V> | FieldUpdateOpFor<V>;
|
|
140
|
+
type UpdateExtra<V, Raw> = (undefined extends V ? null : never) | RawFor<Raw, V> | JsonUpdateOpFor<V> | FieldUpdateOpFor<V>;
|
|
137
141
|
/**
|
|
138
142
|
* What a whole-record write persists: the fields and relations with their declared optionality, a
|
|
139
143
|
* related row's alike, and no methods. Two mapped types, since asking each key costs a conditional.
|
|
@@ -411,6 +415,10 @@ export type RelationReference<O, E> = {
|
|
|
411
415
|
readonly foreign: F;
|
|
412
416
|
};
|
|
413
417
|
}[FieldKey<E>];
|
|
418
|
+
/** The fields of `E` whose value is a `T`, however optional: `FieldKeyOf<E, number | bigint>` are the ones a sum adds up. */
|
|
419
|
+
export type FieldKeyOf<E, T> = {
|
|
420
|
+
readonly [K in FieldKey<E>]-?: [NonNullable<E[K]>] extends [T] ? K : never;
|
|
421
|
+
}[FieldKey<E>];
|
|
414
422
|
/** The fields of `O` that can hold any value `V` takes. */
|
|
415
423
|
type FieldKeyHolding<O, V> = {
|
|
416
424
|
readonly [K in keyof O]-?: [NonNullable<V>] extends [NonNullable<O[K]>] ? K : never;
|
|
@@ -446,16 +454,12 @@ export type KeyMap<E> = {
|
|
|
446
454
|
};
|
|
447
455
|
/** The fields of `E` as {@link ColumnRef}s, for SQL that names them: `refs(User)`, or a definition's callback. */
|
|
448
456
|
export type RefMap<E, F extends keyof E = FieldKey<E>> = {
|
|
449
|
-
readonly [K in F]-?: ColumnRef<K & string>;
|
|
457
|
+
readonly [K in F]-?: ColumnRef<K & string, E[K]>;
|
|
450
458
|
};
|
|
451
459
|
/** SQL a definition writes: `raw`, or a callback reading the fields off its refs, bivariant so the registry can hold it. */
|
|
452
460
|
export type EntitySql<E> = QueryRaw | {
|
|
453
461
|
sql(refs: RefMap<E>): QueryRaw;
|
|
454
462
|
}['sql'];
|
|
455
|
-
/** The fields of `C` a `sum` or an `avg` can add up. */
|
|
456
|
-
type NumericKey<C> = {
|
|
457
|
-
readonly [K in FieldKey<C>]-?: [NonNullable<C[K]>] extends [number | bigint] ? K : never;
|
|
458
|
-
}[FieldKey<C>];
|
|
459
463
|
/** One field of `C`, read off its refs: `(item) => item.amount`. */
|
|
460
464
|
type PickRef<C, K extends keyof C> = (refs: RefMap<C>) => ColumnRef<K & string>;
|
|
461
465
|
/**
|
|
@@ -466,11 +470,11 @@ type PickRef<C, K extends keyof C> = (refs: RefMap<C>) => ColumnRef<K & string>;
|
|
|
466
470
|
export type RelationRef<C> = {
|
|
467
471
|
count(q?: AggregateFilter<C>): RelationAggregate<number, true>;
|
|
468
472
|
count(q: AggregatePage<C>): RelationAggregate<number, false>;
|
|
469
|
-
sum<K extends
|
|
470
|
-
sum<K extends
|
|
473
|
+
sum<K extends FieldKeyOf<C, number | bigint>>(pick: PickRef<C, K>, q?: AggregateFilter<C>): RelationAggregate<NonNullable<C[K]>, true>;
|
|
474
|
+
sum<K extends FieldKeyOf<C, number | bigint>>(pick: PickRef<C, K>, q: AggregateTopRows<C>): RelationAggregate<NonNullable<C[K]>, false>;
|
|
471
475
|
min<K extends FieldKey<C>>(pick: PickRef<C, K>, q?: AggregateRows<C>): RelationAggregate<NonNullable<C[K]> | null, false>;
|
|
472
476
|
max<K extends FieldKey<C>>(pick: PickRef<C, K>, q?: AggregateRows<C>): RelationAggregate<NonNullable<C[K]> | null, false>;
|
|
473
|
-
avg<K extends
|
|
477
|
+
avg<K extends FieldKeyOf<C, number | bigint>>(pick: PickRef<C, K>, q?: AggregateRows<C>): RelationAggregate<number | null, false>;
|
|
474
478
|
};
|
|
475
479
|
/**
|
|
476
480
|
* What an aggregate reads of the related rows. The predicate is an {@link EntityPredicate} rather than a
|
package/dist/type/logger.d.ts
CHANGED
|
@@ -12,9 +12,9 @@ export interface Logger {
|
|
|
12
12
|
* @param values - The parameters passed to the query.
|
|
13
13
|
* @param duration - The time it took to execute the query in milliseconds.
|
|
14
14
|
*/
|
|
15
|
-
logQuery?(query: string, values?: unknown[], duration?: number): void;
|
|
15
|
+
logQuery?(query: string, values?: readonly unknown[], duration?: number): void;
|
|
16
16
|
/** Logs a query that took longer than the threshold, its values `undefined` unless `logValues` is on. */
|
|
17
|
-
logSlowQuery?(query: string, values?: unknown[], duration?: number): void;
|
|
17
|
+
logSlowQuery?(query: string, values?: readonly unknown[], duration?: number): void;
|
|
18
18
|
/**
|
|
19
19
|
* Logs a warning.
|
|
20
20
|
*/
|
package/dist/type/querier.d.ts
CHANGED
|
@@ -106,11 +106,11 @@ export interface SqlQuerier extends Querier {
|
|
|
106
106
|
/**
|
|
107
107
|
* Execute a raw SQL query and return results
|
|
108
108
|
*/
|
|
109
|
-
all<T>(query: string, values?: unknown[]): Promise<T[]>;
|
|
109
|
+
all<T>(query: string, values?: readonly unknown[]): Promise<T[]>;
|
|
110
110
|
/**
|
|
111
111
|
* Execute a raw SQL command (INSERT, UPDATE, DELETE, DDL)
|
|
112
112
|
*/
|
|
113
|
-
run(query: string, values?: unknown[]): Promise<QueryUpdateResult>;
|
|
113
|
+
run(query: string, values?: readonly unknown[]): Promise<QueryUpdateResult>;
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
116
|
* Type guard to check if a querier supports raw SQL execution
|
|
@@ -136,7 +136,7 @@ export declare function isMongoQuerier(querier: Querier): querier is MongoQuerie
|
|
|
136
136
|
export type ListenerContext<E extends object = object> = {
|
|
137
137
|
readonly entity: Type<E>;
|
|
138
138
|
readonly querier: Querier;
|
|
139
|
-
readonly payloads: E[];
|
|
139
|
+
readonly payloads: readonly E[];
|
|
140
140
|
readonly event: HookEvent;
|
|
141
141
|
};
|
|
142
142
|
/**
|
package/dist/type/query.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FieldKey, JsonFieldPaths, RelationKey, RelationTarget, ToManyRelationKey, WrittenId } from './entity.js';
|
|
1
|
+
import type { FieldKey, FieldKeyOf, JsonFieldPaths, RelationKey, RelationTarget, ToManyRelationKey, WrittenId } from './entity.js';
|
|
2
2
|
import type { QueryLock } from './queryLock.js';
|
|
3
3
|
import type { QueryRaw } from './queryRaw.js';
|
|
4
4
|
import type { QueryWhere } from './queryWhere.js';
|
|
@@ -145,17 +145,13 @@ export type QuerySortValue = QuerySortDirection | QueryVectorSearch;
|
|
|
145
145
|
export type QuerySortByCount = {
|
|
146
146
|
$count: QuerySortDirection;
|
|
147
147
|
};
|
|
148
|
-
/** The fields of `E` a vector search can rank by. */
|
|
149
|
-
type VectorFieldKey<E> = {
|
|
150
|
-
[P in FieldKey<E>]: NonNullable<E[P]> extends readonly number[] ? P : never;
|
|
151
|
-
}[FieldKey<E>];
|
|
152
148
|
/**
|
|
153
149
|
* Ordering parents by the row of a to-many nearest a vector, per vector field: its distance is the
|
|
154
150
|
* smallest of theirs. Nothing to `$project`, since no one row of the parent's answers under it. Never
|
|
155
151
|
* where the target has no vector, since an empty map would admit any value at all.
|
|
156
152
|
*/
|
|
157
|
-
export type QuerySortByNearest<E> = [
|
|
158
|
-
[P in
|
|
153
|
+
export type QuerySortByNearest<E> = [FieldKeyOf<E, readonly number[]>] extends [never] ? never : {
|
|
154
|
+
[P in FieldKeyOf<E, readonly number[]>]?: QueryVectorQuery;
|
|
159
155
|
};
|
|
160
156
|
/**
|
|
161
157
|
* Ordering by relevance to the `$text` at the root of `$where`, in either direction as any key sorts. The
|
|
@@ -371,12 +367,6 @@ Pick<E, Exclude<ProjectedKeys<E, S, V, X, P>, PopulatedToMany<E, P>> & keyof E>
|
|
|
371
367
|
} : E;
|
|
372
368
|
/** The to-many relations a query populated, which come back as lists rather than as optional ones. */
|
|
373
369
|
type PopulatedToMany<E, P> = Extract<P, ToManyRelationKey<E>>;
|
|
374
|
-
/**
|
|
375
|
-
* stringified query.
|
|
376
|
-
*/
|
|
377
|
-
export type QueryStringified = {
|
|
378
|
-
[K in keyof Query<unknown>]?: string;
|
|
379
|
-
};
|
|
380
370
|
/** What upserting one row reports. `created` is only knowable for a single statement, so a batch has none. */
|
|
381
371
|
export type QueryUpsertOneResult<E> = {
|
|
382
372
|
readonly id?: WrittenId<E>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FieldKey, RelationKey, RelationTarget } from './entity.js';
|
|
1
|
+
import type { FieldKey, FieldKeyOf, RelationKey, RelationTarget } from './entity.js';
|
|
2
2
|
import type { QueryPager, QuerySelect, QuerySortDirection } from './query.js';
|
|
3
3
|
import type { QueryRaw } from './queryRaw.js';
|
|
4
4
|
import type { QueryWhere, QueryWhereFieldValue } from './queryWhere.js';
|
|
@@ -44,13 +44,6 @@ export declare function resolveAggregateOp(key: string): {
|
|
|
44
44
|
export type QueryFieldRef<E, F extends keyof E = FieldKey<E>> = ExactlyOne<Required<QuerySelect<E, F, true>>>;
|
|
45
45
|
/** The argument of an aggregate function: a field, or `'*'` (only meaningful for `COUNT(*)`). */
|
|
46
46
|
export type QueryAggregateArg<E> = QueryFieldRef<E> | '*';
|
|
47
|
-
/**
|
|
48
|
-
* Fields `SUM`/`AVG` can total. Restricted to numeric columns because totalling a text or date one is
|
|
49
|
-
* either an engine error or a coercion, and neither produces the value the signature promises.
|
|
50
|
-
*/
|
|
51
|
-
type NumericFieldKey<E> = {
|
|
52
|
-
readonly [K in FieldKey<E>]: [NonNullable<E[K]>] extends [number | bigint] ? K : never;
|
|
53
|
-
}[FieldKey<E>];
|
|
54
47
|
/** Every aggregate op, plain and DISTINCT-qualified. */
|
|
55
48
|
type AggregateOp = QueryAggregateOp | QueryAggregateDistinctOp;
|
|
56
49
|
/**
|
|
@@ -67,9 +60,10 @@ type AveragingOp = OpsOf<'$avg'>;
|
|
|
67
60
|
type TotallingOp = SummingOp | AveragingOp;
|
|
68
61
|
/**
|
|
69
62
|
* Every aggregate op mapped to the argument it accepts: `$count` a field or `'*'` (`COUNT(*)`),
|
|
70
|
-
* the totalling ops a numeric field,
|
|
63
|
+
* the totalling ops a numeric field, since totalling any other is an engine error or a coercion,
|
|
64
|
+
* and `$min`/`$max`/`$countDistinct` any field.
|
|
71
65
|
*/
|
|
72
|
-
type QueryAggregateArgMap<E> = Record<'$count', QueryAggregateArg<E>> & Record<TotallingOp, QueryFieldRef<E,
|
|
66
|
+
type QueryAggregateArgMap<E> = Record<'$count', QueryAggregateArg<E>> & Record<TotallingOp, QueryFieldRef<E, FieldKeyOf<E, number | bigint>>> & Record<Exclude<AggregateOp, '$count' | TotallingOp>, QueryFieldRef<E>>;
|
|
73
67
|
/**
|
|
74
68
|
* An aggregate over one field, exactly one op per entry: `{ $sum: { amount: true } }` is `SUM("amount")`,
|
|
75
69
|
* `{ $countDistinct: { id: true } }` is `COUNT(DISTINCT "id")`, and only `$count` takes `'*'`. Its own
|
package/dist/type/queryRaw.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export type QueryRawRenderOptions = {
|
|
|
15
15
|
* computed field's own, or the one whose schema is built. Absent where a statement renders SQL.
|
|
16
16
|
*/
|
|
17
17
|
entity?: Type<unknown>;
|
|
18
|
+
/**
|
|
19
|
+
* The `FROM` a set-based trigger's body reads its rows through, `FROM inserted` and the like, which a
|
|
20
|
+
* write in it names. Absent where the body reads `NEW` and `OLD` bare, and outside a trigger.
|
|
21
|
+
*/
|
|
22
|
+
rows?: string;
|
|
18
23
|
};
|
|
19
24
|
/** {@link QueryRawRenderOptions} as the callers along the way fill them in, every one still optional. */
|
|
20
25
|
export type QueryRawFnOptions = Partial<QueryRawRenderOptions>;
|
|
@@ -45,12 +50,21 @@ export declare class QueryRaw {
|
|
|
45
50
|
}
|
|
46
51
|
/**
|
|
47
52
|
* A field of an entity as SQL, read off `refs(Entity)` or a definition's refs: interpolated into `raw`, it
|
|
48
|
-
* renders as the field's column. Its `key` is how an index tells a column from an expression
|
|
53
|
+
* renders as the field's column. Its `key` is how an index tells a column from an expression, and `V`,
|
|
54
|
+
* the field's type, is what a value slot checks it against: see {@link RawFor}.
|
|
49
55
|
*/
|
|
50
|
-
export declare class ColumnRef<K extends string = string> extends QueryRaw {
|
|
56
|
+
export declare class ColumnRef<K extends string = string, V = unknown> extends QueryRaw {
|
|
51
57
|
readonly key: K;
|
|
58
|
+
readonly __value?: V;
|
|
52
59
|
constructor(key: K, value: QueryRawFn);
|
|
53
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* SQL where a value of type `V` goes: bare SQL, whose type is its author's to know, or a ref to a column
|
|
63
|
+
* holding one, nullability aside. `Raw` is what the transport carries, so the wire's `never` stays one.
|
|
64
|
+
*/
|
|
65
|
+
export type RawFor<Raw, V> = Raw & {
|
|
66
|
+
readonly __value?: V | null;
|
|
67
|
+
};
|
|
54
68
|
/**
|
|
55
69
|
* A relation aggregate as SQL, read off a `computed` field's refs: `(user) => user.resources.count()`.
|
|
56
70
|
* It renders as the correlated subquery a `$count` reads, so a field holding one is read, filtered and
|
|
@@ -63,7 +77,7 @@ export declare class ColumnRef<K extends string = string> extends QueryRaw {
|
|
|
63
77
|
export declare class RelationAggregate<V = unknown, Storable extends boolean = boolean> extends QueryRaw {
|
|
64
78
|
/** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */
|
|
65
79
|
readonly spec: RelationAggregateSpec;
|
|
66
|
-
|
|
80
|
+
readonly __value?: V;
|
|
67
81
|
private readonly __storable;
|
|
68
82
|
constructor(
|
|
69
83
|
/** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */
|
package/dist/type/queryRaw.js
CHANGED
|
@@ -29,7 +29,8 @@ export class QueryRaw {
|
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* A field of an entity as SQL, read off `refs(Entity)` or a definition's refs: interpolated into `raw`, it
|
|
32
|
-
* renders as the field's column. Its `key` is how an index tells a column from an expression
|
|
32
|
+
* renders as the field's column. Its `key` is how an index tells a column from an expression, and `V`,
|
|
33
|
+
* the field's type, is what a value slot checks it against: see {@link RawFor}.
|
|
33
34
|
*/
|
|
34
35
|
export class ColumnRef extends QueryRaw {
|
|
35
36
|
key;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FieldKey, JsonFieldPaths, JsonFieldPathValue, RelationKey, RelationTarget } from './entity.js';
|
|
2
2
|
import type { QuerySelect } from './query.js';
|
|
3
|
-
import type { QueryRaw } from './queryRaw.js';
|
|
3
|
+
import type { QueryRaw, RawFor } from './queryRaw.js';
|
|
4
4
|
import type { AtLeastOne, ExpandScalar, IsMany, QueryComparableScalar, Scalar } from './utility.js';
|
|
5
5
|
import type { QueryVectorQuery } from './vector.js';
|
|
6
6
|
/**
|
|
@@ -136,7 +136,7 @@ export type QueryWhereFieldOperatorMap<T, Raw = QueryRaw> = {
|
|
|
136
136
|
* whether a value is between two values (inclusive). Shorthand for $gte + $lte.
|
|
137
137
|
* @example { age: { $between: [18, 65] } }
|
|
138
138
|
*/
|
|
139
|
-
$between?: [ExpandScalar<T>, ExpandScalar<T>];
|
|
139
|
+
$between?: readonly [ExpandScalar<T>, ExpandScalar<T>];
|
|
140
140
|
/**
|
|
141
141
|
* whether a string begins with the given string (case sensitive).
|
|
142
142
|
*/
|
|
@@ -176,11 +176,11 @@ export type QueryWhereFieldOperatorMap<T, Raw = QueryRaw> = {
|
|
|
176
176
|
/**
|
|
177
177
|
* whether a value matches any of the given values.
|
|
178
178
|
*/
|
|
179
|
-
$in?: ExpandScalar<T>[];
|
|
179
|
+
$in?: readonly ExpandScalar<T>[];
|
|
180
180
|
/**
|
|
181
181
|
* whether a value does not match any of the given values.
|
|
182
182
|
*/
|
|
183
|
-
$nin?: ExpandScalar<T>[];
|
|
183
|
+
$nin?: readonly ExpandScalar<T>[];
|
|
184
184
|
/**
|
|
185
185
|
* whether a value is null.
|
|
186
186
|
* @example { deletedAt: { $isNull: true } }
|
|
@@ -195,7 +195,7 @@ export type QueryWhereFieldOperatorMap<T, Raw = QueryRaw> = {
|
|
|
195
195
|
* whether an array contains all the specified values.
|
|
196
196
|
* @example { tags: { $all: ['typescript', 'orm'] } }
|
|
197
197
|
*/
|
|
198
|
-
$all?: unknown extends T ? unknown[] : NonNullable<T> extends readonly (infer U)[] ? ExpandScalar<U>[] : never;
|
|
198
|
+
$all?: unknown extends T ? readonly unknown[] : NonNullable<T> extends readonly (infer U)[] ? readonly ExpandScalar<U>[] : never;
|
|
199
199
|
/** whether an array has the given length, or one in range: `{ roles: { $size: { $gte: 2 } } }`. */
|
|
200
200
|
$size?: number | QuerySizeComparisonOps;
|
|
201
201
|
/**
|
|
@@ -284,9 +284,9 @@ type IsUntypedColumn<T> = [Scalar] extends [NonNullable<T>] ? true : false;
|
|
|
284
284
|
* A field's filter value: the value, `null` where it is optional, a list as an implicit `$in` (not on
|
|
285
285
|
* an array field, where it would be ambiguous), or an operator map.
|
|
286
286
|
*/
|
|
287
|
-
export type QueryWhereFieldValue<T, Raw = QueryRaw> = T | (undefined extends T ? null : never) | (IsMany<T> extends true ? never : T[]) | QueryWhereFieldOperators<T, Raw> | Raw
|
|
287
|
+
export type QueryWhereFieldValue<T, Raw = QueryRaw> = T | (undefined extends T ? null : never) | (IsMany<T> extends true ? never : readonly T[]) | QueryWhereFieldOperators<T, Raw> | RawFor<Raw, T>;
|
|
288
288
|
/**
|
|
289
289
|
* query filter array - the value every {@link QueryGroupOp} takes.
|
|
290
290
|
*/
|
|
291
|
-
export type QueryWhereArray<E, Raw = QueryRaw> = (QueryWhere<E, Raw> | Raw)[];
|
|
291
|
+
export type QueryWhereArray<E, Raw = QueryRaw> = readonly (QueryWhere<E, Raw> | Raw)[];
|
|
292
292
|
export {};
|
|
@@ -65,11 +65,11 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
|
|
|
65
65
|
* Ids are exact everywhere but MySQL, which infers them from its header and reports `undefined` rather
|
|
66
66
|
* than a guess where it cannot: a batch naming some keys, or a key that is not `AUTO_INCREMENT`.
|
|
67
67
|
*/
|
|
68
|
-
insertMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
68
|
+
insertMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
69
69
|
/** Insert or update a record by its conflict paths; resolves to its id and whether it was created. */
|
|
70
70
|
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>): Promise<QueryUpsertOneResult<E>>;
|
|
71
71
|
/** Insert or update records by their conflict paths; resolves to their ids in payload order. */
|
|
72
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
72
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: readonly EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
73
73
|
/**
|
|
74
74
|
* insert or update a record.
|
|
75
75
|
* @param entity the entity to persist on
|
|
@@ -83,7 +83,7 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
|
|
|
83
83
|
* @param payload the data to be persisted
|
|
84
84
|
* @return the IDs
|
|
85
85
|
*/
|
|
86
|
-
saveMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
86
|
+
saveMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
87
87
|
/**
|
|
88
88
|
* Restore soft-deleted records (sets the soft-delete field back to `null`). Throws if the
|
|
89
89
|
* entity has no soft-delete field.
|
package/dist/type/vector.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { VectorCast } from '../dialect/vectorCast.js';
|
|
2
2
|
import type { IndexType } from '../schema/types.js';
|
|
3
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
3
4
|
/**
|
|
4
5
|
* A vector search's metric: `cosine` (the default), `l2`, `inner` product or `l1`. No hamming: every
|
|
5
6
|
* engine's takes a bit vector, which no field type maps to.
|
|
@@ -37,7 +38,7 @@ export type VectorMetric = {
|
|
|
37
38
|
readonly index?: string;
|
|
38
39
|
};
|
|
39
40
|
/** The error every dialect throws for a metric it lacks. */
|
|
40
|
-
export declare function unsupportedVectorMetric(dialectName: string, distance: VectorDistance, indexName?: string):
|
|
41
|
+
export declare function unsupportedVectorMetric(dialectName: string, distance: VectorDistance, indexName?: string): UqlUsageError;
|
|
41
42
|
/**
|
|
42
43
|
* Vector-specific tuning options shared by `@Index` decorator, entity metadata, and migration schema.
|
|
43
44
|
*/
|
package/dist/type/vector.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
1
2
|
/** The keys that describe the search rather than bound it, so `$near`'s bounds are what is left. */
|
|
2
3
|
export const VECTOR_QUERY_KEYS = ['$vector', '$distance'];
|
|
3
4
|
/** The error every dialect throws for a metric it lacks. */
|
|
4
5
|
export function unsupportedVectorMetric(dialectName, distance, indexName) {
|
|
5
6
|
const where = indexName === undefined ? '' : ` (index "${indexName}")`;
|
|
6
|
-
return new
|
|
7
|
+
return new UqlUsageError(`${dialectName} does not support vector distance metric: ${distance}${where}`);
|
|
7
8
|
}
|
|
8
9
|
/** The metric a search or an index measures by where nothing names one: every engine with vectors has it. */
|
|
9
10
|
export const DEFAULT_VECTOR_DISTANCE = 'cosine';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IndexType } from '../schema/types.js';
|
|
2
|
-
import { type AggregateCall, type CascadeType, type EntityData, type EntityId, type EntityIndexMeta, type EntityMeta, type FieldKey, type FieldOptions, type FieldUpdateOp, type JsonUpdateOp, type OnFieldCallback, type Query, type QueryAggMap, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySearch, type QuerySelect, type QuerySelectValue, type QuerySortDirection, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorQuery, type QueryVectorSearch, type QueryWhere, type RelationKey, type UpdatePayload, type VectorDistance } from '../type/index.js';
|
|
2
|
+
import { type AggregateCall, type CascadeType, type EntityData, type EntityId, type EntityIndexMeta, type EntityMeta, type FieldKey, type FieldOptions, type FieldUpdateOp, type JsonUpdateOp, type OnFieldCallback, type Query, type QueryAggMap, type QueryConflictPaths, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySearch, type QuerySelect, type QuerySelectValue, type QuerySortDirection, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorQuery, type QueryVectorSearch, type QueryWhere, type QueryWhereArray, type RelationKey, type UpdatePayload, type VectorDistance } from '../type/index.js';
|
|
3
3
|
export type CallbackKey = keyof Pick<FieldOptions, 'onInsert' | 'onUpdate'>;
|
|
4
4
|
/** The keys of `payload` a write persists as columns. */
|
|
5
5
|
export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: EntityData<E> | UpdatePayload<E>, callbackKey: CallbackKey): FieldKey<E>[];
|
|
@@ -50,10 +50,18 @@ export declare function isCascadable(action: CascadeType, configuration?: boolea
|
|
|
50
50
|
*/
|
|
51
51
|
export declare function isPagedQuery<E>(q: QuerySearch<E>): boolean;
|
|
52
52
|
/**
|
|
53
|
-
* `
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*/
|
|
53
|
+
* Each of `keys` switched on, as a `$select` or conflict paths name them. This and the `where*` builders
|
|
54
|
+
* below hold the casts a statement built in generic code needs: a key read at run time is no key of
|
|
55
|
+
* these maps to the compiler, whose values it works out per entity.
|
|
56
|
+
*/
|
|
57
|
+
export declare function keySet<E>(keys: readonly FieldKey<E>[]): QueryConflictPaths<E>;
|
|
58
|
+
/** `where`, or no `$where`, with `key` held to `value` as well: spread, so the two `AND`. */
|
|
59
|
+
export declare function whereWith<E>(key: FieldKey<E>, value: unknown, where?: QueryWhere<E>): QueryWhere<E>;
|
|
60
|
+
/** The `$where` holding each of `keys` to what `valueOf` reads for it. */
|
|
61
|
+
export declare function whereEach<E>(keys: readonly FieldKey<E>[], valueOf: (key: FieldKey<E>) => unknown): QueryWhere<E>;
|
|
62
|
+
/** The `$where` any one of `clauses` satisfies. */
|
|
63
|
+
export declare function whereAnyOf<E>(clauses: QueryWhereArray<E>): QueryWhere<E>;
|
|
64
|
+
/** `q` selecting nothing but the id: what a write hands its backend's own read builder to settle the rows it will name. */
|
|
57
65
|
export declare function idOnlyQuery<E>(meta: EntityMeta<E>, q: QuerySearch<E>): Query<E>;
|
|
58
66
|
/**
|
|
59
67
|
* The map form of a `$select` value, or `undefined` for the raw-array form. Centralizes the one
|
|
@@ -2,7 +2,7 @@ import { getContext, UqlSecurityError } from '../context/context.js';
|
|
|
2
2
|
import { soleIdOf } from '../entity/metadata/definition.js';
|
|
3
3
|
import { QueryRaw, resolveAggregateOp, SOFT_DELETE_FILTER, } from '../type/index.js';
|
|
4
4
|
import { DEFAULT_VECTOR_DISTANCE, VECTOR_INDEX_TYPES } from '../type/vector.js';
|
|
5
|
-
import {
|
|
5
|
+
import { defaultReadKeys, fieldKeys, isDatabaseWritten } from './field.util.js';
|
|
6
6
|
import { entityName, getKeys, hasKeys, isOperatorObject, isScalarId, isRecord, isWhereMap, someKey, } from './object.util.js';
|
|
7
7
|
import { UqlUsageError } from './uqlError.js';
|
|
8
8
|
/** The keys of `payload` a write persists as columns. */
|
|
@@ -50,12 +50,7 @@ export function getInsertFieldKeys(meta, payloads) {
|
|
|
50
50
|
for (const record of payloads) {
|
|
51
51
|
addInsertFieldKeys(meta, record, seen, keys);
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
if (meta.fields[key].onInsert !== undefined && !seen.has(key)) {
|
|
55
|
-
keys.push(key);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return keys;
|
|
53
|
+
return [...keys, ...fieldKeys(meta, (field) => field.onInsert !== undefined).filter((key) => !seen.has(key))];
|
|
59
54
|
}
|
|
60
55
|
export function getFieldCallbackValue(val) {
|
|
61
56
|
return typeof val === 'function' ? val() : val;
|
|
@@ -72,7 +67,7 @@ export function fillOnFields(meta, payload, callbackKey) {
|
|
|
72
67
|
const payloads = Array.isArray(payload) ? payload : [payload];
|
|
73
68
|
// By presence, not truthiness, as `addInsertFieldKeys` above reads it: `onInsert: 0` and `onInsert: ''`
|
|
74
69
|
// are values a caller meant, and a falsy one was silently never filled.
|
|
75
|
-
const keys =
|
|
70
|
+
const keys = fieldKeys(meta, (field) => field[callbackKey] !== undefined);
|
|
76
71
|
if (keys.length === 0) {
|
|
77
72
|
return payloads;
|
|
78
73
|
}
|
|
@@ -120,12 +115,28 @@ export function isPagedQuery(q) {
|
|
|
120
115
|
return q.$sort !== undefined || q.$limit !== undefined || q.$skip !== undefined;
|
|
121
116
|
}
|
|
122
117
|
/**
|
|
123
|
-
* `
|
|
124
|
-
*
|
|
125
|
-
*
|
|
118
|
+
* Each of `keys` switched on, as a `$select` or conflict paths name them. This and the `where*` builders
|
|
119
|
+
* below hold the casts a statement built in generic code needs: a key read at run time is no key of
|
|
120
|
+
* these maps to the compiler, whose values it works out per entity.
|
|
126
121
|
*/
|
|
122
|
+
export function keySet(keys) {
|
|
123
|
+
return Object.fromEntries(keys.map((key) => [key, true]));
|
|
124
|
+
}
|
|
125
|
+
/** `where`, or no `$where`, with `key` held to `value` as well: spread, so the two `AND`. */
|
|
126
|
+
export function whereWith(key, value, where) {
|
|
127
|
+
return { ...where, [key]: value };
|
|
128
|
+
}
|
|
129
|
+
/** The `$where` holding each of `keys` to what `valueOf` reads for it. */
|
|
130
|
+
export function whereEach(keys, valueOf) {
|
|
131
|
+
return Object.fromEntries(keys.map((key) => [key, valueOf(key)]));
|
|
132
|
+
}
|
|
133
|
+
/** The `$where` any one of `clauses` satisfies. */
|
|
134
|
+
export function whereAnyOf(clauses) {
|
|
135
|
+
return { $or: clauses };
|
|
136
|
+
}
|
|
137
|
+
/** `q` selecting nothing but the id: what a write hands its backend's own read builder to settle the rows it will name. */
|
|
127
138
|
export function idOnlyQuery(meta, q) {
|
|
128
|
-
return { ...q, $select:
|
|
139
|
+
return { ...q, $select: keySet(meta.ids) };
|
|
129
140
|
}
|
|
130
141
|
/**
|
|
131
142
|
* The map form of a `$select` value, or `undefined` for the raw-array form. Centralizes the one
|
|
@@ -165,7 +176,7 @@ export function normalizeScalarFieldSelection(meta, select, exclude) {
|
|
|
165
176
|
}
|
|
166
177
|
}
|
|
167
178
|
}
|
|
168
|
-
const allFields =
|
|
179
|
+
const allFields = defaultReadKeys(meta);
|
|
169
180
|
if (!excludedFields) {
|
|
170
181
|
return allFields;
|
|
171
182
|
}
|
|
@@ -217,10 +228,7 @@ export function findVectorIndex(meta, key) {
|
|
|
217
228
|
* serves no other, else cosine. The one fallback every engine resolves, so none can rank by another.
|
|
218
229
|
*/
|
|
219
230
|
export function vectorDistanceOf(meta, key, search) {
|
|
220
|
-
return (search.$distance ??
|
|
221
|
-
meta.fields[key]?.distance ??
|
|
222
|
-
findVectorIndex(meta, key)?.distance ??
|
|
223
|
-
DEFAULT_VECTOR_DISTANCE);
|
|
231
|
+
return (search.$distance ?? meta.fields[key]?.distance ?? findVectorIndex(meta, key)?.distance ?? DEFAULT_VECTOR_DISTANCE);
|
|
224
232
|
}
|
|
225
233
|
/**
|
|
226
234
|
* Whether a `$where` filters by vector distance anywhere in its tree, `$and`/`$or`/`$not` included.
|
|
@@ -271,7 +279,7 @@ export function fieldUpdateOf(key, value) {
|
|
|
271
279
|
*/
|
|
272
280
|
export function whereIds(meta, ids) {
|
|
273
281
|
if (Array.isArray(ids) ? ids.every(isScalarId) : isScalarId(ids)) {
|
|
274
|
-
return
|
|
282
|
+
return whereWith(soleIdOf(meta, 'addressing by a bare id value'), ids);
|
|
275
283
|
}
|
|
276
284
|
return (Array.isArray(ids) ? { $or: ids } : ids);
|
|
277
285
|
}
|
|
@@ -492,11 +500,11 @@ export function fulltextWeights(index) {
|
|
|
492
500
|
return undefined;
|
|
493
501
|
}
|
|
494
502
|
if (index.type !== 'fulltext') {
|
|
495
|
-
throw new
|
|
503
|
+
throw new UqlUsageError(`a column weight ranks a fulltext index, and this one is ${index.type ?? 'btree'}`);
|
|
496
504
|
}
|
|
497
505
|
const weights = index.entries.map(({ weight = 1 }) => {
|
|
498
506
|
if (!Number.isInteger(weight) || weight < 1 || weight > MAX_TEXT_WEIGHT) {
|
|
499
|
-
throw new
|
|
507
|
+
throw new UqlUsageError(`a column weight is a whole number from 1 to ${MAX_TEXT_WEIGHT}, not ${weight}`);
|
|
500
508
|
}
|
|
501
509
|
return weight;
|
|
502
510
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ColumnFamily, type EntityMeta, type FieldKey, type FieldOptions, type StampEvent, type RelationAggregateSpec } from '../type/index.js';
|
|
1
|
+
import { type ColumnFamily, type EntityMeta, type FieldKey, type FieldMeta, 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
|
/**
|
|
@@ -39,11 +39,11 @@ export declare function isSoleIdField<E>(meta: EntityMeta<E>, field: FieldOption
|
|
|
39
39
|
* states its width. The one answer the create statement and the diff both read.
|
|
40
40
|
*/
|
|
41
41
|
export declare function isAutoIncrement(field: FieldOptions, isPrimaryKey: boolean): boolean;
|
|
42
|
+
/** The fields `meta` declares whose options `pick` accepts, in declaration order, each as the entity's own key. */
|
|
43
|
+
export declare function fieldKeys<E>(meta: EntityMeta<E>, pick: (field: FieldMeta) => unknown): FieldKey<E>[];
|
|
42
44
|
/**
|
|
43
45
|
* The fields a read answers with where it names none. A relation aggregate is left out unless it asks
|
|
44
46
|
* for `eager: true`: it reads the related rows, which is what a relation does, and a relation is loaded
|
|
45
47
|
* only when a query asks for it. Naming one in `$select` reads it, whatever the default.
|
|
46
48
|
*/
|
|
47
|
-
export declare function
|
|
48
|
-
[K in FieldKey<E>]?: FieldOptions;
|
|
49
|
-
}): FieldKey<E>[];
|
|
49
|
+
export declare function defaultReadKeys<E>(meta: EntityMeta<E>): FieldKey<E>[];
|
package/dist/util/field.util.js
CHANGED
|
@@ -83,11 +83,19 @@ export function isAutoIncrement(field, isPrimaryKey) {
|
|
|
83
83
|
return field.autoIncrement;
|
|
84
84
|
return isPrimaryKey && columnFamily(field.type) === 'numeric' && !field.onInsert && !field.references;
|
|
85
85
|
}
|
|
86
|
+
/** The fields `meta` declares whose options `pick` accepts, in declaration order, each as the entity's own key. */
|
|
87
|
+
export function fieldKeys(meta, pick) {
|
|
88
|
+
const fields = meta.fields;
|
|
89
|
+
return getKeys(fields).filter((key) => {
|
|
90
|
+
const field = fields[key];
|
|
91
|
+
return field !== undefined && Boolean(pick(field));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
86
94
|
/**
|
|
87
95
|
* The fields a read answers with where it names none. A relation aggregate is left out unless it asks
|
|
88
96
|
* for `eager: true`: it reads the related rows, which is what a relation does, and a relation is loaded
|
|
89
97
|
* only when a query asks for it. Naming one in `$select` reads it, whatever the default.
|
|
90
98
|
*/
|
|
91
|
-
export function
|
|
92
|
-
return
|
|
99
|
+
export function defaultReadKeys(meta) {
|
|
100
|
+
return fieldKeys(meta, (field) => field.eager ?? !aggregateOf(field));
|
|
93
101
|
}
|
package/dist/util/hook.util.d.ts
CHANGED
|
@@ -11,4 +11,4 @@ export type HookContext = {
|
|
|
11
11
|
* Hooks are invoked with `this` bound to the payload via `call`,
|
|
12
12
|
* so mutations go directly to the original object.
|
|
13
13
|
*/
|
|
14
|
-
export declare function runHooks<E extends object>(entity: Type<E>, event: HookEvent, payloads: E[], ctx: HookContext): Promise<void>;
|
|
14
|
+
export declare function runHooks<E extends object>(entity: Type<E>, event: HookEvent, payloads: readonly E[], ctx: HookContext): Promise<void>;
|
package/dist/util/hook.util.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getMeta } from '../entity/index.js';
|
|
2
|
+
import { UqlUsageError } from './uqlError.js';
|
|
2
3
|
/**
|
|
3
4
|
* Run all registered hooks for the given event on each payload.
|
|
4
5
|
* Hooks are invoked with `this` bound to the payload via `call`,
|
|
@@ -9,9 +10,15 @@ export async function runHooks(entity, event, payloads, ctx) {
|
|
|
9
10
|
const registrations = meta.hooks?.[event];
|
|
10
11
|
if (!registrations?.length)
|
|
11
12
|
return;
|
|
13
|
+
// A prototype is typed `any`, so what is read off it is typed here, where it enters.
|
|
14
|
+
const prototype = entity.prototype;
|
|
12
15
|
for (const payload of payloads) {
|
|
13
16
|
for (const { methodName } of registrations) {
|
|
14
|
-
const
|
|
17
|
+
const method = prototype[methodName];
|
|
18
|
+
if (typeof method !== 'function') {
|
|
19
|
+
throw new UqlUsageError(`'${entity.name}' runs '${methodName}' on ${event}, but has no such method`);
|
|
20
|
+
}
|
|
21
|
+
const result = method.call(payload, ctx);
|
|
15
22
|
if (result instanceof Promise)
|
|
16
23
|
await result;
|
|
17
24
|
}
|
package/dist/util/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './ddlExpression.util.js';
|
|
|
7
7
|
export * from './logger.js';
|
|
8
8
|
export * from './object.util.js';
|
|
9
9
|
export * from './raw.js';
|
|
10
|
+
export * from './triggerWrite.js';
|
|
10
11
|
export * from './rowKey.util.js';
|
|
11
12
|
export * from './relationQuery.util.js';
|
|
12
13
|
export * from './sql.util.js';
|