velocious 1.0.430 → 1.0.432

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