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