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