adminizer 5.0.0-build.8 → 5.1.0-build.24

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 (232) hide show
  1. package/assets/abnfDiagram-VCTEODGH-DFY3Pztx.js +1 -0
  2. package/assets/add-31tS-g4V.js +1 -0
  3. package/assets/add-group-Cf3zT7WE.js +1 -0
  4. package/assets/add-user-C62wOna-.js +1 -0
  5. package/assets/ai-assistant/agent.es.js +28464 -0
  6. package/assets/app-DPmI-MvN.css +1 -0
  7. package/assets/app-vdHbjOQX.js +138 -0
  8. package/assets/arc-DP4o1zz3.js +1 -0
  9. package/assets/architectureDiagram-5GKGNRK7-BjXW5Zu0.js +36 -0
  10. package/assets/asmcrypto-DZeQH0LC.js +1 -0
  11. package/assets/blockDiagram-I7D4REHJ-BUPnDwN0.js +129 -0
  12. package/assets/c4Diagram-7LVT6UL2-Cmb_dAhd.js +38 -0
  13. package/assets/{catalog-BEvJZYJP.js → catalog-C-y5rMaG.js} +3 -3
  14. package/assets/channel-CkJRUsq_.js +1 -0
  15. package/assets/chunk-2Q5K7J3B-CmS-dWwQ.js +1 -0
  16. package/assets/chunk-5VM5RSS4-Cqs2Ow64.js +15 -0
  17. package/assets/chunk-F27PBJKO-BoMCpJA1.js +1 -0
  18. package/assets/chunk-IMKFNOWR-DlDcPk1x.js +231 -0
  19. package/assets/chunk-JWPE2WC7-DzL_SU8s.js +1 -0
  20. package/assets/chunk-POPQ4Y6H-Cwq2-Vb8.js +1 -0
  21. package/assets/chunk-SVP7TREG-BfRHY5FU.js +88 -0
  22. package/assets/chunk-TICWLB2K-CRP7rHkW.js +206 -0
  23. package/assets/chunk-XXDRQBXY-Bg2jg-98.js +1 -0
  24. package/assets/classDiagram-ZZMXUADV-D4EF3HGj.js +1 -0
  25. package/assets/classDiagram-v2-VYDZK3BY-D4EF3HGj.js +1 -0
  26. package/assets/controls/ckeditor.es.js +9382 -9320
  27. package/assets/controls/handsontable.es.js +11971 -11938
  28. package/assets/controls/jsoneditor.es.js +10442 -10233
  29. package/assets/controls/toast-ui.es.js +1308 -1279
  30. package/assets/cose-bilkent-JH36ORCC-CCJ0SGyO.js +1 -0
  31. package/assets/cynefin-OW5HDTMX-B2Jx5fw3.js +166 -0
  32. package/assets/cynefinDiagram-5FMLGOSQ-DuK0Lb62.js +62 -0
  33. package/assets/cytoscape.esm-DzSz-X2X.js +321 -0
  34. package/assets/dagre-GXQ25YYZ-DnOYexET.js +4 -0
  35. package/assets/dashboard-X8k4X6Sw.js +3 -0
  36. package/assets/debounce-B3mXI_sx.js +1 -0
  37. package/assets/defaultLocale-DX6XiGOO.js +1 -0
  38. package/assets/diagram-S7CK7UJ4-DDgPBHsK.js +30 -0
  39. package/assets/diagram-UQ7AKVKN-qlL0GZs8.js +41 -0
  40. package/assets/diagram-VSXAHHWV-CQbiU1_c.js +3 -0
  41. package/assets/diagram-VX7I27RA-DFDI9_au.js +24 -0
  42. package/assets/diagram-Z3DM3KII-B5C9dtmJ.js +24 -0
  43. package/assets/docs-BiZOD7PP.js +1 -0
  44. package/assets/ebnfDiagram-PWID7BFC-CLMUsXEI.js +1 -0
  45. package/assets/erDiagram-RLTQ6QDP-ax9Xol5-.js +99 -0
  46. package/assets/flowDiagram-HODETNUW-CvaH7l3S.js +168 -0
  47. package/assets/ganttDiagram-EL5Y4UJY-Cga5U19j.js +292 -0
  48. package/assets/gitGraphDiagram-WWUBYQGX-B6o1T3-d.js +106 -0
  49. package/assets/history-CVBJUNSQ.js +1 -0
  50. package/assets/infoDiagram-27XIBGKW-BpWxgBCE.js +2 -0
  51. package/assets/init-Gi6I4Gst.js +1 -0
  52. package/assets/init-user-xSvnFTF0.js +1 -0
  53. package/assets/isObject-DL-BuaBl.js +1 -0
  54. package/assets/ishikawaDiagram-5VMMS53U-zE83db4H.js +70 -0
  55. package/assets/journeyDiagram-3NMN7TZE-DZNVFqrX.js +139 -0
  56. package/assets/kanban-definition-UXKFOSKX-CqxNgk27.js +89 -0
  57. package/assets/katex-HP8lGamR.js +257 -0
  58. package/assets/layout-CN3kt_jt.js +1 -0
  59. package/assets/linear-nvkaQ6BA.js +1 -0
  60. package/assets/{list-DhK0McVn.js → list-CrIU2eZz.js} +2 -2
  61. package/assets/login-CX9xVjCn.js +1 -0
  62. package/assets/manifest.json +834 -58
  63. package/assets/media-manager-BfYftLIH.js +6 -0
  64. package/assets/mermaid.core-C0V1NaVJ.js +310 -0
  65. package/assets/mindmap-definition-YA3MSWOX-Bh7_d9f1.js +96 -0
  66. package/assets/module-DPEvnad3.js +1 -0
  67. package/assets/notification-Dqpt5AIZ.js +20 -0
  68. package/assets/ordinal-Cboi1Yqb.js +1 -0
  69. package/assets/pegDiagram-XKGWAZYB-DMnjm6Z1.js +1 -0
  70. package/assets/pieDiagram-E7YTZNPT-D-RWCgYb.js +39 -0
  71. package/assets/quadrantDiagram-AXDQQJYC-BNBGP7RM.js +7 -0
  72. package/assets/railroadDiagram-O6MQD6OU-BIjVPT01.js +1 -0
  73. package/assets/register-CF4KQ97e.js +1 -0
  74. package/assets/requirementDiagram-BXWQKSXE-VPrtGdjM.js +84 -0
  75. package/assets/ru-CFJ4fysR.js +1 -0
  76. package/assets/sankeyDiagram-P5KCCOFB-ct9mLF3U.js +40 -0
  77. package/assets/sequenceDiagram-WJ2MYXX4-TC06Mbmw.js +162 -0
  78. package/assets/sizeCapture-INFHLROL-BMMwriCf.js +1 -0
  79. package/assets/stateDiagram-D77RDMKH-DtwYRPCX.js +1 -0
  80. package/assets/stateDiagram-v2-MP3YSRHH-C1yB0O5U.js +1 -0
  81. package/assets/swimlanes-42K2YHIH-DE3hRbbr.js +1 -0
  82. package/assets/swimlanesDiagram-VR7AAH4N-Ce4l1sQD.js +8 -0
  83. package/assets/timeline-definition-24CTP7MA-N7s_hfjj.js +120 -0
  84. package/assets/toNumber-DZb-r9sy.js +1 -0
  85. package/assets/use-appearance-DYTIKpT2.js +1 -0
  86. package/assets/user-filters-list-fR0YNJ3l.js +1 -0
  87. package/assets/vennDiagram-4TSXK5OY-Dj5bFQkR.js +34 -0
  88. package/assets/wardleyDiagram-VM6X3IG4-83ax25Z6.js +78 -0
  89. package/assets/welcome-v11MTsRK.js +1 -0
  90. package/assets/with-app-layout-CH6RIib1.js +31 -0
  91. package/assets/xychartDiagram-S5SC5T6Z-CW9Br5Xp.js +7 -0
  92. package/controllers/access-rights/permissionOptions.d.ts +2 -0
  93. package/controllers/access-rights/permissionOptions.js +23 -0
  94. package/controllers/add.js +2 -2
  95. package/controllers/addGroup.js +15 -6
  96. package/controllers/ai/AiAgentController.d.ts +66 -0
  97. package/controllers/ai/AiAgentController.js +435 -0
  98. package/controllers/ai/AiAssistantController.d.ts +0 -4
  99. package/controllers/ai/AiAssistantController.js +4 -78
  100. package/controllers/catalog/FrontendCatalogAdapter.js +7 -8
  101. package/controllers/docs/index.d.ts +22 -0
  102. package/controllers/docs/index.js +126 -0
  103. package/controllers/edit.js +2 -2
  104. package/controllers/editGroup.js +15 -6
  105. package/controllers/filter-fields/savedFilters.js +1 -1
  106. package/controllers/history-actions/HistoryController.js +7 -7
  107. package/controllers/inlineUpdate.js +10 -3
  108. package/controllers/list.js +1 -1
  109. package/controllers/login.js +1 -1
  110. package/controllers/notifications/NotificationController.js +14 -8
  111. package/controllers/view.js +3 -3
  112. package/controllers/widgets/filterInfo.js +1 -1
  113. package/helpers/accessRightsHelper.d.ts +30 -3
  114. package/helpers/accessRightsHelper.js +140 -12
  115. package/helpers/assetVersionHelper.d.ts +14 -0
  116. package/helpers/assetVersionHelper.js +42 -0
  117. package/helpers/configHelper.js +19 -18
  118. package/helpers/controllerHelper.d.ts +1 -0
  119. package/helpers/controllerHelper.js +25 -15
  120. package/helpers/fieldsHelper.d.ts +2 -1
  121. package/helpers/fieldsHelper.js +11 -5
  122. package/helpers/inertiaActionsHelper.d.ts +1 -1
  123. package/helpers/inertiaActionsHelper.js +4 -5
  124. package/helpers/inertiaAddHelper.d.ts +1 -1
  125. package/helpers/inertiaAddHelper.js +7 -6
  126. package/helpers/inertiaGroupHelper.js +10 -2
  127. package/helpers/inertiaListHelper.d.ts +1 -1
  128. package/helpers/inertiaListHelper.js +7 -8
  129. package/helpers/inertiaMenuHelper.d.ts +7 -2
  130. package/helpers/inertiaMenuHelper.js +12 -27
  131. package/helpers/menuHelper.d.ts +11 -1
  132. package/helpers/menuHelper.js +12 -0
  133. package/helpers/modelResourceHelper.d.ts +9 -0
  134. package/helpers/modelResourceHelper.js +43 -0
  135. package/helpers/navigationAccessHelper.d.ts +13 -0
  136. package/helpers/navigationAccessHelper.js +45 -0
  137. package/index.d.ts +11 -0
  138. package/index.js +11 -0
  139. package/interfaces/adminpanelConfig.d.ts +160 -2
  140. package/interfaces/queryCriteria.d.ts +13 -0
  141. package/interfaces/types.d.ts +169 -0
  142. package/lib/Adminizer.d.ts +8 -0
  143. package/lib/Adminizer.js +29 -2
  144. package/lib/DataAccessor.d.ts +100 -3
  145. package/lib/DataAccessor.js +454 -245
  146. package/lib/access-graph/AccessGraphResolver.d.ts +136 -0
  147. package/lib/access-graph/AccessGraphResolver.js +605 -0
  148. package/lib/access-graph/RecordAccessCache.d.ts +23 -0
  149. package/lib/access-graph/RecordAccessCache.js +30 -0
  150. package/lib/access-graph/RecordAccessResolver.d.ts +83 -0
  151. package/lib/access-graph/RecordAccessResolver.js +295 -0
  152. package/lib/access-graph/shared.d.ts +60 -0
  153. package/lib/access-graph/shared.js +121 -0
  154. package/lib/admin-links/AdminLinkHandler.d.ts +72 -0
  155. package/lib/admin-links/AdminLinkHandler.js +183 -0
  156. package/lib/ai-assistant/AbstractAiConversationHistoryService.d.ts +39 -0
  157. package/lib/ai-assistant/AbstractAiConversationHistoryService.js +98 -0
  158. package/lib/ai-assistant/AbstractAiModelService.d.ts +139 -2
  159. package/lib/ai-assistant/AbstractAiModelService.js +154 -1
  160. package/lib/ai-assistant/AiAgentRunManager.d.ts +35 -0
  161. package/lib/ai-assistant/AiAgentRunManager.js +109 -0
  162. package/lib/ai-assistant/AiAssistantAgentSkillHandler.d.ts +61 -0
  163. package/lib/ai-assistant/AiAssistantAgentSkillHandler.js +90 -0
  164. package/lib/ai-assistant/AiAssistantHandler.d.ts +1 -0
  165. package/lib/ai-assistant/AiAssistantHandler.js +4 -0
  166. package/lib/ai-assistant/AiAssistantUiMethodHandler.d.ts +60 -0
  167. package/lib/ai-assistant/AiAssistantUiMethodHandler.js +144 -0
  168. package/lib/ai-assistant/builtinAgentSkills.d.ts +9 -0
  169. package/lib/ai-assistant/builtinAgentSkills.js +259 -0
  170. package/lib/ai-assistant/jsonSafe.d.ts +13 -0
  171. package/lib/ai-assistant/jsonSafe.js +81 -0
  172. package/lib/app-manager/AdminizerApp.d.ts +31 -2
  173. package/lib/app-manager/AppManager.js +43 -42
  174. package/lib/app-manager/ConfigLayerHandler.js +4 -0
  175. package/lib/docs/AbstractDocumentation.d.ts +100 -0
  176. package/lib/docs/AbstractDocumentation.js +108 -0
  177. package/lib/docs/DocumentationHandler.d.ts +100 -0
  178. package/lib/docs/DocumentationHandler.js +198 -0
  179. package/lib/docs/FileDocumentation.d.ts +47 -0
  180. package/lib/docs/FileDocumentation.js +189 -0
  181. package/lib/docs/docsAgentSkills.d.ts +10 -0
  182. package/lib/docs/docsAgentSkills.js +93 -0
  183. package/lib/filters/FilterService.js +4 -3
  184. package/lib/history-actions/AbstractHistoryAdapter.d.ts +26 -1
  185. package/lib/history-actions/AbstractHistoryAdapter.js +155 -25
  186. package/lib/media-manager/helpers/MediaManagerHelper.js +9 -7
  187. package/lib/model/AbstractModel.d.ts +48 -0
  188. package/lib/model/AbstractModel.js +14 -4
  189. package/lib/model/ModelHandler.d.ts +50 -2
  190. package/lib/model/ModelHandler.js +117 -8
  191. package/lib/model/adapter/sequelize.d.ts +43 -3
  192. package/lib/model/adapter/sequelize.js +317 -138
  193. package/lib/model/adapter/typeorm.d.ts +16 -0
  194. package/lib/model/adapter/typeorm.js +136 -9
  195. package/lib/notifications/GeneralNotificationService.js +6 -5
  196. package/lib/notifications/SystemNotificationService.js +1 -1
  197. package/lib/ui-i18n/uiTranslationKeys.d.ts +2 -1
  198. package/lib/ui-i18n/uiTranslationKeys.js +42 -1
  199. package/lib/widgets/widgetHandler.js +10 -6
  200. package/models/Group.d.ts +2 -1
  201. package/package.json +35 -23
  202. package/policies/authPolicies.js +6 -6
  203. package/policies/permissionResolvers.d.ts +1 -0
  204. package/policies/permissionResolvers.js +3 -0
  205. package/system/Router.js +21 -3
  206. package/system/bindAccessRights.js +13 -3
  207. package/system/bindDocs.d.ts +13 -0
  208. package/system/bindDocs.js +77 -0
  209. package/system/bindInertia.js +30 -7
  210. package/system/buildInternalModelAccess.d.ts +8 -0
  211. package/system/buildInternalModelAccess.js +36 -4
  212. package/system/defaults.js +7 -1
  213. package/system/validateSystemModels.js +29 -9
  214. package/assets/add-DED_wpga.js +0 -1
  215. package/assets/add-group-CL80RdYb.js +0 -1
  216. package/assets/add-user-BRmwvQB8.js +0 -1
  217. package/assets/app-DNXiApCk.css +0 -1
  218. package/assets/app-layout-86yxiZOy.js +0 -1
  219. package/assets/app.js +0 -138
  220. package/assets/dashboard-ZP6K2s_8.js +0 -3
  221. package/assets/debounce-BMmXVQ06.js +0 -1
  222. package/assets/history-M61Bcv02.js +0 -1
  223. package/assets/init-user-8aUohVDq.js +0 -1
  224. package/assets/login-CO4laf6m.js +0 -1
  225. package/assets/media-manager-BDbpSSjH.js +0 -6
  226. package/assets/module-DfBneaUl.js +0 -1
  227. package/assets/notification-CVnQf3ef.js +0 -20
  228. package/assets/register-DBYIMXGQ.js +0 -1
  229. package/assets/ru-rYKdQbWF.js +0 -1
  230. package/assets/use-appearance-_EbbjHTh.js +0 -1
  231. package/assets/user-filters-list-DXhGLFTn.js +0 -1
  232. package/assets/welcome-CLxvXkcg.js +0 -1
@@ -1,22 +1,16 @@
1
- let Sequelize;
2
- let Op;
3
- let HasMany;
4
1
  import { AbstractAdapter, AbstractModel } from "../AbstractModel.js";
5
- try {
6
- const sequelize = require('sequelize');
7
- Sequelize = sequelize.Sequelize;
8
- Op = sequelize.Op;
9
- HasMany = sequelize.HasMany;
10
- }
11
- catch (error) {
12
- const stub = new Proxy({}, {
13
- get: () => () => {
14
- throw new Error('Sequelize is not installed. Run: npm install sequelize');
15
- }
16
- });
17
- Sequelize = stub;
18
- Op = stub;
19
- HasMany = stub;
2
+ import { Adminizer } from "../../Adminizer.js";
3
+ let _Sequelize = null;
4
+ let _Op = null;
5
+ let _HasMany = null;
6
+ async function ensureSequelize() {
7
+ if (_Sequelize)
8
+ return _Sequelize;
9
+ const sequelize = await import('sequelize');
10
+ _Sequelize = sequelize.Sequelize;
11
+ _Op = sequelize.Op;
12
+ _HasMany = sequelize.HasMany;
13
+ return _Sequelize;
20
14
  }
21
15
  function resolveType(type) {
22
16
  const sqlType = typeof type.toString === "function"
@@ -112,15 +106,129 @@ function capitalize(str) {
112
106
  }
113
107
  export class SequelizeModel extends AbstractModel {
114
108
  model;
109
+ _init = false;
110
+ async _ensure() {
111
+ if (!this._init) {
112
+ await ensureSequelize();
113
+ this._init = true;
114
+ }
115
+ }
115
116
  constructor(modelName, model) {
116
117
  super(modelName, mapSequelizeToAdminizerAttributes(model), model.primaryKeyAttribute, model.name);
117
118
  this.model = model;
118
119
  }
120
+ /**
121
+ * accessGraph pushdown: compiles the level chain into one nested-subquery literal for
122
+ * an `{in: ...}` criteria, so the DB planner optimizes the whole walk instead of the
123
+ * app materializing id lists. Returns undefined — falling back to materialization —
124
+ * when any level lives on another adapter or connection, an attribute is unknown, or a
125
+ * level carries a defaultScope we cannot reproduce.
126
+ *
127
+ * The materialized path queries through `model.find()`, so it inherits paranoid and
128
+ * defaultScope filtering; this raw SQL must match it exactly or the same config would
129
+ * yield two different record sets depending on `pushdown`.
130
+ *
131
+ * Every bail-out reports through `decline` before returning: a fallback is invisible in the
132
+ * result (the records are the same, only slower), so the reason is the only thing that tells
133
+ * an operator why `pushdown: true` changed nothing.
134
+ */
135
+ async compileGraphInSubquery(levels, decline) {
136
+ await this._ensure();
137
+ /** Reports the reason and bails out of the whole compilation. */
138
+ const no = (reason) => {
139
+ decline?.(reason);
140
+ return undefined;
141
+ };
142
+ const targets = [];
143
+ for (const level of levels) {
144
+ if (!(level.model instanceof SequelizeModel)) {
145
+ return no(`"${level.model.modelname}" is not a Sequelize model`);
146
+ }
147
+ const target = level.model.model;
148
+ if (!target.sequelize || target.sequelize !== this.model.sequelize) {
149
+ return no(`"${level.model.modelname}" lives on another Sequelize connection`);
150
+ }
151
+ targets.push(target);
152
+ }
153
+ const queryGenerator = this.model.sequelize.getQueryInterface().queryGenerator;
154
+ // Adminizer attribute (association alias or plain column) → quoted physical column
155
+ const columnFor = (levelModel, target, attributeName) => {
156
+ const attribute = levelModel.attributes?.[attributeName];
157
+ const rawName = (attribute?.type === "association" && attribute.via) ? attribute.via : attributeName;
158
+ const raw = target.rawAttributes?.[rawName];
159
+ if (!raw) {
160
+ return undefined;
161
+ }
162
+ return queryGenerator.quoteIdentifier(raw.field ?? rawName);
163
+ };
164
+ let sql;
165
+ for (let i = 0; i < levels.length; i++) {
166
+ const level = levels[i];
167
+ const target = targets[i];
168
+ const table = queryGenerator.quoteTable(target.getTableName());
169
+ const selectColumn = columnFor(level.model, target, level.select);
170
+ if (!selectColumn) {
171
+ return no(`"${level.model.modelname}.${level.select}" has no column to select`);
172
+ }
173
+ const modelOptions = target.options ?? {};
174
+ // A defaultScope silently narrows every findAll on the model; reproducing an
175
+ // arbitrary one in raw SQL is not possible, so this chain is not pushed down.
176
+ if (modelOptions.defaultScope && Object.keys(modelOptions.defaultScope).length) {
177
+ return no(`"${level.model.modelname}" declares a defaultScope, which raw SQL cannot reproduce `
178
+ + `(pushing down would return a different record set than the materialized walk)`);
179
+ }
180
+ // Conditions that constrain the level (parent link / literal ids) versus guards that
181
+ // only exclude rows (paranoid). A level constrained by guards alone is NOT narrowed,
182
+ // so the emptiness check below must look at linkConditions only.
183
+ const linkConditions = [];
184
+ const guardConditions = [];
185
+ if (modelOptions.paranoid) {
186
+ const deletedAtAttribute = target._timestampAttributes?.deletedAt
187
+ ?? modelOptions.deletedAt
188
+ ?? "deletedAt";
189
+ const rawDeletedAt = target.rawAttributes?.[deletedAtAttribute];
190
+ if (!rawDeletedAt) {
191
+ return no(`"${level.model.modelname}" is paranoid but its "${deletedAtAttribute}" column cannot be `
192
+ + `resolved, so soft-deleted rows could not be excluded`);
193
+ }
194
+ const deletedAtColumn = queryGenerator.quoteIdentifier(rawDeletedAt.field ?? deletedAtAttribute);
195
+ guardConditions.push(`${table}.${deletedAtColumn} IS NULL`);
196
+ }
197
+ if (level.parentAttribute) {
198
+ if (!sql) {
199
+ return no(`"${level.model.modelname}" links to an inner level that produced no subquery`);
200
+ }
201
+ const parentColumn = columnFor(level.model, target, level.parentAttribute);
202
+ if (!parentColumn) {
203
+ return no(`"${level.model.modelname}.${level.parentAttribute}" has no column to link on`);
204
+ }
205
+ linkConditions.push(`${table}.${parentColumn} IN ${sql}`);
206
+ }
207
+ for (const condition of level.conditions ?? []) {
208
+ const column = columnFor(level.model, target, condition.attribute);
209
+ if (!column) {
210
+ return no(`"${level.model.modelname}.${condition.attribute}" has no column to filter on`);
211
+ }
212
+ if (!condition.values.length) {
213
+ linkConditions.push("1 = 0");
214
+ continue;
215
+ }
216
+ const escaped = condition.values.map((value) => queryGenerator.escape(value)).join(", ");
217
+ linkConditions.push(`${table}.${column} IN (${escaped})`);
218
+ }
219
+ if (!linkConditions.length) {
220
+ return no(`"${level.model.modelname}" has nothing to narrow it — the level would select every row`);
221
+ }
222
+ const conditions = [...linkConditions, ...guardConditions];
223
+ sql = `(SELECT ${table}.${selectColumn} FROM ${table} WHERE ${conditions.join(" AND ")})`;
224
+ }
225
+ return sql ? _Sequelize.literal(sql) : undefined;
226
+ }
119
227
  _buildJsonContainsCondition(targetKey, item) {
120
228
  const pattern = `%${JSON.stringify(item).replace(/[%_]/g, "\\$&")}%`;
121
229
  const attribute = this.model.rawAttributes?.[targetKey];
122
230
  const columnName = typeof attribute?.field === "string" ? attribute.field : targetKey;
123
- return Sequelize.where(Sequelize.cast(Sequelize.col(`${this.model.name}.${columnName}`), "TEXT"), { [Op.like]: pattern });
231
+ return _Sequelize.where(_Sequelize.cast(_Sequelize.col(`${this.model.name}.${columnName}`), "TEXT"), { [_Op.like]: pattern });
124
232
  }
125
233
  _convertCriteriaToSequelize(criteria) {
126
234
  // Special handling for Sequelize.literal() at the top level
@@ -155,24 +263,24 @@ export class SequelizeModel extends AbstractModel {
155
263
  }
156
264
  // Special handling for null values (IS NULL condition)
157
265
  if (value === null) {
158
- result[targetKey] = { [Op.is]: null };
266
+ result[targetKey] = { [_Op.is]: null };
159
267
  continue;
160
268
  }
161
269
  // 🔹 Handle Sequelize Op operators (already in Sequelize format)
162
- if (key === Op.and || key === 'and') {
163
- result[Op.and] = Array.isArray(value)
270
+ if (key === _Op.and || key === 'and') {
271
+ result[_Op.and] = Array.isArray(value)
164
272
  ? value.map((item) => this._convertCriteriaToSequelize(item))
165
273
  : [];
166
274
  continue;
167
275
  }
168
- if (key === Op.or || key === 'or') {
169
- result[Op.or] = Array.isArray(value)
276
+ if (key === _Op.or || key === 'or') {
277
+ result[_Op.or] = Array.isArray(value)
170
278
  ? value.map((item) => this._convertCriteriaToSequelize(item))
171
279
  : [];
172
280
  continue;
173
281
  }
174
- if (key === Op.not || key === 'not' || key === '$not') {
175
- result[Op.not] = this._convertCriteriaToSequelize(value);
282
+ if (key === _Op.not || key === 'not' || key === '$not') {
283
+ result[_Op.not] = this._convertCriteriaToSequelize(value);
176
284
  continue;
177
285
  }
178
286
  // Skip Symbol keys that are not handled above (should not happen, but safety check)
@@ -181,7 +289,7 @@ export class SequelizeModel extends AbstractModel {
181
289
  }
182
290
  if (Array.isArray(value)) {
183
291
  // ✅ Array processing - use the IN operator
184
- result[targetKey] = { [Op.in]: value };
292
+ result[targetKey] = { [_Op.in]: value };
185
293
  }
186
294
  else if (typeof value === "object" && !Array.isArray(value)) {
187
295
  // Check if value already contains Sequelize Op operators
@@ -191,10 +299,10 @@ export class SequelizeModel extends AbstractModel {
191
299
  // Helper function to check if a symbol/op is a Sequelize operator
192
300
  const isSequelizeOp = (op) => {
193
301
  // Check direct Symbol equality first
194
- if (op === Op.gt || op === Op.gte || op === Op.lt || op === Op.lte ||
195
- op === Op.eq || op === Op.ne || op === Op.in || op === Op.notIn ||
196
- op === Op.like || op === Op.iLike || op === Op.between ||
197
- op === Op.startsWith || op === Op.endsWith || op === Op.is || op === Op.not) {
302
+ if (op === _Op.gt || op === _Op.gte || op === _Op.lt || op === _Op.lte ||
303
+ op === _Op.eq || op === _Op.ne || op === _Op.in || op === _Op.notIn ||
304
+ op === _Op.like || op === _Op.iLike || op === _Op.between ||
305
+ op === _Op.startsWith || op === _Op.endsWith || op === _Op.is || op === _Op.not) {
198
306
  return true;
199
307
  }
200
308
  // Fallback: check by Symbol description (handles different Symbol instances)
@@ -220,26 +328,33 @@ export class SequelizeModel extends AbstractModel {
220
328
  }
221
329
  // Handle operator objects like {contains: 'val'} from Adminizer's criteria format
222
330
  for (const [op, val] of Object.entries(value)) {
223
- if (val === undefined || val === null)
331
+ if (val === undefined || val === null) {
332
+ // Access scoping emits `in` lists, so an absent operand must fail
333
+ // closed (match nothing) rather than silently widen the query.
334
+ // Other operators keep the historical skip: filter builders rely on it.
335
+ if (op === "in") {
336
+ result[targetKey] = { [_Op.in]: [] };
337
+ }
224
338
  continue;
339
+ }
225
340
  switch (op) {
226
341
  case "eq":
227
- result[targetKey] = { [Op.eq]: val };
342
+ result[targetKey] = { [_Op.eq]: val };
228
343
  break;
229
344
  case "ne":
230
- result[targetKey] = { [Op.ne]: val };
345
+ result[targetKey] = { [_Op.ne]: val };
231
346
  break;
232
347
  case "gt":
233
- result[targetKey] = { [Op.gt]: val };
348
+ result[targetKey] = { [_Op.gt]: val };
234
349
  break;
235
350
  case "gte":
236
- result[targetKey] = { [Op.gte]: val };
351
+ result[targetKey] = { [_Op.gte]: val };
237
352
  break;
238
353
  case "lt":
239
- result[targetKey] = { [Op.lt]: val };
354
+ result[targetKey] = { [_Op.lt]: val };
240
355
  break;
241
356
  case "lte":
242
- result[targetKey] = { [Op.lte]: val };
357
+ result[targetKey] = { [_Op.lte]: val };
243
358
  break;
244
359
  case "contains":
245
360
  // Check if field is a date/datetime type
@@ -262,83 +377,88 @@ export class SequelizeModel extends AbstractModel {
262
377
  }
263
378
  else {
264
379
  // Valid date - use exact match
265
- result[targetKey] = { [Op.eq]: dateValue };
380
+ result[targetKey] = { [_Op.eq]: dateValue };
266
381
  }
267
382
  }
268
383
  else {
269
384
  // For time fields and non-date fields - use Op.like with wildcards
270
- result[targetKey] = { [Op.like]: `%${val}%` };
385
+ result[targetKey] = { [_Op.like]: `%${val}%` };
271
386
  }
272
387
  break;
273
388
  case "startsWith":
274
- result[targetKey] = { [Op.startsWith]: val };
389
+ result[targetKey] = { [_Op.startsWith]: val };
275
390
  break;
276
391
  case "endsWith":
277
- result[targetKey] = { [Op.endsWith]: val };
392
+ result[targetKey] = { [_Op.endsWith]: val };
278
393
  break;
279
394
  case ">":
280
- result[targetKey] = { [Op.gt]: val };
395
+ result[targetKey] = { [_Op.gt]: val };
281
396
  break;
282
397
  case ">=":
283
- result[targetKey] = { [Op.gte]: val };
398
+ result[targetKey] = { [_Op.gte]: val };
284
399
  break;
285
400
  case "<":
286
- result[targetKey] = { [Op.lt]: val };
401
+ result[targetKey] = { [_Op.lt]: val };
287
402
  break;
288
403
  case "<=":
289
- result[targetKey] = { [Op.lte]: val };
404
+ result[targetKey] = { [_Op.lte]: val };
290
405
  break;
291
406
  case "!=":
292
- result[targetKey] = { [Op.ne]: val };
407
+ result[targetKey] = { [_Op.ne]: val };
293
408
  break;
294
409
  case "in":
295
- result[targetKey] = { [Op.in]: val };
410
+ result[targetKey] = { [_Op.in]: val };
296
411
  break;
297
412
  case "notIn":
298
- result[targetKey] = { [Op.notIn]: val };
413
+ result[targetKey] = { [_Op.notIn]: val };
299
414
  break;
300
415
  case "nin":
301
- result[targetKey] = { [Op.notIn]: val };
416
+ result[targetKey] = { [_Op.notIn]: val };
302
417
  break;
303
418
  case "between":
304
- result[targetKey] = { [Op.between]: val };
419
+ result[targetKey] = { [_Op.between]: val };
305
420
  break;
421
+ case "intersects":
422
+ // Emitted only by the (unsupported) collection form of
423
+ // `userAccessRelation` — fail loudly instead of the default
424
+ // Op.eq fallback silently producing a wrong query.
425
+ throw new Error(`Sequelize adapter does not support the "intersects" operator (field "${String(targetKey)}")`);
306
426
  case "isNull":
307
427
  if (val) {
308
- result[targetKey] = { [Op.is]: null };
428
+ result[targetKey] = { [_Op.is]: null };
309
429
  }
310
430
  break;
311
431
  case "isNotNull":
312
432
  if (val) {
313
- result[targetKey] = { [Op.not]: null };
433
+ result[targetKey] = { [_Op.not]: null };
314
434
  }
315
435
  break;
316
436
  case "regex":
317
- result[targetKey] = { [Op.regexp]: val };
437
+ result[targetKey] = { [_Op.regexp]: val };
318
438
  break;
319
439
  case "jsonContains": {
320
440
  const values = Array.isArray(val) ? val : [val];
321
441
  const clauses = values.map((item) => this._buildJsonContainsCondition(targetKey, item));
322
- result[Op.and] = [
323
- ...(result[Op.and] ?? []),
442
+ result[_Op.and] = [
443
+ ...(result[_Op.and] ?? []),
324
444
  ...clauses
325
445
  ];
326
446
  break;
327
447
  }
328
448
  case "$is":
329
- result[targetKey] = { [Op.is]: val };
449
+ result[targetKey] = { [_Op.is]: val };
330
450
  break;
331
451
  case "$not":
332
- result[targetKey] = { [Op.not]: val };
452
+ result[targetKey] = { [_Op.not]: val };
333
453
  break;
334
454
  case "$ne":
335
- result[targetKey] = { [Op.ne]: val };
455
+ result[targetKey] = { [_Op.ne]: val };
336
456
  break;
337
457
  case "not":
338
- result[targetKey] = { [Op.not]: val };
458
+ result[targetKey] = { [_Op.not]: val };
339
459
  break;
340
460
  default:
341
- result[targetKey] = { [Op.eq]: val };
461
+ result[targetKey] = { [_Op.eq]: val };
342
462
  }
343
463
  }
344
464
  }
@@ -355,7 +475,7 @@ export class SequelizeModel extends AbstractModel {
355
475
  // 'sort' as boolean/object = model field — goes to WHERE
356
476
  const criteriaSortValue = 'sort' in criteria ? criteria.sort : undefined;
357
477
  const isOrderingSort = typeof criteriaSortValue === 'string';
358
- const { where: nestedWhere, skip, limit, sort: criteriaSort, select, populate, ...rest } = criteria;
478
+ const { where: nestedWhere, skip, limit, sort: criteriaSort, select, populate, populateOn: _populateOn, ...rest } = criteria;
359
479
  // If 'sort' is a model field (not an ordering string), put it back
360
480
  if (!isOrderingSort && criteriaSortValue !== undefined) {
361
481
  rest.sort = criteriaSortValue;
@@ -421,6 +541,7 @@ export class SequelizeModel extends AbstractModel {
421
541
  }
422
542
  // --- CREATE ---
423
543
  async _create(data) {
544
+ await this._ensure();
424
545
  // console.clear()
425
546
  const assocNames = Object.keys(this.model.associations);
426
547
  const plainData = {};
@@ -492,16 +613,19 @@ export class SequelizeModel extends AbstractModel {
492
613
  }
493
614
  // --- FIND ONE ---
494
615
  async _findOne(criteria) {
616
+ await this._ensure();
495
617
  // console.debug(">> _findOne: input criteria:", criteria);
496
618
  const { where, attributes } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
497
619
  const includes = criteria.populate
498
620
  ? this._buildCriteriaIncludes(criteria.populate)
499
621
  : this._buildIncludes();
622
+ this._applyPopulateOn(includes, criteria.populateOn);
623
+ const selectedAttributes = this._ensureForeignKeysSelected(attributes, criteria.populateOn);
500
624
  // console.debug(">> _findOne: converted where:", where);
501
625
  // console.debug(">> _findOne: includes:", includes);
502
626
  let instance = null;
503
627
  try {
504
- instance = await this.model.findOne({ where, attributes, include: includes });
628
+ instance = await this.model.findOne({ where, attributes: selectedAttributes, include: includes });
505
629
  // console.debug(">> _findOne: raw instance:", instance ? instance.toJSON() : null);
506
630
  }
507
631
  catch (err) {
@@ -513,11 +637,13 @@ export class SequelizeModel extends AbstractModel {
513
637
  return null;
514
638
  }
515
639
  const plain = instance.get({ plain: true });
640
+ this._collapsePopulateOn([plain], criteria.populateOn);
516
641
  // console.debug(">> _findOne: plain result:", plain);
517
642
  return plain;
518
643
  }
519
644
  // --- FIND MANY ---
520
645
  async _find(criteria = {}) {
646
+ await this._ensure();
521
647
  const assocNames = Object.keys(this.model.associations);
522
648
  // console.debug(">> _find: input criteria:", criteria, "options:", options);
523
649
  const { where, limit, offset, order, attributes } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
@@ -528,6 +654,8 @@ export class SequelizeModel extends AbstractModel {
528
654
  const includes = criteria.populate
529
655
  ? this._buildCriteriaIncludes(criteria.populate)
530
656
  : assocNames.map((alias) => this._buildListInclude(alias, usedRelationAliases, hasRelationPathCondition));
657
+ this._applyPopulateOn(includes, criteria.populateOn);
658
+ const selectedAttributes = this._ensureForeignKeysSelected(attributes, criteria.populateOn);
531
659
  // console.debug(">> _find: where, limit, offset, order, includes:", {
532
660
  // where,
533
661
  // limit,
@@ -538,33 +666,18 @@ export class SequelizeModel extends AbstractModel {
538
666
  let instances;
539
667
  instances = await this.model.findAll({
540
668
  where,
541
- attributes,
669
+ attributes: selectedAttributes,
542
670
  limit,
543
671
  offset,
544
672
  order,
545
673
  include: includes,
546
674
  subQuery: hasRelationPathCondition ? false : undefined
547
675
  });
548
- for (const inst of instances) {
549
- //For each association, we call getxxx () once again
550
- for (const alias of assocNames) {
551
- // @ts-ignore accessors is present
552
- const getAccessor = this.model.associations[alias].accessors.get;
553
- if (typeof inst[getAccessor] === "function") {
554
- try {
555
- const related = await inst[getAccessor]();
556
- const mapped = Array.isArray(related)
557
- ? related.map((r) => r.toJSON())
558
- : related?.toJSON();
559
- // console.debug(`---- get${alias}():`, mapped);
560
- }
561
- catch (e) {
562
- // console.error(`!! error when calling ${getAccessor}():`, e);
563
- }
564
- }
565
- }
566
- }
676
+ // Associations come from the `include` above: sequelize getters never write into
677
+ // dataValues, so calling them here would only cost one query per association per
678
+ // row and change nothing in the result.
567
679
  const plain = instances.map(i => i.get({ plain: true }));
680
+ this._collapsePopulateOn(plain, criteria.populateOn);
568
681
  // console.debug(">> _find: plain results:", plain);
569
682
  return plain;
570
683
  }
@@ -573,6 +686,7 @@ export class SequelizeModel extends AbstractModel {
573
686
  * Bypasses Adminizer criteria conversion for direct Sequelize usage
574
687
  */
575
688
  async findWithRawWhere(where, options = {}) {
689
+ await this._ensure();
576
690
  const assocNames = Object.keys(this.model.associations);
577
691
  const includes = options.populate !== false
578
692
  ? assocNames.map(a => ({ association: a }))
@@ -590,6 +704,7 @@ export class SequelizeModel extends AbstractModel {
590
704
  * Public method for count with raw SQL where clauses
591
705
  */
592
706
  async countWithRawWhere(where) {
707
+ await this._ensure();
593
708
  const assocNames = Object.keys(this.model.associations);
594
709
  const include = assocNames.map((association) => ({ association }));
595
710
  return await this.model.count({
@@ -601,6 +716,7 @@ export class SequelizeModel extends AbstractModel {
601
716
  }
602
717
  // --- UPDATE ONE ---
603
718
  async _updateOne(criteria, data) {
719
+ await this._ensure();
604
720
  const { where } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
605
721
  const record = await this.model.findOne({ where });
606
722
  if (!record)
@@ -632,6 +748,7 @@ export class SequelizeModel extends AbstractModel {
632
748
  }
633
749
  // --- UPDATE MANY ---
634
750
  async _update(criteria, data) {
751
+ await this._ensure();
635
752
  const { where } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
636
753
  const assocNames = Object.keys(this.model.associations);
637
754
  const plainData = {};
@@ -664,74 +781,68 @@ export class SequelizeModel extends AbstractModel {
664
781
  });
665
782
  return reloaded.map((r) => r.get({ plain: true }));
666
783
  }
784
+ /**
785
+ * Application-level cascade for the records `ids` identifies, one statement per
786
+ * association instead of a getter plus a delete per related row:
787
+ *
788
+ * - `belongsTo` — the parent is never touched; deleting a task must not delete its project;
789
+ * - `belongsToMany` — only the link rows go, never the far side: those rows are shared
790
+ * (deleting one post must not delete a tag another post still uses);
791
+ * - `hasOne` / `hasMany` — the children are deleted, which is the actual cascade.
792
+ *
793
+ * Row-level destroy hooks are preserved for targets that declare them.
794
+ */
795
+ async _cascadeDelete(ids) {
796
+ if (!ids.length) {
797
+ return;
798
+ }
799
+ for (const alias of Object.keys(this.model.associations)) {
800
+ const assoc = this.model.associations[alias];
801
+ if (assoc.associationType === "BelongsTo") {
802
+ continue;
803
+ }
804
+ const foreignKey = typeof assoc.foreignKey === "string" ? assoc.foreignKey : undefined;
805
+ if (!foreignKey) {
806
+ continue;
807
+ }
808
+ // Many-to-many: the link rows live in the through model, the targets are shared
809
+ const target = assoc.associationType === "BelongsToMany"
810
+ ? assoc.through?.model
811
+ : assoc.target;
812
+ if (!target?.destroy) {
813
+ continue;
814
+ }
815
+ try {
816
+ const hasRowHooks = typeof target.hasHook === "function"
817
+ && (target.hasHook("beforeDestroy") || target.hasHook("afterDestroy"));
818
+ await target.destroy({
819
+ where: { [foreignKey]: { [_Op.in]: ids } },
820
+ individualHooks: hasRowHooks,
821
+ });
822
+ }
823
+ catch (e) {
824
+ Adminizer.log.error(`Failed to cascade the deletion of "${this.modelname}.${alias}"`, e);
825
+ }
826
+ }
827
+ }
667
828
  // --- DESTROY ONE ---
668
829
  async _destroyOne(criteria) {
830
+ await this._ensure();
669
831
  const { where } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
670
832
  const record = await this.model.findOne({ where });
671
833
  if (!record)
672
834
  return null;
673
- const assocNames = Object.keys(this.model.associations);
674
- for (const alias of assocNames) {
675
- const assoc = this.model.associations[alias];
676
- // @ts-ignore: accessors should exist
677
- const getAccessor = assoc.accessors?.get;
678
- if (typeof record[getAccessor] === "function") {
679
- try {
680
- const related = await record[getAccessor]();
681
- // 🧹Deleting related posts
682
- if (Array.isArray(related)) {
683
- for (const r of related) {
684
- if (typeof r.destroy === "function") {
685
- await r.destroy();
686
- }
687
- }
688
- }
689
- else if (related && typeof related.destroy === "function") {
690
- await related.destroy();
691
- }
692
- }
693
- catch (e) {
694
- // console.warn(`Failed to fetch/delete relation "${alias}":`, e);
695
- }
696
- }
697
- }
835
+ await this._cascadeDelete([record.get(this.model.primaryKeyAttribute)]);
698
836
  const raw = record.get({ plain: true });
699
837
  await record.destroy();
700
838
  return raw;
701
839
  }
702
840
  // --- DESTROY MANY ---
703
841
  async _destroy(criteria) {
842
+ await this._ensure();
704
843
  const { where } = this._convertAdminizerCriteriaToSequelizeOptions(criteria);
705
844
  const records = await this.model.findAll({ where });
706
- const assocNames = Object.keys(this.model.associations);
707
- for (const record of records) {
708
- for (const alias of assocNames) {
709
- const assoc = this.model.associations[alias];
710
- // 🛑 We don’t touch parental ties
711
- if (assoc.associationType === "BelongsTo")
712
- continue;
713
- // @ts-ignore accessor exists
714
- const getAccessor = assoc.accessors?.get;
715
- if (typeof record[getAccessor] === "function") {
716
- try {
717
- const related = await record[getAccessor]();
718
- if (Array.isArray(related)) {
719
- for (const rel of related) {
720
- if (typeof rel.destroy === "function") {
721
- await rel.destroy();
722
- }
723
- }
724
- }
725
- else if (related && typeof related.destroy === "function") {
726
- await related.destroy();
727
- }
728
- }
729
- catch (e) {
730
- // console.warn(`Failed to delete relation ${alias}:`, e);
731
- }
732
- }
733
- }
734
- }
845
+ await this._cascadeDelete(records.map((r) => r.get(this.model.primaryKeyAttribute)));
735
846
  const raw = records.map((r) => r.get({ plain: true }));
736
847
  await this.model.destroy({ where });
737
848
  return raw;
@@ -805,7 +916,7 @@ export class SequelizeModel extends AbstractModel {
805
916
  }
806
917
  const association = this.model.associations[alias];
807
918
  const isUsedInWhere = usedRelationAliases.has(alias);
808
- const isHasMany = association instanceof HasMany || association?.associationType === 'HasMany';
919
+ const isHasMany = association instanceof _HasMany || association?.associationType === 'HasMany';
809
920
  // When filtering by relation path ($alias.field$), extra joined hasMany associations
810
921
  // can duplicate base rows and break page size. Load such associations separately.
811
922
  if (isHasMany && !isUsedInWhere) {
@@ -816,6 +927,70 @@ export class SequelizeModel extends AbstractModel {
816
927
  _buildIncludes() {
817
928
  return Object.keys(this.model.associations).map(key => ({ association: key }));
818
929
  }
930
+ /**
931
+ * `criteria.populateOn` support: BelongsTo only — the owning row keeps its FK
932
+ * column, so a record filtered out of the JOIN can be handed back as that bare value.
933
+ */
934
+ canPushdownPopulateAccess(association) {
935
+ const assoc = this.model.associations[association];
936
+ return Boolean(assoc
937
+ && assoc.associationType === "BelongsTo"
938
+ && typeof assoc.foreignKey === "string"
939
+ && this.model.rawAttributes[assoc.foreignKey]);
940
+ }
941
+ /** ANDs `criteria.populateOn` into the matching includes' ON clause (LEFT JOIN — owning rows stay). */
942
+ _applyPopulateOn(includes, populateOn) {
943
+ if (!populateOn) {
944
+ return;
945
+ }
946
+ for (const [association, on] of Object.entries(populateOn)) {
947
+ const include = includes.find((entry) => entry.association === association);
948
+ if (!include) {
949
+ continue;
950
+ }
951
+ const condition = this._convertCriteriaToSequelize(on);
952
+ if (include.where) {
953
+ // An explicit populate-where keeps filtering the owning rows (INNER JOIN),
954
+ // now matching only records inside the access confinement.
955
+ include.where = { [_Op.and]: [include.where, condition] };
956
+ }
957
+ else {
958
+ include.where = condition;
959
+ include.required = false;
960
+ }
961
+ }
962
+ }
963
+ /** A record filtered out of a `populateOn` JOIN comes back as its bare foreign key. */
964
+ _collapsePopulateOn(records, populateOn) {
965
+ if (!populateOn) {
966
+ return;
967
+ }
968
+ for (const association of Object.keys(populateOn)) {
969
+ const foreignKey = this.model.associations[association]?.foreignKey;
970
+ if (typeof foreignKey !== "string") {
971
+ continue;
972
+ }
973
+ for (const record of records) {
974
+ if (record[association] == null && record[foreignKey] != null) {
975
+ record[association] = record[foreignKey];
976
+ }
977
+ }
978
+ }
979
+ }
980
+ /** The collapse above reads the FK columns — keep them selected under an explicit `select`. */
981
+ _ensureForeignKeysSelected(attributes, populateOn) {
982
+ if (!attributes || !populateOn) {
983
+ return attributes;
984
+ }
985
+ const result = [...attributes];
986
+ for (const association of Object.keys(populateOn)) {
987
+ const foreignKey = this.model.associations[association]?.foreignKey;
988
+ if (typeof foreignKey === "string" && !result.includes(foreignKey)) {
989
+ result.push(foreignKey);
990
+ }
991
+ }
992
+ return result;
993
+ }
819
994
  _buildAttributes(select, rawAttributes = this.model.rawAttributes) {
820
995
  if (!select) {
821
996
  return undefined;
@@ -861,10 +1036,14 @@ export class SequelizeAdapter extends AbstractAdapter {
861
1036
  sequelize;
862
1037
  Model = SequelizeModel;
863
1038
  constructor(sequelize, options = {}) {
1039
+ ensureSequelize();
864
1040
  super("sequelize", sequelize, options);
1041
+ // Note: ensureSequelize is fire-and-forget during construction
1042
+ // Methods also call this._ensure() to guarantee initialization
865
1043
  this.sequelize = sequelize;
866
1044
  }
867
1045
  get models() {
1046
+ ``;
868
1047
  return this.sequelize.models;
869
1048
  }
870
1049
  getModel(modelName) {