velocious 1.0.510 → 1.0.512

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 (46) hide show
  1. package/README.md +1 -0
  2. package/build/database/advisory-lock-runner.js +192 -0
  3. package/build/database/drivers/mysql/index.js +3 -0
  4. package/build/database/live-query.js +244 -0
  5. package/build/database/record/index.js +48 -111
  6. package/build/database/record-changes.js +135 -0
  7. package/build/database/use-live-query.js +118 -0
  8. package/build/src/configuration-types.d.ts +1 -1
  9. package/build/src/configuration.d.ts +1 -1
  10. package/build/src/database/advisory-lock-runner.d.ts +124 -0
  11. package/build/src/database/advisory-lock-runner.d.ts.map +1 -0
  12. package/build/src/database/advisory-lock-runner.js +176 -0
  13. package/build/src/database/drivers/mysql/index.d.ts +2 -2
  14. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  15. package/build/src/database/drivers/mysql/index.js +3 -1
  16. package/build/src/database/live-query.d.ts +168 -0
  17. package/build/src/database/live-query.d.ts.map +1 -0
  18. package/build/src/database/live-query.js +211 -0
  19. package/build/src/database/record/attachments/handle.d.ts +1 -1
  20. package/build/src/database/record/index.d.ts +24 -61
  21. package/build/src/database/record/index.d.ts.map +1 -1
  22. package/build/src/database/record/index.js +46 -108
  23. package/build/src/database/record-changes.d.ts +111 -0
  24. package/build/src/database/record-changes.d.ts.map +1 -0
  25. package/build/src/database/record-changes.js +115 -0
  26. package/build/src/database/use-live-query.d.ts +44 -0
  27. package/build/src/database/use-live-query.d.ts.map +1 -0
  28. package/build/src/database/use-live-query.js +97 -0
  29. package/build/src/sync/sync-api-client.d.ts.map +1 -1
  30. package/build/src/sync/sync-api-client.js +17 -10
  31. package/build/src/sync/sync-envelope-replay-service.d.ts +1 -1
  32. package/build/src/sync/sync-realtime-bridge.d.ts.map +1 -1
  33. package/build/src/sync/sync-realtime-bridge.js +9 -5
  34. package/build/src/sync/sync-replay-upsert-applier.d.ts +2 -2
  35. package/build/sync/sync-api-client.js +19 -11
  36. package/build/sync/sync-realtime-bridge.js +9 -4
  37. package/build/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +1 -1
  39. package/src/database/advisory-lock-runner.js +192 -0
  40. package/src/database/drivers/mysql/index.js +3 -0
  41. package/src/database/live-query.js +244 -0
  42. package/src/database/record/index.js +48 -111
  43. package/src/database/record-changes.js +135 -0
  44. package/src/database/use-live-query.js +118 -0
  45. package/src/sync/sync-api-client.js +19 -11
  46. package/src/sync/sync-realtime-bridge.js +9 -4
@@ -1,5 +1,7 @@
1
1
  // @ts-check
2
2
 
3
+ import recordChanges from "../database/record-changes.js"
4
+
3
5
  import SyncApiClient from "./sync-api-client.js"
4
6
  import {VELOCIOUS_SYNC_CHANNEL} from "./sync-channel-name.js"
5
7
 
@@ -350,11 +352,14 @@ export default class SyncRealtimeBridge {
350
352
  const syncPayloads = Array.isArray(body.syncs) ? body.syncs : [body]
351
353
  const applySync = this.syncClient.remoteApplySync({source: "remote change"})
352
354
 
353
- for (const syncPayload of syncPayloads) {
354
- const sync = SyncApiClient.syncEnvelopeFromPayload({resourceType, ...syncPayload})
355
+ // Coalesce record-change events across the pushed batch so it triggers one live-query re-run.
356
+ await recordChanges.batch(async () => {
357
+ for (const syncPayload of syncPayloads) {
358
+ const sync = SyncApiClient.syncEnvelopeFromPayload({resourceType, ...syncPayload})
355
359
 
356
- await applySync(sync)
357
- }
360
+ await applySync(sync)
361
+ }
362
+ })
358
363
  }
359
364
 
360
365
  /**
@@ -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/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/api-manifest/controller.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-change-fanout.js","../src/sync/sync-channel-name.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-publish-suppression.js","../src/sync/sync-publisher-types.js","../src/sync/sync-publisher.js","../src/sync/sync-realtime-bridge.js","../src/sync/sync-replay-upsert-applier.js","../src/sync/sync-resource-base.js","../src/sync/sync-scope-attributes.js","../src/sync/sync-scope-store.js","../src/sync/sync-websocket-channel.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/advisory-lock-runner.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/live-query.js","../src/database/migrations-ledger.js","../src/database/migrator.js","../src/database/record-changes.js","../src/database/use-database.js","../src/database/use-live-query.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/api-manifest/controller.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-change-fanout.js","../src/sync/sync-channel-name.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-publish-suppression.js","../src/sync/sync-publisher-types.js","../src/sync/sync-publisher.js","../src/sync/sync-realtime-bridge.js","../src/sync/sync-replay-upsert-applier.js","../src/sync/sync-resource-base.js","../src/sync/sync-scope-attributes.js","../src/sync/sync-scope-store.js","../src/sync/sync-websocket-channel.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.510",
6
+ "version": "1.0.512",
7
7
  "main": "build/index.js",
8
8
  "types": "build/index.d.ts",
9
9
  "files": [
@@ -0,0 +1,192 @@
1
+ // @ts-check
2
+
3
+ import timeout from "awaitery/build/timeout.js"
4
+
5
+ /**
6
+ * Thrown when an advisory lock could not be acquired before `timeoutMs` elapsed.
7
+ */
8
+ class AdvisoryLockTimeoutError extends Error {
9
+ /**
10
+ * Runs constructor.
11
+ * @param {string} message - Error message.
12
+ * @param {{name: string}} args - The advisory lock name that timed out.
13
+ */
14
+ constructor(message, {name}) {
15
+ super(message)
16
+ this.name = "AdvisoryLockTimeoutError"
17
+ this.lockName = name
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Thrown when `withAdvisoryLockOrFail` finds the lock already held.
23
+ */
24
+ class AdvisoryLockBusyError extends Error {
25
+ /**
26
+ * Runs constructor.
27
+ * @param {string} message - Error message.
28
+ * @param {{name: string}} args - The advisory lock name that was already held.
29
+ */
30
+ constructor(message, {name}) {
31
+ super(message)
32
+ this.name = "AdvisoryLockBusyError"
33
+ this.lockName = name
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Thrown when a callback holds an advisory lock longer than `holdTimeoutMs`.
39
+ */
40
+ class AdvisoryLockHoldTimeoutError extends Error {
41
+ /**
42
+ * Runs constructor.
43
+ * @param {string} message - Error message.
44
+ * @param {{name: string}} args - The advisory lock name whose hold timed out.
45
+ */
46
+ constructor(message, {name}) {
47
+ super(message)
48
+ this.name = "AdvisoryLockHoldTimeoutError"
49
+ this.lockName = name
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Runs advisory locks on the caller connection by default, using a dedicated
55
+ * lock connection only when a positive hold timeout needs separate ownership.
56
+ */
57
+ export default class AdvisoryLockRunner {
58
+ /**
59
+ * Creates an advisory-lock runner for one model database identifier.
60
+ * @param {{configuration: import("../configuration.js").default, connectionProvider: () => import("./drivers/base.js").default, databaseIdentifier: string}} args - Runner dependencies.
61
+ */
62
+ constructor({configuration, connectionProvider, databaseIdentifier}) {
63
+ this.configuration = configuration
64
+ this.connectionProvider = connectionProvider
65
+ this.databaseIdentifier = databaseIdentifier
66
+ }
67
+
68
+ /**
69
+ * Runs a callback after acquiring the advisory lock, waiting up to `timeoutMs`.
70
+ * @template T
71
+ * @param {string} name - Lock name.
72
+ * @param {() => Promise<T>} callback - Callback to invoke while the lock is held.
73
+ * @param {{timeoutMs?: number | null, holdTimeoutMs?: number | null}} [args] - Lock and hold timeout options.
74
+ * @returns {Promise<T>} - Resolves with the callback result.
75
+ */
76
+ async withAdvisoryLock(name, callback, args = {}) {
77
+ return await this.withLockConnection(args.holdTimeoutMs, async (connection) => {
78
+ const acquired = await connection.acquireAdvisoryLock(name, args)
79
+
80
+ if (!acquired) {
81
+ throw new AdvisoryLockTimeoutError(`Timed out waiting for advisory lock ${JSON.stringify(name)}`, {name})
82
+ }
83
+
84
+ return await this.runLockedCallback({callback, connection, holdTimeoutMs: args.holdTimeoutMs, name})
85
+ })
86
+ }
87
+
88
+ /**
89
+ * Runs a callback only if the advisory lock can be acquired immediately.
90
+ * @template T
91
+ * @param {string} name - Lock name.
92
+ * @param {() => Promise<T>} callback - Callback to invoke while the lock is held.
93
+ * @param {{holdTimeoutMs?: number | null}} [args] - Hold timeout options.
94
+ * @returns {Promise<T>} - Resolves with the callback result.
95
+ */
96
+ async withAdvisoryLockOrFail(name, callback, args = {}) {
97
+ return await this.withLockConnection(args.holdTimeoutMs, async (connection) => {
98
+ const acquired = await connection.tryAcquireAdvisoryLock(name)
99
+
100
+ if (!acquired) {
101
+ throw new AdvisoryLockBusyError(`Advisory lock ${JSON.stringify(name)} is already held`, {name})
102
+ }
103
+
104
+ return await this.runLockedCallback({callback, connection, holdTimeoutMs: args.holdTimeoutMs, name})
105
+ })
106
+ }
107
+
108
+ /**
109
+ * Runs the lock holder callback and releases the lock from its owning connection.
110
+ * @template T
111
+ * @param {{callback: () => Promise<T>, connection: import("./drivers/base.js").default, holdTimeoutMs?: number | null, name: string}} args - Locked callback args.
112
+ * @returns {Promise<T>} - Resolves with the callback result.
113
+ */
114
+ async runLockedCallback({callback, connection, holdTimeoutMs, name}) {
115
+ try {
116
+ return await AdvisoryLockRunner.runWithAdvisoryLockHoldTimeout(name, callback, holdTimeoutMs)
117
+ } finally {
118
+ await connection.releaseAdvisoryLock(name)
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Runs lock work on the caller connection unless a positive hold timeout needs
124
+ * its own lock connection.
125
+ * @template T
126
+ * @param {number | null | undefined} holdTimeoutMs - Max hold time; positive values use a dedicated lock connection.
127
+ * @param {(connection: import("./drivers/base.js").default) => Promise<T>} callback - Callback receiving the connection that owns the advisory lock.
128
+ * @returns {Promise<T>} - Resolves with the callback result.
129
+ */
130
+ async withLockConnection(holdTimeoutMs, callback) {
131
+ if (holdTimeoutMs && holdTimeoutMs > 0) {
132
+ return await this.withDedicatedConnection(callback)
133
+ }
134
+
135
+ return await callback(this.connectionProvider())
136
+ }
137
+
138
+ /**
139
+ * Spawns a hold-timeout lock connection and closes it after lock work completes when
140
+ * the spawned driver owns the underlying physical connection.
141
+ * @template T
142
+ * @param {(connection: import("./drivers/base.js").default) => Promise<T>} callback - Callback that receives the dedicated lock connection.
143
+ * @returns {Promise<T>} - Resolves with the callback result.
144
+ */
145
+ async withDedicatedConnection(callback) {
146
+ const connection = await this.configuration.getDatabasePool(this.databaseIdentifier).spawnConnection()
147
+
148
+ try {
149
+ return await callback(connection)
150
+ } finally {
151
+ if (!connection.getArgs().getConnection) {
152
+ await connection.close()
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Runs `callback`, rejecting with `AdvisoryLockHoldTimeoutError` if it has
159
+ * not settled within `holdTimeoutMs`. The callback is not cancelled; callers
160
+ * use a dedicated advisory-lock connection so the lock can still be released.
161
+ * @template T
162
+ * @param {string} name - Lock name (for the error message).
163
+ * @param {() => Promise<T>} callback - Callback holding the lock.
164
+ * @param {number | null} [holdTimeoutMs] - Max hold time; falsy disables the timeout.
165
+ * @returns {Promise<T>} - Resolves with the callback result.
166
+ */
167
+ static async runWithAdvisoryLockHoldTimeout(name, callback, holdTimeoutMs) {
168
+ if (!holdTimeoutMs || holdTimeoutMs <= 0) {
169
+ return await callback()
170
+ }
171
+
172
+ let callbackSettled = false
173
+
174
+ try {
175
+ return await timeout({timeout: holdTimeoutMs}, async () => {
176
+ try {
177
+ return await callback()
178
+ } finally {
179
+ callbackSettled = true
180
+ }
181
+ })
182
+ } catch (error) {
183
+ if (!callbackSettled) {
184
+ throw new AdvisoryLockHoldTimeoutError(`Advisory lock ${JSON.stringify(name)} held longer than ${holdTimeoutMs}ms`, {name})
185
+ }
186
+
187
+ throw error
188
+ }
189
+ }
190
+ }
191
+
192
+ export {AdvisoryLockBusyError, AdvisoryLockHoldTimeoutError, AdvisoryLockTimeoutError}
@@ -31,6 +31,9 @@ import Update from "./sql/update.js"
31
31
  const MYSQL_INDEFINITE_LOCK_TIMEOUT_SECONDS = 60 * 60 * 24 * 365
32
32
 
33
33
  export default class VelociousDatabaseDriversMysql extends Base{
34
+ /** @type {import("mysql").Pool | undefined} */
35
+ pool = undefined
36
+
34
37
  /** @type {string | null} */
35
38
  _desiredSessionTimeZone = "+00:00"
36
39
 
@@ -0,0 +1,244 @@
1
+ // @ts-check
2
+
3
+ import debounceFunction from "debounce"
4
+
5
+ import recordChanges from "./record-changes.js"
6
+ import restArgsError from "../utils/rest-args-error.js"
7
+
8
+ /**
9
+ * RecordModelClass type.
10
+ * @typedef {typeof import("./record/index.js").default} RecordModelClass */
11
+
12
+ /**
13
+ * The minimal query contract a live query needs: a root model class to observe
14
+ * for committed changes and a way to run the query and return the current rows.
15
+ * `Model.where({...})` (a model-class query) satisfies this directly.
16
+ * @template T
17
+ * @typedef {object} LiveQuerySource
18
+ * @property {() => RecordModelClass} getModelClass - Root model class the query reads.
19
+ * @property {() => Promise<T[]>} toArray - Runs the query and resolves the current rows.
20
+ */
21
+
22
+ /**
23
+ * LiveQueryState type.
24
+ * @template T
25
+ * @typedef {object} LiveQueryState
26
+ * @property {T[]} results - Current query results.
27
+ * @property {boolean} loading - Whether the initial results are still loading.
28
+ * @property {Error | null} error - The last run error, or null when the last run succeeded.
29
+ */
30
+
31
+ /**
32
+ * A reactive query controller: fetches once, subscribes to committed changes of
33
+ * its model class(es), and re-runs whenever a watched model changes. Re-runs are
34
+ * coalesced (microtask by default, or a trailing debounce) and protected against
35
+ * stale responses by a monotonically increasing request id, so an in-flight run
36
+ * superseded by a newer change never overwrites fresher results. Framework-level
37
+ * and React-free so it can be unit tested and wrapped by `useLiveQuery`.
38
+ *
39
+ * Cost model: invalidation is by model class. A change to model M schedules one
40
+ * re-run of every live query observing M (no per-condition matching); a batch of
41
+ * changes coalesces into a single re-run.
42
+ * @template T
43
+ */
44
+ class LiveQuery {
45
+ /**
46
+ * Builds a live query controller for a query source.
47
+ * @param {object} args - Options.
48
+ * @param {LiveQuerySource<T>} args.query - Query source providing model class and `toArray`.
49
+ * @param {RecordModelClass[]} [args.models] - Model classes to observe. Defaults to `[query.getModelClass()]`; pass this to also react to joined models.
50
+ * @param {number} [args.debounce] - Trailing debounce in ms for re-runs. Defaults to microtask coalescing.
51
+ */
52
+ constructor({query, ...restArgs}) {
53
+ const {debounce, models, ...unknownArgs} = restArgs
54
+
55
+ restArgsError(unknownArgs)
56
+
57
+ if (!query) throw new Error("No query given to LiveQuery")
58
+
59
+ /** @type {LiveQuerySource<T>} */
60
+ this._query = query
61
+
62
+ /** @type {RecordModelClass[]} */
63
+ this._modelClasses = models ?? [query.getModelClass()]
64
+
65
+ /** @type {LiveQueryState<T>} */
66
+ this._state = {error: null, loading: true, results: []}
67
+
68
+ /**
69
+ * State-change listeners notified after every state transition.
70
+ * @type {Set<() => void>} */
71
+ this._listeners = new Set()
72
+
73
+ /**
74
+ * Record-change unsubscribe callbacks registered on `start`.
75
+ * @type {Array<() => void>} */
76
+ this._unsubscribes = []
77
+
78
+ /** @type {number} */
79
+ this._requestId = 0
80
+
81
+ /** @type {boolean} */
82
+ this._closed = false
83
+
84
+ /** @type {boolean} */
85
+ this._started = false
86
+
87
+ /** @type {boolean} */
88
+ this._runScheduled = false
89
+
90
+ /**
91
+ * Promise for the currently in-flight run, or null when idle.
92
+ * @type {Promise<void> | null} */
93
+ this._runningPromise = null
94
+
95
+ /**
96
+ * Schedules a coalesced re-run: a trailing debounce when configured, else microtask coalescing.
97
+ * @type {(() => void) & {clear?: () => void}} */
98
+ this._scheduleRun = typeof debounce === "number"
99
+ ? debounceFunction(() => this._run(), debounce)
100
+ : () => this._scheduleMicrotaskRun()
101
+
102
+ /**
103
+ * Record-change listener scheduling a re-run while the controller is open.
104
+ * @type {() => void} */
105
+ this._onRecordChange = () => {
106
+ if (!this._closed) this._scheduleRun()
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Subscribes to record changes and runs the initial query. Idempotent.
112
+ * @returns {void}
113
+ */
114
+ start() {
115
+ if (this._closed || this._started) return
116
+
117
+ this._started = true
118
+
119
+ for (const modelClass of this._modelClasses) {
120
+ this._unsubscribes.push(recordChanges.subscribe(modelClass, this._onRecordChange))
121
+ }
122
+
123
+ this._run()
124
+ }
125
+
126
+ /**
127
+ * Returns the current state. The reference only changes when the state changes,
128
+ * so it is safe to use as a React external-store snapshot.
129
+ * @returns {LiveQueryState<T>} Current live-query state.
130
+ */
131
+ getState() {
132
+ return this._state
133
+ }
134
+
135
+ /**
136
+ * Subscribes a listener notified after every state change.
137
+ * @param {() => void} listener - State-change listener.
138
+ * @returns {() => void} Unsubscribe callback.
139
+ */
140
+ subscribe(listener) {
141
+ this._listeners.add(listener)
142
+
143
+ return () => {
144
+ this._listeners.delete(listener)
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Unsubscribes from record changes, drops listeners, and prevents further runs.
150
+ * @returns {void}
151
+ */
152
+ close() {
153
+ if (this._closed) return
154
+
155
+ this._closed = true
156
+
157
+ for (const unsubscribe of this._unsubscribes) {
158
+ unsubscribe()
159
+ }
160
+
161
+ this._unsubscribes = []
162
+ this._listeners.clear()
163
+
164
+ if (this._scheduleRun.clear) this._scheduleRun.clear()
165
+ }
166
+
167
+ /**
168
+ * Awaits any scheduled or in-flight run so callers (tests) can observe settled
169
+ * results. Bounded so a continuous change stream cannot loop forever.
170
+ * @returns {Promise<void>}
171
+ */
172
+ async whenSettled() {
173
+ for (let attempt = 0; attempt < 100; attempt++) {
174
+ if (this._closed) return
175
+ if (!this._runScheduled && !this._runningPromise) return
176
+
177
+ if (this._runningPromise) await this._runningPromise
178
+
179
+ await new Promise((resolve) => queueMicrotask(() => resolve(undefined)))
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Schedules a microtask-coalesced re-run, collapsing a synchronous burst of
185
+ * change events into a single run.
186
+ * @returns {void}
187
+ */
188
+ _scheduleMicrotaskRun() {
189
+ if (this._runScheduled) return
190
+
191
+ this._runScheduled = true
192
+
193
+ queueMicrotask(() => {
194
+ this._runScheduled = false
195
+
196
+ if (!this._closed) this._run()
197
+ })
198
+ }
199
+
200
+ /**
201
+ * Runs the query and applies its results unless a newer run superseded it or the
202
+ * controller was closed. A run error surfaces in state (with the previous
203
+ * results kept) rather than rejecting a background promise.
204
+ * @returns {Promise<void>}
205
+ */
206
+ _run() {
207
+ const requestId = ++this._requestId
208
+ const runningPromise = (async () => {
209
+ try {
210
+ const results = await this._query.toArray()
211
+
212
+ if (this._closed || requestId !== this._requestId) return
213
+
214
+ this._setState({error: null, loading: false, results})
215
+ } catch (error) {
216
+ if (this._closed || requestId !== this._requestId) return
217
+
218
+ this._setState({error: /** @type {Error} */ (error), loading: false, results: this._state.results})
219
+ }
220
+ })()
221
+
222
+ this._runningPromise = runningPromise
223
+ void runningPromise.then(() => {
224
+ if (this._runningPromise === runningPromise) this._runningPromise = null
225
+ })
226
+
227
+ return runningPromise
228
+ }
229
+
230
+ /**
231
+ * Replaces the state and notifies listeners.
232
+ * @param {LiveQueryState<T>} state - Next state.
233
+ * @returns {void}
234
+ */
235
+ _setState(state) {
236
+ this._state = state
237
+
238
+ for (const listener of Array.from(this._listeners)) {
239
+ listener()
240
+ }
241
+ }
242
+ }
243
+
244
+ export default LiveQuery