uql-orm 0.26.0 → 0.26.2

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 CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  <h3>The smartest TypeScript ORM</h3>
11
11
 
12
- <p>Type-safe to the leaf, serializable queries, no codegen, and one API across every SQL database, MongoDB, and every runtime. And the <a href="https://uql-orm.dev/benchmark">fastest</a>.</p>
12
+ <p>Serializable queries, type-safe to the leaf, no codegen, and one API across every SQL database, MongoDB, and every runtime. And the <a href="https://uql-orm.dev/benchmark">fastest</a>.</p>
13
13
 
14
14
  <p>
15
15
  <a href="https://uql-orm.dev"><b>Website</b></a> ·
@@ -50,8 +50,8 @@ from the browser to the server. The same object runs on every supported database
50
50
 
51
51
  ## Why UQL?
52
52
 
53
+ - **Serializable queries (JSON), not method chains.** Plain JSON in, typed rows out. No DSL to learn.
53
54
  - **Type-safe to the leaf, nothing to generate.** Every key is checked against your entity, down into populated relations and [JSON/JSONB](https://uql-orm.dev/querying/json) dot-paths, so `$like` on a numeric column is a compile error. Entities are plain classes: no `.prisma` file, no generated client, no build step.
54
- - **Queries are data (JSON), not method chains.** Plain JSON in, typed rows out. No DSL to learn.
55
55
  - **One API, everywhere it runs.** PostgreSQL, CockroachDB, MySQL, MariaDB, SQLite, Turso, libSQL, Neon, Cloudflare D1, Bun's native SQL, and even MongoDB. The same code on Node 24+, Bun, Deno, [Cloudflare Workers](https://uql-orm.dev/cloudflare-d1), [AWS Lambda and Vercel](https://uql-orm.dev/serverless), and [the browser](https://uql-orm.dev/browser), with no native binaries on the `fetch`-based drivers.
56
56
  - **Relations without N+1.** [`$populate`](https://uql-orm.dev/querying/relations) loads a to-many with one query for all parents, not one per parent. Nothing is lazy, so nothing fires behind your back in a serializer.
57
57
  - **Migrations you read before they run.** Edit an entity, run `uql-migrate generate:entities`, review the SQL in the PR like any other file. [`drift:check`](https://uql-orm.dev/migrations) catches a database that no longer matches.
@@ -1,6 +1,6 @@
1
1
  import { decodeColumn } from '../dialect/hydrateColumn.js';
2
2
  import { getMeta } from '../entity/index.js';
3
- import { buildUpdateResult, clone, getInsertFieldKeys, getRelationRequestSummary, isAutoIncrement, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, withoutSoftDeleteFilter, } from '../util/index.js';
3
+ import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, getRelationRequestSummary, isAutoIncrement, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, withoutSoftDeleteFilter, } from '../util/index.js';
4
4
  import { AbstractQuerier } from './abstractQuerier.js';
5
5
  import { enrichError } from './queryError.js';
6
6
  export class AbstractSqlQuerier extends AbstractQuerier {
@@ -222,6 +222,17 @@ export class AbstractSqlQuerier extends AbstractQuerier {
222
222
  }
223
223
  async internalDeleteMany(entity, q, opts) {
224
224
  const meta = getMeta(entity);
225
+ // Resolving the ids first is what makes the two hard cases work at all: a cascade needs its
226
+ // parents' ids to find their children, and no engine but MySQL accepts `ORDER BY`/`LIMIT` on a
227
+ // DELETE, so a paged delete has to name the rows it settled on. A plain predicate needs neither,
228
+ // and there the round trip buys nothing: the statement can say what the caller already said.
229
+ const hasPagination = q.$sort !== undefined || q.$limit !== undefined || q.$skip !== undefined;
230
+ if (!hasPagination && !cascadesOnDelete(meta)) {
231
+ const ctx = this.dialect.createContext();
232
+ this.dialect.delete(ctx, entity, q, opts);
233
+ const { changes = 0 } = await this.run(ctx.sql, ctx.values);
234
+ return changes;
235
+ }
225
236
  // A hard delete also targets already-soft-deleted rows, so drop the soft-delete filter when finding ids.
226
237
  const findOpts = opts?.hardDelete ? { ...opts, filters: withoutSoftDeleteFilter(opts.filters) } : opts;
227
238
  const findCtx = this.dialect.createContext();
@@ -314,7 +314,7 @@ export type OnFieldCallback<V = TsTypeOf<FieldType>> = V | QueryRaw | (() => V |
314
314
  * `type` allowed. A decorator has it the other way round: `@Field({ type: String })` is checked before
315
315
  * the class exists, so the only way to reach the property is to state what `type: String` implies and
316
316
  * let the decorator's context position compare it against the real field. Neither can be derived from
317
- * the other by inference, so `entityOptions.type-test.ts` asserts they agree instead.
317
+ * the other by inference, so `entityOptions.test-d.ts` asserts they agree instead.
318
318
  */
319
319
  export type TsTypeOf<T> = T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BigIntConstructor ? bigint : T extends BooleanConstructor ? boolean : T extends DateConstructor ? Date : T extends StringColumnType ? string : T extends NumericColumnType ? number | bigint : T extends BooleanColumnType ? boolean : T extends DateColumnType ? Date : T extends JsonColumnType ? Json<unknown> | readonly Json<unknown>[] : T extends BlobColumnType ? Uint8Array : T extends VectorColumnType ? readonly number[] : unknown;
320
320
  /**
@@ -35,6 +35,11 @@ export declare function fillOnFields<E>(meta: EntityMeta<E>, payload: E | E[], c
35
35
  * or `meta.relations` itself to enumerate every cascadable relation).
36
36
  */
37
37
  export declare function filterPersistableRelationKeys<E>(meta: EntityMeta<E>, payload: object, action: CascadeType): RelationKey<E>[];
38
+ /**
39
+ * Whether deleting this entity has to delete anything else, which is the reason a delete resolves the
40
+ * matching ids before issuing anything: a child is reached through the ids of its parent.
41
+ */
42
+ export declare function cascadesOnDelete<E>(meta: EntityMeta<E>): boolean;
38
43
  export declare function isCascadable(action: CascadeType, configuration?: boolean | CascadeType): boolean;
39
44
  /**
40
45
  * The map form of a `$select` value, or `undefined` for the raw-array form. Centralizes the one
@@ -83,6 +83,13 @@ export function filterPersistableRelationKeys(meta, payload, action) {
83
83
  return relOpts && isCascadable(action, relOpts.cascade);
84
84
  });
85
85
  }
86
+ /**
87
+ * Whether deleting this entity has to delete anything else, which is the reason a delete resolves the
88
+ * matching ids before issuing anything: a child is reached through the ids of its parent.
89
+ */
90
+ export function cascadesOnDelete(meta) {
91
+ return filterPersistableRelationKeys(meta, meta.relations, 'delete').length > 0;
92
+ }
86
93
  export function isCascadable(action, configuration) {
87
94
  if (typeof configuration === 'boolean') {
88
95
  return configuration;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "homepage": "https://uql-orm.dev",
4
4
  "description": "Extremely fast, type-safe TypeScript ORM - one API for every database",
5
5
  "license": "MIT",
6
- "version": "0.26.0",
6
+ "version": "0.26.2",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"
@@ -127,9 +127,9 @@
127
127
  "devDependencies": {
128
128
  "@libsql/client": "^0.17.4",
129
129
  "@neondatabase/serverless": "^1.1.0",
130
- "@nestjs/common": "^11.1.28",
131
- "@nestjs/core": "^11.1.28",
132
- "@nestjs/testing": "^11.1.28",
130
+ "@nestjs/common": "^11.1.29",
131
+ "@nestjs/core": "^11.1.29",
132
+ "@nestjs/testing": "^11.1.29",
133
133
  "@tursodatabase/database": "^0.7.2",
134
134
  "@tursodatabase/serverless": "^1.4.0",
135
135
  "@types/better-sqlite3": "^9.6.0",
@@ -140,7 +140,7 @@
140
140
  "express": "^5.2.1",
141
141
  "mariadb": "^3.5.3",
142
142
  "mongodb": "^7.5.0",
143
- "mysql2": "^3.23.2",
143
+ "mysql2": "^3.23.3",
144
144
  "pg": "^8.23.0",
145
145
  "pg-query-stream": "^4.17.0",
146
146
  "rxjs": "^7.8.2",