uql-orm 0.29.0 → 0.30.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.
@@ -257,8 +257,11 @@ export type QueryWhereFieldOperators<T> = unknown extends T ? QueryWhereFieldOpe
257
257
  * Value for a field comparison. A bare array is an implicit `$in` for scalar fields only:
258
258
  * on array-typed fields (e.g. a vector `number[]`) an array of arrays is ambiguous, so
259
259
  * membership there requires an explicit operator.
260
+ *
261
+ * `null` is accepted on a nullable field (an optional property is a nullable column), matching what
262
+ * `$eq: null` already took.
260
263
  */
261
- export type QueryWhereFieldValue<T> = T | ([NonNullable<T>] extends [readonly unknown[]] ? never : T[]) | QueryWhereFieldOperators<T> | QueryRaw;
264
+ export type QueryWhereFieldValue<T> = T | (undefined extends T ? null : never) | ([NonNullable<T>] extends [readonly unknown[]] ? never : T[]) | QueryWhereFieldOperators<T> | QueryRaw;
262
265
  /**
263
266
  * query filter array - used for `$and`, `$or`, `$not`, `$nor` operators.
264
267
  */
@@ -1,5 +1,5 @@
1
- import type { IdValue, UpdatePayload } from './entity.js';
2
- import type { Query, QueryConflictPaths, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
1
+ import type { EntityData, IdValue, UpdatePayload } from './entity.js';
2
+ import type { Query, QueryConflictPaths, QueryFilter, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
3
3
  import type { QueryAggMap, QueryAggregate, QueryAggregateResult, QueryGroupMap } from './queryAggregate.js';
4
4
  import type { Type } from './utility.js';
5
5
  /**
@@ -46,12 +46,12 @@ export interface UniversalQuerier {
46
46
  */
47
47
  findManyAndCount<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<[E[], number]>;
48
48
  /**
49
- * counts the number of records matching the given search parameters.
49
+ * counts the number of records matching the given filter.
50
50
  * @param entity the target entity
51
- * @param q the search options
51
+ * @param q the filter
52
52
  * @return the count
53
53
  */
54
- count<E extends object>(entity: Type<E>, q?: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
54
+ count<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: QueryOptions): Promise<number>;
55
55
  /**
56
56
  * Insert a single record and return its ID (provided, `onInsert`-generated, or
57
57
  * database-generated - see {@link UniversalQuerier.insertMany} for the exact semantics).
@@ -61,7 +61,7 @@ export interface UniversalQuerier {
61
61
  * @param payload the data to be persisted
62
62
  * @return the ID
63
63
  */
64
- insertOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E> | undefined>;
64
+ insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E> | undefined>;
65
65
  /**
66
66
  * Insert multiple records in a single statement (auto-chunked when the batch exceeds the
67
67
  * dialect's bind-parameter limit) and return their IDs in payload order.
@@ -75,7 +75,7 @@ export interface UniversalQuerier {
75
75
  * @param payload the data to be persisted
76
76
  * @return the IDs
77
77
  */
78
- insertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
78
+ insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
79
79
  /**
80
80
  * updates a record partially.
81
81
  * @param entity the entity to persist on
@@ -99,7 +99,7 @@ export interface UniversalQuerier {
99
99
  * @param payload the data to be persisted
100
100
  * @return operation metadata; see {@link QueryUpdateResult}
101
101
  */
102
- upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E): Promise<QueryUpdateResult>;
102
+ upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
103
103
  /**
104
104
  * Insert or update many records based on the conflict paths.
105
105
  * @param entity the entity to persist on
@@ -107,21 +107,21 @@ export interface UniversalQuerier {
107
107
  * @param payload the data to be persisted
108
108
  * @return operation metadata; see {@link QueryUpdateResult}
109
109
  */
110
- upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E[]): Promise<QueryUpdateResult>;
110
+ upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
111
111
  /**
112
112
  * insert or update a record.
113
113
  * @param entity the entity to persist on
114
114
  * @param payload the data to be persisted
115
115
  * @return the ID
116
116
  */
117
- saveOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E>>;
117
+ saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E>>;
118
118
  /**
119
119
  * Insert or update records.
120
120
  * @param entity the entity to persist on
121
121
  * @param payload the data to be persisted
122
122
  * @return the IDs
123
123
  */
124
- saveMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
124
+ saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
125
125
  /**
126
126
  * delete or SoftDelete a record.
127
127
  * @param entity the entity to persist on
@@ -1,6 +1,6 @@
1
- import { type CascadeType, type EntityMeta, type FieldKey, type FieldOptions, type JsonUpdateOp, type OnFieldCallback, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type QueryVectorSearch, type QueryWhere, type QueryWhereMap, type RelationKey } from '../type/index.js';
1
+ import { type CascadeType, type EntityData, type EntityMeta, type FieldKey, type FieldOptions, type JsonUpdateOp, type OnFieldCallback, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type QueryVectorSearch, type QueryWhere, type QueryWhereMap, type RelationKey } from '../type/index.js';
2
2
  export type CallbackKey = keyof Pick<FieldOptions, 'onInsert' | 'onUpdate'>;
3
- export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: E, callbackKey: CallbackKey): FieldKey<E>[];
3
+ export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: EntityData<E>, callbackKey: CallbackKey): FieldKey<E>[];
4
4
  /**
5
5
  * Resolves the columns of an INSERT statement: the union of the persistable fields provided by
6
6
  * any record (in first-seen order), plus every `onInsert` field. Records missing one of these
@@ -13,7 +13,7 @@ export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: E, call
13
13
  * has run {@link fillOnFields} first (it stamps them on every record, but the querier's
14
14
  * chunk-size estimate inspects the raw payload).
15
15
  */
16
- export declare function getInsertFieldKeys<E>(meta: EntityMeta<E>, payloads: E[]): FieldKey<E>[];
16
+ export declare function getInsertFieldKeys<E>(meta: EntityMeta<E>, payloads: EntityData<E>[]): FieldKey<E>[];
17
17
  export declare function getFieldCallbackValue(val: OnFieldCallback): string | number | bigint | boolean | Date | QueryRaw | readonly {
18
18
  readonly __json?: never;
19
19
  }[] | readonly number[] | Uint8Array<ArrayBufferLike> | {
@@ -28,7 +28,7 @@ export declare function getSoftDeleteValue(field: FieldOptions): string | number
28
28
  }[] | readonly number[] | Uint8Array<ArrayBufferLike> | {
29
29
  readonly __json?: never;
30
30
  };
31
- export declare function fillOnFields<E>(meta: EntityMeta<E>, payload: E | E[], callbackKey: CallbackKey): E[];
31
+ export declare function fillOnFields<E>(meta: EntityMeta<E>, payload: EntityData<E> | EntityData<E>[], callbackKey: CallbackKey): EntityData<E>[];
32
32
  /**
33
33
  * The relation keys present in `payload` whose cascade configuration allows `action`. Only
34
34
  * `payload`'s keys are read, so any keys-bearing object works (an entity, an update payload,
@@ -100,3 +100,25 @@ export declare function parseRelationSize(val: unknown): number | QuerySizeCompa
100
100
  * entries consumable by any dialect. Grouped columns come first, then computed columns.
101
101
  */
102
102
  export declare function parseGroupMap<E>(group?: QueryGroupMap<E>, agg?: QueryAggMap<E>): ParsedGroupEntry[];
103
+ /**
104
+ * Whether `value` is a map of comparison operators rather than a value to compare against. Only a
105
+ * plain object qualifies: `Date`, `QueryRaw`, `Uint8Array` and arrays are all `typeof 'object'`, and
106
+ * reading their keys as operators drops the condition (a `Date` has none) or throws on an array's
107
+ * indices. Shared by the SQL and MongoDB builders, whose `$where` and `$having` all face this.
108
+ */
109
+ export declare function isOperatorMap(value: unknown): value is Record<string, unknown>;
110
+ /**
111
+ * A page operand, checked before it reaches an engine. These arrive from page arithmetic and from
112
+ * REST query strings (`parseQueryParams` yields `NaN` for a non-numeric one), and each engine's own
113
+ * complaint names neither the clause nor the value - or, for `$limit: 0`, quietly means something
114
+ * else. Shared so every backend rejects the same input.
115
+ */
116
+ export declare function assertNonNegativeInteger(value: number, clause: string): number;
117
+ /**
118
+ * Rejects a `$having`/`$sort` key naming something an aggregate does not emit. Its rows are its
119
+ * `$group` columns and its `$agg` aliases; anything else is a value that is not there. Shared so
120
+ * SQL and MongoDB refuse the same query with the same words.
121
+ */
122
+ export declare function throwUnknownAggregateColumn(key: string, clause: string): never;
123
+ /** {@link throwUnknownAggregateColumn} over every key of a clause, for backends that check up front. */
124
+ export declare function assertAggregateColumns(clauseMap: object | undefined, emitted: ReadonlySet<string>, clause: string): void;
@@ -301,3 +301,45 @@ export function parseGroupMap(group, agg) {
301
301
  }
302
302
  return entries;
303
303
  }
304
+ /**
305
+ * Whether `value` is a map of comparison operators rather than a value to compare against. Only a
306
+ * plain object qualifies: `Date`, `QueryRaw`, `Uint8Array` and arrays are all `typeof 'object'`, and
307
+ * reading their keys as operators drops the condition (a `Date` has none) or throws on an array's
308
+ * indices. Shared by the SQL and MongoDB builders, whose `$where` and `$having` all face this.
309
+ */
310
+ export function isOperatorMap(value) {
311
+ return (typeof value === 'object' &&
312
+ value !== null &&
313
+ !Array.isArray(value) &&
314
+ !(value instanceof Date) &&
315
+ !(value instanceof Uint8Array) &&
316
+ !(value instanceof QueryRaw));
317
+ }
318
+ /**
319
+ * A page operand, checked before it reaches an engine. These arrive from page arithmetic and from
320
+ * REST query strings (`parseQueryParams` yields `NaN` for a non-numeric one), and each engine's own
321
+ * complaint names neither the clause nor the value - or, for `$limit: 0`, quietly means something
322
+ * else. Shared so every backend rejects the same input.
323
+ */
324
+ export function assertNonNegativeInteger(value, clause) {
325
+ if (!Number.isInteger(value) || value < 0) {
326
+ throw new TypeError(`${clause} must be a non-negative integer, got ${value}`);
327
+ }
328
+ return value;
329
+ }
330
+ /**
331
+ * Rejects a `$having`/`$sort` key naming something an aggregate does not emit. Its rows are its
332
+ * `$group` columns and its `$agg` aliases; anything else is a value that is not there. Shared so
333
+ * SQL and MongoDB refuse the same query with the same words.
334
+ */
335
+ export function throwUnknownAggregateColumn(key, clause) {
336
+ throw new TypeError(`cannot ${clause} by '${key}': it is neither a $group column nor an $agg alias`);
337
+ }
338
+ /** {@link throwUnknownAggregateColumn} over every key of a clause, for backends that check up front. */
339
+ export function assertAggregateColumns(clauseMap, emitted, clause) {
340
+ for (const key of getKeys(clauseMap ?? {})) {
341
+ if (!emitted.has(key)) {
342
+ throwUnknownAggregateColumn(key, clause);
343
+ }
344
+ }
345
+ }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "homepage": "https://uql-orm.dev",
4
4
  "description": "JSON-native TypeScript ORM for Node.js, Bun and Deno. Supports PostgreSQL, MySQL, MariaDB, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
5
5
  "license": "MIT",
6
- "version": "0.29.0",
6
+ "version": "0.30.0",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"
@@ -133,13 +133,13 @@
133
133
  "@tursodatabase/serverless": "^1.4.0",
134
134
  "@types/better-sqlite3": "^9.6.0",
135
135
  "@types/express": "^5.0.6",
136
- "@types/pg": "^8.21.0",
136
+ "@types/pg": "^8.23.1",
137
137
  "@types/ws": "^8.18.1",
138
138
  "better-sqlite3": "^13.0.3",
139
139
  "express": "^5.2.1",
140
140
  "mariadb": "^3.5.3",
141
141
  "mongodb": "^7.5.0",
142
- "mysql2": "^3.23.3",
142
+ "mysql2": "^3.23.4",
143
143
  "pg": "^8.23.0",
144
144
  "pg-query-stream": "^4.17.0",
145
145
  "rxjs": "^7.8.2",