velocious 1.0.431 → 1.0.433

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 (794) hide show
  1. package/build/application.js +229 -0
  2. package/build/authorization/ability.js +329 -0
  3. package/build/authorization/base-resource.js +143 -0
  4. package/build/background-jobs/client.js +50 -0
  5. package/build/background-jobs/cron-expression.js +277 -0
  6. package/build/background-jobs/forked-runner-child.js +86 -0
  7. package/build/background-jobs/job-record.js +13 -0
  8. package/build/background-jobs/job-registry.js +92 -0
  9. package/build/background-jobs/job-runner.js +107 -0
  10. package/build/background-jobs/job.js +77 -0
  11. package/build/background-jobs/json-socket.js +78 -0
  12. package/build/background-jobs/main.js +926 -0
  13. package/build/background-jobs/normalize-error.js +26 -0
  14. package/build/background-jobs/scheduler.js +274 -0
  15. package/build/background-jobs/socket-request.js +68 -0
  16. package/build/background-jobs/status-reporter.js +101 -0
  17. package/build/background-jobs/store.js +994 -0
  18. package/build/background-jobs/types.js +70 -0
  19. package/build/background-jobs/web/authorization.js +89 -0
  20. package/build/background-jobs/web/controller.js +280 -0
  21. package/build/background-jobs/web/index.js +57 -0
  22. package/build/background-jobs/web/path-matcher.js +74 -0
  23. package/build/background-jobs/web/registry.js +49 -0
  24. package/build/background-jobs/worker.js +683 -0
  25. package/build/beacon/client.js +330 -0
  26. package/build/beacon/in-process-broker.js +71 -0
  27. package/build/beacon/in-process-client.js +139 -0
  28. package/build/beacon/server.js +148 -0
  29. package/build/beacon/types.js +55 -0
  30. package/build/cli/base-command.js +67 -0
  31. package/build/cli/browser-cli.js +45 -0
  32. package/build/cli/commands/background-jobs-main.js +7 -0
  33. package/build/cli/commands/background-jobs-runner.js +7 -0
  34. package/build/cli/commands/background-jobs-worker.js +7 -0
  35. package/build/cli/commands/beacon.js +7 -0
  36. package/build/cli/commands/console.js +12 -0
  37. package/build/cli/commands/db/base-command.js +82 -0
  38. package/build/cli/commands/db/create.js +64 -0
  39. package/build/cli/commands/db/drop.js +75 -0
  40. package/build/cli/commands/db/migrate.js +17 -0
  41. package/build/cli/commands/db/reset.js +22 -0
  42. package/build/cli/commands/db/rollback.js +15 -0
  43. package/build/cli/commands/db/schema/dump.js +12 -0
  44. package/build/cli/commands/db/schema/load.js +12 -0
  45. package/build/cli/commands/db/seed.js +12 -0
  46. package/build/cli/commands/db/tenants/check.js +38 -0
  47. package/build/cli/commands/db/tenants/create.js +33 -0
  48. package/build/cli/commands/db/tenants/migrate.js +49 -0
  49. package/build/cli/commands/destroy/migration.js +7 -0
  50. package/build/cli/commands/generate/base-models.js +7 -0
  51. package/build/cli/commands/generate/frontend-models.js +12 -0
  52. package/build/cli/commands/generate/migration.js +7 -0
  53. package/build/cli/commands/generate/model.js +7 -0
  54. package/build/cli/commands/init.js +11 -0
  55. package/build/cli/commands/routes.js +7 -0
  56. package/build/cli/commands/run-script.js +12 -0
  57. package/build/cli/commands/runner.js +12 -0
  58. package/build/cli/commands/server.js +7 -0
  59. package/build/cli/commands/test.js +9 -0
  60. package/build/cli/index.js +152 -0
  61. package/build/cli/tenant-database-command-helper.js +198 -0
  62. package/build/cli/use-browser-cli.js +30 -0
  63. package/build/configuration-resolver.js +65 -0
  64. package/build/configuration-types.js +429 -0
  65. package/build/configuration.js +2590 -0
  66. package/build/controller.js +421 -0
  67. package/build/current-configuration.js +31 -0
  68. package/build/current.js +80 -0
  69. package/build/database/annotations-async-hooks.js +47 -0
  70. package/build/database/annotations.js +40 -0
  71. package/build/database/drivers/base-column.js +182 -0
  72. package/build/database/drivers/base-columns-index.js +81 -0
  73. package/build/database/drivers/base-foreign-key.js +104 -0
  74. package/build/database/drivers/base-table.js +156 -0
  75. package/build/database/drivers/base.js +1609 -0
  76. package/build/database/drivers/mssql/column.js +74 -0
  77. package/build/database/drivers/mssql/columns-index.js +6 -0
  78. package/build/database/drivers/mssql/connect-connection.js +16 -0
  79. package/build/database/drivers/mssql/foreign-key.js +12 -0
  80. package/build/database/drivers/mssql/index.js +590 -0
  81. package/build/database/drivers/mssql/options.js +79 -0
  82. package/build/database/drivers/mssql/query-parser.js +6 -0
  83. package/build/database/drivers/mssql/sql/alter-table.js +4 -0
  84. package/build/database/drivers/mssql/sql/create-database.js +36 -0
  85. package/build/database/drivers/mssql/sql/create-index.js +4 -0
  86. package/build/database/drivers/mssql/sql/create-table.js +4 -0
  87. package/build/database/drivers/mssql/sql/delete.js +19 -0
  88. package/build/database/drivers/mssql/sql/drop-database.js +36 -0
  89. package/build/database/drivers/mssql/sql/drop-table.js +4 -0
  90. package/build/database/drivers/mssql/sql/insert.js +4 -0
  91. package/build/database/drivers/mssql/sql/update.js +31 -0
  92. package/build/database/drivers/mssql/sql/upsert.js +23 -0
  93. package/build/database/drivers/mssql/structure-sql.js +120 -0
  94. package/build/database/drivers/mssql/table.js +145 -0
  95. package/build/database/drivers/mysql/column.js +112 -0
  96. package/build/database/drivers/mysql/columns-index.js +22 -0
  97. package/build/database/drivers/mysql/foreign-key.js +12 -0
  98. package/build/database/drivers/mysql/index.js +473 -0
  99. package/build/database/drivers/mysql/options.js +34 -0
  100. package/build/database/drivers/mysql/query-parser.js +6 -0
  101. package/build/database/drivers/mysql/query.js +37 -0
  102. package/build/database/drivers/mysql/sql/alter-table.js +6 -0
  103. package/build/database/drivers/mysql/sql/create-database.js +39 -0
  104. package/build/database/drivers/mysql/sql/create-index.js +6 -0
  105. package/build/database/drivers/mysql/sql/create-table.js +6 -0
  106. package/build/database/drivers/mysql/sql/delete.js +21 -0
  107. package/build/database/drivers/mysql/sql/drop-database.js +6 -0
  108. package/build/database/drivers/mysql/sql/drop-table.js +6 -0
  109. package/build/database/drivers/mysql/sql/insert.js +6 -0
  110. package/build/database/drivers/mysql/sql/update.js +33 -0
  111. package/build/database/drivers/mysql/sql/upsert.js +13 -0
  112. package/build/database/drivers/mysql/structure-sql.js +93 -0
  113. package/build/database/drivers/mysql/table.js +121 -0
  114. package/build/database/drivers/pgsql/column.js +90 -0
  115. package/build/database/drivers/pgsql/columns-index.js +6 -0
  116. package/build/database/drivers/pgsql/foreign-key.js +12 -0
  117. package/build/database/drivers/pgsql/index.js +441 -0
  118. package/build/database/drivers/pgsql/options.js +32 -0
  119. package/build/database/drivers/pgsql/query-parser.js +6 -0
  120. package/build/database/drivers/pgsql/sql/alter-table.js +6 -0
  121. package/build/database/drivers/pgsql/sql/create-database.js +38 -0
  122. package/build/database/drivers/pgsql/sql/create-index.js +6 -0
  123. package/build/database/drivers/pgsql/sql/create-table.js +6 -0
  124. package/build/database/drivers/pgsql/sql/delete.js +21 -0
  125. package/build/database/drivers/pgsql/sql/drop-database.js +6 -0
  126. package/build/database/drivers/pgsql/sql/drop-table.js +6 -0
  127. package/build/database/drivers/pgsql/sql/insert.js +6 -0
  128. package/build/database/drivers/pgsql/sql/update.js +33 -0
  129. package/build/database/drivers/pgsql/sql/upsert.js +14 -0
  130. package/build/database/drivers/pgsql/structure-sql.js +126 -0
  131. package/build/database/drivers/pgsql/table.js +135 -0
  132. package/build/database/drivers/sqlite/base.js +509 -0
  133. package/build/database/drivers/sqlite/column.js +75 -0
  134. package/build/database/drivers/sqlite/columns-index.js +30 -0
  135. package/build/database/drivers/sqlite/connection-sql-js.js +46 -0
  136. package/build/database/drivers/sqlite/foreign-key.js +24 -0
  137. package/build/database/drivers/sqlite/index.js +394 -0
  138. package/build/database/drivers/sqlite/index.native.js +72 -0
  139. package/build/database/drivers/sqlite/index.web.js +99 -0
  140. package/build/database/drivers/sqlite/options.js +32 -0
  141. package/build/database/drivers/sqlite/query-parser.js +6 -0
  142. package/build/database/drivers/sqlite/query.js +35 -0
  143. package/build/database/drivers/sqlite/query.native.js +35 -0
  144. package/build/database/drivers/sqlite/query.web.js +49 -0
  145. package/build/database/drivers/sqlite/sql/alter-table.js +187 -0
  146. package/build/database/drivers/sqlite/sql/create-index.js +6 -0
  147. package/build/database/drivers/sqlite/sql/create-table.js +6 -0
  148. package/build/database/drivers/sqlite/sql/delete.js +26 -0
  149. package/build/database/drivers/sqlite/sql/drop-table.js +6 -0
  150. package/build/database/drivers/sqlite/sql/insert.js +6 -0
  151. package/build/database/drivers/sqlite/sql/update.js +33 -0
  152. package/build/database/drivers/sqlite/sql/upsert.js +14 -0
  153. package/build/database/drivers/sqlite/structure-sql.js +56 -0
  154. package/build/database/drivers/sqlite/table-rebuilder.js +96 -0
  155. package/build/database/drivers/sqlite/table.js +131 -0
  156. package/build/database/drivers/structure-sql/utils.js +35 -0
  157. package/build/database/handler.js +13 -0
  158. package/build/database/initializer-from-require-context.js +101 -0
  159. package/build/database/migration/index.js +438 -0
  160. package/build/database/migrator/files-finder.js +55 -0
  161. package/build/database/migrator/types.js +31 -0
  162. package/build/database/migrator.js +557 -0
  163. package/build/database/pool/async-tracked-multi-connection.js +1164 -0
  164. package/build/database/pool/base-methods-forward.js +52 -0
  165. package/build/database/pool/base.js +380 -0
  166. package/build/database/pool/single-multi-use.js +118 -0
  167. package/build/database/query/alter-table-base.js +104 -0
  168. package/build/database/query/base.js +49 -0
  169. package/build/database/query/create-database-base.js +42 -0
  170. package/build/database/query/create-index-base.js +117 -0
  171. package/build/database/query/create-table-base.js +205 -0
  172. package/build/database/query/delete-base.js +19 -0
  173. package/build/database/query/drop-database-base.js +38 -0
  174. package/build/database/query/drop-table-base.js +58 -0
  175. package/build/database/query/from-base.js +36 -0
  176. package/build/database/query/from-plain.js +16 -0
  177. package/build/database/query/from-table.js +18 -0
  178. package/build/database/query/index.js +533 -0
  179. package/build/database/query/insert-base.js +172 -0
  180. package/build/database/query/join-base.js +43 -0
  181. package/build/database/query/join-object.js +167 -0
  182. package/build/database/query/join-plain.js +18 -0
  183. package/build/database/query/join-tracker.js +93 -0
  184. package/build/database/query/model-class-query.js +1577 -0
  185. package/build/database/query/order-base.js +33 -0
  186. package/build/database/query/order-column.js +77 -0
  187. package/build/database/query/order-plain.js +28 -0
  188. package/build/database/query/preloader/belongs-to.js +267 -0
  189. package/build/database/query/preloader/ensure-model-class-initialized.js +18 -0
  190. package/build/database/query/preloader/has-many.js +316 -0
  191. package/build/database/query/preloader/has-one.js +123 -0
  192. package/build/database/query/preloader/selection.js +152 -0
  193. package/build/database/query/preloader.js +201 -0
  194. package/build/database/query/query-data.js +305 -0
  195. package/build/database/query/select-base.js +30 -0
  196. package/build/database/query/select-plain.js +18 -0
  197. package/build/database/query/select-table-and-column.js +28 -0
  198. package/build/database/query/update-base.js +41 -0
  199. package/build/database/query/upsert-base.js +103 -0
  200. package/build/database/query/where-base.js +38 -0
  201. package/build/database/query/where-combinator.js +31 -0
  202. package/build/database/query/where-hash.js +77 -0
  203. package/build/database/query/where-model-class-hash.js +505 -0
  204. package/build/database/query/where-not.js +23 -0
  205. package/build/database/query/where-plain.js +20 -0
  206. package/build/database/query/with-count.js +219 -0
  207. package/build/database/query-parser/base-query-parser.js +40 -0
  208. package/build/database/query-parser/from-parser.js +49 -0
  209. package/build/database/query-parser/group-parser.js +55 -0
  210. package/build/database/query-parser/joins-parser.js +37 -0
  211. package/build/database/query-parser/limit-parser.js +77 -0
  212. package/build/database/query-parser/options.js +94 -0
  213. package/build/database/query-parser/order-parser.js +45 -0
  214. package/build/database/query-parser/select-parser.js +67 -0
  215. package/build/database/query-parser/where-parser.js +46 -0
  216. package/build/database/record/acts-as-list.js +374 -0
  217. package/build/database/record/attachments/download.js +49 -0
  218. package/build/database/record/attachments/handle.js +188 -0
  219. package/build/database/record/attachments/normalize-input.js +213 -0
  220. package/build/database/record/attachments/storage-drivers/filesystem.js +114 -0
  221. package/build/database/record/attachments/storage-drivers/native.js +146 -0
  222. package/build/database/record/attachments/storage-drivers/s3.js +245 -0
  223. package/build/database/record/attachments/store.js +591 -0
  224. package/build/database/record/index.js +4119 -0
  225. package/build/database/record/instance-relationships/base.js +289 -0
  226. package/build/database/record/instance-relationships/belongs-to.js +84 -0
  227. package/build/database/record/instance-relationships/has-many.js +284 -0
  228. package/build/database/record/instance-relationships/has-one.js +117 -0
  229. package/build/database/record/record-not-found-error.js +3 -0
  230. package/build/database/record/relationships/base.js +195 -0
  231. package/build/database/record/relationships/belongs-to.js +57 -0
  232. package/build/database/record/relationships/has-many.js +46 -0
  233. package/build/database/record/relationships/has-one.js +46 -0
  234. package/build/database/record/state-machine.js +278 -0
  235. package/build/database/record/user-module.js +43 -0
  236. package/build/database/record/validators/base.js +27 -0
  237. package/build/database/record/validators/format.js +50 -0
  238. package/build/database/record/validators/presence.js +24 -0
  239. package/build/database/record/validators/uniqueness.js +124 -0
  240. package/build/database/table-data/index.js +241 -0
  241. package/build/database/table-data/table-column.js +416 -0
  242. package/build/database/table-data/table-foreign-key.js +69 -0
  243. package/build/database/table-data/table-index.js +46 -0
  244. package/build/database/table-data/table-reference.js +13 -0
  245. package/build/database/use-database.js +48 -0
  246. package/build/environment-handlers/base.js +561 -0
  247. package/build/environment-handlers/browser.js +338 -0
  248. package/build/environment-handlers/node/cli/commands/background-jobs-main.js +21 -0
  249. package/build/environment-handlers/node/cli/commands/background-jobs-runner.js +24 -0
  250. package/build/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -0
  251. package/build/environment-handlers/node/cli/commands/beacon.js +21 -0
  252. package/build/environment-handlers/node/cli/commands/cli-command-context.js +31 -0
  253. package/build/environment-handlers/node/cli/commands/console.js +149 -0
  254. package/build/environment-handlers/node/cli/commands/db/schema/dump.js +43 -0
  255. package/build/environment-handlers/node/cli/commands/db/schema/load.js +69 -0
  256. package/build/environment-handlers/node/cli/commands/db/seed.js +79 -0
  257. package/build/environment-handlers/node/cli/commands/destroy/migration.js +47 -0
  258. package/build/environment-handlers/node/cli/commands/generate/base-models.js +396 -0
  259. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +872 -0
  260. package/build/environment-handlers/node/cli/commands/generate/migration.js +45 -0
  261. package/build/environment-handlers/node/cli/commands/generate/model.js +45 -0
  262. package/build/environment-handlers/node/cli/commands/init.js +68 -0
  263. package/build/environment-handlers/node/cli/commands/routes.js +63 -0
  264. package/build/environment-handlers/node/cli/commands/run-script.js +85 -0
  265. package/build/environment-handlers/node/cli/commands/runner.js +84 -0
  266. package/build/environment-handlers/node/cli/commands/server.js +151 -0
  267. package/build/environment-handlers/node/cli/commands/test.js +118 -0
  268. package/build/environment-handlers/node.js +887 -0
  269. package/build/error-logger.js +30 -0
  270. package/build/frontend-model-controller.js +3491 -0
  271. package/build/frontend-model-resource/base-resource.js +939 -0
  272. package/build/frontend-models/base.js +4004 -0
  273. package/build/frontend-models/clear-pending-debounced-callback.js +16 -0
  274. package/build/frontend-models/event-hook-models.js +49 -0
  275. package/build/frontend-models/model-registry.js +28 -0
  276. package/build/frontend-models/outgoing-event-buffer.js +51 -0
  277. package/build/frontend-models/preloader.js +169 -0
  278. package/build/frontend-models/query.js +2245 -0
  279. package/build/frontend-models/resource-config-validation.js +56 -0
  280. package/build/frontend-models/resource-definition.js +399 -0
  281. package/build/frontend-models/transport-serialization.js +369 -0
  282. package/build/frontend-models/use-created-event.js +21 -0
  283. package/build/frontend-models/use-destroyed-event.js +148 -0
  284. package/build/frontend-models/use-model-class-event.js +164 -0
  285. package/build/frontend-models/use-updated-event.js +152 -0
  286. package/build/frontend-models/websocket-channel.js +494 -0
  287. package/build/frontend-models/websocket-publishers.js +224 -0
  288. package/build/http-client/header.js +17 -0
  289. package/build/http-client/index.js +139 -0
  290. package/build/http-client/request.js +94 -0
  291. package/build/http-client/response.js +151 -0
  292. package/build/http-client/websocket-client.js +27 -0
  293. package/build/http-server/client/index.js +507 -0
  294. package/build/http-server/client/params-to-object.js +152 -0
  295. package/build/http-server/client/request-buffer/form-data-part.js +139 -0
  296. package/build/http-server/client/request-buffer/header.js +19 -0
  297. package/build/http-server/client/request-buffer/index.js +535 -0
  298. package/build/http-server/client/request-parser.js +195 -0
  299. package/build/http-server/client/request-runner.js +321 -0
  300. package/build/http-server/client/request-timing.js +171 -0
  301. package/build/http-server/client/request.js +114 -0
  302. package/build/http-server/client/response.js +251 -0
  303. package/build/http-server/client/uploaded-file/memory-uploaded-file.js +32 -0
  304. package/build/http-server/client/uploaded-file/temporary-uploaded-file.js +32 -0
  305. package/build/http-server/client/uploaded-file/uploaded-file.js +36 -0
  306. package/build/http-server/client/websocket-request.js +147 -0
  307. package/build/http-server/client/websocket-session.js +1755 -0
  308. package/build/http-server/cookie.js +245 -0
  309. package/build/http-server/development-reloader.js +240 -0
  310. package/build/http-server/index.js +561 -0
  311. package/build/http-server/remote-address.js +77 -0
  312. package/build/http-server/server-client.js +222 -0
  313. package/build/http-server/server-lock.js +178 -0
  314. package/build/http-server/websocket-channel-subscribers.js +110 -0
  315. package/build/http-server/websocket-channel.js +137 -0
  316. package/build/http-server/websocket-connection.js +118 -0
  317. package/build/http-server/websocket-event-log-store.js +433 -0
  318. package/build/http-server/websocket-events-host.js +170 -0
  319. package/build/http-server/websocket-events.js +50 -0
  320. package/build/http-server/worker-handler/channel-subscriber-dispatch.js +28 -0
  321. package/build/http-server/worker-handler/in-process.js +155 -0
  322. package/build/http-server/worker-handler/index.js +370 -0
  323. package/build/http-server/worker-handler/worker-script.js +6 -0
  324. package/build/http-server/worker-handler/worker-thread.js +286 -0
  325. package/build/initializer.js +39 -0
  326. package/build/jobs/mail-delivery.js +22 -0
  327. package/build/logger/base-logger.js +34 -0
  328. package/build/logger/console-logger.js +28 -0
  329. package/build/logger/file-logger.js +36 -0
  330. package/build/logger/outputs/array-output.js +50 -0
  331. package/build/logger/outputs/console-output.js +32 -0
  332. package/build/logger/outputs/file-output.js +55 -0
  333. package/build/logger/outputs/stdout-output.js +64 -0
  334. package/build/logger.js +507 -0
  335. package/build/mailer/backends/smtp.js +197 -0
  336. package/build/mailer/base.js +337 -0
  337. package/build/mailer/delivery.js +70 -0
  338. package/build/mailer/index.js +24 -0
  339. package/build/mailer.js +15 -0
  340. package/build/plugins/sqljs-wasm-route-controller.js +70 -0
  341. package/build/plugins/sqljs-wasm-route.js +71 -0
  342. package/build/record-payload-values.js +83 -0
  343. package/build/routes/app-routes.js +17 -0
  344. package/build/routes/base-route.js +133 -0
  345. package/build/routes/basic-route.js +109 -0
  346. package/build/routes/built-in/debug/controller.js +12 -0
  347. package/build/routes/built-in/errors/controller.js +7 -0
  348. package/build/routes/get-route.js +75 -0
  349. package/build/routes/hooks/frontend-model-command-route-hook.js +100 -0
  350. package/build/routes/index.js +50 -0
  351. package/build/routes/namespace-route.js +51 -0
  352. package/build/routes/plugin-routes.js +141 -0
  353. package/build/routes/post-route.js +74 -0
  354. package/build/routes/resolver.js +535 -0
  355. package/build/routes/resource-route.js +154 -0
  356. package/build/routes/root-route.js +11 -0
  357. package/build/src/application.js +187 -214
  358. package/build/src/authorization/ability.js +250 -297
  359. package/build/src/authorization/base-resource.js +120 -136
  360. package/build/src/background-jobs/client.js +43 -47
  361. package/build/src/background-jobs/cron-expression.js +127 -166
  362. package/build/src/background-jobs/forked-runner-child.js +37 -47
  363. package/build/src/background-jobs/job-record.js +8 -10
  364. package/build/src/background-jobs/job-registry.js +72 -84
  365. package/build/src/background-jobs/job-runner.js +74 -81
  366. package/build/src/background-jobs/job.js +62 -72
  367. package/build/src/background-jobs/json-socket.js +65 -70
  368. package/build/src/background-jobs/main.js +841 -900
  369. package/build/src/background-jobs/normalize-error.js +12 -11
  370. package/build/src/background-jobs/scheduler.js +205 -247
  371. package/build/src/background-jobs/socket-request.js +60 -65
  372. package/build/src/background-jobs/status-reporter.js +86 -96
  373. package/build/src/background-jobs/store.js +862 -980
  374. package/build/src/background-jobs/types.js +2 -3
  375. package/build/src/background-jobs/web/authorization.js +38 -50
  376. package/build/src/background-jobs/web/controller.js +232 -268
  377. package/build/src/background-jobs/web/index.js +36 -40
  378. package/build/src/background-jobs/web/path-matcher.js +45 -48
  379. package/build/src/background-jobs/web/registry.js +9 -14
  380. package/build/src/background-jobs/worker.js +585 -639
  381. package/build/src/beacon/client.js +264 -293
  382. package/build/src/beacon/in-process-broker.js +20 -25
  383. package/build/src/beacon/in-process-client.js +104 -116
  384. package/build/src/beacon/server.js +110 -126
  385. package/build/src/beacon/types.js +2 -8
  386. package/build/src/cli/base-command.js +49 -57
  387. package/build/src/cli/browser-cli.js +37 -42
  388. package/build/src/cli/commands/background-jobs-main.js +5 -5
  389. package/build/src/cli/commands/background-jobs-runner.js +5 -5
  390. package/build/src/cli/commands/background-jobs-worker.js +5 -5
  391. package/build/src/cli/commands/beacon.js +5 -5
  392. package/build/src/cli/commands/console.js +10 -10
  393. package/build/src/cli/commands/db/base-command.js +71 -76
  394. package/build/src/cli/commands/db/create.js +53 -61
  395. package/build/src/cli/commands/db/drop.js +62 -71
  396. package/build/src/cli/commands/db/migrate.js +13 -15
  397. package/build/src/cli/commands/db/reset.js +16 -19
  398. package/build/src/cli/commands/db/rollback.js +12 -13
  399. package/build/src/cli/commands/db/schema/dump.js +9 -9
  400. package/build/src/cli/commands/db/schema/load.js +9 -9
  401. package/build/src/cli/commands/db/seed.js +9 -9
  402. package/build/src/cli/commands/db/tenants/check.js +32 -35
  403. package/build/src/cli/commands/db/tenants/create.js +26 -29
  404. package/build/src/cli/commands/db/tenants/migrate.js +40 -44
  405. package/build/src/cli/commands/destroy/migration.js +5 -5
  406. package/build/src/cli/commands/generate/base-models.js +5 -5
  407. package/build/src/cli/commands/generate/frontend-models.js +9 -9
  408. package/build/src/cli/commands/generate/migration.js +5 -5
  409. package/build/src/cli/commands/generate/model.js +5 -5
  410. package/build/src/cli/commands/init.js +7 -9
  411. package/build/src/cli/commands/routes.js +6 -6
  412. package/build/src/cli/commands/run-script.js +9 -9
  413. package/build/src/cli/commands/runner.js +9 -9
  414. package/build/src/cli/commands/server.js +6 -6
  415. package/build/src/cli/commands/test.js +6 -7
  416. package/build/src/cli/index.js +127 -141
  417. package/build/src/cli/tenant-database-command-helper.js +154 -185
  418. package/build/src/cli/use-browser-cli.js +15 -20
  419. package/build/src/configuration-resolver.js +47 -54
  420. package/build/src/configuration-types.d.ts +5 -3
  421. package/build/src/configuration-types.d.ts.map +1 -1
  422. package/build/src/configuration-types.js +3 -54
  423. package/build/src/configuration.js +2240 -2547
  424. package/build/src/controller.js +363 -407
  425. package/build/src/current-configuration.js +9 -12
  426. package/build/src/current.js +70 -75
  427. package/build/src/database/annotations-async-hooks.js +16 -22
  428. package/build/src/database/annotations.js +12 -18
  429. package/build/src/database/drivers/base-column.js +155 -179
  430. package/build/src/database/drivers/base-columns-index.js +69 -78
  431. package/build/src/database/drivers/base-foreign-key.js +89 -101
  432. package/build/src/database/drivers/base-table.js +124 -149
  433. package/build/src/database/drivers/base.js +1306 -1489
  434. package/build/src/database/drivers/mssql/column.js +39 -50
  435. package/build/src/database/drivers/mssql/columns-index.js +2 -3
  436. package/build/src/database/drivers/mssql/connect-connection.js +11 -9
  437. package/build/src/database/drivers/mssql/foreign-key.js +8 -9
  438. package/build/src/database/drivers/mssql/index.js +507 -587
  439. package/build/src/database/drivers/mssql/options.js +68 -75
  440. package/build/src/database/drivers/mssql/query-parser.js +2 -3
  441. package/build/src/database/drivers/mssql/sql/alter-table.js +2 -2
  442. package/build/src/database/drivers/mssql/sql/create-database.js +24 -31
  443. package/build/src/database/drivers/mssql/sql/create-index.js +2 -2
  444. package/build/src/database/drivers/mssql/sql/create-table.js +2 -2
  445. package/build/src/database/drivers/mssql/sql/delete.js +14 -16
  446. package/build/src/database/drivers/mssql/sql/drop-database.js +24 -31
  447. package/build/src/database/drivers/mssql/sql/drop-table.js +2 -2
  448. package/build/src/database/drivers/mssql/sql/insert.js +2 -2
  449. package/build/src/database/drivers/mssql/sql/update.js +24 -28
  450. package/build/src/database/drivers/mssql/sql/upsert.js +18 -20
  451. package/build/src/database/drivers/mssql/structure-sql.js +102 -114
  452. package/build/src/database/drivers/mssql/table.js +81 -96
  453. package/build/src/database/drivers/mysql/column.js +75 -92
  454. package/build/src/database/drivers/mysql/columns-index.js +16 -19
  455. package/build/src/database/drivers/mysql/foreign-key.js +8 -9
  456. package/build/src/database/drivers/mysql/index.js +396 -457
  457. package/build/src/database/drivers/mysql/options.js +26 -30
  458. package/build/src/database/drivers/mysql/query-parser.js +2 -3
  459. package/build/src/database/drivers/mysql/query.js +26 -29
  460. package/build/src/database/drivers/mysql/sql/alter-table.js +2 -3
  461. package/build/src/database/drivers/mysql/sql/create-database.js +23 -28
  462. package/build/src/database/drivers/mysql/sql/create-index.js +2 -3
  463. package/build/src/database/drivers/mysql/sql/create-table.js +2 -3
  464. package/build/src/database/drivers/mysql/sql/delete.js +14 -17
  465. package/build/src/database/drivers/mysql/sql/drop-database.js +2 -3
  466. package/build/src/database/drivers/mysql/sql/drop-table.js +2 -3
  467. package/build/src/database/drivers/mysql/sql/insert.js +2 -3
  468. package/build/src/database/drivers/mysql/sql/update.js +24 -29
  469. package/build/src/database/drivers/mysql/sql/upsert.js +8 -10
  470. package/build/src/database/drivers/mysql/structure-sql.js +79 -88
  471. package/build/src/database/drivers/mysql/table.js +83 -98
  472. package/build/src/database/drivers/pgsql/column.js +56 -72
  473. package/build/src/database/drivers/pgsql/columns-index.js +2 -3
  474. package/build/src/database/drivers/pgsql/foreign-key.js +8 -9
  475. package/build/src/database/drivers/pgsql/index.js +377 -438
  476. package/build/src/database/drivers/pgsql/options.js +25 -28
  477. package/build/src/database/drivers/pgsql/query-parser.js +2 -3
  478. package/build/src/database/drivers/pgsql/sql/alter-table.js +2 -3
  479. package/build/src/database/drivers/pgsql/sql/create-database.js +19 -23
  480. package/build/src/database/drivers/pgsql/sql/create-index.js +2 -3
  481. package/build/src/database/drivers/pgsql/sql/create-table.js +2 -3
  482. package/build/src/database/drivers/pgsql/sql/delete.js +14 -17
  483. package/build/src/database/drivers/pgsql/sql/drop-database.js +2 -3
  484. package/build/src/database/drivers/pgsql/sql/drop-table.js +2 -3
  485. package/build/src/database/drivers/pgsql/sql/insert.js +2 -3
  486. package/build/src/database/drivers/pgsql/sql/update.js +24 -29
  487. package/build/src/database/drivers/pgsql/sql/upsert.js +9 -11
  488. package/build/src/database/drivers/pgsql/structure-sql.js +108 -120
  489. package/build/src/database/drivers/pgsql/table.js +60 -77
  490. package/build/src/database/drivers/sqlite/base.js +405 -478
  491. package/build/src/database/drivers/sqlite/column.js +54 -69
  492. package/build/src/database/drivers/sqlite/columns-index.js +22 -27
  493. package/build/src/database/drivers/sqlite/connection-sql-js.js +35 -42
  494. package/build/src/database/drivers/sqlite/foreign-key.js +18 -21
  495. package/build/src/database/drivers/sqlite/index.js +330 -373
  496. package/build/src/database/drivers/sqlite/index.native.js +55 -64
  497. package/build/src/database/drivers/sqlite/index.web.js +69 -87
  498. package/build/src/database/drivers/sqlite/options.js +25 -28
  499. package/build/src/database/drivers/sqlite/query-parser.js +2 -3
  500. package/build/src/database/drivers/sqlite/query.js +21 -24
  501. package/build/src/database/drivers/sqlite/query.native.js +20 -25
  502. package/build/src/database/drivers/sqlite/query.web.js +30 -37
  503. package/build/src/database/drivers/sqlite/sql/alter-table.js +159 -179
  504. package/build/src/database/drivers/sqlite/sql/create-index.js +2 -3
  505. package/build/src/database/drivers/sqlite/sql/create-table.js +2 -3
  506. package/build/src/database/drivers/sqlite/sql/delete.js +17 -22
  507. package/build/src/database/drivers/sqlite/sql/drop-table.js +2 -3
  508. package/build/src/database/drivers/sqlite/sql/insert.js +2 -3
  509. package/build/src/database/drivers/sqlite/sql/update.js +24 -29
  510. package/build/src/database/drivers/sqlite/sql/upsert.js +9 -11
  511. package/build/src/database/drivers/sqlite/structure-sql.js +49 -52
  512. package/build/src/database/drivers/sqlite/table-rebuilder.js +62 -75
  513. package/build/src/database/drivers/sqlite/table.js +102 -125
  514. package/build/src/database/drivers/structure-sql/utils.js +14 -17
  515. package/build/src/database/handler.js +9 -10
  516. package/build/src/database/initializer-from-require-context.js +76 -87
  517. package/build/src/database/migration/index.js +332 -395
  518. package/build/src/database/migrator/files-finder.js +40 -50
  519. package/build/src/database/migrator/types.js +2 -30
  520. package/build/src/database/migrator.js +454 -526
  521. package/build/src/database/pool/async-tracked-multi-connection.js +997 -1147
  522. package/build/src/database/pool/base-methods-forward.js +40 -43
  523. package/build/src/database/pool/base.js +298 -343
  524. package/build/src/database/pool/single-multi-use.js +93 -110
  525. package/build/src/database/query/alter-table-base.js +84 -99
  526. package/build/src/database/query/base.js +39 -46
  527. package/build/src/database/query/create-database-base.js +25 -30
  528. package/build/src/database/query/create-index-base.js +75 -94
  529. package/build/src/database/query/create-table-base.js +151 -193
  530. package/build/src/database/query/delete-base.js +14 -16
  531. package/build/src/database/query/drop-database-base.js +23 -28
  532. package/build/src/database/query/drop-table-base.js +42 -53
  533. package/build/src/database/query/from-base.js +30 -33
  534. package/build/src/database/query/from-plain.js +11 -13
  535. package/build/src/database/query/from-table.js +13 -15
  536. package/build/src/database/query/index.js +410 -472
  537. package/build/src/database/query/insert-base.js +143 -164
  538. package/build/src/database/query/join-base.js +35 -40
  539. package/build/src/database/query/join-object.js +128 -153
  540. package/build/src/database/query/join-plain.js +13 -15
  541. package/build/src/database/query/join-tracker.js +76 -90
  542. package/build/src/database/query/model-class-query.js +1134 -1370
  543. package/build/src/database/query/order-base.js +27 -30
  544. package/build/src/database/query/order-column.js +44 -53
  545. package/build/src/database/query/order-plain.js +20 -24
  546. package/build/src/database/query/preloader/belongs-to.js +210 -258
  547. package/build/src/database/query/preloader/ensure-model-class-initialized.js +8 -9
  548. package/build/src/database/query/preloader/has-many.js +240 -301
  549. package/build/src/database/query/preloader/has-one.js +91 -117
  550. package/build/src/database/query/preloader/selection.js +117 -129
  551. package/build/src/database/query/preloader.js +160 -185
  552. package/build/src/database/query/query-data.js +157 -201
  553. package/build/src/database/query/select-base.js +25 -27
  554. package/build/src/database/query/select-plain.js +13 -15
  555. package/build/src/database/query/select-table-and-column.js +21 -25
  556. package/build/src/database/query/update-base.js +35 -38
  557. package/build/src/database/query/upsert-base.js +93 -100
  558. package/build/src/database/query/where-base.js +32 -35
  559. package/build/src/database/query/where-combinator.js +25 -28
  560. package/build/src/database/query/where-hash.js +61 -68
  561. package/build/src/database/query/where-model-class-hash.js +414 -469
  562. package/build/src/database/query/where-not.js +18 -20
  563. package/build/src/database/query/where-plain.js +15 -17
  564. package/build/src/database/query/with-count.js +125 -159
  565. package/build/src/database/query-parser/base-query-parser.js +32 -37
  566. package/build/src/database/query-parser/from-parser.js +36 -45
  567. package/build/src/database/query-parser/group-parser.js +42 -50
  568. package/build/src/database/query-parser/joins-parser.js +28 -33
  569. package/build/src/database/query-parser/limit-parser.js +67 -70
  570. package/build/src/database/query-parser/options.js +75 -82
  571. package/build/src/database/query-parser/order-parser.js +36 -40
  572. package/build/src/database/query-parser/select-parser.js +49 -60
  573. package/build/src/database/query-parser/where-parser.js +36 -41
  574. package/build/src/database/record/acts-as-list.js +235 -273
  575. package/build/src/database/record/attachments/download.js +44 -45
  576. package/build/src/database/record/attachments/handle.js +141 -161
  577. package/build/src/database/record/attachments/normalize-input.js +128 -138
  578. package/build/src/database/record/attachments/storage-drivers/filesystem.js +77 -91
  579. package/build/src/database/record/attachments/storage-drivers/native.js +112 -121
  580. package/build/src/database/record/attachments/storage-drivers/s3.js +177 -208
  581. package/build/src/database/record/attachments/store.js +467 -539
  582. package/build/src/database/record/index.d.ts +109 -25
  583. package/build/src/database/record/index.d.ts.map +1 -1
  584. package/build/src/database/record/index.js +3502 -3898
  585. package/build/src/database/record/instance-relationships/base.js +234 -268
  586. package/build/src/database/record/instance-relationships/belongs-to.js +58 -73
  587. package/build/src/database/record/instance-relationships/has-many.js +225 -264
  588. package/build/src/database/record/instance-relationships/has-one.js +85 -105
  589. package/build/src/database/record/record-not-found-error.js +3 -2
  590. package/build/src/database/record/relationships/base.js +144 -166
  591. package/build/src/database/record/relationships/belongs-to.js +44 -51
  592. package/build/src/database/record/relationships/has-many.js +32 -40
  593. package/build/src/database/record/relationships/has-one.js +32 -40
  594. package/build/src/database/record/state-machine.js +156 -208
  595. package/build/src/database/record/user-module.js +32 -38
  596. package/build/src/database/record/validators/base.js +22 -24
  597. package/build/src/database/record/validators/format.js +36 -46
  598. package/build/src/database/record/validators/presence.js +18 -20
  599. package/build/src/database/record/validators/uniqueness.js +99 -117
  600. package/build/src/database/table-data/index.js +199 -231
  601. package/build/src/database/table-data/table-column.js +338 -382
  602. package/build/src/database/table-data/table-foreign-key.js +57 -66
  603. package/build/src/database/table-data/table-index.js +29 -36
  604. package/build/src/database/table-data/table-reference.js +10 -10
  605. package/build/src/database/use-database.js +32 -40
  606. package/build/src/environment-handlers/base.js +484 -544
  607. package/build/src/environment-handlers/browser.js +241 -294
  608. package/build/src/environment-handlers/node/cli/commands/background-jobs-main.js +16 -19
  609. package/build/src/environment-handlers/node/cli/commands/background-jobs-runner.js +18 -21
  610. package/build/src/environment-handlers/node/cli/commands/background-jobs-worker.js +22 -29
  611. package/build/src/environment-handlers/node/cli/commands/beacon.js +16 -19
  612. package/build/src/environment-handlers/node/cli/commands/cli-command-context.js +14 -15
  613. package/build/src/environment-handlers/node/cli/commands/console.js +99 -120
  614. package/build/src/environment-handlers/node/cli/commands/db/schema/dump.js +34 -39
  615. package/build/src/environment-handlers/node/cli/commands/db/schema/load.js +57 -63
  616. package/build/src/environment-handlers/node/cli/commands/db/seed.js +51 -63
  617. package/build/src/environment-handlers/node/cli/commands/destroy/migration.js +32 -40
  618. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts +4 -2
  619. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
  620. package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +326 -358
  621. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +10 -10
  622. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  623. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +729 -844
  624. package/build/src/environment-handlers/node/cli/commands/generate/migration.js +34 -38
  625. package/build/src/environment-handlers/node/cli/commands/generate/model.js +34 -38
  626. package/build/src/environment-handlers/node/cli/commands/init.js +56 -61
  627. package/build/src/environment-handlers/node/cli/commands/routes.js +51 -59
  628. package/build/src/environment-handlers/node/cli/commands/run-script.js +54 -68
  629. package/build/src/environment-handlers/node/cli/commands/runner.js +56 -74
  630. package/build/src/environment-handlers/node/cli/commands/server.js +93 -106
  631. package/build/src/environment-handlers/node/cli/commands/test.js +97 -113
  632. package/build/src/environment-handlers/node.js +753 -874
  633. package/build/src/error-logger.js +22 -21
  634. package/build/src/frontend-model-controller.js +2791 -3291
  635. package/build/src/frontend-model-resource/base-resource.d.ts +8 -3
  636. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  637. package/build/src/frontend-model-resource/base-resource.js +770 -865
  638. package/build/src/frontend-models/base.js +3136 -3593
  639. package/build/src/frontend-models/clear-pending-debounced-callback.js +7 -8
  640. package/build/src/frontend-models/event-hook-models.js +16 -21
  641. package/build/src/frontend-models/model-registry.js +9 -11
  642. package/build/src/frontend-models/outgoing-event-buffer.js +10 -17
  643. package/build/src/frontend-models/preloader.js +131 -149
  644. package/build/src/frontend-models/query.js +1557 -1855
  645. package/build/src/frontend-models/resource-config-validation.js +27 -37
  646. package/build/src/frontend-models/resource-definition.d.ts +6 -7
  647. package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
  648. package/build/src/frontend-models/resource-definition.js +237 -291
  649. package/build/src/frontend-models/transport-serialization.js +203 -266
  650. package/build/src/frontend-models/use-created-event.js +5 -7
  651. package/build/src/frontend-models/use-destroyed-event.js +80 -93
  652. package/build/src/frontend-models/use-model-class-event.js +79 -91
  653. package/build/src/frontend-models/use-updated-event.js +84 -97
  654. package/build/src/frontend-models/websocket-channel.js +381 -441
  655. package/build/src/frontend-models/websocket-publishers.js +142 -175
  656. package/build/src/http-client/header.js +13 -14
  657. package/build/src/http-client/index.js +116 -132
  658. package/build/src/http-client/request.js +71 -87
  659. package/build/src/http-client/response.js +122 -140
  660. package/build/src/http-client/websocket-client.js +15 -17
  661. package/build/src/http-server/client/index.js +409 -465
  662. package/build/src/http-server/client/params-to-object.js +124 -135
  663. package/build/src/http-server/client/request-buffer/form-data-part.js +111 -132
  664. package/build/src/http-server/client/request-buffer/header.js +15 -16
  665. package/build/src/http-server/client/request-buffer/index.js +446 -506
  666. package/build/src/http-server/client/request-parser.js +163 -186
  667. package/build/src/http-server/client/request-runner.js +226 -259
  668. package/build/src/http-server/client/request-timing.js +132 -151
  669. package/build/src/http-server/client/request.js +96 -108
  670. package/build/src/http-server/client/response.js +213 -235
  671. package/build/src/http-server/client/uploaded-file/memory-uploaded-file.js +25 -29
  672. package/build/src/http-server/client/uploaded-file/temporary-uploaded-file.js +25 -29
  673. package/build/src/http-server/client/uploaded-file/uploaded-file.js +33 -33
  674. package/build/src/http-server/client/websocket-request.js +114 -137
  675. package/build/src/http-server/client/websocket-session.js +1452 -1657
  676. package/build/src/http-server/cookie.js +216 -236
  677. package/build/src/http-server/development-reloader.js +190 -221
  678. package/build/src/http-server/index.js +451 -525
  679. package/build/src/http-server/remote-address.js +38 -50
  680. package/build/src/http-server/server-client.js +181 -208
  681. package/build/src/http-server/server-lock.js +153 -167
  682. package/build/src/http-server/websocket-channel-subscribers.js +81 -93
  683. package/build/src/http-server/websocket-channel.js +104 -117
  684. package/build/src/http-server/websocket-connection.js +96 -104
  685. package/build/src/http-server/websocket-event-log-store.js +350 -404
  686. package/build/src/http-server/websocket-events-host.js +145 -164
  687. package/build/src/http-server/websocket-events.js +47 -47
  688. package/build/src/http-server/worker-handler/channel-subscriber-dispatch.js +13 -14
  689. package/build/src/http-server/worker-handler/in-process.js +123 -141
  690. package/build/src/http-server/worker-handler/index.js +313 -349
  691. package/build/src/http-server/worker-handler/worker-script.js +4 -5
  692. package/build/src/http-server/worker-handler/worker-thread.js +240 -269
  693. package/build/src/initializer.js +31 -36
  694. package/build/src/jobs/mail-delivery.js +13 -15
  695. package/build/src/logger/base-logger.js +24 -26
  696. package/build/src/logger/console-logger.js +21 -23
  697. package/build/src/logger/file-logger.js +29 -31
  698. package/build/src/logger/outputs/array-output.js +37 -42
  699. package/build/src/logger/outputs/console-output.js +20 -24
  700. package/build/src/logger/outputs/file-output.js +43 -48
  701. package/build/src/logger/outputs/stdout-output.js +39 -48
  702. package/build/src/logger.js +338 -394
  703. package/build/src/mailer/backends/smtp.js +134 -163
  704. package/build/src/mailer/base.js +211 -251
  705. package/build/src/mailer/delivery.js +56 -64
  706. package/build/src/mailer/index.js +4 -22
  707. package/build/src/mailer.js +4 -13
  708. package/build/src/plugins/sqljs-wasm-route-controller.js +42 -52
  709. package/build/src/plugins/sqljs-wasm-route.js +28 -38
  710. package/build/src/record-payload-values.js +25 -28
  711. package/build/src/routes/app-routes.js +12 -14
  712. package/build/src/routes/base-route.js +112 -130
  713. package/build/src/routes/basic-route.js +83 -102
  714. package/build/src/routes/built-in/debug/controller.js +10 -10
  715. package/build/src/routes/built-in/errors/controller.js +5 -5
  716. package/build/src/routes/get-route.js +50 -63
  717. package/build/src/routes/hooks/frontend-model-command-route-hook.js +66 -80
  718. package/build/src/routes/index.js +36 -43
  719. package/build/src/routes/namespace-route.js +38 -47
  720. package/build/src/routes/plugin-routes.js +107 -124
  721. package/build/src/routes/post-route.js +51 -62
  722. package/build/src/routes/resolver.js +422 -494
  723. package/build/src/routes/resource-route.js +124 -143
  724. package/build/src/routes/root-route.js +7 -8
  725. package/build/src/testing/base-expect.js +13 -14
  726. package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +329 -405
  727. package/build/src/testing/browser-test-app.js +23 -29
  728. package/build/src/testing/expect-to-change.js +41 -50
  729. package/build/src/testing/expect-utils.js +139 -184
  730. package/build/src/testing/expect.js +638 -731
  731. package/build/src/testing/request-client.js +70 -85
  732. package/build/src/testing/test-files-finder.js +285 -339
  733. package/build/src/testing/test-filter-parser.js +124 -155
  734. package/build/src/testing/test-runner.js +883 -1020
  735. package/build/src/testing/test-suite-splitter.js +114 -142
  736. package/build/src/testing/test.js +216 -256
  737. package/build/src/utils/backtrace-cleaner-node.js +62 -69
  738. package/build/src/utils/backtrace-cleaner.js +188 -216
  739. package/build/src/utils/ensure-error.js +7 -7
  740. package/build/src/utils/event-emitter.js +4 -6
  741. package/build/src/utils/file-exists.js +9 -10
  742. package/build/src/utils/format-value.js +67 -76
  743. package/build/src/utils/model-scope.js +27 -31
  744. package/build/src/utils/nest-callbacks.js +10 -13
  745. package/build/src/utils/plain-object.js +5 -6
  746. package/build/src/utils/ransack.js +448 -563
  747. package/build/src/utils/rest-args-error.js +5 -6
  748. package/build/src/utils/singularize-model-name.js +9 -11
  749. package/build/src/utils/split-sql-statements.js +68 -79
  750. package/build/src/utils/to-import-specifier.js +24 -30
  751. package/build/src/utils/with-tracked-stack-async-hooks.js +60 -74
  752. package/build/src/utils/with-tracked-stack.js +14 -18
  753. package/build/src/velocious-error.js +27 -30
  754. package/build/templates/configuration.js +61 -0
  755. package/build/templates/generate-migration.js +11 -0
  756. package/build/templates/generate-model.js +6 -0
  757. package/build/templates/routes.js +11 -0
  758. package/build/testing/base-expect.js +17 -0
  759. package/build/testing/browser-frontend-model-event-hook-scenarios.js +520 -0
  760. package/build/testing/browser-test-app.js +32 -0
  761. package/build/testing/expect-to-change.js +55 -0
  762. package/build/testing/expect-utils.js +269 -0
  763. package/build/testing/expect.js +763 -0
  764. package/build/testing/request-client.js +90 -0
  765. package/build/testing/test-files-finder.js +364 -0
  766. package/build/testing/test-filter-parser.js +198 -0
  767. package/build/testing/test-runner.js +1168 -0
  768. package/build/testing/test-suite-splitter.js +177 -0
  769. package/build/testing/test.js +370 -0
  770. package/build/utils/backtrace-cleaner-node.js +87 -0
  771. package/build/utils/backtrace-cleaner.js +266 -0
  772. package/build/utils/ensure-error.js +15 -0
  773. package/build/utils/event-emitter.js +8 -0
  774. package/build/utils/file-exists.js +18 -0
  775. package/build/utils/format-value.js +101 -0
  776. package/build/utils/model-scope.js +56 -0
  777. package/build/utils/nest-callbacks.js +22 -0
  778. package/build/utils/plain-object.js +14 -0
  779. package/build/utils/ransack.js +859 -0
  780. package/build/utils/rest-args-error.js +14 -0
  781. package/build/utils/singularize-model-name.js +18 -0
  782. package/build/utils/split-sql-statements.js +88 -0
  783. package/build/utils/to-import-specifier.js +53 -0
  784. package/build/utils/with-tracked-stack-async-hooks.js +103 -0
  785. package/build/utils/with-tracked-stack.js +38 -0
  786. package/build/velocious-error.js +34 -0
  787. package/package.json +3 -3
  788. package/src/configuration-types.js +1 -1
  789. package/src/database/record/index.js +174 -25
  790. package/src/environment-handlers/node/cli/commands/generate/base-models.js +50 -21
  791. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +5 -5
  792. package/src/frontend-model-resource/base-resource.js +6 -2
  793. package/src/frontend-models/resource-definition.js +3 -3
  794. package/src/frontend-models/websocket-publishers.js +6 -6
@@ -0,0 +1,4119 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Defines this typedef.
5
+ * @typedef {{type: string, message: string}} ValidationErrorObjectType
6
+ */
7
+
8
+ /**
9
+ * LifecycleCallbackType type.
10
+ * @template {VelociousDatabaseRecord} [T=VelociousDatabaseRecord]
11
+ * @typedef {((model: T) => void | Promise<void>) | string} LifecycleCallbackType
12
+ */
13
+
14
+ /**
15
+ * Model class constructor type used for static `this` typing.
16
+ * @template {VelociousDatabaseRecord} T
17
+ * @typedef {{new (...args: Array<never>): T}} ModelConstructor
18
+ */
19
+
20
+ import timeout from "awaitery/build/timeout.js"
21
+ import BelongsToInstanceRelationship from "./instance-relationships/belongs-to.js"
22
+ import BelongsToRelationship from "./relationships/belongs-to.js"
23
+ import Configuration from "../../configuration.js"
24
+ import Current from "../../current.js"
25
+ import FromTable from "../query/from-table.js"
26
+ import Handler from "../handler.js"
27
+ import HasManyInstanceRelationship from "./instance-relationships/has-many.js"
28
+ import HasManyRelationship from "./relationships/has-many.js"
29
+ import HasOneInstanceRelationship from "./instance-relationships/has-one.js"
30
+ import HasOneRelationship from "./relationships/has-one.js"
31
+ import RecordAttachmentHandle from "./attachments/handle.js"
32
+ import * as inflection from "inflection"
33
+ import ModelClassQuery from "../query/model-class-query.js"
34
+ import Preloader from "../query/preloader.js"
35
+ import {readPayloadAssociationCount, readPayloadComputedAbility, readPayloadQueryData, setPayloadAssociationCount, setPayloadComputedAbility, setPayloadQueryData} from "../../record-payload-values.js"
36
+ import restArgsError from "../../utils/rest-args-error.js"
37
+ import singularizeModelName from "../../utils/singularize-model-name.js"
38
+ import {defineModelScope} from "../../utils/model-scope.js"
39
+ import {formatValue} from "../../utils/format-value.js"
40
+ import ValidatorsFormat from "./validators/format.js"
41
+ import ValidatorsPresence from "./validators/presence.js"
42
+ import ValidatorsUniqueness from "./validators/uniqueness.js"
43
+ import registerActsAsListCallbacks from "./acts-as-list.js"
44
+ import UUID from "pure-uuid"
45
+
46
+ /**
47
+ * AttachmentDriverConstructor type.
48
+ * @typedef {import("../../configuration-types.js").AttachmentDriverConstructor} AttachmentDriverConstructor
49
+ */
50
+
51
+ /** Stored values that a declared `"boolean"` cast reads back as `true`. */
52
+ const declaredBooleanTruthyValues = new Set([1, true, "1"])
53
+
54
+ /** Stored values that a declared `"boolean"` cast reads back as `false`. */
55
+ const declaredBooleanFalsyValues = new Set([0, false, "0"])
56
+
57
+ class ValidationError extends Error {
58
+ /**
59
+ * Narrows the runtime value to the documented type.
60
+ * @type {Record<string, ?> | undefined} - Velocious metadata for frontend-model error reporting.
61
+ */
62
+ velocious
63
+
64
+ /**
65
+ * Runs get model.
66
+ * @returns {VelociousDatabaseRecord} - The model.
67
+ */
68
+ getModel() {
69
+ if (!this._model) throw new Error("Model hasn't been set")
70
+
71
+ return this._model
72
+ }
73
+
74
+ /**
75
+ * Runs set model.
76
+ * @param {VelociousDatabaseRecord} model - Model instance.
77
+ * @returns {void} - No return value.
78
+ */
79
+ setModel(model) {
80
+ this._model = model
81
+ }
82
+
83
+ /**
84
+ * Runs get validation errors.
85
+ * @returns {Record<string, ValidationErrorObjectType[]>} - The validation errors.
86
+ */
87
+ getValidationErrors() {
88
+ if (!this._validationErrors) throw new Error("Validation errors hasn't been set")
89
+
90
+ return this._validationErrors
91
+ }
92
+
93
+ /**
94
+ * Runs set validation errors.
95
+ * @param {Record<string, ValidationErrorObjectType[]>} validationErrors - Validation errors to assign.
96
+ */
97
+ setValidationErrors(validationErrors) {
98
+ this._validationErrors = validationErrors
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Runs apply built record inverse relationship.
104
+ * @param {object} args - Options.
105
+ * @param {VelociousDatabaseRecord} args.parent - Parent record being built from.
106
+ * @param {{getRelationshipByName: VelociousDatabaseRecord["getRelationshipByName"]}} args.record - Newly built related record.
107
+ * @param {string | undefined | null} args.inverseOf - Inverse relationship name.
108
+ * @param {boolean} args.allowHasMany - Whether a has-many inverse should be appended.
109
+ * @returns {void}
110
+ */
111
+ function applyBuiltRecordInverseRelationship({allowHasMany, inverseOf, parent, record}) {
112
+ if (!inverseOf) return
113
+
114
+ const inverseInstanceRelationship = record.getRelationshipByName(inverseOf)
115
+
116
+ inverseInstanceRelationship.setAutoSave(false)
117
+
118
+ if (!allowHasMany || inverseInstanceRelationship.getType() == "hasOne") {
119
+ inverseInstanceRelationship.setLoaded(parent)
120
+ return
121
+ }
122
+
123
+ if (inverseInstanceRelationship.getType() == "hasMany") {
124
+ inverseInstanceRelationship.addToLoaded(parent)
125
+ return
126
+ }
127
+
128
+ throw new Error(`Unknown relationship type: ${inverseInstanceRelationship.getType()}`)
129
+ }
130
+
131
+ /**
132
+ * Build a related record and wire its inverse relationship to the parent.
133
+ * @param {VelociousDatabaseRecord} parent - Parent record building the relationship.
134
+ * @param {string} relationshipName - Relationship name being built.
135
+ * @param {Record<string, ?>} attributes - Attributes for the new related record.
136
+ * @param {boolean} allowHasMany - Whether has-many inverse relationships should append the parent.
137
+ * @returns {Record<string, ?>} - Built related record.
138
+ */
139
+ function buildRelatedRecordWithInverse(parent, relationshipName, attributes, allowHasMany) {
140
+ const instanceRelationship = parent.getRelationshipByName(relationshipName)
141
+ const record = instanceRelationship.build(attributes)
142
+ const inverseOf = instanceRelationship.getRelationship().getInverseOf()
143
+
144
+ applyBuiltRecordInverseRelationship({
145
+ allowHasMany,
146
+ inverseOf,
147
+ parent,
148
+ record: /**
149
+ * Narrows the runtime value to the documented type.
150
+ @type {{getRelationshipByName: VelociousDatabaseRecord["getRelationshipByName"]}} */ (record)
151
+ })
152
+
153
+ return record
154
+ }
155
+
156
+ /**
157
+ * Thrown by `Record.withAdvisoryLock` when the caller supplied a
158
+ * `timeoutMs` and the lock was not granted before it elapsed.
159
+ */
160
+ class AdvisoryLockTimeoutError extends Error {
161
+ /**
162
+ * Runs constructor.
163
+ * @param {string} message - Error message.
164
+ * @param {{name: string}} args - The advisory lock name that timed out.
165
+ */
166
+ constructor(message, {name}) {
167
+ super(message)
168
+ this.name = "AdvisoryLockTimeoutError"
169
+ this.lockName = name
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Thrown by `Record.withAdvisoryLockOrFail` when the lock is already held
175
+ * by another session at the moment of the call.
176
+ */
177
+ class AdvisoryLockBusyError extends Error {
178
+ /**
179
+ * Runs constructor.
180
+ * @param {string} message - Error message.
181
+ * @param {{name: string}} args - The advisory lock name that was already held.
182
+ */
183
+ constructor(message, {name}) {
184
+ super(message)
185
+ this.name = "AdvisoryLockBusyError"
186
+ this.lockName = name
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Thrown by `Record.withAdvisoryLock` / `withAdvisoryLockOrFail` when the
192
+ * caller supplied a `holdTimeoutMs` and the callback ran longer than it. The
193
+ * lock is released before this is thrown, so a hung holder can't block other
194
+ * sessions indefinitely. Note: the callback itself is not cancelled — pass an
195
+ * AbortSignal to the work if it needs to stop.
196
+ */
197
+ class AdvisoryLockHoldTimeoutError extends Error {
198
+ /**
199
+ * Runs constructor.
200
+ * @param {string} message - Error message.
201
+ * @param {{name: string}} args - The advisory lock name whose hold timed out.
202
+ */
203
+ constructor(message, {name}) {
204
+ super(message)
205
+ this.name = "AdvisoryLockHoldTimeoutError"
206
+ this.lockName = name
207
+ }
208
+ }
209
+
210
+ class TenantDatabaseScopeError extends Error {
211
+ /**
212
+ * Runs constructor.
213
+ * @param {string} message - Error message.
214
+ * @param {{modelName: string}} args - Context for the failed tenant-scoped model.
215
+ */
216
+ constructor(message, {modelName}) {
217
+ super(message)
218
+ this.name = "TenantDatabaseScopeError"
219
+ this.modelName = modelName
220
+ }
221
+ }
222
+
223
+ class VelociousDatabaseRecord {
224
+ /**
225
+ * Narrows the runtime value to the documented type.
226
+ @type {string | undefined} */
227
+ static modelName
228
+
229
+ /**
230
+ * Narrows the runtime value to the documented type.
231
+ @type {Promise<void> | null | undefined} */
232
+ static _initializeRecordPromise
233
+
234
+ /**
235
+ * Narrows the runtime value to the documented type.
236
+ @type {boolean | undefined} */
237
+ static _eagerLoadRecordMetadata
238
+
239
+ /**
240
+ * Returns the model name, preferring an explicit `static modelName` declaration
241
+ * over the JavaScript class `.name` property. This allows minified builds to
242
+ * preserve correct model names without relying on `keep_classnames`.
243
+ * @returns {string} - The model name.
244
+ */
245
+ static getModelName() {
246
+ if (typeof this.modelName === "string" && this.modelName.length > 0) return this.modelName
247
+
248
+ return this.name
249
+ }
250
+
251
+ static getAttributeNameToColumnNameMap() {
252
+ if (!this._attributeNameToColumnName) {
253
+ /**
254
+ * Narrows the runtime value to the documented type.
255
+ @type {Record<string, string>} */
256
+ this._attributeNameToColumnName = {}
257
+ }
258
+
259
+ return this._attributeNameToColumnName
260
+ }
261
+
262
+ /**
263
+ * Resolves the database column name for a record attribute name.
264
+ * @param {string} attributeName - Attribute name to resolve.
265
+ * @returns {string} - Mapped column name, or the underscored attribute name when no mapping exists.
266
+ */
267
+ static getColumnNameForAttributeName(attributeName) {
268
+ const columnName = this.getAttributeNameToColumnNameMap()[attributeName]
269
+
270
+ if (columnName) return columnName
271
+
272
+ return inflection.underscore(attributeName)
273
+ }
274
+
275
+ /**
276
+ * Runs define scope.
277
+ * @param {(...args: Array<?>) => ?} callback - Scope callback.
278
+ * @returns {((...args: Array<?>) => import("../query/model-class-query.js").default<typeof VelociousDatabaseRecord>) & {scope: (...args: Array<?>) => import("../../utils/model-scope.js").ModelScopeDescriptor}} - Scope helper.
279
+ */
280
+ static defineScope(callback) {
281
+ return defineModelScope({
282
+ callback,
283
+ modelClass: this,
284
+ startQuery: () => this._newQuery()
285
+ })
286
+ }
287
+
288
+ static getColumnNameToAttributeNameMap() {
289
+ if (!this._columnNameToAttributeName) {
290
+ /**
291
+ * Narrows the runtime value to the documented type.
292
+ @type {Record<string, string>} */
293
+ this._columnNameToAttributeName = {}
294
+ }
295
+
296
+ return this._columnNameToAttributeName
297
+ }
298
+
299
+ static getTranslationsMap() {
300
+ if (!this._translations) {
301
+ /**
302
+ * Narrows the runtime value to the documented type.
303
+ @type {Record<string, object>} */
304
+ this._translations = {}
305
+ }
306
+
307
+ return this._translations
308
+ }
309
+
310
+ static getValidatorsMap() {
311
+ if (!this._validators) {
312
+ /**
313
+ * Narrows the runtime value to the documented type.
314
+ @type {Record<string, import("./validators/base.js").default[]>} */
315
+ this._validators = {}
316
+ }
317
+
318
+ return this._validators
319
+ }
320
+
321
+ /**
322
+ * Runs get lifecycle callbacks map.
323
+ * @returns {Record<string, LifecycleCallbackType[]>} - Lifecycle callbacks keyed by name.
324
+ */
325
+ static getLifecycleCallbacksMap() {
326
+ if (!this._lifecycleCallbacks) {
327
+ /**
328
+ * Narrows the runtime value to the documented type.
329
+ @type {Record<string, LifecycleCallbackType[]>} */
330
+ this._lifecycleCallbacks = {}
331
+ }
332
+
333
+ return this._lifecycleCallbacks
334
+ }
335
+
336
+ static getValidatorTypesMap() {
337
+ if (!this._validatorTypes) {
338
+ /**
339
+ * Narrows the runtime value to the documented type.
340
+ @type {Record<string, typeof import("./validators/base.js").default>} */
341
+ this._validatorTypes = {}
342
+ }
343
+
344
+ return this._validatorTypes
345
+ }
346
+
347
+ /**
348
+ * Runs get attachments map.
349
+ * @returns {Record<string, {driver?: string | AttachmentDriverConstructor | Record<string, ?>, type: "hasOne" | "hasMany"}>} - Attachment definitions keyed by name.
350
+ */
351
+ static getAttachmentsMap() {
352
+ if (!this._attachmentsMap) {
353
+ /**
354
+ * Narrows the runtime value to the documented type.
355
+ @type {Record<string, {driver?: string | AttachmentDriverConstructor | Record<string, ?>, type: "hasOne" | "hasMany"}>} */
356
+ this._attachmentsMap = {}
357
+ }
358
+
359
+ return this._attachmentsMap
360
+ }
361
+
362
+ /**
363
+ * Attributes.
364
+ @type {Record<string, ?>} */
365
+ _attributes = {}
366
+
367
+ /**
368
+ * Changes.
369
+ @type {Record<string, ?>} */
370
+ _changes = {}
371
+
372
+ /**
373
+ * Columns as hash.
374
+ @type {Record<string, import("../drivers/base-column.js").default>} */
375
+ _columnsAsHash = {}
376
+
377
+ /**
378
+ * Connection.
379
+ @type {import("../drivers/base.js").default | undefined} */
380
+ __connection = undefined
381
+
382
+ /**
383
+ * Instance relationships.
384
+ @type {Record<string, import("./instance-relationships/base.js").default>} */
385
+ _instanceRelationships = {}
386
+ /**
387
+ * Attachments.
388
+ @type {Record<string, RecordAttachmentHandle>} */
389
+ _attachments = {}
390
+
391
+ /**
392
+ * Load cohort.
393
+ * @type {Array<VelociousDatabaseRecord> | undefined} - Shared reference to sibling records loaded in the same batch. Used by auto-preload.
394
+ */
395
+ _loadCohort = undefined
396
+
397
+ /**
398
+ * Table name.
399
+ @type {string | undefined} */
400
+ __tableName = undefined
401
+
402
+ /**
403
+ * Validation errors.
404
+ @type {Record<string, ValidationErrorObjectType[]>} */
405
+ _validationErrors = {}
406
+
407
+ static validatorTypes() {
408
+ return this.getValidatorTypesMap()
409
+ }
410
+
411
+ /**
412
+ * Runs register validator type.
413
+ * @param {string} name - Name.
414
+ * @param {typeof import("./validators/base.js").default} validatorClass - Validator class.
415
+ */
416
+ static registerValidatorType(name, validatorClass) {
417
+ this.validatorTypes()[name] = validatorClass
418
+ }
419
+
420
+ /**
421
+ * Runs register lifecycle callback.
422
+ * @param {"afterCreate" | "afterDestroy" | "afterSave" | "afterUpdate" | "beforeCreate" | "beforeDestroy" | "beforeSave" | "beforeUpdate" | "beforeValidation"} callbackName - Callback type.
423
+ * @param {LifecycleCallbackType} callback - Callback function or instance method name.
424
+ * @returns {void}
425
+ */
426
+ static registerLifecycleCallback(callbackName, callback) {
427
+ const callbacks = this.getLifecycleCallbacksMap()
428
+
429
+ if (!callbacks[callbackName]) {
430
+ callbacks[callbackName] = []
431
+ }
432
+
433
+ callbacks[callbackName].push(callback)
434
+ }
435
+
436
+ /**
437
+ * Runs before validation.
438
+ * @template {VelociousDatabaseRecord} T
439
+ * @this {ModelConstructor<T> & typeof VelociousDatabaseRecord}
440
+ * @param {LifecycleCallbackType<T>} callback - Callback function or instance method name.
441
+ * @returns {void}
442
+ */
443
+ static beforeValidation(callback) {
444
+ this.registerLifecycleCallback("beforeValidation", /** @type {LifecycleCallbackType} */ (callback))
445
+ }
446
+
447
+ /**
448
+ * Runs before save.
449
+ * @template {VelociousDatabaseRecord} T
450
+ * @this {ModelConstructor<T> & typeof VelociousDatabaseRecord}
451
+ * @param {LifecycleCallbackType<T>} callback - Callback function or instance method name.
452
+ * @returns {void}
453
+ */
454
+ static beforeSave(callback) {
455
+ this.registerLifecycleCallback("beforeSave", /** @type {LifecycleCallbackType} */ (callback))
456
+ }
457
+
458
+ /**
459
+ * Runs before create.
460
+ * @template {VelociousDatabaseRecord} T
461
+ * @this {ModelConstructor<T> & typeof VelociousDatabaseRecord}
462
+ * @param {LifecycleCallbackType<T>} callback - Callback function or instance method name.
463
+ * @returns {void}
464
+ */
465
+ static beforeCreate(callback) {
466
+ this.registerLifecycleCallback("beforeCreate", /** @type {LifecycleCallbackType} */ (callback))
467
+ }
468
+
469
+ /**
470
+ * Runs before update.
471
+ * @template {VelociousDatabaseRecord} T
472
+ * @this {ModelConstructor<T> & typeof VelociousDatabaseRecord}
473
+ * @param {LifecycleCallbackType<T>} callback - Callback function or instance method name.
474
+ * @returns {void}
475
+ */
476
+ static beforeUpdate(callback) {
477
+ this.registerLifecycleCallback("beforeUpdate", /** @type {LifecycleCallbackType} */ (callback))
478
+ }
479
+
480
+ /**
481
+ * Runs before destroy.
482
+ * @template {VelociousDatabaseRecord} T
483
+ * @this {ModelConstructor<T> & typeof VelociousDatabaseRecord}
484
+ * @param {LifecycleCallbackType<T>} callback - Callback function or instance method name.
485
+ * @returns {void}
486
+ */
487
+ static beforeDestroy(callback) {
488
+ this.registerLifecycleCallback("beforeDestroy", /** @type {LifecycleCallbackType} */ (callback))
489
+ }
490
+
491
+ /**
492
+ * Runs after save.
493
+ * @template {VelociousDatabaseRecord} T
494
+ * @this {ModelConstructor<T> & typeof VelociousDatabaseRecord}
495
+ * @param {LifecycleCallbackType<T>} callback - Callback function or instance method name.
496
+ * @returns {void}
497
+ */
498
+ static afterSave(callback) {
499
+ this.registerLifecycleCallback("afterSave", /** @type {LifecycleCallbackType} */ (callback))
500
+ }
501
+
502
+ /**
503
+ * Runs after create.
504
+ * @template {VelociousDatabaseRecord} T
505
+ * @this {ModelConstructor<T> & typeof VelociousDatabaseRecord}
506
+ * @param {LifecycleCallbackType<T>} callback - Callback function or instance method name.
507
+ * @returns {void}
508
+ */
509
+ static afterCreate(callback) {
510
+ this.registerLifecycleCallback("afterCreate", /** @type {LifecycleCallbackType} */ (callback))
511
+ }
512
+
513
+ /**
514
+ * Runs after update.
515
+ * @template {VelociousDatabaseRecord} T
516
+ * @this {ModelConstructor<T> & typeof VelociousDatabaseRecord}
517
+ * @param {LifecycleCallbackType<T>} callback - Callback function or instance method name.
518
+ * @returns {void}
519
+ */
520
+ static afterUpdate(callback) {
521
+ this.registerLifecycleCallback("afterUpdate", /** @type {LifecycleCallbackType} */ (callback))
522
+ }
523
+
524
+ /**
525
+ * Runs after destroy.
526
+ * @template {VelociousDatabaseRecord} T
527
+ * @this {ModelConstructor<T> & typeof VelociousDatabaseRecord}
528
+ * @param {LifecycleCallbackType<T>} callback - Callback function or instance method name.
529
+ * @returns {void}
530
+ */
531
+ static afterDestroy(callback) {
532
+ this.registerLifecycleCallback("afterDestroy", /** @type {LifecycleCallbackType} */ (callback))
533
+ }
534
+
535
+ /**
536
+ * Runs get validator type.
537
+ * @param {string} validatorName - Validator name.
538
+ * @returns {typeof import("./validators/base.js").default} - The validator type.
539
+ */
540
+ static getValidatorType(validatorName) {
541
+ if (!(validatorName in this.validatorTypes())) throw new Error(`Validator type ${validatorName} not found`)
542
+
543
+ return this.validatorTypes()[validatorName]
544
+ }
545
+
546
+ /**
547
+ * Runs relationship exists.
548
+ * @param {string} relationshipName - Relationship name.
549
+ * @returns {boolean} - Whether relationship exists.
550
+ */
551
+ static _relationshipExists(relationshipName) {
552
+ if (relationshipName in this.getRelationshipsMap()) {
553
+ return true
554
+ }
555
+
556
+ return false
557
+ }
558
+
559
+ /**
560
+ * RelationshipScopeCallback type.
561
+ * @typedef {(query: import("../query/model-class-query.js").default<typeof VelociousDatabaseRecord>) => (import("../query/model-class-query.js").default<typeof VelociousDatabaseRecord> | void)} RelationshipScopeCallback
562
+ */
563
+ /**
564
+ * RelationshipDataArgumentType type.
565
+ * @typedef {object} RelationshipDataArgumentType
566
+ * @property {boolean} [autoload] - Disable auto-batch-preload for this relationship by passing false. Default true.
567
+ * @property {string} [className] - Model class name for the related record.
568
+ * @property {string} [dependent] - Dependent action when parent is destroyed (e.g. "destroy").
569
+ * @property {typeof VelociousDatabaseRecord} [klass] - Model class for the related record.
570
+ * @property {RelationshipScopeCallback} [scope] - Optional scope callback for the relationship.
571
+ * @property {string} [type] - Relationship type (e.g. "hasMany", "belongsTo").
572
+ */
573
+ /**
574
+ * Runs define relationship.
575
+ * @param {string} relationshipName - Relationship name.
576
+ * @param {RelationshipDataArgumentType} data - Data payload.
577
+ */
578
+ static _defineRelationship(relationshipName, data) {
579
+ if (!relationshipName) throw new Error(`Invalid relationship name given: ${relationshipName}`)
580
+ if (this._relationshipExists(relationshipName)) throw new Error(`Relationship ${relationshipName} already exists`)
581
+
582
+ const actualData = Object.assign(
583
+ {
584
+ modelClass: this,
585
+ relationshipName,
586
+ type: "hasMany"
587
+ },
588
+ data
589
+ )
590
+
591
+ if (!actualData.className && !actualData.klass) {
592
+ actualData.className = singularizeModelName(relationshipName)
593
+ }
594
+
595
+ let relationship
596
+ const prototype = /**
597
+ * Narrows the runtime value to the documented type.
598
+ @type {Record<string, ?>} */ (/**
599
+ * Narrows the runtime value to the documented type.
600
+ @type {?} */ (this.prototype))
601
+
602
+ if (actualData.type == "belongsTo") {
603
+ relationship = new BelongsToRelationship(actualData)
604
+
605
+ prototype[relationshipName] = function() {
606
+ const relationship = this.getRelationshipByName(relationshipName)
607
+
608
+ return relationship.loaded()
609
+ }
610
+
611
+ prototype[`build${inflection.camelize(relationshipName)}`] = function(/**
612
+ * Narrows the runtime value to the documented type.
613
+ @type {Record<string, ?>} */ attributes) {
614
+ return buildRelatedRecordWithInverse(/**
615
+ * Narrows the runtime value to the documented type.
616
+ @type {VelociousDatabaseRecord} */ (this), relationshipName, attributes, true)
617
+ }
618
+
619
+ prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
620
+ return await this.loadRelationship(relationshipName)
621
+ }
622
+
623
+ prototype[`${relationshipName}OrLoad`] = async function() {
624
+ return await this.relationshipOrLoad(relationshipName)
625
+ }
626
+
627
+ prototype[`set${inflection.camelize(relationshipName)}`] = function(/**
628
+ * Narrows the runtime value to the documented type.
629
+ @type {?} */ model) {
630
+ const relationship = this.getRelationshipByName(relationshipName)
631
+
632
+ relationship.setLoaded(model)
633
+ relationship.setDirty(true)
634
+ }
635
+ } else if (actualData.type == "hasMany") {
636
+ relationship = new HasManyRelationship(actualData)
637
+
638
+ prototype[relationshipName] = function() {
639
+ return /** Narrows the runtime value to the documented type. @type {import("./instance-relationships/has-many.js").default<?, ?>} */ (this.getRelationshipByName(relationshipName))
640
+ }
641
+
642
+ prototype[`${relationshipName}Loaded`] = function() {
643
+ return this.getRelationshipByName(relationshipName).loaded()
644
+ }
645
+
646
+ prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
647
+ return await this.loadRelationship(relationshipName)
648
+ }
649
+
650
+ prototype[`${relationshipName}OrLoad`] = async function() {
651
+ return await this.relationshipOrLoad(relationshipName)
652
+ }
653
+ } else if (actualData.type == "hasOne") {
654
+ relationship = new HasOneRelationship(actualData)
655
+
656
+ prototype[relationshipName] = function() {
657
+ return this.getRelationshipByName(relationshipName).loaded()
658
+ }
659
+
660
+ prototype[`build${inflection.camelize(relationshipName)}`] = function(/**
661
+ * Narrows the runtime value to the documented type.
662
+ @type {Record<string, ?>} */ attributes) {
663
+ return buildRelatedRecordWithInverse(/**
664
+ * Narrows the runtime value to the documented type.
665
+ @type {VelociousDatabaseRecord} */ (this), relationshipName, attributes, false)
666
+ }
667
+
668
+ prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
669
+ return await this.loadRelationship(relationshipName)
670
+ }
671
+
672
+ prototype[`${relationshipName}OrLoad`] = async function() {
673
+ return await this.relationshipOrLoad(relationshipName)
674
+ }
675
+ } else {
676
+ throw new Error(`Unknown relationship type: ${actualData.type}`)
677
+ }
678
+
679
+ this.getRelationshipsMap()[relationshipName] = relationship
680
+ }
681
+
682
+ /**
683
+ * Runs normalize relationship args.
684
+ * @param {RelationshipScopeCallback | object | undefined} scopeOrOptions - Scope callback or options.
685
+ * @param {object | undefined} options - Options.
686
+ * @returns {{scope: (RelationshipScopeCallback | undefined), relationshipOptions: object}} - Normalized arguments.
687
+ */
688
+ static _normalizeRelationshipArgs(scopeOrOptions, options) {
689
+ if (typeof scopeOrOptions == "function") {
690
+ return {
691
+ scope: /**
692
+ * Narrows the runtime value to the documented type.
693
+ @type {RelationshipScopeCallback} */ (scopeOrOptions),
694
+ relationshipOptions: options || {}
695
+ }
696
+ }
697
+
698
+ return {
699
+ scope: undefined,
700
+ relationshipOptions: scopeOrOptions || {}
701
+ }
702
+ }
703
+
704
+ /**
705
+ * Registers afterCreate, afterSave, and afterDestroy callbacks to sync
706
+ * a counter cache column on the parent model. The column name follows
707
+ * the convention `<childModelPluralCamelCase>Count`.
708
+ * @param {string} relationshipName - The belongsTo relationship name.
709
+ */
710
+ static _registerCounterCacheCallbacks(relationshipName) {
711
+ const ChildModel = this
712
+
713
+ /**
714
+ * Atomically recomputes the counter cache column on the parent via a
715
+ * single UPDATE ... SET col = (SELECT COUNT(*)) so concurrent
716
+ * creates/destroys cannot race into a stale count.
717
+ * @param {number | string | null} parentId - Parent primary-key value.
718
+ * @returns {Promise<void>} - Resolves when the counter cache has been synced.
719
+ */
720
+ async function syncCounter(parentId) {
721
+ if (!parentId) return
722
+
723
+ const relationship = ChildModel.getRelationshipByName(relationshipName)
724
+ const ParentModel = relationship.getTargetModelClass()
725
+
726
+ if (!ParentModel) return
727
+
728
+ const primaryKey = relationship.getPrimaryKey()
729
+ const fk = relationship.getForeignKey()
730
+ const childModelName = ChildModel.getModelName()
731
+ const counterColumn = inflection.underscore(`${inflection.pluralize(childModelName)}Count`)
732
+ const parentTable = ParentModel.tableName()
733
+ const childTable = ChildModel.tableName()
734
+ const pkColumn = inflection.underscore(primaryKey)
735
+ const connection = ParentModel.connection()
736
+ const quoted = connection.quote(parentId)
737
+
738
+ const sql = `UPDATE ${connection.quoteTable(parentTable)} SET ${connection.quoteColumn(counterColumn)} = (SELECT COUNT(*) FROM ${connection.quoteTable(childTable)} WHERE ${connection.quoteColumn(fk)} = ${quoted}) WHERE ${connection.quoteColumn(pkColumn)} = ${quoted}`
739
+
740
+ await connection.query(sql, {logName: `${ParentModel.name} Update`})
741
+ }
742
+
743
+ /**
744
+ * Runs read fk attribute.
745
+ * @param {?} record - Child record instance.
746
+ * @returns {?} - Current foreign-key attribute value.
747
+ */
748
+ function readFkAttribute(record) {
749
+ const relationship = ChildModel.getRelationshipByName(relationshipName)
750
+ const fkAttribute = inflection.camelize(relationship.getForeignKey().replace(/_id$/, "Id"), true)
751
+
752
+ return record.readAttribute(fkAttribute)
753
+ }
754
+
755
+ ChildModel.afterCreate(async (record) => {
756
+ await syncCounter(readFkAttribute(record))
757
+ })
758
+
759
+ ChildModel.afterDestroy(async (record) => {
760
+ await syncCounter(readFkAttribute(record))
761
+ })
762
+
763
+ ChildModel.beforeSave(async (record) => {
764
+ const model = /**
765
+ * Narrows the runtime value to the documented type.
766
+ @type {?} */ (record)
767
+
768
+ if (model.isNewRecord()) return
769
+
770
+ const relationship = ChildModel.getRelationshipByName(relationshipName)
771
+ const fkColumn = relationship.getForeignKey()
772
+
773
+ // Detect FK change via direct attribute assignment or relationship setter.
774
+ const directChange = fkColumn in model._changes
775
+ const belongsToChange = model._instanceRelationships?.[relationshipName]?.getDirty?.()
776
+
777
+ if (directChange || belongsToChange) {
778
+ model[`_counterCachePrev_${relationshipName}`] = model._attributes[fkColumn]
779
+ }
780
+ })
781
+
782
+ ChildModel.afterSave(async (record) => {
783
+ const model = /**
784
+ * Narrows the runtime value to the documented type.
785
+ @type {?} */ (record)
786
+ const prevKey = `_counterCachePrev_${relationshipName}`
787
+ const previousParentId = model[prevKey]
788
+
789
+ if (previousParentId !== undefined) {
790
+ delete model[prevKey]
791
+ await syncCounter(previousParentId)
792
+ await syncCounter(readFkAttribute(model))
793
+ }
794
+ })
795
+ }
796
+
797
+ /**
798
+ * Runs get relationship by name.
799
+ * @param {string} relationshipName - Relationship name.
800
+ * @returns {import("./relationships/base.js").default} - The relationship by name.
801
+ */
802
+ static getRelationshipByName(relationshipName) {
803
+ const relationship = this.getRelationshipsMap()[relationshipName]
804
+
805
+ if (!relationship) throw new Error(`No relationship in ${this.name} called "${relationshipName}" in list: ${Object.keys(this.getRelationshipsMap()).join(", ")}`)
806
+
807
+ return relationship
808
+ }
809
+
810
+ /**
811
+ * Runs get relationships.
812
+ * @returns {Array<import("./relationships/base.js").default>} - The relationships.
813
+ */
814
+ static getRelationships() {
815
+ return Object.values(this.getRelationshipsMap())
816
+ }
817
+
818
+ /**
819
+ * Runs get relationships map.
820
+ * @returns {Record<string, import("./relationships/base.js").default>} - Relationship definitions keyed by name.
821
+ */
822
+ static getRelationshipsMap() {
823
+ if (!Object.hasOwn(this, "_relationships")) {
824
+ /**
825
+ * Narrows the runtime value to the documented type.
826
+ @type {Record<string, import("./relationships/base.js").default>} */
827
+ this._relationships = {}
828
+ }
829
+
830
+ return /** Narrows the runtime value to the documented type. @type {Record<string, import("./relationships/base.js").default>} */ (this._relationships)
831
+ }
832
+
833
+ /**
834
+ * Runs get relationship names.
835
+ * @returns {Array<string>} - The relationship names.
836
+ */
837
+ static getRelationshipNames() {
838
+ return this.getRelationships().map((relationship) => relationship.getRelationshipName())
839
+ }
840
+
841
+ /**
842
+ * Register a consumer-defined queryData entry. The callback receives
843
+ * a grouped query already joined down the relationship chain from the
844
+ * root of `.queryData(...)` to this model, already filtered by the
845
+ * root parent IDs, and with `parent_id` pre-selected — so the fn
846
+ * only needs to add its own SELECT (and optionally joins/where). Any
847
+ * aliases the fn selects are attached to each **root** record via
848
+ * `record.queryData(aliasName)`. Multi-column selects are fine — one
849
+ * alias maps to one queryData key.
850
+ *
851
+ * **Quote AS aliases on PostgreSQL.** PostgreSQL folds unquoted
852
+ * identifiers (including SELECT aliases) to lowercase, so a
853
+ * `... AS manualTasksCount` lands in the result row as
854
+ * `manualtaskscount` while the lookup `record.queryData("manualTasksCount")`
855
+ * never finds it. Use `driver.quoteColumn("manualTasksCount")` for the
856
+ * alias to preserve the case on every supported driver:
857
+ * query.select(`COUNT(...) AS ${driver.quoteColumn("manualTasksCount")}`)
858
+ * @param {string} name - Identifier used in the `.queryData(...)` spec.
859
+ * @param {import("../query/query-data.js").QueryDataFn} fn - Callback that mutates the query.
860
+ * @returns {void}
861
+ */
862
+ static queryData(name, fn) {
863
+ if (!name || typeof name !== "string") {
864
+ throw new Error(`Invalid queryData name: ${name}`)
865
+ }
866
+
867
+ if (typeof fn !== "function") {
868
+ throw new Error(`queryData fn for ${this.name}.queryData(${JSON.stringify(name)}) must be a function`)
869
+ }
870
+
871
+ const map = this.getQueryDataMap()
872
+
873
+ // Use Object.hasOwn so a name that happens to match an inherited
874
+ // Object.prototype key (e.g. "toString", "constructor") isn't
875
+ // falsely treated as already registered.
876
+ if (Object.hasOwn(map, name)) {
877
+ throw new Error(`queryData for ${this.name}.${name} is already registered`)
878
+ }
879
+
880
+ map[name] = fn
881
+ }
882
+
883
+ /**
884
+ * Runs get query data map.
885
+ * @returns {Record<string, import("../query/query-data.js").QueryDataFn>} - queryData registrations keyed by name.
886
+ */
887
+ static getQueryDataMap() {
888
+ if (!Object.hasOwn(this, "_queryDataRegistrations")) {
889
+ // Prototype-less map so bracket access can only ever surface
890
+ // registrations actually made on this class — never inherited
891
+ // Object.prototype members.
892
+ /**
893
+ * Narrows the runtime value to the documented type.
894
+ @type {Record<string, import("../query/query-data.js").QueryDataFn>} */
895
+ this._queryDataRegistrations = Object.create(null)
896
+ }
897
+
898
+ return /** Narrows the runtime value to the documented type. @type {Record<string, import("../query/query-data.js").QueryDataFn>} */ (this._queryDataRegistrations)
899
+ }
900
+
901
+ /**
902
+ * Runs get query data by name.
903
+ * @param {string} name - queryData name.
904
+ * @returns {import("../query/query-data.js").QueryDataFn | null} - Registered fn or null when not found.
905
+ */
906
+ static getQueryDataByName(name) {
907
+ const map = this.getQueryDataMap()
908
+
909
+ // Own-property lookup so a spec containing e.g. "toString" doesn't
910
+ // resolve to an inherited Object.prototype member — matching the
911
+ // Object.hasOwn guard used when registering.
912
+ return Object.hasOwn(map, name) ? map[name] : null
913
+ }
914
+
915
+ /**
916
+ * Runs get attachments.
917
+ * @returns {Record<string, {driver?: string | AttachmentDriverConstructor | Record<string, ?>, type: "hasOne" | "hasMany"}>} - Attachment definitions.
918
+ */
919
+ static getAttachments() {
920
+ return this.getAttachmentsMap()
921
+ }
922
+
923
+ /**
924
+ * Runs get attachment by name.
925
+ * @param {string} attachmentName - Attachment name.
926
+ * @returns {{driver?: string | AttachmentDriverConstructor | Record<string, ?>, type: "hasOne" | "hasMany"}} - Attachment definition.
927
+ */
928
+ static getAttachmentByName(attachmentName) {
929
+ const definition = this.getAttachmentsMap()[attachmentName]
930
+
931
+ if (!definition) throw new Error(`No attachment in ${this.name} called "${attachmentName}" in list: ${Object.keys(this.getAttachmentsMap()).join(", ")}`)
932
+
933
+ return definition
934
+ }
935
+
936
+ /**
937
+ * Runs get relationship by name.
938
+ * @param {string} relationshipName - Relationship name.
939
+ * @returns {import("./instance-relationships/base.js").default} - The relationship by name.
940
+ */
941
+ getRelationshipByName(relationshipName) {
942
+ if (!(relationshipName in this._instanceRelationships)) {
943
+ const modelClassRelationship = this.getModelClass().getRelationshipByName(relationshipName)
944
+ const relationshipType = modelClassRelationship.getType()
945
+ let instanceRelationship
946
+
947
+ if (relationshipType == "belongsTo") {
948
+ instanceRelationship = new BelongsToInstanceRelationship({model: this, relationship: modelClassRelationship})
949
+ } else if (relationshipType == "hasMany") {
950
+ instanceRelationship = new HasManyInstanceRelationship({model: this, relationship: modelClassRelationship})
951
+ } else if (relationshipType == "hasOne") {
952
+ instanceRelationship = new HasOneInstanceRelationship({model: this, relationship: modelClassRelationship})
953
+ } else {
954
+ throw new Error(`Unknown relationship type: ${relationshipType}`)
955
+ }
956
+
957
+ this._instanceRelationships[relationshipName] = instanceRelationship
958
+ }
959
+
960
+ return this._instanceRelationships[relationshipName]
961
+ }
962
+
963
+ /**
964
+ * Preloads relationship(s) onto this already-loaded record. Accepts either a
965
+ * query built via `Model.preload(...).select(...)` or a raw preload spec
966
+ * (string / array / nested object). A relationship that is already preloaded
967
+ * with all the required columns present is left untouched unless `force` is
968
+ * set. Preloading onto the relationship cache lets later accessors reuse the
969
+ * loaded data instead of issuing identical queries.
970
+ * @param {import("../query/model-class-query.js").default | import("../query/index.js").NestedPreloadRecord | string | Array<string | import("../query/index.js").NestedPreloadRecord>} queryOrSpec - Preload source.
971
+ * @param {{force?: boolean}} [options] - Options.
972
+ * @returns {Promise<void>} - Resolves when preloading completes.
973
+ */
974
+ async preload(queryOrSpec, options = {}) {
975
+ await Preloader.preload([this], queryOrSpec, options)
976
+ }
977
+
978
+ /**
979
+ * Runs load relationship.
980
+ * @param {string} relationshipName - Relationship name.
981
+ * @returns {Promise<?>} - Loaded relationship value.
982
+ */
983
+ async loadRelationship(relationshipName) {
984
+ const relationship = this.getRelationshipByName(relationshipName)
985
+
986
+ await relationship.load()
987
+
988
+ return relationship.loaded()
989
+ }
990
+
991
+ /**
992
+ * Runs relationship or load.
993
+ * @param {string} relationshipName - Relationship name.
994
+ * @returns {Promise<?>} - Loaded relationship value.
995
+ */
996
+ async relationshipOrLoad(relationshipName) {
997
+ const relationship = this.getRelationshipByName(relationshipName)
998
+
999
+ return await relationship.autoloadOrLoad()
1000
+ }
1001
+
1002
+ /**
1003
+ * Runs get attachment by name.
1004
+ * @param {string} attachmentName - Attachment name.
1005
+ * @returns {RecordAttachmentHandle} - Attachment handle.
1006
+ */
1007
+ getAttachmentByName(attachmentName) {
1008
+ if (!(attachmentName in this._attachments)) {
1009
+ const attachmentDefinition = this.getModelClass().getAttachmentByName(attachmentName)
1010
+
1011
+ this._attachments[attachmentName] = new RecordAttachmentHandle({
1012
+ model: this,
1013
+ name: attachmentName,
1014
+ type: attachmentDefinition.type
1015
+ })
1016
+ }
1017
+
1018
+ return this._attachments[attachmentName]
1019
+ }
1020
+
1021
+ /**
1022
+ * Adds a belongs-to-relationship to the model.
1023
+ * @param {string} relationshipName The name of the relationship.
1024
+ * @param {RelationshipScopeCallback | object} [scopeOrOptions] The scope callback or options for the relationship.
1025
+ * @param {object} [options] The options for the relationship.
1026
+ */
1027
+ static belongsTo(relationshipName, scopeOrOptions, options) {
1028
+ const {scope, relationshipOptions} = this._normalizeRelationshipArgs(scopeOrOptions, options)
1029
+
1030
+ this._defineRelationship(relationshipName, Object.assign({type: "belongsTo", scope}, relationshipOptions))
1031
+
1032
+ if (/**
1033
+ * Narrows the runtime value to the documented type.
1034
+ @type {?} */ (relationshipOptions)?.counterCache) {
1035
+ this._registerCounterCacheCallbacks(relationshipName)
1036
+ }
1037
+ }
1038
+
1039
+ /**
1040
+ * Runs connection.
1041
+ * @param {object} [args] - Options.
1042
+ * @param {boolean} [args.enforceTenantDatabaseScope] - Whether tenant-switched models must resolve a tenant database identifier.
1043
+ * @returns {import("../drivers/base.js").default} - The connection.
1044
+ */
1045
+ static connection({enforceTenantDatabaseScope = true, ...restArgs} = {}) {
1046
+ restArgsError(restArgs)
1047
+
1048
+ const databasePool = this._getConfiguration().getDatabasePool(this.getDatabaseIdentifier({enforceTenantDatabaseScope}))
1049
+ const connection = databasePool.getCurrentConnection()
1050
+
1051
+ if (!connection) throw new Error("No connection?")
1052
+
1053
+ return connection
1054
+ }
1055
+
1056
+ /**
1057
+ * Runs create.
1058
+ * @template {typeof VelociousDatabaseRecord} MC
1059
+ * @this {MC}
1060
+ * @param {Record<string, ?>} [attributes] - Attributes.
1061
+ * @returns {Promise<InstanceType<MC>>} - Resolves with the create.
1062
+ */
1063
+ static async create(attributes) {
1064
+ await this.ensureInitialized()
1065
+
1066
+ const record = /**
1067
+ * Narrows the runtime value to the documented type.
1068
+ @type {InstanceType<MC>} */ (new this(attributes))
1069
+
1070
+ await record.save()
1071
+
1072
+ return record
1073
+ }
1074
+
1075
+ /**
1076
+ * Runs get configuration.
1077
+ * @returns {import("../../configuration.js").default} - The configuration.
1078
+ */
1079
+ static _getConfiguration() {
1080
+ if (!this._configuration) {
1081
+ this._configuration = Configuration.current()
1082
+
1083
+ if (!this._configuration) {
1084
+ throw new Error("Configuration hasn't been set (model class probably hasn't been initialized)")
1085
+ }
1086
+ }
1087
+
1088
+ return this._configuration
1089
+ }
1090
+
1091
+ /**
1092
+ * Runs get configuration.
1093
+ * @returns {import("../../configuration.js").default} - The configuration.
1094
+ */
1095
+ _getConfiguration() {
1096
+ return this.getModelClass()._getConfiguration()
1097
+ }
1098
+
1099
+ /**
1100
+ * Adds a has-many-relationship to the model class.
1101
+ * @param {string} relationshipName The name of the relationship (e.g. "posts")
1102
+ * @param {RelationshipScopeCallback | object} [scopeOrOptions] The scope callback or options for the relationship.
1103
+ * @param {object} [options] The options for the relationship (e.g. {className: "Post"})
1104
+ * @returns {void} - No return value.
1105
+ */
1106
+ static hasMany(relationshipName, scopeOrOptions, options) {
1107
+ const {scope, relationshipOptions} = this._normalizeRelationshipArgs(scopeOrOptions, options)
1108
+
1109
+ return this._defineRelationship(relationshipName, Object.assign({type: "hasMany", scope}, relationshipOptions))
1110
+ }
1111
+
1112
+ /**
1113
+ * Rails-style declaration that this model accepts nested-attribute writes
1114
+ * for a relationship when saved through a parent. Required — Velocious
1115
+ * will refuse nested writes for any relationship not listed here, even
1116
+ * if a frontend-model resource permits them.
1117
+ *
1118
+ * Options:
1119
+ * - allowDestroy: whether `_destroy: true` entries are allowed. Default false.
1120
+ * - limit: optional upper bound on the number of nested entries per request.
1121
+ * - rejectIf: optional predicate `(attributes) => boolean` that silently skips entries.
1122
+ *
1123
+ * Usage:
1124
+ * class Project extends Record {}
1125
+ * Project.hasMany("tasks")
1126
+ * Project.acceptsNestedAttributesFor("tasks", {allowDestroy: true})
1127
+ * @param {string} relationshipName - Relationship name on this model.
1128
+ * @param {{allowDestroy?: boolean, limit?: number, rejectIf?: (attributes: Record<string, ?>) => boolean}} [options] - Policy options.
1129
+ * @returns {void}
1130
+ */
1131
+ static acceptsNestedAttributesFor(relationshipName, options = {}) {
1132
+ if (!relationshipName || typeof relationshipName !== "string") {
1133
+ throw new Error(`Invalid relationshipName passed to acceptsNestedAttributesFor: ${relationshipName}`)
1134
+ }
1135
+
1136
+ if (!Object.prototype.hasOwnProperty.call(this, "_acceptedNestedAttributes")) {
1137
+ /**
1138
+ * Narrows the runtime value to the documented type.
1139
+ @type {Record<string, {allowDestroy?: boolean, limit?: number, rejectIf?: (attributes: Record<string, ?>) => boolean}>} */
1140
+ this._acceptedNestedAttributes = {}
1141
+ }
1142
+
1143
+ /**
1144
+ * Narrows the runtime value to the documented type.
1145
+ @type {Record<string, {allowDestroy?: boolean, limit?: number, rejectIf?: (attributes: Record<string, ?>) => boolean}>} */ (this._acceptedNestedAttributes)[relationshipName] = {...options}
1146
+ }
1147
+
1148
+ /**
1149
+ * Runs accepted nested attributes for.
1150
+ * @param {string} relationshipName - Relationship name.
1151
+ * @returns {{allowDestroy?: boolean, limit?: number, rejectIf?: (attributes: Record<string, ?>) => boolean} | null} - Policy declared via `acceptsNestedAttributesFor`, or null when not accepted.
1152
+ */
1153
+ static acceptedNestedAttributesFor(relationshipName) {
1154
+ return this._acceptedNestedAttributes?.[relationshipName] || null
1155
+ }
1156
+
1157
+ /**
1158
+ * Adds a has-one-relationship to the model class.
1159
+ * @param {string} relationshipName The name of the relationship (e.g. "post")
1160
+ * @param {RelationshipScopeCallback | object} [scopeOrOptions] The scope callback or options for the relationship.
1161
+ * @param {object} [options] The options for the relationship (e.g. {className: "Post"})
1162
+ * @returns {void} - No return value.
1163
+ */
1164
+ static hasOne(relationshipName, scopeOrOptions, options) {
1165
+ const {scope, relationshipOptions} = this._normalizeRelationshipArgs(scopeOrOptions, options)
1166
+
1167
+ return this._defineRelationship(relationshipName, Object.assign({type: "hasOne", scope}, relationshipOptions))
1168
+ }
1169
+
1170
+ /**
1171
+ * Runs define attachment.
1172
+ * @param {string} attachmentName - Attachment name.
1173
+ * @param {object} args - Attachment args.
1174
+ * @param {string | AttachmentDriverConstructor | Record<string, ?>} [args.driver] - Attachment driver name, class, or instance.
1175
+ * @param {"hasOne" | "hasMany"} args.type - Attachment type.
1176
+ * @returns {void} - No return value.
1177
+ */
1178
+ static _defineAttachment(attachmentName, {driver, type}) {
1179
+ if (!attachmentName || typeof attachmentName !== "string") throw new Error(`Invalid attachment name: ${attachmentName}`)
1180
+ if (attachmentName in this.getAttachmentsMap()) throw new Error(`Attachment ${attachmentName} already exists`)
1181
+
1182
+ this.getAttachmentsMap()[attachmentName] = {driver, type}
1183
+
1184
+ const prototype = /**
1185
+ * Narrows the runtime value to the documented type.
1186
+ @type {Record<string, ?>} */ (/**
1187
+ * Narrows the runtime value to the documented type.
1188
+ @type {?} */ (this.prototype))
1189
+
1190
+ prototype[attachmentName] = function() {
1191
+ return this.getAttachmentByName(attachmentName)
1192
+ }
1193
+
1194
+ prototype[`set${inflection.camelize(attachmentName)}`] = function(/**
1195
+ * Narrows the runtime value to the documented type.
1196
+ @type {?} */ newValue) {
1197
+ this.getAttachmentByName(attachmentName).queueAttach(newValue)
1198
+ return newValue
1199
+ }
1200
+ }
1201
+
1202
+ /**
1203
+ * Adds a single attachment helper to the model.
1204
+ * @param {string} attachmentName - Attachment name.
1205
+ * @param {{driver?: string | AttachmentDriverConstructor | Record<string, ?>}} [args] - Attachment options.
1206
+ * @returns {void} - No return value.
1207
+ */
1208
+ static hasOneAttachment(attachmentName, args = {}) {
1209
+ this._defineAttachment(attachmentName, {driver: args.driver, type: "hasOne"})
1210
+ }
1211
+
1212
+ /**
1213
+ * Adds a collection attachment helper to the model.
1214
+ * @param {string} attachmentName - Attachment name.
1215
+ * @param {{driver?: string | AttachmentDriverConstructor | Record<string, ?>}} [args] - Attachment options.
1216
+ * @returns {void} - No return value.
1217
+ */
1218
+ static hasManyAttachments(attachmentName, args = {}) {
1219
+ this._defineAttachment(attachmentName, {driver: args.driver, type: "hasMany"})
1220
+ }
1221
+
1222
+ /**
1223
+ * Runs human attribute name.
1224
+ * @param {string} attributeName - Attribute name.
1225
+ * @returns {string} - The human attribute name.
1226
+ */
1227
+ static humanAttributeName(attributeName) {
1228
+ const modelNameKey = inflection.underscore(this.getModelName())
1229
+
1230
+ return this._getConfiguration().getTranslator()(`velocious.database.record.attributes.${modelNameKey}.${attributeName}`, {defaultValue: inflection.camelize(attributeName)})
1231
+ }
1232
+
1233
+ /**
1234
+ * Runs get database type.
1235
+ * @returns {string} - The database type.
1236
+ */
1237
+ static getDatabaseType() {
1238
+ if (!this._databaseType) throw new Error("Database type hasn't been set")
1239
+
1240
+ return this._databaseType
1241
+ }
1242
+
1243
+ /**
1244
+ * Runs set eager load record metadata.
1245
+ * @param {boolean} eagerLoadRecordMetadata - Whether require-context initialization should load table metadata for this model.
1246
+ * @returns {void} - No return value.
1247
+ */
1248
+ static setEagerLoadRecordMetadata(eagerLoadRecordMetadata) {
1249
+ this._eagerLoadRecordMetadata = eagerLoadRecordMetadata
1250
+ }
1251
+
1252
+ /**
1253
+ * Runs get eager load record metadata.
1254
+ * @returns {boolean} - Whether require-context initialization should load table metadata for this model.
1255
+ */
1256
+ static getEagerLoadRecordMetadata() {
1257
+ if (this._eagerLoadRecordMetadata === undefined) return true
1258
+
1259
+ return this._eagerLoadRecordMetadata
1260
+ }
1261
+
1262
+ /**
1263
+ * Runs reset record metadata.
1264
+ * @returns {void} - No return value.
1265
+ */
1266
+ static resetRecordMetadata() {
1267
+ this._initialized = false
1268
+ this._initializeRecordPromise = null
1269
+ this._databaseType = undefined
1270
+ this._table = undefined
1271
+ this._columns = undefined
1272
+ this._columnsAsHash = undefined
1273
+ this._columnNames = undefined
1274
+ this._columnTypeByName = undefined
1275
+ this._attributeNameToColumnName = undefined
1276
+ this._columnNameToAttributeName = undefined
1277
+ }
1278
+
1279
+ /**
1280
+ * Registers the model class with a configuration without loading table metadata.
1281
+ * @param {object} args - Options object.
1282
+ * @param {import("../../configuration.js").default} args.configuration - Configuration instance.
1283
+ * @returns {void} - No return value.
1284
+ */
1285
+ static registerRecordClass({configuration, ...restArgs}) {
1286
+ restArgsError(restArgs)
1287
+
1288
+ if (!configuration) throw new Error(`No configuration given for ${this.name}`)
1289
+
1290
+ this.resetRecordMetadata()
1291
+ this._configuration = configuration
1292
+ this._configuration.registerModelClass(this)
1293
+ }
1294
+
1295
+ /**
1296
+ * Runs initialize record.
1297
+ * @param {object} args - Options object.
1298
+ * @param {import("../../configuration.js").default} args.configuration - Configuration instance.
1299
+ * @returns {Promise<void>} - Resolves when complete.
1300
+ */
1301
+ static async initializeRecord({configuration, ...restArgs}) {
1302
+ restArgsError(restArgs)
1303
+
1304
+ if (!configuration) throw new Error(`No configuration given for ${this.name}`)
1305
+
1306
+ this.registerRecordClass({configuration})
1307
+ const connection = this.connection({enforceTenantDatabaseScope: false})
1308
+
1309
+ this._databaseType = connection.getType()
1310
+
1311
+ this._table = await connection.getTableByName(this.tableName())
1312
+ this._columns = await this._getTable().getColumns()
1313
+
1314
+ /**
1315
+ * Narrows the runtime value to the documented type.
1316
+ @type {Record<string, import("../drivers/base-column.js").default>} */
1317
+ this._columnsAsHash = {}
1318
+
1319
+ const columnNameToAttributeName = this.getColumnNameToAttributeNameMap()
1320
+ const attributeNameToColumnName = this.getAttributeNameToColumnNameMap()
1321
+ const prototype = /**
1322
+ * Narrows the runtime value to the documented type.
1323
+ @type {Record<string, ?>} */ (/**
1324
+ * Narrows the runtime value to the documented type.
1325
+ @type {?} */ (this.prototype))
1326
+
1327
+ for (const column of this._columns) {
1328
+ this._columnsAsHash[column.getName()] = column
1329
+
1330
+ const camelizedColumnName = inflection.camelize(column.getName(), true)
1331
+ const camelizedColumnNameBigFirst = inflection.camelize(column.getName())
1332
+
1333
+ attributeNameToColumnName[camelizedColumnName] = column.getName()
1334
+ columnNameToAttributeName[column.getName()] = camelizedColumnName
1335
+
1336
+ if (!(camelizedColumnName in prototype)) {
1337
+ prototype[camelizedColumnName] = function() {
1338
+ return this.readAttribute(camelizedColumnName)
1339
+ }
1340
+ }
1341
+
1342
+ if (!(`set${camelizedColumnNameBigFirst}` in prototype)) {
1343
+ prototype[`set${camelizedColumnNameBigFirst}`] = function(/**
1344
+ * Narrows the runtime value to the documented type.
1345
+ @type {?} */ newValue) {
1346
+ return this._setColumnAttribute(camelizedColumnName, newValue)
1347
+ }
1348
+ }
1349
+
1350
+ if (!(`has${camelizedColumnNameBigFirst}` in prototype)) {
1351
+ prototype[`has${camelizedColumnNameBigFirst}`] = function() {
1352
+ const dynamicThis = /**
1353
+ * Narrows the runtime value to the documented type.
1354
+ @type {Record<string, (...args: Array<?>) => ?>} */ (/**
1355
+ * Narrows the runtime value to the documented type.
1356
+ @type {?} */ (this))
1357
+ const value = dynamicThis[camelizedColumnName]()
1358
+
1359
+ return this._hasAttribute(value)
1360
+ }
1361
+ }
1362
+ }
1363
+
1364
+ await this._defineTranslationMethods()
1365
+ this._initialized = true
1366
+ }
1367
+
1368
+ /**
1369
+ * Initializes the model class the first time an async record API needs table
1370
+ * metadata. Concurrent callers share the same initialization promise, and a
1371
+ * failed initialization can be retried by a later call.
1372
+ * @param {{configuration?: import("../../configuration.js").default}} [args] - Optional configuration override.
1373
+ * @returns {Promise<void>} - Resolves when the model class is initialized.
1374
+ */
1375
+ static async ensureInitialized(args = {}) {
1376
+ const {configuration, ...restArgs} = args
1377
+
1378
+ restArgsError(restArgs)
1379
+
1380
+ if (this._initialized) return
1381
+
1382
+ if (this._initializeRecordPromise) {
1383
+ await this._initializeRecordPromise
1384
+ return
1385
+ }
1386
+
1387
+ const resolvedConfiguration = configuration || this._configuration || Configuration.current()
1388
+
1389
+ const initializeRecordPromise = this.initializeRecord({configuration: resolvedConfiguration})
1390
+
1391
+ this._initializeRecordPromise = initializeRecordPromise
1392
+
1393
+ try {
1394
+ await initializeRecordPromise
1395
+ } finally {
1396
+ if (this._initializeRecordPromise === initializeRecordPromise) {
1397
+ this._initializeRecordPromise = null
1398
+ }
1399
+ }
1400
+ }
1401
+
1402
+ /**
1403
+ * Runs has attribute.
1404
+ * @param {?} value - Value to use.
1405
+ * @returns {boolean} - Whether attribute.
1406
+ */
1407
+ _hasAttribute(value) {
1408
+ if (typeof value == "string") {
1409
+ value = value.trim()
1410
+ }
1411
+
1412
+ if (value) {
1413
+ return true
1414
+ }
1415
+
1416
+ return false
1417
+ }
1418
+
1419
+ /**
1420
+ * Runs is initialized.
1421
+ * @returns {boolean} - Whether initialized.
1422
+ */
1423
+ static isInitialized() {
1424
+ if (this._initialized) return true
1425
+
1426
+ return false
1427
+ }
1428
+
1429
+ /**
1430
+ * Runs assert has been initialized.
1431
+ * @returns {void} - No return value.
1432
+ */
1433
+ static _assertHasBeenInitialized() {
1434
+ if (this._initialized) return
1435
+
1436
+ throw new Error(`${this.name} used before initialization. Call ${this.name}.initializeRecord(...) or configuration.initialize().`)
1437
+ }
1438
+
1439
+ static async _defineTranslationMethods() {
1440
+ if (this._translations && Object.keys(this._translations).length > 0) {
1441
+ const locales = this._getConfiguration().getLocales()
1442
+
1443
+ if (!locales) throw new Error("Locales hasn't been set in the configuration")
1444
+
1445
+ await this.getTranslationClass().initializeRecord({configuration: this._getConfiguration()})
1446
+
1447
+ for (const name in this._translations) {
1448
+ const nameCamelized = inflection.camelize(name)
1449
+ const setterMethodName = `set${nameCamelized}`
1450
+ const prototype = /**
1451
+ * Narrows the runtime value to the documented type.
1452
+ @type {Record<string, ?>} */ (/**
1453
+ * Narrows the runtime value to the documented type.
1454
+ @type {?} */ (this.prototype))
1455
+
1456
+ prototype[name] = function getTranslatedAttribute() {
1457
+ const locale = this._getConfiguration().getLocale()
1458
+
1459
+ return this._getTranslatedAttributeWithFallback(name, locale)
1460
+ }
1461
+
1462
+ prototype[`has${nameCamelized}`] = function hasTranslatedAttribute() {
1463
+ const dynamicThis = /**
1464
+ * Narrows the runtime value to the documented type.
1465
+ @type {Record<string, ?>} */ (/**
1466
+ * Narrows the runtime value to the documented type.
1467
+ @type {?} */ (this))
1468
+ const candidate = dynamicThis[name]
1469
+
1470
+ if (typeof candidate == "function") {
1471
+ const value = candidate.bind(this)()
1472
+
1473
+ return this._hasAttribute(value)
1474
+ } else {
1475
+ throw new Error(`Expected candidate to be a function but it was: ${typeof candidate}`)
1476
+ }
1477
+ }
1478
+
1479
+ prototype[setterMethodName] = function setTranslatedAttribute(/**
1480
+ * Narrows the runtime value to the documented type.
1481
+ @type {?} */ newValue) {
1482
+ const locale = this._getConfiguration().getLocale()
1483
+
1484
+ return this._setTranslatedAttribute(name, locale, newValue)
1485
+ }
1486
+
1487
+ for (const locale of locales) {
1488
+ const localeCamelized = inflection.camelize(locale)
1489
+ const getterMethodNameLocalized = `${name}${localeCamelized}`
1490
+ const setterMethodNameLocalized = `${setterMethodName}${localeCamelized}`
1491
+ const hasMethodNameLocalized = `has${inflection.camelize(name)}${localeCamelized}`
1492
+
1493
+ prototype[getterMethodNameLocalized] = function getTranslatedAttributeWithLocale() {
1494
+ return this._getTranslatedAttribute(name, locale)
1495
+ }
1496
+
1497
+ prototype[setterMethodNameLocalized] = function setTranslatedAttributeWithLocale(/**
1498
+ * Narrows the runtime value to the documented type.
1499
+ @type {?} */ newValue) {
1500
+ return this._setTranslatedAttribute(name, locale, newValue)
1501
+ }
1502
+
1503
+ prototype[hasMethodNameLocalized] = function hasTranslatedAttribute() {
1504
+ const dynamicThis = /**
1505
+ * Narrows the runtime value to the documented type.
1506
+ @type {Record<string, ?>} */ (/**
1507
+ * Narrows the runtime value to the documented type.
1508
+ @type {?} */ (this))
1509
+ const candidate = dynamicThis[getterMethodNameLocalized]
1510
+
1511
+ if (typeof candidate == "function") {
1512
+ const value = candidate.bind(this)()
1513
+
1514
+ return this._hasAttribute(value)
1515
+ } else {
1516
+ throw new Error(`Expected candidate to be a function but it was: ${typeof candidate}`)
1517
+ }
1518
+ }
1519
+ }
1520
+ }
1521
+ }
1522
+ }
1523
+
1524
+ /**
1525
+ * Runs get configured database identifier.
1526
+ * @returns {string} - The configured non-tenant database identifier.
1527
+ */
1528
+ static getConfiguredDatabaseIdentifier() {
1529
+ return this._databaseIdentifier || "default"
1530
+ }
1531
+
1532
+ /**
1533
+ * Runs get database identifier.
1534
+ * @param {object} [args] - Options.
1535
+ * @param {boolean} [args.enforceTenantDatabaseScope] - Whether tenant-switched models must resolve a tenant database identifier.
1536
+ * @returns {string} - The database identifier.
1537
+ */
1538
+ static getDatabaseIdentifier({enforceTenantDatabaseScope = true, ...restArgs} = {}) {
1539
+ restArgsError(restArgs)
1540
+
1541
+ const tenant = Current.tenant()
1542
+ const tenantDatabaseIdentifier = this.getTenantDatabaseIdentifier(tenant)
1543
+
1544
+ if (tenantDatabaseIdentifier) {
1545
+ if (
1546
+ enforceTenantDatabaseScope &&
1547
+ this._getConfiguration().getEnforceTenantDatabaseScopes() &&
1548
+ !this._getConfiguration().isDatabaseIdentifierActive(tenantDatabaseIdentifier, tenant)
1549
+ ) {
1550
+ throw new TenantDatabaseScopeError(
1551
+ `${this.getModelName()} resolved tenant database identifier ${JSON.stringify(tenantDatabaseIdentifier)} but that database identifier is not active for the current tenant. Wrap the model query in configuration.runWithTenant(...) or set enforceTenantDatabaseScopes: false to allow legacy fallback behavior.`,
1552
+ {modelName: this.getModelName()}
1553
+ )
1554
+ }
1555
+
1556
+ return tenantDatabaseIdentifier
1557
+ }
1558
+
1559
+ if (enforceTenantDatabaseScope && this._tenantDatabaseIdentifierResolver && this._getConfiguration().getEnforceTenantDatabaseScopes()) {
1560
+ throw new TenantDatabaseScopeError(
1561
+ `${this.getModelName()} is configured with switchesTenantDatabase(...) but no tenant database identifier resolved for the current tenant. Wrap the model query in configuration.runWithTenant(...) or set enforceTenantDatabaseScopes: false to allow legacy fallback behavior.`,
1562
+ {modelName: this.getModelName()}
1563
+ )
1564
+ }
1565
+
1566
+ return this.getConfiguredDatabaseIdentifier()
1567
+ }
1568
+
1569
+ /**
1570
+ * Runs set database identifier.
1571
+ * @param {string} databaseIdentifier - Database identifier.
1572
+ * @returns {void} - No return value.
1573
+ */
1574
+ static setDatabaseIdentifier(databaseIdentifier) {
1575
+ this._databaseIdentifier = databaseIdentifier
1576
+ }
1577
+
1578
+ /**
1579
+ * Declares a tenant-aware database identifier resolver for this model class.
1580
+ * @param {string | ((args: {modelClass: typeof VelociousDatabaseRecord, tenant: ?}) => string | undefined)} databaseIdentifierOrResolver - Static identifier or resolver.
1581
+ * @returns {void} - No return value.
1582
+ */
1583
+ static switchesTenantDatabase(databaseIdentifierOrResolver) {
1584
+ this._tenantDatabaseIdentifierResolver = databaseIdentifierOrResolver
1585
+ }
1586
+
1587
+ /**
1588
+ * Runs get tenant database identifier.
1589
+ * @param {?} [tenant] - Tenant override.
1590
+ * @returns {string | undefined} - Tenant-scoped database identifier when configured.
1591
+ */
1592
+ static getTenantDatabaseIdentifier(tenant = Current.tenant()) {
1593
+ const tenantDatabaseIdentifierResolver = this._tenantDatabaseIdentifierResolver
1594
+
1595
+ if (!tenantDatabaseIdentifierResolver) {
1596
+ return
1597
+ }
1598
+
1599
+ if (typeof tenantDatabaseIdentifierResolver === "function") {
1600
+ return tenantDatabaseIdentifierResolver({
1601
+ modelClass: this,
1602
+ tenant
1603
+ })
1604
+ }
1605
+
1606
+ return tenantDatabaseIdentifierResolver
1607
+ }
1608
+
1609
+ /**
1610
+ * Runs get attribute.
1611
+ * @param {string} name - Name.
1612
+ * @returns {?} - The attribute.
1613
+ */
1614
+ getAttribute(name) {
1615
+ const columnName = inflection.underscore(name)
1616
+
1617
+ if (!this.isNewRecord() && !(columnName in this._attributes)) {
1618
+ throw new Error(`${this.constructor.name}#${name} attribute hasn't been loaded yet in ${Object.keys(this._attributes).join(", ")}`)
1619
+ }
1620
+
1621
+ return this._attributes[columnName]
1622
+ }
1623
+
1624
+ /**
1625
+ * Runs get model class.
1626
+ * @abstract
1627
+ * @returns {typeof VelociousDatabaseRecord} - The model class.
1628
+ */
1629
+ getModelClass() {
1630
+ const modelClass = /**
1631
+ * Narrows the runtime value to the documented type.
1632
+ @type {typeof VelociousDatabaseRecord} */ (this.constructor)
1633
+
1634
+ return modelClass
1635
+ }
1636
+
1637
+ /**
1638
+ * Runs set attribute.
1639
+ * @param {string} name - Name.
1640
+ * @param {?} newValue - New value.
1641
+ * @returns {void} - No return value.
1642
+ */
1643
+ setAttribute(name, newValue) {
1644
+ const setterName = `set${inflection.camelize(name)}`
1645
+ const dynamicThis = /**
1646
+ * Narrows the runtime value to the documented type.
1647
+ @type {Record<string, (value: ?) => void>} */ (/**
1648
+ * Narrows the runtime value to the documented type.
1649
+ @type {?} */ (this))
1650
+
1651
+ this.getModelClass()._assertHasBeenInitialized()
1652
+ if (!this.getModelClass().isInitialized()) throw new Error(`${this.constructor.name} model isn't initialized yet`)
1653
+ if (!(setterName in this)) throw new Error(`No such setter method: ${this.constructor.name}#${setterName}`)
1654
+
1655
+ dynamicThis[setterName](newValue)
1656
+ }
1657
+
1658
+ /**
1659
+ * Runs set column attribute.
1660
+ * @param {string} name - Name.
1661
+ * @param {?} newValue - New value.
1662
+ */
1663
+ _setColumnAttribute(name, newValue) {
1664
+ this.getModelClass()._assertHasBeenInitialized()
1665
+ if (!this.getModelClass()._attributeNameToColumnName) throw new Error("No attribute-to-column mapping. Has record been initialized?")
1666
+
1667
+ const columnName = this.getModelClass().getAttributeNameToColumnNameMap()[name]
1668
+
1669
+ if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${name}`)
1670
+
1671
+ let normalizedValue = newValue
1672
+ const columnType = this.getModelClass().getColumnTypeByName(columnName)
1673
+
1674
+ if (columnType && this.getModelClass()._isDateLikeType(columnType)) {
1675
+ normalizedValue = this._normalizeDateValue(newValue)
1676
+ }
1677
+
1678
+ normalizedValue = this._normalizeBooleanValueForWrite({attributeName: name, columnType, value: normalizedValue})
1679
+
1680
+ if (this._attributes[columnName] != normalizedValue) {
1681
+ this._clearBelongsToRelationshipForChangedForeignKey(columnName, normalizedValue)
1682
+ this._changes[columnName] = normalizedValue
1683
+ }
1684
+ }
1685
+
1686
+ /**
1687
+ * Clears loaded belongs-to caches when callers assign the foreign key directly.
1688
+ * @param {string} columnName - Changed database column name.
1689
+ * @param {?} normalizedValue - New normalized column value.
1690
+ * @returns {void} - No return value.
1691
+ */
1692
+ _clearBelongsToRelationshipForChangedForeignKey(columnName, normalizedValue) {
1693
+ for (const relationship of this._belongsToRelationshipsForForeignKey(columnName)) {
1694
+ if (this._belongsToRelationshipMatchesForeignKeyValue({normalizedValue, relationship})) continue
1695
+
1696
+ this._clearLoadedBelongsToRelationship(relationship)
1697
+ }
1698
+ }
1699
+
1700
+ /**
1701
+ * Runs belongs to relationships for foreign key.
1702
+ * @param {string} columnName - Changed database column name.
1703
+ * @returns {Array<?>} - Loaded relationship instances that use the changed foreign key.
1704
+ */
1705
+ _belongsToRelationshipsForForeignKey(columnName) {
1706
+ if (!this._instanceRelationships) return []
1707
+
1708
+ return Object
1709
+ .values(this._instanceRelationships)
1710
+ .filter((relationship) => this._belongsToRelationshipUsesForeignKey({columnName, relationship}))
1711
+ }
1712
+
1713
+ /**
1714
+ * Runs belongs to relationship uses foreign key.
1715
+ * @param {object} args - Relationship match arguments.
1716
+ * @param {string} args.columnName - Changed database column name.
1717
+ * @param {?} args.relationship - Relationship instance.
1718
+ * @returns {boolean} - Whether the relationship is a belongs-to using the changed foreign key.
1719
+ */
1720
+ _belongsToRelationshipUsesForeignKey({columnName, relationship}) {
1721
+ if (relationship.getType() != "belongsTo") return false
1722
+
1723
+ return relationship.getForeignKey() == columnName
1724
+ }
1725
+
1726
+ /**
1727
+ * Runs belongs to relationship matches foreign key value.
1728
+ * @param {object} args - Relationship cache arguments.
1729
+ * @param {?} args.normalizedValue - New normalized column value.
1730
+ * @param {?} args.relationship - Relationship instance.
1731
+ * @returns {boolean} - Whether the loaded related record still matches the changed foreign key.
1732
+ */
1733
+ _belongsToRelationshipMatchesForeignKeyValue({normalizedValue, relationship}) {
1734
+ const loaded = relationship.getLoadedOrUndefined()
1735
+
1736
+ if (!loaded) return false
1737
+ if (Array.isArray(loaded)) return false
1738
+ if (!relationship.getTargetModelClass()) return false
1739
+
1740
+ return loaded.readColumn(relationship.getPrimaryKey()) == normalizedValue
1741
+ }
1742
+
1743
+ /**
1744
+ * Runs clear loaded belongs to relationship.
1745
+ * @param {?} relationship - Relationship instance.
1746
+ * @returns {void} - No return value.
1747
+ */
1748
+ _clearLoadedBelongsToRelationship(relationship) {
1749
+ relationship.setLoaded(undefined)
1750
+ relationship.setPreloaded(false)
1751
+ relationship.setDirty(false)
1752
+ }
1753
+
1754
+ /**
1755
+ * Runs normalize date value.
1756
+ * @param {?} value - Value to use.
1757
+ * @returns {?} - The date value.
1758
+ */
1759
+ _normalizeDateValue(value) {
1760
+ if (typeof value != "string") return value
1761
+
1762
+ const isoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/
1763
+
1764
+ if (!isoDateTimeRegex.test(value)) return value
1765
+
1766
+ const timestamp = Date.parse(value)
1767
+
1768
+ if (Number.isNaN(timestamp)) return value
1769
+
1770
+ return new Date(timestamp)
1771
+ }
1772
+
1773
+ /**
1774
+ * Runs normalize sqlite boolean value.
1775
+ * @param {object} args - Options object.
1776
+ * @param {string | undefined} args.columnType - Column type.
1777
+ * @param {?} args.value - Value to normalize.
1778
+ * @returns {?} - Normalized value.
1779
+ */
1780
+ _normalizeSqliteBooleanValue({columnType, value}) {
1781
+ if (this.getModelClass().getDatabaseType() != "sqlite") return value
1782
+ if (!columnType) return value
1783
+ if (columnType.toLowerCase() !== "boolean") return value
1784
+ if (value === true) return 1
1785
+ if (value === false) return 0
1786
+
1787
+ return value
1788
+ }
1789
+
1790
+ /**
1791
+ * Normalizes a boolean value before storing. A declared `"boolean"` attribute cast stores
1792
+ * booleans as 1/0 only for integer-backed columns (e.g. an MSSQL `bit`). Columns whose
1793
+ * underlying type is already a native boolean (e.g. Postgres `boolean`) keep `true`/`false`
1794
+ * so the driver can emit the proper boolean literal; otherwise the sqlite-only normalizer applies.
1795
+ * @param {object} args - Options object.
1796
+ * @param {string} args.attributeName - Attribute name being written.
1797
+ * @param {string | undefined} args.columnType - Column type.
1798
+ * @param {?} args.value - Value to normalize.
1799
+ * @returns {?} - Normalized value.
1800
+ */
1801
+ _normalizeBooleanValueForWrite({attributeName, columnType, value}) {
1802
+ if (!this.getModelClass()._declaredBooleanStoresAsInteger(attributeName)) {
1803
+ return this._normalizeSqliteBooleanValue({columnType, value})
1804
+ }
1805
+
1806
+ if (value === true) return 1
1807
+ if (value === false) return 0
1808
+
1809
+ return value
1810
+ }
1811
+
1812
+ /**
1813
+ * Whether a declared `"boolean"` attribute cast is backed by an integer column (e.g. an MSSQL
1814
+ * `bit`), so booleans must be stored as 1/0. A native boolean column (e.g. Postgres `boolean`)
1815
+ * returns false and keeps `true`/`false` for the driver.
1816
+ * @param {string} attributeName - Attribute name.
1817
+ * @returns {boolean} - Whether the declared boolean is stored as an integer.
1818
+ */
1819
+ static _declaredBooleanStoresAsInteger(attributeName) {
1820
+ if (this.getAttributeCast(attributeName) !== "boolean") return false
1821
+
1822
+ const columnName = this.getAttributeNameToColumnNameMap()[attributeName]
1823
+ const introspectedType = columnName ? this.getColumnsHash()[columnName]?.getType() : undefined
1824
+
1825
+ return typeof introspectedType === "string" && introspectedType.toLowerCase() !== "boolean"
1826
+ }
1827
+
1828
+ /**
1829
+ * Runs get columns.
1830
+ * @returns {import("../drivers/base-column.js").default[]} - The columns.
1831
+ */
1832
+ static getColumns() {
1833
+ this._assertHasBeenInitialized()
1834
+ if (!this._columns) throw new Error(`${this.name} hasn't been initialized yet`)
1835
+
1836
+ return this._columns
1837
+ }
1838
+
1839
+ /**
1840
+ * Runs get columns hash.
1841
+ * @returns {Record<string, import("../drivers/base-column.js").default>} - The columns hash.
1842
+ */
1843
+ static getColumnsHash() {
1844
+ if (!this._columnsAsHash) {
1845
+ /**
1846
+ * Narrows the runtime value to the documented type.
1847
+ @type {Record<string, import("../drivers/base-column.js").default>} */
1848
+ this._columnsAsHash = {}
1849
+
1850
+ for (const column of this.getColumns()) {
1851
+ this._columnsAsHash[column.getName()] = column
1852
+ }
1853
+ }
1854
+
1855
+ return this._columnsAsHash
1856
+ }
1857
+
1858
+ /**
1859
+ * Runs get column type by name.
1860
+ * @param {string} name - Name.
1861
+ * @returns {string | undefined} - The column type by name.
1862
+ */
1863
+ static getColumnTypeByName(name) {
1864
+ if (!this._columnTypeByName) {
1865
+ /**
1866
+ * Narrows the runtime value to the documented type.
1867
+ @type {Record<string, string | undefined>} */
1868
+ this._columnTypeByName = {}
1869
+
1870
+ for (const column of this.getColumns()) {
1871
+ this._columnTypeByName[column.getName()] = column.getType()
1872
+ }
1873
+ }
1874
+
1875
+ const attributeName = this.getColumnNameToAttributeNameMap()[name]
1876
+
1877
+ if (attributeName) {
1878
+ const cast = this.getAttributeCast(attributeName)
1879
+
1880
+ if (cast) return cast
1881
+ }
1882
+
1883
+ return this._columnTypeByName[name]
1884
+ }
1885
+
1886
+ /**
1887
+ * Runs is date like type.
1888
+ * @param {string} type - Type identifier.
1889
+ * @returns {boolean} - Whether date like type.
1890
+ */
1891
+ static _isDateLikeType(type) {
1892
+ const normalizedType = type.toLowerCase()
1893
+
1894
+ return normalizedType == "date" ||
1895
+ normalizedType == "datetime" ||
1896
+ normalizedType == "timestamp" ||
1897
+ normalizedType == "timestamptz" ||
1898
+ normalizedType.startsWith("timestamp ")
1899
+ }
1900
+
1901
+ /**
1902
+ * Runs get column names.
1903
+ * @returns {Array<string>} - The column names.
1904
+ */
1905
+ static getColumnNames() {
1906
+ if (!this._columnNames) {
1907
+ this._columnNames = this.getColumns().map((column) => column.getName())
1908
+ }
1909
+
1910
+ return this._columnNames
1911
+ }
1912
+
1913
+ /**
1914
+ * Runs get table.
1915
+ * @returns {import("../drivers/base-table.js").default} - The table.
1916
+ */
1917
+ static _getTable() {
1918
+ if (!this._table) throw new Error(`${this.name} hasn't been initialized yet`)
1919
+
1920
+ return this._table
1921
+ }
1922
+
1923
+ /**
1924
+ * Runs insert multiple.
1925
+ * @param {Array<string>} columns - Column names.
1926
+ * @param {Array<Array<?>>} rows - Rows to insert.
1927
+ * @param {object} [args] - Options object.
1928
+ * @param {boolean} [args.cast] - Whether to cast values based on column types.
1929
+ * @param {boolean} [args.retryIndividuallyOnFailure] - Retry rows individually if a batch insert fails.
1930
+ * @param {boolean} [args.returnResults] - Return succeeded/failed rows instead of throwing when retries fail.
1931
+ * @returns {Promise<void | {succeededRows: Array<Array<?>>, failedRows: Array<Array<?>>, errors: Array<{row: Array<?>, error: ?}>}>} - Resolves when complete.
1932
+ */
1933
+ static async insertMultiple(columns, rows, args = {}) {
1934
+ const {cast = true, retryIndividuallyOnFailure = false, returnResults = false, ...restArgs} = args
1935
+
1936
+ restArgsError(restArgs)
1937
+ await this.ensureInitialized()
1938
+
1939
+ const normalizedRows = cast
1940
+ ? this._normalizeInsertMultipleRows({columns, rows})
1941
+ : rows
1942
+ const tableName = this.tableName()
1943
+
1944
+ if (!retryIndividuallyOnFailure) {
1945
+ await this.connection().insertMultiple(tableName, columns, normalizedRows)
1946
+ if (returnResults) return {succeededRows: normalizedRows.slice(), failedRows: [], errors: []}
1947
+ return
1948
+ }
1949
+
1950
+ try {
1951
+ // Wrap the batch in a transaction/savepoint. On databases that abort the
1952
+ // whole transaction when a statement fails (PostgreSQL), a failed batch
1953
+ // would otherwise poison the surrounding transaction so that the
1954
+ // individual retries below all fail with "current transaction is aborted".
1955
+ // transaction() opens a savepoint when already inside a transaction and a
1956
+ // real transaction otherwise, so a failure rolls back only this attempt.
1957
+ await this.connection().transaction(async () => {
1958
+ await this.connection().insertMultiple(tableName, columns, normalizedRows)
1959
+ })
1960
+ if (returnResults) return {succeededRows: normalizedRows.slice(), failedRows: [], errors: []}
1961
+ return
1962
+ } catch {
1963
+ /**
1964
+ * Results.
1965
+ @type {{succeededRows: Array<?>[], failedRows: Array<?>[], errors: Array<{row: Array<?>, error: ?}>}} */
1966
+ const results = {
1967
+ succeededRows: [],
1968
+ failedRows: [],
1969
+ errors: []
1970
+ }
1971
+
1972
+ for (const row of normalizedRows) {
1973
+ try {
1974
+ // Each retry runs in its own savepoint so a failed row rolls back only
1975
+ // that row and leaves the surrounding transaction usable for the rest.
1976
+ await this.connection().transaction(async () => {
1977
+ await this.connection().insertMultiple(tableName, columns, [row])
1978
+ })
1979
+ results.succeededRows.push(row)
1980
+ } catch (rowError) {
1981
+ results.failedRows.push(row)
1982
+ results.errors.push({row, error: rowError})
1983
+ }
1984
+ }
1985
+
1986
+ if (results.failedRows.length > 0) {
1987
+ const combinedErrors = results.errors.map((entry, index) => {
1988
+ const message = entry.error instanceof Error ? entry.error.message : String(entry.error)
1989
+ return `[${index}] ${message}. Row: ${this._safeSerializeInsertRow(entry.row)}`
1990
+ }).join(" | ")
1991
+ const combinedError = new Error(`insertMultiple failed for ${results.failedRows.length} rows. ${combinedErrors}`)
1992
+
1993
+ if (returnResults) return results
1994
+ throw combinedError
1995
+ }
1996
+
1997
+ if (returnResults) return results
1998
+ return
1999
+ }
2000
+ }
2001
+
2002
+ /**
2003
+ * Runs normalize insert multiple rows.
2004
+ * @param {object} args - Options object.
2005
+ * @param {Array<string>} args.columns - Column names.
2006
+ * @param {Array<Array<?>>} args.rows - Rows to insert.
2007
+ * @returns {Array<Array<?>>} - Normalized rows.
2008
+ */
2009
+ static _normalizeInsertMultipleRows({columns, rows}) {
2010
+ return rows.map((row) => {
2011
+ if (!Array.isArray(row) || row.length !== columns.length) {
2012
+ const rowLength = Array.isArray(row) ? row.length : "non-array"
2013
+
2014
+ throw new Error(`insertMultiple row length mismatch. Expected ${columns.length} values but got ${rowLength}. Row: ${JSON.stringify(row)}`)
2015
+ }
2016
+
2017
+ const normalizedRow = []
2018
+
2019
+ for (let index = 0; index < columns.length; index++) {
2020
+ const columnName = columns[index]
2021
+ const value = row[index]
2022
+
2023
+ normalizedRow[index] = this._normalizeInsertValueForColumn({columnName, value})
2024
+ }
2025
+
2026
+ return normalizedRow
2027
+ })
2028
+ }
2029
+
2030
+ /**
2031
+ * Runs safe serialize insert row.
2032
+ * @param {Array<?>} row - Row to serialize.
2033
+ * @returns {string} - Safe row representation.
2034
+ */
2035
+ static _safeSerializeInsertRow(row) {
2036
+ return formatValue(row)
2037
+ }
2038
+
2039
+ /**
2040
+ * Runs normalize insert value for column.
2041
+ * @param {object} args - Options object.
2042
+ * @param {string} args.columnName - Column name.
2043
+ * @param {?} args.value - Column value.
2044
+ * @returns {?} - Normalized value.
2045
+ */
2046
+ static _normalizeInsertValueForColumn({columnName, value}) {
2047
+ const column = this.getColumnsHash()[columnName]
2048
+
2049
+ if (!column) return value
2050
+
2051
+ const columnType = column.getType()
2052
+ const normalizedType = typeof columnType === "string" ? columnType.toLowerCase() : undefined
2053
+ let normalizedValue = value
2054
+
2055
+ if (normalizedType && this._isDateLikeType(normalizedType)) {
2056
+ normalizedValue = this._normalizeDateValueForInsert(normalizedValue)
2057
+ }
2058
+
2059
+ normalizedValue = this._normalizeSqliteBooleanValueForInsert({columnType, value: normalizedValue})
2060
+
2061
+ if (normalizedValue === "" && column.getNull() && !this._isStringType(normalizedType)) {
2062
+ normalizedValue = null
2063
+ }
2064
+
2065
+ if (normalizedType && this._isNumericType(normalizedType)) {
2066
+ normalizedValue = this._normalizeNumericValue({columnType: normalizedType, value: normalizedValue})
2067
+ }
2068
+
2069
+ return normalizedValue
2070
+ }
2071
+
2072
+ /**
2073
+ * Runs is string type.
2074
+ * @param {string | undefined} columnType - Column type.
2075
+ * @returns {boolean} - Whether string-like type.
2076
+ */
2077
+ static _isStringType(columnType) {
2078
+ if (!columnType) return false
2079
+
2080
+ const stringTypes = new Set(["char", "varchar", "nvarchar", "string", "enum", "json", "jsonb", "citext", "binary", "varbinary"])
2081
+
2082
+ return columnType.includes("uuid") ||
2083
+ columnType.includes("text") ||
2084
+ stringTypes.has(columnType)
2085
+ }
2086
+
2087
+ /**
2088
+ * Runs is numeric type.
2089
+ * @param {string} columnType - Column type.
2090
+ * @returns {boolean} - Whether numeric-like type.
2091
+ */
2092
+ static _isNumericType(columnType) {
2093
+ return columnType.includes("int") ||
2094
+ columnType.includes("decimal") ||
2095
+ columnType.includes("numeric") ||
2096
+ columnType.includes("float") ||
2097
+ columnType.includes("double") ||
2098
+ columnType.includes("real")
2099
+ }
2100
+
2101
+ /**
2102
+ * Runs normalize numeric value.
2103
+ * @param {object} args - Options object.
2104
+ * @param {string} args.columnType - Column type.
2105
+ * @param {?} args.value - Value to normalize.
2106
+ * @returns {?} - Normalized value.
2107
+ */
2108
+ static _normalizeNumericValue({columnType, value}) {
2109
+ if (value === "" || value === null || value === undefined) return value
2110
+ if (typeof value !== "string") return value
2111
+
2112
+ if (columnType.includes("decimal") || columnType.includes("numeric")) {
2113
+ return value
2114
+ }
2115
+
2116
+ const parsed = Number(value)
2117
+
2118
+ if (!Number.isFinite(parsed)) return value
2119
+
2120
+ if (columnType.includes("int")) {
2121
+ if (!Number.isSafeInteger(parsed)) return value
2122
+ if (!/^-?\d+$/.test(value)) return value
2123
+ }
2124
+
2125
+ return parsed
2126
+ }
2127
+
2128
+ /**
2129
+ * Runs normalize date value for insert.
2130
+ * @param {?} value - Value to normalize.
2131
+ * @returns {?} - Normalized value.
2132
+ */
2133
+ static _normalizeDateValueForInsert(value) {
2134
+ let normalizedValue = value
2135
+
2136
+ if (typeof normalizedValue == "string") {
2137
+ normalizedValue = this._normalizeDateStringForInsert(normalizedValue)
2138
+ }
2139
+
2140
+ if (normalizedValue instanceof Date) {
2141
+ const configuration = this._getConfiguration()
2142
+ const offsetMinutes = configuration.getEnvironmentHandler().getTimezoneOffsetMinutes(configuration)
2143
+ const offsetMs = offsetMinutes * 60 * 1000
2144
+
2145
+ normalizedValue = new Date(normalizedValue.getTime() - offsetMs)
2146
+ }
2147
+
2148
+ return normalizedValue
2149
+ }
2150
+
2151
+ /**
2152
+ * Runs normalize date string for insert.
2153
+ * @param {string} value - Date string value.
2154
+ * @returns {string | Date} - Parsed date or original string.
2155
+ */
2156
+ static _normalizeDateStringForInsert(value) {
2157
+ const isoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/
2158
+
2159
+ if (!isoDateTimeRegex.test(value)) return value
2160
+
2161
+ const timestamp = Date.parse(value)
2162
+
2163
+ if (Number.isNaN(timestamp)) return value
2164
+
2165
+ return new Date(timestamp)
2166
+ }
2167
+
2168
+ /**
2169
+ * Runs normalize sqlite boolean value for insert.
2170
+ * @param {object} args - Options object.
2171
+ * @param {string | undefined} args.columnType - Column type.
2172
+ * @param {?} args.value - Value to normalize.
2173
+ * @returns {?} - Normalized value.
2174
+ */
2175
+ static _normalizeSqliteBooleanValueForInsert({columnType, value}) {
2176
+ if (this.getDatabaseType() != "sqlite") return value
2177
+ if (!columnType) return value
2178
+ if (columnType.toLowerCase() !== "boolean") return value
2179
+ if (value === true) return 1
2180
+ if (value === false) return 0
2181
+
2182
+ return value
2183
+ }
2184
+
2185
+ /**
2186
+ * Runs next primary key.
2187
+ * @returns {Promise<number>} - Resolves with the next primary key.
2188
+ */
2189
+ static async nextPrimaryKey() {
2190
+ await this.ensureInitialized()
2191
+
2192
+ const primaryKey = this.primaryKey()
2193
+ const tableName = this.tableName()
2194
+ const connection = this.connection()
2195
+ const newestRecord = await this.order(`${connection.quoteTable(tableName)}.${connection.quoteColumn(primaryKey)}`).last()
2196
+
2197
+ if (newestRecord) {
2198
+ const id = newestRecord.id()
2199
+
2200
+ if (typeof id == "number") {
2201
+ return id + 1
2202
+ } else {
2203
+ throw new Error("ID from newest record wasn't a number")
2204
+ }
2205
+ } else {
2206
+ return 1
2207
+ }
2208
+ }
2209
+
2210
+ /**
2211
+ * Runs set primary key.
2212
+ * @param {string} primaryKey - Primary key.
2213
+ * @returns {void} - No return value.
2214
+ */
2215
+ static setPrimaryKey(primaryKey) {
2216
+ this._primaryKey = primaryKey
2217
+ }
2218
+
2219
+ /**
2220
+ * Returns this class's own attribute-cast map, creating it on the class itself
2221
+ * (never inherited from a parent) so subclasses don't share the same object.
2222
+ * @returns {Record<string, string>} - Declared casts keyed by attribute name.
2223
+ */
2224
+ static getAttributeCastsMap() {
2225
+ if (!Object.prototype.hasOwnProperty.call(this, "_attributeCasts") || !this._attributeCasts) {
2226
+ /**
2227
+ * Narrows the runtime value to the documented type.
2228
+ @type {Record<string, string>} */
2229
+ this._attributeCasts = {}
2230
+ }
2231
+
2232
+ return this._attributeCasts
2233
+ }
2234
+
2235
+ /**
2236
+ * Declares a Rails-style per-attribute cast so a column whose introspected type
2237
+ * isn't what the app wants (e.g. an MSSQL `bit` mapped to `number`) can be
2238
+ * exposed as another type with real runtime conversion. Currently fully
2239
+ * implements the `"boolean"` cast (0/1 <-> false/true); other types only record
2240
+ * the label so the effective type and generated typings reflect them.
2241
+ * @param {string} attributeName - Attribute name (camelCase), e.g. `"sichtbarVVK"`.
2242
+ * @param {string} type - Declared type, e.g. `"boolean"`.
2243
+ * @returns {void} - No return value.
2244
+ */
2245
+ static attribute(attributeName, type) {
2246
+ this.getAttributeCastsMap()[attributeName] = type
2247
+ }
2248
+
2249
+ /**
2250
+ * Returns the declared cast type for an attribute, if any.
2251
+ * @param {string} attributeName - Attribute name (camelCase).
2252
+ * @returns {string | undefined} - Declared cast type, or undefined when none is declared.
2253
+ */
2254
+ static getAttributeCast(attributeName) {
2255
+ return this.getAttributeCastsMap()[attributeName]
2256
+ }
2257
+
2258
+ /**
2259
+ * Runs primary key.
2260
+ * @returns {string} - The primary key.
2261
+ */
2262
+ static primaryKey() {
2263
+ if (this._primaryKey) return this._primaryKey
2264
+
2265
+ return "id"
2266
+ }
2267
+
2268
+ /**
2269
+ * Runs save.
2270
+ * @returns {Promise<void>} - Resolves when complete.
2271
+ */
2272
+ async save() {
2273
+ const isNewRecord = this.isNewRecord()
2274
+ let result
2275
+
2276
+ await this._getConfiguration().ensureConnections({name: `${this.getModelClass().name} save`}, async () => {
2277
+ await this._runLifecycleCallbacks("beforeValidation")
2278
+ await this._runValidations()
2279
+
2280
+ await this.getModelClass().transaction(async () => {
2281
+ await this._runLifecycleCallbacks("beforeSave")
2282
+
2283
+ // If any belongs-to-relationships was saved, then updated-at should still be set on this record.
2284
+ const {savedCount} = await this._autoSaveBelongsToRelationships()
2285
+
2286
+ if (this.isPersisted()) {
2287
+ await this._runLifecycleCallbacks("beforeUpdate")
2288
+
2289
+ // If any has-many-relationships will be saved, then updated-at should still be set on this record.
2290
+ const autoSaveHasManyrelationships = this._autoSaveHasManyAndHasOneRelationshipsToSave()
2291
+
2292
+ if (this._hasChanges() || savedCount > 0 || autoSaveHasManyrelationships.length > 0) {
2293
+ result = await this._updateRecordWithChanges()
2294
+ }
2295
+
2296
+ await this._runLifecycleCallbacks("afterUpdate")
2297
+ } else {
2298
+ await this._runLifecycleCallbacks("beforeCreate")
2299
+ result = await this._createNewRecord()
2300
+ await this._runLifecycleCallbacks("afterCreate")
2301
+ }
2302
+
2303
+ await this._autoSaveHasManyAndHasOneRelationships({isNewRecord})
2304
+ await this._autoSaveAttachments()
2305
+ await this._runLifecycleCallbacks("afterSave")
2306
+ })
2307
+ })
2308
+
2309
+ return result
2310
+ }
2311
+
2312
+ async _autoSaveBelongsToRelationships() {
2313
+ let savedCount = 0
2314
+
2315
+ for (const relationshipName in this._instanceRelationships) {
2316
+ const instanceRelationship = this._instanceRelationships[relationshipName]
2317
+
2318
+ if (instanceRelationship.getType() != "belongsTo") {
2319
+ continue
2320
+ }
2321
+
2322
+ if (instanceRelationship.getAutoSave() === false) {
2323
+ continue
2324
+ }
2325
+
2326
+ const model = instanceRelationship.getLoadedOrUndefined()
2327
+
2328
+ if (model) {
2329
+ if (model instanceof VelociousDatabaseRecord) {
2330
+ if (model.isChanged()) {
2331
+ await model.save()
2332
+
2333
+ const foreignKey = instanceRelationship.getForeignKey()
2334
+
2335
+ this.setAttribute(foreignKey, model.id())
2336
+
2337
+ instanceRelationship.setPreloaded(true)
2338
+ instanceRelationship.setDirty(false)
2339
+
2340
+ savedCount++
2341
+ }
2342
+ } else {
2343
+ throw new Error(`Expected a record but got: ${typeof model}`)
2344
+ }
2345
+ }
2346
+ }
2347
+
2348
+ return {savedCount}
2349
+ }
2350
+
2351
+ _autoSaveHasManyAndHasOneRelationshipsToSave() {
2352
+ const relationships = []
2353
+
2354
+ for (const relationshipName in this._instanceRelationships) {
2355
+ const instanceRelationship = this._instanceRelationships[relationshipName]
2356
+
2357
+ if (instanceRelationship.getType() != "hasMany" && instanceRelationship.getType() != "hasOne") {
2358
+ continue
2359
+ }
2360
+
2361
+ if (instanceRelationship.getAutoSave() === false) {
2362
+ continue
2363
+ }
2364
+
2365
+ /**
2366
+ * Defines loaded.
2367
+ @type {VelociousDatabaseRecord[]} */
2368
+ let loaded
2369
+
2370
+ const hasManyOrOneLoaded = instanceRelationship.getLoadedOrUndefined()
2371
+
2372
+ if (hasManyOrOneLoaded) {
2373
+ if (Array.isArray(hasManyOrOneLoaded)) {
2374
+ loaded = hasManyOrOneLoaded
2375
+ } else if (hasManyOrOneLoaded instanceof VelociousDatabaseRecord) {
2376
+ loaded = [hasManyOrOneLoaded]
2377
+ } else {
2378
+ throw new Error(`Expected hasOneLoaded to be a record but it wasn't: ${typeof hasManyOrOneLoaded}`)
2379
+ }
2380
+ } else {
2381
+ continue
2382
+ }
2383
+
2384
+ let useRelationship = false
2385
+
2386
+ if (loaded) {
2387
+ for (const model of loaded) {
2388
+ const foreignKey = instanceRelationship.getForeignKey()
2389
+
2390
+ model.setAttribute(foreignKey, this.id())
2391
+
2392
+ if (model.isChanged()) {
2393
+ useRelationship = true
2394
+ continue
2395
+ }
2396
+ }
2397
+ }
2398
+
2399
+ if (useRelationship) relationships.push(instanceRelationship)
2400
+ }
2401
+
2402
+ return relationships
2403
+ }
2404
+
2405
+ /**
2406
+ * Runs auto save has many and has one relationships.
2407
+ * @param {object} args - Options object.
2408
+ * @param {boolean} args.isNewRecord - Whether is new record.
2409
+ */
2410
+ async _autoSaveHasManyAndHasOneRelationships({isNewRecord}) {
2411
+ for (const instanceRelationship of this._autoSaveHasManyAndHasOneRelationshipsToSave()) {
2412
+ let hasManyOrOneLoaded = instanceRelationship.getLoadedOrUndefined()
2413
+
2414
+ /**
2415
+ * Defines loaded.
2416
+ @type {VelociousDatabaseRecord[]} */
2417
+ let loaded
2418
+
2419
+ if (hasManyOrOneLoaded === undefined) {
2420
+ loaded = []
2421
+ } else if (hasManyOrOneLoaded instanceof VelociousDatabaseRecord) {
2422
+ loaded = [hasManyOrOneLoaded]
2423
+ } else if (Array.isArray(hasManyOrOneLoaded)) {
2424
+ loaded = hasManyOrOneLoaded
2425
+ } else {
2426
+ throw new Error(`Unexpected type for hasManyOrOneLoaded: ${typeof hasManyOrOneLoaded}`)
2427
+ }
2428
+
2429
+ for (const model of loaded) {
2430
+ const foreignKey = instanceRelationship.getForeignKey()
2431
+
2432
+ model.setAttribute(foreignKey, this.id())
2433
+
2434
+ if (model.isChanged()) {
2435
+ await model.save()
2436
+ }
2437
+ }
2438
+
2439
+ if (isNewRecord) {
2440
+ instanceRelationship.setPreloaded(true)
2441
+ }
2442
+ }
2443
+ }
2444
+
2445
+ /**
2446
+ * Runs auto save attachments.
2447
+ * @returns {Promise<void>} - Resolves when pending attachments have been saved.
2448
+ */
2449
+ async _autoSaveAttachments() {
2450
+ for (const attachmentName in this._attachments) {
2451
+ const attachment = this._attachments[attachmentName]
2452
+
2453
+ if (!attachment.hasPendingAttachments()) continue
2454
+
2455
+ await attachment.flushPendingAttachments()
2456
+ }
2457
+ }
2458
+
2459
+ /**
2460
+ * Runs table name.
2461
+ * @returns {string} - The table name.
2462
+ */
2463
+ static tableName() {
2464
+ if (!this._tableName) this._tableName = inflection.underscore(inflection.pluralize(this.getModelName()))
2465
+
2466
+ return this._tableName
2467
+ }
2468
+
2469
+ /**
2470
+ * Runs set table name.
2471
+ * @param {string} tableName - Table name.
2472
+ * @returns {void} - No return value.
2473
+ */
2474
+ static setTableName(tableName) {
2475
+ this._tableName = tableName
2476
+ }
2477
+
2478
+ /**
2479
+ * Runs transaction.
2480
+ * @param {function() : Promise<void>} callback - Callback function.
2481
+ * @returns {Promise<?>} - Resolves with the transaction.
2482
+ */
2483
+ static async transaction(callback) {
2484
+ await this.ensureInitialized()
2485
+
2486
+ const useTransactions = this.connection().getArgs().record?.transactions
2487
+
2488
+ if (useTransactions !== false) {
2489
+ return await this.connection().transaction(callback)
2490
+ } else {
2491
+ return await callback()
2492
+ }
2493
+ }
2494
+
2495
+ /**
2496
+ * Runs the callback while holding a named advisory lock on the current
2497
+ * connection. Advisory locks are cooperative and connection-scoped: they
2498
+ * serialize callers that opt into the same `name`, without touching row
2499
+ * or table locks, so unrelated traffic is free to proceed.
2500
+ *
2501
+ * The lock is acquired before the callback runs and released in a
2502
+ * `finally` block afterwards, so the callback's return value is
2503
+ * propagated and thrown errors still release the lock.
2504
+ * @template T
2505
+ * @param {string} name - Lock name.
2506
+ * @param {() => Promise<T>} callback - Callback to invoke while the lock is held.
2507
+ * @param {{timeoutMs?: number | null, holdTimeoutMs?: number | null}} [args] - `timeoutMs` caps how long we wait to acquire the lock; `holdTimeoutMs` caps how long the callback may hold it before the lock is released and `AdvisoryLockHoldTimeoutError` is thrown.
2508
+ * @returns {Promise<T>} - Resolves with the callback's return value.
2509
+ * @throws {AdvisoryLockTimeoutError} - If `timeoutMs` elapses before the lock is granted.
2510
+ * @throws {AdvisoryLockHoldTimeoutError} - If `holdTimeoutMs` elapses while the callback holds the lock.
2511
+ */
2512
+ static async withAdvisoryLock(name, callback, args = {}) {
2513
+ await this.ensureInitialized()
2514
+
2515
+ const connection = this.connection()
2516
+ const acquired = await connection.acquireAdvisoryLock(name, args)
2517
+
2518
+ if (!acquired) {
2519
+ throw new AdvisoryLockTimeoutError(`Timed out waiting for advisory lock ${JSON.stringify(name)}`, {name})
2520
+ }
2521
+
2522
+ try {
2523
+ return await this.runWithAdvisoryLockHoldTimeout(name, callback, args.holdTimeoutMs)
2524
+ } finally {
2525
+ await connection.releaseAdvisoryLock(name)
2526
+ }
2527
+ }
2528
+
2529
+ /**
2530
+ * Runs the callback only if the named advisory lock can be acquired
2531
+ * immediately. If the lock is already held by any session, throws
2532
+ * `AdvisoryLockBusyError` without waiting.
2533
+ * Use this when contention is a signal that somebody else is already
2534
+ * doing the work and you want to bail out rather than queue up.
2535
+ * @template T
2536
+ * @param {string} name - Lock name.
2537
+ * @param {() => Promise<T>} callback - Callback to invoke while the lock is held.
2538
+ * @param {{holdTimeoutMs?: number | null}} [args] - `holdTimeoutMs` caps how long the callback may hold the lock before it is released and `AdvisoryLockHoldTimeoutError` is thrown.
2539
+ * @returns {Promise<T>} - Resolves with the callback's return value.
2540
+ * @throws {AdvisoryLockBusyError} - If the lock is already held.
2541
+ * @throws {AdvisoryLockHoldTimeoutError} - If `holdTimeoutMs` elapses while the callback holds the lock.
2542
+ */
2543
+ static async withAdvisoryLockOrFail(name, callback, args = {}) {
2544
+ await this.ensureInitialized()
2545
+
2546
+ const connection = this.connection()
2547
+ const acquired = await connection.tryAcquireAdvisoryLock(name)
2548
+
2549
+ if (!acquired) {
2550
+ throw new AdvisoryLockBusyError(`Advisory lock ${JSON.stringify(name)} is already held`, {name})
2551
+ }
2552
+
2553
+ try {
2554
+ return await this.runWithAdvisoryLockHoldTimeout(name, callback, args.holdTimeoutMs)
2555
+ } finally {
2556
+ await connection.releaseAdvisoryLock(name)
2557
+ }
2558
+ }
2559
+
2560
+ /**
2561
+ * Runs `callback`, rejecting with `AdvisoryLockHoldTimeoutError` if it has
2562
+ * not settled within `holdTimeoutMs`. The caller's `finally` then releases
2563
+ * the lock, so a hung holder can't block other sessions forever. The
2564
+ * callback is not cancelled — this is a safety net, not cancellation.
2565
+ *
2566
+ * Uses `awaitery`'s shared `timeout` helper for the hard timeout, then
2567
+ * translates its timeout into the typed `AdvisoryLockHoldTimeoutError` so
2568
+ * callers can catch it like the other advisory-lock errors. A `callbackSettled`
2569
+ * flag distinguishes the timeout from a rejection thrown by the callback
2570
+ * itself, which is rethrown unchanged.
2571
+ * @template T
2572
+ * @param {string} name - Lock name (for the error message).
2573
+ * @param {() => Promise<T>} callback - Callback holding the lock.
2574
+ * @param {number | null} [holdTimeoutMs] - Max hold time; falsy disables the timeout.
2575
+ * @returns {Promise<T>}
2576
+ */
2577
+ static async runWithAdvisoryLockHoldTimeout(name, callback, holdTimeoutMs) {
2578
+ if (!holdTimeoutMs || holdTimeoutMs <= 0) {
2579
+ return await callback()
2580
+ }
2581
+
2582
+ let callbackSettled = false
2583
+
2584
+ try {
2585
+ return await timeout({timeout: holdTimeoutMs}, async () => {
2586
+ try {
2587
+ return await callback()
2588
+ } finally {
2589
+ callbackSettled = true
2590
+ }
2591
+ })
2592
+ } catch (error) {
2593
+ if (!callbackSettled) {
2594
+ throw new AdvisoryLockHoldTimeoutError(`Advisory lock ${JSON.stringify(name)} held longer than ${holdTimeoutMs}ms`, {name})
2595
+ }
2596
+
2597
+ throw error
2598
+ }
2599
+ }
2600
+
2601
+ /**
2602
+ * Returns true if the named advisory lock is currently held by any
2603
+ * session. Primarily useful as a diagnostic; callers that want to act
2604
+ * on the result should prefer `withAdvisoryLockOrFail` to avoid a
2605
+ * TOCTOU window between the check and the action.
2606
+ * @param {string} name - Lock name.
2607
+ * @returns {Promise<boolean>} - Whether the advisory lock is currently held.
2608
+ */
2609
+ static async hasAdvisoryLock(name) {
2610
+ await this.ensureInitialized()
2611
+
2612
+ return await this.connection().isAdvisoryLockHeld(name)
2613
+ }
2614
+
2615
+ /**
2616
+ * Runs translates.
2617
+ * @param {...string} names - Names.
2618
+ * @returns {void} - No return value.
2619
+ */
2620
+ static translates(...names) {
2621
+ const translations = this.getTranslationsMap()
2622
+
2623
+ for (const name of names) {
2624
+ if (name in translations) throw new Error(`Translation already exists: ${name}`)
2625
+
2626
+ translations[name] = {}
2627
+
2628
+ if (!this._relationshipExists("translations")) {
2629
+ this._defineRelationship("translations", {dependent: "destroy", klass: this.getTranslationClass(), type: "hasMany"})
2630
+ }
2631
+
2632
+ if (!this._relationshipExists("currentTranslation")) {
2633
+ this._defineRelationship("currentTranslation", {
2634
+ klass: this.getTranslationClass(),
2635
+ scope: (query) => this.currentTranslationScope(query),
2636
+ type: "hasOne"
2637
+ })
2638
+ }
2639
+ }
2640
+ }
2641
+
2642
+ /**
2643
+ * Runs current translation scope.
2644
+ * @param {ModelClassQuery} query - Translation query.
2645
+ * @returns {ModelClassQuery} - Scoped query.
2646
+ */
2647
+ static currentTranslationScope(query) {
2648
+ const configuration = this._getConfiguration()
2649
+ const locale = configuration.getLocale()
2650
+ const fallbacks = configuration.getLocaleFallbacks()
2651
+ const locales = locale ? (fallbacks?.[locale] || [locale]) : []
2652
+
2653
+ if (locales.length === 0) return query.where("1=0")
2654
+
2655
+ const driver = query.driver
2656
+ const translationClass = this.getTranslationClass()
2657
+ const relationship = this.getRelationshipByName("currentTranslation")
2658
+ const tableName = translationClass.tableName()
2659
+ const scopeTableReference = `${tableName}_current_translation_scope`
2660
+ const targetTableSql = driver.quoteTable(query.getTableReferenceForJoin())
2661
+ const scopeTableSql = driver.quoteTable(scopeTableReference)
2662
+ const scopeTableFromSql = `${driver.quoteTable(tableName)} AS ${scopeTableSql}`
2663
+ const primaryKeyColumn = translationClass.primaryKey()
2664
+ const foreignKeyColumn = relationship.getForeignKey()
2665
+ const targetPrimaryKeySql = `${targetTableSql}.${driver.quoteColumn(primaryKeyColumn)}`
2666
+ const targetForeignKeySql = `${targetTableSql}.${driver.quoteColumn(foreignKeyColumn)}`
2667
+ const scopePrimaryKeySql = `${scopeTableSql}.${driver.quoteColumn(primaryKeyColumn)}`
2668
+ const scopeForeignKeySql = `${scopeTableSql}.${driver.quoteColumn(foreignKeyColumn)}`
2669
+ const scopeLocaleSql = `${scopeTableSql}.${driver.quoteColumn("locale")}`
2670
+ const localeListSql = locales.map((fallbackLocale) => driver.quote(fallbackLocale)).join(", ")
2671
+ const localeOrderSql = locales.map((fallbackLocale, index) => `WHEN ${scopeLocaleSql} = ${driver.quote(fallbackLocale)} THEN ${driver.quote(index)}`).join(" ")
2672
+ const fallbackOrderSql = `CASE ${localeOrderSql} ELSE ${driver.quote(locales.length)} END`
2673
+ const selectedTranslationSql = driver.getType() == "mssql"
2674
+ ? `SELECT TOP 1 ${scopePrimaryKeySql} FROM ${scopeTableFromSql} WHERE ${scopeForeignKeySql} = ${targetForeignKeySql} AND ${scopeLocaleSql} IN (${localeListSql}) ORDER BY ${fallbackOrderSql}, ${scopePrimaryKeySql} ASC`
2675
+ : `SELECT ${scopePrimaryKeySql} FROM ${scopeTableFromSql} WHERE ${scopeForeignKeySql} = ${targetForeignKeySql} AND ${scopeLocaleSql} IN (${localeListSql}) ORDER BY ${fallbackOrderSql}, ${scopePrimaryKeySql} ASC LIMIT 1`
2676
+
2677
+ return query.where(`${targetPrimaryKeySql} = (${selectedTranslationSql})`)
2678
+ }
2679
+
2680
+ /**
2681
+ * Runs get translation class.
2682
+ * @returns {typeof VelociousDatabaseRecord} - The translation class.
2683
+ */
2684
+ static getTranslationClass() {
2685
+ if (this._translationClass) return this._translationClass
2686
+ if (this.tableName().endsWith("_translations")) throw new Error("Trying to define a translations class for a translation class")
2687
+
2688
+ const className = `${this.getModelName()}Translation`
2689
+ const TranslationClass = class Translation extends VelociousDatabaseRecord {}
2690
+ const belongsTo = singularizeModelName(inflection.camelize(this.tableName(), true))
2691
+
2692
+ Object.defineProperty(TranslationClass, "name", {value: className})
2693
+ TranslationClass.setTableName(this.getTranslationsTableName())
2694
+ TranslationClass.belongsTo(belongsTo)
2695
+
2696
+ this._translationClass = TranslationClass
2697
+
2698
+ return this._translationClass
2699
+ }
2700
+
2701
+ /**
2702
+ * Runs get translations table name.
2703
+ * @returns {string} - The translations table name.
2704
+ */
2705
+ static getTranslationsTableName() {
2706
+ const tableNameParts = this.tableName().split("_")
2707
+
2708
+ tableNameParts[tableNameParts.length - 1] = inflection.singularize(tableNameParts[tableNameParts.length - 1])
2709
+
2710
+ return `${tableNameParts.join("_")}_translations`
2711
+ }
2712
+
2713
+ /**
2714
+ * Runs has translations table.
2715
+ * @returns {Promise<boolean>} - Resolves with Whether it has translations table.
2716
+ */
2717
+ static async hasTranslationsTable() {
2718
+ try {
2719
+ await this.connection().getTableByName(this.getTranslationsTableName())
2720
+
2721
+ return true
2722
+ } catch {
2723
+ return false
2724
+ }
2725
+ }
2726
+
2727
+ /**
2728
+ * Adds a validation to an attribute.
2729
+ * @param {string} attributeName The name of the attribute to validate.
2730
+ * @param {Record<string, boolean | Record<string, ?>>} validators The validators to add. Key is the validator name, value is the validator arguments.
2731
+ */
2732
+ static async validates(attributeName, validators) {
2733
+ for (const validatorName in validators) {
2734
+ /**
2735
+ * Defines validatorArgs.
2736
+ @type {Record<string, ?>} */
2737
+ let validatorArgs
2738
+
2739
+ /**
2740
+ * Use validator.
2741
+ @type {boolean} */
2742
+ let useValidator = true
2743
+
2744
+ const validatorArgsCandidate = validators[validatorName]
2745
+
2746
+ if (typeof validatorArgsCandidate == "boolean") {
2747
+ validatorArgs = {}
2748
+ useValidator
2749
+
2750
+ if (!validatorArgsCandidate) {
2751
+ useValidator = false
2752
+ }
2753
+ } else {
2754
+ validatorArgs = validatorArgsCandidate
2755
+ }
2756
+
2757
+ if (!useValidator) {
2758
+ continue
2759
+ }
2760
+
2761
+ const ValidatorClass = this.getValidatorType(validatorName)
2762
+ const validator = new ValidatorClass({attributeName, args: validatorArgs})
2763
+
2764
+ if (!this._validators) this._validators = {}
2765
+ if (!(attributeName in this._validators)) this._validators[attributeName] = []
2766
+
2767
+ this._validators[attributeName].push(validator)
2768
+ }
2769
+ }
2770
+
2771
+ /**
2772
+ * Registers gap-less positional list callbacks for a column scoped by
2773
+ * another column. Inserts and moves shift surrounding positions so the
2774
+ * list stays compact (1,2,3,...). Destroys close the resulting gap.
2775
+ *
2776
+ * Callers must ensure a UNIQUE index on (scopeColumn, positionColumn)
2777
+ * exists in the database — use `Migration.addActsAsList()` for the
2778
+ * schema half.
2779
+ * @param {string} positionColumn - camelCase position attribute (e.g. "rowNumber").
2780
+ * @param {object} options - Options with a required scope attribute.
2781
+ * @param {string} options.scope - camelCase scope attribute (e.g. "boardColumnId").
2782
+ */
2783
+ static actsAsList(positionColumn, options) {
2784
+ const {scope} = options
2785
+
2786
+ registerActsAsListCallbacks(this, positionColumn, {scope})
2787
+ }
2788
+
2789
+ /**
2790
+ * Runs translations loaded.
2791
+ * @abstract
2792
+ * @returns {TranslationBase[]} - The translations loaded.
2793
+ */
2794
+ translationsLoaded() {
2795
+ throw new Error("'translationsLoaded' not implemented")
2796
+ }
2797
+
2798
+ /**
2799
+ * Runs get translated attribute.
2800
+ * @param {string} name - Name.
2801
+ * @param {string} locale - Locale.
2802
+ * @returns {string | undefined} - The translated attribute, if found.
2803
+ */
2804
+ _getTranslatedAttribute(name, locale) {
2805
+ const translation = this.translationsLoaded().find((translation) => translation.locale() == locale)
2806
+
2807
+ if (translation) {
2808
+ /**
2809
+ * Dict.
2810
+ @type {Record<string, ?>} */
2811
+ const dict = translation
2812
+
2813
+ const attributeMethod = /**
2814
+ * Narrows the runtime value to the documented type.
2815
+ @type {function() : string | undefined} */ (dict[name])
2816
+
2817
+ if (typeof attributeMethod == "function") {
2818
+ return attributeMethod.bind(translation)()
2819
+ } else {
2820
+ throw new Error(`No such translated method: ${name} (${typeof attributeMethod})`)
2821
+ }
2822
+ }
2823
+
2824
+ return undefined
2825
+ }
2826
+
2827
+ /**
2828
+ * Runs get translated attribute with fallback.
2829
+ * @param {string} name - Name.
2830
+ * @param {string} locale - Locale.
2831
+ * @returns {string | undefined} - The translated attribute with fallback, if found.
2832
+ */
2833
+ _getTranslatedAttributeWithFallback(name, locale) {
2834
+ let localesInOrder
2835
+ const fallbacks = this._getConfiguration().getLocaleFallbacks()
2836
+
2837
+ if (fallbacks && locale in fallbacks) {
2838
+ localesInOrder = fallbacks[locale]
2839
+ } else {
2840
+ localesInOrder = [locale]
2841
+ }
2842
+
2843
+ for (const fallbackLocale of localesInOrder) {
2844
+ const result = this._getTranslatedAttribute(name, fallbackLocale)
2845
+
2846
+ if (result && result.trim() != "") {
2847
+ return result
2848
+ }
2849
+ }
2850
+
2851
+ return undefined
2852
+ }
2853
+
2854
+ /**
2855
+ * Runs set translated attribute.
2856
+ * @param {string} name - Name.
2857
+ * @param {string} locale - Locale.
2858
+ * @param {?} newValue - New value.
2859
+ * @returns {void} - No return value.
2860
+ */
2861
+ _setTranslatedAttribute(name, locale, newValue) {
2862
+ /**
2863
+ * Defines translation.
2864
+ @type {VelociousDatabaseRecord | TranslationBase | undefined} */
2865
+ let translation
2866
+
2867
+ translation = this.translationsLoaded()?.find((translation) => translation.locale() == locale)
2868
+
2869
+ if (!translation) {
2870
+ const instanceRelationship = this.getRelationshipByName("translations")
2871
+
2872
+ translation = instanceRelationship.build({locale})
2873
+ }
2874
+
2875
+ /**
2876
+ * Assignments.
2877
+ @type {Record<string, ?>} */
2878
+ const assignments = {}
2879
+
2880
+ assignments[name] = newValue
2881
+
2882
+ translation.assign(assignments)
2883
+ }
2884
+
2885
+ /**
2886
+ * Runs new query.
2887
+ * @template {typeof VelociousDatabaseRecord} MC
2888
+ * @this {MC}
2889
+ * @returns {ModelClassQuery<MC>} - The new query.
2890
+ */
2891
+ static _newQuery() {
2892
+ this._assertHasBeenInitialized()
2893
+ const handler = new Handler()
2894
+ const query = new ModelClassQuery({
2895
+ driver: () => this.connection(),
2896
+ handler,
2897
+ modelClass: this
2898
+ })
2899
+
2900
+ return query.from(new FromTable(this.tableName()))
2901
+ }
2902
+
2903
+ /**
2904
+ * Runs orderable column.
2905
+ * @returns {string} - The orderable column.
2906
+ */
2907
+ static orderableColumn() {
2908
+ // FIXME: Allow to change to 'created_at' if using UUID?
2909
+
2910
+ return this.primaryKey()
2911
+ }
2912
+
2913
+ /**
2914
+ * Runs all.
2915
+ * @template {typeof VelociousDatabaseRecord} MC
2916
+ * @this {MC}
2917
+ * @returns {ModelClassQuery<MC>} - The all.
2918
+ */
2919
+ static all() {
2920
+ return this._newQuery()
2921
+ }
2922
+
2923
+ /**
2924
+ * Runs accessible for.
2925
+ * @template {typeof VelociousDatabaseRecord} MC
2926
+ * @this {MC}
2927
+ * @param {string} action - Ability action to scope by.
2928
+ * @param {import("../../authorization/ability.js").default | undefined} [ability] - Ability instance.
2929
+ * @returns {ModelClassQuery<MC>} - Authorized query.
2930
+ */
2931
+ static accessibleFor(action, ability) {
2932
+ const query = this._newQuery()
2933
+ const currentAbility = ability || Current.ability()
2934
+
2935
+ if (!currentAbility) {
2936
+ throw new Error(`No ability in context for ${this.name}. Pass an ability or configure ability resolver on the request`)
2937
+ }
2938
+
2939
+ return /** Narrows the runtime value to the documented type. @type {ModelClassQuery<MC>} */ (currentAbility.applyToQuery({
2940
+ action,
2941
+ modelClass: this,
2942
+ query
2943
+ }))
2944
+ }
2945
+
2946
+ /**
2947
+ * Runs accessible.
2948
+ * @template {typeof VelociousDatabaseRecord} MC
2949
+ * @this {MC}
2950
+ * @param {import("../../authorization/ability.js").default | undefined} [ability] - Ability instance.
2951
+ * @returns {ModelClassQuery<MC>} - Authorized query.
2952
+ */
2953
+ static accessible(ability) {
2954
+ return this.accessibleFor("read", ability)
2955
+ }
2956
+
2957
+ /**
2958
+ * Runs accessible by.
2959
+ * @template {typeof VelociousDatabaseRecord} MC
2960
+ * @this {MC}
2961
+ * @param {import("../../authorization/ability.js").default} ability - Ability instance.
2962
+ * @returns {ModelClassQuery<MC>} - Authorized query.
2963
+ */
2964
+ static accessibleBy(ability) {
2965
+ if (!ability) {
2966
+ throw new Error(`No ability passed to ${this.name}.accessibleBy(ability).`)
2967
+ }
2968
+
2969
+ return this.accessible(ability)
2970
+ }
2971
+
2972
+ /**
2973
+ * Runs count.
2974
+ * @returns {Promise<number>} - Resolves with the count.
2975
+ */
2976
+ static async count() {
2977
+ await this.ensureInitialized()
2978
+
2979
+ return await this._newQuery().count()
2980
+ }
2981
+
2982
+ /**
2983
+ * Runs group.
2984
+ * @template {typeof VelociousDatabaseRecord} MC
2985
+ * @this {MC}
2986
+ * @param {string} group - Group.
2987
+ * @returns {ModelClassQuery<MC>} - The group.
2988
+ */
2989
+ static group(group) {
2990
+ return this._newQuery().group(group)
2991
+ }
2992
+
2993
+ static async destroyAll() {
2994
+ await this.ensureInitialized()
2995
+
2996
+ return await this._newQuery().destroyAll()
2997
+ }
2998
+
2999
+ /**
3000
+ * Runs pluck.
3001
+ * @template {typeof VelociousDatabaseRecord} MC
3002
+ * @this {MC}
3003
+ * @param {...string|string[]} columns - Column names.
3004
+ * @returns {Promise<Array<?>>} - Resolves with the pluck.
3005
+ */
3006
+ static async pluck(...columns) {
3007
+ await this.ensureInitialized()
3008
+
3009
+ return await this._newQuery().pluck(...columns)
3010
+ }
3011
+
3012
+ /**
3013
+ * Runs find.
3014
+ * @template {typeof VelociousDatabaseRecord} MC
3015
+ * @this {MC}
3016
+ * @param {number|string} recordId - Record id.
3017
+ * @returns {Promise<InstanceType<MC>>} - Resolves with the find.
3018
+ */
3019
+ static async find(recordId) {
3020
+ await this.ensureInitialized()
3021
+
3022
+ return await this._newQuery().find(recordId)
3023
+ }
3024
+
3025
+ /**
3026
+ * Runs find by.
3027
+ * @template {typeof VelociousDatabaseRecord} MC
3028
+ * @this {MC}
3029
+ * @param {{[key: string]: string | number}} conditions - Conditions hash keyed by attribute name.
3030
+ * @returns {Promise<InstanceType<MC> | null>} - Resolves with the by.
3031
+ */
3032
+ static async findBy(conditions) {
3033
+ await this.ensureInitialized()
3034
+
3035
+ return await this._newQuery().findBy(conditions)
3036
+ }
3037
+
3038
+ /**
3039
+ * Runs find by or fail.
3040
+ * @template {typeof VelociousDatabaseRecord} MC
3041
+ * @this {MC}
3042
+ * @param {{[key: string]: string | number}} conditions - Conditions hash keyed by attribute name.
3043
+ * @returns {Promise<InstanceType<MC>>} - Resolves with the by or fail.
3044
+ */
3045
+ static async findByOrFail(conditions) {
3046
+ await this.ensureInitialized()
3047
+
3048
+ return await this._newQuery().findByOrFail(conditions)
3049
+ }
3050
+
3051
+ /**
3052
+ * Runs find or create by.
3053
+ * @template {typeof VelociousDatabaseRecord} MC
3054
+ * @this {MC}
3055
+ * @param {{[key: string]: string | number}} conditions - Conditions hash keyed by attribute name.
3056
+ * @param {function() : void} [callback] - Callback function.
3057
+ * @returns {Promise<InstanceType<MC>>} - Resolves with the or create by.
3058
+ */
3059
+ static async findOrCreateBy(conditions, callback) {
3060
+ await this.ensureInitialized()
3061
+
3062
+ return await this._newQuery().findOrCreateBy(conditions, callback)
3063
+ }
3064
+
3065
+ /**
3066
+ * Runs find or initialize by.
3067
+ * @template {typeof VelociousDatabaseRecord} MC
3068
+ * @this {MC}
3069
+ * @param {Record<string, string | number>} conditions - Conditions.
3070
+ * @param {function(InstanceType<MC>) : void} [callback] - Callback function.
3071
+ * @returns {Promise<InstanceType<MC>>} - Resolves with the or initialize by.
3072
+ */
3073
+ static async findOrInitializeBy(conditions, callback) {
3074
+ await this.ensureInitialized()
3075
+
3076
+ return await this._newQuery().findOrInitializeBy(conditions, callback)
3077
+ }
3078
+
3079
+ /**
3080
+ * Runs first.
3081
+ * @template {typeof VelociousDatabaseRecord} MC
3082
+ * @this {MC}
3083
+ * @returns {Promise<InstanceType<MC>>} - Resolves with the first.
3084
+ */
3085
+ static async first() {
3086
+ await this.ensureInitialized()
3087
+
3088
+ const result = await this._newQuery().first()
3089
+
3090
+ if (!result) throw new Error(`${this.name}.first() returned no records`)
3091
+
3092
+ return result
3093
+ }
3094
+
3095
+ /**
3096
+ * Runs joins.
3097
+ * @template {typeof VelociousDatabaseRecord} MC
3098
+ * @this {MC}
3099
+ * @param {string | import("../query/join-object.js").JoinObject} join - Join clause or join descriptor.
3100
+ * @returns {ModelClassQuery<MC>} - The joins.
3101
+ */
3102
+ static joins(join) {
3103
+ return this._newQuery().joins(join)
3104
+ }
3105
+
3106
+ /**
3107
+ * Runs last.
3108
+ * @template {typeof VelociousDatabaseRecord} MC
3109
+ * @this {MC}
3110
+ * @returns {Promise<InstanceType<MC>>} - Resolves with the last.
3111
+ */
3112
+ static async last() {
3113
+ await this.ensureInitialized()
3114
+
3115
+ const result = await this._newQuery().last()
3116
+
3117
+ if (!result) throw new Error(`${this.name}.last() returned no records`)
3118
+
3119
+ return result
3120
+ }
3121
+
3122
+ /**
3123
+ * Runs limit.
3124
+ * @template {typeof VelociousDatabaseRecord} MC
3125
+ * @this {MC}
3126
+ * @param {number} value - Value to use.
3127
+ * @returns {ModelClassQuery<MC>} - The limit.
3128
+ */
3129
+ static limit(value) {
3130
+ return this._newQuery().limit(value)
3131
+ }
3132
+
3133
+ /**
3134
+ * Runs order.
3135
+ * @template {typeof VelociousDatabaseRecord} MC
3136
+ * @this {MC}
3137
+ * @param {import("../query/index.js").OrderArgumentType} order - Order.
3138
+ * @returns {ModelClassQuery<MC>} - The order.
3139
+ */
3140
+ static order(order) {
3141
+ return this._newQuery().order(order)
3142
+ }
3143
+
3144
+ /**
3145
+ * Runs distinct.
3146
+ * @template {typeof VelociousDatabaseRecord} MC
3147
+ * @this {MC}
3148
+ * @param {boolean} [value] - Value to use.
3149
+ * @returns {ModelClassQuery<MC>} - The distinct.
3150
+ */
3151
+ static distinct(value = true) {
3152
+ return this._newQuery().distinct(value)
3153
+ }
3154
+
3155
+ /**
3156
+ * Runs preload.
3157
+ * @template {typeof VelociousDatabaseRecord} MC
3158
+ * @this {MC}
3159
+ * @param {import("../query/index.js").NestedPreloadRecord | string | Array<string | import("../query/index.js").NestedPreloadRecord>} preload - Preload.
3160
+ * @returns {ModelClassQuery<MC>} - The preload.
3161
+ */
3162
+ static preload(preload) {
3163
+ const query = /**
3164
+ * Narrows the runtime value to the documented type.
3165
+ @type {ModelClassQuery<MC>} */ (this._newQuery().preload(preload))
3166
+
3167
+ return query
3168
+ }
3169
+
3170
+ /**
3171
+ * Runs select.
3172
+ * @template {typeof VelociousDatabaseRecord} MC
3173
+ * @this {MC}
3174
+ * @param {import("../query/index.js").SelectArgumentType} select - Select.
3175
+ * @returns {ModelClassQuery<MC>} - The select.
3176
+ */
3177
+ static select(select) {
3178
+ return this._newQuery().select(select)
3179
+ }
3180
+
3181
+ /**
3182
+ * Runs to array.
3183
+ * @template {typeof VelociousDatabaseRecord} MC
3184
+ * @this {MC}
3185
+ * @returns {Promise<InstanceType<MC>[]>} - Resolves with the array.
3186
+ */
3187
+ static async toArray() {
3188
+ await this.ensureInitialized()
3189
+
3190
+ return await this._newQuery().toArray()
3191
+ }
3192
+
3193
+ /**
3194
+ * Runs load.
3195
+ * @template {typeof VelociousDatabaseRecord} MC
3196
+ * @this {MC}
3197
+ * @returns {Promise<InstanceType<MC>[]>} - Resolves with the array.
3198
+ */
3199
+ static async load() {
3200
+ await this.ensureInitialized()
3201
+
3202
+ return await this._newQuery().load()
3203
+ }
3204
+
3205
+ /**
3206
+ * Runs where.
3207
+ * @template {typeof VelociousDatabaseRecord} MC
3208
+ * @this {MC}
3209
+ * @param {import("../query/index.js").WhereArgumentType} where - Where.
3210
+ * @returns {ModelClassQuery<MC>} - The where.
3211
+ */
3212
+ static where(where) {
3213
+ return this._newQuery().where(where)
3214
+ }
3215
+
3216
+ /**
3217
+ * Runs ransack.
3218
+ * @template {typeof VelociousDatabaseRecord} MC
3219
+ * @this {MC}
3220
+ * @param {Record<string, ?>} params - Ransack-style params hash.
3221
+ * @returns {ModelClassQuery<MC>} - Query with Ransack filters applied.
3222
+ */
3223
+ static ransack(params) {
3224
+ return this._newQuery().ransack(params)
3225
+ }
3226
+
3227
+ /**
3228
+ * Runs constructor.
3229
+ * @param {Record<string, ?>} changes - Changes.
3230
+ */
3231
+ constructor(changes = {}) {
3232
+ this.getModelClass()._assertHasBeenInitialized()
3233
+ this._attributes = {}
3234
+ this._changes = {}
3235
+ this._isNewRecord = true
3236
+
3237
+ for (const key in changes) {
3238
+ this.setAttribute(key, changes[key])
3239
+ }
3240
+ }
3241
+
3242
+ /**
3243
+ * Runs load existing record.
3244
+ * @param {object} attributes - Attributes.
3245
+ * @returns {void} - No return value.
3246
+ */
3247
+ loadExistingRecord(attributes) {
3248
+ this._attributes = attributes
3249
+ this._isNewRecord = false
3250
+ }
3251
+
3252
+ /**
3253
+ * Assigns the given attributes to the record.
3254
+ * @param {Record<string, ?>} attributesToAssign - Attributes to assign.
3255
+ * @returns {void} - No return value.
3256
+ */
3257
+ assign(attributesToAssign) {
3258
+ for (const attributeToAssign in attributesToAssign) {
3259
+ this.setAttribute(attributeToAssign, attributesToAssign[attributeToAssign])
3260
+ }
3261
+ }
3262
+
3263
+ /**
3264
+ * Returns a the current attributes of the record (original attributes from database plus changes)
3265
+ * @returns {Record<string, ?>} - The attributes.
3266
+ */
3267
+ attributes() {
3268
+ const data = this.rawAttributes()
3269
+ const columnNameToAttributeName = this.getModelClass().getColumnNameToAttributeNameMap()
3270
+ /**
3271
+ * Attributes.
3272
+ @type {Record<string, ?>} */
3273
+ const attributes = {}
3274
+
3275
+ for (const columnName in data) {
3276
+ const attributeName = columnNameToAttributeName[columnName] || columnName
3277
+
3278
+ attributes[attributeName] = this.readAttribute(attributeName)
3279
+ }
3280
+
3281
+ return attributes
3282
+ }
3283
+
3284
+ /**
3285
+ * Returns column-name keyed data (original attributes from database plus changes)
3286
+ * @returns {Record<string, ?>} - The raw attributes.
3287
+ */
3288
+ rawAttributes() {
3289
+ return Object.assign({}, this._attributes, this._changes)
3290
+ }
3291
+
3292
+ /**
3293
+ * Runs connection.
3294
+ * @returns {import("../drivers/base.js").default} - The connection.
3295
+ */
3296
+ _connection() {
3297
+ if (this.__connection) return this.__connection
3298
+
3299
+ return this.getModelClass().connection()
3300
+ }
3301
+
3302
+ /**
3303
+ * Destroys the record in the database and all of its dependent records.
3304
+ * @returns {Promise<void>} - Resolves when complete.
3305
+ */
3306
+ async destroy() {
3307
+ await this._runLifecycleCallbacks("beforeDestroy")
3308
+
3309
+ for (const relationship of this.getModelClass().getRelationships()) {
3310
+ if (relationship.getDependent() == "restrict") {
3311
+ const instanceRelationship = /**
3312
+ * Narrows the runtime value to the documented type.
3313
+ @type {?} */ (this.getRelationshipByName(relationship.getRelationshipName()))
3314
+ const count = await instanceRelationship.query().count()
3315
+
3316
+ if (count > 0) {
3317
+ throw new Error(`Cannot delete record because dependent ${relationship.getRelationshipName()} exist`)
3318
+ }
3319
+
3320
+ continue
3321
+ }
3322
+
3323
+ if (relationship.getDependent() != "destroy") {
3324
+ continue
3325
+ }
3326
+
3327
+ const instanceRelationship = this.getRelationshipByName(relationship.getRelationshipName())
3328
+
3329
+ /**
3330
+ * Defines models.
3331
+ @type {VelociousDatabaseRecord[]} */
3332
+ let models
3333
+
3334
+ if (instanceRelationship.getType() == "belongsTo") {
3335
+ if (!instanceRelationship.isLoaded()) {
3336
+ await instanceRelationship.load()
3337
+ }
3338
+
3339
+ const model = instanceRelationship.loaded()
3340
+
3341
+ if (model instanceof VelociousDatabaseRecord) {
3342
+ models = [model]
3343
+ } else {
3344
+ throw new Error(`Unexpected loaded type: ${typeof model}`)
3345
+ }
3346
+ } else if (instanceRelationship.getType() == "hasMany") {
3347
+ if (!instanceRelationship.isLoaded()) {
3348
+ await instanceRelationship.load()
3349
+ }
3350
+
3351
+ const loadedModels = instanceRelationship.loaded()
3352
+
3353
+ if (Array.isArray(loadedModels)) {
3354
+ models = loadedModels
3355
+ } else {
3356
+ throw new Error(`Unexpected loaded type: ${typeof loadedModels}`)
3357
+ }
3358
+ } else {
3359
+ throw new Error(`Unhandled relationship type: ${instanceRelationship.getType()}`)
3360
+ }
3361
+
3362
+ for (const model of models) {
3363
+ if (model.isPersisted()) {
3364
+ await model.destroy()
3365
+ }
3366
+ }
3367
+ }
3368
+
3369
+ /**
3370
+ * Conditions.
3371
+ @type {Record<string, ?>} */
3372
+ const conditions = {}
3373
+
3374
+ conditions[this.getModelClass().primaryKey()] = this.id()
3375
+
3376
+ const sql = this._connection().deleteSql({
3377
+ conditions,
3378
+ tableName: this._tableName()
3379
+ })
3380
+
3381
+ await this._connection().query(sql, {logName: `${this.getModelClass().name} Destroy`})
3382
+ await this._runLifecycleCallbacks("afterDestroy")
3383
+ }
3384
+
3385
+ /**
3386
+ * Runs run lifecycle callbacks.
3387
+ * @param {"afterCreate" | "afterDestroy" | "afterSave" | "afterUpdate" | "beforeCreate" | "beforeDestroy" | "beforeSave" | "beforeUpdate" | "beforeValidation"} callbackName - Callback type.
3388
+ * @returns {Promise<void>}
3389
+ */
3390
+ async _runLifecycleCallbacks(callbackName) {
3391
+ const callbacks = this.getModelClass().getLifecycleCallbacksMap()[callbackName] || []
3392
+ let callbackNameRegisteredAsString = false
3393
+
3394
+ for (const callback of callbacks) {
3395
+ if (typeof callback == "string") {
3396
+ if (callback == callbackName) {
3397
+ callbackNameRegisteredAsString = true
3398
+ }
3399
+ const dynamicThis = /**
3400
+ * Narrows the runtime value to the documented type.
3401
+ @type {Record<string, ?>} */ (/**
3402
+ * Narrows the runtime value to the documented type.
3403
+ @type {?} */ (this))
3404
+ const methodCallback = dynamicThis[callback]
3405
+
3406
+ if (typeof methodCallback != "function") {
3407
+ throw new Error(`Lifecycle callback "${callback}" is not a function on ${this.getModelClass().name}`)
3408
+ }
3409
+
3410
+ await methodCallback.call(this)
3411
+ } else {
3412
+ await callback(this)
3413
+ }
3414
+ }
3415
+
3416
+ const dynamicThis = /**
3417
+ * Narrows the runtime value to the documented type.
3418
+ @type {Record<string, ?>} */ (/**
3419
+ * Narrows the runtime value to the documented type.
3420
+ @type {?} */ (this))
3421
+ const instanceCallback = dynamicThis[callbackName]
3422
+
3423
+ if (!callbackNameRegisteredAsString && typeof instanceCallback === "function") {
3424
+ await instanceCallback.call(this)
3425
+ }
3426
+ }
3427
+
3428
+ /**
3429
+ * Runs has changes.
3430
+ * @returns {boolean} - Whether changes.
3431
+ */
3432
+ _hasChanges() { return Object.keys(this._changes).length > 0 }
3433
+
3434
+ /**
3435
+ * Returns true if the model has been changed since it was loaded from the database.
3436
+ * @returns {boolean} - Whether changed.
3437
+ */
3438
+ isChanged() {
3439
+ if (this.isNewRecord() || this._hasChanges()){
3440
+ return true
3441
+ }
3442
+
3443
+ // Check if a loaded sub-model of a relationship is changed and should be saved along with this model.
3444
+ if (this._instanceRelationships) {
3445
+ for (const instanceRelationshipName in this._instanceRelationships) {
3446
+ const instanceRelationship = this._instanceRelationships[instanceRelationshipName]
3447
+ let loaded = instanceRelationship._loaded
3448
+
3449
+ if (instanceRelationship.getAutoSave() === false) {
3450
+ continue
3451
+ }
3452
+
3453
+ if (!loaded) continue
3454
+ if (!Array.isArray(loaded)) loaded = [loaded]
3455
+
3456
+ for (const model of loaded) {
3457
+ if (model.isChanged()) {
3458
+ return true
3459
+ }
3460
+ }
3461
+ }
3462
+ }
3463
+
3464
+ return false
3465
+ }
3466
+
3467
+ /**
3468
+ * Returns the changes that have been made to this record since it was loaded from the database.
3469
+ * @returns {Record<string, Array<?>>} - The changes.
3470
+ */
3471
+ changes() {
3472
+ /**
3473
+ * Changes.
3474
+ @type {Record<string, Array<?>>} */
3475
+ const changes = {}
3476
+
3477
+ for (const changeKey in this._changes) {
3478
+ const changeValue = this._changes[changeKey]
3479
+
3480
+ changes[changeKey] = [this._attributes[changeKey], changeValue]
3481
+ }
3482
+
3483
+ return changes
3484
+ }
3485
+
3486
+ /**
3487
+ * Runs table name.
3488
+ * @returns {string} - The table name.
3489
+ */
3490
+ _tableName() {
3491
+ if (this.__tableName) return this.__tableName
3492
+
3493
+ return this.getModelClass().tableName()
3494
+ }
3495
+
3496
+ /**
3497
+ * Reads an attribute value from the record. Read dynamically by name, so the value can be any
3498
+ * column type and may be overridden by a user-defined getter on the model.
3499
+ * @template V
3500
+ * @param {string} attributeName The name of the attribute to read. This is the attribute name, not the column name.
3501
+ * @returns {V} The attribute value, typed by the caller's accessor contract.
3502
+ */
3503
+ readAttribute(attributeName) {
3504
+ this.getModelClass()._assertHasBeenInitialized()
3505
+ const columnName = this.getModelClass().getAttributeNameToColumnNameMap()[attributeName]
3506
+
3507
+ if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${attributeName} from these mappings: ${Object.keys(this.getModelClass().getAttributeNameToColumnNameMap()).join(", ")}`)
3508
+
3509
+ return /** @type {V} */ (this.readColumn(columnName))
3510
+ }
3511
+
3512
+ /**
3513
+ * Read an association count attached by `.withCount(...)`. Counts are
3514
+ * stored on a separate map from the record's `_attributes` so a
3515
+ * virtual count like `tasksCount` cannot silently shadow a real
3516
+ * column of the same name. Returns the attached number, or 0 when
3517
+ * `.withCount(...)` wasn't requested for this attribute.
3518
+ * @param {string} attributeName - Attribute name, e.g. `"tasksCount"` or a custom `"activeMembersCount"` from `.withCount({activeMembersCount: {...}})`.
3519
+ * @returns {number}
3520
+ */
3521
+ readCount(attributeName) {
3522
+ return readPayloadAssociationCount(/**
3523
+ * Narrows the runtime value to the documented type.
3524
+ @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/**
3525
+ * Narrows the runtime value to the documented type.
3526
+ @type {?} */ (this)), attributeName)
3527
+ }
3528
+
3529
+ /**
3530
+ * Attach an association count to this record. Internal helper used by
3531
+ * the `withCount` runner; outside code should not call this directly.
3532
+ * @param {string} attributeName - Attribute name.
3533
+ * @param {number} value - Count value.
3534
+ * @returns {void}
3535
+ */
3536
+ _setAssociationCount(attributeName, value) {
3537
+ setPayloadAssociationCount(/**
3538
+ * Narrows the runtime value to the documented type.
3539
+ @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/**
3540
+ * Narrows the runtime value to the documented type.
3541
+ @type {?} */ (this)), attributeName, value)
3542
+ }
3543
+
3544
+ /**
3545
+ * All attached association counts as a plain object. Used by the
3546
+ * frontend-model serializer to ship counts alongside the record
3547
+ * attributes on the wire.
3548
+ * @returns {Record<string, number>}
3549
+ */
3550
+ associationCounts() {
3551
+ /**
3552
+ * Result.
3553
+ @type {Record<string, number>} */
3554
+ const result = {}
3555
+
3556
+ const target = /**
3557
+ * Narrows the runtime value to the documented type.
3558
+ @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/**
3559
+ * Narrows the runtime value to the documented type.
3560
+ @type {?} */ (this))
3561
+
3562
+ if (!target._associationCounts) return result
3563
+
3564
+ for (const [attributeName, value] of target._associationCounts) {
3565
+ result[attributeName] = value
3566
+ }
3567
+
3568
+ return result
3569
+ }
3570
+
3571
+ /**
3572
+ * Read a value attached by `.queryData(...)`. Stored on a dedicated
3573
+ * map rather than on `_attributes`, so a virtual queryData key like
3574
+ * `transportSecondsSum` cannot silently shadow a real column of the
3575
+ * same name. Returns `null` when the key wasn't produced by any
3576
+ * registered fn for this record (e.g. no child rows matched the
3577
+ * aggregate).
3578
+ * @param {string} name - queryData attribute name (matches a SELECT alias from the registered fn).
3579
+ * @returns {?}
3580
+ */
3581
+ queryData(name) {
3582
+ return readPayloadQueryData(/**
3583
+ * Narrows the runtime value to the documented type.
3584
+ @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/**
3585
+ * Narrows the runtime value to the documented type.
3586
+ @type {?} */ (this)), name)
3587
+ }
3588
+
3589
+ /**
3590
+ * Attach a queryData value to this record. Internal helper used by
3591
+ * the `queryData` runner and by frontend-model hydration; outside
3592
+ * code should not call this directly.
3593
+ * @param {string} name - queryData attribute name.
3594
+ * @param {?} value - Value to attach.
3595
+ * @returns {void}
3596
+ */
3597
+ _setQueryData(name, value) {
3598
+ setPayloadQueryData(/**
3599
+ * Narrows the runtime value to the documented type.
3600
+ @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/**
3601
+ * Narrows the runtime value to the documented type.
3602
+ @type {?} */ (this)), name, value)
3603
+ }
3604
+
3605
+ /**
3606
+ * All attached queryData values as a plain object. Used by the
3607
+ * frontend-model serializer to ship queryData alongside the record
3608
+ * attributes on the wire.
3609
+ * @returns {Record<string, ?>}
3610
+ */
3611
+ queryDataValues() {
3612
+ /**
3613
+ * Result.
3614
+ @type {Record<string, ?>} */
3615
+ const result = {}
3616
+
3617
+ const target = /**
3618
+ * Narrows the runtime value to the documented type.
3619
+ @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/**
3620
+ * Narrows the runtime value to the documented type.
3621
+ @type {?} */ (this))
3622
+
3623
+ if (!target._queryDataValues) return result
3624
+
3625
+ for (const [name, value] of target._queryDataValues) {
3626
+ result[name] = value
3627
+ }
3628
+
3629
+ return result
3630
+ }
3631
+
3632
+ /**
3633
+ * Read a per-record ability result attached by `.abilities(...)`. The
3634
+ * backend evaluates each requested action against the current ability
3635
+ * for this record instance and ships the result alongside the
3636
+ * record's attributes. Returns `false` when the action wasn't
3637
+ * requested for this record — so UI code can safely branch on
3638
+ * `record.can("update")` without first checking whether the ability
3639
+ * was loaded.
3640
+ * @param {string} action - Ability action name, e.g. `"update"`.
3641
+ * @returns {boolean}
3642
+ */
3643
+ can(action) {
3644
+ return readPayloadComputedAbility(/**
3645
+ * Narrows the runtime value to the documented type.
3646
+ @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/**
3647
+ * Narrows the runtime value to the documented type.
3648
+ @type {?} */ (this)), action)
3649
+ }
3650
+
3651
+ /**
3652
+ * Attach a per-record ability result to this record. Internal helper
3653
+ * used by the `abilities` runner and by frontend-model hydration;
3654
+ * outside code should not call this directly.
3655
+ * @param {string} action - Ability action name.
3656
+ * @param {boolean} value - Whether the current ability permits the action on this record.
3657
+ * @returns {void}
3658
+ */
3659
+ _setComputedAbility(action, value) {
3660
+ setPayloadComputedAbility(/**
3661
+ * Narrows the runtime value to the documented type.
3662
+ @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/**
3663
+ * Narrows the runtime value to the documented type.
3664
+ @type {?} */ (this)), action, value)
3665
+ }
3666
+
3667
+ /**
3668
+ * All attached per-record ability results as a plain object. Used
3669
+ * by the frontend-model serializer to ship results alongside the
3670
+ * record attributes on the wire.
3671
+ * @returns {Record<string, boolean>}
3672
+ */
3673
+ computedAbilities() {
3674
+ /**
3675
+ * Result.
3676
+ @type {Record<string, boolean>} */
3677
+ const result = {}
3678
+
3679
+ const target = /**
3680
+ * Narrows the runtime value to the documented type.
3681
+ @type {import("../../record-payload-values.js").RecordPayloadValuesTarget} */ (/**
3682
+ * Narrows the runtime value to the documented type.
3683
+ @type {?} */ (this))
3684
+
3685
+ if (!target._computedAbilities) return result
3686
+
3687
+ for (const [action, value] of target._computedAbilities) {
3688
+ result[action] = value
3689
+ }
3690
+
3691
+ return result
3692
+ }
3693
+
3694
+ /**
3695
+ * Reads a column value from the record.
3696
+ * @param {string} attributeName The name of the column to read. This is the column name, not the attribute name.
3697
+ * @returns {?} - The column.
3698
+ */
3699
+ readColumn(attributeName) {
3700
+ this.getModelClass()._assertHasBeenInitialized()
3701
+ let result
3702
+
3703
+ if (attributeName in this._changes) {
3704
+ result = this._changes[attributeName]
3705
+ } else if (attributeName in this._attributes) {
3706
+ result = this._attributes[attributeName]
3707
+ } else if (this.isPersisted()) {
3708
+ throw new Error(`No such attribute or not selected ${this.constructor.name}#${attributeName}`)
3709
+ }
3710
+
3711
+ const columnType = this.getModelClass().getColumnTypeByName(attributeName)
3712
+
3713
+ if (columnType && this.getModelClass()._isDateLikeType(columnType)) {
3714
+ result = this._normalizeDateValueForRead(result)
3715
+ }
3716
+
3717
+ result = this._normalizeBooleanValueForRead({columnName: attributeName, columnType, value: result})
3718
+
3719
+ return result
3720
+ }
3721
+
3722
+ /**
3723
+ * Resolves any declared per-attribute cast for a database column name.
3724
+ * @param {string} columnName - Database column name.
3725
+ * @returns {string | undefined} - Declared cast type, or undefined when none is declared.
3726
+ */
3727
+ _declaredAttributeCastForColumn(columnName) {
3728
+ const attributeName = this.getModelClass().getColumnNameToAttributeNameMap()[columnName]
3729
+
3730
+ if (!attributeName) return undefined
3731
+
3732
+ return this.getModelClass().getAttributeCast(attributeName)
3733
+ }
3734
+
3735
+ /**
3736
+ * Converts a stored value to a real boolean for a declared `"boolean"` cast.
3737
+ * Leaves null/undefined untouched; treats 1/true/"1" as true and 0/false/"0" as false.
3738
+ * @param {?} value - Stored database value.
3739
+ * @returns {?} - Converted boolean, or the original value when not recognized.
3740
+ */
3741
+ _castDeclaredBooleanForRead(value) {
3742
+ if (value === null || value === undefined) return value
3743
+ if (declaredBooleanTruthyValues.has(value)) return true
3744
+ if (declaredBooleanFalsyValues.has(value)) return false
3745
+
3746
+ return value
3747
+ }
3748
+
3749
+ /**
3750
+ * Whether a column value is currently loaded on this record (either as a
3751
+ * persisted attribute or a pending change). Used to decide whether a preload
3752
+ * can be skipped because the required columns are already present.
3753
+ * @param {string} columnName - The column name to check.
3754
+ * @returns {boolean} - Whether the column is loaded.
3755
+ */
3756
+ hasLoadedColumn(columnName) {
3757
+ return columnName in this._changes || columnName in this._attributes
3758
+ }
3759
+
3760
+ /**
3761
+ * Runs normalize boolean value for read. A declared `"boolean"` attribute cast converts the
3762
+ * stored value (e.g. an MSSQL `bit` 0/1) to a real boolean; otherwise the existing
3763
+ * introspected-type normalization applies (no behaviour change for non-declared columns).
3764
+ * @param {object} args - Options object.
3765
+ * @param {string} args.columnName - Database column name being read.
3766
+ * @param {string | undefined} args.columnType - Column type.
3767
+ * @param {?} args.value - Value to normalize.
3768
+ * @returns {?} - Normalized value.
3769
+ */
3770
+ _normalizeBooleanValueForRead({columnName, columnType, value}) {
3771
+ if (this._declaredAttributeCastForColumn(columnName) === "boolean") {
3772
+ return this._castDeclaredBooleanForRead(value)
3773
+ }
3774
+
3775
+ if (!columnType) return value
3776
+ if (columnType.toLowerCase() !== "boolean") return value
3777
+ if (value === 1) return true
3778
+ if (value === 0) return false
3779
+
3780
+ return value
3781
+ }
3782
+
3783
+ /**
3784
+ * Runs normalize date value for read.
3785
+ * @param {?} value - Value from database.
3786
+ * @returns {?} - Normalized value.
3787
+ */
3788
+ _normalizeDateValueForRead(value) {
3789
+ if (value === null || value === undefined) return value
3790
+
3791
+ const configuration = this.getModelClass()._getConfiguration()
3792
+ const offsetMinutes = configuration.getEnvironmentHandler().getTimezoneOffsetMinutes(configuration)
3793
+ const offsetMs = offsetMinutes * 60 * 1000
3794
+
3795
+ if (value instanceof Date) {
3796
+ return new Date(value.getTime() + offsetMs)
3797
+ }
3798
+
3799
+ if (typeof value != "string") return value
3800
+
3801
+ const hasTimezone = /[zZ]|[+-]\d{2}:\d{2}$/.test(value)
3802
+ const normalized = value.includes("T") ? value : value.replace(" ", "T")
3803
+ const parseValue = hasTimezone ? normalized : `${normalized}Z`
3804
+ const parsed = Date.parse(parseValue)
3805
+
3806
+ if (Number.isNaN(parsed)) return value
3807
+
3808
+ return new Date(parsed + offsetMs)
3809
+ }
3810
+
3811
+ _belongsToChanges() {
3812
+ /**
3813
+ * Belongs to changes.
3814
+ @type {Record<string, ?>} */
3815
+ const belongsToChanges = {}
3816
+
3817
+ if (this._instanceRelationships) {
3818
+ for (const relationshipName in this._instanceRelationships) {
3819
+ const relationship = this._instanceRelationships[relationshipName]
3820
+
3821
+ if (relationship.getType() == "belongsTo" && relationship.getDirty()) {
3822
+ const model = relationship.loaded()
3823
+
3824
+ if (model) {
3825
+ if (model instanceof VelociousDatabaseRecord) {
3826
+ belongsToChanges[relationship.getForeignKey()] = model?.id()
3827
+ } else {
3828
+ throw new Error(`Unexpected model type: ${typeof model}`)
3829
+ }
3830
+ }
3831
+ }
3832
+ }
3833
+ }
3834
+
3835
+ return belongsToChanges
3836
+ }
3837
+
3838
+ /**
3839
+ * Runs create new record.
3840
+ * @returns {Promise<void>} - Resolves when complete.
3841
+ */
3842
+ async _createNewRecord() {
3843
+ if (!this.getModelClass().connection()["insertSql"]) {
3844
+ throw new Error(`No insertSql on ${this.getModelClass().connection().constructor.name}`)
3845
+ }
3846
+
3847
+ const createdAtColumn = this.getModelClass().getColumns().find((column) => column.getName() == "created_at")
3848
+ const updatedAtColumn = this.getModelClass().getColumns().find((column) => column.getName() == "updated_at")
3849
+ const data = Object.assign({}, this._belongsToChanges(), this.rawAttributes())
3850
+ const primaryKey = this.getModelClass().primaryKey()
3851
+ const primaryKeyColumn = this.getModelClass().getColumns().find((column) => column.getName() == primaryKey)
3852
+ const primaryKeyType = primaryKeyColumn?.getType()?.toLowerCase()
3853
+ const driverSupportsDefaultUUID = typeof this._connection().supportsDefaultPrimaryKeyUUID == "function" && this._connection().supportsDefaultPrimaryKeyUUID()
3854
+ const isUUIDPrimaryKey = primaryKeyType?.includes("uuid")
3855
+ const shouldAssignUUIDPrimaryKey = isUUIDPrimaryKey && !driverSupportsDefaultUUID
3856
+ const currentDate = new Date()
3857
+
3858
+ if (createdAtColumn && (data.created_at === undefined || data.created_at === null || data.created_at === "")) {
3859
+ data.created_at = currentDate
3860
+ }
3861
+ if (updatedAtColumn && (data.updated_at === undefined || data.updated_at === null || data.updated_at === "")) {
3862
+ data.updated_at = currentDate
3863
+ }
3864
+
3865
+ const columnNames = this.getModelClass().getColumnNames()
3866
+ const hasUserProvidedPrimaryKey = data[primaryKey] !== undefined && data[primaryKey] !== null && data[primaryKey] !== ""
3867
+
3868
+ if (shouldAssignUUIDPrimaryKey && !hasUserProvidedPrimaryKey) {
3869
+ data[primaryKey] = new UUID(4).format()
3870
+ }
3871
+
3872
+ this._normalizeDateValuesForWrite(data)
3873
+
3874
+ const sql = this._connection().insertSql({
3875
+ returnLastInsertedColumnNames: columnNames,
3876
+ tableName: this._tableName(),
3877
+ data
3878
+ })
3879
+ const insertResult = await this._connection().query(sql, {logName: `${this.getModelClass().name} Create`})
3880
+
3881
+ if (Array.isArray(insertResult) && insertResult[0] && insertResult[0][primaryKey]) {
3882
+ this._attributes = insertResult[0]
3883
+ this._changes = {}
3884
+ } else if (primaryKeyType == "uuid" && data[primaryKey] !== undefined) {
3885
+ this._attributes = Object.assign({}, data)
3886
+ this._changes = {}
3887
+ } else {
3888
+ const id = await this._connection().lastInsertID()
3889
+
3890
+ await this._reloadWithId(id)
3891
+ }
3892
+
3893
+ this.setIsNewRecord(false)
3894
+
3895
+ // Mark all relationships as preloaded, since we don't expect anything to have magically appeared since we created the record.
3896
+ for (const relationship of this.getModelClass().getRelationships()) {
3897
+ const instanceRelationship = this.getRelationshipByName(relationship.getRelationshipName())
3898
+
3899
+ if (instanceRelationship.getType() == "hasMany" && instanceRelationship.getLoadedOrUndefined() === null) {
3900
+ instanceRelationship.setLoaded([])
3901
+ }
3902
+
3903
+ instanceRelationship.setPreloaded(true)
3904
+ }
3905
+ }
3906
+
3907
+ /**
3908
+ * Runs normalize date values for write.
3909
+ * @param {Record<string, ?>} data - Column-keyed data.
3910
+ * @returns {void} - No return value.
3911
+ */
3912
+ _normalizeDateValuesForWrite(data) {
3913
+ const configuration = this.getModelClass()._getConfiguration()
3914
+ const offsetMinutes = configuration.getEnvironmentHandler().getTimezoneOffsetMinutes(configuration)
3915
+ const offsetMs = offsetMinutes * 60 * 1000
3916
+
3917
+ for (const columnName in data) {
3918
+ const columnType = this.getModelClass().getColumnTypeByName(columnName)
3919
+
3920
+ if (!columnType || !this.getModelClass()._isDateLikeType(columnType)) continue
3921
+
3922
+ const value = data[columnName]
3923
+
3924
+ if (!(value instanceof Date)) continue
3925
+
3926
+ data[columnName] = new Date(value.getTime() - offsetMs)
3927
+ }
3928
+ }
3929
+
3930
+ /**
3931
+ * Runs update record with changes.
3932
+ * @returns {Promise<void>} - Resolves when complete.
3933
+ */
3934
+ async _updateRecordWithChanges() {
3935
+ /**
3936
+ * Conditions.
3937
+ @type {Record<string, ?>} */
3938
+ const conditions = {}
3939
+
3940
+ conditions[this.getModelClass().primaryKey()] = this.id()
3941
+
3942
+ const changes = Object.assign({}, this._belongsToChanges(), this._changes)
3943
+ const updatedAtColumn = this.getModelClass().getColumns().find((column) => column.getName() == "updated_at")
3944
+ const currentDate = new Date()
3945
+
3946
+ if (updatedAtColumn && (changes.updated_at === undefined || changes.updated_at === null || changes.updated_at === "")) {
3947
+ changes.updated_at = currentDate
3948
+ }
3949
+
3950
+ if (Object.keys(changes).length > 0) {
3951
+ this._normalizeDateValuesForWrite(changes)
3952
+ const sql = this._connection().updateSql({
3953
+ tableName: this._tableName(),
3954
+ data: changes,
3955
+ conditions
3956
+ })
3957
+ await this._connection().query(sql, {logName: `${this.getModelClass().name} Update`})
3958
+ await this._reloadWithId(this.id())
3959
+ }
3960
+ }
3961
+
3962
+ /**
3963
+ * Runs id.
3964
+ * @returns {number|string} - The id.
3965
+ */
3966
+ id() {
3967
+ if (!this.getModelClass()._columnNameToAttributeName) {
3968
+ throw new Error(`Column names mapping hasn't been set on ${this.constructor.name}. Has the model been initialized?`)
3969
+ }
3970
+
3971
+ const primaryKey = this.getModelClass().primaryKey()
3972
+ const attributeName = this.getModelClass().getColumnNameToAttributeNameMap()[primaryKey]
3973
+
3974
+ if (attributeName === undefined) {
3975
+ throw new Error(`Primary key ${primaryKey} doesn't exist in columns: ${Object.keys(this.getModelClass().getColumnNameToAttributeNameMap()).join(", ")}`)
3976
+ }
3977
+
3978
+ return /** Narrows the runtime value to the documented type. @type {number | string} */ (this.readAttribute(attributeName))
3979
+ }
3980
+
3981
+ /**
3982
+ * Runs is persisted.
3983
+ * @returns {boolean} - Whether persisted.
3984
+ */
3985
+ isPersisted() { return !this._isNewRecord }
3986
+
3987
+ /**
3988
+ * Runs is new record.
3989
+ * @returns {boolean} - Whether new record.
3990
+ */
3991
+ isNewRecord() { return this._isNewRecord }
3992
+
3993
+ /**
3994
+ * Runs set is new record.
3995
+ * @param {boolean} newIsNewRecord - New is new record.
3996
+ * @returns {void} - No return value.
3997
+ */
3998
+ setIsNewRecord(newIsNewRecord) {
3999
+ this._isNewRecord = newIsNewRecord
4000
+ }
4001
+
4002
+ /**
4003
+ * Runs reload with id.
4004
+ * @template {typeof VelociousDatabaseRecord} MC
4005
+ * @param {string | number} id - Record identifier.
4006
+ * @returns {Promise<void>} - Resolves when complete.
4007
+ */
4008
+ async _reloadWithId(id) {
4009
+ const primaryKey = this.getModelClass().primaryKey()
4010
+
4011
+ /**
4012
+ * Where object.
4013
+ @type {Record<string, ?>} */
4014
+ const whereObject = {}
4015
+
4016
+ whereObject[primaryKey] = id
4017
+
4018
+ const query = /**
4019
+ * Narrows the runtime value to the documented type.
4020
+ @type {import("../query/model-class-query.js").default<MC>} */ (this.getModelClass().where(whereObject))
4021
+ const reloadedModel = await query.first()
4022
+
4023
+ if (!reloadedModel) throw new Error(`${this.constructor.name}#${id} couldn't be reloaded - record didn't exist`)
4024
+
4025
+ this._attributes = reloadedModel.rawAttributes()
4026
+ this._changes = {}
4027
+ }
4028
+
4029
+ /**
4030
+ * Runs reload.
4031
+ * @returns {Promise<void>} - Resolves when complete.
4032
+ */
4033
+ async reload() {
4034
+ const recordId = /**
4035
+ * Narrows the runtime value to the documented type.
4036
+ @type {string | number} */ (this.readAttribute("id"))
4037
+ await this._reloadWithId(recordId)
4038
+ }
4039
+
4040
+ async _runValidations() {
4041
+ /**
4042
+ * Narrows the runtime value to the documented type.
4043
+ @type {Record<string, {type: string, message: string}>} */
4044
+ this._validationErrors = {}
4045
+
4046
+ const validators = this.getModelClass()._validators
4047
+
4048
+ if (validators) {
4049
+ for (const attributeName in validators) {
4050
+ const attributeValidators = validators[attributeName]
4051
+
4052
+ for (const validator of attributeValidators) {
4053
+ await validator.validate({model: this, attributeName})
4054
+ }
4055
+ }
4056
+ }
4057
+
4058
+ if (Object.keys(this._validationErrors).length > 0) {
4059
+ const validationError = new ValidationError(this.fullErrorMessages().join(". "))
4060
+
4061
+ validationError.setValidationErrors(this._validationErrors)
4062
+ validationError.setModel(this)
4063
+ validationError.velocious = {type: "validation_error"}
4064
+
4065
+ throw validationError
4066
+ }
4067
+ }
4068
+
4069
+ /**
4070
+ * Runs full error messages.
4071
+ * @returns {string[]} - The full error messages.
4072
+ */
4073
+ fullErrorMessages() {
4074
+ /**
4075
+ * Validation error messages.
4076
+ @type {string[]} */
4077
+ const validationErrorMessages = []
4078
+
4079
+ if (this._validationErrors) {
4080
+ for (const attributeName in this._validationErrors) {
4081
+ for (const validationError of this._validationErrors[attributeName]) {
4082
+ const message = `${this.getModelClass().humanAttributeName(attributeName)} ${validationError.message}`
4083
+
4084
+ validationErrorMessages.push(message)
4085
+ }
4086
+ }
4087
+ }
4088
+
4089
+ return validationErrorMessages
4090
+ }
4091
+
4092
+ /**
4093
+ * Assigns the attributes to the record and saves it.
4094
+ * @param {object} attributesToAssign - The attributes to assign to the record.
4095
+ */
4096
+ async update(attributesToAssign) {
4097
+ if (attributesToAssign) this.assign(attributesToAssign)
4098
+
4099
+ await this.save()
4100
+ }
4101
+ }
4102
+
4103
+ class TranslationBase extends VelociousDatabaseRecord {
4104
+ /**
4105
+ * Runs locale.
4106
+ * @abstract
4107
+ * @returns {string} - The locale.
4108
+ */
4109
+ locale() {
4110
+ throw new Error("'locale' not implemented")
4111
+ }
4112
+ }
4113
+
4114
+ VelociousDatabaseRecord.registerValidatorType("format", ValidatorsFormat)
4115
+ VelociousDatabaseRecord.registerValidatorType("presence", ValidatorsPresence)
4116
+ VelociousDatabaseRecord.registerValidatorType("uniqueness", ValidatorsUniqueness)
4117
+
4118
+ export {AdvisoryLockBusyError, AdvisoryLockHoldTimeoutError, AdvisoryLockTimeoutError, TenantDatabaseScopeError, ValidationError}
4119
+ export default VelociousDatabaseRecord