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,1755 @@
1
+ // @ts-check
2
+
3
+ import {randomUUID} from "node:crypto"
4
+
5
+ import EventEmitter from "../../utils/event-emitter.js"
6
+ import Logger from "../../logger.js"
7
+ import RequestRunner from "./request-runner.js"
8
+ import WebsocketRequest from "./websocket-request.js"
9
+ import WebsocketChannel from "../websocket-channel.js"
10
+ import {websocketEventLogStoreForConfiguration} from "../websocket-event-log-store.js"
11
+
12
+ const WEBSOCKET_FINAL_FRAME = 0x80
13
+ const WEBSOCKET_OPCODE_CONTINUATION = 0x0
14
+ const WEBSOCKET_OPCODE_TEXT = 0x1
15
+ const WEBSOCKET_OPCODE_BINARY = 0x2
16
+ const WEBSOCKET_OPCODE_CLOSE = 0x8
17
+ const WEBSOCKET_OPCODE_PING = 0x9
18
+ const WEBSOCKET_OPCODE_PONG = 0xA
19
+
20
+ /** Cap on the paused outbound queue; oldest frames drop on overflow. */
21
+ const WEBSOCKET_PAUSED_QUEUE_CAP = 1000
22
+
23
+ /** Cap on total bytes buffered for a single fragmented message. */
24
+ const WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES = 16 * 1024 * 1024
25
+
26
+ /** Cap on fragment count for a single fragmented message. */
27
+ const WEBSOCKET_MAX_FRAGMENTED_MESSAGE_FRAGMENTS = 1024
28
+
29
+ /**
30
+ * Defines this typedef.
31
+ * @typedef {{type: "subscribe", channel: string, lastEventId?: string, params?: Record<string, ?>} | {type: "metadata", data?: Record<string, ?>} | {type?: "request", body?: ?, headers?: Record<string, ?>, id?: string | number | null, method: string, path: string} | Record<string, ?>} WebsocketSessionMessage
32
+ */
33
+
34
+ /**
35
+ * Runs subscribe message.
36
+ * @param {WebsocketSessionMessage} message - Raw websocket message.
37
+ * @returns {{type: "subscribe", channel: string, lastEventId?: string, params?: Record<string, ?>} | null} - Subscribe message when matched.
38
+ */
39
+ function subscribeMessage(message) {
40
+ return message.type === "subscribe"
41
+ ? /**
42
+ * Narrows the runtime value to the documented type.
43
+ @type {{type: "subscribe", channel: string, lastEventId?: string, params?: Record<string, ?>}} */ (message)
44
+ : null
45
+ }
46
+
47
+ /**
48
+ * Runs request message.
49
+ * @param {WebsocketSessionMessage} message - Raw websocket message.
50
+ * @returns {{type?: "request", body?: ?, headers?: Record<string, ?>, id?: string | number | null, method: string, path: string} | null} - Request message when matched.
51
+ */
52
+ function requestMessage(message) {
53
+ if (message.type && message.type !== "request") return null
54
+
55
+ return /** Narrows the runtime value to the documented type. @type {{type?: "request", body?: ?, headers?: Record<string, ?>, id?: string | number | null, method: string, path: string}} */ (message)
56
+ }
57
+
58
+ /**
59
+ * Compares two identity values from `getWebsocketSessionIdentityResolver`.
60
+ * Nullish values compare equal to each other but not to a real identity.
61
+ * Plain objects are compared via JSON round-trip so apps can return a
62
+ * `{userId, tenantId}`-style object without building their own equality.
63
+ * @param {?} a - Paused-time identity.
64
+ * @param {?} b - Resume-time identity.
65
+ * @returns {boolean} - True when the two identities are considered the same caller.
66
+ */
67
+ function identitiesMatch(a, b) {
68
+ if (a === b) return true
69
+ if (a == null || b == null) return false
70
+ if (typeof a !== "object" || typeof b !== "object") return false
71
+
72
+ try {
73
+ return JSON.stringify(a) === JSON.stringify(b)
74
+ } catch {
75
+ return false
76
+ }
77
+ }
78
+
79
+ export default class VelociousHttpServerClientWebsocketSession {
80
+ events = new EventEmitter()
81
+ subscriptions = new Set()
82
+ channels = new Set()
83
+ subscriptionHandlers = new Map()
84
+ handlerSubscriptions = new Map()
85
+ channelTenants = new Map()
86
+ channelReplayStates = new Map()
87
+ /**
88
+ * Message queue.
89
+ @type {WebsocketSessionMessage[]} */
90
+ messageQueue = []
91
+
92
+ /**
93
+ * Runs constructor.
94
+ * @param {object} args - Options object.
95
+ * @param {import("../../configuration.js").default} args.configuration - Configuration instance.
96
+ * @param {import("./index.js").default} args.client - Client instance.
97
+ * @param {import("./request.js").default | import("./websocket-request.js").default} [args.upgradeRequest] - Initial websocket upgrade request.
98
+ * @param {import("../../configuration-types.js").WebsocketMessageHandler} [args.messageHandler] - Optional raw message handler.
99
+ * @param {Promise<import("../../configuration-types.js").WebsocketMessageHandler | void>} [args.messageHandlerPromise] - Optional raw message handler promise.
100
+ */
101
+ constructor({client, configuration, upgradeRequest, messageHandler, messageHandlerPromise}) {
102
+ this.buffer = Buffer.alloc(0)
103
+ this.client = client
104
+ this.configuration = configuration
105
+ this.upgradeRequest = upgradeRequest
106
+ this.messageHandler = messageHandler
107
+ this.messageHandlerPromise = messageHandlerPromise
108
+ this.pendingMessageHandler = Boolean(messageHandlerPromise)
109
+ this.logger = new Logger(this)
110
+
111
+ /**
112
+ * Narrows the runtime value to the documented type.
113
+ @type {Record<string, ?>} */
114
+ this._metadata = {}
115
+
116
+ /**
117
+ * Long-lived per-session state bag. Stable across reconnects once
118
+ * grace-period resumption lands in Phase 2; today it just lives
119
+ * for the duration of the underlying socket.
120
+ * @type {Record<string, ?>}
121
+ */
122
+ this.data = {}
123
+
124
+ /**
125
+ * Narrows the runtime value to the documented type.
126
+ @type {Map<string, import("../websocket-connection.js").default>} */
127
+ this._connections = new Map()
128
+
129
+ /**
130
+ * Narrows the runtime value to the documented type.
131
+ @type {Map<string, {channelType: string, subscription: import("../websocket-channel.js").default}>} */
132
+ this._channelSubscriptions = new Map()
133
+
134
+ /**
135
+ * Unique id assigned to this session on first connect. Sent to the
136
+ * client via `session-established`; the client echoes it back via
137
+ * `session-resume` after a WS drop to reattach to this session
138
+ * within the grace period.
139
+ * @type {string}
140
+ */
141
+ this.sessionId = randomUUID()
142
+
143
+ /**
144
+ * Narrows the runtime value to the documented type.
145
+ * @type {boolean} - true after `_handleClose` pauses instead of tearing down.
146
+ */
147
+ this._paused = false
148
+
149
+ /**
150
+ * Narrows the runtime value to the documented type.
151
+ * @type {Array<?>} - frames produced while paused; flushed on resume.
152
+ */
153
+ this._outboundQueue = []
154
+
155
+ /**
156
+ * Narrows the runtime value to the documented type.
157
+ @type {import("./index.js").default | null} */
158
+ this.socket = null
159
+
160
+ /**
161
+ * Tail of a per-session promise chain that serializes message
162
+ * handling. Prevents races where message B reads `session.data`
163
+ * before message A's handler finishes writing it (e.g. a
164
+ * connection-message setting the locale vs. a subsequent request
165
+ * whose aroundRequest wrapper reads it).
166
+ * @type {Promise<void>}
167
+ */
168
+ this._messageChain = Promise.resolve()
169
+
170
+ /**
171
+ * Promise that resolves to the auth identity captured at pause
172
+ * time by `getWebsocketSessionIdentityResolver`. Awaited at resume
173
+ * time to compare against the fresh caller's identity. Undefined
174
+ * on a live (non-paused) session.
175
+ * @type {Promise<?> | undefined}
176
+ */
177
+ this._resumeIdentityPromise = undefined
178
+
179
+ /**
180
+ * Accumulates payloads for a fragmented websocket message per
181
+ * RFC 6455. Non-null while mid-fragment; cleared when the frame
182
+ * with FIN=1 completes and the message is dispatched.
183
+ * @type {Buffer[] | null}
184
+ */
185
+ this._fragmentedPayloads = null
186
+
187
+ /**
188
+ * Opcode (TEXT/BINARY) captured from the first frame of a
189
+ * fragmented message. Continuation frames (opcode 0) inherit it
190
+ * at reassembly time.
191
+ * @type {number | null}
192
+ */
193
+ this._fragmentedOpcode = null
194
+
195
+ /**
196
+ * Running byte total for `_fragmentedPayloads`. Used to enforce
197
+ * `WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES` so a peer cannot
198
+ * exhaust memory by streaming non-final fragments indefinitely.
199
+ * @type {number}
200
+ */
201
+ this._fragmentedBytes = 0
202
+
203
+ this.configuration._websocketSessions.add(this)
204
+ }
205
+
206
+ /**
207
+ * Sends the client its sessionId + grace window. Called by
208
+ * `VelociousHttpServerClient` after the WS upgrade completes.
209
+ * @returns {void}
210
+ */
211
+ sendSessionEstablished() {
212
+ this.sendJson({
213
+ type: "session-established",
214
+ sessionId: this.sessionId,
215
+ graceSeconds: this.configuration.getWebsocketSessionGraceSeconds?.() || 300
216
+ })
217
+ }
218
+
219
+ /**
220
+ * Removes a closed connection from the session registry. Called by
221
+ * `VelociousWebsocketConnection.close()` after it sends the final
222
+ * `connection-closed` frame.
223
+ * @param {string} connectionId
224
+ * @returns {void}
225
+ */
226
+ _removeConnection(connectionId) {
227
+ this._connections.delete(connectionId)
228
+ }
229
+
230
+ /**
231
+ * Runs get metadata.
232
+ * @returns {Record<string, ?>} - Client-provided metadata (defensive copy).
233
+ */
234
+ getMetadata() {
235
+ return {...this._metadata}
236
+ }
237
+
238
+ /**
239
+ * Runs is paused.
240
+ * @returns {boolean} - true while the session is in the paused/grace registry.
241
+ */
242
+ isPaused() {
243
+ return this._paused
244
+ }
245
+
246
+ /**
247
+ * Runs add subscription.
248
+ * @param {string} channel - Channel name.
249
+ * @returns {void} - No return value.
250
+ */
251
+ addSubscription(channel) {
252
+ this.subscriptions.add(channel)
253
+ }
254
+
255
+ destroy() {
256
+ this.configuration._websocketSessions.delete(this)
257
+ this._paused = false
258
+ void this._teardownChannel()
259
+ void this._teardownConnections("session_destroyed")
260
+ void this._teardownChannelSubscriptions()
261
+ this.events.removeAllListeners()
262
+ }
263
+
264
+ /**
265
+ * Runs has subscription.
266
+ * @param {string} channel - Channel name.
267
+ * @returns {boolean} - Whether it has subscription.
268
+ */
269
+ hasSubscription(channel) {
270
+ return this.subscriptions.has(channel)
271
+ }
272
+
273
+ /**
274
+ * Runs on data.
275
+ * @param {Buffer} data - Data payload.
276
+ * @returns {void} - No return value.
277
+ */
278
+ onData(data) {
279
+ this.buffer = Buffer.concat([this.buffer, data])
280
+ this._processBuffer()
281
+ }
282
+
283
+ /**
284
+ * Runs send event.
285
+ * @param {string} channel - Channel name.
286
+ * @param {?} payload - Payload data.
287
+ * @param {{createdAt?: string, eventId?: string, replayed?: boolean, sequence?: number}} [options] - Event metadata.
288
+ * @returns {Promise<void>} - Resolves when complete.
289
+ */
290
+ async sendEvent(channel, payload, options = {}) {
291
+ const channelHandlers = this.subscriptionHandlers.get(channel)
292
+ const hasChannelHandlers = Boolean(channelHandlers && channelHandlers.size > 0)
293
+ const replayState = this.channelReplayStates.get(channel)
294
+
295
+ if (replayState?.replaying && !options.replayed) {
296
+ replayState.buffered = true
297
+ return
298
+ }
299
+
300
+ if (!this.hasSubscription(channel) && !hasChannelHandlers) return
301
+
302
+ if (hasChannelHandlers) {
303
+ await Promise.all(Array.from(channelHandlers).map(async (handler) => {
304
+ const tenant = this.channelTenants.get(handler)
305
+
306
+ await this.configuration.runWithTenant(tenant, async () => {
307
+ await this._withConnections(async () => {
308
+ await handler.receivedBroadcast({
309
+ channel,
310
+ createdAt: options.createdAt,
311
+ eventId: options.eventId,
312
+ payload,
313
+ replayed: options.replayed,
314
+ sequence: options.sequence
315
+ })
316
+ })
317
+ })
318
+ }))
319
+ return
320
+ }
321
+
322
+ this.sendJson({
323
+ channel,
324
+ createdAt: options.createdAt,
325
+ eventId: options.eventId,
326
+ payload,
327
+ replayed: options.replayed,
328
+ sequence: options.sequence,
329
+ type: "event"
330
+ })
331
+ }
332
+
333
+ /**
334
+ * Runs initialize channel.
335
+ * @returns {Promise<void>} - Resolves when complete.
336
+ */
337
+ async initializeChannel() {
338
+ if (this.messageHandlerPromise) {
339
+ await this._resolveMessageHandlerPromise()
340
+
341
+ if (this.messageHandler) return
342
+ }
343
+
344
+ if (this.messageHandler) {
345
+ await this._runMessageHandlerOpen()
346
+ return
347
+ }
348
+
349
+ const resolver = this.configuration.getWebsocketChannelResolver?.()
350
+
351
+ if (!resolver) return
352
+
353
+ try {
354
+ const tenant = await this._resolveTenant({})
355
+ const resolved = await this.configuration.runWithTenant(tenant, async () => {
356
+ return await resolver({
357
+ client: this.client,
358
+ configuration: this.configuration,
359
+ request: this.upgradeRequest,
360
+ websocketSession: this
361
+ })
362
+ })
363
+
364
+ if (!resolved) return
365
+
366
+ const channel = typeof resolved === "function"
367
+ ? new resolved({client: this.client, configuration: this.configuration, request: this.upgradeRequest, websocketSession: this})
368
+ : resolved
369
+
370
+ if (channel && !(channel instanceof WebsocketChannel)) {
371
+ throw new Error("Resolved websocket channel must extend WebsocketChannel")
372
+ }
373
+
374
+ await this._registerChannel(channel, tenant)
375
+ } catch (error) {
376
+ this.logger.error(() => ["Failed to initialize websocket channel", error])
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Runs send goodbye.
382
+ * @param {import("./index.js").default} client - Client instance.
383
+ * @returns {void} - No return value.
384
+ */
385
+ sendGoodbye(client) {
386
+ const frame = Buffer.from([WEBSOCKET_FINAL_FRAME | WEBSOCKET_OPCODE_CLOSE, 0x00])
387
+
388
+ client.events.emit("output", frame)
389
+ }
390
+
391
+ /**
392
+ * Runs handle message.
393
+ * @param {WebsocketSessionMessage} message - Message text.
394
+ * @returns {Promise<void>} - Resolves when complete.
395
+ */
396
+ async _handleMessage(message) {
397
+ // Serialize per-session: chain onto `_messageChain` so messages
398
+ // are processed one at a time. Without this, fire-and-forget
399
+ // dispatch from `_processBuffer` lets message B read
400
+ // `session.data` before A has finished writing it.
401
+ const previous = this._messageChain
402
+ const next = previous.then(() => this._dispatchMessage(message))
403
+
404
+ this._messageChain = next.catch(() => {})
405
+ await next
406
+ }
407
+
408
+ /**
409
+ * Runs dispatch message.
410
+ * @param {WebsocketSessionMessage} message - Message text.
411
+ * @returns {Promise<void>} - Resolves when complete.
412
+ */
413
+ async _dispatchMessage(message) {
414
+ const wrapper = this.configuration.getWebsocketAroundRequest?.()
415
+
416
+ if (wrapper) {
417
+ await wrapper(this, () => this._handleMessageInner(message))
418
+ return
419
+ }
420
+
421
+ await this._handleMessageInner(message)
422
+ }
423
+
424
+ /**
425
+ * The actual message dispatch, extracted so
426
+ * `configuration.getWebsocketAroundRequest()` can wrap it in any
427
+ * per-request context (AsyncLocalStorage, tracing, etc.).
428
+ * @param {WebsocketSessionMessage} message
429
+ * @returns {Promise<void>}
430
+ */
431
+ async _handleMessageInner(message) {
432
+ if (this.pendingMessageHandler) {
433
+ this.messageQueue.push(message)
434
+ return
435
+ }
436
+
437
+ // The messageHandler short-circuits default routing only when the
438
+ // app actually declared an `onMessage` hook. Apps that only want
439
+ // session-lifecycle tracking (`onOpen`/`onClose`) still need the
440
+ // built-in subscribe/connection/channel-subscribe routing below,
441
+ // otherwise every incoming message is silently dropped.
442
+ if (this.messageHandler && typeof this.messageHandler.onMessage === "function") {
443
+ await this._runMessageHandlerMessage(message)
444
+ return
445
+ }
446
+
447
+ const subscribePayload = subscribeMessage(message)
448
+
449
+ if (subscribePayload) {
450
+ const {channel, lastEventId, params} = subscribePayload
451
+
452
+ if (!channel) throw new Error("channel is required for subscribe")
453
+ const resolver = this.configuration.getWebsocketChannelResolver?.()
454
+
455
+ if (resolver) {
456
+ await this._handleChannelSubscription({channel, lastEventId, params})
457
+ } else {
458
+ await this.subscribeToChannel(channel, {acknowledge: true, lastEventId, params})
459
+ }
460
+
461
+ return
462
+ }
463
+
464
+ if (message.type === "metadata") {
465
+ const metadataPayload = /**
466
+ * Narrows the runtime value to the documented type.
467
+ @type {{data?: Record<string, ?>}} */ (message)
468
+
469
+ this._metadata = metadataPayload.data && typeof metadataPayload.data === "object" ? {...metadataPayload.data} : {}
470
+
471
+ for (const {subscription} of this._channelSubscriptions.values()) {
472
+ if (typeof subscription.onMetadataChanged === "function") {
473
+ await this._withConnections(async () => {
474
+ await subscription.onMetadataChanged(this._metadata)
475
+ })
476
+ }
477
+ }
478
+
479
+ return
480
+ }
481
+
482
+ if (message.type === "session-resume") {
483
+ await this._handleSessionResume(message)
484
+ return
485
+ }
486
+
487
+ if (message.type === "connection-open") {
488
+ await this._handleConnectionOpen(message)
489
+ return
490
+ }
491
+
492
+ if (message.type === "connection-message") {
493
+ await this._handleConnectionMessage(message)
494
+ return
495
+ }
496
+
497
+ if (message.type === "connection-close") {
498
+ await this._handleConnectionClose(message)
499
+ return
500
+ }
501
+
502
+ if (message.type === "channel-subscribe") {
503
+ await this._handleChannelSubscribe(message)
504
+ return
505
+ }
506
+
507
+ if (message.type === "channel-unsubscribe") {
508
+ await this._handleChannelUnsubscribe(message)
509
+ return
510
+ }
511
+
512
+ if (message.type && message.type !== "request") {
513
+ this.sendJson({error: `Unknown message type: ${message.type}`, type: "error"})
514
+ return
515
+ }
516
+
517
+ const requestPayload = requestMessage(message)
518
+
519
+ if (!requestPayload) {
520
+ this.sendJson({error: `Unknown message type: ${message.type}`, type: "error"})
521
+ return
522
+ }
523
+
524
+ const {body, headers, id, method, path} = requestPayload
525
+
526
+ if (!method) throw new Error("method is required")
527
+ if (!path) throw new Error("path is required")
528
+
529
+ const request = new WebsocketRequest({
530
+ body,
531
+ headers,
532
+ metadata: this.getMetadata(),
533
+ method,
534
+ path,
535
+ remoteAddress: this.remoteAddress()
536
+ })
537
+ const requestRunner = new RequestRunner({
538
+ configuration: this.configuration,
539
+ request
540
+ })
541
+
542
+ requestRunner.events.on("done", () => {
543
+ const response = requestRunner.response
544
+ const body = response.getBody()
545
+ const headers = response.headers
546
+
547
+ this.sendJson({
548
+ body,
549
+ headers,
550
+ id,
551
+ statusCode: response.getStatusCode(),
552
+ statusMessage: response.getStatusMessage(),
553
+ type: "response"
554
+ })
555
+ void requestRunner.logCompletedRequest().catch((error) => {
556
+ this.logger.warn("Failed to log completed request", error)
557
+ })
558
+ })
559
+
560
+ await requestRunner.run()
561
+ }
562
+
563
+ /**
564
+ * Runs process buffer.
565
+ * @returns {void} - No return value.
566
+ */
567
+ _processBuffer() {
568
+ while (this.buffer.length >= 2) {
569
+ const firstByte = this.buffer[0]
570
+ const secondByte = this.buffer[1]
571
+ const isFinal = (firstByte & WEBSOCKET_FINAL_FRAME) === WEBSOCKET_FINAL_FRAME
572
+ const opcode = firstByte & 0x0F
573
+ const isMasked = (secondByte & 0x80) === 0x80
574
+ let payloadLength = secondByte & 0x7F
575
+ let offset = 2
576
+
577
+ if (payloadLength === 126) {
578
+ if (this.buffer.length < offset + 2) return
579
+ payloadLength = this.buffer.readUInt16BE(offset)
580
+ offset += 2
581
+ } else if (payloadLength === 127) {
582
+ if (this.buffer.length < offset + 8) return
583
+ const bigLength = this.buffer.readBigUInt64BE(offset)
584
+
585
+ payloadLength = Number(bigLength)
586
+ offset += 8
587
+ }
588
+
589
+ const maskLength = isMasked ? 4 : 0
590
+
591
+ if (this.buffer.length < offset + maskLength + payloadLength) return
592
+
593
+ /** Payload. @type {Buffer} */
594
+ let payload = this.buffer.slice(offset + maskLength, offset + maskLength + payloadLength)
595
+
596
+ if (isMasked) {
597
+ const mask = this.buffer.slice(offset, offset + maskLength)
598
+ payload = this._unmaskPayload(payload, mask)
599
+ }
600
+
601
+ this.buffer = this.buffer.slice(offset + maskLength + payloadLength)
602
+
603
+ // Control frames (opcode >= 0x8) must not be fragmented per
604
+ // RFC 6455 and can arrive interleaved with a fragmented data
605
+ // message. Handle them first without touching the fragment
606
+ // accumulator.
607
+ if (opcode === WEBSOCKET_OPCODE_PING) {
608
+ this._sendControlFrame(WEBSOCKET_OPCODE_PONG, payload)
609
+ continue
610
+ }
611
+
612
+ if (opcode === WEBSOCKET_OPCODE_CLOSE) {
613
+ this.sendGoodbye(this.client)
614
+ this._handleClose()
615
+ continue
616
+ }
617
+
618
+ if (opcode >= 0x8) {
619
+ this.logger.warn(`Unsupported websocket control opcode: ${opcode}`)
620
+ continue
621
+ }
622
+
623
+ // Data frame (TEXT/BINARY/CONTINUATION). Reassemble fragments
624
+ // before dispatching. Browsers (Chrome) legitimately fragment
625
+ // longer client→server text frames; a prior version dropped
626
+ // every fragmented message silently, so any payload large
627
+ // enough to hit the browser's fragmentation threshold
628
+ // (e.g. a channel-subscribe with an auth token) never reached
629
+ // the handler.
630
+ if (opcode === WEBSOCKET_OPCODE_CONTINUATION) {
631
+ if (this._fragmentedPayloads === null) {
632
+ this.logger.warn("Received continuation frame with no fragmented message in progress")
633
+ continue
634
+ }
635
+
636
+ if (!this._appendFragment(payload)) return
637
+
638
+ if (!isFinal) continue
639
+ } else if (opcode === WEBSOCKET_OPCODE_TEXT || opcode === WEBSOCKET_OPCODE_BINARY) {
640
+ if (this._fragmentedPayloads !== null) {
641
+ this.logger.warn("Received new data frame while a fragmented message was in progress; discarding prior fragments")
642
+ this._resetFragmentBuffer()
643
+ }
644
+
645
+ if (!isFinal) {
646
+ this._fragmentedPayloads = [payload]
647
+ this._fragmentedOpcode = opcode
648
+ this._fragmentedBytes = payload.length
649
+
650
+ if (!this._enforceFragmentLimits()) return
651
+
652
+ continue
653
+ }
654
+ } else {
655
+ this.logger.warn(`Unsupported websocket data opcode: ${opcode}`)
656
+ continue
657
+ }
658
+
659
+ /**
660
+ * Defines finalPayload.
661
+ @type {Buffer} */
662
+ let finalPayload
663
+ /**
664
+ * Defines finalOpcode.
665
+ @type {number} */
666
+ let finalOpcode
667
+
668
+ if (this._fragmentedPayloads !== null) {
669
+ if (opcode === WEBSOCKET_OPCODE_CONTINUATION) {
670
+ finalPayload = Buffer.concat(this._fragmentedPayloads)
671
+ finalOpcode = this._fragmentedOpcode ?? WEBSOCKET_OPCODE_TEXT
672
+ } else {
673
+ finalPayload = payload
674
+ finalOpcode = opcode
675
+ }
676
+ this._resetFragmentBuffer()
677
+ } else {
678
+ finalPayload = payload
679
+ finalOpcode = opcode
680
+ }
681
+
682
+ if (finalOpcode !== WEBSOCKET_OPCODE_TEXT) {
683
+ this.logger.warn(`Unsupported websocket data opcode after reassembly: ${finalOpcode}`)
684
+ continue
685
+ }
686
+
687
+ try {
688
+ const message = JSON.parse(finalPayload.toString("utf-8"))
689
+
690
+ this._handleMessage(message).catch((error) => {
691
+ this.logger.error(() => ["Websocket message handler failed", error])
692
+ this.sendJson({error: error.message, type: "error"})
693
+ })
694
+ } catch (error) {
695
+ this.logger.error(() => ["Failed to parse websocket message", error])
696
+ this.sendJson({error: "Invalid websocket message", type: "error"})
697
+ }
698
+ }
699
+ }
700
+
701
+ /**
702
+ * Appends a continuation-frame payload to the in-progress
703
+ * fragmented message. Returns true when the fragment was accepted
704
+ * and false when the per-message cap was hit and the socket has
705
+ * been closed.
706
+ * @param {Buffer} payload
707
+ * @returns {boolean}
708
+ */
709
+ _appendFragment(payload) {
710
+ // Guard pushing first so `_enforceFragmentLimits` sees the final
711
+ // state; on overflow the reset inside the enforcer drops the
712
+ // buffered fragments.
713
+ this._fragmentedPayloads?.push(payload)
714
+ this._fragmentedBytes += payload.length
715
+
716
+ return this._enforceFragmentLimits()
717
+ }
718
+
719
+ /**
720
+ * Verifies the fragmented message has not exceeded the byte or
721
+ * fragment-count caps. On overflow, clears the buffer, sends a
722
+ * close frame, and tears the session down. Returns true when the
723
+ * caller can continue processing, false when the session is being
724
+ * closed.
725
+ * @returns {boolean}
726
+ */
727
+ _enforceFragmentLimits() {
728
+ if (this._fragmentedPayloads === null) return true
729
+
730
+ const fragmentCount = this._fragmentedPayloads.length
731
+ const overBytes = this._fragmentedBytes > WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES
732
+ const overFragments = fragmentCount > WEBSOCKET_MAX_FRAGMENTED_MESSAGE_FRAGMENTS
733
+
734
+ if (!overBytes && !overFragments) return true
735
+
736
+ this.logger.warn(() => [
737
+ "Fragmented websocket message exceeded caps; closing connection",
738
+ {
739
+ fragmentBytes: this._fragmentedBytes,
740
+ fragmentCount,
741
+ maxBytes: WEBSOCKET_MAX_FRAGMENTED_MESSAGE_BYTES,
742
+ maxFragments: WEBSOCKET_MAX_FRAGMENTED_MESSAGE_FRAGMENTS
743
+ }
744
+ ])
745
+
746
+ this._resetFragmentBuffer()
747
+ this.buffer = Buffer.alloc(0)
748
+ this.sendGoodbye(this.client)
749
+ this._handleClose()
750
+
751
+ return false
752
+ }
753
+
754
+ /**
755
+ * Runs reset fragment buffer.
756
+ @returns {void} */
757
+ _resetFragmentBuffer() {
758
+ this._fragmentedPayloads = null
759
+ this._fragmentedOpcode = null
760
+ this._fragmentedBytes = 0
761
+ }
762
+
763
+ /**
764
+ * Runs send control frame.
765
+ * @param {number} opcode - Opcode.
766
+ * @param {Buffer} payload - Payload data.
767
+ * @returns {void} - No return value.
768
+ */
769
+ _sendControlFrame(opcode, payload) {
770
+ const header = Buffer.alloc(2)
771
+
772
+ header[0] = WEBSOCKET_FINAL_FRAME | opcode
773
+ header[1] = payload.length
774
+
775
+ this.client.events.emit("output", Buffer.concat([header, payload]))
776
+ }
777
+
778
+ /**
779
+ * Runs send json.
780
+ * @param {object} body - Request body.
781
+ * @returns {void} - No return value.
782
+ */
783
+ sendJson(body) {
784
+ // While paused (waiting for a resume), stash frames in an
785
+ // outbound queue and flush them in order on resume. Capped to
786
+ // prevent runaway memory use while the client is offline.
787
+ if (this._paused) {
788
+ this._outboundQueue ||= []
789
+
790
+ if (this._outboundQueue.length >= WEBSOCKET_PAUSED_QUEUE_CAP) {
791
+ // Drop oldest so the most recent activity wins on resume.
792
+ this._outboundQueue.shift()
793
+ }
794
+
795
+ this._outboundQueue.push(body)
796
+ return
797
+ }
798
+
799
+ if (!this.client?.events) return
800
+
801
+ const json = JSON.stringify(body)
802
+ const payload = Buffer.from(json, "utf-8")
803
+ let header
804
+
805
+ if (payload.length < 126) {
806
+ header = Buffer.alloc(2)
807
+ header[1] = payload.length
808
+ } else if (payload.length < 65536) {
809
+ header = Buffer.alloc(4)
810
+ header[1] = 126
811
+ header.writeUInt16BE(payload.length, 2)
812
+ } else {
813
+ header = Buffer.alloc(10)
814
+ header[1] = 127
815
+ header.writeBigUInt64BE(BigInt(payload.length), 2)
816
+ }
817
+
818
+ header[0] = WEBSOCKET_FINAL_FRAME | WEBSOCKET_OPCODE_TEXT
819
+
820
+ this.client.events.emit("output", Buffer.concat([header, payload]))
821
+ }
822
+
823
+ /**
824
+ * Flushes the paused outbound queue over the current socket.
825
+ * Called during resume after `session-resumed` has been sent on
826
+ * the NEW session's socket (not this session's).
827
+ * @returns {void}
828
+ */
829
+ _flushOutboundQueue() {
830
+ const queue = this._outboundQueue || []
831
+
832
+ this._outboundQueue = []
833
+
834
+ for (const body of queue) {
835
+ this.sendJson(body)
836
+ }
837
+ }
838
+
839
+ /**
840
+ * Runs subscribe to channel.
841
+ * @param {string} channel - Channel name.
842
+ * @param {{acknowledge?: boolean, channelHandler?: import("../websocket-channel.js").default, lastEventId?: string, params?: Record<string, ?>, subscriptionChannel?: string}} [options] - Subscribe options.
843
+ * @returns {Promise<boolean>} - Whether the subscription was added.
844
+ */
845
+ async subscribeToChannel(channel, {acknowledge = true, channelHandler, lastEventId, params, subscriptionChannel} = {}) {
846
+ await websocketEventLogStoreForConfiguration(this.configuration).markChannelInterested(channel)
847
+
848
+ const replayState = await this._prepareReplayState({
849
+ channel,
850
+ lastEventId,
851
+ subscriptionChannel: subscriptionChannel || channel,
852
+ subscriptionParams: params
853
+ })
854
+
855
+ if (replayState === false) return false
856
+ if (replayState) {
857
+ this.channelReplayStates.set(channel, replayState)
858
+ }
859
+
860
+ this.addSubscription(channel)
861
+
862
+ if (channelHandler) {
863
+ if (!this.subscriptionHandlers.has(channel)) {
864
+ this.subscriptionHandlers.set(channel, new Set())
865
+ }
866
+
867
+ this.subscriptionHandlers.get(channel)?.add(channelHandler)
868
+
869
+ if (!this.handlerSubscriptions.has(channelHandler)) {
870
+ this.handlerSubscriptions.set(channelHandler, new Set())
871
+ }
872
+
873
+ this.handlerSubscriptions.get(channelHandler)?.add(channel)
874
+ }
875
+
876
+ if (replayState) {
877
+ try {
878
+ await this._replayChannelEvents({channel, replayState})
879
+ } finally {
880
+ await this._finishReplayState(channel, replayState)
881
+ }
882
+ }
883
+
884
+ if (acknowledge) {
885
+ this.sendJson({channel, type: "subscribed"})
886
+ }
887
+ return true
888
+ }
889
+
890
+ _handleClose() {
891
+ // If the session has resumable state (live Connection or
892
+ // ChannelV2 subscription), move it into the paused registry
893
+ // instead of tearing down; a new socket presenting the sessionId
894
+ // via `session-resume` within the grace window will reattach.
895
+ const hasResumableState = this._connections.size > 0 || this._channelSubscriptions.size > 0
896
+
897
+ if (hasResumableState && !this._paused) {
898
+ this._paused = true
899
+ this.socket = null
900
+ // Kick off auth-identity capture for resume verification. Runs
901
+ // in the background — `_handleSessionResume` awaits
902
+ // `_resumeIdentityPromise` before comparing. Pause registration
903
+ // is synchronous so a resume arriving immediately still finds
904
+ // the session.
905
+ this._resumeIdentityPromise = this._captureResumeIdentity()
906
+ void this._fireOnDisconnect()
907
+ this.configuration._pauseWebsocketSession(this)
908
+ this.events.emit("close")
909
+ return
910
+ }
911
+
912
+ void this._runMessageHandlerClose()
913
+ void this._teardownChannel()
914
+ void this._teardownConnections("session_destroyed")
915
+ void this._teardownChannelSubscriptions()
916
+ this.events.emit("close")
917
+ }
918
+
919
+ /**
920
+ * Called by the grace timer when the paused period expires without
921
+ * a resume. Tears down all live Connections + Channel subs and
922
+ * drops the session.
923
+ * @returns {void}
924
+ */
925
+ _finalizeGraceExpiry() {
926
+ void this._runMessageHandlerClose()
927
+ void this._teardownChannel()
928
+ void this._teardownConnections("grace_expired")
929
+ void this._teardownChannelSubscriptions()
930
+ this.events.emit("close")
931
+ }
932
+
933
+ /**
934
+ * Runs the configured identity resolver against this session.
935
+ * The returned promise is stored at pause time and awaited at
936
+ * resume time so we can reject resume attempts from a different
937
+ * authenticated caller (signed out, swapped user, expired cookie).
938
+ * @returns {Promise<?>}
939
+ */
940
+ async _captureResumeIdentity() {
941
+ const resolver = this.configuration.getWebsocketSessionIdentityResolver?.()
942
+
943
+ if (typeof resolver !== "function") return undefined
944
+
945
+ try {
946
+ return await resolver(this)
947
+ } catch (error) {
948
+ this.logger.error(() => ["Websocket session identity resolver failed at pause", error])
949
+ return undefined
950
+ }
951
+ }
952
+
953
+ /**
954
+ * Fires `onDisconnect` on every live Connection and Channel sub so
955
+ * apps can pause per-instance work while the session is paused.
956
+ * Errors are logged, not rethrown — one broken handler must not
957
+ * block the rest.
958
+ * @returns {Promise<void>}
959
+ */
960
+ async _fireOnDisconnect() {
961
+ await this._fireLifecycleCallback("onDisconnect")
962
+ }
963
+
964
+ /**
965
+ * Fires `onResume` on every live Connection and Channel sub after
966
+ * a successful `session-resume` handoff.
967
+ * @returns {Promise<void>}
968
+ */
969
+ async _fireOnResume() {
970
+ await this._fireLifecycleCallback("onResume")
971
+ }
972
+
973
+ /**
974
+ * Runs fire lifecycle callback.
975
+ * @param {"onDisconnect" | "onResume"} callbackName Lifecycle callback to fire.
976
+ * @returns {Promise<void>} Resolves when every live handler has been attempted.
977
+ */
978
+ async _fireLifecycleCallback(callbackName) {
979
+ for (const connection of this._connections.values()) {
980
+ try {
981
+ await connection[callbackName]?.()
982
+ } catch (error) {
983
+ this.logger.error(() => [`${callbackName} failed for ${connection.connectionId}`, error])
984
+ }
985
+ }
986
+
987
+ for (const {subscription} of this._channelSubscriptions.values()) {
988
+ try {
989
+ await subscription[callbackName]?.()
990
+ } catch (error) {
991
+ this.logger.error(() => [`${callbackName} failed for channel sub ${subscription.subscriptionId}`, error])
992
+ }
993
+ }
994
+ }
995
+
996
+ /**
997
+ * Handles `{type: "session-resume"}`. This session (the newly-
998
+ * created one whose socket just connected) transfers state from
999
+ * the paused session and instructs the client via
1000
+ * `session-resumed` or `session-gone`.
1001
+ * @param {Record<string, ?>} message
1002
+ * @returns {Promise<void>}
1003
+ */
1004
+ async _handleSessionResume(message) {
1005
+ const resumeSessionId = message.sessionId
1006
+
1007
+ if (typeof resumeSessionId !== "string" || !resumeSessionId) {
1008
+ this.sendJson({type: "session-gone"})
1009
+ return
1010
+ }
1011
+
1012
+ const paused = this.configuration._findPausedWebsocketSession(resumeSessionId)
1013
+
1014
+ if (!paused) {
1015
+ this.sendJson({type: "session-gone"})
1016
+ return
1017
+ }
1018
+
1019
+ // Auth re-verify: compare the fresh caller's identity against the
1020
+ // one captured at pause. Mismatch means a different user (or a
1021
+ // signed-out session) is trying to reclaim state that isn't
1022
+ // theirs — destroy the paused session outright.
1023
+ const resolver = this.configuration.getWebsocketSessionIdentityResolver?.()
1024
+
1025
+ if (typeof resolver === "function") {
1026
+ const pausedIdentity = await paused._resumeIdentityPromise
1027
+ let freshIdentity
1028
+
1029
+ try {
1030
+ freshIdentity = await resolver(this)
1031
+ } catch (error) {
1032
+ this.logger.error(() => ["Websocket session identity resolver failed at resume", error])
1033
+ freshIdentity = undefined
1034
+ }
1035
+
1036
+ if (!identitiesMatch(pausedIdentity, freshIdentity)) {
1037
+ this.configuration._clearPausedWebsocketSession(resumeSessionId)
1038
+ paused._finalizeGraceExpiry()
1039
+ this.sendJson({type: "session-gone"})
1040
+ return
1041
+ }
1042
+ }
1043
+
1044
+ this.configuration._clearPausedWebsocketSession(resumeSessionId)
1045
+
1046
+ // Transfer resumable state onto this (live) session. The paused
1047
+ // session shell is discarded after the transfer.
1048
+ for (const [connectionId, connection] of paused._connections) {
1049
+ connection.session = this
1050
+ this._connections.set(connectionId, connection)
1051
+ }
1052
+
1053
+ for (const [subId, entry] of paused._channelSubscriptions) {
1054
+ entry.subscription.session = this
1055
+ this._channelSubscriptions.set(subId, entry)
1056
+ }
1057
+
1058
+ this._metadata = {...paused._metadata}
1059
+ this.data = paused.data
1060
+ this.sessionId = resumeSessionId
1061
+
1062
+ // Transfer any frames queued while the paused session had no
1063
+ // socket. They flush AFTER session-resumed so the client knows
1064
+ // which session they belong to.
1065
+ const queued = paused._outboundQueue || []
1066
+
1067
+ paused._outboundQueue = []
1068
+ paused._connections.clear()
1069
+ paused._channelSubscriptions.clear()
1070
+ paused._paused = false
1071
+ paused.destroy()
1072
+
1073
+ this.sendJson({type: "session-resumed", sessionId: resumeSessionId})
1074
+ for (const body of queued) this.sendJson(body)
1075
+ await this._fireOnResume()
1076
+ }
1077
+
1078
+ /**
1079
+ * Fires `onClose(reason)` on every live app-defined connection, then
1080
+ * drops them from the registry. No network frame is sent — the
1081
+ * socket is already going away.
1082
+ * @param {"session_destroyed" | "grace_expired" | "error"} reason
1083
+ * @returns {Promise<void>}
1084
+ */
1085
+ async _teardownConnections(reason) {
1086
+ const connections = [...this._connections.values()]
1087
+
1088
+ this._connections.clear()
1089
+
1090
+ for (const connection of connections) {
1091
+ connection._closed = true
1092
+
1093
+ try {
1094
+ await this._withConnections(async () => {
1095
+ await connection.onClose(reason)
1096
+ })
1097
+ } catch (error) {
1098
+ this.logger.error(() => [`Failed to tear down connection ${connection.connectionId}`, error])
1099
+ }
1100
+ }
1101
+ }
1102
+
1103
+ /**
1104
+ * Handles a `{type: "connection-open"}` message — instantiates the
1105
+ * registered connection class, stores it on `_connections`, and
1106
+ * fires `onConnect()`. Sends `connection-opened` on success or
1107
+ * `connection-error` on failure.
1108
+ * @param {Record<string, ?>} message
1109
+ * @returns {Promise<void>}
1110
+ */
1111
+ async _handleConnectionOpen(message) {
1112
+ const connectionId = message.connectionId
1113
+ const connectionType = message.connectionType
1114
+ const params = message.params || {}
1115
+
1116
+ if (typeof connectionId !== "string" || !connectionId) {
1117
+ this.sendJson({type: "error", error: "connection-open requires connectionId"})
1118
+ return
1119
+ }
1120
+
1121
+ if (typeof connectionType !== "string" || !connectionType) {
1122
+ this.sendJson({type: "connection-error", connectionId, message: "connectionType is required"})
1123
+ return
1124
+ }
1125
+
1126
+ if (this._connections.has(connectionId)) {
1127
+ this.sendJson({type: "connection-error", connectionId, message: "Connection id already in use"})
1128
+ return
1129
+ }
1130
+
1131
+ const ConnectionClass = this.configuration.getWebsocketConnectionClass?.(connectionType)
1132
+
1133
+ if (!ConnectionClass) {
1134
+ this.sendJson({type: "connection-error", connectionId, message: `Unknown connection type: ${connectionType}`})
1135
+ return
1136
+ }
1137
+
1138
+ const connection = new ConnectionClass({connectionId, params, session: this})
1139
+
1140
+ try {
1141
+ await this._withConnections(async () => {
1142
+ await connection.onConnect()
1143
+ })
1144
+ // Register only after onConnect resolves so a connection-message
1145
+ // can never be routed to a partially initialized connection.
1146
+ this._connections.set(connectionId, connection)
1147
+ this.sendJson({type: "connection-opened", connectionId})
1148
+ } catch (error) {
1149
+ this.logger.error(() => [`Failed to open connection ${connectionType}:${connectionId}`, error])
1150
+ this.sendJson({type: "connection-error", connectionId, message: /**
1151
+ * Narrows the runtime value to the documented type.
1152
+ @type {Error} */ (error).message || "Failed to open connection"})
1153
+ }
1154
+ }
1155
+
1156
+ /**
1157
+ * Handles a `{type: "connection-message"}` from the client.
1158
+ * @param {Record<string, ?>} message
1159
+ * @returns {Promise<void>}
1160
+ */
1161
+ async _handleConnectionMessage(message) {
1162
+ const connectionId = message.connectionId
1163
+ const connection = typeof connectionId === "string" ? this._connections.get(connectionId) : null
1164
+
1165
+ if (!connection) {
1166
+ this.sendJson({type: "connection-error", connectionId, message: "Unknown connection id"})
1167
+ return
1168
+ }
1169
+
1170
+ try {
1171
+ await this._withConnections(async () => {
1172
+ await connection.onMessage(message.body)
1173
+ })
1174
+ } catch (error) {
1175
+ this.logger.error(() => [`Failed to handle connection-message for ${connectionId}`, error])
1176
+ this.sendJson({type: "connection-error", connectionId, message: /**
1177
+ * Narrows the runtime value to the documented type.
1178
+ @type {Error} */ (error).message || "Failed to handle message"})
1179
+ }
1180
+ }
1181
+
1182
+ /**
1183
+ * Handles a `{type: "connection-close"}` from the client — fires
1184
+ * `onClose("client_close")` and confirms with `connection-closed`.
1185
+ * @param {Record<string, ?>} message
1186
+ * @returns {Promise<void>}
1187
+ */
1188
+ async _handleConnectionClose(message) {
1189
+ const connectionId = message.connectionId
1190
+ const connection = typeof connectionId === "string" ? this._connections.get(connectionId) : null
1191
+
1192
+ if (!connection) return
1193
+
1194
+ this._connections.delete(connectionId)
1195
+ // Mark closed before firing onClose so app code holding the
1196
+ // handle sees `isClosed() === true` and can't re-enter sendMessage.
1197
+ connection._closed = true
1198
+
1199
+ try {
1200
+ await this._withConnections(async () => {
1201
+ await connection.onClose("client_close")
1202
+ })
1203
+ } catch (error) {
1204
+ this.logger.error(() => [`Failed to tear down connection ${connectionId}`, error])
1205
+ }
1206
+
1207
+ this.sendJson({type: "connection-closed", connectionId, reason: "client_close"})
1208
+ }
1209
+
1210
+ /**
1211
+ * Handles `{type: "channel-subscribe"}` — runs `canSubscribe()`,
1212
+ * registers with the Configuration's global routing registry on
1213
+ * success, and sends `channel-subscribed` or `channel-error`.
1214
+ * @param {Record<string, ?>} message
1215
+ * @returns {Promise<void>}
1216
+ */
1217
+ async _handleChannelSubscribe(message) {
1218
+ const subscriptionId = message.subscriptionId
1219
+ const channelType = message.channelType
1220
+ const params = message.params || {}
1221
+ const lastEventId = message.lastEventId
1222
+
1223
+ if (typeof subscriptionId !== "string" || !subscriptionId) {
1224
+ this.sendJson({type: "error", error: "channel-subscribe requires subscriptionId"})
1225
+ return
1226
+ }
1227
+
1228
+ if (typeof channelType !== "string" || !channelType) {
1229
+ this.sendJson({type: "channel-error", subscriptionId, message: "channelType is required"})
1230
+ return
1231
+ }
1232
+
1233
+ if (this._channelSubscriptions.has(subscriptionId)) {
1234
+ this.sendJson({type: "channel-error", subscriptionId, message: "Subscription id already in use"})
1235
+ return
1236
+ }
1237
+
1238
+ const ChannelClass = this.configuration.getWebsocketChannelClass?.(channelType)
1239
+
1240
+ if (!ChannelClass) {
1241
+ this.sendJson({type: "channel-error", subscriptionId, message: `Unknown channel type: ${channelType}`})
1242
+ return
1243
+ }
1244
+
1245
+ const subscription = new ChannelClass({subscriptionId, params, session: this})
1246
+
1247
+ try {
1248
+ // Resolving the tenant can run database queries (e.g. looking up the
1249
+ // record's project and the caller's access), so it must happen inside a
1250
+ // connection scope. Without this the resolver borrows a connection that
1251
+ // is checked back in before/while it queries, intermittently surfacing as
1252
+ // "Connection … doesn't exist any more" or a falsely unauthorized
1253
+ // subscription.
1254
+ let tenant
1255
+ await this._withConnections(async () => {
1256
+ tenant = await this._resolveTenant({channel: channelType, params})
1257
+ })
1258
+
1259
+ await this.configuration.runWithTenant(tenant, async () => {
1260
+ let allowed = false
1261
+
1262
+ await this._withConnections(async () => {
1263
+ allowed = Boolean(await subscription.canSubscribe())
1264
+ })
1265
+
1266
+ if (!allowed) {
1267
+ this.sendJson({type: "channel-error", subscriptionId, message: "Subscription not authorized"})
1268
+ return
1269
+ }
1270
+
1271
+ this._channelSubscriptions.set(subscriptionId, {channelType, subscription})
1272
+ this.configuration._registerWebsocketChannelSubscription(channelType, subscription)
1273
+
1274
+ await this._withConnections(async () => await subscription.subscribed())
1275
+
1276
+ // Replay missed events BEFORE sending channel-subscribed so
1277
+ // the client knows: everything before the confirmation is
1278
+ // replayed, everything after is live.
1279
+ if (typeof lastEventId === "string" && lastEventId.length > 0) {
1280
+ await this._replayChannelEventsForSubscription({channelType, lastEventId, subscription})
1281
+ }
1282
+
1283
+ this.sendJson({type: "channel-subscribed", subscriptionId})
1284
+ })
1285
+ } catch (error) {
1286
+ this._channelSubscriptions.delete(subscriptionId)
1287
+ this.configuration._unregisterWebsocketChannelSubscription(channelType, subscription)
1288
+ this.logger.error(() => [`Failed to subscribe channel ${channelType}:${subscriptionId}`, error])
1289
+ this.sendJson({type: "channel-error", subscriptionId, message: /**
1290
+ * Narrows the runtime value to the documented type.
1291
+ @type {Error} */ (error).message || "Failed to subscribe"})
1292
+ }
1293
+ }
1294
+
1295
+ /**
1296
+ * Replays missed events from the persistent event-log store for a
1297
+ * channel subscription that provided `lastEventId`. Sends each
1298
+ * missed event as a `channel-message` with `replayed: true`.
1299
+ * @param {object} args - Options.
1300
+ * @param {string} args.channelType - Channel type name (event-log key).
1301
+ * @param {string} args.lastEventId - Client's last-seen event id.
1302
+ * @param {import("../websocket-channel.js").default} args.subscription - Live subscription.
1303
+ * @returns {Promise<void>}
1304
+ */
1305
+ async _replayChannelEventsForSubscription({channelType, lastEventId, subscription}) {
1306
+ const store = websocketEventLogStoreForConfiguration(this.configuration)
1307
+
1308
+ await this.configuration.awaitPendingBroadcasts()
1309
+
1310
+ const checkpoint = await store.getEventById({channel: channelType, id: lastEventId})
1311
+
1312
+ if (!checkpoint) {
1313
+ this.sendJson({
1314
+ type: "channel-replay-gap",
1315
+ subscriptionId: subscription.subscriptionId,
1316
+ lastEventId
1317
+ })
1318
+ return
1319
+ }
1320
+
1321
+ const ceiling = await store.latestSequence(channelType)
1322
+
1323
+ if (!ceiling || ceiling <= checkpoint.sequence) return
1324
+
1325
+ const events = await store.getEventsAfter({
1326
+ channel: channelType,
1327
+ sequence: checkpoint.sequence,
1328
+ upToSequence: ceiling
1329
+ })
1330
+
1331
+ for (const event of events) {
1332
+ if (subscription.isClosed()) break
1333
+
1334
+ subscription.sendMessage(/**
1335
+ * Narrows the runtime value to the documented type.
1336
+ @type {import("../websocket-channel.js").WebsocketJsonValue} */ (event.payload))
1337
+ }
1338
+ }
1339
+
1340
+ /**
1341
+ * Handles `{type: "channel-unsubscribe"}` from the client — calls
1342
+ * `unsubscribed()` and sends `channel-unsubscribed`.
1343
+ * @param {Record<string, ?>} message
1344
+ * @returns {Promise<void>}
1345
+ */
1346
+ async _handleChannelUnsubscribe(message) {
1347
+ const subscriptionId = message.subscriptionId
1348
+
1349
+ if (typeof subscriptionId !== "string") return
1350
+
1351
+ const entry = this._channelSubscriptions.get(subscriptionId)
1352
+
1353
+ if (!entry) return
1354
+
1355
+ this._channelSubscriptions.delete(subscriptionId)
1356
+ this.configuration._unregisterWebsocketChannelSubscription(entry.channelType, entry.subscription)
1357
+ entry.subscription._closed = true
1358
+
1359
+ try {
1360
+ await this._withConnections(async () => await entry.subscription.unsubscribed())
1361
+ } catch (error) {
1362
+ this.logger.error(() => [`Failed to unsubscribe channel ${entry.channelType}:${subscriptionId}`, error])
1363
+ }
1364
+
1365
+ this.sendJson({type: "channel-unsubscribed", subscriptionId})
1366
+ }
1367
+
1368
+ /**
1369
+ * Fires `unsubscribed()` on every live channel-v2 subscription,
1370
+ * removes them from the Configuration's global registry, and
1371
+ * drops the session's own map. No network frames — the socket
1372
+ * is already going away.
1373
+ * @returns {Promise<void>}
1374
+ */
1375
+ async _teardownChannelSubscriptions() {
1376
+ const entries = [...this._channelSubscriptions.values()]
1377
+
1378
+ this._channelSubscriptions.clear()
1379
+
1380
+ for (const {channelType, subscription} of entries) {
1381
+ this.configuration._unregisterWebsocketChannelSubscription(channelType, subscription)
1382
+ subscription._closed = true
1383
+
1384
+ try {
1385
+ await this._withConnections(async () => await subscription.unsubscribed())
1386
+ } catch (error) {
1387
+ this.logger.error(() => [`Failed to tear down channel-v2 ${channelType}:${subscription.subscriptionId}`, error])
1388
+ }
1389
+ }
1390
+ }
1391
+
1392
+ async _teardownChannel() {
1393
+ for (const channel of this.channels) {
1394
+ await this._teardownSingleChannel(channel)
1395
+ }
1396
+ this.channels.clear()
1397
+ this.channelReplayStates.clear()
1398
+ }
1399
+
1400
+ /**
1401
+ * Runs teardown single channel.
1402
+ * @param {WebsocketChannel} channel - Channel instance.
1403
+ * @returns {Promise<void>} - Resolves when complete.
1404
+ */
1405
+ async _teardownSingleChannel(channel) {
1406
+ try {
1407
+ const tenant = this.channelTenants.get(channel)
1408
+
1409
+ await this.configuration.runWithTenant(tenant, async () => {
1410
+ await this._withConnections(async () => {
1411
+ await channel?.unsubscribed?.()
1412
+ })
1413
+ })
1414
+ } catch (error) {
1415
+ this.logger.error(() => ["Failed to teardown websocket channel", error])
1416
+ }
1417
+
1418
+ const subscriptions = this.handlerSubscriptions.get(channel)
1419
+
1420
+ if (subscriptions) {
1421
+ for (const subscriptionChannel of subscriptions) {
1422
+ this.subscriptionHandlers.get(subscriptionChannel)?.delete(channel)
1423
+
1424
+ if (this.subscriptionHandlers.get(subscriptionChannel)?.size === 0) {
1425
+ this.subscriptionHandlers.delete(subscriptionChannel)
1426
+ }
1427
+ }
1428
+
1429
+ this.handlerSubscriptions.delete(channel)
1430
+ }
1431
+
1432
+ this.channelTenants.delete(channel)
1433
+ }
1434
+
1435
+ /**
1436
+ * Runs register channel.
1437
+ * @param {WebsocketChannel | undefined} channel - Channel instance.
1438
+ * @param {string | null | undefined} tenant - Tenant key.
1439
+ * @returns {Promise<void>} - Resolves when complete.
1440
+ */
1441
+ async _registerChannel(channel, tenant) {
1442
+ if (!channel) return
1443
+
1444
+ this.channels.add(channel)
1445
+ this.channelTenants.set(channel, tenant)
1446
+ await this.configuration.runWithTenant(tenant, async () => {
1447
+ await this._withConnections(async () => {
1448
+ await channel?.subscribed?.()
1449
+ })
1450
+ })
1451
+ }
1452
+
1453
+ /**
1454
+ * Runs with connections.
1455
+ * @param {() => Promise<void>} callback - Callback.
1456
+ * @returns {Promise<void>} - Resolves when complete.
1457
+ */
1458
+ async _withConnections(callback) {
1459
+ await this.configuration.ensureConnections({name: "Websocket session"}, async () => {
1460
+ await callback()
1461
+ })
1462
+ }
1463
+
1464
+ /**
1465
+ * Runs handle channel subscription.
1466
+ * @param {{channel: string, lastEventId?: string, params?: Record<string, ?>}} args - Subscription args.
1467
+ * @returns {Promise<void>} - Resolves when complete.
1468
+ */
1469
+ async _handleChannelSubscription({channel, lastEventId, params}) {
1470
+ const resolver = this.configuration.getWebsocketChannelResolver?.()
1471
+
1472
+ if (!resolver) return
1473
+
1474
+ try {
1475
+ // Tenant resolution can run database queries, so it must happen inside a
1476
+ // connection scope (see _handleChannelSubscribe).
1477
+ let tenant
1478
+ await this._withConnections(async () => {
1479
+ tenant = await this._resolveTenant({channel, params})
1480
+ })
1481
+ const resolved = await this.configuration.runWithTenant(tenant, async () => {
1482
+ return await resolver({
1483
+ client: this.client,
1484
+ configuration: this.configuration,
1485
+ request: this.upgradeRequest,
1486
+ subscription: {channel, params},
1487
+ websocketSession: this
1488
+ })
1489
+ })
1490
+
1491
+ if (!resolved) {
1492
+ this.sendJson({channel, error: "Subscription rejected", type: "error"})
1493
+ return
1494
+ }
1495
+
1496
+ const channelInstance = typeof resolved === "function"
1497
+ ? new resolved({
1498
+ client: this.client,
1499
+ configuration: this.configuration,
1500
+ lastEventId,
1501
+ request: this.upgradeRequest,
1502
+ subscriptionChannel: channel,
1503
+ subscriptionParams: params,
1504
+ websocketSession: this
1505
+ })
1506
+ : resolved
1507
+
1508
+ if (channelInstance && !(channelInstance instanceof WebsocketChannel)) {
1509
+ throw new Error("Resolved websocket channel must extend WebsocketChannel")
1510
+ }
1511
+
1512
+ await this._registerChannel(channelInstance, tenant)
1513
+ } catch (error) {
1514
+ this.logger.warn(() => ["Websocket channel subscription failed", error])
1515
+ this.sendJson({channel, error: "Subscription rejected", type: "error"})
1516
+ }
1517
+ }
1518
+
1519
+ /**
1520
+ * Runs prepare replay state.
1521
+ * @param {object} args - Options.
1522
+ * @param {string} args.channel - Internal channel name.
1523
+ * @param {string | undefined} args.lastEventId - Last received event id.
1524
+ * @param {string} args.subscriptionChannel - Client-facing channel name.
1525
+ * @param {Record<string, ?> | undefined} args.subscriptionParams - Client-facing params.
1526
+ * @returns {Promise<false | {buffered: boolean, ceilingSequence: number, checkpointSequence: number, replaying: boolean} | null>} - Replay state.
1527
+ */
1528
+ async _prepareReplayState({channel, lastEventId, subscriptionChannel, subscriptionParams}) {
1529
+ if (!lastEventId) return null
1530
+
1531
+ const store = websocketEventLogStoreForConfiguration(this.configuration)
1532
+ const checkpoint = await store.getEventById({channel, id: lastEventId})
1533
+
1534
+ if (!checkpoint) {
1535
+ this.sendJson({channel: subscriptionChannel, lastEventId, params: subscriptionParams, type: "replay-gap"})
1536
+ return false
1537
+ }
1538
+
1539
+ return {
1540
+ buffered: false,
1541
+ ceilingSequence: (await store.latestSequence(channel)) || checkpoint.sequence,
1542
+ checkpointSequence: checkpoint.sequence,
1543
+ replaying: true
1544
+ }
1545
+ }
1546
+
1547
+ /**
1548
+ * Runs replay channel events.
1549
+ * @param {object} args - Options.
1550
+ * @param {string} args.channel - Channel name.
1551
+ * @param {{buffered: boolean, ceilingSequence: number, checkpointSequence: number, replaying: boolean}} args.replayState - Replay state.
1552
+ * @returns {Promise<void>} - Resolves when replay completes.
1553
+ */
1554
+ async _replayChannelEvents({channel, replayState}) {
1555
+ const store = websocketEventLogStoreForConfiguration(this.configuration)
1556
+ const events = await store.getEventsAfter({
1557
+ channel,
1558
+ sequence: replayState.checkpointSequence,
1559
+ upToSequence: replayState.ceilingSequence
1560
+ })
1561
+
1562
+ for (const event of events) {
1563
+ await this.sendEvent(channel, event.payload, {
1564
+ createdAt: event.createdAt,
1565
+ eventId: event.id,
1566
+ replayed: true,
1567
+ sequence: event.sequence
1568
+ })
1569
+ }
1570
+ }
1571
+
1572
+ /**
1573
+ * Runs finish replay state.
1574
+ * @param {string} channel - Channel name.
1575
+ * @param {{buffered: boolean, ceilingSequence: number, checkpointSequence: number, replaying: boolean}} replayState - Replay state.
1576
+ * @returns {Promise<void>} - Resolves when buffered events are flushed.
1577
+ */
1578
+ async _finishReplayState(channel, replayState) {
1579
+ const store = websocketEventLogStoreForConfiguration(this.configuration)
1580
+
1581
+ replayState.replaying = false
1582
+ this.channelReplayStates.delete(channel)
1583
+
1584
+ if (!replayState.buffered) return
1585
+
1586
+ const liveEvents = await store.getEventsAfter({
1587
+ channel,
1588
+ sequence: replayState.ceilingSequence
1589
+ })
1590
+
1591
+ for (const event of liveEvents) {
1592
+ await this.sendEvent(channel, event.payload, {
1593
+ createdAt: event.createdAt,
1594
+ eventId: event.id,
1595
+ sequence: event.sequence
1596
+ })
1597
+ }
1598
+ }
1599
+
1600
+ /**
1601
+ * Runs resolve tenant.
1602
+ * @param {{channel?: string, params?: Record<string, ?>}} args - Tenant resolution args.
1603
+ * @returns {Promise<string | null | undefined>} - Resolved tenant.
1604
+ */
1605
+ async _resolveTenant({channel, params}) {
1606
+ const requestParams = this.upgradeRequest?.params?.()
1607
+ const mergedParams = {
1608
+ ...(requestParams && typeof requestParams === "object" ? requestParams : {}),
1609
+ ...(params && typeof params === "object" ? params : {})
1610
+ }
1611
+
1612
+ return /** Narrows the runtime value to the documented type. @type {Promise<string | null | undefined>} */ (this.configuration.resolveTenant({
1613
+ params: mergedParams,
1614
+ request: this.upgradeRequest,
1615
+ response: undefined,
1616
+ subscription: channel ? {channel, params} : undefined
1617
+ }))
1618
+ }
1619
+
1620
+ /**
1621
+ * Runs unmask payload.
1622
+ * @param {Buffer} payload - Payload data.
1623
+ * @param {Buffer} mask - Mask.
1624
+ * @returns {Buffer} - The unmask payload.
1625
+ */
1626
+ _unmaskPayload(payload, mask) {
1627
+ /**
1628
+ * Result.
1629
+ @type {Buffer} */
1630
+ const result = Buffer.alloc(payload.length)
1631
+
1632
+ for (let i = 0; i < payload.length; i++) {
1633
+ result[i] = payload[i] ^ mask[i % 4]
1634
+ }
1635
+
1636
+ return result
1637
+ }
1638
+
1639
+ async _runMessageHandlerOpen() {
1640
+ try {
1641
+ const handler = this.messageHandler
1642
+ const onOpen = handler ? handler.onOpen : null
1643
+
1644
+ if (onOpen) {
1645
+ await onOpen({session: this})
1646
+ }
1647
+ } catch (error) {
1648
+ this.logger.error(() => ["Websocket open handler failed", error])
1649
+ }
1650
+ }
1651
+
1652
+ /**
1653
+ * Runs run message handler message.
1654
+ * @param {WebsocketSessionMessage} message - Incoming websocket message.
1655
+ * @returns {Promise<void>} - Resolves when complete.
1656
+ */
1657
+ async _runMessageHandlerMessage(message) {
1658
+ try {
1659
+ const handler = this.messageHandler
1660
+ const onMessage = handler ? handler.onMessage : null
1661
+
1662
+ if (onMessage) {
1663
+ await onMessage({message, session: this})
1664
+ }
1665
+ } catch (error) {
1666
+ this.logger.error(() => ["Websocket message handler failed", error])
1667
+ const handler = this.messageHandler
1668
+ const onError = handler ? handler.onError : null
1669
+
1670
+ if (onError) {
1671
+ await onError({error: error instanceof Error ? error : new Error(String(error)), session: this})
1672
+ }
1673
+ }
1674
+ }
1675
+
1676
+ async _runMessageHandlerClose() {
1677
+ try {
1678
+ const handler = this.messageHandler
1679
+ const onClose = handler ? handler.onClose : null
1680
+
1681
+ if (onClose) {
1682
+ await onClose({session: this})
1683
+ }
1684
+ } catch (error) {
1685
+ this.logger.error(() => ["Websocket close handler failed", error])
1686
+ }
1687
+ }
1688
+
1689
+ /**
1690
+ * Runs remote address.
1691
+ * @returns {string | undefined} - Remote address resolved from the websocket upgrade request.
1692
+ */
1693
+ remoteAddress() {
1694
+ return this.upgradeRequest?.remoteAddress() || this.client.remoteAddress
1695
+ }
1696
+
1697
+ /**
1698
+ * Runs set message handler.
1699
+ * @param {import("../../configuration-types.js").WebsocketMessageHandler} handler - Handler instance.
1700
+ * @returns {void}
1701
+ */
1702
+ setMessageHandler(handler) {
1703
+ this.messageHandler = handler
1704
+ void this._runMessageHandlerOpen()
1705
+ }
1706
+
1707
+ async _resolveMessageHandlerPromise() {
1708
+ if (!this.messageHandlerPromise) return
1709
+
1710
+ try {
1711
+ const handler = await this.messageHandlerPromise
1712
+
1713
+ if (handler) {
1714
+ this.pendingMessageHandler = false
1715
+ this.messageHandlerPromise = undefined
1716
+ // Install handler and drain onOpen before replaying queued
1717
+ // messages. setMessageHandler() fires onOpen as fire-and-forget;
1718
+ // awaiting _runMessageHandlerOpen() directly here closes the
1719
+ // race where queued subscribe/connection-* frames would
1720
+ // dispatch while an async onOpen is still setting up session
1721
+ // state.
1722
+ this.messageHandler = handler
1723
+ await this._runMessageHandlerOpen()
1724
+ await this._flushQueuedMessages({useHandler: typeof handler.onMessage === "function"})
1725
+ return
1726
+ }
1727
+ } catch (error) {
1728
+ this.logger.error(() => ["Websocket message handler resolver failed", error])
1729
+ }
1730
+
1731
+ this.pendingMessageHandler = false
1732
+ this.messageHandlerPromise = undefined
1733
+ await this._flushQueuedMessages({useHandler: false})
1734
+ }
1735
+
1736
+ /**
1737
+ * Runs flush queued messages.
1738
+ * @param {{useHandler: boolean}} args - Args.
1739
+ * @returns {Promise<void>} - Resolves when complete.
1740
+ */
1741
+ async _flushQueuedMessages({useHandler}) {
1742
+ if (this.messageQueue.length === 0) return
1743
+
1744
+ const queued = this.messageQueue.slice()
1745
+ this.messageQueue = []
1746
+
1747
+ for (const message of queued) {
1748
+ if (useHandler && this.messageHandler) {
1749
+ await this._runMessageHandlerMessage(message)
1750
+ } else {
1751
+ await this._handleMessage(message)
1752
+ }
1753
+ }
1754
+ }
1755
+ }