@warlock.js/cascade 4.15.0 → 4.16.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 (64) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/cjs/index.cjs +494 -72
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/contracts/query-builder.contract.d.mts +7 -3
  5. package/esm/contracts/query-builder.contract.d.mts.map +1 -1
  6. package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
  7. package/esm/drivers/mongodb/mongodb-query-builder.mjs +4 -3
  8. package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
  9. package/esm/drivers/mongodb/mongodb-query-parser.d.mts +21 -0
  10. package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
  11. package/esm/drivers/mongodb/mongodb-query-parser.mjs +55 -22
  12. package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
  13. package/esm/errors/unsafe-filter.error.d.mts +33 -0
  14. package/esm/errors/unsafe-filter.error.d.mts.map +1 -0
  15. package/esm/errors/unsafe-filter.error.mjs +40 -0
  16. package/esm/errors/unsafe-filter.error.mjs.map +1 -0
  17. package/esm/errors/unsafe-raw-expression.error.d.mts +23 -0
  18. package/esm/errors/unsafe-raw-expression.error.d.mts.map +1 -0
  19. package/esm/errors/unsafe-raw-expression.error.mjs +28 -0
  20. package/esm/errors/unsafe-raw-expression.error.mjs.map +1 -0
  21. package/esm/index.d.mts +5 -1
  22. package/esm/index.mjs +5 -1
  23. package/esm/model/methods/accessor-methods.mjs +39 -1
  24. package/esm/model/methods/accessor-methods.mjs.map +1 -1
  25. package/esm/model/methods/delete-methods.mjs +3 -2
  26. package/esm/model/methods/delete-methods.mjs.map +1 -1
  27. package/esm/model/methods/query-methods.mjs +7 -4
  28. package/esm/model/methods/query-methods.mjs.map +1 -1
  29. package/esm/model/methods/serialization-methods.mjs +49 -4
  30. package/esm/model/methods/serialization-methods.mjs.map +1 -1
  31. package/esm/model/methods/write-methods.d.mts.map +1 -1
  32. package/esm/model/methods/write-methods.mjs +2 -4
  33. package/esm/model/methods/write-methods.mjs.map +1 -1
  34. package/esm/model/model.d.mts +47 -1
  35. package/esm/model/model.d.mts.map +1 -1
  36. package/esm/model/model.mjs +61 -2
  37. package/esm/model/model.mjs.map +1 -1
  38. package/esm/model/model.types.d.mts +1 -1
  39. package/esm/query-builder/query-builder.d.mts +13 -1
  40. package/esm/query-builder/query-builder.d.mts.map +1 -1
  41. package/esm/query-builder/query-builder.mjs +28 -11
  42. package/esm/query-builder/query-builder.mjs.map +1 -1
  43. package/esm/remover/database-remover.d.mts.map +1 -1
  44. package/esm/remover/database-remover.mjs +1 -1
  45. package/esm/remover/database-remover.mjs.map +1 -1
  46. package/esm/utils/escape-regex.d.mts +67 -0
  47. package/esm/utils/escape-regex.d.mts.map +1 -0
  48. package/esm/utils/escape-regex.mjs +76 -0
  49. package/esm/utils/escape-regex.mjs.map +1 -0
  50. package/esm/utils/sanitize-filter.d.mts +26 -0
  51. package/esm/utils/sanitize-filter.d.mts.map +1 -0
  52. package/esm/utils/sanitize-filter.mjs +76 -0
  53. package/esm/utils/sanitize-filter.mjs.map +1 -0
  54. package/esm/writer/database-writer.d.mts +12 -0
  55. package/esm/writer/database-writer.d.mts.map +1 -1
  56. package/esm/writer/database-writer.mjs +26 -6
  57. package/esm/writer/database-writer.mjs.map +1 -1
  58. package/llms-full.txt +59 -4
  59. package/llms.txt +3 -3
  60. package/package.json +8 -8
  61. package/skills/README.md +3 -3
  62. package/skills/define-model/SKILL.md +17 -1
  63. package/skills/perform-atomic-ops/SKILL.md +4 -1
  64. package/skills/query-data/SKILL.md +38 -2
@@ -1,3 +1,5 @@
1
+ import { sanitizeFilter, sanitizeFilterValue } from "../utils/sanitize-filter.mjs";
2
+
1
3
  //#region ../cascade/src/query-builder/query-builder.ts
2
4
  /**
3
5
  * Pure, driver-agnostic query builder.
@@ -158,7 +160,7 @@ var QueryBuilder = class QueryBuilder {
158
160
  const sub = this.subQuery();
159
161
  args[0](sub);
160
162
  this.addOperation("where", { nested: sub.operations });
161
- } else if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) for (const [key, value] of Object.entries(args[0])) this.addOperation("where", {
163
+ } else if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) for (const [key, value] of Object.entries(sanitizeFilter(args[0]))) this.addOperation("where", {
162
164
  field: key,
163
165
  operator: "=",
164
166
  value
@@ -166,12 +168,12 @@ var QueryBuilder = class QueryBuilder {
166
168
  else if (args.length === 2) this.addOperation("where", {
167
169
  field: args[0],
168
170
  operator: "=",
169
- value: args[1]
171
+ value: sanitizeFilterValue(args[1], String(args[0]))
170
172
  });
171
173
  else this.addOperation("where", {
172
174
  field: args[0],
173
175
  operator: args[1],
174
- value: args[2]
176
+ value: args[1] === "=" ? sanitizeFilterValue(args[2], String(args[0])) : args[2]
175
177
  });
176
178
  return this;
177
179
  }
@@ -180,7 +182,7 @@ var QueryBuilder = class QueryBuilder {
180
182
  const sub = this.subQuery();
181
183
  args[0](sub);
182
184
  this.addOperation("orWhere", { nested: sub.operations });
183
- } else if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) for (const [key, value] of Object.entries(args[0])) this.addOperation("orWhere", {
185
+ } else if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) for (const [key, value] of Object.entries(sanitizeFilter(args[0]))) this.addOperation("orWhere", {
184
186
  field: key,
185
187
  operator: "=",
186
188
  value
@@ -188,12 +190,12 @@ var QueryBuilder = class QueryBuilder {
188
190
  else if (args.length === 2) this.addOperation("orWhere", {
189
191
  field: args[0],
190
192
  operator: "=",
191
- value: args[1]
193
+ value: sanitizeFilterValue(args[1], String(args[0]))
192
194
  });
193
195
  else this.addOperation("orWhere", {
194
196
  field: args[0],
195
197
  operator: args[1],
196
- value: args[2]
198
+ value: args[1] === "=" ? sanitizeFilterValue(args[2], String(args[0])) : args[2]
197
199
  });
198
200
  return this;
199
201
  }
@@ -303,25 +305,40 @@ var QueryBuilder = class QueryBuilder {
303
305
  }
304
306
  /**
305
307
  * LIKE pattern match (AND).
308
+ *
309
+ * A string is a LIKE pattern (`%` wildcard) and is matched literally
310
+ * otherwise — regex metacharacters in it are escaped by the driver, so
311
+ * search input cannot alter the query. Pass an explicit `RegExp` to opt into
312
+ * raw pattern semantics; never build that `RegExp` from user input.
313
+ *
306
314
  * @example q.whereLike("email", "%@gmail.com")
307
315
  */
308
316
  whereLike(field, pattern) {
309
- const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
310
317
  this.addOperation("whereLike", {
311
318
  field,
312
- pattern: patternStr
319
+ ...this.likePatternData(pattern)
313
320
  });
314
321
  return this;
315
322
  }
316
- /** NOT LIKE pattern match. */
323
+ /** NOT LIKE pattern match. @see whereLike for the escaping rules. */
317
324
  whereNotLike(field, pattern) {
318
- const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
319
325
  this.addOperation("whereNotLike", {
320
326
  field,
321
- pattern: patternStr
327
+ ...this.likePatternData(pattern)
322
328
  });
323
329
  return this;
324
330
  }
331
+ /**
332
+ * Flatten a LIKE argument into operation data, keeping the distinction the
333
+ * drivers need: an explicit `RegExp` (developer-authored) stays a pattern,
334
+ * a string (potentially request input) is a literal.
335
+ */
336
+ likePatternData(pattern) {
337
+ return pattern instanceof RegExp ? {
338
+ pattern: pattern.source,
339
+ isRegExp: true
340
+ } : { pattern };
341
+ }
325
342
  /** Starts with a prefix. */
326
343
  whereStartsWith(field, value) {
327
344
  return this.whereLike(field, `${value}%`);
@@ -1 +1 @@
1
- {"version":3,"file":"query-builder.mjs","names":[],"sources":["../../../../../../../cascade/src/query-builder/query-builder.ts"],"sourcesContent":["/**\n * Pure Query Builder Base Class\n *\n * Driver-agnostic operation recorder. All fluent methods push typed entries into\n * `operations[]`. No SQL, no driver references, no table property, no execution.\n *\n * ┌─────────────────────────────────────────────────┐\n * │ Usage contexts │\n * │ (a) Subclassed — PG / Mongo / MySQL / … │\n * │ (b) Instantiated directly (new QueryBuilder()) │\n * │ inside callbacks for: │\n * │ • nested where groups │\n * │ • joinWith constraints │\n * │ • whereExists / whereHas subqueries │\n * └─────────────────────────────────────────────────┘\n *\n * Design rules:\n * - `table` / alias are NOT here — the parser gets them from the executor.\n * - `opIndex` is protected so subclasses can rebuild after direct mutation.\n * - Op type names are stable — parsers switch on them; no renaming without\n * a parser update.\n * - OR-variants keep distinct op types (orWhere, orWhereColumn, …) so existing\n * parsers that switch on type need no changes.\n * - `joinWith` eagerly resolves callbacks → subOps at record time so the\n * driver executor receives a plain data structure, not a live function.\n *\n * @module cascade/query-builder\n */\n\nimport type {\n GroupByInput,\n HavingInput,\n JoinOptions,\n LockForUpdateOptions,\n OrderDirection,\n RawExpression,\n WhereCallback,\n WhereObject,\n WhereOperator,\n} from \"../contracts/query-builder.contract\";\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\n/**\n * A single recorded query operation.\n * `type` is the discriminator; `data` carries all parameters.\n */\nexport type Op = {\n readonly type: string;\n readonly data: Record<string, unknown>;\n};\n\n/**\n * Constraint value accepted by `joinWith()`.\n *\n * - `string` → comma-separated column shorthand: `\"id,name,createdAt\"`\n * - `fn` → callback receives a bare QueryBuilder to record sub-ops\n *\n * @example\n * joinWith({ actions: \"id,status\" })\n * joinWith({ actions: q => q.where(\"status\", \"pending\").limit(5) })\n */\nexport type JoinWithConstraint = string | ((q: QueryBuilder) => void);\n\n// ============================================================================\n// QUERY BUILDER — CONCRETE, DIRECTLY INSTANTIABLE\n// ============================================================================\n\n/**\n * Pure, driver-agnostic query builder.\n *\n * Records operations in `operations[]`. Subclasses own execution, parsing, and\n * driver-specific clause generation. Safe to instantiate directly inside\n * callbacks where only operation recording is needed.\n *\n * @example\n * ```ts\n * // Driver subclass usage:\n * const users = await User.query()\n * .select([\"id\", \"name\"])\n * .where(\"status\", \"active\")\n * .where(q => q.where(\"role\", \"admin\").orWhere(\"role\", \"mod\"))\n * .orderBy(\"createdAt\", \"desc\")\n * .limit(10)\n * .get();\n *\n * // Direct instantiation (callback context — no driver needed):\n * joinWith({ actions: q => q.where(\"status\", \"pending\").limit(5) });\n * // The sub-QB's operations[] are captured and stored in the joinWith op data.\n * ```\n */\nexport class QueryBuilder<T = unknown> {\n // ════════════════════════════════════════════════════════\n // OPERATION STORE\n // ════════════════════════════════════════════════════════\n\n /** Flat, ordered list of recorded operations. Public for parser access. */\n public operations: Op[] = [];\n\n /**\n * type → ordered list of indices into `operations[]`.\n *\n * Protected (not private) so:\n * - `rebuildIndex()` can reset it after direct `operations[]` mutation.\n * - Subclasses can inspect it without unsafe casts.\n *\n * External consumers should use `getOps(type)` instead.\n */\n protected opIndex: Map<string, number[]> = new Map();\n\n // ════════════════════════════════════════════════════════\n // SCOPE STATE (injected by Model.query(), consumed before execution)\n // ════════════════════════════════════════════════════════\n\n /** Global scope definitions injected by Model.query(). Keyed by scope name. */\n public pendingGlobalScopes?: Map<string, any>;\n /** Local scope callbacks injected by Model.query(). Applied on demand via scope(). */\n public availableLocalScopes?: Map<string, (...args: any[]) => void>;\n /** Names of global scopes that have been intentionally disabled. */\n public disabledGlobalScopes: Set<string> = new Set();\n /** True once the driver subclass has applied pending scopes. */\n public scopesApplied = false;\n\n // ════════════════════════════════════════════════════════\n // RELATION STATE (consumed by driver subclass at execute time)\n // ════════════════════════════════════════════════════════\n\n /** Relations to eager-load via separate queries. */\n public eagerLoadRelations: Map<string, boolean | ((query: any) => void)> = new Map();\n /** Count expressions to emit per result row, keyed by output column alias. */\n public countRelations: Map<string, { relation: string; constraintOps?: Op[] }> = new Map();\n /** Relation definition map injected from the owning Model. */\n public relationDefinitions?: Record<string, any>;\n /** The Model class reference, required for relation resolution. */\n public modelClass?: any;\n\n // ════════════════════════════════════════════════════════\n // CORE INTERNALS\n // ════════════════════════════════════════════════════════\n\n /**\n * Append an operation to `operations[]` and update `opIndex`.\n * Every fluent method calls this.\n */\n protected addOperation(type: string, data: Record<string, unknown>): void {\n const idx = this.operations.length;\n this.operations.push({ type, data });\n const list = this.opIndex.get(type);\n if (list) {\n list.push(idx);\n } else {\n this.opIndex.set(type, [idx]);\n }\n }\n\n /**\n * Return all recorded operations of the specified types in original\n * insertion order.\n *\n * @example\n * builder.getOps(\"where\", \"orWhere\", \"whereIn\")\n */\n public getOps(...types: string[]): Op[] {\n if (types.length === 1) {\n return (this.opIndex.get(types[0]) ?? []).map((i) => this.operations[i]);\n }\n const result: Array<{ idx: number; op: Op }> = [];\n for (const type of types) {\n for (const idx of this.opIndex.get(type) ?? []) {\n result.push({ idx, op: this.operations[idx] });\n }\n }\n return result.sort((a, b) => a.idx - b.idx).map((r) => r.op);\n }\n\n /**\n * Rebuild `opIndex` from scratch.\n *\n * Call this after any direct mutation of `this.operations[]` (e.g. scope\n * injection, joinWith consumption in the executor, clone post-processing).\n */\n public rebuildIndex(): void {\n this.opIndex = new Map();\n for (let i = 0; i < this.operations.length; i++) {\n const type = this.operations[i].type;\n const list = this.opIndex.get(type);\n if (list) {\n list.push(i);\n } else {\n this.opIndex.set(type, [i]);\n }\n }\n }\n\n /**\n * Factory for sub-QueryBuilders used inside callbacks.\n *\n * Override in driver subclasses to return a driver-typed instance, so that\n * driver-specific methods (e.g. `whereArrayContains`) are available inside\n * nested `where(q => ...)` / `whereHas` / `joinWith` callbacks.\n *\n * @example\n * // In PostgresQueryBuilder:\n * protected override subQuery(): QueryBuilder {\n * return new PostgresQueryBuilder(\"__sub__\", this.dataSource);\n * }\n */\n protected subQuery(): QueryBuilder {\n return new QueryBuilder();\n }\n\n /**\n * Shallow-clone this builder — copies operations, opIndex, and all shared state.\n *\n * Subclasses MUST call `super.clone()` and then copy their own fields\n * (dataSource, joinRelations, …).\n */\n public clone(): this {\n const cloned = Object.create(Object.getPrototypeOf(this)) as this;\n cloned.operations = [...this.operations];\n cloned.opIndex = new Map(Array.from(this.opIndex.entries()).map(([k, v]) => [k, [...v]]));\n cloned.pendingGlobalScopes = this.pendingGlobalScopes;\n cloned.availableLocalScopes = this.availableLocalScopes;\n cloned.disabledGlobalScopes = new Set(this.disabledGlobalScopes);\n cloned.scopesApplied = this.scopesApplied;\n cloned.eagerLoadRelations = new Map(this.eagerLoadRelations);\n cloned.countRelations = new Map(this.countRelations);\n cloned.relationDefinitions = this.relationDefinitions;\n cloned.modelClass = this.modelClass;\n return cloned;\n }\n\n // ════════════════════════════════════════════════════════\n // SCOPES\n // ════════════════════════════════════════════════════════\n\n /** Disable one or more named global scopes for this query. */\n public withoutGlobalScope(...scopeNames: string[]): this {\n scopeNames.forEach((name) => this.disabledGlobalScopes.add(name));\n return this;\n }\n\n /** Disable ALL pending global scopes for this query. */\n public withoutGlobalScopes(): this {\n this.pendingGlobalScopes?.forEach((_, name) => this.disabledGlobalScopes.add(name));\n return this;\n }\n\n /**\n * Apply a registered local scope by name.\n * @throws if no local scopes are available or the named scope is not found\n */\n public scope(scopeName: string, ...args: unknown[]): this {\n if (!this.availableLocalScopes) {\n throw new Error(\"No local scopes available on this query builder.\");\n }\n const cb = this.availableLocalScopes.get(scopeName);\n if (!cb) throw new Error(`Local scope \"${scopeName}\" not found.`);\n cb(this, ...args);\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // WHERE CLAUSES — CORE\n // ════════════════════════════════════════════════════════\n\n /**\n * Add a WHERE clause (AND).\n *\n * @example\n * q.where(\"status\", \"active\")\n * q.where(\"age\", \">\", 18)\n * q.where({ role: \"admin\", active: true })\n * q.where(q => q.where(\"a\", 1).orWhere(\"b\", 2))\n */\n public where(field: string, value: unknown): this;\n public where(field: string, operator: WhereOperator, value: unknown): this;\n public where(conditions: WhereObject): this;\n public where(callback: WhereCallback<T>): this;\n public where(...args: unknown[]): this {\n if (args.length === 1 && typeof args[0] === \"function\") {\n const sub = this.subQuery();\n (args[0] as (q: QueryBuilder) => void)(sub);\n this.addOperation(\"where\", { nested: sub.operations });\n } else if (args.length === 1 && typeof args[0] === \"object\" && args[0] !== null) {\n for (const [key, value] of Object.entries(args[0] as WhereObject)) {\n this.addOperation(\"where\", { field: key, operator: \"=\", value });\n }\n } else if (args.length === 2) {\n this.addOperation(\"where\", { field: args[0], operator: \"=\", value: args[1] });\n } else {\n this.addOperation(\"where\", { field: args[0], operator: args[1], value: args[2] });\n }\n return this;\n }\n\n /**\n * Add an OR WHERE clause.\n *\n * @example\n * q.where(\"role\", \"admin\").orWhere(\"role\", \"mod\")\n */\n public orWhere(field: string, value: unknown): this;\n public orWhere(field: string, operator: WhereOperator, value: unknown): this;\n public orWhere(conditions: WhereObject): this;\n public orWhere(callback: WhereCallback<T>): this;\n public orWhere(...args: unknown[]): this {\n if (args.length === 1 && typeof args[0] === \"function\") {\n const sub = this.subQuery();\n (args[0] as (q: QueryBuilder) => void)(sub);\n this.addOperation(\"orWhere\", { nested: sub.operations });\n } else if (args.length === 1 && typeof args[0] === \"object\" && args[0] !== null) {\n for (const [key, value] of Object.entries(args[0] as WhereObject)) {\n this.addOperation(\"orWhere\", { field: key, operator: \"=\", value });\n }\n } else if (args.length === 2) {\n this.addOperation(\"orWhere\", { field: args[0], operator: \"=\", value: args[1] });\n } else {\n this.addOperation(\"orWhere\", { field: args[0], operator: args[1], value: args[2] });\n }\n return this;\n }\n\n /**\n * Raw WHERE expression in the target dialect (AND).\n *\n * @example\n * q.whereRaw(\"age > ? AND role = ?\", [18, \"admin\"]) // SQL\n * q.whereRaw({ $expr: { $gt: [\"$stock\", \"$reserved\"] } }) // MongoDB\n */\n public whereRaw(expression: RawExpression, bindings?: unknown[]): this {\n this.addOperation(\"whereRaw\", { expression, bindings: bindings ?? [] });\n return this;\n }\n\n /** Raw OR WHERE expression. */\n public orWhereRaw(expression: RawExpression, bindings?: unknown[]): this {\n this.addOperation(\"orWhereRaw\", { expression, bindings: bindings ?? [] });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // WHERE CLAUSES — COLUMN COMPARISONS\n // ════════════════════════════════════════════════════════\n\n /**\n * Compare two columns directly (AND).\n * @example q.whereColumn(\"stock\", \">\", \"reserved\")\n */\n public whereColumn(first: string, operator: WhereOperator, second: string): this {\n this.addOperation(\"whereColumn\", { first, operator, second });\n return this;\n }\n\n /** Compare two columns directly (OR). */\n public orWhereColumn(first: string, operator: WhereOperator, second: string): this {\n this.addOperation(\"orWhereColumn\", { first, operator, second });\n return this;\n }\n\n /** Compare multiple column pairs in one call. */\n public whereColumns(\n comparisons: Array<[left: string, operator: WhereOperator, right: string]>,\n ): this {\n for (const [left, operator, right] of comparisons) {\n this.whereColumn(left, operator, right);\n }\n return this;\n }\n\n /**\n * Field value must fall between two other column values.\n * Stored as a `whereBetween` op with `useColumns: true` so the SQL parser\n * knows to quote the values as identifiers rather than bind them.\n */\n public whereBetweenColumns(field: string, lowerColumn: string, upperColumn: string): this {\n this.addOperation(\"whereBetween\", { field, lowerColumn, upperColumn, useColumns: true });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // WHERE CLAUSES — STANDARD COMPARISON OPERATORS\n // ════════════════════════════════════════════════════════\n\n /** WHERE field IN values. */\n public whereIn(field: string, values: unknown[]): this {\n this.addOperation(\"whereIn\", { field, values });\n return this;\n }\n\n /** WHERE field NOT IN values. */\n public whereNotIn(field: string, values: unknown[]): this {\n this.addOperation(\"whereNotIn\", { field, values });\n return this;\n }\n\n /** WHERE field IS NULL. */\n public whereNull(field: string): this {\n this.addOperation(\"whereNull\", { field });\n return this;\n }\n\n /** WHERE field IS NOT NULL. */\n public whereNotNull(field: string): this {\n this.addOperation(\"whereNotNull\", { field });\n return this;\n }\n\n /** WHERE field BETWEEN low AND high. */\n public whereBetween(field: string, range: [unknown, unknown]): this {\n this.addOperation(\"whereBetween\", { field, range });\n return this;\n }\n\n /** WHERE field NOT BETWEEN low AND high. */\n public whereNotBetween(field: string, range: [unknown, unknown]): this {\n this.addOperation(\"whereNotBetween\", { field, range });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // WHERE CLAUSES — PATTERN MATCHING\n // ════════════════════════════════════════════════════════\n\n /**\n * LIKE pattern match (AND).\n * @example q.whereLike(\"email\", \"%@gmail.com\")\n */\n public whereLike(field: string, pattern: RegExp | string): this {\n const patternStr = pattern instanceof RegExp ? pattern.source : pattern;\n this.addOperation(\"whereLike\", { field, pattern: patternStr });\n return this;\n }\n\n /** NOT LIKE pattern match. */\n public whereNotLike(field: string, pattern: RegExp | string): this {\n const patternStr = pattern instanceof RegExp ? pattern.source : pattern;\n this.addOperation(\"whereNotLike\", { field, pattern: patternStr });\n return this;\n }\n\n /** Starts with a prefix. */\n public whereStartsWith(field: string, value: string | number): this {\n return this.whereLike(field, `${value}%`);\n }\n\n /** Does NOT start with a prefix. */\n public whereNotStartsWith(field: string, value: string | number): this {\n return this.whereNotLike(field, `${value}%`);\n }\n\n /** Ends with a suffix. */\n public whereEndsWith(field: string, value: string | number): this {\n return this.whereLike(field, `%${value}`);\n }\n\n /** Does NOT end with a suffix. */\n public whereNotEndsWith(field: string, value: string | number): this {\n return this.whereNotLike(field, `%${value}`);\n }\n\n // ════════════════════════════════════════════════════════\n // WHERE CLAUSES — DATE/TIME PARTIALS\n // ════════════════════════════════════════════════════════\n\n /**\n * Match on date portion only (time ignored).\n * @example q.whereDate(\"createdAt\", \"2024-05-01\")\n */\n public whereDate(field: string, value: Date | string): this {\n this.addOperation(\"whereDate\", { field, value });\n return this;\n }\n\n /** Alias for whereDate. */\n public whereDateEquals(field: string, value: Date | string): this {\n return this.whereDate(field, value);\n }\n\n /** Field date is before value. */\n public whereDateBefore(field: string, value: Date | string): this {\n this.addOperation(\"whereDateBefore\", { field, value });\n return this;\n }\n\n /** Field date is after value. */\n public whereDateAfter(field: string, value: Date | string): this {\n this.addOperation(\"whereDateAfter\", { field, value });\n return this;\n }\n\n /** Field date is within a range [from, to]. */\n public whereDateBetween(field: string, range: [Date | string, Date | string]): this {\n this.addOperation(\"whereDateBetween\", { field, range });\n return this;\n }\n\n /** Field date is NOT within a range. */\n public whereDateNotBetween(field: string, range: [Date | string, Date | string]): this {\n this.addOperation(\"whereNotBetween\", { field, range });\n return this;\n }\n\n /**\n * Match on the time portion of a datetime field.\n * Emits a `whereRaw` op with a driver-agnostic marker; the driver parser\n * rewrites it to the appropriate SQL (`TIME(field) = ?`) or Mongo expression.\n */\n public whereTime(field: string, value: string): this {\n this.addOperation(\"whereRaw\", {\n expression: `TIME(${field}) = ?`,\n bindings: [value],\n });\n return this;\n }\n\n /**\n * Day-of-month from a date field (1–31).\n * Uses a `whereRaw` op so SQL parsers get the `EXTRACT` expression directly.\n * MongoDB drivers override to emit `$dayOfMonth`.\n */\n public whereDay(field: string, value: number): this {\n this.addOperation(\"whereRaw\", {\n expression: `EXTRACT(DAY FROM ${field}) = ?`,\n bindings: [value],\n });\n return this;\n }\n\n /** Month extracted from a date field (1–12). */\n public whereMonth(field: string, value: number): this {\n this.addOperation(\"whereRaw\", {\n expression: `EXTRACT(MONTH FROM ${field}) = ?`,\n bindings: [value],\n });\n return this;\n }\n\n /** Year extracted from a date field. */\n public whereYear(field: string, value: number): this {\n this.addOperation(\"whereRaw\", {\n expression: `EXTRACT(YEAR FROM ${field}) = ?`,\n bindings: [value],\n });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // WHERE CLAUSES — JSON / STRUCTURED DATA\n // ════════════════════════════════════════════════════════\n\n /**\n * JSON/array path contains the given value.\n * @example q.whereJsonContains(\"tags\", \"typescript\")\n */\n public whereJsonContains(path: string, value: unknown): this {\n this.addOperation(\"whereJsonContains\", { path, value });\n return this;\n }\n\n /** JSON/array path does NOT contain the value. */\n public whereJsonDoesntContain(path: string, value: unknown): this {\n this.addOperation(\"whereJsonDoesntContain\", { path, value });\n return this;\n }\n\n /**\n * JSON path key exists.\n * Uses a `whereRaw` so existing SQL parsers get `IS NOT NULL` immediately.\n */\n public whereJsonContainsKey(path: string): this {\n this.addOperation(\"whereRaw\", { expression: `${path} IS NOT NULL`, bindings: [] });\n return this;\n }\n\n /**\n * Constrain the length of a JSON array at a path.\n * @example q.whereJsonLength(\"tags\", \">\", 3)\n */\n public whereJsonLength(path: string, operator: WhereOperator, value: number): this {\n this.addOperation(\"whereRaw\", {\n expression: `jsonb_array_length(${path}) ${operator} ?`,\n bindings: [value],\n });\n return this;\n }\n\n /** JSON path must resolve to an array. */\n public whereJsonIsArray(path: string): this {\n this.addOperation(\"whereRaw\", {\n expression: `jsonb_typeof(${path}) = 'array'`,\n bindings: [],\n });\n return this;\n }\n\n /** JSON path must resolve to an object. */\n public whereJsonIsObject(path: string): this {\n this.addOperation(\"whereRaw\", {\n expression: `jsonb_typeof(${path}) = 'object'`,\n bindings: [],\n });\n return this;\n }\n\n /**\n * Constrain the number of elements in an array field.\n * @example q.whereArrayLength(\"roles\", \">=\", 2)\n */\n public whereArrayLength(field: string, operator: WhereOperator, value: number): this {\n this.addOperation(\"whereRaw\", {\n expression: `array_length(${field}, 1) ${operator} ?`,\n bindings: [value],\n });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // WHERE CLAUSES — CONVENIENCE SHORTCUTS\n // ════════════════════════════════════════════════════════\n\n /** WHERE id = value. */\n public whereId(value: string | number): this {\n return this.where(\"id\", value);\n }\n\n /** WHERE id IN values. */\n public whereIds(values: Array<string | number>): this {\n return this.whereIn(\"id\", values);\n }\n\n /** WHERE uuid = value. */\n public whereUuid(value: string): this {\n return this.where(\"uuid\", value);\n }\n\n /** WHERE ulid = value. */\n public whereUlid(value: string): this {\n return this.where(\"ulid\", value);\n }\n\n /**\n * Full-text search across one or more fields.\n * @example q.whereFullText([\"title\", \"body\"], \"typescript\")\n */\n public whereFullText(fields: string | string[], query: string): this {\n this.addOperation(\"whereFullText\", {\n fields: Array.isArray(fields) ? fields : [fields],\n query,\n });\n return this;\n }\n\n /** Full-text search (OR). */\n public orWhereFullText(fields: string | string[], query: string): this {\n return this.whereFullText(fields, query);\n }\n\n /** Alias for whereFullText with a single field. */\n public whereSearch(field: string, query: string): this {\n return this.whereFullText([field], query);\n }\n\n /**\n * Text search with optional extra equality filters.\n * MongoDB-style convenience shorthand.\n */\n public textSearch(query: string, filters?: WhereObject): this {\n if (filters) {\n for (const [key, value] of Object.entries(filters)) this.where(key, value as never);\n }\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // WHERE CLAUSES — EXISTENCE / SUBQUERIES\n // ════════════════════════════════════════════════════════\n\n /**\n * WHERE EXISTS (subquery callback) or field IS NOT NULL (string).\n *\n * @example\n * q.whereExists(sub => sub.where(\"userId\", \"users.id\"))\n * q.whereExists(\"optionalField\")\n */\n public whereExists(field: string): this;\n public whereExists(callback: WhereCallback<T>): this;\n public whereExists(param: string | WhereCallback<T>): this {\n if (typeof param === \"function\") {\n const sub = this.subQuery();\n param(sub as any);\n this.addOperation(\"whereExists\", { subquery: sub.operations });\n } else {\n this.addOperation(\"whereNotNull\", { field: param });\n }\n return this;\n }\n\n /**\n * WHERE NOT EXISTS (subquery callback) or field IS NULL (string).\n */\n public whereNotExists(field: string): this;\n public whereNotExists(callback: WhereCallback<T>): this;\n public whereNotExists(param: string | WhereCallback<T>): this {\n if (typeof param === \"function\") {\n const sub = this.subQuery();\n param(sub as any);\n this.addOperation(\"whereNotExists\", { subquery: sub.operations });\n } else {\n this.addOperation(\"whereNull\", { field: param });\n }\n return this;\n }\n\n /**\n * Constrain an array/collection field by element count.\n *\n * @example\n * q.whereSize(\"tags\", 3) // exactly 3\n * q.whereSize(\"tags\", \">=\", 1) // at least 1\n */\n public whereSize(field: string, size: number): this;\n public whereSize(field: string, operator: WhereOperator, size: number): this;\n public whereSize(field: string, ...args: unknown[]): this {\n const operator = args.length === 2 ? (args[0] as WhereOperator) : \"=\";\n const size = (args.length === 2 ? args[1] : args[0]) as number;\n return this.whereArrayLength(field, operator, size);\n }\n\n /**\n * AND NOT wrapper — negate a nested group.\n * @example q.whereNot(q => q.where(\"status\", \"banned\").where(\"role\", \"user\"))\n */\n public whereNot(callback: WhereCallback<T>): this {\n const sub = this.subQuery();\n callback(sub as any);\n this.addOperation(\"whereNot\", { nested: sub.operations });\n return this;\n }\n\n /** OR NOT wrapper. */\n public orWhereNot(callback: WhereCallback<T>): this {\n const sub = this.subQuery();\n callback(sub as any);\n this.addOperation(\"orWhereNot\", { nested: sub.operations });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // JOINS — STANDARD SQL-STYLE\n // Note: Op type names match parser switch cases exactly.\n // join / innerJoin → INNER JOIN\n // leftJoin → LEFT JOIN\n // rightJoin → RIGHT JOIN\n // fullJoin → FULL OUTER JOIN\n // crossJoin → CROSS JOIN\n // joinRaw → raw expression\n // ════════════════════════════════════════════════════════\n\n /**\n * INNER JOIN.\n * @example q.join(\"categories\", \"posts.categoryId\", \"categories.id\")\n */\n public join(table: string, localField: string, foreignField: string): this;\n public join(options: JoinOptions): this;\n public join(...args: unknown[]): this {\n if (args.length === 3) {\n this.addOperation(\"join\", { table: args[0], localField: args[1], foreignField: args[2] });\n } else {\n this.addOperation(\"join\", args[0] as Record<string, unknown>);\n }\n return this;\n }\n\n /** LEFT JOIN. */\n public leftJoin(table: string, localField: string, foreignField: string): this;\n public leftJoin(options: JoinOptions): this;\n public leftJoin(...args: unknown[]): this {\n if (args.length === 3) {\n this.addOperation(\"leftJoin\", { table: args[0], localField: args[1], foreignField: args[2] });\n } else {\n this.addOperation(\"leftJoin\", args[0] as Record<string, unknown>);\n }\n return this;\n }\n\n /** RIGHT JOIN. */\n public rightJoin(table: string, localField: string, foreignField: string): this;\n public rightJoin(options: JoinOptions): this;\n public rightJoin(...args: unknown[]): this {\n if (args.length === 3) {\n this.addOperation(\"rightJoin\", {\n table: args[0],\n localField: args[1],\n foreignField: args[2],\n });\n } else {\n this.addOperation(\"rightJoin\", args[0] as Record<string, unknown>);\n }\n return this;\n }\n\n /** INNER JOIN (alias for join). */\n public innerJoin(table: string, localField: string, foreignField: string): this;\n public innerJoin(options: JoinOptions): this;\n public innerJoin(...args: unknown[]): this {\n if (args.length === 3) {\n this.addOperation(\"innerJoin\", {\n table: args[0],\n localField: args[1],\n foreignField: args[2],\n });\n } else {\n this.addOperation(\"innerJoin\", args[0] as Record<string, unknown>);\n }\n return this;\n }\n\n /** FULL OUTER JOIN. */\n public fullJoin(table: string, localField: string, foreignField: string): this;\n public fullJoin(options: JoinOptions): this;\n public fullJoin(...args: unknown[]): this {\n if (args.length === 3) {\n this.addOperation(\"fullJoin\", { table: args[0], localField: args[1], foreignField: args[2] });\n } else {\n this.addOperation(\"fullJoin\", args[0] as Record<string, unknown>);\n }\n return this;\n }\n\n /** CROSS JOIN. */\n public crossJoin(table: string): this {\n this.addOperation(\"crossJoin\", { table });\n return this;\n }\n\n /** Raw JOIN expression. Driver responsible for handling. */\n public joinRaw(expression: RawExpression, bindings?: unknown[]): this {\n this.addOperation(\"joinRaw\", { expression, bindings: bindings ?? [] });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // RELATION EAGER LOADING — JOIN-BASED (joinWith)\n // ════════════════════════════════════════════════════════\n\n /**\n * Eager-load named relations via a single JOIN / $lookup query.\n *\n * Constraints are eagerly resolved at call time:\n * - Callbacks are invoked immediately → `subOps` stored in op data.\n * - Column shorthands are parsed into a `columns[]` array.\n *\n * The driver executor reads the `joinWith` op and uses the resolved data\n * alongside its own relation definition map to emit the appropriate SQL JOIN\n * or MongoDB $lookup stage.\n *\n * Supported arg forms (may be mixed):\n * - `\"author\"` / `[\"author\", \"category\"]` — no constraint\n * - `{ author: \"id,name\" }` — column shorthand\n * - `{ actions: q => q.where(\"status\",\"pending\").limit(5) }` — callback\n *\n * @example\n * Post.joinWith(\"author\", \"category\")\n * ChatMessage.joinWith({ actions: q => q.where(\"status\", \"pending\").limit(5) })\n * ChatMessage.joinWith({ org: \"id,name\", actions: q => q.orderBy(\"sort_order\") })\n */\n public joinWith(...args: unknown[]): this {\n const resolved: Record<string, { columns?: string[]; subOps?: Op[] }> = {};\n\n for (const arg of args) {\n if (typeof arg === \"string\") {\n resolved[arg] = {};\n } else if (Array.isArray(arg)) {\n for (const rel of arg as string[]) resolved[rel] = {};\n } else if (typeof arg === \"object\" && arg !== null) {\n for (const [rel, constraint] of Object.entries(arg as Record<string, JoinWithConstraint>)) {\n if (typeof constraint === \"function\") {\n const sub = this.subQuery();\n constraint(sub);\n resolved[rel] = { subOps: sub.operations };\n } else if (typeof constraint === \"string\" && constraint !== \"\") {\n resolved[rel] = {\n columns: constraint\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean),\n };\n } else {\n resolved[rel] = {};\n }\n }\n }\n }\n\n this.addOperation(\"joinWith\", { resolved });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // RELATION EAGER LOADING — SEPARATE QUERIES (with)\n // ════════════════════════════════════════════════════════\n\n /**\n * Eager-load relations via separate queries (N+1 avoided by batching).\n *\n * @example\n * q.with(\"posts\")\n * q.with(\"posts\", q => q.where(\"published\", true))\n * q.with({ posts: true, comments: q => q.limit(5) })\n */\n public with(\n ...args: (string | Record<string, boolean | ((q: any) => void)> | ((q: any) => void))[]\n ): this {\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (typeof arg === \"string\") {\n const next = args[i + 1];\n if (typeof next === \"function\") {\n this.eagerLoadRelations.set(arg, next as (q: any) => void);\n i++;\n } else {\n this.eagerLoadRelations.set(arg, true);\n }\n } else if (typeof arg === \"object\" && arg !== null) {\n for (const [key, value] of Object.entries(\n arg as Record<string, boolean | ((q: any) => void)>,\n )) {\n this.eagerLoadRelations.set(key, value);\n }\n }\n }\n return this;\n }\n\n /**\n * Register one or more relation counts to emit alongside each result row.\n *\n * Accepts:\n * - Bare relation names (variadic strings or array): `withCount(\"posts\", \"comments\")`\n * - Alias shorthand: `withCount(\"posts as totalPosts\")`\n * - Object form for per-relation constraints / aliases:\n * `withCount({ posts: true, \"posts as approved\": (q) => q.where(\"approved\", true) })`\n *\n * Each entry is stored in `countRelations` keyed by its output column alias\n * (default `${relationName}Count`). The driver subclass consumes the map at\n * execute time to emit count expressions.\n *\n * @example\n * ```typescript\n * await User.query().withCount(\"posts\").get(); // postsCount\n * await User.query().withCount(\"posts as totalPosts\").get(); // totalPosts\n * await User.query()\n * .withCount({\n * posts: true,\n * \"posts as published\": (q) => q.where(\"isPublished\", true),\n * comments: \"commentTotal\",\n * })\n * .get();\n * ```\n */\n public withCount(...args: unknown[]): this {\n for (const arg of args) {\n if (typeof arg === \"string\") {\n this.recordCountEntry(arg);\n continue;\n }\n\n if (Array.isArray(arg)) {\n for (const spec of arg as string[]) {\n this.recordCountEntry(spec);\n }\n continue;\n }\n\n if (typeof arg === \"object\" && arg !== null) {\n const entries = Object.entries(\n arg as Record<string, true | string | ((query: any) => void)>,\n );\n\n for (const [key, value] of entries) {\n if (value === true) {\n this.recordCountEntry(key);\n } else if (typeof value === \"string\") {\n this.recordCountEntry(`${key} as ${value}`);\n } else if (typeof value === \"function\") {\n this.recordCountEntry(key, value);\n }\n }\n }\n }\n\n return this;\n }\n\n /**\n * Parse a count spec (\"relation\" or \"relation as alias\") into its relation\n * name and output alias, optionally capturing a constraint callback's\n * operations via a sub-builder. Stored in `countRelations` keyed by alias.\n */\n protected recordCountEntry(spec: string, constraint?: (query: any) => void): void {\n const { relation, alias } = this.parseCountSpec(spec);\n\n let constraintOps: Op[] | undefined;\n\n if (constraint) {\n const sub = this.subQuery();\n constraint(sub);\n constraintOps = sub.operations;\n }\n\n this.countRelations.set(alias, { relation, constraintOps });\n }\n\n /**\n * Split a `\"<relation>\"` or `\"<relation> as <alias>\"` spec. Returns the\n * resolved relation name and the output column alias (defaulting to\n * `${relation}Count` when no `as` is present).\n */\n protected parseCountSpec(spec: string): { relation: string; alias: string } {\n const trimmed = spec.trim();\n const match = /^(.+?)\\s+as\\s+(.+)$/i.exec(trimmed);\n\n if (!match) {\n return { relation: trimmed, alias: `${trimmed}Count` };\n }\n\n return { relation: match[1].trim(), alias: match[2].trim() };\n }\n\n /**\n * Filter to rows that have at least one related record.\n * @example q.has(\"comments\")\n * @example q.has(\"comments\", \">=\", 3)\n */\n public has(relation: string, operator?: WhereOperator, count?: number): this {\n this.addOperation(\"has\", { relation, operator: operator ?? \">=\", count: count ?? 1 });\n return this;\n }\n\n /**\n * Filter to rows with related records matching a sub-query (AND).\n * @example q.whereHas(\"comments\", q => q.where(\"approved\", true))\n */\n public whereHas(relation: string, callback: (q: any) => void): this {\n const sub = this.subQuery();\n callback(sub);\n this.addOperation(\"whereHas\", { relation, subquery: sub.operations });\n return this;\n }\n\n /** Same as whereHas but OR-joined. */\n public orWhereHas(relation: string, callback: (q: any) => void): this {\n const sub = this.subQuery();\n callback(sub);\n this.addOperation(\"orWhereHas\", { relation, subquery: sub.operations });\n return this;\n }\n\n /** Filter to rows with NO related records. */\n public doesntHave(relation: string): this {\n this.addOperation(\"doesntHave\", { relation });\n return this;\n }\n\n /** Filter to rows with NO related records matching conditions. */\n public whereDoesntHave(relation: string, callback: (q: any) => void): this {\n const sub = this.subQuery();\n callback(sub);\n this.addOperation(\"whereDoesntHave\", { relation, subquery: sub.operations });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // SELECT / PROJECTION\n // ════════════════════════════════════════════════════════\n\n /**\n * Select specific columns.\n *\n * @example\n * q.select([\"id\", \"name\"])\n * q.select(\"id\", \"name\")\n * q.select({ name: 1, password: 0 }) // MongoDB-style projection\n */\n public select(fields: string[]): this;\n public select(fields: Record<string, 0 | 1 | boolean>): this;\n public select(...fields: Array<string | string[]>): this;\n public select(...args: unknown[]): this {\n if (args.length === 1 && Array.isArray(args[0])) {\n this.addOperation(\"select\", { fields: args[0] });\n } else if (args.length === 1 && typeof args[0] === \"object\" && !Array.isArray(args[0])) {\n this.addOperation(\"select\", { fields: args[0] as Record<string, unknown> });\n } else {\n this.addOperation(\"select\", { fields: (args as Array<string | string[]>).flat() });\n }\n return this;\n }\n\n /** Select a field under an alias. @example q.selectAs(\"fullName\", \"name\") */\n public selectAs(field: string, alias: string): this {\n this.addOperation(\"select\", { fields: { [field]: alias } });\n return this;\n }\n\n /**\n * Raw SELECT expression.\n * @example q.selectRaw(\"COUNT(*) AS total\")\n */\n public selectRaw(expression: RawExpression, bindings?: unknown[]): this {\n this.addOperation(\"selectRaw\", { expression, bindings: bindings ?? [] });\n return this;\n }\n\n /** Multiple raw SELECT expressions in one call. */\n public selectRawMany(\n definitions: Array<{ alias: string; expression: RawExpression; bindings?: unknown[] }>,\n ): this {\n for (const def of definitions) {\n this.selectRaw({ [def.alias]: def.expression }, def.bindings);\n }\n return this;\n }\n\n /** Subquery as a named projected field. */\n public selectSub(expression: RawExpression, alias: string): this {\n this.addOperation(\"selectRaw\", { expression: { [alias]: expression } });\n return this;\n }\n\n /** Alias for selectSub. */\n public addSelectSub(expression: RawExpression, alias: string): this {\n return this.selectSub(expression, alias);\n }\n\n /**\n * Aggregate function as a projected field.\n * @example q.selectAggregate(\"price\", \"sum\", \"totalRevenue\")\n */\n public selectAggregate(\n field: string,\n aggregate: \"sum\" | \"avg\" | \"min\" | \"max\" | \"count\" | \"first\" | \"last\",\n alias: string,\n ): this {\n return this.selectRaw({ [alias]: `${aggregate.toUpperCase()}(${field})` });\n }\n\n /** Existence check as a projected boolean field. */\n public selectExists(field: string, alias: string): this {\n return this.selectRaw({ [alias]: `${field} IS NOT NULL` });\n }\n\n /** COUNT as a projected field. */\n public selectCount(field: string, alias: string): this {\n return this.selectAggregate(field, \"count\", alias);\n }\n\n /**\n * CASE / switch expression.\n * @example q.selectCase([{ when: \"status = 1\", then: \"'active'\" }], \"'inactive'\", \"statusLabel\")\n */\n public selectCase(\n cases: Array<{ when: RawExpression; then: RawExpression | unknown }>,\n otherwise: RawExpression | unknown,\n alias: string,\n ): this {\n const caseExpr = cases.map((c) => `WHEN ${c.when} THEN ${c.then}`).join(\" \");\n return this.selectRaw({ [alias]: `CASE ${caseExpr} ELSE ${otherwise} END` });\n }\n\n /** IF/ELSE conditional field. */\n public selectWhen(\n condition: RawExpression,\n thenValue: RawExpression | unknown,\n elseValue: RawExpression | unknown,\n alias: string,\n ): this {\n return this.selectRaw({\n [alias]: `CASE WHEN ${condition} THEN ${thenValue} ELSE ${elseValue} END`,\n });\n }\n\n /**\n * Driver-native projection manipulation.\n * No-op in base — override in driver subclasses.\n */\n public selectDriverProjection(_callback: (projection: Record<string, unknown>) => void): this {\n return this;\n }\n\n /** JSON path extraction as a projected field. */\n public selectJson(path: string, alias?: string): this {\n const parts = path.split(\"->\");\n const column = parts[0];\n const jsonPath = parts.slice(1).join(\"->\");\n const expr = jsonPath ? `${column}->>'${jsonPath}'` : column;\n return alias ? this.selectAs(expr, alias) : this.selectRaw(expr);\n }\n\n /** JSON extraction via raw expression. */\n public selectJsonRaw(_path: string, expression: RawExpression, alias: string): this {\n return this.selectRaw({ [alias]: expression });\n }\n\n /** Exclude a JSON path from projection. */\n public deselectJson(path: string): this {\n return this.deselect([path]);\n }\n\n /** String concatenation as a projected field. */\n public selectConcat(fields: Array<string | RawExpression>, alias: string): this {\n return this.selectRaw({ [alias]: fields.join(\" || \") });\n }\n\n /** COALESCE (first non-null) as a projected field. */\n public selectCoalesce(fields: Array<string | RawExpression>, alias: string): this {\n return this.selectRaw({ [alias]: `COALESCE(${fields.join(\", \")})` });\n }\n\n /** Window function expression. */\n public selectWindow(spec: RawExpression): this {\n this.addOperation(\"selectRaw\", { expression: spec });\n return this;\n }\n\n /** Exclude specific columns from results. */\n public deselect(fields: string[]): this {\n this.addOperation(\"deselect\", { fields });\n return this;\n }\n\n /**\n * Remove all select operations (resets to wildcard).\n * Uses `rebuildIndex()` — no unsafe casts.\n */\n public clearSelect(): this {\n this.operations = this.operations.filter(\n (op) => !op.type.startsWith(\"select\") && op.type !== \"deselect\",\n );\n this.rebuildIndex();\n return this;\n }\n\n /** Alias for clearSelect. */\n public selectAll(): this {\n return this.clearSelect();\n }\n\n /** Alias for clearSelect. */\n public selectDefault(): this {\n return this.clearSelect();\n }\n\n /** Append additional fields to existing selection. */\n public addSelect(fields: string[]): this {\n this.addOperation(\"select\", { fields, add: true });\n return this;\n }\n\n /**\n * Record a DISTINCT flag (fluent — does not execute).\n * Subclasses expose a separate async `distinct(field)` execution method.\n */\n public distinctValues(fields?: string | string[]): this {\n const fieldList = fields ? (Array.isArray(fields) ? fields : [fields]) : [];\n this.addOperation(\"distinct\", { fields: fieldList });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // ORDERING\n // ════════════════════════════════════════════════════════\n\n /**\n * ORDER BY a column.\n *\n * @example\n * q.orderBy(\"createdAt\", \"desc\")\n * q.orderBy({ name: \"asc\", age: \"desc\" })\n */\n public orderBy(field: string, direction?: OrderDirection): this;\n public orderBy(fields: Record<string, OrderDirection>): this;\n public orderBy(...args: unknown[]): this {\n if (typeof args[0] === \"string\") {\n this.addOperation(\"orderBy\", {\n field: args[0],\n direction: (args[1] as OrderDirection) ?? \"asc\",\n });\n } else {\n for (const [field, direction] of Object.entries(args[0] as Record<string, OrderDirection>)) {\n this.addOperation(\"orderBy\", { field, direction });\n }\n }\n return this;\n }\n\n /** ORDER BY descending shorthand. */\n public orderByDesc(field: string): this {\n return this.orderBy(field, \"desc\");\n }\n\n /**\n * Raw ORDER BY expression.\n * @example q.orderByRaw(\"RANDOM()\")\n * @example q.orderByRaw({ $meta: \"textScore\" })\n */\n public orderByRaw(expression: RawExpression, bindings?: unknown[]): this {\n this.addOperation(\"orderByRaw\", { expression, bindings: bindings ?? [] });\n return this;\n }\n\n /**\n * Random order. Maps to `RANDOM()` in SQL or `$sample` in MongoDB.\n * @param limit - Optional limit (required for MongoDB $sample)\n */\n public orderByRandom(limit?: number): this {\n this.addOperation(\"orderByRaw\", { expression: \"RANDOM()\" });\n if (limit !== undefined) this.limit(limit);\n return this;\n }\n\n /** Order ascending by a date column (oldest first). */\n public oldest(column = \"createdAt\"): this {\n return this.orderBy(column, \"asc\");\n }\n\n // ════════════════════════════════════════════════════════\n // LIMIT / OFFSET\n // ════════════════════════════════════════════════════════\n\n /** Limit number of results. */\n public limit(value: number): this {\n this.addOperation(\"limit\", { value });\n return this;\n }\n\n /** Skip N results (OFFSET). */\n public skip(value: number): this {\n this.addOperation(\"offset\", { value });\n return this;\n }\n\n /** Alias for skip. */\n public offset(value: number): this {\n return this.skip(value);\n }\n\n /** Alias for limit. */\n public take(value: number): this {\n return this.limit(value);\n }\n\n // ════════════════════════════════════════════════════════\n // ROW LOCKING\n // ════════════════════════════════════════════════════════\n\n /**\n * Lock the selected rows for update (`SELECT ... FOR UPDATE`).\n *\n * `skipLocked` skips rows other transactions hold locks on (concurrent\n * queue-claim shape); `noWait` errors immediately instead of waiting. The\n * two are mutually exclusive. Only meaningful inside a transaction.\n *\n * SQL drivers emit the locking clause; drivers without row locking\n * (MongoDB) override this to throw.\n */\n public lockForUpdate(options?: LockForUpdateOptions): this {\n if (options?.skipLocked && options?.noWait) {\n throw new Error(\"lockForUpdate: `skipLocked` and `noWait` are mutually exclusive.\");\n }\n\n this.addOperation(\"lock\", {\n mode: \"update\",\n skipLocked: options?.skipLocked ?? false,\n noWait: options?.noWait ?? false,\n });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // GROUPING / AGGREGATION\n // ════════════════════════════════════════════════════════\n\n /**\n * GROUP BY clause.\n * @example q.groupBy(\"status\")\n * @example q.groupBy([\"year\", \"month\"])\n */\n public groupBy(input: GroupByInput): this {\n const fields = Array.isArray(input) ? input : [input];\n this.addOperation(\"groupBy\", { fields });\n return this;\n }\n\n /** Raw GROUP BY expression. */\n public groupByRaw(expression: RawExpression, bindings?: unknown[]): this {\n this.addOperation(\"groupBy\", { expression, bindings: bindings ?? [] });\n return this;\n }\n\n /**\n * HAVING clause (post-group filter).\n *\n * @example\n * q.having(\"total\", \">\", 100)\n * q.having([\"total\", \">\", 100])\n * q.having({ total: 100 })\n */\n public having(field: string, value: unknown): this;\n public having(field: string, operator: WhereOperator, value: unknown): this;\n public having(condition: HavingInput): this;\n public having(...args: unknown[]): this {\n if (args.length === 1) {\n const input = args[0] as HavingInput;\n if (Array.isArray(input)) {\n if (input.length === 2) {\n this.addOperation(\"having\", { field: input[0], operator: \"=\", value: input[1] });\n } else {\n this.addOperation(\"having\", { field: input[0], operator: input[1], value: input[2] });\n }\n } else {\n for (const [key, value] of Object.entries(input as Record<string, unknown>)) {\n this.addOperation(\"having\", { field: key, operator: \"=\", value });\n }\n }\n } else if (args.length === 2) {\n this.addOperation(\"having\", { field: args[0], operator: \"=\", value: args[1] });\n } else {\n this.addOperation(\"having\", { field: args[0], operator: args[1], value: args[2] });\n }\n return this;\n }\n\n /** Raw HAVING expression. */\n public havingRaw(expression: RawExpression, bindings?: unknown[]): this {\n this.addOperation(\"havingRaw\", { expression, bindings: bindings ?? [] });\n return this;\n }\n\n // ════════════════════════════════════════════════════════\n // UTILITY / CONTROL FLOW\n // ════════════════════════════════════════════════════════\n\n /**\n * Side-effect tap — executes callback synchronously and returns `this`.\n * @example q.where(...).tap(q => console.log(q.operations.length)).limit(10)\n */\n public tap(callback: (builder: this) => void): this {\n callback(this);\n return this;\n }\n\n /**\n * Conditionally apply query modifications.\n *\n * @example\n * q.when(userId, (q, id) => q.where(\"userId\", id))\n * q.when(isAdmin, q => q.withoutGlobalScopes(), q => q.scope(\"active\"))\n */\n public when<V>(\n condition: V | boolean,\n callback: (builder: this, value: V) => void,\n otherwise?: (builder: this) => void,\n ): this {\n if (condition) {\n callback(this, condition as V);\n } else if (otherwise) {\n otherwise(this);\n }\n return this;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"query-builder.mjs","names":[],"sources":["../../../../../../../cascade/src/query-builder/query-builder.ts"],"sourcesContent":["/**\r\n * Pure Query Builder Base Class\r\n *\r\n * Driver-agnostic operation recorder. All fluent methods push typed entries into\r\n * `operations[]`. No SQL, no driver references, no table property, no execution.\r\n *\r\n * ┌─────────────────────────────────────────────────┐\r\n * │ Usage contexts │\r\n * │ (a) Subclassed — PG / Mongo / MySQL / … │\r\n * │ (b) Instantiated directly (new QueryBuilder()) │\r\n * │ inside callbacks for: │\r\n * │ • nested where groups │\r\n * │ • joinWith constraints │\r\n * │ • whereExists / whereHas subqueries │\r\n * └─────────────────────────────────────────────────┘\r\n *\r\n * Design rules:\r\n * - `table` / alias are NOT here — the parser gets them from the executor.\r\n * - `opIndex` is protected so subclasses can rebuild after direct mutation.\r\n * - Op type names are stable — parsers switch on them; no renaming without\r\n * a parser update.\r\n * - OR-variants keep distinct op types (orWhere, orWhereColumn, …) so existing\r\n * parsers that switch on type need no changes.\r\n * - `joinWith` eagerly resolves callbacks → subOps at record time so the\r\n * driver executor receives a plain data structure, not a live function.\r\n *\r\n * @module cascade/query-builder\r\n */\r\n\r\nimport type {\r\n GroupByInput,\r\n HavingInput,\r\n JoinOptions,\r\n LockForUpdateOptions,\r\n OrderDirection,\r\n RawExpression,\r\n WhereCallback,\r\n WhereObject,\r\n WhereOperator,\r\n} from \"../contracts/query-builder.contract\";\r\nimport { sanitizeFilter, sanitizeFilterValue } from \"../utils/sanitize-filter\";\r\n\r\n// ============================================================================\r\n// TYPES\r\n// ============================================================================\r\n\r\n/**\r\n * A single recorded query operation.\r\n * `type` is the discriminator; `data` carries all parameters.\r\n */\r\nexport type Op = {\r\n readonly type: string;\r\n readonly data: Record<string, unknown>;\r\n};\r\n\r\n/**\r\n * Constraint value accepted by `joinWith()`.\r\n *\r\n * - `string` → comma-separated column shorthand: `\"id,name,createdAt\"`\r\n * - `fn` → callback receives a bare QueryBuilder to record sub-ops\r\n *\r\n * @example\r\n * joinWith({ actions: \"id,status\" })\r\n * joinWith({ actions: q => q.where(\"status\", \"pending\").limit(5) })\r\n */\r\nexport type JoinWithConstraint = string | ((q: QueryBuilder) => void);\r\n\r\n// ============================================================================\r\n// QUERY BUILDER — CONCRETE, DIRECTLY INSTANTIABLE\r\n// ============================================================================\r\n\r\n/**\r\n * Pure, driver-agnostic query builder.\r\n *\r\n * Records operations in `operations[]`. Subclasses own execution, parsing, and\r\n * driver-specific clause generation. Safe to instantiate directly inside\r\n * callbacks where only operation recording is needed.\r\n *\r\n * @example\r\n * ```ts\r\n * // Driver subclass usage:\r\n * const users = await User.query()\r\n * .select([\"id\", \"name\"])\r\n * .where(\"status\", \"active\")\r\n * .where(q => q.where(\"role\", \"admin\").orWhere(\"role\", \"mod\"))\r\n * .orderBy(\"createdAt\", \"desc\")\r\n * .limit(10)\r\n * .get();\r\n *\r\n * // Direct instantiation (callback context — no driver needed):\r\n * joinWith({ actions: q => q.where(\"status\", \"pending\").limit(5) });\r\n * // The sub-QB's operations[] are captured and stored in the joinWith op data.\r\n * ```\r\n */\r\nexport class QueryBuilder<T = unknown> {\r\n // ════════════════════════════════════════════════════════\r\n // OPERATION STORE\r\n // ════════════════════════════════════════════════════════\r\n\r\n /** Flat, ordered list of recorded operations. Public for parser access. */\r\n public operations: Op[] = [];\r\n\r\n /**\r\n * type → ordered list of indices into `operations[]`.\r\n *\r\n * Protected (not private) so:\r\n * - `rebuildIndex()` can reset it after direct `operations[]` mutation.\r\n * - Subclasses can inspect it without unsafe casts.\r\n *\r\n * External consumers should use `getOps(type)` instead.\r\n */\r\n protected opIndex: Map<string, number[]> = new Map();\r\n\r\n // ════════════════════════════════════════════════════════\r\n // SCOPE STATE (injected by Model.query(), consumed before execution)\r\n // ════════════════════════════════════════════════════════\r\n\r\n /** Global scope definitions injected by Model.query(). Keyed by scope name. */\r\n public pendingGlobalScopes?: Map<string, any>;\r\n /** Local scope callbacks injected by Model.query(). Applied on demand via scope(). */\r\n public availableLocalScopes?: Map<string, (...args: any[]) => void>;\r\n /** Names of global scopes that have been intentionally disabled. */\r\n public disabledGlobalScopes: Set<string> = new Set();\r\n /** True once the driver subclass has applied pending scopes. */\r\n public scopesApplied = false;\r\n\r\n // ════════════════════════════════════════════════════════\r\n // RELATION STATE (consumed by driver subclass at execute time)\r\n // ════════════════════════════════════════════════════════\r\n\r\n /** Relations to eager-load via separate queries. */\r\n public eagerLoadRelations: Map<string, boolean | ((query: any) => void)> = new Map();\r\n /** Count expressions to emit per result row, keyed by output column alias. */\r\n public countRelations: Map<string, { relation: string; constraintOps?: Op[] }> = new Map();\r\n /** Relation definition map injected from the owning Model. */\r\n public relationDefinitions?: Record<string, any>;\r\n /** The Model class reference, required for relation resolution. */\r\n public modelClass?: any;\r\n\r\n // ════════════════════════════════════════════════════════\r\n // CORE INTERNALS\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Append an operation to `operations[]` and update `opIndex`.\r\n * Every fluent method calls this.\r\n */\r\n protected addOperation(type: string, data: Record<string, unknown>): void {\r\n const idx = this.operations.length;\r\n this.operations.push({ type, data });\r\n const list = this.opIndex.get(type);\r\n if (list) {\r\n list.push(idx);\r\n } else {\r\n this.opIndex.set(type, [idx]);\r\n }\r\n }\r\n\r\n /**\r\n * Return all recorded operations of the specified types in original\r\n * insertion order.\r\n *\r\n * @example\r\n * builder.getOps(\"where\", \"orWhere\", \"whereIn\")\r\n */\r\n public getOps(...types: string[]): Op[] {\r\n if (types.length === 1) {\r\n return (this.opIndex.get(types[0]) ?? []).map((i) => this.operations[i]);\r\n }\r\n const result: Array<{ idx: number; op: Op }> = [];\r\n for (const type of types) {\r\n for (const idx of this.opIndex.get(type) ?? []) {\r\n result.push({ idx, op: this.operations[idx] });\r\n }\r\n }\r\n return result.sort((a, b) => a.idx - b.idx).map((r) => r.op);\r\n }\r\n\r\n /**\r\n * Rebuild `opIndex` from scratch.\r\n *\r\n * Call this after any direct mutation of `this.operations[]` (e.g. scope\r\n * injection, joinWith consumption in the executor, clone post-processing).\r\n */\r\n public rebuildIndex(): void {\r\n this.opIndex = new Map();\r\n for (let i = 0; i < this.operations.length; i++) {\r\n const type = this.operations[i].type;\r\n const list = this.opIndex.get(type);\r\n if (list) {\r\n list.push(i);\r\n } else {\r\n this.opIndex.set(type, [i]);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Factory for sub-QueryBuilders used inside callbacks.\r\n *\r\n * Override in driver subclasses to return a driver-typed instance, so that\r\n * driver-specific methods (e.g. `whereArrayContains`) are available inside\r\n * nested `where(q => ...)` / `whereHas` / `joinWith` callbacks.\r\n *\r\n * @example\r\n * // In PostgresQueryBuilder:\r\n * protected override subQuery(): QueryBuilder {\r\n * return new PostgresQueryBuilder(\"__sub__\", this.dataSource);\r\n * }\r\n */\r\n protected subQuery(): QueryBuilder {\r\n return new QueryBuilder();\r\n }\r\n\r\n /**\r\n * Shallow-clone this builder — copies operations, opIndex, and all shared state.\r\n *\r\n * Subclasses MUST call `super.clone()` and then copy their own fields\r\n * (dataSource, joinRelations, …).\r\n */\r\n public clone(): this {\r\n const cloned = Object.create(Object.getPrototypeOf(this)) as this;\r\n cloned.operations = [...this.operations];\r\n cloned.opIndex = new Map(Array.from(this.opIndex.entries()).map(([k, v]) => [k, [...v]]));\r\n cloned.pendingGlobalScopes = this.pendingGlobalScopes;\r\n cloned.availableLocalScopes = this.availableLocalScopes;\r\n cloned.disabledGlobalScopes = new Set(this.disabledGlobalScopes);\r\n cloned.scopesApplied = this.scopesApplied;\r\n cloned.eagerLoadRelations = new Map(this.eagerLoadRelations);\r\n cloned.countRelations = new Map(this.countRelations);\r\n cloned.relationDefinitions = this.relationDefinitions;\r\n cloned.modelClass = this.modelClass;\r\n return cloned;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // SCOPES\r\n // ════════════════════════════════════════════════════════\r\n\r\n /** Disable one or more named global scopes for this query. */\r\n public withoutGlobalScope(...scopeNames: string[]): this {\r\n scopeNames.forEach((name) => this.disabledGlobalScopes.add(name));\r\n return this;\r\n }\r\n\r\n /** Disable ALL pending global scopes for this query. */\r\n public withoutGlobalScopes(): this {\r\n this.pendingGlobalScopes?.forEach((_, name) => this.disabledGlobalScopes.add(name));\r\n return this;\r\n }\r\n\r\n /**\r\n * Apply a registered local scope by name.\r\n * @throws if no local scopes are available or the named scope is not found\r\n */\r\n public scope(scopeName: string, ...args: unknown[]): this {\r\n if (!this.availableLocalScopes) {\r\n throw new Error(\"No local scopes available on this query builder.\");\r\n }\r\n const cb = this.availableLocalScopes.get(scopeName);\r\n if (!cb) throw new Error(`Local scope \"${scopeName}\" not found.`);\r\n cb(this, ...args);\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // WHERE CLAUSES — CORE\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Add a WHERE clause (AND).\r\n *\r\n * @example\r\n * q.where(\"status\", \"active\")\r\n * q.where(\"age\", \">\", 18)\r\n * q.where({ role: \"admin\", active: true })\r\n * q.where(q => q.where(\"a\", 1).orWhere(\"b\", 2))\r\n */\r\n public where(field: string, value: unknown): this;\r\n public where(field: string, operator: WhereOperator, value: unknown): this;\r\n public where(conditions: WhereObject): this;\r\n public where(callback: WhereCallback<T>): this;\r\n public where(...args: unknown[]): this {\r\n if (args.length === 1 && typeof args[0] === \"function\") {\r\n const sub = this.subQuery();\r\n (args[0] as (q: QueryBuilder) => void)(sub);\r\n this.addOperation(\"where\", { nested: sub.operations });\r\n } else if (args.length === 1 && typeof args[0] === \"object\" && args[0] !== null) {\r\n for (const [key, value] of Object.entries(sanitizeFilter(args[0] as WhereObject))) {\r\n this.addOperation(\"where\", { field: key, operator: \"=\", value });\r\n }\r\n } else if (args.length === 2) {\r\n this.addOperation(\"where\", {\r\n field: args[0],\r\n operator: \"=\",\r\n value: sanitizeFilterValue(args[1], String(args[0])),\r\n });\r\n } else {\r\n // \"=\" is still an equality position — sanitize like the 2-arg form.\r\n this.addOperation(\"where\", {\r\n field: args[0],\r\n operator: args[1],\r\n value: args[1] === \"=\" ? sanitizeFilterValue(args[2], String(args[0])) : args[2],\r\n });\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Add an OR WHERE clause.\r\n *\r\n * @example\r\n * q.where(\"role\", \"admin\").orWhere(\"role\", \"mod\")\r\n */\r\n public orWhere(field: string, value: unknown): this;\r\n public orWhere(field: string, operator: WhereOperator, value: unknown): this;\r\n public orWhere(conditions: WhereObject): this;\r\n public orWhere(callback: WhereCallback<T>): this;\r\n public orWhere(...args: unknown[]): this {\r\n if (args.length === 1 && typeof args[0] === \"function\") {\r\n const sub = this.subQuery();\r\n (args[0] as (q: QueryBuilder) => void)(sub);\r\n this.addOperation(\"orWhere\", { nested: sub.operations });\r\n } else if (args.length === 1 && typeof args[0] === \"object\" && args[0] !== null) {\r\n for (const [key, value] of Object.entries(sanitizeFilter(args[0] as WhereObject))) {\r\n this.addOperation(\"orWhere\", { field: key, operator: \"=\", value });\r\n }\r\n } else if (args.length === 2) {\r\n this.addOperation(\"orWhere\", {\r\n field: args[0],\r\n operator: \"=\",\r\n value: sanitizeFilterValue(args[1], String(args[0])),\r\n });\r\n } else {\r\n // \"=\" is still an equality position — sanitize like the 2-arg form.\r\n this.addOperation(\"orWhere\", {\r\n field: args[0],\r\n operator: args[1],\r\n value: args[1] === \"=\" ? sanitizeFilterValue(args[2], String(args[0])) : args[2],\r\n });\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Raw WHERE expression in the target dialect (AND).\r\n *\r\n * @example\r\n * q.whereRaw(\"age > ? AND role = ?\", [18, \"admin\"]) // SQL\r\n * q.whereRaw({ $expr: { $gt: [\"$stock\", \"$reserved\"] } }) // MongoDB\r\n */\r\n public whereRaw(expression: RawExpression, bindings?: unknown[]): this {\r\n this.addOperation(\"whereRaw\", { expression, bindings: bindings ?? [] });\r\n return this;\r\n }\r\n\r\n /** Raw OR WHERE expression. */\r\n public orWhereRaw(expression: RawExpression, bindings?: unknown[]): this {\r\n this.addOperation(\"orWhereRaw\", { expression, bindings: bindings ?? [] });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // WHERE CLAUSES — COLUMN COMPARISONS\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Compare two columns directly (AND).\r\n * @example q.whereColumn(\"stock\", \">\", \"reserved\")\r\n */\r\n public whereColumn(first: string, operator: WhereOperator, second: string): this {\r\n this.addOperation(\"whereColumn\", { first, operator, second });\r\n return this;\r\n }\r\n\r\n /** Compare two columns directly (OR). */\r\n public orWhereColumn(first: string, operator: WhereOperator, second: string): this {\r\n this.addOperation(\"orWhereColumn\", { first, operator, second });\r\n return this;\r\n }\r\n\r\n /** Compare multiple column pairs in one call. */\r\n public whereColumns(\r\n comparisons: Array<[left: string, operator: WhereOperator, right: string]>,\r\n ): this {\r\n for (const [left, operator, right] of comparisons) {\r\n this.whereColumn(left, operator, right);\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Field value must fall between two other column values.\r\n * Stored as a `whereBetween` op with `useColumns: true` so the SQL parser\r\n * knows to quote the values as identifiers rather than bind them.\r\n */\r\n public whereBetweenColumns(field: string, lowerColumn: string, upperColumn: string): this {\r\n this.addOperation(\"whereBetween\", { field, lowerColumn, upperColumn, useColumns: true });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // WHERE CLAUSES — STANDARD COMPARISON OPERATORS\r\n // ════════════════════════════════════════════════════════\r\n\r\n /** WHERE field IN values. */\r\n public whereIn(field: string, values: unknown[]): this {\r\n this.addOperation(\"whereIn\", { field, values });\r\n return this;\r\n }\r\n\r\n /** WHERE field NOT IN values. */\r\n public whereNotIn(field: string, values: unknown[]): this {\r\n this.addOperation(\"whereNotIn\", { field, values });\r\n return this;\r\n }\r\n\r\n /** WHERE field IS NULL. */\r\n public whereNull(field: string): this {\r\n this.addOperation(\"whereNull\", { field });\r\n return this;\r\n }\r\n\r\n /** WHERE field IS NOT NULL. */\r\n public whereNotNull(field: string): this {\r\n this.addOperation(\"whereNotNull\", { field });\r\n return this;\r\n }\r\n\r\n /** WHERE field BETWEEN low AND high. */\r\n public whereBetween(field: string, range: [unknown, unknown]): this {\r\n this.addOperation(\"whereBetween\", { field, range });\r\n return this;\r\n }\r\n\r\n /** WHERE field NOT BETWEEN low AND high. */\r\n public whereNotBetween(field: string, range: [unknown, unknown]): this {\r\n this.addOperation(\"whereNotBetween\", { field, range });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // WHERE CLAUSES — PATTERN MATCHING\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * LIKE pattern match (AND).\r\n *\r\n * A string is a LIKE pattern (`%` wildcard) and is matched literally\r\n * otherwise — regex metacharacters in it are escaped by the driver, so\r\n * search input cannot alter the query. Pass an explicit `RegExp` to opt into\r\n * raw pattern semantics; never build that `RegExp` from user input.\r\n *\r\n * @example q.whereLike(\"email\", \"%@gmail.com\")\r\n */\r\n public whereLike(field: string, pattern: RegExp | string): this {\r\n this.addOperation(\"whereLike\", { field, ...this.likePatternData(pattern) });\r\n return this;\r\n }\r\n\r\n /** NOT LIKE pattern match. @see whereLike for the escaping rules. */\r\n public whereNotLike(field: string, pattern: RegExp | string): this {\r\n this.addOperation(\"whereNotLike\", { field, ...this.likePatternData(pattern) });\r\n return this;\r\n }\r\n\r\n /**\r\n * Flatten a LIKE argument into operation data, keeping the distinction the\r\n * drivers need: an explicit `RegExp` (developer-authored) stays a pattern,\r\n * a string (potentially request input) is a literal.\r\n */\r\n private likePatternData(pattern: RegExp | string): {\r\n pattern: string;\r\n isRegExp?: true;\r\n } {\r\n return pattern instanceof RegExp\r\n ? { pattern: pattern.source, isRegExp: true }\r\n : { pattern };\r\n }\r\n\r\n /** Starts with a prefix. */\r\n public whereStartsWith(field: string, value: string | number): this {\r\n return this.whereLike(field, `${value}%`);\r\n }\r\n\r\n /** Does NOT start with a prefix. */\r\n public whereNotStartsWith(field: string, value: string | number): this {\r\n return this.whereNotLike(field, `${value}%`);\r\n }\r\n\r\n /** Ends with a suffix. */\r\n public whereEndsWith(field: string, value: string | number): this {\r\n return this.whereLike(field, `%${value}`);\r\n }\r\n\r\n /** Does NOT end with a suffix. */\r\n public whereNotEndsWith(field: string, value: string | number): this {\r\n return this.whereNotLike(field, `%${value}`);\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // WHERE CLAUSES — DATE/TIME PARTIALS\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Match on date portion only (time ignored).\r\n * @example q.whereDate(\"createdAt\", \"2024-05-01\")\r\n */\r\n public whereDate(field: string, value: Date | string): this {\r\n this.addOperation(\"whereDate\", { field, value });\r\n return this;\r\n }\r\n\r\n /** Alias for whereDate. */\r\n public whereDateEquals(field: string, value: Date | string): this {\r\n return this.whereDate(field, value);\r\n }\r\n\r\n /** Field date is before value. */\r\n public whereDateBefore(field: string, value: Date | string): this {\r\n this.addOperation(\"whereDateBefore\", { field, value });\r\n return this;\r\n }\r\n\r\n /** Field date is after value. */\r\n public whereDateAfter(field: string, value: Date | string): this {\r\n this.addOperation(\"whereDateAfter\", { field, value });\r\n return this;\r\n }\r\n\r\n /** Field date is within a range [from, to]. */\r\n public whereDateBetween(field: string, range: [Date | string, Date | string]): this {\r\n this.addOperation(\"whereDateBetween\", { field, range });\r\n return this;\r\n }\r\n\r\n /** Field date is NOT within a range. */\r\n public whereDateNotBetween(field: string, range: [Date | string, Date | string]): this {\r\n this.addOperation(\"whereNotBetween\", { field, range });\r\n return this;\r\n }\r\n\r\n /**\r\n * Match on the time portion of a datetime field.\r\n * Emits a `whereRaw` op with a driver-agnostic marker; the driver parser\r\n * rewrites it to the appropriate SQL (`TIME(field) = ?`) or Mongo expression.\r\n */\r\n public whereTime(field: string, value: string): this {\r\n this.addOperation(\"whereRaw\", {\r\n expression: `TIME(${field}) = ?`,\r\n bindings: [value],\r\n });\r\n return this;\r\n }\r\n\r\n /**\r\n * Day-of-month from a date field (1–31).\r\n * Uses a `whereRaw` op so SQL parsers get the `EXTRACT` expression directly.\r\n * MongoDB drivers override to emit `$dayOfMonth`.\r\n */\r\n public whereDay(field: string, value: number): this {\r\n this.addOperation(\"whereRaw\", {\r\n expression: `EXTRACT(DAY FROM ${field}) = ?`,\r\n bindings: [value],\r\n });\r\n return this;\r\n }\r\n\r\n /** Month extracted from a date field (1–12). */\r\n public whereMonth(field: string, value: number): this {\r\n this.addOperation(\"whereRaw\", {\r\n expression: `EXTRACT(MONTH FROM ${field}) = ?`,\r\n bindings: [value],\r\n });\r\n return this;\r\n }\r\n\r\n /** Year extracted from a date field. */\r\n public whereYear(field: string, value: number): this {\r\n this.addOperation(\"whereRaw\", {\r\n expression: `EXTRACT(YEAR FROM ${field}) = ?`,\r\n bindings: [value],\r\n });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // WHERE CLAUSES — JSON / STRUCTURED DATA\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * JSON/array path contains the given value.\r\n * @example q.whereJsonContains(\"tags\", \"typescript\")\r\n */\r\n public whereJsonContains(path: string, value: unknown): this {\r\n this.addOperation(\"whereJsonContains\", { path, value });\r\n return this;\r\n }\r\n\r\n /** JSON/array path does NOT contain the value. */\r\n public whereJsonDoesntContain(path: string, value: unknown): this {\r\n this.addOperation(\"whereJsonDoesntContain\", { path, value });\r\n return this;\r\n }\r\n\r\n /**\r\n * JSON path key exists.\r\n * Uses a `whereRaw` so existing SQL parsers get `IS NOT NULL` immediately.\r\n */\r\n public whereJsonContainsKey(path: string): this {\r\n this.addOperation(\"whereRaw\", { expression: `${path} IS NOT NULL`, bindings: [] });\r\n return this;\r\n }\r\n\r\n /**\r\n * Constrain the length of a JSON array at a path.\r\n * @example q.whereJsonLength(\"tags\", \">\", 3)\r\n */\r\n public whereJsonLength(path: string, operator: WhereOperator, value: number): this {\r\n this.addOperation(\"whereRaw\", {\r\n expression: `jsonb_array_length(${path}) ${operator} ?`,\r\n bindings: [value],\r\n });\r\n return this;\r\n }\r\n\r\n /** JSON path must resolve to an array. */\r\n public whereJsonIsArray(path: string): this {\r\n this.addOperation(\"whereRaw\", {\r\n expression: `jsonb_typeof(${path}) = 'array'`,\r\n bindings: [],\r\n });\r\n return this;\r\n }\r\n\r\n /** JSON path must resolve to an object. */\r\n public whereJsonIsObject(path: string): this {\r\n this.addOperation(\"whereRaw\", {\r\n expression: `jsonb_typeof(${path}) = 'object'`,\r\n bindings: [],\r\n });\r\n return this;\r\n }\r\n\r\n /**\r\n * Constrain the number of elements in an array field.\r\n * @example q.whereArrayLength(\"roles\", \">=\", 2)\r\n */\r\n public whereArrayLength(field: string, operator: WhereOperator, value: number): this {\r\n this.addOperation(\"whereRaw\", {\r\n expression: `array_length(${field}, 1) ${operator} ?`,\r\n bindings: [value],\r\n });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // WHERE CLAUSES — CONVENIENCE SHORTCUTS\r\n // ════════════════════════════════════════════════════════\r\n\r\n /** WHERE id = value. */\r\n public whereId(value: string | number): this {\r\n return this.where(\"id\", value);\r\n }\r\n\r\n /** WHERE id IN values. */\r\n public whereIds(values: Array<string | number>): this {\r\n return this.whereIn(\"id\", values);\r\n }\r\n\r\n /** WHERE uuid = value. */\r\n public whereUuid(value: string): this {\r\n return this.where(\"uuid\", value);\r\n }\r\n\r\n /** WHERE ulid = value. */\r\n public whereUlid(value: string): this {\r\n return this.where(\"ulid\", value);\r\n }\r\n\r\n /**\r\n * Full-text search across one or more fields.\r\n * @example q.whereFullText([\"title\", \"body\"], \"typescript\")\r\n */\r\n public whereFullText(fields: string | string[], query: string): this {\r\n this.addOperation(\"whereFullText\", {\r\n fields: Array.isArray(fields) ? fields : [fields],\r\n query,\r\n });\r\n return this;\r\n }\r\n\r\n /** Full-text search (OR). */\r\n public orWhereFullText(fields: string | string[], query: string): this {\r\n return this.whereFullText(fields, query);\r\n }\r\n\r\n /** Alias for whereFullText with a single field. */\r\n public whereSearch(field: string, query: string): this {\r\n return this.whereFullText([field], query);\r\n }\r\n\r\n /**\r\n * Text search with optional extra equality filters.\r\n * MongoDB-style convenience shorthand.\r\n */\r\n public textSearch(query: string, filters?: WhereObject): this {\r\n if (filters) {\r\n for (const [key, value] of Object.entries(filters)) this.where(key, value as never);\r\n }\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // WHERE CLAUSES — EXISTENCE / SUBQUERIES\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * WHERE EXISTS (subquery callback) or field IS NOT NULL (string).\r\n *\r\n * @example\r\n * q.whereExists(sub => sub.where(\"userId\", \"users.id\"))\r\n * q.whereExists(\"optionalField\")\r\n */\r\n public whereExists(field: string): this;\r\n public whereExists(callback: WhereCallback<T>): this;\r\n public whereExists(param: string | WhereCallback<T>): this {\r\n if (typeof param === \"function\") {\r\n const sub = this.subQuery();\r\n param(sub as any);\r\n this.addOperation(\"whereExists\", { subquery: sub.operations });\r\n } else {\r\n this.addOperation(\"whereNotNull\", { field: param });\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * WHERE NOT EXISTS (subquery callback) or field IS NULL (string).\r\n */\r\n public whereNotExists(field: string): this;\r\n public whereNotExists(callback: WhereCallback<T>): this;\r\n public whereNotExists(param: string | WhereCallback<T>): this {\r\n if (typeof param === \"function\") {\r\n const sub = this.subQuery();\r\n param(sub as any);\r\n this.addOperation(\"whereNotExists\", { subquery: sub.operations });\r\n } else {\r\n this.addOperation(\"whereNull\", { field: param });\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Constrain an array/collection field by element count.\r\n *\r\n * @example\r\n * q.whereSize(\"tags\", 3) // exactly 3\r\n * q.whereSize(\"tags\", \">=\", 1) // at least 1\r\n */\r\n public whereSize(field: string, size: number): this;\r\n public whereSize(field: string, operator: WhereOperator, size: number): this;\r\n public whereSize(field: string, ...args: unknown[]): this {\r\n const operator = args.length === 2 ? (args[0] as WhereOperator) : \"=\";\r\n const size = (args.length === 2 ? args[1] : args[0]) as number;\r\n return this.whereArrayLength(field, operator, size);\r\n }\r\n\r\n /**\r\n * AND NOT wrapper — negate a nested group.\r\n * @example q.whereNot(q => q.where(\"status\", \"banned\").where(\"role\", \"user\"))\r\n */\r\n public whereNot(callback: WhereCallback<T>): this {\r\n const sub = this.subQuery();\r\n callback(sub as any);\r\n this.addOperation(\"whereNot\", { nested: sub.operations });\r\n return this;\r\n }\r\n\r\n /** OR NOT wrapper. */\r\n public orWhereNot(callback: WhereCallback<T>): this {\r\n const sub = this.subQuery();\r\n callback(sub as any);\r\n this.addOperation(\"orWhereNot\", { nested: sub.operations });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // JOINS — STANDARD SQL-STYLE\r\n // Note: Op type names match parser switch cases exactly.\r\n // join / innerJoin → INNER JOIN\r\n // leftJoin → LEFT JOIN\r\n // rightJoin → RIGHT JOIN\r\n // fullJoin → FULL OUTER JOIN\r\n // crossJoin → CROSS JOIN\r\n // joinRaw → raw expression\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * INNER JOIN.\r\n * @example q.join(\"categories\", \"posts.categoryId\", \"categories.id\")\r\n */\r\n public join(table: string, localField: string, foreignField: string): this;\r\n public join(options: JoinOptions): this;\r\n public join(...args: unknown[]): this {\r\n if (args.length === 3) {\r\n this.addOperation(\"join\", { table: args[0], localField: args[1], foreignField: args[2] });\r\n } else {\r\n this.addOperation(\"join\", args[0] as Record<string, unknown>);\r\n }\r\n return this;\r\n }\r\n\r\n /** LEFT JOIN. */\r\n public leftJoin(table: string, localField: string, foreignField: string): this;\r\n public leftJoin(options: JoinOptions): this;\r\n public leftJoin(...args: unknown[]): this {\r\n if (args.length === 3) {\r\n this.addOperation(\"leftJoin\", { table: args[0], localField: args[1], foreignField: args[2] });\r\n } else {\r\n this.addOperation(\"leftJoin\", args[0] as Record<string, unknown>);\r\n }\r\n return this;\r\n }\r\n\r\n /** RIGHT JOIN. */\r\n public rightJoin(table: string, localField: string, foreignField: string): this;\r\n public rightJoin(options: JoinOptions): this;\r\n public rightJoin(...args: unknown[]): this {\r\n if (args.length === 3) {\r\n this.addOperation(\"rightJoin\", {\r\n table: args[0],\r\n localField: args[1],\r\n foreignField: args[2],\r\n });\r\n } else {\r\n this.addOperation(\"rightJoin\", args[0] as Record<string, unknown>);\r\n }\r\n return this;\r\n }\r\n\r\n /** INNER JOIN (alias for join). */\r\n public innerJoin(table: string, localField: string, foreignField: string): this;\r\n public innerJoin(options: JoinOptions): this;\r\n public innerJoin(...args: unknown[]): this {\r\n if (args.length === 3) {\r\n this.addOperation(\"innerJoin\", {\r\n table: args[0],\r\n localField: args[1],\r\n foreignField: args[2],\r\n });\r\n } else {\r\n this.addOperation(\"innerJoin\", args[0] as Record<string, unknown>);\r\n }\r\n return this;\r\n }\r\n\r\n /** FULL OUTER JOIN. */\r\n public fullJoin(table: string, localField: string, foreignField: string): this;\r\n public fullJoin(options: JoinOptions): this;\r\n public fullJoin(...args: unknown[]): this {\r\n if (args.length === 3) {\r\n this.addOperation(\"fullJoin\", { table: args[0], localField: args[1], foreignField: args[2] });\r\n } else {\r\n this.addOperation(\"fullJoin\", args[0] as Record<string, unknown>);\r\n }\r\n return this;\r\n }\r\n\r\n /** CROSS JOIN. */\r\n public crossJoin(table: string): this {\r\n this.addOperation(\"crossJoin\", { table });\r\n return this;\r\n }\r\n\r\n /** Raw JOIN expression. Driver responsible for handling. */\r\n public joinRaw(expression: RawExpression, bindings?: unknown[]): this {\r\n this.addOperation(\"joinRaw\", { expression, bindings: bindings ?? [] });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // RELATION EAGER LOADING — JOIN-BASED (joinWith)\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Eager-load named relations via a single JOIN / $lookup query.\r\n *\r\n * Constraints are eagerly resolved at call time:\r\n * - Callbacks are invoked immediately → `subOps` stored in op data.\r\n * - Column shorthands are parsed into a `columns[]` array.\r\n *\r\n * The driver executor reads the `joinWith` op and uses the resolved data\r\n * alongside its own relation definition map to emit the appropriate SQL JOIN\r\n * or MongoDB $lookup stage.\r\n *\r\n * Supported arg forms (may be mixed):\r\n * - `\"author\"` / `[\"author\", \"category\"]` — no constraint\r\n * - `{ author: \"id,name\" }` — column shorthand\r\n * - `{ actions: q => q.where(\"status\",\"pending\").limit(5) }` — callback\r\n *\r\n * @example\r\n * Post.joinWith(\"author\", \"category\")\r\n * ChatMessage.joinWith({ actions: q => q.where(\"status\", \"pending\").limit(5) })\r\n * ChatMessage.joinWith({ org: \"id,name\", actions: q => q.orderBy(\"sort_order\") })\r\n */\r\n public joinWith(...args: unknown[]): this {\r\n const resolved: Record<string, { columns?: string[]; subOps?: Op[] }> = {};\r\n\r\n for (const arg of args) {\r\n if (typeof arg === \"string\") {\r\n resolved[arg] = {};\r\n } else if (Array.isArray(arg)) {\r\n for (const rel of arg as string[]) resolved[rel] = {};\r\n } else if (typeof arg === \"object\" && arg !== null) {\r\n for (const [rel, constraint] of Object.entries(arg as Record<string, JoinWithConstraint>)) {\r\n if (typeof constraint === \"function\") {\r\n const sub = this.subQuery();\r\n constraint(sub);\r\n resolved[rel] = { subOps: sub.operations };\r\n } else if (typeof constraint === \"string\" && constraint !== \"\") {\r\n resolved[rel] = {\r\n columns: constraint\r\n .split(\",\")\r\n .map((s) => s.trim())\r\n .filter(Boolean),\r\n };\r\n } else {\r\n resolved[rel] = {};\r\n }\r\n }\r\n }\r\n }\r\n\r\n this.addOperation(\"joinWith\", { resolved });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // RELATION EAGER LOADING — SEPARATE QUERIES (with)\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Eager-load relations via separate queries (N+1 avoided by batching).\r\n *\r\n * @example\r\n * q.with(\"posts\")\r\n * q.with(\"posts\", q => q.where(\"published\", true))\r\n * q.with({ posts: true, comments: q => q.limit(5) })\r\n */\r\n public with(\r\n ...args: (string | Record<string, boolean | ((q: any) => void)> | ((q: any) => void))[]\r\n ): this {\r\n for (let i = 0; i < args.length; i++) {\r\n const arg = args[i];\r\n if (typeof arg === \"string\") {\r\n const next = args[i + 1];\r\n if (typeof next === \"function\") {\r\n this.eagerLoadRelations.set(arg, next as (q: any) => void);\r\n i++;\r\n } else {\r\n this.eagerLoadRelations.set(arg, true);\r\n }\r\n } else if (typeof arg === \"object\" && arg !== null) {\r\n for (const [key, value] of Object.entries(\r\n arg as Record<string, boolean | ((q: any) => void)>,\r\n )) {\r\n this.eagerLoadRelations.set(key, value);\r\n }\r\n }\r\n }\r\n return this;\r\n }\r\n\r\n /**\r\n * Register one or more relation counts to emit alongside each result row.\r\n *\r\n * Accepts:\r\n * - Bare relation names (variadic strings or array): `withCount(\"posts\", \"comments\")`\r\n * - Alias shorthand: `withCount(\"posts as totalPosts\")`\r\n * - Object form for per-relation constraints / aliases:\r\n * `withCount({ posts: true, \"posts as approved\": (q) => q.where(\"approved\", true) })`\r\n *\r\n * Each entry is stored in `countRelations` keyed by its output column alias\r\n * (default `${relationName}Count`). The driver subclass consumes the map at\r\n * execute time to emit count expressions.\r\n *\r\n * @example\r\n * ```typescript\r\n * await User.query().withCount(\"posts\").get(); // postsCount\r\n * await User.query().withCount(\"posts as totalPosts\").get(); // totalPosts\r\n * await User.query()\r\n * .withCount({\r\n * posts: true,\r\n * \"posts as published\": (q) => q.where(\"isPublished\", true),\r\n * comments: \"commentTotal\",\r\n * })\r\n * .get();\r\n * ```\r\n */\r\n public withCount(...args: unknown[]): this {\r\n for (const arg of args) {\r\n if (typeof arg === \"string\") {\r\n this.recordCountEntry(arg);\r\n continue;\r\n }\r\n\r\n if (Array.isArray(arg)) {\r\n for (const spec of arg as string[]) {\r\n this.recordCountEntry(spec);\r\n }\r\n continue;\r\n }\r\n\r\n if (typeof arg === \"object\" && arg !== null) {\r\n const entries = Object.entries(\r\n arg as Record<string, true | string | ((query: any) => void)>,\r\n );\r\n\r\n for (const [key, value] of entries) {\r\n if (value === true) {\r\n this.recordCountEntry(key);\r\n } else if (typeof value === \"string\") {\r\n this.recordCountEntry(`${key} as ${value}`);\r\n } else if (typeof value === \"function\") {\r\n this.recordCountEntry(key, value);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Parse a count spec (\"relation\" or \"relation as alias\") into its relation\r\n * name and output alias, optionally capturing a constraint callback's\r\n * operations via a sub-builder. Stored in `countRelations` keyed by alias.\r\n */\r\n protected recordCountEntry(spec: string, constraint?: (query: any) => void): void {\r\n const { relation, alias } = this.parseCountSpec(spec);\r\n\r\n let constraintOps: Op[] | undefined;\r\n\r\n if (constraint) {\r\n const sub = this.subQuery();\r\n constraint(sub);\r\n constraintOps = sub.operations;\r\n }\r\n\r\n this.countRelations.set(alias, { relation, constraintOps });\r\n }\r\n\r\n /**\r\n * Split a `\"<relation>\"` or `\"<relation> as <alias>\"` spec. Returns the\r\n * resolved relation name and the output column alias (defaulting to\r\n * `${relation}Count` when no `as` is present).\r\n */\r\n protected parseCountSpec(spec: string): { relation: string; alias: string } {\r\n const trimmed = spec.trim();\r\n const match = /^(.+?)\\s+as\\s+(.+)$/i.exec(trimmed);\r\n\r\n if (!match) {\r\n return { relation: trimmed, alias: `${trimmed}Count` };\r\n }\r\n\r\n return { relation: match[1].trim(), alias: match[2].trim() };\r\n }\r\n\r\n /**\r\n * Filter to rows that have at least one related record.\r\n * @example q.has(\"comments\")\r\n * @example q.has(\"comments\", \">=\", 3)\r\n */\r\n public has(relation: string, operator?: WhereOperator, count?: number): this {\r\n this.addOperation(\"has\", { relation, operator: operator ?? \">=\", count: count ?? 1 });\r\n return this;\r\n }\r\n\r\n /**\r\n * Filter to rows with related records matching a sub-query (AND).\r\n * @example q.whereHas(\"comments\", q => q.where(\"approved\", true))\r\n */\r\n public whereHas(relation: string, callback: (q: any) => void): this {\r\n const sub = this.subQuery();\r\n callback(sub);\r\n this.addOperation(\"whereHas\", { relation, subquery: sub.operations });\r\n return this;\r\n }\r\n\r\n /** Same as whereHas but OR-joined. */\r\n public orWhereHas(relation: string, callback: (q: any) => void): this {\r\n const sub = this.subQuery();\r\n callback(sub);\r\n this.addOperation(\"orWhereHas\", { relation, subquery: sub.operations });\r\n return this;\r\n }\r\n\r\n /** Filter to rows with NO related records. */\r\n public doesntHave(relation: string): this {\r\n this.addOperation(\"doesntHave\", { relation });\r\n return this;\r\n }\r\n\r\n /** Filter to rows with NO related records matching conditions. */\r\n public whereDoesntHave(relation: string, callback: (q: any) => void): this {\r\n const sub = this.subQuery();\r\n callback(sub);\r\n this.addOperation(\"whereDoesntHave\", { relation, subquery: sub.operations });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // SELECT / PROJECTION\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Select specific columns.\r\n *\r\n * @example\r\n * q.select([\"id\", \"name\"])\r\n * q.select(\"id\", \"name\")\r\n * q.select({ name: 1, password: 0 }) // MongoDB-style projection\r\n */\r\n public select(fields: string[]): this;\r\n public select(fields: Record<string, 0 | 1 | boolean>): this;\r\n public select(...fields: Array<string | string[]>): this;\r\n public select(...args: unknown[]): this {\r\n if (args.length === 1 && Array.isArray(args[0])) {\r\n this.addOperation(\"select\", { fields: args[0] });\r\n } else if (args.length === 1 && typeof args[0] === \"object\" && !Array.isArray(args[0])) {\r\n this.addOperation(\"select\", { fields: args[0] as Record<string, unknown> });\r\n } else {\r\n this.addOperation(\"select\", { fields: (args as Array<string | string[]>).flat() });\r\n }\r\n return this;\r\n }\r\n\r\n /** Select a field under an alias. @example q.selectAs(\"fullName\", \"name\") */\r\n public selectAs(field: string, alias: string): this {\r\n this.addOperation(\"select\", { fields: { [field]: alias } });\r\n return this;\r\n }\r\n\r\n /**\r\n * Raw SELECT expression.\r\n * @example q.selectRaw(\"COUNT(*) AS total\")\r\n */\r\n public selectRaw(expression: RawExpression, bindings?: unknown[]): this {\r\n this.addOperation(\"selectRaw\", { expression, bindings: bindings ?? [] });\r\n return this;\r\n }\r\n\r\n /** Multiple raw SELECT expressions in one call. */\r\n public selectRawMany(\r\n definitions: Array<{ alias: string; expression: RawExpression; bindings?: unknown[] }>,\r\n ): this {\r\n for (const def of definitions) {\r\n this.selectRaw({ [def.alias]: def.expression }, def.bindings);\r\n }\r\n return this;\r\n }\r\n\r\n /** Subquery as a named projected field. */\r\n public selectSub(expression: RawExpression, alias: string): this {\r\n this.addOperation(\"selectRaw\", { expression: { [alias]: expression } });\r\n return this;\r\n }\r\n\r\n /** Alias for selectSub. */\r\n public addSelectSub(expression: RawExpression, alias: string): this {\r\n return this.selectSub(expression, alias);\r\n }\r\n\r\n /**\r\n * Aggregate function as a projected field.\r\n * @example q.selectAggregate(\"price\", \"sum\", \"totalRevenue\")\r\n */\r\n public selectAggregate(\r\n field: string,\r\n aggregate: \"sum\" | \"avg\" | \"min\" | \"max\" | \"count\" | \"first\" | \"last\",\r\n alias: string,\r\n ): this {\r\n return this.selectRaw({ [alias]: `${aggregate.toUpperCase()}(${field})` });\r\n }\r\n\r\n /** Existence check as a projected boolean field. */\r\n public selectExists(field: string, alias: string): this {\r\n return this.selectRaw({ [alias]: `${field} IS NOT NULL` });\r\n }\r\n\r\n /** COUNT as a projected field. */\r\n public selectCount(field: string, alias: string): this {\r\n return this.selectAggregate(field, \"count\", alias);\r\n }\r\n\r\n /**\r\n * CASE / switch expression.\r\n * @example q.selectCase([{ when: \"status = 1\", then: \"'active'\" }], \"'inactive'\", \"statusLabel\")\r\n */\r\n public selectCase(\r\n cases: Array<{ when: RawExpression; then: RawExpression | unknown }>,\r\n otherwise: RawExpression | unknown,\r\n alias: string,\r\n ): this {\r\n const caseExpr = cases.map((c) => `WHEN ${c.when} THEN ${c.then}`).join(\" \");\r\n return this.selectRaw({ [alias]: `CASE ${caseExpr} ELSE ${otherwise} END` });\r\n }\r\n\r\n /** IF/ELSE conditional field. */\r\n public selectWhen(\r\n condition: RawExpression,\r\n thenValue: RawExpression | unknown,\r\n elseValue: RawExpression | unknown,\r\n alias: string,\r\n ): this {\r\n return this.selectRaw({\r\n [alias]: `CASE WHEN ${condition} THEN ${thenValue} ELSE ${elseValue} END`,\r\n });\r\n }\r\n\r\n /**\r\n * Driver-native projection manipulation.\r\n * No-op in base — override in driver subclasses.\r\n */\r\n public selectDriverProjection(_callback: (projection: Record<string, unknown>) => void): this {\r\n return this;\r\n }\r\n\r\n /** JSON path extraction as a projected field. */\r\n public selectJson(path: string, alias?: string): this {\r\n const parts = path.split(\"->\");\r\n const column = parts[0];\r\n const jsonPath = parts.slice(1).join(\"->\");\r\n const expr = jsonPath ? `${column}->>'${jsonPath}'` : column;\r\n return alias ? this.selectAs(expr, alias) : this.selectRaw(expr);\r\n }\r\n\r\n /** JSON extraction via raw expression. */\r\n public selectJsonRaw(_path: string, expression: RawExpression, alias: string): this {\r\n return this.selectRaw({ [alias]: expression });\r\n }\r\n\r\n /** Exclude a JSON path from projection. */\r\n public deselectJson(path: string): this {\r\n return this.deselect([path]);\r\n }\r\n\r\n /** String concatenation as a projected field. */\r\n public selectConcat(fields: Array<string | RawExpression>, alias: string): this {\r\n return this.selectRaw({ [alias]: fields.join(\" || \") });\r\n }\r\n\r\n /** COALESCE (first non-null) as a projected field. */\r\n public selectCoalesce(fields: Array<string | RawExpression>, alias: string): this {\r\n return this.selectRaw({ [alias]: `COALESCE(${fields.join(\", \")})` });\r\n }\r\n\r\n /** Window function expression. */\r\n public selectWindow(spec: RawExpression): this {\r\n this.addOperation(\"selectRaw\", { expression: spec });\r\n return this;\r\n }\r\n\r\n /** Exclude specific columns from results. */\r\n public deselect(fields: string[]): this {\r\n this.addOperation(\"deselect\", { fields });\r\n return this;\r\n }\r\n\r\n /**\r\n * Remove all select operations (resets to wildcard).\r\n * Uses `rebuildIndex()` — no unsafe casts.\r\n */\r\n public clearSelect(): this {\r\n this.operations = this.operations.filter(\r\n (op) => !op.type.startsWith(\"select\") && op.type !== \"deselect\",\r\n );\r\n this.rebuildIndex();\r\n return this;\r\n }\r\n\r\n /** Alias for clearSelect. */\r\n public selectAll(): this {\r\n return this.clearSelect();\r\n }\r\n\r\n /** Alias for clearSelect. */\r\n public selectDefault(): this {\r\n return this.clearSelect();\r\n }\r\n\r\n /** Append additional fields to existing selection. */\r\n public addSelect(fields: string[]): this {\r\n this.addOperation(\"select\", { fields, add: true });\r\n return this;\r\n }\r\n\r\n /**\r\n * Record a DISTINCT flag (fluent — does not execute).\r\n * Subclasses expose a separate async `distinct(field)` execution method.\r\n */\r\n public distinctValues(fields?: string | string[]): this {\r\n const fieldList = fields ? (Array.isArray(fields) ? fields : [fields]) : [];\r\n this.addOperation(\"distinct\", { fields: fieldList });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // ORDERING\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * ORDER BY a column.\r\n *\r\n * @example\r\n * q.orderBy(\"createdAt\", \"desc\")\r\n * q.orderBy({ name: \"asc\", age: \"desc\" })\r\n */\r\n public orderBy(field: string, direction?: OrderDirection): this;\r\n public orderBy(fields: Record<string, OrderDirection>): this;\r\n public orderBy(...args: unknown[]): this {\r\n if (typeof args[0] === \"string\") {\r\n this.addOperation(\"orderBy\", {\r\n field: args[0],\r\n direction: (args[1] as OrderDirection) ?? \"asc\",\r\n });\r\n } else {\r\n for (const [field, direction] of Object.entries(args[0] as Record<string, OrderDirection>)) {\r\n this.addOperation(\"orderBy\", { field, direction });\r\n }\r\n }\r\n return this;\r\n }\r\n\r\n /** ORDER BY descending shorthand. */\r\n public orderByDesc(field: string): this {\r\n return this.orderBy(field, \"desc\");\r\n }\r\n\r\n /**\r\n * Raw ORDER BY expression.\r\n * @example q.orderByRaw(\"RANDOM()\")\r\n * @example q.orderByRaw({ $meta: \"textScore\" })\r\n */\r\n public orderByRaw(expression: RawExpression, bindings?: unknown[]): this {\r\n this.addOperation(\"orderByRaw\", { expression, bindings: bindings ?? [] });\r\n return this;\r\n }\r\n\r\n /**\r\n * Random order. Maps to `RANDOM()` in SQL or `$sample` in MongoDB.\r\n * @param limit - Optional limit (required for MongoDB $sample)\r\n */\r\n public orderByRandom(limit?: number): this {\r\n this.addOperation(\"orderByRaw\", { expression: \"RANDOM()\" });\r\n if (limit !== undefined) this.limit(limit);\r\n return this;\r\n }\r\n\r\n /** Order ascending by a date column (oldest first). */\r\n public oldest(column = \"createdAt\"): this {\r\n return this.orderBy(column, \"asc\");\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // LIMIT / OFFSET\r\n // ════════════════════════════════════════════════════════\r\n\r\n /** Limit number of results. */\r\n public limit(value: number): this {\r\n this.addOperation(\"limit\", { value });\r\n return this;\r\n }\r\n\r\n /** Skip N results (OFFSET). */\r\n public skip(value: number): this {\r\n this.addOperation(\"offset\", { value });\r\n return this;\r\n }\r\n\r\n /** Alias for skip. */\r\n public offset(value: number): this {\r\n return this.skip(value);\r\n }\r\n\r\n /** Alias for limit. */\r\n public take(value: number): this {\r\n return this.limit(value);\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // ROW LOCKING\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Lock the selected rows for update (`SELECT ... FOR UPDATE`).\r\n *\r\n * `skipLocked` skips rows other transactions hold locks on (concurrent\r\n * queue-claim shape); `noWait` errors immediately instead of waiting. The\r\n * two are mutually exclusive. Only meaningful inside a transaction.\r\n *\r\n * SQL drivers emit the locking clause; drivers without row locking\r\n * (MongoDB) override this to throw.\r\n */\r\n public lockForUpdate(options?: LockForUpdateOptions): this {\r\n if (options?.skipLocked && options?.noWait) {\r\n throw new Error(\"lockForUpdate: `skipLocked` and `noWait` are mutually exclusive.\");\r\n }\r\n\r\n this.addOperation(\"lock\", {\r\n mode: \"update\",\r\n skipLocked: options?.skipLocked ?? false,\r\n noWait: options?.noWait ?? false,\r\n });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // GROUPING / AGGREGATION\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * GROUP BY clause.\r\n * @example q.groupBy(\"status\")\r\n * @example q.groupBy([\"year\", \"month\"])\r\n */\r\n public groupBy(input: GroupByInput): this {\r\n const fields = Array.isArray(input) ? input : [input];\r\n this.addOperation(\"groupBy\", { fields });\r\n return this;\r\n }\r\n\r\n /** Raw GROUP BY expression. */\r\n public groupByRaw(expression: RawExpression, bindings?: unknown[]): this {\r\n this.addOperation(\"groupBy\", { expression, bindings: bindings ?? [] });\r\n return this;\r\n }\r\n\r\n /**\r\n * HAVING clause (post-group filter).\r\n *\r\n * @example\r\n * q.having(\"total\", \">\", 100)\r\n * q.having([\"total\", \">\", 100])\r\n * q.having({ total: 100 })\r\n */\r\n public having(field: string, value: unknown): this;\r\n public having(field: string, operator: WhereOperator, value: unknown): this;\r\n public having(condition: HavingInput): this;\r\n public having(...args: unknown[]): this {\r\n if (args.length === 1) {\r\n const input = args[0] as HavingInput;\r\n if (Array.isArray(input)) {\r\n if (input.length === 2) {\r\n this.addOperation(\"having\", { field: input[0], operator: \"=\", value: input[1] });\r\n } else {\r\n this.addOperation(\"having\", { field: input[0], operator: input[1], value: input[2] });\r\n }\r\n } else {\r\n for (const [key, value] of Object.entries(input as Record<string, unknown>)) {\r\n this.addOperation(\"having\", { field: key, operator: \"=\", value });\r\n }\r\n }\r\n } else if (args.length === 2) {\r\n this.addOperation(\"having\", { field: args[0], operator: \"=\", value: args[1] });\r\n } else {\r\n this.addOperation(\"having\", { field: args[0], operator: args[1], value: args[2] });\r\n }\r\n return this;\r\n }\r\n\r\n /** Raw HAVING expression. */\r\n public havingRaw(expression: RawExpression, bindings?: unknown[]): this {\r\n this.addOperation(\"havingRaw\", { expression, bindings: bindings ?? [] });\r\n return this;\r\n }\r\n\r\n // ════════════════════════════════════════════════════════\r\n // UTILITY / CONTROL FLOW\r\n // ════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Side-effect tap — executes callback synchronously and returns `this`.\r\n * @example q.where(...).tap(q => console.log(q.operations.length)).limit(10)\r\n */\r\n public tap(callback: (builder: this) => void): this {\r\n callback(this);\r\n return this;\r\n }\r\n\r\n /**\r\n * Conditionally apply query modifications.\r\n *\r\n * @example\r\n * q.when(userId, (q, id) => q.where(\"userId\", id))\r\n * q.when(isAdmin, q => q.withoutGlobalScopes(), q => q.scope(\"active\"))\r\n */\r\n public when<V>(\r\n condition: V | boolean,\r\n callback: (builder: this, value: V) => void,\r\n otherwise?: (builder: this) => void,\r\n ): this {\r\n if (condition) {\r\n callback(this, condition as V);\r\n } else if (otherwise) {\r\n otherwise(this);\r\n }\r\n return this;\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"database-remover.d.mts","names":[],"sources":["../../../../../../../cascade/src/remover/database-remover.ts"],"mappings":";;;;;;AAmCA;;;;;;;;;;;;;;;;;;cAAa,eAAA,YAA2B,eAAA;EA+BnB;EAAA,iBA7BF,KAAA;EA6Ca;EAAA,iBA1Cb,IAAA;EA0CmC;EAAA,iBAvCnC,UAAA;EAmMT;EAAA,iBAhMS,MAAA;EA0OH;EAAA,iBAvOG,KAAA;EAuOQ;EAAA,iBApOR,UAAA;;;;;;;;;;;;;cAcE,KAAA,EAAO,KAAA;;;;;;;;EAgBb,OAAA,CAAQ,OAAA,GAAS,cAAA,GAAsB,OAAA,CAAQ,aAAA;;;;;;;;;;;;;;;;;;;;;UA4JpD,kBAAA;;;;;;;;;;;;UAsBA,iBAAA;;;;;;;;;UAoBM,WAAA;AAAA"}
1
+ {"version":3,"file":"database-remover.d.mts","names":[],"sources":["../../../../../../../cascade/src/remover/database-remover.ts"],"mappings":";;;;;;AAmCA;;;;;;;;;;;;;;;;;;cAAa,eAAA,YAA2B,eAAA;EA+BnB;EAAA,iBA7BF,KAAA;EA6Ca;EAAA,iBA1Cb,IAAA;EA0CmC;EAAA,iBAvCnC,UAAA;EAsMT;EAAA,iBAnMS,MAAA;EA6OH;EAAA,iBA1OG,KAAA;EA0OQ;EAAA,iBAvOR,UAAA;;;;;;;;;;;;;cAcE,KAAA,EAAO,KAAA;;;;;;;;EAgBb,OAAA,CAAQ,OAAA,GAAS,cAAA,GAAsB,OAAA,CAAQ,aAAA;;;;;;;;;;;;;;;;;;;;;UA+JpD,kBAAA;;;;;;;;;;;;UAsBA,iBAAA;;;;;;;;;UAoBM,WAAA;AAAA"}
@@ -65,7 +65,7 @@ var DatabaseRemover = class {
65
65
  async destroy(options = {}) {
66
66
  const strategy = options.strategy ?? this.ctor.deleteStrategy ?? this.dataSource.defaultDeleteStrategy ?? "permanent";
67
67
  if (this.model.isNew) throw new Error(`Cannot destroy ${this.ctor.name} instance that hasn't been saved to the database.`);
68
- const primaryKeyValue = this.model.get(this.primaryKey);
68
+ const primaryKeyValue = this.model.trustedPrimaryKey;
69
69
  if (!primaryKeyValue) throw new Error(`Cannot destroy ${this.ctor.name} instance: primary key (${this.primaryKey}) is missing.`);
70
70
  if (!options.skipEvents) await this.model.emitEvent("deleting", {
71
71
  strategy,
@@ -1 +1 @@
1
- {"version":3,"file":"database-remover.mjs","names":[],"sources":["../../../../../../../cascade/src/remover/database-remover.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport type {\n DriverContract,\n UpdateOperations,\n} from \"../contracts/database-driver.contract\";\nimport type {\n RemoverContract,\n RemoverOptions,\n RemoverResult,\n} from \"../contracts/database-remover.contract\";\nimport type { OnDeletedEventContext } from \"../events/model-events\";\nimport type { ChildModel, Model } from \"../model/model\";\nimport { getModelDeletedEvent } from \"../sync/model-events\";\nimport type { DataSource } from \"./../data-source/data-source\";\n\n/**\n * Database remover service that orchestrates model deletion.\n *\n * Handles the complete deletion pipeline:\n * 1. Strategy resolution (options → model static → data source default)\n * 2. Validation (check if model is new, has primary key)\n * 3. Event emission (deleting, deleted)\n * 4. Driver execution (based on strategy: trash, permanent, or soft)\n * 5. Post-deletion cleanup (mark as new, reset state)\n *\n * @example\n * ```typescript\n * const user = await User.find(1);\n * const remover = new DatabaseRemover(user);\n * const result = await remover.destroy();\n *\n * console.log(result.success); // true\n * console.log(result.strategy); // \"trash\" | \"permanent\" | \"soft\"\n * ```\n */\nexport class DatabaseRemover implements RemoverContract {\n /** The model instance being deleted */\n private readonly model: Model;\n\n /** Model constructor reference */\n private readonly ctor: ChildModel<Model>;\n\n /** Data source containing driver */\n private readonly dataSource: DataSource;\n\n /** Database driver for executing queries */\n private readonly driver: DriverContract;\n\n /** Table/collection name */\n private readonly table: string;\n\n /** Primary key field name */\n private readonly primaryKey: string;\n\n /**\n * Create a new remover instance for a model.\n *\n * @param model - The model instance to delete\n *\n * @example\n * ```typescript\n * const user = await User.find(1);\n * const remover = new DatabaseRemover(user);\n * await remover.destroy();\n * ```\n */\n public constructor(model: Model) {\n this.model = model;\n this.ctor = model.constructor as ChildModel<Model>;\n this.dataSource = this.ctor.getDataSource();\n this.driver = this.dataSource.driver;\n this.table = this.ctor.table;\n this.primaryKey = this.ctor.primaryKey;\n }\n\n /**\n * Destroy (delete) the model instance from the database.\n *\n * @param options - Remover options\n * @returns Result containing success status, strategy used, and metadata\n * @throws {Error} If model is new (not saved) or if deletion fails\n */\n public async destroy(options: RemoverOptions = {}): Promise<RemoverResult> {\n // 1. Resolve strategy (options → model static → data source default → permanent)\n const strategy =\n options.strategy ??\n this.ctor.deleteStrategy ??\n this.dataSource.defaultDeleteStrategy ??\n \"permanent\";\n\n // 2. Validate model is not new and has primary key\n if (this.model.isNew) {\n throw new Error(\n `Cannot destroy ${this.ctor.name} instance that hasn't been saved to the database.`,\n );\n }\n\n const primaryKeyValue = this.model.get(this.primaryKey);\n if (!primaryKeyValue) {\n throw new Error(\n `Cannot destroy ${this.ctor.name} instance: primary key (${this.primaryKey}) is missing.`,\n );\n }\n\n // 3. Emit deleting event (unless skipEvents)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"deleting\", {\n strategy,\n primaryKeyValue,\n primaryKey: this.primaryKey,\n });\n }\n\n // 4. Execute deletion based on strategy\n let deletedCount = 0;\n let trashRecord: Record<string, unknown> | undefined;\n\n const filter = { [this.primaryKey]: primaryKeyValue };\n\n const context: Partial<OnDeletedEventContext> = {\n strategy,\n primaryKeyValue,\n primaryKey: this.primaryKey,\n };\n\n switch (strategy) {\n case \"trash\": {\n // Move to trash table, then delete\n const trashTable = this.resolveTrashTable();\n const documentData = { ...this.model.data };\n\n // Prepare trash record with metadata and handle ID conflicts\n const trashData = this.prepareTrashRecord(documentData);\n\n // Insert into trash table\n const insertResult = await this.driver.insert(trashTable, trashData);\n trashRecord = insertResult.document as Record<string, unknown>;\n\n context.trashRecord = trashRecord;\n\n // Delete original\n const result = await this.driver.delete(this.table, filter);\n deletedCount = result > 0 ? 1 : 0;\n break;\n }\n\n case \"permanent\": {\n // Direct deletion\n const result = await this.driver.delete(this.table, filter);\n deletedCount = result > 0 ? 1 : 0;\n break;\n }\n\n case \"soft\": {\n // Set deletedAt timestamp (using resolved column name)\n const deletedAtColumn = this.ctor.deletedAtColumn;\n\n // Only proceed if deletedAtColumn is configured (not false or undefined)\n if (deletedAtColumn === false || deletedAtColumn === undefined) {\n throw new Error(\n `Cannot perform soft delete on ${this.ctor.name}: deletedAtColumn is not configured. ` +\n `Set a column name or use a different delete strategy.`,\n );\n }\n\n const deletedAt = new Date();\n const updateOperations: UpdateOperations = {\n $set: { [deletedAtColumn]: deletedAt },\n };\n const updateResult = await this.driver.update(\n this.table,\n filter,\n updateOperations,\n );\n deletedCount = updateResult.modifiedCount > 0 ? 1 : 0;\n\n // The row stays (unlike trash/permanent), so reflect the persisted\n // timestamp on the in-memory model — otherwise the instance is stale\n // and `model.get(deletedAtColumn)` stays undefined after destroy().\n if (deletedCount > 0) {\n this.model.set(deletedAtColumn, deletedAt);\n }\n break;\n }\n }\n\n if (deletedCount === 0) {\n throw new Error(\n `Failed to destroy ${this.ctor.name} instance: record not found.`,\n );\n }\n\n context.deletedCount = deletedCount;\n\n // 5. Post-deletion cleanup\n // Only mark as new for permanent and trash (soft delete keeps the record)\n if (strategy !== \"soft\") {\n this.model.isNew = true;\n }\n\n // 6. Emit deleted event (unless skipEvents)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"deleted\", context);\n }\n\n // 7. Trigger sync operations (fire-and-forget, non-blocking)\n if (!options.skipSync) {\n void this.triggerSync();\n }\n\n return {\n success: true,\n deletedCount,\n strategy,\n trashRecord,\n };\n }\n\n /**\n * Prepare the trash record by preserving all original fields and adding deletion metadata.\n *\n * Keeps all original fields intact for easy restoration and adds:\n * - `deletedAt`: Timestamp when the record was deleted\n * - `originalTable`: The table/collection the record came from (for filtering in restoreAll)\n *\n * **ID Handling:**\n * - MongoDB with `_id`: Keeps `_id` as-is (unique across database)\n * - MongoDB with auto-increment `id`: Keeps `id` as a regular field (not primary key)\n * - SQL: Keeps original `id` as a regular field (trash table uses its own auto-increment primary key)\n *\n * The trash table should use its own primary key structure:\n * - MongoDB: Uses `_id` (ObjectId) as primary key, original `id` is just a field\n * - SQL: Uses auto-increment `trashId` as primary key, original `id` is just a field\n *\n * @param documentData - The original document data\n * @returns Prepared trash record data with all original fields + deletedAt + originalTable\n * @private\n */\n private prepareTrashRecord(\n documentData: Record<string, unknown>,\n ): Record<string, unknown> {\n // Preserve all original fields and add deletion metadata\n return {\n ...documentData,\n deletedAt: new Date(),\n originalTable: this.table,\n };\n }\n\n /**\n * Resolve the trash table/collection name.\n *\n * Priority:\n * 1. Model.trashTable (if set)\n * 2. Data source defaultTrashTable (e.g., \"RecycleBin\" for MongoDB)\n * 3. Default pattern: `{table}Trash`\n *\n * @returns The trash table/collection name\n * @private\n */\n private resolveTrashTable(): string {\n if (this.ctor.trashTable) {\n return this.ctor.trashTable;\n }\n\n if (this.dataSource.defaultTrashTable) {\n return this.dataSource.defaultTrashTable;\n }\n\n return `${this.table}Trash`;\n }\n\n /**\n * Trigger sync operations after successful deletion.\n *\n * Emits a model.deleted event that ModelSyncOperation listens to.\n * The sync is handled by registered sync operations, not directly here.\n *\n * @private\n */\n private async triggerSync(): Promise<void> {\n // Emit model.deleted event - ModelSyncOperation listens to these\n await events.triggerAll(getModelDeletedEvent(this.ctor), this.model);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAmCA,IAAa,kBAAb,MAAwD;;CAEtD,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;;;;;;;;;;;;CAcjB,AAAO,YAAY,OAAc;EAC/B,KAAK,QAAQ;EACb,KAAK,OAAO,MAAM;EAClB,KAAK,aAAa,KAAK,KAAK,cAAc;EAC1C,KAAK,SAAS,KAAK,WAAW;EAC9B,KAAK,QAAQ,KAAK,KAAK;EACvB,KAAK,aAAa,KAAK,KAAK;CAC9B;;;;;;;;CASA,MAAa,QAAQ,UAA0B,CAAC,GAA2B;EAEzE,MAAM,WACJ,QAAQ,YACR,KAAK,KAAK,kBACV,KAAK,WAAW,yBAChB;EAGF,IAAI,KAAK,MAAM,OACb,MAAM,IAAI,MACR,kBAAkB,KAAK,KAAK,KAAK,kDACnC;EAGF,MAAM,kBAAkB,KAAK,MAAM,IAAI,KAAK,UAAU;EACtD,IAAI,CAAC,iBACH,MAAM,IAAI,MACR,kBAAkB,KAAK,KAAK,KAAK,0BAA0B,KAAK,WAAW,cAC7E;EAIF,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,YAAY;GACrC;GACA;GACA,YAAY,KAAK;EACnB,CAAC;EAIH,IAAI,eAAe;EACnB,IAAI;EAEJ,MAAM,SAAS,GAAG,KAAK,aAAa,gBAAgB;EAEpD,MAAM,UAA0C;GAC9C;GACA;GACA,YAAY,KAAK;EACnB;EAEA,QAAQ,UAAR;GACE,KAAK,SAAS;IAEZ,MAAM,aAAa,KAAK,kBAAkB;IAC1C,MAAM,eAAe,EAAE,GAAG,KAAK,MAAM,KAAK;IAG1C,MAAM,YAAY,KAAK,mBAAmB,YAAY;IAItD,eAAc,MADa,KAAK,OAAO,OAAO,YAAY,SAAS,EACzC,CAAC;IAE3B,QAAQ,cAAc;IAItB,eAAe,MADM,KAAK,OAAO,OAAO,KAAK,OAAO,MAAM,IAClC,IAAI,IAAI;IAChC;GACF;GAEA,KAAK;IAGH,eAAe,MADM,KAAK,OAAO,OAAO,KAAK,OAAO,MAAM,IAClC,IAAI,IAAI;IAChC;GAGF,KAAK,QAAQ;IAEX,MAAM,kBAAkB,KAAK,KAAK;IAGlC,IAAI,oBAAoB,SAAS,oBAAoB,QACnD,MAAM,IAAI,MACR,iCAAiC,KAAK,KAAK,KAAK,2FAElD;IAGF,MAAM,4BAAY,IAAI,KAAK;IAC3B,MAAM,mBAAqC,EACzC,MAAM,GAAG,kBAAkB,UAAU,EACvC;IAMA,gBAAe,MALY,KAAK,OAAO,OACrC,KAAK,OACL,QACA,gBACF,EAC2B,CAAC,gBAAgB,IAAI,IAAI;IAKpD,IAAI,eAAe,GACjB,KAAK,MAAM,IAAI,iBAAiB,SAAS;IAE3C;GACF;EACF;EAEA,IAAI,iBAAiB,GACnB,MAAM,IAAI,MACR,qBAAqB,KAAK,KAAK,KAAK,6BACtC;EAGF,QAAQ,eAAe;EAIvB,IAAI,aAAa,QACf,KAAK,MAAM,QAAQ;EAIrB,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,WAAW,OAAO;EAI/C,IAAI,CAAC,QAAQ,UACX,AAAK,KAAK,YAAY;EAGxB,OAAO;GACL,SAAS;GACT;GACA;GACA;EACF;CACF;;;;;;;;;;;;;;;;;;;;;CAsBA,AAAQ,mBACN,cACyB;EAEzB,OAAO;GACL,GAAG;GACH,2BAAW,IAAI,KAAK;GACpB,eAAe,KAAK;EACtB;CACF;;;;;;;;;;;;CAaA,AAAQ,oBAA4B;EAClC,IAAI,KAAK,KAAK,YACZ,OAAO,KAAK,KAAK;EAGnB,IAAI,KAAK,WAAW,mBAClB,OAAO,KAAK,WAAW;EAGzB,OAAO,GAAG,KAAK,MAAM;CACvB;;;;;;;;;CAUA,MAAc,cAA6B;EAEzC,MAAM,OAAO,WAAW,qBAAqB,KAAK,IAAI,GAAG,KAAK,KAAK;CACrE;AACF"}
1
+ {"version":3,"file":"database-remover.mjs","names":[],"sources":["../../../../../../../cascade/src/remover/database-remover.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport type {\n DriverContract,\n UpdateOperations,\n} from \"../contracts/database-driver.contract\";\nimport type {\n RemoverContract,\n RemoverOptions,\n RemoverResult,\n} from \"../contracts/database-remover.contract\";\nimport type { OnDeletedEventContext } from \"../events/model-events\";\nimport type { ChildModel, Model } from \"../model/model\";\nimport { getModelDeletedEvent } from \"../sync/model-events\";\nimport type { DataSource } from \"./../data-source/data-source\";\n\n/**\n * Database remover service that orchestrates model deletion.\n *\n * Handles the complete deletion pipeline:\n * 1. Strategy resolution (options → model static → data source default)\n * 2. Validation (check if model is new, has primary key)\n * 3. Event emission (deleting, deleted)\n * 4. Driver execution (based on strategy: trash, permanent, or soft)\n * 5. Post-deletion cleanup (mark as new, reset state)\n *\n * @example\n * ```typescript\n * const user = await User.find(1);\n * const remover = new DatabaseRemover(user);\n * const result = await remover.destroy();\n *\n * console.log(result.success); // true\n * console.log(result.strategy); // \"trash\" | \"permanent\" | \"soft\"\n * ```\n */\nexport class DatabaseRemover implements RemoverContract {\n /** The model instance being deleted */\n private readonly model: Model;\n\n /** Model constructor reference */\n private readonly ctor: ChildModel<Model>;\n\n /** Data source containing driver */\n private readonly dataSource: DataSource;\n\n /** Database driver for executing queries */\n private readonly driver: DriverContract;\n\n /** Table/collection name */\n private readonly table: string;\n\n /** Primary key field name */\n private readonly primaryKey: string;\n\n /**\n * Create a new remover instance for a model.\n *\n * @param model - The model instance to delete\n *\n * @example\n * ```typescript\n * const user = await User.find(1);\n * const remover = new DatabaseRemover(user);\n * await remover.destroy();\n * ```\n */\n public constructor(model: Model) {\n this.model = model;\n this.ctor = model.constructor as ChildModel<Model>;\n this.dataSource = this.ctor.getDataSource();\n this.driver = this.dataSource.driver;\n this.table = this.ctor.table;\n this.primaryKey = this.ctor.primaryKey;\n }\n\n /**\n * Destroy (delete) the model instance from the database.\n *\n * @param options - Remover options\n * @returns Result containing success status, strategy used, and metadata\n * @throws {Error} If model is new (not saved) or if deletion fails\n */\n public async destroy(options: RemoverOptions = {}): Promise<RemoverResult> {\n // 1. Resolve strategy (options → model static → data source default → permanent)\n const strategy =\n options.strategy ??\n this.ctor.deleteStrategy ??\n this.dataSource.defaultDeleteStrategy ??\n \"permanent\";\n\n // 2. Validate model is not new and has primary key\n if (this.model.isNew) {\n throw new Error(\n `Cannot destroy ${this.ctor.name} instance that hasn't been saved to the database.`,\n );\n }\n\n // The value captured when the model became persisted, never the current\n // (mass-assignable) one — a delete must not be retargetable by a payload\n // carrying `{ id: \"<victim-id>\" }`. Same rule as the writer's update filter.\n const primaryKeyValue = this.model.trustedPrimaryKey;\n if (!primaryKeyValue) {\n throw new Error(\n `Cannot destroy ${this.ctor.name} instance: primary key (${this.primaryKey}) is missing.`,\n );\n }\n\n // 3. Emit deleting event (unless skipEvents)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"deleting\", {\n strategy,\n primaryKeyValue,\n primaryKey: this.primaryKey,\n });\n }\n\n // 4. Execute deletion based on strategy\n let deletedCount = 0;\n let trashRecord: Record<string, unknown> | undefined;\n\n const filter = { [this.primaryKey]: primaryKeyValue };\n\n const context: Partial<OnDeletedEventContext> = {\n strategy,\n primaryKeyValue,\n primaryKey: this.primaryKey,\n };\n\n switch (strategy) {\n case \"trash\": {\n // Move to trash table, then delete\n const trashTable = this.resolveTrashTable();\n const documentData = { ...this.model.data };\n\n // Prepare trash record with metadata and handle ID conflicts\n const trashData = this.prepareTrashRecord(documentData);\n\n // Insert into trash table\n const insertResult = await this.driver.insert(trashTable, trashData);\n trashRecord = insertResult.document as Record<string, unknown>;\n\n context.trashRecord = trashRecord;\n\n // Delete original\n const result = await this.driver.delete(this.table, filter);\n deletedCount = result > 0 ? 1 : 0;\n break;\n }\n\n case \"permanent\": {\n // Direct deletion\n const result = await this.driver.delete(this.table, filter);\n deletedCount = result > 0 ? 1 : 0;\n break;\n }\n\n case \"soft\": {\n // Set deletedAt timestamp (using resolved column name)\n const deletedAtColumn = this.ctor.deletedAtColumn;\n\n // Only proceed if deletedAtColumn is configured (not false or undefined)\n if (deletedAtColumn === false || deletedAtColumn === undefined) {\n throw new Error(\n `Cannot perform soft delete on ${this.ctor.name}: deletedAtColumn is not configured. ` +\n `Set a column name or use a different delete strategy.`,\n );\n }\n\n const deletedAt = new Date();\n const updateOperations: UpdateOperations = {\n $set: { [deletedAtColumn]: deletedAt },\n };\n const updateResult = await this.driver.update(\n this.table,\n filter,\n updateOperations,\n );\n deletedCount = updateResult.modifiedCount > 0 ? 1 : 0;\n\n // The row stays (unlike trash/permanent), so reflect the persisted\n // timestamp on the in-memory model — otherwise the instance is stale\n // and `model.get(deletedAtColumn)` stays undefined after destroy().\n if (deletedCount > 0) {\n this.model.set(deletedAtColumn, deletedAt);\n }\n break;\n }\n }\n\n if (deletedCount === 0) {\n throw new Error(\n `Failed to destroy ${this.ctor.name} instance: record not found.`,\n );\n }\n\n context.deletedCount = deletedCount;\n\n // 5. Post-deletion cleanup\n // Only mark as new for permanent and trash (soft delete keeps the record)\n if (strategy !== \"soft\") {\n this.model.isNew = true;\n }\n\n // 6. Emit deleted event (unless skipEvents)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"deleted\", context);\n }\n\n // 7. Trigger sync operations (fire-and-forget, non-blocking)\n if (!options.skipSync) {\n void this.triggerSync();\n }\n\n return {\n success: true,\n deletedCount,\n strategy,\n trashRecord,\n };\n }\n\n /**\n * Prepare the trash record by preserving all original fields and adding deletion metadata.\n *\n * Keeps all original fields intact for easy restoration and adds:\n * - `deletedAt`: Timestamp when the record was deleted\n * - `originalTable`: The table/collection the record came from (for filtering in restoreAll)\n *\n * **ID Handling:**\n * - MongoDB with `_id`: Keeps `_id` as-is (unique across database)\n * - MongoDB with auto-increment `id`: Keeps `id` as a regular field (not primary key)\n * - SQL: Keeps original `id` as a regular field (trash table uses its own auto-increment primary key)\n *\n * The trash table should use its own primary key structure:\n * - MongoDB: Uses `_id` (ObjectId) as primary key, original `id` is just a field\n * - SQL: Uses auto-increment `trashId` as primary key, original `id` is just a field\n *\n * @param documentData - The original document data\n * @returns Prepared trash record data with all original fields + deletedAt + originalTable\n * @private\n */\n private prepareTrashRecord(\n documentData: Record<string, unknown>,\n ): Record<string, unknown> {\n // Preserve all original fields and add deletion metadata\n return {\n ...documentData,\n deletedAt: new Date(),\n originalTable: this.table,\n };\n }\n\n /**\n * Resolve the trash table/collection name.\n *\n * Priority:\n * 1. Model.trashTable (if set)\n * 2. Data source defaultTrashTable (e.g., \"RecycleBin\" for MongoDB)\n * 3. Default pattern: `{table}Trash`\n *\n * @returns The trash table/collection name\n * @private\n */\n private resolveTrashTable(): string {\n if (this.ctor.trashTable) {\n return this.ctor.trashTable;\n }\n\n if (this.dataSource.defaultTrashTable) {\n return this.dataSource.defaultTrashTable;\n }\n\n return `${this.table}Trash`;\n }\n\n /**\n * Trigger sync operations after successful deletion.\n *\n * Emits a model.deleted event that ModelSyncOperation listens to.\n * The sync is handled by registered sync operations, not directly here.\n *\n * @private\n */\n private async triggerSync(): Promise<void> {\n // Emit model.deleted event - ModelSyncOperation listens to these\n await events.triggerAll(getModelDeletedEvent(this.ctor), this.model);\n }\n}\n"],"mappings":"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"}