uql-orm 0.57.0 → 0.59.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 (44) hide show
  1. package/README.md +6 -8
  2. package/dist/browser/uql-browser.min.js +2 -2
  3. package/dist/browser/uql-browser.min.js.map +3 -3
  4. package/dist/dialect/abstractSqlDialect.d.ts +2 -2
  5. package/dist/dialect/abstractSqlDialect.js +4 -4
  6. package/dist/dialect/mysqlLikeSqlDialect.d.ts +1 -1
  7. package/dist/dialect/mysqlLikeSqlDialect.js +1 -1
  8. package/dist/dialect/queryJoins.js +1 -0
  9. package/dist/entity/decorator/bag.d.ts +2 -2
  10. package/dist/entity/decorator/entity.d.ts +8 -9
  11. package/dist/entity/decorator/entity.js +6 -7
  12. package/dist/entity/decorator/members.d.ts +7 -6
  13. package/dist/entity/decorator/members.js +2 -1
  14. package/dist/entity/metadata/definition.d.ts +16 -11
  15. package/dist/entity/metadata/definition.js +73 -50
  16. package/dist/http/handler.d.ts +2 -2
  17. package/dist/http/handler.js +0 -1
  18. package/dist/migrate/codegen/entityCodeGenerator.js +6 -4
  19. package/dist/migrate/codegen/entityTypes.d.ts +1 -1
  20. package/dist/migrate/codegen/entityTypes.js +4 -3
  21. package/dist/migrate/codegen/indexDecoratorSource.d.ts +5 -4
  22. package/dist/migrate/codegen/indexDecoratorSource.js +17 -13
  23. package/dist/migrate/codegen/sourceLiteral.d.ts +2 -0
  24. package/dist/migrate/codegen/sourceLiteral.js +4 -0
  25. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +3 -3
  26. package/dist/migrate/migrator.d.ts +2 -2
  27. package/dist/migrate/schemaGenerator.d.ts +5 -5
  28. package/dist/mongo/mongoDialect.d.ts +1 -1
  29. package/dist/mongo/mongoDialect.js +3 -3
  30. package/dist/mongo/mongodbQuerier.js +0 -1
  31. package/dist/querier/abstractSqlQuerier.js +6 -6
  32. package/dist/schema/schemaASTBuilder.d.ts +3 -3
  33. package/dist/schema/schemaASTBuilder.js +2 -2
  34. package/dist/type/config.d.ts +1 -1
  35. package/dist/type/entity.d.ts +115 -68
  36. package/dist/type/migration.d.ts +7 -7
  37. package/dist/type/querierPool.d.ts +2 -2
  38. package/dist/type/query.d.ts +19 -27
  39. package/dist/type/queryAggregate.d.ts +38 -29
  40. package/dist/type/queryWhere.d.ts +12 -9
  41. package/dist/util/dialect.util.d.ts +3 -3
  42. package/dist/util/dialect.util.js +24 -15
  43. package/dist/util/relationQuery.util.d.ts +1 -1
  44. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import type { FieldKey, JsonFieldPaths, JsonFieldPathValue, RelationKey, RelationTarget } from './entity.js';
2
+ import type { QuerySelect } from './query.js';
2
3
  import type { QueryRaw } from './queryRaw.js';
3
4
  import type { ExpandScalar, IsMany, QueryComparableScalar, Scalar } from './utility.js';
4
5
  import type { QueryVectorQuery } from './vector.js';
@@ -11,9 +12,9 @@ export type QueryTextSearchOptions<E> = {
11
12
  */
12
13
  $value: string;
13
14
  /**
14
- * list of fields to search on.
15
+ * the fields to search, `{ title: true, body: true }`, in the order a MySQL `FULLTEXT` index lists them.
15
16
  */
16
- $fields?: FieldKey<E>[];
17
+ $fields?: QuerySelect<E>;
17
18
  /**
18
19
  * Postgres text-search configuration (e.g. `'english'`), applied to both the document and the
19
20
  * query. Defaults to the server's `default_text_search_config`. Ignored by other dialects.
@@ -27,17 +28,19 @@ export type QueryTextSearchOptions<E> = {
27
28
  * filtered via nested typed objects; dotted relation paths are not supported (the dialects throw
28
29
  * for non-JSON dotted keys).
29
30
  *
30
- * One mapped type over the three key sets rather than three intersected, for the reason
31
- * {@link QuerySortMap} is: the sets are disjoint, and an assignability check against an
32
- * intersection is repeated per constituent, which every `$where` in a codebase pays. The root
33
- * operators stay a separate member - they are a fixed shape, not keyed off the entity.
31
+ * Fields and relations share one mapped type over `K extends keyof E`, which keeps each key linked to
32
+ * its property (see {@link QuerySelect}). JSON paths are not keys of `E`, so they are a second member,
33
+ * and only where the entity has one: an empty member would switch off the weak-type check that
34
+ * rejects `$where: 1`.
34
35
  *
35
36
  * An object and nothing else: in a union with ids or lists, TypeScript reports a wrong value against
36
37
  * the whole `$where` instead of the key holding it. Ids are `{ id: 1 }`, or the by-id methods.
37
38
  */
38
- export type QueryWhere<E> = QueryWhereRootOperator<E> & {
39
- [K in FieldKey<E> | RelationKey<E> | JsonFieldPaths<E>]?: K extends FieldKey<E> ? QueryWhereFieldValue<E[K]> : K extends RelationKey<E> ? QueryWhere<RelationTarget<E[K]>> | QueryRelationSizeFilter : QueryWhereFieldValue<JsonFieldPathValue<E, K & string>>;
40
- };
39
+ export type QueryWhere<E, K extends keyof E = FieldKey<E> | RelationKey<E>> = QueryWhereRootOperator<E> & {
40
+ [P in K]?: P extends FieldKey<E> ? QueryWhereFieldValue<E[P]> : QueryWhere<RelationTarget<E[P]>> | QueryRelationSizeFilter;
41
+ } & ([JsonFieldPaths<E>] extends [never] ? unknown : {
42
+ [P in JsonFieldPaths<E>]?: QueryWhereFieldValue<JsonFieldPathValue<E, P>>;
43
+ });
41
44
  /**
42
45
  * Filter a to-many relation by its row count.
43
46
  * @example { users: { $size: 2 } }
@@ -145,10 +145,10 @@ export declare function parseRelationSize(val: unknown): number | QuerySizeCompa
145
145
  */
146
146
  export declare function parseSortByCount(val: unknown): unknown;
147
147
  /**
148
- * Parse the `$group` (grouped columns) and `$agg` (computed aggregates) maps into structured
148
+ * Parse the `$group` (grouped columns) and `$select` (computed aggregates) maps into structured
149
149
  * entries consumable by any dialect. Grouped columns come first, then computed columns.
150
150
  */
151
- export declare function parseGroupMap<E>(group?: QueryGroupMap<E>, agg?: QueryAggMap<E>): ParsedGroupEntry[];
151
+ export declare function parseGroupMap<E>(group?: QueryGroupMap<E>, select?: QueryAggMap<E>): ParsedGroupEntry[];
152
152
  /**
153
153
  * Whether `value` is a map of comparison operators rather than a value to compare against. Only a
154
154
  * plain object qualifies: `Date`, `QueryRaw`, `Uint8Array` and arrays are all `typeof 'object'`, and
@@ -165,7 +165,7 @@ export declare function isOperatorMap(value: unknown): value is Record<string, u
165
165
  export declare function assertNonNegativeInteger(value: number, clause: string): number;
166
166
  /**
167
167
  * Rejects a `$having`/`$sort` key naming something an aggregate does not emit. Its rows are its
168
- * `$group` columns and its `$agg` aliases; anything else is a value that is not there. Shared so
168
+ * `$group` columns and its `$select` aliases; anything else is a value that is not there. Shared so
169
169
  * SQL and MongoDB refuse the same query with the same words.
170
170
  */
171
171
  export declare function throwUnknownAggregateColumn(key: string, clause: string): never;
@@ -3,7 +3,7 @@ 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
5
  import { isDatabaseWritten } from './field.util.js';
6
- import { entityName, getFieldKeys, getKeys, hasKeys, isScalarId, isWhereMap, someKey } from './object.util.js';
6
+ import { entityName, getFieldKeys, getKeys, hasKeys, isScalarId, isRecord, isWhereMap, someKey, } from './object.util.js';
7
7
  export function filterFieldKeys(meta, payload, callbackKey) {
8
8
  return getKeys(payload).filter((key) => {
9
9
  const fieldOpts = meta.fields[key];
@@ -355,10 +355,10 @@ export function parseSortByCount(val) {
355
355
  return val.$count;
356
356
  }
357
357
  /**
358
- * Parse the `$group` (grouped columns) and `$agg` (computed aggregates) maps into structured
358
+ * Parse the `$group` (grouped columns) and `$select` (computed aggregates) maps into structured
359
359
  * entries consumable by any dialect. Grouped columns come first, then computed columns.
360
360
  */
361
- export function parseGroupMap(group, agg) {
361
+ export function parseGroupMap(group, select) {
362
362
  const entries = [];
363
363
  const groupMap = group ?? {};
364
364
  for (const alias of getKeys(groupMap)) {
@@ -366,22 +366,30 @@ export function parseGroupMap(group, agg) {
366
366
  entries.push({ kind: 'key', alias });
367
367
  }
368
368
  }
369
- if (!agg) {
369
+ if (!select) {
370
370
  return entries;
371
371
  }
372
- for (const alias of getKeys(agg)) {
373
- const fnEntry = agg[alias];
372
+ for (const alias of getKeys(select)) {
373
+ const fnEntry = select[alias];
374
374
  const key = getKeys(fnEntry)[0];
375
375
  // Flat DISTINCT ops (`$countDistinct`, ...) normalize to their base op + a `distinct` flag.
376
376
  const { op, distinct } = resolveAggregateOp(key);
377
- const fieldRef = fnEntry[key];
378
- if (fieldRef === undefined) {
379
- throw TypeError(`empty aggregate function for: ${alias}`);
380
- }
381
- entries.push({ kind: 'fn', alias, op, fieldRef, distinct });
377
+ entries.push({ kind: 'fn', alias, op, fieldRef: aggregateFieldRef(alias, fnEntry[key]), distinct });
382
378
  }
383
379
  return entries;
384
380
  }
381
+ /** The column an aggregate reads: `'*'`, or the one field its `{ field: true }` names. */
382
+ function aggregateFieldRef(alias, arg) {
383
+ const [field, ...rest] = arg === '*' ? [arg] : namedKeys(arg);
384
+ if (field === undefined || rest.length) {
385
+ throw new TypeError(`aggregate '${alias}' takes one field as { field: true }, or '*': got ${JSON.stringify(arg)}`);
386
+ }
387
+ return field;
388
+ }
389
+ /** The keys a `{ key: true }` map switches on; anything that is not such a map switches on none. */
390
+ function namedKeys(map) {
391
+ return isRecord(map) ? getKeys(map).filter((key) => map[key]) : [];
392
+ }
385
393
  /**
386
394
  * Whether `value` is a map of comparison operators rather than a value to compare against. Only a
387
395
  * plain object qualifies: `Date`, `QueryRaw`, `Uint8Array` and arrays are all `typeof 'object'`, and
@@ -410,11 +418,11 @@ export function assertNonNegativeInteger(value, clause) {
410
418
  }
411
419
  /**
412
420
  * Rejects a `$having`/`$sort` key naming something an aggregate does not emit. Its rows are its
413
- * `$group` columns and its `$agg` aliases; anything else is a value that is not there. Shared so
421
+ * `$group` columns and its `$select` aliases; anything else is a value that is not there. Shared so
414
422
  * SQL and MongoDB refuse the same query with the same words.
415
423
  */
416
424
  export function throwUnknownAggregateColumn(key, clause) {
417
- throw new TypeError(`cannot ${clause} by '${key}': it is neither a $group column nor an $agg alias`);
425
+ throw new TypeError(`cannot ${clause} by '${key}': it is neither a $group column nor a $select alias`);
418
426
  }
419
427
  /** {@link throwUnknownAggregateColumn} over every key of a clause, for backends that check up front. */
420
428
  export function assertAggregateColumns(clauseMap, emitted, clause) {
@@ -430,8 +438,9 @@ export function assertAggregateColumns(clauseMap, emitted, clause) {
430
438
  * where neither says, rather than guessed: every engine answers a guess with an error of its own.
431
439
  */
432
440
  export function textSearchFields(meta, search) {
433
- if (search.$fields?.length) {
434
- return search.$fields;
441
+ const named = namedKeys(search.$fields);
442
+ if (named.length) {
443
+ return named;
435
444
  }
436
445
  const fulltext = (meta.indexes ?? []).filter((index) => index.type === 'fulltext');
437
446
  if (fulltext.length === 1) {
@@ -57,7 +57,7 @@ export declare function populatesRelations<E>(meta: EntityMeta<E>, populate?: Qu
57
57
  export declare function countedRelations<E>(meta: EntityMeta<E>, counts: QueryCount<E> | undefined): {
58
58
  readonly relKey: RelationKey<E>;
59
59
  readonly relation: RelationMeta;
60
- readonly where: QueryWhere<unknown>;
60
+ readonly where: QueryWhere<object>;
61
61
  }[];
62
62
  export type ParsedRelationQuery<E extends object = object> = {
63
63
  query: RelationQuery<E>;
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.57.0",
6
+ "version": "0.59.0",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"