velocious 1.0.430 → 1.0.432

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (831) hide show
  1. package/bin/velocious.js +48 -0
  2. package/build/application.js +229 -0
  3. package/build/authorization/ability.js +329 -0
  4. package/build/authorization/base-resource.js +143 -0
  5. package/build/background-jobs/client.js +50 -0
  6. package/build/background-jobs/cron-expression.js +277 -0
  7. package/build/background-jobs/forked-runner-child.js +86 -0
  8. package/build/background-jobs/job-record.js +13 -0
  9. package/build/background-jobs/job-registry.js +92 -0
  10. package/build/background-jobs/job-runner.js +107 -0
  11. package/build/background-jobs/job.js +77 -0
  12. package/build/background-jobs/json-socket.js +78 -0
  13. package/build/background-jobs/main.js +926 -0
  14. package/build/background-jobs/normalize-error.js +26 -0
  15. package/build/background-jobs/scheduler.js +274 -0
  16. package/build/background-jobs/socket-request.js +68 -0
  17. package/build/background-jobs/status-reporter.js +101 -0
  18. package/build/background-jobs/store.js +994 -0
  19. package/build/background-jobs/types.js +70 -0
  20. package/build/background-jobs/web/authorization.js +89 -0
  21. package/build/background-jobs/web/controller.js +280 -0
  22. package/build/background-jobs/web/index.js +57 -0
  23. package/build/background-jobs/web/path-matcher.js +74 -0
  24. package/build/background-jobs/web/registry.js +49 -0
  25. package/build/background-jobs/worker.js +683 -0
  26. package/build/beacon/client.js +330 -0
  27. package/build/beacon/in-process-broker.js +71 -0
  28. package/build/beacon/in-process-client.js +139 -0
  29. package/build/beacon/server.js +148 -0
  30. package/build/beacon/types.js +55 -0
  31. package/build/bin/velocious.js +39 -34
  32. package/build/cli/base-command.js +67 -0
  33. package/build/cli/browser-cli.js +45 -0
  34. package/build/cli/commands/background-jobs-main.js +7 -0
  35. package/build/cli/commands/background-jobs-runner.js +7 -0
  36. package/build/cli/commands/background-jobs-worker.js +7 -0
  37. package/build/cli/commands/beacon.js +7 -0
  38. package/build/cli/commands/console.js +12 -0
  39. package/build/cli/commands/db/base-command.js +82 -0
  40. package/build/cli/commands/db/create.js +64 -0
  41. package/build/cli/commands/db/drop.js +75 -0
  42. package/build/cli/commands/db/migrate.js +17 -0
  43. package/build/cli/commands/db/reset.js +22 -0
  44. package/build/cli/commands/db/rollback.js +15 -0
  45. package/build/cli/commands/db/schema/dump.js +12 -0
  46. package/build/cli/commands/db/schema/load.js +12 -0
  47. package/build/cli/commands/db/seed.js +12 -0
  48. package/build/cli/commands/db/tenants/check.js +38 -0
  49. package/build/cli/commands/db/tenants/create.js +33 -0
  50. package/build/cli/commands/db/tenants/migrate.js +49 -0
  51. package/build/cli/commands/destroy/migration.js +7 -0
  52. package/build/cli/commands/generate/base-models.js +7 -0
  53. package/build/cli/commands/generate/frontend-models.js +12 -0
  54. package/build/cli/commands/generate/migration.js +7 -0
  55. package/build/cli/commands/generate/model.js +7 -0
  56. package/build/cli/commands/init.js +11 -0
  57. package/build/cli/commands/routes.js +7 -0
  58. package/build/cli/commands/run-script.js +12 -0
  59. package/build/cli/commands/runner.js +12 -0
  60. package/build/cli/commands/server.js +7 -0
  61. package/build/cli/commands/test.js +9 -0
  62. package/build/cli/index.js +152 -0
  63. package/build/cli/tenant-database-command-helper.js +198 -0
  64. package/build/cli/use-browser-cli.js +30 -0
  65. package/build/configuration-resolver.js +65 -0
  66. package/build/configuration-types.js +429 -0
  67. package/build/configuration.js +2590 -0
  68. package/build/controller.js +421 -0
  69. package/build/current-configuration.js +31 -0
  70. package/build/current.js +80 -0
  71. package/build/database/annotations-async-hooks.js +47 -0
  72. package/build/database/annotations.js +40 -0
  73. package/build/database/drivers/base-column.js +182 -0
  74. package/build/database/drivers/base-columns-index.js +81 -0
  75. package/build/database/drivers/base-foreign-key.js +104 -0
  76. package/build/database/drivers/base-table.js +156 -0
  77. package/build/database/drivers/base.js +1609 -0
  78. package/build/database/drivers/mssql/column.js +74 -0
  79. package/build/database/drivers/mssql/columns-index.js +6 -0
  80. package/build/database/drivers/mssql/connect-connection.js +16 -0
  81. package/build/database/drivers/mssql/foreign-key.js +12 -0
  82. package/build/database/drivers/mssql/index.js +590 -0
  83. package/build/database/drivers/mssql/options.js +79 -0
  84. package/build/database/drivers/mssql/query-parser.js +6 -0
  85. package/build/database/drivers/mssql/sql/alter-table.js +4 -0
  86. package/build/database/drivers/mssql/sql/create-database.js +36 -0
  87. package/build/database/drivers/mssql/sql/create-index.js +4 -0
  88. package/build/database/drivers/mssql/sql/create-table.js +4 -0
  89. package/build/database/drivers/mssql/sql/delete.js +19 -0
  90. package/build/database/drivers/mssql/sql/drop-database.js +36 -0
  91. package/build/database/drivers/mssql/sql/drop-table.js +4 -0
  92. package/build/database/drivers/mssql/sql/insert.js +4 -0
  93. package/build/database/drivers/mssql/sql/update.js +31 -0
  94. package/build/database/drivers/mssql/sql/upsert.js +23 -0
  95. package/build/database/drivers/mssql/structure-sql.js +120 -0
  96. package/build/database/drivers/mssql/table.js +145 -0
  97. package/build/database/drivers/mysql/column.js +112 -0
  98. package/build/database/drivers/mysql/columns-index.js +22 -0
  99. package/build/database/drivers/mysql/foreign-key.js +12 -0
  100. package/build/database/drivers/mysql/index.js +473 -0
  101. package/build/database/drivers/mysql/options.js +34 -0
  102. package/build/database/drivers/mysql/query-parser.js +6 -0
  103. package/build/database/drivers/mysql/query.js +37 -0
  104. package/build/database/drivers/mysql/sql/alter-table.js +6 -0
  105. package/build/database/drivers/mysql/sql/create-database.js +39 -0
  106. package/build/database/drivers/mysql/sql/create-index.js +6 -0
  107. package/build/database/drivers/mysql/sql/create-table.js +6 -0
  108. package/build/database/drivers/mysql/sql/delete.js +21 -0
  109. package/build/database/drivers/mysql/sql/drop-database.js +6 -0
  110. package/build/database/drivers/mysql/sql/drop-table.js +6 -0
  111. package/build/database/drivers/mysql/sql/insert.js +6 -0
  112. package/build/database/drivers/mysql/sql/update.js +33 -0
  113. package/build/database/drivers/mysql/sql/upsert.js +13 -0
  114. package/build/database/drivers/mysql/structure-sql.js +93 -0
  115. package/build/database/drivers/mysql/table.js +121 -0
  116. package/build/database/drivers/pgsql/column.js +90 -0
  117. package/build/database/drivers/pgsql/columns-index.js +6 -0
  118. package/build/database/drivers/pgsql/foreign-key.js +12 -0
  119. package/build/database/drivers/pgsql/index.js +441 -0
  120. package/build/database/drivers/pgsql/options.js +32 -0
  121. package/build/database/drivers/pgsql/query-parser.js +6 -0
  122. package/build/database/drivers/pgsql/sql/alter-table.js +6 -0
  123. package/build/database/drivers/pgsql/sql/create-database.js +38 -0
  124. package/build/database/drivers/pgsql/sql/create-index.js +6 -0
  125. package/build/database/drivers/pgsql/sql/create-table.js +6 -0
  126. package/build/database/drivers/pgsql/sql/delete.js +21 -0
  127. package/build/database/drivers/pgsql/sql/drop-database.js +6 -0
  128. package/build/database/drivers/pgsql/sql/drop-table.js +6 -0
  129. package/build/database/drivers/pgsql/sql/insert.js +6 -0
  130. package/build/database/drivers/pgsql/sql/update.js +33 -0
  131. package/build/database/drivers/pgsql/sql/upsert.js +14 -0
  132. package/build/database/drivers/pgsql/structure-sql.js +126 -0
  133. package/build/database/drivers/pgsql/table.js +135 -0
  134. package/build/database/drivers/sqlite/base.js +509 -0
  135. package/build/database/drivers/sqlite/column.js +75 -0
  136. package/build/database/drivers/sqlite/columns-index.js +30 -0
  137. package/build/database/drivers/sqlite/connection-sql-js.js +46 -0
  138. package/build/database/drivers/sqlite/foreign-key.js +24 -0
  139. package/build/database/drivers/sqlite/index.js +394 -0
  140. package/build/database/drivers/sqlite/index.native.js +72 -0
  141. package/build/database/drivers/sqlite/index.web.js +99 -0
  142. package/build/database/drivers/sqlite/options.js +32 -0
  143. package/build/database/drivers/sqlite/query-parser.js +6 -0
  144. package/build/database/drivers/sqlite/query.js +35 -0
  145. package/build/database/drivers/sqlite/query.native.js +35 -0
  146. package/build/database/drivers/sqlite/query.web.js +49 -0
  147. package/build/database/drivers/sqlite/sql/alter-table.js +187 -0
  148. package/build/database/drivers/sqlite/sql/create-index.js +6 -0
  149. package/build/database/drivers/sqlite/sql/create-table.js +6 -0
  150. package/build/database/drivers/sqlite/sql/delete.js +26 -0
  151. package/build/database/drivers/sqlite/sql/drop-table.js +6 -0
  152. package/build/database/drivers/sqlite/sql/insert.js +6 -0
  153. package/build/database/drivers/sqlite/sql/update.js +33 -0
  154. package/build/database/drivers/sqlite/sql/upsert.js +14 -0
  155. package/build/database/drivers/sqlite/structure-sql.js +56 -0
  156. package/build/database/drivers/sqlite/table-rebuilder.js +96 -0
  157. package/build/database/drivers/sqlite/table.js +131 -0
  158. package/build/database/drivers/structure-sql/utils.js +35 -0
  159. package/build/database/handler.js +13 -0
  160. package/build/database/initializer-from-require-context.js +101 -0
  161. package/build/database/migration/index.js +438 -0
  162. package/build/database/migrator/files-finder.js +55 -0
  163. package/build/database/migrator/types.js +31 -0
  164. package/build/database/migrator.js +557 -0
  165. package/build/database/pool/async-tracked-multi-connection.js +1164 -0
  166. package/build/database/pool/base-methods-forward.js +52 -0
  167. package/build/database/pool/base.js +380 -0
  168. package/build/database/pool/single-multi-use.js +118 -0
  169. package/build/database/query/alter-table-base.js +104 -0
  170. package/build/database/query/base.js +49 -0
  171. package/build/database/query/create-database-base.js +42 -0
  172. package/build/database/query/create-index-base.js +117 -0
  173. package/build/database/query/create-table-base.js +205 -0
  174. package/build/database/query/delete-base.js +19 -0
  175. package/build/database/query/drop-database-base.js +38 -0
  176. package/build/database/query/drop-table-base.js +58 -0
  177. package/build/database/query/from-base.js +36 -0
  178. package/build/database/query/from-plain.js +16 -0
  179. package/build/database/query/from-table.js +18 -0
  180. package/build/database/query/index.js +533 -0
  181. package/build/database/query/insert-base.js +172 -0
  182. package/build/database/query/join-base.js +43 -0
  183. package/build/database/query/join-object.js +167 -0
  184. package/build/database/query/join-plain.js +18 -0
  185. package/build/database/query/join-tracker.js +93 -0
  186. package/build/database/query/model-class-query.js +1577 -0
  187. package/build/database/query/order-base.js +33 -0
  188. package/build/database/query/order-column.js +77 -0
  189. package/build/database/query/order-plain.js +28 -0
  190. package/build/database/query/preloader/belongs-to.js +267 -0
  191. package/build/database/query/preloader/ensure-model-class-initialized.js +18 -0
  192. package/build/database/query/preloader/has-many.js +316 -0
  193. package/build/database/query/preloader/has-one.js +123 -0
  194. package/build/database/query/preloader/selection.js +152 -0
  195. package/build/database/query/preloader.js +201 -0
  196. package/build/database/query/query-data.js +305 -0
  197. package/build/database/query/select-base.js +30 -0
  198. package/build/database/query/select-plain.js +18 -0
  199. package/build/database/query/select-table-and-column.js +28 -0
  200. package/build/database/query/update-base.js +41 -0
  201. package/build/database/query/upsert-base.js +103 -0
  202. package/build/database/query/where-base.js +38 -0
  203. package/build/database/query/where-combinator.js +31 -0
  204. package/build/database/query/where-hash.js +77 -0
  205. package/build/database/query/where-model-class-hash.js +505 -0
  206. package/build/database/query/where-not.js +23 -0
  207. package/build/database/query/where-plain.js +20 -0
  208. package/build/database/query/with-count.js +219 -0
  209. package/build/database/query-parser/base-query-parser.js +40 -0
  210. package/build/database/query-parser/from-parser.js +49 -0
  211. package/build/database/query-parser/group-parser.js +55 -0
  212. package/build/database/query-parser/joins-parser.js +37 -0
  213. package/build/database/query-parser/limit-parser.js +77 -0
  214. package/build/database/query-parser/options.js +94 -0
  215. package/build/database/query-parser/order-parser.js +45 -0
  216. package/build/database/query-parser/select-parser.js +67 -0
  217. package/build/database/query-parser/where-parser.js +46 -0
  218. package/build/database/record/acts-as-list.js +374 -0
  219. package/build/database/record/attachments/download.js +49 -0
  220. package/build/database/record/attachments/handle.js +188 -0
  221. package/build/database/record/attachments/normalize-input.js +213 -0
  222. package/build/database/record/attachments/storage-drivers/filesystem.js +114 -0
  223. package/build/database/record/attachments/storage-drivers/native.js +146 -0
  224. package/build/database/record/attachments/storage-drivers/s3.js +245 -0
  225. package/build/database/record/attachments/store.js +591 -0
  226. package/build/database/record/index.js +4094 -0
  227. package/build/database/record/instance-relationships/base.js +289 -0
  228. package/build/database/record/instance-relationships/belongs-to.js +84 -0
  229. package/build/database/record/instance-relationships/has-many.js +284 -0
  230. package/build/database/record/instance-relationships/has-one.js +117 -0
  231. package/build/database/record/record-not-found-error.js +3 -0
  232. package/build/database/record/relationships/base.js +195 -0
  233. package/build/database/record/relationships/belongs-to.js +57 -0
  234. package/build/database/record/relationships/has-many.js +46 -0
  235. package/build/database/record/relationships/has-one.js +46 -0
  236. package/build/database/record/state-machine.js +278 -0
  237. package/build/database/record/user-module.js +43 -0
  238. package/build/database/record/validators/base.js +27 -0
  239. package/build/database/record/validators/format.js +50 -0
  240. package/build/database/record/validators/presence.js +24 -0
  241. package/build/database/record/validators/uniqueness.js +124 -0
  242. package/build/database/table-data/index.js +241 -0
  243. package/build/database/table-data/table-column.js +416 -0
  244. package/build/database/table-data/table-foreign-key.js +69 -0
  245. package/build/database/table-data/table-index.js +46 -0
  246. package/build/database/table-data/table-reference.js +13 -0
  247. package/build/database/use-database.js +48 -0
  248. package/build/environment-handlers/base.js +561 -0
  249. package/build/environment-handlers/browser.js +338 -0
  250. package/build/environment-handlers/node/cli/commands/background-jobs-main.js +21 -0
  251. package/build/environment-handlers/node/cli/commands/background-jobs-runner.js +24 -0
  252. package/build/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -0
  253. package/build/environment-handlers/node/cli/commands/beacon.js +21 -0
  254. package/build/environment-handlers/node/cli/commands/cli-command-context.js +31 -0
  255. package/build/environment-handlers/node/cli/commands/console.js +149 -0
  256. package/build/environment-handlers/node/cli/commands/db/schema/dump.js +43 -0
  257. package/build/environment-handlers/node/cli/commands/db/schema/load.js +69 -0
  258. package/build/environment-handlers/node/cli/commands/db/seed.js +79 -0
  259. package/build/environment-handlers/node/cli/commands/destroy/migration.js +47 -0
  260. package/build/environment-handlers/node/cli/commands/generate/base-models.js +396 -0
  261. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +872 -0
  262. package/build/environment-handlers/node/cli/commands/generate/migration.js +45 -0
  263. package/build/environment-handlers/node/cli/commands/generate/model.js +45 -0
  264. package/build/environment-handlers/node/cli/commands/init.js +68 -0
  265. package/build/environment-handlers/node/cli/commands/routes.js +63 -0
  266. package/build/environment-handlers/node/cli/commands/run-script.js +85 -0
  267. package/build/environment-handlers/node/cli/commands/runner.js +84 -0
  268. package/build/environment-handlers/node/cli/commands/server.js +151 -0
  269. package/build/environment-handlers/node/cli/commands/test.js +118 -0
  270. package/build/environment-handlers/node.js +887 -0
  271. package/build/error-logger.js +30 -0
  272. package/build/frontend-model-controller.js +3491 -0
  273. package/build/frontend-model-resource/base-resource.js +935 -0
  274. package/build/frontend-models/base.js +4004 -0
  275. package/build/frontend-models/clear-pending-debounced-callback.js +16 -0
  276. package/build/frontend-models/event-hook-models.js +49 -0
  277. package/build/frontend-models/model-registry.js +28 -0
  278. package/build/frontend-models/outgoing-event-buffer.js +51 -0
  279. package/build/frontend-models/preloader.js +169 -0
  280. package/build/frontend-models/query.js +2245 -0
  281. package/build/frontend-models/resource-config-validation.js +56 -0
  282. package/build/frontend-models/resource-definition.js +399 -0
  283. package/build/frontend-models/transport-serialization.js +369 -0
  284. package/build/frontend-models/use-created-event.js +21 -0
  285. package/build/frontend-models/use-destroyed-event.js +148 -0
  286. package/build/frontend-models/use-model-class-event.js +164 -0
  287. package/build/frontend-models/use-updated-event.js +152 -0
  288. package/build/frontend-models/websocket-channel.js +494 -0
  289. package/build/frontend-models/websocket-publishers.js +224 -0
  290. package/build/http-client/header.js +17 -0
  291. package/build/http-client/index.js +139 -0
  292. package/build/http-client/request.js +94 -0
  293. package/build/http-client/response.js +151 -0
  294. package/build/http-client/websocket-client.js +27 -0
  295. package/build/http-server/client/index.js +507 -0
  296. package/build/http-server/client/params-to-object.js +152 -0
  297. package/build/http-server/client/request-buffer/form-data-part.js +139 -0
  298. package/build/http-server/client/request-buffer/header.js +19 -0
  299. package/build/http-server/client/request-buffer/index.js +535 -0
  300. package/build/http-server/client/request-parser.js +195 -0
  301. package/build/http-server/client/request-runner.js +321 -0
  302. package/build/http-server/client/request-timing.js +171 -0
  303. package/build/http-server/client/request.js +114 -0
  304. package/build/http-server/client/response.js +251 -0
  305. package/build/http-server/client/uploaded-file/memory-uploaded-file.js +32 -0
  306. package/build/http-server/client/uploaded-file/temporary-uploaded-file.js +32 -0
  307. package/build/http-server/client/uploaded-file/uploaded-file.js +36 -0
  308. package/build/http-server/client/websocket-request.js +147 -0
  309. package/build/http-server/client/websocket-session.js +1755 -0
  310. package/build/http-server/cookie.js +245 -0
  311. package/build/http-server/development-reloader.js +240 -0
  312. package/build/http-server/index.js +561 -0
  313. package/build/http-server/remote-address.js +77 -0
  314. package/build/http-server/server-client.js +222 -0
  315. package/build/http-server/server-lock.js +178 -0
  316. package/build/http-server/websocket-channel-subscribers.js +110 -0
  317. package/build/http-server/websocket-channel.js +137 -0
  318. package/build/http-server/websocket-connection.js +118 -0
  319. package/build/http-server/websocket-event-log-store.js +433 -0
  320. package/build/http-server/websocket-events-host.js +170 -0
  321. package/build/http-server/websocket-events.js +50 -0
  322. package/build/http-server/worker-handler/channel-subscriber-dispatch.js +28 -0
  323. package/build/http-server/worker-handler/in-process.js +155 -0
  324. package/build/http-server/worker-handler/index.js +370 -0
  325. package/build/http-server/worker-handler/worker-script.js +6 -0
  326. package/build/http-server/worker-handler/worker-thread.js +286 -0
  327. package/build/index.js +1 -2
  328. package/build/initializer.js +39 -0
  329. package/build/jobs/mail-delivery.js +22 -0
  330. package/build/logger/base-logger.js +34 -0
  331. package/build/logger/console-logger.js +28 -0
  332. package/build/logger/file-logger.js +36 -0
  333. package/build/logger/outputs/array-output.js +50 -0
  334. package/build/logger/outputs/console-output.js +32 -0
  335. package/build/logger/outputs/file-output.js +55 -0
  336. package/build/logger/outputs/stdout-output.js +64 -0
  337. package/build/logger.js +507 -0
  338. package/build/mailer/backends/smtp.js +197 -0
  339. package/build/mailer/base.js +337 -0
  340. package/build/mailer/delivery.js +70 -0
  341. package/build/mailer/index.js +24 -0
  342. package/build/mailer.js +15 -0
  343. package/build/plugins/sqljs-wasm-route-controller.js +70 -0
  344. package/build/plugins/sqljs-wasm-route.js +71 -0
  345. package/build/record-payload-values.js +83 -0
  346. package/build/routes/app-routes.js +17 -0
  347. package/build/routes/base-route.js +133 -0
  348. package/build/routes/basic-route.js +109 -0
  349. package/build/routes/built-in/debug/controller.js +12 -0
  350. package/build/routes/built-in/errors/controller.js +7 -0
  351. package/build/routes/get-route.js +75 -0
  352. package/build/routes/hooks/frontend-model-command-route-hook.js +100 -0
  353. package/build/routes/index.js +50 -0
  354. package/build/routes/namespace-route.js +51 -0
  355. package/build/routes/plugin-routes.js +141 -0
  356. package/build/routes/post-route.js +74 -0
  357. package/build/routes/resolver.js +535 -0
  358. package/build/routes/resource-route.js +154 -0
  359. package/build/routes/root-route.js +11 -0
  360. package/build/src/authorization/ability.d.ts +24 -23
  361. package/build/src/authorization/ability.d.ts.map +1 -1
  362. package/build/src/authorization/ability.js +14 -13
  363. package/build/src/authorization/base-resource.d.ts +20 -26
  364. package/build/src/authorization/base-resource.d.ts.map +1 -1
  365. package/build/src/authorization/base-resource.js +13 -11
  366. package/build/src/configuration-types.d.ts +21 -2
  367. package/build/src/configuration-types.d.ts.map +1 -1
  368. package/build/src/configuration-types.js +8 -2
  369. package/build/src/database/query/where-combinator.d.ts.map +1 -1
  370. package/build/src/database/query/where-combinator.js +1 -2
  371. package/build/src/database/record/acts-as-list.js +2 -2
  372. package/build/src/database/record/attachments/store.d.ts +1 -1
  373. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  374. package/build/src/database/record/attachments/store.js +2 -2
  375. package/build/src/database/record/index.d.ts +82 -20
  376. package/build/src/database/record/index.d.ts.map +1 -1
  377. package/build/src/database/record/index.js +126 -13
  378. package/build/src/database/record/relationships/base.d.ts +2 -2
  379. package/build/src/database/record/relationships/base.d.ts.map +1 -1
  380. package/build/src/database/record/relationships/base.js +3 -3
  381. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts +4 -2
  382. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
  383. package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +59 -36
  384. package/build/src/frontend-model-controller.d.ts +6 -6
  385. package/build/src/frontend-model-controller.d.ts.map +1 -1
  386. package/build/src/frontend-model-controller.js +4 -4
  387. package/build/src/frontend-model-resource/base-resource.d.ts +18 -17
  388. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  389. package/build/src/frontend-model-resource/base-resource.js +22 -10
  390. package/build/src/frontend-models/base.d.ts +19 -12
  391. package/build/src/frontend-models/base.d.ts.map +1 -1
  392. package/build/src/frontend-models/base.js +79 -48
  393. package/build/src/frontend-models/preloader.d.ts +6 -6
  394. package/build/src/frontend-models/preloader.d.ts.map +1 -1
  395. package/build/src/frontend-models/preloader.js +5 -5
  396. package/build/src/frontend-models/query.d.ts.map +1 -1
  397. package/build/src/frontend-models/query.js +1 -4
  398. package/build/src/utils/ransack.d.ts.map +1 -1
  399. package/build/src/utils/ransack.js +1 -2
  400. package/build/templates/configuration.js +61 -0
  401. package/build/templates/generate-migration.js +11 -0
  402. package/build/templates/generate-model.js +6 -0
  403. package/build/templates/routes.js +11 -0
  404. package/build/testing/base-expect.js +17 -0
  405. package/build/testing/browser-frontend-model-event-hook-scenarios.js +520 -0
  406. package/build/testing/browser-test-app.js +32 -0
  407. package/build/testing/expect-to-change.js +55 -0
  408. package/build/testing/expect-utils.js +269 -0
  409. package/build/testing/expect.js +763 -0
  410. package/build/testing/request-client.js +90 -0
  411. package/build/testing/test-files-finder.js +364 -0
  412. package/build/testing/test-filter-parser.js +198 -0
  413. package/build/testing/test-runner.js +1168 -0
  414. package/build/testing/test-suite-splitter.js +177 -0
  415. package/build/testing/test.js +370 -0
  416. package/build/utils/backtrace-cleaner-node.js +87 -0
  417. package/build/utils/backtrace-cleaner.js +266 -0
  418. package/build/utils/ensure-error.js +15 -0
  419. package/build/utils/event-emitter.js +8 -0
  420. package/build/utils/file-exists.js +18 -0
  421. package/build/utils/format-value.js +101 -0
  422. package/build/utils/model-scope.js +56 -0
  423. package/build/utils/nest-callbacks.js +22 -0
  424. package/build/utils/plain-object.js +14 -0
  425. package/build/utils/ransack.js +859 -0
  426. package/build/utils/rest-args-error.js +14 -0
  427. package/build/utils/singularize-model-name.js +18 -0
  428. package/build/utils/split-sql-statements.js +88 -0
  429. package/build/utils/to-import-specifier.js +53 -0
  430. package/build/utils/with-tracked-stack-async-hooks.js +103 -0
  431. package/build/utils/with-tracked-stack.js +38 -0
  432. package/build/velocious-error.js +34 -0
  433. package/index.js +1 -0
  434. package/package.json +10 -4
  435. package/scripts/clean-build.js +8 -0
  436. package/scripts/ensure-bin-executable.js +13 -0
  437. package/scripts/run-tests.js +37 -0
  438. package/scripts/test-browser.js +486 -0
  439. package/src/application.js +229 -0
  440. package/src/authorization/ability.js +329 -0
  441. package/src/authorization/base-resource.js +143 -0
  442. package/src/background-jobs/client.js +50 -0
  443. package/src/background-jobs/cron-expression.js +277 -0
  444. package/src/background-jobs/forked-runner-child.js +86 -0
  445. package/src/background-jobs/job-record.js +13 -0
  446. package/src/background-jobs/job-registry.js +92 -0
  447. package/src/background-jobs/job-runner.js +107 -0
  448. package/src/background-jobs/job.js +77 -0
  449. package/src/background-jobs/json-socket.js +78 -0
  450. package/src/background-jobs/main.js +926 -0
  451. package/src/background-jobs/normalize-error.js +26 -0
  452. package/src/background-jobs/scheduler.js +274 -0
  453. package/src/background-jobs/socket-request.js +68 -0
  454. package/src/background-jobs/status-reporter.js +101 -0
  455. package/src/background-jobs/store.js +994 -0
  456. package/src/background-jobs/types.js +70 -0
  457. package/src/background-jobs/web/authorization.js +89 -0
  458. package/src/background-jobs/web/controller.js +280 -0
  459. package/src/background-jobs/web/index.js +57 -0
  460. package/src/background-jobs/web/path-matcher.js +74 -0
  461. package/src/background-jobs/web/registry.js +49 -0
  462. package/src/background-jobs/worker.js +683 -0
  463. package/src/beacon/client.js +330 -0
  464. package/src/beacon/in-process-broker.js +71 -0
  465. package/src/beacon/in-process-client.js +139 -0
  466. package/src/beacon/server.js +148 -0
  467. package/src/beacon/types.js +55 -0
  468. package/src/cli/base-command.js +67 -0
  469. package/src/cli/browser-cli.js +45 -0
  470. package/src/cli/commands/background-jobs-main.js +7 -0
  471. package/src/cli/commands/background-jobs-runner.js +7 -0
  472. package/src/cli/commands/background-jobs-worker.js +7 -0
  473. package/src/cli/commands/beacon.js +7 -0
  474. package/src/cli/commands/console.js +12 -0
  475. package/src/cli/commands/db/base-command.js +82 -0
  476. package/src/cli/commands/db/create.js +64 -0
  477. package/src/cli/commands/db/drop.js +75 -0
  478. package/src/cli/commands/db/migrate.js +17 -0
  479. package/src/cli/commands/db/reset.js +22 -0
  480. package/src/cli/commands/db/rollback.js +15 -0
  481. package/src/cli/commands/db/schema/dump.js +12 -0
  482. package/src/cli/commands/db/schema/load.js +12 -0
  483. package/src/cli/commands/db/seed.js +12 -0
  484. package/src/cli/commands/db/tenants/check.js +38 -0
  485. package/src/cli/commands/db/tenants/create.js +33 -0
  486. package/src/cli/commands/db/tenants/migrate.js +49 -0
  487. package/src/cli/commands/destroy/migration.js +7 -0
  488. package/src/cli/commands/generate/base-models.js +7 -0
  489. package/src/cli/commands/generate/frontend-models.js +12 -0
  490. package/src/cli/commands/generate/migration.js +7 -0
  491. package/src/cli/commands/generate/model.js +7 -0
  492. package/src/cli/commands/init.js +11 -0
  493. package/src/cli/commands/routes.js +7 -0
  494. package/src/cli/commands/run-script.js +12 -0
  495. package/src/cli/commands/runner.js +12 -0
  496. package/src/cli/commands/server.js +7 -0
  497. package/src/cli/commands/test.js +9 -0
  498. package/src/cli/index.js +152 -0
  499. package/src/cli/tenant-database-command-helper.js +198 -0
  500. package/src/cli/use-browser-cli.js +30 -0
  501. package/src/configuration-resolver.js +65 -0
  502. package/src/configuration-types.js +429 -0
  503. package/src/configuration.js +2590 -0
  504. package/src/controller.js +421 -0
  505. package/src/current-configuration.js +31 -0
  506. package/src/current.js +80 -0
  507. package/src/database/annotations-async-hooks.js +47 -0
  508. package/src/database/annotations.js +40 -0
  509. package/src/database/drivers/base-column.js +182 -0
  510. package/src/database/drivers/base-columns-index.js +81 -0
  511. package/src/database/drivers/base-foreign-key.js +104 -0
  512. package/src/database/drivers/base-table.js +156 -0
  513. package/src/database/drivers/base.js +1609 -0
  514. package/src/database/drivers/mssql/column.js +74 -0
  515. package/src/database/drivers/mssql/columns-index.js +6 -0
  516. package/src/database/drivers/mssql/connect-connection.js +16 -0
  517. package/src/database/drivers/mssql/foreign-key.js +12 -0
  518. package/src/database/drivers/mssql/index.js +590 -0
  519. package/src/database/drivers/mssql/options.js +79 -0
  520. package/src/database/drivers/mssql/query-parser.js +6 -0
  521. package/src/database/drivers/mssql/sql/alter-table.js +4 -0
  522. package/src/database/drivers/mssql/sql/create-database.js +36 -0
  523. package/src/database/drivers/mssql/sql/create-index.js +4 -0
  524. package/src/database/drivers/mssql/sql/create-table.js +4 -0
  525. package/src/database/drivers/mssql/sql/delete.js +19 -0
  526. package/src/database/drivers/mssql/sql/drop-database.js +36 -0
  527. package/src/database/drivers/mssql/sql/drop-table.js +4 -0
  528. package/src/database/drivers/mssql/sql/insert.js +4 -0
  529. package/src/database/drivers/mssql/sql/update.js +31 -0
  530. package/src/database/drivers/mssql/sql/upsert.js +23 -0
  531. package/src/database/drivers/mssql/structure-sql.js +120 -0
  532. package/src/database/drivers/mssql/table.js +145 -0
  533. package/src/database/drivers/mysql/column.js +112 -0
  534. package/src/database/drivers/mysql/columns-index.js +22 -0
  535. package/src/database/drivers/mysql/foreign-key.js +12 -0
  536. package/src/database/drivers/mysql/index.js +473 -0
  537. package/src/database/drivers/mysql/options.js +34 -0
  538. package/src/database/drivers/mysql/query-parser.js +6 -0
  539. package/src/database/drivers/mysql/query.js +37 -0
  540. package/src/database/drivers/mysql/sql/alter-table.js +6 -0
  541. package/src/database/drivers/mysql/sql/create-database.js +39 -0
  542. package/src/database/drivers/mysql/sql/create-index.js +6 -0
  543. package/src/database/drivers/mysql/sql/create-table.js +6 -0
  544. package/src/database/drivers/mysql/sql/delete.js +21 -0
  545. package/src/database/drivers/mysql/sql/drop-database.js +6 -0
  546. package/src/database/drivers/mysql/sql/drop-table.js +6 -0
  547. package/src/database/drivers/mysql/sql/insert.js +6 -0
  548. package/src/database/drivers/mysql/sql/update.js +33 -0
  549. package/src/database/drivers/mysql/sql/upsert.js +13 -0
  550. package/src/database/drivers/mysql/structure-sql.js +93 -0
  551. package/src/database/drivers/mysql/table.js +121 -0
  552. package/src/database/drivers/pgsql/column.js +90 -0
  553. package/src/database/drivers/pgsql/columns-index.js +6 -0
  554. package/src/database/drivers/pgsql/foreign-key.js +12 -0
  555. package/src/database/drivers/pgsql/index.js +441 -0
  556. package/src/database/drivers/pgsql/options.js +32 -0
  557. package/src/database/drivers/pgsql/query-parser.js +6 -0
  558. package/src/database/drivers/pgsql/sql/alter-table.js +6 -0
  559. package/src/database/drivers/pgsql/sql/create-database.js +38 -0
  560. package/src/database/drivers/pgsql/sql/create-index.js +6 -0
  561. package/src/database/drivers/pgsql/sql/create-table.js +6 -0
  562. package/src/database/drivers/pgsql/sql/delete.js +21 -0
  563. package/src/database/drivers/pgsql/sql/drop-database.js +6 -0
  564. package/src/database/drivers/pgsql/sql/drop-table.js +6 -0
  565. package/src/database/drivers/pgsql/sql/insert.js +6 -0
  566. package/src/database/drivers/pgsql/sql/update.js +33 -0
  567. package/src/database/drivers/pgsql/sql/upsert.js +14 -0
  568. package/src/database/drivers/pgsql/structure-sql.js +126 -0
  569. package/src/database/drivers/pgsql/table.js +135 -0
  570. package/src/database/drivers/sqlite/base.js +509 -0
  571. package/src/database/drivers/sqlite/column.js +75 -0
  572. package/src/database/drivers/sqlite/columns-index.js +30 -0
  573. package/src/database/drivers/sqlite/connection-sql-js.js +46 -0
  574. package/src/database/drivers/sqlite/foreign-key.js +24 -0
  575. package/src/database/drivers/sqlite/index.js +394 -0
  576. package/src/database/drivers/sqlite/index.native.js +72 -0
  577. package/src/database/drivers/sqlite/index.web.js +99 -0
  578. package/src/database/drivers/sqlite/options.js +32 -0
  579. package/src/database/drivers/sqlite/query-parser.js +6 -0
  580. package/src/database/drivers/sqlite/query.js +35 -0
  581. package/src/database/drivers/sqlite/query.native.js +35 -0
  582. package/src/database/drivers/sqlite/query.web.js +49 -0
  583. package/src/database/drivers/sqlite/sql/alter-table.js +187 -0
  584. package/src/database/drivers/sqlite/sql/create-index.js +6 -0
  585. package/src/database/drivers/sqlite/sql/create-table.js +6 -0
  586. package/src/database/drivers/sqlite/sql/delete.js +26 -0
  587. package/src/database/drivers/sqlite/sql/drop-table.js +6 -0
  588. package/src/database/drivers/sqlite/sql/insert.js +6 -0
  589. package/src/database/drivers/sqlite/sql/update.js +33 -0
  590. package/src/database/drivers/sqlite/sql/upsert.js +14 -0
  591. package/src/database/drivers/sqlite/structure-sql.js +56 -0
  592. package/src/database/drivers/sqlite/table-rebuilder.js +96 -0
  593. package/src/database/drivers/sqlite/table.js +131 -0
  594. package/src/database/drivers/structure-sql/utils.js +35 -0
  595. package/src/database/handler.js +13 -0
  596. package/src/database/initializer-from-require-context.js +101 -0
  597. package/src/database/migration/index.js +438 -0
  598. package/src/database/migrator/files-finder.js +55 -0
  599. package/src/database/migrator/types.js +31 -0
  600. package/src/database/migrator.js +557 -0
  601. package/src/database/pool/async-tracked-multi-connection.js +1164 -0
  602. package/src/database/pool/base-methods-forward.js +52 -0
  603. package/src/database/pool/base.js +380 -0
  604. package/src/database/pool/single-multi-use.js +118 -0
  605. package/src/database/query/alter-table-base.js +104 -0
  606. package/src/database/query/base.js +49 -0
  607. package/src/database/query/create-database-base.js +42 -0
  608. package/src/database/query/create-index-base.js +117 -0
  609. package/src/database/query/create-table-base.js +205 -0
  610. package/src/database/query/delete-base.js +19 -0
  611. package/src/database/query/drop-database-base.js +38 -0
  612. package/src/database/query/drop-table-base.js +58 -0
  613. package/src/database/query/from-base.js +36 -0
  614. package/src/database/query/from-plain.js +16 -0
  615. package/src/database/query/from-table.js +18 -0
  616. package/src/database/query/index.js +533 -0
  617. package/src/database/query/insert-base.js +172 -0
  618. package/src/database/query/join-base.js +43 -0
  619. package/src/database/query/join-object.js +167 -0
  620. package/src/database/query/join-plain.js +18 -0
  621. package/src/database/query/join-tracker.js +93 -0
  622. package/src/database/query/model-class-query.js +1577 -0
  623. package/src/database/query/order-base.js +33 -0
  624. package/src/database/query/order-column.js +77 -0
  625. package/src/database/query/order-plain.js +28 -0
  626. package/src/database/query/preloader/belongs-to.js +267 -0
  627. package/src/database/query/preloader/ensure-model-class-initialized.js +18 -0
  628. package/src/database/query/preloader/has-many.js +316 -0
  629. package/src/database/query/preloader/has-one.js +123 -0
  630. package/src/database/query/preloader/selection.js +152 -0
  631. package/src/database/query/preloader.js +201 -0
  632. package/src/database/query/query-data.js +305 -0
  633. package/src/database/query/select-base.js +30 -0
  634. package/src/database/query/select-plain.js +18 -0
  635. package/src/database/query/select-table-and-column.js +28 -0
  636. package/src/database/query/update-base.js +41 -0
  637. package/src/database/query/upsert-base.js +103 -0
  638. package/src/database/query/where-base.js +38 -0
  639. package/src/database/query/where-combinator.js +31 -0
  640. package/src/database/query/where-hash.js +77 -0
  641. package/src/database/query/where-model-class-hash.js +505 -0
  642. package/src/database/query/where-not.js +23 -0
  643. package/src/database/query/where-plain.js +20 -0
  644. package/src/database/query/with-count.js +219 -0
  645. package/src/database/query-parser/base-query-parser.js +40 -0
  646. package/src/database/query-parser/from-parser.js +49 -0
  647. package/src/database/query-parser/group-parser.js +55 -0
  648. package/src/database/query-parser/joins-parser.js +37 -0
  649. package/src/database/query-parser/limit-parser.js +77 -0
  650. package/src/database/query-parser/options.js +94 -0
  651. package/src/database/query-parser/order-parser.js +45 -0
  652. package/src/database/query-parser/select-parser.js +67 -0
  653. package/src/database/query-parser/where-parser.js +46 -0
  654. package/src/database/record/acts-as-list.js +374 -0
  655. package/src/database/record/attachments/download.js +49 -0
  656. package/src/database/record/attachments/handle.js +188 -0
  657. package/src/database/record/attachments/normalize-input.js +213 -0
  658. package/src/database/record/attachments/storage-drivers/filesystem.js +114 -0
  659. package/src/database/record/attachments/storage-drivers/native.js +146 -0
  660. package/src/database/record/attachments/storage-drivers/s3.js +245 -0
  661. package/src/database/record/attachments/store.js +591 -0
  662. package/src/database/record/index.js +4094 -0
  663. package/src/database/record/instance-relationships/base.js +289 -0
  664. package/src/database/record/instance-relationships/belongs-to.js +84 -0
  665. package/src/database/record/instance-relationships/has-many.js +284 -0
  666. package/src/database/record/instance-relationships/has-one.js +117 -0
  667. package/src/database/record/record-not-found-error.js +3 -0
  668. package/src/database/record/relationships/base.js +195 -0
  669. package/src/database/record/relationships/belongs-to.js +57 -0
  670. package/src/database/record/relationships/has-many.js +46 -0
  671. package/src/database/record/relationships/has-one.js +46 -0
  672. package/src/database/record/state-machine.js +278 -0
  673. package/src/database/record/user-module.js +43 -0
  674. package/src/database/record/validators/base.js +27 -0
  675. package/src/database/record/validators/format.js +50 -0
  676. package/src/database/record/validators/presence.js +24 -0
  677. package/src/database/record/validators/uniqueness.js +124 -0
  678. package/src/database/table-data/index.js +241 -0
  679. package/src/database/table-data/table-column.js +416 -0
  680. package/src/database/table-data/table-foreign-key.js +69 -0
  681. package/src/database/table-data/table-index.js +46 -0
  682. package/src/database/table-data/table-reference.js +13 -0
  683. package/src/database/use-database.js +48 -0
  684. package/src/environment-handlers/base.js +561 -0
  685. package/src/environment-handlers/browser.js +338 -0
  686. package/src/environment-handlers/node/cli/commands/background-jobs-main.js +21 -0
  687. package/src/environment-handlers/node/cli/commands/background-jobs-runner.js +24 -0
  688. package/src/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -0
  689. package/src/environment-handlers/node/cli/commands/beacon.js +21 -0
  690. package/src/environment-handlers/node/cli/commands/cli-command-context.js +31 -0
  691. package/src/environment-handlers/node/cli/commands/console.js +149 -0
  692. package/src/environment-handlers/node/cli/commands/db/schema/dump.js +43 -0
  693. package/src/environment-handlers/node/cli/commands/db/schema/load.js +69 -0
  694. package/src/environment-handlers/node/cli/commands/db/seed.js +79 -0
  695. package/src/environment-handlers/node/cli/commands/destroy/migration.js +47 -0
  696. package/src/environment-handlers/node/cli/commands/generate/base-models.js +396 -0
  697. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +872 -0
  698. package/src/environment-handlers/node/cli/commands/generate/migration.js +45 -0
  699. package/src/environment-handlers/node/cli/commands/generate/model.js +45 -0
  700. package/src/environment-handlers/node/cli/commands/init.js +68 -0
  701. package/src/environment-handlers/node/cli/commands/routes.js +63 -0
  702. package/src/environment-handlers/node/cli/commands/run-script.js +85 -0
  703. package/src/environment-handlers/node/cli/commands/runner.js +84 -0
  704. package/src/environment-handlers/node/cli/commands/server.js +151 -0
  705. package/src/environment-handlers/node/cli/commands/test.js +118 -0
  706. package/src/environment-handlers/node.js +887 -0
  707. package/src/error-logger.js +30 -0
  708. package/src/frontend-model-controller.js +3491 -0
  709. package/src/frontend-model-resource/base-resource.js +935 -0
  710. package/src/frontend-models/base.js +4004 -0
  711. package/src/frontend-models/clear-pending-debounced-callback.js +16 -0
  712. package/src/frontend-models/event-hook-models.js +49 -0
  713. package/src/frontend-models/model-registry.js +28 -0
  714. package/src/frontend-models/outgoing-event-buffer.js +51 -0
  715. package/src/frontend-models/preloader.js +169 -0
  716. package/src/frontend-models/query.js +2245 -0
  717. package/src/frontend-models/resource-config-validation.js +56 -0
  718. package/src/frontend-models/resource-definition.js +399 -0
  719. package/src/frontend-models/transport-serialization.js +369 -0
  720. package/src/frontend-models/use-created-event.js +21 -0
  721. package/src/frontend-models/use-destroyed-event.js +148 -0
  722. package/src/frontend-models/use-model-class-event.js +164 -0
  723. package/src/frontend-models/use-updated-event.js +152 -0
  724. package/src/frontend-models/websocket-channel.js +494 -0
  725. package/src/frontend-models/websocket-publishers.js +224 -0
  726. package/src/http-client/header.js +17 -0
  727. package/src/http-client/index.js +139 -0
  728. package/src/http-client/request.js +94 -0
  729. package/src/http-client/response.js +151 -0
  730. package/src/http-client/websocket-client.js +27 -0
  731. package/src/http-server/client/index.js +507 -0
  732. package/src/http-server/client/params-to-object.js +152 -0
  733. package/src/http-server/client/request-buffer/form-data-part.js +139 -0
  734. package/src/http-server/client/request-buffer/header.js +19 -0
  735. package/src/http-server/client/request-buffer/index.js +535 -0
  736. package/src/http-server/client/request-parser.js +195 -0
  737. package/src/http-server/client/request-runner.js +321 -0
  738. package/src/http-server/client/request-timing.js +171 -0
  739. package/src/http-server/client/request.js +114 -0
  740. package/src/http-server/client/response.js +251 -0
  741. package/src/http-server/client/uploaded-file/memory-uploaded-file.js +32 -0
  742. package/src/http-server/client/uploaded-file/temporary-uploaded-file.js +32 -0
  743. package/src/http-server/client/uploaded-file/uploaded-file.js +36 -0
  744. package/src/http-server/client/websocket-request.js +147 -0
  745. package/src/http-server/client/websocket-session.js +1755 -0
  746. package/src/http-server/cookie.js +245 -0
  747. package/src/http-server/development-reloader.js +240 -0
  748. package/src/http-server/index.js +561 -0
  749. package/src/http-server/remote-address.js +77 -0
  750. package/src/http-server/server-client.js +222 -0
  751. package/src/http-server/server-lock.js +178 -0
  752. package/src/http-server/websocket-channel-subscribers.js +110 -0
  753. package/src/http-server/websocket-channel.js +137 -0
  754. package/src/http-server/websocket-connection.js +118 -0
  755. package/src/http-server/websocket-event-log-store.js +433 -0
  756. package/src/http-server/websocket-events-host.js +170 -0
  757. package/src/http-server/websocket-events.js +50 -0
  758. package/src/http-server/worker-handler/channel-subscriber-dispatch.js +28 -0
  759. package/src/http-server/worker-handler/in-process.js +155 -0
  760. package/src/http-server/worker-handler/index.js +370 -0
  761. package/src/http-server/worker-handler/worker-script.js +6 -0
  762. package/src/http-server/worker-handler/worker-thread.js +286 -0
  763. package/src/initializer.js +39 -0
  764. package/src/jobs/.gitkeep +1 -0
  765. package/src/jobs/mail-delivery.js +22 -0
  766. package/src/logger/base-logger.js +34 -0
  767. package/src/logger/console-logger.js +28 -0
  768. package/src/logger/file-logger.js +36 -0
  769. package/src/logger/outputs/array-output.js +50 -0
  770. package/src/logger/outputs/console-output.js +32 -0
  771. package/src/logger/outputs/file-output.js +55 -0
  772. package/src/logger/outputs/stdout-output.js +64 -0
  773. package/src/logger.js +507 -0
  774. package/src/mailer/backends/smtp.js +197 -0
  775. package/src/mailer/base.js +337 -0
  776. package/src/mailer/delivery.js +70 -0
  777. package/src/mailer/index.js +24 -0
  778. package/src/mailer.js +15 -0
  779. package/src/plugins/sqljs-wasm-route-controller.js +70 -0
  780. package/src/plugins/sqljs-wasm-route.js +71 -0
  781. package/src/record-payload-values.js +83 -0
  782. package/src/routes/app-routes.js +17 -0
  783. package/src/routes/base-route.js +133 -0
  784. package/src/routes/basic-route.js +109 -0
  785. package/src/routes/built-in/debug/controller.js +12 -0
  786. package/src/routes/built-in/errors/controller.js +7 -0
  787. package/src/routes/built-in/errors/not-found.ejs +1 -0
  788. package/src/routes/get-route.js +75 -0
  789. package/src/routes/hooks/frontend-model-command-route-hook.js +100 -0
  790. package/src/routes/index.js +50 -0
  791. package/src/routes/namespace-route.js +51 -0
  792. package/src/routes/plugin-routes.js +141 -0
  793. package/src/routes/post-route.js +74 -0
  794. package/src/routes/resolver.js +535 -0
  795. package/src/routes/resource-route.js +154 -0
  796. package/src/routes/root-route.js +11 -0
  797. package/src/templates/configuration.js +61 -0
  798. package/src/templates/generate-migration.js +11 -0
  799. package/src/templates/generate-model.js +6 -0
  800. package/src/templates/routes.js +11 -0
  801. package/src/testing/base-expect.js +17 -0
  802. package/src/testing/browser-frontend-model-event-hook-scenarios.js +520 -0
  803. package/src/testing/browser-test-app.js +32 -0
  804. package/src/testing/expect-to-change.js +55 -0
  805. package/src/testing/expect-utils.js +269 -0
  806. package/src/testing/expect.js +763 -0
  807. package/src/testing/request-client.js +90 -0
  808. package/src/testing/test-files-finder.js +364 -0
  809. package/src/testing/test-filter-parser.js +198 -0
  810. package/src/testing/test-runner.js +1168 -0
  811. package/src/testing/test-suite-splitter.js +177 -0
  812. package/src/testing/test.js +370 -0
  813. package/src/types/external-modules.d.ts +57 -0
  814. package/src/utils/backtrace-cleaner-node.js +87 -0
  815. package/src/utils/backtrace-cleaner.js +266 -0
  816. package/src/utils/ensure-error.js +15 -0
  817. package/src/utils/event-emitter.js +8 -0
  818. package/src/utils/file-exists.js +18 -0
  819. package/src/utils/format-value.js +101 -0
  820. package/src/utils/model-scope.js +56 -0
  821. package/src/utils/nest-callbacks.js +22 -0
  822. package/src/utils/plain-object.js +14 -0
  823. package/src/utils/ransack.js +859 -0
  824. package/src/utils/rest-args-error.js +14 -0
  825. package/src/utils/singularize-model-name.js +18 -0
  826. package/src/utils/split-sql-statements.js +88 -0
  827. package/src/utils/to-import-specifier.js +53 -0
  828. package/src/utils/with-tracked-stack-async-hooks.js +103 -0
  829. package/src/utils/with-tracked-stack.js +38 -0
  830. package/src/velocious-error.js +34 -0
  831. package/tsconfig.json +16 -0
@@ -0,0 +1,2590 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * WithConnectionsCallbackType type.
5
+ * @template T
6
+ * @typedef {function(Record<string, import("./database/drivers/base.js").default>) : Promise<T>} WithConnectionsCallbackType
7
+ */
8
+ /**
9
+ * WithConnectionsOptionsType type.
10
+ * @typedef {object} WithConnectionsOptionsType
11
+ * @property {string} [name] - Human-readable name for the checked-out database connections.
12
+ */
13
+
14
+ import {digg} from "diggerize"
15
+ import gettextConfig from "gettext-universal/build/src/config.js"
16
+ import translate from "gettext-universal/build/src/translate.js"
17
+ import Ability from "./authorization/ability.js"
18
+ import EventEmitter from "./utils/event-emitter.js"
19
+ import VelociousWebsocketChannelSubscribers from "./http-server/websocket-channel-subscribers.js"
20
+ import {CurrentConfigurationNotSetError, currentConfiguration, setCurrentConfiguration} from "./current-configuration.js"
21
+ import {frontendModelResourceConfigurationFromDefinition, frontendModelResourcesForBackendProject} from "./frontend-models/resource-definition.js"
22
+ import PluginRoutes from "./routes/plugin-routes.js"
23
+ import restArgsError from "./utils/rest-args-error.js"
24
+ import {withTrackedStack} from "./utils/with-tracked-stack.js"
25
+
26
+ export {CurrentConfigurationNotSetError}
27
+
28
+ /**
29
+ * Runs current working directory.
30
+ * @returns {string | undefined} - Current working directory when the runtime exposes one.
31
+ */
32
+ function currentWorkingDirectory() {
33
+ const processObject = /**
34
+ * Types the following value.
35
+ @type {{cwd?: ?} | undefined} */ (globalThis.process)
36
+
37
+ if (typeof processObject?.cwd !== "function") return undefined
38
+
39
+ return processObject.cwd()
40
+ }
41
+
42
+ /**
43
+ * Runs canonical debug snapshot value.
44
+ * @param {?} value - Snapshot value to canonicalize.
45
+ * @returns {?} Snapshot value with object keys sorted recursively.
46
+ */
47
+ function canonicalDebugSnapshotValue(value) {
48
+ if (!value || typeof value !== "object") return value
49
+ if (Array.isArray(value)) return value.map((entry) => canonicalDebugSnapshotValue(entry))
50
+
51
+ return Object.keys(value).sort().reduce((result, key) => {
52
+ result[key] = canonicalDebugSnapshotValue(/**
53
+ * Types the following value.
54
+ @type {Record<string, ?>} */ (value)[key])
55
+ return result
56
+ }, /**
57
+ * Types the following value.
58
+ @type {Record<string, ?>} */ ({}))
59
+ }
60
+
61
+ /**
62
+ * Runs merge database configuration.
63
+ * @param {import("./configuration-types.js").DatabaseConfigurationType} databaseConfiguration - Base database configuration.
64
+ * @param {import("./configuration-types.js").DatabaseConfigurationType | Partial<import("./configuration-types.js").DatabaseConfigurationType> | void} overrideConfiguration - Tenant override configuration.
65
+ * @returns {import("./configuration-types.js").DatabaseConfigurationType} - Merged database configuration.
66
+ */
67
+ function mergeDatabaseConfiguration(databaseConfiguration, overrideConfiguration) {
68
+ if (!overrideConfiguration) return databaseConfiguration
69
+
70
+ return {
71
+ ...databaseConfiguration,
72
+ ...overrideConfiguration,
73
+ record: {
74
+ ...(databaseConfiguration.record || {}),
75
+ ...(overrideConfiguration.record || {})
76
+ },
77
+ sqlConfig: {
78
+ ...(databaseConfiguration.sqlConfig || {}),
79
+ ...(overrideConfiguration.sqlConfig || {})
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Resolves the grace window (ms) before a sustained beacon outage is reported.
86
+ * @param {?} value - Configured `unreachableReportMs`, if any.
87
+ * @returns {number} - The configured value when it's a finite number, otherwise the 30s default.
88
+ */
89
+ function resolveBeaconUnreachableReportMs(value) {
90
+ if (typeof value === "number" && Number.isFinite(value)) return value
91
+
92
+ return 30_000
93
+ }
94
+
95
+ export default class VelociousConfiguration {
96
+ /**
97
+ * Close database connections promise.
98
+ @type {Promise<void> | null} */
99
+ _closeDatabaseConnectionsPromise = null
100
+ /**
101
+ * Runs current.
102
+ * @returns {VelociousConfiguration} - The current.
103
+ */
104
+ static current() {
105
+ return currentConfiguration()
106
+ }
107
+
108
+ /**
109
+ * Runs constructor.
110
+ * @param {import("./configuration-types.js").ConfigurationArgsType} args - Configuration arguments.
111
+ */
112
+ constructor({abilityResolver, abilityResources, attachments, autoload = true, backgroundJobs, backendProjects, beacon, cookieSecret, cors, database, debug = false, debugEndpoint = false, directory, enforceTenantDatabaseScopes = true, environment, environmentHandler, exposeInternalErrorsToClients = false, httpServer, initializeModels, initializers, locale, localeFallbacks, locales, logging, mailerBackend, requestTimeoutMs, routeResolverHooks, scheduledBackgroundJobs, structureSql, tenantDatabaseProviders, tenantDatabaseResolver, tenantResolver, testing, timezoneOffsetMinutes, trustedProxies, websocketChannelResolver, websocketMessageHandlerResolver, ...restArgs}) {
113
+ restArgsError(restArgs)
114
+
115
+ this._abilityResolver = abilityResolver
116
+ this._abilityResources = abilityResources || []
117
+ this._autoload = autoload
118
+ this._backgroundJobs = backgroundJobs
119
+ this._beacon = beacon
120
+ /**
121
+ * Stores the beacon client value.
122
+ @type {import("./beacon/client.js").default | import("./beacon/in-process-client.js").default | undefined} */
123
+ this._beaconClient = undefined
124
+ /**
125
+ * Stores the beacon connect promise value.
126
+ @type {Promise<import("./beacon/client.js").default | import("./beacon/in-process-client.js").default | undefined> | undefined} */
127
+ this._beaconConnectPromise = undefined
128
+ /**
129
+ * Stores the beacon report timer value.
130
+ * @type {ReturnType<typeof setTimeout> | undefined} - Pending "beacon still unreachable" report timer.
131
+ */
132
+ this._beaconReportTimer = undefined
133
+ /**
134
+ * Stores the beacon outage reported value.
135
+ * @type {boolean} - Whether the current beacon outage has already been reported.
136
+ */
137
+ this._beaconOutageReported = false
138
+ /**
139
+ * Stores the beacon last down error value.
140
+ * @type {{stage: "beacon-connect" | "beacon-disconnect", error: Error} | undefined} - Latest beacon-down details, reported only if the outage is sustained.
141
+ */
142
+ this._beaconLastDownError = undefined
143
+ this._scheduledBackgroundJobs = scheduledBackgroundJobs
144
+ this._attachments = attachments || {}
145
+ this._backendProjects = backendProjects || []
146
+ this.cors = cors
147
+ this._cookieSecret = cookieSecret
148
+ this.database = database
149
+ this.debug = debug
150
+ this._debugEndpoint = this._normalizeDebugEndpoint(debugEndpoint)
151
+ this._environment = environment || process.env.VELOCIOUS_ENV || process.env.NODE_ENV || "development"
152
+ this._environmentHandler = environmentHandler
153
+ this._enforceTenantDatabaseScopes = enforceTenantDatabaseScopes
154
+ this._exposeInternalErrorsToClients = exposeInternalErrorsToClients
155
+ this._directory = directory
156
+ this._initializeModels = initializeModels
157
+ this._isInitialized = false
158
+ this.httpServer = httpServer || {}
159
+ /**
160
+ * Stores the http server instance value.
161
+ @type {{getDebugSnapshot: () => Promise<Record<string, ?>>} | undefined} */
162
+ this._httpServerInstance = undefined
163
+ this.locale = locale
164
+ this.localeFallbacks = localeFallbacks
165
+ this.locales = locales
166
+ this._initializers = initializers
167
+ this._testing = testing
168
+ this._timezoneOffsetMinutes = timezoneOffsetMinutes
169
+ this._trustedProxies = trustedProxies
170
+ this._requestTimeoutMs = requestTimeoutMs
171
+ this._structureSql = structureSql
172
+ this._tenantDatabaseProviders = tenantDatabaseProviders || {}
173
+ this._tenantDatabaseResolver = tenantDatabaseResolver
174
+ this._tenantResolver = tenantResolver
175
+ this._websocketEvents = undefined
176
+ /**
177
+ * Stores the websocket channel subscribers value.
178
+ @type {VelociousWebsocketChannelSubscribers | undefined} */
179
+ this._websocketChannelSubscribers = undefined
180
+ this._websocketChannelResolver = websocketChannelResolver
181
+ this._websocketMessageHandlerResolver = websocketMessageHandlerResolver
182
+ /**
183
+ * Stores the websocket connection classes value.
184
+ @type {Map<string, typeof import("./http-server/websocket-connection.js").default>} */
185
+ this._websocketConnectionClasses = new Map()
186
+
187
+ /**
188
+ * Stores the websocket channel classes value.
189
+ @type {Map<string, typeof import("./http-server/websocket-channel.js").default>} */
190
+ this._websocketChannelClasses = new Map()
191
+
192
+ /**
193
+ * Stores the websocket channel subscriptions value.
194
+ * @type {Map<string, Set<import("./http-server/websocket-channel.js").default>>} - channelType → live subscriptions across all sessions.
195
+ */
196
+ this._websocketChannelSubscriptions = new Map()
197
+
198
+ /**
199
+ * Stores the websocket sessions value.
200
+ * @type {Set<import("./http-server/client/websocket-session.js").default>} - Live websocket sessions, including paused sessions within the grace window.
201
+ */
202
+ this._websocketSessions = new Set()
203
+
204
+ /**
205
+ * Stores the paused websocket sessions value.
206
+ * @type {Map<string, {session: import("./http-server/client/websocket-session.js").default, graceTimer: ReturnType<typeof setTimeout>, pausedAt: number}>} - sessionId → paused session awaiting resume.
207
+ */
208
+ this._pausedWebsocketSessions = new Map()
209
+
210
+ /** Grace period for paused WebSocket sessions before permanent teardown. */
211
+ this._websocketSessionGraceSeconds = 300
212
+
213
+ /**
214
+ * Optional wrapper called around every WebSocket-borne request /
215
+ * connection message / channel dispatch. Apps register it here
216
+ * to set up per-request context (e.g. AsyncLocalStorage for
217
+ * locale, tenant, tracing) that downstream handlers read.
218
+ * @type {((session: import("./http-server/client/websocket-session.js").default, next: () => Promise<void>) => Promise<void>) | null}
219
+ */
220
+ this._websocketAroundRequest = null
221
+
222
+ /**
223
+ * Stores the around action value.
224
+ @type {((context: {request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, response: import("./http-server/client/response.js").default, next: () => Promise<void>}) => Promise<void>) | null} */
225
+ this._aroundAction = null
226
+
227
+ /**
228
+ * Stores the websocket session identity resolver value.
229
+ @type {((session: import("./http-server/client/websocket-session.js").default) => ? | Promise<?>) | null} */
230
+ this._websocketSessionIdentityResolver = null
231
+ this._logging = logging
232
+ this._mailerBackend = mailerBackend
233
+ this._routeResolverHooks = [...(routeResolverHooks || [])]
234
+ this._addDebugEndpointRouteHook()
235
+
236
+ /**
237
+ * Stores the applied route mounts value.
238
+ @type {WeakSet<object>} */
239
+ this._appliedRouteMounts = new WeakSet()
240
+ this._errorEvents = new EventEmitter()
241
+
242
+ /**
243
+ * Stores the database pools value.
244
+ @type {{[key: string]: import("./database/pool/base.js").default}} */
245
+ this.databasePools = {}
246
+
247
+ /**
248
+ * Stores the model classes value.
249
+ @type {{[key: string]: typeof import("./database/record/index.js").default}} */
250
+ this.modelClasses = {}
251
+
252
+ this.getEnvironmentHandler().setConfiguration(this)
253
+ }
254
+
255
+ /**
256
+ * Runs get autoload.
257
+ * @returns {boolean} Whether auto-batch-preload of relationships on lazy access is enabled globally.
258
+ */
259
+ getAutoload() { return this._autoload }
260
+
261
+ /**
262
+ * Runs get expose internal errors to clients.
263
+ * @returns {boolean} Whether unexpected internal error details may be returned to API clients.
264
+ */
265
+ getExposeInternalErrorsToClients() { return this._exposeInternalErrorsToClients === true }
266
+
267
+ /**
268
+ * Runs get debug endpoint.
269
+ * @returns {{enabled: boolean, path: string, token: string | null}} - Debug endpoint configuration.
270
+ */
271
+ getDebugEndpoint() { return this._debugEndpoint }
272
+
273
+ /**
274
+ * Runs debug endpoint snapshot.
275
+ * @returns {{enabled: boolean, path: string, tokenConfigured: boolean}} - Debug endpoint config for the snapshot, with the token redacted.
276
+ */
277
+ _debugEndpointSnapshot() {
278
+ return {
279
+ enabled: this._debugEndpoint.enabled,
280
+ path: this._debugEndpoint.path,
281
+ tokenConfigured: Boolean(this._debugEndpoint.token)
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Runs normalize debug endpoint.
287
+ * @param {boolean | {path?: string, token?: string}} value - Debug endpoint configuration.
288
+ * @returns {{enabled: boolean, path: string, token: string | null}} - Normalized debug endpoint configuration.
289
+ */
290
+ _normalizeDebugEndpoint(value) {
291
+ if (value === false || value === undefined) return {enabled: false, path: "/velocious/debug", token: null}
292
+ if (value === true) return {enabled: true, path: "/velocious/debug", token: null}
293
+
294
+ if (typeof value !== "object" || value === null) {
295
+ throw new Error(`Expected debugEndpoint to be a boolean or object, got: ${String(value)}`)
296
+ }
297
+
298
+ const path = value.path || "/velocious/debug"
299
+
300
+ if (typeof path !== "string" || !path.startsWith("/")) {
301
+ throw new Error(`Expected debugEndpoint.path to be a string starting with '/', got: ${String(path)}`)
302
+ }
303
+
304
+ const token = value.token === undefined || value.token === null ? null : value.token
305
+
306
+ if (token !== null && (typeof token !== "string" || !token.trim())) {
307
+ throw new Error(`Expected debugEndpoint.token to be a non-empty string, got: ${String(token)}`)
308
+ }
309
+
310
+ return {enabled: true, path, token: token === null ? null : token.trim()}
311
+ }
312
+
313
+ /**
314
+ * Runs add debug endpoint route hook.
315
+ * @returns {void} - No return value.
316
+ */
317
+ _addDebugEndpointRouteHook() {
318
+ if (!this._debugEndpoint.enabled) return
319
+
320
+ this.addRouteResolverHook(({currentPath, request}) => {
321
+ if (request.httpMethod() !== "GET") return null
322
+ if (currentPath !== this._debugEndpoint.path) return null
323
+
324
+ // When a token is configured, an unauthenticated request gets no route at
325
+ // all (404) rather than a 401, so the endpoint's existence stays hidden.
326
+ if (this._debugEndpoint.token && !this.debugEndpointRequestAuthorized(request, this._debugEndpoint.token)) return null
327
+
328
+ return {
329
+ action: "show",
330
+ controller: "velociousDebug",
331
+ controllerPath: "./built-in/debug/controller.js",
332
+ skipControllerConnections: true,
333
+ skipAbilityResolution: true,
334
+ skipTenantResolution: true,
335
+ viewPath: "./built-in/debug"
336
+ }
337
+ })
338
+ }
339
+
340
+ /**
341
+ * Runs set autoload.
342
+ * @param {boolean} newValue - Whether auto-batch-preload of relationships is enabled.
343
+ * @returns {void}
344
+ */
345
+ setAutoload(newValue) { this._autoload = newValue }
346
+
347
+ /**
348
+ * Runs get cors.
349
+ * @returns {import("./configuration-types.js").CorsType | undefined} - The cors.
350
+ */
351
+ getCors() {
352
+ return this.cors
353
+ }
354
+
355
+ /**
356
+ * Runs get cookie secret.
357
+ * @returns {string | undefined} - Cookie secret.
358
+ */
359
+ getCookieSecret() {
360
+ return this._cookieSecret
361
+ }
362
+
363
+ /**
364
+ * Runs get database configuration.
365
+ * @returns {Record<string, import("./configuration-types.js").DatabaseConfigurationType>} - The database configuration.
366
+ */
367
+ getDatabaseConfiguration() {
368
+ if (!this.database) throw new Error("No database configuration")
369
+
370
+ if (!this.database[this.getEnvironment()]) {
371
+ throw new Error(`No database configuration for environment: ${this.getEnvironment()} - ${Object.keys(this.database).join(", ")}`)
372
+ }
373
+
374
+ return digg(this, "database", this.getEnvironment())
375
+ }
376
+
377
+ /**
378
+ * Runs resolve database configuration.
379
+ * @param {string} identifier - Identifier.
380
+ * @param {?} [tenant] - Tenant override.
381
+ * @returns {import("./configuration-types.js").DatabaseConfigurationType} - Resolved database configuration for the identifier.
382
+ */
383
+ resolveDatabaseConfiguration(identifier, tenant = this.getCurrentTenant()) {
384
+ const databaseConfiguration = this.getDatabaseConfiguration()[identifier]
385
+
386
+ if (!databaseConfiguration) {
387
+ throw new Error(`No such database identifier configured: ${identifier}`)
388
+ }
389
+
390
+ if (tenant === undefined || !this._tenantDatabaseResolver) {
391
+ return databaseConfiguration
392
+ }
393
+
394
+ const overrideConfiguration = this._tenantDatabaseResolver({
395
+ configuration: this,
396
+ databaseConfiguration,
397
+ identifier,
398
+ tenant
399
+ })
400
+
401
+ return mergeDatabaseConfiguration(databaseConfiguration, overrideConfiguration)
402
+ }
403
+
404
+ /**
405
+ * Runs get disabled database identifiers.
406
+ * @returns {Set<string>} - Disabled database identifiers from env flags.
407
+ */
408
+ getDisabledDatabaseIdentifiers() {
409
+ const disabledIdentifiers = new Set()
410
+ const disabledIdentifiersRaw = process.env.VELOCIOUS_DISABLED_DATABASE_IDENTIFIERS
411
+
412
+ if (disabledIdentifiersRaw) {
413
+ for (const identifier of disabledIdentifiersRaw.split(",")) {
414
+ const trimmed = identifier.trim()
415
+
416
+ if (trimmed) disabledIdentifiers.add(trimmed)
417
+ }
418
+ }
419
+
420
+ if (process.env.VELOCIOUS_DISABLE_MSSQL === "1") {
421
+ disabledIdentifiers.add("mssql")
422
+ }
423
+
424
+ return disabledIdentifiers
425
+ }
426
+
427
+ /**
428
+ * Runs is database identifier active.
429
+ * @param {string} identifier - Database identifier.
430
+ * @param {?} [tenant] - Tenant override.
431
+ * @returns {boolean} - Whether this database identifier is active in the current tenant context.
432
+ */
433
+ isDatabaseIdentifierActive(identifier, tenant = this.getCurrentTenant()) {
434
+ const databaseConfiguration = this.getDatabaseConfiguration()[identifier]
435
+
436
+ if (!databaseConfiguration) {
437
+ throw new Error(`No such database identifier configured: ${identifier}`)
438
+ }
439
+
440
+ if (!databaseConfiguration.tenantOnly) return true
441
+ if (tenant === undefined || !this._tenantDatabaseResolver) return false
442
+
443
+ const overrideConfiguration = this._tenantDatabaseResolver({
444
+ configuration: this,
445
+ databaseConfiguration,
446
+ identifier,
447
+ tenant
448
+ })
449
+
450
+ return Boolean(overrideConfiguration)
451
+ }
452
+
453
+ /**
454
+ * Runs get database identifiers.
455
+ * @returns {Array<string>} - The database identifiers.
456
+ */
457
+ getDatabaseIdentifiers() {
458
+ const identifiers = Object.keys(this.getDatabaseConfiguration())
459
+ const disabledIdentifiers = this.getDisabledDatabaseIdentifiers()
460
+
461
+ return identifiers.filter((identifier) => !disabledIdentifiers.has(identifier) && this.isDatabaseIdentifierActive(identifier))
462
+ }
463
+
464
+ /**
465
+ * Runs get debug snapshot.
466
+ * @returns {Promise<Record<string, ?>>} - Human-readable server diagnostics.
467
+ */
468
+ async getDebugSnapshot() {
469
+ const localSnapshot = this.getLocalDebugSnapshot()
470
+
471
+ return {
472
+ ...localSnapshot,
473
+ httpServer: await this._debugHttpServerSnapshot()
474
+ }
475
+ }
476
+
477
+ /**
478
+ * Runs get local debug snapshot.
479
+ * @returns {Record<string, ?>} - Human-readable diagnostics for this process only.
480
+ */
481
+ getLocalDebugSnapshot() {
482
+ return {
483
+ backgroundJobs: this._debugBackgroundJobsSnapshot(),
484
+ configuration: this._debugConfigurationSnapshot(),
485
+ database: this._debugDatabaseSnapshot(),
486
+ generatedAt: new Date().toISOString(),
487
+ server: this._debugServerSnapshot(),
488
+ websockets: this._debugWebsocketSnapshot()
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Runs debug http server snapshot.
494
+ * @returns {Promise<Record<string, ?>>} - HTTP server worker diagnostics.
495
+ */
496
+ async _debugHttpServerSnapshot() {
497
+ const httpServer = /**
498
+ * Types the following value.
499
+ @type {{getDebugSnapshot?: () => Promise<Record<string, ?>>} | undefined} */ (this._httpServerInstance)
500
+
501
+ if (!httpServer?.getDebugSnapshot) {
502
+ return {configured: Boolean(this.httpServer), active: false}
503
+ }
504
+
505
+ return await httpServer.getDebugSnapshot()
506
+ }
507
+
508
+ /**
509
+ * Runs debug server snapshot.
510
+ * @returns {Record<string, ?>} - Server runtime diagnostics.
511
+ */
512
+ _debugServerSnapshot() {
513
+ const nodeProcess = typeof process === "undefined" ? undefined : process
514
+
515
+ return {
516
+ environment: this.getEnvironment(),
517
+ memoryUsage: nodeProcess ? nodeProcess.memoryUsage() : undefined,
518
+ nodeVersion: nodeProcess?.versions?.node,
519
+ pid: nodeProcess?.pid,
520
+ platform: nodeProcess?.platform,
521
+ uptimeSeconds: nodeProcess ? nodeProcess.uptime() : undefined
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Runs debug configuration snapshot.
527
+ * @returns {Record<string, ?>} - Configuration diagnostics.
528
+ */
529
+ _debugConfigurationSnapshot() {
530
+ return {
531
+ autoload: this.getAutoload(),
532
+ debug: this.debug === true,
533
+ debugEndpoint: this._debugEndpointSnapshot(),
534
+ enforceTenantDatabaseScopes: this.getEnforceTenantDatabaseScopes(),
535
+ exposeInternalErrorsToClients: this.getExposeInternalErrorsToClients(),
536
+ initialized: this._isInitialized,
537
+ logging: {
538
+ debugLowLevel: this._logging?.debugLowLevel === true,
539
+ outputs: this._logging ? Object.keys(this._logging) : []
540
+ }
541
+ }
542
+ }
543
+
544
+ /**
545
+ * Runs debug background jobs snapshot.
546
+ * @returns {Record<string, ?>} - Background job diagnostics.
547
+ */
548
+ _debugBackgroundJobsSnapshot() {
549
+ return {
550
+ configured: Boolean(this._backgroundJobs),
551
+ scheduledConfigured: Boolean(this._scheduledBackgroundJobs)
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Runs debug database snapshot.
557
+ * @returns {Record<string, ?>} - Database diagnostics.
558
+ */
559
+ _debugDatabaseSnapshot() {
560
+ /**
561
+ * Database pools.
562
+ @type {Record<string, import("./database/pool/base.js").DatabasePoolDebugSnapshot>} */
563
+ const databasePools = {}
564
+ const activeIdentifiers = this.getDatabaseIdentifiers()
565
+
566
+ for (const identifier of activeIdentifiers) {
567
+ databasePools[identifier] = this.getDatabasePool(identifier).getDebugSnapshot()
568
+ }
569
+
570
+ return {
571
+ activeIdentifiers,
572
+ disabledIdentifiers: Array.from(this.getDisabledDatabaseIdentifiers()),
573
+ initializedPools: Object.keys(this.databasePools),
574
+ pools: databasePools
575
+ }
576
+ }
577
+
578
+ /**
579
+ * Runs debug websocket snapshot.
580
+ * @returns {Record<string, ?>} - WebSocket diagnostics.
581
+ */
582
+ _debugWebsocketSnapshot() {
583
+ /**
584
+ * Session buckets.
585
+ @type {Map<string, {count: number, details: {channelSubscriptionCount: number, channelSubscriptions: {channelType: string, count: number, model: string | null}[], connectionCount: number, paused: boolean, subscriptionCount: number}}>} */
586
+ const sessionBuckets = new Map()
587
+ /**
588
+ * Session details.
589
+ @type {{channelSubscriptionCount: number, channelSubscriptions: {channelType: string, count: number, model: string | null}[], connectionCount: number, paused: boolean, queuedMessageCount: number, subscriptionCount: number}[]} */
590
+ const sessionDetails = []
591
+ const subscriptions = Array.from(this._websocketChannelSubscriptions.entries()).map(([channel, channelSubscriptions]) => {
592
+ /**
593
+ * Details buckets.
594
+ @type {Map<string, {count: number, details: Record<string, ?>}>} */
595
+ const detailsBuckets = new Map()
596
+
597
+ for (const subscription of channelSubscriptions) {
598
+ const details = /**
599
+ * Types the following value.
600
+ @type {Record<string, ?>} */ (canonicalDebugSnapshotValue(subscription.debugSnapshot()))
601
+ const key = JSON.stringify(details)
602
+ const existingBucket = detailsBuckets.get(key)
603
+
604
+ if (existingBucket) {
605
+ existingBucket.count += 1
606
+ } else {
607
+ detailsBuckets.set(key, {count: 1, details})
608
+ }
609
+ }
610
+
611
+ return {
612
+ channel,
613
+ count: channelSubscriptions.size,
614
+ details: Array.from(detailsBuckets.values()).sort((a, b) => b.count - a.count)
615
+ }
616
+ })
617
+
618
+ for (const session of this._websocketSessions) {
619
+ /**
620
+ * Channel subscription buckets.
621
+ @type {Map<string, {channelType: string, count: number, model: string | null}>} */
622
+ const channelSubscriptionBuckets = new Map()
623
+
624
+ for (const {channelType, subscription} of session._channelSubscriptions.values()) {
625
+ const details = /**
626
+ * Types the following value.
627
+ @type {Record<string, ?>} */ (subscription.debugSnapshot())
628
+ const model = typeof details.model === "string" ? details.model : null
629
+ const key = JSON.stringify({channelType, model})
630
+ const existingBucket = channelSubscriptionBuckets.get(key)
631
+
632
+ if (existingBucket) {
633
+ existingBucket.count += 1
634
+ } else {
635
+ channelSubscriptionBuckets.set(key, {channelType, count: 1, model})
636
+ }
637
+ }
638
+
639
+ const channelSubscriptions = Array.from(channelSubscriptionBuckets.values()).sort((a, b) => b.count - a.count)
640
+ const snapshot = {
641
+ channelSubscriptionCount: session._channelSubscriptions.size,
642
+ channelSubscriptions,
643
+ connectionCount: session._connections.size,
644
+ paused: session._paused,
645
+ queuedMessageCount: session._outboundQueue.length,
646
+ subscriptionCount: session.subscriptions.size
647
+ }
648
+ const bucketKey = JSON.stringify({
649
+ channelSubscriptionCount: snapshot.channelSubscriptionCount,
650
+ channelSubscriptions: snapshot.channelSubscriptions,
651
+ connectionCount: snapshot.connectionCount,
652
+ paused: snapshot.paused,
653
+ subscriptionCount: snapshot.subscriptionCount
654
+ })
655
+ const existingBucket = sessionBuckets.get(bucketKey)
656
+
657
+ if (existingBucket) {
658
+ existingBucket.count += 1
659
+ } else {
660
+ sessionBuckets.set(bucketKey, {
661
+ count: 1,
662
+ details: {
663
+ channelSubscriptionCount: snapshot.channelSubscriptionCount,
664
+ channelSubscriptions: snapshot.channelSubscriptions,
665
+ connectionCount: snapshot.connectionCount,
666
+ paused: snapshot.paused,
667
+ subscriptionCount: snapshot.subscriptionCount
668
+ }
669
+ })
670
+ }
671
+ sessionDetails.push(snapshot)
672
+ }
673
+
674
+ return {
675
+ pausedSessions: this._pausedWebsocketSessions.size,
676
+ registeredChannels: Array.from(this._websocketChannelClasses.keys()),
677
+ registeredConnections: Array.from(this._websocketConnectionClasses.keys()),
678
+ sessionBuckets: Array.from(sessionBuckets.values()).sort((a, b) => b.count - a.count),
679
+ sessionCount: this._websocketSessions.size,
680
+ sessions: sessionDetails.sort((a, b) => b.channelSubscriptionCount - a.channelSubscriptionCount),
681
+ subscriptionGroups: this._websocketChannelSubscriptions.size,
682
+ subscriptions
683
+ }
684
+ }
685
+
686
+ /**
687
+ * Runs get database pool.
688
+ * @param {string} identifier - Identifier.
689
+ * @returns {import("./database/pool/base.js").default} - The database pool.
690
+ */
691
+ getDatabasePool(identifier = "default") {
692
+ if (!this.isDatabasePoolInitialized(identifier)) {
693
+ this.initializeDatabasePool(identifier)
694
+ }
695
+
696
+ return digg(this, "databasePools", identifier)
697
+ }
698
+
699
+ /**
700
+ * Runs get database identifier.
701
+ * @param {string} identifier - Identifier.
702
+ * @returns {import("./configuration-types.js").DatabaseConfigurationType})
703
+ */
704
+ getDatabaseIdentifier(identifier) {
705
+ return this.resolveDatabaseConfiguration(identifier)
706
+ }
707
+
708
+ /**
709
+ * Clears the schema metadata cached by every initialized pool that targets the
710
+ * same physical database (matched by connection reuse key). Separate pools that
711
+ * point at one database keep independent schema caches, so DDL run through one
712
+ * pool would otherwise leave the others reporting stale tables/columns.
713
+ * @param {string} reuseKey - Connection reuse key identifying the shared database.
714
+ * @returns {void} - No return value.
715
+ */
716
+ clearSchemaCachesForReuseKey(reuseKey) {
717
+ for (const pool of Object.values(this.databasePools)) {
718
+ if (pool.getConfigurationReuseKey() === reuseKey) {
719
+ pool.clearSchemaCache()
720
+ }
721
+ }
722
+ }
723
+
724
+ /**
725
+ * Runs get database pool type.
726
+ * @param {string} identifier - Identifier.
727
+ * @returns {typeof import("./database/pool/base.js").default} - The database pool type.
728
+ */
729
+ getDatabasePoolType(identifier = "default") {
730
+ const poolTypeClass = digg(this.getDatabaseIdentifier(identifier), "poolType")
731
+
732
+ if (!poolTypeClass) {
733
+ throw new Error("No poolType given in database configuration")
734
+ }
735
+
736
+ return poolTypeClass
737
+ }
738
+
739
+ getDatabaseType(identifier = "default") {
740
+ const databaseType = this.getDatabaseIdentifier(identifier).type
741
+
742
+ if (!databaseType) throw new Error("No database type given in database configuration")
743
+
744
+ return databaseType
745
+ }
746
+
747
+ /**
748
+ * Runs get directory.
749
+ * @returns {string} - The directory.
750
+ */
751
+ getDirectory() {
752
+ const directory = this.getDirectoryIfAvailable()
753
+
754
+ if (!directory) throw new Error("No directory configured and process.cwd is unavailable")
755
+
756
+ return directory
757
+ }
758
+
759
+ /**
760
+ * Runs get directory if available.
761
+ * @returns {string | undefined} - The directory when the runtime can resolve one.
762
+ */
763
+ getDirectoryIfAvailable() {
764
+ if (!this._directory) {
765
+ this._directory = currentWorkingDirectory()
766
+ }
767
+
768
+ return this._directory
769
+ }
770
+
771
+ /**
772
+ * Runs get backend projects.
773
+ * @returns {import("./configuration-types.js").BackendProjectConfiguration[]} - Backend projects.
774
+ */
775
+ getBackendProjects() { return this._backendProjects }
776
+
777
+ /**
778
+ * Runs get ability resources.
779
+ * @returns {import("./configuration-types.js").AbilityResourceClassType[]} - Ability resource classes.
780
+ */
781
+ getAbilityResources() { return this._abilityResources }
782
+
783
+ /**
784
+ * Runs set ability resources.
785
+ * @param {import("./configuration-types.js").AbilityResourceClassType[]} resources - Ability resource classes.
786
+ * @returns {void} - No return value.
787
+ */
788
+ setAbilityResources(resources) { this._abilityResources = resources }
789
+
790
+ /**
791
+ * Runs get ability resolver.
792
+ * @returns {import("./configuration-types.js").AbilityResolverType | undefined} - Ability resolver.
793
+ */
794
+ getAbilityResolver() { return this._abilityResolver }
795
+
796
+ /**
797
+ * Runs get tenant resolver.
798
+ * @returns {import("./configuration-types.js").TenantResolverType | undefined} - Tenant resolver.
799
+ */
800
+ getTenantResolver() { return this._tenantResolver }
801
+
802
+ /**
803
+ * Runs get tenant database resolver.
804
+ * @returns {import("./configuration-types.js").TenantDatabaseResolverType | undefined} - Tenant database resolver.
805
+ */
806
+ getTenantDatabaseResolver() { return this._tenantDatabaseResolver }
807
+
808
+ /**
809
+ * Runs get enforce tenant database scopes.
810
+ * @returns {boolean} - Whether tenant-switched models require a resolved tenant database identifier.
811
+ */
812
+ getEnforceTenantDatabaseScopes() { return this._enforceTenantDatabaseScopes }
813
+
814
+ /**
815
+ * Runs get tenant database providers.
816
+ * @returns {Record<string, import("./configuration-types.js").TenantDatabaseProviderType>} - Tenant database lifecycle providers.
817
+ */
818
+ getTenantDatabaseProviders() { return this._tenantDatabaseProviders }
819
+
820
+ /**
821
+ * Runs get tenant database provider.
822
+ * @param {string} identifier - Database identifier.
823
+ * @returns {import("./configuration-types.js").TenantDatabaseProviderType} - Tenant database lifecycle provider.
824
+ */
825
+ getTenantDatabaseProvider(identifier) {
826
+ const provider = this._tenantDatabaseProviders[identifier]
827
+
828
+ if (!provider) {
829
+ throw new Error(`No tenant database provider configured for database identifier: ${identifier}`)
830
+ }
831
+
832
+ return provider
833
+ }
834
+
835
+ /**
836
+ * Runs get attachments configuration.
837
+ * @returns {import("./configuration-types.js").AttachmentsConfiguration} - Attachments configuration.
838
+ */
839
+ getAttachmentsConfiguration() { return this._attachments || {} }
840
+
841
+ /**
842
+ * Runs get route resolver hooks.
843
+ * @returns {import("./configuration-types.js").RouteResolverHookType[]} - Route resolver hooks.
844
+ */
845
+ getRouteResolverHooks() { return this._routeResolverHooks }
846
+
847
+ /**
848
+ * Runs add route resolver hook.
849
+ * @param {import("./configuration-types.js").RouteResolverHookType} hook - Route resolver hook.
850
+ * @returns {void} - No return value.
851
+ */
852
+ addRouteResolverHook(hook) {
853
+ this._routeResolverHooks.push(hook)
854
+ }
855
+
856
+ /**
857
+ * Runs set ability resolver.
858
+ * @param {import("./configuration-types.js").AbilityResolverType | undefined} resolver - Ability resolver.
859
+ * @returns {void} - No return value.
860
+ */
861
+ setAbilityResolver(resolver) { this._abilityResolver = resolver }
862
+
863
+ /**
864
+ * Runs set tenant resolver.
865
+ * @param {import("./configuration-types.js").TenantResolverType | undefined} resolver - Tenant resolver.
866
+ * @returns {void} - No return value.
867
+ */
868
+ setTenantResolver(resolver) { this._tenantResolver = resolver }
869
+
870
+ /**
871
+ * Runs set tenant database resolver.
872
+ * @param {import("./configuration-types.js").TenantDatabaseResolverType | undefined} resolver - Tenant database resolver.
873
+ * @returns {void} - No return value.
874
+ */
875
+ setTenantDatabaseResolver(resolver) { this._tenantDatabaseResolver = resolver }
876
+
877
+ /**
878
+ * Runs set enforce tenant database scopes.
879
+ * @param {boolean} newValue - Whether tenant-switched models require a resolved tenant database identifier.
880
+ * @returns {void} - No return value.
881
+ */
882
+ setEnforceTenantDatabaseScopes(newValue) { this._enforceTenantDatabaseScopes = newValue }
883
+
884
+ /**
885
+ * Runs set tenant database providers.
886
+ * @param {Record<string, import("./configuration-types.js").TenantDatabaseProviderType>} providers - Tenant database lifecycle providers.
887
+ * @returns {void} - No return value.
888
+ */
889
+ setTenantDatabaseProviders(providers) { this._tenantDatabaseProviders = providers }
890
+
891
+ /**
892
+ * Runs get environment.
893
+ * @returns {string} - The environment.
894
+ */
895
+ getEnvironment() { return digg(this, "_environment") }
896
+
897
+ /**
898
+ * Runs get request timeout ms.
899
+ * @returns {number} - Request timeout in seconds.
900
+ */
901
+ getRequestTimeoutMs() {
902
+ const envTimeout = this._parseRequestTimeoutSeconds(process.env.VELOCIOUS_REQUEST_TIMEOUT_MS)
903
+ const value = typeof this._requestTimeoutMs === "function"
904
+ ? this._requestTimeoutMs()
905
+ : this._requestTimeoutMs
906
+
907
+ if (typeof value === "number") return value
908
+ if (typeof envTimeout === "number" && Number.isFinite(envTimeout)) return envTimeout
909
+
910
+ return 60
911
+ }
912
+
913
+ /**
914
+ * Runs parse request timeout seconds.
915
+ * @param {string | undefined} rawValue - Env value.
916
+ * @returns {number | undefined} - Timeout in seconds.
917
+ */
918
+ _parseRequestTimeoutSeconds(rawValue) {
919
+ if (rawValue === undefined) return undefined
920
+
921
+ const trimmed = rawValue.trim().toLowerCase()
922
+
923
+ if (!trimmed) return undefined
924
+
925
+ const match = trimmed.match(/^(\d+(?:\.\d+)?)(ms|s)?$/)
926
+
927
+ if (!match) return undefined
928
+
929
+ const numeric = Number(match[1])
930
+
931
+ if (!Number.isFinite(numeric)) return undefined
932
+
933
+ const unit = match[2]
934
+
935
+ if (unit === "ms") return numeric / 1000
936
+ if (unit === "s") return numeric
937
+
938
+ if (trimmed.includes(".")) return numeric
939
+ if (numeric >= 1000) return numeric / 1000
940
+
941
+ return numeric
942
+ }
943
+
944
+ /**
945
+ * Runs set environment.
946
+ * @param {string} newEnvironment - New environment.
947
+ * @returns {void} - No return value.
948
+ */
949
+ setEnvironment(newEnvironment) { this._environment = newEnvironment }
950
+
951
+ /**
952
+ * Runs get logging configuration.
953
+ * @param {object} [args] - Options object.
954
+ * @param {boolean} [args.defaultConsole] - Whether default console.
955
+ * @returns {Required<Pick<import("./configuration-types.js").LoggingConfiguration, "console" | "file" | "levels">> & Pick<import("./configuration-types.js").LoggingConfiguration, "directory" | "filePath"> & Partial<Pick<import("./configuration-types.js").LoggingConfiguration, "outputs" | "loggers">>} - The logging configuration.
956
+ */
957
+ getLoggingConfiguration({defaultConsole} = {}) {
958
+ const environment = this.getEnvironment()
959
+ const environmentHandler = this.getEnvironmentHandler()
960
+ const directory = this._logging?.directory || environmentHandler.getDefaultLogDirectory({configuration: this})
961
+ const filePath = this._logging?.filePath || environmentHandler.getLogFilePath({configuration: this, directory, environment})
962
+ const consoleOverride = this._logging?.console
963
+ const hasLoggingConfig = Boolean(this._logging)
964
+ const fileLogging = hasLoggingConfig ? (this._logging?.file ?? Boolean(filePath)) : false
965
+ const configuredLevels = this._logging?.levels
966
+ const includeLowLevelDebug = this._logging?.debugLowLevel === true
967
+ const loggers = this._logging?.loggers
968
+
969
+ const consoleDefault = defaultConsole !== undefined ? defaultConsole : true
970
+ const consoleLogging = consoleOverride !== undefined ? consoleOverride : consoleDefault
971
+
972
+ /**
973
+ * Default levels.
974
+ @type {Array<"debug-low-level" | "debug" | "info" | "warn" | "error">} */
975
+ const defaultLevels = ["info", "warn", "error"]
976
+
977
+ if (includeLowLevelDebug) defaultLevels.unshift("debug-low-level")
978
+
979
+ const levels = configuredLevels || defaultLevels
980
+
981
+ return {
982
+ console: consoleLogging,
983
+ directory,
984
+ file: fileLogging ?? false,
985
+ filePath,
986
+ loggers,
987
+ levels,
988
+ outputs: this._logging?.outputs
989
+ }
990
+ }
991
+
992
+ /**
993
+ * Runs get query logging enabled.
994
+ * @returns {boolean} - Whether database query logging is enabled.
995
+ */
996
+ getQueryLoggingEnabled() {
997
+ if (this._logging?.queryLogging !== undefined) return this._logging.queryLogging
998
+
999
+ return this.getEnvironment() !== "test"
1000
+ }
1001
+
1002
+ /**
1003
+ * Runs get background jobs config.
1004
+ * @returns {Required<import("./configuration-types.js").BackgroundJobsConfiguration>} - Background jobs configuration.
1005
+ */
1006
+ getBackgroundJobsConfig() {
1007
+ const envHost = process.env.VELOCIOUS_BACKGROUND_JOBS_HOST
1008
+ const envPortRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_PORT
1009
+ const envDatabaseIdentifier = process.env.VELOCIOUS_BACKGROUND_JOBS_DATABASE_IDENTIFIER
1010
+ const envMaxConcurrentForkedRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_MAX_CONCURRENT_FORKED_JOBS
1011
+ const envMaxConcurrentRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_MAX_CONCURRENT_INLINE_JOBS
1012
+ const envDispatchStrategy = process.env.VELOCIOUS_BACKGROUND_JOBS_DISPATCH_STRATEGY
1013
+ const envPollIntervalRaw = process.env.VELOCIOUS_BACKGROUND_JOBS_POLL_INTERVAL_MS
1014
+ const envPort = envPortRaw ? Number(envPortRaw) : undefined
1015
+ const envMaxConcurrentForked = envMaxConcurrentForkedRaw ? Number(envMaxConcurrentForkedRaw) : undefined
1016
+ const envMaxConcurrent = envMaxConcurrentRaw ? Number(envMaxConcurrentRaw) : undefined
1017
+ const envPollInterval = envPollIntervalRaw ? Number(envPollIntervalRaw) : undefined
1018
+ const configured = this._backgroundJobs || {}
1019
+ const host = configured.host || envHost || "127.0.0.1"
1020
+ const port = typeof configured.port === "number"
1021
+ ? configured.port
1022
+ : (typeof envPort === "number" && Number.isFinite(envPort) ? envPort : 7331)
1023
+ const databaseIdentifier = configured.databaseIdentifier || envDatabaseIdentifier || "default"
1024
+ const maxConcurrentInlineJobs = typeof configured.maxConcurrentInlineJobs === "number" && configured.maxConcurrentInlineJobs >= 1
1025
+ ? configured.maxConcurrentInlineJobs
1026
+ : (typeof envMaxConcurrent === "number" && Number.isFinite(envMaxConcurrent) && envMaxConcurrent >= 1 ? envMaxConcurrent : 4)
1027
+ const maxConcurrentForkedJobs = typeof configured.maxConcurrentForkedJobs === "number" && configured.maxConcurrentForkedJobs >= 1
1028
+ ? configured.maxConcurrentForkedJobs
1029
+ : (typeof envMaxConcurrentForked === "number" && Number.isFinite(envMaxConcurrentForked) && envMaxConcurrentForked >= 1 ? envMaxConcurrentForked : 4)
1030
+ const dispatchStrategyRaw = configured.dispatchStrategy || envDispatchStrategy
1031
+ const dispatchStrategy = dispatchStrategyRaw === "polling" ? "polling" : "beacon"
1032
+ const pollIntervalMs = typeof configured.pollIntervalMs === "number" && configured.pollIntervalMs >= 1
1033
+ ? configured.pollIntervalMs
1034
+ : (typeof envPollInterval === "number" && Number.isFinite(envPollInterval) && envPollInterval >= 1 ? envPollInterval : 1000)
1035
+
1036
+ return {host, port, databaseIdentifier, maxConcurrentForkedJobs, maxConcurrentInlineJobs, dispatchStrategy, pollIntervalMs}
1037
+ }
1038
+
1039
+ /**
1040
+ * Runs set background jobs config.
1041
+ * @param {import("./configuration-types.js").BackgroundJobsConfiguration} backgroundJobs - Background jobs config.
1042
+ * @returns {void}
1043
+ */
1044
+ setBackgroundJobsConfig(backgroundJobs) {
1045
+ this._backgroundJobs = Object.assign({}, this._backgroundJobs, backgroundJobs)
1046
+ }
1047
+
1048
+ /**
1049
+ * Resolves the active Beacon configuration. Beacon is opt-in: it
1050
+ * stays disabled unless the app passes `beacon: {host, port}` /
1051
+ * `beacon: {inProcess: true}`, calls `setBeaconConfig({...})`, or
1052
+ * sets the `VELOCIOUS_BEACON_HOST` / `VELOCIOUS_BEACON_PORT` env vars.
1053
+ * Setting `enabled: false` explicitly disables it even when env vars
1054
+ * are present (useful for tests). When `inProcess: true` is set,
1055
+ * env-var host/port are ignored — code-level config wins.
1056
+ * @returns {{enabled: boolean, host: string, port: number, peerType?: string, inProcess: boolean, unreachableReportMs: number}} - Beacon configuration with defaults applied.
1057
+ */
1058
+ getBeaconConfig() {
1059
+ const configured = this._beacon || {}
1060
+ const inProcess = configured.inProcess === true
1061
+
1062
+ if (inProcess && (configured.host || typeof configured.port === "number")) {
1063
+ throw new Error("Beacon configuration: `inProcess: true` is mutually exclusive with `host`/`port`. Use one or the other.")
1064
+ }
1065
+
1066
+ const envHost = inProcess ? undefined : process.env.VELOCIOUS_BEACON_HOST
1067
+ const envPortRaw = inProcess ? undefined : process.env.VELOCIOUS_BEACON_PORT
1068
+ const envPort = envPortRaw ? Number(envPortRaw) : undefined
1069
+ const host = configured.host || envHost || "127.0.0.1"
1070
+ const port = typeof configured.port === "number"
1071
+ ? configured.port
1072
+ : (typeof envPort === "number" && Number.isFinite(envPort) ? envPort : 7330)
1073
+
1074
+ let enabled
1075
+
1076
+ if (typeof configured.enabled === "boolean") {
1077
+ enabled = configured.enabled
1078
+ } else {
1079
+ enabled = Boolean(inProcess || configured.host || configured.port || envHost || envPort)
1080
+ }
1081
+
1082
+ const unreachableReportMs = resolveBeaconUnreachableReportMs(configured.unreachableReportMs)
1083
+
1084
+ return {enabled, host, port, peerType: configured.peerType, inProcess, unreachableReportMs}
1085
+ }
1086
+
1087
+ /**
1088
+ * Runs set beacon config.
1089
+ * @param {import("./configuration-types.js").BeaconConfiguration} beacon - Beacon config.
1090
+ * @returns {void}
1091
+ */
1092
+ setBeaconConfig(beacon) {
1093
+ this._beacon = Object.assign({}, this._beacon, beacon)
1094
+ }
1095
+
1096
+ /**
1097
+ * Runs get beacon client.
1098
+ * @returns {import("./beacon/client.js").default | import("./beacon/in-process-client.js").default | undefined} - The active Beacon client, if connected.
1099
+ */
1100
+ getBeaconClient() {
1101
+ return this._beaconClient
1102
+ }
1103
+
1104
+ /**
1105
+ * Connects this configuration's Beacon client to the configured
1106
+ * broker, wiring incoming broadcasts to the local delivery path so
1107
+ * any websocket subscribers in this process receive them. Idempotent
1108
+ * — repeat calls return the same in-flight or resolved promise.
1109
+ *
1110
+ * Returns immediately with `undefined` if Beacon is not enabled.
1111
+ *
1112
+ * **Non-blocking by design (TCP mode).** For broker-backed Beacon, the
1113
+ * returned promise resolves as soon as the client is constructed and
1114
+ * the TCP connect is launched — it does **not** wait for the connect
1115
+ * handshake to complete. A broker that silently drops SYNs
1116
+ * (firewall/NACL DROP rules) would otherwise block startup on the OS
1117
+ * TCP connect timeout (tens of seconds), which contradicts the
1118
+ * documented "fall back to local-only and reconnect in the
1119
+ * background" contract. Initial-connect failures surface
1120
+ * asynchronously on the framework-error channel via the
1121
+ * `connect-error` listener registered here. Callers that need a
1122
+ * deterministic publish-readiness boundary should call
1123
+ * `getBeaconClient()?.waitForReady({timeoutMs})`.
1124
+ *
1125
+ * **In-process mode** awaits `connect()` — that path is synchronous,
1126
+ * cannot fail, and gives callers predictable readiness.
1127
+ * @param {object} [args] - Options.
1128
+ * @param {string} [args.peerType] - Override peerType for this connect call (e.g. `"server"`, `"background-jobs-worker"`).
1129
+ * @returns {Promise<import("./beacon/client.js").default | import("./beacon/in-process-client.js").default | undefined>} - Resolves with the registered client (TCP mode: connect may still be in flight), or undefined when Beacon is disabled.
1130
+ */
1131
+ async connectBeacon({peerType} = {}) {
1132
+ if (this._beaconClient) return this._beaconClient
1133
+ if (this._beaconConnectPromise) return await this._beaconConnectPromise
1134
+
1135
+ const config = this.getBeaconConfig()
1136
+
1137
+ if (!config.enabled) return undefined
1138
+
1139
+ this._beaconConnectPromise = (async () => {
1140
+ const client = await this._createBeaconClient({
1141
+ config,
1142
+ peerType: peerType || config.peerType
1143
+ })
1144
+
1145
+ client.onBroadcast((message) => {
1146
+ // Synapse-style fan-out: deliver every broadcast we receive
1147
+ // from the bus through the local delivery path. Echoes of our
1148
+ // own publishes follow the same path so every peer sees the
1149
+ // same delivery semantics.
1150
+ this._deliverBroadcastFromBeacon(message)
1151
+ })
1152
+
1153
+ // Beacon connect/disconnect blips are expected during deploys (the broker
1154
+ // restarts) and the BeaconClient auto-reconnects in the background, so a
1155
+ // single transient failure is NOT reported. Only a sustained outage (still
1156
+ // down after `unreachableReportMs`) is surfaced on the framework-error
1157
+ // channel; a (re)connect within the grace window clears it silently.
1158
+
1159
+ // `connect-error` fires when the *initial* TCP/handshake fails.
1160
+ client.on("connect-error", (error) => {
1161
+ this._handleBeaconDown({stage: "beacon-connect", error, reportAfterMs: config.unreachableReportMs})
1162
+ })
1163
+
1164
+ // `disconnect` fires when an established connection drops. The payload is
1165
+ // the underlying socket error if there was one, or a synthetic
1166
+ // Error("Beacon broker disconnected") otherwise.
1167
+ client.on("disconnect", (reason) => {
1168
+ this._handleBeaconDown({stage: "beacon-disconnect", error: reason, reportAfterMs: config.unreachableReportMs})
1169
+ })
1170
+
1171
+ // `connect` fires on every (re)connect; clear any pending outage state so
1172
+ // a transient blip that recovers within the grace window stays silent.
1173
+ client.on("connect", () => {
1174
+ this._handleBeaconUp()
1175
+ })
1176
+
1177
+ // Register the client *before* kicking off connect so subsequent
1178
+ // `connectBeacon()` calls return this same instance instead of
1179
+ // racing to construct a second one.
1180
+ this._beaconClient = client
1181
+
1182
+ if (config.inProcess) {
1183
+ // In-process connect is synchronous, cannot fail, and resolves
1184
+ // before this await yields — callers can rely on
1185
+ // `isConnected() === true` immediately after `connectBeacon()`.
1186
+ await client.connect()
1187
+ } else {
1188
+ // Fire-and-forget the TCP connect. Awaiting here would block
1189
+ // startup on the OS TCP connect timeout (75s default on Linux)
1190
+ // when the broker silently drops SYNs. Failures surface
1191
+ // asynchronously via the `connect-error` listener registered
1192
+ // above; the BeaconClient's reconnect loop keeps trying.
1193
+ void client.connect().catch(() => {
1194
+ // Already reported via connect-error above.
1195
+ })
1196
+ }
1197
+
1198
+ return client
1199
+ })()
1200
+
1201
+ return await this._beaconConnectPromise
1202
+ }
1203
+
1204
+ /**
1205
+ * Builds a Beacon client matching the configured mode. Split out so
1206
+ * `connectBeacon` stays focused on lifecycle and error wiring.
1207
+ * @param {object} args - Options.
1208
+ * @param {ReturnType<VelociousConfiguration["getBeaconConfig"]>} args.config - Resolved Beacon config.
1209
+ * @param {string} [args.peerType] - Resolved peer type.
1210
+ * @returns {Promise<import("./beacon/client.js").default | import("./beacon/in-process-client.js").default>} - Beacon client.
1211
+ */
1212
+ async _createBeaconClient({config, peerType}) {
1213
+ // Route through the environment handler so the Node-only `node:net`
1214
+ // / `node:crypto` deps in the Beacon client modules don't get pulled
1215
+ // into browser bundles. Browser bundles statically reach
1216
+ // `Configuration` (via `Logger`); putting the dynamic
1217
+ // `import("./beacon/...")` calls here would still drag those modules
1218
+ // through esbuild's static analysis. Hiding the imports inside the
1219
+ // Node environment handler keeps them off the browser path —
1220
+ // browser-bundled apps never reach `environment-handlers/node.js`.
1221
+ const handler = this.getEnvironmentHandler()
1222
+
1223
+ if (config.inProcess) {
1224
+ const InProcessBeaconClient = await handler.loadInProcessBeaconClient()
1225
+
1226
+ return new InProcessBeaconClient({peerType})
1227
+ }
1228
+
1229
+ const BeaconClient = await handler.loadBeaconClient()
1230
+
1231
+ return new BeaconClient({
1232
+ host: config.host,
1233
+ port: config.port,
1234
+ peerType
1235
+ })
1236
+ }
1237
+
1238
+ /**
1239
+ * Records a Beacon connect/disconnect failure without reporting it immediately.
1240
+ * The BeaconClient auto-reconnects, so brief outages (e.g. a deploy restarting
1241
+ * the broker) are expected; only if the beacon is still unreachable after
1242
+ * `reportAfterMs` is a single framework-error surfaced via `_reportBeaconError`.
1243
+ * A subsequent `connect` (see `_handleBeaconUp`) cancels the pending report.
1244
+ * @param {object} args - Options.
1245
+ * @param {"beacon-connect" | "beacon-disconnect"} args.stage - Failure stage.
1246
+ * @param {Error} args.error - Error instance.
1247
+ * @param {number} args.reportAfterMs - Grace window before a sustained outage is reported.
1248
+ * @returns {void}
1249
+ */
1250
+ _handleBeaconDown({stage, error, reportAfterMs}) {
1251
+ this._beaconLastDownError = {stage, error}
1252
+
1253
+ // A report is already pending or already sent for this outage — keep the
1254
+ // latest error but don't stack timers or re-report.
1255
+ if (this._beaconReportTimer || this._beaconOutageReported) return
1256
+
1257
+ const timer = setTimeout(() => {
1258
+ this._beaconReportTimer = undefined
1259
+
1260
+ if (this._beaconClient?.isConnected()) {
1261
+ this._handleBeaconUp()
1262
+ return
1263
+ }
1264
+
1265
+ this._beaconOutageReported = true
1266
+
1267
+ if (this._beaconLastDownError) this._reportBeaconError(this._beaconLastDownError)
1268
+ }, reportAfterMs)
1269
+
1270
+ // Don't let the grace timer keep the process alive.
1271
+ if (typeof timer.unref === "function") timer.unref()
1272
+
1273
+ this._beaconReportTimer = timer
1274
+ }
1275
+
1276
+ /**
1277
+ * Clears beacon-down state on a (re)connect. A blip that recovers within the
1278
+ * grace window is never reported; if a sustained outage had already been
1279
+ * reported, the state resets so a future outage can report again.
1280
+ * @returns {void}
1281
+ */
1282
+ _handleBeaconUp() {
1283
+ if (this._beaconReportTimer) {
1284
+ clearTimeout(this._beaconReportTimer)
1285
+ this._beaconReportTimer = undefined
1286
+ }
1287
+
1288
+ this._beaconOutageReported = false
1289
+ this._beaconLastDownError = undefined
1290
+ }
1291
+
1292
+ /**
1293
+ * Surfaces a Beacon failure on the framework error channel. Mirrors
1294
+ * the pattern used by `request-runner.js` for HTTP errors. When no
1295
+ * listener is attached to either `framework-error` or `all-error`,
1296
+ * also schedules an unhandled promise rejection so process-level bug
1297
+ * reporters (which subscribe to `unhandledRejection` by default) pick
1298
+ * the failure up — and ALSO writes a one-line summary to `stderr` so
1299
+ * the failure isn't completely silent on Node 24+ where the default
1300
+ * behavior of `unhandledRejection` is to terminate the process. An
1301
+ * app that sees its server suddenly exit needs at least one
1302
+ * breadcrumb in the logs to know Beacon was the cause; the previous
1303
+ * behavior left a stack-only crash with no context tying it back to
1304
+ * the broker.
1305
+ * @param {object} args - Options.
1306
+ * @param {"beacon-connect" | "beacon-disconnect"} args.stage - Failure stage.
1307
+ * @param {Error} args.error - Error instance.
1308
+ * @returns {void}
1309
+ */
1310
+ _reportBeaconError({stage, error}) {
1311
+ const errorEvents = this._errorEvents
1312
+ const hasListener = errorEvents.listenerCount("framework-error") > 0
1313
+ || errorEvents.listenerCount("all-error") > 0
1314
+ const payload = {
1315
+ context: {stage},
1316
+ error
1317
+ }
1318
+
1319
+ errorEvents.emit("framework-error", payload)
1320
+ errorEvents.emit("all-error", {...payload, errorType: "framework-error"})
1321
+
1322
+ if (!hasListener) {
1323
+ const message = error instanceof Error ? error.message : String(error)
1324
+
1325
+
1326
+ console.error(`[velocious framework-error stage=${stage}] ${message} — register a listener via configuration.getErrorEvents().on("framework-error", …) to suppress this stderr fallback`)
1327
+ void Promise.reject(error)
1328
+ }
1329
+ }
1330
+
1331
+ /**
1332
+ * Closes the active Beacon client (if any). Safe to call multiple
1333
+ * times.
1334
+ * @returns {Promise<void>}
1335
+ */
1336
+ async disconnectBeacon() {
1337
+ const client = this._beaconClient
1338
+
1339
+ this._beaconClient = undefined
1340
+ this._beaconConnectPromise = undefined
1341
+
1342
+ if (this._beaconReportTimer) {
1343
+ clearTimeout(this._beaconReportTimer)
1344
+ this._beaconReportTimer = undefined
1345
+ }
1346
+
1347
+ this._beaconOutageReported = false
1348
+ this._beaconLastDownError = undefined
1349
+
1350
+ if (client) await client.close()
1351
+ }
1352
+
1353
+ /**
1354
+ * Routes a Beacon-sourced broadcast through the same delivery code
1355
+ * path as a locally-originated one. Prefers the workerthread-aware
1356
+ * `broadcastV2` when an HTTP server is hosting workers, and falls
1357
+ * back to the per-process subscription dispatch otherwise.
1358
+ * @param {import("./beacon/types.js").BeaconBroadcastMessage} message - Broadcast message.
1359
+ * @returns {void}
1360
+ */
1361
+ _deliverBroadcastFromBeacon(message) {
1362
+ /**
1363
+ * Websocket events.
1364
+ @type {?} */
1365
+ const websocketEvents = this._websocketEvents
1366
+
1367
+ if (websocketEvents && typeof websocketEvents.broadcastV2 === "function") {
1368
+ websocketEvents.broadcastV2({
1369
+ channel: message.channel,
1370
+ broadcastParams: message.broadcastParams,
1371
+ body: message.body
1372
+ })
1373
+ return
1374
+ }
1375
+
1376
+ this._broadcastToChannelLocal(message.channel, message.broadcastParams, message.body)
1377
+ }
1378
+
1379
+ /**
1380
+ * Runs get scheduled background jobs config.
1381
+ * @returns {Promise<import("./configuration-types.js").ScheduledBackgroundJobsConfiguration | undefined>} - Scheduled background jobs configuration.
1382
+ */
1383
+ async getScheduledBackgroundJobsConfig() {
1384
+ if (!this._scheduledBackgroundJobs) {
1385
+ return undefined
1386
+ }
1387
+
1388
+ if (typeof this._scheduledBackgroundJobs === "function") {
1389
+ return await this._scheduledBackgroundJobs({configuration: this})
1390
+ }
1391
+
1392
+ return this._scheduledBackgroundJobs
1393
+ }
1394
+
1395
+ /**
1396
+ * Runs set scheduled background jobs config.
1397
+ * @param {import("./configuration-types.js").ScheduledBackgroundJobsConfiguration | import("./configuration-types.js").ScheduledBackgroundJobsLoaderType | undefined} scheduledBackgroundJobs - Scheduled background jobs configuration.
1398
+ * @returns {void}
1399
+ */
1400
+ setScheduledBackgroundJobsConfig(scheduledBackgroundJobs) {
1401
+ this._scheduledBackgroundJobs = scheduledBackgroundJobs
1402
+ }
1403
+
1404
+ /**
1405
+ * Runs get mailer backend.
1406
+ * @returns {import("./configuration-types.js").MailerBackend | undefined} - Mailer backend.
1407
+ */
1408
+ getMailerBackend() {
1409
+ return this._mailerBackend
1410
+ }
1411
+
1412
+ /**
1413
+ * Runs set mailer backend.
1414
+ * @param {import("./configuration-types.js").MailerBackend} mailerBackend - Mailer backend.
1415
+ * @returns {void} - No return value.
1416
+ */
1417
+ setMailerBackend(mailerBackend) {
1418
+ this._mailerBackend = mailerBackend
1419
+ }
1420
+
1421
+ /**
1422
+ * Logging configuration tailored for HTTP request logging. Defaults console logging to true and applies the user `logging.console` flag only for request logging.
1423
+ * @returns {Required<Pick<import("./configuration-types.js").LoggingConfiguration, "console" | "file" | "levels">> & Pick<import("./configuration-types.js").LoggingConfiguration, "directory" | "filePath"> & Partial<Pick<import("./configuration-types.js").LoggingConfiguration, "outputs" | "loggers">>} - The http logging configuration.
1424
+ */
1425
+ getHttpLoggingConfiguration() {
1426
+ return this.getLoggingConfiguration({defaultConsole: true})
1427
+ }
1428
+
1429
+ /**
1430
+ * Runs get environment handler.
1431
+ * @returns {import("./environment-handlers/base.js").default} - The environment handler.
1432
+ */
1433
+ getEnvironmentHandler() {
1434
+ if (!this._environmentHandler) throw new Error("No environment handler set")
1435
+
1436
+ return this._environmentHandler
1437
+ }
1438
+
1439
+ /**
1440
+ * Runs get locale fallbacks.
1441
+ * @returns {import("./configuration-types.js").LocaleFallbacksType | undefined} - The locale fallbacks.
1442
+ */
1443
+ getLocaleFallbacks() { return this.localeFallbacks }
1444
+
1445
+ /**
1446
+ * Runs set locale fallbacks.
1447
+ * @param {import("./configuration-types.js").LocaleFallbacksType} newLocaleFallbacks - New locale fallbacks.
1448
+ * @returns {void} - No return value.
1449
+ */
1450
+ setLocaleFallbacks(newLocaleFallbacks) { this.localeFallbacks = newLocaleFallbacks }
1451
+
1452
+ /**
1453
+ * Runs get structure sql config.
1454
+ * @returns {import("./configuration-types.js").StructureSqlConfiguration | undefined} - Structure SQL config.
1455
+ */
1456
+ getStructureSqlConfig() { return this._structureSql }
1457
+
1458
+ /**
1459
+ * Runs should write structure sql.
1460
+ * @param {{reason?: "migration" | "schemaDump"}} [args] - Call context for the structure sql write decision.
1461
+ * @returns {boolean} - Whether structure SQL files should be generated for the current environment.
1462
+ */
1463
+ shouldWriteStructureSql(args = {}) {
1464
+ const {reason = "migration"} = args
1465
+ const config = this.getStructureSqlConfig()
1466
+ const enabledEnvironments = config?.enabledEnvironments
1467
+ const disabledEnvironments = config?.disabledEnvironments
1468
+
1469
+ if (reason === "schemaDump") {
1470
+ return true
1471
+ }
1472
+
1473
+ if (Array.isArray(enabledEnvironments)) {
1474
+ return enabledEnvironments.includes(this.getEnvironment())
1475
+ }
1476
+
1477
+ if (Array.isArray(disabledEnvironments) && disabledEnvironments.includes(this.getEnvironment())) {
1478
+ return false
1479
+ }
1480
+
1481
+ if (this.getEnvironment() === "test") {
1482
+ return false
1483
+ }
1484
+
1485
+ return true
1486
+ }
1487
+
1488
+ /**
1489
+ * Runs set structure sql config.
1490
+ * @param {import("./configuration-types.js").StructureSqlConfiguration} structureSql - Structure SQL config.
1491
+ * @returns {void} - No return value.
1492
+ */
1493
+ setStructureSqlConfig(structureSql) {
1494
+ this._structureSql = structureSql
1495
+ }
1496
+
1497
+ /**
1498
+ * Runs get locale.
1499
+ * @returns {string} - The locale.
1500
+ */
1501
+ getLocale() {
1502
+ if (typeof this.locale == "function") {
1503
+ return this.locale()
1504
+ } else if (this.locale) {
1505
+ return this.locale
1506
+ } else {
1507
+ return this.getLocales()[0]
1508
+ }
1509
+ }
1510
+
1511
+ /**
1512
+ * Runs get locales.
1513
+ * @returns {Array<string>} - The locales.
1514
+ */
1515
+ getLocales() { return digg(this, "locales") }
1516
+
1517
+ /**
1518
+ * Runs get model class.
1519
+ * @param {string} name - Name.
1520
+ * @returns {typeof import("./database/record/index.js").default} - The model class.
1521
+ */
1522
+ getModelClass(name) {
1523
+ const modelClass = this.modelClasses[name]
1524
+
1525
+ if (!modelClass) throw new Error(`No such model class ${name} in ${Object.keys(this.modelClasses).join(", ")}}`)
1526
+
1527
+ return modelClass
1528
+ }
1529
+
1530
+ /**
1531
+ * Runs get model classes.
1532
+ * @returns {Record<string, typeof import("./database/record/index.js").default>} A hash of all model classes, keyed by model name, as they were defined in the configuration. This is a direct reference to the model classes, not a copy.
1533
+ */
1534
+ getModelClasses() {
1535
+ return this.modelClasses
1536
+ }
1537
+
1538
+ /**
1539
+ * Runs get testing.
1540
+ * @returns {string | undefined} The path to a config file that should be used for testing.
1541
+ */
1542
+ getTesting() { return this._testing }
1543
+
1544
+ /**
1545
+ * Runs get trusted proxies.
1546
+ * @returns {string | string[] | undefined} Trusted reverse proxy address ranges.
1547
+ */
1548
+ getTrustedProxies() { return this._trustedProxies }
1549
+
1550
+ /**
1551
+ * Runs set trusted proxies.
1552
+ * @param {string | string[] | undefined} trustedProxies - Trusted reverse proxy address ranges.
1553
+ * @returns {void}
1554
+ */
1555
+ setTrustedProxies(trustedProxies) { this._trustedProxies = trustedProxies }
1556
+
1557
+ /**
1558
+ * Runs initialize database pool.
1559
+ * @param {string} [identifier] - Database identifier to initialize.
1560
+ * @returns {void} - No return value.
1561
+ */
1562
+ initializeDatabasePool(identifier = "default") {
1563
+ if (!this.database) throw new Error("No 'database' was given")
1564
+ if (this.databasePools[identifier]) throw new Error("DatabasePool has already been initialized")
1565
+
1566
+ const PoolType = this.getDatabasePoolType(identifier)
1567
+
1568
+ this.databasePools[identifier] = new PoolType({configuration: this, identifier})
1569
+ this.databasePools[identifier].setCurrent()
1570
+ }
1571
+
1572
+ /**
1573
+ * Runs is database pool initialized.
1574
+ * @param {string} [identifier] - Database identifier to check.
1575
+ * @returns {boolean} - Whether database pool initialized.
1576
+ */
1577
+ isDatabasePoolInitialized(identifier = "default") { return Boolean(this.databasePools[identifier]) }
1578
+
1579
+ /**
1580
+ * Runs is initialized.
1581
+ * @returns {boolean} - Whether initialized.
1582
+ */
1583
+ isInitialized() { return this._isInitialized }
1584
+
1585
+ /**
1586
+ * Runs initialize models.
1587
+ * @param {object} args - Options object.
1588
+ * @param {string} args.type - Type identifier.
1589
+ * @returns {Promise<void>} - Resolves when complete.
1590
+ */
1591
+ async initializeModels(args = {type: "server"}) {
1592
+ if (!this._modelsInitialized) {
1593
+ this._modelsInitialized = true
1594
+
1595
+ const shouldSkipDummyModelInitialization = process.env.VELOCIOUS_SKIP_DUMMY_MODEL_INITIALIZATION === "1"
1596
+ && process.env.VELOCIOUS_BROWSER_TESTS === "true"
1597
+ && this.getEnvironment() === "test"
1598
+
1599
+ if (shouldSkipDummyModelInitialization) {
1600
+ return
1601
+ }
1602
+
1603
+ if (this._initializeModels) {
1604
+ await this._initializeModels({configuration: this, type: args.type})
1605
+ }
1606
+
1607
+ await this.getEnvironmentHandler().initializeFrontendModelWebsocketPublishers(this)
1608
+ }
1609
+ }
1610
+
1611
+ /**
1612
+ * Ensures each configured database pool has a global connection available.
1613
+ * Useful when `getCurrentConnection` might be called without an async context.
1614
+ * @returns {Promise<void>} - Resolves when complete.
1615
+ */
1616
+ async ensureGlobalConnections() {
1617
+ for (const identifier of this.getDatabaseIdentifiers()) {
1618
+ const pool = this.getDatabasePool(identifier)
1619
+
1620
+ await pool.ensureGlobalConnection()
1621
+ }
1622
+ }
1623
+
1624
+ /**
1625
+ * Runs initialize.
1626
+ * @param {object} args - Options object.
1627
+ * @param {string} args.type - Type identifier.
1628
+ * @returns {Promise<void>} - Resolves when complete.
1629
+ */
1630
+ async initialize({type} = {type: "undefined"}) {
1631
+ if (!this.isInitialized()) {
1632
+ this._isInitialized = true
1633
+
1634
+ await this.initializeModels({type})
1635
+ await this.getEnvironmentHandler().autoDiscoverResources(this)
1636
+ this._validateResourceRelationshipsOnModels()
1637
+
1638
+ if (this._initializers) {
1639
+ const initializers = await this._initializers({configuration: this})
1640
+ const {requireContext, ...restArgs} = initializers
1641
+
1642
+ restArgsError(restArgs)
1643
+
1644
+ if (requireContext) {
1645
+ for (const initializerKey of requireContext.keys()) {
1646
+ const InitializerClass = requireContext(initializerKey).default
1647
+ const initializerInstance = new InitializerClass({configuration: this, type})
1648
+
1649
+ await initializerInstance.run()
1650
+ }
1651
+ }
1652
+ }
1653
+ }
1654
+ }
1655
+
1656
+ /**
1657
+ * Validates that resource-defined relationships are also defined on the corresponding model classes.
1658
+ * Throws an error if a relationship is defined on a resource but missing from the model.
1659
+ * @returns {void}
1660
+ */
1661
+ _validateResourceRelationshipsOnModels() {
1662
+ for (const backendProject of this._backendProjects) {
1663
+ const resources = frontendModelResourcesForBackendProject(backendProject)
1664
+
1665
+ for (const [modelName, resourceDefinition] of Object.entries(resources)) {
1666
+ const resourceConfig = frontendModelResourceConfigurationFromDefinition(resourceDefinition)
1667
+
1668
+ if (!resourceConfig?.relationships) continue
1669
+
1670
+ if (!Array.isArray(resourceConfig.relationships)) {
1671
+ throw new Error(`Resource for ${modelName} defines relationships as an object. Use an array instead: static relationships = ${JSON.stringify(Object.keys(resourceConfig.relationships))}`)
1672
+ }
1673
+
1674
+ const modelClass = /**
1675
+ * Types the following value.
1676
+ @type {typeof import("./database/record/index.js").default | undefined} */ (this.modelClasses[modelName])
1677
+
1678
+ if (!modelClass) continue
1679
+
1680
+ const existingRelationships = modelClass.getRelationshipsMap()
1681
+
1682
+ for (const relationshipName of resourceConfig.relationships) {
1683
+ if (!(relationshipName in existingRelationships)) {
1684
+ throw new Error(
1685
+ `Resource for ${modelName} defines relationship "${relationshipName}" but ${modelName} model does not. ` +
1686
+ `Add ${modelName}.belongsTo("${relationshipName}", ...) or the appropriate relationship call on the model class.`
1687
+ )
1688
+ }
1689
+ }
1690
+ }
1691
+ }
1692
+ }
1693
+
1694
+ /**
1695
+ * Runs register model class.
1696
+ * @param {typeof import("./database/record/index.js").default} modelClass - Model class.
1697
+ * @returns {void} - No return value.
1698
+ */
1699
+ registerModelClass(modelClass) {
1700
+ this.modelClasses[modelClass.getModelName()] = modelClass
1701
+ }
1702
+
1703
+ /**
1704
+ * Runs set current.
1705
+ * @returns {void} - No return value.
1706
+ */
1707
+ setCurrent() {
1708
+ setCurrentConfiguration(this)
1709
+ }
1710
+
1711
+ /**
1712
+ * Runs get routes.
1713
+ * @returns {import("./routes/index.js").default | undefined} - The routes.
1714
+ */
1715
+ getRoutes() { return this._routes }
1716
+
1717
+ /**
1718
+ * Runs set routes.
1719
+ * @param {import("./routes/index.js").default} newRoutes - New routes.
1720
+ * @returns {void} - No return value.
1721
+ */
1722
+ setRoutes(newRoutes) {
1723
+ this._routes = newRoutes
1724
+ this._applyRouteMounts(newRoutes)
1725
+ }
1726
+
1727
+ /**
1728
+ * Applies any `route.mount(...)` registrations from the routes file by letting
1729
+ * each mountable register its routes (typically route-resolver hooks) against
1730
+ * this configuration. Guarded so repeated setRoutes calls with the same routes
1731
+ * don't register a mount more than once.
1732
+ * @param {import("./routes/index.js").default} newRoutes - Routes instance.
1733
+ * @returns {void} - No return value.
1734
+ */
1735
+ _applyRouteMounts(newRoutes) {
1736
+ if (!newRoutes || typeof newRoutes.getMounts !== "function") return
1737
+
1738
+ for (const mount of newRoutes.getMounts()) {
1739
+ if (this._appliedRouteMounts.has(mount)) continue
1740
+
1741
+ this._appliedRouteMounts.add(mount)
1742
+ mount.mountable.mountInto({configuration: this, ...mount.options})
1743
+ }
1744
+ }
1745
+
1746
+ /**
1747
+ * Adds plugin/library routes using a lightweight route DSL backed by route resolver hooks.
1748
+ * @param {(routes: import("./routes/plugin-routes.js").default) => void} callback - Routes callback.
1749
+ * @returns {void} - No return value.
1750
+ */
1751
+ routes(callback) {
1752
+ const pluginRoutes = new PluginRoutes({configuration: this})
1753
+
1754
+ callback(pluginRoutes)
1755
+ }
1756
+
1757
+ /**
1758
+ * Runs set translator.
1759
+ * @param {function(string, Record<string, ?> | undefined) : string} callback - Translator callback.
1760
+ * @returns {void} - No return value.
1761
+ */
1762
+ setTranslator(callback) { this._translator = callback }
1763
+
1764
+ /**
1765
+ * Runs default translator.
1766
+ * @param {string} msgID - Msg id.
1767
+ * @param {Record<string, ?>} [args] - Translator options and variables.
1768
+ * @returns {string} - The default translator.
1769
+ */
1770
+ _defaultTranslator(msgID, args) {
1771
+ this._configureDefaultTranslator()
1772
+
1773
+ const translateArgs = args ? {...args} : undefined
1774
+ const defaultValue = translateArgs?.defaultValue
1775
+ const locales = translateArgs?.locales
1776
+
1777
+ if (translateArgs) {
1778
+ delete translateArgs.defaultValue
1779
+ delete translateArgs.locales
1780
+ }
1781
+
1782
+ const variables = translateArgs && Object.keys(translateArgs).length > 0 ? translateArgs : undefined
1783
+
1784
+ const locale = this.getLocale()
1785
+ const preferredLocales = locales || (locale ? undefined : [])
1786
+ const message = translate(msgID, variables, preferredLocales)
1787
+
1788
+ if (message === msgID && defaultValue) return translate(defaultValue, variables, [])
1789
+
1790
+ return message
1791
+ }
1792
+
1793
+ /**
1794
+ * Runs get translator.
1795
+ @returns {(msgID: string, args?: Record<string, ?>) => string} */
1796
+ getTranslator() {
1797
+ if (this._translator) return this._translator
1798
+
1799
+ if (!this._defaultTranslatorBound) {
1800
+ this._defaultTranslatorBound = this._defaultTranslator.bind(this)
1801
+ }
1802
+
1803
+ return this._defaultTranslatorBound
1804
+ }
1805
+
1806
+ /**
1807
+ * Runs configure default translator.
1808
+ * @returns {void} - Configure gettext defaults for this configuration.
1809
+ */
1810
+ _configureDefaultTranslator() {
1811
+ const locale = this.getLocale()
1812
+
1813
+ gettextConfig.setLocale(locale || "")
1814
+
1815
+ const fallbacks = locale ? this.getLocaleFallbacks()?.[locale] : []
1816
+
1817
+ gettextConfig.setFallbacks(fallbacks || [])
1818
+ }
1819
+
1820
+ /**
1821
+ * Runs get timezone offset minutes.
1822
+ * @returns {number | undefined} - The timezone offset in minutes.
1823
+ */
1824
+ getTimezoneOffsetMinutes() {
1825
+ if (typeof this._timezoneOffsetMinutes === "function") {
1826
+ const configuredOffset = this._timezoneOffsetMinutes()
1827
+
1828
+ if (typeof configuredOffset === "number") return configuredOffset
1829
+ }
1830
+
1831
+ if (typeof this._timezoneOffsetMinutes === "number") {
1832
+ return this._timezoneOffsetMinutes
1833
+ }
1834
+
1835
+ return new Date().getTimezoneOffset()
1836
+ }
1837
+
1838
+ /**
1839
+ * Runs get websocket events.
1840
+ * @returns {import("./http-server/websocket-events.js").default | undefined} - The websocket events.
1841
+ */
1842
+ getWebsocketEvents() {
1843
+ return this._websocketEvents
1844
+ }
1845
+
1846
+ /**
1847
+ * Runs set websocket events.
1848
+ * @param {import("./http-server/websocket-events.js").default} websocketEvents - Websocket events.
1849
+ * @returns {void} - No return value.
1850
+ */
1851
+ setWebsocketEvents(websocketEvents) {
1852
+ this._websocketEvents = websocketEvents
1853
+ }
1854
+
1855
+ /**
1856
+ * Per-process registry of channel subscribers used by worker code that
1857
+ * needs to react to events broadcast via `websocketEventsHost.publish(...)`
1858
+ * without holding an actual websocket session.
1859
+ * @returns {import("./http-server/websocket-channel-subscribers.js").default} - The channel subscribers registry.
1860
+ */
1861
+ getWebsocketChannelSubscribers() {
1862
+ if (!this._websocketChannelSubscribers) {
1863
+ this._websocketChannelSubscribers = new VelociousWebsocketChannelSubscribers()
1864
+ }
1865
+
1866
+ return this._websocketChannelSubscribers
1867
+ }
1868
+
1869
+ /**
1870
+ * Runs get websocket channel resolver.
1871
+ * @returns {import("./configuration-types.js").WebsocketChannelResolverType | undefined} - The websocket channel resolver.
1872
+ */
1873
+ getWebsocketChannelResolver() {
1874
+ return this._websocketChannelResolver
1875
+ }
1876
+
1877
+ /**
1878
+ * Registers a `VelociousWebsocketConnection` subclass under a name.
1879
+ * Clients that send `{type: "connection-open", connectionType: name}`
1880
+ * will have this class instantiated for their connection.
1881
+ * @param {string} name
1882
+ * @param {typeof import("./http-server/websocket-connection.js").default} ConnectionClass
1883
+ * @returns {void}
1884
+ */
1885
+ registerWebsocketConnection(name, ConnectionClass) {
1886
+ if (!name) throw new Error("Connection name is required")
1887
+ if (!ConnectionClass) throw new Error("ConnectionClass is required")
1888
+ this._websocketConnectionClasses.set(name, ConnectionClass)
1889
+ }
1890
+
1891
+ /**
1892
+ * Runs get websocket connection class.
1893
+ * @param {string} name
1894
+ * @returns {typeof import("./http-server/websocket-connection.js").default | undefined}
1895
+ */
1896
+ getWebsocketConnectionClass(name) {
1897
+ return this._websocketConnectionClasses.get(name)
1898
+ }
1899
+
1900
+ /**
1901
+ * Registers a `VelociousWebsocketChannel` subclass under a name.
1902
+ * Clients subscribe via `{type: "channel-subscribe", channelType: name, ...}`.
1903
+ * @param {string} name
1904
+ * @param {typeof import("./http-server/websocket-channel.js").default} ChannelClass
1905
+ * @returns {void}
1906
+ */
1907
+ registerWebsocketChannel(name, ChannelClass) {
1908
+ if (!name) throw new Error("Channel name is required")
1909
+ if (!ChannelClass) throw new Error("ChannelClass is required")
1910
+ this._websocketChannelClasses.set(name, ChannelClass)
1911
+ }
1912
+
1913
+ /**
1914
+ * Runs get websocket channel class.
1915
+ * @param {string} name
1916
+ * @returns {typeof import("./http-server/websocket-channel.js").default | undefined}
1917
+ */
1918
+ getWebsocketChannelClass(name) {
1919
+ return this._websocketChannelClasses.get(name)
1920
+ }
1921
+
1922
+ /**
1923
+ * Tracks a live channel subscription in the global routing registry.
1924
+ * Called by the session when `canSubscribe()` resolves truthy; the
1925
+ * session calls `_unregisterWebsocketChannelSubscription` on unsubscribe.
1926
+ * @param {string} name
1927
+ * @param {import("./http-server/websocket-channel.js").default} subscription
1928
+ * @returns {void}
1929
+ */
1930
+ _registerWebsocketChannelSubscription(name, subscription) {
1931
+ let bucket = this._websocketChannelSubscriptions.get(name)
1932
+
1933
+ if (!bucket) {
1934
+ bucket = new Set()
1935
+ this._websocketChannelSubscriptions.set(name, bucket)
1936
+ }
1937
+
1938
+ bucket.add(subscription)
1939
+ }
1940
+
1941
+ /**
1942
+ * Runs unregister websocket channel subscription.
1943
+ * @param {string} name
1944
+ * @param {import("./http-server/websocket-channel.js").default} subscription
1945
+ * @returns {void}
1946
+ */
1947
+ _unregisterWebsocketChannelSubscription(name, subscription) {
1948
+ const bucket = this._websocketChannelSubscriptions.get(name)
1949
+
1950
+ if (!bucket) return
1951
+
1952
+ bucket.delete(subscription)
1953
+
1954
+ if (bucket.size === 0) {
1955
+ this._websocketChannelSubscriptions.delete(name)
1956
+ }
1957
+ }
1958
+
1959
+ /**
1960
+ * Delivers `body` to every live subscriber of `name` whose
1961
+ * `matches(broadcastParams)` returns true. Pure routing — no auth
1962
+ * re-check, no persistence. Subscribers who were admitted by
1963
+ * `canSubscribe()` continue to receive broadcasts until they
1964
+ * unsubscribe or the session ends.
1965
+ * @param {string} name
1966
+ * @param {Record<string, ?>} broadcastParams
1967
+ * @param {?} body
1968
+ * @returns {void}
1969
+ */
1970
+ /**
1971
+ * Runs get websocket session grace seconds.
1972
+ * @returns {number} - Grace period (seconds) before a paused WS session is torn down.
1973
+ */
1974
+ getWebsocketSessionGraceSeconds() { return this._websocketSessionGraceSeconds }
1975
+
1976
+ /**
1977
+ * Registers a wrapper invoked around every WS-borne request /
1978
+ * connection message / channel dispatch. The wrapper receives the
1979
+ * session and a `next` callback; it must call `next()` to run the
1980
+ * handler. Use it to set up AsyncLocalStorage per request.
1981
+ * @param {((session: import("./http-server/client/websocket-session.js").default, next: () => Promise<void>) => Promise<void>) | null} wrapper
1982
+ * @returns {void}
1983
+ */
1984
+ setWebsocketAroundRequest(wrapper) {
1985
+ this._websocketAroundRequest = wrapper
1986
+ }
1987
+
1988
+ /**
1989
+ * Runs get websocket around request.
1990
+ * @returns {((session: import("./http-server/client/websocket-session.js").default, next: () => Promise<void>) => Promise<void>) | null}
1991
+ */
1992
+ getWebsocketAroundRequest() {
1993
+ return this._websocketAroundRequest
1994
+ }
1995
+
1996
+ /**
1997
+ * Registers a wrapper invoked around every controller action — both
1998
+ * HTTP and WS-borne. Receives `{request, response, next}` and must
1999
+ * call `next()` to run the action. Use it for per-request context
2000
+ * like AsyncLocalStorage-scoped locale or tracing.
2001
+ * @param {((context: {request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, response: import("./http-server/client/response.js").default, next: () => Promise<void>}) => Promise<void>) | null} wrapper
2002
+ * @returns {void}
2003
+ */
2004
+ setAroundAction(wrapper) {
2005
+ this._aroundAction = wrapper
2006
+ }
2007
+
2008
+ /**
2009
+ * Runs get around action.
2010
+ * @returns {((context: {request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default, response: import("./http-server/client/response.js").default, next: () => Promise<void>}) => Promise<void>) | null}
2011
+ */
2012
+ getAroundAction() {
2013
+ return this._aroundAction
2014
+ }
2015
+
2016
+ /**
2017
+ * Registers an identity resolver called once at pause time and once
2018
+ * at resume time. The resolver receives the session and returns any
2019
+ * value that identifies the authenticated caller — typically a
2020
+ * `userId` read from the session's upgrade-request cookie. Velocious
2021
+ * captures the pause-time value on the paused session and compares
2022
+ * it via `===` (or deep-equality for plain objects) to the fresh
2023
+ * resume-time value. If they differ, the resume is rejected with
2024
+ * `session-gone` and the paused session is destroyed so a signed-out
2025
+ * or re-authenticated client cannot reclaim another user's state.
2026
+ *
2027
+ * Return `null`/`undefined` to mean "no identity" — resumes still
2028
+ * succeed if pause and resume both resolve to a nullish value.
2029
+ * @param {((session: import("./http-server/client/websocket-session.js").default) => ? | Promise<?>) | null} resolver
2030
+ * @returns {void}
2031
+ */
2032
+ setWebsocketSessionIdentityResolver(resolver) {
2033
+ this._websocketSessionIdentityResolver = resolver
2034
+ }
2035
+
2036
+ /**
2037
+ * Runs get websocket session identity resolver.
2038
+ @returns {((session: import("./http-server/client/websocket-session.js").default) => ? | Promise<?>) | null} */
2039
+ getWebsocketSessionIdentityResolver() {
2040
+ return this._websocketSessionIdentityResolver
2041
+ }
2042
+
2043
+ /**
2044
+ * Runs set websocket session grace seconds.
2045
+ * @param {number} seconds
2046
+ * @returns {void}
2047
+ */
2048
+ setWebsocketSessionGraceSeconds(seconds) {
2049
+ if (!Number.isFinite(seconds) || seconds < 0) throw new Error(`Invalid grace seconds: ${seconds}`)
2050
+ this._websocketSessionGraceSeconds = seconds
2051
+ }
2052
+
2053
+ /**
2054
+ * Moves a session into the paused registry and starts the grace
2055
+ * timer. When the timer fires, the session's permanent teardown
2056
+ * hook is invoked. Called by the session itself from `_handleClose`
2057
+ * when there is resumable state (live Connections / Channel subs).
2058
+ * @param {import("./http-server/client/websocket-session.js").default} session
2059
+ * @returns {void}
2060
+ */
2061
+ _pauseWebsocketSession(session) {
2062
+ const sessionId = session.sessionId
2063
+
2064
+ if (!sessionId) throw new Error("Session must have a sessionId to be paused")
2065
+ if (this._pausedWebsocketSessions.has(sessionId)) return
2066
+
2067
+ const graceMs = this._websocketSessionGraceSeconds * 1000
2068
+ const graceTimer = setTimeout(() => {
2069
+ this._expireWebsocketSession(sessionId)
2070
+ }, graceMs)
2071
+
2072
+ // Don't keep the process alive purely for a paused session timer.
2073
+ if (typeof graceTimer.unref === "function") graceTimer.unref()
2074
+
2075
+ this._pausedWebsocketSessions.set(sessionId, {session, graceTimer, pausedAt: Date.now()})
2076
+ }
2077
+
2078
+ /**
2079
+ * Looks up a paused session by id (does NOT remove it — caller is
2080
+ * expected to call `_resumeWebsocketSession` to complete the handoff).
2081
+ * @param {string} sessionId
2082
+ * @returns {import("./http-server/client/websocket-session.js").default | null}
2083
+ */
2084
+ _findPausedWebsocketSession(sessionId) {
2085
+ return this._pausedWebsocketSessions.get(sessionId)?.session || null
2086
+ }
2087
+
2088
+ /**
2089
+ * Removes a paused session from the registry and cancels its grace
2090
+ * timer. Called on successful resume handoff and on explicit
2091
+ * expiry.
2092
+ * @param {string} sessionId
2093
+ * @returns {void}
2094
+ */
2095
+ _clearPausedWebsocketSession(sessionId) {
2096
+ const entry = this._pausedWebsocketSessions.get(sessionId)
2097
+
2098
+ if (!entry) return
2099
+
2100
+ clearTimeout(entry.graceTimer)
2101
+ this._pausedWebsocketSessions.delete(sessionId)
2102
+ }
2103
+
2104
+ /**
2105
+ * Grace-timer callback. Calls the session's permanent-teardown
2106
+ * hook and drops it from the registry.
2107
+ * @param {string} sessionId
2108
+ * @returns {void}
2109
+ */
2110
+ _expireWebsocketSession(sessionId) {
2111
+ const entry = this._pausedWebsocketSessions.get(sessionId)
2112
+
2113
+ if (!entry) return
2114
+
2115
+ this._pausedWebsocketSessions.delete(sessionId)
2116
+ try {
2117
+ entry.session._finalizeGraceExpiry()
2118
+ } catch (error) {
2119
+ console.error(`Failed to finalize expired WS session ${sessionId}`, error)
2120
+ }
2121
+ }
2122
+
2123
+ /**
2124
+ * Runs broadcast to channel.
2125
+ * @param {string} name
2126
+ * @param {Record<string, ?>} broadcastParams
2127
+ * @param {?} body
2128
+ * @returns {void}
2129
+ */
2130
+ broadcastToChannel(name, broadcastParams, body) {
2131
+ // When Beacon is connected, ship the broadcast onto the bus. The
2132
+ // daemon echoes it back to every peer (including this one) and
2133
+ // each peer's `_deliverBroadcastFromBeacon` performs the same
2134
+ // local delivery as the synchronous paths below — so every
2135
+ // subscriber, in any process, sees broadcasts via a single code
2136
+ // path.
2137
+ if (this._beaconClient && this._beaconClient.isConnected()) {
2138
+ const sent = this._beaconClient.publish({channel: name, broadcastParams, body})
2139
+
2140
+ if (sent) return
2141
+ }
2142
+
2143
+ // V2 subscriptions live per worker-thread. When running in
2144
+ // worker-thread mode, the publisher runs either in the main
2145
+ // process (host) or in one of the workers:
2146
+ //
2147
+ // - Main process: `_websocketEvents` is the host singleton and
2148
+ // `broadcastV2` fans out to every worker directly.
2149
+ // - Worker: `_websocketEvents` has `publishV2Broadcast` that
2150
+ // posts to main, which then fans out to every worker.
2151
+ //
2152
+ // In-process mode doesn't install a websocket-events transport,
2153
+ // so fall through to the local dispatch.
2154
+ /**
2155
+ * Websocket events.
2156
+ @type {?} */
2157
+ const websocketEvents = this._websocketEvents
2158
+
2159
+ if (websocketEvents && typeof websocketEvents.broadcastV2 === "function") {
2160
+ websocketEvents.broadcastV2({channel: name, broadcastParams, body})
2161
+ return
2162
+ }
2163
+
2164
+ if (websocketEvents && typeof websocketEvents.publishV2Broadcast === "function" && websocketEvents.parentPort) {
2165
+ websocketEvents.publishV2Broadcast({channel: name, broadcastParams, body})
2166
+ return
2167
+ }
2168
+
2169
+ this._broadcastToChannelLocal(name, broadcastParams, body)
2170
+ }
2171
+
2172
+ /**
2173
+ * Awaits all pending broadcast operations (including event-log
2174
+ * persistence). Call this after `broadcastToChannel` when you need
2175
+ * the event to be persisted before continuing (e.g. before
2176
+ * responding to an HTTP request).
2177
+ * @returns {Promise<void>}
2178
+ */
2179
+ async awaitPendingBroadcasts() {
2180
+ /**
2181
+ * Websocket events.
2182
+ @type {?} */
2183
+ const websocketEvents = this._websocketEvents
2184
+
2185
+ if (websocketEvents && typeof websocketEvents.awaitPendingBroadcasts === "function") {
2186
+ await websocketEvents.awaitPendingBroadcasts()
2187
+ }
2188
+ }
2189
+
2190
+ /**
2191
+ * Local (per-worker) channel broadcast dispatch. Called either
2192
+ * directly (in-process mode) or by the worker thread after the
2193
+ * main-process fan-out.
2194
+ * @param {string} name - Channel name.
2195
+ * @param {Record<string, ?>} broadcastParams - Params passed to each subscription's `matches()`.
2196
+ * @param {?} body - Message body delivered via `sendMessage()`.
2197
+ * @param {{eventId?: string}} [meta] - Optional event metadata for replay tracking.
2198
+ * @returns {void}
2199
+ */
2200
+ _broadcastToChannelLocal(name, broadcastParams, body, meta) {
2201
+ const bucket = this._websocketChannelSubscriptions.get(name)
2202
+
2203
+ if (!bucket) return
2204
+
2205
+ for (const subscription of bucket) {
2206
+ if (subscription.isClosed()) continue
2207
+
2208
+ let matches
2209
+
2210
+ try {
2211
+ matches = subscription.matches(broadcastParams || {})
2212
+ } catch (error) {
2213
+ // A broken `matches()` on one subscriber must not poison the
2214
+ // broadcast to other subscribers. Skip and continue.
2215
+ console.error(`broadcastToChannel: ${name} subscription ${subscription.subscriptionId} matches() threw`, error)
2216
+ continue
2217
+ }
2218
+
2219
+ if (!matches) continue
2220
+
2221
+ this.withoutCurrentConnectionContexts(() => {
2222
+ void Promise
2223
+ .resolve()
2224
+ .then(() => this._deliverWebsocketChannelBroadcast(subscription, body, {eventId: meta?.eventId}))
2225
+ .catch((error) => {
2226
+ console.error(`broadcastToChannel: ${name} subscription ${subscription.subscriptionId} deliverBroadcast threw`, error)
2227
+ })
2228
+ })
2229
+ }
2230
+ }
2231
+
2232
+ /**
2233
+ * Runs deliver websocket channel broadcast.
2234
+ * @param {import("./http-server/websocket-channel.js").default} subscription - Channel subscription.
2235
+ * @param {import("./http-server/websocket-channel.js").WebsocketJsonValue} body - Broadcast body.
2236
+ * @param {{eventId?: string}} meta - Broadcast metadata.
2237
+ * @returns {void | Promise<void>} Broadcast delivery result.
2238
+ */
2239
+ _deliverWebsocketChannelBroadcast(subscription, body, meta) {
2240
+ if (typeof subscription.deliverBroadcast === "function") {
2241
+ return subscription.deliverBroadcast(body, meta)
2242
+ }
2243
+
2244
+ return subscription.sendMessage(body, meta)
2245
+ }
2246
+
2247
+ /**
2248
+ * Runs get websocket message handler resolver.
2249
+ * @returns {import("./configuration-types.js").WebsocketMessageHandlerResolverType | undefined} - The websocket message handler resolver.
2250
+ */
2251
+ getWebsocketMessageHandlerResolver() {
2252
+ return this._websocketMessageHandlerResolver
2253
+ }
2254
+
2255
+ /**
2256
+ * Runs set websocket channel resolver.
2257
+ * @param {import("./configuration-types.js").WebsocketChannelResolverType} resolver - Resolver.
2258
+ * @returns {void} - No return value.
2259
+ */
2260
+ setWebsocketChannelResolver(resolver) {
2261
+ this._websocketChannelResolver = resolver
2262
+ }
2263
+
2264
+ /**
2265
+ * Runs set websocket message handler resolver.
2266
+ * @param {import("./configuration-types.js").WebsocketMessageHandlerResolverType} resolver - Resolver.
2267
+ * @returns {void} - No return value.
2268
+ */
2269
+ setWebsocketMessageHandlerResolver(resolver) {
2270
+ this._websocketMessageHandlerResolver = resolver
2271
+ }
2272
+
2273
+ /**
2274
+ * Runs resolve ability.
2275
+ * @param {object} args - Ability resolver args.
2276
+ * @param {Record<string, ?>} args.params - Request params.
2277
+ * @param {import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default} args.request - Request object.
2278
+ * @param {import("./http-server/client/response.js").default} args.response - Response object.
2279
+ * @returns {Promise<import("./authorization/ability.js").default | undefined>} - Resolved ability.
2280
+ */
2281
+ async resolveAbility({params, request, response}) {
2282
+ const resolver = this.getAbilityResolver()
2283
+
2284
+ if (resolver) {
2285
+ const resolved = await resolver({configuration: this, params, request, response})
2286
+
2287
+ if (resolved) return resolved
2288
+ }
2289
+
2290
+ const resources = this.getAbilityResources()
2291
+
2292
+ if (resources.length === 0) return
2293
+
2294
+ return new Ability({
2295
+ context: {configuration: this, params, request, response},
2296
+ resources
2297
+ })
2298
+ }
2299
+
2300
+ /**
2301
+ * Runs run with ability.
2302
+ * @param {import("./authorization/ability.js").default | undefined} ability - Ability instance.
2303
+ * @param {() => Promise<?>} callback - Callback.
2304
+ * @returns {Promise<?>} - Callback result.
2305
+ */
2306
+ async runWithAbility(ability, callback) {
2307
+ return await this.getEnvironmentHandler().runWithAbility(ability, callback)
2308
+ }
2309
+
2310
+ /**
2311
+ * Runs run with request timing.
2312
+ * @param {import("./http-server/client/request-timing.js").default | undefined} requestTiming - Request timing collector.
2313
+ * @param {() => Promise<?>} callback - Callback.
2314
+ * @returns {Promise<?>} - Callback result.
2315
+ */
2316
+ async runWithRequestTiming(requestTiming, callback) {
2317
+ return await this.getEnvironmentHandler().runWithRequestTiming(requestTiming, callback)
2318
+ }
2319
+
2320
+ /**
2321
+ * Runs get current ability.
2322
+ * @returns {import("./authorization/ability.js").default | undefined} - Current ability from context.
2323
+ */
2324
+ getCurrentAbility() {
2325
+ return this.getEnvironmentHandler().getCurrentAbility()
2326
+ }
2327
+
2328
+ /**
2329
+ * Runs get current request timing.
2330
+ * @returns {import("./http-server/client/request-timing.js").default | undefined} - Current request timing collector.
2331
+ */
2332
+ getCurrentRequestTiming() {
2333
+ return this.getEnvironmentHandler().getCurrentRequestTiming()
2334
+ }
2335
+
2336
+ /**
2337
+ * Runs get current tenant.
2338
+ * @returns {?} - Current tenant from context.
2339
+ */
2340
+ getCurrentTenant() {
2341
+ return this.getEnvironmentHandler().getCurrentTenant()
2342
+ }
2343
+
2344
+ /**
2345
+ * Runs run with tenant.
2346
+ * @param {?} tenant - Tenant.
2347
+ * @param {() => Promise<?>} callback - Callback.
2348
+ * @returns {Promise<?>} - Callback result.
2349
+ */
2350
+ async runWithTenant(tenant, callback) {
2351
+ return await this.getEnvironmentHandler().runWithTenant(tenant, callback)
2352
+ }
2353
+
2354
+ /**
2355
+ * Runs resolve tenant.
2356
+ * @param {object} args - Tenant resolver args.
2357
+ * @param {Record<string, ?>} args.params - Request params.
2358
+ * @param {import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default | undefined} args.request - Request object.
2359
+ * @param {import("./http-server/client/response.js").default | undefined} args.response - Response object.
2360
+ * @param {{channel: string, params?: Record<string, ?>}} [args.subscription] - Subscription metadata.
2361
+ * @returns {Promise<?>} - Resolved tenant.
2362
+ */
2363
+ async resolveTenant({params, request, response, subscription}) {
2364
+ const resolver = this.getTenantResolver()
2365
+
2366
+ if (!resolver) return
2367
+
2368
+ return await resolver({
2369
+ configuration: this,
2370
+ params,
2371
+ request,
2372
+ response,
2373
+ subscription
2374
+ })
2375
+ }
2376
+
2377
+ /**
2378
+ * Runs get error events.
2379
+ * @returns {import("eventemitter3").EventEmitter} - Framework error events emitter.
2380
+ */
2381
+ getErrorEvents() {
2382
+ return this._errorEvents
2383
+ }
2384
+
2385
+ /**
2386
+ * Runs with connections.
2387
+ * @template T
2388
+ * @param {WithConnectionsOptionsType | WithConnectionsCallbackType<T>} optionsOrCallback - Checkout options or callback function.
2389
+ * @param {WithConnectionsCallbackType<T>} [callback] - Callback function.
2390
+ * @returns {Promise<T>} - Resolves with the callback result.
2391
+ */
2392
+ async withConnections(optionsOrCallback, callback) {
2393
+ const name = typeof optionsOrCallback == "function" ? "Configuration.withConnections" : (optionsOrCallback.name || "Configuration.withConnections")
2394
+ /**
2395
+ * Actual with connections callback.
2396
+ @type {WithConnectionsCallbackType<T> | undefined} */
2397
+ const actualWithConnectionsCallback = typeof optionsOrCallback == "function" ? /**
2398
+ * Types the following value.
2399
+ @type {WithConnectionsCallbackType<T>} */ (optionsOrCallback) : callback
2400
+
2401
+ if (!actualWithConnectionsCallback) throw new Error("withConnections requires a callback")
2402
+
2403
+ /**
2404
+ * Dbs.
2405
+ @type {{[key: string]: import("./database/drivers/base.js").default}} */
2406
+ const dbs = {}
2407
+
2408
+ const stack = Error().stack
2409
+ const actualCallback = async () => {
2410
+ return await withTrackedStack(stack || "withConnections", async () => {
2411
+ return await actualWithConnectionsCallback(dbs)
2412
+ })
2413
+ }
2414
+
2415
+ /**
2416
+ * Run request.
2417
+ @type {() => Promise<T>} */
2418
+ let runRequest = actualCallback
2419
+
2420
+ for (const identifier of this.getDatabaseIdentifiers()) {
2421
+ let actualRunRequest = runRequest
2422
+
2423
+ const nextRunRequest = async () => {
2424
+ return await this.getDatabasePool(identifier).withConnection({name}, async (db) => {
2425
+ dbs[identifier] = db
2426
+
2427
+ return await actualRunRequest()
2428
+ })
2429
+ }
2430
+
2431
+ runRequest = nextRunRequest
2432
+ }
2433
+
2434
+ return await runRequest()
2435
+ }
2436
+
2437
+ /**
2438
+ * Runs get current connections.
2439
+ * @returns {Record<string, import("./database/drivers/base.js").default>} A map of database connections with identifier as key
2440
+ */
2441
+ getCurrentConnections() {
2442
+ /**
2443
+ * Dbs.
2444
+ @type {{[key: string]: import("./database/drivers/base.js").default}} */
2445
+ const dbs = {}
2446
+
2447
+ for (const identifier of this.getDatabaseIdentifiers()) {
2448
+ try {
2449
+ const pool = this.getDatabasePool(identifier)
2450
+ const currentConnection = pool.getCurrentContextConnection ? pool.getCurrentContextConnection() : pool.getCurrentConnection()
2451
+
2452
+ if (currentConnection && (!pool.connectionMatchesCurrentConfiguration || pool.connectionMatchesCurrentConfiguration(currentConnection))) {
2453
+ dbs[identifier] = currentConnection
2454
+ }
2455
+ } catch (error) {
2456
+ if (this.isMissingCurrentConnectionError(error)) {
2457
+ // Ignore
2458
+ } else {
2459
+ throw error
2460
+ }
2461
+ }
2462
+ }
2463
+
2464
+ return dbs
2465
+ }
2466
+
2467
+ /**
2468
+ * Runs without current connection contexts.
2469
+ * @template T
2470
+ * @param {() => T} callback - Callback to run without inherited DB connection contexts.
2471
+ * @returns {T} - Callback result.
2472
+ */
2473
+ withoutCurrentConnectionContexts(callback) {
2474
+ let runCallback = callback
2475
+
2476
+ for (const pool of Object.values(this.databasePools)) {
2477
+ if (!pool) continue
2478
+ const previousRunCallback = runCallback
2479
+
2480
+ runCallback = () => pool.withoutCurrentConnectionContext(previousRunCallback)
2481
+ }
2482
+
2483
+ return runCallback()
2484
+ }
2485
+
2486
+ /**
2487
+ * Runs is missing current connection error.
2488
+ * @param {?} error - Error thrown while looking up the current connection.
2489
+ * @returns {boolean} - Whether the error means no current connection is available.
2490
+ */
2491
+ isMissingCurrentConnectionError(error) {
2492
+ return error instanceof Error && (
2493
+ error.message == "ID hasn't been set for this async context" ||
2494
+ error.message == "A connection hasn't been made yet" ||
2495
+ error.message.startsWith("No async context set for database connection") ||
2496
+ error.message.startsWith("Connection ") && error.message.includes("doesn't exist any more")
2497
+ )
2498
+ }
2499
+
2500
+ /**
2501
+ * Runs ensure connections.
2502
+ * @template T
2503
+ * @param {WithConnectionsOptionsType | WithConnectionsCallbackType<T>} optionsOrCallback - Checkout options or callback function.
2504
+ * @param {WithConnectionsCallbackType<T>} [callback] - Callback function.
2505
+ * @returns {Promise<T>} - Resolves with the callback result.
2506
+ */
2507
+ async ensureConnections(optionsOrCallback, callback) {
2508
+ const name = typeof optionsOrCallback == "function" ? "Configuration.ensureConnections" : (optionsOrCallback.name || "Configuration.ensureConnections")
2509
+ /**
2510
+ * Actual with connections callback.
2511
+ @type {WithConnectionsCallbackType<T> | undefined} */
2512
+ const actualWithConnectionsCallback = typeof optionsOrCallback == "function" ? /**
2513
+ * Types the following value.
2514
+ @type {WithConnectionsCallbackType<T>} */ (optionsOrCallback) : callback
2515
+
2516
+ if (!actualWithConnectionsCallback) throw new Error("ensureConnections requires a callback")
2517
+
2518
+ const dbs = this.getCurrentConnections()
2519
+ const identifiers = this.getDatabaseIdentifiers()
2520
+ const hasAllConnections = identifiers.every((identifier) => dbs[identifier])
2521
+
2522
+ if (hasAllConnections) {
2523
+ return await actualWithConnectionsCallback(dbs)
2524
+ } else {
2525
+ return await this.withConnections({name}, actualWithConnectionsCallback)
2526
+ }
2527
+ }
2528
+
2529
+ /**
2530
+ * Closes active database connections and clears global connections.
2531
+ * @returns {Promise<void>} - Resolves when complete.
2532
+ */
2533
+ async closeDatabaseConnections() {
2534
+ if (this._closeDatabaseConnectionsPromise) {
2535
+ await this._closeDatabaseConnectionsPromise
2536
+ return
2537
+ }
2538
+
2539
+ const constructors = new Set()
2540
+
2541
+ this._closeDatabaseConnectionsPromise = (async () => {
2542
+ for (const pool of Object.values(this.databasePools)) {
2543
+ if (!pool) continue
2544
+
2545
+ if (typeof pool.closeAll === "function") {
2546
+ await pool.closeAll()
2547
+ }
2548
+
2549
+ const poolConstructor = /**
2550
+ * Types the following value.
2551
+ @type {{clearGlobalConnections?: (configuration: VelociousConfiguration) => void}} */ (pool.constructor)
2552
+
2553
+ if (typeof poolConstructor?.clearGlobalConnections === "function") {
2554
+ constructors.add(poolConstructor)
2555
+ }
2556
+ }
2557
+
2558
+ for (const constructor of constructors) {
2559
+ constructor.clearGlobalConnections?.(this)
2560
+ }
2561
+
2562
+ // Allow models to be re-initialized after connections are closed.
2563
+ this._modelsInitialized = false
2564
+ })()
2565
+
2566
+ try {
2567
+ await this._closeDatabaseConnectionsPromise
2568
+ } finally {
2569
+ this._closeDatabaseConnectionsPromise = null
2570
+ }
2571
+ }
2572
+
2573
+ /**
2574
+ * Runs debug endpoint request authorized.
2575
+ * @param {{header: (name: string) => string | null | undefined}} request - Incoming request.
2576
+ * @param {string} expectedToken - Configured debug-endpoint token.
2577
+ * @returns {boolean} - Whether the request carries the expected bearer token.
2578
+ */
2579
+ debugEndpointRequestAuthorized(request, expectedToken) {
2580
+ const header = request.header("authorization")
2581
+
2582
+ if (typeof header !== "string") return false
2583
+
2584
+ const match = (/^Bearer\s+(.+)$/i).exec(header.trim())
2585
+
2586
+ if (!match) return false
2587
+
2588
+ return this.getEnvironmentHandler().debugEndpointTokenMatches(match[1], expectedToken)
2589
+ }
2590
+ }