velocious 1.0.430 → 1.0.431

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