velocious 1.0.498 → 1.0.500
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/README.md +1 -1
- package/build/database/drivers/mssql/index.js +79 -2
- package/build/database/record/counter-cache-magnitude.js +240 -0
- package/build/database/record/index.js +26 -0
- package/build/database/record/state-machine.js +25 -3
- package/build/database/tenants/data-copier.js +27 -0
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +4 -20
- package/build/src/database/drivers/mssql/index.d.ts +22 -0
- package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/index.js +74 -3
- package/build/src/database/record/counter-cache-magnitude.d.ts +67 -0
- package/build/src/database/record/counter-cache-magnitude.d.ts.map +1 -0
- package/build/src/database/record/counter-cache-magnitude.js +204 -0
- package/build/src/database/record/index.d.ts +18 -0
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +25 -1
- package/build/src/database/record/state-machine.d.ts +2 -0
- package/build/src/database/record/state-machine.d.ts.map +1 -1
- package/build/src/database/record/state-machine.js +21 -4
- package/build/src/database/tenants/data-copier.d.ts +16 -0
- package/build/src/database/tenants/data-copier.d.ts.map +1 -1
- package/build/src/database/tenants/data-copier.js +25 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +0 -9
- 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 +5 -16
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/database/drivers/mssql/index.js +79 -2
- package/src/database/record/counter-cache-magnitude.js +240 -0
- package/src/database/record/index.js +26 -0
- package/src/database/record/state-machine.js +25 -3
- package/src/database/tenants/data-copier.js +27 -0
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +4 -20
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../index.js","../bin/velocious.js","../src/application.js","../src/configuration-resolver.js","../src/configuration-types.js","../src/configuration.js","../src/controller.js","../src/current-configuration.js","../src/current.js","../src/error-logger.js","../src/frontend-model-controller.js","../src/initializer.js","../src/logger.js","../src/mailer.js","../src/record-payload-values.js","../src/time-zone.js","../src/velocious-error.js","../src/authorization/ability.js","../src/authorization/base-resource.js","../src/background-jobs/client.js","../src/background-jobs/cron-expression.js","../src/background-jobs/forked-runner-child.js","../src/background-jobs/job-record.js","../src/background-jobs/job-registry.js","../src/background-jobs/job-runner.js","../src/background-jobs/job.js","../src/background-jobs/json-socket.js","../src/background-jobs/main.js","../src/background-jobs/normalize-error.js","../src/background-jobs/scheduler.js","../src/background-jobs/socket-request.js","../src/background-jobs/status-reporter.js","../src/background-jobs/store.js","../src/background-jobs/types.js","../src/background-jobs/worker.js","../src/background-jobs/web/authorization.js","../src/background-jobs/web/controller.js","../src/background-jobs/web/index.js","../src/background-jobs/web/path-matcher.js","../src/background-jobs/web/registry.js","../src/beacon/client.js","../src/beacon/in-process-broker.js","../src/beacon/in-process-client.js","../src/beacon/server.js","../src/beacon/types.js","../src/cli/base-command.js","../src/cli/browser-cli.js","../src/cli/index.js","../src/cli/tenant-database-command-helper.js","../src/cli/use-browser-cli.js","../src/cli/commands/background-jobs-main.js","../src/cli/commands/background-jobs-runner.js","../src/cli/commands/background-jobs-worker.js","../src/cli/commands/beacon.js","../src/cli/commands/console.js","../src/cli/commands/init.js","../src/cli/commands/routes.js","../src/cli/commands/run-script.js","../src/cli/commands/runner.js","../src/cli/commands/server.js","../src/cli/commands/test.js","../src/cli/commands/db/base-command.js","../src/cli/commands/db/create.js","../src/cli/commands/db/drop.js","../src/cli/commands/db/migrate.js","../src/cli/commands/db/reset.js","../src/cli/commands/db/rollback.js","../src/cli/commands/db/seed.js","../src/cli/commands/db/schema/dump.js","../src/cli/commands/db/schema/load.js","../src/cli/commands/db/tenants/check.js","../src/cli/commands/db/tenants/create.js","../src/cli/commands/db/tenants/drop.js","../src/cli/commands/db/tenants/migrate.js","../src/cli/commands/destroy/migration.js","../src/cli/commands/generate/base-models.js","../src/cli/commands/generate/frontend-models.js","../src/cli/commands/generate/migration.js","../src/cli/commands/generate/model.js","../src/cli/commands/lint/relationships.js","../src/database/annotations-async-hooks.js","../src/database/annotations.js","../src/database/column-types.js","../src/database/datetime-storage.js","../src/database/handler.js","../src/database/initializer-from-require-context.js","../src/database/migrations-ledger.js","../src/database/migrator.js","../src/database/use-database.js","../src/database/drivers/base-column.js","../src/database/drivers/base-columns-index.js","../src/database/drivers/base-foreign-key.js","../src/database/drivers/base-table.js","../src/database/drivers/base.js","../src/database/drivers/mssql/column.js","../src/database/drivers/mssql/columns-index.js","../src/database/drivers/mssql/connect-connection.js","../src/database/drivers/mssql/foreign-key.js","../src/database/drivers/mssql/index.js","../src/database/drivers/mssql/options.js","../src/database/drivers/mssql/query-parser.js","../src/database/drivers/mssql/structure-sql.js","../src/database/drivers/mssql/table.js","../src/database/drivers/mssql/sql/alter-table.js","../src/database/drivers/mssql/sql/create-database.js","../src/database/drivers/mssql/sql/create-index.js","../src/database/drivers/mssql/sql/create-table.js","../src/database/drivers/mssql/sql/delete.js","../src/database/drivers/mssql/sql/drop-database.js","../src/database/drivers/mssql/sql/drop-table.js","../src/database/drivers/mssql/sql/insert.js","../src/database/drivers/mssql/sql/remove-index.js","../src/database/drivers/mssql/sql/update.js","../src/database/drivers/mssql/sql/upsert.js","../src/database/drivers/mysql/column.js","../src/database/drivers/mysql/columns-index.js","../src/database/drivers/mysql/foreign-key.js","../src/database/drivers/mysql/index.js","../src/database/drivers/mysql/options.js","../src/database/drivers/mysql/query-parser.js","../src/database/drivers/mysql/query.js","../src/database/drivers/mysql/structure-sql.js","../src/database/drivers/mysql/table.js","../src/database/drivers/mysql/sql/alter-table.js","../src/database/drivers/mysql/sql/create-database.js","../src/database/drivers/mysql/sql/create-index.js","../src/database/drivers/mysql/sql/create-table.js","../src/database/drivers/mysql/sql/delete.js","../src/database/drivers/mysql/sql/drop-database.js","../src/database/drivers/mysql/sql/drop-table.js","../src/database/drivers/mysql/sql/insert.js","../src/database/drivers/mysql/sql/remove-index.js","../src/database/drivers/mysql/sql/update.js","../src/database/drivers/mysql/sql/upsert.js","../src/database/drivers/pgsql/column.js","../src/database/drivers/pgsql/columns-index.js","../src/database/drivers/pgsql/foreign-key.js","../src/database/drivers/pgsql/index.js","../src/database/drivers/pgsql/options.js","../src/database/drivers/pgsql/query-parser.js","../src/database/drivers/pgsql/structure-sql.js","../src/database/drivers/pgsql/table.js","../src/database/drivers/pgsql/sql/alter-table.js","../src/database/drivers/pgsql/sql/create-database.js","../src/database/drivers/pgsql/sql/create-index.js","../src/database/drivers/pgsql/sql/create-table.js","../src/database/drivers/pgsql/sql/delete.js","../src/database/drivers/pgsql/sql/drop-database.js","../src/database/drivers/pgsql/sql/drop-table.js","../src/database/drivers/pgsql/sql/insert.js","../src/database/drivers/pgsql/sql/remove-index.js","../src/database/drivers/pgsql/sql/update.js","../src/database/drivers/pgsql/sql/upsert.js","../src/database/drivers/sqlite/base.js","../src/database/drivers/sqlite/column.js","../src/database/drivers/sqlite/columns-index.js","../src/database/drivers/sqlite/connection-sql-js.js","../src/database/drivers/sqlite/foreign-key.js","../src/database/drivers/sqlite/index.js","../src/database/drivers/sqlite/index.native.js","../src/database/drivers/sqlite/index.web.js","../src/database/drivers/sqlite/options.js","../src/database/drivers/sqlite/query-parser.js","../src/database/drivers/sqlite/query.js","../src/database/drivers/sqlite/query.native.js","../src/database/drivers/sqlite/query.web.js","../src/database/drivers/sqlite/structure-sql.js","../src/database/drivers/sqlite/table-rebuilder.js","../src/database/drivers/sqlite/table.js","../src/database/drivers/sqlite/web-persistence.js","../src/database/drivers/sqlite/sql/alter-table.js","../src/database/drivers/sqlite/sql/create-index.js","../src/database/drivers/sqlite/sql/create-table.js","../src/database/drivers/sqlite/sql/delete.js","../src/database/drivers/sqlite/sql/drop-table.js","../src/database/drivers/sqlite/sql/insert.js","../src/database/drivers/sqlite/sql/remove-index.js","../src/database/drivers/sqlite/sql/update.js","../src/database/drivers/sqlite/sql/upsert.js","../src/database/drivers/structure-sql/utils.js","../src/database/migration/index.js","../src/database/migrator/files-finder.js","../src/database/migrator/types.js","../src/database/pool/async-tracked-multi-connection.js","../src/database/pool/base-methods-forward.js","../src/database/pool/base.js","../src/database/pool/single-multi-use.js","../src/database/query/alter-table-base.js","../src/database/query/base.js","../src/database/query/create-database-base.js","../src/database/query/create-index-base.js","../src/database/query/create-table-base.js","../src/database/query/delete-base.js","../src/database/query/drop-database-base.js","../src/database/query/drop-table-base.js","../src/database/query/from-base.js","../src/database/query/from-plain.js","../src/database/query/from-table.js","../src/database/query/index.js","../src/database/query/insert-base.js","../src/database/query/join-base.js","../src/database/query/join-object.js","../src/database/query/join-plain.js","../src/database/query/join-tracker.js","../src/database/query/model-class-query.js","../src/database/query/order-base.js","../src/database/query/order-column.js","../src/database/query/order-plain.js","../src/database/query/preloader.js","../src/database/query/query-data.js","../src/database/query/remove-index-base.js","../src/database/query/select-base.js","../src/database/query/select-plain.js","../src/database/query/select-table-and-column.js","../src/database/query/update-base.js","../src/database/query/upsert-base.js","../src/database/query/where-base.js","../src/database/query/where-combinator.js","../src/database/query/where-hash.js","../src/database/query/where-model-class-hash.js","../src/database/query/where-not.js","../src/database/query/where-plain.js","../src/database/query/with-count.js","../src/database/query/preloader/belongs-to.js","../src/database/query/preloader/ensure-model-class-initialized.js","../src/database/query/preloader/has-many.js","../src/database/query/preloader/has-one.js","../src/database/query/preloader/selection.js","../src/database/query-parser/base-query-parser.js","../src/database/query-parser/from-parser.js","../src/database/query-parser/group-parser.js","../src/database/query-parser/joins-parser.js","../src/database/query-parser/limit-parser.js","../src/database/query-parser/options.js","../src/database/query-parser/order-parser.js","../src/database/query-parser/select-parser.js","../src/database/query-parser/where-parser.js","../src/database/record/acts-as-list.js","../src/database/record/auditing.js","../src/database/record/index.js","../src/database/record/record-not-found-error.js","../src/database/record/state-machine.js","../src/database/record/user-module.js","../src/database/record/validation-messages.js","../src/database/record/attachments/attachment-record.js","../src/database/record/attachments/download.js","../src/database/record/attachments/handle.js","../src/database/record/attachments/normalize-input.js","../src/database/record/attachments/store.js","../src/database/record/attachments/storage-drivers/filesystem.js","../src/database/record/attachments/storage-drivers/native.js","../src/database/record/attachments/storage-drivers/s3.js","../src/database/record/instance-relationships/base.js","../src/database/record/instance-relationships/belongs-to.js","../src/database/record/instance-relationships/has-many.js","../src/database/record/instance-relationships/has-one.js","../src/database/record/relationships/base.js","../src/database/record/relationships/belongs-to.js","../src/database/record/relationships/has-many.js","../src/database/record/relationships/has-one.js","../src/database/record/validators/base.js","../src/database/record/validators/format.js","../src/database/record/validators/length.js","../src/database/record/validators/presence.js","../src/database/record/validators/uniqueness.js","../src/database/table-data/index.js","../src/database/table-data/table-column.js","../src/database/table-data/table-foreign-key.js","../src/database/table-data/table-index.js","../src/database/table-data/table-reference.js","../src/database/tenants/data-copier.js","../src/database/tenants/schema-cloner.js","../src/database/tenants/tenant-table-plan.js","../src/environment-handlers/base.js","../src/environment-handlers/browser.js","../src/environment-handlers/node.js","../src/environment-handlers/node/cli/commands/background-jobs-main.js","../src/environment-handlers/node/cli/commands/background-jobs-runner.js","../src/environment-handlers/node/cli/commands/background-jobs-worker.js","../src/environment-handlers/node/cli/commands/beacon.js","../src/environment-handlers/node/cli/commands/cli-command-context.js","../src/environment-handlers/node/cli/commands/console.js","../src/environment-handlers/node/cli/commands/init.js","../src/environment-handlers/node/cli/commands/routes.js","../src/environment-handlers/node/cli/commands/run-script.js","../src/environment-handlers/node/cli/commands/runner.js","../src/environment-handlers/node/cli/commands/server.js","../src/environment-handlers/node/cli/commands/test.js","../src/environment-handlers/node/cli/commands/db/seed.js","../src/environment-handlers/node/cli/commands/db/schema/dump.js","../src/environment-handlers/node/cli/commands/db/schema/load.js","../src/environment-handlers/node/cli/commands/destroy/migration.js","../src/environment-handlers/node/cli/commands/generate/base-models.js","../src/environment-handlers/node/cli/commands/generate/frontend-models.js","../src/environment-handlers/node/cli/commands/generate/generated-file-banner.js","../src/environment-handlers/node/cli/commands/generate/migration.js","../src/environment-handlers/node/cli/commands/generate/model.js","../src/environment-handlers/node/cli/commands/lint/relationships.js","../src/error-reporting/request-details.js","../src/frontend-model-resource/base-resource.js","../src/frontend-model-resource/velocious-attachment-resource.js","../src/frontend-models/base.js","../src/frontend-models/built-in-resources.js","../src/frontend-models/clear-pending-debounced-callback.js","../src/frontend-models/event-hook-models.js","../src/frontend-models/model-registry.js","../src/frontend-models/outgoing-event-buffer.js","../src/frontend-models/preloader.js","../src/frontend-models/query.js","../src/frontend-models/resource-config-validation.js","../src/frontend-models/resource-definition.js","../src/frontend-models/transport-serialization.js","../src/frontend-models/use-created-event.js","../src/frontend-models/use-destroyed-event.js","../src/frontend-models/use-model-class-event.js","../src/frontend-models/use-updated-event.js","../src/frontend-models/websocket-channel.js","../src/frontend-models/websocket-publishers.js","../src/http-client/header.js","../src/http-client/index.js","../src/http-client/request.js","../src/http-client/response.js","../src/http-client/websocket-client.js","../src/http-server/cookie.js","../src/http-server/development-reloader.js","../src/http-server/index.js","../src/http-server/remote-address.js","../src/http-server/server-client.js","../src/http-server/server-lock.js","../src/http-server/websocket-channel-subscribers.js","../src/http-server/websocket-channel.js","../src/http-server/websocket-connection.js","../src/http-server/websocket-event-log-store.js","../src/http-server/websocket-events-host.js","../src/http-server/websocket-events.js","../src/http-server/client/index.js","../src/http-server/client/params-to-object.js","../src/http-server/client/request-parser.js","../src/http-server/client/request-runner.js","../src/http-server/client/request-timing.js","../src/http-server/client/request.js","../src/http-server/client/response.js","../src/http-server/client/websocket-request.js","../src/http-server/client/websocket-session.js","../src/http-server/client/request-buffer/form-data-part.js","../src/http-server/client/request-buffer/header.js","../src/http-server/client/request-buffer/index.js","../src/http-server/client/uploaded-file/memory-uploaded-file.js","../src/http-server/client/uploaded-file/temporary-uploaded-file.js","../src/http-server/client/uploaded-file/uploaded-file.js","../src/http-server/worker-handler/channel-subscriber-dispatch.js","../src/http-server/worker-handler/in-process.js","../src/http-server/worker-handler/index.js","../src/http-server/worker-handler/worker-script.js","../src/http-server/worker-handler/worker-thread.js","../src/jobs/mail-delivery.js","../src/logger/base-logger.js","../src/logger/console-logger.js","../src/logger/file-logger.js","../src/logger/outputs/array-output.js","../src/logger/outputs/console-output.js","../src/logger/outputs/file-output.js","../src/logger/outputs/stdout-output.js","../src/mailer/base.js","../src/mailer/delivery.js","../src/mailer/index.js","../src/mailer/backends/smtp.js","../src/packages/velocious-package.js","../src/plugins/sqljs-wasm-route-controller.js","../src/plugins/sqljs-wasm-route.js","../src/routes/app-routes.js","../src/routes/base-route.js","../src/routes/basic-route.js","../src/routes/get-route.js","../src/routes/index.js","../src/routes/namespace-route.js","../src/routes/plugin-routes.js","../src/routes/post-route.js","../src/routes/resolver.js","../src/routes/resource-route.js","../src/routes/root-route.js","../src/routes/built-in/debug/controller.js","../src/routes/built-in/errors/controller.js","../src/routes/hooks/frontend-model-command-route-hook.js","../src/sync/conflict-strategy.js","../src/sync/device-identity.js","../src/sync/local-mutation-log.js","../src/sync/offline-grant.js","../src/sync/peer-mutation-bundle.js","../src/sync/query-scope.js","../src/sync/server-change-feed.js","../src/sync/server-sequence-allocator.js","../src/sync/stable-json.js","../src/sync/sync-api-client-types.js","../src/sync/sync-api-client.js","../src/sync/sync-api-controller.js","../src/sync/sync-client-registry.js","../src/sync/sync-client-types.js","../src/sync/sync-client.js","../src/sync/sync-envelope-replay-service.js","../src/sync/sync-model-change-feed-service.js","../src/sync/sync-replay-upsert-applier.js","../src/sync/sync-resource-base.js","../src/sync/sync-scope-store.js","../src/tenants/default-tenant-database-provisioning.js","../src/tenants/tenant-iterator.js","../src/tenants/tenant.js","../src/testing/base-expect.js","../src/testing/browser-frontend-model-event-hook-scenarios.js","../src/testing/browser-test-app.js","../src/testing/expect-to-change.js","../src/testing/expect-utils.js","../src/testing/expect.js","../src/testing/request-client.js","../src/testing/test-files-finder.js","../src/testing/test-filter-parser.js","../src/testing/test-runner.js","../src/testing/test-suite-splitter.js","../src/testing/test.js","../src/types/external-modules.d.ts","../src/utils/backtrace-cleaner-node.js","../src/utils/backtrace-cleaner.js","../src/utils/deburr-column-name.js","../src/utils/event-emitter.js","../src/utils/file-exists.js","../src/utils/format-value.js","../src/utils/is-date.js","../src/utils/model-scope.js","../src/utils/nest-callbacks.js","../src/utils/plain-object.js","../src/utils/ransack.js","../src/utils/rest-args-error.js","../src/utils/singularize-model-name.js","../src/utils/split-sql-statements.js","../src/utils/to-import-specifier.js","../src/utils/with-tracked-stack-async-hooks.js","../src/utils/with-tracked-stack.js"],"version":"6.0.3"}
|
|
1
|
+
{"root":["../index.js","../bin/velocious.js","../src/application.js","../src/configuration-resolver.js","../src/configuration-types.js","../src/configuration.js","../src/controller.js","../src/current-configuration.js","../src/current.js","../src/error-logger.js","../src/frontend-model-controller.js","../src/initializer.js","../src/logger.js","../src/mailer.js","../src/record-payload-values.js","../src/time-zone.js","../src/velocious-error.js","../src/authorization/ability.js","../src/authorization/base-resource.js","../src/background-jobs/client.js","../src/background-jobs/cron-expression.js","../src/background-jobs/forked-runner-child.js","../src/background-jobs/job-record.js","../src/background-jobs/job-registry.js","../src/background-jobs/job-runner.js","../src/background-jobs/job.js","../src/background-jobs/json-socket.js","../src/background-jobs/main.js","../src/background-jobs/normalize-error.js","../src/background-jobs/scheduler.js","../src/background-jobs/socket-request.js","../src/background-jobs/status-reporter.js","../src/background-jobs/store.js","../src/background-jobs/types.js","../src/background-jobs/worker.js","../src/background-jobs/web/authorization.js","../src/background-jobs/web/controller.js","../src/background-jobs/web/index.js","../src/background-jobs/web/path-matcher.js","../src/background-jobs/web/registry.js","../src/beacon/client.js","../src/beacon/in-process-broker.js","../src/beacon/in-process-client.js","../src/beacon/server.js","../src/beacon/types.js","../src/cli/base-command.js","../src/cli/browser-cli.js","../src/cli/index.js","../src/cli/tenant-database-command-helper.js","../src/cli/use-browser-cli.js","../src/cli/commands/background-jobs-main.js","../src/cli/commands/background-jobs-runner.js","../src/cli/commands/background-jobs-worker.js","../src/cli/commands/beacon.js","../src/cli/commands/console.js","../src/cli/commands/init.js","../src/cli/commands/routes.js","../src/cli/commands/run-script.js","../src/cli/commands/runner.js","../src/cli/commands/server.js","../src/cli/commands/test.js","../src/cli/commands/db/base-command.js","../src/cli/commands/db/create.js","../src/cli/commands/db/drop.js","../src/cli/commands/db/migrate.js","../src/cli/commands/db/reset.js","../src/cli/commands/db/rollback.js","../src/cli/commands/db/seed.js","../src/cli/commands/db/schema/dump.js","../src/cli/commands/db/schema/load.js","../src/cli/commands/db/tenants/check.js","../src/cli/commands/db/tenants/create.js","../src/cli/commands/db/tenants/drop.js","../src/cli/commands/db/tenants/migrate.js","../src/cli/commands/destroy/migration.js","../src/cli/commands/generate/base-models.js","../src/cli/commands/generate/frontend-models.js","../src/cli/commands/generate/migration.js","../src/cli/commands/generate/model.js","../src/cli/commands/lint/relationships.js","../src/database/annotations-async-hooks.js","../src/database/annotations.js","../src/database/column-types.js","../src/database/datetime-storage.js","../src/database/handler.js","../src/database/initializer-from-require-context.js","../src/database/migrations-ledger.js","../src/database/migrator.js","../src/database/use-database.js","../src/database/drivers/base-column.js","../src/database/drivers/base-columns-index.js","../src/database/drivers/base-foreign-key.js","../src/database/drivers/base-table.js","../src/database/drivers/base.js","../src/database/drivers/mssql/column.js","../src/database/drivers/mssql/columns-index.js","../src/database/drivers/mssql/connect-connection.js","../src/database/drivers/mssql/foreign-key.js","../src/database/drivers/mssql/index.js","../src/database/drivers/mssql/options.js","../src/database/drivers/mssql/query-parser.js","../src/database/drivers/mssql/structure-sql.js","../src/database/drivers/mssql/table.js","../src/database/drivers/mssql/sql/alter-table.js","../src/database/drivers/mssql/sql/create-database.js","../src/database/drivers/mssql/sql/create-index.js","../src/database/drivers/mssql/sql/create-table.js","../src/database/drivers/mssql/sql/delete.js","../src/database/drivers/mssql/sql/drop-database.js","../src/database/drivers/mssql/sql/drop-table.js","../src/database/drivers/mssql/sql/insert.js","../src/database/drivers/mssql/sql/remove-index.js","../src/database/drivers/mssql/sql/update.js","../src/database/drivers/mssql/sql/upsert.js","../src/database/drivers/mysql/column.js","../src/database/drivers/mysql/columns-index.js","../src/database/drivers/mysql/foreign-key.js","../src/database/drivers/mysql/index.js","../src/database/drivers/mysql/options.js","../src/database/drivers/mysql/query-parser.js","../src/database/drivers/mysql/query.js","../src/database/drivers/mysql/structure-sql.js","../src/database/drivers/mysql/table.js","../src/database/drivers/mysql/sql/alter-table.js","../src/database/drivers/mysql/sql/create-database.js","../src/database/drivers/mysql/sql/create-index.js","../src/database/drivers/mysql/sql/create-table.js","../src/database/drivers/mysql/sql/delete.js","../src/database/drivers/mysql/sql/drop-database.js","../src/database/drivers/mysql/sql/drop-table.js","../src/database/drivers/mysql/sql/insert.js","../src/database/drivers/mysql/sql/remove-index.js","../src/database/drivers/mysql/sql/update.js","../src/database/drivers/mysql/sql/upsert.js","../src/database/drivers/pgsql/column.js","../src/database/drivers/pgsql/columns-index.js","../src/database/drivers/pgsql/foreign-key.js","../src/database/drivers/pgsql/index.js","../src/database/drivers/pgsql/options.js","../src/database/drivers/pgsql/query-parser.js","../src/database/drivers/pgsql/structure-sql.js","../src/database/drivers/pgsql/table.js","../src/database/drivers/pgsql/sql/alter-table.js","../src/database/drivers/pgsql/sql/create-database.js","../src/database/drivers/pgsql/sql/create-index.js","../src/database/drivers/pgsql/sql/create-table.js","../src/database/drivers/pgsql/sql/delete.js","../src/database/drivers/pgsql/sql/drop-database.js","../src/database/drivers/pgsql/sql/drop-table.js","../src/database/drivers/pgsql/sql/insert.js","../src/database/drivers/pgsql/sql/remove-index.js","../src/database/drivers/pgsql/sql/update.js","../src/database/drivers/pgsql/sql/upsert.js","../src/database/drivers/sqlite/base.js","../src/database/drivers/sqlite/column.js","../src/database/drivers/sqlite/columns-index.js","../src/database/drivers/sqlite/connection-sql-js.js","../src/database/drivers/sqlite/foreign-key.js","../src/database/drivers/sqlite/index.js","../src/database/drivers/sqlite/index.native.js","../src/database/drivers/sqlite/index.web.js","../src/database/drivers/sqlite/options.js","../src/database/drivers/sqlite/query-parser.js","../src/database/drivers/sqlite/query.js","../src/database/drivers/sqlite/query.native.js","../src/database/drivers/sqlite/query.web.js","../src/database/drivers/sqlite/structure-sql.js","../src/database/drivers/sqlite/table-rebuilder.js","../src/database/drivers/sqlite/table.js","../src/database/drivers/sqlite/web-persistence.js","../src/database/drivers/sqlite/sql/alter-table.js","../src/database/drivers/sqlite/sql/create-index.js","../src/database/drivers/sqlite/sql/create-table.js","../src/database/drivers/sqlite/sql/delete.js","../src/database/drivers/sqlite/sql/drop-table.js","../src/database/drivers/sqlite/sql/insert.js","../src/database/drivers/sqlite/sql/remove-index.js","../src/database/drivers/sqlite/sql/update.js","../src/database/drivers/sqlite/sql/upsert.js","../src/database/drivers/structure-sql/utils.js","../src/database/migration/index.js","../src/database/migrator/files-finder.js","../src/database/migrator/types.js","../src/database/pool/async-tracked-multi-connection.js","../src/database/pool/base-methods-forward.js","../src/database/pool/base.js","../src/database/pool/single-multi-use.js","../src/database/query/alter-table-base.js","../src/database/query/base.js","../src/database/query/create-database-base.js","../src/database/query/create-index-base.js","../src/database/query/create-table-base.js","../src/database/query/delete-base.js","../src/database/query/drop-database-base.js","../src/database/query/drop-table-base.js","../src/database/query/from-base.js","../src/database/query/from-plain.js","../src/database/query/from-table.js","../src/database/query/index.js","../src/database/query/insert-base.js","../src/database/query/join-base.js","../src/database/query/join-object.js","../src/database/query/join-plain.js","../src/database/query/join-tracker.js","../src/database/query/model-class-query.js","../src/database/query/order-base.js","../src/database/query/order-column.js","../src/database/query/order-plain.js","../src/database/query/preloader.js","../src/database/query/query-data.js","../src/database/query/remove-index-base.js","../src/database/query/select-base.js","../src/database/query/select-plain.js","../src/database/query/select-table-and-column.js","../src/database/query/update-base.js","../src/database/query/upsert-base.js","../src/database/query/where-base.js","../src/database/query/where-combinator.js","../src/database/query/where-hash.js","../src/database/query/where-model-class-hash.js","../src/database/query/where-not.js","../src/database/query/where-plain.js","../src/database/query/with-count.js","../src/database/query/preloader/belongs-to.js","../src/database/query/preloader/ensure-model-class-initialized.js","../src/database/query/preloader/has-many.js","../src/database/query/preloader/has-one.js","../src/database/query/preloader/selection.js","../src/database/query-parser/base-query-parser.js","../src/database/query-parser/from-parser.js","../src/database/query-parser/group-parser.js","../src/database/query-parser/joins-parser.js","../src/database/query-parser/limit-parser.js","../src/database/query-parser/options.js","../src/database/query-parser/order-parser.js","../src/database/query-parser/select-parser.js","../src/database/query-parser/where-parser.js","../src/database/record/acts-as-list.js","../src/database/record/auditing.js","../src/database/record/counter-cache-magnitude.js","../src/database/record/index.js","../src/database/record/record-not-found-error.js","../src/database/record/state-machine.js","../src/database/record/user-module.js","../src/database/record/validation-messages.js","../src/database/record/attachments/attachment-record.js","../src/database/record/attachments/download.js","../src/database/record/attachments/handle.js","../src/database/record/attachments/normalize-input.js","../src/database/record/attachments/store.js","../src/database/record/attachments/storage-drivers/filesystem.js","../src/database/record/attachments/storage-drivers/native.js","../src/database/record/attachments/storage-drivers/s3.js","../src/database/record/instance-relationships/base.js","../src/database/record/instance-relationships/belongs-to.js","../src/database/record/instance-relationships/has-many.js","../src/database/record/instance-relationships/has-one.js","../src/database/record/relationships/base.js","../src/database/record/relationships/belongs-to.js","../src/database/record/relationships/has-many.js","../src/database/record/relationships/has-one.js","../src/database/record/validators/base.js","../src/database/record/validators/format.js","../src/database/record/validators/length.js","../src/database/record/validators/presence.js","../src/database/record/validators/uniqueness.js","../src/database/table-data/index.js","../src/database/table-data/table-column.js","../src/database/table-data/table-foreign-key.js","../src/database/table-data/table-index.js","../src/database/table-data/table-reference.js","../src/database/tenants/data-copier.js","../src/database/tenants/schema-cloner.js","../src/database/tenants/tenant-table-plan.js","../src/environment-handlers/base.js","../src/environment-handlers/browser.js","../src/environment-handlers/node.js","../src/environment-handlers/node/cli/commands/background-jobs-main.js","../src/environment-handlers/node/cli/commands/background-jobs-runner.js","../src/environment-handlers/node/cli/commands/background-jobs-worker.js","../src/environment-handlers/node/cli/commands/beacon.js","../src/environment-handlers/node/cli/commands/cli-command-context.js","../src/environment-handlers/node/cli/commands/console.js","../src/environment-handlers/node/cli/commands/init.js","../src/environment-handlers/node/cli/commands/routes.js","../src/environment-handlers/node/cli/commands/run-script.js","../src/environment-handlers/node/cli/commands/runner.js","../src/environment-handlers/node/cli/commands/server.js","../src/environment-handlers/node/cli/commands/test.js","../src/environment-handlers/node/cli/commands/db/seed.js","../src/environment-handlers/node/cli/commands/db/schema/dump.js","../src/environment-handlers/node/cli/commands/db/schema/load.js","../src/environment-handlers/node/cli/commands/destroy/migration.js","../src/environment-handlers/node/cli/commands/generate/base-models.js","../src/environment-handlers/node/cli/commands/generate/frontend-models.js","../src/environment-handlers/node/cli/commands/generate/generated-file-banner.js","../src/environment-handlers/node/cli/commands/generate/migration.js","../src/environment-handlers/node/cli/commands/generate/model.js","../src/environment-handlers/node/cli/commands/lint/relationships.js","../src/error-reporting/request-details.js","../src/frontend-model-resource/base-resource.js","../src/frontend-model-resource/velocious-attachment-resource.js","../src/frontend-models/base.js","../src/frontend-models/built-in-resources.js","../src/frontend-models/clear-pending-debounced-callback.js","../src/frontend-models/event-hook-models.js","../src/frontend-models/model-registry.js","../src/frontend-models/outgoing-event-buffer.js","../src/frontend-models/preloader.js","../src/frontend-models/query.js","../src/frontend-models/resource-config-validation.js","../src/frontend-models/resource-definition.js","../src/frontend-models/transport-serialization.js","../src/frontend-models/use-created-event.js","../src/frontend-models/use-destroyed-event.js","../src/frontend-models/use-model-class-event.js","../src/frontend-models/use-updated-event.js","../src/frontend-models/websocket-channel.js","../src/frontend-models/websocket-publishers.js","../src/http-client/header.js","../src/http-client/index.js","../src/http-client/request.js","../src/http-client/response.js","../src/http-client/websocket-client.js","../src/http-server/cookie.js","../src/http-server/development-reloader.js","../src/http-server/index.js","../src/http-server/remote-address.js","../src/http-server/server-client.js","../src/http-server/server-lock.js","../src/http-server/websocket-channel-subscribers.js","../src/http-server/websocket-channel.js","../src/http-server/websocket-connection.js","../src/http-server/websocket-event-log-store.js","../src/http-server/websocket-events-host.js","../src/http-server/websocket-events.js","../src/http-server/client/index.js","../src/http-server/client/params-to-object.js","../src/http-server/client/request-parser.js","../src/http-server/client/request-runner.js","../src/http-server/client/request-timing.js","../src/http-server/client/request.js","../src/http-server/client/response.js","../src/http-server/client/websocket-request.js","../src/http-server/client/websocket-session.js","../src/http-server/client/request-buffer/form-data-part.js","../src/http-server/client/request-buffer/header.js","../src/http-server/client/request-buffer/index.js","../src/http-server/client/uploaded-file/memory-uploaded-file.js","../src/http-server/client/uploaded-file/temporary-uploaded-file.js","../src/http-server/client/uploaded-file/uploaded-file.js","../src/http-server/worker-handler/channel-subscriber-dispatch.js","../src/http-server/worker-handler/in-process.js","../src/http-server/worker-handler/index.js","../src/http-server/worker-handler/worker-script.js","../src/http-server/worker-handler/worker-thread.js","../src/jobs/mail-delivery.js","../src/logger/base-logger.js","../src/logger/console-logger.js","../src/logger/file-logger.js","../src/logger/outputs/array-output.js","../src/logger/outputs/console-output.js","../src/logger/outputs/file-output.js","../src/logger/outputs/stdout-output.js","../src/mailer/base.js","../src/mailer/delivery.js","../src/mailer/index.js","../src/mailer/backends/smtp.js","../src/packages/velocious-package.js","../src/plugins/sqljs-wasm-route-controller.js","../src/plugins/sqljs-wasm-route.js","../src/routes/app-routes.js","../src/routes/base-route.js","../src/routes/basic-route.js","../src/routes/get-route.js","../src/routes/index.js","../src/routes/namespace-route.js","../src/routes/plugin-routes.js","../src/routes/post-route.js","../src/routes/resolver.js","../src/routes/resource-route.js","../src/routes/root-route.js","../src/routes/built-in/debug/controller.js","../src/routes/built-in/errors/controller.js","../src/routes/hooks/frontend-model-command-route-hook.js","../src/sync/conflict-strategy.js","../src/sync/device-identity.js","../src/sync/local-mutation-log.js","../src/sync/offline-grant.js","../src/sync/peer-mutation-bundle.js","../src/sync/query-scope.js","../src/sync/server-change-feed.js","../src/sync/server-sequence-allocator.js","../src/sync/stable-json.js","../src/sync/sync-api-client-types.js","../src/sync/sync-api-client.js","../src/sync/sync-api-controller.js","../src/sync/sync-client-registry.js","../src/sync/sync-client-types.js","../src/sync/sync-client.js","../src/sync/sync-envelope-replay-service.js","../src/sync/sync-model-change-feed-service.js","../src/sync/sync-replay-upsert-applier.js","../src/sync/sync-resource-base.js","../src/sync/sync-scope-store.js","../src/tenants/default-tenant-database-provisioning.js","../src/tenants/tenant-iterator.js","../src/tenants/tenant.js","../src/testing/base-expect.js","../src/testing/browser-frontend-model-event-hook-scenarios.js","../src/testing/browser-test-app.js","../src/testing/expect-to-change.js","../src/testing/expect-utils.js","../src/testing/expect.js","../src/testing/request-client.js","../src/testing/test-files-finder.js","../src/testing/test-filter-parser.js","../src/testing/test-runner.js","../src/testing/test-suite-splitter.js","../src/testing/test.js","../src/types/external-modules.d.ts","../src/utils/backtrace-cleaner-node.js","../src/utils/backtrace-cleaner.js","../src/utils/deburr-column-name.js","../src/utils/event-emitter.js","../src/utils/file-exists.js","../src/utils/format-value.js","../src/utils/is-date.js","../src/utils/model-scope.js","../src/utils/nest-callbacks.js","../src/utils/plain-object.js","../src/utils/ransack.js","../src/utils/rest-args-error.js","../src/utils/singularize-model-name.js","../src/utils/split-sql-statements.js","../src/utils/to-import-specifier.js","../src/utils/with-tracked-stack-async-hooks.js","../src/utils/with-tracked-stack.js"],"version":"6.0.3"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"velocious": "build/bin/velocious.js"
|
|
4
4
|
},
|
|
5
5
|
"name": "velocious",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.500",
|
|
7
7
|
"main": "build/index.js",
|
|
8
8
|
"types": "build/index.d.ts",
|
|
9
9
|
"files": [
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"diggerize": "^1.0.5",
|
|
58
58
|
"ejs": "^6.0.1",
|
|
59
59
|
"env-sense": "^1.0.2",
|
|
60
|
-
"epic-locks": "^1.0.
|
|
60
|
+
"epic-locks": "^1.0.8",
|
|
61
61
|
"escape-string-regexp": "^1.0.5",
|
|
62
62
|
"eventemitter3": "^5.0.1",
|
|
63
63
|
"gettext-universal": "^1.0.23",
|
|
@@ -148,12 +148,89 @@ export default class VelociousDatabaseDriversMssql extends Base{
|
|
|
148
148
|
return digg(rows, 0, "db_name")
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Disables every foreign key constraint (bulk `NOCHECK`).
|
|
153
|
+
* @returns {Promise<void>} - Resolves when foreign keys are disabled.
|
|
154
|
+
*/
|
|
151
155
|
async disableForeignKeys() {
|
|
152
|
-
await this.
|
|
156
|
+
await this._execConstraintToggle("EXEC sp_MSforeachtable \"ALTER TABLE ? NOCHECK CONSTRAINT all\"", "disableForeignKeys")
|
|
153
157
|
}
|
|
154
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Re-enables and re-validates every foreign key constraint (`WITH CHECK`).
|
|
161
|
+
* @returns {Promise<void>} - Resolves when foreign keys are enabled.
|
|
162
|
+
*/
|
|
155
163
|
async enableForeignKeys() {
|
|
156
|
-
await this.
|
|
164
|
+
await this._execConstraintToggle("EXEC sp_MSforeachtable @command1=\"print '?'\", @command2=\"ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all\"", "enableForeignKeys")
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Runs a bulk constraint-toggle statement. `ALTER TABLE ... NOCHECK/CHECK
|
|
169
|
+
* CONSTRAINT` needs a schema-modification lock on every table, so if the
|
|
170
|
+
* request times out it is almost always blocked by another session that is
|
|
171
|
+
* still holding a lock (a leaked/uncommitted connection). On a timeout,
|
|
172
|
+
* capture which sessions were blocking so the real culprit is named instead
|
|
173
|
+
* of leaving a bare "Request failed to complete in 15000ms".
|
|
174
|
+
* @param {string} sql - Constraint-toggle SQL.
|
|
175
|
+
* @param {string} label - Operation label for the error.
|
|
176
|
+
* @returns {Promise<void>} - Resolves when the toggle completes.
|
|
177
|
+
*/
|
|
178
|
+
async _execConstraintToggle(sql, label) {
|
|
179
|
+
try {
|
|
180
|
+
await this.query(sql)
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof Error && /Timeout: Request failed to complete/i.test(error.message)) {
|
|
183
|
+
const snapshot = await this._captureBlockingSessionsForDebug().catch(
|
|
184
|
+
(diagError) => `(blocking diagnostics query failed: ${diagError instanceof Error ? diagError.message : diagError})`
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
throw new Error(`${error.message}\n\n[${label} blocked] other user sessions with open transactions or active requests:\n${snapshot}`, {cause: error})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
throw error
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Snapshots the sessions that could be blocking a constraint toggle in THIS
|
|
196
|
+
* database — every session other than this one that holds a lock in `DB_ID()`
|
|
197
|
+
* or is running a request against it — with its last statement, wait state,
|
|
198
|
+
* and blocking session, enough to identify a connection that leaked a lock.
|
|
199
|
+
* Scoped to the current database so a multi-database server does not leak
|
|
200
|
+
* unrelated sessions' SQL into the error and bury the real blocker.
|
|
201
|
+
* @returns {Promise<string>} - JSON snapshot, or a "(none)" marker.
|
|
202
|
+
*/
|
|
203
|
+
async _captureBlockingSessionsForDebug() {
|
|
204
|
+
const rows = await this.query(`
|
|
205
|
+
WITH lock_sessions AS (
|
|
206
|
+
SELECT DISTINCT request_session_id AS session_id
|
|
207
|
+
FROM sys.dm_tran_locks
|
|
208
|
+
WHERE resource_database_id = DB_ID()
|
|
209
|
+
)
|
|
210
|
+
SELECT
|
|
211
|
+
s.session_id AS sessionId,
|
|
212
|
+
s.status AS sessionStatus,
|
|
213
|
+
s.login_time AS loginTime,
|
|
214
|
+
s.last_request_start_time AS lastRequestStart,
|
|
215
|
+
s.last_request_end_time AS lastRequestEnd,
|
|
216
|
+
s.open_transaction_count AS openTransactionCount,
|
|
217
|
+
r.status AS requestStatus,
|
|
218
|
+
r.command AS command,
|
|
219
|
+
r.wait_type AS waitType,
|
|
220
|
+
r.wait_time AS waitTimeMs,
|
|
221
|
+
r.blocking_session_id AS blockingSessionId,
|
|
222
|
+
CAST(ib.event_info AS NVARCHAR(MAX)) AS lastSql
|
|
223
|
+
FROM sys.dm_exec_sessions s
|
|
224
|
+
LEFT JOIN sys.dm_exec_requests r ON r.session_id = s.session_id
|
|
225
|
+
OUTER APPLY sys.dm_exec_input_buffer(s.session_id, NULL) ib
|
|
226
|
+
WHERE s.is_user_process = 1
|
|
227
|
+
AND s.session_id <> @@SPID
|
|
228
|
+
AND (s.session_id IN (SELECT session_id FROM lock_sessions) OR r.database_id = DB_ID())
|
|
229
|
+
`)
|
|
230
|
+
|
|
231
|
+
if (rows.length === 0) return "(none — no other session held a lock or ran a request in this database when queried)"
|
|
232
|
+
|
|
233
|
+
return JSON.stringify(rows, null, 2)
|
|
157
234
|
}
|
|
158
235
|
|
|
159
236
|
/**
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reactive counter-cache driven by a per-record magnitude.
|
|
5
|
+
*
|
|
6
|
+
* Maintains a counter column on a `belongsTo` parent as the running sum of each
|
|
7
|
+
* child's magnitude — a small number derived from one source attribute (e.g.
|
|
8
|
+
* `1` while a build's `status` is `running`, else `0`). On every create, update
|
|
9
|
+
* and destroy the change in magnitude is applied to the parent as a single
|
|
10
|
+
* atomic increment (`SET col = col + delta`), and when the foreign key changes
|
|
11
|
+
* the magnitude is moved from the old parent to the new one.
|
|
12
|
+
*
|
|
13
|
+
* Because the counter is derived from the source attribute and diffed on every
|
|
14
|
+
* write, it follows that attribute automatically no matter which code path wrote
|
|
15
|
+
* it — there is no per-transition increment/decrement to forget. The old value is
|
|
16
|
+
* captured in `beforeSave` (Velocious clears `changes()` during the post-update
|
|
17
|
+
* reload) and consumed in `afterSave`, so the increment commits atomically with
|
|
18
|
+
* the row it reflects.
|
|
19
|
+
* @typedef {{
|
|
20
|
+
* belongsTo: string,
|
|
21
|
+
* counterColumn: string,
|
|
22
|
+
* sourceAttribute: string,
|
|
23
|
+
* magnitude: (sourceValue: ?) => number
|
|
24
|
+
* }} MagnitudeCounterCacheDefinition
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Captured pending magnitude change stashed on a record between beforeSave and afterSave.
|
|
29
|
+
* @typedef {{newMagnitude: number, oldMagnitude: number, newParentId: ?, oldParentId: ?}} PendingMagnitudeDelta
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Registers a reactive magnitude counter-cache on a model class.
|
|
34
|
+
* @param {typeof import("./index.js").default} modelClass - Model class to add the counter cache to.
|
|
35
|
+
* @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
|
|
36
|
+
* @returns {void}
|
|
37
|
+
*/
|
|
38
|
+
export function registerMagnitudeCounterCache(modelClass, definition) {
|
|
39
|
+
/**
|
|
40
|
+
* Dynamic class.
|
|
41
|
+
* @type {?} */
|
|
42
|
+
const dynamicClass = modelClass
|
|
43
|
+
const registeredFlag = `_magnitudeCounterCacheRegistered_${definition.counterColumn}`
|
|
44
|
+
|
|
45
|
+
// Idempotent per counter column: re-declaring (or subclassing) must not double-register.
|
|
46
|
+
if (Object.prototype.hasOwnProperty.call(dynamicClass, registeredFlag) && dynamicClass[registeredFlag]) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
dynamicClass[registeredFlag] = true
|
|
51
|
+
|
|
52
|
+
const pendingKey = `_magnitudeCounterCachePending_${definition.counterColumn}`
|
|
53
|
+
|
|
54
|
+
modelClass.beforeSave(async function (record) {
|
|
55
|
+
/**
|
|
56
|
+
* Dynamic record.
|
|
57
|
+
* @type {?} */
|
|
58
|
+
const dynamicRecord = record
|
|
59
|
+
|
|
60
|
+
dynamicRecord[pendingKey] = computePendingMagnitudeDelta(record, definition)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
modelClass.afterSave(async function (record) {
|
|
64
|
+
/**
|
|
65
|
+
* Dynamic record.
|
|
66
|
+
* @type {?} */
|
|
67
|
+
const dynamicRecord = record
|
|
68
|
+
const pending = /** @type {PendingMagnitudeDelta | null} */ (dynamicRecord[pendingKey])
|
|
69
|
+
|
|
70
|
+
dynamicRecord[pendingKey] = null
|
|
71
|
+
|
|
72
|
+
if (pending) {
|
|
73
|
+
await applyPendingMagnitudeDelta(record, definition, pending)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
modelClass.afterDestroy(async function (record) {
|
|
78
|
+
const magnitude = definition.magnitude(record.readAttribute(definition.sourceAttribute))
|
|
79
|
+
const parentId = currentParentId(record, definition)
|
|
80
|
+
|
|
81
|
+
if (parentId && magnitude !== 0) {
|
|
82
|
+
await incrementParentCounter(record, definition, parentId, -magnitude)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Diffs the source attribute (and foreign key) across the pending save to capture
|
|
89
|
+
* how the parent counter should move. Runs in beforeSave, where `changes()` still
|
|
90
|
+
* holds the pre-save values.
|
|
91
|
+
* @param {import("./index.js").default} record - Record being saved.
|
|
92
|
+
* @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
|
|
93
|
+
* @returns {PendingMagnitudeDelta} The captured magnitude change.
|
|
94
|
+
*/
|
|
95
|
+
function computePendingMagnitudeDelta(record, definition) {
|
|
96
|
+
const changes = record.changes()
|
|
97
|
+
const sourceColumn = columnNameForAttribute(record, definition.sourceAttribute)
|
|
98
|
+
const foreignKeyColumn = record.getModelClass().getRelationshipByName(definition.belongsTo).getForeignKey()
|
|
99
|
+
|
|
100
|
+
const newSource = record.readAttribute(definition.sourceAttribute)
|
|
101
|
+
const oldSource = oldSourceValueThroughReadCast(record, definition.sourceAttribute, sourceColumn, changes, newSource)
|
|
102
|
+
|
|
103
|
+
const newParentId = currentParentId(record, definition)
|
|
104
|
+
const oldParentId = foreignKeyColumn in changes ? changes[foreignKeyColumn][0] : newParentId
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
newMagnitude: definition.magnitude(newSource),
|
|
108
|
+
oldMagnitude: definition.magnitude(oldSource),
|
|
109
|
+
newParentId,
|
|
110
|
+
oldParentId
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Applies a captured magnitude change to the parent counter(s) as atomic increments.
|
|
116
|
+
* @param {import("./index.js").default} record - Record that was saved.
|
|
117
|
+
* @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
|
|
118
|
+
* @param {PendingMagnitudeDelta} pending - The captured magnitude change.
|
|
119
|
+
* @returns {Promise<void>}
|
|
120
|
+
*/
|
|
121
|
+
async function applyPendingMagnitudeDelta(record, definition, pending) {
|
|
122
|
+
const {newMagnitude, oldMagnitude, newParentId, oldParentId} = pending
|
|
123
|
+
|
|
124
|
+
if (oldParentId === newParentId) {
|
|
125
|
+
const delta = newMagnitude - oldMagnitude
|
|
126
|
+
|
|
127
|
+
if (newParentId && delta !== 0) {
|
|
128
|
+
await incrementParentCounter(record, definition, newParentId, delta)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// The foreign key moved: take the old magnitude off the old parent and put the
|
|
135
|
+
// new magnitude on the new parent.
|
|
136
|
+
if (oldParentId && oldMagnitude !== 0) {
|
|
137
|
+
await incrementParentCounter(record, definition, oldParentId, -oldMagnitude)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (newParentId && newMagnitude !== 0) {
|
|
141
|
+
await incrementParentCounter(record, definition, newParentId, newMagnitude)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Reads the pre-save value of the source attribute through the normal read/cast
|
|
147
|
+
* path, so `magnitude` receives the old value in the same shape as the new one
|
|
148
|
+
* (e.g. a declared boolean as `true`/`false`, not the raw stored `1`/`0`). Drops
|
|
149
|
+
* the pending change so `readAttribute` falls back to the committed value and
|
|
150
|
+
* applies the same cast a fresh read would, then restores the pending change.
|
|
151
|
+
* @param {import("./index.js").default} record - Record being saved.
|
|
152
|
+
* @param {string} sourceAttribute - Source attribute name.
|
|
153
|
+
* @param {string} sourceColumn - Source column name.
|
|
154
|
+
* @param {Record<string, ?>} changes - The record's pre-save changes (column-keyed).
|
|
155
|
+
* @param {?} currentValue - The current (new) read value, returned when the source did not change.
|
|
156
|
+
* @returns {?} The read-cast pre-save value.
|
|
157
|
+
*/
|
|
158
|
+
function oldSourceValueThroughReadCast(record, sourceAttribute, sourceColumn, changes, currentValue) {
|
|
159
|
+
if (!(sourceColumn in changes)) {
|
|
160
|
+
return currentValue
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Dynamic record.
|
|
165
|
+
* @type {?} */
|
|
166
|
+
const dynamicRecord = record
|
|
167
|
+
const pendingChange = dynamicRecord._changes[sourceColumn]
|
|
168
|
+
|
|
169
|
+
delete dynamicRecord._changes[sourceColumn]
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
return record.readAttribute(sourceAttribute)
|
|
173
|
+
} finally {
|
|
174
|
+
dynamicRecord._changes[sourceColumn] = pendingChange
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Reads the record's current foreign-key value for the counter-cache parent.
|
|
180
|
+
* @param {import("./index.js").default} record - Record whose parent is targeted.
|
|
181
|
+
* @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
|
|
182
|
+
* @returns {?} The current foreign-key value.
|
|
183
|
+
*/
|
|
184
|
+
function currentParentId(record, definition) {
|
|
185
|
+
const foreignKeyColumn = record.getModelClass().getRelationshipByName(definition.belongsTo).getForeignKey()
|
|
186
|
+
|
|
187
|
+
return record.readAttribute(attributeNameForColumn(record, foreignKeyColumn))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Atomically adds `amount` to the parent's counter column for one parent row.
|
|
192
|
+
* @param {import("./index.js").default} record - Child record (for connection + relationship metadata).
|
|
193
|
+
* @param {MagnitudeCounterCacheDefinition} definition - Counter cache definition.
|
|
194
|
+
* @param {?} parentId - Parent primary-key value.
|
|
195
|
+
* @param {number} amount - Signed integer to add.
|
|
196
|
+
* @returns {Promise<void>}
|
|
197
|
+
*/
|
|
198
|
+
async function incrementParentCounter(record, definition, parentId, amount) {
|
|
199
|
+
const modelClass = record.getModelClass()
|
|
200
|
+
const relationship = modelClass.getRelationshipByName(definition.belongsTo)
|
|
201
|
+
const parentModelClass = relationship.getTargetModelClass()
|
|
202
|
+
|
|
203
|
+
if (!parentModelClass) {
|
|
204
|
+
throw new Error(`magnitudeCounterCache on ${modelClass.getModelName()} could not resolve the "${definition.belongsTo}" parent model class`)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Update through the PARENT model's connection: the row being modified belongs
|
|
208
|
+
// to the parent, which may live on a different database/tenant than the child.
|
|
209
|
+
const db = parentModelClass.connection()
|
|
210
|
+
const counterColumnSql = db.quoteColumn(definition.counterColumn)
|
|
211
|
+
const truncatedAmount = Math.trunc(amount)
|
|
212
|
+
|
|
213
|
+
await db.query(
|
|
214
|
+
`UPDATE ${db.quoteTable(parentModelClass.tableName())} ` +
|
|
215
|
+
`SET ${counterColumnSql} = COALESCE(${counterColumnSql}, 0) + ${truncatedAmount} ` +
|
|
216
|
+
`WHERE ${db.quoteColumn(relationship.getPrimaryKey())} = ${db.quote(parentId)}`
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Resolves the column name backing an attribute name.
|
|
222
|
+
* @param {import("./index.js").default} record - Record.
|
|
223
|
+
* @param {string} attributeName - Attribute name.
|
|
224
|
+
* @returns {string} The column name for the attribute (falls back to the attribute name).
|
|
225
|
+
*/
|
|
226
|
+
function columnNameForAttribute(record, attributeName) {
|
|
227
|
+
return record.getModelClass().getAttributeNameToColumnNameMap()[attributeName] || attributeName
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Resolves the attribute name backing a column name.
|
|
232
|
+
* @param {import("./index.js").default} record - Record.
|
|
233
|
+
* @param {string} columnName - Column name.
|
|
234
|
+
* @returns {string} The attribute name for the column (falls back to the column name).
|
|
235
|
+
*/
|
|
236
|
+
function attributeNameForColumn(record, columnName) {
|
|
237
|
+
const map = record.getModelClass().getAttributeNameToColumnNameMap()
|
|
238
|
+
|
|
239
|
+
return Object.keys(map).find((attributeName) => map[attributeName] === columnName) || columnName
|
|
240
|
+
}
|
|
@@ -47,6 +47,8 @@ import {defineModelScope} from "../../utils/model-scope.js"
|
|
|
47
47
|
import { normalizeDateStringForWrite, normalizeDateValueForRead, normalizeDateValueForWrite } from "../datetime-storage.js"
|
|
48
48
|
import {formatValue} from "../../utils/format-value.js"
|
|
49
49
|
import {captureCreateAuditChanges, captureUpdateAuditChanges, createAudit, createCreateAudit, createDestroyAudit, createUpdateAudit, registerAuditCallback, registerAuditing, withoutAudit} from "./auditing.js"
|
|
50
|
+
import {registerMagnitudeCounterCache} from "./counter-cache-magnitude.js"
|
|
51
|
+
import {stateMachine} from "./state-machine.js"
|
|
50
52
|
import ValidatorsFormat from "./validators/format.js"
|
|
51
53
|
import ValidatorsLength from "./validators/length.js"
|
|
52
54
|
import ValidatorsPresence from "./validators/presence.js"
|
|
@@ -668,6 +670,30 @@ class VelociousDatabaseRecord {
|
|
|
668
670
|
registerAuditing(this)
|
|
669
671
|
}
|
|
670
672
|
|
|
673
|
+
/**
|
|
674
|
+
* Declares an aasm-style state machine on this model: named states, events
|
|
675
|
+
* (guarded transitions), and enter/exit + before/after transition hooks. See
|
|
676
|
+
* `state-machine.js`. Generates `event()` / `eventAndSave()` / `canEvent()`
|
|
677
|
+
* transition methods per declared event.
|
|
678
|
+
* @param {import("./state-machine.js").StateMachineDefinition} definition - State machine definition.
|
|
679
|
+
* @returns {void}
|
|
680
|
+
*/
|
|
681
|
+
static stateMachine(definition) {
|
|
682
|
+
stateMachine(this, definition)
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Maintains a counter column on a `belongsTo` parent as the sum of a per-record
|
|
687
|
+
* magnitude, kept current by atomic increments diffed on every create/update/
|
|
688
|
+
* destroy (and moved between parents when the foreign key changes). See
|
|
689
|
+
* `counter-cache-magnitude.js`.
|
|
690
|
+
* @param {import("./counter-cache-magnitude.js").MagnitudeCounterCacheDefinition} definition - Counter cache definition.
|
|
691
|
+
* @returns {void}
|
|
692
|
+
*/
|
|
693
|
+
static magnitudeCounterCache(definition) {
|
|
694
|
+
registerMagnitudeCounterCache(this, definition)
|
|
695
|
+
}
|
|
696
|
+
|
|
671
697
|
/**
|
|
672
698
|
* Registers a callback invoked after this model writes an audit row for the action.
|
|
673
699
|
* @template {typeof VelociousDatabaseRecord} MC
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* }} StateMachineDefinition
|
|
11
11
|
* @typedef {{
|
|
12
12
|
* beforeEnter?: (model: import("./index.js").default) => void | Promise<void>,
|
|
13
|
-
* afterEnter?: (model: import("./index.js").default) => void | Promise<void
|
|
13
|
+
* afterEnter?: (model: import("./index.js").default) => void | Promise<void>,
|
|
14
|
+
* beforeExit?: (model: import("./index.js").default) => void | Promise<void>,
|
|
15
|
+
* afterExit?: (model: import("./index.js").default) => void | Promise<void>
|
|
14
16
|
* }} StateDefinition
|
|
15
17
|
* @typedef {{
|
|
16
18
|
* from: string | string[],
|
|
@@ -68,6 +70,14 @@ export function stateMachine(ModelClass, definition) {
|
|
|
68
70
|
* @type {?} */
|
|
69
71
|
const dynamicClass = ModelClass
|
|
70
72
|
|
|
73
|
+
// Idempotent: re-declaring on the same class (or a re-evaluated module) must not
|
|
74
|
+
// register the before/after-save transition hooks twice. Guard on an own property
|
|
75
|
+
// so a subclass declaring its own machine is unaffected by the parent's flag.
|
|
76
|
+
if (Object.prototype.hasOwnProperty.call(dynamicClass, "_stateMachineRegistered") && dynamicClass._stateMachineRegistered) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
dynamicClass._stateMachineRegistered = true
|
|
71
81
|
dynamicClass._stateMachineDefinition = definition
|
|
72
82
|
dynamicClass._stateMachineColumn = column
|
|
73
83
|
|
|
@@ -229,7 +239,13 @@ export function stateMachine(ModelClass, definition) {
|
|
|
229
239
|
await eventDef.before(model)
|
|
230
240
|
}
|
|
231
241
|
|
|
232
|
-
// Run state
|
|
242
|
+
// Run the exited state's beforeExit, then the entered state's beforeEnter
|
|
243
|
+
const fromStateDefinition = definition.states[pending.from]
|
|
244
|
+
|
|
245
|
+
if (fromStateDefinition?.beforeExit) {
|
|
246
|
+
await fromStateDefinition.beforeExit(model)
|
|
247
|
+
}
|
|
248
|
+
|
|
233
249
|
const stateDefinition = definition.states[pending.to]
|
|
234
250
|
|
|
235
251
|
if (stateDefinition?.beforeEnter) {
|
|
@@ -252,13 +268,19 @@ export function stateMachine(ModelClass, definition) {
|
|
|
252
268
|
// Clear the pending transition now that save is complete
|
|
253
269
|
dynamicModel[PENDING_TRANSITION_KEY] = null
|
|
254
270
|
|
|
255
|
-
// Run state
|
|
271
|
+
// Run the entered state's afterEnter, then the exited state's afterExit
|
|
256
272
|
const stateDefinition = definition.states[pending.to]
|
|
257
273
|
|
|
258
274
|
if (stateDefinition?.afterEnter) {
|
|
259
275
|
await stateDefinition.afterEnter(model)
|
|
260
276
|
}
|
|
261
277
|
|
|
278
|
+
const fromStateDefinition = definition.states[pending.from]
|
|
279
|
+
|
|
280
|
+
if (fromStateDefinition?.afterExit) {
|
|
281
|
+
await fromStateDefinition.afterExit(model)
|
|
282
|
+
}
|
|
283
|
+
|
|
262
284
|
// Run event-level after callback
|
|
263
285
|
const eventDef = definition.events[pending.eventName]
|
|
264
286
|
|
|
@@ -94,6 +94,33 @@ export default class DataCopier {
|
|
|
94
94
|
return sourceRowsByTableName
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Deletes one tenant's rows from the target database, children before parents, with
|
|
99
|
+
* foreign keys disabled inside a single transaction. This is `copy` without the reinsert:
|
|
100
|
+
* the same plan traversal selects the tenant's current target rows and `deleteTargetRows`
|
|
101
|
+
* removes them children-first so the ordering never trips a foreign key. Multi-tenant apps
|
|
102
|
+
* use it to purge a tenant — for example clearing the tenant's master copy in the
|
|
103
|
+
* global/default database on teardown, so foreign keys stop referencing the tenant's
|
|
104
|
+
* about-to-be-removed root row.
|
|
105
|
+
*
|
|
106
|
+
* Returns the deleted rows keyed by table name so the caller can perform any app-specific
|
|
107
|
+
* post-delete work (record-location cleanup, auditing) without that policy leaking into the
|
|
108
|
+
* framework.
|
|
109
|
+
* @param {string} keyValue - Tenant key whose rows should be removed from the target.
|
|
110
|
+
* @returns {Promise<Map<string, Record<string, unknown>[]>>} - The deleted rows by table name.
|
|
111
|
+
*/
|
|
112
|
+
async deleteTenantRows(keyValue) {
|
|
113
|
+
const rowsByTableName = await this.loadRows(this.targetDb, keyValue)
|
|
114
|
+
|
|
115
|
+
await this.targetDb.withDisabledForeignKeys(async () => {
|
|
116
|
+
await this.targetDb.transaction(async () => {
|
|
117
|
+
await this.deleteTargetRows(rowsByTableName)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return rowsByTableName
|
|
122
|
+
}
|
|
123
|
+
|
|
97
124
|
/**
|
|
98
125
|
* Loads the rows for `keyValue` for every table in the plan from `db`, resolving
|
|
99
126
|
* parent-scoped tables from the ids already selected for their parent table. Used for
|