velocious 1.0.430 → 1.0.432

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 (831) hide show
  1. package/bin/velocious.js +48 -0
  2. package/build/application.js +229 -0
  3. package/build/authorization/ability.js +329 -0
  4. package/build/authorization/base-resource.js +143 -0
  5. package/build/background-jobs/client.js +50 -0
  6. package/build/background-jobs/cron-expression.js +277 -0
  7. package/build/background-jobs/forked-runner-child.js +86 -0
  8. package/build/background-jobs/job-record.js +13 -0
  9. package/build/background-jobs/job-registry.js +92 -0
  10. package/build/background-jobs/job-runner.js +107 -0
  11. package/build/background-jobs/job.js +77 -0
  12. package/build/background-jobs/json-socket.js +78 -0
  13. package/build/background-jobs/main.js +926 -0
  14. package/build/background-jobs/normalize-error.js +26 -0
  15. package/build/background-jobs/scheduler.js +274 -0
  16. package/build/background-jobs/socket-request.js +68 -0
  17. package/build/background-jobs/status-reporter.js +101 -0
  18. package/build/background-jobs/store.js +994 -0
  19. package/build/background-jobs/types.js +70 -0
  20. package/build/background-jobs/web/authorization.js +89 -0
  21. package/build/background-jobs/web/controller.js +280 -0
  22. package/build/background-jobs/web/index.js +57 -0
  23. package/build/background-jobs/web/path-matcher.js +74 -0
  24. package/build/background-jobs/web/registry.js +49 -0
  25. package/build/background-jobs/worker.js +683 -0
  26. package/build/beacon/client.js +330 -0
  27. package/build/beacon/in-process-broker.js +71 -0
  28. package/build/beacon/in-process-client.js +139 -0
  29. package/build/beacon/server.js +148 -0
  30. package/build/beacon/types.js +55 -0
  31. package/build/bin/velocious.js +39 -34
  32. package/build/cli/base-command.js +67 -0
  33. package/build/cli/browser-cli.js +45 -0
  34. package/build/cli/commands/background-jobs-main.js +7 -0
  35. package/build/cli/commands/background-jobs-runner.js +7 -0
  36. package/build/cli/commands/background-jobs-worker.js +7 -0
  37. package/build/cli/commands/beacon.js +7 -0
  38. package/build/cli/commands/console.js +12 -0
  39. package/build/cli/commands/db/base-command.js +82 -0
  40. package/build/cli/commands/db/create.js +64 -0
  41. package/build/cli/commands/db/drop.js +75 -0
  42. package/build/cli/commands/db/migrate.js +17 -0
  43. package/build/cli/commands/db/reset.js +22 -0
  44. package/build/cli/commands/db/rollback.js +15 -0
  45. package/build/cli/commands/db/schema/dump.js +12 -0
  46. package/build/cli/commands/db/schema/load.js +12 -0
  47. package/build/cli/commands/db/seed.js +12 -0
  48. package/build/cli/commands/db/tenants/check.js +38 -0
  49. package/build/cli/commands/db/tenants/create.js +33 -0
  50. package/build/cli/commands/db/tenants/migrate.js +49 -0
  51. package/build/cli/commands/destroy/migration.js +7 -0
  52. package/build/cli/commands/generate/base-models.js +7 -0
  53. package/build/cli/commands/generate/frontend-models.js +12 -0
  54. package/build/cli/commands/generate/migration.js +7 -0
  55. package/build/cli/commands/generate/model.js +7 -0
  56. package/build/cli/commands/init.js +11 -0
  57. package/build/cli/commands/routes.js +7 -0
  58. package/build/cli/commands/run-script.js +12 -0
  59. package/build/cli/commands/runner.js +12 -0
  60. package/build/cli/commands/server.js +7 -0
  61. package/build/cli/commands/test.js +9 -0
  62. package/build/cli/index.js +152 -0
  63. package/build/cli/tenant-database-command-helper.js +198 -0
  64. package/build/cli/use-browser-cli.js +30 -0
  65. package/build/configuration-resolver.js +65 -0
  66. package/build/configuration-types.js +429 -0
  67. package/build/configuration.js +2590 -0
  68. package/build/controller.js +421 -0
  69. package/build/current-configuration.js +31 -0
  70. package/build/current.js +80 -0
  71. package/build/database/annotations-async-hooks.js +47 -0
  72. package/build/database/annotations.js +40 -0
  73. package/build/database/drivers/base-column.js +182 -0
  74. package/build/database/drivers/base-columns-index.js +81 -0
  75. package/build/database/drivers/base-foreign-key.js +104 -0
  76. package/build/database/drivers/base-table.js +156 -0
  77. package/build/database/drivers/base.js +1609 -0
  78. package/build/database/drivers/mssql/column.js +74 -0
  79. package/build/database/drivers/mssql/columns-index.js +6 -0
  80. package/build/database/drivers/mssql/connect-connection.js +16 -0
  81. package/build/database/drivers/mssql/foreign-key.js +12 -0
  82. package/build/database/drivers/mssql/index.js +590 -0
  83. package/build/database/drivers/mssql/options.js +79 -0
  84. package/build/database/drivers/mssql/query-parser.js +6 -0
  85. package/build/database/drivers/mssql/sql/alter-table.js +4 -0
  86. package/build/database/drivers/mssql/sql/create-database.js +36 -0
  87. package/build/database/drivers/mssql/sql/create-index.js +4 -0
  88. package/build/database/drivers/mssql/sql/create-table.js +4 -0
  89. package/build/database/drivers/mssql/sql/delete.js +19 -0
  90. package/build/database/drivers/mssql/sql/drop-database.js +36 -0
  91. package/build/database/drivers/mssql/sql/drop-table.js +4 -0
  92. package/build/database/drivers/mssql/sql/insert.js +4 -0
  93. package/build/database/drivers/mssql/sql/update.js +31 -0
  94. package/build/database/drivers/mssql/sql/upsert.js +23 -0
  95. package/build/database/drivers/mssql/structure-sql.js +120 -0
  96. package/build/database/drivers/mssql/table.js +145 -0
  97. package/build/database/drivers/mysql/column.js +112 -0
  98. package/build/database/drivers/mysql/columns-index.js +22 -0
  99. package/build/database/drivers/mysql/foreign-key.js +12 -0
  100. package/build/database/drivers/mysql/index.js +473 -0
  101. package/build/database/drivers/mysql/options.js +34 -0
  102. package/build/database/drivers/mysql/query-parser.js +6 -0
  103. package/build/database/drivers/mysql/query.js +37 -0
  104. package/build/database/drivers/mysql/sql/alter-table.js +6 -0
  105. package/build/database/drivers/mysql/sql/create-database.js +39 -0
  106. package/build/database/drivers/mysql/sql/create-index.js +6 -0
  107. package/build/database/drivers/mysql/sql/create-table.js +6 -0
  108. package/build/database/drivers/mysql/sql/delete.js +21 -0
  109. package/build/database/drivers/mysql/sql/drop-database.js +6 -0
  110. package/build/database/drivers/mysql/sql/drop-table.js +6 -0
  111. package/build/database/drivers/mysql/sql/insert.js +6 -0
  112. package/build/database/drivers/mysql/sql/update.js +33 -0
  113. package/build/database/drivers/mysql/sql/upsert.js +13 -0
  114. package/build/database/drivers/mysql/structure-sql.js +93 -0
  115. package/build/database/drivers/mysql/table.js +121 -0
  116. package/build/database/drivers/pgsql/column.js +90 -0
  117. package/build/database/drivers/pgsql/columns-index.js +6 -0
  118. package/build/database/drivers/pgsql/foreign-key.js +12 -0
  119. package/build/database/drivers/pgsql/index.js +441 -0
  120. package/build/database/drivers/pgsql/options.js +32 -0
  121. package/build/database/drivers/pgsql/query-parser.js +6 -0
  122. package/build/database/drivers/pgsql/sql/alter-table.js +6 -0
  123. package/build/database/drivers/pgsql/sql/create-database.js +38 -0
  124. package/build/database/drivers/pgsql/sql/create-index.js +6 -0
  125. package/build/database/drivers/pgsql/sql/create-table.js +6 -0
  126. package/build/database/drivers/pgsql/sql/delete.js +21 -0
  127. package/build/database/drivers/pgsql/sql/drop-database.js +6 -0
  128. package/build/database/drivers/pgsql/sql/drop-table.js +6 -0
  129. package/build/database/drivers/pgsql/sql/insert.js +6 -0
  130. package/build/database/drivers/pgsql/sql/update.js +33 -0
  131. package/build/database/drivers/pgsql/sql/upsert.js +14 -0
  132. package/build/database/drivers/pgsql/structure-sql.js +126 -0
  133. package/build/database/drivers/pgsql/table.js +135 -0
  134. package/build/database/drivers/sqlite/base.js +509 -0
  135. package/build/database/drivers/sqlite/column.js +75 -0
  136. package/build/database/drivers/sqlite/columns-index.js +30 -0
  137. package/build/database/drivers/sqlite/connection-sql-js.js +46 -0
  138. package/build/database/drivers/sqlite/foreign-key.js +24 -0
  139. package/build/database/drivers/sqlite/index.js +394 -0
  140. package/build/database/drivers/sqlite/index.native.js +72 -0
  141. package/build/database/drivers/sqlite/index.web.js +99 -0
  142. package/build/database/drivers/sqlite/options.js +32 -0
  143. package/build/database/drivers/sqlite/query-parser.js +6 -0
  144. package/build/database/drivers/sqlite/query.js +35 -0
  145. package/build/database/drivers/sqlite/query.native.js +35 -0
  146. package/build/database/drivers/sqlite/query.web.js +49 -0
  147. package/build/database/drivers/sqlite/sql/alter-table.js +187 -0
  148. package/build/database/drivers/sqlite/sql/create-index.js +6 -0
  149. package/build/database/drivers/sqlite/sql/create-table.js +6 -0
  150. package/build/database/drivers/sqlite/sql/delete.js +26 -0
  151. package/build/database/drivers/sqlite/sql/drop-table.js +6 -0
  152. package/build/database/drivers/sqlite/sql/insert.js +6 -0
  153. package/build/database/drivers/sqlite/sql/update.js +33 -0
  154. package/build/database/drivers/sqlite/sql/upsert.js +14 -0
  155. package/build/database/drivers/sqlite/structure-sql.js +56 -0
  156. package/build/database/drivers/sqlite/table-rebuilder.js +96 -0
  157. package/build/database/drivers/sqlite/table.js +131 -0
  158. package/build/database/drivers/structure-sql/utils.js +35 -0
  159. package/build/database/handler.js +13 -0
  160. package/build/database/initializer-from-require-context.js +101 -0
  161. package/build/database/migration/index.js +438 -0
  162. package/build/database/migrator/files-finder.js +55 -0
  163. package/build/database/migrator/types.js +31 -0
  164. package/build/database/migrator.js +557 -0
  165. package/build/database/pool/async-tracked-multi-connection.js +1164 -0
  166. package/build/database/pool/base-methods-forward.js +52 -0
  167. package/build/database/pool/base.js +380 -0
  168. package/build/database/pool/single-multi-use.js +118 -0
  169. package/build/database/query/alter-table-base.js +104 -0
  170. package/build/database/query/base.js +49 -0
  171. package/build/database/query/create-database-base.js +42 -0
  172. package/build/database/query/create-index-base.js +117 -0
  173. package/build/database/query/create-table-base.js +205 -0
  174. package/build/database/query/delete-base.js +19 -0
  175. package/build/database/query/drop-database-base.js +38 -0
  176. package/build/database/query/drop-table-base.js +58 -0
  177. package/build/database/query/from-base.js +36 -0
  178. package/build/database/query/from-plain.js +16 -0
  179. package/build/database/query/from-table.js +18 -0
  180. package/build/database/query/index.js +533 -0
  181. package/build/database/query/insert-base.js +172 -0
  182. package/build/database/query/join-base.js +43 -0
  183. package/build/database/query/join-object.js +167 -0
  184. package/build/database/query/join-plain.js +18 -0
  185. package/build/database/query/join-tracker.js +93 -0
  186. package/build/database/query/model-class-query.js +1577 -0
  187. package/build/database/query/order-base.js +33 -0
  188. package/build/database/query/order-column.js +77 -0
  189. package/build/database/query/order-plain.js +28 -0
  190. package/build/database/query/preloader/belongs-to.js +267 -0
  191. package/build/database/query/preloader/ensure-model-class-initialized.js +18 -0
  192. package/build/database/query/preloader/has-many.js +316 -0
  193. package/build/database/query/preloader/has-one.js +123 -0
  194. package/build/database/query/preloader/selection.js +152 -0
  195. package/build/database/query/preloader.js +201 -0
  196. package/build/database/query/query-data.js +305 -0
  197. package/build/database/query/select-base.js +30 -0
  198. package/build/database/query/select-plain.js +18 -0
  199. package/build/database/query/select-table-and-column.js +28 -0
  200. package/build/database/query/update-base.js +41 -0
  201. package/build/database/query/upsert-base.js +103 -0
  202. package/build/database/query/where-base.js +38 -0
  203. package/build/database/query/where-combinator.js +31 -0
  204. package/build/database/query/where-hash.js +77 -0
  205. package/build/database/query/where-model-class-hash.js +505 -0
  206. package/build/database/query/where-not.js +23 -0
  207. package/build/database/query/where-plain.js +20 -0
  208. package/build/database/query/with-count.js +219 -0
  209. package/build/database/query-parser/base-query-parser.js +40 -0
  210. package/build/database/query-parser/from-parser.js +49 -0
  211. package/build/database/query-parser/group-parser.js +55 -0
  212. package/build/database/query-parser/joins-parser.js +37 -0
  213. package/build/database/query-parser/limit-parser.js +77 -0
  214. package/build/database/query-parser/options.js +94 -0
  215. package/build/database/query-parser/order-parser.js +45 -0
  216. package/build/database/query-parser/select-parser.js +67 -0
  217. package/build/database/query-parser/where-parser.js +46 -0
  218. package/build/database/record/acts-as-list.js +374 -0
  219. package/build/database/record/attachments/download.js +49 -0
  220. package/build/database/record/attachments/handle.js +188 -0
  221. package/build/database/record/attachments/normalize-input.js +213 -0
  222. package/build/database/record/attachments/storage-drivers/filesystem.js +114 -0
  223. package/build/database/record/attachments/storage-drivers/native.js +146 -0
  224. package/build/database/record/attachments/storage-drivers/s3.js +245 -0
  225. package/build/database/record/attachments/store.js +591 -0
  226. package/build/database/record/index.js +4094 -0
  227. package/build/database/record/instance-relationships/base.js +289 -0
  228. package/build/database/record/instance-relationships/belongs-to.js +84 -0
  229. package/build/database/record/instance-relationships/has-many.js +284 -0
  230. package/build/database/record/instance-relationships/has-one.js +117 -0
  231. package/build/database/record/record-not-found-error.js +3 -0
  232. package/build/database/record/relationships/base.js +195 -0
  233. package/build/database/record/relationships/belongs-to.js +57 -0
  234. package/build/database/record/relationships/has-many.js +46 -0
  235. package/build/database/record/relationships/has-one.js +46 -0
  236. package/build/database/record/state-machine.js +278 -0
  237. package/build/database/record/user-module.js +43 -0
  238. package/build/database/record/validators/base.js +27 -0
  239. package/build/database/record/validators/format.js +50 -0
  240. package/build/database/record/validators/presence.js +24 -0
  241. package/build/database/record/validators/uniqueness.js +124 -0
  242. package/build/database/table-data/index.js +241 -0
  243. package/build/database/table-data/table-column.js +416 -0
  244. package/build/database/table-data/table-foreign-key.js +69 -0
  245. package/build/database/table-data/table-index.js +46 -0
  246. package/build/database/table-data/table-reference.js +13 -0
  247. package/build/database/use-database.js +48 -0
  248. package/build/environment-handlers/base.js +561 -0
  249. package/build/environment-handlers/browser.js +338 -0
  250. package/build/environment-handlers/node/cli/commands/background-jobs-main.js +21 -0
  251. package/build/environment-handlers/node/cli/commands/background-jobs-runner.js +24 -0
  252. package/build/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -0
  253. package/build/environment-handlers/node/cli/commands/beacon.js +21 -0
  254. package/build/environment-handlers/node/cli/commands/cli-command-context.js +31 -0
  255. package/build/environment-handlers/node/cli/commands/console.js +149 -0
  256. package/build/environment-handlers/node/cli/commands/db/schema/dump.js +43 -0
  257. package/build/environment-handlers/node/cli/commands/db/schema/load.js +69 -0
  258. package/build/environment-handlers/node/cli/commands/db/seed.js +79 -0
  259. package/build/environment-handlers/node/cli/commands/destroy/migration.js +47 -0
  260. package/build/environment-handlers/node/cli/commands/generate/base-models.js +396 -0
  261. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +872 -0
  262. package/build/environment-handlers/node/cli/commands/generate/migration.js +45 -0
  263. package/build/environment-handlers/node/cli/commands/generate/model.js +45 -0
  264. package/build/environment-handlers/node/cli/commands/init.js +68 -0
  265. package/build/environment-handlers/node/cli/commands/routes.js +63 -0
  266. package/build/environment-handlers/node/cli/commands/run-script.js +85 -0
  267. package/build/environment-handlers/node/cli/commands/runner.js +84 -0
  268. package/build/environment-handlers/node/cli/commands/server.js +151 -0
  269. package/build/environment-handlers/node/cli/commands/test.js +118 -0
  270. package/build/environment-handlers/node.js +887 -0
  271. package/build/error-logger.js +30 -0
  272. package/build/frontend-model-controller.js +3491 -0
  273. package/build/frontend-model-resource/base-resource.js +935 -0
  274. package/build/frontend-models/base.js +4004 -0
  275. package/build/frontend-models/clear-pending-debounced-callback.js +16 -0
  276. package/build/frontend-models/event-hook-models.js +49 -0
  277. package/build/frontend-models/model-registry.js +28 -0
  278. package/build/frontend-models/outgoing-event-buffer.js +51 -0
  279. package/build/frontend-models/preloader.js +169 -0
  280. package/build/frontend-models/query.js +2245 -0
  281. package/build/frontend-models/resource-config-validation.js +56 -0
  282. package/build/frontend-models/resource-definition.js +399 -0
  283. package/build/frontend-models/transport-serialization.js +369 -0
  284. package/build/frontend-models/use-created-event.js +21 -0
  285. package/build/frontend-models/use-destroyed-event.js +148 -0
  286. package/build/frontend-models/use-model-class-event.js +164 -0
  287. package/build/frontend-models/use-updated-event.js +152 -0
  288. package/build/frontend-models/websocket-channel.js +494 -0
  289. package/build/frontend-models/websocket-publishers.js +224 -0
  290. package/build/http-client/header.js +17 -0
  291. package/build/http-client/index.js +139 -0
  292. package/build/http-client/request.js +94 -0
  293. package/build/http-client/response.js +151 -0
  294. package/build/http-client/websocket-client.js +27 -0
  295. package/build/http-server/client/index.js +507 -0
  296. package/build/http-server/client/params-to-object.js +152 -0
  297. package/build/http-server/client/request-buffer/form-data-part.js +139 -0
  298. package/build/http-server/client/request-buffer/header.js +19 -0
  299. package/build/http-server/client/request-buffer/index.js +535 -0
  300. package/build/http-server/client/request-parser.js +195 -0
  301. package/build/http-server/client/request-runner.js +321 -0
  302. package/build/http-server/client/request-timing.js +171 -0
  303. package/build/http-server/client/request.js +114 -0
  304. package/build/http-server/client/response.js +251 -0
  305. package/build/http-server/client/uploaded-file/memory-uploaded-file.js +32 -0
  306. package/build/http-server/client/uploaded-file/temporary-uploaded-file.js +32 -0
  307. package/build/http-server/client/uploaded-file/uploaded-file.js +36 -0
  308. package/build/http-server/client/websocket-request.js +147 -0
  309. package/build/http-server/client/websocket-session.js +1755 -0
  310. package/build/http-server/cookie.js +245 -0
  311. package/build/http-server/development-reloader.js +240 -0
  312. package/build/http-server/index.js +561 -0
  313. package/build/http-server/remote-address.js +77 -0
  314. package/build/http-server/server-client.js +222 -0
  315. package/build/http-server/server-lock.js +178 -0
  316. package/build/http-server/websocket-channel-subscribers.js +110 -0
  317. package/build/http-server/websocket-channel.js +137 -0
  318. package/build/http-server/websocket-connection.js +118 -0
  319. package/build/http-server/websocket-event-log-store.js +433 -0
  320. package/build/http-server/websocket-events-host.js +170 -0
  321. package/build/http-server/websocket-events.js +50 -0
  322. package/build/http-server/worker-handler/channel-subscriber-dispatch.js +28 -0
  323. package/build/http-server/worker-handler/in-process.js +155 -0
  324. package/build/http-server/worker-handler/index.js +370 -0
  325. package/build/http-server/worker-handler/worker-script.js +6 -0
  326. package/build/http-server/worker-handler/worker-thread.js +286 -0
  327. package/build/index.js +1 -2
  328. package/build/initializer.js +39 -0
  329. package/build/jobs/mail-delivery.js +22 -0
  330. package/build/logger/base-logger.js +34 -0
  331. package/build/logger/console-logger.js +28 -0
  332. package/build/logger/file-logger.js +36 -0
  333. package/build/logger/outputs/array-output.js +50 -0
  334. package/build/logger/outputs/console-output.js +32 -0
  335. package/build/logger/outputs/file-output.js +55 -0
  336. package/build/logger/outputs/stdout-output.js +64 -0
  337. package/build/logger.js +507 -0
  338. package/build/mailer/backends/smtp.js +197 -0
  339. package/build/mailer/base.js +337 -0
  340. package/build/mailer/delivery.js +70 -0
  341. package/build/mailer/index.js +24 -0
  342. package/build/mailer.js +15 -0
  343. package/build/plugins/sqljs-wasm-route-controller.js +70 -0
  344. package/build/plugins/sqljs-wasm-route.js +71 -0
  345. package/build/record-payload-values.js +83 -0
  346. package/build/routes/app-routes.js +17 -0
  347. package/build/routes/base-route.js +133 -0
  348. package/build/routes/basic-route.js +109 -0
  349. package/build/routes/built-in/debug/controller.js +12 -0
  350. package/build/routes/built-in/errors/controller.js +7 -0
  351. package/build/routes/get-route.js +75 -0
  352. package/build/routes/hooks/frontend-model-command-route-hook.js +100 -0
  353. package/build/routes/index.js +50 -0
  354. package/build/routes/namespace-route.js +51 -0
  355. package/build/routes/plugin-routes.js +141 -0
  356. package/build/routes/post-route.js +74 -0
  357. package/build/routes/resolver.js +535 -0
  358. package/build/routes/resource-route.js +154 -0
  359. package/build/routes/root-route.js +11 -0
  360. package/build/src/authorization/ability.d.ts +24 -23
  361. package/build/src/authorization/ability.d.ts.map +1 -1
  362. package/build/src/authorization/ability.js +14 -13
  363. package/build/src/authorization/base-resource.d.ts +20 -26
  364. package/build/src/authorization/base-resource.d.ts.map +1 -1
  365. package/build/src/authorization/base-resource.js +13 -11
  366. package/build/src/configuration-types.d.ts +21 -2
  367. package/build/src/configuration-types.d.ts.map +1 -1
  368. package/build/src/configuration-types.js +8 -2
  369. package/build/src/database/query/where-combinator.d.ts.map +1 -1
  370. package/build/src/database/query/where-combinator.js +1 -2
  371. package/build/src/database/record/acts-as-list.js +2 -2
  372. package/build/src/database/record/attachments/store.d.ts +1 -1
  373. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  374. package/build/src/database/record/attachments/store.js +2 -2
  375. package/build/src/database/record/index.d.ts +82 -20
  376. package/build/src/database/record/index.d.ts.map +1 -1
  377. package/build/src/database/record/index.js +126 -13
  378. package/build/src/database/record/relationships/base.d.ts +2 -2
  379. package/build/src/database/record/relationships/base.d.ts.map +1 -1
  380. package/build/src/database/record/relationships/base.js +3 -3
  381. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts +4 -2
  382. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
  383. package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +59 -36
  384. package/build/src/frontend-model-controller.d.ts +6 -6
  385. package/build/src/frontend-model-controller.d.ts.map +1 -1
  386. package/build/src/frontend-model-controller.js +4 -4
  387. package/build/src/frontend-model-resource/base-resource.d.ts +18 -17
  388. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  389. package/build/src/frontend-model-resource/base-resource.js +22 -10
  390. package/build/src/frontend-models/base.d.ts +19 -12
  391. package/build/src/frontend-models/base.d.ts.map +1 -1
  392. package/build/src/frontend-models/base.js +79 -48
  393. package/build/src/frontend-models/preloader.d.ts +6 -6
  394. package/build/src/frontend-models/preloader.d.ts.map +1 -1
  395. package/build/src/frontend-models/preloader.js +5 -5
  396. package/build/src/frontend-models/query.d.ts.map +1 -1
  397. package/build/src/frontend-models/query.js +1 -4
  398. package/build/src/utils/ransack.d.ts.map +1 -1
  399. package/build/src/utils/ransack.js +1 -2
  400. package/build/templates/configuration.js +61 -0
  401. package/build/templates/generate-migration.js +11 -0
  402. package/build/templates/generate-model.js +6 -0
  403. package/build/templates/routes.js +11 -0
  404. package/build/testing/base-expect.js +17 -0
  405. package/build/testing/browser-frontend-model-event-hook-scenarios.js +520 -0
  406. package/build/testing/browser-test-app.js +32 -0
  407. package/build/testing/expect-to-change.js +55 -0
  408. package/build/testing/expect-utils.js +269 -0
  409. package/build/testing/expect.js +763 -0
  410. package/build/testing/request-client.js +90 -0
  411. package/build/testing/test-files-finder.js +364 -0
  412. package/build/testing/test-filter-parser.js +198 -0
  413. package/build/testing/test-runner.js +1168 -0
  414. package/build/testing/test-suite-splitter.js +177 -0
  415. package/build/testing/test.js +370 -0
  416. package/build/utils/backtrace-cleaner-node.js +87 -0
  417. package/build/utils/backtrace-cleaner.js +266 -0
  418. package/build/utils/ensure-error.js +15 -0
  419. package/build/utils/event-emitter.js +8 -0
  420. package/build/utils/file-exists.js +18 -0
  421. package/build/utils/format-value.js +101 -0
  422. package/build/utils/model-scope.js +56 -0
  423. package/build/utils/nest-callbacks.js +22 -0
  424. package/build/utils/plain-object.js +14 -0
  425. package/build/utils/ransack.js +859 -0
  426. package/build/utils/rest-args-error.js +14 -0
  427. package/build/utils/singularize-model-name.js +18 -0
  428. package/build/utils/split-sql-statements.js +88 -0
  429. package/build/utils/to-import-specifier.js +53 -0
  430. package/build/utils/with-tracked-stack-async-hooks.js +103 -0
  431. package/build/utils/with-tracked-stack.js +38 -0
  432. package/build/velocious-error.js +34 -0
  433. package/index.js +1 -0
  434. package/package.json +10 -4
  435. package/scripts/clean-build.js +8 -0
  436. package/scripts/ensure-bin-executable.js +13 -0
  437. package/scripts/run-tests.js +37 -0
  438. package/scripts/test-browser.js +486 -0
  439. package/src/application.js +229 -0
  440. package/src/authorization/ability.js +329 -0
  441. package/src/authorization/base-resource.js +143 -0
  442. package/src/background-jobs/client.js +50 -0
  443. package/src/background-jobs/cron-expression.js +277 -0
  444. package/src/background-jobs/forked-runner-child.js +86 -0
  445. package/src/background-jobs/job-record.js +13 -0
  446. package/src/background-jobs/job-registry.js +92 -0
  447. package/src/background-jobs/job-runner.js +107 -0
  448. package/src/background-jobs/job.js +77 -0
  449. package/src/background-jobs/json-socket.js +78 -0
  450. package/src/background-jobs/main.js +926 -0
  451. package/src/background-jobs/normalize-error.js +26 -0
  452. package/src/background-jobs/scheduler.js +274 -0
  453. package/src/background-jobs/socket-request.js +68 -0
  454. package/src/background-jobs/status-reporter.js +101 -0
  455. package/src/background-jobs/store.js +994 -0
  456. package/src/background-jobs/types.js +70 -0
  457. package/src/background-jobs/web/authorization.js +89 -0
  458. package/src/background-jobs/web/controller.js +280 -0
  459. package/src/background-jobs/web/index.js +57 -0
  460. package/src/background-jobs/web/path-matcher.js +74 -0
  461. package/src/background-jobs/web/registry.js +49 -0
  462. package/src/background-jobs/worker.js +683 -0
  463. package/src/beacon/client.js +330 -0
  464. package/src/beacon/in-process-broker.js +71 -0
  465. package/src/beacon/in-process-client.js +139 -0
  466. package/src/beacon/server.js +148 -0
  467. package/src/beacon/types.js +55 -0
  468. package/src/cli/base-command.js +67 -0
  469. package/src/cli/browser-cli.js +45 -0
  470. package/src/cli/commands/background-jobs-main.js +7 -0
  471. package/src/cli/commands/background-jobs-runner.js +7 -0
  472. package/src/cli/commands/background-jobs-worker.js +7 -0
  473. package/src/cli/commands/beacon.js +7 -0
  474. package/src/cli/commands/console.js +12 -0
  475. package/src/cli/commands/db/base-command.js +82 -0
  476. package/src/cli/commands/db/create.js +64 -0
  477. package/src/cli/commands/db/drop.js +75 -0
  478. package/src/cli/commands/db/migrate.js +17 -0
  479. package/src/cli/commands/db/reset.js +22 -0
  480. package/src/cli/commands/db/rollback.js +15 -0
  481. package/src/cli/commands/db/schema/dump.js +12 -0
  482. package/src/cli/commands/db/schema/load.js +12 -0
  483. package/src/cli/commands/db/seed.js +12 -0
  484. package/src/cli/commands/db/tenants/check.js +38 -0
  485. package/src/cli/commands/db/tenants/create.js +33 -0
  486. package/src/cli/commands/db/tenants/migrate.js +49 -0
  487. package/src/cli/commands/destroy/migration.js +7 -0
  488. package/src/cli/commands/generate/base-models.js +7 -0
  489. package/src/cli/commands/generate/frontend-models.js +12 -0
  490. package/src/cli/commands/generate/migration.js +7 -0
  491. package/src/cli/commands/generate/model.js +7 -0
  492. package/src/cli/commands/init.js +11 -0
  493. package/src/cli/commands/routes.js +7 -0
  494. package/src/cli/commands/run-script.js +12 -0
  495. package/src/cli/commands/runner.js +12 -0
  496. package/src/cli/commands/server.js +7 -0
  497. package/src/cli/commands/test.js +9 -0
  498. package/src/cli/index.js +152 -0
  499. package/src/cli/tenant-database-command-helper.js +198 -0
  500. package/src/cli/use-browser-cli.js +30 -0
  501. package/src/configuration-resolver.js +65 -0
  502. package/src/configuration-types.js +429 -0
  503. package/src/configuration.js +2590 -0
  504. package/src/controller.js +421 -0
  505. package/src/current-configuration.js +31 -0
  506. package/src/current.js +80 -0
  507. package/src/database/annotations-async-hooks.js +47 -0
  508. package/src/database/annotations.js +40 -0
  509. package/src/database/drivers/base-column.js +182 -0
  510. package/src/database/drivers/base-columns-index.js +81 -0
  511. package/src/database/drivers/base-foreign-key.js +104 -0
  512. package/src/database/drivers/base-table.js +156 -0
  513. package/src/database/drivers/base.js +1609 -0
  514. package/src/database/drivers/mssql/column.js +74 -0
  515. package/src/database/drivers/mssql/columns-index.js +6 -0
  516. package/src/database/drivers/mssql/connect-connection.js +16 -0
  517. package/src/database/drivers/mssql/foreign-key.js +12 -0
  518. package/src/database/drivers/mssql/index.js +590 -0
  519. package/src/database/drivers/mssql/options.js +79 -0
  520. package/src/database/drivers/mssql/query-parser.js +6 -0
  521. package/src/database/drivers/mssql/sql/alter-table.js +4 -0
  522. package/src/database/drivers/mssql/sql/create-database.js +36 -0
  523. package/src/database/drivers/mssql/sql/create-index.js +4 -0
  524. package/src/database/drivers/mssql/sql/create-table.js +4 -0
  525. package/src/database/drivers/mssql/sql/delete.js +19 -0
  526. package/src/database/drivers/mssql/sql/drop-database.js +36 -0
  527. package/src/database/drivers/mssql/sql/drop-table.js +4 -0
  528. package/src/database/drivers/mssql/sql/insert.js +4 -0
  529. package/src/database/drivers/mssql/sql/update.js +31 -0
  530. package/src/database/drivers/mssql/sql/upsert.js +23 -0
  531. package/src/database/drivers/mssql/structure-sql.js +120 -0
  532. package/src/database/drivers/mssql/table.js +145 -0
  533. package/src/database/drivers/mysql/column.js +112 -0
  534. package/src/database/drivers/mysql/columns-index.js +22 -0
  535. package/src/database/drivers/mysql/foreign-key.js +12 -0
  536. package/src/database/drivers/mysql/index.js +473 -0
  537. package/src/database/drivers/mysql/options.js +34 -0
  538. package/src/database/drivers/mysql/query-parser.js +6 -0
  539. package/src/database/drivers/mysql/query.js +37 -0
  540. package/src/database/drivers/mysql/sql/alter-table.js +6 -0
  541. package/src/database/drivers/mysql/sql/create-database.js +39 -0
  542. package/src/database/drivers/mysql/sql/create-index.js +6 -0
  543. package/src/database/drivers/mysql/sql/create-table.js +6 -0
  544. package/src/database/drivers/mysql/sql/delete.js +21 -0
  545. package/src/database/drivers/mysql/sql/drop-database.js +6 -0
  546. package/src/database/drivers/mysql/sql/drop-table.js +6 -0
  547. package/src/database/drivers/mysql/sql/insert.js +6 -0
  548. package/src/database/drivers/mysql/sql/update.js +33 -0
  549. package/src/database/drivers/mysql/sql/upsert.js +13 -0
  550. package/src/database/drivers/mysql/structure-sql.js +93 -0
  551. package/src/database/drivers/mysql/table.js +121 -0
  552. package/src/database/drivers/pgsql/column.js +90 -0
  553. package/src/database/drivers/pgsql/columns-index.js +6 -0
  554. package/src/database/drivers/pgsql/foreign-key.js +12 -0
  555. package/src/database/drivers/pgsql/index.js +441 -0
  556. package/src/database/drivers/pgsql/options.js +32 -0
  557. package/src/database/drivers/pgsql/query-parser.js +6 -0
  558. package/src/database/drivers/pgsql/sql/alter-table.js +6 -0
  559. package/src/database/drivers/pgsql/sql/create-database.js +38 -0
  560. package/src/database/drivers/pgsql/sql/create-index.js +6 -0
  561. package/src/database/drivers/pgsql/sql/create-table.js +6 -0
  562. package/src/database/drivers/pgsql/sql/delete.js +21 -0
  563. package/src/database/drivers/pgsql/sql/drop-database.js +6 -0
  564. package/src/database/drivers/pgsql/sql/drop-table.js +6 -0
  565. package/src/database/drivers/pgsql/sql/insert.js +6 -0
  566. package/src/database/drivers/pgsql/sql/update.js +33 -0
  567. package/src/database/drivers/pgsql/sql/upsert.js +14 -0
  568. package/src/database/drivers/pgsql/structure-sql.js +126 -0
  569. package/src/database/drivers/pgsql/table.js +135 -0
  570. package/src/database/drivers/sqlite/base.js +509 -0
  571. package/src/database/drivers/sqlite/column.js +75 -0
  572. package/src/database/drivers/sqlite/columns-index.js +30 -0
  573. package/src/database/drivers/sqlite/connection-sql-js.js +46 -0
  574. package/src/database/drivers/sqlite/foreign-key.js +24 -0
  575. package/src/database/drivers/sqlite/index.js +394 -0
  576. package/src/database/drivers/sqlite/index.native.js +72 -0
  577. package/src/database/drivers/sqlite/index.web.js +99 -0
  578. package/src/database/drivers/sqlite/options.js +32 -0
  579. package/src/database/drivers/sqlite/query-parser.js +6 -0
  580. package/src/database/drivers/sqlite/query.js +35 -0
  581. package/src/database/drivers/sqlite/query.native.js +35 -0
  582. package/src/database/drivers/sqlite/query.web.js +49 -0
  583. package/src/database/drivers/sqlite/sql/alter-table.js +187 -0
  584. package/src/database/drivers/sqlite/sql/create-index.js +6 -0
  585. package/src/database/drivers/sqlite/sql/create-table.js +6 -0
  586. package/src/database/drivers/sqlite/sql/delete.js +26 -0
  587. package/src/database/drivers/sqlite/sql/drop-table.js +6 -0
  588. package/src/database/drivers/sqlite/sql/insert.js +6 -0
  589. package/src/database/drivers/sqlite/sql/update.js +33 -0
  590. package/src/database/drivers/sqlite/sql/upsert.js +14 -0
  591. package/src/database/drivers/sqlite/structure-sql.js +56 -0
  592. package/src/database/drivers/sqlite/table-rebuilder.js +96 -0
  593. package/src/database/drivers/sqlite/table.js +131 -0
  594. package/src/database/drivers/structure-sql/utils.js +35 -0
  595. package/src/database/handler.js +13 -0
  596. package/src/database/initializer-from-require-context.js +101 -0
  597. package/src/database/migration/index.js +438 -0
  598. package/src/database/migrator/files-finder.js +55 -0
  599. package/src/database/migrator/types.js +31 -0
  600. package/src/database/migrator.js +557 -0
  601. package/src/database/pool/async-tracked-multi-connection.js +1164 -0
  602. package/src/database/pool/base-methods-forward.js +52 -0
  603. package/src/database/pool/base.js +380 -0
  604. package/src/database/pool/single-multi-use.js +118 -0
  605. package/src/database/query/alter-table-base.js +104 -0
  606. package/src/database/query/base.js +49 -0
  607. package/src/database/query/create-database-base.js +42 -0
  608. package/src/database/query/create-index-base.js +117 -0
  609. package/src/database/query/create-table-base.js +205 -0
  610. package/src/database/query/delete-base.js +19 -0
  611. package/src/database/query/drop-database-base.js +38 -0
  612. package/src/database/query/drop-table-base.js +58 -0
  613. package/src/database/query/from-base.js +36 -0
  614. package/src/database/query/from-plain.js +16 -0
  615. package/src/database/query/from-table.js +18 -0
  616. package/src/database/query/index.js +533 -0
  617. package/src/database/query/insert-base.js +172 -0
  618. package/src/database/query/join-base.js +43 -0
  619. package/src/database/query/join-object.js +167 -0
  620. package/src/database/query/join-plain.js +18 -0
  621. package/src/database/query/join-tracker.js +93 -0
  622. package/src/database/query/model-class-query.js +1577 -0
  623. package/src/database/query/order-base.js +33 -0
  624. package/src/database/query/order-column.js +77 -0
  625. package/src/database/query/order-plain.js +28 -0
  626. package/src/database/query/preloader/belongs-to.js +267 -0
  627. package/src/database/query/preloader/ensure-model-class-initialized.js +18 -0
  628. package/src/database/query/preloader/has-many.js +316 -0
  629. package/src/database/query/preloader/has-one.js +123 -0
  630. package/src/database/query/preloader/selection.js +152 -0
  631. package/src/database/query/preloader.js +201 -0
  632. package/src/database/query/query-data.js +305 -0
  633. package/src/database/query/select-base.js +30 -0
  634. package/src/database/query/select-plain.js +18 -0
  635. package/src/database/query/select-table-and-column.js +28 -0
  636. package/src/database/query/update-base.js +41 -0
  637. package/src/database/query/upsert-base.js +103 -0
  638. package/src/database/query/where-base.js +38 -0
  639. package/src/database/query/where-combinator.js +31 -0
  640. package/src/database/query/where-hash.js +77 -0
  641. package/src/database/query/where-model-class-hash.js +505 -0
  642. package/src/database/query/where-not.js +23 -0
  643. package/src/database/query/where-plain.js +20 -0
  644. package/src/database/query/with-count.js +219 -0
  645. package/src/database/query-parser/base-query-parser.js +40 -0
  646. package/src/database/query-parser/from-parser.js +49 -0
  647. package/src/database/query-parser/group-parser.js +55 -0
  648. package/src/database/query-parser/joins-parser.js +37 -0
  649. package/src/database/query-parser/limit-parser.js +77 -0
  650. package/src/database/query-parser/options.js +94 -0
  651. package/src/database/query-parser/order-parser.js +45 -0
  652. package/src/database/query-parser/select-parser.js +67 -0
  653. package/src/database/query-parser/where-parser.js +46 -0
  654. package/src/database/record/acts-as-list.js +374 -0
  655. package/src/database/record/attachments/download.js +49 -0
  656. package/src/database/record/attachments/handle.js +188 -0
  657. package/src/database/record/attachments/normalize-input.js +213 -0
  658. package/src/database/record/attachments/storage-drivers/filesystem.js +114 -0
  659. package/src/database/record/attachments/storage-drivers/native.js +146 -0
  660. package/src/database/record/attachments/storage-drivers/s3.js +245 -0
  661. package/src/database/record/attachments/store.js +591 -0
  662. package/src/database/record/index.js +4094 -0
  663. package/src/database/record/instance-relationships/base.js +289 -0
  664. package/src/database/record/instance-relationships/belongs-to.js +84 -0
  665. package/src/database/record/instance-relationships/has-many.js +284 -0
  666. package/src/database/record/instance-relationships/has-one.js +117 -0
  667. package/src/database/record/record-not-found-error.js +3 -0
  668. package/src/database/record/relationships/base.js +195 -0
  669. package/src/database/record/relationships/belongs-to.js +57 -0
  670. package/src/database/record/relationships/has-many.js +46 -0
  671. package/src/database/record/relationships/has-one.js +46 -0
  672. package/src/database/record/state-machine.js +278 -0
  673. package/src/database/record/user-module.js +43 -0
  674. package/src/database/record/validators/base.js +27 -0
  675. package/src/database/record/validators/format.js +50 -0
  676. package/src/database/record/validators/presence.js +24 -0
  677. package/src/database/record/validators/uniqueness.js +124 -0
  678. package/src/database/table-data/index.js +241 -0
  679. package/src/database/table-data/table-column.js +416 -0
  680. package/src/database/table-data/table-foreign-key.js +69 -0
  681. package/src/database/table-data/table-index.js +46 -0
  682. package/src/database/table-data/table-reference.js +13 -0
  683. package/src/database/use-database.js +48 -0
  684. package/src/environment-handlers/base.js +561 -0
  685. package/src/environment-handlers/browser.js +338 -0
  686. package/src/environment-handlers/node/cli/commands/background-jobs-main.js +21 -0
  687. package/src/environment-handlers/node/cli/commands/background-jobs-runner.js +24 -0
  688. package/src/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -0
  689. package/src/environment-handlers/node/cli/commands/beacon.js +21 -0
  690. package/src/environment-handlers/node/cli/commands/cli-command-context.js +31 -0
  691. package/src/environment-handlers/node/cli/commands/console.js +149 -0
  692. package/src/environment-handlers/node/cli/commands/db/schema/dump.js +43 -0
  693. package/src/environment-handlers/node/cli/commands/db/schema/load.js +69 -0
  694. package/src/environment-handlers/node/cli/commands/db/seed.js +79 -0
  695. package/src/environment-handlers/node/cli/commands/destroy/migration.js +47 -0
  696. package/src/environment-handlers/node/cli/commands/generate/base-models.js +396 -0
  697. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +872 -0
  698. package/src/environment-handlers/node/cli/commands/generate/migration.js +45 -0
  699. package/src/environment-handlers/node/cli/commands/generate/model.js +45 -0
  700. package/src/environment-handlers/node/cli/commands/init.js +68 -0
  701. package/src/environment-handlers/node/cli/commands/routes.js +63 -0
  702. package/src/environment-handlers/node/cli/commands/run-script.js +85 -0
  703. package/src/environment-handlers/node/cli/commands/runner.js +84 -0
  704. package/src/environment-handlers/node/cli/commands/server.js +151 -0
  705. package/src/environment-handlers/node/cli/commands/test.js +118 -0
  706. package/src/environment-handlers/node.js +887 -0
  707. package/src/error-logger.js +30 -0
  708. package/src/frontend-model-controller.js +3491 -0
  709. package/src/frontend-model-resource/base-resource.js +935 -0
  710. package/src/frontend-models/base.js +4004 -0
  711. package/src/frontend-models/clear-pending-debounced-callback.js +16 -0
  712. package/src/frontend-models/event-hook-models.js +49 -0
  713. package/src/frontend-models/model-registry.js +28 -0
  714. package/src/frontend-models/outgoing-event-buffer.js +51 -0
  715. package/src/frontend-models/preloader.js +169 -0
  716. package/src/frontend-models/query.js +2245 -0
  717. package/src/frontend-models/resource-config-validation.js +56 -0
  718. package/src/frontend-models/resource-definition.js +399 -0
  719. package/src/frontend-models/transport-serialization.js +369 -0
  720. package/src/frontend-models/use-created-event.js +21 -0
  721. package/src/frontend-models/use-destroyed-event.js +148 -0
  722. package/src/frontend-models/use-model-class-event.js +164 -0
  723. package/src/frontend-models/use-updated-event.js +152 -0
  724. package/src/frontend-models/websocket-channel.js +494 -0
  725. package/src/frontend-models/websocket-publishers.js +224 -0
  726. package/src/http-client/header.js +17 -0
  727. package/src/http-client/index.js +139 -0
  728. package/src/http-client/request.js +94 -0
  729. package/src/http-client/response.js +151 -0
  730. package/src/http-client/websocket-client.js +27 -0
  731. package/src/http-server/client/index.js +507 -0
  732. package/src/http-server/client/params-to-object.js +152 -0
  733. package/src/http-server/client/request-buffer/form-data-part.js +139 -0
  734. package/src/http-server/client/request-buffer/header.js +19 -0
  735. package/src/http-server/client/request-buffer/index.js +535 -0
  736. package/src/http-server/client/request-parser.js +195 -0
  737. package/src/http-server/client/request-runner.js +321 -0
  738. package/src/http-server/client/request-timing.js +171 -0
  739. package/src/http-server/client/request.js +114 -0
  740. package/src/http-server/client/response.js +251 -0
  741. package/src/http-server/client/uploaded-file/memory-uploaded-file.js +32 -0
  742. package/src/http-server/client/uploaded-file/temporary-uploaded-file.js +32 -0
  743. package/src/http-server/client/uploaded-file/uploaded-file.js +36 -0
  744. package/src/http-server/client/websocket-request.js +147 -0
  745. package/src/http-server/client/websocket-session.js +1755 -0
  746. package/src/http-server/cookie.js +245 -0
  747. package/src/http-server/development-reloader.js +240 -0
  748. package/src/http-server/index.js +561 -0
  749. package/src/http-server/remote-address.js +77 -0
  750. package/src/http-server/server-client.js +222 -0
  751. package/src/http-server/server-lock.js +178 -0
  752. package/src/http-server/websocket-channel-subscribers.js +110 -0
  753. package/src/http-server/websocket-channel.js +137 -0
  754. package/src/http-server/websocket-connection.js +118 -0
  755. package/src/http-server/websocket-event-log-store.js +433 -0
  756. package/src/http-server/websocket-events-host.js +170 -0
  757. package/src/http-server/websocket-events.js +50 -0
  758. package/src/http-server/worker-handler/channel-subscriber-dispatch.js +28 -0
  759. package/src/http-server/worker-handler/in-process.js +155 -0
  760. package/src/http-server/worker-handler/index.js +370 -0
  761. package/src/http-server/worker-handler/worker-script.js +6 -0
  762. package/src/http-server/worker-handler/worker-thread.js +286 -0
  763. package/src/initializer.js +39 -0
  764. package/src/jobs/.gitkeep +1 -0
  765. package/src/jobs/mail-delivery.js +22 -0
  766. package/src/logger/base-logger.js +34 -0
  767. package/src/logger/console-logger.js +28 -0
  768. package/src/logger/file-logger.js +36 -0
  769. package/src/logger/outputs/array-output.js +50 -0
  770. package/src/logger/outputs/console-output.js +32 -0
  771. package/src/logger/outputs/file-output.js +55 -0
  772. package/src/logger/outputs/stdout-output.js +64 -0
  773. package/src/logger.js +507 -0
  774. package/src/mailer/backends/smtp.js +197 -0
  775. package/src/mailer/base.js +337 -0
  776. package/src/mailer/delivery.js +70 -0
  777. package/src/mailer/index.js +24 -0
  778. package/src/mailer.js +15 -0
  779. package/src/plugins/sqljs-wasm-route-controller.js +70 -0
  780. package/src/plugins/sqljs-wasm-route.js +71 -0
  781. package/src/record-payload-values.js +83 -0
  782. package/src/routes/app-routes.js +17 -0
  783. package/src/routes/base-route.js +133 -0
  784. package/src/routes/basic-route.js +109 -0
  785. package/src/routes/built-in/debug/controller.js +12 -0
  786. package/src/routes/built-in/errors/controller.js +7 -0
  787. package/src/routes/built-in/errors/not-found.ejs +1 -0
  788. package/src/routes/get-route.js +75 -0
  789. package/src/routes/hooks/frontend-model-command-route-hook.js +100 -0
  790. package/src/routes/index.js +50 -0
  791. package/src/routes/namespace-route.js +51 -0
  792. package/src/routes/plugin-routes.js +141 -0
  793. package/src/routes/post-route.js +74 -0
  794. package/src/routes/resolver.js +535 -0
  795. package/src/routes/resource-route.js +154 -0
  796. package/src/routes/root-route.js +11 -0
  797. package/src/templates/configuration.js +61 -0
  798. package/src/templates/generate-migration.js +11 -0
  799. package/src/templates/generate-model.js +6 -0
  800. package/src/templates/routes.js +11 -0
  801. package/src/testing/base-expect.js +17 -0
  802. package/src/testing/browser-frontend-model-event-hook-scenarios.js +520 -0
  803. package/src/testing/browser-test-app.js +32 -0
  804. package/src/testing/expect-to-change.js +55 -0
  805. package/src/testing/expect-utils.js +269 -0
  806. package/src/testing/expect.js +763 -0
  807. package/src/testing/request-client.js +90 -0
  808. package/src/testing/test-files-finder.js +364 -0
  809. package/src/testing/test-filter-parser.js +198 -0
  810. package/src/testing/test-runner.js +1168 -0
  811. package/src/testing/test-suite-splitter.js +177 -0
  812. package/src/testing/test.js +370 -0
  813. package/src/types/external-modules.d.ts +57 -0
  814. package/src/utils/backtrace-cleaner-node.js +87 -0
  815. package/src/utils/backtrace-cleaner.js +266 -0
  816. package/src/utils/ensure-error.js +15 -0
  817. package/src/utils/event-emitter.js +8 -0
  818. package/src/utils/file-exists.js +18 -0
  819. package/src/utils/format-value.js +101 -0
  820. package/src/utils/model-scope.js +56 -0
  821. package/src/utils/nest-callbacks.js +22 -0
  822. package/src/utils/plain-object.js +14 -0
  823. package/src/utils/ransack.js +859 -0
  824. package/src/utils/rest-args-error.js +14 -0
  825. package/src/utils/singularize-model-name.js +18 -0
  826. package/src/utils/split-sql-statements.js +88 -0
  827. package/src/utils/to-import-specifier.js +53 -0
  828. package/src/utils/with-tracked-stack-async-hooks.js +103 -0
  829. package/src/utils/with-tracked-stack.js +38 -0
  830. package/src/velocious-error.js +34 -0
  831. package/tsconfig.json +16 -0
@@ -0,0 +1,2245 @@
1
+ // @ts-check
2
+
3
+ import {resolveFrontendModelClass} from "./model-registry.js"
4
+ import {normalizeRansackGroup, parseRansackSort} from "../utils/ransack.js"
5
+ import {isModelScopeDescriptor} from "../utils/model-scope.js"
6
+ import isPlainObject from "../utils/plain-object.js"
7
+
8
+ /**
9
+ * FrontendModelSearch type.
10
+ * @typedef {object} FrontendModelSearch
11
+ * @property {string} column - Attribute name to search.
12
+ * @property {"eq" | "like" | "notEq" | "gt" | "gteq" | "lt" | "lteq"} operator - Search operator.
13
+ * @property {string[]} path - Relationship path from root model.
14
+ * @property {?} value - Search value.
15
+ */
16
+ /**
17
+ * FrontendModelTransportValue type.
18
+ * @typedef {null | boolean | number | string | object} FrontendModelTransportValue
19
+ */
20
+ /**
21
+ * Defines this typedef.
22
+ * @typedef {{attributeName: string, relationshipName: string, where?: Record<string, FrontendModelTransportValue>}} FrontendModelWithCountPayloadEntry
23
+ */
24
+ /**
25
+ * Defines this typedef.
26
+ * @typedef {{modelName: string, actions: string[]}} FrontendModelAbilitiesPayloadEntry
27
+ */
28
+ /**
29
+ * FrontendModelProjectionOptions type.
30
+ * @typedef {object} FrontendModelProjectionOptions
31
+ * @property {Record<string, string[] | string> | string | string[]} [select] - Model-aware attribute select map or root-model shorthand.
32
+ * @property {Record<string, string[] | string> | string | string[]} [selectsExtra] - Extra attributes to load in addition to the defaults, keyed by model name or root-model shorthand.
33
+ * @property {import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord>} [preload] - Relationship preload tree.
34
+ * @property {string | string[] | Record<string, boolean | {relationship?: string, where?: Record<string, FrontendModelTransportValue>}>} [withCount] - Association count spec.
35
+ * @property {string[] | Record<string, string[]>} [abilities] - Ability actions to compute per record.
36
+ * @property {string | Array<string | Record<string, FrontendModelTransportValue>> | Record<string, FrontendModelTransportValue>} [queryData] - Backend query data names/spec.
37
+ */
38
+ /**
39
+ * Defines this typedef.
40
+ * @typedef {FrontendModelProjectionOptions & {query?: FrontendModelQuery<typeof import("./base.js").default>}} FrontendModelEventOptionsObject
41
+ */
42
+ /**
43
+ * FrontendModelEventOptions type.
44
+ * @typedef {FrontendModelEventOptionsObject | FrontendModelQuery<typeof import("./base.js").default>} FrontendModelEventOptions
45
+ */
46
+ /**
47
+ * FrontendModelProjectionPayload type.
48
+ * @typedef {object} FrontendModelProjectionPayload
49
+ * @property {Record<string, string[]>} [select] - Normalized select map.
50
+ * @property {Record<string, string[]>} [selectsExtra] - Normalized extra select map.
51
+ * @property {import("../database/query/index.js").NestedPreloadRecord} [preload] - Normalized preload tree.
52
+ * @property {FrontendModelWithCountPayloadEntry[]} [withCount] - Normalized count specs.
53
+ * @property {FrontendModelAbilitiesPayloadEntry[]} [abilities] - Normalized ability specs.
54
+ * @property {FrontendModelTransportValue} [queryData] - Normalized queryData spec.
55
+ */
56
+ /**
57
+ * FrontendModelEventFilterPayload type.
58
+ * @typedef {object} FrontendModelEventFilterPayload
59
+ * @property {Record<string, FrontendModelTransportValue>} [joins] - Relationship joins needed for matching.
60
+ * @property {FrontendModelSearch[]} [searches] - Search predicates needed for matching.
61
+ * @property {Record<string, FrontendModelTransportValue>} [where] - Structured where predicates needed for matching.
62
+ */
63
+ /**
64
+ * Defines this typedef.
65
+ * @typedef {FrontendModelEventFilterPayload & {key: string}} FrontendModelEventFilterPayloadEntry
66
+ */
67
+ /**
68
+ * FrontendModelEventOptionsPayload type.
69
+ * @typedef {object} FrontendModelEventOptionsPayload
70
+ * @property {string | null} eventFilterKey - Stable event filter key, or null when no filter is present.
71
+ * @property {FrontendModelEventFilterPayload | null} eventFilterPayload - Normalized event filter payload, or null when unfiltered.
72
+ * @property {FrontendModelProjectionPayload} projectionPayload - Normalized event serialization projection payload.
73
+ */
74
+
75
+ /**
76
+ * Runs the normalizePreload helper.
77
+ * @param {import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord> | boolean | undefined | null} preload - Preload shorthand.
78
+ * @returns {import("../database/query/index.js").NestedPreloadRecord} - Normalized preload.
79
+ */
80
+ export function normalizePreload(preload) {
81
+ if (!preload) return {}
82
+
83
+ if (preload === true) return {}
84
+
85
+ if (typeof preload === "string") {
86
+ return {[preload]: true}
87
+ }
88
+
89
+ if (Array.isArray(preload)) {
90
+ /**
91
+ * Normalized.
92
+ @type {import("../database/query/index.js").NestedPreloadRecord} */
93
+ const normalized = {}
94
+
95
+ for (const entry of preload) {
96
+ if (typeof entry === "string") {
97
+ normalized[entry] = true
98
+ continue
99
+ }
100
+
101
+ if (isPlainObject(entry)) {
102
+ mergePreloadRecord(normalized, normalizePreload(entry))
103
+ continue
104
+ }
105
+
106
+ throw new Error(`Invalid preload entry type: ${typeof entry}`)
107
+ }
108
+
109
+ return normalized
110
+ }
111
+
112
+ if (!isPlainObject(preload)) {
113
+ throw new Error(`Invalid preload type: ${typeof preload}`)
114
+ }
115
+
116
+ /**
117
+ * Normalized.
118
+ @type {import("../database/query/index.js").NestedPreloadRecord} */
119
+ const normalized = {}
120
+
121
+ for (const [relationshipName, relationshipPreload] of Object.entries(preload)) {
122
+ if (relationshipPreload === true || relationshipPreload === false) {
123
+ normalized[relationshipName] = relationshipPreload
124
+ continue
125
+ }
126
+
127
+ if (typeof relationshipPreload === "string" || Array.isArray(relationshipPreload) || isPlainObject(relationshipPreload)) {
128
+ normalized[relationshipName] = normalizePreload(relationshipPreload)
129
+ continue
130
+ }
131
+
132
+ throw new Error(`Invalid preload value for ${relationshipName}: ${typeof relationshipPreload}`)
133
+ }
134
+
135
+ return normalized
136
+ }
137
+
138
+ /**
139
+ * Normalize the shorthand `withCount` argument from the frontend-model
140
+ * query API into the strict internal entries used in the transport
141
+ * payload. Shares the shape semantics with the backend normalizer in
142
+ * `database/query/with-count.js`.
143
+ * @param {string | string[] | Record<string, boolean | {relationship?: string, where?: Record<string, ?>}>} spec
144
+ * @returns {Array<{attributeName: string, relationshipName: string, where?: Record<string, ?>}>}
145
+ */
146
+ function normalizeWithCountFrontend(spec) {
147
+ if (spec == null) return []
148
+
149
+ if (typeof spec === "string") {
150
+ return [{attributeName: `${spec}Count`, relationshipName: spec}]
151
+ }
152
+
153
+ if (Array.isArray(spec)) {
154
+ return spec.flatMap((item) => {
155
+ if (typeof item !== "string") {
156
+ throw new Error(`withCount array entries must be strings; got ${typeof item}`)
157
+ }
158
+
159
+ return [{attributeName: `${item}Count`, relationshipName: item}]
160
+ })
161
+ }
162
+
163
+ if (!isPlainObject(spec)) {
164
+ throw new Error(`Invalid withCount spec: ${typeof spec}`)
165
+ }
166
+
167
+ const entries = []
168
+
169
+ for (const [key, value] of Object.entries(spec)) {
170
+ if (value === true) {
171
+ entries.push({attributeName: `${key}Count`, relationshipName: key})
172
+ continue
173
+ }
174
+
175
+ if (value === false) continue
176
+
177
+ if (isPlainObject(value)) {
178
+ const options = /**
179
+ * Narrows the runtime value to the documented type.
180
+ @type {{relationship?: string, where?: Record<string, ?>}} */ (value)
181
+ entries.push({
182
+ attributeName: key,
183
+ relationshipName: options.relationship || key,
184
+ where: options.where
185
+ })
186
+ continue
187
+ }
188
+
189
+ throw new Error(`Invalid withCount value for ${key}: ${typeof value}`)
190
+ }
191
+
192
+ return entries
193
+ }
194
+
195
+ /**
196
+ * Normalize a frontend `.abilities(...)` spec into a flat list of
197
+ * `{modelName, actions}` entries. Accepts the flat actions-array
198
+ * shorthand (applies to the query's own model class) and the keyed
199
+ * `{ModelName: [action, ...]}` form (applies to records of that model
200
+ * class, useful for preloaded children).
201
+ * @param {string[] | Record<string, string[]>} spec
202
+ * @param {{getModelName: () => string}} rootModelClass
203
+ * @returns {Array<{modelName: string, actions: string[]}>}
204
+ */
205
+ function normalizeAbilitiesSpec(spec, rootModelClass) {
206
+ if (spec == null) return []
207
+
208
+ if (Array.isArray(spec)) {
209
+ for (const action of spec) {
210
+ if (typeof action !== "string" || action.length < 1) {
211
+ throw new Error(`abilities flat-form actions must be non-empty strings; got ${typeof action}`)
212
+ }
213
+ }
214
+
215
+ const rootModelName = typeof rootModelClass?.getModelName === "function"
216
+ ? rootModelClass.getModelName()
217
+ : undefined
218
+ if (!rootModelName) {
219
+ throw new Error("abilities flat-form requires a root model class with getModelName()")
220
+ }
221
+
222
+ return [{actions: [...spec], modelName: rootModelName}]
223
+ }
224
+
225
+ if (!isPlainObject(spec)) {
226
+ throw new Error(`Invalid abilities spec: ${typeof spec}`)
227
+ }
228
+
229
+ /**
230
+ * Entries.
231
+ @type {Array<{modelName: string, actions: string[]}>} */
232
+ const entries = []
233
+
234
+ for (const [modelName, actions] of Object.entries(spec)) {
235
+ if (!Array.isArray(actions)) {
236
+ throw new Error(`abilities[${modelName}] must be an array of action names; got ${typeof actions}`)
237
+ }
238
+
239
+ const sanitized = actions.map((action) => {
240
+ if (typeof action !== "string" || action.length < 1) {
241
+ throw new Error(`abilities[${modelName}] entries must be non-empty strings; got ${typeof action}`)
242
+ }
243
+
244
+ return action
245
+ })
246
+
247
+ entries.push({actions: sanitized, modelName})
248
+ }
249
+
250
+ return entries
251
+ }
252
+
253
+ /**
254
+ * Runs merge preload record.
255
+ * @param {import("../database/query/index.js").NestedPreloadRecord} targetPreload - Existing preload data.
256
+ * @param {import("../database/query/index.js").NestedPreloadRecord} incomingPreload - New preload data.
257
+ * @returns {void}
258
+ */
259
+ function mergePreloadRecord(targetPreload, incomingPreload) {
260
+ for (const [relationshipName, incomingValue] of Object.entries(incomingPreload)) {
261
+ const existingValue = targetPreload[relationshipName]
262
+
263
+ if (incomingValue === false) {
264
+ targetPreload[relationshipName] = false
265
+ continue
266
+ }
267
+
268
+ if (incomingValue === true) {
269
+ if (existingValue === undefined) {
270
+ targetPreload[relationshipName] = true
271
+ }
272
+ continue
273
+ }
274
+
275
+ if (!isPlainObject(incomingValue)) {
276
+ throw new Error(`Invalid preload value for ${relationshipName}: ${typeof incomingValue}`)
277
+ }
278
+
279
+ if (isPlainObject(existingValue)) {
280
+ mergePreloadRecord(
281
+ /**
282
+ * Narrows the runtime value to the documented type.
283
+ @type {import("../database/query/index.js").NestedPreloadRecord} */ (existingValue),
284
+ /**
285
+ * Narrows the runtime value to the documented type.
286
+ @type {import("../database/query/index.js").NestedPreloadRecord} */ (incomingValue)
287
+ )
288
+ continue
289
+ }
290
+
291
+ targetPreload[relationshipName] = normalizePreload(incomingValue)
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Runs normalize select.
297
+ * @param {?} select - Select payload.
298
+ * @param {string | null} [rootModelName] - Optional root model name for shorthand select payloads.
299
+ * @returns {Record<string, string[]>} - Normalized model-name keyed select record.
300
+ */
301
+ function normalizeSelect(select, rootModelName = null) {
302
+ if (!select) return {}
303
+
304
+ if (typeof select === "string") {
305
+ if (!rootModelName) throw new Error("Invalid select shorthand without root model name")
306
+
307
+ return {[rootModelName]: [select]}
308
+ }
309
+
310
+ if (Array.isArray(select)) {
311
+ if (!rootModelName) throw new Error("Invalid select shorthand without root model name")
312
+
313
+ for (const attributeName of select) {
314
+ if (typeof attributeName !== "string") {
315
+ throw new Error(`Invalid select attribute for ${rootModelName}: ${typeof attributeName}`)
316
+ }
317
+ }
318
+
319
+ return {[rootModelName]: Array.from(new Set(select))}
320
+ }
321
+
322
+ if (!isPlainObject(select)) {
323
+ throw new Error(`Invalid select type: ${typeof select}`)
324
+ }
325
+
326
+ /**
327
+ * Normalized.
328
+ @type {Record<string, string[]>} */
329
+ const normalized = {}
330
+
331
+ for (const [modelName, selection] of Object.entries(select)) {
332
+ if (typeof selection === "string") {
333
+ normalized[modelName] = [selection]
334
+ continue
335
+ }
336
+
337
+ if (!Array.isArray(selection)) {
338
+ throw new Error(`Invalid select value for ${modelName}: ${typeof selection}`)
339
+ }
340
+
341
+ for (const attributeName of selection) {
342
+ if (typeof attributeName !== "string") {
343
+ throw new Error(`Invalid select attribute for ${modelName}: ${typeof attributeName}`)
344
+ }
345
+ }
346
+
347
+ normalized[modelName] = Array.from(new Set(selection))
348
+ }
349
+
350
+ return normalized
351
+ }
352
+
353
+ /**
354
+ * Runs merge select record.
355
+ * @param {Record<string, string[]>} targetSelect - Existing select record.
356
+ * @param {Record<string, string[]>} incomingSelect - Incoming select record.
357
+ * @returns {void}
358
+ */
359
+ function mergeSelectRecord(targetSelect, incomingSelect) {
360
+ for (const [modelName, incomingAttributes] of Object.entries(incomingSelect)) {
361
+ const existingAttributes = targetSelect[modelName] || []
362
+
363
+ targetSelect[modelName] = Array.from(new Set([...existingAttributes, ...incomingAttributes]))
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Runs the normalizeSearchOperator helper.
369
+ * @param {string} operator - Raw search operator.
370
+ * @returns {"eq" | "like" | "notEq" | "gt" | "gteq" | "lt" | "lteq"} - Normalized operator.
371
+ */
372
+ export function normalizeSearchOperator(operator) {
373
+ const operatorAliases = {
374
+ "<": "lt",
375
+ "<=": "lteq",
376
+ ">": "gt",
377
+ ">=": "gteq"
378
+ }
379
+ const normalizedOperator = operatorAliases[/**
380
+ * Narrows the runtime value to the documented type.
381
+ @type {"<" | "<=" | ">" | ">="} */ (operator)] || operator
382
+ const supportedOperators = new Set(["eq", "like", "notEq", "gt", "gteq", "lt", "lteq"])
383
+
384
+ if (!supportedOperators.has(normalizedOperator)) {
385
+ throw new Error(`search operator must be one of: eq, like, notEq, gt, gteq, lt, lteq, >, >=, <, <= (got: ${operator})`)
386
+ }
387
+
388
+ return /** Narrows the runtime value to the documented type. @type {"eq" | "like" | "notEq" | "gt" | "gteq" | "lt" | "lteq"} */ (normalizedOperator)
389
+ }
390
+
391
+ /**
392
+ * Runs merge join record.
393
+ * @param {Record<string, ?>} targetJoins - Existing join record.
394
+ * @param {Record<string, ?>} incomingJoins - Incoming join record.
395
+ * @returns {void}
396
+ */
397
+ function mergeJoinRecord(targetJoins, incomingJoins) {
398
+ for (const [relationshipName, incomingValue] of Object.entries(incomingJoins)) {
399
+ const existingValue = targetJoins[relationshipName]
400
+
401
+ if (incomingValue === true) {
402
+ if (existingValue === undefined) {
403
+ targetJoins[relationshipName] = true
404
+ }
405
+ continue
406
+ }
407
+
408
+ if (!isPlainObject(incomingValue)) {
409
+ throw new Error(`Invalid join value for ${relationshipName}: ${typeof incomingValue}`)
410
+ }
411
+
412
+ if (isPlainObject(existingValue)) {
413
+ mergeJoinRecord(existingValue, incomingValue)
414
+ continue
415
+ }
416
+
417
+ if (existingValue === true) {
418
+ targetJoins[relationshipName] = normalizeJoins(incomingValue)
419
+ continue
420
+ }
421
+
422
+ targetJoins[relationshipName] = normalizeJoins(incomingValue)
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Runs the normalizeJoins helper.
428
+ * @param {?} joins - Join payload.
429
+ * @returns {Record<string, ?>} - Normalized relationship descriptor joins.
430
+ */
431
+ export function normalizeJoins(joins) {
432
+ if (!joins) return {}
433
+
434
+ if (Array.isArray(joins)) {
435
+ /**
436
+ * Normalized.
437
+ @type {Record<string, ?>} */
438
+ const normalized = {}
439
+
440
+ for (const joinEntry of joins) {
441
+ if (!isPlainObject(joinEntry)) {
442
+ throw new Error(`Invalid joins entry type: ${typeof joinEntry}`)
443
+ }
444
+
445
+ mergeJoinRecord(normalized, normalizeJoins(joinEntry))
446
+ }
447
+
448
+ return normalized
449
+ }
450
+
451
+ if (!isPlainObject(joins)) {
452
+ throw new Error(`Invalid joins type: ${typeof joins}`)
453
+ }
454
+
455
+ /**
456
+ * Normalized.
457
+ @type {Record<string, ?>} */
458
+ const normalized = {}
459
+
460
+ for (const [relationshipName, relationshipJoin] of Object.entries(joins)) {
461
+ if (relationshipJoin === true) {
462
+ normalized[relationshipName] = true
463
+ continue
464
+ }
465
+
466
+ if (isPlainObject(relationshipJoin)) {
467
+ normalized[relationshipName] = normalizeJoins(relationshipJoin)
468
+ continue
469
+ }
470
+
471
+ throw new Error(`Invalid join definition for "${relationshipName}": ${typeof relationshipJoin}`)
472
+ }
473
+
474
+ return normalized
475
+ }
476
+
477
+ /**
478
+ * FrontendModelSort type.
479
+ * @typedef {object} FrontendModelSort
480
+ * @property {string} column - Attribute name to sort by.
481
+ * @property {"asc" | "desc"} direction - Sort direction.
482
+ * @property {string[]} path - Relationship path from root model.
483
+ */
484
+
485
+ /**
486
+ * FrontendModelGroup type.
487
+ * @typedef {object} FrontendModelGroup
488
+ * @property {string} column - Attribute name to group by.
489
+ * @property {string[]} path - Relationship path from root model.
490
+ */
491
+
492
+ /**
493
+ * FrontendModelPluck type.
494
+ * @typedef {object} FrontendModelPluck
495
+ * @property {string} column - Attribute name to pluck.
496
+ * @property {string[]} path - Relationship path from root model.
497
+ */
498
+
499
+ /**
500
+ * Runs normalize sort direction.
501
+ * @param {?} direction - Direction value.
502
+ * @returns {"asc" | "desc"} - Normalized direction.
503
+ */
504
+ function normalizeSortDirection(direction) {
505
+ if (typeof direction !== "string") {
506
+ throw new Error(`Invalid sort direction type: ${typeof direction}`)
507
+ }
508
+
509
+ const normalizedDirection = direction.trim().toLowerCase()
510
+
511
+ if (normalizedDirection !== "asc" && normalizedDirection !== "desc") {
512
+ throw new Error(`Invalid sort direction: ${direction}`)
513
+ }
514
+
515
+ return normalizedDirection
516
+ }
517
+
518
+ /**
519
+ * Check whether a value is a two-item `[column, direction]` sort tuple.
520
+ * @param {?} value - Candidate tuple.
521
+ * @returns {value is [string, string]} - Whether value is a sort tuple.
522
+ */
523
+ function sortTuple(value) {
524
+ if (!Array.isArray(value)) return false
525
+ if (value.length !== 2) return false
526
+ if (typeof value[0] !== "string") return false
527
+ if (typeof value[1] !== "string") return false
528
+ if (value[0].trim().length < 1) return false
529
+
530
+ const direction = value[1].trim().toLowerCase()
531
+
532
+ return direction === "asc" || direction === "desc"
533
+ }
534
+
535
+ /**
536
+ * Check whether a value is a structured sort descriptor with a relationship path.
537
+ * @param {?} value - Candidate descriptor.
538
+ * @returns {value is {column: string, direction: string, path: string[]}} - Whether value is an explicit sort descriptor object.
539
+ */
540
+ function sortDescriptor(value) {
541
+ if (!isPlainObject(value)) return false
542
+ if (!("column" in value) || !("direction" in value) || !("path" in value)) return false
543
+ if (typeof value.column !== "string") return false
544
+ if (typeof value.direction !== "string") return false
545
+ if (!Array.isArray(value.path)) return false
546
+
547
+ return value.path.every((pathEntry) => typeof pathEntry === "string")
548
+ }
549
+
550
+ /**
551
+ * Parse a string shorthand into a sort descriptor.
552
+ * @param {string} sortValue - Sort string.
553
+ * @param {string[]} [path] - Relationship path.
554
+ * @returns {FrontendModelSort} - Normalized sort descriptor.
555
+ */
556
+ function parseSortString(sortValue, path = []) {
557
+ const trimmed = sortValue.trim()
558
+
559
+ if (trimmed.length < 1) {
560
+ throw new Error("sort value must be a non-empty string")
561
+ }
562
+
563
+ if (trimmed.startsWith("-")) {
564
+ const column = trimmed.slice(1).trim()
565
+
566
+ if (column.length < 1) {
567
+ throw new Error(`Invalid sort definition: ${sortValue}`)
568
+ }
569
+
570
+ return {
571
+ column,
572
+ direction: "desc",
573
+ path: [...path]
574
+ }
575
+ }
576
+
577
+ const sortParts = trimmed.split(/\s+/).filter(Boolean)
578
+
579
+ if (sortParts.length > 2) {
580
+ throw new Error(`Invalid sort definition: ${sortValue}`)
581
+ }
582
+
583
+ const column = sortParts[0]
584
+
585
+ if (column.length < 1) {
586
+ throw new Error(`Invalid sort definition: ${sortValue}`)
587
+ }
588
+
589
+ const direction = sortParts.length === 2
590
+ ? normalizeSortDirection(sortParts[1])
591
+ : "asc"
592
+
593
+ return {
594
+ column,
595
+ direction,
596
+ path: [...path]
597
+ }
598
+ }
599
+
600
+ /**
601
+ * Parse a tuple shorthand into a sort descriptor.
602
+ * @param {[string, string]} sortValue - Sort tuple.
603
+ * @param {string[]} [path] - Relationship path.
604
+ * @returns {FrontendModelSort} - Normalized sort descriptor.
605
+ */
606
+ function parseSortTuple(sortValue, path = []) {
607
+ const [columnValue, directionValue] = sortValue
608
+ const column = columnValue.trim()
609
+
610
+ if (column.length < 1) {
611
+ throw new Error("sort tuple column must be a non-empty string")
612
+ }
613
+
614
+ return {
615
+ column,
616
+ direction: normalizeSortDirection(directionValue),
617
+ path: [...path]
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Normalize a nested object sort payload into flat sort descriptors.
623
+ * @param {Record<string, ?>} sortValue - Nested sort object.
624
+ * @param {string[]} path - Relationship path.
625
+ * @returns {FrontendModelSort[]} - Normalized sort descriptors.
626
+ */
627
+ function normalizeSortObject(sortValue, path) {
628
+ /**
629
+ * Normalized sorts.
630
+ @type {FrontendModelSort[]} */
631
+ const normalizedSorts = []
632
+
633
+ for (const [sortKey, sortEntry] of Object.entries(sortValue)) {
634
+ if (typeof sortEntry === "string") {
635
+ normalizedSorts.push({
636
+ column: sortKey,
637
+ direction: normalizeSortDirection(sortEntry),
638
+ path: [...path]
639
+ })
640
+ continue
641
+ }
642
+
643
+ if (sortTuple(sortEntry)) {
644
+ normalizedSorts.push(parseSortTuple(sortEntry, [...path, sortKey]))
645
+ continue
646
+ }
647
+
648
+ if (Array.isArray(sortEntry)) {
649
+ if (sortEntry.length < 1) {
650
+ throw new Error(`Invalid sort definition for "${sortKey}": empty array`)
651
+ }
652
+
653
+ for (const nestedSortEntry of sortEntry) {
654
+ if (!sortTuple(nestedSortEntry)) {
655
+ throw new Error(`Invalid sort definition for "${sortKey}": expected [column, direction] tuples`)
656
+ }
657
+
658
+ normalizedSorts.push(parseSortTuple(nestedSortEntry, [...path, sortKey]))
659
+ }
660
+ continue
661
+ }
662
+
663
+ if (isPlainObject(sortEntry)) {
664
+ normalizedSorts.push(...normalizeSortObject(sortEntry, [...path, sortKey]))
665
+ continue
666
+ }
667
+
668
+ throw new Error(`Invalid sort definition for "${sortKey}": ${typeof sortEntry}`)
669
+ }
670
+
671
+ return normalizedSorts
672
+ }
673
+
674
+ /**
675
+ * Normalize any supported sort payload into flat sort descriptors.
676
+ * @param {?} sort - Sort payload.
677
+ * @returns {FrontendModelSort[]} - Normalized sort definitions.
678
+ */
679
+ export function normalizeSort(sort) {
680
+ if (!sort) return []
681
+
682
+ if (typeof sort === "string") {
683
+ return [parseSortString(sort)]
684
+ }
685
+
686
+ if (sortTuple(sort)) {
687
+ return [parseSortTuple(sort)]
688
+ }
689
+
690
+ if (sortDescriptor(sort)) {
691
+ return [{
692
+ column: sort.column.trim(),
693
+ direction: normalizeSortDirection(sort.direction),
694
+ path: [...sort.path]
695
+ }]
696
+ }
697
+
698
+ if (isPlainObject(sort)) {
699
+ return normalizeSortObject(sort, [])
700
+ }
701
+
702
+ if (Array.isArray(sort)) {
703
+ /**
704
+ * Normalized.
705
+ @type {FrontendModelSort[]} */
706
+ const normalized = []
707
+
708
+ for (const sortEntry of sort) {
709
+ if (typeof sortEntry === "string") {
710
+ normalized.push(parseSortString(sortEntry))
711
+ continue
712
+ }
713
+
714
+ if (sortTuple(sortEntry)) {
715
+ normalized.push(parseSortTuple(sortEntry))
716
+ continue
717
+ }
718
+
719
+ if (sortDescriptor(sortEntry)) {
720
+ normalized.push({
721
+ column: sortEntry.column.trim(),
722
+ direction: normalizeSortDirection(sortEntry.direction),
723
+ path: [...sortEntry.path]
724
+ })
725
+ continue
726
+ }
727
+
728
+ if (isPlainObject(sortEntry)) {
729
+ normalized.push(...normalizeSortObject(sortEntry, []))
730
+ continue
731
+ }
732
+
733
+ throw new Error(`Invalid sort entry type: ${typeof sortEntry}`)
734
+ }
735
+
736
+ return normalized
737
+ }
738
+
739
+ throw new Error(`Invalid sort type: ${typeof sort}`)
740
+ }
741
+
742
+ /**
743
+ * Parse a string shorthand into a group descriptor.
744
+ * @param {string} groupValue - Group string.
745
+ * @param {string[]} [path] - Relationship path.
746
+ * @returns {FrontendModelGroup} - Normalized group descriptor.
747
+ */
748
+ function parseGroupString(groupValue, path = []) {
749
+ const trimmed = groupValue.trim()
750
+
751
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
752
+ throw new Error(`Invalid group column: ${groupValue}`)
753
+ }
754
+
755
+ return {
756
+ column: trimmed,
757
+ path: [...path]
758
+ }
759
+ }
760
+
761
+ /**
762
+ * Check whether a value is a structured column/path descriptor.
763
+ * @param {?} value - Candidate descriptor.
764
+ * @returns {value is {column: string, path: string[]}} - Whether candidate is an explicit column descriptor object.
765
+ */
766
+ function columnPathDescriptor(value) {
767
+ if (!isPlainObject(value)) return false
768
+ if (!("column" in value) || !("path" in value)) return false
769
+ if (typeof value.column !== "string") return false
770
+ if (!Array.isArray(value.path)) return false
771
+
772
+ return value.path.every((pathEntry) => typeof pathEntry === "string")
773
+ }
774
+
775
+ /**
776
+ * Normalize a nested object column projection payload into flat descriptors.
777
+ * @template {{column: string, path: string[]}} T
778
+ * @param {Record<string, ?>} value - Nested projection object.
779
+ * @param {string[]} path - Relationship path.
780
+ * @param {(columnValue: string, path?: string[]) => T} parseString - String projection parser.
781
+ * @param {string} label - Projection label for errors.
782
+ * @returns {T[]} - Normalized projection descriptors.
783
+ */
784
+ function normalizeColumnProjectionObject(value, path, parseString, label) {
785
+ /**
786
+ * Normalized.
787
+ @type {T[]} */
788
+ const normalized = []
789
+
790
+ for (const [projectionKey, projectionEntry] of Object.entries(value)) {
791
+ if (typeof projectionEntry === "string") {
792
+ normalized.push(parseString(projectionEntry, [...path, projectionKey]))
793
+ continue
794
+ }
795
+
796
+ if (Array.isArray(projectionEntry)) {
797
+ if (projectionEntry.length < 1) {
798
+ throw new Error(`Invalid ${label} definition for "${projectionKey}": empty array`)
799
+ }
800
+
801
+ for (const nestedProjectionEntry of projectionEntry) {
802
+ if (typeof nestedProjectionEntry !== "string") {
803
+ throw new Error(`Invalid ${label} definition for "${projectionKey}": expected string columns`)
804
+ }
805
+
806
+ normalized.push(parseString(nestedProjectionEntry, [...path, projectionKey]))
807
+ }
808
+
809
+ continue
810
+ }
811
+
812
+ if (isPlainObject(projectionEntry)) {
813
+ normalized.push(...normalizeColumnProjectionObject(projectionEntry, [...path, projectionKey], parseString, label))
814
+ continue
815
+ }
816
+
817
+ throw new Error(`Invalid ${label} definition for "${projectionKey}": ${typeof projectionEntry}`)
818
+ }
819
+
820
+ return normalized
821
+ }
822
+
823
+ /**
824
+ * Normalize any supported group payload into flat group descriptors.
825
+ * @param {?} group - Group payload.
826
+ * @returns {FrontendModelGroup[]} - Normalized group definitions.
827
+ */
828
+ export function normalizeGroup(group) {
829
+ if (!group) return []
830
+
831
+ if (typeof group === "string") {
832
+ return [parseGroupString(group)]
833
+ }
834
+
835
+ if (columnPathDescriptor(group)) {
836
+ return [{
837
+ column: parseGroupString(group.column).column,
838
+ path: [...group.path]
839
+ }]
840
+ }
841
+
842
+ if (isPlainObject(group)) {
843
+ return normalizeColumnProjectionObject(group, [], parseGroupString, "group")
844
+ }
845
+
846
+ if (Array.isArray(group)) {
847
+ /**
848
+ * Normalized.
849
+ @type {FrontendModelGroup[]} */
850
+ const normalized = []
851
+
852
+ for (const groupEntry of group) {
853
+ if (typeof groupEntry === "string") {
854
+ normalized.push(parseGroupString(groupEntry))
855
+ continue
856
+ }
857
+
858
+ if (columnPathDescriptor(groupEntry)) {
859
+ normalized.push({
860
+ column: parseGroupString(groupEntry.column).column,
861
+ path: [...groupEntry.path]
862
+ })
863
+ continue
864
+ }
865
+
866
+ if (isPlainObject(groupEntry)) {
867
+ normalized.push(...normalizeColumnProjectionObject(groupEntry, [], parseGroupString, "group"))
868
+ continue
869
+ }
870
+
871
+ throw new Error(`Invalid group entry type: ${typeof groupEntry}`)
872
+ }
873
+
874
+ return normalized
875
+ }
876
+
877
+ throw new Error(`Invalid group type: ${typeof group}`)
878
+ }
879
+
880
+ /**
881
+ * Parse a string shorthand into a pluck descriptor.
882
+ * @param {string} pluckValue - Pluck string.
883
+ * @param {string[]} [path] - Relationship path.
884
+ * @returns {FrontendModelPluck} - Normalized pluck descriptor.
885
+ */
886
+ function parsePluckString(pluckValue, path = []) {
887
+ const trimmed = pluckValue.trim()
888
+
889
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
890
+ throw new Error(`Invalid pluck column: ${pluckValue}`)
891
+ }
892
+
893
+ return {
894
+ column: trimmed,
895
+ path: [...path]
896
+ }
897
+ }
898
+
899
+ /**
900
+ * Normalize any supported pluck payload into flat pluck descriptors.
901
+ * @param {?} pluck - Pluck payload.
902
+ * @returns {FrontendModelPluck[]} - Normalized pluck definitions.
903
+ */
904
+ export function normalizePluck(pluck) {
905
+ if (!pluck) return []
906
+
907
+ if (typeof pluck === "string") {
908
+ return [parsePluckString(pluck)]
909
+ }
910
+
911
+ if (columnPathDescriptor(pluck)) {
912
+ return [{
913
+ column: parsePluckString(pluck.column).column,
914
+ path: [...pluck.path]
915
+ }]
916
+ }
917
+
918
+ if (isPlainObject(pluck)) {
919
+ return normalizeColumnProjectionObject(pluck, [], parsePluckString, "pluck")
920
+ }
921
+
922
+ if (Array.isArray(pluck)) {
923
+ /**
924
+ * Normalized.
925
+ @type {FrontendModelPluck[]} */
926
+ const normalized = []
927
+
928
+ for (const pluckEntry of pluck) {
929
+ if (typeof pluckEntry === "string") {
930
+ normalized.push(parsePluckString(pluckEntry))
931
+ continue
932
+ }
933
+
934
+ if (columnPathDescriptor(pluckEntry)) {
935
+ normalized.push({
936
+ column: parsePluckString(pluckEntry.column).column,
937
+ path: [...pluckEntry.path]
938
+ })
939
+ continue
940
+ }
941
+
942
+ if (isPlainObject(pluckEntry)) {
943
+ normalized.push(...normalizeColumnProjectionObject(pluckEntry, [], parsePluckString, "pluck"))
944
+ continue
945
+ }
946
+
947
+ throw new Error(`Invalid pluck entry type: ${typeof pluckEntry}`)
948
+ }
949
+
950
+ return normalized
951
+ }
952
+
953
+ throw new Error(`Invalid pluck type: ${typeof pluck}`)
954
+ }
955
+
956
+ /**
957
+ * Runs frontend model resource attributes.
958
+ * @param {typeof import("./base.js").default} modelClass - Model class.
959
+ * @returns {Set<string>} - Resource attribute names.
960
+ */
961
+ function frontendModelResourceAttributes(modelClass) {
962
+ const resourceConfig = /**
963
+ * Narrows the runtime value to the documented type.
964
+ @type {Record<string, ?>} */ (modelClass.resourceConfig())
965
+ const attributes = resourceConfig.attributes
966
+
967
+ if (Array.isArray(attributes)) {
968
+ return new Set(attributes)
969
+ }
970
+
971
+ if (isPlainObject(attributes)) {
972
+ return new Set(Object.keys(attributes))
973
+ }
974
+
975
+ return new Set()
976
+ }
977
+
978
+ /**
979
+ * Runs frontend model pluck target model class.
980
+ * @param {typeof import("./base.js").default} modelClass - Root model class.
981
+ * @param {string[]} path - Relationship path.
982
+ * @returns {typeof import("./base.js").default} - Target model class for path.
983
+ */
984
+ function frontendModelPluckTargetModelClass(modelClass, path) {
985
+ let targetModelClass = modelClass
986
+
987
+ for (const relationshipName of path) {
988
+ const relationshipDefinitions = typeof targetModelClass.relationshipDefinitions === "function"
989
+ ? targetModelClass.relationshipDefinitions()
990
+ : {}
991
+ const relationshipModelClasses = typeof targetModelClass.relationshipModelClasses === "function"
992
+ ? targetModelClass.relationshipModelClasses()
993
+ : {}
994
+ const relationshipDefinition = relationshipDefinitions[relationshipName]
995
+ const relationshipTargetModelClass = resolveFrontendModelClass(relationshipModelClasses[relationshipName])
996
+
997
+ if (!relationshipDefinition) {
998
+ throw new Error(`Unknown pluck relationship "${relationshipName}" for ${targetModelClass.name}`)
999
+ }
1000
+
1001
+ if (!relationshipTargetModelClass) {
1002
+ throw new Error(`No relationship model class configured for ${targetModelClass.name}#${relationshipName}`)
1003
+ }
1004
+
1005
+ targetModelClass = relationshipTargetModelClass
1006
+ }
1007
+
1008
+ return targetModelClass
1009
+ }
1010
+
1011
+ /**
1012
+ * Runs validate pluck definitions.
1013
+ * @param {object} args - Pluck validation args.
1014
+ * @param {typeof import("./base.js").default} args.modelClass - Root model class.
1015
+ * @param {FrontendModelPluck[]} args.pluck - Pluck descriptors.
1016
+ * @returns {FrontendModelPluck[]} - Validated pluck descriptors.
1017
+ */
1018
+ function validatePluckDefinitions({modelClass, pluck}) {
1019
+ return pluck.map((pluckEntry) => {
1020
+ const targetModelClass = frontendModelPluckTargetModelClass(modelClass, pluckEntry.path)
1021
+ const targetAttributes = frontendModelResourceAttributes(targetModelClass)
1022
+
1023
+ if (!targetAttributes.has(pluckEntry.column)) {
1024
+ throw new Error(`Unknown pluck column "${pluckEntry.column}" for ${targetModelClass.name}`)
1025
+ }
1026
+
1027
+ return {
1028
+ column: pluckEntry.column,
1029
+ path: [...pluckEntry.path]
1030
+ }
1031
+ })
1032
+ }
1033
+
1034
+ /**
1035
+ * Runs serialize find conditions.
1036
+ * @param {Record<string, ?>} conditions - findBy conditions.
1037
+ * @returns {string} - Serialized conditions for error messages.
1038
+ */
1039
+ function serializeFindConditions(conditions) {
1040
+ try {
1041
+ return JSON.stringify(conditions)
1042
+ } catch {
1043
+ return "[unserializable conditions]"
1044
+ }
1045
+ }
1046
+
1047
+ /**
1048
+ * Runs normalize integer argument.
1049
+ * @param {?} value - Candidate integer value.
1050
+ * @param {string} argumentName - Argument name for errors.
1051
+ * @param {object} options - Validation options.
1052
+ * @param {number} options.min - Minimum allowed value.
1053
+ * @returns {number} - Normalized integer value.
1054
+ */
1055
+ function normalizeIntegerArgument(value, argumentName, {min}) {
1056
+ if (typeof value !== "number" || !Number.isInteger(value)) {
1057
+ throw new Error(`${argumentName} must be an integer number`)
1058
+ }
1059
+
1060
+ if (value < min) {
1061
+ throw new Error(`${argumentName} must be greater than or equal to ${min}`)
1062
+ }
1063
+
1064
+ return value
1065
+ }
1066
+
1067
+ /**
1068
+ * Runs reverse sort direction.
1069
+ * @param {"asc" | "desc"} direction - Current sort direction.
1070
+ * @returns {"asc" | "desc"} - Reversed direction.
1071
+ */
1072
+ function reverseSortDirection(direction) {
1073
+ return direction === "asc" ? "desc" : "asc"
1074
+ }
1075
+
1076
+ /**
1077
+ * Query wrapper for frontend model commands.
1078
+ * @template {typeof import("./base.js").default} T
1079
+ */
1080
+ export default class FrontendModelQuery {
1081
+ /**
1082
+ * Ransack.
1083
+ @type {Record<string, ?>[]} */
1084
+ _ransack = []
1085
+ /**
1086
+ * Searches.
1087
+ @type {FrontendModelSearch[]} */
1088
+ _searches = []
1089
+ /**
1090
+ * Sort.
1091
+ @type {FrontendModelSort[]} */
1092
+ _sort = []
1093
+ /**
1094
+ * Group.
1095
+ @type {FrontendModelGroup[]} */
1096
+ _group = []
1097
+
1098
+ /**
1099
+ * Runs constructor.
1100
+ * @param {object} args - Constructor args.
1101
+ * @param {T} args.modelClass - Frontend model class.
1102
+ * @param {import("../database/query/index.js").NestedPreloadRecord} [args.preload] - Preload map.
1103
+ */
1104
+ constructor({modelClass, preload = {}}) {
1105
+ this.modelClass = modelClass
1106
+ this._preload = normalizePreload(preload)
1107
+ this._joins = {}
1108
+ this._where = {}
1109
+ this._searches = []
1110
+ /**
1111
+ * Narrows the runtime value to the documented type.
1112
+ @type {Record<string, string[]>} */
1113
+ this._select = {}
1114
+ /**
1115
+ * Narrows the runtime value to the documented type.
1116
+ @type {Record<string, string[]>} */
1117
+ this._selectsExtra = {}
1118
+ this._sort = []
1119
+ this._group = []
1120
+ this._distinct = false
1121
+ this._limit = null
1122
+ this._offset = null
1123
+ this._page = null
1124
+ this._perPage = null
1125
+ /**
1126
+ * Narrows the runtime value to the documented type.
1127
+ @type {Array<{attributeName: string, relationshipName: string, where?: Record<string, ?>}>} */
1128
+ this._withCount = []
1129
+ /**
1130
+ * Narrows the runtime value to the documented type.
1131
+ @type {Array<string | Record<string, ?>>} */
1132
+ this._queryData = []
1133
+ /**
1134
+ * Per-record ability spec. Normalized to a list of
1135
+ * `{modelName, actions}` entries — one entry per model that should
1136
+ * have ability results attached. The root query's model class
1137
+ * name is implicit via `"__root__"` when the caller used the flat
1138
+ * array form.
1139
+ * @type {Array<{modelName: string, actions: string[]}>}
1140
+ */
1141
+ this._abilities = []
1142
+ }
1143
+
1144
+ /**
1145
+ * Tell the backend to evaluate one or more ability actions against
1146
+ * each returned record (and its preloaded relations, when keyed by
1147
+ * model name) and ship the results back so the frontend can read
1148
+ * them via `record.can(action)`.
1149
+ *
1150
+ * Flat form — applies to the query's own model class:
1151
+ * ```
1152
+ * const timelogs = await Timelog.where({taskId})
1153
+ * .abilities(["update", "destroy"])
1154
+ * .toArray()
1155
+ * timelogs[0].can("update") // → boolean
1156
+ * ```
1157
+ *
1158
+ * Keyed form — targets records by model name, useful for preloaded
1159
+ * children:
1160
+ * ```
1161
+ * const project = await Project
1162
+ * .preload("timelogs")
1163
+ * .abilities({Timelog: ["update", "destroy"]})
1164
+ * .first()
1165
+ * project.timelogs().loaded()[0].can("update") // → boolean
1166
+ * ```
1167
+ *
1168
+ * Keys in the keyed form are the backend model names (as returned by
1169
+ * `ModelClass.getModelName()` / the `modelName` field of the
1170
+ * frontend-model resource config). Values are the ability-action
1171
+ * strings — typically `"update"` / `"destroy"` / `"create"` /
1172
+ * `"read"`, but any custom action registered on the resource's
1173
+ * authorization ability is accepted.
1174
+ * @param {string[] | Record<string, string[]>} spec
1175
+ * @returns {this}
1176
+ */
1177
+ abilities(spec) {
1178
+ for (const entry of normalizeAbilitiesSpec(spec, this.modelClass)) {
1179
+ this._mergeAbilityEntry(entry)
1180
+ }
1181
+
1182
+ return this
1183
+ }
1184
+
1185
+ /**
1186
+ * Runs merge ability entry.
1187
+ * @param {{modelName: string, actions: string[]}} entry
1188
+ * @returns {void}
1189
+ */
1190
+ _mergeAbilityEntry(entry) {
1191
+ const existing = this._abilities.find((candidate) => candidate.modelName === entry.modelName)
1192
+
1193
+ if (!existing) {
1194
+ this._abilities.push({actions: [...entry.actions], modelName: entry.modelName})
1195
+ return
1196
+ }
1197
+
1198
+ for (const action of entry.actions) {
1199
+ if (!existing.actions.includes(action)) existing.actions.push(action)
1200
+ }
1201
+ }
1202
+
1203
+ /**
1204
+ * Tell the backend index query to attach one or more association
1205
+ * counts to each returned record. Parses the same shapes as the
1206
+ * backend `ModelClassQuery#withCount`, then ships the normalized
1207
+ * entries as part of the `index` command payload.
1208
+ * @param {string | string[] | Record<string, boolean | {relationship?: string, where?: Record<string, ?>}>} spec
1209
+ * @returns {this}
1210
+ */
1211
+ withCount(spec) {
1212
+ for (const entry of normalizeWithCountFrontend(spec)) {
1213
+ this._withCount.push(entry)
1214
+ }
1215
+
1216
+ return this
1217
+ }
1218
+
1219
+ /**
1220
+ * Request one or more backend queryData entries for each returned
1221
+ * record. The spec is a name or nested-record shape matching the
1222
+ * `Model.queryData(name, fn)` registrations on the backend — the
1223
+ * frontend ships only these names; the SQL fragments stay server-
1224
+ * side. All resulting aliases are attached to the root record and
1225
+ * read back with `record.queryData(aliasName)`.
1226
+ * @param {string | Array<string | Record<string, ?>> | Record<string, ?>} spec
1227
+ * @returns {this}
1228
+ */
1229
+ queryData(spec) {
1230
+ if (spec == null) return this
1231
+
1232
+ this._queryData.push(/**
1233
+ * Narrows the runtime value to the documented type.
1234
+ @type {?} */ (spec))
1235
+
1236
+ return this
1237
+ }
1238
+
1239
+ /**
1240
+ * Runs where.
1241
+ * @param {Record<string, ?>} conditions - Root-model where conditions.
1242
+ * @returns {this} - Query with merged where conditions.
1243
+ */
1244
+ where(conditions) {
1245
+ this.modelClass.assertFindByConditions(conditions)
1246
+
1247
+ this._where = {
1248
+ ...this._where,
1249
+ ...conditions
1250
+ }
1251
+
1252
+ return this
1253
+ }
1254
+
1255
+ /**
1256
+ * Runs scope.
1257
+ * @param {import("../utils/model-scope.js").ModelScopeDescriptor} scopeDescriptor - Scope descriptor.
1258
+ * @returns {this} - Scoped query.
1259
+ */
1260
+ scope(scopeDescriptor) {
1261
+ if (!isModelScopeDescriptor(scopeDescriptor)) {
1262
+ throw new Error("scope() expects a descriptor returned by defineScope(...).scope(...)")
1263
+ }
1264
+
1265
+ if (scopeDescriptor.modelClass !== this.modelClass) {
1266
+ throw new Error(`Cannot apply ${scopeDescriptor.modelClass.name} scope to ${this.modelClass.name} query`)
1267
+ }
1268
+
1269
+ const scopedQuery = /**
1270
+ * Narrows the runtime value to the documented type.
1271
+ @type {this | void} */ (scopeDescriptor.callback({
1272
+ driver: null,
1273
+ modelClass: this.modelClass,
1274
+ query: this,
1275
+ table: null
1276
+ }, ...scopeDescriptor.scopeArgs))
1277
+
1278
+ return scopedQuery || this
1279
+ }
1280
+
1281
+ /**
1282
+ * Runs ransack.
1283
+ * @param {Record<string, ?>} params - Ransack-style params hash. Supports `s` key for sorting (e.g., `{s: "name asc"}`).
1284
+ * @returns {this} - Query with Ransack filters and sort applied.
1285
+ */
1286
+ ransack(params) {
1287
+ const {s, ...filterParams} = params
1288
+ const hasFilters = Object.keys(filterParams).length > 0
1289
+
1290
+ if (hasFilters) {
1291
+ normalizeRansackGroup(this.modelClass, filterParams)
1292
+ this._ransack.push(filterParams)
1293
+ }
1294
+
1295
+ if (typeof s === "string" && s.trim().length > 0) {
1296
+ const sorts = parseRansackSort(this.modelClass, s)
1297
+
1298
+ for (const sortDef of sorts) {
1299
+ this.sort([[sortDef.attribute, sortDef.direction]])
1300
+ }
1301
+ }
1302
+
1303
+ return this
1304
+ }
1305
+
1306
+ /**
1307
+ * Runs select with required root attributes.
1308
+ * @param {string[]} [requiredAttributes] - Extra required attributes for the root model.
1309
+ * @returns {Record<string, string[]>} - Select map with required root attributes merged when root select exists.
1310
+ */
1311
+ selectWithRequiredRootAttributes(requiredAttributes = []) {
1312
+ const rootModelName = this.modelClass.getModelName()
1313
+ const selectMap = /**
1314
+ * Narrows the runtime value to the documented type.
1315
+ @type {Record<string, string[]>} */ (this._select)
1316
+ const existingRootAttributes = selectMap[rootModelName]
1317
+
1318
+ if (!existingRootAttributes) {
1319
+ return selectMap
1320
+ }
1321
+
1322
+ const rootPrimaryKey = this.modelClass.primaryKey()
1323
+
1324
+ return {
1325
+ ...selectMap,
1326
+ [rootModelName]: Array.from(new Set([rootPrimaryKey, ...existingRootAttributes, ...requiredAttributes]))
1327
+ }
1328
+ }
1329
+
1330
+ /**
1331
+ * Runs preload.
1332
+ * @param {import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord>} preload - Preload to merge.
1333
+ * @returns {this} - Query with merged preloads.
1334
+ */
1335
+ preload(preload) {
1336
+ mergePreloadRecord(this._preload, normalizePreload(preload))
1337
+
1338
+ return this
1339
+ }
1340
+
1341
+ /**
1342
+ * Runs select.
1343
+ * @param {Record<string, string[] | string> | string | string[]} select - Model-aware attribute select map or root-model shorthand.
1344
+ * @returns {this} - Query with merged selected attributes.
1345
+ */
1346
+ select(select) {
1347
+ mergeSelectRecord(this._select, normalizeSelect(select, this.modelClass.getModelName()))
1348
+
1349
+ return this
1350
+ }
1351
+
1352
+ /**
1353
+ * Like `select(...)`, but keeps the default serialized attributes and loads
1354
+ * the given extras in addition (for example attributes declared
1355
+ * `selectedByDefault: false`). Keyed by model name, with root-model shorthand.
1356
+ * @param {Record<string, string[] | string> | string | string[]} select - Extra attributes to load, keyed by model name or root-model shorthand.
1357
+ * @returns {this} - Query with merged extra selected attributes.
1358
+ */
1359
+ selectsExtra(select) {
1360
+ mergeSelectRecord(this._selectsExtra, normalizeSelect(select, this.modelClass.getModelName()))
1361
+
1362
+ return this
1363
+ }
1364
+
1365
+ /**
1366
+ * Runs joins.
1367
+ * @param {Record<string, ?> | Array<Record<string, ?>>} joins - Relationship descriptor joins.
1368
+ * @returns {this} - Query with merged joins.
1369
+ */
1370
+ joins(joins) {
1371
+ mergeJoinRecord(this._joins, normalizeJoins(joins))
1372
+
1373
+ return this
1374
+ }
1375
+
1376
+ /**
1377
+ * Returns the search result.
1378
+ * @param {string[]} path - Relationship path.
1379
+ * @param {string} column - Column or attribute name.
1380
+ * @param {"eq" | "like" | "notEq" | "gt" | "gteq" | "lt" | "lteq" | ">" | ">=" | "<" | "<="} operator - Search operator.
1381
+ * @param {?} value - Search value.
1382
+ * @returns {this} - Query with appended search.
1383
+ */
1384
+ search(path, column, operator, value) {
1385
+ if (!Array.isArray(path)) {
1386
+ throw new Error(`search path must be an array, got: ${typeof path}`)
1387
+ }
1388
+
1389
+ for (const pathEntry of path) {
1390
+ if (typeof pathEntry !== "string" || pathEntry.length < 1) {
1391
+ throw new Error("search path entries must be non-empty strings")
1392
+ }
1393
+ }
1394
+
1395
+ if (typeof column !== "string" || column.length < 1) {
1396
+ throw new Error("search column must be a non-empty string")
1397
+ }
1398
+
1399
+ if (typeof operator !== "string" || operator.length < 1) {
1400
+ throw new Error("search operator must be a non-empty string")
1401
+ }
1402
+
1403
+ const normalizedOperator = normalizeSearchOperator(operator)
1404
+
1405
+ this._searches.push({
1406
+ column,
1407
+ operator: normalizedOperator,
1408
+ path: [...path],
1409
+ value
1410
+ })
1411
+
1412
+ return this
1413
+ }
1414
+
1415
+ /**
1416
+ * Runs sort.
1417
+ * @param {string | string[] | [string, string] | Array<[string, string]> | Record<string, ?> | Array<Record<string, ?>>} sort - Sort definition(s).
1418
+ * @returns {this} - Query with appended sort definitions.
1419
+ */
1420
+ sort(sort) {
1421
+ this._sort.push(...normalizeSort(sort))
1422
+
1423
+ return this
1424
+ }
1425
+
1426
+ /**
1427
+ * Runs order.
1428
+ * @param {string | string[] | [string, string] | Array<[string, string]> | Record<string, ?> | Array<Record<string, ?>>} order - Order definition(s).
1429
+ * @returns {this} - Query with appended sort definitions.
1430
+ */
1431
+ order(order) {
1432
+ return this.sort(order)
1433
+ }
1434
+
1435
+ /**
1436
+ * Runs group.
1437
+ * @param {string | string[] | Record<string, ?> | Array<Record<string, ?>>} group - Group definition(s).
1438
+ * @returns {this} - Query with appended group definitions.
1439
+ */
1440
+ group(group) {
1441
+ this._group.push(...normalizeGroup(group))
1442
+
1443
+ return this
1444
+ }
1445
+
1446
+ /**
1447
+ * Runs distinct.
1448
+ * @param {boolean} [value] - Whether to request distinct rows.
1449
+ * @returns {this} - Query with distinct flag.
1450
+ */
1451
+ distinct(value = true) {
1452
+ if (typeof value !== "boolean") {
1453
+ throw new Error(`distinct must be a boolean, got: ${typeof value}`)
1454
+ }
1455
+
1456
+ this._distinct = value
1457
+
1458
+ return this
1459
+ }
1460
+
1461
+ /**
1462
+ * Returns the limit result.
1463
+ * @param {number} value - Maximum number of records.
1464
+ * @returns {this} - Query with limit.
1465
+ */
1466
+ limit(value) {
1467
+ this._limit = normalizeIntegerArgument(value, "limit", {min: 0})
1468
+ this._page = null
1469
+
1470
+ return this
1471
+ }
1472
+
1473
+ /**
1474
+ * Runs offset.
1475
+ * @param {number} value - Number of records to skip.
1476
+ * @returns {this} - Query with offset.
1477
+ */
1478
+ offset(value) {
1479
+ this._offset = normalizeIntegerArgument(value, "offset", {min: 0})
1480
+ this._page = null
1481
+
1482
+ return this
1483
+ }
1484
+
1485
+ /**
1486
+ * Runs page.
1487
+ * @param {number} pageNumber - 1-based page number.
1488
+ * @returns {this} - Query with page applied.
1489
+ */
1490
+ page(pageNumber) {
1491
+ this._page = normalizeIntegerArgument(pageNumber, "page", {min: 1})
1492
+ const pageSize = this._perPage || 30
1493
+
1494
+ this._limit = pageSize
1495
+ this._offset = (this._page - 1) * pageSize
1496
+
1497
+ return this
1498
+ }
1499
+
1500
+ /**
1501
+ * Runs per page.
1502
+ * @param {number} perPage - Page size.
1503
+ * @returns {this} - Query with per-page applied.
1504
+ */
1505
+ perPage(perPage) {
1506
+ this._perPage = normalizeIntegerArgument(perPage, "perPage", {min: 1})
1507
+
1508
+ if (this._page !== null) {
1509
+ this._limit = this._perPage
1510
+ this._offset = (this._page - 1) * this._perPage
1511
+ }
1512
+
1513
+ return this
1514
+ }
1515
+
1516
+ /**
1517
+ * Runs clone.
1518
+ * @returns {FrontendModelQuery<T>} - Cloned query instance.
1519
+ */
1520
+ clone() {
1521
+ const newQuery = /**
1522
+ * Narrows the runtime value to the documented type.
1523
+ @type {FrontendModelQuery<T>} */ (new FrontendModelQuery({
1524
+ modelClass: this.modelClass,
1525
+ preload: normalizePreload(this._preload)
1526
+ }))
1527
+
1528
+ newQuery._joins = normalizeJoins(this._joins)
1529
+ newQuery._where = {...this._where}
1530
+ newQuery._ransack = this._ransack.map((ransackParams) => ({...ransackParams}))
1531
+ newQuery._searches = this._searches.map((search) => ({
1532
+ column: search.column,
1533
+ operator: search.operator,
1534
+ path: [...search.path],
1535
+ value: search.value
1536
+ }))
1537
+ newQuery._select = normalizeSelect(this._select)
1538
+ newQuery._selectsExtra = normalizeSelect(this._selectsExtra)
1539
+ newQuery._sort = this._sort.map((sortEntry) => ({
1540
+ column: sortEntry.column,
1541
+ direction: sortEntry.direction,
1542
+ path: [...sortEntry.path]
1543
+ }))
1544
+ newQuery._group = this._group.map((groupEntry) => ({
1545
+ column: groupEntry.column,
1546
+ path: [...groupEntry.path]
1547
+ }))
1548
+ newQuery._distinct = this._distinct
1549
+ newQuery._limit = this._limit
1550
+ newQuery._offset = this._offset
1551
+ newQuery._page = this._page
1552
+ newQuery._perPage = this._perPage
1553
+ newQuery._withCount = this._withCount.map((entry) => ({
1554
+ attributeName: entry.attributeName,
1555
+ relationshipName: entry.relationshipName,
1556
+ where: entry.where ? {...entry.where} : undefined
1557
+ }))
1558
+ newQuery._queryData = this._queryData.map((entry) => (
1559
+ typeof entry === "string" ? entry : {...entry}
1560
+ ))
1561
+ newQuery._abilities = this._abilities.map((entry) => ({
1562
+ actions: [...entry.actions],
1563
+ modelName: entry.modelName
1564
+ }))
1565
+
1566
+ return newQuery
1567
+ }
1568
+
1569
+ /**
1570
+ * Runs get model class.
1571
+ * @returns {T} - Root model class.
1572
+ */
1573
+ getModelClass() {
1574
+ return this.modelClass
1575
+ }
1576
+
1577
+ /**
1578
+ * Runs preload payload.
1579
+ * @returns {Record<string, ?>} - Payload preload hash when present.
1580
+ */
1581
+ preloadPayload() {
1582
+ if (Object.keys(this._preload).length === 0) return {}
1583
+
1584
+ return {preload: this._preload}
1585
+ }
1586
+
1587
+ /**
1588
+ * Runs with count payload.
1589
+ * @returns {Record<string, ?>} - Payload withCount array when present.
1590
+ */
1591
+ withCountPayload() {
1592
+ if (this._withCount.length === 0) return {}
1593
+
1594
+ return {
1595
+ withCount: this._withCount.map((entry) => ({
1596
+ attributeName: entry.attributeName,
1597
+ relationshipName: entry.relationshipName,
1598
+ where: entry.where || undefined
1599
+ }))
1600
+ }
1601
+ }
1602
+
1603
+ /**
1604
+ * Runs abilities payload.
1605
+ * @returns {Record<string, ?>} - Payload abilities array when present.
1606
+ */
1607
+ abilitiesPayload() {
1608
+ if (this._abilities.length === 0) return {}
1609
+
1610
+ return {
1611
+ abilities: this._abilities.map((entry) => ({
1612
+ actions: [...entry.actions],
1613
+ modelName: entry.modelName
1614
+ }))
1615
+ }
1616
+ }
1617
+
1618
+ /**
1619
+ * Runs query data payload.
1620
+ * @returns {Record<string, ?>} - Payload queryData spec when present.
1621
+ */
1622
+ queryDataPayload() {
1623
+ if (this._queryData.length === 0) return {}
1624
+
1625
+ // Single accumulated spec goes on the wire verbatim. The backend
1626
+ // normalizer accepts string/array/object at each level, so we can
1627
+ // ship multiple `.queryData(...)` calls as an array.
1628
+ return {
1629
+ queryData: this._queryData.length === 1 ? this._queryData[0] : this._queryData
1630
+ }
1631
+ }
1632
+
1633
+ /**
1634
+ * Runs select payload.
1635
+ * @param {string[]} [requiredAttributes] - Extra required attributes for root model selection.
1636
+ * @returns {Record<string, ?>} - Payload select hash when present.
1637
+ */
1638
+ selectPayload(requiredAttributes = []) {
1639
+ const select = this.selectWithRequiredRootAttributes(requiredAttributes)
1640
+
1641
+ if (Object.keys(select).length === 0) return {}
1642
+
1643
+ return {select}
1644
+ }
1645
+
1646
+ /**
1647
+ * Runs selects extra payload.
1648
+ * @returns {Record<string, ?>} - Payload selectsExtra hash when present.
1649
+ */
1650
+ selectsExtraPayload() {
1651
+ if (Object.keys(this._selectsExtra).length === 0) return {}
1652
+
1653
+ return {selectsExtra: this._selectsExtra}
1654
+ }
1655
+
1656
+ /**
1657
+ * Runs search payload.
1658
+ * @returns {Record<string, ?>} - Payload searches array when present.
1659
+ */
1660
+ searchPayload() {
1661
+ if (this._searches.length === 0) return {}
1662
+
1663
+ return {
1664
+ searches: this._searches.map((search) => ({
1665
+ column: search.column,
1666
+ operator: search.operator,
1667
+ path: [...search.path],
1668
+ value: search.value
1669
+ }))
1670
+ }
1671
+ }
1672
+
1673
+ /**
1674
+ * Runs ransack payload.
1675
+ * @returns {Record<string, ?>} - Payload ransack hash when present.
1676
+ */
1677
+ ransackPayload() {
1678
+ if (this._ransack.length === 0) return {}
1679
+
1680
+ if (this._ransack.length === 1) {
1681
+ return {ransack: this._ransack[0]}
1682
+ }
1683
+
1684
+ return {
1685
+ ransack: {
1686
+ g: this._ransack,
1687
+ m: "and"
1688
+ }
1689
+ }
1690
+ }
1691
+
1692
+ /**
1693
+ * Runs joins payload.
1694
+ * @returns {Record<string, ?>} - Payload joins hash when present.
1695
+ */
1696
+ joinsPayload() {
1697
+ if (Object.keys(this._joins).length === 0) return {}
1698
+
1699
+ return {
1700
+ joins: normalizeJoins(this._joins)
1701
+ }
1702
+ }
1703
+
1704
+ /**
1705
+ * Runs sort payload.
1706
+ * @returns {Record<string, ?>} - Payload sort array when present.
1707
+ */
1708
+ sortPayload() {
1709
+ if (this._sort.length === 0) return {}
1710
+
1711
+ return {
1712
+ sort: this._sort.map((sortEntry) => ({
1713
+ column: sortEntry.column,
1714
+ direction: sortEntry.direction,
1715
+ path: [...sortEntry.path]
1716
+ }))
1717
+ }
1718
+ }
1719
+
1720
+ /**
1721
+ * Runs group payload.
1722
+ * @returns {Record<string, ?>} - Payload group array when present.
1723
+ */
1724
+ groupPayload() {
1725
+ if (this._group.length === 0) return {}
1726
+
1727
+ return {
1728
+ group: this._group.map((groupEntry) => ({
1729
+ column: groupEntry.column,
1730
+ path: [...groupEntry.path]
1731
+ }))
1732
+ }
1733
+ }
1734
+
1735
+ /**
1736
+ * Runs distinct payload.
1737
+ * @returns {Record<string, ?>} - Payload distinct flag when enabled.
1738
+ */
1739
+ distinctPayload() {
1740
+ if (!this._distinct) return {}
1741
+
1742
+ return {
1743
+ distinct: true
1744
+ }
1745
+ }
1746
+
1747
+ /**
1748
+ * Runs where payload.
1749
+ * @returns {Record<string, ?>} - Payload where hash when present.
1750
+ */
1751
+ wherePayload() {
1752
+ if (Object.keys(this._where).length === 0) return {}
1753
+
1754
+ return {
1755
+ where: this._where
1756
+ }
1757
+ }
1758
+
1759
+ /**
1760
+ * Runs pagination payload.
1761
+ * @returns {Record<string, ?>} - Payload pagination params when present.
1762
+ */
1763
+ paginationPayload() {
1764
+ /**
1765
+ * Payload.
1766
+ @type {Record<string, ?>} */
1767
+ const payload = {}
1768
+
1769
+ if (this._limit !== null) payload.limit = this._limit
1770
+ if (this._offset !== null) payload.offset = this._offset
1771
+ if (this._page !== null) payload.page = this._page
1772
+ if (this._perPage !== null) payload.perPage = this._perPage
1773
+
1774
+ return payload
1775
+ }
1776
+
1777
+ /**
1778
+ * Runs assert event query supported.
1779
+ * @returns {void}
1780
+ * @throws {Error} When the query contains list-only options that cannot filter a single lifecycle event.
1781
+ */
1782
+ assertEventQuerySupported() {
1783
+ /**
1784
+ * Unsupported options.
1785
+ @type {string[]} */
1786
+ const unsupportedOptions = []
1787
+
1788
+ if (this._sort.length > 0) unsupportedOptions.push("sort")
1789
+ if (this._group.length > 0) unsupportedOptions.push("group")
1790
+ if (this._distinct) unsupportedOptions.push("distinct")
1791
+ if (this._ransack.length > 0) unsupportedOptions.push("ransack")
1792
+ if (this._limit !== null || this._offset !== null || this._page !== null || this._perPage !== null) unsupportedOptions.push("pagination")
1793
+
1794
+ if (unsupportedOptions.length === 0) return
1795
+
1796
+ throw new Error(`Frontend model event queries do not support ${unsupportedOptions.join(", ")}`)
1797
+ }
1798
+
1799
+ /**
1800
+ * Runs event projection payload.
1801
+ * @returns {FrontendModelProjectionPayload} - Projection payload used when serializing lifecycle events.
1802
+ */
1803
+ eventProjectionPayload() {
1804
+ this.assertEventQuerySupported()
1805
+
1806
+ return {
1807
+ ...this.preloadPayload(),
1808
+ ...this.selectPayload(),
1809
+ ...this.selectsExtraPayload(),
1810
+ ...this.withCountPayload(),
1811
+ ...this.abilitiesPayload(),
1812
+ ...this.queryDataPayload()
1813
+ }
1814
+ }
1815
+
1816
+ /**
1817
+ * Runs event filter payload.
1818
+ * @returns {FrontendModelEventFilterPayload | null} - Query pieces used to match lifecycle events.
1819
+ */
1820
+ eventFilterPayload() {
1821
+ this.assertEventQuerySupported()
1822
+
1823
+ const payload = {
1824
+ ...this.joinsPayload(),
1825
+ ...this.searchPayload(),
1826
+ ...this.wherePayload()
1827
+ }
1828
+
1829
+ return Object.keys(payload).length === 0 ? null : payload
1830
+ }
1831
+
1832
+ /**
1833
+ * Returns the eventOptionsPayload result.
1834
+ * @returns {FrontendModelEventOptionsPayload} - Combined event filter and projection payload.
1835
+ */
1836
+ eventOptionsPayload() {
1837
+ const eventFilterPayload = this.eventFilterPayload()
1838
+
1839
+ return {
1840
+ eventFilterKey: eventFilterPayload ? frontendModelEventFilterKey(eventFilterPayload) : null,
1841
+ eventFilterPayload,
1842
+ projectionPayload: this.eventProjectionPayload()
1843
+ }
1844
+ }
1845
+
1846
+ /**
1847
+ * Runs load.
1848
+ * @returns {Promise<InstanceType<T>[]>} - Loaded model instances.
1849
+ */
1850
+ async load() {
1851
+ const response = await this.modelClass.executeCommand("index", {
1852
+ ...this.preloadPayload(),
1853
+ ...this.joinsPayload(),
1854
+ ...this.ransackPayload(),
1855
+ ...this.searchPayload(),
1856
+ ...this.selectPayload(),
1857
+ ...this.selectsExtraPayload(),
1858
+ ...this.groupPayload(),
1859
+ ...this.distinctPayload(),
1860
+ ...this.sortPayload(),
1861
+ ...this.wherePayload(),
1862
+ ...this.withCountPayload(),
1863
+ ...this.abilitiesPayload(),
1864
+ ...this.queryDataPayload(),
1865
+ ...this.paginationPayload()
1866
+ })
1867
+
1868
+ if (!response || typeof response !== "object") {
1869
+ throw new Error(`Expected object response but got: ${response}`)
1870
+ }
1871
+
1872
+ const modelsData = Array.isArray(response.models) ? response.models : []
1873
+ /**
1874
+ * Models.
1875
+ @type {InstanceType<T>[]} */
1876
+ const models = modelsData.map((model) => this.modelClass.instantiateFromResponse(model))
1877
+
1878
+ // Share a single cohort reference across every sibling so auto-batch-preload
1879
+ // can batch lazy relationship access later. Single-record lookups still flow
1880
+ // through here (with a cohort of one) and degrade cleanly to per-record load.
1881
+ for (const model of models) {
1882
+ /**
1883
+ * Narrows the runtime value to the documented type.
1884
+ @type {?} */ (model)._loadCohort = models
1885
+ }
1886
+
1887
+ return models
1888
+ }
1889
+
1890
+ /**
1891
+ * Runs to array.
1892
+ * @returns {Promise<InstanceType<T>[]>} - Loaded model instances.
1893
+ */
1894
+ async toArray() {
1895
+ return await this.load()
1896
+ }
1897
+
1898
+ /**
1899
+ * Runs count.
1900
+ * @returns {Promise<number>} - Number of loaded model instances.
1901
+ */
1902
+ async count() {
1903
+ const response = await this.modelClass.executeCommand("index", {
1904
+ ...this.joinsPayload(),
1905
+ ...this.ransackPayload(),
1906
+ ...this.searchPayload(),
1907
+ ...this.groupPayload(),
1908
+ ...this.distinctPayload(),
1909
+ ...this.wherePayload(),
1910
+ ...this.paginationPayload(),
1911
+ count: true
1912
+ })
1913
+
1914
+ if (!response || typeof response !== "object") {
1915
+ throw new Error(`Expected object response but got: ${response}`)
1916
+ }
1917
+
1918
+ if (!Number.isFinite(response.count)) {
1919
+ throw new Error(`Expected numeric count response but got: ${response.count}`)
1920
+ }
1921
+
1922
+ return response.count
1923
+ }
1924
+
1925
+ /**
1926
+ * Runs first.
1927
+ * @returns {Promise<InstanceType<T> | null>} - First model matching query.
1928
+ */
1929
+ async first() {
1930
+ const query = this.clone()
1931
+
1932
+ if (query._sort.length < 1) {
1933
+ query.sort([[this.modelClass.primaryKey(), "asc"]])
1934
+ }
1935
+
1936
+ query.limit(1)
1937
+
1938
+ const models = await query.toArray()
1939
+
1940
+ return models[0] || null
1941
+ }
1942
+
1943
+ /**
1944
+ * Runs last.
1945
+ * @returns {Promise<InstanceType<T> | null>} - Last model matching query.
1946
+ */
1947
+ async last() {
1948
+ // When pagination is already applied, fetch that scoped window and return its last item.
1949
+ if (this._offset !== null || this._page !== null || this._perPage !== null) {
1950
+ const models = await this.toArray()
1951
+
1952
+ if (models.length < 1) return null
1953
+
1954
+ return models[models.length - 1]
1955
+ }
1956
+
1957
+ const query = this.clone()
1958
+
1959
+ if (query._sort.length < 1) {
1960
+ query.sort([[this.modelClass.primaryKey(), "desc"]])
1961
+ } else {
1962
+ query._sort = query._sort.map((sortEntry) => ({
1963
+ ...sortEntry,
1964
+ direction: reverseSortDirection(sortEntry.direction)
1965
+ }))
1966
+ }
1967
+
1968
+ query.limit(1)
1969
+
1970
+ const models = await query.toArray()
1971
+
1972
+ return models[0] || null
1973
+ }
1974
+
1975
+ /**
1976
+ * Runs pluck.
1977
+ * @param {...(string | string[] | Record<string, ?> | Array<Record<string, ?>>)} columns - Pluck definition(s).
1978
+ * @returns {Promise<Array<?>>} - Plucked values.
1979
+ */
1980
+ async pluck(...columns) {
1981
+ if (columns.length < 1) {
1982
+ throw new Error("No columns given to pluck")
1983
+ }
1984
+
1985
+ const normalizedPluck = normalizePluck(columns.length === 1 ? columns[0] : columns)
1986
+ const validatedPluck = validatePluckDefinitions({
1987
+ modelClass: this.modelClass,
1988
+ pluck: normalizedPluck
1989
+ })
1990
+ const response = await this.modelClass.executeCommand("index", {
1991
+ ...this.joinsPayload(),
1992
+ ...this.searchPayload(),
1993
+ ...this.groupPayload(),
1994
+ ...this.distinctPayload(),
1995
+ ...this.sortPayload(),
1996
+ ...this.wherePayload(),
1997
+ ...this.paginationPayload(),
1998
+ pluck: validatedPluck
1999
+ })
2000
+
2001
+ if (!response || typeof response !== "object") {
2002
+ throw new Error(`Expected object response but got: ${response}`)
2003
+ }
2004
+
2005
+ if (!Array.isArray(response.values)) {
2006
+ return []
2007
+ }
2008
+
2009
+ return response.values
2010
+ }
2011
+
2012
+ /**
2013
+ * Runs find.
2014
+ * @param {number | string} id - Record id.
2015
+ * @returns {Promise<InstanceType<T>>} - Found model.
2016
+ */
2017
+ async find(id) {
2018
+ const pk = this.modelClass.primaryKey()
2019
+ const model = await this.findBy({[pk]: id})
2020
+
2021
+ if (!model) {
2022
+ throw new Error(`${this.modelClass.getModelName()} not found with ${pk}=${id}`)
2023
+ }
2024
+
2025
+ return model
2026
+ }
2027
+
2028
+ /**
2029
+ * Runs find by.
2030
+ * @param {Record<string, ?>} conditions - Conditions.
2031
+ * @returns {Promise<InstanceType<T> | null>} - Found model or null.
2032
+ */
2033
+ async findBy(conditions) {
2034
+ const normalizedConditions = this.validatedStructuredConditions(conditions)
2035
+ const mergedWhere = {
2036
+ ...this._where,
2037
+ ...normalizedConditions
2038
+ }
2039
+
2040
+ const response = await this.modelClass.executeCommand("index", {
2041
+ ...this.preloadPayload(),
2042
+ ...this.joinsPayload(),
2043
+ ...this.searchPayload(),
2044
+ ...this.selectPayload(Object.keys(mergedWhere)),
2045
+ ...this.selectsExtraPayload(),
2046
+ ...this.groupPayload(),
2047
+ ...this.distinctPayload(),
2048
+ ...this.sortPayload(),
2049
+ ...this.abilitiesPayload(),
2050
+ ...this.paginationPayload(),
2051
+ where: mergedWhere
2052
+ })
2053
+
2054
+ if (!response || typeof response !== "object") {
2055
+ throw new Error(`Expected object response but got: ${response}`)
2056
+ }
2057
+
2058
+ const models = Array.isArray(response.models) ? response.models : []
2059
+
2060
+ for (const modelData of models) {
2061
+ const model = this.modelClass.instantiateFromResponse(modelData)
2062
+
2063
+ if (this.modelClass.matchesFindByConditions(model, mergedWhere)) {
2064
+ return model
2065
+ }
2066
+ }
2067
+
2068
+ return null
2069
+ }
2070
+
2071
+ /**
2072
+ * Runs find by or fail.
2073
+ * @param {Record<string, ?>} conditions - Conditions.
2074
+ * @returns {Promise<InstanceType<T>>} - Found model.
2075
+ */
2076
+ async findByOrFail(conditions) {
2077
+ const model = await this.findBy(conditions)
2078
+
2079
+ if (!model) {
2080
+ throw new Error(`${this.modelClass.name} not found for conditions: ${serializeFindConditions(conditions)}`)
2081
+ }
2082
+
2083
+ return model
2084
+ }
2085
+
2086
+ /**
2087
+ * Runs find or initialize by.
2088
+ * @param {Record<string, ?>} conditions - Conditions.
2089
+ * @returns {Promise<InstanceType<T>>} - Existing or initialized model.
2090
+ */
2091
+ async findOrInitializeBy(conditions) {
2092
+ const normalizedConditions = this.validatedStructuredConditions(conditions)
2093
+ const model = await this.findBy(conditions)
2094
+
2095
+ if (model) return model
2096
+
2097
+ return /** Narrows the runtime value to the documented type. @type {InstanceType<T>} */ (new this.modelClass(normalizedConditions))
2098
+ }
2099
+
2100
+ /**
2101
+ * Runs find or create by.
2102
+ * @param {Record<string, ?>} conditions - Conditions.
2103
+ * @param {(model: InstanceType<T>) => Promise<void> | void} [callback] - Optional callback before save.
2104
+ * @returns {Promise<InstanceType<T>>} - Existing or newly created model.
2105
+ */
2106
+ async findOrCreateBy(conditions, callback) {
2107
+ const normalizedConditions = this.validatedStructuredConditions(conditions)
2108
+ const model = await this.findBy(conditions)
2109
+
2110
+ if (model) return model
2111
+
2112
+ const newModel = /**
2113
+ * Narrows the runtime value to the documented type.
2114
+ @type {InstanceType<T>} */ (new this.modelClass(normalizedConditions))
2115
+
2116
+ if (callback) {
2117
+ await callback(newModel)
2118
+ }
2119
+
2120
+ await newModel.save()
2121
+
2122
+ return newModel
2123
+ }
2124
+
2125
+ /**
2126
+ * Runs validated structured conditions.
2127
+ * @param {Record<string, ?>} conditions - Candidate structured conditions.
2128
+ * @returns {Record<string, ?>} - Validated conditions.
2129
+ */
2130
+ validatedStructuredConditions(conditions) {
2131
+ this.modelClass.assertFindByConditions(conditions)
2132
+
2133
+ return conditions
2134
+ }
2135
+ }
2136
+
2137
+ /**
2138
+ * Runs frontend model event filter key.
2139
+ * @param {FrontendModelEventFilterPayload} payload - Event filter payload.
2140
+ * @returns {string} - Stable key for event filter matching.
2141
+ */
2142
+ function frontendModelEventFilterKey(payload) {
2143
+ return JSON.stringify(payload)
2144
+ }
2145
+
2146
+ /**
2147
+ * Runs apply frontend model projection options.
2148
+ * @param {FrontendModelQuery<typeof import("./base.js").default>} query - Query receiving projection options.
2149
+ * @param {FrontendModelProjectionOptions} options - Projection options.
2150
+ * @returns {void}
2151
+ */
2152
+ function applyFrontendModelProjectionOptions(query, options) {
2153
+ if (options.select !== undefined) query.select(options.select)
2154
+ if (options.selectsExtra !== undefined) query.selectsExtra(options.selectsExtra)
2155
+ if (options.preload !== undefined) query.preload(options.preload)
2156
+ if (options.withCount !== undefined) query.withCount(options.withCount)
2157
+ if (options.abilities !== undefined) query.abilities(options.abilities)
2158
+ if (options.queryData !== undefined) query.queryData(options.queryData)
2159
+ }
2160
+
2161
+ /**
2162
+ * Runs assert frontend model event query class.
2163
+ * @param {typeof import("./base.js").default} modelClass - Expected frontend model class.
2164
+ * @param {FrontendModelQuery<typeof import("./base.js").default>} query - Event query.
2165
+ * @returns {void}
2166
+ */
2167
+ function assertFrontendModelEventQueryClass(modelClass, query) {
2168
+ if (query.modelClass === modelClass) return
2169
+
2170
+ throw new Error(`Cannot subscribe ${modelClass.name} events with a ${query.modelClass.name} query`)
2171
+ }
2172
+
2173
+ /**
2174
+ * Runs assert frontend model event options object.
2175
+ * @param {FrontendModelEventOptions} options - Candidate event options.
2176
+ * @returns {void}
2177
+ */
2178
+ function assertFrontendModelEventOptionsObject(options) {
2179
+ if (options && typeof options === "object" && !Array.isArray(options)) return
2180
+
2181
+ throw new Error(`Frontend model event options must be a query or an options object, got: ${options}`)
2182
+ }
2183
+
2184
+ /**
2185
+ * Runs cloned frontend model event query.
2186
+ * @param {typeof import("./base.js").default} modelClass - Frontend model class.
2187
+ * @param {FrontendModelQuery<typeof import("./base.js").default>} query - Event query.
2188
+ * @returns {FrontendModelQuery<typeof import("./base.js").default>} - Cloned query used by event subscriptions.
2189
+ */
2190
+ function clonedFrontendModelEventQuery(modelClass, query) {
2191
+ assertFrontendModelEventQueryClass(modelClass, query)
2192
+
2193
+ return query.clone()
2194
+ }
2195
+
2196
+ /**
2197
+ * Runs frontend model event query from options object.
2198
+ * @param {typeof import("./base.js").default} modelClass - Frontend model class.
2199
+ * @param {FrontendModelEventOptionsObject} options - Event options object.
2200
+ * @returns {FrontendModelQuery<typeof import("./base.js").default>} - Query used by event subscriptions.
2201
+ */
2202
+ function frontendModelEventQueryFromOptionsObject(modelClass, options) {
2203
+ if (options.query !== undefined && !(options.query instanceof FrontendModelQuery)) {
2204
+ throw new Error("Frontend model event option query must be a FrontendModelQuery")
2205
+ }
2206
+
2207
+ const query = options.query
2208
+ ? options.query.clone()
2209
+ : new FrontendModelQuery({modelClass})
2210
+
2211
+ assertFrontendModelEventQueryClass(modelClass, query)
2212
+
2213
+ return query
2214
+ }
2215
+
2216
+ /**
2217
+ * Runs frontend model event query.
2218
+ * @param {typeof import("./base.js").default} modelClass - Frontend model class.
2219
+ * @param {FrontendModelEventOptions} [options] - Event query or projection options.
2220
+ * @returns {FrontendModelQuery<typeof import("./base.js").default>} - Normalized query used by event subscriptions.
2221
+ */
2222
+ function frontendModelEventQuery(modelClass, options = {}) {
2223
+ if (options instanceof FrontendModelQuery) return clonedFrontendModelEventQuery(modelClass, options)
2224
+
2225
+ assertFrontendModelEventOptionsObject(options)
2226
+
2227
+ const optionsObject = /**
2228
+ * Narrows the runtime value to the documented type.
2229
+ @type {FrontendModelEventOptionsObject} */ (options)
2230
+ const query = frontendModelEventQueryFromOptionsObject(modelClass, optionsObject)
2231
+
2232
+ applyFrontendModelProjectionOptions(query, optionsObject)
2233
+
2234
+ return query
2235
+ }
2236
+
2237
+ /**
2238
+ * Runs the frontendModelEventOptionsPayload helper.
2239
+ * @param {typeof import("./base.js").default} modelClass - Frontend model class.
2240
+ * @param {FrontendModelEventOptions} [options] - Event query or projection options.
2241
+ * @returns {FrontendModelEventOptionsPayload} - Normalized event subscription payload.
2242
+ */
2243
+ export function frontendModelEventOptionsPayload(modelClass, options = {}) {
2244
+ return frontendModelEventQuery(modelClass, options).eventOptionsPayload()
2245
+ }