@spinajs/orm 2.0.481 → 2.0.484

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 (228) hide show
  1. package/README.md +72 -3
  2. package/lib/cjs/bootstrap.js +10 -0
  3. package/lib/cjs/bootstrap.js.map +1 -1
  4. package/lib/cjs/builders.d.ts +127 -11
  5. package/lib/cjs/builders.d.ts.map +1 -1
  6. package/lib/cjs/builders.js +368 -283
  7. package/lib/cjs/builders.js.map +1 -1
  8. package/lib/cjs/converters.d.ts +9 -2
  9. package/lib/cjs/converters.d.ts.map +1 -1
  10. package/lib/cjs/converters.js +48 -8
  11. package/lib/cjs/converters.js.map +1 -1
  12. package/lib/cjs/decorators.d.ts +26 -3
  13. package/lib/cjs/decorators.d.ts.map +1 -1
  14. package/lib/cjs/decorators.js +95 -42
  15. package/lib/cjs/decorators.js.map +1 -1
  16. package/lib/cjs/dehydrators.js +1 -1
  17. package/lib/cjs/dehydrators.js.map +1 -1
  18. package/lib/cjs/descriptor.d.ts +1 -0
  19. package/lib/cjs/descriptor.d.ts.map +1 -1
  20. package/lib/cjs/descriptor.js +16 -40
  21. package/lib/cjs/descriptor.js.map +1 -1
  22. package/lib/cjs/driver.d.ts +136 -7
  23. package/lib/cjs/driver.d.ts.map +1 -1
  24. package/lib/cjs/driver.js +279 -0
  25. package/lib/cjs/driver.js.map +1 -1
  26. package/lib/cjs/existsRelationHandlers.d.ts +29 -0
  27. package/lib/cjs/existsRelationHandlers.d.ts.map +1 -0
  28. package/lib/cjs/existsRelationHandlers.js +138 -0
  29. package/lib/cjs/existsRelationHandlers.js.map +1 -0
  30. package/lib/cjs/fp.d.ts.map +1 -1
  31. package/lib/cjs/fp.js +10 -15
  32. package/lib/cjs/fp.js.map +1 -1
  33. package/lib/cjs/hydrators.d.ts.map +1 -1
  34. package/lib/cjs/hydrators.js +29 -2
  35. package/lib/cjs/hydrators.js.map +1 -1
  36. package/lib/cjs/identity-map.d.ts +60 -0
  37. package/lib/cjs/identity-map.d.ts.map +1 -0
  38. package/lib/cjs/identity-map.js +135 -0
  39. package/lib/cjs/identity-map.js.map +1 -0
  40. package/lib/cjs/index.d.ts +12 -0
  41. package/lib/cjs/index.d.ts.map +1 -1
  42. package/lib/cjs/index.js +12 -0
  43. package/lib/cjs/index.js.map +1 -1
  44. package/lib/cjs/interfaces.d.ts +300 -10
  45. package/lib/cjs/interfaces.d.ts.map +1 -1
  46. package/lib/cjs/interfaces.js +54 -1
  47. package/lib/cjs/interfaces.js.map +1 -1
  48. package/lib/cjs/metadata.d.ts.map +1 -1
  49. package/lib/cjs/metadata.js +4 -1
  50. package/lib/cjs/metadata.js.map +1 -1
  51. package/lib/cjs/metrics.d.ts +53 -0
  52. package/lib/cjs/metrics.d.ts.map +1 -0
  53. package/lib/cjs/metrics.js +82 -0
  54. package/lib/cjs/metrics.js.map +1 -0
  55. package/lib/cjs/middlewares.d.ts +13 -1
  56. package/lib/cjs/middlewares.d.ts.map +1 -1
  57. package/lib/cjs/middlewares.js +55 -15
  58. package/lib/cjs/middlewares.js.map +1 -1
  59. package/lib/cjs/model.d.ts +129 -13
  60. package/lib/cjs/model.d.ts.map +1 -1
  61. package/lib/cjs/model.js +449 -93
  62. package/lib/cjs/model.js.map +1 -1
  63. package/lib/cjs/orm.d.ts.map +1 -1
  64. package/lib/cjs/orm.js +12 -3
  65. package/lib/cjs/orm.js.map +1 -1
  66. package/lib/cjs/orphan.d.ts +27 -0
  67. package/lib/cjs/orphan.d.ts.map +1 -0
  68. package/lib/cjs/orphan.js +55 -0
  69. package/lib/cjs/orphan.js.map +1 -0
  70. package/lib/cjs/primary-keys.d.ts +60 -0
  71. package/lib/cjs/primary-keys.d.ts.map +1 -0
  72. package/lib/cjs/primary-keys.js +243 -0
  73. package/lib/cjs/primary-keys.js.map +1 -0
  74. package/lib/cjs/relation-objects.d.ts +101 -9
  75. package/lib/cjs/relation-objects.d.ts.map +1 -1
  76. package/lib/cjs/relation-objects.js +194 -43
  77. package/lib/cjs/relation-objects.js.map +1 -1
  78. package/lib/cjs/relations.d.ts +7 -1
  79. package/lib/cjs/relations.d.ts.map +1 -1
  80. package/lib/cjs/relations.js +16 -1
  81. package/lib/cjs/relations.js.map +1 -1
  82. package/lib/cjs/resilience.d.ts +50 -0
  83. package/lib/cjs/resilience.d.ts.map +1 -0
  84. package/lib/cjs/resilience.js +70 -0
  85. package/lib/cjs/resilience.js.map +1 -0
  86. package/lib/cjs/snapshot.d.ts +58 -0
  87. package/lib/cjs/snapshot.d.ts.map +1 -0
  88. package/lib/cjs/snapshot.js +125 -0
  89. package/lib/cjs/snapshot.js.map +1 -0
  90. package/lib/cjs/statements.d.ts +11 -1
  91. package/lib/cjs/statements.d.ts.map +1 -1
  92. package/lib/cjs/statements.js +13 -2
  93. package/lib/cjs/statements.js.map +1 -1
  94. package/lib/cjs/subject-builder.d.ts +79 -0
  95. package/lib/cjs/subject-builder.d.ts.map +1 -0
  96. package/lib/cjs/subject-builder.js +296 -0
  97. package/lib/cjs/subject-builder.js.map +1 -0
  98. package/lib/cjs/subject-executor.d.ts +122 -0
  99. package/lib/cjs/subject-executor.d.ts.map +1 -0
  100. package/lib/cjs/subject-executor.js +319 -0
  101. package/lib/cjs/subject-executor.js.map +1 -0
  102. package/lib/cjs/subject-sorter.d.ts +70 -0
  103. package/lib/cjs/subject-sorter.d.ts.map +1 -0
  104. package/lib/cjs/subject-sorter.js +173 -0
  105. package/lib/cjs/subject-sorter.js.map +1 -0
  106. package/lib/cjs/subject.d.ts +100 -0
  107. package/lib/cjs/subject.d.ts.map +1 -0
  108. package/lib/cjs/subject.js +89 -0
  109. package/lib/cjs/subject.js.map +1 -0
  110. package/lib/cjs/unit-of-work.d.ts +52 -0
  111. package/lib/cjs/unit-of-work.d.ts.map +1 -0
  112. package/lib/cjs/unit-of-work.js +170 -0
  113. package/lib/cjs/unit-of-work.js.map +1 -0
  114. package/lib/mjs/bootstrap.js +10 -0
  115. package/lib/mjs/bootstrap.js.map +1 -1
  116. package/lib/mjs/builders.d.ts +127 -11
  117. package/lib/mjs/builders.d.ts.map +1 -1
  118. package/lib/mjs/builders.js +370 -285
  119. package/lib/mjs/builders.js.map +1 -1
  120. package/lib/mjs/converters.d.ts +9 -2
  121. package/lib/mjs/converters.d.ts.map +1 -1
  122. package/lib/mjs/converters.js +48 -8
  123. package/lib/mjs/converters.js.map +1 -1
  124. package/lib/mjs/decorators.d.ts +26 -3
  125. package/lib/mjs/decorators.d.ts.map +1 -1
  126. package/lib/mjs/decorators.js +96 -44
  127. package/lib/mjs/decorators.js.map +1 -1
  128. package/lib/mjs/dehydrators.js +1 -1
  129. package/lib/mjs/dehydrators.js.map +1 -1
  130. package/lib/mjs/descriptor.d.ts +1 -0
  131. package/lib/mjs/descriptor.d.ts.map +1 -1
  132. package/lib/mjs/descriptor.js +17 -39
  133. package/lib/mjs/descriptor.js.map +1 -1
  134. package/lib/mjs/driver.d.ts +136 -7
  135. package/lib/mjs/driver.d.ts.map +1 -1
  136. package/lib/mjs/driver.js +279 -0
  137. package/lib/mjs/driver.js.map +1 -1
  138. package/lib/mjs/existsRelationHandlers.d.ts +29 -0
  139. package/lib/mjs/existsRelationHandlers.d.ts.map +1 -0
  140. package/lib/mjs/existsRelationHandlers.js +134 -0
  141. package/lib/mjs/existsRelationHandlers.js.map +1 -0
  142. package/lib/mjs/fp.d.ts.map +1 -1
  143. package/lib/mjs/fp.js +10 -15
  144. package/lib/mjs/fp.js.map +1 -1
  145. package/lib/mjs/hydrators.d.ts.map +1 -1
  146. package/lib/mjs/hydrators.js +29 -2
  147. package/lib/mjs/hydrators.js.map +1 -1
  148. package/lib/mjs/identity-map.d.ts +60 -0
  149. package/lib/mjs/identity-map.d.ts.map +1 -0
  150. package/lib/mjs/identity-map.js +130 -0
  151. package/lib/mjs/identity-map.js.map +1 -0
  152. package/lib/mjs/index.d.ts +12 -0
  153. package/lib/mjs/index.d.ts.map +1 -1
  154. package/lib/mjs/index.js +12 -0
  155. package/lib/mjs/index.js.map +1 -1
  156. package/lib/mjs/interfaces.d.ts +300 -10
  157. package/lib/mjs/interfaces.d.ts.map +1 -1
  158. package/lib/mjs/interfaces.js +53 -0
  159. package/lib/mjs/interfaces.js.map +1 -1
  160. package/lib/mjs/metadata.d.ts.map +1 -1
  161. package/lib/mjs/metadata.js +4 -1
  162. package/lib/mjs/metadata.js.map +1 -1
  163. package/lib/mjs/metrics.d.ts +53 -0
  164. package/lib/mjs/metrics.d.ts.map +1 -0
  165. package/lib/mjs/metrics.js +76 -0
  166. package/lib/mjs/metrics.js.map +1 -0
  167. package/lib/mjs/middlewares.d.ts +13 -1
  168. package/lib/mjs/middlewares.d.ts.map +1 -1
  169. package/lib/mjs/middlewares.js +55 -15
  170. package/lib/mjs/middlewares.js.map +1 -1
  171. package/lib/mjs/model.d.ts +129 -13
  172. package/lib/mjs/model.d.ts.map +1 -1
  173. package/lib/mjs/model.js +451 -95
  174. package/lib/mjs/model.js.map +1 -1
  175. package/lib/mjs/orm.d.ts.map +1 -1
  176. package/lib/mjs/orm.js +12 -3
  177. package/lib/mjs/orm.js.map +1 -1
  178. package/lib/mjs/orphan.d.ts +27 -0
  179. package/lib/mjs/orphan.d.ts.map +1 -0
  180. package/lib/mjs/orphan.js +52 -0
  181. package/lib/mjs/orphan.js.map +1 -0
  182. package/lib/mjs/primary-keys.d.ts +60 -0
  183. package/lib/mjs/primary-keys.d.ts.map +1 -0
  184. package/lib/mjs/primary-keys.js +223 -0
  185. package/lib/mjs/primary-keys.js.map +1 -0
  186. package/lib/mjs/relation-objects.d.ts +101 -9
  187. package/lib/mjs/relation-objects.d.ts.map +1 -1
  188. package/lib/mjs/relation-objects.js +194 -43
  189. package/lib/mjs/relation-objects.js.map +1 -1
  190. package/lib/mjs/relations.d.ts +7 -1
  191. package/lib/mjs/relations.d.ts.map +1 -1
  192. package/lib/mjs/relations.js +16 -1
  193. package/lib/mjs/relations.js.map +1 -1
  194. package/lib/mjs/resilience.d.ts +50 -0
  195. package/lib/mjs/resilience.d.ts.map +1 -0
  196. package/lib/mjs/resilience.js +64 -0
  197. package/lib/mjs/resilience.js.map +1 -0
  198. package/lib/mjs/snapshot.d.ts +58 -0
  199. package/lib/mjs/snapshot.d.ts.map +1 -0
  200. package/lib/mjs/snapshot.js +115 -0
  201. package/lib/mjs/snapshot.js.map +1 -0
  202. package/lib/mjs/statements.d.ts +11 -1
  203. package/lib/mjs/statements.d.ts.map +1 -1
  204. package/lib/mjs/statements.js +14 -3
  205. package/lib/mjs/statements.js.map +1 -1
  206. package/lib/mjs/subject-builder.d.ts +79 -0
  207. package/lib/mjs/subject-builder.d.ts.map +1 -0
  208. package/lib/mjs/subject-builder.js +292 -0
  209. package/lib/mjs/subject-builder.js.map +1 -0
  210. package/lib/mjs/subject-executor.d.ts +122 -0
  211. package/lib/mjs/subject-executor.d.ts.map +1 -0
  212. package/lib/mjs/subject-executor.js +312 -0
  213. package/lib/mjs/subject-executor.js.map +1 -0
  214. package/lib/mjs/subject-sorter.d.ts +70 -0
  215. package/lib/mjs/subject-sorter.d.ts.map +1 -0
  216. package/lib/mjs/subject-sorter.js +168 -0
  217. package/lib/mjs/subject-sorter.js.map +1 -0
  218. package/lib/mjs/subject.d.ts +100 -0
  219. package/lib/mjs/subject.d.ts.map +1 -0
  220. package/lib/mjs/subject.js +84 -0
  221. package/lib/mjs/subject.js.map +1 -0
  222. package/lib/mjs/unit-of-work.d.ts +52 -0
  223. package/lib/mjs/unit-of-work.d.ts.map +1 -0
  224. package/lib/mjs/unit-of-work.js +166 -0
  225. package/lib/mjs/unit-of-work.js.map +1 -0
  226. package/lib/tsconfig.cjs.tsbuildinfo +1 -1
  227. package/lib/tsconfig.mjs.tsbuildinfo +1 -1
  228. package/package.json +10 -7
@@ -10,7 +10,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  var RawQuery_1, WhereBuilder_1, SelectQueryBuilder_1;
11
11
  /* eslint-disable prettier/prettier */
12
12
  import { Container, Inject, NewInstance, DI, Injectable, isConstructor } from '@spinajs/di';
13
- import { InvalidArgument, MethodNotImplemented, InvalidOperation } from '@spinajs/exceptions';
13
+ import { InvalidArgument, MethodNotImplemented, InvalidOperation, NotSupported } from '@spinajs/exceptions';
14
14
  import { OrmException, OrmNotFoundException } from './exceptions.js';
15
15
  import _ from 'lodash';
16
16
  import { use } from 'typescript-mix';
@@ -18,9 +18,10 @@ import { ColumnMethods, ColumnType, QueryMethod, SortOrder, WhereBoolean, SqlOpe
18
18
  import { DeleteQueryCompiler, InsertQueryCompiler, SelectQueryCompiler, TruncateTableQueryCompiler, TableQueryCompiler, AlterTableQueryCompiler, UpdateQueryCompiler, QueryContext, IndexQueryCompiler, RelationType, ReferentialAction, DefaultValueBuilder, ColumnAlterationType, TableExistsCompiler, DropViewCompiler, DropTableCompiler, TableCloneQueryCompiler, QueryMiddleware, DropEventQueryCompiler, EventQueryCompiler, RawSchemaQueryCompiler } from './interfaces.js';
19
19
  import { BetweenStatement, ColumnMethodStatement, ColumnStatement, ExistsQueryStatement, InSetStatement, InStatement, RawQueryStatement, WhereQueryStatement, WhereStatement, ColumnRawStatement, JoinStatement, WithRecursiveStatement, GroupByStatement, Wrap, LazyQueryStatement } from './statements.js';
20
20
  import { BelongsToRelation, OneToManyRelation, ManyToManyRelation, BelongsToRecursiveRelation, QueryRelation, VirtualRelation } from './relations.js';
21
- import { Lazy } from '@spinajs/util';
21
+ import { Lazy, isNullOrWhitespace, _check_arg, _positive } from '@spinajs/util';
22
22
  import { DiscriminationMapMiddleware } from './discrimination-middleware.js';
23
23
  import { extractModelDescriptor } from './descriptor.js';
24
+ import { ExistsRelationHandler } from './existsRelationHandlers.js';
24
25
  function isWhereOperator(val) {
25
26
  return _.isString(val) && Object.values(SqlOperator).includes(val.toLowerCase());
26
27
  }
@@ -37,6 +38,12 @@ let Builder = class Builder {
37
38
  constructor(container, driver, model) {
38
39
  this._middlewares = [];
39
40
  this._queryMiddlewares = [];
41
+ /**
42
+ * Memoized result of {@link execute}. A builder executes at most once; awaiting
43
+ * it again resolves with the same value instead of re-running the query.
44
+ * Call `clone()` first if you genuinely need a second round-trip.
45
+ */
46
+ this._executionPromise = null;
40
47
  this._driver = driver;
41
48
  this._container = container;
42
49
  this._model = model;
@@ -44,82 +51,104 @@ let Builder = class Builder {
44
51
  this._asRaw = false;
45
52
  this._queryMiddlewares = DI.resolve(Array.ofType(QueryMiddleware));
46
53
  }
47
- then(onfulfilled, onrejected) {
48
- return this._driver
49
- .execute(this)
50
- .then((result) => {
51
- try {
52
- if (this._asRaw) {
53
- onfulfilled?.(result);
54
- return;
55
- }
56
- let transformedResult = result;
57
- // if we have something to transform ...
58
- if (transformedResult) {
59
- this._middlewares.forEach((m) => {
60
- Object.assign(transformedResult, m.afterQuery(transformedResult));
61
- });
62
- }
63
- if (this._model && !this._nonSelect) {
64
- // TODO: rething this casting
65
- const models = transformedResult.map((r) => {
66
- let model = null;
67
- for (const middleware of this._middlewares.reverse()) {
68
- model = middleware.modelCreation(r);
69
- if (model !== null) {
70
- break;
71
- }
72
- }
73
- if (model === null) {
74
- model = DI.resolve('__orm_model_factory__', [this._model]);
75
- }
76
- model.hydrate(r);
77
- model.IsDirty = false;
78
- return model;
79
- });
80
- const afterMiddlewarePromises = this._middlewares.reduce((prev, current) => {
81
- return prev.concat([current.afterHydration(models)]);
82
- }, []);
83
- if (this._middlewares.length > 0) {
84
- Promise.all(afterMiddlewarePromises).then(() => {
85
- try {
86
- onfulfilled?.(models);
87
- }
88
- catch (err) {
89
- if (onrejected) {
90
- onrejected(err);
91
- }
92
- else {
93
- throw err;
94
- }
95
- }
96
- }, onrejected);
97
- }
98
- else {
99
- onfulfilled?.(models);
54
+ /**
55
+ * The execution engine. Sends the compiled query to the driver, applies the
56
+ * result middlewares, hydrates models and awaits the post-hydration middlewares.
57
+ *
58
+ * Always *returns* its value — the caller's promise chain is what propagates it.
59
+ * Subclasses override this (not `execute()`) so that their extra work also lands
60
+ * inside the memo.
61
+ */
62
+ async _run() {
63
+ // Fires for EVERY builder type, not just SELECT. This used to live on
64
+ // `SelectQueryBuilder._run`, so an INSERT/UPDATE/DELETE never reached a
65
+ // `beforeQueryExecution` hook at all — which silently disabled any middleware that
66
+ // needs the finished query (rbac's insert-ownership check is exactly that: the payload
67
+ // does not exist until `values()` has been called, long after construction).
68
+ //
69
+ // Awaited in sequence, not `forEach`ed: a hook that needs a database round-trip to
70
+ // decide an rbac rule whose ownership lives in another table — could otherwise only
71
+ // return a promise into the void, and the query would execute anyway. For a security
72
+ // check that is the difference between enforcing and pretending to.
73
+ for (const middleware of this._queryMiddlewares) {
74
+ await middleware.beforeQueryExecution(this);
75
+ }
76
+ const result = (await this._driver.execute(this));
77
+ // Snapshot the pipeline once, *after* the driver call: compiling the query is what
78
+ // registers the relation middlewares (the driver calls `toDB()`), so this is the first
79
+ // point at which the list is complete. Everything below runs against this immutable copy,
80
+ // so a middleware registered later cannot change the pipeline mid-flight, and — crucially —
81
+ // we never call `Array.prototype.reverse()` on `this._middlewares`, which mutates in place
82
+ // and used to flip `modelCreation` resolution order on every execution ( B8 ).
83
+ const middlewares = [...this._middlewares];
84
+ const creationOrder = [...middlewares].reverse();
85
+ if (this._asRaw) {
86
+ return result;
87
+ }
88
+ const transformedResult = result;
89
+ // if we have something to transform ...
90
+ if (transformedResult) {
91
+ middlewares.forEach((m) => {
92
+ Object.assign(transformedResult, m.afterQuery(transformedResult));
93
+ });
94
+ }
95
+ if (this._model && !this._nonSelect) {
96
+ // TODO: rething this casting
97
+ const models = transformedResult.map((r) => {
98
+ let model = null;
99
+ for (const middleware of creationOrder) {
100
+ model = middleware.modelCreation(r);
101
+ if (model !== null) {
102
+ break;
100
103
  }
101
104
  }
102
- else {
103
- onfulfilled?.(transformedResult);
105
+ if (model === null) {
106
+ model = DI.resolve('__orm_model_factory__', [this._model]);
104
107
  }
105
- }
106
- catch (err) {
107
- if (onrejected) {
108
- onrejected(err);
109
- }
110
- else {
111
- throw err;
108
+ model.hydrate(r);
109
+ model.IsDirty = false;
110
+ // This is the one point at which the instance's columns hold exactly what the
111
+ // database returned, so it is the diff baseline for `save()`. Relation members
112
+ // are attached later by the afterHydration middlewares below, which record their
113
+ // own member keys into this same snapshot.
114
+ model.takeSnapshot();
115
+ // Nested relation data that arrived on the row itself ( belongsTo LEFT JOIN,
116
+ // and hasMany arrays passed straight to hydrate ) was attached by the hydrators
117
+ // inside `hydrate()` above — i.e. before the snapshot existed, so their own
118
+ // `snapshotRelation` calls no-opped. Record those relations now.
119
+ for (const name of (model.ModelDescriptor?.Relations ?? new Map()).keys()) {
120
+ if (model[name]?.Populated) {
121
+ model.snapshotRelation(name);
122
+ }
112
123
  }
124
+ return model;
125
+ });
126
+ if (middlewares.length > 0) {
127
+ await Promise.all(middlewares.map((m) => m.afterHydration(models)));
113
128
  }
114
- })
115
- .catch((err) => {
116
- if (onrejected) {
117
- onrejected(err);
118
- }
119
- else {
120
- throw err;
121
- }
122
- });
129
+ return models;
130
+ }
131
+ return transformedResult;
132
+ }
133
+ /**
134
+ * Executes the query. The single entry point for execution — `then()` delegates here.
135
+ * The underlying work runs exactly once per builder instance; subsequent calls resolve
136
+ * with the memoized result.
137
+ */
138
+ execute() {
139
+ if (!this._executionPromise) {
140
+ this._executionPromise = this._run();
141
+ }
142
+ return this._executionPromise;
143
+ }
144
+ then(onfulfilled, onrejected) {
145
+ return this.execute().then(onfulfilled, onrejected);
146
+ }
147
+ catch(onrejected) {
148
+ return this.execute().catch(onrejected);
149
+ }
150
+ finally(onfinally) {
151
+ return this.execute().finally(onfinally);
123
152
  }
124
153
  middleware(middleware) {
125
154
  this._middlewares.push(middleware);
@@ -191,7 +220,7 @@ let QueryBuilder = class QueryBuilder extends Builder {
191
220
  *
192
221
  */
193
222
  setTable(table, alias) {
194
- if (!table.trim()) {
223
+ if (isNullOrWhitespace(table)) {
195
224
  throw new InvalidArgument('table name is empty');
196
225
  }
197
226
  this._table = table;
@@ -228,9 +257,7 @@ let LimitBuilder = class LimitBuilder {
228
257
  };
229
258
  }
230
259
  take(count) {
231
- if (count <= 0) {
232
- throw new InvalidArgument(`take count cannot be negative number`);
233
- }
260
+ _check_arg(_positive())(count, 'take count');
234
261
  this._limit.limit = count;
235
262
  return this;
236
263
  }
@@ -256,7 +283,7 @@ let LimitBuilder = class LimitBuilder {
256
283
  const result = (await this);
257
284
  if (result === undefined || (Array.isArray(result) && result.length === 0)) {
258
285
  if (typeof error === 'function') {
259
- error = error(this.toDB());
286
+ throw error(this.toDB());
260
287
  }
261
288
  else
262
289
  throw error;
@@ -285,10 +312,7 @@ LimitBuilder = __decorate([
285
312
  export { LimitBuilder };
286
313
  let OrderByBuilder = class OrderByBuilder {
287
314
  constructor() {
288
- this._sort = {
289
- column: '',
290
- order: SortOrder.ASC,
291
- };
315
+ this._sorts = [];
292
316
  }
293
317
  order(column, direction) {
294
318
  if (!column) {
@@ -301,10 +325,10 @@ let OrderByBuilder = class OrderByBuilder {
301
325
  });
302
326
  return this;
303
327
  }
304
- this._sort = {
328
+ this._sorts.push({
305
329
  column,
306
330
  order: direction,
307
- };
331
+ });
308
332
  return this;
309
333
  }
310
334
  orderBy(column) {
@@ -315,10 +339,10 @@ let OrderByBuilder = class OrderByBuilder {
315
339
  });
316
340
  return this;
317
341
  }
318
- this._sort = {
342
+ this._sorts.push({
319
343
  column,
320
344
  order: SortOrder.ASC,
321
- };
345
+ });
322
346
  return this;
323
347
  }
324
348
  orderByDescending(column) {
@@ -329,14 +353,25 @@ let OrderByBuilder = class OrderByBuilder {
329
353
  });
330
354
  return this;
331
355
  }
332
- this._sort = {
356
+ this._sorts.push({
333
357
  column,
334
358
  order: SortOrder.DESC,
335
- };
359
+ });
336
360
  return this;
337
361
  }
362
+ /**
363
+ * Returns the FIRST sort entry (or null) for backward compat with dialect
364
+ * packages that emit a single ORDER BY column. Use getSorts() for all entries.
365
+ */
338
366
  getSort() {
339
- return this._sort.column.trim() !== '' ? this._sort : null;
367
+ const sort = this._sorts.find((s) => s.column.trim() !== '');
368
+ return sort ?? null;
369
+ }
370
+ /**
371
+ * Returns all sort entries (multi-column ORDER BY), skipping empty columns.
372
+ */
373
+ getSorts() {
374
+ return this._sorts.filter((s) => s.column.trim() !== '');
340
375
  }
341
376
  };
342
377
  OrderByBuilder = __decorate([
@@ -548,6 +583,23 @@ let WithRecursiveBuilder = class WithRecursiveBuilder {
548
583
  this._cteStatement = this._container.resolve(WithRecursiveStatement, ['cte', this, rcKeyName, pkName]);
549
584
  return this;
550
585
  }
586
+ /**
587
+ * Drops the recursive CTE from this builder.
588
+ *
589
+ * `WithRecursiveStatement.build()` compiles two CLONES of the owning query — the anchor
590
+ * member and the recursive member of the CTE. `clone()` copies `_cteStatement`, so each
591
+ * clone was still marked recursive and compiling it re-entered `build()`, which cloned
592
+ * again: an unbounded mutual recursion between `toDB()` and the recursive compiler that
593
+ * ended in a stack overflow rather than a query. The two member queries are by definition
594
+ * not themselves recursive, so the statement clears the flag on its clones.
595
+ *
596
+ * Named alongside `clearJoins()` / `clearWhere()`, which `build()` already uses to strip the
597
+ * parts of the parent query each member must not inherit.
598
+ */
599
+ clearRecursive() {
600
+ this._cteStatement = undefined;
601
+ return this;
602
+ }
551
603
  };
552
604
  WithRecursiveBuilder = __decorate([
553
605
  NewInstance()
@@ -582,12 +634,31 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
582
634
  clone(_parent) {
583
635
  // TODO: fix this cast
584
636
  const builder = new WhereBuilder_1(_parent);
585
- builder._statements = this._statements.map((s) => s.clone(builder));
637
+ builder._statements = this._statements.map((s) => {
638
+ const cloned = s.clone(builder);
639
+ // preserve the per-statement boolean connector (clone() rebuilds the
640
+ // statement from scratch and would otherwise reset it to the AND default)
641
+ cloned.Boolean = s.Boolean;
642
+ return cloned;
643
+ });
586
644
  builder._boolean = this._boolean;
587
645
  builder._model = this._model;
588
646
  builder._tableAlias = this.TableAlias;
589
647
  return builder;
590
648
  }
649
+ /**
650
+ * Pushes a statement onto this builder, stamping it with the currently pending
651
+ * boolean connector (set by {@link orWhere}/{@link andWhere}). The pending
652
+ * connector applies to the NEXT pushed statement only and resets to AND
653
+ * afterwards, so `where(a).where(b).orWhere(c).where(d)` compiles to
654
+ * `a AND b OR c AND d` rather than rewriting the whole clause.
655
+ */
656
+ pushStatement(statement) {
657
+ statement.Boolean = this._boolean;
658
+ this._statements.push(statement);
659
+ this._boolean = WhereBoolean.AND;
660
+ return this;
661
+ }
591
662
  when(condition, callback, callbackElse) {
592
663
  if (condition) {
593
664
  if (callback)
@@ -632,18 +703,18 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
632
703
  return this.where(RawQuery.create(column ? 'TRUE' : 'FALSE'));
633
704
  }
634
705
  if (column instanceof RawQuery) {
635
- this.Statements.push(this._container.resolve(RawQueryStatement, [column.Query, column.Bindings, self.TableAlias]));
706
+ this.pushStatement(this._container.resolve(RawQueryStatement, [column.Query, column.Bindings, self.TableAlias]));
636
707
  return this;
637
708
  }
638
709
  // handle nested where's
639
710
  if (_.isFunction(column)) {
640
711
  const builder = new WhereBuilder_1(this);
641
712
  column.call(builder);
642
- self.Statements.push(this._container.resolve(WhereQueryStatement, [builder, self.TableAlias]));
713
+ self.pushStatement(this._container.resolve(WhereQueryStatement, [builder, self.TableAlias]));
643
714
  return this;
644
715
  }
645
716
  if (column instanceof Lazy) {
646
- this.Statements.push(this._container.resolve(LazyQueryStatement, [column, this]));
717
+ this.pushStatement(this._container.resolve(LazyQueryStatement, [column, this]));
647
718
  return this;
648
719
  }
649
720
  // handle simple key = object[key] AND ....
@@ -672,7 +743,7 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
672
743
  if (sVal === null) {
673
744
  return this.whereNull(c);
674
745
  }
675
- self._statements.push(self._container.resolve(WhereStatement, [c, SqlOperator.EQ, sVal, this]));
746
+ self.pushStatement(self._container.resolve(WhereStatement, [c, SqlOperator.EQ, sVal, this]));
676
747
  return self;
677
748
  }
678
749
  /**
@@ -691,12 +762,17 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
691
762
  throw new InvalidArgument(`value cannot be NaN`);
692
763
  }
693
764
  if (sVal === null) {
694
- return this.whereNull(c);
695
- }
696
- if (sVal === null) {
697
- return o === SqlOperator.NOT_NULL ? this.whereNotNull(c) : this.whereNull(c);
765
+ const op = String(o).toLowerCase();
766
+ // where(col, '=', null) => IS NULL ; where(col, '!=' / '<>', null) => IS NOT NULL
767
+ if (op === SqlOperator.EQ) {
768
+ return this.whereNull(c);
769
+ }
770
+ if (op === SqlOperator.NOT || op === SqlOperator.NOT_2) {
771
+ return this.whereNotNull(c);
772
+ }
773
+ throw new InvalidArgument(`operator ${o} cannot be used with null value ( only =, !=, <> are allowed )`);
698
774
  }
699
- self._statements.push(self._container.resolve(WhereStatement, [c, o, sVal, self]));
775
+ self.pushStatement(self._container.resolve(WhereStatement, [c, o, sVal, self]));
700
776
  return this;
701
777
  }
702
778
  }
@@ -713,9 +789,9 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
713
789
  for (const key of Object.keys(obj).filter((x) => obj[x] !== undefined)) {
714
790
  const val = obj[key];
715
791
  if (Array.isArray(val)) {
716
- if (val.length !== 0) {
717
- this.whereIn(key, val);
718
- }
792
+ // empty array => SQL `IN ()` semantics => match nothing (FALSE),
793
+ // never "no condition" (which would match everything)
794
+ this.whereIn(key, val);
719
795
  }
720
796
  else if (val === null) {
721
797
  this.whereNull(key);
@@ -726,165 +802,82 @@ let WhereBuilder = WhereBuilder_1 = class WhereBuilder {
726
802
  return this;
727
803
  }
728
804
  whereNotNull(column) {
729
- this._statements.push(this._container.resolve(WhereStatement, [column, SqlOperator.NOT_NULL, null, this]));
805
+ this.pushStatement(this._container.resolve(WhereStatement, [column, SqlOperator.NOT_NULL, null, this]));
730
806
  return this;
731
807
  }
732
808
  whereNull(column) {
733
- this._statements.push(this._container.resolve(WhereStatement, [column, SqlOperator.NULL, null, this]));
809
+ this.pushStatement(this._container.resolve(WhereStatement, [column, SqlOperator.NULL, null, this]));
734
810
  return this;
735
811
  }
736
812
  whereNot(column, val) {
737
813
  return this.where(column, SqlOperator.NOT, val);
738
814
  }
739
815
  whereIn(column, val) {
740
- this._statements.push(this._container.resolve(InStatement, [column, val, false, this]));
816
+ // `IN ()` matches nothing in SQL; compile an empty set to FALSE rather than
817
+ // emitting no condition (which would silently match every row).
818
+ if (Array.isArray(val) && val.length === 0) {
819
+ this.where(false);
820
+ return this;
821
+ }
822
+ this.pushStatement(this._container.resolve(InStatement, [column, val, false, this]));
741
823
  return this;
742
824
  }
743
825
  whereNotIn(column, val) {
744
- this._statements.push(this._container.resolve(InStatement, [column, val, true, this]));
826
+ this.pushStatement(this._container.resolve(InStatement, [column, val, true, this]));
745
827
  return this;
746
828
  }
747
829
  whereExist(query, callback) {
748
- // TODO: refactor and remove code duplication with whereNotExists
749
- // TODO: move relation handling to separate DI service for every exists relation type
750
- let relQuery;
751
- let sourcePKey = '';
752
- const self = this;
753
- let tableName = '';
754
- let tDesc;
755
- if (typeof query === 'string') {
756
- const rel = this._model.getRelationDescriptor(query);
757
- if (!rel) {
758
- throw new OrmException(`relation ${query} not found in model ${this.constructor.name}`);
759
- }
760
- switch (rel.Type) {
761
- case RelationType.One:
762
- this.whereNotNull(rel.ForeignKey);
763
- // simply use right join for condition check
764
- if (callback) {
765
- // TODO: cast fix
766
- this.rightJoin(rel.TargetModel, callback.bind(query));
767
- }
768
- break;
769
- case RelationType.Many:
770
- tableName = rel.TargetModel.getModelDescriptor().TableName;
771
- tDesc = self._model.getModelDescriptor();
772
- // set alias to avoid conflicts in case of multiple relations to same model and to make sure that relation query is correct even if source query has alias
773
- relQuery = rel.TargetModel.query().setAlias(`${tableName}_exists`);
774
- relQuery.where(Lazy.oF(function () {
775
- const sourceAlias = self._tableAlias ?? (self._parent ? self._parent.TableAlias : tDesc.TableName);
776
- sourcePKey = `\`${sourceAlias}\`.\`${self._model.getModelDescriptor().PrimaryKey}\``;
777
- // relQuery is guaranteed assigned above before this lazy callback executes
778
- relQuery.where(new RawQuery(`${rel.ForeignKey} = ${sourcePKey}`));
779
- }));
780
- if (callback) {
781
- callback.apply(relQuery);
782
- }
783
- this.whereExist(relQuery);
784
- break;
785
- case RelationType.ManyToMany:
786
- relQuery = rel.JunctionModel.query();
787
- tableName = rel.TargetModel.getModelDescriptor().TableName;
788
- tDesc = self._model.getModelDescriptor();
789
- relQuery = rel.TargetModel.query().setAlias(`${tableName}_exists`);
790
- relQuery.where(Lazy.oF(function () {
791
- const sourceAlias = self._tableAlias ?? (self._parent ? self._parent.TableAlias : tDesc.TableName);
792
- sourcePKey = `\`${sourceAlias}\`.\`${self._model.getModelDescriptor().PrimaryKey}\``;
793
- // relQuery is guaranteed assigned above before this lazy callback executes
794
- relQuery.where(new RawQuery(`${rel.JunctionModelSourceModelFKey_Name} = ${sourcePKey}`));
795
- }));
796
- this.setAlias();
797
- relQuery.rightJoin({
798
- joinModel: rel.TargetModel,
799
- joinTableForeignKey: rel.ForeignKey,
800
- sourceTablePrimaryKey: rel.JunctionModelTargetModelFKey_Name,
801
- callback: callback,
802
- });
803
- this.whereExist(relQuery);
804
- break;
805
- }
806
- }
807
- else {
808
- this._statements.push(this._container.resolve(ExistsQueryStatement, [query, false]));
809
- }
810
- return this;
830
+ return this.buildExistsClause(query, false, callback);
811
831
  }
812
832
  whereNotExists(query, callback) {
813
- let relQuery;
814
- let sourcePKey = '';
815
- const self = this;
816
- let tableName = '';
817
- let tDesc;
818
- if (typeof query === 'string') {
819
- const rel = this._model.getRelationDescriptor(query);
820
- if (!rel) {
821
- throw new OrmException(`relation ${query} not found in model ${this.constructor.name}`);
822
- }
823
- switch (rel.Type) {
824
- case RelationType.One:
825
- this.whereNotNull(rel.ForeignKey);
826
- // simply use right join for condition check
827
- if (callback) {
828
- // TODO: cast fix
829
- this.rightJoin(rel.TargetModel, callback.bind(query));
830
- }
831
- break;
832
- case RelationType.Many:
833
- tableName = rel.TargetModel.getModelDescriptor().TableName;
834
- tDesc = self._model.getModelDescriptor();
835
- // set alias to avoid conflicts in case of multiple relations to same model and to make sure that relation query is correct even if source query has alias
836
- relQuery = rel.TargetModel.query().setAlias(`${tableName}_exists`);
837
- relQuery.where(Lazy.oF(function () {
838
- const sourceAlias = self._tableAlias || (self._parent ? self._parent.TableAlias : tDesc.TableName);
839
- sourcePKey = `\`${sourceAlias}\`.\`${self._model.getModelDescriptor().PrimaryKey}\``;
840
- // relQuery is guaranteed assigned above before this lazy callback executes
841
- relQuery.where(new RawQuery(`${rel.ForeignKey} = ${sourcePKey}`));
842
- }));
843
- if (callback) {
844
- callback.apply(relQuery);
845
- }
846
- this.whereNotExists(relQuery);
847
- break;
848
- case RelationType.ManyToMany:
849
- relQuery = rel.JunctionModel.query();
850
- tableName = rel.TargetModel.getModelDescriptor().TableName;
851
- tDesc = self._model.getModelDescriptor();
852
- relQuery = rel.TargetModel.query().setAlias(`${tableName}_exists`);
853
- relQuery.where(Lazy.oF(function () {
854
- const sourceAlias = self._tableAlias || (self._parent ? self._parent.TableAlias : tDesc.TableName);
855
- sourcePKey = `\`${sourceAlias}\`.\`${self._model.getModelDescriptor().PrimaryKey}\``;
856
- // relQuery is guaranteed assigned above before this lazy callback executes
857
- relQuery.where(new RawQuery(`${rel.JunctionModelSourceModelFKey_Name} = ${sourcePKey}`));
858
- }));
859
- relQuery.rightJoin({
860
- joinModel: rel.TargetModel,
861
- joinTableForeignKey: rel.PrimaryKey,
862
- sourceTablePrimaryKey: rel.ForeignKey,
863
- callback: callback,
864
- });
865
- this.whereNotExists(relQuery);
866
- break;
867
- }
833
+ return this.buildExistsClause(query, true, callback);
834
+ }
835
+ /**
836
+ * Shared implementation for {@link whereExist} / {@link whereNotExists}.
837
+ *
838
+ * For a ready sub-query it pushes an {@link ExistsQueryStatement} directly. For a relation
839
+ * name it resolves the matching {@link ExistsRelationHandler} from the container (one per
840
+ * {@link RelationType}) and lets it either mutate this builder or return a correlated
841
+ * sub-query that we then wrap in EXISTS / NOT EXISTS.
842
+ *
843
+ * @param query relation name or a ready sub-query
844
+ * @param negated `true` for NOT EXISTS, `false` for EXISTS
845
+ * @param callback optional where-callback applied to the relation sub-query
846
+ */
847
+ buildExistsClause(query, negated, callback) {
848
+ if (typeof query !== 'string') {
849
+ this.pushStatement(this._container.resolve(ExistsQueryStatement, [query, negated]));
850
+ return this;
868
851
  }
869
- else {
870
- this._statements.push(this._container.resolve(ExistsQueryStatement, [query, true]));
852
+ const rel = this._model.getRelationDescriptor(query);
853
+ if (!rel) {
854
+ throw new OrmException(`relation ${query} not found in model ${this.constructor.name}`);
855
+ }
856
+ const handlers = this._container.resolve(Array.ofType(ExistsRelationHandler));
857
+ const handler = handlers.find((h) => h.Type === rel.Type);
858
+ if (!handler) {
859
+ throw new OrmException(`no ExistsRelationHandler registered for relation type ${rel.Type} (relation ${query} on ${this.constructor.name})`);
860
+ }
861
+ const subquery = handler.apply(this, rel, query, callback);
862
+ if (subquery) {
863
+ this.pushStatement(this._container.resolve(ExistsQueryStatement, [subquery, negated]));
871
864
  }
872
865
  return this;
873
866
  }
874
867
  whereBetween(column, val) {
875
- this._statements.push(this._container.resolve(BetweenStatement, [column, val, false, this.TableAlias]));
868
+ this.pushStatement(this._container.resolve(BetweenStatement, [column, val, false, this.TableAlias]));
876
869
  return this;
877
870
  }
878
871
  whereNotBetween(column, val) {
879
- this._statements.push(this._container.resolve(BetweenStatement, [column, val, true, this.TableAlias]));
872
+ this.pushStatement(this._container.resolve(BetweenStatement, [column, val, true, this.TableAlias]));
880
873
  return this;
881
874
  }
882
875
  whereInSet(column, val) {
883
- this._statements.push(this._container.resolve(InSetStatement, [column, val, false, this.TableAlias]));
876
+ this.pushStatement(this._container.resolve(InSetStatement, [column, val, false, this.TableAlias]));
884
877
  return this;
885
878
  }
886
879
  whereNotInSet(column, val) {
887
- this._statements.push(this._container.resolve(InSetStatement, [column, val, true, this.TableAlias]));
880
+ this.pushStatement(this._container.resolve(InSetStatement, [column, val, true, this.TableAlias]));
888
881
  return this;
889
882
  }
890
883
  clearWhere() {
@@ -924,10 +917,7 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
924
917
  this._distinct = false;
925
918
  this._method = QueryMethod.SELECT;
926
919
  this._boolean = WhereBoolean.AND;
927
- this._sort = {
928
- column: '',
929
- order: SortOrder.NONE,
930
- };
920
+ this._sorts = [];
931
921
  this._first = false;
932
922
  this._limit = {
933
923
  limit: -1,
@@ -942,13 +932,12 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
942
932
  return (await this);
943
933
  }
944
934
  setAlias(alias) {
945
- if (!alias || alias.trim() === '') {
946
- alias = `${this._driver.Options.AliasSeparator}${this._table}${this._driver.Options.AliasSeparator}`;
947
- }
948
- this._tableAlias = alias;
949
- this._columns.forEach((c) => (c.TableAlias = alias));
950
- this._joinStatements.forEach((c) => (c.TableAlias = alias));
951
- this._statements.forEach((c) => (c.TableAlias = alias));
935
+ // isNullOrWhitespace is not a type predicate; a false result guarantees a non-blank string
936
+ const resolvedAlias = isNullOrWhitespace(alias) ? `${this._driver.Options.AliasSeparator}${this._table}${this._driver.Options.AliasSeparator}` : alias;
937
+ this._tableAlias = resolvedAlias;
938
+ this._columns.forEach((c) => (c.TableAlias = resolvedAlias));
939
+ this._joinStatements.forEach((c) => (c.TableAlias = resolvedAlias));
940
+ this._statements.forEach((c) => (c.TableAlias = resolvedAlias));
952
941
  return this;
953
942
  }
954
943
  clone() {
@@ -959,11 +948,18 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
959
948
  builder._joinStatements = this._joinStatements.map(c => c.clone(builder));
960
949
  // Clone statements with mapped WhereBuilder references
961
950
  builder._statements = this._statements.map(c => c.clone(builder));
951
+ // Clone group-by statements (previously dropped, silently losing GROUP BY
952
+ // on a cloned query — eg. orm-api count-pagination clones the builder).
953
+ builder._groupStatements = this._groupStatements.map(c => c.clone(builder));
954
+ // Carry relations and result middlewares over. These hold live objects and
955
+ // are shared the same way mergeRelations()/mergeBuilder() already share them.
956
+ builder._relations = [...this._relations];
957
+ builder._middlewares = [...this._middlewares];
962
958
  /**
963
959
  * ------------------------------------------------------------------
964
960
  */
965
961
  builder._limit = { ...this._limit };
966
- builder._sort = { ...this._sort };
962
+ builder._sorts = this._sorts.map((s) => ({ ...s }));
967
963
  builder._boolean = this._boolean;
968
964
  builder._distinct = this._distinct;
969
965
  builder._table = this._table;
@@ -1062,15 +1058,21 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
1062
1058
  this._getRelationInstance(relation).execute(callback);
1063
1059
  return this;
1064
1060
  }
1065
- mergeBuilder(builder) {
1061
+ mergeBuilder(builder, includeStatements = true) {
1066
1062
  this._columns = this._columns.concat(builder._columns);
1067
1063
  this._cteStatement = builder._cteStatement;
1068
1064
  this._distinct = builder._distinct;
1069
- this._sort = {
1070
- column: builder._sort.column !== '' ? builder._sort.column : this._sort.column,
1071
- order: builder._sort.order !== '' ? builder._sort.order : this._sort.order,
1072
- };
1073
- this.mergeStatements(builder);
1065
+ // Fold the merged builder's sorts into this query's sorts (multi-column
1066
+ // ORDER BY). If the merged builder has no sorts, this keeps our own.
1067
+ this._sorts = this._sorts.concat(builder._sorts.map((s) => ({ ...s })));
1068
+ // `includeStatements: false` is used by JoinStatement so that a join
1069
+ // callback's WHERE conditions are NOT folded into the main query's WHERE
1070
+ // (which silently turns a LEFT JOIN into an inner filter). The join emits
1071
+ // those conditions in its own ON clause instead. Columns/sort are still
1072
+ // merged so join-callback selects (e.g. extra joined columns) keep working.
1073
+ if (includeStatements) {
1074
+ this.mergeStatements(builder);
1075
+ }
1074
1076
  }
1075
1077
  mergeRelations(builder) {
1076
1078
  this._relations = this._relations.concat(builder._relations);
@@ -1081,6 +1083,21 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
1081
1083
  this._joinStatements = this._joinStatements.concat(builder._joinStatements);
1082
1084
  this._statements = this._statements.concat(stms);
1083
1085
  }
1086
+ /**
1087
+ * Includes soft-deleted rows in the result set by removing the default
1088
+ * `DeletedAt IS NULL` filter added by createQuery for @SoftDelete models.
1089
+ */
1090
+ withDeleted() {
1091
+ const descriptor = extractModelDescriptor(this._model);
1092
+ const deletedAt = descriptor?.SoftDelete?.DeletedAt;
1093
+ if (!deletedAt) {
1094
+ return this;
1095
+ }
1096
+ this._statements = this._statements.filter((s) => {
1097
+ return !(s instanceof WhereStatement && s.Column === deletedAt && s.Operator === SqlOperator.NULL);
1098
+ });
1099
+ return this;
1100
+ }
1084
1101
  min(column, as) {
1085
1102
  this._columns.push(this._container.resolve(ColumnMethodStatement, [column, ColumnMethods.MIN, as, this._tableAlias]));
1086
1103
  return this;
@@ -1122,44 +1139,26 @@ let SelectQueryBuilder = SelectQueryBuilder_1 = class SelectQueryBuilder extends
1122
1139
  return compiler.compile();
1123
1140
  }
1124
1141
  async all() {
1125
- return await this;
1142
+ return await this.execute();
1126
1143
  }
1127
1144
  async resultExists() {
1128
- return this.then((res) => {
1129
- if (Array.isArray(res)) {
1130
- return res.length > 0;
1131
- }
1132
- return res !== undefined && res !== null;
1133
- });
1134
- }
1135
- then(onfulfilled, onrejected) {
1136
- this._queryMiddlewares.forEach((x) => x.beforeQueryExecution(this));
1137
- return super.then((result) => {
1138
- if (this._first) {
1139
- if (Array.isArray(result)) {
1140
- if (result.length !== 0) {
1141
- return onfulfilled?.(result[0]);
1142
- }
1143
- else {
1144
- try {
1145
- return onfulfilled?.(undefined);
1146
- }
1147
- catch (err) {
1148
- onrejected?.(err);
1149
- }
1150
- }
1151
- }
1152
- else {
1153
- return onfulfilled?.(result);
1154
- }
1155
- }
1156
- else {
1157
- return onfulfilled?.(result);
1158
- }
1159
- }, onrejected);
1145
+ const res = await this.execute();
1146
+ if (Array.isArray(res)) {
1147
+ return res.length > 0;
1148
+ }
1149
+ return res !== undefined && res !== null;
1160
1150
  }
1161
- async execute() {
1162
- return (await this);
1151
+ /**
1152
+ * Overrides the engine rather than `execute()` so that the `takeFirst()` unwrapping
1153
+ * happens *inside* the memo. `beforeQueryExecution` is dispatched by `Builder._run()`
1154
+ * for every builder type, so it still fires once per builder rather than once per await.
1155
+ */
1156
+ async _run() {
1157
+ const result = await super._run();
1158
+ if (this._first && Array.isArray(result)) {
1159
+ return (result.length !== 0 ? result[0] : undefined);
1160
+ }
1161
+ return result;
1163
1162
  }
1164
1163
  };
1165
1164
  __decorate([
@@ -1184,6 +1183,10 @@ let DeleteQueryBuilder = class DeleteQueryBuilder extends QueryBuilder {
1184
1183
  offset: -1,
1185
1184
  };
1186
1185
  this.QueryContext = QueryContext.Delete;
1186
+ // Query middlewares (e.g. rbac ownership enforcement) must run for deletes
1187
+ // too, not only selects — otherwise :own permission constraints are never
1188
+ // applied and any row can be deleted.
1189
+ this._queryMiddlewares.forEach((x) => x.afterQueryCreation(this));
1187
1190
  }
1188
1191
  toDB() {
1189
1192
  return this._container.resolve(DeleteQueryCompiler, [this]).compile();
@@ -1223,6 +1226,9 @@ let OnDuplicateQueryBuilder = class OnDuplicateQueryBuilder {
1223
1226
  this._columnsToUpdate = columns;
1224
1227
  return this;
1225
1228
  }
1229
+ execute() {
1230
+ return this._parent.execute();
1231
+ }
1226
1232
  then(onfulfilled, onrejected) {
1227
1233
  return this._parent.then(onfulfilled, onrejected);
1228
1234
  }
@@ -1246,6 +1252,10 @@ let UpdateQueryBuilder = class UpdateQueryBuilder extends QueryBuilder {
1246
1252
  this._boolean = WhereBoolean.AND;
1247
1253
  this._statements = [];
1248
1254
  this.QueryContext = QueryContext.Update;
1255
+ // Query middlewares (e.g. rbac ownership enforcement) must run for updates
1256
+ // too, not only selects — otherwise :own permission constraints are never
1257
+ // applied and any row can be updated.
1258
+ this._queryMiddlewares.forEach((x) => x.afterQueryCreation(this));
1249
1259
  }
1250
1260
  in(name) {
1251
1261
  this.setTable(name);
@@ -1281,6 +1291,10 @@ let InsertQueryBuilder = class InsertQueryBuilder extends QueryBuilder {
1281
1291
  get Replace() {
1282
1292
  return this._replace;
1283
1293
  }
1294
+ /** Columns requested via {@link returning}. Empty when no RETURNING clause was asked for. */
1295
+ get Returning() {
1296
+ return this._returning ?? [];
1297
+ }
1284
1298
  constructor(container, driver, model) {
1285
1299
  super(container, driver, model);
1286
1300
  this._update = false;
@@ -1289,6 +1303,52 @@ let InsertQueryBuilder = class InsertQueryBuilder extends QueryBuilder {
1289
1303
  this._columns = [];
1290
1304
  this._values = [];
1291
1305
  this.QueryContext = QueryContext.Insert;
1306
+ // Inserts get the same middleware pass as the other three builders. Note that a
1307
+ // middleware which needs to see or amend the row payload must use
1308
+ // `beforeQueryExecution` instead — at construction time `values()` has not been called.
1309
+ this._queryMiddlewares.forEach((x) => x.afterQueryCreation(this));
1310
+ }
1311
+ /**
1312
+ * The value of `column` on every row about to be inserted, in row order. Empty when the
1313
+ * payload does not carry that column at all.
1314
+ *
1315
+ * The read counterpart of {@link forceColumn}, and the only sane way for a
1316
+ * `beforeQueryExecution` hook to inspect what is being written: the payload lives in two
1317
+ * parallel structures ( `_columns` and one array per row ), so every caller would
1318
+ * otherwise repeat the same index lookup.
1319
+ *
1320
+ * @param column - column to read off each row
1321
+ */
1322
+ getColumnValues(column) {
1323
+ const index = this._columns.findIndex((c) => !(c.Column instanceof RawQuery) && c.Column === column);
1324
+ if (index === -1) {
1325
+ return [];
1326
+ }
1327
+ // eslint-disable-next-line security/detect-object-injection
1328
+ return this._values.map((row) => row[index]);
1329
+ }
1330
+ /**
1331
+ * Forces `column` to `value` on every row of the payload, overwriting whatever the caller
1332
+ * supplied.
1333
+ *
1334
+ * This is the write path for a policy that must not be negotiable — rbac's `createOwn`
1335
+ * ownership stamp. `values()` cannot serve: it appends rows and takes the column list from
1336
+ * its own argument, so calling it a second time adds a row rather than amending the
1337
+ * existing ones.
1338
+ *
1339
+ * @param column - column to overwrite on every row
1340
+ * @param value - value to force
1341
+ */
1342
+ forceColumn(column, value) {
1343
+ const existing = this._columns.findIndex((c) => !(c.Column instanceof RawQuery) && c.Column === column);
1344
+ if (existing === -1) {
1345
+ this.columns([...this._columns.filter((c) => !(c.Column instanceof RawQuery)).map((c) => c.Column), column]);
1346
+ this._values.forEach((row) => row.push(value));
1347
+ return this;
1348
+ }
1349
+ // eslint-disable-next-line security/detect-object-injection
1350
+ this._values.forEach((row) => (row[existing] = value));
1351
+ return this;
1292
1352
  }
1293
1353
  /**
1294
1354
  * Sets insert to ignore on duplicate
@@ -1301,8 +1361,21 @@ let InsertQueryBuilder = class InsertQueryBuilder extends QueryBuilder {
1301
1361
  this._replace = true;
1302
1362
  return this;
1303
1363
  }
1364
+ /**
1365
+ * Asks the dialect to echo the given columns of every inserted row back.
1366
+ *
1367
+ * @throws NotSupported on drivers whose `supportedFeatures().insertReturning` is false —
1368
+ * silently doing nothing is how this API was a no-op on MySQL and MSSQL for years.
1369
+ */
1304
1370
  returning(columns) {
1371
+ if (!this.Driver.supportedFeatures().insertReturning) {
1372
+ throw new NotSupported(`driver ${this.Driver.Options.Driver} does not support RETURNING on INSERT`);
1373
+ }
1305
1374
  this._returning = columns;
1375
+ // onDuplicate() sets Upsert unconditionally and wins if it runs afterwards.
1376
+ if (this.QueryContext === QueryContext.Insert) {
1377
+ this.QueryContext = QueryContext.InsertReturning;
1378
+ }
1306
1379
  return this;
1307
1380
  }
1308
1381
  values(data) {
@@ -1464,6 +1537,7 @@ let ColumnQueryBuilder = class ColumnQueryBuilder {
1464
1537
  this.Comment = '';
1465
1538
  this.Unique = false;
1466
1539
  this.Unsigned = false;
1540
+ this.InlinePrimaryKey = true;
1467
1541
  this.Args.push(...args);
1468
1542
  }
1469
1543
  notNull() {
@@ -1634,10 +1708,11 @@ AlterTableQueryBuilder = __decorate([
1634
1708
  export { AlterTableQueryBuilder };
1635
1709
  let TableQueryBuilder = class TableQueryBuilder extends QueryBuilder {
1636
1710
  /**
1637
- * Alias for string(name, 36 )
1711
+ * Alias for binary(name, 16 ) - uuids are stored as 16-byte BINARY to match
1712
+ * the UuidConverter ( which writes a dashed uuid as a 16-byte buffer ).
1638
1713
  */
1639
1714
  uuid(name) {
1640
- return this.string(name, 36);
1715
+ return this.binary(name, 16);
1641
1716
  }
1642
1717
  ifExists() {
1643
1718
  this._checkExists = true;
@@ -2043,6 +2118,16 @@ export function createQuery(model, query, injectModel = true) {
2043
2118
  }
2044
2119
  qr.middleware(new DiscriminationMapMiddleware(dsc));
2045
2120
  qr.setTable(dsc.TableName);
2121
+ // Soft-delete read filtering: by default exclude rows that have been soft
2122
+ // deleted (DeletedAt IS NOT NULL). SelectQueryBuilder.withDeleted() removes
2123
+ // this default statement to include soft-deleted rows again.
2124
+ // Guarded on the DeletedAt column actually being present in the model's
2125
+ // reflected columns — a filter on a column the schema does not expose is
2126
+ // impossible anyway, and the guard keeps queries working when table info
2127
+ // has not (yet) surfaced the column.
2128
+ if (qr instanceof SelectQueryBuilder && dsc.SoftDelete?.DeletedAt && dsc.Columns?.some((c) => c.Name === dsc.SoftDelete.DeletedAt)) {
2129
+ qr.whereNull(dsc.SoftDelete.DeletedAt);
2130
+ }
2046
2131
  if (driver.Options.Database) {
2047
2132
  qr.database(driver.Options.Database);
2048
2133
  }