@vibesdotdev/storage 0.0.1 → 1.0.0-rc.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -0
- package/README.md +87 -63
- package/dist/adapters/cloud-api/cloud-api.adapter.descriptor.d.ts +10 -0
- package/dist/adapters/cloud-api/cloud-api.adapter.descriptor.d.ts.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.adapter.descriptor.js +40 -0
- package/dist/adapters/cloud-api/cloud-api.adapter.descriptor.js.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.adapter.impl.d.ts +93 -0
- package/dist/adapters/cloud-api/cloud-api.adapter.impl.d.ts.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.adapter.impl.js +437 -0
- package/dist/adapters/cloud-api/cloud-api.adapter.impl.js.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.fetch-client.d.ts +59 -0
- package/dist/adapters/cloud-api/cloud-api.fetch-client.d.ts.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.fetch-client.js +357 -0
- package/dist/adapters/cloud-api/cloud-api.fetch-client.js.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.record.d.ts +85 -0
- package/dist/adapters/cloud-api/cloud-api.record.d.ts.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.record.helpers.d.ts +71 -0
- package/dist/adapters/cloud-api/cloud-api.record.helpers.d.ts.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.record.helpers.js +270 -0
- package/dist/adapters/cloud-api/cloud-api.record.helpers.js.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.record.js +351 -0
- package/dist/adapters/cloud-api/cloud-api.record.js.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.test-utils.d.ts +38 -0
- package/dist/adapters/cloud-api/cloud-api.test-utils.d.ts.map +1 -0
- package/dist/adapters/cloud-api/cloud-api.test-utils.js +139 -0
- package/dist/adapters/cloud-api/cloud-api.test-utils.js.map +1 -0
- package/dist/adapters/cloud-api/index.d.ts +10 -0
- package/dist/adapters/cloud-api/index.d.ts.map +1 -0
- package/dist/adapters/cloud-api/index.js +10 -0
- package/dist/adapters/cloud-api/index.js.map +1 -0
- package/dist/adapters/cloud-api/storage-adapter-cloud-api.plugin.d.ts +9 -0
- package/dist/adapters/cloud-api/storage-adapter-cloud-api.plugin.d.ts.map +1 -0
- package/dist/adapters/cloud-api/storage-adapter-cloud-api.plugin.js +21 -0
- package/dist/adapters/cloud-api/storage-adapter-cloud-api.plugin.js.map +1 -0
- package/dist/adapters/drizzle/batch-type-proof.d.ts +21 -0
- package/dist/adapters/drizzle/batch-type-proof.d.ts.map +1 -0
- package/dist/adapters/drizzle/batch-type-proof.js +2 -0
- package/dist/adapters/drizzle/batch-type-proof.js.map +1 -0
- package/dist/adapters/drizzle/canonical.adapter.descriptor.d.ts +4 -0
- package/dist/adapters/drizzle/canonical.adapter.descriptor.d.ts.map +1 -0
- package/dist/adapters/drizzle/canonical.adapter.descriptor.js +29 -0
- package/dist/adapters/drizzle/canonical.adapter.descriptor.js.map +1 -0
- package/dist/adapters/drizzle/contracts.d.ts +24 -0
- package/dist/adapters/drizzle/contracts.d.ts.map +1 -0
- package/dist/adapters/drizzle/contracts.js +5 -0
- package/dist/adapters/drizzle/contracts.js.map +1 -0
- package/dist/adapters/drizzle/docs/storage.adapters.drizzle.docs.descriptor.d.ts +4 -0
- package/dist/adapters/drizzle/docs/storage.adapters.drizzle.docs.descriptor.d.ts.map +1 -0
- package/dist/adapters/drizzle/docs/storage.adapters.drizzle.docs.descriptor.js +187 -0
- package/dist/adapters/drizzle/docs/storage.adapters.drizzle.docs.descriptor.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-journal.migration-format.impl.d.ts +15 -0
- package/dist/adapters/drizzle/drizzle-journal.migration-format.impl.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-journal.migration-format.impl.js +59 -0
- package/dist/adapters/drizzle/drizzle-journal.migration-format.impl.js.map +1 -0
- package/dist/adapters/drizzle/drizzle-journal.migration-format.plugin.d.ts +5 -0
- package/dist/adapters/drizzle/drizzle-journal.migration-format.plugin.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle-journal.migration-format.plugin.js +27 -0
- package/dist/adapters/drizzle/drizzle-journal.migration-format.plugin.js.map +1 -0
- package/dist/adapters/drizzle/drizzle.adapter.descriptor.d.ts +2 -0
- package/dist/adapters/drizzle/drizzle.adapter.descriptor.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle.adapter.descriptor.js +3 -0
- package/dist/adapters/drizzle/drizzle.adapter.descriptor.js.map +1 -0
- package/dist/adapters/drizzle/drizzle.adapter.impl.d.ts +37 -0
- package/dist/adapters/drizzle/drizzle.adapter.impl.d.ts.map +1 -0
- package/dist/adapters/drizzle/drizzle.adapter.impl.js +1163 -0
- package/dist/adapters/drizzle/drizzle.adapter.impl.js.map +1 -0
- package/dist/adapters/drizzle/extensions.d.ts +5 -0
- package/dist/adapters/drizzle/extensions.d.ts.map +1 -0
- package/dist/adapters/drizzle/extensions.js +30 -0
- package/dist/adapters/drizzle/extensions.js.map +1 -0
- package/dist/adapters/drizzle/index.d.ts +12 -0
- package/dist/adapters/drizzle/index.d.ts.map +1 -0
- package/dist/adapters/drizzle/index.js +12 -0
- package/dist/adapters/drizzle/index.js.map +1 -0
- package/dist/adapters/drizzle/libsql-migrator.d.ts +23 -0
- package/dist/adapters/drizzle/libsql-migrator.d.ts.map +1 -0
- package/dist/adapters/drizzle/libsql-migrator.js +67 -0
- package/dist/adapters/drizzle/libsql-migrator.js.map +1 -0
- package/dist/adapters/drizzle/libsql.d.ts +15 -0
- package/dist/adapters/drizzle/libsql.d.ts.map +1 -0
- package/dist/adapters/drizzle/libsql.js +19 -0
- package/dist/adapters/drizzle/libsql.js.map +1 -0
- package/dist/adapters/drizzle/migration-files.d.ts +15 -0
- package/dist/adapters/drizzle/migration-files.d.ts.map +1 -0
- package/dist/adapters/drizzle/migration-files.js +54 -0
- package/dist/adapters/drizzle/migration-files.js.map +1 -0
- package/dist/adapters/drizzle/migration-statements.d.ts +7 -0
- package/dist/adapters/drizzle/migration-statements.d.ts.map +1 -0
- package/dist/adapters/drizzle/migration-statements.js +14 -0
- package/dist/adapters/drizzle/migration-statements.js.map +1 -0
- package/dist/adapters/drizzle/persistence-errors.d.ts +33 -0
- package/dist/adapters/drizzle/persistence-errors.d.ts.map +1 -0
- package/dist/adapters/drizzle/persistence-errors.js +75 -0
- package/dist/adapters/drizzle/persistence-errors.js.map +1 -0
- package/dist/adapters/drizzle/persistence-factory.d.ts +100 -0
- package/dist/adapters/drizzle/persistence-factory.d.ts.map +1 -0
- package/dist/adapters/drizzle/persistence-factory.js +391 -0
- package/dist/adapters/drizzle/persistence-factory.js.map +1 -0
- package/dist/adapters/drizzle/persistence.client.d.ts +73 -0
- package/dist/adapters/drizzle/persistence.client.d.ts.map +1 -0
- package/dist/adapters/drizzle/persistence.client.js +238 -0
- package/dist/adapters/drizzle/persistence.client.js.map +1 -0
- package/dist/adapters/drizzle/providers.d.ts +19 -0
- package/dist/adapters/drizzle/providers.d.ts.map +1 -0
- package/dist/adapters/drizzle/providers.js +62 -0
- package/dist/adapters/drizzle/providers.js.map +1 -0
- package/dist/adapters/drizzle/record-ddl.d.ts +13 -0
- package/dist/adapters/drizzle/record-ddl.d.ts.map +1 -0
- package/dist/adapters/drizzle/record-ddl.js +70 -0
- package/dist/adapters/drizzle/record-ddl.js.map +1 -0
- package/dist/adapters/drizzle/record-table.d.ts +59 -0
- package/dist/adapters/drizzle/record-table.d.ts.map +1 -0
- package/dist/adapters/drizzle/record-table.js +87 -0
- package/dist/adapters/drizzle/record-table.js.map +1 -0
- package/dist/adapters/drizzle/schemas/record-migration.d.ts +30 -0
- package/dist/adapters/drizzle/schemas/record-migration.d.ts.map +1 -0
- package/dist/adapters/drizzle/schemas/record-migration.js +12 -0
- package/dist/adapters/drizzle/schemas/record-migration.js.map +1 -0
- package/dist/adapters/drizzle/storage-adapter-drizzle.plugin.d.ts +26 -0
- package/dist/adapters/drizzle/storage-adapter-drizzle.plugin.d.ts.map +1 -0
- package/dist/adapters/drizzle/storage-adapter-drizzle.plugin.js +90 -0
- package/dist/adapters/drizzle/storage-adapter-drizzle.plugin.js.map +1 -0
- package/dist/adapters/drizzle/storage-drizzle-db.context.d.ts +53 -0
- package/dist/adapters/drizzle/storage-drizzle-db.context.d.ts.map +1 -0
- package/dist/adapters/drizzle/storage-drizzle-db.context.js +8 -0
- package/dist/adapters/drizzle/storage-drizzle-db.context.js.map +1 -0
- package/dist/adapters/drizzle/test.d.ts +60 -0
- package/dist/adapters/drizzle/test.d.ts.map +1 -0
- package/dist/adapters/drizzle/test.js +49 -0
- package/dist/adapters/drizzle/test.js.map +1 -0
- package/dist/adapters/drizzle/transforms.d.ts +42 -0
- package/dist/adapters/drizzle/transforms.d.ts.map +1 -0
- package/dist/adapters/drizzle/transforms.js +147 -0
- package/dist/adapters/drizzle/transforms.js.map +1 -0
- package/dist/adapters/filesystem/filesystem.adapter.descriptor.d.ts +13 -0
- package/dist/adapters/filesystem/filesystem.adapter.descriptor.d.ts.map +1 -0
- package/dist/adapters/filesystem/filesystem.adapter.descriptor.js +34 -0
- package/dist/adapters/filesystem/filesystem.adapter.descriptor.js.map +1 -0
- package/dist/adapters/filesystem/filesystem.adapter.impl.d.ts +57 -0
- package/dist/adapters/filesystem/filesystem.adapter.impl.d.ts.map +1 -0
- package/dist/adapters/filesystem/filesystem.adapter.impl.js +257 -0
- package/dist/adapters/filesystem/filesystem.adapter.impl.js.map +1 -0
- package/dist/adapters/filesystem/index.d.ts +10 -0
- package/dist/adapters/filesystem/index.d.ts.map +1 -0
- package/dist/adapters/filesystem/index.js +10 -0
- package/dist/adapters/filesystem/index.js.map +1 -0
- package/dist/adapters/filesystem/storage-adapter-filesystem.plugin.d.ts +11 -0
- package/dist/adapters/filesystem/storage-adapter-filesystem.plugin.d.ts.map +1 -0
- package/dist/adapters/filesystem/storage-adapter-filesystem.plugin.js +25 -0
- package/dist/adapters/filesystem/storage-adapter-filesystem.plugin.js.map +1 -0
- package/dist/adapters/fixture/fixture.adapter.descriptor.d.ts +14 -0
- package/dist/adapters/fixture/fixture.adapter.descriptor.d.ts.map +1 -0
- package/dist/adapters/fixture/fixture.adapter.descriptor.js +35 -0
- package/dist/adapters/fixture/fixture.adapter.descriptor.js.map +1 -0
- package/dist/adapters/fixture/fixture.adapter.impl.d.ts +41 -0
- package/dist/adapters/fixture/fixture.adapter.impl.d.ts.map +1 -0
- package/dist/adapters/fixture/fixture.adapter.impl.js +162 -0
- package/dist/adapters/fixture/fixture.adapter.impl.js.map +1 -0
- package/dist/adapters/fixture/index.d.ts +9 -0
- package/dist/adapters/fixture/index.d.ts.map +1 -0
- package/dist/adapters/fixture/index.js +9 -0
- package/dist/adapters/fixture/index.js.map +1 -0
- package/dist/adapters/fixture/storage-adapter-fixture.plugin.d.ts +10 -0
- package/dist/adapters/fixture/storage-adapter-fixture.plugin.d.ts.map +1 -0
- package/dist/adapters/fixture/storage-adapter-fixture.plugin.js +21 -0
- package/dist/adapters/fixture/storage-adapter-fixture.plugin.js.map +1 -0
- package/dist/adapters/idb/idb-backend.d.ts +33 -0
- package/dist/adapters/idb/idb-backend.d.ts.map +1 -0
- package/dist/adapters/idb/idb-backend.js +217 -0
- package/dist/adapters/idb/idb-backend.js.map +1 -0
- package/dist/adapters/idb/idb.adapter.descriptor.d.ts +14 -0
- package/dist/adapters/idb/idb.adapter.descriptor.d.ts.map +1 -0
- package/dist/adapters/idb/idb.adapter.descriptor.js +35 -0
- package/dist/adapters/idb/idb.adapter.descriptor.js.map +1 -0
- package/dist/adapters/idb/idb.adapter.impl.d.ts +56 -0
- package/dist/adapters/idb/idb.adapter.impl.d.ts.map +1 -0
- package/dist/adapters/idb/idb.adapter.impl.js +227 -0
- package/dist/adapters/idb/idb.adapter.impl.js.map +1 -0
- package/dist/adapters/idb/index.d.ts +10 -0
- package/dist/adapters/idb/index.d.ts.map +1 -0
- package/dist/adapters/idb/index.js +10 -0
- package/dist/adapters/idb/index.js.map +1 -0
- package/dist/adapters/idb/storage-adapter-idb.plugin.d.ts +11 -0
- package/dist/adapters/idb/storage-adapter-idb.plugin.d.ts.map +1 -0
- package/dist/adapters/idb/storage-adapter-idb.plugin.js +22 -0
- package/dist/adapters/idb/storage-adapter-idb.plugin.js.map +1 -0
- package/dist/adapters/json/filesystem-binding.d.ts +14 -0
- package/dist/adapters/json/filesystem-binding.d.ts.map +1 -0
- package/dist/adapters/json/filesystem-binding.js +7 -0
- package/dist/adapters/json/filesystem-binding.js.map +1 -0
- package/dist/adapters/json/index.d.ts +10 -0
- package/dist/adapters/json/index.d.ts.map +1 -0
- package/dist/adapters/json/index.js +10 -0
- package/dist/adapters/json/index.js.map +1 -0
- package/dist/adapters/json/json.adapter.descriptor.d.ts +10 -0
- package/dist/adapters/json/json.adapter.descriptor.d.ts.map +1 -0
- package/dist/adapters/json/json.adapter.descriptor.js +38 -0
- package/dist/adapters/json/json.adapter.descriptor.js.map +1 -0
- package/dist/adapters/json/json.adapter.impl.d.ts +59 -0
- package/dist/adapters/json/json.adapter.impl.d.ts.map +1 -0
- package/dist/adapters/json/json.adapter.impl.js +395 -0
- package/dist/adapters/json/json.adapter.impl.js.map +1 -0
- package/dist/adapters/json/storage-adapter-json.plugin.d.ts +9 -0
- package/dist/adapters/json/storage-adapter-json.plugin.d.ts.map +1 -0
- package/dist/adapters/json/storage-adapter-json.plugin.js +21 -0
- package/dist/adapters/json/storage-adapter-json.plugin.js.map +1 -0
- package/dist/adapters/localdb/index.d.ts +9 -0
- package/dist/adapters/localdb/index.d.ts.map +1 -0
- package/dist/adapters/localdb/index.js +9 -0
- package/dist/adapters/localdb/index.js.map +1 -0
- package/dist/adapters/localdb/localdb.adapter.descriptor.d.ts +10 -0
- package/dist/adapters/localdb/localdb.adapter.descriptor.d.ts.map +1 -0
- package/dist/adapters/localdb/localdb.adapter.descriptor.js +37 -0
- package/dist/adapters/localdb/localdb.adapter.descriptor.js.map +1 -0
- package/dist/adapters/localdb/localdb.adapter.impl.d.ts +237 -0
- package/dist/adapters/localdb/localdb.adapter.impl.d.ts.map +1 -0
- package/dist/adapters/localdb/localdb.adapter.impl.js +1370 -0
- package/dist/adapters/localdb/localdb.adapter.impl.js.map +1 -0
- package/dist/adapters/localdb/localdb.adapter.loader.consumer.d.ts +2 -0
- package/dist/adapters/localdb/localdb.adapter.loader.consumer.d.ts.map +1 -0
- package/dist/adapters/localdb/localdb.adapter.loader.consumer.js +2 -0
- package/dist/adapters/localdb/localdb.adapter.loader.consumer.js.map +1 -0
- package/dist/adapters/localdb/localdb.inputs.d.ts +3 -0
- package/dist/adapters/localdb/localdb.inputs.d.ts.map +1 -0
- package/dist/adapters/localdb/localdb.inputs.js +3 -0
- package/dist/adapters/localdb/localdb.inputs.js.map +1 -0
- package/dist/adapters/localdb/migration-failure.d.ts +3 -0
- package/dist/adapters/localdb/migration-failure.d.ts.map +1 -0
- package/dist/adapters/localdb/migration-failure.js +16 -0
- package/dist/adapters/localdb/migration-failure.js.map +1 -0
- package/dist/adapters/localdb/outcome.d.ts +13 -0
- package/dist/adapters/localdb/outcome.d.ts.map +1 -0
- package/dist/adapters/localdb/outcome.js +12 -0
- package/dist/adapters/localdb/outcome.js.map +1 -0
- package/dist/adapters/localdb/repository-query.d.ts +19 -0
- package/dist/adapters/localdb/repository-query.d.ts.map +1 -0
- package/dist/adapters/localdb/repository-query.js +127 -0
- package/dist/adapters/localdb/repository-query.js.map +1 -0
- package/dist/adapters/localdb/storage-adapter-localdb.plugin.d.ts +14 -0
- package/dist/adapters/localdb/storage-adapter-localdb.plugin.d.ts.map +1 -0
- package/dist/adapters/localdb/storage-adapter-localdb.plugin.js +30 -0
- package/dist/adapters/localdb/storage-adapter-localdb.plugin.js.map +1 -0
- package/dist/adapters/memory/index.d.ts +9 -0
- package/dist/adapters/memory/index.d.ts.map +1 -0
- package/dist/adapters/memory/index.js +9 -0
- package/dist/adapters/memory/index.js.map +1 -0
- package/dist/adapters/memory/memory-root-store.d.ts +20 -0
- package/dist/adapters/memory/memory-root-store.d.ts.map +1 -0
- package/dist/adapters/memory/memory-root-store.js +49 -0
- package/dist/adapters/memory/memory-root-store.js.map +1 -0
- package/dist/adapters/memory/memory.adapter.impl.d.ts +61 -0
- package/dist/adapters/memory/memory.adapter.impl.d.ts.map +1 -0
- package/dist/adapters/memory/memory.adapter.impl.js +718 -0
- package/dist/adapters/memory/memory.adapter.impl.js.map +1 -0
- package/dist/adapters/memory/memory.adapter.storage-adapter-memory.descriptor.d.ts +10 -0
- package/dist/adapters/memory/memory.adapter.storage-adapter-memory.descriptor.d.ts.map +1 -0
- package/dist/adapters/memory/memory.adapter.storage-adapter-memory.descriptor.js +37 -0
- package/dist/adapters/memory/memory.adapter.storage-adapter-memory.descriptor.js.map +1 -0
- package/dist/adapters/memory/storage-adapter-memory.plugin.d.ts +9 -0
- package/dist/adapters/memory/storage-adapter-memory.plugin.d.ts.map +1 -0
- package/dist/adapters/memory/storage-adapter-memory.plugin.js +44 -0
- package/dist/adapters/memory/storage-adapter-memory.plugin.js.map +1 -0
- package/dist/adapters/parse-adapter-input.d.ts +22 -0
- package/dist/adapters/parse-adapter-input.d.ts.map +1 -0
- package/dist/adapters/parse-adapter-input.js +36 -0
- package/dist/adapters/parse-adapter-input.js.map +1 -0
- package/dist/adapters/select-adapter.d.ts +36 -0
- package/dist/adapters/select-adapter.d.ts.map +1 -0
- package/dist/adapters/select-adapter.js +88 -0
- package/dist/adapters/select-adapter.js.map +1 -0
- package/dist/adapters/validate-for-persist.d.ts +21 -0
- package/dist/adapters/validate-for-persist.d.ts.map +1 -0
- package/dist/adapters/validate-for-persist.js +36 -0
- package/dist/adapters/validate-for-persist.js.map +1 -0
- package/dist/backup/backup.plugin.d.ts +276 -0
- package/dist/backup/backup.plugin.d.ts.map +1 -0
- package/dist/backup/backup.plugin.js +72 -0
- package/dist/backup/backup.plugin.js.map +1 -0
- package/dist/backup/channels/backup.channel.d.ts +21 -0
- package/dist/backup/channels/backup.channel.d.ts.map +1 -0
- package/dist/backup/channels/backup.channel.js +89 -0
- package/dist/backup/channels/backup.channel.js.map +1 -0
- package/dist/backup/config/storage.backup.config.descriptor.d.ts +57 -0
- package/dist/backup/config/storage.backup.config.descriptor.d.ts.map +1 -0
- package/dist/backup/config/storage.backup.config.descriptor.js +53 -0
- package/dist/backup/config/storage.backup.config.descriptor.js.map +1 -0
- package/dist/backup/core/hash.d.ts +8 -0
- package/dist/backup/core/hash.d.ts.map +1 -0
- package/dist/backup/core/hash.js +10 -0
- package/dist/backup/core/hash.js.map +1 -0
- package/dist/backup/core/homes.d.ts +13 -0
- package/dist/backup/core/homes.d.ts.map +1 -0
- package/dist/backup/core/homes.js +54 -0
- package/dist/backup/core/homes.js.map +1 -0
- package/dist/backup/core/index.d.ts +17 -0
- package/dist/backup/core/index.d.ts.map +1 -0
- package/dist/backup/core/index.js +12 -0
- package/dist/backup/core/index.js.map +1 -0
- package/dist/backup/core/io.d.ts +76 -0
- package/dist/backup/core/io.d.ts.map +1 -0
- package/dist/backup/core/io.js +177 -0
- package/dist/backup/core/io.js.map +1 -0
- package/dist/backup/core/paths.d.ts +50 -0
- package/dist/backup/core/paths.d.ts.map +1 -0
- package/dist/backup/core/paths.js +75 -0
- package/dist/backup/core/paths.js.map +1 -0
- package/dist/backup/core/readiness.d.ts +9 -0
- package/dist/backup/core/readiness.d.ts.map +1 -0
- package/dist/backup/core/readiness.js +104 -0
- package/dist/backup/core/readiness.js.map +1 -0
- package/dist/backup/core/recovery.d.ts +11 -0
- package/dist/backup/core/recovery.d.ts.map +1 -0
- package/dist/backup/core/recovery.js +75 -0
- package/dist/backup/core/recovery.js.map +1 -0
- package/dist/backup/core/registry.d.ts +13 -0
- package/dist/backup/core/registry.d.ts.map +1 -0
- package/dist/backup/core/registry.js +18 -0
- package/dist/backup/core/registry.js.map +1 -0
- package/dist/backup/core/restore.d.ts +36 -0
- package/dist/backup/core/restore.d.ts.map +1 -0
- package/dist/backup/core/restore.js +280 -0
- package/dist/backup/core/restore.js.map +1 -0
- package/dist/backup/core/rotate.d.ts +19 -0
- package/dist/backup/core/rotate.d.ts.map +1 -0
- package/dist/backup/core/rotate.js +153 -0
- package/dist/backup/core/rotate.js.map +1 -0
- package/dist/backup/core/rotation.d.ts +19 -0
- package/dist/backup/core/rotation.d.ts.map +1 -0
- package/dist/backup/core/rotation.js +100 -0
- package/dist/backup/core/rotation.js.map +1 -0
- package/dist/backup/core/snapshot.d.ts +45 -0
- package/dist/backup/core/snapshot.d.ts.map +1 -0
- package/dist/backup/core/snapshot.js +217 -0
- package/dist/backup/core/snapshot.js.map +1 -0
- package/dist/backup/docs/backup.disaster-recovery.docs.descriptor.d.ts +19 -0
- package/dist/backup/docs/backup.disaster-recovery.docs.descriptor.d.ts.map +1 -0
- package/dist/backup/docs/backup.disaster-recovery.docs.descriptor.js +42 -0
- package/dist/backup/docs/backup.disaster-recovery.docs.descriptor.js.map +1 -0
- package/dist/backup/index.d.ts +19 -0
- package/dist/backup/index.d.ts.map +1 -0
- package/dist/backup/index.js +16 -0
- package/dist/backup/index.js.map +1 -0
- package/dist/backup/jobs/backup.snapshot.descriptor.d.ts +95 -0
- package/dist/backup/jobs/backup.snapshot.descriptor.d.ts.map +1 -0
- package/dist/backup/jobs/backup.snapshot.descriptor.js +33 -0
- package/dist/backup/jobs/backup.snapshot.descriptor.js.map +1 -0
- package/dist/backup/jobs/backup.snapshot.impl.d.ts +59 -0
- package/dist/backup/jobs/backup.snapshot.impl.d.ts.map +1 -0
- package/dist/backup/jobs/backup.snapshot.impl.js +393 -0
- package/dist/backup/jobs/backup.snapshot.impl.js.map +1 -0
- package/dist/backup/jobs/backup.snapshot.schema.d.ts +3 -0
- package/dist/backup/jobs/backup.snapshot.schema.d.ts.map +1 -0
- package/dist/backup/jobs/backup.snapshot.schema.js +3 -0
- package/dist/backup/jobs/backup.snapshot.schema.js.map +1 -0
- package/dist/backup/kinds/backup-preflight.kind.d.ts +16 -0
- package/dist/backup/kinds/backup-preflight.kind.d.ts.map +1 -0
- package/dist/backup/kinds/backup-preflight.kind.js +27 -0
- package/dist/backup/kinds/backup-preflight.kind.js.map +1 -0
- package/dist/backup/kinds/backup-recovery-plan.kind.d.ts +11 -0
- package/dist/backup/kinds/backup-recovery-plan.kind.d.ts.map +1 -0
- package/dist/backup/kinds/backup-recovery-plan.kind.js +16 -0
- package/dist/backup/kinds/backup-recovery-plan.kind.js.map +1 -0
- package/dist/backup/kinds/backup-target.kind.d.ts +16 -0
- package/dist/backup/kinds/backup-target.kind.d.ts.map +1 -0
- package/dist/backup/kinds/backup-target.kind.js +21 -0
- package/dist/backup/kinds/backup-target.kind.js.map +1 -0
- package/dist/backup/schemas/core.d.ts +169 -0
- package/dist/backup/schemas/core.d.ts.map +1 -0
- package/dist/backup/schemas/core.js +117 -0
- package/dist/backup/schemas/core.js.map +1 -0
- package/dist/backup/schemas/disaster-recovery.d.ts +512 -0
- package/dist/backup/schemas/disaster-recovery.d.ts.map +1 -0
- package/dist/backup/schemas/disaster-recovery.js +410 -0
- package/dist/backup/schemas/disaster-recovery.js.map +1 -0
- package/dist/backup/schemas/index.d.ts +10 -0
- package/dist/backup/schemas/index.d.ts.map +1 -0
- package/dist/backup/schemas/index.js +10 -0
- package/dist/backup/schemas/index.js.map +1 -0
- package/dist/backup/schemas/job.d.ts +102 -0
- package/dist/backup/schemas/job.d.ts.map +1 -0
- package/dist/backup/schemas/job.js +47 -0
- package/dist/backup/schemas/job.js.map +1 -0
- package/dist/backup/schemas/preflight.d.ts +38 -0
- package/dist/backup/schemas/preflight.d.ts.map +1 -0
- package/dist/backup/schemas/preflight.js +21 -0
- package/dist/backup/schemas/preflight.js.map +1 -0
- package/dist/backup/schemas/snapshot.d.ts +110 -0
- package/dist/backup/schemas/snapshot.d.ts.map +1 -0
- package/dist/backup/schemas/snapshot.js +84 -0
- package/dist/backup/schemas/snapshot.js.map +1 -0
- package/dist/backup/schemas/target.d.ts +95 -0
- package/dist/backup/schemas/target.d.ts.map +1 -0
- package/dist/backup/schemas/target.js +105 -0
- package/dist/backup/schemas/target.js.map +1 -0
- package/dist/backup/storage/snapshot.record.descriptor.d.ts +10 -0
- package/dist/backup/storage/snapshot.record.descriptor.d.ts.map +1 -0
- package/dist/backup/storage/snapshot.record.descriptor.js +41 -0
- package/dist/backup/storage/snapshot.record.descriptor.js.map +1 -0
- package/dist/backup/targets/awareness-bus.target.descriptor.d.ts +10 -0
- package/dist/backup/targets/awareness-bus.target.descriptor.d.ts.map +1 -0
- package/dist/backup/targets/awareness-bus.target.descriptor.js +32 -0
- package/dist/backup/targets/awareness-bus.target.descriptor.js.map +1 -0
- package/dist/backup/targets/presence-identity.target.descriptor.d.ts +10 -0
- package/dist/backup/targets/presence-identity.target.descriptor.d.ts.map +1 -0
- package/dist/backup/targets/presence-identity.target.descriptor.js +40 -0
- package/dist/backup/targets/presence-identity.target.descriptor.js.map +1 -0
- package/dist/backup/targets/presence-inner.target.descriptor.d.ts +10 -0
- package/dist/backup/targets/presence-inner.target.descriptor.d.ts.map +1 -0
- package/dist/backup/targets/presence-inner.target.descriptor.js +36 -0
- package/dist/backup/targets/presence-inner.target.descriptor.js.map +1 -0
- package/dist/backup/targets/storage-records.target.descriptor.d.ts +16 -0
- package/dist/backup/targets/storage-records.target.descriptor.d.ts.map +1 -0
- package/dist/backup/targets/storage-records.target.descriptor.js +38 -0
- package/dist/backup/targets/storage-records.target.descriptor.js.map +1 -0
- package/dist/bridge/index.d.ts +2 -2
- package/dist/bridge/index.d.ts.map +1 -1
- package/dist/bridge/index.js +1 -1
- package/dist/bridge/index.js.map +1 -1
- package/dist/bridge/storage-kind-bridge.d.ts +13 -2
- package/dist/bridge/storage-kind-bridge.d.ts.map +1 -1
- package/dist/bridge/storage-kind-bridge.js +72 -69
- package/dist/bridge/storage-kind-bridge.js.map +1 -1
- package/dist/bridge/types.d.ts +4 -3
- package/dist/bridge/types.d.ts.map +1 -1
- package/dist/cli/backup/backup.cli-group.descriptor.d.ts +9 -0
- package/dist/cli/backup/backup.cli-group.descriptor.d.ts.map +1 -0
- package/dist/cli/backup/backup.cli-group.descriptor.js +16 -0
- package/dist/cli/backup/backup.cli-group.descriptor.js.map +1 -0
- package/dist/cli/backup/commands/backup.now.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/backup/commands/backup.now.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/backup/commands/backup.now.cli-command.descriptor.js +24 -0
- package/dist/cli/backup/commands/backup.now.cli-command.descriptor.js.map +1 -0
- package/dist/cli/backup/commands/backup.now.cli-command.impl.d.ts +26 -0
- package/dist/cli/backup/commands/backup.now.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/backup/commands/backup.now.cli-command.impl.js +61 -0
- package/dist/cli/backup/commands/backup.now.cli-command.impl.js.map +1 -0
- package/dist/cli/backup/commands/backup.restore.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/backup/commands/backup.restore.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/backup/commands/backup.restore.cli-command.descriptor.js +24 -0
- package/dist/cli/backup/commands/backup.restore.cli-command.descriptor.js.map +1 -0
- package/dist/cli/backup/commands/backup.restore.cli-command.impl.d.ts +23 -0
- package/dist/cli/backup/commands/backup.restore.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/backup/commands/backup.restore.cli-command.impl.js +50 -0
- package/dist/cli/backup/commands/backup.restore.cli-command.impl.js.map +1 -0
- package/dist/cli/backup/commands/backup.status.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/backup/commands/backup.status.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/backup/commands/backup.status.cli-command.descriptor.js +19 -0
- package/dist/cli/backup/commands/backup.status.cli-command.descriptor.js.map +1 -0
- package/dist/cli/backup/commands/backup.status.cli-command.impl.d.ts +21 -0
- package/dist/cli/backup/commands/backup.status.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/backup/commands/backup.status.cli-command.impl.js +76 -0
- package/dist/cli/backup/commands/backup.status.cli-command.impl.js.map +1 -0
- package/dist/cli/backup/commands/backup.verify.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/backup/commands/backup.verify.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/backup/commands/backup.verify.cli-command.descriptor.js +20 -0
- package/dist/cli/backup/commands/backup.verify.cli-command.descriptor.js.map +1 -0
- package/dist/cli/backup/commands/backup.verify.cli-command.impl.d.ts +20 -0
- package/dist/cli/backup/commands/backup.verify.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/backup/commands/backup.verify.cli-command.impl.js +48 -0
- package/dist/cli/backup/commands/backup.verify.cli-command.impl.js.map +1 -0
- package/dist/cli/backup/plugin.d.ts +5 -0
- package/dist/cli/backup/plugin.d.ts.map +1 -0
- package/dist/cli/backup/plugin.js +30 -0
- package/dist/cli/backup/plugin.js.map +1 -0
- package/dist/cli/commands/list/storage.list.cli-command.descriptor.d.ts +1 -1
- package/dist/cli/commands/list/storage.list.cli-command.descriptor.d.ts.map +1 -1
- package/dist/cli/commands/list/storage.list.cli-command.impl.d.ts +8 -1
- package/dist/cli/commands/list/storage.list.cli-command.impl.d.ts.map +1 -1
- package/dist/cli/commands/list/storage.list.cli-command.impl.js +17 -8
- package/dist/cli/commands/list/storage.list.cli-command.impl.js.map +1 -1
- package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.descriptor.d.ts +1 -1
- package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.descriptor.d.ts.map +1 -1
- package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.impl.d.ts +9 -1
- package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.impl.d.ts.map +1 -1
- package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.impl.js +110 -76
- package/dist/cli/commands/sync/pull/storage.sync.pull.cli-command.impl.js.map +1 -1
- package/dist/cli/commands/sync/push/storage.sync.push.cli-command.descriptor.d.ts +1 -1
- package/dist/cli/commands/sync/push/storage.sync.push.cli-command.descriptor.d.ts.map +1 -1
- package/dist/cli/commands/sync/push/storage.sync.push.cli-command.impl.d.ts +9 -1
- package/dist/cli/commands/sync/push/storage.sync.push.cli-command.impl.d.ts.map +1 -1
- package/dist/cli/commands/sync/push/storage.sync.push.cli-command.impl.js +131 -108
- package/dist/cli/commands/sync/push/storage.sync.push.cli-command.impl.js.map +1 -1
- package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.descriptor.d.ts +1 -1
- package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.descriptor.d.ts.map +1 -1
- package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.impl.d.ts +12 -1
- package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.impl.d.ts.map +1 -1
- package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.impl.js +34 -26
- package/dist/cli/commands/sync/resolve/storage.sync.resolve.cli-command.impl.js.map +1 -1
- package/dist/cli/commands/sync/status/storage.sync.status.cli-command.descriptor.d.ts +1 -1
- package/dist/cli/commands/sync/status/storage.sync.status.cli-command.descriptor.d.ts.map +1 -1
- package/dist/cli/commands/sync/status/storage.sync.status.cli-command.impl.d.ts +11 -1
- package/dist/cli/commands/sync/status/storage.sync.status.cli-command.impl.d.ts.map +1 -1
- package/dist/cli/commands/sync/status/storage.sync.status.cli-command.impl.js +89 -42
- package/dist/cli/commands/sync/status/storage.sync.status.cli-command.impl.js.map +1 -1
- package/dist/cli/commands/sync/sync-command-input.d.ts +20 -0
- package/dist/cli/commands/sync/sync-command-input.d.ts.map +1 -0
- package/dist/cli/commands/sync/sync-command-input.js +20 -0
- package/dist/cli/commands/sync/sync-command-input.js.map +1 -0
- package/dist/cli/gc/commands/gc.run.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/gc/commands/gc.run.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/gc/commands/gc.run.cli-command.descriptor.js +20 -0
- package/dist/cli/gc/commands/gc.run.cli-command.descriptor.js.map +1 -0
- package/dist/cli/gc/commands/gc.run.cli-command.impl.d.ts +24 -0
- package/dist/cli/gc/commands/gc.run.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/gc/commands/gc.run.cli-command.impl.js +104 -0
- package/dist/cli/gc/commands/gc.run.cli-command.impl.js.map +1 -0
- package/dist/cli/gc/commands/gc.status.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/gc/commands/gc.status.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/gc/commands/gc.status.cli-command.descriptor.js +19 -0
- package/dist/cli/gc/commands/gc.status.cli-command.descriptor.js.map +1 -0
- package/dist/cli/gc/commands/gc.status.cli-command.impl.d.ts +21 -0
- package/dist/cli/gc/commands/gc.status.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/gc/commands/gc.status.cli-command.impl.js +74 -0
- package/dist/cli/gc/commands/gc.status.cli-command.impl.js.map +1 -0
- package/dist/cli/gc/gc.cli-group.descriptor.d.ts +4 -0
- package/dist/cli/gc/gc.cli-group.descriptor.d.ts.map +1 -0
- package/dist/cli/gc/gc.cli-group.descriptor.js +11 -0
- package/dist/cli/gc/gc.cli-group.descriptor.js.map +1 -0
- package/dist/cli/gc/plugin.d.ts +5 -0
- package/dist/cli/gc/plugin.d.ts.map +1 -0
- package/dist/cli/gc/plugin.js +28 -0
- package/dist/cli/gc/plugin.js.map +1 -0
- package/dist/cli/plugin.d.ts +17 -0
- package/dist/cli/plugin.d.ts.map +1 -0
- package/dist/cli/plugin.js +51 -0
- package/dist/cli/plugin.js.map +1 -0
- package/dist/cli/storage.cli-group.descriptor.d.ts +1 -1
- package/dist/cli/storage.cli-group.descriptor.d.ts.map +1 -1
- package/dist/cli/storage.sync.cli-group.descriptor.d.ts +1 -1
- package/dist/cli/storage.sync.cli-group.descriptor.d.ts.map +1 -1
- package/dist/cli/types.d.ts +4 -20
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js +3 -4
- package/dist/cli/types.js.map +1 -1
- package/dist/client/browse-adapter.d.ts +49 -0
- package/dist/client/browse-adapter.d.ts.map +1 -0
- package/dist/client/browse-adapter.js +99 -0
- package/dist/client/browse-adapter.js.map +1 -0
- package/dist/client/diagnose-adapter.d.ts +9 -0
- package/dist/client/diagnose-adapter.d.ts.map +1 -0
- package/dist/client/diagnose-adapter.js +9 -0
- package/dist/client/diagnose-adapter.js.map +1 -0
- package/dist/client/index.d.ts +7 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +5 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/list-browsable-adapters.d.ts +4 -3
- package/dist/client/list-browsable-adapters.d.ts.map +1 -1
- package/dist/client/list-browsable-adapters.js +3 -8
- package/dist/client/list-browsable-adapters.js.map +1 -1
- package/dist/client/object-adapter.d.ts +33 -0
- package/dist/client/object-adapter.d.ts.map +1 -0
- package/dist/client/object-adapter.js +45 -0
- package/dist/client/object-adapter.js.map +1 -0
- package/dist/client/storage-explorer-capability.d.ts +81 -0
- package/dist/client/storage-explorer-capability.d.ts.map +1 -0
- package/dist/client/storage-explorer-capability.impl.worker.d.ts +23 -0
- package/dist/client/storage-explorer-capability.impl.worker.d.ts.map +1 -0
- package/dist/client/storage-explorer-capability.impl.worker.js +124 -0
- package/dist/client/storage-explorer-capability.impl.worker.js.map +1 -0
- package/dist/client/storage-explorer-capability.js +125 -0
- package/dist/client/storage-explorer-capability.js.map +1 -0
- package/dist/client/storage-failure.d.ts +43 -0
- package/dist/client/storage-failure.d.ts.map +1 -0
- package/dist/client/storage-failure.js +100 -0
- package/dist/client/storage-failure.js.map +1 -0
- package/dist/daemon/commands/browse.d.ts +29 -0
- package/dist/daemon/commands/browse.d.ts.map +1 -0
- package/dist/daemon/commands/browse.js +91 -0
- package/dist/daemon/commands/browse.js.map +1 -0
- package/dist/daemon/commands/sync.d.ts +1 -1
- package/dist/daemon/commands/sync.d.ts.map +1 -1
- package/dist/daemon/commands/sync.js +60 -52
- package/dist/daemon/commands/sync.js.map +1 -1
- package/dist/docs/records.docs.descriptor.d.ts +1 -1
- package/dist/docs/records.docs.descriptor.d.ts.map +1 -1
- package/dist/docs/records.docs.descriptor.js +136 -151
- package/dist/docs/records.docs.descriptor.js.map +1 -1
- package/dist/docs/storage.adapters.docs.descriptor.d.ts +4 -0
- package/dist/docs/storage.adapters.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/storage.adapters.docs.descriptor.js +175 -0
- package/dist/docs/storage.adapters.docs.descriptor.js.map +1 -0
- package/dist/docs/storage.browse.docs.descriptor.d.ts +4 -0
- package/dist/docs/storage.browse.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/storage.browse.docs.descriptor.js +74 -0
- package/dist/docs/storage.browse.docs.descriptor.js.map +1 -0
- package/dist/docs/storage.docs.descriptor.d.ts +4 -0
- package/dist/docs/storage.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/storage.docs.descriptor.js +51 -0
- package/dist/docs/storage.docs.descriptor.js.map +1 -0
- package/dist/docs/storage.manifests.docs.descriptor.d.ts +4 -0
- package/dist/docs/storage.manifests.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/{manifests.docs.descriptor.js → storage.manifests.docs.descriptor.js} +69 -34
- package/dist/docs/storage.manifests.docs.descriptor.js.map +1 -0
- package/dist/docs/sync.docs.descriptor.d.ts +1 -1
- package/dist/docs/sync.docs.descriptor.d.ts.map +1 -1
- package/dist/docs/sync.docs.descriptor.js +140 -150
- package/dist/docs/sync.docs.descriptor.js.map +1 -1
- package/dist/docs/transform.docs.descriptor.d.ts +4 -0
- package/dist/docs/transform.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/transform.docs.descriptor.js +134 -0
- package/dist/docs/transform.docs.descriptor.js.map +1 -0
- package/dist/host-files.d.ts +35 -0
- package/dist/host-files.d.ts.map +1 -0
- package/dist/host-files.js +60 -0
- package/dist/host-files.js.map +1 -0
- package/dist/index.d.ts +15 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/interfaces/adapter.d.ts +166 -47
- package/dist/interfaces/adapter.d.ts.map +1 -1
- package/dist/interfaces/document-store.d.ts +41 -1
- package/dist/interfaces/document-store.d.ts.map +1 -1
- package/dist/interfaces/index.d.ts +8 -7
- package/dist/interfaces/index.d.ts.map +1 -1
- package/dist/interfaces/index.js +9 -7
- package/dist/interfaces/index.js.map +1 -1
- package/dist/interfaces/object-store.d.ts +54 -0
- package/dist/interfaces/object-store.d.ts.map +1 -0
- package/dist/interfaces/object-store.js +13 -0
- package/dist/interfaces/object-store.js.map +1 -0
- package/dist/interfaces/query-builder.d.ts +2 -30
- package/dist/interfaces/query-builder.d.ts.map +1 -1
- package/dist/interfaces/query-builder.js +0 -9
- package/dist/interfaces/query-builder.js.map +1 -1
- package/dist/interfaces/record/index.d.ts +1 -1
- package/dist/interfaces/record/index.d.ts.map +1 -1
- package/dist/interfaces/record/index.js +1 -1
- package/dist/interfaces/record/index.js.map +1 -1
- package/dist/interfaces/record/interface.d.ts +30 -9
- package/dist/interfaces/record/interface.d.ts.map +1 -1
- package/dist/interfaces/schema/index.d.ts +2 -2
- package/dist/interfaces/schema/index.d.ts.map +1 -1
- package/dist/interfaces/schema/index.js +2 -2
- package/dist/interfaces/schema/index.js.map +1 -1
- package/dist/interfaces/schema/schema-interface.d.ts +9 -8
- package/dist/interfaces/schema/schema-interface.d.ts.map +1 -1
- package/dist/interfaces/schema/table-interface.d.ts +4 -3
- package/dist/interfaces/schema/table-interface.d.ts.map +1 -1
- package/dist/interfaces/storage-consumer-fs.d.ts +25 -27
- package/dist/interfaces/storage-consumer-fs.d.ts.map +1 -1
- package/dist/interfaces/storage-consumer-fs.js +29 -30
- package/dist/interfaces/storage-consumer-fs.js.map +1 -1
- package/dist/manifest/base.d.ts +104 -22
- package/dist/manifest/base.d.ts.map +1 -1
- package/dist/manifest/base.js +393 -151
- package/dist/manifest/base.js.map +1 -1
- package/dist/manifest/cloud-sync.browser.d.ts +3 -0
- package/dist/manifest/cloud-sync.browser.d.ts.map +1 -0
- package/dist/manifest/cloud-sync.browser.js +3 -0
- package/dist/manifest/cloud-sync.browser.js.map +1 -0
- package/dist/manifest/cloud-sync.consumer.d.ts +2 -6
- package/dist/manifest/cloud-sync.consumer.d.ts.map +1 -1
- package/dist/manifest/cloud-sync.consumer.js +2 -81
- package/dist/manifest/cloud-sync.consumer.js.map +1 -1
- package/dist/manifest/cloud-sync.core.d.ts +30 -0
- package/dist/manifest/cloud-sync.core.d.ts.map +1 -0
- package/dist/manifest/cloud-sync.core.js +67 -0
- package/dist/manifest/cloud-sync.core.js.map +1 -0
- package/dist/manifest/cloud-sync.d.ts +3 -0
- package/dist/manifest/cloud-sync.d.ts.map +1 -0
- package/dist/manifest/cloud-sync.js +8 -0
- package/dist/manifest/cloud-sync.js.map +1 -0
- package/dist/manifest/index.d.ts +1 -1
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/index.js +1 -1
- package/dist/manifest/index.js.map +1 -1
- package/dist/manifest/source-policy.d.ts +21 -4
- package/dist/manifest/source-policy.d.ts.map +1 -1
- package/dist/manifest/source-policy.js +48 -50
- package/dist/manifest/source-policy.js.map +1 -1
- package/dist/migration-format/index.d.ts +3 -0
- package/dist/migration-format/index.d.ts.map +1 -0
- package/dist/migration-format/index.js +3 -0
- package/dist/migration-format/index.js.map +1 -0
- package/dist/migration-format/migration-format.kind.d.ts +5 -0
- package/dist/migration-format/migration-format.kind.d.ts.map +1 -0
- package/dist/migration-format/migration-format.kind.js +18 -0
- package/dist/migration-format/migration-format.kind.js.map +1 -0
- package/dist/path-format.d.ts +4 -0
- package/dist/path-format.d.ts.map +1 -0
- package/dist/path-format.js +71 -0
- package/dist/path-format.js.map +1 -0
- package/dist/path-policy.d.ts +28 -0
- package/dist/path-policy.d.ts.map +1 -0
- package/dist/path-policy.js +123 -0
- package/dist/path-policy.js.map +1 -0
- package/dist/paths.d.ts +14 -5
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +17 -150
- package/dist/paths.js.map +1 -1
- package/dist/policy/context.d.ts +3 -2
- package/dist/policy/context.d.ts.map +1 -1
- package/dist/policy/context.js +13 -16
- package/dist/policy/context.js.map +1 -1
- package/dist/policy/index.d.ts +3 -3
- package/dist/policy/index.d.ts.map +1 -1
- package/dist/policy/index.js +3 -3
- package/dist/policy/index.js.map +1 -1
- package/dist/policy/overrides.d.ts +5 -39
- package/dist/policy/overrides.d.ts.map +1 -1
- package/dist/policy/overrides.js +19 -24
- package/dist/policy/overrides.js.map +1 -1
- package/dist/policy/persistence.d.ts +2 -35
- package/dist/policy/persistence.d.ts.map +1 -1
- package/dist/policy/persistence.js +2 -12
- package/dist/policy/persistence.js.map +1 -1
- package/dist/policy/types.d.ts +5 -5
- package/dist/policy/types.d.ts.map +1 -1
- package/dist/record/array-fields.d.ts +15 -0
- package/dist/record/array-fields.d.ts.map +1 -0
- package/dist/record/array-fields.js +80 -0
- package/dist/record/array-fields.js.map +1 -0
- package/dist/record/atomic.d.ts +5 -0
- package/dist/record/atomic.d.ts.map +1 -0
- package/dist/record/atomic.js +10 -0
- package/dist/record/atomic.js.map +1 -0
- package/dist/record/base.d.ts +177 -39
- package/dist/record/base.d.ts.map +1 -1
- package/dist/record/base.js +1708 -525
- package/dist/record/base.js.map +1 -1
- package/dist/record/columns.d.ts +23 -0
- package/dist/record/columns.d.ts.map +1 -0
- package/dist/record/columns.js +161 -0
- package/dist/record/columns.js.map +1 -0
- package/dist/record/delivery.d.ts +57 -0
- package/dist/record/delivery.d.ts.map +1 -0
- package/dist/record/delivery.js +262 -0
- package/dist/record/delivery.js.map +1 -0
- package/dist/record/events.js +1 -1
- package/dist/record/events.js.map +1 -1
- package/dist/record/extensions.d.ts +17 -14
- package/dist/record/extensions.d.ts.map +1 -1
- package/dist/record/extensions.js +79 -53
- package/dist/record/extensions.js.map +1 -1
- package/dist/record/failure-condition.d.ts +14 -0
- package/dist/record/failure-condition.d.ts.map +1 -0
- package/dist/record/failure-condition.js +17 -0
- package/dist/record/failure-condition.js.map +1 -0
- package/dist/record/index.d.ts +5 -1
- package/dist/record/index.d.ts.map +1 -1
- package/dist/record/index.js +5 -1
- package/dist/record/index.js.map +1 -1
- package/dist/record/merge.d.ts +21 -0
- package/dist/record/merge.d.ts.map +1 -0
- package/dist/record/merge.js +147 -0
- package/dist/record/merge.js.map +1 -0
- package/dist/record/relations.d.ts +23 -0
- package/dist/record/relations.d.ts.map +1 -0
- package/dist/record/relations.js +164 -0
- package/dist/record/relations.js.map +1 -0
- package/dist/record/revision.d.ts +5 -0
- package/dist/record/revision.d.ts.map +1 -0
- package/dist/record/revision.js +11 -0
- package/dist/record/revision.js.map +1 -0
- package/dist/record/schema-coordination.client.d.ts +16 -0
- package/dist/record/schema-coordination.client.d.ts.map +1 -0
- package/dist/record/schema-coordination.client.js +59 -0
- package/dist/record/schema-coordination.client.js.map +1 -0
- package/dist/record/storage.record-delivery.reconcile.descriptor.d.ts +74 -0
- package/dist/record/storage.record-delivery.reconcile.descriptor.d.ts.map +1 -0
- package/dist/record/storage.record-delivery.reconcile.descriptor.js +54 -0
- package/dist/record/storage.record-delivery.reconcile.descriptor.js.map +1 -0
- package/dist/record/storage.record-delivery.reconcile.impl.d.ts +22 -0
- package/dist/record/storage.record-delivery.reconcile.impl.d.ts.map +1 -0
- package/dist/record/storage.record-delivery.reconcile.impl.js +52 -0
- package/dist/record/storage.record-delivery.reconcile.impl.js.map +1 -0
- package/dist/resolver/storage-backed-resolver.d.ts +12 -5
- package/dist/resolver/storage-backed-resolver.d.ts.map +1 -1
- package/dist/resolver/storage-backed-resolver.js +129 -48
- package/dist/resolver/storage-backed-resolver.js.map +1 -1
- package/dist/retention/capacity.d.ts +37 -0
- package/dist/retention/capacity.d.ts.map +1 -0
- package/dist/retention/capacity.js +71 -0
- package/dist/retention/capacity.js.map +1 -0
- package/dist/retention/catalog-chunks.d.ts +11 -0
- package/dist/retention/catalog-chunks.d.ts.map +1 -0
- package/dist/retention/catalog-chunks.js +62 -0
- package/dist/retention/catalog-chunks.js.map +1 -0
- package/dist/retention/reclaim.d.ts +59 -0
- package/dist/retention/reclaim.d.ts.map +1 -0
- package/dist/retention/reclaim.js +259 -0
- package/dist/retention/reclaim.js.map +1 -0
- package/dist/retention/roots.d.ts +141 -0
- package/dist/retention/roots.d.ts.map +1 -0
- package/dist/retention/roots.js +223 -0
- package/dist/retention/roots.js.map +1 -0
- package/dist/retention/scratch-custody.d.ts +40 -0
- package/dist/retention/scratch-custody.d.ts.map +1 -0
- package/dist/retention/scratch-custody.js +15 -0
- package/dist/retention/scratch-custody.js.map +1 -0
- package/dist/retention/startup-sweep.d.ts +57 -0
- package/dist/retention/startup-sweep.d.ts.map +1 -0
- package/dist/retention/startup-sweep.js +137 -0
- package/dist/retention/startup-sweep.js.map +1 -0
- package/dist/retention/storage.retention-sweep.descriptor.d.ts +82 -0
- package/dist/retention/storage.retention-sweep.descriptor.d.ts.map +1 -0
- package/dist/retention/storage.retention-sweep.descriptor.js +42 -0
- package/dist/retention/storage.retention-sweep.descriptor.js.map +1 -0
- package/dist/retention/storage.retention-sweep.impl.d.ts +22 -0
- package/dist/retention/storage.retention-sweep.impl.d.ts.map +1 -0
- package/dist/retention/storage.retention-sweep.impl.js +78 -0
- package/dist/retention/storage.retention-sweep.impl.js.map +1 -0
- package/dist/retention/storage.retention-sweep.schema.d.ts +3 -0
- package/dist/retention/storage.retention-sweep.schema.d.ts.map +1 -0
- package/dist/retention/storage.retention-sweep.schema.js +3 -0
- package/dist/retention/storage.retention-sweep.schema.js.map +1 -0
- package/dist/retention/sweep.d.ts +31 -0
- package/dist/retention/sweep.d.ts.map +1 -0
- package/dist/retention/sweep.js +190 -0
- package/dist/retention/sweep.js.map +1 -0
- package/dist/retention/tmp-scratch.d.ts +35 -0
- package/dist/retention/tmp-scratch.d.ts.map +1 -0
- package/dist/retention/tmp-scratch.js +149 -0
- package/dist/retention/tmp-scratch.js.map +1 -0
- package/dist/schemas/adapter-config.d.ts +42 -0
- package/dist/schemas/adapter-config.d.ts.map +1 -0
- package/dist/schemas/adapter-config.js +21 -0
- package/dist/schemas/adapter-config.js.map +1 -0
- package/dist/schemas/adapter-configuration-diagnostic.d.ts +35 -0
- package/dist/schemas/adapter-configuration-diagnostic.d.ts.map +1 -0
- package/dist/schemas/adapter-configuration-diagnostic.js +30 -0
- package/dist/schemas/adapter-configuration-diagnostic.js.map +1 -0
- package/dist/schemas/adapter-descriptor.d.ts +215 -5
- package/dist/schemas/adapter-descriptor.d.ts.map +1 -1
- package/dist/schemas/adapter-descriptor.js +158 -4
- package/dist/schemas/adapter-descriptor.js.map +1 -1
- package/dist/schemas/cloud-api.d.ts +99 -0
- package/dist/schemas/cloud-api.d.ts.map +1 -0
- package/dist/schemas/cloud-api.js +55 -0
- package/dist/schemas/cloud-api.js.map +1 -0
- package/dist/schemas/cloud-endpoints.d.ts +25 -0
- package/dist/schemas/cloud-endpoints.d.ts.map +1 -0
- package/dist/schemas/cloud-endpoints.js +38 -0
- package/dist/schemas/cloud-endpoints.js.map +1 -0
- package/dist/schemas/document-identity.d.ts +40 -0
- package/dist/schemas/document-identity.d.ts.map +1 -0
- package/dist/schemas/document-identity.js +21 -0
- package/dist/schemas/document-identity.js.map +1 -0
- package/dist/schemas/document-revision.d.ts +37 -0
- package/dist/schemas/document-revision.d.ts.map +1 -0
- package/dist/schemas/document-revision.js +14 -0
- package/dist/schemas/document-revision.js.map +1 -0
- package/dist/schemas/drizzle/config.d.ts +83 -0
- package/dist/schemas/drizzle/config.d.ts.map +1 -0
- package/dist/schemas/drizzle/config.js +55 -0
- package/dist/schemas/drizzle/config.js.map +1 -0
- package/dist/schemas/drizzle/connection.d.ts +41 -0
- package/dist/schemas/drizzle/connection.d.ts.map +1 -0
- package/dist/schemas/drizzle/connection.js +23 -0
- package/dist/schemas/drizzle/connection.js.map +1 -0
- package/dist/schemas/drizzle/journal-read.d.ts +11 -0
- package/dist/schemas/drizzle/journal-read.d.ts.map +1 -0
- package/dist/schemas/drizzle/journal-read.js +2 -0
- package/dist/schemas/drizzle/journal-read.js.map +1 -0
- package/dist/schemas/drizzle/persistence-config.d.ts +39 -0
- package/dist/schemas/drizzle/persistence-config.d.ts.map +1 -0
- package/dist/schemas/drizzle/persistence-config.js +71 -0
- package/dist/schemas/drizzle/persistence-config.js.map +1 -0
- package/dist/schemas/drizzle/persistence.d.ts +102 -0
- package/dist/schemas/drizzle/persistence.d.ts.map +1 -0
- package/dist/schemas/drizzle/persistence.js +28 -0
- package/dist/schemas/drizzle/persistence.js.map +1 -0
- package/dist/schemas/drizzle/ports.d.ts +39 -0
- package/dist/schemas/drizzle/ports.d.ts.map +1 -0
- package/dist/schemas/drizzle/ports.js +2 -0
- package/dist/schemas/drizzle/ports.js.map +1 -0
- package/dist/schemas/drizzle/providers.d.ts +64 -0
- package/dist/schemas/drizzle/providers.d.ts.map +1 -0
- package/dist/schemas/drizzle/providers.js +19 -0
- package/dist/schemas/drizzle/providers.js.map +1 -0
- package/dist/schemas/drizzle/raw-sql.d.ts +33 -0
- package/dist/schemas/drizzle/raw-sql.d.ts.map +1 -0
- package/dist/schemas/drizzle/raw-sql.js +34 -0
- package/dist/schemas/drizzle/raw-sql.js.map +1 -0
- package/dist/schemas/explorer/capability.d.ts +41 -0
- package/dist/schemas/explorer/capability.d.ts.map +1 -0
- package/dist/schemas/explorer/capability.js +14 -0
- package/dist/schemas/explorer/capability.js.map +1 -0
- package/dist/schemas/explorer/overview.d.ts +41 -0
- package/dist/schemas/explorer/overview.d.ts.map +1 -0
- package/dist/schemas/explorer/overview.js +45 -0
- package/dist/schemas/explorer/overview.js.map +1 -0
- package/dist/schemas/explorer/refs.d.ts +38 -0
- package/dist/schemas/explorer/refs.d.ts.map +1 -0
- package/dist/schemas/explorer/refs.js +21 -0
- package/dist/schemas/explorer/refs.js.map +1 -0
- package/dist/schemas/explorer/rpc.d.ts +211 -0
- package/dist/schemas/explorer/rpc.d.ts.map +1 -0
- package/dist/schemas/explorer/rpc.js +103 -0
- package/dist/schemas/explorer/rpc.js.map +1 -0
- package/dist/schemas/explorer/views.d.ts +99 -0
- package/dist/schemas/explorer/views.d.ts.map +1 -0
- package/dist/schemas/explorer/views.js +84 -0
- package/dist/schemas/explorer/views.js.map +1 -0
- package/dist/schemas/index.d.ts +16 -8
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +12 -4
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/localdb/inputs.d.ts +80 -0
- package/dist/schemas/localdb/inputs.d.ts.map +1 -0
- package/dist/schemas/localdb/inputs.js +99 -0
- package/dist/schemas/localdb/inputs.js.map +1 -0
- package/dist/schemas/localdb/query.d.ts +28 -0
- package/dist/schemas/localdb/query.d.ts.map +1 -0
- package/dist/schemas/localdb/query.js +27 -0
- package/dist/schemas/localdb/query.js.map +1 -0
- package/dist/schemas/manifest-descriptor.d.ts +15 -5
- package/dist/schemas/manifest-descriptor.d.ts.map +1 -1
- package/dist/schemas/manifest-descriptor.js +2 -2
- package/dist/schemas/manifest-descriptor.js.map +1 -1
- package/dist/schemas/memory-store.d.ts +8 -0
- package/dist/schemas/memory-store.d.ts.map +1 -0
- package/dist/schemas/memory-store.js +7 -0
- package/dist/schemas/memory-store.js.map +1 -0
- package/dist/schemas/migration-format.d.ts +50 -0
- package/dist/schemas/migration-format.d.ts.map +1 -0
- package/dist/schemas/migration-format.js +17 -0
- package/dist/schemas/migration-format.js.map +1 -0
- package/dist/schemas/policy/overrides.d.ts +41 -0
- package/dist/schemas/policy/overrides.d.ts.map +1 -0
- package/dist/schemas/policy/overrides.js +14 -0
- package/dist/schemas/policy/overrides.js.map +1 -0
- package/dist/schemas/policy/persistence.d.ts +62 -0
- package/dist/schemas/policy/persistence.d.ts.map +1 -0
- package/dist/schemas/policy/persistence.js +24 -0
- package/dist/schemas/policy/persistence.js.map +1 -0
- package/dist/schemas/record/atomic.d.ts +41 -0
- package/dist/schemas/record/atomic.d.ts.map +1 -0
- package/dist/schemas/record/atomic.js +12 -0
- package/dist/schemas/record/atomic.js.map +1 -0
- package/dist/schemas/record/batch.d.ts +53 -0
- package/dist/schemas/record/batch.d.ts.map +1 -0
- package/dist/schemas/record/batch.js +36 -0
- package/dist/schemas/record/batch.js.map +1 -0
- package/dist/schemas/record/columns.d.ts +44 -0
- package/dist/schemas/record/columns.d.ts.map +1 -0
- package/dist/schemas/record/columns.js +25 -0
- package/dist/schemas/record/columns.js.map +1 -0
- package/dist/schemas/record/delivery.d.ts +54 -0
- package/dist/schemas/record/delivery.d.ts.map +1 -0
- package/dist/schemas/record/delivery.js +23 -0
- package/dist/schemas/record/delivery.js.map +1 -0
- package/dist/schemas/record/descriptor.d.ts +153 -51
- package/dist/schemas/record/descriptor.d.ts.map +1 -1
- package/dist/schemas/record/descriptor.js +61 -11
- package/dist/schemas/record/descriptor.js.map +1 -1
- package/dist/schemas/record/index.d.ts +8 -2
- package/dist/schemas/record/index.d.ts.map +1 -1
- package/dist/schemas/record/index.js +5 -1
- package/dist/schemas/record/index.js.map +1 -1
- package/dist/schemas/record/query-values.d.ts +62 -0
- package/dist/schemas/record/query-values.d.ts.map +1 -0
- package/dist/schemas/record/query-values.js +33 -0
- package/dist/schemas/record/query-values.js.map +1 -0
- package/dist/schemas/record/relation.d.ts +1 -1
- package/dist/schemas/record/relation.d.ts.map +1 -1
- package/dist/schemas/record/relation.js +8 -4
- package/dist/schemas/record/relation.js.map +1 -1
- package/dist/schemas/record/revision.d.ts +92 -0
- package/dist/schemas/record/revision.d.ts.map +1 -0
- package/dist/schemas/record/revision.js +39 -0
- package/dist/schemas/record/revision.js.map +1 -0
- package/dist/schemas/retention/jobs.d.ts +103 -0
- package/dist/schemas/retention/jobs.d.ts.map +1 -0
- package/dist/schemas/retention/jobs.js +72 -0
- package/dist/schemas/retention/jobs.js.map +1 -0
- package/dist/schemas/retention/results.d.ts +153 -0
- package/dist/schemas/retention/results.d.ts.map +1 -0
- package/dist/schemas/retention/results.js +84 -0
- package/dist/schemas/retention/results.js.map +1 -0
- package/dist/schemas/schema/descriptor.d.ts +14 -4
- package/dist/schemas/schema/descriptor.d.ts.map +1 -1
- package/dist/schemas/schema/descriptor.js +3 -3
- package/dist/schemas/schema/descriptor.js.map +1 -1
- package/dist/schemas/schema/index.d.ts +1 -1
- package/dist/schemas/schema/index.d.ts.map +1 -1
- package/dist/schemas/schema/index.js +1 -1
- package/dist/schemas/schema/index.js.map +1 -1
- package/dist/schemas/storage-provider.d.ts +79 -0
- package/dist/schemas/storage-provider.d.ts.map +1 -0
- package/dist/schemas/storage-provider.js +54 -0
- package/dist/schemas/storage-provider.js.map +1 -0
- package/dist/schemas/storage-ref.d.ts +39 -0
- package/dist/schemas/storage-ref.d.ts.map +1 -0
- package/dist/schemas/storage-ref.js +64 -0
- package/dist/schemas/storage-ref.js.map +1 -0
- package/dist/schemas/sync/command.d.ts +15 -0
- package/dist/schemas/sync/command.d.ts.map +1 -0
- package/dist/schemas/sync/command.js +10 -0
- package/dist/schemas/sync/command.js.map +1 -0
- package/dist/schemas/sync/crud.d.ts +26 -0
- package/dist/schemas/sync/crud.d.ts.map +1 -0
- package/dist/schemas/sync/crud.js +16 -0
- package/dist/schemas/sync/crud.js.map +1 -0
- package/dist/schemas/sync/document.d.ts +4 -0
- package/dist/schemas/sync/document.d.ts.map +1 -0
- package/dist/schemas/sync/document.js +4 -0
- package/dist/schemas/sync/document.js.map +1 -0
- package/dist/schemas/sync/local-first.d.ts +26 -0
- package/dist/schemas/sync/local-first.d.ts.map +1 -0
- package/dist/schemas/sync/local-first.js +14 -0
- package/dist/schemas/sync/local-first.js.map +1 -0
- package/dist/schemas/sync/results.d.ts +61 -0
- package/dist/schemas/sync/results.d.ts.map +1 -0
- package/dist/schemas/sync/results.js +47 -0
- package/dist/schemas/sync/results.js.map +1 -0
- package/dist/schemas/sync-bridge-descriptor.d.ts +34 -15
- package/dist/schemas/sync-bridge-descriptor.d.ts.map +1 -1
- package/dist/schemas/sync-bridge-descriptor.js +9 -2
- package/dist/schemas/sync-bridge-descriptor.js.map +1 -1
- package/dist/schemas/sync-memory.d.ts +7 -0
- package/dist/schemas/sync-memory.d.ts.map +1 -0
- package/dist/schemas/sync-memory.js +4 -0
- package/dist/schemas/sync-memory.js.map +1 -0
- package/dist/schemas/tools/views.d.ts +83 -0
- package/dist/schemas/tools/views.d.ts.map +1 -0
- package/dist/schemas/tools/views.js +57 -0
- package/dist/schemas/tools/views.js.map +1 -0
- package/dist/schemas/tools/widgets.d.ts +41 -0
- package/dist/schemas/tools/widgets.d.ts.map +1 -0
- package/dist/schemas/tools/widgets.js +13 -0
- package/dist/schemas/tools/widgets.js.map +1 -0
- package/dist/schemas/transform.d.ts +32 -0
- package/dist/schemas/transform.d.ts.map +1 -0
- package/dist/schemas/transform.js +18 -0
- package/dist/schemas/transform.js.map +1 -0
- package/dist/schemas/zod-schema-value.d.ts +8 -0
- package/dist/schemas/zod-schema-value.d.ts.map +1 -0
- package/dist/schemas/zod-schema-value.js +8 -0
- package/dist/schemas/zod-schema-value.js.map +1 -0
- package/dist/server/manifest-route.d.ts +18 -10
- package/dist/server/manifest-route.d.ts.map +1 -1
- package/dist/server/manifest-route.js +88 -90
- package/dist/server/manifest-route.js.map +1 -1
- package/dist/storage-explorer.consumer.plugin.d.ts +10 -0
- package/dist/storage-explorer.consumer.plugin.d.ts.map +1 -0
- package/dist/storage-explorer.consumer.plugin.js +24 -0
- package/dist/storage-explorer.consumer.plugin.js.map +1 -0
- package/dist/storage-policy.context.descriptor.js +1 -1
- package/dist/storage-policy.context.descriptor.js.map +1 -1
- package/dist/storage-policy.context.server.d.ts +7 -9
- package/dist/storage-policy.context.server.d.ts.map +1 -1
- package/dist/storage-policy.context.server.js +7 -20
- package/dist/storage-policy.context.server.js.map +1 -1
- package/dist/storage.consumer.plugin.d.ts +3 -0
- package/dist/storage.consumer.plugin.d.ts.map +1 -0
- package/dist/storage.consumer.plugin.js +15 -0
- package/dist/storage.consumer.plugin.js.map +1 -0
- package/dist/storage.node.plugin.d.ts +3 -0
- package/dist/storage.node.plugin.d.ts.map +1 -0
- package/dist/storage.node.plugin.js +12 -0
- package/dist/storage.node.plugin.js.map +1 -0
- package/dist/storage.plugin.d.ts +10 -16
- package/dist/storage.plugin.d.ts.map +1 -1
- package/dist/storage.plugin.js +150 -91
- package/dist/storage.plugin.js.map +1 -1
- package/dist/svelte/components/ConnectStorageProviderDialog.svelte +345 -0
- package/dist/svelte/components/ConnectStorageProviderDialog.svelte.d.ts +17 -0
- package/dist/svelte/components/ConnectStorageProviderDialog.svelte.d.ts.map +1 -0
- package/dist/svelte/components/PlatformStorageExplorer.svelte +57 -0
- package/dist/svelte/components/PlatformStorageExplorer.svelte.d.ts +23 -0
- package/dist/svelte/components/PlatformStorageExplorer.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageBrowser.svelte +436 -0
- package/dist/svelte/components/StorageBrowser.svelte.d.ts +51 -0
- package/dist/svelte/components/StorageBrowser.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageBrowserPage.svelte +71 -0
- package/dist/svelte/components/StorageBrowserPage.svelte.d.ts +23 -0
- package/dist/svelte/components/StorageBrowserPage.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageCollectionManager.svelte +193 -0
- package/dist/svelte/components/StorageCollectionManager.svelte.d.ts +14 -0
- package/dist/svelte/components/StorageCollectionManager.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageDetailsPane.svelte +160 -0
- package/dist/svelte/components/StorageDetailsPane.svelte.d.ts +11 -0
- package/dist/svelte/components/StorageDetailsPane.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageDropzone.svelte +76 -0
- package/dist/svelte/components/StorageDropzone.svelte.d.ts +9 -0
- package/dist/svelte/components/StorageDropzone.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageExplorer.svelte +954 -0
- package/dist/svelte/components/StorageExplorer.svelte.d.ts +48 -0
- package/dist/svelte/components/StorageExplorer.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageFileBrowser.svelte +201 -0
- package/dist/svelte/components/StorageFileBrowser.svelte.d.ts +18 -0
- package/dist/svelte/components/StorageFileBrowser.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageFileCard.svelte +162 -0
- package/dist/svelte/components/StorageFileCard.svelte.d.ts +10 -0
- package/dist/svelte/components/StorageFileCard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StoragePathBar.svelte +104 -0
- package/dist/svelte/components/StoragePathBar.svelte.d.ts +19 -0
- package/dist/svelte/components/StoragePathBar.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageProviderSelect.svelte +163 -0
- package/dist/svelte/components/StorageProviderSelect.svelte.d.ts +20 -0
- package/dist/svelte/components/StorageProviderSelect.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageProviderSidebar.svelte +187 -0
- package/dist/svelte/components/StorageProviderSidebar.svelte.d.ts +30 -0
- package/dist/svelte/components/StorageProviderSidebar.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageReadinessNotice.svelte +181 -0
- package/dist/svelte/components/StorageReadinessNotice.svelte.d.ts +32 -0
- package/dist/svelte/components/StorageReadinessNotice.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageSearchBox.svelte +106 -0
- package/dist/svelte/components/StorageSearchBox.svelte.d.ts +19 -0
- package/dist/svelte/components/StorageSearchBox.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageToolbar.svelte +160 -0
- package/dist/svelte/components/StorageToolbar.svelte.d.ts +26 -0
- package/dist/svelte/components/StorageToolbar.svelte.d.ts.map +1 -0
- package/dist/svelte/components/StorageUploadChip.svelte +116 -0
- package/dist/svelte/components/StorageUploadChip.svelte.d.ts +13 -0
- package/dist/svelte/components/StorageUploadChip.svelte.d.ts.map +1 -0
- package/dist/svelte/components/index.d.ts +14 -0
- package/dist/svelte/components/index.d.ts.map +1 -0
- package/dist/svelte/components/index.js +12 -0
- package/dist/svelte/components/storage-media.d.ts +14 -0
- package/dist/svelte/components/storage-media.d.ts.map +1 -0
- package/dist/svelte/components/storage-media.js +13 -0
- package/dist/svelte/index.d.ts +14 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +13 -0
- package/dist/svelte/note-store-failure.d.ts +15 -0
- package/dist/svelte/note-store-failure.d.ts.map +1 -0
- package/dist/svelte/note-store-failure.js +21 -0
- package/dist/svelte/platform-storage-port.d.ts +86 -0
- package/dist/svelte/platform-storage-port.d.ts.map +1 -0
- package/dist/svelte/platform-storage-port.js +71 -0
- package/dist/svelte/storage-browse-runtime.d.ts +65 -0
- package/dist/svelte/storage-browse-runtime.d.ts.map +1 -0
- package/dist/svelte/storage-browse-runtime.js +68 -0
- package/dist/svelte/storage-connection.d.ts +23 -0
- package/dist/svelte/storage-connection.d.ts.map +1 -0
- package/dist/svelte/storage-connection.js +40 -0
- package/dist/svelte/storage-display.d.ts +6 -0
- package/dist/svelte/storage-display.d.ts.map +1 -0
- package/dist/svelte/storage-display.js +58 -0
- package/dist/svelte/storage-explorer-runtime.d.ts +120 -0
- package/dist/svelte/storage-explorer-runtime.d.ts.map +1 -0
- package/dist/svelte/storage-explorer-runtime.js +251 -0
- package/dist/svelte/storage-explorer.load.d.ts +22 -0
- package/dist/svelte/storage-explorer.load.d.ts.map +1 -0
- package/dist/svelte/storage-explorer.load.js +37 -0
- package/dist/svelte/storage-refs-runtime.d.ts +36 -0
- package/dist/svelte/storage-refs-runtime.d.ts.map +1 -0
- package/dist/svelte/storage-refs-runtime.js +47 -0
- package/dist/svelte/storage-runtime.d.ts +30 -0
- package/dist/svelte/storage-runtime.d.ts.map +1 -0
- package/dist/svelte/storage-runtime.js +39 -0
- package/dist/svelte/storage-svelte.plugin.d.ts +40 -0
- package/dist/svelte/storage-svelte.plugin.d.ts.map +1 -0
- package/dist/svelte/storage-svelte.plugin.js +50 -0
- package/dist/svelte/storage-upload-runtime.d.ts +8 -0
- package/dist/svelte/storage-upload-runtime.d.ts.map +1 -0
- package/dist/svelte/storage-upload-runtime.js +12 -0
- package/dist/svelte/stores/index.d.ts +4 -0
- package/dist/svelte/stores/index.d.ts.map +1 -0
- package/dist/svelte/stores/index.js +3 -0
- package/dist/svelte/stores/storage-browse.svelte.d.ts +70 -0
- package/dist/svelte/stores/storage-browse.svelte.d.ts.map +1 -0
- package/dist/svelte/stores/storage-browse.svelte.js +133 -0
- package/dist/svelte/stores/storage-providers.svelte.d.ts +32 -0
- package/dist/svelte/stores/storage-providers.svelte.d.ts.map +1 -0
- package/dist/svelte/stores/storage-providers.svelte.js +73 -0
- package/dist/svelte/stores/storage-refs.svelte.d.ts +57 -0
- package/dist/svelte/stores/storage-refs.svelte.d.ts.map +1 -0
- package/dist/svelte/stores/storage-refs.svelte.js +90 -0
- package/dist/svelte/styles.css +14 -0
- package/dist/svelte/test-assets.d.ts +8 -0
- package/dist/svelte/tool-widgets/StorageBrowseToolCard.svelte +109 -0
- package/dist/svelte/tool-widgets/StorageBrowseToolCard.svelte.d.ts +9 -0
- package/dist/svelte/tool-widgets/StorageBrowseToolCard.svelte.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/StorageCopyToolCard.svelte +87 -0
- package/dist/svelte/tool-widgets/StorageCopyToolCard.svelte.d.ts +9 -0
- package/dist/svelte/tool-widgets/StorageCopyToolCard.svelte.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/StorageSearchToolCard.svelte +94 -0
- package/dist/svelte/tool-widgets/StorageSearchToolCard.svelte.d.ts +9 -0
- package/dist/svelte/tool-widgets/StorageSearchToolCard.svelte.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/format.d.ts +15 -0
- package/dist/svelte/tool-widgets/format.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/format.js +32 -0
- package/dist/svelte/tool-widgets/index.d.ts +2 -0
- package/dist/svelte/tool-widgets/index.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/index.js +12 -0
- package/dist/svelte/tool-widgets/runtime.d.ts +9 -0
- package/dist/svelte/tool-widgets/runtime.d.ts.map +1 -0
- package/dist/svelte/tool-widgets/runtime.js +42 -0
- package/dist/sync/cloud-document.d.ts +24 -0
- package/dist/sync/cloud-document.d.ts.map +1 -0
- package/dist/sync/cloud-document.js +47 -0
- package/dist/sync/cloud-document.js.map +1 -0
- package/dist/sync/cloud-pull.d.ts +9 -3
- package/dist/sync/cloud-pull.d.ts.map +1 -1
- package/dist/sync/cloud-pull.js +85 -86
- package/dist/sync/cloud-pull.js.map +1 -1
- package/dist/sync/cloud-record-crud-config.d.ts +7 -17
- package/dist/sync/cloud-record-crud-config.d.ts.map +1 -1
- package/dist/sync/cloud-record-crud-config.js +17 -9
- package/dist/sync/cloud-record-crud-config.js.map +1 -1
- package/dist/sync/local-first.d.ts +61 -0
- package/dist/sync/local-first.d.ts.map +1 -0
- package/dist/sync/local-first.js +121 -0
- package/dist/sync/local-first.js.map +1 -0
- package/dist/sync/record-sync-bridge.d.ts +35 -3
- package/dist/sync/record-sync-bridge.d.ts.map +1 -1
- package/dist/sync/record-sync-bridge.js +50 -46
- package/dist/sync/record-sync-bridge.js.map +1 -1
- package/dist/sync/resolve-sync-target.d.ts +16 -17
- package/dist/sync/resolve-sync-target.d.ts.map +1 -1
- package/dist/sync/resolve-sync-target.js +18 -43
- package/dist/sync/resolve-sync-target.js.map +1 -1
- package/dist/sync/runtime-sync-state.d.ts +18 -0
- package/dist/sync/runtime-sync-state.d.ts.map +1 -0
- package/dist/sync/runtime-sync-state.js +12 -0
- package/dist/sync/runtime-sync-state.js.map +1 -0
- package/dist/sync/schemas/cloud-pull.d.ts +9 -5
- package/dist/sync/schemas/cloud-pull.d.ts.map +1 -1
- package/dist/sync/schemas/index.d.ts +3 -3
- package/dist/sync/schemas/index.d.ts.map +1 -1
- package/dist/sync/schemas/index.js +3 -3
- package/dist/sync/schemas/index.js.map +1 -1
- package/dist/sync/schemas/sync-helpers.d.ts +4 -2
- package/dist/sync/schemas/sync-helpers.d.ts.map +1 -1
- package/dist/sync/schemas/sync-state.d.ts +7 -5
- package/dist/sync/schemas/sync-state.d.ts.map +1 -1
- package/dist/sync/schemas/sync-state.js +1 -0
- package/dist/sync/schemas/sync-state.js.map +1 -1
- package/dist/sync/sync-bridge.d.ts +39 -31
- package/dist/sync/sync-bridge.d.ts.map +1 -1
- package/dist/sync/sync-bridge.js +427 -196
- package/dist/sync/sync-bridge.js.map +1 -1
- package/dist/sync/sync-manifest.d.ts +7 -19
- package/dist/sync/sync-manifest.d.ts.map +1 -1
- package/dist/sync/sync-manifest.js +125 -84
- package/dist/sync/sync-manifest.js.map +1 -1
- package/dist/sync/sync-state.browser.d.ts +37 -0
- package/dist/sync/sync-state.browser.d.ts.map +1 -0
- package/dist/sync/sync-state.browser.js +136 -0
- package/dist/sync/sync-state.browser.js.map +1 -0
- package/dist/sync/sync-state.client.d.ts +71 -0
- package/dist/sync/sync-state.client.d.ts.map +1 -0
- package/dist/sync/sync-state.client.js +78 -0
- package/dist/sync/sync-state.client.js.map +1 -0
- package/dist/sync/sync-state.consumer.d.ts +44 -17
- package/dist/sync/sync-state.consumer.d.ts.map +1 -1
- package/dist/sync/sync-state.consumer.js +161 -87
- package/dist/sync/sync-state.consumer.js.map +1 -1
- package/dist/sync/sync-state.core.d.ts +40 -0
- package/dist/sync/sync-state.core.d.ts.map +1 -0
- package/dist/sync/sync-state.core.js +130 -0
- package/dist/sync/sync-state.core.js.map +1 -0
- package/dist/sync/sync-state.descriptor.d.ts +39 -0
- package/dist/sync/sync-state.descriptor.d.ts.map +1 -0
- package/dist/sync/sync-state.descriptor.js +23 -0
- package/dist/sync/sync-state.descriptor.js.map +1 -0
- package/dist/testing.d.ts +31 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +82 -0
- package/dist/testing.js.map +1 -0
- package/dist/tools/descriptors.d.ts +312 -0
- package/dist/tools/descriptors.d.ts.map +1 -0
- package/dist/tools/descriptors.js +98 -0
- package/dist/tools/descriptors.js.map +1 -0
- package/dist/tools/impl.d.ts +72 -0
- package/dist/tools/impl.d.ts.map +1 -0
- package/dist/tools/impl.js +572 -0
- package/dist/tools/impl.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/schemas.d.ts +136 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +113 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools/widget-views.d.ts +9 -0
- package/dist/tools/widget-views.d.ts.map +1 -0
- package/dist/tools/widget-views.js +119 -0
- package/dist/tools/widget-views.js.map +1 -0
- package/dist/transform/index.d.ts +7 -18
- package/dist/transform/index.d.ts.map +1 -1
- package/dist/transform/index.js +16 -14
- package/dist/transform/index.js.map +1 -1
- package/dist/transform/transform-descriptor.d.ts +1 -21
- package/dist/transform/transform-descriptor.d.ts.map +1 -1
- package/dist/transform/transform-descriptor.js +1 -29
- package/dist/transform/transform-descriptor.js.map +1 -1
- package/dist/tui/plugin.d.ts +26 -0
- package/dist/tui/plugin.d.ts.map +1 -0
- package/dist/tui/plugin.js +107 -0
- package/dist/tui/plugin.js.map +1 -0
- package/dist/tui/storage-explorer.descriptor.d.ts +25 -4
- package/dist/tui/storage-explorer.descriptor.d.ts.map +1 -1
- package/dist/tui/storage-explorer.descriptor.js +7 -3
- package/dist/tui/storage-explorer.descriptor.js.map +1 -1
- package/dist/tui/storage-explorer.impl.consumer.d.ts +23 -6
- package/dist/tui/storage-explorer.impl.consumer.d.ts.map +1 -1
- package/dist/tui/storage-explorer.impl.consumer.js +231 -8
- package/dist/tui/storage-explorer.impl.consumer.js.map +1 -1
- package/dist/tui/storage-open.descriptor.d.ts +16 -3
- package/dist/tui/storage-open.descriptor.d.ts.map +1 -1
- package/dist/tui/storage-open.descriptor.js +5 -1
- package/dist/tui/storage-open.descriptor.js.map +1 -1
- package/dist/tui/storage-open.impl.consumer.d.ts +6 -1
- package/dist/tui/storage-open.impl.consumer.d.ts.map +1 -1
- package/dist/tui/storage-open.impl.consumer.js.map +1 -1
- package/dist/tui/storage-signed-out-ui.d.ts +21 -0
- package/dist/tui/storage-signed-out-ui.d.ts.map +1 -0
- package/dist/tui/storage-signed-out-ui.js +34 -0
- package/dist/tui/storage-signed-out-ui.js.map +1 -0
- package/dist/tui/storage.tui-state.descriptor.d.ts +61 -0
- package/dist/tui/storage.tui-state.descriptor.d.ts.map +1 -0
- package/dist/tui/storage.tui-state.descriptor.js +52 -0
- package/dist/tui/storage.tui-state.descriptor.js.map +1 -0
- package/dist/tui/storage.tui-state.impl.consumer.d.ts +29 -0
- package/dist/tui/storage.tui-state.impl.consumer.d.ts.map +1 -0
- package/dist/tui/storage.tui-state.impl.consumer.js +207 -0
- package/dist/tui/storage.tui-state.impl.consumer.js.map +1 -0
- package/dist/tui/storage.tui-state.key.d.ts +27 -0
- package/dist/tui/storage.tui-state.key.d.ts.map +1 -0
- package/dist/tui/storage.tui-state.key.js +12 -0
- package/dist/tui/storage.tui-state.key.js.map +1 -0
- package/dist/tui/tool-widgets/StorageBrowseToolWidget.impl.consumer.d.ts +14 -0
- package/dist/tui/tool-widgets/StorageBrowseToolWidget.impl.consumer.d.ts.map +1 -0
- package/dist/tui/tool-widgets/StorageBrowseToolWidget.impl.consumer.js +28 -0
- package/dist/tui/tool-widgets/StorageBrowseToolWidget.impl.consumer.js.map +1 -0
- package/dist/tui/tool-widgets/StorageCopyToolWidget.impl.consumer.d.ts +14 -0
- package/dist/tui/tool-widgets/StorageCopyToolWidget.impl.consumer.d.ts.map +1 -0
- package/dist/tui/tool-widgets/StorageCopyToolWidget.impl.consumer.js +21 -0
- package/dist/tui/tool-widgets/StorageCopyToolWidget.impl.consumer.js.map +1 -0
- package/dist/tui/tool-widgets/StorageSearchToolWidget.impl.consumer.d.ts +14 -0
- package/dist/tui/tool-widgets/StorageSearchToolWidget.impl.consumer.d.ts.map +1 -0
- package/dist/tui/tool-widgets/StorageSearchToolWidget.impl.consumer.js +22 -0
- package/dist/tui/tool-widgets/StorageSearchToolWidget.impl.consumer.js.map +1 -0
- package/dist/tui/tool-widgets/format.d.ts +19 -0
- package/dist/tui/tool-widgets/format.d.ts.map +1 -0
- package/dist/tui/tool-widgets/format.js +53 -0
- package/dist/tui/tool-widgets/format.js.map +1 -0
- package/dist/tui/tool-widgets/storage.browse.tool-widget.descriptor.d.ts +14 -0
- package/dist/tui/tool-widgets/storage.browse.tool-widget.descriptor.d.ts.map +1 -0
- package/dist/tui/tool-widgets/storage.browse.tool-widget.descriptor.js +14 -0
- package/dist/tui/tool-widgets/storage.browse.tool-widget.descriptor.js.map +1 -0
- package/dist/tui/tool-widgets/storage.copy.tool-widget.descriptor.d.ts +14 -0
- package/dist/tui/tool-widgets/storage.copy.tool-widget.descriptor.d.ts.map +1 -0
- package/dist/tui/tool-widgets/storage.copy.tool-widget.descriptor.js +14 -0
- package/dist/tui/tool-widgets/storage.copy.tool-widget.descriptor.js.map +1 -0
- package/dist/tui/tool-widgets/storage.search.tool-widget.descriptor.d.ts +14 -0
- package/dist/tui/tool-widgets/storage.search.tool-widget.descriptor.d.ts.map +1 -0
- package/dist/tui/tool-widgets/storage.search.tool-widget.descriptor.js +14 -0
- package/dist/tui/tool-widgets/storage.search.tool-widget.descriptor.js.map +1 -0
- package/dist/utils/caller-bearer.d.ts +13 -0
- package/dist/utils/caller-bearer.d.ts.map +1 -0
- package/dist/utils/caller-bearer.js +17 -0
- package/dist/utils/caller-bearer.js.map +1 -0
- package/dist/utils/error-context.d.ts +9 -1
- package/dist/utils/error-context.d.ts.map +1 -1
- package/dist/utils/error-context.js +7 -14
- package/dist/utils/error-context.js.map +1 -1
- package/dist/utils/failure-of.d.ts +20 -0
- package/dist/utils/failure-of.d.ts.map +1 -0
- package/dist/utils/failure-of.js +35 -0
- package/dist/utils/failure-of.js.map +1 -0
- package/dist/workers-port.d.ts +37 -0
- package/dist/workers-port.d.ts.map +1 -0
- package/dist/workers-port.js +60 -0
- package/dist/workers-port.js.map +1 -0
- package/package.json +644 -148
- package/SPEC.md +0 -60
- package/dist/adapters/index.d.ts +0 -14
- package/dist/adapters/index.d.ts.map +0 -1
- package/dist/adapters/index.js +0 -15
- package/dist/adapters/index.js.map +0 -1
- package/dist/cli/context.consumer.d.ts +0 -26
- package/dist/cli/context.consumer.d.ts.map +0 -1
- package/dist/cli/context.consumer.js +0 -164
- package/dist/cli/context.consumer.js.map +0 -1
- package/dist/docs/adapters.docs.descriptor.d.ts +0 -4
- package/dist/docs/adapters.docs.descriptor.d.ts.map +0 -1
- package/dist/docs/adapters.docs.descriptor.js +0 -223
- package/dist/docs/adapters.docs.descriptor.js.map +0 -1
- package/dist/docs/manifests.docs.descriptor.d.ts +0 -4
- package/dist/docs/manifests.docs.descriptor.d.ts.map +0 -1
- package/dist/docs/manifests.docs.descriptor.js.map +0 -1
- package/src/adapters/index.ts +0 -13
- package/src/bridge/index.ts +0 -2
- package/src/bridge/storage-kind-bridge.ts +0 -131
- package/src/bridge/types.ts +0 -46
- package/src/cli/commands/list/storage.list.cli-command.descriptor.ts +0 -20
- package/src/cli/commands/list/storage.list.cli-command.impl.ts +0 -72
- package/src/cli/commands/sync/pull/storage.sync.pull.cli-command.descriptor.ts +0 -28
- package/src/cli/commands/sync/pull/storage.sync.pull.cli-command.impl.ts +0 -171
- package/src/cli/commands/sync/push/storage.sync.push.cli-command.descriptor.ts +0 -28
- package/src/cli/commands/sync/push/storage.sync.push.cli-command.impl.ts +0 -213
- package/src/cli/commands/sync/resolve/storage.sync.resolve.cli-command.descriptor.ts +0 -22
- package/src/cli/commands/sync/resolve/storage.sync.resolve.cli-command.impl.ts +0 -129
- package/src/cli/commands/sync/status/storage.sync.status.cli-command.descriptor.ts +0 -17
- package/src/cli/commands/sync/status/storage.sync.status.cli-command.impl.ts +0 -192
- package/src/cli/context.consumer.ts +0 -195
- package/src/cli/storage.cli-group.descriptor.ts +0 -13
- package/src/cli/storage.sync.cli-group.descriptor.ts +0 -14
- package/src/cli/types.ts +0 -25
- package/src/client/index.ts +0 -10
- package/src/client/list-browsable-adapters.ts +0 -42
- package/src/daemon/commands/sync.ts +0 -225
- package/src/docs/adapters.docs.descriptor.ts +0 -225
- package/src/docs/manifests.docs.descriptor.ts +0 -318
- package/src/docs/records.docs.descriptor.ts +0 -271
- package/src/docs/sync.docs.descriptor.ts +0 -281
- package/src/index.ts +0 -109
- package/src/interfaces/adapter.ts +0 -451
- package/src/interfaces/document-store.ts +0 -74
- package/src/interfaces/index.ts +0 -117
- package/src/interfaces/key-value.ts +0 -112
- package/src/interfaces/query-builder.ts +0 -31
- package/src/interfaces/record/index.ts +0 -58
- package/src/interfaces/record/interface.ts +0 -723
- package/src/interfaces/repository.ts +0 -300
- package/src/interfaces/schema/index.ts +0 -37
- package/src/interfaces/schema/schema-interface.ts +0 -586
- package/src/interfaces/schema/table-interface.ts +0 -314
- package/src/interfaces/storage-consumer-fs.ts +0 -66
- package/src/manifest/base.ts +0 -594
- package/src/manifest/cloud-sync.consumer.ts +0 -120
- package/src/manifest/index.ts +0 -7
- package/src/manifest/source-policy.ts +0 -74
- package/src/paths.ts +0 -171
- package/src/policy/context.ts +0 -30
- package/src/policy/index.ts +0 -20
- package/src/policy/overrides.ts +0 -46
- package/src/policy/persistence.ts +0 -117
- package/src/policy/types.ts +0 -28
- package/src/record/base.ts +0 -1872
- package/src/record/events.ts +0 -56
- package/src/record/extensions.ts +0 -308
- package/src/record/index.ts +0 -7
- package/src/resolver/storage-backed-resolver.ts +0 -255
- package/src/schemas/adapter-descriptor.ts +0 -128
- package/src/schemas/index.ts +0 -104
- package/src/schemas/manifest-descriptor.ts +0 -143
- package/src/schemas/record/descriptor.ts +0 -621
- package/src/schemas/record/index.ts +0 -60
- package/src/schemas/record/relation.ts +0 -49
- package/src/schemas/schema/descriptor.ts +0 -278
- package/src/schemas/schema/index.ts +0 -14
- package/src/schemas/sync-bridge-descriptor.ts +0 -143
- package/src/server/manifest-route.ts +0 -319
- package/src/storage-policy.context.descriptor.ts +0 -17
- package/src/storage-policy.context.server.ts +0 -34
- package/src/storage.plugin.ts +0 -264
- package/src/sync/cloud-pull.ts +0 -202
- package/src/sync/cloud-record-crud-config.ts +0 -41
- package/src/sync/record-sync-bridge.ts +0 -183
- package/src/sync/resolve-sync-target.ts +0 -57
- package/src/sync/schemas/cloud-pull.ts +0 -26
- package/src/sync/schemas/index.ts +0 -18
- package/src/sync/schemas/sync-helpers.ts +0 -18
- package/src/sync/schemas/sync-state.ts +0 -41
- package/src/sync/sync-bridge.ts +0 -763
- package/src/sync/sync-manifest.ts +0 -212
- package/src/sync/sync-state.consumer.ts +0 -144
- package/src/transform/index.ts +0 -130
- package/src/transform/transform-descriptor.ts +0 -53
- package/src/tui/storage-explorer.descriptor.ts +0 -32
- package/src/tui/storage-explorer.impl.consumer.ts +0 -14
- package/src/tui/storage-open.descriptor.ts +0 -31
- package/src/tui/storage-open.impl.consumer.ts +0 -14
- package/src/utils/error-context.ts +0 -19
package/dist/record/base.js
CHANGED
|
@@ -16,16 +16,248 @@
|
|
|
16
16
|
* });
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
+
import * as z from 'zod/v4';
|
|
20
|
+
import { isRecordTableMissing } from "./failure-condition.js";
|
|
19
21
|
import { getLogger } from '@vibesdotdev/logging';
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
import { CapabilityError } from '@vibesdotdev/kernel';
|
|
23
|
+
import { JsonObjectSchema, JsonValueSchema } from '@vibesdotdev/kernel/schemas/json';
|
|
24
|
+
import { toErrorContext } from "../utils/error-context.js";
|
|
25
|
+
import { failureOfSync, outcomeOf } from "../utils/failure-of.js";
|
|
26
|
+
import { RecordFilterOperatorSchema } from "../schemas/record/query-values.js";
|
|
27
|
+
import { StorageAdapterConfigSchema } from "../schemas/adapter-config.js";
|
|
28
|
+
import { ZodSchemaValueSchema } from "../schemas/zod-schema-value.js";
|
|
29
|
+
import { resolveScopePathTemplate, resolveVibesBaseDir, resolveWorkspacePathWithPolicy } from "../path-policy.js";
|
|
30
|
+
import { createTracer } from '@vibesdotdev/kernel/debug';
|
|
31
|
+
import { getPathRoots } from '@vibesdotdev/paths';
|
|
32
|
+
import { selectStorageAdapter } from "../adapters/select-adapter.js";
|
|
33
|
+
import { parseAdapterInput } from "../adapters/parse-adapter-input.js";
|
|
34
|
+
import { extractCloudEndpoint, registerCloudEndpointIfSupported } from "../schemas/cloud-endpoints.js";
|
|
35
|
+
import { enqueueRecordDelivery } from "./delivery.js";
|
|
36
|
+
import { applySetAdditions, assertArrayField, assertArrayMembers, updatePatch } from "./array-fields.js";
|
|
37
|
+
import { resolveSchemaCoordination } from "./schema-coordination.client.js";
|
|
38
|
+
import { loadRecordRelations } from "./relations.js";
|
|
39
|
+
import { assertRecordRevisionSchema, recordIndexName, resolveTimestampField, softDeleteColumn, timestampColumn } from "./columns.js";
|
|
40
|
+
import { RECORD_REVISION_COLUMN, RecordRevisionMetadataSchema, RecordRevisionQuerySchema } from "../schemas/record/revision.js";
|
|
41
|
+
import { RecordAtomicMutationsSchema } from "../schemas/record/atomic.js";
|
|
42
|
+
/** Whether a value is a field map: an object that is not an array (every stored row's JSON is one). */
|
|
43
|
+
function isFieldMap(value) {
|
|
44
|
+
return value instanceof Object && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
/** A record value seen as its field map: the same object, never a copy, so a hook's or loader's change lands on it. */
|
|
47
|
+
const RecordFieldsViewSchema = z.custom((value) => isFieldMap(value));
|
|
48
|
+
const RecordLifecycleHookSchema = z.custom((value) => value instanceof Function);
|
|
49
|
+
/** An inline migration's `up` step: SQL text, or a function run against the record's database. */
|
|
50
|
+
const MigrationSqlSchema = z.string();
|
|
51
|
+
const MigrationFunctionSchema = z.custom((value) => value instanceof Function);
|
|
52
|
+
/** A descriptor's custom id generator (`autoId.strategy: 'custom'`) and the id it produces. */
|
|
53
|
+
const IdGeneratorSchema = z.custom((value) => value instanceof Function);
|
|
54
|
+
const GeneratedIdSchema = z.string();
|
|
55
|
+
/** A descriptor's per-scope storage path templates (`scopePaths`), when it declares them. */
|
|
56
|
+
const ScopePathTemplatesSchema = z.object({ scopePaths: z.record(z.string(), z.string().optional()).optional() });
|
|
57
|
+
/** What a SQL database connection must offer this record: prepared statements and statement execution. */
|
|
58
|
+
const SqlHandleMethodsSchema = z.object({ prepare: z.function(), exec: z.function() });
|
|
59
|
+
const SqlDatabaseHandleSchema = z.custom((value) => SqlHandleMethodsSchema.safeParse(value).success);
|
|
60
|
+
const SyncTransactionMethodsSchema = z.object({ transaction: z.function(), inTransaction: z.boolean() });
|
|
61
|
+
const SyncTransactionDatabaseSchema = z.custom(value => SyncTransactionMethodsSchema.safeParse(value).success);
|
|
62
|
+
/** Where a record-scoped database opens: the record's resolved base path and its adapter's database file name. */
|
|
63
|
+
const RecordDatabaseTargetSchema = z.object({ basePath: z.string(), dbName: z.string().optional() });
|
|
64
|
+
/** The workspace a record's kind context names (`runtime/workspace-path`), when it names one. */
|
|
65
|
+
const ContextWorkspacePathSchema = z.string();
|
|
66
|
+
/** A hierarchy query's id path (`json_array` of the primary keys from the root). */
|
|
67
|
+
const HierarchyPathSchema = z.array(z.union([z.string(), z.number()]).transform(String));
|
|
68
|
+
/**
|
|
69
|
+
* A filter's fields, seen as the caller's own object (never a copy, so no own
|
|
70
|
+
* key is dropped): each field's condition, an operator object or an equality value.
|
|
71
|
+
*/
|
|
72
|
+
const FilterFieldsViewSchema = z.custom((value) => isFieldMap(value));
|
|
73
|
+
const JsonFilterOperandSchema = z.union([JsonObjectSchema, z.array(JsonValueSchema)]);
|
|
74
|
+
/**
|
|
75
|
+
* Whether a field condition is an operator object (`{ eq, in, isNull, … }`):
|
|
76
|
+
* any object, null-prototype ones included, except a Date (a Date, a function
|
|
77
|
+
* or a primitive is an equality value). The condition is read in place.
|
|
78
|
+
*/
|
|
79
|
+
function isOperatorObject(value) {
|
|
80
|
+
if (value === null || value === undefined || Array.isArray(value))
|
|
81
|
+
return false;
|
|
82
|
+
if (value instanceof Object)
|
|
83
|
+
return !(value instanceof Date) && !(value instanceof Function);
|
|
84
|
+
return Object.getPrototypeOf(value) === null;
|
|
85
|
+
}
|
|
86
|
+
const FilterOperatorsSchema = z.custom((value) => isOperatorObject(value));
|
|
87
|
+
/**
|
|
88
|
+
* Whether a descriptor's schema is a zod schema. The record's row type `T` is
|
|
89
|
+
* the one its registered descriptor declares: the schema it carries produces it.
|
|
90
|
+
*/
|
|
91
|
+
function isRecordSchema(schema) {
|
|
92
|
+
return ZodSchemaValueSchema.safeParse(schema).success;
|
|
93
|
+
}
|
|
94
|
+
const connectionTransactions = new WeakMap();
|
|
95
|
+
const signalConnectionStates = new WeakMap();
|
|
96
|
+
function getConnectionTransactionState(db) {
|
|
97
|
+
let state = connectionTransactions.get(db);
|
|
98
|
+
if (!state) {
|
|
99
|
+
state = {
|
|
100
|
+
leased: false,
|
|
101
|
+
waiters: [],
|
|
102
|
+
framesBySignal: new Map(),
|
|
103
|
+
fallbackFrame: null,
|
|
104
|
+
savepointSeq: 0
|
|
105
|
+
};
|
|
106
|
+
connectionTransactions.set(db, state);
|
|
107
|
+
}
|
|
108
|
+
return state;
|
|
109
|
+
}
|
|
110
|
+
function currentInvocationSignal(runtime) {
|
|
111
|
+
let signal;
|
|
112
|
+
const failure = failureOfSync(() => {
|
|
113
|
+
signal = runtime.invocationSignal;
|
|
114
|
+
});
|
|
115
|
+
return failure ? undefined : signal;
|
|
116
|
+
}
|
|
117
|
+
function getActiveTransactionFrame(db, runtime) {
|
|
118
|
+
const state = connectionTransactions.get(db);
|
|
119
|
+
if (!state || !state.leased)
|
|
120
|
+
return null;
|
|
121
|
+
const signal = currentInvocationSignal(runtime);
|
|
122
|
+
if (signal !== undefined) {
|
|
123
|
+
return state.framesBySignal.get(signal) ?? null;
|
|
124
|
+
}
|
|
125
|
+
return state.fallbackFrame;
|
|
126
|
+
}
|
|
127
|
+
function isConnectionTransactionActive(db) {
|
|
128
|
+
const state = connectionTransactions.get(db);
|
|
129
|
+
return Boolean(state?.leased);
|
|
130
|
+
}
|
|
131
|
+
async function waitForConnectionLease(db, runtime) {
|
|
132
|
+
const state = connectionTransactions.get(db);
|
|
133
|
+
if (!state)
|
|
134
|
+
return;
|
|
135
|
+
while (state.leased && getActiveTransactionFrame(db, runtime) === null) {
|
|
136
|
+
await new Promise((resolve) => {
|
|
137
|
+
state.waiters.push(resolve);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async function withTransactionFrame(runtime, state, parentFrame, operation) {
|
|
142
|
+
const parentSignal = currentInvocationSignal(runtime);
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
const runWithFrame = async (ownedSignal) => {
|
|
145
|
+
const frame = {
|
|
146
|
+
state,
|
|
147
|
+
parent: parentFrame,
|
|
148
|
+
signal: ownedSignal,
|
|
149
|
+
childLeased: false,
|
|
150
|
+
childWaiters: [],
|
|
151
|
+
onRollback: []
|
|
152
|
+
};
|
|
153
|
+
const inheritedStates = [];
|
|
154
|
+
if (ownedSignal !== undefined) {
|
|
155
|
+
state.framesBySignal.set(ownedSignal, frame);
|
|
156
|
+
const ownedSet = new Set([state]);
|
|
157
|
+
if (parentSignal !== undefined) {
|
|
158
|
+
const parentSet = signalConnectionStates.get(parentSignal);
|
|
159
|
+
if (parentSet) {
|
|
160
|
+
for (const otherState of parentSet) {
|
|
161
|
+
ownedSet.add(otherState);
|
|
162
|
+
if (otherState !== state) {
|
|
163
|
+
const otherFrame = otherState.framesBySignal.get(parentSignal);
|
|
164
|
+
if (otherFrame) {
|
|
165
|
+
otherState.framesBySignal.set(ownedSignal, otherFrame);
|
|
166
|
+
inheritedStates.push(otherState);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
signalConnectionStates.set(ownedSignal, ownedSet);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
state.fallbackFrame = frame;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
return await operation(frame);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
if (ownedSignal !== undefined) {
|
|
182
|
+
state.framesBySignal.delete(ownedSignal);
|
|
183
|
+
for (const otherState of inheritedStates) {
|
|
184
|
+
otherState.framesBySignal.delete(ownedSignal);
|
|
185
|
+
}
|
|
186
|
+
signalConnectionStates.delete(ownedSignal);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
state.fallbackFrame = parentFrame;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
return runtime.runInvocation(() => runWithFrame(currentInvocationSignal(runtime)), { signal: controller.signal });
|
|
194
|
+
}
|
|
195
|
+
function flushRollbackCallbacks(frame) {
|
|
196
|
+
for (let i = frame.onRollback.length - 1; i >= 0; i--) {
|
|
197
|
+
frame.onRollback[i]();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function runInSavepoint(runtime, parentFrame, db, operation) {
|
|
201
|
+
while (parentFrame.childLeased) {
|
|
202
|
+
await new Promise((resolve) => {
|
|
203
|
+
parentFrame.childWaiters.push(resolve);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
parentFrame.childLeased = true;
|
|
207
|
+
const savepoint = `vibes_tx_sp_${++parentFrame.state.savepointSeq}`;
|
|
208
|
+
try {
|
|
209
|
+
return await withTransactionFrame(runtime, parentFrame.state, parentFrame, async (childFrame) => {
|
|
210
|
+
db.exec(`SAVEPOINT ${savepoint}`);
|
|
211
|
+
try {
|
|
212
|
+
const result = await operation(childFrame);
|
|
213
|
+
db.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
214
|
+
parentFrame.onRollback.push(...childFrame.onRollback);
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
db.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
|
|
219
|
+
db.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
220
|
+
flushRollbackCallbacks(childFrame);
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
parentFrame.childLeased = false;
|
|
227
|
+
const waiters = parentFrame.childWaiters.splice(0);
|
|
228
|
+
for (const wake of waiters) {
|
|
229
|
+
wake();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function withImmediateSchemaTransaction(runtime, db, operation) {
|
|
234
|
+
const state = getConnectionTransactionState(db);
|
|
235
|
+
while (state.leased) {
|
|
236
|
+
await new Promise((resolve) => {
|
|
237
|
+
state.waiters.push(resolve);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
state.leased = true;
|
|
241
|
+
try {
|
|
242
|
+
await withTransactionFrame(runtime, state, null, async () => {
|
|
243
|
+
db.exec('BEGIN IMMEDIATE');
|
|
244
|
+
try {
|
|
245
|
+
await operation();
|
|
246
|
+
db.exec('COMMIT');
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
db.exec('ROLLBACK');
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
state.leased = false;
|
|
256
|
+
const waiters = state.waiters.splice(0);
|
|
257
|
+
for (const wake of waiters) {
|
|
258
|
+
wake();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
29
261
|
}
|
|
30
262
|
export function applyCreateTimestamps(record, timestamps, now) {
|
|
31
263
|
if (!timestamps) {
|
|
@@ -49,6 +281,93 @@ export function applyUpdateTimestamp(record, update, timestamps, now) {
|
|
|
49
281
|
record[updatedAtField] = now;
|
|
50
282
|
}
|
|
51
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* Split a multi-statement SQL string into individual statements.
|
|
286
|
+
*
|
|
287
|
+
* Unlike a naive `split(';')`, this respects:
|
|
288
|
+
* - single/double-quoted string literals (including `''` escapes),
|
|
289
|
+
* - line comments (--) and block comments,
|
|
290
|
+
* - `CREATE TRIGGER ... BEGIN ... END` bodies, whose internal `;`s must not
|
|
291
|
+
* terminate the statement (a `CASE ... END` inside the body is balanced too).
|
|
292
|
+
*/
|
|
293
|
+
export function splitSqlStatements(sql) {
|
|
294
|
+
const statements = [];
|
|
295
|
+
let current = '';
|
|
296
|
+
let triggerDepth = 0;
|
|
297
|
+
let i = 0;
|
|
298
|
+
const isWordChar = (c) => /[A-Za-z0-9_]/.test(c);
|
|
299
|
+
while (i < sql.length) {
|
|
300
|
+
const c = sql[i];
|
|
301
|
+
const next = sql[i + 1];
|
|
302
|
+
if (c === '-' && next === '-') {
|
|
303
|
+
const end = sql.indexOf('\n', i);
|
|
304
|
+
const stop = end === -1 ? sql.length : end + 1;
|
|
305
|
+
current += sql.slice(i, stop);
|
|
306
|
+
i = stop;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (c === '/' && next === '*') {
|
|
310
|
+
const end = sql.indexOf('*/', i + 2);
|
|
311
|
+
const stop = end === -1 ? sql.length : end + 2;
|
|
312
|
+
current += sql.slice(i, stop);
|
|
313
|
+
i = stop;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (c === "'" || c === '"') {
|
|
317
|
+
let j = i + 1;
|
|
318
|
+
while (j < sql.length) {
|
|
319
|
+
if (sql[j] === c) {
|
|
320
|
+
// '' inside a single-quoted literal is an escaped quote
|
|
321
|
+
if (c === "'" && sql[j + 1] === "'") {
|
|
322
|
+
j += 2;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
j++;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
j++;
|
|
329
|
+
}
|
|
330
|
+
current += sql.slice(i, j);
|
|
331
|
+
i = j;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (c === ';') {
|
|
335
|
+
if (triggerDepth > 0) {
|
|
336
|
+
current += c;
|
|
337
|
+
i++;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (current.trim())
|
|
341
|
+
statements.push(current.trim());
|
|
342
|
+
current = '';
|
|
343
|
+
i++;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (isWordChar(c)) {
|
|
347
|
+
let j = i;
|
|
348
|
+
while (j < sql.length && isWordChar(sql[j]))
|
|
349
|
+
j++;
|
|
350
|
+
const word = sql.slice(i, j).toUpperCase();
|
|
351
|
+
if (triggerDepth > 0) {
|
|
352
|
+
if (word === 'CASE')
|
|
353
|
+
triggerDepth++;
|
|
354
|
+
else if (word === 'END')
|
|
355
|
+
triggerDepth--;
|
|
356
|
+
}
|
|
357
|
+
else if (word === 'BEGIN' && /\bCREATE\s+(TEMP(ORARY)?\s+)?TRIGGER\b/i.test(current)) {
|
|
358
|
+
triggerDepth = 1;
|
|
359
|
+
}
|
|
360
|
+
current += sql.slice(i, j);
|
|
361
|
+
i = j;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
current += c;
|
|
365
|
+
i++;
|
|
366
|
+
}
|
|
367
|
+
if (current.trim())
|
|
368
|
+
statements.push(current.trim());
|
|
369
|
+
return statements;
|
|
370
|
+
}
|
|
52
371
|
/**
|
|
53
372
|
* StorageRecord Implementation
|
|
54
373
|
*
|
|
@@ -58,22 +377,188 @@ export function applyUpdateTimestamp(record, update, timestamps, now) {
|
|
|
58
377
|
* Uses runtime query API to resolve the appropriate adapter based on
|
|
59
378
|
* the descriptor's allowedAdapters and defaultAdapter settings.
|
|
60
379
|
*/
|
|
380
|
+
/**
|
|
381
|
+
* Thrown by `update()` when an `UpdateOptions.where` precondition does not
|
|
382
|
+
* match the record's current state at write time — an optimistic-lock
|
|
383
|
+
* conflict. The precondition is enforced ATOMICALLY inside the UPDATE
|
|
384
|
+
* statement's WHERE (single SQL statement, evaluated against the live row), so
|
|
385
|
+
* this is a genuine compare-and-set with no read-check-write TOCTOU window:
|
|
386
|
+
* of two concurrent writers passing `{ where: { updatedAt: <read> } }`, the
|
|
387
|
+
* second sees 0 rows matched and gets this error instead of clobbering the
|
|
388
|
+
* first. Callers catch it to detect a concurrent write and retry.
|
|
389
|
+
*
|
|
390
|
+
* It is a `CapabilityError` (`CONFLICT`, reason `record-precondition-conflict`)
|
|
391
|
+
* named `OptimisticLockError`. Its message and details are value-free (the
|
|
392
|
+
* table only); the record id and the precondition travel on its own fields.
|
|
393
|
+
*/
|
|
394
|
+
export class OptimisticLockError extends CapabilityError {
|
|
395
|
+
recordId;
|
|
396
|
+
tableName;
|
|
397
|
+
where;
|
|
398
|
+
constructor(tableName, recordId, where, cause) {
|
|
399
|
+
super(`Optimistic-lock conflict updating a ${tableName} record: it no longer matches its update precondition (concurrent write).`, 'CONFLICT', { reason: 'record-precondition-conflict', retryable: true, details: { tableName }, cause });
|
|
400
|
+
this.name = 'OptimisticLockError';
|
|
401
|
+
this.tableName = tableName;
|
|
402
|
+
this.recordId = recordId;
|
|
403
|
+
this.where = where;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
61
406
|
export class StorageRecord {
|
|
62
407
|
tableName;
|
|
63
408
|
primaryKey;
|
|
64
|
-
schema;
|
|
65
409
|
relations;
|
|
410
|
+
supportsConditionalWrites = true;
|
|
411
|
+
get revisions() {
|
|
412
|
+
if (!this.descriptor.versioning)
|
|
413
|
+
return undefined;
|
|
414
|
+
return {
|
|
415
|
+
metadata: id => this.revisionMetadata(id),
|
|
416
|
+
list: options => this.revisionList(options),
|
|
417
|
+
readAtRevision: (id, revision) => this.readAtRevision(id, revision)
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
revisionLivePredicate() {
|
|
421
|
+
const deletedAt = this.softDeleteField();
|
|
422
|
+
if (!deletedAt)
|
|
423
|
+
return '';
|
|
424
|
+
const path = `$.${JSON.stringify(deletedAt)}`.replaceAll("'", "''");
|
|
425
|
+
return ` AND json_extract(data, '${path}') IS NULL`;
|
|
426
|
+
}
|
|
427
|
+
async revisionMetadata(id) {
|
|
428
|
+
await this.ensureInitialized();
|
|
429
|
+
const row = this.db.prepare(`SELECT CAST(${this.primaryKey} AS TEXT) AS id, ${RECORD_REVISION_COLUMN} AS revision FROM ${this.tableName} WHERE ${this.primaryKey} = ?${this.revisionLivePredicate()}`).get(id);
|
|
430
|
+
return row ? RecordRevisionMetadataSchema.parse(row) : null;
|
|
431
|
+
}
|
|
432
|
+
async readAtRevision(id, revision) {
|
|
433
|
+
await this.ensureInitialized();
|
|
434
|
+
const row = this.db.prepare(`SELECT data, ${RECORD_REVISION_COLUMN} AS revision FROM ${this.tableName} WHERE ${this.primaryKey} = ? AND ${RECORD_REVISION_COLUMN} = ?${this.revisionLivePredicate()}`).get(id, revision);
|
|
435
|
+
if (row)
|
|
436
|
+
return { status: 'read', revision: row.revision, value: this.decodeRecord(row.data) };
|
|
437
|
+
const current = await this.revisionMetadata(id);
|
|
438
|
+
return current ? { status: 'changed', revision: current.revision } : { status: 'not-found' };
|
|
439
|
+
}
|
|
440
|
+
async revisionList(options) {
|
|
441
|
+
await this.ensureInitialized();
|
|
442
|
+
const query = RecordRevisionQuerySchema.parse(options ?? {});
|
|
443
|
+
const filter = this.buildWhereClause(query.where ?? {});
|
|
444
|
+
const predicate = `${filter.clause || '1 = 1'}${this.revisionLivePredicate()}`;
|
|
445
|
+
const params = [...filter.values];
|
|
446
|
+
const projection = `CAST(${this.primaryKey} AS TEXT) AS id, ${RECORD_REVISION_COLUMN} AS revision`;
|
|
447
|
+
let sql = `SELECT ${projection} FROM ${this.tableName} WHERE ${predicate}`;
|
|
448
|
+
const order = Array.isArray(query.orderBy) ? query.orderBy : Object.entries(query.orderBy ?? {}).map(([field, direction]) => ({ field, direction }));
|
|
449
|
+
const ordering = order.map(item => `${this.filterColumn(item.field)} ${item.direction}`);
|
|
450
|
+
if (!order.some(item => item.field === this.primaryKey))
|
|
451
|
+
ordering.push(this.primaryKey);
|
|
452
|
+
sql += ` ORDER BY ${ordering.join(', ')}`;
|
|
453
|
+
if (query.limit !== undefined) {
|
|
454
|
+
sql += ' LIMIT ?';
|
|
455
|
+
params.push(query.limit + 1);
|
|
456
|
+
}
|
|
457
|
+
else if (query.offset !== undefined)
|
|
458
|
+
sql += ' LIMIT -1';
|
|
459
|
+
if (query.offset !== undefined) {
|
|
460
|
+
sql += ' OFFSET ?';
|
|
461
|
+
params.push(query.offset);
|
|
462
|
+
}
|
|
463
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
464
|
+
const hasMore = query.limit !== undefined && rows.length > query.limit;
|
|
465
|
+
const items = (hasMore ? rows.slice(0, query.limit) : rows).map(row => RecordRevisionMetadataSchema.parse(row));
|
|
466
|
+
const page = { items, hasMore };
|
|
467
|
+
if (query.limit === undefined || query.includeTotal) {
|
|
468
|
+
page.total = this.db.prepare(`SELECT COUNT(*) AS count FROM ${this.tableName} WHERE ${predicate}`).get(...filter.values)?.count ?? 0;
|
|
469
|
+
}
|
|
470
|
+
return page;
|
|
471
|
+
}
|
|
66
472
|
descriptor;
|
|
67
473
|
context;
|
|
68
474
|
adapterCache = null;
|
|
475
|
+
adapterCacheId = null;
|
|
476
|
+
adapterBasePathSemantics = 'unused';
|
|
69
477
|
db = null;
|
|
70
478
|
initialized = false;
|
|
71
|
-
|
|
72
|
-
|
|
479
|
+
initialization = null;
|
|
480
|
+
nativeRecords = new WeakMap();
|
|
481
|
+
/** This record's row type: a stored field map, the same object (rows are written after this record's schema accepted them). */
|
|
482
|
+
recordValueSchema = z.custom((value) => isFieldMap(value));
|
|
483
|
+
/** The runtime this record was handed; there is no ambient default. */
|
|
484
|
+
getRuntime() {
|
|
485
|
+
const runtime = this.context.runtime;
|
|
486
|
+
if (!runtime) {
|
|
487
|
+
throw new CapabilityError(`Storage record '${this.descriptor.id}' was built without a runtime.`, 'INVALID_INPUT', { reason: 'storage-runtime-missing', retryable: false });
|
|
488
|
+
}
|
|
489
|
+
return runtime;
|
|
490
|
+
}
|
|
491
|
+
/** This record's Logging: the handed runtime's, never an ambient default. */
|
|
492
|
+
get logger() {
|
|
493
|
+
return getLogger('storage:record', this.getRuntime());
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* This record's zod schema, as its descriptor declares it. The record's
|
|
497
|
+
* row type `T` is the one its owner declares: the schema it carries produces it.
|
|
498
|
+
*/
|
|
499
|
+
get schema() {
|
|
500
|
+
const schema = this.descriptor.schema;
|
|
501
|
+
if (!isRecordSchema(schema)) {
|
|
502
|
+
throw new CapabilityError(`Storage record '${this.descriptor.id}' declares no zod schema.`, 'INVALID_INPUT', {
|
|
503
|
+
reason: 'storage-record-schema-missing',
|
|
504
|
+
retryable: false,
|
|
505
|
+
details: { recordKind: this.descriptor.id }
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
return schema;
|
|
509
|
+
}
|
|
510
|
+
/** A record value's field map: the same object, refused typed when it is not one. */
|
|
511
|
+
fieldsOf(value) {
|
|
512
|
+
const fields = parseAdapterInput(RecordFieldsViewSchema, value, {
|
|
513
|
+
message: 'A storage record value must be a field map.',
|
|
514
|
+
code: 'INVALID_INPUT',
|
|
515
|
+
reason: 'record-value-invalid',
|
|
516
|
+
details: { recordKind: this.descriptor.id }
|
|
517
|
+
});
|
|
518
|
+
if (this.descriptor.versioning && Object.hasOwn(fields, RECORD_REVISION_COLUMN))
|
|
519
|
+
throw new CapabilityError('Storage revision metadata cannot be written as record data.', 'INVALID_INPUT', {
|
|
520
|
+
reason: 'record-revision-field-reserved', retryable: false
|
|
521
|
+
});
|
|
522
|
+
return fields;
|
|
523
|
+
}
|
|
524
|
+
/** A field map as this record's row type: the same object. */
|
|
525
|
+
recordOf(fields) {
|
|
526
|
+
this.fieldsOf(fields);
|
|
527
|
+
return parseAdapterInput(this.recordValueSchema, fields, {
|
|
528
|
+
message: 'A storage record value must be a field map.',
|
|
529
|
+
code: 'INVALID_INPUT',
|
|
530
|
+
reason: 'record-value-invalid',
|
|
531
|
+
details: { recordKind: this.descriptor.id }
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
/** One stored row's JSON `data` column as its field map; a row that is not a JSON object is refused typed. */
|
|
535
|
+
decodeFields(data) {
|
|
536
|
+
return parseAdapterInput(RecordFieldsViewSchema, JSON.parse(data), {
|
|
537
|
+
message: `A stored ${this.tableName} row is not a JSON object.`,
|
|
538
|
+
code: 'INTERNAL',
|
|
539
|
+
reason: 'record-row-invalid',
|
|
540
|
+
details: { recordKind: this.descriptor.id }
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
/** One stored row's JSON `data` column as this record's row type. */
|
|
544
|
+
decodeRecord(data) {
|
|
545
|
+
return this.recordOf(this.decodeFields(data));
|
|
546
|
+
}
|
|
547
|
+
/** A hierarchy query's id path, each id as text. */
|
|
548
|
+
decodePath(path) {
|
|
549
|
+
return parseAdapterInput(HierarchyPathSchema, JSON.parse(path), {
|
|
550
|
+
message: `A ${this.tableName} hierarchy path is not a list of ids.`,
|
|
551
|
+
code: 'INTERNAL',
|
|
552
|
+
reason: 'record-hierarchy-path-invalid',
|
|
553
|
+
details: { recordKind: this.descriptor.id }
|
|
554
|
+
});
|
|
73
555
|
}
|
|
74
556
|
readStringField(record, field) {
|
|
75
|
-
const
|
|
76
|
-
|
|
557
|
+
const fields = RecordFieldsViewSchema.safeParse(record);
|
|
558
|
+
if (!fields.success)
|
|
559
|
+
return null;
|
|
560
|
+
const value = z.string().safeParse(fields.data[field]);
|
|
561
|
+
return value.success ? value.data : null;
|
|
77
562
|
}
|
|
78
563
|
readPrimaryKey(record) {
|
|
79
564
|
const id = this.readStringField(record, this.primaryKey);
|
|
@@ -82,33 +567,196 @@ export class StorageRecord {
|
|
|
82
567
|
}
|
|
83
568
|
return id;
|
|
84
569
|
}
|
|
570
|
+
/** Run one descriptor lifecycle hook, when the descriptor declares it, with the row's fields; it must be a function. */
|
|
571
|
+
async runHook(name, fields) {
|
|
572
|
+
const declared = this.descriptor[name];
|
|
573
|
+
if (!declared)
|
|
574
|
+
return;
|
|
575
|
+
const hook = parseAdapterInput(RecordLifecycleHookSchema, declared, {
|
|
576
|
+
message: `Storage record '${this.descriptor.id}' declares a ${name} hook that is not a function.`,
|
|
577
|
+
code: 'INVALID_INPUT',
|
|
578
|
+
reason: 'record-hook-invalid',
|
|
579
|
+
details: { recordKind: this.descriptor.id, hook: name }
|
|
580
|
+
});
|
|
581
|
+
await hook(fields);
|
|
582
|
+
}
|
|
85
583
|
/**
|
|
86
|
-
* Emit a lifecycle event via the runtime HookBus.
|
|
87
|
-
*
|
|
584
|
+
* Emit a lifecycle event via the handed runtime's HookBus. A failing
|
|
585
|
+
* subscriber never blocks the CRUD that already happened; it is reported.
|
|
88
586
|
*/
|
|
89
|
-
emitLifecycle(phase,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
587
|
+
emitLifecycle(phase, fields) {
|
|
588
|
+
const runtime = this.getRuntime();
|
|
589
|
+
const payload = {
|
|
590
|
+
recordId: fields[this.primaryKey],
|
|
591
|
+
recordKind: this.descriptor.id,
|
|
592
|
+
tableName: this.tableName,
|
|
593
|
+
data: fields
|
|
594
|
+
};
|
|
595
|
+
void outcomeOf(() => runtime.hooks('storage/record').emit(phase, payload)).then(outcome => {
|
|
596
|
+
if (!outcome.ok)
|
|
597
|
+
this.logger.warn('storage/record lifecycle hook failed', {
|
|
598
|
+
phase, recordKind: this.descriptor.id, error: outcome.failure
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
atomic = { commit: mutations => this.commitAtomic(mutations) };
|
|
603
|
+
/** Prepare complete row mutations, then commit without yielding to another record or reader. */
|
|
604
|
+
async commitAtomic(input) {
|
|
605
|
+
const runtime = this.getRuntime();
|
|
606
|
+
const lifecycle = runtime.lifecycle;
|
|
607
|
+
const assertLive = () => {
|
|
608
|
+
if (lifecycle.phase === 'live' && !lifecycle.signal.aborted && runtime.lifecycle === lifecycle)
|
|
609
|
+
return;
|
|
610
|
+
throw new CapabilityError('A prepared commit cannot outlive its admitted runtime.', 'CANCELLED', {
|
|
611
|
+
reason: 'runtime-disposed', retryable: false, cause: lifecycle.signal.reason
|
|
98
612
|
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
613
|
+
};
|
|
614
|
+
assertLive();
|
|
615
|
+
if (this.constructor !== StorageRecord)
|
|
616
|
+
throw new CapabilityError('A custom record cannot initiate a native prepared commit.', 'UNAVAILABLE', {
|
|
617
|
+
reason: 'record-atomic-target-unsupported', retryable: false
|
|
618
|
+
});
|
|
619
|
+
if (!this.initialized || !this.db)
|
|
620
|
+
throw new CapabilityError('Prepared commits require explicitly initialized native records.', 'UNAVAILABLE', {
|
|
621
|
+
reason: 'record-atomic-not-initialized', retryable: false
|
|
622
|
+
});
|
|
623
|
+
const db = this.db;
|
|
624
|
+
const transactionDb = parseAdapterInput(SyncTransactionDatabaseSchema, db, {
|
|
625
|
+
message: 'The selected connection has no synchronous transaction capability.', code: 'UNAVAILABLE',
|
|
626
|
+
reason: 'record-atomic-transaction-unsupported'
|
|
627
|
+
});
|
|
628
|
+
const assertNoTransaction = () => {
|
|
629
|
+
if (transactionDb.inTransaction || isConnectionTransactionActive(db)) {
|
|
630
|
+
throw new CapabilityError('A prepared commit cannot join an existing transaction.', 'CONFLICT', {
|
|
631
|
+
reason: 'record-atomic-transaction-active', retryable: true
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
assertNoTransaction();
|
|
636
|
+
const mutations = parseAdapterInput(RecordAtomicMutationsSchema, input, {
|
|
637
|
+
message: 'Prepared record mutations are invalid.', code: 'INVALID_INPUT',
|
|
638
|
+
reason: 'record-atomic-input-invalid'
|
|
639
|
+
});
|
|
640
|
+
// Snapshot only row data, never selected record handles. The bytes and their
|
|
641
|
+
// validation/event drafts belong to this admission before preparation yields.
|
|
642
|
+
const snapshots = mutations.map(mutation => mutation.type === 'delete'
|
|
643
|
+
? undefined : JSON.stringify(mutation.data));
|
|
644
|
+
const targets = mutations.map(mutation => {
|
|
645
|
+
const record = mutation.record;
|
|
646
|
+
if (!(record instanceof StorageRecord) || record.constructor !== StorageRecord || record.getRuntime() !== runtime) {
|
|
647
|
+
throw new CapabilityError('Prepared commits require native records from the same runtime.', 'UNAVAILABLE', {
|
|
648
|
+
reason: 'record-atomic-target-unsupported', retryable: false
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
if (!record.initialized || !record.db)
|
|
652
|
+
throw new CapabilityError('Prepared commits require explicitly initialized native records.', 'UNAVAILABLE', {
|
|
653
|
+
reason: 'record-atomic-not-initialized', retryable: false,
|
|
654
|
+
details: { recordKind: record.descriptor.id }
|
|
655
|
+
});
|
|
656
|
+
if (record.db !== db)
|
|
657
|
+
throw new CapabilityError('Prepared records must share one physical connection.', 'UNAVAILABLE', {
|
|
658
|
+
reason: 'record-atomic-connection-mismatch', retryable: false
|
|
659
|
+
});
|
|
660
|
+
const descriptor = record.descriptor;
|
|
661
|
+
const hooks = ['beforeCreate', 'afterCreate', 'beforeUpdate', 'afterUpdate', 'beforeDelete', 'afterDelete'];
|
|
662
|
+
if (descriptor.autoId || descriptor.timestamps || descriptor.versioning || descriptor.softDelete ||
|
|
663
|
+
descriptor.audit || descriptor.changeTracking || descriptor.delivery || descriptor.sideEffects || descriptor.validate !== undefined ||
|
|
664
|
+
hooks.some(hook => descriptor[hook] !== undefined)) {
|
|
665
|
+
throw new CapabilityError('This record requires behavior outside the prepared commit contract.', 'UNAVAILABLE', {
|
|
666
|
+
reason: 'record-atomic-descriptor-unsupported', retryable: false,
|
|
667
|
+
details: { recordKind: descriptor.id }
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
const condition = mutation.type === 'create' || !mutation.where
|
|
671
|
+
? { clause: '', values: [] } : record.buildWhereClause(mutation.where);
|
|
672
|
+
return { record, descriptor, condition };
|
|
673
|
+
});
|
|
674
|
+
const prepared = [];
|
|
675
|
+
for (const [index, mutation] of mutations.entries()) {
|
|
676
|
+
const { record, descriptor, condition } = targets[index];
|
|
677
|
+
const serialized = snapshots[index];
|
|
678
|
+
const fields = serialized === undefined ? undefined : record.decodeFields(serialized);
|
|
679
|
+
if (fields && String(fields[record.primaryKey]) !== mutation.id) {
|
|
680
|
+
throw new CapabilityError('A prepared row must carry its exact primary key.', 'INVALID_INPUT', {
|
|
681
|
+
reason: 'record-primary-key-immutable', retryable: false
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
if (fields && descriptor.validateOnWrite !== false) {
|
|
685
|
+
const validation = await record.validate(fields);
|
|
686
|
+
if (!validation.success)
|
|
687
|
+
throw new CapabilityError('Prepared record validation failed.', 'INVALID_INPUT', {
|
|
688
|
+
reason: 'record-atomic-validation-failed', retryable: false, details: { recordKind: descriptor.id }
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
const predicate = `${record.primaryKey} = ?${condition.clause ? ` AND (${condition.clause})` : ''}`;
|
|
692
|
+
const parameters = [mutation.id, ...condition.values];
|
|
693
|
+
const sql = mutation.type === 'create'
|
|
694
|
+
? `INSERT INTO ${record.tableName} (${record.primaryKey}, data) VALUES (?, ?)`
|
|
695
|
+
: mutation.type === 'replace'
|
|
696
|
+
? `UPDATE ${record.tableName} SET data = ? WHERE ${predicate}`
|
|
697
|
+
: `DELETE FROM ${record.tableName} WHERE ${predicate} RETURNING data`;
|
|
698
|
+
const statement = db.prepare(sql);
|
|
699
|
+
prepared.push({ record, type: mutation.type, run: () => {
|
|
700
|
+
if (mutation.type === 'delete') {
|
|
701
|
+
const deleted = statement.get(...parameters);
|
|
702
|
+
if (deleted)
|
|
703
|
+
return record.decodeFields(deleted.data);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
const result = mutation.type === 'create'
|
|
707
|
+
? statement.run(mutation.id, serialized) : statement.run(serialized, ...parameters);
|
|
708
|
+
if (result.changes === 1 && serialized !== undefined)
|
|
709
|
+
return record.decodeFields(serialized);
|
|
710
|
+
}
|
|
711
|
+
throw new CapabilityError('A prepared record precondition no longer holds.', 'CONFLICT', {
|
|
712
|
+
reason: 'record-atomic-precondition-failed', retryable: false, details: { index }
|
|
713
|
+
});
|
|
714
|
+
} });
|
|
715
|
+
}
|
|
716
|
+
assertLive();
|
|
717
|
+
assertNoTransaction();
|
|
718
|
+
const committed = transactionDb.transaction(() => prepared.map(operation => ({ ...operation, fields: operation.run() })));
|
|
719
|
+
for (const operation of committed) {
|
|
720
|
+
const phase = operation.type === 'create' ? 'afterCreate' : operation.type === 'replace' ? 'afterUpdate' : 'afterDelete';
|
|
721
|
+
operation.record.emitLifecycle(phase, operation.fields);
|
|
102
722
|
}
|
|
103
723
|
}
|
|
104
724
|
constructor(descriptor, context) {
|
|
725
|
+
assertRecordRevisionSchema(descriptor);
|
|
105
726
|
this.descriptor = descriptor;
|
|
106
727
|
this.context = context;
|
|
107
728
|
this.tableName = descriptor.tableName;
|
|
108
729
|
this.primaryKey = descriptor.primaryKey || 'id';
|
|
109
|
-
this.schema = descriptor.schema;
|
|
110
730
|
this.relations = descriptor.relations || {};
|
|
111
731
|
}
|
|
732
|
+
/** Default records use the selected adapter's native record implementation. */
|
|
733
|
+
static async resolveValue(implementation) {
|
|
734
|
+
if (!(implementation instanceof StorageRecord) || implementation.constructor !== StorageRecord) {
|
|
735
|
+
return implementation;
|
|
736
|
+
}
|
|
737
|
+
return implementation.resolveNativeRecord();
|
|
738
|
+
}
|
|
739
|
+
async resolveNativeRecord() {
|
|
740
|
+
const adapter = await this.getAdapter();
|
|
741
|
+
const config = await this.getAdapterConfig(this.adapterCacheId ?? adapter.id, this.adapterBasePathSemantics);
|
|
742
|
+
// Non-filesystem adapters require no workspace; their selected owner remains
|
|
743
|
+
// the identity. Filesystem records retain the Kernel-resolved target at selection.
|
|
744
|
+
const key = this.adapterBasePathSemantics === 'filesystem'
|
|
745
|
+
? JSON.stringify([config.basePath, config.dbName]) : '';
|
|
746
|
+
let records = this.nativeRecords.get(adapter);
|
|
747
|
+
if (!records) {
|
|
748
|
+
records = new Map();
|
|
749
|
+
this.nativeRecords.set(adapter, records);
|
|
750
|
+
}
|
|
751
|
+
const existing = records.get(key);
|
|
752
|
+
if (existing)
|
|
753
|
+
return existing;
|
|
754
|
+
const context = this.adapterBasePathSemantics === 'filesystem'
|
|
755
|
+
? { ...this.context, 'runtime/workspace-path': config.workspacePath } : this.context;
|
|
756
|
+
const record = adapter.record(this.descriptor, context);
|
|
757
|
+
records.set(key, record);
|
|
758
|
+
return record;
|
|
759
|
+
}
|
|
112
760
|
/**
|
|
113
761
|
* Resolve the adapter for this record using runtime query API.
|
|
114
762
|
*
|
|
@@ -117,62 +765,73 @@ export class StorageRecord {
|
|
|
117
765
|
*/
|
|
118
766
|
async getAdapter() {
|
|
119
767
|
if (this.adapterCache) {
|
|
768
|
+
const adapterId = this.adapterCacheId ?? this.adapterCache.id;
|
|
769
|
+
const config = await this.getAdapterConfig(adapterId, this.adapterBasePathSemantics);
|
|
770
|
+
this.adapterCache.configure(config);
|
|
120
771
|
return this.adapterCache;
|
|
121
772
|
}
|
|
122
773
|
const adapterTracer = createTracer(`adapter:${this.descriptor.id}`);
|
|
123
774
|
adapterTracer.start();
|
|
124
|
-
const runtime =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
775
|
+
const runtime = this.getRuntime();
|
|
776
|
+
// A default adapter that is offered but fails is an error — never a
|
|
777
|
+
// silent reroute to another allowed adapter (RC06 U6).
|
|
778
|
+
const request = {
|
|
779
|
+
ownerKind: 'record',
|
|
780
|
+
ownerId: this.descriptor.id,
|
|
781
|
+
allowedAdapters: this.descriptor.allowedAdapters
|
|
782
|
+
};
|
|
783
|
+
if (this.descriptor.defaultAdapter) {
|
|
784
|
+
request.preferred = { adapterId: this.descriptor.defaultAdapter, source: 'descriptor' };
|
|
785
|
+
}
|
|
786
|
+
const { adapter, adapterId, descriptor: adapterDescriptor } = await selectStorageAdapter(runtime, request);
|
|
787
|
+
// Storage adapters are runtime singletons, while records can target different
|
|
788
|
+
// files/scopes through descriptor adapterConfig. Re-apply this record's config
|
|
789
|
+
// before opening the connection so one record cannot inherit another's DB.
|
|
790
|
+
const basePathSemantics = adapterDescriptor.basePathSemantics ?? 'unused';
|
|
791
|
+
const config = await this.getAdapterConfig(adapterId, basePathSemantics);
|
|
792
|
+
adapter.configure(config);
|
|
793
|
+
// Register per-record cloud endpoint overrides so cloud-api uses
|
|
794
|
+
// descriptor endpoints instead of the namespace fallback.
|
|
795
|
+
if (adapterId === 'storage.cloud-api') {
|
|
796
|
+
const cloudEndpoint = extractCloudEndpoint(this.descriptor.adapterConfig);
|
|
797
|
+
if (cloudEndpoint) {
|
|
798
|
+
registerCloudEndpointIfSupported(adapter, this.descriptor.namespace ?? this.descriptor.id, cloudEndpoint);
|
|
149
799
|
}
|
|
150
800
|
}
|
|
151
|
-
if (!adapter || !adapterId) {
|
|
152
|
-
throw new Error(`No available adapter for record '${this.descriptor.id}'. Checked: ${this.descriptor.allowedAdapters.join(', ')}`);
|
|
153
|
-
}
|
|
154
|
-
if (!adapter.isConfigured()) {
|
|
155
|
-
const config = this.getAdapterConfig(adapterId);
|
|
156
|
-
adapter.configure(config);
|
|
157
|
-
}
|
|
158
801
|
this.adapterCache = adapter;
|
|
802
|
+
this.adapterCacheId = adapterId;
|
|
803
|
+
this.adapterBasePathSemantics = basePathSemantics;
|
|
159
804
|
adapterTracer.end('resolved');
|
|
160
805
|
return adapter;
|
|
161
806
|
}
|
|
162
807
|
/**
|
|
163
808
|
* Get adapter configuration based on record scope.
|
|
164
809
|
*/
|
|
165
|
-
getAdapterConfig(adapterId) {
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
810
|
+
async getAdapterConfig(adapterId, basePathSemantics = this.adapterBasePathSemantics) {
|
|
811
|
+
const runtime = this.getRuntime();
|
|
812
|
+
// Every adapter call runs on this record's runtime: an adapter that
|
|
813
|
+
// resolves origins or credentials (cloud-api) uses it, never an ambient one.
|
|
814
|
+
const config = { runtime };
|
|
815
|
+
if (basePathSemantics === 'filesystem') {
|
|
816
|
+
// The storage workspace policy the manifest uses (SPEC): the record's own
|
|
817
|
+
// kind context (`runtime/workspace-path`) first, else the host's bound
|
|
818
|
+
// workspace. Only a record whose context names none asks the runtime,
|
|
819
|
+
// whose reader refuses `workspace-unbound` for an unbound host — before
|
|
820
|
+
// any path is resolved or file opened, and with no home-directory fallback.
|
|
821
|
+
const hostWorkspace = ContextWorkspacePathSchema.safeParse(this.context['runtime/workspace-path']).success
|
|
822
|
+
? undefined
|
|
823
|
+
: await runtime.context('runtime/workspace-path');
|
|
824
|
+
const roots = getPathRoots(runtime);
|
|
825
|
+
const workspacePath = resolveWorkspacePathWithPolicy(this.context, undefined, roots, undefined, undefined, hostWorkspace);
|
|
826
|
+
config.basePath = this.getBasePath(workspacePath, roots);
|
|
827
|
+
config.workspacePath = workspacePath;
|
|
828
|
+
config.roots = roots;
|
|
829
|
+
}
|
|
171
830
|
if (this.descriptor.adapterConfig) {
|
|
172
831
|
const selectedAdapterId = adapterId || this.descriptor.defaultAdapter || this.descriptor.allowedAdapters[0];
|
|
173
|
-
const adapterSpecificConfig = this.descriptor.adapterConfig[selectedAdapterId];
|
|
174
|
-
if (adapterSpecificConfig
|
|
175
|
-
Object.assign(config, adapterSpecificConfig);
|
|
832
|
+
const adapterSpecificConfig = StorageAdapterConfigSchema.safeParse(this.descriptor.adapterConfig[selectedAdapterId]);
|
|
833
|
+
if (adapterSpecificConfig.success) {
|
|
834
|
+
Object.assign(config, adapterSpecificConfig.data);
|
|
176
835
|
}
|
|
177
836
|
}
|
|
178
837
|
return config;
|
|
@@ -181,24 +840,21 @@ export class StorageRecord {
|
|
|
181
840
|
* Get base path for storage based on scope.
|
|
182
841
|
*
|
|
183
842
|
* Scope mapping:
|
|
184
|
-
* - global:
|
|
843
|
+
* - global: canonical Vibes home selected by runtime/environment
|
|
185
844
|
* - project: ./.vibes/ (project-level, within .vibes dir)
|
|
186
845
|
* - local: ./.vibes/ (alias for project)
|
|
187
846
|
* - auto: ./.vibes/ (defaults to project scope)
|
|
188
847
|
*/
|
|
189
|
-
getBasePath(workspacePath
|
|
848
|
+
getBasePath(workspacePath, roots) {
|
|
190
849
|
const scope = this.descriptor.scope || 'project';
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
if (customPath) {
|
|
196
|
-
return resolveScopePathTemplate(customPath, workspacePath);
|
|
197
|
-
}
|
|
850
|
+
const declared = ScopePathTemplatesSchema.safeParse(this.descriptor);
|
|
851
|
+
const customPath = declared.success ? declared.data.scopePaths?.[scope] : undefined;
|
|
852
|
+
if (customPath) {
|
|
853
|
+
return resolveScopePathTemplate(customPath, workspacePath, roots);
|
|
198
854
|
}
|
|
199
855
|
switch (scope) {
|
|
200
856
|
case 'global':
|
|
201
|
-
return
|
|
857
|
+
return resolveVibesBaseDir(roots);
|
|
202
858
|
case 'local':
|
|
203
859
|
case 'project':
|
|
204
860
|
case 'auto':
|
|
@@ -206,6 +862,15 @@ export class StorageRecord {
|
|
|
206
862
|
return `${workspacePath}/.vibes`;
|
|
207
863
|
}
|
|
208
864
|
}
|
|
865
|
+
/** The field soft deletion stamps, or null when the record does not soft-delete. */
|
|
866
|
+
softDeleteField() {
|
|
867
|
+
return softDeleteColumn(this.descriptor.softDelete);
|
|
868
|
+
}
|
|
869
|
+
/** The descriptor's `autoId` options, when it declares them as an object. */
|
|
870
|
+
autoIdOptions() {
|
|
871
|
+
const autoId = this.descriptor.autoId;
|
|
872
|
+
return autoId === true || autoId === false ? undefined : autoId;
|
|
873
|
+
}
|
|
209
874
|
/**
|
|
210
875
|
* Initialize the storage record
|
|
211
876
|
* - Creates table if it doesn't exist
|
|
@@ -213,30 +878,80 @@ export class StorageRecord {
|
|
|
213
878
|
* - Creates indexes
|
|
214
879
|
*/
|
|
215
880
|
async initialize() {
|
|
881
|
+
if (this.initialized)
|
|
882
|
+
return;
|
|
883
|
+
const runtime = this.getRuntime();
|
|
884
|
+
const db = this.db ?? (await this.getDatabase());
|
|
885
|
+
this.db = db;
|
|
886
|
+
await waitForConnectionLease(db, runtime);
|
|
887
|
+
if (this.initialized)
|
|
888
|
+
return;
|
|
889
|
+
if (this.initialization)
|
|
890
|
+
return this.initialization;
|
|
891
|
+
const initialization = this.initializeOnce();
|
|
892
|
+
this.initialization = initialization;
|
|
893
|
+
try {
|
|
894
|
+
await initialization;
|
|
895
|
+
}
|
|
896
|
+
finally {
|
|
897
|
+
if (this.initialization === initialization) {
|
|
898
|
+
this.initialization = null;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
async initializeOnce() {
|
|
216
903
|
if (this.initialized)
|
|
217
904
|
return;
|
|
218
905
|
// Get database connection from adapter
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
906
|
+
const runtime = this.getRuntime();
|
|
907
|
+
const db = this.db ?? (await this.getDatabase());
|
|
908
|
+
this.db = db;
|
|
909
|
+
const initializeSchema = async () => {
|
|
910
|
+
// Create table if using inline migrations
|
|
911
|
+
if (this.descriptor.migrations && this.descriptor.migrations.length > 0) {
|
|
912
|
+
await this.runMigrations();
|
|
913
|
+
// Inline migrations may create normalized column schemas.
|
|
914
|
+
// StorageRecord CRUD operations expect a `data` TEXT column for JSON storage.
|
|
915
|
+
// If the migrated table lacks a `data` column and is empty, recreate it
|
|
916
|
+
// with the minimal (primaryKey, data) schema that StorageRecord requires.
|
|
917
|
+
await this.ensureDataColumn();
|
|
918
|
+
}
|
|
919
|
+
else if (this.descriptor.migrationKey) {
|
|
920
|
+
// Migrations should be pre-registered by the module
|
|
921
|
+
// The adapter will handle them
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
// Auto-create table based on schema
|
|
925
|
+
await this.createTableFromSchema();
|
|
926
|
+
}
|
|
927
|
+
if (this.descriptor.versioning) {
|
|
928
|
+
const columns = this.db.prepare(`PRAGMA table_info(${this.tableName})`).all();
|
|
929
|
+
if (!columns.some(column => column.name === RECORD_REVISION_COLUMN)) {
|
|
930
|
+
this.db.exec(`ALTER TABLE ${this.tableName} ADD COLUMN ${RECORD_REVISION_COLUMN} TEXT`);
|
|
931
|
+
}
|
|
932
|
+
this.db.exec(`UPDATE ${this.tableName} SET ${RECORD_REVISION_COLUMN} = lower(hex(randomblob(16))) WHERE ${RECORD_REVISION_COLUMN} IS NULL`);
|
|
933
|
+
}
|
|
934
|
+
// Create indexes
|
|
935
|
+
if (this.descriptor.indexes && this.descriptor.indexes.length > 0) {
|
|
936
|
+
await this.createIndexes();
|
|
937
|
+
}
|
|
938
|
+
await this.createFtsArtifacts();
|
|
939
|
+
};
|
|
940
|
+
const activeFrame = getActiveTransactionFrame(db, runtime);
|
|
941
|
+
if (activeFrame) {
|
|
942
|
+
await runInSavepoint(runtime, activeFrame, db, async (savepointFrame) => {
|
|
943
|
+
await initializeSchema();
|
|
944
|
+
savepointFrame.onRollback.push(() => {
|
|
945
|
+
this.initialized = false;
|
|
946
|
+
});
|
|
947
|
+
});
|
|
232
948
|
}
|
|
233
|
-
else {
|
|
234
|
-
|
|
235
|
-
await
|
|
949
|
+
else if (this.adapterCacheId === 'storage.localdb') {
|
|
950
|
+
const coordination = await resolveSchemaCoordination(runtime);
|
|
951
|
+
await coordination.initializeSchema(db, () => withImmediateSchemaTransaction(runtime, db, initializeSchema));
|
|
236
952
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
await this.createIndexes();
|
|
953
|
+
else {
|
|
954
|
+
await initializeSchema();
|
|
240
955
|
}
|
|
241
956
|
this.initialized = true;
|
|
242
957
|
}
|
|
@@ -251,15 +966,15 @@ export class StorageRecord {
|
|
|
251
966
|
async ensureDataColumn() {
|
|
252
967
|
if (!this.db)
|
|
253
968
|
throw new Error('Database not initialized');
|
|
254
|
-
const cols = this.db.
|
|
969
|
+
const cols = this.db.prepare(`PRAGMA table_info(${this.tableName})`).all();
|
|
255
970
|
if (cols.some((c) => c.name === 'data'))
|
|
256
971
|
return;
|
|
257
972
|
// Table lacks a data column. Only recreate if it is empty (no data to lose).
|
|
258
973
|
const row = this.db
|
|
259
|
-
.
|
|
974
|
+
.prepare(`SELECT COUNT(*) as n FROM ${this.tableName}`)
|
|
260
975
|
.get();
|
|
261
976
|
if (!row || row.n > 0) {
|
|
262
|
-
logger.warn(`[StorageRecord] Table '${this.tableName}' has no 'data' column and contains rows. ` +
|
|
977
|
+
this.logger.warn(`[StorageRecord] Table '${this.tableName}' has no 'data' column and contains rows. ` +
|
|
263
978
|
`CRUD operations may fail. Consider adding a migration to add a 'data TEXT' column.`);
|
|
264
979
|
return;
|
|
265
980
|
}
|
|
@@ -269,16 +984,69 @@ export class StorageRecord {
|
|
|
269
984
|
}
|
|
270
985
|
/**
|
|
271
986
|
* Get database connection from adapter.
|
|
272
|
-
*
|
|
987
|
+
*
|
|
988
|
+
* Prefers the record-scoped connection when the adapter supports one
|
|
989
|
+
* (`getRecordDatabaseConnection`). The adapter instance is a runtime
|
|
990
|
+
* singleton shared by every record that resolves it, while `configure()`
|
|
991
|
+
* mutates that shared instance's `this.database`/`this.config` in place —
|
|
992
|
+
* two records with different descriptors (different `scope`/`dbName`)
|
|
993
|
+
* resolving concurrently can otherwise race each other's `configure()` +
|
|
994
|
+
* `ensureReady()` + `getDatabaseConnection()` sequence and hand back a
|
|
995
|
+
* connection open on the WRONG descriptor's physical database (live
|
|
996
|
+
* forensics: stray `brain_state`/`session_state` tables inside
|
|
997
|
+
* `awareness.db`, frozen partial `awareness_*` tables inside
|
|
998
|
+
* `sessions.db`).
|
|
999
|
+
*
|
|
1000
|
+
* `getRecordDatabaseConnection({ basePath, dbName })` resolves from a
|
|
1001
|
+
* dbPath-keyed pool instead, keyed off THIS call's own `getAdapterConfig()`
|
|
1002
|
+
* output — computed purely from `this.descriptor`/`this.context` (never
|
|
1003
|
+
* from the adapter's shared mutable fields) — so it never depends on that
|
|
1004
|
+
* shared state surviving to the end of this call. This is the record
|
|
1005
|
+
* analogue of the fix already applied to the document-store path (see the
|
|
1006
|
+
* localdb adapter's `getScopedDb` for that incident writeup); it is a
|
|
1007
|
+
* DIFFERENT method (not the same `getScopedDb` pool) because a record's
|
|
1008
|
+
* scope→path mapping (`getBasePath()` below) is its own and does not match
|
|
1009
|
+
* a manifest's scope→path mapping — reusing `getScopedDb` here would
|
|
1010
|
+
* silently retarget every existing local record store (e.g.
|
|
1011
|
+
* `~/.vibes/awareness.db`) to a different, empty file (verified live: for
|
|
1012
|
+
* a global-scope record, `getScopedDb`'s manifest resolution lands on
|
|
1013
|
+
* `resolveGlobalStorageBasePath()` — a different absolute path than this
|
|
1014
|
+
* record's own configured basePath). Adapters that don't implement
|
|
1015
|
+
* `getRecordDatabaseConnection` (memory, a single remote connection, etc.)
|
|
1016
|
+
* fall back to the plain ensureReady() + getDatabaseConnection() interface.
|
|
273
1017
|
*/
|
|
274
1018
|
async getDatabase() {
|
|
275
1019
|
const adapter = await this.getAdapter();
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
1020
|
+
if (adapter.getRecordDatabaseConnection) {
|
|
1021
|
+
// Computed purely from this record's own (immutable per-instance)
|
|
1022
|
+
// descriptor/context — not from the adapter's shared mutable
|
|
1023
|
+
// `this.config` — so it can't be clobbered by a sibling record's
|
|
1024
|
+
// concurrent configure() call.
|
|
1025
|
+
const config = await this.getAdapterConfig(this.adapterCacheId ?? adapter.id, this.adapterBasePathSemantics);
|
|
1026
|
+
const target = parseAdapterInput(RecordDatabaseTargetSchema, config, {
|
|
1027
|
+
message: `Storage record '${this.descriptor.id}' has no database location for its adapter.`,
|
|
1028
|
+
code: 'INVALID_INPUT',
|
|
1029
|
+
reason: 'storage-record-database-location-invalid',
|
|
1030
|
+
details: { recordKind: this.descriptor.id, adapterId: adapter.id }
|
|
1031
|
+
});
|
|
1032
|
+
const db = await adapter.getRecordDatabaseConnection({
|
|
1033
|
+
basePath: target.basePath,
|
|
1034
|
+
dbName: target.dbName,
|
|
1035
|
+
runtime: this.getRuntime()
|
|
1036
|
+
});
|
|
1037
|
+
return this.sqlHandleOf(db, adapter.id);
|
|
280
1038
|
}
|
|
281
|
-
|
|
1039
|
+
await adapter.ensureReady();
|
|
1040
|
+
return this.sqlHandleOf(adapter.getDatabaseConnection(), adapter.id);
|
|
1041
|
+
}
|
|
1042
|
+
/** An adapter's database connection as a SQL handle; anything else (no connection) is refused typed. */
|
|
1043
|
+
sqlHandleOf(connection, adapterId) {
|
|
1044
|
+
return parseAdapterInput(SqlDatabaseHandleSchema, connection, {
|
|
1045
|
+
message: `Adapter '${adapterId}' returned no SQL database connection for record '${this.descriptor.id}'.`,
|
|
1046
|
+
code: 'INTERNAL',
|
|
1047
|
+
reason: 'storage-record-database-unavailable',
|
|
1048
|
+
details: { recordKind: this.descriptor.id, adapterId }
|
|
1049
|
+
});
|
|
282
1050
|
}
|
|
283
1051
|
/**
|
|
284
1052
|
* Create table from schema definition
|
|
@@ -288,8 +1056,8 @@ export class StorageRecord {
|
|
|
288
1056
|
throw new Error('Database not initialized');
|
|
289
1057
|
// Build column definitions from schema
|
|
290
1058
|
const columns = this.buildColumnDefinitions();
|
|
291
|
-
const columnDefs =
|
|
292
|
-
.map(([name, def]) =>
|
|
1059
|
+
const columnDefs = [...columns]
|
|
1060
|
+
.map(([name, def]) => `"${name.replaceAll('"', '""')}" ${def}`)
|
|
293
1061
|
.join(',\n ');
|
|
294
1062
|
const sql = `
|
|
295
1063
|
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
@@ -299,36 +1067,33 @@ export class StorageRecord {
|
|
|
299
1067
|
this.db.exec(sql);
|
|
300
1068
|
}
|
|
301
1069
|
/**
|
|
302
|
-
* Build column definitions
|
|
1070
|
+
* Build column definitions (name to SQL definition, in declaration order)
|
|
1071
|
+
* from schema and descriptor
|
|
303
1072
|
*/
|
|
304
1073
|
buildColumnDefinitions() {
|
|
305
|
-
const columns =
|
|
1074
|
+
const columns = new Map();
|
|
306
1075
|
// Primary key
|
|
307
1076
|
const pkType = this.descriptor.fieldConfig?.[this.primaryKey]?.type || 'TEXT';
|
|
308
|
-
columns
|
|
1077
|
+
columns.set(this.primaryKey, `${pkType} PRIMARY KEY`);
|
|
309
1078
|
// Auto-managed fields
|
|
310
1079
|
if (this.descriptor.timestamps) {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
? this.descriptor.timestamps.updatedAt
|
|
316
|
-
: 'updatedAt';
|
|
317
|
-
if (createdAt) {
|
|
318
|
-
const createdAtField = typeof createdAt === 'string' ? createdAt : 'createdAt';
|
|
319
|
-
columns[createdAtField] = 'TEXT DEFAULT CURRENT_TIMESTAMP';
|
|
1080
|
+
const createdAtField = timestampColumn(this.descriptor.timestamps, 'createdAt');
|
|
1081
|
+
const updatedAtField = timestampColumn(this.descriptor.timestamps, 'updatedAt');
|
|
1082
|
+
if (createdAtField) {
|
|
1083
|
+
columns.set(createdAtField, 'TEXT DEFAULT CURRENT_TIMESTAMP');
|
|
320
1084
|
}
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
columns[updatedAtField] = 'TEXT DEFAULT CURRENT_TIMESTAMP';
|
|
1085
|
+
if (updatedAtField) {
|
|
1086
|
+
columns.set(updatedAtField, 'TEXT DEFAULT CURRENT_TIMESTAMP');
|
|
324
1087
|
}
|
|
325
1088
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
columns
|
|
1089
|
+
const deletedAtField = this.softDeleteField();
|
|
1090
|
+
if (deletedAtField) {
|
|
1091
|
+
columns.set(deletedAtField, 'TEXT');
|
|
329
1092
|
}
|
|
330
1093
|
// Schema introspection is not yet wired; all non-meta fields live in a JSON `data` blob.
|
|
331
|
-
columns
|
|
1094
|
+
columns.set('data', 'TEXT');
|
|
1095
|
+
if (this.descriptor.versioning)
|
|
1096
|
+
columns.set(RECORD_REVISION_COLUMN, 'TEXT');
|
|
332
1097
|
return columns;
|
|
333
1098
|
}
|
|
334
1099
|
/**
|
|
@@ -337,12 +1102,13 @@ export class StorageRecord {
|
|
|
337
1102
|
async createIndexes() {
|
|
338
1103
|
if (!this.db)
|
|
339
1104
|
throw new Error('Database not initialized');
|
|
1105
|
+
const db = this.db;
|
|
340
1106
|
// Detect whether the table uses JSON blob storage (has a 'data' column).
|
|
341
1107
|
// ensureDataColumn() may have recreated the table as (primaryKey, data TEXT),
|
|
342
1108
|
// so direct column references from the descriptor won't exist. Use json_extract
|
|
343
1109
|
// expressions instead so indexes still function and unique constraints are enforced.
|
|
344
|
-
const tableInfo =
|
|
345
|
-
.
|
|
1110
|
+
const tableInfo = db
|
|
1111
|
+
.prepare(`PRAGMA table_info(${this.tableName})`)
|
|
346
1112
|
.all();
|
|
347
1113
|
const hasDataColumn = tableInfo.some((c) => c.name === 'data');
|
|
348
1114
|
const toSnakeCase = (value) => value
|
|
@@ -351,13 +1117,14 @@ export class StorageRecord {
|
|
|
351
1117
|
.toLowerCase();
|
|
352
1118
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
353
1119
|
for (const index of this.descriptor.indexes || []) {
|
|
354
|
-
const indexName =
|
|
1120
|
+
const indexName = recordIndexName(this.tableName, index);
|
|
355
1121
|
const unique = index.unique ? 'UNIQUE' : '';
|
|
356
1122
|
let fieldExpr;
|
|
357
1123
|
let whereExpr = '';
|
|
358
1124
|
if (hasDataColumn) {
|
|
359
|
-
// JSON blob mode: reference fields
|
|
360
|
-
|
|
1125
|
+
// JSON blob mode: reference fields as queries compile them (`filterColumn`:
|
|
1126
|
+
// the primary-key column, else json_extract) so an index serves its query.
|
|
1127
|
+
fieldExpr = index.fields.map((f) => this.filterColumn(f)).join(', ');
|
|
361
1128
|
if (index.where) {
|
|
362
1129
|
// Replace field name references in the WHERE clause with json_extract.
|
|
363
1130
|
// Some descriptors still use snake_case predicates (legacy migration SQL style).
|
|
@@ -367,7 +1134,7 @@ export class StorageRecord {
|
|
|
367
1134
|
// which would corrupt an already-substituted json_extract expression.
|
|
368
1135
|
const aliases = [...new Set([f, toSnakeCase(f)])];
|
|
369
1136
|
for (const alias of aliases) {
|
|
370
|
-
w = w.replace(new RegExp(`\\b${escapeRegExp(alias)}\\b`, 'g'),
|
|
1137
|
+
w = w.replace(new RegExp(`\\b${escapeRegExp(alias)}\\b`, 'g'), this.filterColumn(f));
|
|
371
1138
|
}
|
|
372
1139
|
}
|
|
373
1140
|
whereExpr = `WHERE ${w}`;
|
|
@@ -378,31 +1145,155 @@ export class StorageRecord {
|
|
|
378
1145
|
fieldExpr = index.fields.join(', ');
|
|
379
1146
|
whereExpr = index.where ? `WHERE ${index.where}` : '';
|
|
380
1147
|
}
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
1148
|
+
const indexIdentifier = /^[A-Za-z_][A-Za-z0-9_]*$/.test(indexName) ? indexName : `"${indexName.replace(/"/g, '""')}"`;
|
|
1149
|
+
const sql = `CREATE ${unique} INDEX IF NOT EXISTS ${indexIdentifier} ON ${this.tableName} (${fieldExpr}) ${whereExpr}`;
|
|
1150
|
+
const existing = db.prepare("SELECT sql, tbl_name FROM sqlite_master WHERE type = 'index' AND name = ?").all(indexName)[0];
|
|
1151
|
+
if (existing) {
|
|
1152
|
+
if (existing.tbl_name !== this.tableName || !existing.sql) {
|
|
1153
|
+
throw new CapabilityError('The declared record index belongs to another schema.', 'CONFLICT', {
|
|
1154
|
+
reason: 'record-index-owner-conflict'
|
|
1155
|
+
});
|
|
389
1156
|
}
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
|
|
1157
|
+
// SQLite omits IF NOT EXISTS from stored DDL. Preserve quoted values
|
|
1158
|
+
// while ignoring statement whitespace, so unchanged indexes stay intact.
|
|
1159
|
+
const definition = (statement) => statement
|
|
1160
|
+
.replace(/^(\s*CREATE\s+(?:UNIQUE\s+)?INDEX\s+)IF\s+NOT\s+EXISTS\s+/i, '$1')
|
|
1161
|
+
.replace(/'(?:[^']|'')*'|"(?:[^"]|"")*"|\s+/g, (token) => /^\s+$/.test(token) ? ' ' : token)
|
|
1162
|
+
.trim().replace(/;$/, '');
|
|
1163
|
+
if (definition(existing.sql) === definition(sql))
|
|
1164
|
+
continue;
|
|
1165
|
+
// Initialization holds BEGIN IMMEDIATE: a failed replacement rolls
|
|
1166
|
+
// back the drop, preserving the previous index and unique constraint.
|
|
1167
|
+
db.exec(`DROP INDEX "${indexName.replace(/"/g, '""')}"`);
|
|
1168
|
+
db.prepare(sql).run();
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
if (index.unique) {
|
|
1172
|
+
// Unique index failures must be fatal — they protect data integrity.
|
|
1173
|
+
db.prepare(sql).run();
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
// Non-unique index creation can fail if descriptor field names don't match
|
|
1177
|
+
// actual column names (e.g. camelCase vs snake_case mismatch). Log and continue.
|
|
1178
|
+
const failure = failureOfSync(() => {
|
|
1179
|
+
db.prepare(sql).run();
|
|
1180
|
+
});
|
|
1181
|
+
if (failure) {
|
|
1182
|
+
this.logger.warn('[StorageRecord] Skipping a non-unique index that could not be created', {
|
|
1183
|
+
index: indexName,
|
|
1184
|
+
table: this.tableName,
|
|
1185
|
+
error: failure
|
|
1186
|
+
});
|
|
393
1187
|
}
|
|
394
1188
|
}
|
|
395
1189
|
}
|
|
1190
|
+
sqlString(value) {
|
|
1191
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1192
|
+
}
|
|
1193
|
+
jsonPathForField(field) {
|
|
1194
|
+
return `$."${field.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
1195
|
+
}
|
|
1196
|
+
validateTokenizer(tokenizer) {
|
|
1197
|
+
if (!/^[A-Za-z0-9_\s]+$/.test(tokenizer)) {
|
|
1198
|
+
throw new Error(`Invalid FTS tokenizer for ${this.tableName}: ${tokenizer}`);
|
|
1199
|
+
}
|
|
1200
|
+
return tokenizer.trim() || 'unicode61';
|
|
1201
|
+
}
|
|
1202
|
+
async createFtsArtifacts() {
|
|
1203
|
+
if (!this.db || !this.descriptor.fts)
|
|
1204
|
+
return;
|
|
1205
|
+
const fields = this.descriptor.fts.fields.map((field) => String(field));
|
|
1206
|
+
if (fields.length === 0)
|
|
1207
|
+
return;
|
|
1208
|
+
const tableInfo = this.db
|
|
1209
|
+
.prepare(`PRAGMA table_info(${this.tableName})`)
|
|
1210
|
+
.all();
|
|
1211
|
+
const hasDataColumn = tableInfo.some((column) => column.name === 'data');
|
|
1212
|
+
const ftsRawTableName = this.descriptor.fts.tableName || `${this.tableName}_fts`;
|
|
1213
|
+
const tableName = this.quoteIdentifier(this.tableName);
|
|
1214
|
+
const ftsTableName = this.quoteIdentifier(ftsRawTableName);
|
|
1215
|
+
const insertTriggerName = this.quoteIdentifier(`${ftsRawTableName}_ai`);
|
|
1216
|
+
const deleteTriggerName = this.quoteIdentifier(`${ftsRawTableName}_ad`);
|
|
1217
|
+
const updateTriggerName = this.quoteIdentifier(`${ftsRawTableName}_au`);
|
|
1218
|
+
const quotedFields = fields.map((field) => this.quoteIdentifier(field));
|
|
1219
|
+
const fieldList = quotedFields.join(', ');
|
|
1220
|
+
const tokenizer = this.validateTokenizer(this.descriptor.fts.tokenizer || 'unicode61');
|
|
1221
|
+
const valueExpression = (source, field) => {
|
|
1222
|
+
if (hasDataColumn) {
|
|
1223
|
+
const dataRef = source ? `${source}.data` : 'data';
|
|
1224
|
+
return `json_extract(${dataRef}, ${this.sqlString(this.jsonPathForField(field))})`;
|
|
1225
|
+
}
|
|
1226
|
+
const tableRef = source ? `${source}.` : '';
|
|
1227
|
+
return `${tableRef}${this.quoteIdentifier(field)}`;
|
|
1228
|
+
};
|
|
1229
|
+
const newValues = fields.map((field) => valueExpression('NEW', field)).join(', ');
|
|
1230
|
+
const selectValues = fields.map((field) => valueExpression(null, field)).join(', ');
|
|
1231
|
+
const triggerNames = new Set([
|
|
1232
|
+
`${ftsRawTableName}_ai`,
|
|
1233
|
+
`${ftsRawTableName}_ad`,
|
|
1234
|
+
`${ftsRawTableName}_au`,
|
|
1235
|
+
`${this.tableName}_fts_insert`,
|
|
1236
|
+
`${this.tableName}_fts_delete`,
|
|
1237
|
+
`${this.tableName}_fts_update`
|
|
1238
|
+
]);
|
|
1239
|
+
for (const row of this.db
|
|
1240
|
+
.prepare(`SELECT name FROM sqlite_master WHERE type = 'trigger' AND tbl_name = ? AND instr(sql, ?) > 0`)
|
|
1241
|
+
.all(this.tableName, ftsRawTableName)) {
|
|
1242
|
+
triggerNames.add(row.name);
|
|
1243
|
+
}
|
|
1244
|
+
for (const triggerName of triggerNames) {
|
|
1245
|
+
this.db.exec(`DROP TRIGGER IF EXISTS ${this.quoteIdentifier(triggerName)}`);
|
|
1246
|
+
}
|
|
1247
|
+
this.db.exec(`DROP TABLE IF EXISTS ${ftsTableName}`);
|
|
1248
|
+
this.db.exec(`
|
|
1249
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS ${ftsTableName} USING fts5(
|
|
1250
|
+
${fieldList},
|
|
1251
|
+
tokenize=${this.sqlString(tokenizer)}
|
|
1252
|
+
)
|
|
1253
|
+
`);
|
|
1254
|
+
this.execStatements(`
|
|
1255
|
+
CREATE TRIGGER IF NOT EXISTS ${insertTriggerName} AFTER INSERT ON ${tableName}
|
|
1256
|
+
BEGIN
|
|
1257
|
+
INSERT INTO ${ftsTableName}(rowid, ${fieldList})
|
|
1258
|
+
VALUES (NEW.rowid, ${newValues});
|
|
1259
|
+
END
|
|
1260
|
+
`);
|
|
1261
|
+
this.execStatements(`
|
|
1262
|
+
CREATE TRIGGER IF NOT EXISTS ${deleteTriggerName} AFTER DELETE ON ${tableName}
|
|
1263
|
+
BEGIN
|
|
1264
|
+
DELETE FROM ${ftsTableName} WHERE rowid = OLD.rowid;
|
|
1265
|
+
END
|
|
1266
|
+
`);
|
|
1267
|
+
this.execStatements(`
|
|
1268
|
+
CREATE TRIGGER IF NOT EXISTS ${updateTriggerName} AFTER UPDATE ON ${tableName}
|
|
1269
|
+
BEGIN
|
|
1270
|
+
DELETE FROM ${ftsTableName} WHERE rowid = OLD.rowid;
|
|
1271
|
+
INSERT INTO ${ftsTableName}(rowid, ${fieldList})
|
|
1272
|
+
VALUES (NEW.rowid, ${newValues});
|
|
1273
|
+
END
|
|
1274
|
+
`);
|
|
1275
|
+
this.db
|
|
1276
|
+
.prepare(`
|
|
1277
|
+
INSERT INTO ${ftsTableName}(rowid, ${fieldList})
|
|
1278
|
+
SELECT rowid, ${selectValues}
|
|
1279
|
+
FROM ${tableName}
|
|
1280
|
+
`)
|
|
1281
|
+
.run();
|
|
1282
|
+
}
|
|
396
1283
|
/**
|
|
397
1284
|
* Execute one or more SQL statements, splitting on `;` and running each
|
|
398
1285
|
* via the prepared-statement path so bun:sqlite does not misinterpret `$`
|
|
399
1286
|
* tokens inside string literals (e.g. json_extract paths).
|
|
1287
|
+
*
|
|
1288
|
+
* Splitting is statement-aware: `;` inside string literals, comments, or a
|
|
1289
|
+
* `CREATE TRIGGER ... BEGIN ... END` body does NOT terminate a statement —
|
|
1290
|
+
* a naive split truncates trigger bodies and SQLite throws "incomplete input".
|
|
400
1291
|
*/
|
|
401
1292
|
execStatements(sql) {
|
|
402
1293
|
if (!this.db)
|
|
403
1294
|
throw new Error('Database not initialized');
|
|
404
|
-
for (const stmt of sql
|
|
405
|
-
this.db.
|
|
1295
|
+
for (const stmt of splitSqlStatements(sql)) {
|
|
1296
|
+
this.db.prepare(stmt).run();
|
|
406
1297
|
}
|
|
407
1298
|
}
|
|
408
1299
|
/**
|
|
@@ -411,9 +1302,10 @@ export class StorageRecord {
|
|
|
411
1302
|
async runMigrations() {
|
|
412
1303
|
if (!this.db)
|
|
413
1304
|
throw new Error('Database not initialized');
|
|
1305
|
+
const db = this.db;
|
|
414
1306
|
const migrations = this.descriptor.migrations || [];
|
|
415
1307
|
// Create migrations tracking table if not exists
|
|
416
|
-
|
|
1308
|
+
db.exec(`
|
|
417
1309
|
CREATE TABLE IF NOT EXISTS _migrations_${this.tableName} (
|
|
418
1310
|
version INTEGER PRIMARY KEY,
|
|
419
1311
|
name TEXT,
|
|
@@ -421,8 +1313,8 @@ export class StorageRecord {
|
|
|
421
1313
|
)
|
|
422
1314
|
`);
|
|
423
1315
|
// Get applied migrations
|
|
424
|
-
const applied =
|
|
425
|
-
.
|
|
1316
|
+
const applied = db
|
|
1317
|
+
.prepare(`SELECT version FROM _migrations_${this.tableName}`)
|
|
426
1318
|
.all()
|
|
427
1319
|
.map((r) => r.version);
|
|
428
1320
|
// Run pending migrations
|
|
@@ -430,17 +1322,21 @@ export class StorageRecord {
|
|
|
430
1322
|
if (applied.includes(migration.version))
|
|
431
1323
|
continue;
|
|
432
1324
|
try {
|
|
433
|
-
|
|
434
|
-
|
|
1325
|
+
// The `up` step: SQL text runs statement by statement; a function runs against this database.
|
|
1326
|
+
const upSql = MigrationSqlSchema.safeParse(migration.up);
|
|
1327
|
+
if (upSql.success) {
|
|
1328
|
+
this.execStatements(upSql.data);
|
|
435
1329
|
}
|
|
436
|
-
else
|
|
437
|
-
|
|
1330
|
+
else {
|
|
1331
|
+
const upFunction = MigrationFunctionSchema.safeParse(migration.up);
|
|
1332
|
+
if (upFunction.success)
|
|
1333
|
+
await upFunction.data(db);
|
|
438
1334
|
}
|
|
439
1335
|
// Record migration
|
|
440
|
-
|
|
441
|
-
.
|
|
1336
|
+
db
|
|
1337
|
+
.prepare(`INSERT INTO _migrations_${this.tableName} (version, name) VALUES (?, ?)`)
|
|
442
1338
|
.run(migration.version, migration.name || '');
|
|
443
|
-
logger.debug(`[StorageRecord] Applied migration ${migration.version} for ${this.tableName}`);
|
|
1339
|
+
this.logger.debug(`[StorageRecord] Applied migration ${migration.version} for ${this.tableName}`);
|
|
444
1340
|
}
|
|
445
1341
|
catch (error) {
|
|
446
1342
|
// A "no such table" failure typically means the consumer is
|
|
@@ -449,13 +1345,12 @@ export class StorageRecord {
|
|
|
449
1345
|
// layer catches the throw and renders empty state; demote the
|
|
450
1346
|
// log so the spurious noise doesn't reach the user. Real
|
|
451
1347
|
// migration failures still surface at warn level + the throw.
|
|
452
|
-
const
|
|
453
|
-
const isTableMissing = /no such table/i.test(message);
|
|
1348
|
+
const isTableMissing = error instanceof Error && isRecordTableMissing(error);
|
|
454
1349
|
if (isTableMissing) {
|
|
455
|
-
logger.debug(`[StorageRecord] Migration ${migration.version} skipped for ${this.tableName} (table not yet initialized)`);
|
|
1350
|
+
this.logger.debug(`[StorageRecord] Migration ${migration.version} skipped for ${this.tableName} (table not yet initialized)`);
|
|
456
1351
|
}
|
|
457
1352
|
else {
|
|
458
|
-
logger.error(`[StorageRecord] Migration ${migration.version} failed for ${this.tableName}:`, toErrorContext(error));
|
|
1353
|
+
this.logger.error(`[StorageRecord] Migration ${migration.version} failed for ${this.tableName}:`, toErrorContext(error));
|
|
459
1354
|
}
|
|
460
1355
|
throw error;
|
|
461
1356
|
}
|
|
@@ -463,14 +1358,14 @@ export class StorageRecord {
|
|
|
463
1358
|
}
|
|
464
1359
|
// CREATE
|
|
465
1360
|
async create(data) {
|
|
1361
|
+
return this.createFields(this.fieldsOf({ ...data }));
|
|
1362
|
+
}
|
|
1363
|
+
/** Create one row from its own (already copied) field map. */
|
|
1364
|
+
async createFields(record) {
|
|
466
1365
|
await this.ensureInitialized();
|
|
467
|
-
// Prepare record
|
|
468
|
-
const record = { ...data };
|
|
469
1366
|
// Auto-generate ID (must happen before validation so the schema check passes)
|
|
470
1367
|
if (this.descriptor.autoId) {
|
|
471
|
-
const idField =
|
|
472
|
-
? this.descriptor.autoId.field
|
|
473
|
-
: this.primaryKey;
|
|
1368
|
+
const idField = this.autoIdOptions()?.field || this.primaryKey;
|
|
474
1369
|
if (!record[idField]) {
|
|
475
1370
|
record[idField] = this.generateId();
|
|
476
1371
|
}
|
|
@@ -481,9 +1376,8 @@ export class StorageRecord {
|
|
|
481
1376
|
applyCreateTimestamps(record, this.descriptor.timestamps, now);
|
|
482
1377
|
}
|
|
483
1378
|
// Execute beforeCreate hook (may set additional required fields)
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
1379
|
+
await this.runHook('beforeCreate', record);
|
|
1380
|
+
this.fieldsOf(record);
|
|
487
1381
|
// Validate after all auto-fields are populated
|
|
488
1382
|
if (this.descriptor.validateOnWrite !== false) {
|
|
489
1383
|
const validation = await this.validate(record);
|
|
@@ -494,13 +1388,52 @@ export class StorageRecord {
|
|
|
494
1388
|
// Insert into database
|
|
495
1389
|
const id = record[this.primaryKey];
|
|
496
1390
|
const dataJson = JSON.stringify(record);
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
1391
|
+
const db = await this.acquireConnection();
|
|
1392
|
+
const inserted = this.descriptor.versioning
|
|
1393
|
+
? db.prepare(`INSERT INTO ${this.tableName} (${this.primaryKey}, data, ${RECORD_REVISION_COLUMN}) VALUES (?, ?, ?) ON CONFLICT (${this.primaryKey}) DO NOTHING`).run(id, dataJson, crypto.randomUUID())
|
|
1394
|
+
: db.prepare(`INSERT INTO ${this.tableName} (${this.primaryKey}, data) VALUES (?, ?) ON CONFLICT (${this.primaryKey}) DO NOTHING`).run(id, dataJson);
|
|
1395
|
+
if (inserted.changes === 0) {
|
|
1396
|
+
throw new CapabilityError(`Record with duplicate ${this.primaryKey} already exists in ${this.tableName}.`, 'CONFLICT', {
|
|
1397
|
+
reason: 'record-identity-conflict',
|
|
1398
|
+
retryable: true,
|
|
1399
|
+
details: { tableName: this.tableName, primaryKey: this.primaryKey }
|
|
1400
|
+
});
|
|
501
1401
|
}
|
|
1402
|
+
// Execute afterCreate hook
|
|
1403
|
+
await this.runHook('afterCreate', record);
|
|
1404
|
+
// In-process notification only (D-RC11): the hook bus dies with this
|
|
1405
|
+
// runtime and never delivers across processes.
|
|
502
1406
|
this.emitLifecycle('afterCreate', record);
|
|
503
|
-
|
|
1407
|
+
// Durable delivery the record kind declares: a Workers job keyed by this
|
|
1408
|
+
// record's id, enqueued before create() returns. A gated kind is claimed
|
|
1409
|
+
// by the reconcile once its gate admits the pass (one eligibility decision
|
|
1410
|
+
// per pass, not per row); the row itself is the durable intent.
|
|
1411
|
+
if (this.descriptor.delivery && !this.descriptor.delivery.gate) {
|
|
1412
|
+
await this.enqueueDelivery(String(id));
|
|
1413
|
+
}
|
|
1414
|
+
return this.recordOf(record);
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Enqueue this record kind's declared delivery job for one created row. The
|
|
1418
|
+
* row is already persisted, so a failed enqueue does not fail the write: it
|
|
1419
|
+
* is logged at error with its value-free facts, the row stays pending, and
|
|
1420
|
+
* the Workers consumer enqueues it again when it starts
|
|
1421
|
+
* (`requeuePendingDeliveries`).
|
|
1422
|
+
*/
|
|
1423
|
+
async enqueueDelivery(recordId) {
|
|
1424
|
+
const delivery = this.descriptor.delivery;
|
|
1425
|
+
if (!delivery)
|
|
1426
|
+
return;
|
|
1427
|
+
const enqueued = await outcomeOf(() => enqueueRecordDelivery(this.getRuntime(), this.descriptor.id, delivery, recordId));
|
|
1428
|
+
if (enqueued.ok)
|
|
1429
|
+
return;
|
|
1430
|
+
this.logger.error('Record delivery could not be enqueued; the record stays pending until the Workers consumer re-enqueues it', {
|
|
1431
|
+
reason: 'storage-record-delivery-enqueue-failed',
|
|
1432
|
+
recordKind: this.descriptor.id,
|
|
1433
|
+
recordId,
|
|
1434
|
+
job: delivery.job,
|
|
1435
|
+
error: enqueued.failure
|
|
1436
|
+
});
|
|
504
1437
|
}
|
|
505
1438
|
async createMany(data, options) {
|
|
506
1439
|
const results = [];
|
|
@@ -515,19 +1448,22 @@ export class StorageRecord {
|
|
|
515
1448
|
// READ
|
|
516
1449
|
async get(id, options) {
|
|
517
1450
|
await this.ensureInitialized();
|
|
518
|
-
const row = this.db.
|
|
1451
|
+
const row = this.db.prepare(`SELECT data FROM ${this.tableName} WHERE ${this.primaryKey} = ?`).get(id);
|
|
519
1452
|
if (!row)
|
|
520
1453
|
return null;
|
|
521
|
-
const record =
|
|
1454
|
+
const record = this.decodeRecord(row.data);
|
|
522
1455
|
// Load relations if requested
|
|
523
1456
|
if (options?.include) {
|
|
524
|
-
await this.
|
|
1457
|
+
await loadRecordRelations(this.relationOwner, record, options.include);
|
|
525
1458
|
}
|
|
526
1459
|
// Validate on read if configured
|
|
527
1460
|
if (this.descriptor.validateOnRead) {
|
|
528
1461
|
const validation = await this.validate(record);
|
|
529
1462
|
if (!validation.success) {
|
|
530
|
-
logger.warn(
|
|
1463
|
+
this.logger.warn('[StorageRecord] Validation failed on read', {
|
|
1464
|
+
recordKind: this.descriptor.id,
|
|
1465
|
+
issues: validation.errors?.issues.length ?? 0
|
|
1466
|
+
});
|
|
531
1467
|
}
|
|
532
1468
|
}
|
|
533
1469
|
return record;
|
|
@@ -544,15 +1480,20 @@ export class StorageRecord {
|
|
|
544
1480
|
if (ids.length === 0)
|
|
545
1481
|
return [];
|
|
546
1482
|
const placeholders = ids.map(() => '?').join(', ');
|
|
547
|
-
const rows = this.db.
|
|
548
|
-
let records = rows.map((row) =>
|
|
1483
|
+
const rows = this.db.prepare(`SELECT data FROM ${this.tableName} WHERE ${this.primaryKey} IN (${placeholders})`).all(...ids);
|
|
1484
|
+
let records = rows.map((row) => this.decodeRecord(row.data));
|
|
549
1485
|
// Load relations if requested
|
|
550
|
-
|
|
551
|
-
|
|
1486
|
+
const include = options?.include;
|
|
1487
|
+
if (include) {
|
|
1488
|
+
records = await Promise.all(records.map((record) => loadRecordRelations(this.relationOwner, record, include)));
|
|
552
1489
|
}
|
|
553
1490
|
return records;
|
|
554
1491
|
}
|
|
555
1492
|
async list(options) {
|
|
1493
|
+
return this.listRecords(options);
|
|
1494
|
+
}
|
|
1495
|
+
/** `list()` over any relation names: the bulk paths (stream, updateMany, deleteMany) list through it. */
|
|
1496
|
+
async listRecords(options) {
|
|
556
1497
|
await this.ensureInitialized();
|
|
557
1498
|
// Build query
|
|
558
1499
|
let sql = `SELECT data FROM ${this.tableName}`;
|
|
@@ -568,19 +1509,23 @@ export class StorageRecord {
|
|
|
568
1509
|
}
|
|
569
1510
|
}
|
|
570
1511
|
// Soft delete filter
|
|
571
|
-
|
|
572
|
-
|
|
1512
|
+
const deletedAtField = this.softDeleteField();
|
|
1513
|
+
if (deletedAtField && !options?.includeDeleted) {
|
|
573
1514
|
sql += hasWhereConditions ? ` AND ` : ` WHERE `;
|
|
574
1515
|
sql += `json_extract(data, '$.${deletedAtField}') IS NULL`;
|
|
575
1516
|
}
|
|
1517
|
+
// A bounded page does not scan the entire match set merely to report
|
|
1518
|
+
// metadata. Callers that need it explicitly request the independent count.
|
|
1519
|
+
let total;
|
|
1520
|
+
if (options?.limit === undefined || options.includeTotal === true) {
|
|
1521
|
+
const countSql = sql.replace('SELECT data', 'SELECT COUNT(*) as count');
|
|
1522
|
+
const countRow = this.db.prepare(countSql).get(...params);
|
|
1523
|
+
total = countRow?.count ?? 0;
|
|
1524
|
+
}
|
|
576
1525
|
// ORDER BY
|
|
577
1526
|
if (options?.orderBy) {
|
|
578
1527
|
sql += ` ORDER BY ${this.buildOrderBy(options.orderBy)}`;
|
|
579
1528
|
}
|
|
580
|
-
// COUNT query for total
|
|
581
|
-
const countSql = sql.replace('SELECT data', 'SELECT COUNT(*) as count');
|
|
582
|
-
const countRow = this.db.query(countSql).get(...params);
|
|
583
|
-
const total = countRow?.count || 0;
|
|
584
1529
|
// LIMIT and OFFSET
|
|
585
1530
|
if (options?.limit) {
|
|
586
1531
|
sql += ` LIMIT ?`;
|
|
@@ -591,17 +1536,20 @@ export class StorageRecord {
|
|
|
591
1536
|
params.push(options.offset);
|
|
592
1537
|
}
|
|
593
1538
|
// Execute query
|
|
594
|
-
const rows = this.db.
|
|
595
|
-
let items = rows.map((row) =>
|
|
1539
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
1540
|
+
let items = rows.map((row) => this.decodeRecord(row.data));
|
|
596
1541
|
// Load relations if requested
|
|
597
|
-
|
|
598
|
-
|
|
1542
|
+
const include = options?.include;
|
|
1543
|
+
if (include) {
|
|
1544
|
+
items = await Promise.all(items.map((item) => loadRecordRelations(this.relationOwner, item, include)));
|
|
599
1545
|
}
|
|
600
|
-
|
|
1546
|
+
const result = {
|
|
601
1547
|
items,
|
|
602
|
-
total,
|
|
603
1548
|
hasMore: options?.limit ? items.length === options.limit : false
|
|
604
1549
|
};
|
|
1550
|
+
if (total !== undefined)
|
|
1551
|
+
result.total = total;
|
|
1552
|
+
return result;
|
|
605
1553
|
}
|
|
606
1554
|
async findFirst(options) {
|
|
607
1555
|
const result = await this.list({ ...options, limit: 1 });
|
|
@@ -616,7 +1564,7 @@ export class StorageRecord {
|
|
|
616
1564
|
}
|
|
617
1565
|
async exists(id) {
|
|
618
1566
|
await this.ensureInitialized();
|
|
619
|
-
const row = this.db.
|
|
1567
|
+
const row = this.db.prepare(`SELECT COUNT(*) as count FROM ${this.tableName} WHERE ${this.primaryKey} = ?`).get(id);
|
|
620
1568
|
return (row?.count || 0) > 0;
|
|
621
1569
|
}
|
|
622
1570
|
async count(where) {
|
|
@@ -633,24 +1581,46 @@ export class StorageRecord {
|
|
|
633
1581
|
}
|
|
634
1582
|
}
|
|
635
1583
|
// Soft delete filter
|
|
636
|
-
|
|
637
|
-
|
|
1584
|
+
const deletedAtField = this.softDeleteField();
|
|
1585
|
+
if (deletedAtField) {
|
|
638
1586
|
sql += hasWhereConditions ? ` AND ` : ` WHERE `;
|
|
639
1587
|
sql += `json_extract(data, '$.${deletedAtField}') IS NULL`;
|
|
640
1588
|
}
|
|
641
|
-
const row = this.db.
|
|
1589
|
+
const row = this.db.prepare(sql).get(...params);
|
|
642
1590
|
return row?.count || 0;
|
|
643
1591
|
}
|
|
644
1592
|
// UPDATE
|
|
645
|
-
async update(id, data,
|
|
1593
|
+
async update(id, data, options) {
|
|
1594
|
+
return this.updateFields(id, data, options);
|
|
1595
|
+
}
|
|
1596
|
+
/** One update of a row's fields; a `where` precondition that no longer holds throws its OptimisticLockError. */
|
|
1597
|
+
async updateFields(id, data, options) {
|
|
1598
|
+
const updated = await this.updateWithConditions(id, data, options);
|
|
1599
|
+
if (updated instanceof OptimisticLockError)
|
|
1600
|
+
throw updated;
|
|
1601
|
+
return updated;
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* One conditional update: the updated record, or the optimistic-lock
|
|
1605
|
+
* conflict when its precondition no longer holds (the drizzle record's
|
|
1606
|
+
* same contract), so a guarded bulk write skips a conflicting row without
|
|
1607
|
+
* catching anything.
|
|
1608
|
+
*/
|
|
1609
|
+
async updateWithConditions(id, data, options, bulkWhere) {
|
|
646
1610
|
await this.ensureInitialized();
|
|
647
1611
|
// Get existing record
|
|
648
1612
|
const existing = await this.get(id);
|
|
649
1613
|
if (!existing) {
|
|
650
|
-
throw new
|
|
1614
|
+
throw new CapabilityError(`Record with ${this.primaryKey}=${id} not found in ${this.tableName}`, 'NOT_FOUND', {
|
|
1615
|
+
reason: 'record-absent',
|
|
1616
|
+
retryable: false,
|
|
1617
|
+
details: { tableName: this.tableName, id }
|
|
1618
|
+
});
|
|
651
1619
|
}
|
|
652
1620
|
// Merge updates
|
|
653
|
-
const
|
|
1621
|
+
const snapshot = this.fieldsOf(structuredClone(existing));
|
|
1622
|
+
const updated = this.fieldsOf({ ...existing, ...data });
|
|
1623
|
+
const additions = applySetAdditions(updated, data, options);
|
|
654
1624
|
// Auto-manage timestamps
|
|
655
1625
|
if (this.descriptor.timestamps) {
|
|
656
1626
|
applyUpdateTimestamp(updated, data, this.descriptor.timestamps, new Date().toISOString());
|
|
@@ -663,34 +1633,105 @@ export class StorageRecord {
|
|
|
663
1633
|
}
|
|
664
1634
|
}
|
|
665
1635
|
// Execute beforeUpdate hook
|
|
666
|
-
|
|
667
|
-
|
|
1636
|
+
await this.runHook('beforeUpdate', updated);
|
|
1637
|
+
this.fieldsOf(updated);
|
|
1638
|
+
// A record's identity is its key column, and the stored JSON carries the same id; a filter
|
|
1639
|
+
// on the primary key reads the column. An update (or a beforeUpdate hook) naming another
|
|
1640
|
+
// primary key is refused, so the two never diverge.
|
|
1641
|
+
const nextKey = updated[this.primaryKey];
|
|
1642
|
+
if (nextKey !== undefined && nextKey !== null && String(nextKey) !== id) {
|
|
1643
|
+
throw new CapabilityError('A record update cannot change its primary key.', 'INVALID_INPUT', {
|
|
1644
|
+
reason: 'record-primary-key-immutable',
|
|
1645
|
+
retryable: false,
|
|
1646
|
+
details: { recordKind: this.descriptor.id, field: this.primaryKey }
|
|
1647
|
+
});
|
|
668
1648
|
}
|
|
669
|
-
// Update in database
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
//
|
|
673
|
-
|
|
674
|
-
|
|
1649
|
+
// Update in database. Honor the declared `where` precondition
|
|
1650
|
+
// (UpdateOptions.where — "update only if conditions match") ATOMICALLY:
|
|
1651
|
+
// the precondition rides in the UPDATE's WHERE, so SQLite evaluates it
|
|
1652
|
+
// against the current row at write time in a single statement. If a
|
|
1653
|
+
// concurrent writer moved the row between our get() and here, 0 rows
|
|
1654
|
+
// match → changes === 0 → conflict. This is a real compare-and-set, not
|
|
1655
|
+
// a read-check-write (which would leave a TOCTOU window — two writers
|
|
1656
|
+
// both passing an in-memory check before either writes). Single-record
|
|
1657
|
+
// update() previously ignored this param entirely (silent unconditional
|
|
1658
|
+
// write); the lost-update hazard it exists to prevent (TF-05) is now
|
|
1659
|
+
// actually closed for every record type.
|
|
1660
|
+
const patch = updatePatch(snapshot, updated, data, additions);
|
|
1661
|
+
const argumentsSql = [];
|
|
1662
|
+
const params = [];
|
|
1663
|
+
const removals = [];
|
|
1664
|
+
for (const [field, value] of Object.entries(patch)) {
|
|
1665
|
+
const path = `$.${JSON.stringify(field)}`;
|
|
1666
|
+
if (value === undefined) {
|
|
1667
|
+
removals.push(path);
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
argumentsSql.push('?, json(?)');
|
|
1671
|
+
params.push(path, JSON.stringify(value));
|
|
1672
|
+
}
|
|
1673
|
+
for (const [field, value] of Object.entries(additions)) {
|
|
1674
|
+
const path = `$.${field}`;
|
|
1675
|
+
argumentsSql.push(`?, json(CASE WHEN EXISTS (
|
|
1676
|
+
SELECT 1 FROM json_each(COALESCE(json_extract(data, ?), '[]'))
|
|
1677
|
+
WHERE type = 'text' AND value = ?
|
|
1678
|
+
) THEN COALESCE(json_extract(data, ?), '[]')
|
|
1679
|
+
ELSE json_insert(COALESCE(json_extract(data, ?), '[]'), '$[#]', ?) END)`);
|
|
1680
|
+
params.push(path, path, value, path, path, value);
|
|
1681
|
+
}
|
|
1682
|
+
let expression = argumentsSql.length ? `json_set(data, ${argumentsSql.join(', ')})` : 'data';
|
|
1683
|
+
if (removals.length) {
|
|
1684
|
+
expression = `json_remove(${expression}, ${removals.map(() => '?').join(', ')})`;
|
|
1685
|
+
params.push(...removals);
|
|
1686
|
+
}
|
|
1687
|
+
let revisionAssignment = '';
|
|
1688
|
+
if (this.descriptor.versioning) {
|
|
1689
|
+
revisionAssignment = `, ${RECORD_REVISION_COLUMN} = ?`;
|
|
1690
|
+
params.push(crypto.randomUUID());
|
|
1691
|
+
}
|
|
1692
|
+
let sql = `UPDATE ${this.tableName} SET data = ${expression}${revisionAssignment} WHERE ${this.primaryKey} = ?`;
|
|
1693
|
+
params.push(id);
|
|
1694
|
+
// Build the bulk and additional preconditions independently: overlapping
|
|
1695
|
+
// fields must both match the live row, never overwrite each other.
|
|
1696
|
+
for (const where of [bulkWhere, options?.where]) {
|
|
1697
|
+
if (!where)
|
|
1698
|
+
continue;
|
|
1699
|
+
const { clause, values } = this.buildWhereClause(where);
|
|
1700
|
+
if (clause) {
|
|
1701
|
+
sql += ` AND (${clause})`;
|
|
1702
|
+
params.push(...values);
|
|
1703
|
+
}
|
|
675
1704
|
}
|
|
676
|
-
this.
|
|
677
|
-
|
|
1705
|
+
const db = await this.acquireConnection();
|
|
1706
|
+
const result = db.prepare(`${sql} RETURNING data`).get(...params);
|
|
1707
|
+
const precondition = options?.where ?? bulkWhere;
|
|
1708
|
+
if (precondition && !result) {
|
|
1709
|
+
// The row exists (get() above) but the precondition no longer holds.
|
|
1710
|
+
return new OptimisticLockError(this.tableName, String(id), precondition);
|
|
1711
|
+
}
|
|
1712
|
+
if (!result)
|
|
1713
|
+
throw new Error(`Record with ${this.primaryKey}=${id} not found in ${this.tableName}`);
|
|
1714
|
+
// RETURNING reads the same JSON record just patched by SQLite.
|
|
1715
|
+
const persisted = this.decodeFields(result.data);
|
|
1716
|
+
// Execute afterUpdate hook
|
|
1717
|
+
await this.runHook('afterUpdate', persisted);
|
|
1718
|
+
this.emitLifecycle('afterUpdate', persisted);
|
|
1719
|
+
return this.recordOf(persisted);
|
|
678
1720
|
}
|
|
679
1721
|
async updateMany(where, data, options) {
|
|
680
1722
|
await this.ensureInitialized();
|
|
681
1723
|
// Find matching records
|
|
682
|
-
const result = await this.
|
|
1724
|
+
const result = await this.listRecords({ where });
|
|
683
1725
|
const ids = result.items.map((item) => this.readPrimaryKey(item));
|
|
684
|
-
// Update each record
|
|
1726
|
+
// Update each record under the same filter, so a row that stopped
|
|
1727
|
+
// matching between the list and its update (another writer claimed it)
|
|
1728
|
+
// is skipped instead of overwritten: updateMany is a guarded write.
|
|
685
1729
|
let count = 0;
|
|
686
1730
|
for (const id of ids) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
catch (error) {
|
|
692
|
-
logger.error(`[StorageRecord] Failed to update record ${id}:`, toErrorContext(error));
|
|
693
|
-
}
|
|
1731
|
+
const updated = await this.updateWithConditions(id, data, options, where);
|
|
1732
|
+
if (updated instanceof OptimisticLockError)
|
|
1733
|
+
continue;
|
|
1734
|
+
count++;
|
|
694
1735
|
}
|
|
695
1736
|
return { count, success: count > 0 };
|
|
696
1737
|
}
|
|
@@ -700,75 +1741,131 @@ export class StorageRecord {
|
|
|
700
1741
|
return this.update(id, update);
|
|
701
1742
|
}
|
|
702
1743
|
else {
|
|
703
|
-
return this.
|
|
1744
|
+
return this.createFields(this.fieldsOf({ ...create, [this.primaryKey]: id }));
|
|
704
1745
|
}
|
|
705
1746
|
}
|
|
706
1747
|
// DELETE
|
|
707
1748
|
async delete(id, options) {
|
|
1749
|
+
return this.deleteWithConditions(id, options);
|
|
1750
|
+
}
|
|
1751
|
+
async deleteWithConditions(id, options, bulkWhere) {
|
|
708
1752
|
await this.ensureInitialized();
|
|
1753
|
+
const predicates = [`${this.primaryKey} = ?`];
|
|
1754
|
+
const values = [id];
|
|
1755
|
+
for (const where of [bulkWhere, options?.where]) {
|
|
1756
|
+
if (where === undefined)
|
|
1757
|
+
continue;
|
|
1758
|
+
const condition = this.buildDeleteWhereClause(where);
|
|
1759
|
+
if (condition.clause) {
|
|
1760
|
+
predicates.push(`(${condition.clause})`);
|
|
1761
|
+
values.push(...condition.values);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
709
1764
|
// Check if exists
|
|
710
|
-
let existing =
|
|
1765
|
+
let existing = null;
|
|
1766
|
+
const live = await this.get(id, { include: undefined });
|
|
1767
|
+
if (live)
|
|
1768
|
+
existing = this.fieldsOf(live);
|
|
711
1769
|
if (!existing && this.descriptor.softDelete && options?.force) {
|
|
712
|
-
const
|
|
713
|
-
|
|
1770
|
+
const forceDb = await this.acquireConnection();
|
|
1771
|
+
const row = forceDb.prepare(`SELECT data FROM ${this.tableName} WHERE ${this.primaryKey} = ?`).get(id);
|
|
1772
|
+
existing = row ? this.decodeFields(row.data) : null;
|
|
714
1773
|
}
|
|
715
1774
|
if (!existing) {
|
|
716
1775
|
return { count: 0, success: false };
|
|
717
1776
|
}
|
|
718
1777
|
// Execute beforeDelete hook
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
1778
|
+
await this.runHook('beforeDelete', existing);
|
|
1779
|
+
// Patch only deletion fields against live JSON; a before hook may yield to a peer.
|
|
1780
|
+
let sql;
|
|
1781
|
+
let parameters;
|
|
1782
|
+
const deletedAtField = this.softDeleteField();
|
|
1783
|
+
if (deletedAtField && !options?.force) {
|
|
1784
|
+
const now = new Date().toISOString();
|
|
1785
|
+
const deleted = { [deletedAtField]: now };
|
|
1786
|
+
applyUpdateTimestamp(deleted, {}, this.descriptor.timestamps, now);
|
|
1787
|
+
const assignments = Object.entries(deleted);
|
|
1788
|
+
sql = `UPDATE ${this.tableName} SET data = json_set(data, ${assignments.map(() => '?, json(?)').join(', ')})`;
|
|
1789
|
+
parameters = assignments.flatMap(([field, value]) => [`$.${JSON.stringify(field)}`, JSON.stringify(value)]);
|
|
1790
|
+
if (this.descriptor.versioning) {
|
|
1791
|
+
sql += `, ${RECORD_REVISION_COLUMN} = ?`;
|
|
1792
|
+
parameters.push(crypto.randomUUID());
|
|
1793
|
+
}
|
|
731
1794
|
}
|
|
732
1795
|
else {
|
|
733
|
-
|
|
1796
|
+
sql = `DELETE FROM ${this.tableName}`;
|
|
1797
|
+
parameters = [];
|
|
734
1798
|
}
|
|
1799
|
+
const db = await this.acquireConnection();
|
|
1800
|
+
const result = db.prepare(`${sql} WHERE ${predicates.join(' AND ')} RETURNING data`).get(...parameters, ...values);
|
|
1801
|
+
if (!result)
|
|
1802
|
+
return { count: 0, success: false };
|
|
1803
|
+
// RETURNING carries the same JSON row actually mutated.
|
|
1804
|
+
const persisted = this.decodeFields(result.data);
|
|
735
1805
|
// Execute afterDelete hook
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
this.emitLifecycle('afterDelete', existing);
|
|
1806
|
+
await this.runHook('afterDelete', persisted);
|
|
1807
|
+
this.emitLifecycle('afterDelete', persisted);
|
|
740
1808
|
return { count: 1, success: true };
|
|
741
1809
|
}
|
|
742
1810
|
async deleteMany(where, options) {
|
|
743
1811
|
await this.ensureInitialized();
|
|
1812
|
+
const predicates = [];
|
|
1813
|
+
const values = [];
|
|
1814
|
+
for (const filter of [where, options?.where]) {
|
|
1815
|
+
if (filter === undefined)
|
|
1816
|
+
continue;
|
|
1817
|
+
const condition = this.buildDeleteWhereClause(filter);
|
|
1818
|
+
if (condition.clause) {
|
|
1819
|
+
predicates.push(`(${condition.clause})`);
|
|
1820
|
+
values.push(...condition.values);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
const physicalDelete = !this.descriptor.softDelete || Boolean(options?.force);
|
|
1824
|
+
// A forced delete must be able to see rows that are already soft-deleted.
|
|
1825
|
+
// `list()` appends `deletedAt IS NULL` unless told otherwise, so without
|
|
1826
|
+
// this a soft-deleted row can never be physically removed in bulk — soft
|
|
1827
|
+
// delete becomes a one-way trip and the table grows forever. Retention
|
|
1828
|
+
// that stamps at one window and reaps at a longer one depends on this.
|
|
1829
|
+
const includeDeleted = Boolean(options?.force);
|
|
1830
|
+
// Set-based fast path. The per-row loop below exists to run
|
|
1831
|
+
// beforeDelete/afterDelete hooks and emit per-record lifecycle events; a
|
|
1832
|
+
// descriptor with neither needs none of that, and paying one SELECT plus
|
|
1833
|
+
// one statement per row is ruinous at scale (a real retention sweep hit
|
|
1834
|
+
// ~500k rows, i.e. ~1M statements and 500k hook emissions, one
|
|
1835
|
+
// transaction each). Hook-bearing descriptors keep the old semantics.
|
|
1836
|
+
const hasDeleteHooks = Boolean(this.descriptor.beforeDelete || this.descriptor.afterDelete);
|
|
1837
|
+
if (physicalDelete && !hasDeleteHooks) {
|
|
1838
|
+
const clause = predicates.join(' AND ');
|
|
1839
|
+
const sql = clause
|
|
1840
|
+
? `DELETE FROM ${this.tableName} WHERE ${clause}`
|
|
1841
|
+
: `DELETE FROM ${this.tableName}`;
|
|
1842
|
+
const result = this.db.prepare(sql).run(...values);
|
|
1843
|
+
const count = Number(result?.changes ?? 0);
|
|
1844
|
+
return { count, success: count > 0 };
|
|
1845
|
+
}
|
|
744
1846
|
// Find matching records
|
|
745
|
-
const result = await this.
|
|
1847
|
+
const result = await this.listRecords({ where, includeDeleted });
|
|
746
1848
|
const ids = result.items.map((item) => this.readPrimaryKey(item));
|
|
747
1849
|
// Delete each record
|
|
748
1850
|
let count = 0;
|
|
749
1851
|
for (const id of ids) {
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
count += deleteResult.count;
|
|
753
|
-
}
|
|
754
|
-
catch (error) {
|
|
755
|
-
logger.error(`[StorageRecord] Failed to delete record ${id}:`, toErrorContext(error));
|
|
756
|
-
}
|
|
1852
|
+
const deleteResult = await this.deleteWithConditions(id, options, where);
|
|
1853
|
+
count += deleteResult.count;
|
|
757
1854
|
}
|
|
758
1855
|
return { count, success: count > 0 };
|
|
759
1856
|
}
|
|
760
1857
|
async restore(id) {
|
|
761
1858
|
await this.ensureInitialized();
|
|
762
|
-
|
|
1859
|
+
const deletedAtField = this.softDeleteField();
|
|
1860
|
+
if (!deletedAtField) {
|
|
763
1861
|
throw new Error(`Soft delete is not enabled for ${this.tableName}`);
|
|
764
1862
|
}
|
|
765
|
-
const deletedAtField = typeof this.descriptor.softDelete === 'string' ? this.descriptor.softDelete : 'deletedAt';
|
|
766
1863
|
// Get record (including soft-deleted)
|
|
767
|
-
const row = this.db.
|
|
1864
|
+
const row = this.db.prepare(`SELECT data FROM ${this.tableName} WHERE ${this.primaryKey} = ?`).get(id);
|
|
768
1865
|
if (!row) {
|
|
769
1866
|
throw new Error(`Record with ${this.primaryKey}=${id} not found in ${this.tableName}`);
|
|
770
1867
|
}
|
|
771
|
-
const record =
|
|
1868
|
+
const record = this.decodeFields(row.data);
|
|
772
1869
|
if (!record[deletedAtField]) {
|
|
773
1870
|
throw new Error(`Record ${id} is not soft-deleted`);
|
|
774
1871
|
}
|
|
@@ -776,15 +1873,16 @@ export class StorageRecord {
|
|
|
776
1873
|
delete record[deletedAtField];
|
|
777
1874
|
// Update timestamp
|
|
778
1875
|
if (this.descriptor.timestamps) {
|
|
779
|
-
|
|
780
|
-
typeof this.descriptor.timestamps.updatedAt === 'string'
|
|
781
|
-
? this.descriptor.timestamps.updatedAt
|
|
782
|
-
: 'updatedAt';
|
|
783
|
-
record[updatedAtField] = new Date().toISOString();
|
|
1876
|
+
record[resolveTimestampField(this.descriptor.timestamps, 'updatedAt')] = new Date().toISOString();
|
|
784
1877
|
}
|
|
785
1878
|
const dataJson = JSON.stringify(record);
|
|
786
|
-
|
|
787
|
-
|
|
1879
|
+
if (this.descriptor.versioning) {
|
|
1880
|
+
this.db.prepare(`UPDATE ${this.tableName} SET data = ?, ${RECORD_REVISION_COLUMN} = ? WHERE ${this.primaryKey} = ?`).run(dataJson, crypto.randomUUID(), id);
|
|
1881
|
+
}
|
|
1882
|
+
else {
|
|
1883
|
+
this.db.prepare(`UPDATE ${this.tableName} SET data = ? WHERE ${this.primaryKey} = ?`).run(dataJson, id);
|
|
1884
|
+
}
|
|
1885
|
+
return this.recordOf(record);
|
|
788
1886
|
}
|
|
789
1887
|
// AGGREGATION
|
|
790
1888
|
async aggregate(options) {
|
|
@@ -834,36 +1932,72 @@ export class StorageRecord {
|
|
|
834
1932
|
.join(', ');
|
|
835
1933
|
sql += ` GROUP BY ${groupByExpr}`;
|
|
836
1934
|
}
|
|
837
|
-
const rows = this.db.
|
|
1935
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
838
1936
|
return rows.map((row) => ({
|
|
839
1937
|
value: row.value || 0
|
|
840
1938
|
}));
|
|
841
1939
|
}
|
|
842
1940
|
// TRANSACTIONS
|
|
843
1941
|
async transaction(fn) {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1942
|
+
const runtime = this.getRuntime();
|
|
1943
|
+
const db = this.db ?? (await this.getDatabase());
|
|
1944
|
+
this.db = db;
|
|
1945
|
+
const existingFrame = getActiveTransactionFrame(db, runtime);
|
|
1946
|
+
if (existingFrame) {
|
|
1947
|
+
await this.ensureInitialized();
|
|
1948
|
+
return runInSavepoint(runtime, existingFrame, db, async () => {
|
|
1949
|
+
const tx = {
|
|
1950
|
+
id: `tx-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
1951
|
+
startTime: Date.now(),
|
|
1952
|
+
isActive: true
|
|
1953
|
+
};
|
|
1954
|
+
try {
|
|
1955
|
+
return await fn(tx);
|
|
1956
|
+
}
|
|
1957
|
+
finally {
|
|
1958
|
+
tx.isActive = false;
|
|
1959
|
+
}
|
|
1960
|
+
});
|
|
847
1961
|
}
|
|
848
|
-
|
|
849
|
-
this.
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1962
|
+
const state = getConnectionTransactionState(db);
|
|
1963
|
+
while (state.leased || !this.initialized) {
|
|
1964
|
+
if (state.leased) {
|
|
1965
|
+
await new Promise((resolve) => {
|
|
1966
|
+
state.waiters.push(resolve);
|
|
1967
|
+
});
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
await this.initialize();
|
|
1971
|
+
}
|
|
1972
|
+
state.leased = true;
|
|
855
1973
|
try {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1974
|
+
return await withTransactionFrame(runtime, state, null, async (frame) => {
|
|
1975
|
+
const tx = {
|
|
1976
|
+
id: `tx-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
1977
|
+
startTime: Date.now(),
|
|
1978
|
+
isActive: true
|
|
1979
|
+
};
|
|
1980
|
+
db.exec('BEGIN TRANSACTION');
|
|
1981
|
+
try {
|
|
1982
|
+
const result = await fn(tx);
|
|
1983
|
+
db.exec('COMMIT');
|
|
1984
|
+
tx.isActive = false;
|
|
1985
|
+
return result;
|
|
1986
|
+
}
|
|
1987
|
+
catch (error) {
|
|
1988
|
+
db.exec('ROLLBACK');
|
|
1989
|
+
tx.isActive = false;
|
|
1990
|
+
flushRollbackCallbacks(frame);
|
|
1991
|
+
throw error;
|
|
1992
|
+
}
|
|
1993
|
+
});
|
|
861
1994
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1995
|
+
finally {
|
|
1996
|
+
state.leased = false;
|
|
1997
|
+
const waiters = state.waiters.splice(0);
|
|
1998
|
+
for (const wake of waiters) {
|
|
1999
|
+
wake();
|
|
2000
|
+
}
|
|
867
2001
|
}
|
|
868
2002
|
}
|
|
869
2003
|
// STREAMING
|
|
@@ -871,11 +2005,12 @@ export class StorageRecord {
|
|
|
871
2005
|
await this.ensureInitialized();
|
|
872
2006
|
const batchSize = options?.batchSize || 100;
|
|
873
2007
|
let offset = options?.offset || 0;
|
|
2008
|
+
// StreamOptions extends QueryOptions but adds batchSize; `batchSize` is
|
|
2009
|
+
// stream-only — strip it before passing the rest to list().
|
|
2010
|
+
const streamOptions = options ?? {};
|
|
2011
|
+
const { batchSize: _strip, ...listOptions } = streamOptions;
|
|
874
2012
|
while (true) {
|
|
875
|
-
|
|
876
|
-
// for the list() call below, but `batchSize` is stream-only — strip it before passing along.
|
|
877
|
-
const { batchSize: _strip, ...listOptions } = (options ?? {});
|
|
878
|
-
const result = await this.list({
|
|
2013
|
+
const result = await this.listRecords({
|
|
879
2014
|
...listOptions,
|
|
880
2015
|
limit: batchSize,
|
|
881
2016
|
offset
|
|
@@ -900,12 +2035,12 @@ export class StorageRecord {
|
|
|
900
2035
|
let result;
|
|
901
2036
|
switch (op.type) {
|
|
902
2037
|
case 'create':
|
|
903
|
-
result = await this.
|
|
2038
|
+
result = await this.createFields({ ...this.fieldsOf(op.data) });
|
|
904
2039
|
break;
|
|
905
2040
|
case 'update':
|
|
906
2041
|
if (!op.id)
|
|
907
2042
|
throw new Error('ID required for update');
|
|
908
|
-
result = await this.
|
|
2043
|
+
result = await this.updateFields(op.id, this.fieldsOf(op.data));
|
|
909
2044
|
break;
|
|
910
2045
|
case 'delete':
|
|
911
2046
|
if (!op.id)
|
|
@@ -920,16 +2055,12 @@ export class StorageRecord {
|
|
|
920
2055
|
}
|
|
921
2056
|
// UTILITIES
|
|
922
2057
|
async validate(data) {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
return { success: false, errors: error };
|
|
930
|
-
}
|
|
931
|
-
return { success: false, errors: undefined };
|
|
932
|
-
}
|
|
2058
|
+
// A schema refusal is the validation result; any other failure while
|
|
2059
|
+
// validating (a refinement that throws) is not a result and propagates.
|
|
2060
|
+
const result = this.schema.safeParse(data);
|
|
2061
|
+
if (result.success)
|
|
2062
|
+
return { success: true, data: result.data };
|
|
2063
|
+
return { success: false, errors: result.error };
|
|
933
2064
|
}
|
|
934
2065
|
getAdapterId() {
|
|
935
2066
|
if (this.adapterCache) {
|
|
@@ -943,12 +2074,30 @@ export class StorageRecord {
|
|
|
943
2074
|
}
|
|
944
2075
|
// PRIVATE HELPERS
|
|
945
2076
|
async ensureInitialized() {
|
|
946
|
-
|
|
2077
|
+
const runtime = this.getRuntime();
|
|
2078
|
+
const db = this.db ?? (await this.getDatabase());
|
|
2079
|
+
this.db = db;
|
|
2080
|
+
const state = connectionTransactions.get(db);
|
|
2081
|
+
while ((state?.leased && getActiveTransactionFrame(db, runtime) === null) || !this.initialized) {
|
|
2082
|
+
if (state?.leased && getActiveTransactionFrame(db, runtime) === null) {
|
|
2083
|
+
await new Promise((resolve) => {
|
|
2084
|
+
state.waiters.push(resolve);
|
|
2085
|
+
});
|
|
2086
|
+
continue;
|
|
2087
|
+
}
|
|
947
2088
|
await this.initialize();
|
|
948
2089
|
}
|
|
949
2090
|
}
|
|
2091
|
+
async acquireConnection() {
|
|
2092
|
+
if (!this.db) {
|
|
2093
|
+
throw new Error('Database not initialized');
|
|
2094
|
+
}
|
|
2095
|
+
await waitForConnectionLease(this.db, this.getRuntime());
|
|
2096
|
+
return this.db;
|
|
2097
|
+
}
|
|
950
2098
|
generateId() {
|
|
951
|
-
const
|
|
2099
|
+
const autoId = this.autoIdOptions();
|
|
2100
|
+
const strategy = autoId ? autoId.strategy : 'uuid';
|
|
952
2101
|
if (strategy === 'uuid') {
|
|
953
2102
|
return crypto.randomUUID();
|
|
954
2103
|
}
|
|
@@ -961,66 +2110,241 @@ export class StorageRecord {
|
|
|
961
2110
|
}
|
|
962
2111
|
return id;
|
|
963
2112
|
}
|
|
964
|
-
else if (strategy === 'custom' &&
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
2113
|
+
else if (strategy === 'custom' && autoId?.generator) {
|
|
2114
|
+
const refusal = {
|
|
2115
|
+
message: `Storage record '${this.descriptor.id}' declares a custom id generator that does not produce a string id.`,
|
|
2116
|
+
code: 'INVALID_INPUT',
|
|
2117
|
+
reason: 'record-id-generator-invalid',
|
|
2118
|
+
details: { recordKind: this.descriptor.id }
|
|
2119
|
+
};
|
|
2120
|
+
const generator = parseAdapterInput(IdGeneratorSchema, autoId.generator, refusal);
|
|
2121
|
+
return parseAdapterInput(GeneratedIdSchema, generator(), refusal);
|
|
968
2122
|
}
|
|
969
2123
|
return crypto.randomUUID();
|
|
970
2124
|
}
|
|
2125
|
+
buildDeleteWhereClause(where) {
|
|
2126
|
+
const scalarSchema = z.union([z.string(), z.number(), z.boolean(), z.null(), z.date()]);
|
|
2127
|
+
const filter = z.record(z.string(), z.unknown()).safeParse(where);
|
|
2128
|
+
if (!filter.success) {
|
|
2129
|
+
throw new CapabilityError('Deletion filters must be field objects.', 'INVALID_INPUT', { reason: 'record-delete-filter-invalid', retryable: false });
|
|
2130
|
+
}
|
|
2131
|
+
for (const [field, condition] of Object.entries(filter.data)) {
|
|
2132
|
+
const invalid = () => { throw new CapabilityError(`Invalid deletion filter for '${field}'.`, 'INVALID_INPUT', { reason: 'record-delete-filter-invalid', retryable: false }); };
|
|
2133
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field) || field === '__proto__')
|
|
2134
|
+
invalid();
|
|
2135
|
+
if (scalarSchema.safeParse(condition).success)
|
|
2136
|
+
continue;
|
|
2137
|
+
const comparison = z.record(z.string(), z.unknown()).safeParse(condition);
|
|
2138
|
+
if (!comparison.success)
|
|
2139
|
+
return invalid();
|
|
2140
|
+
const operators = Object.entries(comparison.data);
|
|
2141
|
+
if (!operators.length)
|
|
2142
|
+
invalid();
|
|
2143
|
+
for (const [operator, value] of operators) {
|
|
2144
|
+
if (!RecordFilterOperatorSchema.safeParse(operator).success)
|
|
2145
|
+
invalid();
|
|
2146
|
+
if (operator === 'hasAll' && !z.array(z.string()).min(1).safeParse(value).success)
|
|
2147
|
+
invalid();
|
|
2148
|
+
if (['eq', 'ne', 'gt', 'gte', 'lt', 'lte'].includes(operator) && !scalarSchema.safeParse(value).success)
|
|
2149
|
+
invalid();
|
|
2150
|
+
if (['like', 'ilike', 'contains', 'startsWith', 'endsWith'].includes(operator) && !z.string().safeParse(value).success)
|
|
2151
|
+
invalid();
|
|
2152
|
+
if ((operator === 'in' || operator === 'notIn') && !z.array(scalarSchema).safeParse(value).success)
|
|
2153
|
+
invalid();
|
|
2154
|
+
if (operator === 'between' && !z.tuple([scalarSchema, scalarSchema]).safeParse(value).success)
|
|
2155
|
+
invalid();
|
|
2156
|
+
if ((operator === 'isNull' || operator === 'isNotNull') && !z.boolean().safeParse(value).success)
|
|
2157
|
+
invalid();
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
return this.buildWhereClause(where);
|
|
2161
|
+
}
|
|
2162
|
+
encodeDateFilterOperand(value) {
|
|
2163
|
+
if (!(value instanceof Date))
|
|
2164
|
+
return value;
|
|
2165
|
+
const date = z.date().safeParse(value);
|
|
2166
|
+
if (!date.success) {
|
|
2167
|
+
throw new CapabilityError('Date filter operands must be valid dates.', 'INVALID_INPUT', {
|
|
2168
|
+
reason: 'record-filter-date-invalid', retryable: false
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
return date.data.toISOString();
|
|
2172
|
+
}
|
|
2173
|
+
/** json_extract returns object/array fields as their persisted JSON text. */
|
|
2174
|
+
encodeEqualityFilterOperand(value) {
|
|
2175
|
+
const structured = JsonFilterOperandSchema.safeParse(value);
|
|
2176
|
+
if (structured.success)
|
|
2177
|
+
return JSON.stringify(structured.data);
|
|
2178
|
+
return this.encodeDateFilterOperand(value);
|
|
2179
|
+
}
|
|
2180
|
+
/**
|
|
2181
|
+
* The SQL a filter on `field` reads. The primary key is the table's own key
|
|
2182
|
+
* column, which always equals the stored record's id, so filtering on it is an
|
|
2183
|
+
* index lookup; every other field is read from the stored JSON. Before this,
|
|
2184
|
+
* an id filter scanned the whole table through json_extract, which made a
|
|
2185
|
+
* per-row guarded update quadratic in the table size.
|
|
2186
|
+
*/
|
|
2187
|
+
filterColumn(field) {
|
|
2188
|
+
const path = `$.${field}`.replaceAll("'", "''");
|
|
2189
|
+
return field === this.primaryKey ? this.primaryKey : `json_extract(data, '${path}')`;
|
|
2190
|
+
}
|
|
2191
|
+
/** The SQL type of `field` in the stored JSON (`NULL` when the key is absent, `'null'` when JSON `null`). */
|
|
2192
|
+
filterFieldType(field) {
|
|
2193
|
+
const path = `$.${field}`.replaceAll("'", "''");
|
|
2194
|
+
return field === this.primaryKey ? `typeof(${this.primaryKey})` : `json_type(data, '${path}')`;
|
|
2195
|
+
}
|
|
971
2196
|
buildWhereClause(where) {
|
|
972
2197
|
const conditions = [];
|
|
973
2198
|
const values = [];
|
|
974
|
-
|
|
975
|
-
|
|
2199
|
+
const filter = parseAdapterInput(FilterFieldsViewSchema, where, {
|
|
2200
|
+
message: 'Record filters must be field objects.',
|
|
2201
|
+
code: 'INVALID_INPUT',
|
|
2202
|
+
reason: 'record-filter-invalid',
|
|
2203
|
+
details: { recordKind: this.descriptor.id }
|
|
2204
|
+
});
|
|
2205
|
+
for (const [field, condition] of Object.entries(filter)) {
|
|
2206
|
+
// An operator object (`{ eq, in, … }`) is a complex condition; a Date or any other value is equality.
|
|
2207
|
+
const operators = FilterOperatorsSchema.safeParse(condition);
|
|
2208
|
+
if (operators.success) {
|
|
976
2209
|
// Complex condition
|
|
977
|
-
const cond =
|
|
2210
|
+
const cond = operators.data;
|
|
2211
|
+
const refuse = (check) => {
|
|
2212
|
+
throw new CapabilityError('Record filter clause cannot be enforced.', 'INVALID_INPUT', {
|
|
2213
|
+
reason: 'record-filter-invalid',
|
|
2214
|
+
retryable: false,
|
|
2215
|
+
details: { recordKind: this.descriptor.id, field, check }
|
|
2216
|
+
});
|
|
2217
|
+
};
|
|
2218
|
+
const operatorKeys = Object.keys(cond);
|
|
2219
|
+
// An operator object with no operator constrains nothing: refused, never read as "every row".
|
|
2220
|
+
if (!operatorKeys.length)
|
|
2221
|
+
refuse('empty-comparison');
|
|
2222
|
+
for (const operator of operatorKeys) {
|
|
2223
|
+
if (!RecordFilterOperatorSchema.safeParse(operator).success)
|
|
2224
|
+
refuse('unknown-operator');
|
|
2225
|
+
}
|
|
2226
|
+
if ('in' in cond && !Array.isArray(cond.in))
|
|
2227
|
+
refuse('array-operand-required');
|
|
2228
|
+
if ('notIn' in cond && !Array.isArray(cond.notIn))
|
|
2229
|
+
refuse('array-operand-required');
|
|
2230
|
+
if ('between' in cond && !(Array.isArray(cond.between) && cond.between.length === 2))
|
|
2231
|
+
refuse('two-bounds-required');
|
|
2232
|
+
if ('hasAll' in cond) {
|
|
2233
|
+
for (const member of assertArrayMembers(field, cond.hasAll)) {
|
|
2234
|
+
conditions.push(`EXISTS (SELECT 1 FROM json_each(COALESCE(json_extract(data, ?), '[]')) WHERE type = 'text' AND value = ?)`);
|
|
2235
|
+
values.push(`$.${field}`, member);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
if ('hasNot' in cond) {
|
|
2239
|
+
assertArrayField(field, cond.hasNot);
|
|
2240
|
+
conditions.push(`NOT EXISTS (SELECT 1 FROM json_each(COALESCE(json_extract(data, ?), '[]')) WHERE type = 'text' AND value = ?)`);
|
|
2241
|
+
values.push(`$.${field}`, cond.hasNot);
|
|
2242
|
+
}
|
|
978
2243
|
if ('eq' in cond) {
|
|
979
|
-
|
|
980
|
-
|
|
2244
|
+
if (cond.eq === undefined) {
|
|
2245
|
+
conditions.push(`${this.filterFieldType(field)} IS NULL`);
|
|
2246
|
+
}
|
|
2247
|
+
else {
|
|
2248
|
+
conditions.push(`${this.filterColumn(field)} ${cond.eq === null ? 'IS NULL' : '= ?'}`);
|
|
2249
|
+
if (cond.eq !== null)
|
|
2250
|
+
values.push(this.encodeEqualityFilterOperand(cond.eq));
|
|
2251
|
+
}
|
|
981
2252
|
}
|
|
982
2253
|
if ('ne' in cond) {
|
|
983
|
-
|
|
984
|
-
|
|
2254
|
+
if (cond.ne === undefined) {
|
|
2255
|
+
conditions.push(`${this.filterFieldType(field)} IS NOT NULL`);
|
|
2256
|
+
}
|
|
2257
|
+
else {
|
|
2258
|
+
conditions.push(`${this.filterColumn(field)} ${cond.ne === null ? 'IS NOT NULL' : '!= ?'}`);
|
|
2259
|
+
if (cond.ne !== null)
|
|
2260
|
+
values.push(this.encodeEqualityFilterOperand(cond.ne));
|
|
2261
|
+
}
|
|
985
2262
|
}
|
|
986
2263
|
if ('gt' in cond) {
|
|
987
|
-
conditions.push(
|
|
988
|
-
values.push(cond.gt);
|
|
2264
|
+
conditions.push(`${this.filterColumn(field)} > ?`);
|
|
2265
|
+
values.push(this.encodeDateFilterOperand(cond.gt));
|
|
989
2266
|
}
|
|
990
2267
|
if ('gte' in cond) {
|
|
991
|
-
conditions.push(
|
|
992
|
-
values.push(cond.gte);
|
|
2268
|
+
conditions.push(`${this.filterColumn(field)} >= ?`);
|
|
2269
|
+
values.push(this.encodeDateFilterOperand(cond.gte));
|
|
993
2270
|
}
|
|
994
2271
|
if ('lt' in cond) {
|
|
995
|
-
conditions.push(
|
|
996
|
-
values.push(cond.lt);
|
|
2272
|
+
conditions.push(`${this.filterColumn(field)} < ?`);
|
|
2273
|
+
values.push(this.encodeDateFilterOperand(cond.lt));
|
|
997
2274
|
}
|
|
998
2275
|
if ('lte' in cond) {
|
|
999
|
-
conditions.push(
|
|
1000
|
-
values.push(cond.lte);
|
|
2276
|
+
conditions.push(`${this.filterColumn(field)} <= ?`);
|
|
2277
|
+
values.push(this.encodeDateFilterOperand(cond.lte));
|
|
1001
2278
|
}
|
|
1002
2279
|
if ('in' in cond && Array.isArray(cond.in)) {
|
|
1003
2280
|
const placeholders = cond.in.map(() => '?').join(', ');
|
|
1004
|
-
conditions.push(
|
|
1005
|
-
values.push(...cond.in);
|
|
2281
|
+
conditions.push(`${this.filterColumn(field)} IN (${placeholders})`);
|
|
2282
|
+
values.push(...cond.in.map(value => this.encodeEqualityFilterOperand(value)));
|
|
1006
2283
|
}
|
|
1007
2284
|
if ('like' in cond) {
|
|
1008
|
-
conditions.push(
|
|
2285
|
+
conditions.push(`${this.filterColumn(field)} LIKE ?`);
|
|
1009
2286
|
values.push(cond.like);
|
|
1010
2287
|
}
|
|
1011
2288
|
if ('isNull' in cond) {
|
|
1012
2289
|
if (cond.isNull) {
|
|
1013
|
-
conditions.push(
|
|
2290
|
+
conditions.push(`${this.filterColumn(field)} IS NULL`);
|
|
2291
|
+
}
|
|
2292
|
+
else {
|
|
2293
|
+
conditions.push(`${this.filterColumn(field)} IS NOT NULL`);
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
// Operators below were declared by WhereFilter but never built, so a
|
|
2297
|
+
// query using them was silently UNFILTERED — no error, just wrong
|
|
2298
|
+
// rows. Found when a retention sweep's `{ processedAt: { isNotNull:
|
|
2299
|
+
// true } }` guard was dropped and the delete matched 328,232 rows
|
|
2300
|
+
// instead of the ~148k processed ones, i.e. it would have removed
|
|
2301
|
+
// 178,639 unprocessed rows the caller had explicitly excluded.
|
|
2302
|
+
// A typed operator that no builder honours is worse than a missing
|
|
2303
|
+
// one: it reads as a guarantee.
|
|
2304
|
+
if ('isNotNull' in cond) {
|
|
2305
|
+
if (cond.isNotNull) {
|
|
2306
|
+
conditions.push(`${this.filterColumn(field)} IS NOT NULL`);
|
|
1014
2307
|
}
|
|
1015
2308
|
else {
|
|
1016
|
-
conditions.push(
|
|
2309
|
+
conditions.push(`${this.filterColumn(field)} IS NULL`);
|
|
1017
2310
|
}
|
|
1018
2311
|
}
|
|
2312
|
+
if ('notIn' in cond && Array.isArray(cond.notIn)) {
|
|
2313
|
+
const placeholders = cond.notIn.map(() => '?').join(', ');
|
|
2314
|
+
conditions.push(`${this.filterColumn(field)} NOT IN (${placeholders})`);
|
|
2315
|
+
values.push(...cond.notIn.map(value => this.encodeEqualityFilterOperand(value)));
|
|
2316
|
+
}
|
|
2317
|
+
if ('ilike' in cond) {
|
|
2318
|
+
// SQLite LIKE is case-insensitive for ASCII by default; lower()
|
|
2319
|
+
// both sides so the intent holds regardless of collation.
|
|
2320
|
+
conditions.push(`lower(${this.filterColumn(field)}) LIKE lower(?)`);
|
|
2321
|
+
values.push(cond.ilike);
|
|
2322
|
+
}
|
|
2323
|
+
if ('contains' in cond) {
|
|
2324
|
+
conditions.push(`${this.filterColumn(field)} LIKE ?`);
|
|
2325
|
+
values.push(`%${String(cond.contains)}%`);
|
|
2326
|
+
}
|
|
2327
|
+
if ('startsWith' in cond) {
|
|
2328
|
+
conditions.push(`${this.filterColumn(field)} LIKE ?`);
|
|
2329
|
+
values.push(`${String(cond.startsWith)}%`);
|
|
2330
|
+
}
|
|
2331
|
+
if ('endsWith' in cond) {
|
|
2332
|
+
conditions.push(`${this.filterColumn(field)} LIKE ?`);
|
|
2333
|
+
values.push(`%${String(cond.endsWith)}`);
|
|
2334
|
+
}
|
|
2335
|
+
if ('between' in cond && Array.isArray(cond.between) && cond.between.length === 2) {
|
|
2336
|
+
conditions.push(`${this.filterColumn(field)} BETWEEN ? AND ?`);
|
|
2337
|
+
values.push(this.encodeDateFilterOperand(cond.between[0]), this.encodeDateFilterOperand(cond.between[1]));
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
else if (condition === undefined) {
|
|
2341
|
+
conditions.push(`${this.filterFieldType(field)} IS NULL`);
|
|
1019
2342
|
}
|
|
1020
2343
|
else {
|
|
1021
|
-
//
|
|
1022
|
-
conditions.push(
|
|
1023
|
-
|
|
2344
|
+
// SQL equality with NULL never matches; nullable fields use the null predicate.
|
|
2345
|
+
conditions.push(`${this.filterColumn(field)} ${condition === null ? 'IS NULL' : '= ?'}`);
|
|
2346
|
+
if (condition !== null)
|
|
2347
|
+
values.push(this.encodeEqualityFilterOperand(condition));
|
|
1024
2348
|
}
|
|
1025
2349
|
}
|
|
1026
2350
|
return {
|
|
@@ -1031,181 +2355,19 @@ export class StorageRecord {
|
|
|
1031
2355
|
buildOrderBy(orderBy) {
|
|
1032
2356
|
if (Array.isArray(orderBy)) {
|
|
1033
2357
|
return orderBy
|
|
1034
|
-
.map((o) =>
|
|
2358
|
+
.map((o) => `${this.filterColumn(String(o.field))} ${o.direction}`)
|
|
1035
2359
|
.join(', ');
|
|
1036
2360
|
}
|
|
1037
2361
|
else {
|
|
1038
2362
|
const fields = Object.entries(orderBy).map(([field, direction]) => {
|
|
1039
|
-
return
|
|
2363
|
+
return `${this.filterColumn(field)} ${direction}`;
|
|
1040
2364
|
});
|
|
1041
2365
|
return fields.join(', ');
|
|
1042
2366
|
}
|
|
1043
2367
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1048
|
-
const relationNames = Array.isArray(include) ? include : Object.keys(include);
|
|
1049
|
-
for (const relationName of relationNames) {
|
|
1050
|
-
const relation = this.relations[relationName];
|
|
1051
|
-
if (!relation)
|
|
1052
|
-
continue;
|
|
1053
|
-
const related = await this.loadRelation(record, relation);
|
|
1054
|
-
this.asMutableRecord(record)[relationName] = related;
|
|
1055
|
-
}
|
|
1056
|
-
return record;
|
|
1057
|
-
}
|
|
1058
|
-
async loadRelation(record, relation) {
|
|
1059
|
-
// For now, we'll implement basic relation loading
|
|
1060
|
-
// This would be enhanced with proper relation resolution
|
|
1061
|
-
switch (relation.type) {
|
|
1062
|
-
case 'belongs_to': {
|
|
1063
|
-
const foreignKey = relation.foreignKey;
|
|
1064
|
-
if (!foreignKey)
|
|
1065
|
-
return null;
|
|
1066
|
-
const foreignId = this.readStringField(record, foreignKey);
|
|
1067
|
-
if (!foreignId)
|
|
1068
|
-
return null;
|
|
1069
|
-
// Resolving belongs_to requires runtime access to the target storage; unimplemented.
|
|
1070
|
-
return null;
|
|
1071
|
-
}
|
|
1072
|
-
case 'has_many': {
|
|
1073
|
-
// Resolving has_many requires runtime access to the target storage; unimplemented.
|
|
1074
|
-
return [];
|
|
1075
|
-
}
|
|
1076
|
-
case 'has_one': {
|
|
1077
|
-
// Resolving has_one requires runtime access to the target storage; unimplemented.
|
|
1078
|
-
return null;
|
|
1079
|
-
}
|
|
1080
|
-
case 'many_to_many': {
|
|
1081
|
-
// Implement many-to-many through junction table
|
|
1082
|
-
return await this.loadManyToManyRelation(record, relation);
|
|
1083
|
-
}
|
|
1084
|
-
default:
|
|
1085
|
-
return null;
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
/**
|
|
1089
|
-
* Load many-to-many relation through junction table
|
|
1090
|
-
*/
|
|
1091
|
-
async loadManyToManyRelation(record, relation) {
|
|
1092
|
-
if (!relation.through) {
|
|
1093
|
-
logger.warn(`Many-to-many relation '${relation.as || 'unknown'}' has no 'through' property`);
|
|
1094
|
-
return [];
|
|
1095
|
-
}
|
|
1096
|
-
try {
|
|
1097
|
-
const runtime = getVibesRuntime();
|
|
1098
|
-
if (!runtime) {
|
|
1099
|
-
logger.warn('Runtime not available for relation loading');
|
|
1100
|
-
return [];
|
|
1101
|
-
}
|
|
1102
|
-
// Resolve the junction table storage interface
|
|
1103
|
-
const junctionStorage = await runtime
|
|
1104
|
-
.query('storage/record')
|
|
1105
|
-
.withId(relation.through)
|
|
1106
|
-
.enabled()
|
|
1107
|
-
.resolve()
|
|
1108
|
-
.catch((error) => {
|
|
1109
|
-
logger.warn(`Failed to resolve junction table '${relation.through}':`, toErrorContext(error));
|
|
1110
|
-
return null;
|
|
1111
|
-
});
|
|
1112
|
-
if (!junctionStorage) {
|
|
1113
|
-
return [];
|
|
1114
|
-
}
|
|
1115
|
-
// Resolve the target table storage interface
|
|
1116
|
-
const targetStorage = await runtime
|
|
1117
|
-
.query('storage/record')
|
|
1118
|
-
.withId(relation.target)
|
|
1119
|
-
.enabled()
|
|
1120
|
-
.resolve()
|
|
1121
|
-
.catch((error) => {
|
|
1122
|
-
logger.warn(`Failed to resolve target table '${relation.target}':`, toErrorContext(error));
|
|
1123
|
-
return null;
|
|
1124
|
-
});
|
|
1125
|
-
if (!targetStorage) {
|
|
1126
|
-
return [];
|
|
1127
|
-
}
|
|
1128
|
-
// Get the primary key for this record
|
|
1129
|
-
const thisPrimaryKey = this.readStringField(record, this.primaryKey);
|
|
1130
|
-
if (!thisPrimaryKey) {
|
|
1131
|
-
return [];
|
|
1132
|
-
}
|
|
1133
|
-
const junctionTable = this.quoteIdentifier(junctionStorage.tableName);
|
|
1134
|
-
const thisFkColumn = await this.findJunctionForeignKeyColumn(junctionStorage, thisPrimaryKey, junctionTable);
|
|
1135
|
-
if (!thisFkColumn) {
|
|
1136
|
-
return [];
|
|
1137
|
-
}
|
|
1138
|
-
// Query junction rows by this record id
|
|
1139
|
-
const junctionResults = await junctionStorage.raw(`SELECT * FROM ${junctionTable} WHERE ${this.quoteIdentifier(thisFkColumn)} = ?`, thisPrimaryKey);
|
|
1140
|
-
if (junctionResults.length === 0) {
|
|
1141
|
-
return [];
|
|
1142
|
-
}
|
|
1143
|
-
// Extract target foreign keys from junction results
|
|
1144
|
-
const targetIds = this.extractJunctionTargetIds(junctionResults, thisFkColumn, targetStorage.primaryKey);
|
|
1145
|
-
if (targetIds.length === 0) {
|
|
1146
|
-
return [];
|
|
1147
|
-
}
|
|
1148
|
-
return await targetStorage.getMany(targetIds);
|
|
1149
|
-
}
|
|
1150
|
-
catch (error) {
|
|
1151
|
-
logger.error(`Error loading many-to-many relation: ${error}`);
|
|
1152
|
-
return [];
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
/**
|
|
1156
|
-
* Find a junction table column that stores this record's primary key.
|
|
1157
|
-
*/
|
|
1158
|
-
async findJunctionForeignKeyColumn(junctionStorage, recordId, quotedJunctionTable) {
|
|
1159
|
-
let tableInfo;
|
|
1160
|
-
try {
|
|
1161
|
-
tableInfo = await junctionStorage.raw(`PRAGMA table_info(${quotedJunctionTable})`);
|
|
1162
|
-
}
|
|
1163
|
-
catch {
|
|
1164
|
-
return null;
|
|
1165
|
-
}
|
|
1166
|
-
const candidateColumns = tableInfo
|
|
1167
|
-
.map((column) => column.name)
|
|
1168
|
-
.filter((name) => this.isLikelyIdColumn(name))
|
|
1169
|
-
.filter((name) => name !== 'id' && name !== junctionStorage.primaryKey)
|
|
1170
|
-
.filter((name) => name !== 'data');
|
|
1171
|
-
for (const column of candidateColumns) {
|
|
1172
|
-
const rows = await junctionStorage.raw(`SELECT COUNT(*) as count FROM ${quotedJunctionTable} WHERE ${this.quoteIdentifier(column)} = ?`, recordId);
|
|
1173
|
-
if ((rows[0]?.count ?? 0) > 0) {
|
|
1174
|
-
return column;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
return null;
|
|
1178
|
-
}
|
|
1179
|
-
/**
|
|
1180
|
-
* Extract target IDs from junction rows.
|
|
1181
|
-
*/
|
|
1182
|
-
extractJunctionTargetIds(junctionRows, thisFkColumn, targetPrimaryKey) {
|
|
1183
|
-
const targetIds = new Set();
|
|
1184
|
-
for (const row of junctionRows) {
|
|
1185
|
-
for (const [field, value] of Object.entries(row)) {
|
|
1186
|
-
if (field === thisFkColumn || field === targetPrimaryKey) {
|
|
1187
|
-
continue;
|
|
1188
|
-
}
|
|
1189
|
-
if (!this.isLikelyIdColumn(field) || typeof value !== 'string') {
|
|
1190
|
-
continue;
|
|
1191
|
-
}
|
|
1192
|
-
if (value.length > 0) {
|
|
1193
|
-
targetIds.add(value);
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
return [...targetIds];
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* True when a field name is likely an identifier / foreign key field.
|
|
1201
|
-
*/
|
|
1202
|
-
isLikelyIdColumn(name) {
|
|
1203
|
-
const lower = name.toLowerCase();
|
|
1204
|
-
return (lower === 'id' ||
|
|
1205
|
-
lower === 'data' ||
|
|
1206
|
-
/(^|[._])id$/i.test(name) ||
|
|
1207
|
-
/_id$/.test(lower) ||
|
|
1208
|
-
/Id$/.test(name));
|
|
2368
|
+
/** What relation loading reads of this record: its kind, primary key, declared relations and runtime. */
|
|
2369
|
+
get relationOwner() {
|
|
2370
|
+
return { recordKind: this.descriptor.id, primaryKey: this.primaryKey, relations: this.relations, runtime: this.context.runtime };
|
|
1209
2371
|
}
|
|
1210
2372
|
/**
|
|
1211
2373
|
* Escape SQL identifier safely for query fragments.
|
|
@@ -1223,6 +2385,9 @@ export class StorageRecord {
|
|
|
1223
2385
|
throw new Error(`FTS is not configured for ${this.tableName}`);
|
|
1224
2386
|
}
|
|
1225
2387
|
const ftsTable = this.descriptor.fts.tableName || `${this.tableName}_fts`;
|
|
2388
|
+
const baseTableName = this.quoteIdentifier(this.tableName);
|
|
2389
|
+
const primaryKeyName = this.quoteIdentifier(this.primaryKey);
|
|
2390
|
+
const ftsTableName = this.quoteIdentifier(ftsTable);
|
|
1226
2391
|
const ftsFields = this.descriptor.fts.fields;
|
|
1227
2392
|
// Split into words and AND them for multi-word queries.
|
|
1228
2393
|
// Each word is quoted to escape FTS5 special characters.
|
|
@@ -1230,23 +2395,42 @@ export class StorageRecord {
|
|
|
1230
2395
|
const ftsQuery = words.length <= 1
|
|
1231
2396
|
? `"${query.replace(/"/g, '""')}"`
|
|
1232
2397
|
: words.map((w) => `"${w.replace(/"/g, '""')}"`).join(' AND ');
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
FROM ${this.tableName}
|
|
1240
|
-
JOIN ${ftsTable} ON ${ftsTable}.rowid = ${this.tableName}.rowid
|
|
1241
|
-
WHERE ${ftsTable} MATCH ?
|
|
2398
|
+
// MATCH + where fragment shared by the page query and the COUNT query so
|
|
2399
|
+
// the two can never drift.
|
|
2400
|
+
let fromWhere = `
|
|
2401
|
+
FROM ${baseTableName}
|
|
2402
|
+
JOIN ${ftsTableName} ON ${ftsTableName}.rowid = ${baseTableName}.rowid
|
|
2403
|
+
WHERE ${ftsTableName} MATCH ?
|
|
1242
2404
|
`;
|
|
2405
|
+
const filterParams = [ftsQuery];
|
|
1243
2406
|
if (options?.where) {
|
|
1244
2407
|
const { clause, values } = this.buildWhereClause(options.where);
|
|
1245
2408
|
if (clause) {
|
|
1246
|
-
|
|
1247
|
-
|
|
2409
|
+
fromWhere += ` AND ${clause}`;
|
|
2410
|
+
filterParams.push(...values);
|
|
1248
2411
|
}
|
|
1249
2412
|
}
|
|
2413
|
+
// Soft delete filter — search honors softDelete exactly like list()/count()
|
|
2414
|
+
// (includeDeleted opts out). Callers must never re-implement this per-lane.
|
|
2415
|
+
const deletedAtField = this.softDeleteField();
|
|
2416
|
+
if (deletedAtField && !options?.includeDeleted) {
|
|
2417
|
+
fromWhere += ` AND json_extract(data, '$.${deletedAtField}') IS NULL`;
|
|
2418
|
+
}
|
|
2419
|
+
// True total: COUNT over the same MATCH + where BEFORE limit/offset.
|
|
2420
|
+
// Returning the page length as `total` hid every match past the cap.
|
|
2421
|
+
// (minScore is applied per-row after fetch, so the total counts matches
|
|
2422
|
+
// before that post-filter.)
|
|
2423
|
+
const countRow = this.db.prepare(`SELECT COUNT(*) as count ${fromWhere}`).get(...filterParams);
|
|
2424
|
+
const total = countRow?.count || 0;
|
|
2425
|
+
const params = [...filterParams];
|
|
2426
|
+
let sql = `
|
|
2427
|
+
SELECT
|
|
2428
|
+
${baseTableName}.${primaryKeyName},
|
|
2429
|
+
${baseTableName}.data,
|
|
2430
|
+
${ftsTableName}.rowid as _fts_rowid,
|
|
2431
|
+
${ftsTableName}.rank as _rank
|
|
2432
|
+
${fromWhere}
|
|
2433
|
+
`;
|
|
1250
2434
|
if (options?.orderBy) {
|
|
1251
2435
|
sql += ` ORDER BY ${this.buildOrderBy(options.orderBy)}`;
|
|
1252
2436
|
}
|
|
@@ -1261,12 +2445,12 @@ export class StorageRecord {
|
|
|
1261
2445
|
sql += ` OFFSET ?`;
|
|
1262
2446
|
params.push(options.offset);
|
|
1263
2447
|
}
|
|
1264
|
-
const rows = this.db.
|
|
2448
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
1265
2449
|
const items = [];
|
|
1266
2450
|
const scores = [];
|
|
1267
2451
|
const highlights = new Map();
|
|
1268
2452
|
for (const row of rows) {
|
|
1269
|
-
const record =
|
|
2453
|
+
const record = this.decodeRecord(row.data);
|
|
1270
2454
|
items.push(record);
|
|
1271
2455
|
scores.push(row._rank);
|
|
1272
2456
|
if (options?.highlight) {
|
|
@@ -1274,13 +2458,13 @@ export class StorageRecord {
|
|
|
1274
2458
|
const suffix = options.highlightSuffix || '</mark>';
|
|
1275
2459
|
const id = this.readPrimaryKey(record);
|
|
1276
2460
|
const fieldHighlights = [];
|
|
1277
|
-
for (const
|
|
2461
|
+
for (const fieldIndex of ftsFields.keys()) {
|
|
1278
2462
|
const highlightSql = `
|
|
1279
|
-
SELECT highlight(${
|
|
1280
|
-
|
|
2463
|
+
SELECT highlight(${ftsTableName}, ?, ?, ?) as highlighted
|
|
2464
|
+
FROM ${ftsTableName}
|
|
2465
|
+
WHERE ${ftsTableName} MATCH ? AND rowid = ?
|
|
1281
2466
|
`;
|
|
1282
|
-
const
|
|
1283
|
-
const highlightRow = this.db.query(highlightSql).get(fieldIndex, prefix, suffix, ftsQuery, rows.indexOf(row) + 1);
|
|
2467
|
+
const highlightRow = this.db.prepare(highlightSql).get(fieldIndex, prefix, suffix, ftsQuery, row._fts_rowid);
|
|
1284
2468
|
if (highlightRow?.highlighted) {
|
|
1285
2469
|
fieldHighlights.push(highlightRow.highlighted);
|
|
1286
2470
|
}
|
|
@@ -1294,10 +2478,10 @@ export class StorageRecord {
|
|
|
1294
2478
|
}
|
|
1295
2479
|
return {
|
|
1296
2480
|
items,
|
|
1297
|
-
total
|
|
2481
|
+
total,
|
|
1298
2482
|
scores,
|
|
1299
2483
|
highlights: highlights.size > 0 ? highlights : undefined,
|
|
1300
|
-
hasMore: options?.
|
|
2484
|
+
hasMore: (options?.offset ?? 0) + items.length < total
|
|
1301
2485
|
};
|
|
1302
2486
|
}
|
|
1303
2487
|
// HIERARCHY
|
|
@@ -1330,12 +2514,11 @@ export class StorageRecord {
|
|
|
1330
2514
|
)
|
|
1331
2515
|
SELECT * FROM hierarchy ORDER BY depth, ${this.primaryKey}
|
|
1332
2516
|
`;
|
|
1333
|
-
const rows = this.db.
|
|
2517
|
+
const rows = this.db.prepare(sql).all(id, maxDepth);
|
|
1334
2518
|
const items = rows.map((row) => {
|
|
1335
|
-
const record =
|
|
1336
|
-
record.depth = row.depth;
|
|
2519
|
+
const record = Object.assign(this.decodeRecord(row.data), { depth: row.depth });
|
|
1337
2520
|
if (includePath && row.path) {
|
|
1338
|
-
record
|
|
2521
|
+
return Object.assign(record, { path: this.decodePath(row.path) });
|
|
1339
2522
|
}
|
|
1340
2523
|
return record;
|
|
1341
2524
|
});
|
|
@@ -1363,8 +2546,8 @@ export class StorageRecord {
|
|
|
1363
2546
|
)
|
|
1364
2547
|
SELECT * FROM ancestors ORDER BY depth DESC
|
|
1365
2548
|
`;
|
|
1366
|
-
const rows = this.db.
|
|
1367
|
-
return rows.map((row) =>
|
|
2549
|
+
const rows = this.db.prepare(sql).all(id);
|
|
2550
|
+
return rows.map((row) => this.decodeRecord(row.data));
|
|
1368
2551
|
}
|
|
1369
2552
|
async getTree(options) {
|
|
1370
2553
|
await this.ensureInitialized();
|
|
@@ -1397,18 +2580,18 @@ export class StorageRecord {
|
|
|
1397
2580
|
)
|
|
1398
2581
|
SELECT * FROM tree ORDER BY depth, ${this.primaryKey}
|
|
1399
2582
|
`;
|
|
1400
|
-
const rows = this.db.
|
|
2583
|
+
const rows = this.db.prepare(sql).all(...whereClause.values, maxDepth);
|
|
1401
2584
|
const nodeMap = new Map();
|
|
1402
2585
|
const rootNodes = [];
|
|
1403
2586
|
for (const row of rows) {
|
|
1404
|
-
const record =
|
|
2587
|
+
const record = this.decodeRecord(row.data);
|
|
1405
2588
|
const recordId = this.readPrimaryKey(record);
|
|
1406
2589
|
const parentId = this.readStringField(record, parentField);
|
|
1407
2590
|
const node = {
|
|
1408
2591
|
record,
|
|
1409
2592
|
children: [],
|
|
1410
2593
|
depth: row.depth,
|
|
1411
|
-
path:
|
|
2594
|
+
path: this.decodePath(row.path)
|
|
1412
2595
|
};
|
|
1413
2596
|
nodeMap.set(recordId, node);
|
|
1414
2597
|
if (parentId && nodeMap.has(parentId)) {
|
|
@@ -1423,11 +2606,11 @@ export class StorageRecord {
|
|
|
1423
2606
|
// RAW QUERIES
|
|
1424
2607
|
async raw(sql, ...params) {
|
|
1425
2608
|
await this.ensureInitialized();
|
|
1426
|
-
return this.db.
|
|
2609
|
+
return this.db.prepare(sql).all(...params);
|
|
1427
2610
|
}
|
|
1428
2611
|
async queryRaw(options) {
|
|
1429
2612
|
await this.ensureInitialized();
|
|
1430
|
-
const rows = this.db.
|
|
2613
|
+
const rows = this.db.prepare(options.sql).all(...(options.params || []));
|
|
1431
2614
|
if (options.schema) {
|
|
1432
2615
|
return rows.map((row) => options.schema.parse(row));
|
|
1433
2616
|
}
|