uql-orm 0.68.1 → 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.
@@ -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.1",
6
+ "version": "0.69.0",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"