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.
Files changed (33) hide show
  1. package/README.md +1 -1
  2. package/build/database/drivers/mssql/index.js +79 -2
  3. package/build/database/record/counter-cache-magnitude.js +240 -0
  4. package/build/database/record/index.js +26 -0
  5. package/build/database/record/state-machine.js +25 -3
  6. package/build/database/tenants/data-copier.js +27 -0
  7. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +4 -20
  8. package/build/src/database/drivers/mssql/index.d.ts +22 -0
  9. package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
  10. package/build/src/database/drivers/mssql/index.js +74 -3
  11. package/build/src/database/record/counter-cache-magnitude.d.ts +67 -0
  12. package/build/src/database/record/counter-cache-magnitude.d.ts.map +1 -0
  13. package/build/src/database/record/counter-cache-magnitude.js +204 -0
  14. package/build/src/database/record/index.d.ts +18 -0
  15. package/build/src/database/record/index.d.ts.map +1 -1
  16. package/build/src/database/record/index.js +25 -1
  17. package/build/src/database/record/state-machine.d.ts +2 -0
  18. package/build/src/database/record/state-machine.d.ts.map +1 -1
  19. package/build/src/database/record/state-machine.js +21 -4
  20. package/build/src/database/tenants/data-copier.d.ts +16 -0
  21. package/build/src/database/tenants/data-copier.d.ts.map +1 -1
  22. package/build/src/database/tenants/data-copier.js +25 -1
  23. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +0 -9
  24. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  25. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +5 -16
  26. package/build/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +2 -2
  28. package/src/database/drivers/mssql/index.js +79 -2
  29. package/src/database/record/counter-cache-magnitude.js +240 -0
  30. package/src/database/record/index.js +26 -0
  31. package/src/database/record/state-machine.js +25 -3
  32. package/src/database/tenants/data-copier.js +27 -0
  33. 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.498",
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.7",
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.query("EXEC sp_MSforeachtable \"ALTER TABLE ? NOCHECK CONSTRAINT all\"")
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.query("EXEC sp_MSforeachtable @command1=\"print '?'\", @command2=\"ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all\"")
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-level beforeEnter callback
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-level afterEnter callback
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