uql-orm 0.26.1 → 0.26.3

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
@@ -58,7 +58,7 @@ from the browser to the server. The same object runs on every supported database
58
58
  - **Raw SQL when you want it.** [`raw()`](https://uql-orm.dev/querying/raw-sql) fits anywhere in a query, [virtual fields](https://uql-orm.dev/entities/virtual-fields) are sub-queries you can filter on, and a migration can be plain SQL.
59
59
  - **Light.** Zero runtime dependencies, 305 kB on the wire, every dialect included. See [what we deleted to get there](https://uql-orm.dev/blog/zero-dependencies).
60
60
  - **The hard things are built in.** [Semantic and vector search](https://uql-orm.dev/ai-semantic-search), [multi-tenant filters you cannot bypass by accident](https://uql-orm.dev/multi-tenancy), [soft-delete with restore](https://uql-orm.dev/entities/soft-delete), [streaming](https://uql-orm.dev/querying/streaming), and [a REST API from your entities](https://uql-orm.dev/http).
61
- - **The fastest ORM.** On a full PostgreSQL round trip it adds the least over hand-written driver code of any ORM in our open-source [benchmark](https://github.com/rogerpadilla/ts-orm-benchmark): 278µs, against 621µs for the next closest and 1,889µs for the slowest.
61
+ - **The fastest ORM.** On a full PostgreSQL round trip it adds the least over hand-written driver code of any ORM in our open-source [benchmark](https://github.com/rogerpadilla/ts-orm-benchmark): 239µs, against 812µs for the next closest and 2,286µs for the slowest.
62
62
 
63
63
  ## Get started
64
64
 
@@ -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();
@@ -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.1",
6
+ "version": "0.26.3",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"