uql-orm 0.68.0 → 0.69.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.
Files changed (37) hide show
  1. package/dist/browser/http/http.js +5 -8
  2. package/dist/browser/querier/httpQuerier.d.ts +9 -9
  3. package/dist/browser/uql-browser.min.js +2 -2
  4. package/dist/browser/uql-browser.min.js.map +9 -8
  5. package/dist/d1/d1Querier.d.ts +5 -5
  6. package/dist/d1/d1QuerierPool.d.ts +3 -3
  7. package/dist/dialect/abstractSqlDialect.d.ts +31 -4
  8. package/dist/dialect/abstractSqlDialect.js +99 -21
  9. package/dist/dialect/aliases.d.ts +8 -0
  10. package/dist/dialect/aliases.js +8 -0
  11. package/dist/entity/decorator/members.d.ts +29 -1
  12. package/dist/entity/decorator/members.js +0 -5
  13. package/dist/entity/metadata/definition.js +10 -3
  14. package/dist/http/query.d.ts +10 -2
  15. package/dist/http/query.js +26 -1
  16. package/dist/migrate/introspection/postgresIntrospector.d.ts +6 -0
  17. package/dist/migrate/introspection/postgresIntrospector.js +7 -1
  18. package/dist/mongo/mongoDialect.d.ts +29 -3
  19. package/dist/mongo/mongoDialect.js +111 -14
  20. package/dist/mongo/mongodbQuerier.d.ts +2 -2
  21. package/dist/mongo/mongodbQuerier.js +4 -3
  22. package/dist/type/dialect.d.ts +29 -2
  23. package/dist/type/entity.d.ts +94 -7
  24. package/dist/type/query.d.ts +30 -34
  25. package/dist/type/queryRaw.d.ts +19 -1
  26. package/dist/type/queryRaw.js +18 -0
  27. package/dist/type/queryWhere.d.ts +20 -20
  28. package/dist/type/universalQuerier.d.ts +10 -10
  29. package/dist/type/wire.d.ts +9 -0
  30. package/dist/util/dialect.util.js +2 -2
  31. package/dist/util/field.util.d.ts +15 -1
  32. package/dist/util/field.util.js +18 -1
  33. package/dist/util/object.util.d.ts +1 -4
  34. package/dist/util/object.util.js +0 -3
  35. package/dist/util/raw.d.ts +2 -2
  36. package/dist/util/raw.js +29 -2
  37. package/package.json +2 -2
@@ -30,3 +30,21 @@ export class ColumnRef extends QueryRaw {
30
30
  this.key = key;
31
31
  }
32
32
  }
33
+ /**
34
+ * A relation aggregate as SQL, read off a `computed` field's refs: `(user) => user.resources.count()`.
35
+ * It renders as the correlated subquery a `$count` reads, so a field holding one is read, filtered and
36
+ * sorted like any other.
37
+ *
38
+ * `V` is the value it reads and `Storable` whether a trigger could keep it, both carried in phantom
39
+ * fields so the aggregate a field declares decides the property's type and refuses `stored: true` on
40
+ * one no delta can maintain.
41
+ */
42
+ export class RelationAggregate extends QueryRaw {
43
+ spec;
44
+ constructor(
45
+ /** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */
46
+ spec, value) {
47
+ super(value);
48
+ this.spec = spec;
49
+ }
50
+ }
@@ -26,10 +26,10 @@ export type QueryTextSearchOptions<E> = {
26
26
  * entity's keys so each stays linked for rename. An object and nothing else, so a wrong value is
27
27
  * reported on its key: ids go through `{ id: 1 }` or the by-id methods.
28
28
  */
29
- export type QueryWhere<E, K extends keyof E = FieldKey<E> | RelationKey<E>> = QueryWhereRootOperator<E> & {
30
- [P in K]?: P extends FieldKey<E> ? QueryWhereFieldValue<E[P]> : QueryWhere<RelationTarget<E[P]>> | QueryRelationSizeFilter;
29
+ export type QueryWhere<E, Raw = QueryRaw, K extends keyof E = FieldKey<E> | RelationKey<E>> = QueryWhereRootOperator<E, Raw> & {
30
+ [P in K]?: P extends FieldKey<E> ? QueryWhereFieldValue<E[P], Raw> : QueryWhere<RelationTarget<E[P]>, Raw> | QueryRelationSizeFilter;
31
31
  } & ([JsonFieldPaths<E>] extends [never] ? unknown : {
32
- [P in JsonFieldPaths<E>]?: QueryWhereFieldValue<JsonFieldPathValue<E, P>>;
32
+ [P in JsonFieldPaths<E>]?: QueryWhereFieldValue<JsonFieldPathValue<E, P>, Raw>;
33
33
  });
34
34
  /**
35
35
  * Filter a to-many relation by its row count.
@@ -39,24 +39,24 @@ export type QueryWhere<E, K extends keyof E = FieldKey<E> | RelationKey<E>> = Qu
39
39
  export type QueryRelationSizeFilter = {
40
40
  readonly $size: number | QuerySizeComparisonOps;
41
41
  };
42
- export type QueryWhereRootOperator<E> = {
42
+ export type QueryWhereRootOperator<E, Raw = QueryRaw> = {
43
43
  /**
44
44
  * joins query clauses with a logical `AND`, returns records that match all the clauses.
45
45
  */
46
- $and?: QueryWhereArray<E>;
46
+ $and?: QueryWhereArray<E, Raw>;
47
47
  /**
48
48
  * joins query clauses with a logical `OR`, returns records that match any of the clauses.
49
49
  */
50
- $or?: QueryWhereArray<E>;
50
+ $or?: QueryWhereArray<E, Raw>;
51
51
  /**
52
52
  * joins query clauses with a logical `AND`, returns records that do not match all the clauses.
53
53
  * @see {@link QueryWhereFieldOperatorMap.$not} for per-field negation.
54
54
  */
55
- $not?: QueryWhereArray<E>;
55
+ $not?: QueryWhereArray<E, Raw>;
56
56
  /**
57
57
  * joins query clauses with a logical `OR`, returns records that do not match any of the clauses.
58
58
  */
59
- $nor?: QueryWhereArray<E>;
59
+ $nor?: QueryWhereArray<E, Raw>;
60
60
  /**
61
61
  * whether the specified fields match against a full-text search of the given string.
62
62
  */
@@ -64,11 +64,11 @@ export type QueryWhereRootOperator<E> = {
64
64
  /**
65
65
  * whether the record exists in the given sub-query.
66
66
  */
67
- $exists?: QueryRaw;
67
+ $exists?: Raw;
68
68
  /**
69
69
  * whether the record does not exists in the given sub-query.
70
70
  */
71
- $nexists?: QueryRaw;
71
+ $nexists?: Raw;
72
72
  };
73
73
  /**
74
74
  * Per-field negation operators. `Pick`'s constraint ties this back to
@@ -100,7 +100,7 @@ export type QuerySizeComparisonOps = {
100
100
  export type QueryVectorNear = QueryVectorQuery & {
101
101
  [K in QueryOrderedOp]?: NonNullable<QueryWhereFieldOperatorMap<number>[K]>;
102
102
  };
103
- export type QueryWhereFieldOperatorMap<T> = {
103
+ export type QueryWhereFieldOperatorMap<T, Raw = QueryRaw> = {
104
104
  /**
105
105
  * whether a value is equal to the given value.
106
106
  */
@@ -113,7 +113,7 @@ export type QueryWhereFieldOperatorMap<T> = {
113
113
  * negates the given comparison for a single field.
114
114
  * @see {@link QueryWhereRootOperator.$not} for root-level clause negation.
115
115
  */
116
- $not?: QueryWhereFieldValue<T>;
116
+ $not?: QueryWhereFieldValue<T, Raw>;
117
117
  /**
118
118
  * whether a value is less than the given value.
119
119
  */
@@ -202,7 +202,7 @@ export type QueryWhereFieldOperatorMap<T> = {
202
202
  * @example { addresses: { $elemMatch: { city: 'NYC', zip: '10001' } } }
203
203
  * @example { addresses: { $elemMatch: { city: { $like: 'New%' } } } }
204
204
  */
205
- $elemMatch?: unknown extends T ? QueryWhereElemMatch<unknown> : NonNullable<T> extends readonly (infer U)[] ? QueryWhereElemMatch<U> : never;
205
+ $elemMatch?: unknown extends T ? QueryWhereElemMatch<unknown, Raw> : NonNullable<T> extends readonly (infer U)[] ? QueryWhereElemMatch<U, Raw> : never;
206
206
  /**
207
207
  * whether a vector is within a given distance of the query vector. `$sort` ranks by distance;
208
208
  * this filters by it, so "the closest ten" and "everything closer than 0.35" are separate asks.
@@ -216,10 +216,10 @@ export type QueryWhereFieldOperatorMap<T> = {
216
216
  * field comparison. An untyped element (`unknown`) accepts any keys but still requires the
217
217
  * object-of-conditions shape (a bare scalar is rejected).
218
218
  */
219
- export type QueryWhereElemMatch<U> = unknown extends U ? {
220
- [key: string]: QueryWhereFieldValue<unknown> | undefined;
221
- } : NonNullable<U> extends Scalar ? QueryWhereFieldOperators<NonNullable<U>> : {
222
- [K in keyof NonNullable<U>]?: QueryWhereFieldValue<NonNullable<U>[K]>;
219
+ export type QueryWhereElemMatch<U, Raw = QueryRaw> = unknown extends U ? {
220
+ [key: string]: QueryWhereFieldValue<unknown, Raw> | undefined;
221
+ } : NonNullable<U> extends Scalar ? QueryWhereFieldOperators<NonNullable<U>, Raw> : {
222
+ [K in keyof NonNullable<U>]?: QueryWhereFieldValue<NonNullable<U>[K], Raw>;
223
223
  };
224
224
  /**
225
225
  * Simple relational comparison operators. `Pick`'s constraint ties this back to
@@ -269,7 +269,7 @@ type QueryAllowedOp<T> = QueryCommonOp | ([NonNullable<T>] extends [QueryCompara
269
269
  * The operators a field of type `T` takes. `unknown`, and a column typed as every scalar at once (a
270
270
  * runtime-defined entity), take all of them, since nothing narrows what they hold.
271
271
  */
272
- export type QueryWhereFieldOperators<T> = unknown extends T ? QueryWhereFieldOperatorMap<T> : IsUntypedColumn<T> extends true ? QueryWhereFieldOperatorMap<T> : Pick<QueryWhereFieldOperatorMap<T>, QueryAllowedOp<T>>;
272
+ export type QueryWhereFieldOperators<T, Raw = QueryRaw> = unknown extends T ? QueryWhereFieldOperatorMap<T, Raw> : IsUntypedColumn<T> extends true ? QueryWhereFieldOperatorMap<T, Raw> : Pick<QueryWhereFieldOperatorMap<T, Raw>, QueryAllowedOp<T>>;
273
273
  /**
274
274
  * Whether a column admits every scalar at once, which is what an entity keyed by an index signature
275
275
  * says about all of its columns. `Scalar` is the yardstick rather than a parameter: the question is
@@ -280,9 +280,9 @@ type IsUntypedColumn<T> = [Scalar] extends [NonNullable<T>] ? true : false;
280
280
  * A field's filter value: the value, `null` where it is optional, a list as an implicit `$in` (not on
281
281
  * an array field, where it would be ambiguous), or an operator map.
282
282
  */
283
- export type QueryWhereFieldValue<T> = T | (undefined extends T ? null : never) | (IsMany<T> extends true ? never : T[]) | QueryWhereFieldOperators<T> | QueryRaw;
283
+ export type QueryWhereFieldValue<T, Raw = QueryRaw> = T | (undefined extends T ? null : never) | (IsMany<T> extends true ? never : T[]) | QueryWhereFieldOperators<T, Raw> | Raw;
284
284
  /**
285
285
  * query filter array - the value every {@link QueryGroupOp} takes.
286
286
  */
287
- export type QueryWhereArray<E> = (QueryWhere<E> | QueryRaw)[];
287
+ export type QueryWhereArray<E, Raw = QueryRaw> = (QueryWhere<E, Raw> | Raw)[];
288
288
  export {};
@@ -2,38 +2,38 @@ import type { EntityData, EntityId, FieldKey, RelationKey, UpdatePayload, Writte
2
2
  import type { QueryConflictPaths, QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryUpsertOneResult, QueryUpsertManyResult } from './query.js';
3
3
  import type { QueryAggMap, QueryAggregate, QueryAggregateResult, QueryGroupMap } from './queryAggregate.js';
4
4
  import type { Type } from './utility.js';
5
- import type { QuerierCountedResult, QuerierResult, QuerierTransport } from './wire.js';
5
+ import type { QuerierCountedResult, QuerierRaw, QuerierResult, QuerierTransport } from './wire.js';
6
6
  /**
7
7
  * The operations the server and the browser client declare alike, per transport `W`, options `O`,
8
8
  * and delete options `DO`, which on the client also carry the {@link QueryOptions} it cannot pass otherwise.
9
9
  */
10
10
  export interface SharedQuerier<W extends QuerierTransport, O, DO = O> {
11
11
  /** Find the record with the given primary key. */
12
- findOneById<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, id: EntityId<E>, q?: QueryOneProjected<E, S, V, X, P, C>, opts?: O): QuerierResult<W, QueryFindResult<E, S, V, X, P, C> | undefined>;
12
+ findOneById<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, id: EntityId<E>, q?: QueryOneProjected<E, S, V, X, P, C, QuerierRaw<W>>, opts?: O): QuerierResult<W, QueryFindResult<E, S, V, X, P, C> | undefined>;
13
13
  /**
14
14
  * obtains the first record matching the given search parameters.
15
15
  * @param entity the target entity
16
16
  * @param q the criteria options
17
17
  * @return the record
18
18
  */
19
- findOne<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryOneProjected<E, S, V, X, P, C>, opts?: O): QuerierResult<W, QueryFindResult<E, S, V, X, P, C> | undefined>;
19
+ findOne<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryOneProjected<E, S, V, X, P, C, QuerierRaw<W>>, opts?: O): QuerierResult<W, QueryFindResult<E, S, V, X, P, C> | undefined>;
20
20
  /**
21
21
  * obtains the records matching the given search parameters.
22
22
  * @param entity the target entity
23
23
  * @param q the criteria options
24
24
  * @return the records
25
25
  */
26
- findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C>, opts?: O): QuerierResult<W, QueryFindResult<E, S, V, X, P, C>[]>;
26
+ findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C, QuerierRaw<W>>, opts?: O): QuerierResult<W, QueryFindResult<E, S, V, X, P, C>[]>;
27
27
  /** Find the records matching the query, and count every match past its page. */
28
- findManyAndCount<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C>, opts?: O): QuerierCountedResult<W, QueryFindResult<E, S, V, X, P, C>>;
28
+ findManyAndCount<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C, QuerierRaw<W>>, opts?: O): QuerierCountedResult<W, QueryFindResult<E, S, V, X, P, C>>;
29
29
  /** Count the records matching the filter, or those a page of them takes. */
30
- count<E extends object>(entity: Type<E>, q?: QueryPage<E>, opts?: O): QuerierResult<W, number>;
30
+ count<E extends object>(entity: Type<E>, q?: QueryPage<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, number>;
31
31
  /** Whether any record matches: a count capped at one row, so the engine stops at the first match. */
32
- exists<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: O): QuerierResult<W, boolean>;
32
+ exists<E extends object>(entity: Type<E>, q?: QueryFilter<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, boolean>;
33
33
  /** Update the record with the given primary key; resolves to the number of affected rows. */
34
- updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E>, opts?: O): QuerierResult<W, number>;
34
+ updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, number>;
35
35
  /** Update the records matching the query; resolves to the number of affected rows. */
36
- updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: O): QuerierResult<W, number>;
36
+ updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E, QuerierRaw<W>>, payload: UpdatePayload<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, number>;
37
37
  /**
38
38
  * delete or SoftDelete a record.
39
39
  * @param entity the entity to persist on
@@ -47,7 +47,7 @@ export interface SharedQuerier<W extends QuerierTransport, O, DO = O> {
47
47
  * @param q the criteria to look for the records
48
48
  * @return the number of affected records
49
49
  */
50
- deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: DO): QuerierResult<W, number>;
50
+ deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E, QuerierRaw<W>>, opts?: DO): QuerierResult<W, number>;
51
51
  }
52
52
  /**
53
53
  * A `querier` allows to interact with the datasource to perform persistence operations on any entity.
@@ -1,3 +1,4 @@
1
+ import type { QueryRaw } from './queryRaw.js';
1
2
  /**
2
3
  * The envelope a wire response wraps its result in.
3
4
  */
@@ -16,6 +17,14 @@ export type RequestCountedSuccessResponse<E> = RequestSuccessResponse<E> & {
16
17
  * client one hands back the envelope its transport wrapped it in.
17
18
  */
18
19
  export type QuerierTransport = 'server' | 'client';
20
+ /**
21
+ * The `raw` SQL a transport carries. A client's query and payload travel as JSON, which a `raw` fragment
22
+ * is not: it would arrive as `{}`, so the client's types refuse one rather than let it leave.
23
+ */
24
+ export type QuerierRaw<W extends QuerierTransport> = {
25
+ server: QueryRaw;
26
+ client: never;
27
+ }[W];
19
28
  /**
20
29
  * A querier method's result on a transport: `Promise<User[]>` on the server, the response envelope on
21
30
  * the client. A map indexed by the transport, which resolves away in hovers.
@@ -2,8 +2,8 @@ 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 { VECTOR_INDEX_TYPES } from '../type/vector.js';
5
- import { isDatabaseWritten } from './field.util.js';
6
- import { entityName, getFieldKeys, getKeys, hasKeys, isOperatorObject, isScalarId, isRecord, isWhereMap, someKey, } from './object.util.js';
5
+ import { getFieldKeys, isDatabaseWritten } from './field.util.js';
6
+ import { entityName, getKeys, hasKeys, isOperatorObject, isScalarId, isRecord, isWhereMap, someKey, } from './object.util.js';
7
7
  /** The keys of `payload` a write persists as columns. */
8
8
  export function filterFieldKeys(meta, payload, callbackKey) {
9
9
  return getKeys(payload).filter((key) => {
@@ -1,4 +1,4 @@
1
- import { type ColumnFamily, type EntityMeta, type FieldOptions } from '../type/index.js';
1
+ import { type ColumnFamily, type EntityMeta, type FieldKey, type FieldOptions, 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,12 @@ 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
+ /**
16
+ * The relation aggregate a field computes, where it computes one rather than writing SQL: what it
17
+ * reads, off which relation, narrowed and capped how. Every engine renders it from this - a correlated
18
+ * subquery on SQL, a lookup on MongoDB - so both read the same declaration rather than parsing SQL.
19
+ */
20
+ export declare function aggregateOf(field: FieldOptions | undefined): RelationAggregateSpec | undefined;
15
21
  /**
16
22
  * Whether the database supplies this field's value, so no insert or update may write it: a stored
17
23
  * computed column *is* a real column, read like one, but writing to it is an error on every engine.
@@ -25,3 +31,11 @@ export declare function isSoleIdField<E>(meta: EntityMeta<E>, field: FieldOption
25
31
  * states its width. The one answer the create statement and the diff both read.
26
32
  */
27
33
  export declare function isAutoIncrement(field: FieldOptions, isPrimaryKey: boolean): boolean;
34
+ /**
35
+ * The fields a read answers with where it names none. A relation aggregate is left out unless it asks
36
+ * for `eager: true`: it reads the related rows, which is what a relation does, and a relation is loaded
37
+ * only when a query asks for it. Naming one in `$select` reads it, whatever the default.
38
+ */
39
+ export declare function getFieldKeys<E>(fields: {
40
+ [K in FieldKey<E>]?: FieldOptions;
41
+ }): FieldKey<E>[];
@@ -1,4 +1,4 @@
1
- import { COLUMN_TYPES, } from '../type/index.js';
1
+ import { COLUMN_TYPES, RelationAggregate, } from '../type/index.js';
2
2
  import { 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.
@@ -41,6 +41,15 @@ export function isIntegerColumn(field) {
41
41
  export function isInlinedExpression(field) {
42
42
  return field.computed !== undefined && field.stored !== true;
43
43
  }
44
+ /**
45
+ * The relation aggregate a field computes, where it computes one rather than writing SQL: what it
46
+ * reads, off which relation, narrowed and capped how. Every engine renders it from this - a correlated
47
+ * subquery on SQL, a lookup on MongoDB - so both read the same declaration rather than parsing SQL.
48
+ */
49
+ export function aggregateOf(field) {
50
+ const computed = field?.computed;
51
+ return computed instanceof RelationAggregate ? computed.spec : undefined;
52
+ }
44
53
  /**
45
54
  * Whether the database supplies this field's value, so no insert or update may write it: a stored
46
55
  * computed column *is* a real column, read like one, but writing to it is an error on every engine.
@@ -62,3 +71,11 @@ export function isAutoIncrement(field, isPrimaryKey) {
62
71
  return field.autoIncrement;
63
72
  return isPrimaryKey && columnFamily(field.type) === 'numeric' && !field.onInsert && !field.references;
64
73
  }
74
+ /**
75
+ * The fields a read answers with where it names none. A relation aggregate is left out unless it asks
76
+ * for `eager: true`: it reads the related rows, which is what a relation does, and a relation is loaded
77
+ * only when a query asks for it. Naming one in `$select` reads it, whatever the default.
78
+ */
79
+ export function getFieldKeys(fields) {
80
+ return getKeys(fields).filter((field) => fields[field].eager ?? !aggregateOf(fields[field]));
81
+ }
@@ -1,4 +1,4 @@
1
- import type { EntityMeta, FieldKey, FieldOptions } from '../type/index.js';
1
+ import type { EntityMeta } from '../type/index.js';
2
2
  export declare function throwPendingTransaction(): never;
3
3
  export declare function throwNoPendingTransaction(): never;
4
4
  export declare function clone<T>(value: T): T;
@@ -24,9 +24,6 @@ export declare function definedEntries<K extends string, V>(record: Partial<Reco
24
24
  * out reached for three different fallbacks, `?? ''` among them, and named nothing at all.
25
25
  */
26
26
  export declare function entityName<E>(meta: EntityMeta<E>): string;
27
- export declare function getFieldKeys<E>(fields: {
28
- [K in FieldKey<E>]?: FieldOptions;
29
- }): FieldKey<E>[];
30
27
  /**
31
28
  * Whether `value` addresses a row by itself rather than naming columns: every primitive, and the
32
29
  * object ids a driver deals in (`ObjectId`, `Date`, bytes). Only a plain object names columns, which
@@ -59,9 +59,6 @@ export function definedEntries(record) {
59
59
  export function entityName(meta) {
60
60
  return meta.name ?? meta.entity.name;
61
61
  }
62
- export function getFieldKeys(fields) {
63
- return getKeys(fields).filter((field) => fields[field].eager ?? true);
64
- }
65
62
  /**
66
63
  * Whether `value` addresses a row by itself rather than naming columns: every primitive, and the
67
64
  * object ids a driver deals in (`ObjectId`, `Date`, bytes). Only a plain object names columns, which
@@ -1,4 +1,4 @@
1
- import { type EntitySql, type EntityWhere, type EntityWhereMeta, QueryRaw, type QueryRawFn, type RefMap, type Type } from '../type/index.js';
1
+ import { type EntitySql, type EntityWhere, type EntityWhereMeta, QueryRaw, type QueryRawFn, type ComputedRefs, type RefMap, 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
@@ -16,7 +16,7 @@ export declare function refs<E>(entity: Type<E>): RefMap<E>;
16
16
  * The refs a definition's callbacks read: an index's, a check's, a computed field's. A member decorator
17
17
  * sees no class, so these name no entity and resolve against the one rendering them.
18
18
  */
19
- export declare function memberRefs<E>(): RefMap<E>;
19
+ export declare function memberRefs<E>(): ComputedRefs<E>;
20
20
  /** SQL a definition writes, a callback's refs read off {@link memberRefs}. */
21
21
  export declare function entitySql<E>(sql: EntitySql<E>): QueryRaw;
22
22
  /** A definition's predicate, its callback resolved the way {@link entitySql} resolves one. */
package/dist/util/raw.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { getMeta } from '../entity/metadata/definition.js';
2
- import { ColumnRef, QueryRaw, } from '../type/index.js';
2
+ import { ColumnRef, QueryRaw, RelationAggregate, } from '../type/index.js';
3
3
  import { isInlinedExpression } from './field.util.js';
4
4
  export function raw(value, ...rest) {
5
5
  if (!isTemplateStrings(value)) {
@@ -27,7 +27,7 @@ export function raw(value, ...rest) {
27
27
  export function refs(entity) {
28
28
  return new Proxy({}, { get: (_, key) => columnRef(entity, String(key)) });
29
29
  }
30
- const MEMBER_REFS = new Proxy({}, { get: (_, key) => columnRef(undefined, String(key)) });
30
+ const MEMBER_REFS = new Proxy({}, { get: (_, key) => memberRef(String(key)) });
31
31
  /**
32
32
  * The refs a definition's callbacks read: an index's, a check's, a computed field's. A member decorator
33
33
  * sees no class, so these name no entity and resolve against the one rendering them.
@@ -43,6 +43,33 @@ export function entitySql(sql) {
43
43
  export function entityWhere(where) {
44
44
  return typeof where === 'function' ? where(memberRefs()) : where;
45
45
  }
46
+ /**
47
+ * One member as a definition reads it: a {@link ColumnRef} where it names a field, and the same object
48
+ * answering `count`, `sum`, `min`, `max` and `avg` where it names a to-many. One runtime object, since
49
+ * a member decorator sees no class and so cannot know which the key is; the types keep them apart.
50
+ */
51
+ function memberRef(relation) {
52
+ const over = (op) => (pick, q) => relationAggregate({ relation, op, field: pick(memberRefs()).key, ...(q && { query: q }) });
53
+ return Object.assign(columnRef(undefined, relation), {
54
+ count: (q) => relationAggregate({ relation, op: '$count', ...(q && { query: q }) }),
55
+ sum: over('$sum'),
56
+ min: over('$min'),
57
+ max: over('$max'),
58
+ avg: over('$avg'),
59
+ });
60
+ }
61
+ /**
62
+ * A relation aggregate as SQL: the dialect writes the same correlated subquery a `$count` reads,
63
+ * correlated to whichever alias the clause naming the field is rendering under.
64
+ */
65
+ function relationAggregate(spec) {
66
+ return new RelationAggregate(spec, (opts) => {
67
+ if (!opts.entity) {
68
+ throw new TypeError(`'${spec.relation}' was read off a definition's refs, so it renders only inside its entity's SQL`);
69
+ }
70
+ opts.dialect.appendRelationAggregate(opts.ctx, opts.entity, spec, opts.prefix);
71
+ });
72
+ }
46
73
  /** One field as SQL, against its own entity or, read off a definition, the entity rendering it. */
47
74
  function columnRef(entity, key) {
48
75
  return new ColumnRef(key, (opts) => {
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.68.0",
6
+ "version": "0.69.0",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"
@@ -161,7 +161,7 @@
161
161
  "repository": {
162
162
  "type": "git",
163
163
  "url": "git+https://github.com/rogerpadilla/uql.git",
164
- "directory": "packages/uql-orm"
164
+ "directory": "packages/orm"
165
165
  },
166
166
  "bugs": {
167
167
  "url": "https://github.com/rogerpadilla/uql/issues"