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,2245 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {resolveFrontendModelClass} from "./model-registry.js"
|
|
4
|
+
import {normalizeRansackGroup, parseRansackSort} from "../utils/ransack.js"
|
|
5
|
+
import {isModelScopeDescriptor} from "../utils/model-scope.js"
|
|
6
|
+
import isPlainObject from "../utils/plain-object.js"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* FrontendModelSearch type.
|
|
10
|
+
* @typedef {object} FrontendModelSearch
|
|
11
|
+
* @property {string} column - Attribute name to search.
|
|
12
|
+
* @property {"eq" | "like" | "notEq" | "gt" | "gteq" | "lt" | "lteq"} operator - Search operator.
|
|
13
|
+
* @property {string[]} path - Relationship path from root model.
|
|
14
|
+
* @property {?} value - Search value.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* FrontendModelTransportValue type.
|
|
18
|
+
* @typedef {null | boolean | number | string | object} FrontendModelTransportValue
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Defines this typedef.
|
|
22
|
+
* @typedef {{attributeName: string, relationshipName: string, where?: Record<string, FrontendModelTransportValue>}} FrontendModelWithCountPayloadEntry
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Defines this typedef.
|
|
26
|
+
* @typedef {{modelName: string, actions: string[]}} FrontendModelAbilitiesPayloadEntry
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* FrontendModelProjectionOptions type.
|
|
30
|
+
* @typedef {object} FrontendModelProjectionOptions
|
|
31
|
+
* @property {Record<string, string[] | string> | string | string[]} [select] - Model-aware attribute select map or root-model shorthand.
|
|
32
|
+
* @property {Record<string, string[] | string> | string | string[]} [selectsExtra] - Extra attributes to load in addition to the defaults, keyed by model name or root-model shorthand.
|
|
33
|
+
* @property {import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord>} [preload] - Relationship preload tree.
|
|
34
|
+
* @property {string | string[] | Record<string, boolean | {relationship?: string, where?: Record<string, FrontendModelTransportValue>}>} [withCount] - Association count spec.
|
|
35
|
+
* @property {string[] | Record<string, string[]>} [abilities] - Ability actions to compute per record.
|
|
36
|
+
* @property {string | Array<string | Record<string, FrontendModelTransportValue>> | Record<string, FrontendModelTransportValue>} [queryData] - Backend query data names/spec.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Defines this typedef.
|
|
40
|
+
* @typedef {FrontendModelProjectionOptions & {query?: FrontendModelQuery<typeof import("./base.js").default>}} FrontendModelEventOptionsObject
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* FrontendModelEventOptions type.
|
|
44
|
+
* @typedef {FrontendModelEventOptionsObject | FrontendModelQuery<typeof import("./base.js").default>} FrontendModelEventOptions
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* FrontendModelProjectionPayload type.
|
|
48
|
+
* @typedef {object} FrontendModelProjectionPayload
|
|
49
|
+
* @property {Record<string, string[]>} [select] - Normalized select map.
|
|
50
|
+
* @property {Record<string, string[]>} [selectsExtra] - Normalized extra select map.
|
|
51
|
+
* @property {import("../database/query/index.js").NestedPreloadRecord} [preload] - Normalized preload tree.
|
|
52
|
+
* @property {FrontendModelWithCountPayloadEntry[]} [withCount] - Normalized count specs.
|
|
53
|
+
* @property {FrontendModelAbilitiesPayloadEntry[]} [abilities] - Normalized ability specs.
|
|
54
|
+
* @property {FrontendModelTransportValue} [queryData] - Normalized queryData spec.
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* FrontendModelEventFilterPayload type.
|
|
58
|
+
* @typedef {object} FrontendModelEventFilterPayload
|
|
59
|
+
* @property {Record<string, FrontendModelTransportValue>} [joins] - Relationship joins needed for matching.
|
|
60
|
+
* @property {FrontendModelSearch[]} [searches] - Search predicates needed for matching.
|
|
61
|
+
* @property {Record<string, FrontendModelTransportValue>} [where] - Structured where predicates needed for matching.
|
|
62
|
+
*/
|
|
63
|
+
/**
|
|
64
|
+
* Defines this typedef.
|
|
65
|
+
* @typedef {FrontendModelEventFilterPayload & {key: string}} FrontendModelEventFilterPayloadEntry
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* FrontendModelEventOptionsPayload type.
|
|
69
|
+
* @typedef {object} FrontendModelEventOptionsPayload
|
|
70
|
+
* @property {string | null} eventFilterKey - Stable event filter key, or null when no filter is present.
|
|
71
|
+
* @property {FrontendModelEventFilterPayload | null} eventFilterPayload - Normalized event filter payload, or null when unfiltered.
|
|
72
|
+
* @property {FrontendModelProjectionPayload} projectionPayload - Normalized event serialization projection payload.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Runs the normalizePreload helper.
|
|
77
|
+
* @param {import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord> | boolean | undefined | null} preload - Preload shorthand.
|
|
78
|
+
* @returns {import("../database/query/index.js").NestedPreloadRecord} - Normalized preload.
|
|
79
|
+
*/
|
|
80
|
+
export function normalizePreload(preload) {
|
|
81
|
+
if (!preload) return {}
|
|
82
|
+
|
|
83
|
+
if (preload === true) return {}
|
|
84
|
+
|
|
85
|
+
if (typeof preload === "string") {
|
|
86
|
+
return {[preload]: true}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (Array.isArray(preload)) {
|
|
90
|
+
/**
|
|
91
|
+
* Normalized.
|
|
92
|
+
@type {import("../database/query/index.js").NestedPreloadRecord} */
|
|
93
|
+
const normalized = {}
|
|
94
|
+
|
|
95
|
+
for (const entry of preload) {
|
|
96
|
+
if (typeof entry === "string") {
|
|
97
|
+
normalized[entry] = true
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (isPlainObject(entry)) {
|
|
102
|
+
mergePreloadRecord(normalized, normalizePreload(entry))
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new Error(`Invalid preload entry type: ${typeof entry}`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return normalized
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!isPlainObject(preload)) {
|
|
113
|
+
throw new Error(`Invalid preload type: ${typeof preload}`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Normalized.
|
|
118
|
+
@type {import("../database/query/index.js").NestedPreloadRecord} */
|
|
119
|
+
const normalized = {}
|
|
120
|
+
|
|
121
|
+
for (const [relationshipName, relationshipPreload] of Object.entries(preload)) {
|
|
122
|
+
if (relationshipPreload === true || relationshipPreload === false) {
|
|
123
|
+
normalized[relationshipName] = relationshipPreload
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (typeof relationshipPreload === "string" || Array.isArray(relationshipPreload) || isPlainObject(relationshipPreload)) {
|
|
128
|
+
normalized[relationshipName] = normalizePreload(relationshipPreload)
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throw new Error(`Invalid preload value for ${relationshipName}: ${typeof relationshipPreload}`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return normalized
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Normalize the shorthand `withCount` argument from the frontend-model
|
|
140
|
+
* query API into the strict internal entries used in the transport
|
|
141
|
+
* payload. Shares the shape semantics with the backend normalizer in
|
|
142
|
+
* `database/query/with-count.js`.
|
|
143
|
+
* @param {string | string[] | Record<string, boolean | {relationship?: string, where?: Record<string, ?>}>} spec
|
|
144
|
+
* @returns {Array<{attributeName: string, relationshipName: string, where?: Record<string, ?>}>}
|
|
145
|
+
*/
|
|
146
|
+
function normalizeWithCountFrontend(spec) {
|
|
147
|
+
if (spec == null) return []
|
|
148
|
+
|
|
149
|
+
if (typeof spec === "string") {
|
|
150
|
+
return [{attributeName: `${spec}Count`, relationshipName: spec}]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (Array.isArray(spec)) {
|
|
154
|
+
return spec.flatMap((item) => {
|
|
155
|
+
if (typeof item !== "string") {
|
|
156
|
+
throw new Error(`withCount array entries must be strings; got ${typeof item}`)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return [{attributeName: `${item}Count`, relationshipName: item}]
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!isPlainObject(spec)) {
|
|
164
|
+
throw new Error(`Invalid withCount spec: ${typeof spec}`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const entries = []
|
|
168
|
+
|
|
169
|
+
for (const [key, value] of Object.entries(spec)) {
|
|
170
|
+
if (value === true) {
|
|
171
|
+
entries.push({attributeName: `${key}Count`, relationshipName: key})
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (value === false) continue
|
|
176
|
+
|
|
177
|
+
if (isPlainObject(value)) {
|
|
178
|
+
const options = /**
|
|
179
|
+
* Narrows the runtime value to the documented type.
|
|
180
|
+
@type {{relationship?: string, where?: Record<string, ?>}} */ (value)
|
|
181
|
+
entries.push({
|
|
182
|
+
attributeName: key,
|
|
183
|
+
relationshipName: options.relationship || key,
|
|
184
|
+
where: options.where
|
|
185
|
+
})
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
throw new Error(`Invalid withCount value for ${key}: ${typeof value}`)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return entries
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Normalize a frontend `.abilities(...)` spec into a flat list of
|
|
197
|
+
* `{modelName, actions}` entries. Accepts the flat actions-array
|
|
198
|
+
* shorthand (applies to the query's own model class) and the keyed
|
|
199
|
+
* `{ModelName: [action, ...]}` form (applies to records of that model
|
|
200
|
+
* class, useful for preloaded children).
|
|
201
|
+
* @param {string[] | Record<string, string[]>} spec
|
|
202
|
+
* @param {{getModelName: () => string}} rootModelClass
|
|
203
|
+
* @returns {Array<{modelName: string, actions: string[]}>}
|
|
204
|
+
*/
|
|
205
|
+
function normalizeAbilitiesSpec(spec, rootModelClass) {
|
|
206
|
+
if (spec == null) return []
|
|
207
|
+
|
|
208
|
+
if (Array.isArray(spec)) {
|
|
209
|
+
for (const action of spec) {
|
|
210
|
+
if (typeof action !== "string" || action.length < 1) {
|
|
211
|
+
throw new Error(`abilities flat-form actions must be non-empty strings; got ${typeof action}`)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const rootModelName = typeof rootModelClass?.getModelName === "function"
|
|
216
|
+
? rootModelClass.getModelName()
|
|
217
|
+
: undefined
|
|
218
|
+
if (!rootModelName) {
|
|
219
|
+
throw new Error("abilities flat-form requires a root model class with getModelName()")
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return [{actions: [...spec], modelName: rootModelName}]
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!isPlainObject(spec)) {
|
|
226
|
+
throw new Error(`Invalid abilities spec: ${typeof spec}`)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Entries.
|
|
231
|
+
@type {Array<{modelName: string, actions: string[]}>} */
|
|
232
|
+
const entries = []
|
|
233
|
+
|
|
234
|
+
for (const [modelName, actions] of Object.entries(spec)) {
|
|
235
|
+
if (!Array.isArray(actions)) {
|
|
236
|
+
throw new Error(`abilities[${modelName}] must be an array of action names; got ${typeof actions}`)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const sanitized = actions.map((action) => {
|
|
240
|
+
if (typeof action !== "string" || action.length < 1) {
|
|
241
|
+
throw new Error(`abilities[${modelName}] entries must be non-empty strings; got ${typeof action}`)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return action
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
entries.push({actions: sanitized, modelName})
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return entries
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Runs merge preload record.
|
|
255
|
+
* @param {import("../database/query/index.js").NestedPreloadRecord} targetPreload - Existing preload data.
|
|
256
|
+
* @param {import("../database/query/index.js").NestedPreloadRecord} incomingPreload - New preload data.
|
|
257
|
+
* @returns {void}
|
|
258
|
+
*/
|
|
259
|
+
function mergePreloadRecord(targetPreload, incomingPreload) {
|
|
260
|
+
for (const [relationshipName, incomingValue] of Object.entries(incomingPreload)) {
|
|
261
|
+
const existingValue = targetPreload[relationshipName]
|
|
262
|
+
|
|
263
|
+
if (incomingValue === false) {
|
|
264
|
+
targetPreload[relationshipName] = false
|
|
265
|
+
continue
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (incomingValue === true) {
|
|
269
|
+
if (existingValue === undefined) {
|
|
270
|
+
targetPreload[relationshipName] = true
|
|
271
|
+
}
|
|
272
|
+
continue
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!isPlainObject(incomingValue)) {
|
|
276
|
+
throw new Error(`Invalid preload value for ${relationshipName}: ${typeof incomingValue}`)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (isPlainObject(existingValue)) {
|
|
280
|
+
mergePreloadRecord(
|
|
281
|
+
/**
|
|
282
|
+
* Narrows the runtime value to the documented type.
|
|
283
|
+
@type {import("../database/query/index.js").NestedPreloadRecord} */ (existingValue),
|
|
284
|
+
/**
|
|
285
|
+
* Narrows the runtime value to the documented type.
|
|
286
|
+
@type {import("../database/query/index.js").NestedPreloadRecord} */ (incomingValue)
|
|
287
|
+
)
|
|
288
|
+
continue
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
targetPreload[relationshipName] = normalizePreload(incomingValue)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Runs normalize select.
|
|
297
|
+
* @param {?} select - Select payload.
|
|
298
|
+
* @param {string | null} [rootModelName] - Optional root model name for shorthand select payloads.
|
|
299
|
+
* @returns {Record<string, string[]>} - Normalized model-name keyed select record.
|
|
300
|
+
*/
|
|
301
|
+
function normalizeSelect(select, rootModelName = null) {
|
|
302
|
+
if (!select) return {}
|
|
303
|
+
|
|
304
|
+
if (typeof select === "string") {
|
|
305
|
+
if (!rootModelName) throw new Error("Invalid select shorthand without root model name")
|
|
306
|
+
|
|
307
|
+
return {[rootModelName]: [select]}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (Array.isArray(select)) {
|
|
311
|
+
if (!rootModelName) throw new Error("Invalid select shorthand without root model name")
|
|
312
|
+
|
|
313
|
+
for (const attributeName of select) {
|
|
314
|
+
if (typeof attributeName !== "string") {
|
|
315
|
+
throw new Error(`Invalid select attribute for ${rootModelName}: ${typeof attributeName}`)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return {[rootModelName]: Array.from(new Set(select))}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!isPlainObject(select)) {
|
|
323
|
+
throw new Error(`Invalid select type: ${typeof select}`)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Normalized.
|
|
328
|
+
@type {Record<string, string[]>} */
|
|
329
|
+
const normalized = {}
|
|
330
|
+
|
|
331
|
+
for (const [modelName, selection] of Object.entries(select)) {
|
|
332
|
+
if (typeof selection === "string") {
|
|
333
|
+
normalized[modelName] = [selection]
|
|
334
|
+
continue
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!Array.isArray(selection)) {
|
|
338
|
+
throw new Error(`Invalid select value for ${modelName}: ${typeof selection}`)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for (const attributeName of selection) {
|
|
342
|
+
if (typeof attributeName !== "string") {
|
|
343
|
+
throw new Error(`Invalid select attribute for ${modelName}: ${typeof attributeName}`)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
normalized[modelName] = Array.from(new Set(selection))
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return normalized
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Runs merge select record.
|
|
355
|
+
* @param {Record<string, string[]>} targetSelect - Existing select record.
|
|
356
|
+
* @param {Record<string, string[]>} incomingSelect - Incoming select record.
|
|
357
|
+
* @returns {void}
|
|
358
|
+
*/
|
|
359
|
+
function mergeSelectRecord(targetSelect, incomingSelect) {
|
|
360
|
+
for (const [modelName, incomingAttributes] of Object.entries(incomingSelect)) {
|
|
361
|
+
const existingAttributes = targetSelect[modelName] || []
|
|
362
|
+
|
|
363
|
+
targetSelect[modelName] = Array.from(new Set([...existingAttributes, ...incomingAttributes]))
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Runs the normalizeSearchOperator helper.
|
|
369
|
+
* @param {string} operator - Raw search operator.
|
|
370
|
+
* @returns {"eq" | "like" | "notEq" | "gt" | "gteq" | "lt" | "lteq"} - Normalized operator.
|
|
371
|
+
*/
|
|
372
|
+
export function normalizeSearchOperator(operator) {
|
|
373
|
+
const operatorAliases = {
|
|
374
|
+
"<": "lt",
|
|
375
|
+
"<=": "lteq",
|
|
376
|
+
">": "gt",
|
|
377
|
+
">=": "gteq"
|
|
378
|
+
}
|
|
379
|
+
const normalizedOperator = operatorAliases[/**
|
|
380
|
+
* Narrows the runtime value to the documented type.
|
|
381
|
+
@type {"<" | "<=" | ">" | ">="} */ (operator)] || operator
|
|
382
|
+
const supportedOperators = new Set(["eq", "like", "notEq", "gt", "gteq", "lt", "lteq"])
|
|
383
|
+
|
|
384
|
+
if (!supportedOperators.has(normalizedOperator)) {
|
|
385
|
+
throw new Error(`search operator must be one of: eq, like, notEq, gt, gteq, lt, lteq, >, >=, <, <= (got: ${operator})`)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return /** Narrows the runtime value to the documented type. @type {"eq" | "like" | "notEq" | "gt" | "gteq" | "lt" | "lteq"} */ (normalizedOperator)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Runs merge join record.
|
|
393
|
+
* @param {Record<string, ?>} targetJoins - Existing join record.
|
|
394
|
+
* @param {Record<string, ?>} incomingJoins - Incoming join record.
|
|
395
|
+
* @returns {void}
|
|
396
|
+
*/
|
|
397
|
+
function mergeJoinRecord(targetJoins, incomingJoins) {
|
|
398
|
+
for (const [relationshipName, incomingValue] of Object.entries(incomingJoins)) {
|
|
399
|
+
const existingValue = targetJoins[relationshipName]
|
|
400
|
+
|
|
401
|
+
if (incomingValue === true) {
|
|
402
|
+
if (existingValue === undefined) {
|
|
403
|
+
targetJoins[relationshipName] = true
|
|
404
|
+
}
|
|
405
|
+
continue
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!isPlainObject(incomingValue)) {
|
|
409
|
+
throw new Error(`Invalid join value for ${relationshipName}: ${typeof incomingValue}`)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (isPlainObject(existingValue)) {
|
|
413
|
+
mergeJoinRecord(existingValue, incomingValue)
|
|
414
|
+
continue
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (existingValue === true) {
|
|
418
|
+
targetJoins[relationshipName] = normalizeJoins(incomingValue)
|
|
419
|
+
continue
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
targetJoins[relationshipName] = normalizeJoins(incomingValue)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Runs the normalizeJoins helper.
|
|
428
|
+
* @param {?} joins - Join payload.
|
|
429
|
+
* @returns {Record<string, ?>} - Normalized relationship descriptor joins.
|
|
430
|
+
*/
|
|
431
|
+
export function normalizeJoins(joins) {
|
|
432
|
+
if (!joins) return {}
|
|
433
|
+
|
|
434
|
+
if (Array.isArray(joins)) {
|
|
435
|
+
/**
|
|
436
|
+
* Normalized.
|
|
437
|
+
@type {Record<string, ?>} */
|
|
438
|
+
const normalized = {}
|
|
439
|
+
|
|
440
|
+
for (const joinEntry of joins) {
|
|
441
|
+
if (!isPlainObject(joinEntry)) {
|
|
442
|
+
throw new Error(`Invalid joins entry type: ${typeof joinEntry}`)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
mergeJoinRecord(normalized, normalizeJoins(joinEntry))
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return normalized
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!isPlainObject(joins)) {
|
|
452
|
+
throw new Error(`Invalid joins type: ${typeof joins}`)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Normalized.
|
|
457
|
+
@type {Record<string, ?>} */
|
|
458
|
+
const normalized = {}
|
|
459
|
+
|
|
460
|
+
for (const [relationshipName, relationshipJoin] of Object.entries(joins)) {
|
|
461
|
+
if (relationshipJoin === true) {
|
|
462
|
+
normalized[relationshipName] = true
|
|
463
|
+
continue
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (isPlainObject(relationshipJoin)) {
|
|
467
|
+
normalized[relationshipName] = normalizeJoins(relationshipJoin)
|
|
468
|
+
continue
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
throw new Error(`Invalid join definition for "${relationshipName}": ${typeof relationshipJoin}`)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return normalized
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* FrontendModelSort type.
|
|
479
|
+
* @typedef {object} FrontendModelSort
|
|
480
|
+
* @property {string} column - Attribute name to sort by.
|
|
481
|
+
* @property {"asc" | "desc"} direction - Sort direction.
|
|
482
|
+
* @property {string[]} path - Relationship path from root model.
|
|
483
|
+
*/
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* FrontendModelGroup type.
|
|
487
|
+
* @typedef {object} FrontendModelGroup
|
|
488
|
+
* @property {string} column - Attribute name to group by.
|
|
489
|
+
* @property {string[]} path - Relationship path from root model.
|
|
490
|
+
*/
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* FrontendModelPluck type.
|
|
494
|
+
* @typedef {object} FrontendModelPluck
|
|
495
|
+
* @property {string} column - Attribute name to pluck.
|
|
496
|
+
* @property {string[]} path - Relationship path from root model.
|
|
497
|
+
*/
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Runs normalize sort direction.
|
|
501
|
+
* @param {?} direction - Direction value.
|
|
502
|
+
* @returns {"asc" | "desc"} - Normalized direction.
|
|
503
|
+
*/
|
|
504
|
+
function normalizeSortDirection(direction) {
|
|
505
|
+
if (typeof direction !== "string") {
|
|
506
|
+
throw new Error(`Invalid sort direction type: ${typeof direction}`)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const normalizedDirection = direction.trim().toLowerCase()
|
|
510
|
+
|
|
511
|
+
if (normalizedDirection !== "asc" && normalizedDirection !== "desc") {
|
|
512
|
+
throw new Error(`Invalid sort direction: ${direction}`)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return normalizedDirection
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Check whether a value is a two-item `[column, direction]` sort tuple.
|
|
520
|
+
* @param {?} value - Candidate tuple.
|
|
521
|
+
* @returns {value is [string, string]} - Whether value is a sort tuple.
|
|
522
|
+
*/
|
|
523
|
+
function sortTuple(value) {
|
|
524
|
+
if (!Array.isArray(value)) return false
|
|
525
|
+
if (value.length !== 2) return false
|
|
526
|
+
if (typeof value[0] !== "string") return false
|
|
527
|
+
if (typeof value[1] !== "string") return false
|
|
528
|
+
if (value[0].trim().length < 1) return false
|
|
529
|
+
|
|
530
|
+
const direction = value[1].trim().toLowerCase()
|
|
531
|
+
|
|
532
|
+
return direction === "asc" || direction === "desc"
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Check whether a value is a structured sort descriptor with a relationship path.
|
|
537
|
+
* @param {?} value - Candidate descriptor.
|
|
538
|
+
* @returns {value is {column: string, direction: string, path: string[]}} - Whether value is an explicit sort descriptor object.
|
|
539
|
+
*/
|
|
540
|
+
function sortDescriptor(value) {
|
|
541
|
+
if (!isPlainObject(value)) return false
|
|
542
|
+
if (!("column" in value) || !("direction" in value) || !("path" in value)) return false
|
|
543
|
+
if (typeof value.column !== "string") return false
|
|
544
|
+
if (typeof value.direction !== "string") return false
|
|
545
|
+
if (!Array.isArray(value.path)) return false
|
|
546
|
+
|
|
547
|
+
return value.path.every((pathEntry) => typeof pathEntry === "string")
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Parse a string shorthand into a sort descriptor.
|
|
552
|
+
* @param {string} sortValue - Sort string.
|
|
553
|
+
* @param {string[]} [path] - Relationship path.
|
|
554
|
+
* @returns {FrontendModelSort} - Normalized sort descriptor.
|
|
555
|
+
*/
|
|
556
|
+
function parseSortString(sortValue, path = []) {
|
|
557
|
+
const trimmed = sortValue.trim()
|
|
558
|
+
|
|
559
|
+
if (trimmed.length < 1) {
|
|
560
|
+
throw new Error("sort value must be a non-empty string")
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (trimmed.startsWith("-")) {
|
|
564
|
+
const column = trimmed.slice(1).trim()
|
|
565
|
+
|
|
566
|
+
if (column.length < 1) {
|
|
567
|
+
throw new Error(`Invalid sort definition: ${sortValue}`)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
column,
|
|
572
|
+
direction: "desc",
|
|
573
|
+
path: [...path]
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const sortParts = trimmed.split(/\s+/).filter(Boolean)
|
|
578
|
+
|
|
579
|
+
if (sortParts.length > 2) {
|
|
580
|
+
throw new Error(`Invalid sort definition: ${sortValue}`)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const column = sortParts[0]
|
|
584
|
+
|
|
585
|
+
if (column.length < 1) {
|
|
586
|
+
throw new Error(`Invalid sort definition: ${sortValue}`)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const direction = sortParts.length === 2
|
|
590
|
+
? normalizeSortDirection(sortParts[1])
|
|
591
|
+
: "asc"
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
column,
|
|
595
|
+
direction,
|
|
596
|
+
path: [...path]
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Parse a tuple shorthand into a sort descriptor.
|
|
602
|
+
* @param {[string, string]} sortValue - Sort tuple.
|
|
603
|
+
* @param {string[]} [path] - Relationship path.
|
|
604
|
+
* @returns {FrontendModelSort} - Normalized sort descriptor.
|
|
605
|
+
*/
|
|
606
|
+
function parseSortTuple(sortValue, path = []) {
|
|
607
|
+
const [columnValue, directionValue] = sortValue
|
|
608
|
+
const column = columnValue.trim()
|
|
609
|
+
|
|
610
|
+
if (column.length < 1) {
|
|
611
|
+
throw new Error("sort tuple column must be a non-empty string")
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
column,
|
|
616
|
+
direction: normalizeSortDirection(directionValue),
|
|
617
|
+
path: [...path]
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Normalize a nested object sort payload into flat sort descriptors.
|
|
623
|
+
* @param {Record<string, ?>} sortValue - Nested sort object.
|
|
624
|
+
* @param {string[]} path - Relationship path.
|
|
625
|
+
* @returns {FrontendModelSort[]} - Normalized sort descriptors.
|
|
626
|
+
*/
|
|
627
|
+
function normalizeSortObject(sortValue, path) {
|
|
628
|
+
/**
|
|
629
|
+
* Normalized sorts.
|
|
630
|
+
@type {FrontendModelSort[]} */
|
|
631
|
+
const normalizedSorts = []
|
|
632
|
+
|
|
633
|
+
for (const [sortKey, sortEntry] of Object.entries(sortValue)) {
|
|
634
|
+
if (typeof sortEntry === "string") {
|
|
635
|
+
normalizedSorts.push({
|
|
636
|
+
column: sortKey,
|
|
637
|
+
direction: normalizeSortDirection(sortEntry),
|
|
638
|
+
path: [...path]
|
|
639
|
+
})
|
|
640
|
+
continue
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (sortTuple(sortEntry)) {
|
|
644
|
+
normalizedSorts.push(parseSortTuple(sortEntry, [...path, sortKey]))
|
|
645
|
+
continue
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (Array.isArray(sortEntry)) {
|
|
649
|
+
if (sortEntry.length < 1) {
|
|
650
|
+
throw new Error(`Invalid sort definition for "${sortKey}": empty array`)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
for (const nestedSortEntry of sortEntry) {
|
|
654
|
+
if (!sortTuple(nestedSortEntry)) {
|
|
655
|
+
throw new Error(`Invalid sort definition for "${sortKey}": expected [column, direction] tuples`)
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
normalizedSorts.push(parseSortTuple(nestedSortEntry, [...path, sortKey]))
|
|
659
|
+
}
|
|
660
|
+
continue
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (isPlainObject(sortEntry)) {
|
|
664
|
+
normalizedSorts.push(...normalizeSortObject(sortEntry, [...path, sortKey]))
|
|
665
|
+
continue
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
throw new Error(`Invalid sort definition for "${sortKey}": ${typeof sortEntry}`)
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return normalizedSorts
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Normalize any supported sort payload into flat sort descriptors.
|
|
676
|
+
* @param {?} sort - Sort payload.
|
|
677
|
+
* @returns {FrontendModelSort[]} - Normalized sort definitions.
|
|
678
|
+
*/
|
|
679
|
+
export function normalizeSort(sort) {
|
|
680
|
+
if (!sort) return []
|
|
681
|
+
|
|
682
|
+
if (typeof sort === "string") {
|
|
683
|
+
return [parseSortString(sort)]
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (sortTuple(sort)) {
|
|
687
|
+
return [parseSortTuple(sort)]
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (sortDescriptor(sort)) {
|
|
691
|
+
return [{
|
|
692
|
+
column: sort.column.trim(),
|
|
693
|
+
direction: normalizeSortDirection(sort.direction),
|
|
694
|
+
path: [...sort.path]
|
|
695
|
+
}]
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (isPlainObject(sort)) {
|
|
699
|
+
return normalizeSortObject(sort, [])
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (Array.isArray(sort)) {
|
|
703
|
+
/**
|
|
704
|
+
* Normalized.
|
|
705
|
+
@type {FrontendModelSort[]} */
|
|
706
|
+
const normalized = []
|
|
707
|
+
|
|
708
|
+
for (const sortEntry of sort) {
|
|
709
|
+
if (typeof sortEntry === "string") {
|
|
710
|
+
normalized.push(parseSortString(sortEntry))
|
|
711
|
+
continue
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (sortTuple(sortEntry)) {
|
|
715
|
+
normalized.push(parseSortTuple(sortEntry))
|
|
716
|
+
continue
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (sortDescriptor(sortEntry)) {
|
|
720
|
+
normalized.push({
|
|
721
|
+
column: sortEntry.column.trim(),
|
|
722
|
+
direction: normalizeSortDirection(sortEntry.direction),
|
|
723
|
+
path: [...sortEntry.path]
|
|
724
|
+
})
|
|
725
|
+
continue
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (isPlainObject(sortEntry)) {
|
|
729
|
+
normalized.push(...normalizeSortObject(sortEntry, []))
|
|
730
|
+
continue
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
throw new Error(`Invalid sort entry type: ${typeof sortEntry}`)
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return normalized
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
throw new Error(`Invalid sort type: ${typeof sort}`)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Parse a string shorthand into a group descriptor.
|
|
744
|
+
* @param {string} groupValue - Group string.
|
|
745
|
+
* @param {string[]} [path] - Relationship path.
|
|
746
|
+
* @returns {FrontendModelGroup} - Normalized group descriptor.
|
|
747
|
+
*/
|
|
748
|
+
function parseGroupString(groupValue, path = []) {
|
|
749
|
+
const trimmed = groupValue.trim()
|
|
750
|
+
|
|
751
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
|
|
752
|
+
throw new Error(`Invalid group column: ${groupValue}`)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return {
|
|
756
|
+
column: trimmed,
|
|
757
|
+
path: [...path]
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Check whether a value is a structured column/path descriptor.
|
|
763
|
+
* @param {?} value - Candidate descriptor.
|
|
764
|
+
* @returns {value is {column: string, path: string[]}} - Whether candidate is an explicit column descriptor object.
|
|
765
|
+
*/
|
|
766
|
+
function columnPathDescriptor(value) {
|
|
767
|
+
if (!isPlainObject(value)) return false
|
|
768
|
+
if (!("column" in value) || !("path" in value)) return false
|
|
769
|
+
if (typeof value.column !== "string") return false
|
|
770
|
+
if (!Array.isArray(value.path)) return false
|
|
771
|
+
|
|
772
|
+
return value.path.every((pathEntry) => typeof pathEntry === "string")
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Normalize a nested object column projection payload into flat descriptors.
|
|
777
|
+
* @template {{column: string, path: string[]}} T
|
|
778
|
+
* @param {Record<string, ?>} value - Nested projection object.
|
|
779
|
+
* @param {string[]} path - Relationship path.
|
|
780
|
+
* @param {(columnValue: string, path?: string[]) => T} parseString - String projection parser.
|
|
781
|
+
* @param {string} label - Projection label for errors.
|
|
782
|
+
* @returns {T[]} - Normalized projection descriptors.
|
|
783
|
+
*/
|
|
784
|
+
function normalizeColumnProjectionObject(value, path, parseString, label) {
|
|
785
|
+
/**
|
|
786
|
+
* Normalized.
|
|
787
|
+
@type {T[]} */
|
|
788
|
+
const normalized = []
|
|
789
|
+
|
|
790
|
+
for (const [projectionKey, projectionEntry] of Object.entries(value)) {
|
|
791
|
+
if (typeof projectionEntry === "string") {
|
|
792
|
+
normalized.push(parseString(projectionEntry, [...path, projectionKey]))
|
|
793
|
+
continue
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (Array.isArray(projectionEntry)) {
|
|
797
|
+
if (projectionEntry.length < 1) {
|
|
798
|
+
throw new Error(`Invalid ${label} definition for "${projectionKey}": empty array`)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
for (const nestedProjectionEntry of projectionEntry) {
|
|
802
|
+
if (typeof nestedProjectionEntry !== "string") {
|
|
803
|
+
throw new Error(`Invalid ${label} definition for "${projectionKey}": expected string columns`)
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
normalized.push(parseString(nestedProjectionEntry, [...path, projectionKey]))
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
continue
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (isPlainObject(projectionEntry)) {
|
|
813
|
+
normalized.push(...normalizeColumnProjectionObject(projectionEntry, [...path, projectionKey], parseString, label))
|
|
814
|
+
continue
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
throw new Error(`Invalid ${label} definition for "${projectionKey}": ${typeof projectionEntry}`)
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
return normalized
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Normalize any supported group payload into flat group descriptors.
|
|
825
|
+
* @param {?} group - Group payload.
|
|
826
|
+
* @returns {FrontendModelGroup[]} - Normalized group definitions.
|
|
827
|
+
*/
|
|
828
|
+
export function normalizeGroup(group) {
|
|
829
|
+
if (!group) return []
|
|
830
|
+
|
|
831
|
+
if (typeof group === "string") {
|
|
832
|
+
return [parseGroupString(group)]
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (columnPathDescriptor(group)) {
|
|
836
|
+
return [{
|
|
837
|
+
column: parseGroupString(group.column).column,
|
|
838
|
+
path: [...group.path]
|
|
839
|
+
}]
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (isPlainObject(group)) {
|
|
843
|
+
return normalizeColumnProjectionObject(group, [], parseGroupString, "group")
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (Array.isArray(group)) {
|
|
847
|
+
/**
|
|
848
|
+
* Normalized.
|
|
849
|
+
@type {FrontendModelGroup[]} */
|
|
850
|
+
const normalized = []
|
|
851
|
+
|
|
852
|
+
for (const groupEntry of group) {
|
|
853
|
+
if (typeof groupEntry === "string") {
|
|
854
|
+
normalized.push(parseGroupString(groupEntry))
|
|
855
|
+
continue
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (columnPathDescriptor(groupEntry)) {
|
|
859
|
+
normalized.push({
|
|
860
|
+
column: parseGroupString(groupEntry.column).column,
|
|
861
|
+
path: [...groupEntry.path]
|
|
862
|
+
})
|
|
863
|
+
continue
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (isPlainObject(groupEntry)) {
|
|
867
|
+
normalized.push(...normalizeColumnProjectionObject(groupEntry, [], parseGroupString, "group"))
|
|
868
|
+
continue
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
throw new Error(`Invalid group entry type: ${typeof groupEntry}`)
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return normalized
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
throw new Error(`Invalid group type: ${typeof group}`)
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Parse a string shorthand into a pluck descriptor.
|
|
882
|
+
* @param {string} pluckValue - Pluck string.
|
|
883
|
+
* @param {string[]} [path] - Relationship path.
|
|
884
|
+
* @returns {FrontendModelPluck} - Normalized pluck descriptor.
|
|
885
|
+
*/
|
|
886
|
+
function parsePluckString(pluckValue, path = []) {
|
|
887
|
+
const trimmed = pluckValue.trim()
|
|
888
|
+
|
|
889
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(trimmed)) {
|
|
890
|
+
throw new Error(`Invalid pluck column: ${pluckValue}`)
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
return {
|
|
894
|
+
column: trimmed,
|
|
895
|
+
path: [...path]
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Normalize any supported pluck payload into flat pluck descriptors.
|
|
901
|
+
* @param {?} pluck - Pluck payload.
|
|
902
|
+
* @returns {FrontendModelPluck[]} - Normalized pluck definitions.
|
|
903
|
+
*/
|
|
904
|
+
export function normalizePluck(pluck) {
|
|
905
|
+
if (!pluck) return []
|
|
906
|
+
|
|
907
|
+
if (typeof pluck === "string") {
|
|
908
|
+
return [parsePluckString(pluck)]
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (columnPathDescriptor(pluck)) {
|
|
912
|
+
return [{
|
|
913
|
+
column: parsePluckString(pluck.column).column,
|
|
914
|
+
path: [...pluck.path]
|
|
915
|
+
}]
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (isPlainObject(pluck)) {
|
|
919
|
+
return normalizeColumnProjectionObject(pluck, [], parsePluckString, "pluck")
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (Array.isArray(pluck)) {
|
|
923
|
+
/**
|
|
924
|
+
* Normalized.
|
|
925
|
+
@type {FrontendModelPluck[]} */
|
|
926
|
+
const normalized = []
|
|
927
|
+
|
|
928
|
+
for (const pluckEntry of pluck) {
|
|
929
|
+
if (typeof pluckEntry === "string") {
|
|
930
|
+
normalized.push(parsePluckString(pluckEntry))
|
|
931
|
+
continue
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (columnPathDescriptor(pluckEntry)) {
|
|
935
|
+
normalized.push({
|
|
936
|
+
column: parsePluckString(pluckEntry.column).column,
|
|
937
|
+
path: [...pluckEntry.path]
|
|
938
|
+
})
|
|
939
|
+
continue
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (isPlainObject(pluckEntry)) {
|
|
943
|
+
normalized.push(...normalizeColumnProjectionObject(pluckEntry, [], parsePluckString, "pluck"))
|
|
944
|
+
continue
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
throw new Error(`Invalid pluck entry type: ${typeof pluckEntry}`)
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return normalized
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
throw new Error(`Invalid pluck type: ${typeof pluck}`)
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Runs frontend model resource attributes.
|
|
958
|
+
* @param {typeof import("./base.js").default} modelClass - Model class.
|
|
959
|
+
* @returns {Set<string>} - Resource attribute names.
|
|
960
|
+
*/
|
|
961
|
+
function frontendModelResourceAttributes(modelClass) {
|
|
962
|
+
const resourceConfig = /**
|
|
963
|
+
* Narrows the runtime value to the documented type.
|
|
964
|
+
@type {Record<string, ?>} */ (modelClass.resourceConfig())
|
|
965
|
+
const attributes = resourceConfig.attributes
|
|
966
|
+
|
|
967
|
+
if (Array.isArray(attributes)) {
|
|
968
|
+
return new Set(attributes)
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
if (isPlainObject(attributes)) {
|
|
972
|
+
return new Set(Object.keys(attributes))
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
return new Set()
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Runs frontend model pluck target model class.
|
|
980
|
+
* @param {typeof import("./base.js").default} modelClass - Root model class.
|
|
981
|
+
* @param {string[]} path - Relationship path.
|
|
982
|
+
* @returns {typeof import("./base.js").default} - Target model class for path.
|
|
983
|
+
*/
|
|
984
|
+
function frontendModelPluckTargetModelClass(modelClass, path) {
|
|
985
|
+
let targetModelClass = modelClass
|
|
986
|
+
|
|
987
|
+
for (const relationshipName of path) {
|
|
988
|
+
const relationshipDefinitions = typeof targetModelClass.relationshipDefinitions === "function"
|
|
989
|
+
? targetModelClass.relationshipDefinitions()
|
|
990
|
+
: {}
|
|
991
|
+
const relationshipModelClasses = typeof targetModelClass.relationshipModelClasses === "function"
|
|
992
|
+
? targetModelClass.relationshipModelClasses()
|
|
993
|
+
: {}
|
|
994
|
+
const relationshipDefinition = relationshipDefinitions[relationshipName]
|
|
995
|
+
const relationshipTargetModelClass = resolveFrontendModelClass(relationshipModelClasses[relationshipName])
|
|
996
|
+
|
|
997
|
+
if (!relationshipDefinition) {
|
|
998
|
+
throw new Error(`Unknown pluck relationship "${relationshipName}" for ${targetModelClass.name}`)
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (!relationshipTargetModelClass) {
|
|
1002
|
+
throw new Error(`No relationship model class configured for ${targetModelClass.name}#${relationshipName}`)
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
targetModelClass = relationshipTargetModelClass
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
return targetModelClass
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Runs validate pluck definitions.
|
|
1013
|
+
* @param {object} args - Pluck validation args.
|
|
1014
|
+
* @param {typeof import("./base.js").default} args.modelClass - Root model class.
|
|
1015
|
+
* @param {FrontendModelPluck[]} args.pluck - Pluck descriptors.
|
|
1016
|
+
* @returns {FrontendModelPluck[]} - Validated pluck descriptors.
|
|
1017
|
+
*/
|
|
1018
|
+
function validatePluckDefinitions({modelClass, pluck}) {
|
|
1019
|
+
return pluck.map((pluckEntry) => {
|
|
1020
|
+
const targetModelClass = frontendModelPluckTargetModelClass(modelClass, pluckEntry.path)
|
|
1021
|
+
const targetAttributes = frontendModelResourceAttributes(targetModelClass)
|
|
1022
|
+
|
|
1023
|
+
if (!targetAttributes.has(pluckEntry.column)) {
|
|
1024
|
+
throw new Error(`Unknown pluck column "${pluckEntry.column}" for ${targetModelClass.name}`)
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
return {
|
|
1028
|
+
column: pluckEntry.column,
|
|
1029
|
+
path: [...pluckEntry.path]
|
|
1030
|
+
}
|
|
1031
|
+
})
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Runs serialize find conditions.
|
|
1036
|
+
* @param {Record<string, ?>} conditions - findBy conditions.
|
|
1037
|
+
* @returns {string} - Serialized conditions for error messages.
|
|
1038
|
+
*/
|
|
1039
|
+
function serializeFindConditions(conditions) {
|
|
1040
|
+
try {
|
|
1041
|
+
return JSON.stringify(conditions)
|
|
1042
|
+
} catch {
|
|
1043
|
+
return "[unserializable conditions]"
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Runs normalize integer argument.
|
|
1049
|
+
* @param {?} value - Candidate integer value.
|
|
1050
|
+
* @param {string} argumentName - Argument name for errors.
|
|
1051
|
+
* @param {object} options - Validation options.
|
|
1052
|
+
* @param {number} options.min - Minimum allowed value.
|
|
1053
|
+
* @returns {number} - Normalized integer value.
|
|
1054
|
+
*/
|
|
1055
|
+
function normalizeIntegerArgument(value, argumentName, {min}) {
|
|
1056
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
1057
|
+
throw new Error(`${argumentName} must be an integer number`)
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (value < min) {
|
|
1061
|
+
throw new Error(`${argumentName} must be greater than or equal to ${min}`)
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
return value
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Runs reverse sort direction.
|
|
1069
|
+
* @param {"asc" | "desc"} direction - Current sort direction.
|
|
1070
|
+
* @returns {"asc" | "desc"} - Reversed direction.
|
|
1071
|
+
*/
|
|
1072
|
+
function reverseSortDirection(direction) {
|
|
1073
|
+
return direction === "asc" ? "desc" : "asc"
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Query wrapper for frontend model commands.
|
|
1078
|
+
* @template {typeof import("./base.js").default} T
|
|
1079
|
+
*/
|
|
1080
|
+
export default class FrontendModelQuery {
|
|
1081
|
+
/**
|
|
1082
|
+
* Ransack.
|
|
1083
|
+
@type {Record<string, ?>[]} */
|
|
1084
|
+
_ransack = []
|
|
1085
|
+
/**
|
|
1086
|
+
* Searches.
|
|
1087
|
+
@type {FrontendModelSearch[]} */
|
|
1088
|
+
_searches = []
|
|
1089
|
+
/**
|
|
1090
|
+
* Sort.
|
|
1091
|
+
@type {FrontendModelSort[]} */
|
|
1092
|
+
_sort = []
|
|
1093
|
+
/**
|
|
1094
|
+
* Group.
|
|
1095
|
+
@type {FrontendModelGroup[]} */
|
|
1096
|
+
_group = []
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Runs constructor.
|
|
1100
|
+
* @param {object} args - Constructor args.
|
|
1101
|
+
* @param {T} args.modelClass - Frontend model class.
|
|
1102
|
+
* @param {import("../database/query/index.js").NestedPreloadRecord} [args.preload] - Preload map.
|
|
1103
|
+
*/
|
|
1104
|
+
constructor({modelClass, preload = {}}) {
|
|
1105
|
+
this.modelClass = modelClass
|
|
1106
|
+
this._preload = normalizePreload(preload)
|
|
1107
|
+
this._joins = {}
|
|
1108
|
+
this._where = {}
|
|
1109
|
+
this._searches = []
|
|
1110
|
+
/**
|
|
1111
|
+
* Narrows the runtime value to the documented type.
|
|
1112
|
+
@type {Record<string, string[]>} */
|
|
1113
|
+
this._select = {}
|
|
1114
|
+
/**
|
|
1115
|
+
* Narrows the runtime value to the documented type.
|
|
1116
|
+
@type {Record<string, string[]>} */
|
|
1117
|
+
this._selectsExtra = {}
|
|
1118
|
+
this._sort = []
|
|
1119
|
+
this._group = []
|
|
1120
|
+
this._distinct = false
|
|
1121
|
+
this._limit = null
|
|
1122
|
+
this._offset = null
|
|
1123
|
+
this._page = null
|
|
1124
|
+
this._perPage = null
|
|
1125
|
+
/**
|
|
1126
|
+
* Narrows the runtime value to the documented type.
|
|
1127
|
+
@type {Array<{attributeName: string, relationshipName: string, where?: Record<string, ?>}>} */
|
|
1128
|
+
this._withCount = []
|
|
1129
|
+
/**
|
|
1130
|
+
* Narrows the runtime value to the documented type.
|
|
1131
|
+
@type {Array<string | Record<string, ?>>} */
|
|
1132
|
+
this._queryData = []
|
|
1133
|
+
/**
|
|
1134
|
+
* Per-record ability spec. Normalized to a list of
|
|
1135
|
+
* `{modelName, actions}` entries — one entry per model that should
|
|
1136
|
+
* have ability results attached. The root query's model class
|
|
1137
|
+
* name is implicit via `"__root__"` when the caller used the flat
|
|
1138
|
+
* array form.
|
|
1139
|
+
* @type {Array<{modelName: string, actions: string[]}>}
|
|
1140
|
+
*/
|
|
1141
|
+
this._abilities = []
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Tell the backend to evaluate one or more ability actions against
|
|
1146
|
+
* each returned record (and its preloaded relations, when keyed by
|
|
1147
|
+
* model name) and ship the results back so the frontend can read
|
|
1148
|
+
* them via `record.can(action)`.
|
|
1149
|
+
*
|
|
1150
|
+
* Flat form — applies to the query's own model class:
|
|
1151
|
+
* ```
|
|
1152
|
+
* const timelogs = await Timelog.where({taskId})
|
|
1153
|
+
* .abilities(["update", "destroy"])
|
|
1154
|
+
* .toArray()
|
|
1155
|
+
* timelogs[0].can("update") // → boolean
|
|
1156
|
+
* ```
|
|
1157
|
+
*
|
|
1158
|
+
* Keyed form — targets records by model name, useful for preloaded
|
|
1159
|
+
* children:
|
|
1160
|
+
* ```
|
|
1161
|
+
* const project = await Project
|
|
1162
|
+
* .preload("timelogs")
|
|
1163
|
+
* .abilities({Timelog: ["update", "destroy"]})
|
|
1164
|
+
* .first()
|
|
1165
|
+
* project.timelogs().loaded()[0].can("update") // → boolean
|
|
1166
|
+
* ```
|
|
1167
|
+
*
|
|
1168
|
+
* Keys in the keyed form are the backend model names (as returned by
|
|
1169
|
+
* `ModelClass.getModelName()` / the `modelName` field of the
|
|
1170
|
+
* frontend-model resource config). Values are the ability-action
|
|
1171
|
+
* strings — typically `"update"` / `"destroy"` / `"create"` /
|
|
1172
|
+
* `"read"`, but any custom action registered on the resource's
|
|
1173
|
+
* authorization ability is accepted.
|
|
1174
|
+
* @param {string[] | Record<string, string[]>} spec
|
|
1175
|
+
* @returns {this}
|
|
1176
|
+
*/
|
|
1177
|
+
abilities(spec) {
|
|
1178
|
+
for (const entry of normalizeAbilitiesSpec(spec, this.modelClass)) {
|
|
1179
|
+
this._mergeAbilityEntry(entry)
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
return this
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Runs merge ability entry.
|
|
1187
|
+
* @param {{modelName: string, actions: string[]}} entry
|
|
1188
|
+
* @returns {void}
|
|
1189
|
+
*/
|
|
1190
|
+
_mergeAbilityEntry(entry) {
|
|
1191
|
+
const existing = this._abilities.find((candidate) => candidate.modelName === entry.modelName)
|
|
1192
|
+
|
|
1193
|
+
if (!existing) {
|
|
1194
|
+
this._abilities.push({actions: [...entry.actions], modelName: entry.modelName})
|
|
1195
|
+
return
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
for (const action of entry.actions) {
|
|
1199
|
+
if (!existing.actions.includes(action)) existing.actions.push(action)
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* Tell the backend index query to attach one or more association
|
|
1205
|
+
* counts to each returned record. Parses the same shapes as the
|
|
1206
|
+
* backend `ModelClassQuery#withCount`, then ships the normalized
|
|
1207
|
+
* entries as part of the `index` command payload.
|
|
1208
|
+
* @param {string | string[] | Record<string, boolean | {relationship?: string, where?: Record<string, ?>}>} spec
|
|
1209
|
+
* @returns {this}
|
|
1210
|
+
*/
|
|
1211
|
+
withCount(spec) {
|
|
1212
|
+
for (const entry of normalizeWithCountFrontend(spec)) {
|
|
1213
|
+
this._withCount.push(entry)
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
return this
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* Request one or more backend queryData entries for each returned
|
|
1221
|
+
* record. The spec is a name or nested-record shape matching the
|
|
1222
|
+
* `Model.queryData(name, fn)` registrations on the backend — the
|
|
1223
|
+
* frontend ships only these names; the SQL fragments stay server-
|
|
1224
|
+
* side. All resulting aliases are attached to the root record and
|
|
1225
|
+
* read back with `record.queryData(aliasName)`.
|
|
1226
|
+
* @param {string | Array<string | Record<string, ?>> | Record<string, ?>} spec
|
|
1227
|
+
* @returns {this}
|
|
1228
|
+
*/
|
|
1229
|
+
queryData(spec) {
|
|
1230
|
+
if (spec == null) return this
|
|
1231
|
+
|
|
1232
|
+
this._queryData.push(/**
|
|
1233
|
+
* Narrows the runtime value to the documented type.
|
|
1234
|
+
@type {?} */ (spec))
|
|
1235
|
+
|
|
1236
|
+
return this
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/**
|
|
1240
|
+
* Runs where.
|
|
1241
|
+
* @param {Record<string, ?>} conditions - Root-model where conditions.
|
|
1242
|
+
* @returns {this} - Query with merged where conditions.
|
|
1243
|
+
*/
|
|
1244
|
+
where(conditions) {
|
|
1245
|
+
this.modelClass.assertFindByConditions(conditions)
|
|
1246
|
+
|
|
1247
|
+
this._where = {
|
|
1248
|
+
...this._where,
|
|
1249
|
+
...conditions
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
return this
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Runs scope.
|
|
1257
|
+
* @param {import("../utils/model-scope.js").ModelScopeDescriptor} scopeDescriptor - Scope descriptor.
|
|
1258
|
+
* @returns {this} - Scoped query.
|
|
1259
|
+
*/
|
|
1260
|
+
scope(scopeDescriptor) {
|
|
1261
|
+
if (!isModelScopeDescriptor(scopeDescriptor)) {
|
|
1262
|
+
throw new Error("scope() expects a descriptor returned by defineScope(...).scope(...)")
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
if (scopeDescriptor.modelClass !== this.modelClass) {
|
|
1266
|
+
throw new Error(`Cannot apply ${scopeDescriptor.modelClass.name} scope to ${this.modelClass.name} query`)
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const scopedQuery = /**
|
|
1270
|
+
* Narrows the runtime value to the documented type.
|
|
1271
|
+
@type {this | void} */ (scopeDescriptor.callback({
|
|
1272
|
+
driver: null,
|
|
1273
|
+
modelClass: this.modelClass,
|
|
1274
|
+
query: this,
|
|
1275
|
+
table: null
|
|
1276
|
+
}, ...scopeDescriptor.scopeArgs))
|
|
1277
|
+
|
|
1278
|
+
return scopedQuery || this
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* Runs ransack.
|
|
1283
|
+
* @param {Record<string, ?>} params - Ransack-style params hash. Supports `s` key for sorting (e.g., `{s: "name asc"}`).
|
|
1284
|
+
* @returns {this} - Query with Ransack filters and sort applied.
|
|
1285
|
+
*/
|
|
1286
|
+
ransack(params) {
|
|
1287
|
+
const {s, ...filterParams} = params
|
|
1288
|
+
const hasFilters = Object.keys(filterParams).length > 0
|
|
1289
|
+
|
|
1290
|
+
if (hasFilters) {
|
|
1291
|
+
normalizeRansackGroup(this.modelClass, filterParams)
|
|
1292
|
+
this._ransack.push(filterParams)
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (typeof s === "string" && s.trim().length > 0) {
|
|
1296
|
+
const sorts = parseRansackSort(this.modelClass, s)
|
|
1297
|
+
|
|
1298
|
+
for (const sortDef of sorts) {
|
|
1299
|
+
this.sort([[sortDef.attribute, sortDef.direction]])
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
return this
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* Runs select with required root attributes.
|
|
1308
|
+
* @param {string[]} [requiredAttributes] - Extra required attributes for the root model.
|
|
1309
|
+
* @returns {Record<string, string[]>} - Select map with required root attributes merged when root select exists.
|
|
1310
|
+
*/
|
|
1311
|
+
selectWithRequiredRootAttributes(requiredAttributes = []) {
|
|
1312
|
+
const rootModelName = this.modelClass.getModelName()
|
|
1313
|
+
const selectMap = /**
|
|
1314
|
+
* Narrows the runtime value to the documented type.
|
|
1315
|
+
@type {Record<string, string[]>} */ (this._select)
|
|
1316
|
+
const existingRootAttributes = selectMap[rootModelName]
|
|
1317
|
+
|
|
1318
|
+
if (!existingRootAttributes) {
|
|
1319
|
+
return selectMap
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
const rootPrimaryKey = this.modelClass.primaryKey()
|
|
1323
|
+
|
|
1324
|
+
return {
|
|
1325
|
+
...selectMap,
|
|
1326
|
+
[rootModelName]: Array.from(new Set([rootPrimaryKey, ...existingRootAttributes, ...requiredAttributes]))
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* Runs preload.
|
|
1332
|
+
* @param {import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord>} preload - Preload to merge.
|
|
1333
|
+
* @returns {this} - Query with merged preloads.
|
|
1334
|
+
*/
|
|
1335
|
+
preload(preload) {
|
|
1336
|
+
mergePreloadRecord(this._preload, normalizePreload(preload))
|
|
1337
|
+
|
|
1338
|
+
return this
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
/**
|
|
1342
|
+
* Runs select.
|
|
1343
|
+
* @param {Record<string, string[] | string> | string | string[]} select - Model-aware attribute select map or root-model shorthand.
|
|
1344
|
+
* @returns {this} - Query with merged selected attributes.
|
|
1345
|
+
*/
|
|
1346
|
+
select(select) {
|
|
1347
|
+
mergeSelectRecord(this._select, normalizeSelect(select, this.modelClass.getModelName()))
|
|
1348
|
+
|
|
1349
|
+
return this
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
/**
|
|
1353
|
+
* Like `select(...)`, but keeps the default serialized attributes and loads
|
|
1354
|
+
* the given extras in addition (for example attributes declared
|
|
1355
|
+
* `selectedByDefault: false`). Keyed by model name, with root-model shorthand.
|
|
1356
|
+
* @param {Record<string, string[] | string> | string | string[]} select - Extra attributes to load, keyed by model name or root-model shorthand.
|
|
1357
|
+
* @returns {this} - Query with merged extra selected attributes.
|
|
1358
|
+
*/
|
|
1359
|
+
selectsExtra(select) {
|
|
1360
|
+
mergeSelectRecord(this._selectsExtra, normalizeSelect(select, this.modelClass.getModelName()))
|
|
1361
|
+
|
|
1362
|
+
return this
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* Runs joins.
|
|
1367
|
+
* @param {Record<string, ?> | Array<Record<string, ?>>} joins - Relationship descriptor joins.
|
|
1368
|
+
* @returns {this} - Query with merged joins.
|
|
1369
|
+
*/
|
|
1370
|
+
joins(joins) {
|
|
1371
|
+
mergeJoinRecord(this._joins, normalizeJoins(joins))
|
|
1372
|
+
|
|
1373
|
+
return this
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/**
|
|
1377
|
+
* Returns the search result.
|
|
1378
|
+
* @param {string[]} path - Relationship path.
|
|
1379
|
+
* @param {string} column - Column or attribute name.
|
|
1380
|
+
* @param {"eq" | "like" | "notEq" | "gt" | "gteq" | "lt" | "lteq" | ">" | ">=" | "<" | "<="} operator - Search operator.
|
|
1381
|
+
* @param {?} value - Search value.
|
|
1382
|
+
* @returns {this} - Query with appended search.
|
|
1383
|
+
*/
|
|
1384
|
+
search(path, column, operator, value) {
|
|
1385
|
+
if (!Array.isArray(path)) {
|
|
1386
|
+
throw new Error(`search path must be an array, got: ${typeof path}`)
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
for (const pathEntry of path) {
|
|
1390
|
+
if (typeof pathEntry !== "string" || pathEntry.length < 1) {
|
|
1391
|
+
throw new Error("search path entries must be non-empty strings")
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
if (typeof column !== "string" || column.length < 1) {
|
|
1396
|
+
throw new Error("search column must be a non-empty string")
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
if (typeof operator !== "string" || operator.length < 1) {
|
|
1400
|
+
throw new Error("search operator must be a non-empty string")
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
const normalizedOperator = normalizeSearchOperator(operator)
|
|
1404
|
+
|
|
1405
|
+
this._searches.push({
|
|
1406
|
+
column,
|
|
1407
|
+
operator: normalizedOperator,
|
|
1408
|
+
path: [...path],
|
|
1409
|
+
value
|
|
1410
|
+
})
|
|
1411
|
+
|
|
1412
|
+
return this
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* Runs sort.
|
|
1417
|
+
* @param {string | string[] | [string, string] | Array<[string, string]> | Record<string, ?> | Array<Record<string, ?>>} sort - Sort definition(s).
|
|
1418
|
+
* @returns {this} - Query with appended sort definitions.
|
|
1419
|
+
*/
|
|
1420
|
+
sort(sort) {
|
|
1421
|
+
this._sort.push(...normalizeSort(sort))
|
|
1422
|
+
|
|
1423
|
+
return this
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
/**
|
|
1427
|
+
* Runs order.
|
|
1428
|
+
* @param {string | string[] | [string, string] | Array<[string, string]> | Record<string, ?> | Array<Record<string, ?>>} order - Order definition(s).
|
|
1429
|
+
* @returns {this} - Query with appended sort definitions.
|
|
1430
|
+
*/
|
|
1431
|
+
order(order) {
|
|
1432
|
+
return this.sort(order)
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* Runs group.
|
|
1437
|
+
* @param {string | string[] | Record<string, ?> | Array<Record<string, ?>>} group - Group definition(s).
|
|
1438
|
+
* @returns {this} - Query with appended group definitions.
|
|
1439
|
+
*/
|
|
1440
|
+
group(group) {
|
|
1441
|
+
this._group.push(...normalizeGroup(group))
|
|
1442
|
+
|
|
1443
|
+
return this
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* Runs distinct.
|
|
1448
|
+
* @param {boolean} [value] - Whether to request distinct rows.
|
|
1449
|
+
* @returns {this} - Query with distinct flag.
|
|
1450
|
+
*/
|
|
1451
|
+
distinct(value = true) {
|
|
1452
|
+
if (typeof value !== "boolean") {
|
|
1453
|
+
throw new Error(`distinct must be a boolean, got: ${typeof value}`)
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
this._distinct = value
|
|
1457
|
+
|
|
1458
|
+
return this
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
/**
|
|
1462
|
+
* Returns the limit result.
|
|
1463
|
+
* @param {number} value - Maximum number of records.
|
|
1464
|
+
* @returns {this} - Query with limit.
|
|
1465
|
+
*/
|
|
1466
|
+
limit(value) {
|
|
1467
|
+
this._limit = normalizeIntegerArgument(value, "limit", {min: 0})
|
|
1468
|
+
this._page = null
|
|
1469
|
+
|
|
1470
|
+
return this
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* Runs offset.
|
|
1475
|
+
* @param {number} value - Number of records to skip.
|
|
1476
|
+
* @returns {this} - Query with offset.
|
|
1477
|
+
*/
|
|
1478
|
+
offset(value) {
|
|
1479
|
+
this._offset = normalizeIntegerArgument(value, "offset", {min: 0})
|
|
1480
|
+
this._page = null
|
|
1481
|
+
|
|
1482
|
+
return this
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Runs page.
|
|
1487
|
+
* @param {number} pageNumber - 1-based page number.
|
|
1488
|
+
* @returns {this} - Query with page applied.
|
|
1489
|
+
*/
|
|
1490
|
+
page(pageNumber) {
|
|
1491
|
+
this._page = normalizeIntegerArgument(pageNumber, "page", {min: 1})
|
|
1492
|
+
const pageSize = this._perPage || 30
|
|
1493
|
+
|
|
1494
|
+
this._limit = pageSize
|
|
1495
|
+
this._offset = (this._page - 1) * pageSize
|
|
1496
|
+
|
|
1497
|
+
return this
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* Runs per page.
|
|
1502
|
+
* @param {number} perPage - Page size.
|
|
1503
|
+
* @returns {this} - Query with per-page applied.
|
|
1504
|
+
*/
|
|
1505
|
+
perPage(perPage) {
|
|
1506
|
+
this._perPage = normalizeIntegerArgument(perPage, "perPage", {min: 1})
|
|
1507
|
+
|
|
1508
|
+
if (this._page !== null) {
|
|
1509
|
+
this._limit = this._perPage
|
|
1510
|
+
this._offset = (this._page - 1) * this._perPage
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
return this
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* Runs clone.
|
|
1518
|
+
* @returns {FrontendModelQuery<T>} - Cloned query instance.
|
|
1519
|
+
*/
|
|
1520
|
+
clone() {
|
|
1521
|
+
const newQuery = /**
|
|
1522
|
+
* Narrows the runtime value to the documented type.
|
|
1523
|
+
@type {FrontendModelQuery<T>} */ (new FrontendModelQuery({
|
|
1524
|
+
modelClass: this.modelClass,
|
|
1525
|
+
preload: normalizePreload(this._preload)
|
|
1526
|
+
}))
|
|
1527
|
+
|
|
1528
|
+
newQuery._joins = normalizeJoins(this._joins)
|
|
1529
|
+
newQuery._where = {...this._where}
|
|
1530
|
+
newQuery._ransack = this._ransack.map((ransackParams) => ({...ransackParams}))
|
|
1531
|
+
newQuery._searches = this._searches.map((search) => ({
|
|
1532
|
+
column: search.column,
|
|
1533
|
+
operator: search.operator,
|
|
1534
|
+
path: [...search.path],
|
|
1535
|
+
value: search.value
|
|
1536
|
+
}))
|
|
1537
|
+
newQuery._select = normalizeSelect(this._select)
|
|
1538
|
+
newQuery._selectsExtra = normalizeSelect(this._selectsExtra)
|
|
1539
|
+
newQuery._sort = this._sort.map((sortEntry) => ({
|
|
1540
|
+
column: sortEntry.column,
|
|
1541
|
+
direction: sortEntry.direction,
|
|
1542
|
+
path: [...sortEntry.path]
|
|
1543
|
+
}))
|
|
1544
|
+
newQuery._group = this._group.map((groupEntry) => ({
|
|
1545
|
+
column: groupEntry.column,
|
|
1546
|
+
path: [...groupEntry.path]
|
|
1547
|
+
}))
|
|
1548
|
+
newQuery._distinct = this._distinct
|
|
1549
|
+
newQuery._limit = this._limit
|
|
1550
|
+
newQuery._offset = this._offset
|
|
1551
|
+
newQuery._page = this._page
|
|
1552
|
+
newQuery._perPage = this._perPage
|
|
1553
|
+
newQuery._withCount = this._withCount.map((entry) => ({
|
|
1554
|
+
attributeName: entry.attributeName,
|
|
1555
|
+
relationshipName: entry.relationshipName,
|
|
1556
|
+
where: entry.where ? {...entry.where} : undefined
|
|
1557
|
+
}))
|
|
1558
|
+
newQuery._queryData = this._queryData.map((entry) => (
|
|
1559
|
+
typeof entry === "string" ? entry : {...entry}
|
|
1560
|
+
))
|
|
1561
|
+
newQuery._abilities = this._abilities.map((entry) => ({
|
|
1562
|
+
actions: [...entry.actions],
|
|
1563
|
+
modelName: entry.modelName
|
|
1564
|
+
}))
|
|
1565
|
+
|
|
1566
|
+
return newQuery
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
/**
|
|
1570
|
+
* Runs get model class.
|
|
1571
|
+
* @returns {T} - Root model class.
|
|
1572
|
+
*/
|
|
1573
|
+
getModelClass() {
|
|
1574
|
+
return this.modelClass
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
/**
|
|
1578
|
+
* Runs preload payload.
|
|
1579
|
+
* @returns {Record<string, ?>} - Payload preload hash when present.
|
|
1580
|
+
*/
|
|
1581
|
+
preloadPayload() {
|
|
1582
|
+
if (Object.keys(this._preload).length === 0) return {}
|
|
1583
|
+
|
|
1584
|
+
return {preload: this._preload}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Runs with count payload.
|
|
1589
|
+
* @returns {Record<string, ?>} - Payload withCount array when present.
|
|
1590
|
+
*/
|
|
1591
|
+
withCountPayload() {
|
|
1592
|
+
if (this._withCount.length === 0) return {}
|
|
1593
|
+
|
|
1594
|
+
return {
|
|
1595
|
+
withCount: this._withCount.map((entry) => ({
|
|
1596
|
+
attributeName: entry.attributeName,
|
|
1597
|
+
relationshipName: entry.relationshipName,
|
|
1598
|
+
where: entry.where || undefined
|
|
1599
|
+
}))
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* Runs abilities payload.
|
|
1605
|
+
* @returns {Record<string, ?>} - Payload abilities array when present.
|
|
1606
|
+
*/
|
|
1607
|
+
abilitiesPayload() {
|
|
1608
|
+
if (this._abilities.length === 0) return {}
|
|
1609
|
+
|
|
1610
|
+
return {
|
|
1611
|
+
abilities: this._abilities.map((entry) => ({
|
|
1612
|
+
actions: [...entry.actions],
|
|
1613
|
+
modelName: entry.modelName
|
|
1614
|
+
}))
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Runs query data payload.
|
|
1620
|
+
* @returns {Record<string, ?>} - Payload queryData spec when present.
|
|
1621
|
+
*/
|
|
1622
|
+
queryDataPayload() {
|
|
1623
|
+
if (this._queryData.length === 0) return {}
|
|
1624
|
+
|
|
1625
|
+
// Single accumulated spec goes on the wire verbatim. The backend
|
|
1626
|
+
// normalizer accepts string/array/object at each level, so we can
|
|
1627
|
+
// ship multiple `.queryData(...)` calls as an array.
|
|
1628
|
+
return {
|
|
1629
|
+
queryData: this._queryData.length === 1 ? this._queryData[0] : this._queryData
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
/**
|
|
1634
|
+
* Runs select payload.
|
|
1635
|
+
* @param {string[]} [requiredAttributes] - Extra required attributes for root model selection.
|
|
1636
|
+
* @returns {Record<string, ?>} - Payload select hash when present.
|
|
1637
|
+
*/
|
|
1638
|
+
selectPayload(requiredAttributes = []) {
|
|
1639
|
+
const select = this.selectWithRequiredRootAttributes(requiredAttributes)
|
|
1640
|
+
|
|
1641
|
+
if (Object.keys(select).length === 0) return {}
|
|
1642
|
+
|
|
1643
|
+
return {select}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/**
|
|
1647
|
+
* Runs selects extra payload.
|
|
1648
|
+
* @returns {Record<string, ?>} - Payload selectsExtra hash when present.
|
|
1649
|
+
*/
|
|
1650
|
+
selectsExtraPayload() {
|
|
1651
|
+
if (Object.keys(this._selectsExtra).length === 0) return {}
|
|
1652
|
+
|
|
1653
|
+
return {selectsExtra: this._selectsExtra}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
/**
|
|
1657
|
+
* Runs search payload.
|
|
1658
|
+
* @returns {Record<string, ?>} - Payload searches array when present.
|
|
1659
|
+
*/
|
|
1660
|
+
searchPayload() {
|
|
1661
|
+
if (this._searches.length === 0) return {}
|
|
1662
|
+
|
|
1663
|
+
return {
|
|
1664
|
+
searches: this._searches.map((search) => ({
|
|
1665
|
+
column: search.column,
|
|
1666
|
+
operator: search.operator,
|
|
1667
|
+
path: [...search.path],
|
|
1668
|
+
value: search.value
|
|
1669
|
+
}))
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/**
|
|
1674
|
+
* Runs ransack payload.
|
|
1675
|
+
* @returns {Record<string, ?>} - Payload ransack hash when present.
|
|
1676
|
+
*/
|
|
1677
|
+
ransackPayload() {
|
|
1678
|
+
if (this._ransack.length === 0) return {}
|
|
1679
|
+
|
|
1680
|
+
if (this._ransack.length === 1) {
|
|
1681
|
+
return {ransack: this._ransack[0]}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
return {
|
|
1685
|
+
ransack: {
|
|
1686
|
+
g: this._ransack,
|
|
1687
|
+
m: "and"
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
/**
|
|
1693
|
+
* Runs joins payload.
|
|
1694
|
+
* @returns {Record<string, ?>} - Payload joins hash when present.
|
|
1695
|
+
*/
|
|
1696
|
+
joinsPayload() {
|
|
1697
|
+
if (Object.keys(this._joins).length === 0) return {}
|
|
1698
|
+
|
|
1699
|
+
return {
|
|
1700
|
+
joins: normalizeJoins(this._joins)
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* Runs sort payload.
|
|
1706
|
+
* @returns {Record<string, ?>} - Payload sort array when present.
|
|
1707
|
+
*/
|
|
1708
|
+
sortPayload() {
|
|
1709
|
+
if (this._sort.length === 0) return {}
|
|
1710
|
+
|
|
1711
|
+
return {
|
|
1712
|
+
sort: this._sort.map((sortEntry) => ({
|
|
1713
|
+
column: sortEntry.column,
|
|
1714
|
+
direction: sortEntry.direction,
|
|
1715
|
+
path: [...sortEntry.path]
|
|
1716
|
+
}))
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
/**
|
|
1721
|
+
* Runs group payload.
|
|
1722
|
+
* @returns {Record<string, ?>} - Payload group array when present.
|
|
1723
|
+
*/
|
|
1724
|
+
groupPayload() {
|
|
1725
|
+
if (this._group.length === 0) return {}
|
|
1726
|
+
|
|
1727
|
+
return {
|
|
1728
|
+
group: this._group.map((groupEntry) => ({
|
|
1729
|
+
column: groupEntry.column,
|
|
1730
|
+
path: [...groupEntry.path]
|
|
1731
|
+
}))
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
/**
|
|
1736
|
+
* Runs distinct payload.
|
|
1737
|
+
* @returns {Record<string, ?>} - Payload distinct flag when enabled.
|
|
1738
|
+
*/
|
|
1739
|
+
distinctPayload() {
|
|
1740
|
+
if (!this._distinct) return {}
|
|
1741
|
+
|
|
1742
|
+
return {
|
|
1743
|
+
distinct: true
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
/**
|
|
1748
|
+
* Runs where payload.
|
|
1749
|
+
* @returns {Record<string, ?>} - Payload where hash when present.
|
|
1750
|
+
*/
|
|
1751
|
+
wherePayload() {
|
|
1752
|
+
if (Object.keys(this._where).length === 0) return {}
|
|
1753
|
+
|
|
1754
|
+
return {
|
|
1755
|
+
where: this._where
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* Runs pagination payload.
|
|
1761
|
+
* @returns {Record<string, ?>} - Payload pagination params when present.
|
|
1762
|
+
*/
|
|
1763
|
+
paginationPayload() {
|
|
1764
|
+
/**
|
|
1765
|
+
* Payload.
|
|
1766
|
+
@type {Record<string, ?>} */
|
|
1767
|
+
const payload = {}
|
|
1768
|
+
|
|
1769
|
+
if (this._limit !== null) payload.limit = this._limit
|
|
1770
|
+
if (this._offset !== null) payload.offset = this._offset
|
|
1771
|
+
if (this._page !== null) payload.page = this._page
|
|
1772
|
+
if (this._perPage !== null) payload.perPage = this._perPage
|
|
1773
|
+
|
|
1774
|
+
return payload
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
/**
|
|
1778
|
+
* Runs assert event query supported.
|
|
1779
|
+
* @returns {void}
|
|
1780
|
+
* @throws {Error} When the query contains list-only options that cannot filter a single lifecycle event.
|
|
1781
|
+
*/
|
|
1782
|
+
assertEventQuerySupported() {
|
|
1783
|
+
/**
|
|
1784
|
+
* Unsupported options.
|
|
1785
|
+
@type {string[]} */
|
|
1786
|
+
const unsupportedOptions = []
|
|
1787
|
+
|
|
1788
|
+
if (this._sort.length > 0) unsupportedOptions.push("sort")
|
|
1789
|
+
if (this._group.length > 0) unsupportedOptions.push("group")
|
|
1790
|
+
if (this._distinct) unsupportedOptions.push("distinct")
|
|
1791
|
+
if (this._ransack.length > 0) unsupportedOptions.push("ransack")
|
|
1792
|
+
if (this._limit !== null || this._offset !== null || this._page !== null || this._perPage !== null) unsupportedOptions.push("pagination")
|
|
1793
|
+
|
|
1794
|
+
if (unsupportedOptions.length === 0) return
|
|
1795
|
+
|
|
1796
|
+
throw new Error(`Frontend model event queries do not support ${unsupportedOptions.join(", ")}`)
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
/**
|
|
1800
|
+
* Runs event projection payload.
|
|
1801
|
+
* @returns {FrontendModelProjectionPayload} - Projection payload used when serializing lifecycle events.
|
|
1802
|
+
*/
|
|
1803
|
+
eventProjectionPayload() {
|
|
1804
|
+
this.assertEventQuerySupported()
|
|
1805
|
+
|
|
1806
|
+
return {
|
|
1807
|
+
...this.preloadPayload(),
|
|
1808
|
+
...this.selectPayload(),
|
|
1809
|
+
...this.selectsExtraPayload(),
|
|
1810
|
+
...this.withCountPayload(),
|
|
1811
|
+
...this.abilitiesPayload(),
|
|
1812
|
+
...this.queryDataPayload()
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
/**
|
|
1817
|
+
* Runs event filter payload.
|
|
1818
|
+
* @returns {FrontendModelEventFilterPayload | null} - Query pieces used to match lifecycle events.
|
|
1819
|
+
*/
|
|
1820
|
+
eventFilterPayload() {
|
|
1821
|
+
this.assertEventQuerySupported()
|
|
1822
|
+
|
|
1823
|
+
const payload = {
|
|
1824
|
+
...this.joinsPayload(),
|
|
1825
|
+
...this.searchPayload(),
|
|
1826
|
+
...this.wherePayload()
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
return Object.keys(payload).length === 0 ? null : payload
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
/**
|
|
1833
|
+
* Returns the eventOptionsPayload result.
|
|
1834
|
+
* @returns {FrontendModelEventOptionsPayload} - Combined event filter and projection payload.
|
|
1835
|
+
*/
|
|
1836
|
+
eventOptionsPayload() {
|
|
1837
|
+
const eventFilterPayload = this.eventFilterPayload()
|
|
1838
|
+
|
|
1839
|
+
return {
|
|
1840
|
+
eventFilterKey: eventFilterPayload ? frontendModelEventFilterKey(eventFilterPayload) : null,
|
|
1841
|
+
eventFilterPayload,
|
|
1842
|
+
projectionPayload: this.eventProjectionPayload()
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
/**
|
|
1847
|
+
* Runs load.
|
|
1848
|
+
* @returns {Promise<InstanceType<T>[]>} - Loaded model instances.
|
|
1849
|
+
*/
|
|
1850
|
+
async load() {
|
|
1851
|
+
const response = await this.modelClass.executeCommand("index", {
|
|
1852
|
+
...this.preloadPayload(),
|
|
1853
|
+
...this.joinsPayload(),
|
|
1854
|
+
...this.ransackPayload(),
|
|
1855
|
+
...this.searchPayload(),
|
|
1856
|
+
...this.selectPayload(),
|
|
1857
|
+
...this.selectsExtraPayload(),
|
|
1858
|
+
...this.groupPayload(),
|
|
1859
|
+
...this.distinctPayload(),
|
|
1860
|
+
...this.sortPayload(),
|
|
1861
|
+
...this.wherePayload(),
|
|
1862
|
+
...this.withCountPayload(),
|
|
1863
|
+
...this.abilitiesPayload(),
|
|
1864
|
+
...this.queryDataPayload(),
|
|
1865
|
+
...this.paginationPayload()
|
|
1866
|
+
})
|
|
1867
|
+
|
|
1868
|
+
if (!response || typeof response !== "object") {
|
|
1869
|
+
throw new Error(`Expected object response but got: ${response}`)
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
const modelsData = Array.isArray(response.models) ? response.models : []
|
|
1873
|
+
/**
|
|
1874
|
+
* Models.
|
|
1875
|
+
@type {InstanceType<T>[]} */
|
|
1876
|
+
const models = modelsData.map((model) => this.modelClass.instantiateFromResponse(model))
|
|
1877
|
+
|
|
1878
|
+
// Share a single cohort reference across every sibling so auto-batch-preload
|
|
1879
|
+
// can batch lazy relationship access later. Single-record lookups still flow
|
|
1880
|
+
// through here (with a cohort of one) and degrade cleanly to per-record load.
|
|
1881
|
+
for (const model of models) {
|
|
1882
|
+
/**
|
|
1883
|
+
* Narrows the runtime value to the documented type.
|
|
1884
|
+
@type {?} */ (model)._loadCohort = models
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
return models
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
/**
|
|
1891
|
+
* Runs to array.
|
|
1892
|
+
* @returns {Promise<InstanceType<T>[]>} - Loaded model instances.
|
|
1893
|
+
*/
|
|
1894
|
+
async toArray() {
|
|
1895
|
+
return await this.load()
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
/**
|
|
1899
|
+
* Runs count.
|
|
1900
|
+
* @returns {Promise<number>} - Number of loaded model instances.
|
|
1901
|
+
*/
|
|
1902
|
+
async count() {
|
|
1903
|
+
const response = await this.modelClass.executeCommand("index", {
|
|
1904
|
+
...this.joinsPayload(),
|
|
1905
|
+
...this.ransackPayload(),
|
|
1906
|
+
...this.searchPayload(),
|
|
1907
|
+
...this.groupPayload(),
|
|
1908
|
+
...this.distinctPayload(),
|
|
1909
|
+
...this.wherePayload(),
|
|
1910
|
+
...this.paginationPayload(),
|
|
1911
|
+
count: true
|
|
1912
|
+
})
|
|
1913
|
+
|
|
1914
|
+
if (!response || typeof response !== "object") {
|
|
1915
|
+
throw new Error(`Expected object response but got: ${response}`)
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
if (!Number.isFinite(response.count)) {
|
|
1919
|
+
throw new Error(`Expected numeric count response but got: ${response.count}`)
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
return response.count
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
/**
|
|
1926
|
+
* Runs first.
|
|
1927
|
+
* @returns {Promise<InstanceType<T> | null>} - First model matching query.
|
|
1928
|
+
*/
|
|
1929
|
+
async first() {
|
|
1930
|
+
const query = this.clone()
|
|
1931
|
+
|
|
1932
|
+
if (query._sort.length < 1) {
|
|
1933
|
+
query.sort([[this.modelClass.primaryKey(), "asc"]])
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
query.limit(1)
|
|
1937
|
+
|
|
1938
|
+
const models = await query.toArray()
|
|
1939
|
+
|
|
1940
|
+
return models[0] || null
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
/**
|
|
1944
|
+
* Runs last.
|
|
1945
|
+
* @returns {Promise<InstanceType<T> | null>} - Last model matching query.
|
|
1946
|
+
*/
|
|
1947
|
+
async last() {
|
|
1948
|
+
// When pagination is already applied, fetch that scoped window and return its last item.
|
|
1949
|
+
if (this._offset !== null || this._page !== null || this._perPage !== null) {
|
|
1950
|
+
const models = await this.toArray()
|
|
1951
|
+
|
|
1952
|
+
if (models.length < 1) return null
|
|
1953
|
+
|
|
1954
|
+
return models[models.length - 1]
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
const query = this.clone()
|
|
1958
|
+
|
|
1959
|
+
if (query._sort.length < 1) {
|
|
1960
|
+
query.sort([[this.modelClass.primaryKey(), "desc"]])
|
|
1961
|
+
} else {
|
|
1962
|
+
query._sort = query._sort.map((sortEntry) => ({
|
|
1963
|
+
...sortEntry,
|
|
1964
|
+
direction: reverseSortDirection(sortEntry.direction)
|
|
1965
|
+
}))
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
query.limit(1)
|
|
1969
|
+
|
|
1970
|
+
const models = await query.toArray()
|
|
1971
|
+
|
|
1972
|
+
return models[0] || null
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
/**
|
|
1976
|
+
* Runs pluck.
|
|
1977
|
+
* @param {...(string | string[] | Record<string, ?> | Array<Record<string, ?>>)} columns - Pluck definition(s).
|
|
1978
|
+
* @returns {Promise<Array<?>>} - Plucked values.
|
|
1979
|
+
*/
|
|
1980
|
+
async pluck(...columns) {
|
|
1981
|
+
if (columns.length < 1) {
|
|
1982
|
+
throw new Error("No columns given to pluck")
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
const normalizedPluck = normalizePluck(columns.length === 1 ? columns[0] : columns)
|
|
1986
|
+
const validatedPluck = validatePluckDefinitions({
|
|
1987
|
+
modelClass: this.modelClass,
|
|
1988
|
+
pluck: normalizedPluck
|
|
1989
|
+
})
|
|
1990
|
+
const response = await this.modelClass.executeCommand("index", {
|
|
1991
|
+
...this.joinsPayload(),
|
|
1992
|
+
...this.searchPayload(),
|
|
1993
|
+
...this.groupPayload(),
|
|
1994
|
+
...this.distinctPayload(),
|
|
1995
|
+
...this.sortPayload(),
|
|
1996
|
+
...this.wherePayload(),
|
|
1997
|
+
...this.paginationPayload(),
|
|
1998
|
+
pluck: validatedPluck
|
|
1999
|
+
})
|
|
2000
|
+
|
|
2001
|
+
if (!response || typeof response !== "object") {
|
|
2002
|
+
throw new Error(`Expected object response but got: ${response}`)
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
if (!Array.isArray(response.values)) {
|
|
2006
|
+
return []
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
return response.values
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
/**
|
|
2013
|
+
* Runs find.
|
|
2014
|
+
* @param {number | string} id - Record id.
|
|
2015
|
+
* @returns {Promise<InstanceType<T>>} - Found model.
|
|
2016
|
+
*/
|
|
2017
|
+
async find(id) {
|
|
2018
|
+
const pk = this.modelClass.primaryKey()
|
|
2019
|
+
const model = await this.findBy({[pk]: id})
|
|
2020
|
+
|
|
2021
|
+
if (!model) {
|
|
2022
|
+
throw new Error(`${this.modelClass.getModelName()} not found with ${pk}=${id}`)
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
return model
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
/**
|
|
2029
|
+
* Runs find by.
|
|
2030
|
+
* @param {Record<string, ?>} conditions - Conditions.
|
|
2031
|
+
* @returns {Promise<InstanceType<T> | null>} - Found model or null.
|
|
2032
|
+
*/
|
|
2033
|
+
async findBy(conditions) {
|
|
2034
|
+
const normalizedConditions = this.validatedStructuredConditions(conditions)
|
|
2035
|
+
const mergedWhere = {
|
|
2036
|
+
...this._where,
|
|
2037
|
+
...normalizedConditions
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
const response = await this.modelClass.executeCommand("index", {
|
|
2041
|
+
...this.preloadPayload(),
|
|
2042
|
+
...this.joinsPayload(),
|
|
2043
|
+
...this.searchPayload(),
|
|
2044
|
+
...this.selectPayload(Object.keys(mergedWhere)),
|
|
2045
|
+
...this.selectsExtraPayload(),
|
|
2046
|
+
...this.groupPayload(),
|
|
2047
|
+
...this.distinctPayload(),
|
|
2048
|
+
...this.sortPayload(),
|
|
2049
|
+
...this.abilitiesPayload(),
|
|
2050
|
+
...this.paginationPayload(),
|
|
2051
|
+
where: mergedWhere
|
|
2052
|
+
})
|
|
2053
|
+
|
|
2054
|
+
if (!response || typeof response !== "object") {
|
|
2055
|
+
throw new Error(`Expected object response but got: ${response}`)
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
const models = Array.isArray(response.models) ? response.models : []
|
|
2059
|
+
|
|
2060
|
+
for (const modelData of models) {
|
|
2061
|
+
const model = this.modelClass.instantiateFromResponse(modelData)
|
|
2062
|
+
|
|
2063
|
+
if (this.modelClass.matchesFindByConditions(model, mergedWhere)) {
|
|
2064
|
+
return model
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
return null
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
/**
|
|
2072
|
+
* Runs find by or fail.
|
|
2073
|
+
* @param {Record<string, ?>} conditions - Conditions.
|
|
2074
|
+
* @returns {Promise<InstanceType<T>>} - Found model.
|
|
2075
|
+
*/
|
|
2076
|
+
async findByOrFail(conditions) {
|
|
2077
|
+
const model = await this.findBy(conditions)
|
|
2078
|
+
|
|
2079
|
+
if (!model) {
|
|
2080
|
+
throw new Error(`${this.modelClass.name} not found for conditions: ${serializeFindConditions(conditions)}`)
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
return model
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
/**
|
|
2087
|
+
* Runs find or initialize by.
|
|
2088
|
+
* @param {Record<string, ?>} conditions - Conditions.
|
|
2089
|
+
* @returns {Promise<InstanceType<T>>} - Existing or initialized model.
|
|
2090
|
+
*/
|
|
2091
|
+
async findOrInitializeBy(conditions) {
|
|
2092
|
+
const normalizedConditions = this.validatedStructuredConditions(conditions)
|
|
2093
|
+
const model = await this.findBy(conditions)
|
|
2094
|
+
|
|
2095
|
+
if (model) return model
|
|
2096
|
+
|
|
2097
|
+
return /** Narrows the runtime value to the documented type. @type {InstanceType<T>} */ (new this.modelClass(normalizedConditions))
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
/**
|
|
2101
|
+
* Runs find or create by.
|
|
2102
|
+
* @param {Record<string, ?>} conditions - Conditions.
|
|
2103
|
+
* @param {(model: InstanceType<T>) => Promise<void> | void} [callback] - Optional callback before save.
|
|
2104
|
+
* @returns {Promise<InstanceType<T>>} - Existing or newly created model.
|
|
2105
|
+
*/
|
|
2106
|
+
async findOrCreateBy(conditions, callback) {
|
|
2107
|
+
const normalizedConditions = this.validatedStructuredConditions(conditions)
|
|
2108
|
+
const model = await this.findBy(conditions)
|
|
2109
|
+
|
|
2110
|
+
if (model) return model
|
|
2111
|
+
|
|
2112
|
+
const newModel = /**
|
|
2113
|
+
* Narrows the runtime value to the documented type.
|
|
2114
|
+
@type {InstanceType<T>} */ (new this.modelClass(normalizedConditions))
|
|
2115
|
+
|
|
2116
|
+
if (callback) {
|
|
2117
|
+
await callback(newModel)
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
await newModel.save()
|
|
2121
|
+
|
|
2122
|
+
return newModel
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
/**
|
|
2126
|
+
* Runs validated structured conditions.
|
|
2127
|
+
* @param {Record<string, ?>} conditions - Candidate structured conditions.
|
|
2128
|
+
* @returns {Record<string, ?>} - Validated conditions.
|
|
2129
|
+
*/
|
|
2130
|
+
validatedStructuredConditions(conditions) {
|
|
2131
|
+
this.modelClass.assertFindByConditions(conditions)
|
|
2132
|
+
|
|
2133
|
+
return conditions
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
/**
|
|
2138
|
+
* Runs frontend model event filter key.
|
|
2139
|
+
* @param {FrontendModelEventFilterPayload} payload - Event filter payload.
|
|
2140
|
+
* @returns {string} - Stable key for event filter matching.
|
|
2141
|
+
*/
|
|
2142
|
+
function frontendModelEventFilterKey(payload) {
|
|
2143
|
+
return JSON.stringify(payload)
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
/**
|
|
2147
|
+
* Runs apply frontend model projection options.
|
|
2148
|
+
* @param {FrontendModelQuery<typeof import("./base.js").default>} query - Query receiving projection options.
|
|
2149
|
+
* @param {FrontendModelProjectionOptions} options - Projection options.
|
|
2150
|
+
* @returns {void}
|
|
2151
|
+
*/
|
|
2152
|
+
function applyFrontendModelProjectionOptions(query, options) {
|
|
2153
|
+
if (options.select !== undefined) query.select(options.select)
|
|
2154
|
+
if (options.selectsExtra !== undefined) query.selectsExtra(options.selectsExtra)
|
|
2155
|
+
if (options.preload !== undefined) query.preload(options.preload)
|
|
2156
|
+
if (options.withCount !== undefined) query.withCount(options.withCount)
|
|
2157
|
+
if (options.abilities !== undefined) query.abilities(options.abilities)
|
|
2158
|
+
if (options.queryData !== undefined) query.queryData(options.queryData)
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
/**
|
|
2162
|
+
* Runs assert frontend model event query class.
|
|
2163
|
+
* @param {typeof import("./base.js").default} modelClass - Expected frontend model class.
|
|
2164
|
+
* @param {FrontendModelQuery<typeof import("./base.js").default>} query - Event query.
|
|
2165
|
+
* @returns {void}
|
|
2166
|
+
*/
|
|
2167
|
+
function assertFrontendModelEventQueryClass(modelClass, query) {
|
|
2168
|
+
if (query.modelClass === modelClass) return
|
|
2169
|
+
|
|
2170
|
+
throw new Error(`Cannot subscribe ${modelClass.name} events with a ${query.modelClass.name} query`)
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
/**
|
|
2174
|
+
* Runs assert frontend model event options object.
|
|
2175
|
+
* @param {FrontendModelEventOptions} options - Candidate event options.
|
|
2176
|
+
* @returns {void}
|
|
2177
|
+
*/
|
|
2178
|
+
function assertFrontendModelEventOptionsObject(options) {
|
|
2179
|
+
if (options && typeof options === "object" && !Array.isArray(options)) return
|
|
2180
|
+
|
|
2181
|
+
throw new Error(`Frontend model event options must be a query or an options object, got: ${options}`)
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
/**
|
|
2185
|
+
* Runs cloned frontend model event query.
|
|
2186
|
+
* @param {typeof import("./base.js").default} modelClass - Frontend model class.
|
|
2187
|
+
* @param {FrontendModelQuery<typeof import("./base.js").default>} query - Event query.
|
|
2188
|
+
* @returns {FrontendModelQuery<typeof import("./base.js").default>} - Cloned query used by event subscriptions.
|
|
2189
|
+
*/
|
|
2190
|
+
function clonedFrontendModelEventQuery(modelClass, query) {
|
|
2191
|
+
assertFrontendModelEventQueryClass(modelClass, query)
|
|
2192
|
+
|
|
2193
|
+
return query.clone()
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
/**
|
|
2197
|
+
* Runs frontend model event query from options object.
|
|
2198
|
+
* @param {typeof import("./base.js").default} modelClass - Frontend model class.
|
|
2199
|
+
* @param {FrontendModelEventOptionsObject} options - Event options object.
|
|
2200
|
+
* @returns {FrontendModelQuery<typeof import("./base.js").default>} - Query used by event subscriptions.
|
|
2201
|
+
*/
|
|
2202
|
+
function frontendModelEventQueryFromOptionsObject(modelClass, options) {
|
|
2203
|
+
if (options.query !== undefined && !(options.query instanceof FrontendModelQuery)) {
|
|
2204
|
+
throw new Error("Frontend model event option query must be a FrontendModelQuery")
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
const query = options.query
|
|
2208
|
+
? options.query.clone()
|
|
2209
|
+
: new FrontendModelQuery({modelClass})
|
|
2210
|
+
|
|
2211
|
+
assertFrontendModelEventQueryClass(modelClass, query)
|
|
2212
|
+
|
|
2213
|
+
return query
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
/**
|
|
2217
|
+
* Runs frontend model event query.
|
|
2218
|
+
* @param {typeof import("./base.js").default} modelClass - Frontend model class.
|
|
2219
|
+
* @param {FrontendModelEventOptions} [options] - Event query or projection options.
|
|
2220
|
+
* @returns {FrontendModelQuery<typeof import("./base.js").default>} - Normalized query used by event subscriptions.
|
|
2221
|
+
*/
|
|
2222
|
+
function frontendModelEventQuery(modelClass, options = {}) {
|
|
2223
|
+
if (options instanceof FrontendModelQuery) return clonedFrontendModelEventQuery(modelClass, options)
|
|
2224
|
+
|
|
2225
|
+
assertFrontendModelEventOptionsObject(options)
|
|
2226
|
+
|
|
2227
|
+
const optionsObject = /**
|
|
2228
|
+
* Narrows the runtime value to the documented type.
|
|
2229
|
+
@type {FrontendModelEventOptionsObject} */ (options)
|
|
2230
|
+
const query = frontendModelEventQueryFromOptionsObject(modelClass, optionsObject)
|
|
2231
|
+
|
|
2232
|
+
applyFrontendModelProjectionOptions(query, optionsObject)
|
|
2233
|
+
|
|
2234
|
+
return query
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
/**
|
|
2238
|
+
* Runs the frontendModelEventOptionsPayload helper.
|
|
2239
|
+
* @param {typeof import("./base.js").default} modelClass - Frontend model class.
|
|
2240
|
+
* @param {FrontendModelEventOptions} [options] - Event query or projection options.
|
|
2241
|
+
* @returns {FrontendModelEventOptionsPayload} - Normalized event subscription payload.
|
|
2242
|
+
*/
|
|
2243
|
+
export function frontendModelEventOptionsPayload(modelClass, options = {}) {
|
|
2244
|
+
return frontendModelEventQuery(modelClass, options).eventOptionsPayload()
|
|
2245
|
+
}
|