@aigne/afs-did-space 1.12.0-beta.5
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.md +26 -0
- package/README.md +129 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/_virtual/rolldown_runtime.cjs +29 -0
- package/dist/afs-metadata-context.cjs +20 -0
- package/dist/afs-metadata-context.d.cts +21 -0
- package/dist/afs-metadata-context.d.cts.map +1 -0
- package/dist/afs-metadata-context.d.mts +21 -0
- package/dist/afs-metadata-context.d.mts.map +1 -0
- package/dist/afs-metadata-context.mjs +18 -0
- package/dist/afs-metadata-context.mjs.map +1 -0
- package/dist/blocklet-hook.cjs +180 -0
- package/dist/blocklet-hook.d.cts +29 -0
- package/dist/blocklet-hook.d.cts.map +1 -0
- package/dist/blocklet-hook.d.mts +29 -0
- package/dist/blocklet-hook.d.mts.map +1 -0
- package/dist/blocklet-hook.mjs +178 -0
- package/dist/blocklet-hook.mjs.map +1 -0
- package/dist/changelog-schema.cjs +603 -0
- package/dist/changelog-schema.d.cts +395 -0
- package/dist/changelog-schema.d.cts.map +1 -0
- package/dist/changelog-schema.d.mts +395 -0
- package/dist/changelog-schema.d.mts.map +1 -0
- package/dist/changelog-schema.mjs +570 -0
- package/dist/changelog-schema.mjs.map +1 -0
- package/dist/changelog-write.cjs +80 -0
- package/dist/changelog-write.d.cts +70 -0
- package/dist/changelog-write.d.cts.map +1 -0
- package/dist/changelog-write.d.mts +70 -0
- package/dist/changelog-write.d.mts.map +1 -0
- package/dist/changelog-write.mjs +77 -0
- package/dist/changelog-write.mjs.map +1 -0
- package/dist/cid-byte-cache.cjs +126 -0
- package/dist/cid-byte-cache.d.cts +46 -0
- package/dist/cid-byte-cache.d.cts.map +1 -0
- package/dist/cid-byte-cache.d.mts +47 -0
- package/dist/cid-byte-cache.d.mts.map +1 -0
- package/dist/cid-byte-cache.mjs +125 -0
- package/dist/cid-byte-cache.mjs.map +1 -0
- package/dist/cid.cjs +68 -0
- package/dist/cid.d.cts +22 -0
- package/dist/cid.d.cts.map +1 -0
- package/dist/cid.d.mts +22 -0
- package/dist/cid.d.mts.map +1 -0
- package/dist/cid.mjs +65 -0
- package/dist/cid.mjs.map +1 -0
- package/dist/client.cjs +221 -0
- package/dist/client.d.cts +72 -0
- package/dist/client.d.cts.map +1 -0
- package/dist/client.d.mts +72 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +220 -0
- package/dist/client.mjs.map +1 -0
- package/dist/cloudflare.cjs +510 -0
- package/dist/cloudflare.d.cts +307 -0
- package/dist/cloudflare.d.cts.map +1 -0
- package/dist/cloudflare.d.mts +308 -0
- package/dist/cloudflare.d.mts.map +1 -0
- package/dist/cloudflare.mjs +507 -0
- package/dist/cloudflare.mjs.map +1 -0
- package/dist/ctime-backfill.cjs +272 -0
- package/dist/ctime-backfill.d.cts +169 -0
- package/dist/ctime-backfill.d.cts.map +1 -0
- package/dist/ctime-backfill.d.mts +169 -0
- package/dist/ctime-backfill.d.mts.map +1 -0
- package/dist/ctime-backfill.mjs +267 -0
- package/dist/ctime-backfill.mjs.map +1 -0
- package/dist/d1-gc-queue.cjs +80 -0
- package/dist/d1-gc-queue.d.cts +59 -0
- package/dist/d1-gc-queue.d.cts.map +1 -0
- package/dist/d1-gc-queue.d.mts +59 -0
- package/dist/d1-gc-queue.d.mts.map +1 -0
- package/dist/d1-gc-queue.mjs +81 -0
- package/dist/d1-gc-queue.mjs.map +1 -0
- package/dist/d1-resolve-store.cjs +189 -0
- package/dist/d1-resolve-store.d.cts +93 -0
- package/dist/d1-resolve-store.d.cts.map +1 -0
- package/dist/d1-resolve-store.d.mts +93 -0
- package/dist/d1-resolve-store.d.mts.map +1 -0
- package/dist/d1-resolve-store.mjs +190 -0
- package/dist/d1-resolve-store.mjs.map +1 -0
- package/dist/d1-schema.cjs +361 -0
- package/dist/d1-schema.d.cts +237 -0
- package/dist/d1-schema.d.cts.map +1 -0
- package/dist/d1-schema.d.mts +237 -0
- package/dist/d1-schema.d.mts.map +1 -0
- package/dist/d1-schema.mjs +341 -0
- package/dist/d1-schema.mjs.map +1 -0
- package/dist/d1-share-store.cjs +0 -0
- package/dist/d1-share-store.d.cts +53 -0
- package/dist/d1-share-store.d.cts.map +1 -0
- package/dist/d1-share-store.d.mts +53 -0
- package/dist/d1-share-store.d.mts.map +1 -0
- package/dist/d1-share-store.mjs +0 -0
- package/dist/d1-share-store.mjs.map +1 -0
- package/dist/d1-tree-index.cjs +1098 -0
- package/dist/d1-tree-index.d.cts +203 -0
- package/dist/d1-tree-index.d.cts.map +1 -0
- package/dist/d1-tree-index.d.mts +203 -0
- package/dist/d1-tree-index.d.mts.map +1 -0
- package/dist/d1-tree-index.mjs +1094 -0
- package/dist/d1-tree-index.mjs.map +1 -0
- package/dist/data-backend.cjs +147 -0
- package/dist/data-backend.d.cts +30 -0
- package/dist/data-backend.d.cts.map +1 -0
- package/dist/data-backend.d.mts +30 -0
- package/dist/data-backend.d.mts.map +1 -0
- package/dist/data-backend.mjs +148 -0
- package/dist/data-backend.mjs.map +1 -0
- package/dist/dedup-backfill.cjs +278 -0
- package/dist/dedup-backfill.d.cts +165 -0
- package/dist/dedup-backfill.d.cts.map +1 -0
- package/dist/dedup-backfill.d.mts +165 -0
- package/dist/dedup-backfill.d.mts.map +1 -0
- package/dist/dedup-backfill.mjs +273 -0
- package/dist/dedup-backfill.mjs.map +1 -0
- package/dist/did-space-like.d.cts +2 -0
- package/dist/did-space-like.d.mts +2 -0
- package/dist/did-utils.cjs +44 -0
- package/dist/did-utils.d.cts +25 -0
- package/dist/did-utils.d.cts.map +1 -0
- package/dist/did-utils.d.mts +25 -0
- package/dist/did-utils.d.mts.map +1 -0
- package/dist/did-utils.mjs +43 -0
- package/dist/did-utils.mjs.map +1 -0
- package/dist/dirty-path-queue.cjs +31 -0
- package/dist/dirty-path-queue.d.cts +104 -0
- package/dist/dirty-path-queue.d.cts.map +1 -0
- package/dist/dirty-path-queue.d.mts +104 -0
- package/dist/dirty-path-queue.d.mts.map +1 -0
- package/dist/dirty-path-queue.mjs +31 -0
- package/dist/dirty-path-queue.mjs.map +1 -0
- package/dist/dirty-path-schema.cjs +142 -0
- package/dist/dirty-path-schema.d.cts +90 -0
- package/dist/dirty-path-schema.d.cts.map +1 -0
- package/dist/dirty-path-schema.d.mts +90 -0
- package/dist/dirty-path-schema.d.mts.map +1 -0
- package/dist/dirty-path-schema.mjs +136 -0
- package/dist/dirty-path-schema.mjs.map +1 -0
- package/dist/download-actions.cjs +43 -0
- package/dist/download-actions.mjs +40 -0
- package/dist/download-actions.mjs.map +1 -0
- package/dist/fs-object-store.cjs +153 -0
- package/dist/fs-object-store.d.cts +21 -0
- package/dist/fs-object-store.d.cts.map +1 -0
- package/dist/fs-object-store.d.mts +22 -0
- package/dist/fs-object-store.d.mts.map +1 -0
- package/dist/fs-object-store.mjs +153 -0
- package/dist/fs-object-store.mjs.map +1 -0
- package/dist/gc-queue.d.cts +43 -0
- package/dist/gc-queue.d.cts.map +1 -0
- package/dist/gc-queue.d.mts +43 -0
- package/dist/gc-queue.d.mts.map +1 -0
- package/dist/index.cjs +2712 -0
- package/dist/index.d.cts +504 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +505 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2537 -0
- package/dist/index.mjs.map +1 -0
- package/dist/inline-content-migration.cjs +212 -0
- package/dist/inline-content-migration.d.cts +110 -0
- package/dist/inline-content-migration.d.cts.map +1 -0
- package/dist/inline-content-migration.d.mts +111 -0
- package/dist/inline-content-migration.d.mts.map +1 -0
- package/dist/inline-content-migration.mjs +207 -0
- package/dist/inline-content-migration.mjs.map +1 -0
- package/dist/inline-content.cjs +145 -0
- package/dist/inline-content.d.cts +98 -0
- package/dist/inline-content.d.cts.map +1 -0
- package/dist/inline-content.d.mts +98 -0
- package/dist/inline-content.d.mts.map +1 -0
- package/dist/inline-content.mjs +137 -0
- package/dist/inline-content.mjs.map +1 -0
- package/dist/local-changelog.cjs +241 -0
- package/dist/local-changelog.d.cts +119 -0
- package/dist/local-changelog.d.cts.map +1 -0
- package/dist/local-changelog.d.mts +119 -0
- package/dist/local-changelog.d.mts.map +1 -0
- package/dist/local-changelog.mjs +240 -0
- package/dist/local-changelog.mjs.map +1 -0
- package/dist/local.cjs +435 -0
- package/dist/local.d.cts +254 -0
- package/dist/local.d.cts.map +1 -0
- package/dist/local.d.mts +254 -0
- package/dist/local.d.mts.map +1 -0
- package/dist/local.mjs +427 -0
- package/dist/local.mjs.map +1 -0
- package/dist/memory-gc-queue.cjs +36 -0
- package/dist/memory-gc-queue.d.cts +22 -0
- package/dist/memory-gc-queue.d.cts.map +1 -0
- package/dist/memory-gc-queue.d.mts +22 -0
- package/dist/memory-gc-queue.d.mts.map +1 -0
- package/dist/memory-gc-queue.mjs +36 -0
- package/dist/memory-gc-queue.mjs.map +1 -0
- package/dist/memory-tree-index.cjs +734 -0
- package/dist/memory-tree-index.d.cts +159 -0
- package/dist/memory-tree-index.d.cts.map +1 -0
- package/dist/memory-tree-index.d.mts +159 -0
- package/dist/memory-tree-index.d.mts.map +1 -0
- package/dist/memory-tree-index.mjs +735 -0
- package/dist/memory-tree-index.mjs.map +1 -0
- package/dist/metadata-db.cjs +562 -0
- package/dist/metadata-db.d.cts +102 -0
- package/dist/metadata-db.d.cts.map +1 -0
- package/dist/metadata-db.d.mts +102 -0
- package/dist/metadata-db.d.mts.map +1 -0
- package/dist/metadata-db.mjs +562 -0
- package/dist/metadata-db.mjs.map +1 -0
- package/dist/mime-utils.cjs +2 -0
- package/dist/mime-utils.d.cts +2 -0
- package/dist/mime-utils.d.mts +2 -0
- package/dist/mime-utils.mjs +3 -0
- package/dist/object-key.cjs +41 -0
- package/dist/object-key.d.cts +25 -0
- package/dist/object-key.d.cts.map +1 -0
- package/dist/object-key.d.mts +25 -0
- package/dist/object-key.d.mts.map +1 -0
- package/dist/object-key.mjs +40 -0
- package/dist/object-key.mjs.map +1 -0
- package/dist/packages/session/dist/index.cjs +788 -0
- package/dist/packages/session/dist/index.mjs +782 -0
- package/dist/packages/session/dist/index.mjs.map +1 -0
- package/dist/platform/r2/dist/types.d.cts +49 -0
- package/dist/platform/r2/dist/types.d.cts.map +1 -0
- package/dist/prefix-range.cjs +33 -0
- package/dist/prefix-range.d.cts +29 -0
- package/dist/prefix-range.d.cts.map +1 -0
- package/dist/prefix-range.d.mts +29 -0
- package/dist/prefix-range.d.mts.map +1 -0
- package/dist/prefix-range.mjs +33 -0
- package/dist/prefix-range.mjs.map +1 -0
- package/dist/prefixed-tree-index.cjs +238 -0
- package/dist/prefixed-tree-index.d.cts +85 -0
- package/dist/prefixed-tree-index.d.cts.map +1 -0
- package/dist/prefixed-tree-index.d.mts +85 -0
- package/dist/prefixed-tree-index.d.mts.map +1 -0
- package/dist/prefixed-tree-index.mjs +239 -0
- package/dist/prefixed-tree-index.mjs.map +1 -0
- package/dist/presign-signer.cjs +7 -0
- package/dist/presign-signer.d.cts +99 -0
- package/dist/presign-signer.d.cts.map +1 -0
- package/dist/presign-signer.d.mts +99 -0
- package/dist/presign-signer.d.mts.map +1 -0
- package/dist/presign-signer.mjs +7 -0
- package/dist/presign-signer.mjs.map +1 -0
- package/dist/providers/platform/r2/dist/index.d.mts +3 -0
- package/dist/providers/platform/r2/dist/types.d.mts +49 -0
- package/dist/providers/platform/r2/dist/types.d.mts.map +1 -0
- package/dist/reconcile.cjs +184 -0
- package/dist/reconcile.d.cts +26 -0
- package/dist/reconcile.d.cts.map +1 -0
- package/dist/reconcile.d.mts +26 -0
- package/dist/reconcile.d.mts.map +1 -0
- package/dist/reconcile.mjs +184 -0
- package/dist/reconcile.mjs.map +1 -0
- package/dist/reference-check.cjs +49 -0
- package/dist/reference-check.d.cts +10 -0
- package/dist/reference-check.d.cts.map +1 -0
- package/dist/reference-check.d.mts +10 -0
- package/dist/reference-check.d.mts.map +1 -0
- package/dist/reference-check.mjs +48 -0
- package/dist/reference-check.mjs.map +1 -0
- package/dist/remote-afs.cjs +681 -0
- package/dist/remote-afs.d.cts +236 -0
- package/dist/remote-afs.d.cts.map +1 -0
- package/dist/remote-afs.d.mts +236 -0
- package/dist/remote-afs.d.mts.map +1 -0
- package/dist/remote-afs.mjs +676 -0
- package/dist/remote-afs.mjs.map +1 -0
- package/dist/rename-guard.cjs +18 -0
- package/dist/rename-guard.mjs +18 -0
- package/dist/rename-guard.mjs.map +1 -0
- package/dist/resolve.cjs +260 -0
- package/dist/resolve.d.cts +113 -0
- package/dist/resolve.d.cts.map +1 -0
- package/dist/resolve.d.mts +113 -0
- package/dist/resolve.d.mts.map +1 -0
- package/dist/resolve.mjs +260 -0
- package/dist/resolve.mjs.map +1 -0
- package/dist/rsync-content.cjs +33 -0
- package/dist/rsync-content.mjs +33 -0
- package/dist/rsync-content.mjs.map +1 -0
- package/dist/rsync.cjs +400 -0
- package/dist/rsync.d.cts +157 -0
- package/dist/rsync.d.cts.map +1 -0
- package/dist/rsync.d.mts +157 -0
- package/dist/rsync.d.mts.map +1 -0
- package/dist/rsync.mjs +397 -0
- package/dist/rsync.mjs.map +1 -0
- package/dist/run-gc.cjs +57 -0
- package/dist/run-gc.d.cts +40 -0
- package/dist/run-gc.d.cts.map +1 -0
- package/dist/run-gc.d.mts +41 -0
- package/dist/run-gc.d.mts.map +1 -0
- package/dist/run-gc.mjs +57 -0
- package/dist/run-gc.mjs.map +1 -0
- package/dist/scope-id.cjs +70 -0
- package/dist/scope-id.d.cts +35 -0
- package/dist/scope-id.d.cts.map +1 -0
- package/dist/scope-id.d.mts +35 -0
- package/dist/scope-id.d.mts.map +1 -0
- package/dist/scope-id.mjs +68 -0
- package/dist/scope-id.mjs.map +1 -0
- package/dist/scope-migration-node.cjs +172 -0
- package/dist/scope-migration-node.d.cts +44 -0
- package/dist/scope-migration-node.d.cts.map +1 -0
- package/dist/scope-migration-node.d.mts +44 -0
- package/dist/scope-migration-node.d.mts.map +1 -0
- package/dist/scope-migration-node.mjs +171 -0
- package/dist/scope-migration-node.mjs.map +1 -0
- package/dist/scope-migration.cjs +155 -0
- package/dist/scope-migration.d.cts +65 -0
- package/dist/scope-migration.d.cts.map +1 -0
- package/dist/scope-migration.d.mts +65 -0
- package/dist/scope-migration.d.mts.map +1 -0
- package/dist/scope-migration.mjs +154 -0
- package/dist/scope-migration.mjs.map +1 -0
- package/dist/share-declaration.cjs +225 -0
- package/dist/share-declaration.d.cts +30 -0
- package/dist/share-declaration.d.cts.map +1 -0
- package/dist/share-declaration.d.mts +32 -0
- package/dist/share-declaration.d.mts.map +1 -0
- package/dist/share-declaration.mjs +221 -0
- package/dist/share-declaration.mjs.map +1 -0
- package/dist/share-hook.cjs +63 -0
- package/dist/share-hook.mjs +62 -0
- package/dist/share-hook.mjs.map +1 -0
- package/dist/share-resolve.cjs +176 -0
- package/dist/share-resolve.d.cts +36 -0
- package/dist/share-resolve.d.cts.map +1 -0
- package/dist/share-resolve.d.mts +36 -0
- package/dist/share-resolve.d.mts.map +1 -0
- package/dist/share-resolve.mjs +177 -0
- package/dist/share-resolve.mjs.map +1 -0
- package/dist/share-serve.cjs +145 -0
- package/dist/share-serve.d.cts +30 -0
- package/dist/share-serve.d.cts.map +1 -0
- package/dist/share-serve.d.mts +30 -0
- package/dist/share-serve.d.mts.map +1 -0
- package/dist/share-serve.mjs +144 -0
- package/dist/share-serve.mjs.map +1 -0
- package/dist/share-store.cjs +25 -0
- package/dist/share-store.d.cts +65 -0
- package/dist/share-store.d.cts.map +1 -0
- package/dist/share-store.d.mts +65 -0
- package/dist/share-store.d.mts.map +1 -0
- package/dist/share-store.mjs +25 -0
- package/dist/share-store.mjs.map +1 -0
- package/dist/share-virtual-path.cjs +102 -0
- package/dist/share-virtual-path.mjs +101 -0
- package/dist/share-virtual-path.mjs.map +1 -0
- package/dist/space-resolver.cjs +54 -0
- package/dist/space-resolver.d.cts +34 -0
- package/dist/space-resolver.d.cts.map +1 -0
- package/dist/space-resolver.d.mts +34 -0
- package/dist/space-resolver.d.mts.map +1 -0
- package/dist/space-resolver.mjs +55 -0
- package/dist/space-resolver.mjs.map +1 -0
- package/dist/sqlite-gc-queue.cjs +51 -0
- package/dist/sqlite-gc-queue.d.cts +22 -0
- package/dist/sqlite-gc-queue.d.cts.map +1 -0
- package/dist/sqlite-gc-queue.d.mts +22 -0
- package/dist/sqlite-gc-queue.d.mts.map +1 -0
- package/dist/sqlite-gc-queue.mjs +52 -0
- package/dist/sqlite-gc-queue.mjs.map +1 -0
- package/dist/sqlite-resolve-store.cjs +163 -0
- package/dist/sqlite-resolve-store.d.cts +50 -0
- package/dist/sqlite-resolve-store.d.cts.map +1 -0
- package/dist/sqlite-resolve-store.d.mts +50 -0
- package/dist/sqlite-resolve-store.d.mts.map +1 -0
- package/dist/sqlite-resolve-store.mjs +164 -0
- package/dist/sqlite-resolve-store.mjs.map +1 -0
- package/dist/sqlite-share-store.cjs +157 -0
- package/dist/sqlite-share-store.d.cts +31 -0
- package/dist/sqlite-share-store.d.cts.map +1 -0
- package/dist/sqlite-share-store.d.mts +31 -0
- package/dist/sqlite-share-store.d.mts.map +1 -0
- package/dist/sqlite-share-store.mjs +157 -0
- package/dist/sqlite-share-store.mjs.map +1 -0
- package/dist/sqlite-tree-index.cjs +901 -0
- package/dist/sqlite-tree-index.d.cts +107 -0
- package/dist/sqlite-tree-index.d.cts.map +1 -0
- package/dist/sqlite-tree-index.d.mts +107 -0
- package/dist/sqlite-tree-index.d.mts.map +1 -0
- package/dist/sqlite-tree-index.mjs +902 -0
- package/dist/sqlite-tree-index.mjs.map +1 -0
- package/dist/sync-engine.cjs +1324 -0
- package/dist/sync-engine.d.cts +473 -0
- package/dist/sync-engine.d.cts.map +1 -0
- package/dist/sync-engine.d.mts +473 -0
- package/dist/sync-engine.d.mts.map +1 -0
- package/dist/sync-engine.mjs +1315 -0
- package/dist/sync-engine.mjs.map +1 -0
- package/dist/sync-state.cjs +303 -0
- package/dist/sync-state.d.cts +224 -0
- package/dist/sync-state.d.cts.map +1 -0
- package/dist/sync-state.d.mts +224 -0
- package/dist/sync-state.d.mts.map +1 -0
- package/dist/sync-state.mjs +297 -0
- package/dist/sync-state.mjs.map +1 -0
- package/dist/timestamp.cjs +92 -0
- package/dist/timestamp.mjs +91 -0
- package/dist/timestamp.mjs.map +1 -0
- package/dist/transports/http.cjs +54 -0
- package/dist/transports/http.d.cts +26 -0
- package/dist/transports/http.d.cts.map +1 -0
- package/dist/transports/http.d.mts +26 -0
- package/dist/transports/http.d.mts.map +1 -0
- package/dist/transports/http.mjs +54 -0
- package/dist/transports/http.mjs.map +1 -0
- package/dist/transports/static.cjs +55 -0
- package/dist/transports/static.d.cts +28 -0
- package/dist/transports/static.d.cts.map +1 -0
- package/dist/transports/static.d.mts +28 -0
- package/dist/transports/static.d.mts.map +1 -0
- package/dist/transports/static.mjs +55 -0
- package/dist/transports/static.mjs.map +1 -0
- package/dist/transports/types.cjs +13 -0
- package/dist/transports/types.d.cts +72 -0
- package/dist/transports/types.d.cts.map +1 -0
- package/dist/transports/types.d.mts +72 -0
- package/dist/transports/types.d.mts.map +1 -0
- package/dist/transports/types.mjs +13 -0
- package/dist/transports/types.mjs.map +1 -0
- package/dist/transports/ws.cjs +207 -0
- package/dist/transports/ws.d.cts +47 -0
- package/dist/transports/ws.d.cts.map +1 -0
- package/dist/transports/ws.d.mts +47 -0
- package/dist/transports/ws.d.mts.map +1 -0
- package/dist/transports/ws.mjs +206 -0
- package/dist/transports/ws.mjs.map +1 -0
- package/dist/tree-index.cjs +333 -0
- package/dist/tree-index.d.cts +524 -0
- package/dist/tree-index.d.cts.map +1 -0
- package/dist/tree-index.d.mts +524 -0
- package/dist/tree-index.d.mts.map +1 -0
- package/dist/tree-index.mjs +322 -0
- package/dist/tree-index.mjs.map +1 -0
- package/dist/tree-query.cjs +655 -0
- package/dist/tree-query.d.cts +70 -0
- package/dist/tree-query.d.cts.map +1 -0
- package/dist/tree-query.d.mts +70 -0
- package/dist/tree-query.d.mts.map +1 -0
- package/dist/tree-query.mjs +648 -0
- package/dist/tree-query.mjs.map +1 -0
- package/dist/upload-actions.cjs +97 -0
- package/dist/upload-actions.d.cts +19 -0
- package/dist/upload-actions.d.cts.map +1 -0
- package/dist/upload-actions.d.mts +21 -0
- package/dist/upload-actions.d.mts.map +1 -0
- package/dist/upload-actions.mjs +90 -0
- package/dist/upload-actions.mjs.map +1 -0
- package/dist/upload-session-store.cjs +185 -0
- package/dist/upload-session-store.d.cts +99 -0
- package/dist/upload-session-store.d.cts.map +1 -0
- package/dist/upload-session-store.d.mts +99 -0
- package/dist/upload-session-store.d.mts.map +1 -0
- package/dist/upload-session-store.mjs +184 -0
- package/dist/upload-session-store.mjs.map +1 -0
- package/dist/url-normalize.cjs +61 -0
- package/dist/url-normalize.d.cts +33 -0
- package/dist/url-normalize.d.cts.map +1 -0
- package/dist/url-normalize.d.mts +33 -0
- package/dist/url-normalize.d.mts.map +1 -0
- package/dist/url-normalize.mjs +61 -0
- package/dist/url-normalize.mjs.map +1 -0
- package/dist/versioned-content-backends.cjs +155 -0
- package/dist/versioned-content-backends.d.cts +49 -0
- package/dist/versioned-content-backends.d.cts.map +1 -0
- package/dist/versioned-content-backends.d.mts +49 -0
- package/dist/versioned-content-backends.d.mts.map +1 -0
- package/dist/versioned-content-backends.mjs +154 -0
- package/dist/versioned-content-backends.mjs.map +1 -0
- package/dist/versioned-content-index.cjs +102 -0
- package/dist/versioned-content-index.d.cts +46 -0
- package/dist/versioned-content-index.d.cts.map +1 -0
- package/dist/versioned-content-index.d.mts +46 -0
- package/dist/versioned-content-index.d.mts.map +1 -0
- package/dist/versioned-content-index.mjs +103 -0
- package/dist/versioned-content-index.mjs.map +1 -0
- package/dist/versioned-content-store.cjs +121 -0
- package/dist/versioned-content-store.d.cts +90 -0
- package/dist/versioned-content-store.d.cts.map +1 -0
- package/dist/versioned-content-store.d.mts +90 -0
- package/dist/versioned-content-store.d.mts.map +1 -0
- package/dist/versioned-content-store.mjs +122 -0
- package/dist/versioned-content-store.mjs.map +1 -0
- package/dist/versioned-scope.cjs +39 -0
- package/dist/versioned-scope.d.cts +57 -0
- package/dist/versioned-scope.d.cts.map +1 -0
- package/dist/versioned-scope.d.mts +57 -0
- package/dist/versioned-scope.d.mts.map +1 -0
- package/dist/versioned-scope.mjs +37 -0
- package/dist/versioned-scope.mjs.map +1 -0
- package/migrations/0001_tree_entries.sql +17 -0
- package/migrations/0002_resolve_entries.sql +7 -0
- package/migrations/0003_entries_v2.sql +38 -0
- package/migrations/0004_gc_queue.sql +5 -0
- package/migrations/0006_resolve_enhance.sql +14 -0
- package/migrations/0007_resolve_created_at.sql +10 -0
- package/migrations/0008_changelog.sql +34 -0
- package/migrations/0009_device_sync_state.sql +29 -0
- package/migrations/0010_scope_meta.sql +33 -0
- package/migrations/0011_shares.sql +25 -0
- package/migrations/0012_scope_meta_file_count.sql +41 -0
- package/migrations/0013_scope_meta_total_bytes.sql +47 -0
- package/migrations/0014_rename_tables.sql +62 -0
- package/package.json +102 -0
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
import { CHANGELOG_INDEX_SQL, CHANGELOG_TABLE_SQL, PREFIX_WATERMARK_BUMP_SQL, PREFIX_WATERMARK_READ_SQL, PREFIX_WATERMARK_TABLE_SQL, SCOPE_META_FILE_COUNT_DELETE_ONE_SQL, SCOPE_META_FILE_COUNT_DELETE_RANGE_SQL, SCOPE_META_FILE_COUNT_TRIGGER_SQL, SCOPE_META_TABLE_SQL, SCOPE_META_TOTAL_BYTES_COLUMN_SQL, SCOPE_META_TOTAL_BYTES_DELETE_ONE_SQL, SCOPE_META_TOTAL_BYTES_DELETE_RANGE_SQL, SCOPE_META_TOTAL_BYTES_TRIGGER_SQL, watermarkPrefix, watermarkWritePrefix } from "./changelog-schema.mjs";
|
|
2
|
+
import { resolveInlineConfig, resolveInlineContent } from "./inline-content.mjs";
|
|
3
|
+
import { resolveParentDirStamp } from "./timestamp.mjs";
|
|
4
|
+
import { STORAGE_QUOTA_EXCEEDED_CODE, buildKeysetCursor, buildOrderedQueryParts, parseTreeCursor, parseTreeEntryMetadata, serializeTreeEntryMetadata, storageQuotaError, treeEntryMetadataEqual } from "./tree-index.mjs";
|
|
5
|
+
import { buildChildrenFrom, buildTreeQueryResultFromSql, buildTreeQueryStatements, mapAggregateRows } from "./tree-query.mjs";
|
|
6
|
+
import { ENTRIES_CONTENT_TYPE_COLUMN_SQL, ENTRIES_CONTENT_TYPE_INDEX_DROP_SQL, ENTRIES_DEDUP_INDEX_SQL, ENTRIES_DEDUP_KEY_COLUMN_SQL, ENTRIES_DEDUP_SCOPE_COLUMN_SQL, ENTRIES_INDEX_SQL, ENTRIES_INLINE_CONTENT_COLUMN_SQL, ENTRIES_METADATA_COLUMN_SQL, ENTRIES_TABLE_SQL } from "./d1-schema.mjs";
|
|
7
|
+
import { validateDeviceId } from "./changelog-write.mjs";
|
|
8
|
+
import { prefixRange } from "./prefix-range.mjs";
|
|
9
|
+
import { DIRTY_PATH_ACK_SQL, DIRTY_PATH_INDEX_SQL, DIRTY_PATH_LIST_SQL, DIRTY_PATH_MARK_ERROR_SQL, DIRTY_PATH_TABLE_SQL, DIRTY_PATH_TRIGGER_SQL } from "./dirty-path-schema.mjs";
|
|
10
|
+
|
|
11
|
+
//#region src/d1-tree-index.ts
|
|
12
|
+
/**
|
|
13
|
+
* D1TreeIndex — D1 (SQLite) implementation of TreeIndex.
|
|
14
|
+
*
|
|
15
|
+
* Uses the `entries` table (v2 schema) with parent/name/kind columns
|
|
16
|
+
* for efficient directory operations. WITHOUT ROWID for direct PK lookup.
|
|
17
|
+
*
|
|
18
|
+
* Each instance is scoped to a (scope) partition.
|
|
19
|
+
*/
|
|
20
|
+
/** Extract parent directory path from a file/dir path */
|
|
21
|
+
function parentOf(path) {
|
|
22
|
+
if (!path) return "";
|
|
23
|
+
const clean = path.endsWith("/") ? path.slice(0, -1) : path;
|
|
24
|
+
const lastSlash = clean.lastIndexOf("/");
|
|
25
|
+
if (lastSlash <= 0) return "";
|
|
26
|
+
return clean.slice(0, lastSlash + 1);
|
|
27
|
+
}
|
|
28
|
+
/** Extract basename from a file/dir path */
|
|
29
|
+
function nameOf(path) {
|
|
30
|
+
if (!path) return "";
|
|
31
|
+
const clean = path.endsWith("/") ? path.slice(0, -1) : path;
|
|
32
|
+
const lastSlash = clean.lastIndexOf("/");
|
|
33
|
+
return lastSlash < 0 ? clean : clean.slice(lastSlash + 1);
|
|
34
|
+
}
|
|
35
|
+
/** Collect all ancestor directory paths from a file path (deepest first) */
|
|
36
|
+
function ancestorDirs(path) {
|
|
37
|
+
const dirs = [];
|
|
38
|
+
let current = path;
|
|
39
|
+
while (current.includes("/")) {
|
|
40
|
+
const slashIdx = current.lastIndexOf("/");
|
|
41
|
+
const dirPath = current.slice(0, slashIdx + 1);
|
|
42
|
+
if (!dirPath || dirPath === "/") break;
|
|
43
|
+
dirs.push(dirPath);
|
|
44
|
+
current = dirPath.slice(0, -1);
|
|
45
|
+
}
|
|
46
|
+
return dirs;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parent-dir-chain statements (Rule R2, feed-sort-createdat design §5.2).
|
|
50
|
+
*
|
|
51
|
+
* `DIR_INSERT_IGNORE_SQL` is the legacy "scaffold a dir if absent" — used for
|
|
52
|
+
* the whole ancestor chain and for a direct parent whose write carries no
|
|
53
|
+
* application `createdAt`. `DIR_STAMP_UPSERT_SQL` additionally overwrites the
|
|
54
|
+
* existing dir row's ctime/mtime — used ONLY for the DIRECT parent when the
|
|
55
|
+
* write carries a valid createdAt, so the feed's `orderBy ctime` (which sorts
|
|
56
|
+
* the DIR row `e.ctime`) reflects the application createdAt. Both take the
|
|
57
|
+
* SAME 6 binds: (scope, path, parent, name, ctime, mtime).
|
|
58
|
+
*/
|
|
59
|
+
const DIR_COLS = "(scope, path, parent, name, kind, size, ctime, mtime)";
|
|
60
|
+
const DIR_INSERT_IGNORE_SQL = `INSERT OR IGNORE INTO ds_entries ${DIR_COLS}
|
|
61
|
+
VALUES (?, ?, ?, ?, 'dir', 0, ?, ?)`;
|
|
62
|
+
const DIR_STAMP_UPSERT_SQL = `INSERT INTO ds_entries ${DIR_COLS}
|
|
63
|
+
VALUES (?, ?, ?, ?, 'dir', 0, ?, ?)
|
|
64
|
+
ON CONFLICT(scope, path) DO UPDATE SET ctime = excluded.ctime, mtime = excluded.mtime`;
|
|
65
|
+
/**
|
|
66
|
+
* Build the dir-chain statement for one `dirPath`: a Rule-R2 stamping UPSERT
|
|
67
|
+
* when `stamp` is non-null (the direct parent of a createdAt-bearing write),
|
|
68
|
+
* else the scaffold-only `INSERT OR IGNORE` at insert time `now`. Backend-
|
|
69
|
+
* agnostic `{ sql, args }`; the D1 caller wraps with `prepare().bind()`, the
|
|
70
|
+
* SQLite caller pushes it as-is.
|
|
71
|
+
*/
|
|
72
|
+
function buildDirChainStmt(scope, dirPath, stamp, now) {
|
|
73
|
+
const sql = stamp ? DIR_STAMP_UPSERT_SQL : DIR_INSERT_IGNORE_SQL;
|
|
74
|
+
const { ctime, mtime } = stamp ?? {
|
|
75
|
+
ctime: now,
|
|
76
|
+
mtime: now
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
sql,
|
|
80
|
+
args: [
|
|
81
|
+
scope,
|
|
82
|
+
dirPath,
|
|
83
|
+
parentOf(dirPath),
|
|
84
|
+
nameOf(dirPath),
|
|
85
|
+
ctime,
|
|
86
|
+
mtime
|
|
87
|
+
]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Maximum file entries per scope (files only, excludes dirs) */
|
|
91
|
+
const MAX_ENTRIES_PER_SCOPE = 1e6;
|
|
92
|
+
/**
|
|
93
|
+
* `isReferencedMany` IN-shard size for D1 (design §7.2, Finding 5). D1 caps a
|
|
94
|
+
* query at 100 bound parameters. The three-way UNION binds each cid 3× plus
|
|
95
|
+
* scope per branch (3 total), and 2 more per branch (lo/hi) when prefix-local:
|
|
96
|
+
* - whole-scope: 3N + 3 ≤ 100 → N ≤ 32
|
|
97
|
+
* - prefix-local: 3N + 3 + 6 ≤ 100 → N ≤ 30
|
|
98
|
+
* 28 is the conservative single value covering both branches (never 30+).
|
|
99
|
+
*/
|
|
100
|
+
const D1_MANY_SHARD_SIZE = 28;
|
|
101
|
+
var D1TreeIndex = class D1TreeIndex {
|
|
102
|
+
conn;
|
|
103
|
+
maxEntriesPerScope;
|
|
104
|
+
/** upload-limits Phase 5 byte quota (0 = disabled, the opt-in default). */
|
|
105
|
+
maxBytesPerScope;
|
|
106
|
+
inline;
|
|
107
|
+
constructor(db, scope, opts) {
|
|
108
|
+
this.db = db;
|
|
109
|
+
this.scope = scope;
|
|
110
|
+
this.conn = opts?.useReplica && typeof db.withSession === "function" ? db.withSession("first-unconstrained") : db;
|
|
111
|
+
this.maxEntriesPerScope = opts?.maxEntriesPerScope ?? MAX_ENTRIES_PER_SCOPE;
|
|
112
|
+
this.maxBytesPerScope = typeof opts?.maxBytesPerScope === "number" && opts.maxBytesPerScope > 0 ? opts.maxBytesPerScope : 0;
|
|
113
|
+
this.inline = resolveInlineConfig(opts);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Create entries table + indexes (v2 schema) AND the Phase 2 changelog table.
|
|
117
|
+
* Production: schema created by `wrangler d1 migrations apply`.
|
|
118
|
+
* Tests: call this in beforeEach with in-memory DB.
|
|
119
|
+
*
|
|
120
|
+
* Changelog DDL is sourced from `changelog-schema.ts` to keep the four
|
|
121
|
+
* runtimes (D1, desktop, Swift, Kotlin) byte-equivalent — see
|
|
122
|
+
* `scripts/check-changelog-ddl-parity.ts`. Schema-only here; producers come
|
|
123
|
+
* online in Task 2.4.
|
|
124
|
+
*/
|
|
125
|
+
async ensureTable() {
|
|
126
|
+
await this.db.batch([
|
|
127
|
+
this.db.prepare(ENTRIES_TABLE_SQL),
|
|
128
|
+
...ENTRIES_INDEX_SQL.map((sql) => this.db.prepare(sql)),
|
|
129
|
+
this.db.prepare(CHANGELOG_TABLE_SQL),
|
|
130
|
+
...CHANGELOG_INDEX_SQL.map((sql) => this.db.prepare(sql)),
|
|
131
|
+
this.db.prepare(SCOPE_META_TABLE_SQL),
|
|
132
|
+
...SCOPE_META_FILE_COUNT_TRIGGER_SQL.map((sql) => this.db.prepare(sql)),
|
|
133
|
+
this.db.prepare(PREFIX_WATERMARK_TABLE_SQL),
|
|
134
|
+
this.db.prepare(DIRTY_PATH_TABLE_SQL),
|
|
135
|
+
...DIRTY_PATH_INDEX_SQL.map((sql) => this.db.prepare(sql)),
|
|
136
|
+
...DIRTY_PATH_TRIGGER_SQL.map((sql) => this.db.prepare(sql))
|
|
137
|
+
]);
|
|
138
|
+
const { results } = await this.db.prepare("SELECT name FROM pragma_table_info('ds_entries')").all();
|
|
139
|
+
const colNames = new Set(results.map((r) => r.name));
|
|
140
|
+
if (!colNames.has("content")) try {
|
|
141
|
+
await this.db.prepare(ENTRIES_INLINE_CONTENT_COLUMN_SQL).run();
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
|
|
144
|
+
}
|
|
145
|
+
if (!colNames.has("metadata")) try {
|
|
146
|
+
await this.db.prepare(ENTRIES_METADATA_COLUMN_SQL).run();
|
|
147
|
+
} catch (err) {
|
|
148
|
+
if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
|
|
149
|
+
}
|
|
150
|
+
if (!colNames.has("dedup_scope")) try {
|
|
151
|
+
await this.db.prepare(ENTRIES_DEDUP_SCOPE_COLUMN_SQL).run();
|
|
152
|
+
} catch (err) {
|
|
153
|
+
if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
|
|
154
|
+
}
|
|
155
|
+
if (!colNames.has("dedup_key")) try {
|
|
156
|
+
await this.db.prepare(ENTRIES_DEDUP_KEY_COLUMN_SQL).run();
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
|
|
159
|
+
}
|
|
160
|
+
await this.db.prepare(ENTRIES_DEDUP_INDEX_SQL).run();
|
|
161
|
+
if (!colNames.has("content_type")) try {
|
|
162
|
+
await this.db.prepare(ENTRIES_CONTENT_TYPE_COLUMN_SQL).run();
|
|
163
|
+
} catch (err) {
|
|
164
|
+
if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
|
|
165
|
+
}
|
|
166
|
+
await this.db.prepare(ENTRIES_CONTENT_TYPE_INDEX_DROP_SQL).run();
|
|
167
|
+
const scopeMetaCols = await this.db.prepare("SELECT name FROM pragma_table_info('ds_scope_meta')").all();
|
|
168
|
+
if (!new Set(scopeMetaCols.results.map((r) => r.name)).has("total_bytes")) try {
|
|
169
|
+
await this.db.prepare(SCOPE_META_TOTAL_BYTES_COLUMN_SQL).run();
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (!(err instanceof Error ? err.message : String(err)).includes("duplicate column name")) throw err;
|
|
172
|
+
}
|
|
173
|
+
await this.db.batch(SCOPE_META_TOTAL_BYTES_TRIGGER_SQL.map((sql) => this.db.prepare(sql)));
|
|
174
|
+
}
|
|
175
|
+
async get(path, opts) {
|
|
176
|
+
const contentCol = (opts?.includeContent ?? true) && this.inline.inlineCache === "on" ? ", content" : "";
|
|
177
|
+
const row = await this.conn.prepare(`SELECT cid, size, mtime, ctime, mime_type, metadata${contentCol} FROM ds_entries WHERE scope = ? AND path = ?`).bind(this.scope, path).first();
|
|
178
|
+
if (!row || !row.cid) return null;
|
|
179
|
+
const entry = {
|
|
180
|
+
cid: row.cid,
|
|
181
|
+
size: row.size,
|
|
182
|
+
mtime: row.mtime,
|
|
183
|
+
ctime: row.ctime,
|
|
184
|
+
mimeType: row.mime_type ?? void 0
|
|
185
|
+
};
|
|
186
|
+
if (row.content != null) entry.content = row.content;
|
|
187
|
+
const meta = parseTreeEntryMetadata(row.metadata);
|
|
188
|
+
if (meta) entry.metadata = meta;
|
|
189
|
+
return entry;
|
|
190
|
+
}
|
|
191
|
+
async getDirTimes(dirPath) {
|
|
192
|
+
const row = await this.conn.prepare("SELECT ctime, mtime FROM ds_entries WHERE scope = ? AND path = ?").bind(this.scope, dirPath).first();
|
|
193
|
+
return row ? {
|
|
194
|
+
ctime: row.ctime,
|
|
195
|
+
mtime: row.mtime
|
|
196
|
+
} : null;
|
|
197
|
+
}
|
|
198
|
+
/** Inline content this entry would store — parity with the loop reference impl. */
|
|
199
|
+
inlineForEntry(entry) {
|
|
200
|
+
return resolveInlineContent(entry, this.inline);
|
|
201
|
+
}
|
|
202
|
+
/** Point seek on the `(scope, dedup_scope, dedup_key)` partial UNIQUE index. */
|
|
203
|
+
async findByDedupKey(dedupScope, dedupKey) {
|
|
204
|
+
const row = await this.conn.prepare(`SELECT path, cid, size, mtime, ctime, mime_type, metadata
|
|
205
|
+
FROM ds_entries WHERE scope = ? AND dedup_scope = ? AND dedup_key = ? LIMIT 1`).bind(this.scope, dedupScope, dedupKey).first();
|
|
206
|
+
if (!row || !row.cid) return null;
|
|
207
|
+
const entry = {
|
|
208
|
+
path: row.path,
|
|
209
|
+
cid: row.cid,
|
|
210
|
+
size: row.size,
|
|
211
|
+
mtime: row.mtime,
|
|
212
|
+
ctime: row.ctime,
|
|
213
|
+
mimeType: row.mime_type ?? void 0
|
|
214
|
+
};
|
|
215
|
+
const meta = parseTreeEntryMetadata(row.metadata);
|
|
216
|
+
if (meta) entry.metadata = meta;
|
|
217
|
+
return entry;
|
|
218
|
+
}
|
|
219
|
+
/** Index-only backfill: set the dedup columns without touching content/changelog. */
|
|
220
|
+
async setDedupKey(path, dedupScope, dedupKey, opts) {
|
|
221
|
+
validateDeviceId(opts.deviceId);
|
|
222
|
+
await this.db.prepare("UPDATE ds_entries SET dedup_scope = ?, dedup_key = ? WHERE scope = ? AND path = ?").bind(dedupScope, dedupKey, this.scope, path).run();
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Batch metadata read — one chunked `IN (…)` query per ≤90 paths (D1's
|
|
226
|
+
* 100-bind limit minus the scope bind, design §4.3). Result map carries an
|
|
227
|
+
* entry for EVERY requested path (`null` when absent), keyed verbatim.
|
|
228
|
+
*/
|
|
229
|
+
async getMany(paths) {
|
|
230
|
+
const map = /* @__PURE__ */ new Map();
|
|
231
|
+
for (const path of paths) map.set(path, null);
|
|
232
|
+
const contentCol = this.inline.inlineCache === "on" ? ", content" : "";
|
|
233
|
+
for (let offset = 0; offset < paths.length; offset += 90) {
|
|
234
|
+
const chunk = paths.slice(offset, offset + 90);
|
|
235
|
+
const { results } = await this.conn.prepare(`SELECT path, cid, size, mtime, ctime, mime_type, metadata${contentCol} FROM ds_entries
|
|
236
|
+
WHERE scope = ? AND kind = 'file' AND path IN (${chunk.map(() => "?").join(",")})`).bind(this.scope, ...chunk).all();
|
|
237
|
+
for (const row of results) {
|
|
238
|
+
if (!row.cid) continue;
|
|
239
|
+
const entry = {
|
|
240
|
+
cid: row.cid,
|
|
241
|
+
size: row.size,
|
|
242
|
+
mtime: row.mtime,
|
|
243
|
+
ctime: row.ctime,
|
|
244
|
+
mimeType: row.mime_type ?? void 0
|
|
245
|
+
};
|
|
246
|
+
if (row.content != null) entry.content = row.content;
|
|
247
|
+
const meta = parseTreeEntryMetadata(row.metadata);
|
|
248
|
+
if (meta) entry.metadata = meta;
|
|
249
|
+
map.set(row.path, entry);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return map;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Merged batch put (afs-rpc-batch §4.3 / §6.3) — the CF subrequest-budget
|
|
256
|
+
* fast path: ONE preread (caller-supplied or chunked IN), ONE capacity
|
|
257
|
+
* read, ONE atomic `db.batch` carrying every surviving entry's
|
|
258
|
+
* changelog+upsert (+1 watermark bump per write), then ONE watermark
|
|
259
|
+
* readback per distinct collection root to attribute per-entry seqs
|
|
260
|
+
* (`liveSeq[i] = final − N + 1 + i`, skip/conflict entries take no seq).
|
|
261
|
+
*
|
|
262
|
+
* Statement order inside the batch mirrors single `put`: dirs first, then
|
|
263
|
+
* per entry changelog INSERT (correlated old_cid subquery reads the
|
|
264
|
+
* pre-upsert cid) BEFORE its upsert — D1 preserves batch statement order.
|
|
265
|
+
*/
|
|
266
|
+
async batchPutEntries(entries, ctx) {
|
|
267
|
+
if (entries.length === 0) return [];
|
|
268
|
+
for (const e of entries) validateDeviceId(e.opts.deviceId);
|
|
269
|
+
const preread = ctx?.preread ?? await this.getMany(entries.map((e) => e.path));
|
|
270
|
+
const results = new Array(entries.length);
|
|
271
|
+
const live = [];
|
|
272
|
+
let newFileCount = 0;
|
|
273
|
+
for (let i = 0; i < entries.length; i++) {
|
|
274
|
+
const { path, entry, opts } = entries[i];
|
|
275
|
+
const existing = preread.get(path) ?? null;
|
|
276
|
+
if (opts.ifMatch !== void 0 && (!existing || existing.cid !== opts.ifMatch)) {
|
|
277
|
+
results[i] = {
|
|
278
|
+
path,
|
|
279
|
+
conflict: {
|
|
280
|
+
currentCid: existing?.cid ?? null,
|
|
281
|
+
currentMtime: existing?.mtime ?? null,
|
|
282
|
+
currentDeviceId: null
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
if (existing && existing.cid === entry.cid && existing.mimeType === entry.mimeType && treeEntryMetadataEqual(existing.metadata, entry.metadata)) {
|
|
288
|
+
if (this.inline.inlineCache !== "on") {
|
|
289
|
+
results[i] = {
|
|
290
|
+
path,
|
|
291
|
+
oldCid: existing.cid,
|
|
292
|
+
skipped: true
|
|
293
|
+
};
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const wouldBeInline = await resolveInlineContent(entry, this.inline);
|
|
297
|
+
if ((existing.content ?? null) === wouldBeInline) {
|
|
298
|
+
results[i] = {
|
|
299
|
+
path,
|
|
300
|
+
oldCid: existing.cid,
|
|
301
|
+
skipped: true
|
|
302
|
+
};
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (!existing) newFileCount++;
|
|
307
|
+
live.push(i);
|
|
308
|
+
}
|
|
309
|
+
if (newFileCount > 0) {
|
|
310
|
+
const currentCount = await this.getScopeFileCount();
|
|
311
|
+
let remaining = this.maxEntriesPerScope - currentCount;
|
|
312
|
+
if (remaining < newFileCount) {
|
|
313
|
+
const surviving = [];
|
|
314
|
+
for (const i of live) if (!(preread.get(entries[i].path) ?? null)) if (remaining > 0) {
|
|
315
|
+
remaining--;
|
|
316
|
+
surviving.push(i);
|
|
317
|
+
} else results[i] = {
|
|
318
|
+
path: entries[i].path,
|
|
319
|
+
error: `Storage quota exceeded: scope "${this.scope}" has reached the ${this.maxEntriesPerScope} file limit`,
|
|
320
|
+
code: STORAGE_QUOTA_EXCEEDED_CODE
|
|
321
|
+
};
|
|
322
|
+
else surviving.push(i);
|
|
323
|
+
live.length = 0;
|
|
324
|
+
live.push(...surviving);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (this.maxBytesPerScope > 0 && live.length > 0) {
|
|
328
|
+
const total = await this.getScopeTotalBytes();
|
|
329
|
+
let used = 0;
|
|
330
|
+
const surviving = [];
|
|
331
|
+
for (const i of live) {
|
|
332
|
+
const e = entries[i];
|
|
333
|
+
const newBytes = e.entry.size - (preread.get(e.path)?.size ?? 0);
|
|
334
|
+
if (newBytes > 0 && total + used + newBytes > this.maxBytesPerScope) results[i] = {
|
|
335
|
+
path: e.path,
|
|
336
|
+
error: `Storage quota exceeded: scope "${this.scope}" has reached the ${this.maxBytesPerScope}-byte limit`,
|
|
337
|
+
code: STORAGE_QUOTA_EXCEEDED_CODE
|
|
338
|
+
};
|
|
339
|
+
else {
|
|
340
|
+
if (newBytes > 0) used += newBytes;
|
|
341
|
+
surviving.push(i);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
live.length = 0;
|
|
345
|
+
live.push(...surviving);
|
|
346
|
+
}
|
|
347
|
+
if (live.length === 0) return results;
|
|
348
|
+
const now = Date.now();
|
|
349
|
+
const stmts = [];
|
|
350
|
+
const dirStamps = /* @__PURE__ */ new Map();
|
|
351
|
+
const allDirs = /* @__PURE__ */ new Set();
|
|
352
|
+
for (const i of live) {
|
|
353
|
+
const path = entries[i].path;
|
|
354
|
+
const stamp = resolveParentDirStamp(entries[i].entry.metadata);
|
|
355
|
+
if (stamp) dirStamps.set(parentOf(path), stamp);
|
|
356
|
+
for (const dirPath of ancestorDirs(path)) {
|
|
357
|
+
if (allDirs.has(dirPath)) break;
|
|
358
|
+
allDirs.add(dirPath);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
for (const dirPath of allDirs) {
|
|
362
|
+
const { sql, args } = buildDirChainStmt(this.scope, dirPath, dirStamps.get(dirPath) ?? null, now);
|
|
363
|
+
stmts.push(this.db.prepare(sql).bind(...args));
|
|
364
|
+
}
|
|
365
|
+
const writesByPrefix = /* @__PURE__ */ new Map();
|
|
366
|
+
for (const i of live) {
|
|
367
|
+
const { path, entry, opts } = entries[i];
|
|
368
|
+
stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
|
|
369
|
+
VALUES (?, ?, 'put', ?, (SELECT cid FROM ds_entries WHERE scope = ? AND path = ?), ?, ?, ?)`).bind(this.scope, path, entry.cid, this.scope, path, entry.size, entry.mtime, opts.deviceId));
|
|
370
|
+
stmts.push(await this.buildFileUpsert(path, entry));
|
|
371
|
+
const wmPrefix = watermarkWritePrefix(path);
|
|
372
|
+
if (wmPrefix !== null) {
|
|
373
|
+
stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
|
|
374
|
+
const order = writesByPrefix.get(wmPrefix) ?? [];
|
|
375
|
+
order.push(i);
|
|
376
|
+
writesByPrefix.set(wmPrefix, order);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
await this.db.batch(stmts);
|
|
380
|
+
for (const [wmPrefix, order] of writesByPrefix) {
|
|
381
|
+
const final = (await this.conn.prepare(PREFIX_WATERMARK_READ_SQL).bind(this.scope, wmPrefix).first())?.version ?? 0;
|
|
382
|
+
order.forEach((i, j) => {
|
|
383
|
+
const existing = preread.get(entries[i].path) ?? null;
|
|
384
|
+
results[i] = {
|
|
385
|
+
path: entries[i].path,
|
|
386
|
+
...existing ? { oldCid: existing.cid } : {},
|
|
387
|
+
liveSeq: final - order.length + 1 + j
|
|
388
|
+
};
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
for (const i of live) if (!results[i]) {
|
|
392
|
+
const existing = preread.get(entries[i].path) ?? null;
|
|
393
|
+
results[i] = {
|
|
394
|
+
path: entries[i].path,
|
|
395
|
+
...existing ? { oldCid: existing.cid } : {}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
return results;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Merged batch delete — same shape as {@link batchPutEntries} without the
|
|
402
|
+
* R2 leg: ONE preread, ONE atomic `db.batch` (per entry: changelog INSERT
|
|
403
|
+
* SELECT + file_count decrement + DELETE, +1 watermark bump), then empty-
|
|
404
|
+
* parent cleanup over the DISTINCT ancestor chain. FILE entries only.
|
|
405
|
+
*/
|
|
406
|
+
async batchDeleteEntries(entries, ctx) {
|
|
407
|
+
if (entries.length === 0) return [];
|
|
408
|
+
for (const e of entries) validateDeviceId(e.opts.deviceId);
|
|
409
|
+
const preread = ctx?.preread ?? await this.getMany(entries.map((e) => e.path));
|
|
410
|
+
const results = new Array(entries.length);
|
|
411
|
+
const live = [];
|
|
412
|
+
for (let i = 0; i < entries.length; i++) {
|
|
413
|
+
const { path, opts } = entries[i];
|
|
414
|
+
const existing = preread.get(path) ?? null;
|
|
415
|
+
if (opts.ifMatch !== void 0 && (!existing || existing.cid !== opts.ifMatch)) {
|
|
416
|
+
results[i] = {
|
|
417
|
+
path,
|
|
418
|
+
conflict: {
|
|
419
|
+
currentCid: existing?.cid ?? null,
|
|
420
|
+
currentMtime: existing?.mtime ?? null,
|
|
421
|
+
currentDeviceId: null
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (!existing) {
|
|
427
|
+
results[i] = {
|
|
428
|
+
path,
|
|
429
|
+
notFound: true
|
|
430
|
+
};
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
live.push(i);
|
|
434
|
+
}
|
|
435
|
+
if (live.length === 0) return results;
|
|
436
|
+
const stmts = [];
|
|
437
|
+
const writesByPrefix = /* @__PURE__ */ new Map();
|
|
438
|
+
for (const i of live) {
|
|
439
|
+
const { path, opts } = entries[i];
|
|
440
|
+
stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
|
|
441
|
+
SELECT ?, ?, 'delete', NULL, cid, NULL, ?, ?
|
|
442
|
+
FROM ds_entries WHERE scope = ? AND path = ? AND cid IS NOT NULL`).bind(this.scope, path, opts.mtime ?? Date.now(), opts.deviceId, this.scope, path));
|
|
443
|
+
stmts.push(this.db.prepare(SCOPE_META_FILE_COUNT_DELETE_ONE_SQL).bind(this.scope, this.scope, path));
|
|
444
|
+
stmts.push(this.db.prepare(SCOPE_META_TOTAL_BYTES_DELETE_ONE_SQL).bind(this.scope, path, this.scope));
|
|
445
|
+
stmts.push(this.db.prepare("DELETE FROM ds_entries WHERE scope = ? AND path = ?").bind(this.scope, path));
|
|
446
|
+
const wmPrefix = watermarkWritePrefix(path);
|
|
447
|
+
if (wmPrefix !== null) {
|
|
448
|
+
stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
|
|
449
|
+
const order = writesByPrefix.get(wmPrefix) ?? [];
|
|
450
|
+
order.push(i);
|
|
451
|
+
writesByPrefix.set(wmPrefix, order);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
await this.db.batch(stmts);
|
|
455
|
+
for (const [wmPrefix, order] of writesByPrefix) {
|
|
456
|
+
const final = (await this.conn.prepare(PREFIX_WATERMARK_READ_SQL).bind(this.scope, wmPrefix).first())?.version ?? 0;
|
|
457
|
+
order.forEach((i, j) => {
|
|
458
|
+
results[i] = {
|
|
459
|
+
path: entries[i].path,
|
|
460
|
+
deletedCid: (preread.get(entries[i].path) ?? null)?.cid,
|
|
461
|
+
liveSeq: final - order.length + 1 + j
|
|
462
|
+
};
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
for (const i of live) if (!results[i]) results[i] = {
|
|
466
|
+
path: entries[i].path,
|
|
467
|
+
deletedCid: (preread.get(entries[i].path) ?? null)?.cid
|
|
468
|
+
};
|
|
469
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
470
|
+
for (const i of live) for (const dirPath of ancestorDirs(entries[i].path)) dirs.add(dirPath);
|
|
471
|
+
const ordered = [...dirs].sort((a, b) => b.length - a.length);
|
|
472
|
+
for (const dirPath of ordered) {
|
|
473
|
+
if (await this.db.prepare("SELECT 1 FROM ds_entries WHERE scope = ? AND parent = ? LIMIT 1").bind(this.scope, dirPath).first()) continue;
|
|
474
|
+
await this.db.prepare("DELETE FROM ds_entries WHERE scope = ? AND path = ?").bind(this.scope, dirPath).run();
|
|
475
|
+
}
|
|
476
|
+
return results;
|
|
477
|
+
}
|
|
478
|
+
async put(path, entry, opts) {
|
|
479
|
+
validateDeviceId(opts.deviceId);
|
|
480
|
+
parentOf(path);
|
|
481
|
+
nameOf(path);
|
|
482
|
+
const now = Date.now();
|
|
483
|
+
const existing = await this.get(path);
|
|
484
|
+
if (existing && existing.cid === entry.cid && existing.mimeType === entry.mimeType && treeEntryMetadataEqual(existing.metadata, entry.metadata)) {
|
|
485
|
+
if (this.inline.inlineCache !== "on") return { oldCid: existing.cid };
|
|
486
|
+
const wouldBeInline = await resolveInlineContent(entry, this.inline);
|
|
487
|
+
if ((existing.content ?? null) === wouldBeInline) return { oldCid: existing.cid };
|
|
488
|
+
}
|
|
489
|
+
if (!existing) {
|
|
490
|
+
if (await this.getScopeFileCount() >= this.maxEntriesPerScope) throw storageQuotaError(`Storage quota exceeded: scope "${this.scope}" has reached the ${this.maxEntriesPerScope} file limit`);
|
|
491
|
+
}
|
|
492
|
+
if (this.maxBytesPerScope > 0) {
|
|
493
|
+
const newBytes = entry.size - (existing?.size ?? 0);
|
|
494
|
+
if (newBytes > 0) {
|
|
495
|
+
if (await this.getScopeTotalBytes() + newBytes > this.maxBytesPerScope) throw storageQuotaError(`Storage quota exceeded: scope "${this.scope}" has reached the ${this.maxBytesPerScope}-byte limit`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const stmts = [];
|
|
499
|
+
const dirStamp = resolveParentDirStamp(entry.metadata);
|
|
500
|
+
const directParent = parentOf(path);
|
|
501
|
+
for (const dirPath of ancestorDirs(path)) {
|
|
502
|
+
const { sql, args } = buildDirChainStmt(this.scope, dirPath, dirStamp && dirPath === directParent ? dirStamp : null, now);
|
|
503
|
+
stmts.push(this.db.prepare(sql).bind(...args));
|
|
504
|
+
}
|
|
505
|
+
if (!opts.skipChangelog) stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
|
|
506
|
+
VALUES (?, ?, 'put', ?, (SELECT cid FROM ds_entries WHERE scope = ? AND path = ?), ?, ?, ?)`).bind(this.scope, path, entry.cid, this.scope, path, entry.size, entry.mtime, opts.deviceId));
|
|
507
|
+
stmts.push(await this.buildFileUpsert(path, entry));
|
|
508
|
+
if (!opts.skipChangelog) {
|
|
509
|
+
const wmPrefix = watermarkWritePrefix(path);
|
|
510
|
+
if (wmPrefix !== null) stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
|
|
511
|
+
}
|
|
512
|
+
await this.db.batch(stmts);
|
|
513
|
+
return { oldCid: existing?.cid };
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Build the file-row UPSERT. Inline-enabled indexes always write the
|
|
517
|
+
* content column (inline copy or NULL — an overwrite without inline content
|
|
518
|
+
* MUST clear any prior copy so the column never goes stale relative to the
|
|
519
|
+
* new cid); default-off indexes keep the byte-identical legacy statement
|
|
520
|
+
* (and never touch a not-yet-migrated database's missing column).
|
|
521
|
+
*/
|
|
522
|
+
async buildFileUpsert(path, entry) {
|
|
523
|
+
const metadataValue = serializeTreeEntryMetadata(entry.metadata);
|
|
524
|
+
const dedupScopeValue = entry.dedupScope ?? null;
|
|
525
|
+
const dedupKeyValue = entry.dedupKey ?? null;
|
|
526
|
+
if (this.inline.inlineCache !== "on") return this.db.prepare(`INSERT INTO ds_entries (scope, path, parent, name, kind, cid, size, mime_type, ctime, mtime, metadata, dedup_scope, dedup_key)
|
|
527
|
+
VALUES (?, ?, ?, ?, 'file', ?, ?, ?, ?, ?, ?, ?, ?)
|
|
528
|
+
ON CONFLICT(scope, path) DO UPDATE SET
|
|
529
|
+
cid = excluded.cid,
|
|
530
|
+
size = excluded.size,
|
|
531
|
+
mtime = excluded.mtime,
|
|
532
|
+
mime_type = excluded.mime_type,
|
|
533
|
+
metadata = excluded.metadata,
|
|
534
|
+
dedup_scope = COALESCE(excluded.dedup_scope, ds_entries.dedup_scope),
|
|
535
|
+
dedup_key = COALESCE(excluded.dedup_key, ds_entries.dedup_key)`).bind(this.scope, path, parentOf(path), nameOf(path), entry.cid, entry.size, entry.mimeType ?? null, entry.ctime, entry.mtime, metadataValue, dedupScopeValue, dedupKeyValue);
|
|
536
|
+
const inlineContent = await resolveInlineContent(entry, this.inline);
|
|
537
|
+
return this.db.prepare(`INSERT INTO ds_entries (scope, path, parent, name, kind, cid, size, mime_type, ctime, mtime, content, metadata, dedup_scope, dedup_key)
|
|
538
|
+
VALUES (?, ?, ?, ?, 'file', ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
539
|
+
ON CONFLICT(scope, path) DO UPDATE SET
|
|
540
|
+
cid = excluded.cid,
|
|
541
|
+
size = excluded.size,
|
|
542
|
+
mtime = excluded.mtime,
|
|
543
|
+
mime_type = excluded.mime_type,
|
|
544
|
+
content = excluded.content,
|
|
545
|
+
metadata = excluded.metadata,
|
|
546
|
+
dedup_scope = COALESCE(excluded.dedup_scope, ds_entries.dedup_scope),
|
|
547
|
+
dedup_key = COALESCE(excluded.dedup_key, ds_entries.dedup_key)`).bind(this.scope, path, parentOf(path), nameOf(path), entry.cid, entry.size, entry.mimeType ?? null, entry.ctime, entry.mtime, inlineContent, metadataValue, dedupScopeValue, dedupKeyValue);
|
|
548
|
+
}
|
|
549
|
+
async delete(path, opts) {
|
|
550
|
+
validateDeviceId(opts.deviceId);
|
|
551
|
+
const stmts = [];
|
|
552
|
+
if (!opts.skipChangelog) stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
|
|
553
|
+
SELECT ?, ?, 'delete', NULL, cid, NULL, ?, ?
|
|
554
|
+
FROM ds_entries WHERE scope = ? AND path = ? AND cid IS NOT NULL`).bind(this.scope, path, opts.mtime ?? Date.now(), opts.deviceId, this.scope, path));
|
|
555
|
+
stmts.push(this.db.prepare(SCOPE_META_FILE_COUNT_DELETE_ONE_SQL).bind(this.scope, this.scope, path));
|
|
556
|
+
stmts.push(this.db.prepare(SCOPE_META_TOTAL_BYTES_DELETE_ONE_SQL).bind(this.scope, path, this.scope));
|
|
557
|
+
stmts.push(this.db.prepare("DELETE FROM ds_entries WHERE scope = ? AND path = ?").bind(this.scope, path));
|
|
558
|
+
if (!opts.skipChangelog) {
|
|
559
|
+
const wmPrefix = watermarkWritePrefix(path);
|
|
560
|
+
if (wmPrefix !== null) stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
|
|
561
|
+
}
|
|
562
|
+
await this.db.batch(stmts);
|
|
563
|
+
await this.cleanEmptyParents(path);
|
|
564
|
+
}
|
|
565
|
+
/** Walk up parent chain, removing directory entries that have no remaining children */
|
|
566
|
+
async cleanEmptyParents(path) {
|
|
567
|
+
for (const dirPath of ancestorDirs(path)) {
|
|
568
|
+
if (await this.db.prepare("SELECT 1 FROM ds_entries WHERE scope = ? AND parent = ? LIMIT 1").bind(this.scope, dirPath).first()) break;
|
|
569
|
+
await this.db.prepare("DELETE FROM ds_entries WHERE scope = ? AND path = ?").bind(this.scope, dirPath).run();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
static VALID_ORDER_BY = {
|
|
573
|
+
path: "path",
|
|
574
|
+
mtime: "mtime",
|
|
575
|
+
size: "size",
|
|
576
|
+
ctime: "ctime",
|
|
577
|
+
mimeType: "mime_type",
|
|
578
|
+
name: "name"
|
|
579
|
+
};
|
|
580
|
+
/** The only nullable sortable column — NULLs sort last (see tree-index.ts). */
|
|
581
|
+
static NULLABLE_ORDER_COLUMNS = new Set(["mime_type"]);
|
|
582
|
+
async list(prefix, options) {
|
|
583
|
+
const limit = options?.limit ?? 1e3;
|
|
584
|
+
const orderByKey = options?.orderBy ?? "path";
|
|
585
|
+
const order = options?.order ?? "asc";
|
|
586
|
+
const cursor = options?.cursor;
|
|
587
|
+
const col = D1TreeIndex.VALID_ORDER_BY[orderByKey];
|
|
588
|
+
if (!col) throw new Error(`Invalid orderBy: ${orderByKey}`);
|
|
589
|
+
if (limit === 0) return {
|
|
590
|
+
entries: [],
|
|
591
|
+
hasMore: false
|
|
592
|
+
};
|
|
593
|
+
const [lo, hi] = prefixRange(prefix);
|
|
594
|
+
const params = [
|
|
595
|
+
this.scope,
|
|
596
|
+
lo,
|
|
597
|
+
hi
|
|
598
|
+
];
|
|
599
|
+
let sql = `SELECT path, ${col} as _sort FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'`;
|
|
600
|
+
if (cursor) {
|
|
601
|
+
sql += ` AND ${col} ${order === "asc" ? ">" : "<"} ?`;
|
|
602
|
+
params.push(cursor);
|
|
603
|
+
}
|
|
604
|
+
sql += ` ORDER BY ${col} ${order} LIMIT ?`;
|
|
605
|
+
params.push(limit + 1);
|
|
606
|
+
const { results } = await this.conn.prepare(sql).bind(...params).all();
|
|
607
|
+
const hasMore = results.length > limit;
|
|
608
|
+
const selected = hasMore ? results.slice(0, limit) : results;
|
|
609
|
+
return {
|
|
610
|
+
entries: selected.map((r) => ({ path: r.path })),
|
|
611
|
+
hasMore,
|
|
612
|
+
cursor: hasMore && selected.length > 0 ? String(selected[selected.length - 1]._sort) : void 0
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
async listEntries(prefix, options) {
|
|
616
|
+
const limit = options?.limit ?? 1e3;
|
|
617
|
+
const hasOrderBy = options?.orderBy !== void 0;
|
|
618
|
+
const orderByKey = options?.orderBy ?? "path";
|
|
619
|
+
const order = options?.order === "desc" ? "desc" : "asc";
|
|
620
|
+
const col = D1TreeIndex.VALID_ORDER_BY[orderByKey];
|
|
621
|
+
if (!col) throw new Error(`Invalid orderBy: ${orderByKey}`);
|
|
622
|
+
if (limit === 0) return {
|
|
623
|
+
entries: [],
|
|
624
|
+
hasMore: false
|
|
625
|
+
};
|
|
626
|
+
const parsed = parseTreeCursor(options?.cursor, hasOrderBy);
|
|
627
|
+
const [lo, hi] = prefixRange(prefix);
|
|
628
|
+
const params = [
|
|
629
|
+
this.scope,
|
|
630
|
+
lo,
|
|
631
|
+
hi
|
|
632
|
+
];
|
|
633
|
+
let sql = `SELECT path, cid, size, mtime, ctime, mime_type${options?.includeContent ? ", content" : ""}, ${col} as _sort FROM ds_entries${col === "path" ? " INDEXED BY idx_entries_kind" : ""} WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'`;
|
|
634
|
+
if (hasOrderBy) {
|
|
635
|
+
const parts = buildOrderedQueryParts({
|
|
636
|
+
col,
|
|
637
|
+
nullable: D1TreeIndex.NULLABLE_ORDER_COLUMNS.has(col),
|
|
638
|
+
order,
|
|
639
|
+
tieCol: "path",
|
|
640
|
+
cursor: parsed.kind === "v2" ? parsed.cursor : void 0
|
|
641
|
+
});
|
|
642
|
+
if (parts.cursorWhereSql) {
|
|
643
|
+
sql += ` AND ${parts.cursorWhereSql}`;
|
|
644
|
+
params.push(...parts.cursorArgs);
|
|
645
|
+
}
|
|
646
|
+
sql += ` ORDER BY ${parts.orderSql} LIMIT ?`;
|
|
647
|
+
} else {
|
|
648
|
+
if (parsed.kind === "legacy") {
|
|
649
|
+
sql += ` AND ${col} ${order === "asc" ? ">" : "<"} ?`;
|
|
650
|
+
params.push(parsed.value);
|
|
651
|
+
}
|
|
652
|
+
sql += ` ORDER BY ${col} ${order} LIMIT ?`;
|
|
653
|
+
}
|
|
654
|
+
params.push(limit + 1);
|
|
655
|
+
const { results } = await this.conn.prepare(sql).bind(...params).all();
|
|
656
|
+
const hasMore = results.length > limit;
|
|
657
|
+
const selected = hasMore ? results.slice(0, limit) : results;
|
|
658
|
+
const last = selected[selected.length - 1];
|
|
659
|
+
return {
|
|
660
|
+
entries: selected.map((r) => {
|
|
661
|
+
const e = {
|
|
662
|
+
path: r.path,
|
|
663
|
+
cid: r.cid,
|
|
664
|
+
size: r.size,
|
|
665
|
+
mtime: r.mtime,
|
|
666
|
+
ctime: r.ctime,
|
|
667
|
+
mimeType: r.mime_type ?? void 0
|
|
668
|
+
};
|
|
669
|
+
if (options?.includeContent && r.content != null) e.content = r.content;
|
|
670
|
+
return e;
|
|
671
|
+
}),
|
|
672
|
+
hasMore,
|
|
673
|
+
cursor: !hasMore || !last ? void 0 : hasOrderBy ? buildKeysetCursor(last._sort ?? null, last.path) : String(last._sort)
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
async hasChildren(prefix) {
|
|
677
|
+
const [lo, hi] = prefixRange(prefix);
|
|
678
|
+
return await this.conn.prepare("SELECT 1 FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file' LIMIT 1").bind(this.scope, lo, hi).first() !== null;
|
|
679
|
+
}
|
|
680
|
+
async getAndHasChildren(filePath, dirPrefix) {
|
|
681
|
+
const contentCol = this.inline.inlineCache === "on" ? ", content" : "";
|
|
682
|
+
const [lo, hi] = prefixRange(dirPrefix);
|
|
683
|
+
const batchResults = await this.db.batch([this.db.prepare(`SELECT cid, size, mtime, ctime, mime_type, metadata${contentCol} FROM ds_entries WHERE scope = ? AND path = ?`).bind(this.scope, filePath), this.db.prepare("SELECT 1 FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file' LIMIT 1").bind(this.scope, lo, hi)]);
|
|
684
|
+
const getResult = batchResults[0];
|
|
685
|
+
const hasChildResult = batchResults[1];
|
|
686
|
+
const row = getResult.results[0];
|
|
687
|
+
let entry = null;
|
|
688
|
+
if (row?.cid) {
|
|
689
|
+
entry = {
|
|
690
|
+
cid: row.cid,
|
|
691
|
+
size: row.size,
|
|
692
|
+
mtime: row.mtime,
|
|
693
|
+
ctime: row.ctime,
|
|
694
|
+
mimeType: row.mime_type ?? void 0
|
|
695
|
+
};
|
|
696
|
+
if (row.content != null) entry.content = row.content;
|
|
697
|
+
const meta = parseTreeEntryMetadata(row.metadata);
|
|
698
|
+
if (meta) entry.metadata = meta;
|
|
699
|
+
}
|
|
700
|
+
return [entry, (hasChildResult.results?.length ?? 0) > 0];
|
|
701
|
+
}
|
|
702
|
+
async count(prefix) {
|
|
703
|
+
const [lo, hi] = prefixRange(prefix);
|
|
704
|
+
return (await this.conn.prepare("SELECT COUNT(*) as cnt FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'").bind(this.scope, lo, hi).first())?.cnt ?? 0;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* O(1) read of the trigger-maintained file count for this scope, used by the
|
|
708
|
+
* `put()` capacity guard. A missing `scope_meta` row means "no files counted
|
|
709
|
+
* yet" → 0, which is also the correct answer for a brand-new scope and the
|
|
710
|
+
* safe (permissive) fallback for a scope not yet backfilled. This replaces
|
|
711
|
+
* the per-write full-scope `count("")` scan — the source of the O(N²)
|
|
712
|
+
* `rows_read` blow-up that exhausted the D1 free tier during bulk sync.
|
|
713
|
+
*/
|
|
714
|
+
async getScopeFileCount() {
|
|
715
|
+
return (await this.conn.prepare("SELECT file_count FROM ds_scope_meta WHERE scope = ?").bind(this.scope).first())?.file_count ?? 0;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* O(1) read of the trigger-maintained byte total for this scope, used by the
|
|
719
|
+
* Phase 5 byte-quota guard (only when `maxBytesPerScope > 0`). Same O(1)
|
|
720
|
+
* contract as {@link getScopeFileCount} — never a `SELECT SUM(size)` scan.
|
|
721
|
+
* Missing row → 0 (new/un-backfilled scope; permissive).
|
|
722
|
+
*/
|
|
723
|
+
async getScopeTotalBytes() {
|
|
724
|
+
return (await this.conn.prepare("SELECT total_bytes FROM ds_scope_meta WHERE scope = ?").bind(this.scope).first())?.total_bytes ?? 0;
|
|
725
|
+
}
|
|
726
|
+
async getCompactedBelowSeq() {
|
|
727
|
+
return (await this.conn.prepare("SELECT compacted_below_seq FROM ds_scope_meta WHERE scope = ?").bind(this.scope).first())?.compacted_below_seq ?? 0;
|
|
728
|
+
}
|
|
729
|
+
async deleteByPrefix(prefix, opts) {
|
|
730
|
+
validateDeviceId(opts.deviceId);
|
|
731
|
+
const [lo, hi] = prefixRange(prefix);
|
|
732
|
+
const stmts = [];
|
|
733
|
+
if (!opts.skipChangelog) {
|
|
734
|
+
const now = opts.mtime ?? Date.now();
|
|
735
|
+
stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
|
|
736
|
+
SELECT ?, path, 'delete', NULL, cid, NULL, ?, ?
|
|
737
|
+
FROM ds_entries INDEXED BY idx_entries_kind
|
|
738
|
+
WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file' AND cid IS NOT NULL`).bind(this.scope, now, opts.deviceId, this.scope, lo, hi));
|
|
739
|
+
}
|
|
740
|
+
stmts.push(this.db.prepare(SCOPE_META_FILE_COUNT_DELETE_RANGE_SQL).bind(this.scope, lo, hi, this.scope));
|
|
741
|
+
stmts.push(this.db.prepare(SCOPE_META_TOTAL_BYTES_DELETE_RANGE_SQL).bind(this.scope, lo, hi, this.scope));
|
|
742
|
+
if (!opts.skipChangelog) {
|
|
743
|
+
const wmPrefix = watermarkPrefix(prefix);
|
|
744
|
+
if (wmPrefix !== null) stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
|
|
745
|
+
}
|
|
746
|
+
stmts.push(this.db.prepare("DELETE FROM ds_entries WHERE scope = ? AND path >= ? AND path < ?").bind(this.scope, lo, hi));
|
|
747
|
+
const batchResults = await this.db.batch(stmts);
|
|
748
|
+
return batchResults[batchResults.length - 1].meta.changes;
|
|
749
|
+
}
|
|
750
|
+
async listChildren(parent, options) {
|
|
751
|
+
const limit = options?.limit ?? 1e3;
|
|
752
|
+
const order = options?.order === "desc" ? "desc" : "asc";
|
|
753
|
+
if (limit === 0) return {
|
|
754
|
+
entries: [],
|
|
755
|
+
hasMore: false
|
|
756
|
+
};
|
|
757
|
+
const hasOrderBy = options?.orderBy !== void 0;
|
|
758
|
+
const parsed = parseTreeCursor(options?.cursor, hasOrderBy);
|
|
759
|
+
const params = [this.scope, parent];
|
|
760
|
+
const includeContent = options?.includeContent === true;
|
|
761
|
+
const t = "e.";
|
|
762
|
+
const from = buildChildrenFrom(void 0);
|
|
763
|
+
const fromClause = from.sql;
|
|
764
|
+
const contentCol = includeContent ? `, ${t}content` : "";
|
|
765
|
+
params.unshift(...from.args);
|
|
766
|
+
const mapChild = (r) => {
|
|
767
|
+
const e = {
|
|
768
|
+
path: r.path,
|
|
769
|
+
cid: r.cid ?? "",
|
|
770
|
+
size: r.size,
|
|
771
|
+
mtime: r.mtime,
|
|
772
|
+
ctime: r.ctime,
|
|
773
|
+
mimeType: r.mime_type ?? void 0
|
|
774
|
+
};
|
|
775
|
+
if (includeContent && r.content != null) e.content = r.content;
|
|
776
|
+
return e;
|
|
777
|
+
};
|
|
778
|
+
if (hasOrderBy) {
|
|
779
|
+
const col = D1TreeIndex.VALID_ORDER_BY[options.orderBy];
|
|
780
|
+
if (!col) throw new Error(`Invalid orderBy: ${options?.orderBy}`);
|
|
781
|
+
const parts = buildOrderedQueryParts({
|
|
782
|
+
col: `${t}${col}`,
|
|
783
|
+
nullable: D1TreeIndex.NULLABLE_ORDER_COLUMNS.has(col),
|
|
784
|
+
order,
|
|
785
|
+
tieCol: `${t}name`,
|
|
786
|
+
cursor: parsed.kind === "v2" ? parsed.cursor : void 0
|
|
787
|
+
});
|
|
788
|
+
let sql$1 = `SELECT ${t}path, ${t}cid, ${t}size, ${t}mtime, ${t}ctime, ${t}mime_type, ${t}name${contentCol}, ${t}${col} as _sort ${fromClause} WHERE ${t}scope = ? AND ${t}parent = ?`;
|
|
789
|
+
if (parts.cursorWhereSql) {
|
|
790
|
+
sql$1 += ` AND ${parts.cursorWhereSql}`;
|
|
791
|
+
params.push(...parts.cursorArgs);
|
|
792
|
+
}
|
|
793
|
+
sql$1 += ` ORDER BY ${parts.orderSql} LIMIT ?`;
|
|
794
|
+
params.push(limit + 1);
|
|
795
|
+
const { results: results$1 } = await this.conn.prepare(sql$1).bind(...params).all();
|
|
796
|
+
const hasMore$1 = results$1.length > limit;
|
|
797
|
+
const selected$1 = hasMore$1 ? results$1.slice(0, limit) : results$1;
|
|
798
|
+
const last = selected$1[selected$1.length - 1];
|
|
799
|
+
return {
|
|
800
|
+
entries: selected$1.map(mapChild),
|
|
801
|
+
hasMore: hasMore$1,
|
|
802
|
+
cursor: hasMore$1 && last ? buildKeysetCursor(last._sort ?? null, last.name) : void 0
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
let sql = `SELECT ${t}path, ${t}cid, ${t}size, ${t}mtime, ${t}ctime, ${t}mime_type, ${t}name${contentCol}, ${t}name as _sort ${fromClause} WHERE ${t}scope = ? AND ${t}parent = ?`;
|
|
806
|
+
if (parsed.kind === "legacy") {
|
|
807
|
+
sql += ` AND ${t}name ${order === "asc" ? ">" : "<"} ?`;
|
|
808
|
+
params.push(parsed.value);
|
|
809
|
+
}
|
|
810
|
+
sql += ` ORDER BY ${t}name ${order} LIMIT ?`;
|
|
811
|
+
params.push(limit + 1);
|
|
812
|
+
const { results } = await this.conn.prepare(sql).bind(...params).all();
|
|
813
|
+
const hasMore = results.length > limit;
|
|
814
|
+
const selected = hasMore ? results.slice(0, limit) : results;
|
|
815
|
+
return {
|
|
816
|
+
entries: selected.map(mapChild),
|
|
817
|
+
hasMore,
|
|
818
|
+
cursor: hasMore && selected.length > 0 ? String(selected[selected.length - 1].name) : void 0
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
async countChildren(parent) {
|
|
822
|
+
return (await this.conn.prepare("SELECT COUNT(*) as cnt FROM ds_entries WHERE scope = ? AND parent = ?").bind(this.scope, parent).first())?.cnt ?? 0;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Collection query (afs-collection-query Phase 2) — same shared compiler +
|
|
826
|
+
* result builder as SQLiteTreeIndex (tree-query.ts), executed over the
|
|
827
|
+
* replica-aware `this.conn`: query is read-class and follows the SAME
|
|
828
|
+
* replica routing policy as list (AFS_D1_READ_REPLICA gate upstream picks
|
|
829
|
+
* the session; no separate consistency standard — design T2.1).
|
|
830
|
+
*/
|
|
831
|
+
async query(parent, spec) {
|
|
832
|
+
const statements = buildTreeQueryStatements({
|
|
833
|
+
scope: this.scope,
|
|
834
|
+
parent,
|
|
835
|
+
spec
|
|
836
|
+
});
|
|
837
|
+
if (statements.aggregate) {
|
|
838
|
+
const [aggRes, countRow$1] = await Promise.all([this.conn.prepare(statements.aggregate.select.sql).bind(...statements.aggregate.select.args).all(), this.conn.prepare(statements.aggregate.countGroups.sql).bind(...statements.aggregate.countGroups.args).first()]);
|
|
839
|
+
const aggregateGroups = mapAggregateRows(aggRes.results, statements.aggregate.groupKeys, statements.aggregate.aggregations);
|
|
840
|
+
return {
|
|
841
|
+
rows: [],
|
|
842
|
+
total: statements.aggregate.groupKeys.length === 0 ? 1 : countRow$1?.cnt ?? 0,
|
|
843
|
+
aggregateGroups
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
const [rowsRes, countRow, groupsRes, seekRow] = await Promise.all([
|
|
847
|
+
this.conn.prepare(statements.rows.sql).bind(...statements.rows.args).all(),
|
|
848
|
+
statements.count ? this.conn.prepare(statements.count.sql).bind(...statements.count.args).first() : Promise.resolve(void 0),
|
|
849
|
+
statements.groups ? this.conn.prepare(statements.groups.sql).bind(...statements.groups.args).all() : Promise.resolve(void 0),
|
|
850
|
+
statements.seek ? this.conn.prepare(statements.seek.sql).bind(...statements.seek.args).first() : Promise.resolve(void 0)
|
|
851
|
+
]);
|
|
852
|
+
const seekOffset = statements.seek ? seekRow?.rn ?? -1 : void 0;
|
|
853
|
+
return buildTreeQueryResultFromSql({
|
|
854
|
+
rawRows: rowsRes.results,
|
|
855
|
+
total: statements.count ? countRow?.cnt ?? 0 : -1,
|
|
856
|
+
groupRows: groupsRes?.results,
|
|
857
|
+
spec,
|
|
858
|
+
orderField: statements.order.field,
|
|
859
|
+
seekOffset
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Page-bounded grouped child counts: ONE chunked `parent IN (…)` aggregate
|
|
864
|
+
* per ≤90 parents (D1's 100-bind limit minus the scope bind — same chunking
|
|
865
|
+
* as `getMany`), index-only seeks on `idx_entries_children(scope, parent,
|
|
866
|
+
* name)`. rows_read is bounded by the listed page's direct grandchildren.
|
|
867
|
+
*
|
|
868
|
+
* Predecessor (`countChildrenGrouped(prefix)`) used a `parent >= lo AND
|
|
869
|
+
* parent < hi` range — a scan/group over EVERY descendant under the listed
|
|
870
|
+
* prefix on EVERY list. Once the aside user space grew to ~1.8k dirs, each
|
|
871
|
+
* list of any ancestor dir read ~3.8k rows; at list frequency that burned
|
|
872
|
+
* ~100M D1 rows_read/day (42× the free tier).
|
|
873
|
+
*/
|
|
874
|
+
async countChildrenMany(parents) {
|
|
875
|
+
const map = /* @__PURE__ */ new Map();
|
|
876
|
+
for (let offset = 0; offset < parents.length; offset += 90) {
|
|
877
|
+
const chunk = parents.slice(offset, offset + 90);
|
|
878
|
+
const { results } = await this.conn.prepare(`SELECT parent, COUNT(*) as cnt FROM ds_entries WHERE scope = ? AND parent IN (${chunk.map(() => "?").join(",")}) GROUP BY parent`).bind(this.scope, ...chunk).all();
|
|
879
|
+
for (const row of results) map.set(row.parent, row.cnt);
|
|
880
|
+
}
|
|
881
|
+
return map;
|
|
882
|
+
}
|
|
883
|
+
async aggregate(prefix) {
|
|
884
|
+
const [lo, hi] = prefixRange(prefix);
|
|
885
|
+
const row = await this.conn.prepare(`SELECT COUNT(*) as file_count, COALESCE(SUM(size), 0) as total_size
|
|
886
|
+
FROM ds_entries INDEXED BY idx_entries_kind WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'`).bind(this.scope, lo, hi).first();
|
|
887
|
+
return {
|
|
888
|
+
fileCount: row?.file_count ?? 0,
|
|
889
|
+
totalSize: row?.total_size ?? 0
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
async putBatch(entries, opts) {
|
|
893
|
+
if (entries.length === 0) return;
|
|
894
|
+
validateDeviceId(opts.deviceId);
|
|
895
|
+
const now = Date.now();
|
|
896
|
+
const stmts = [];
|
|
897
|
+
const dirStamps = /* @__PURE__ */ new Map();
|
|
898
|
+
const allDirs = /* @__PURE__ */ new Set();
|
|
899
|
+
for (const { path, entry } of entries) {
|
|
900
|
+
const stamp = resolveParentDirStamp(entry.metadata);
|
|
901
|
+
if (stamp) dirStamps.set(parentOf(path), stamp);
|
|
902
|
+
for (const dirPath of ancestorDirs(path)) {
|
|
903
|
+
if (allDirs.has(dirPath)) break;
|
|
904
|
+
allDirs.add(dirPath);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
for (const dirPath of allDirs) {
|
|
908
|
+
const { sql, args } = buildDirChainStmt(this.scope, dirPath, dirStamps.get(dirPath) ?? null, now);
|
|
909
|
+
stmts.push(this.db.prepare(sql).bind(...args));
|
|
910
|
+
}
|
|
911
|
+
for (const { path, entry } of entries) {
|
|
912
|
+
if (!opts.skipChangelog) stmts.push(this.db.prepare(`INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)
|
|
913
|
+
VALUES (?, ?, 'put', ?, (SELECT cid FROM ds_entries WHERE scope = ? AND path = ?), ?, ?, ?)`).bind(this.scope, path, entry.cid, this.scope, path, entry.size, entry.mtime, opts.deviceId));
|
|
914
|
+
stmts.push(await this.buildFileUpsert(path, entry));
|
|
915
|
+
}
|
|
916
|
+
if (!opts.skipChangelog) {
|
|
917
|
+
const wmPrefixes = /* @__PURE__ */ new Set();
|
|
918
|
+
for (const { path } of entries) {
|
|
919
|
+
const wmPrefix = watermarkWritePrefix(path);
|
|
920
|
+
if (wmPrefix !== null) wmPrefixes.add(wmPrefix);
|
|
921
|
+
}
|
|
922
|
+
for (const wmPrefix of wmPrefixes) stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));
|
|
923
|
+
}
|
|
924
|
+
await this.db.batch(stmts);
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Read the per-prefix watermark version (Live Update Resilience §11.4).
|
|
928
|
+
* Reduces `prefix` to its first path segment (collection root) and returns
|
|
929
|
+
* that row's `version`, or 0 when no row exists. Replica-aware via `this.conn`
|
|
930
|
+
* — but reconcile passes a primary-pinned index for read-your-watermark
|
|
931
|
+
* (DEC-5; see CloudflareDIDSpace.getInstanceSpace consistency option).
|
|
932
|
+
*/
|
|
933
|
+
async getPrefixWatermark(prefix) {
|
|
934
|
+
const wmPrefix = watermarkPrefix(prefix);
|
|
935
|
+
if (wmPrefix === null) return null;
|
|
936
|
+
return (await this.conn.prepare(PREFIX_WATERMARK_READ_SQL).bind(this.scope, wmPrefix).first())?.version ?? 0;
|
|
937
|
+
}
|
|
938
|
+
async listModifiedSince(prefix, since, options) {
|
|
939
|
+
const limit = options?.limit ?? 1e3;
|
|
940
|
+
const order = options?.order ?? "asc";
|
|
941
|
+
const cursor = options?.cursor;
|
|
942
|
+
if (limit === 0) return {
|
|
943
|
+
entries: [],
|
|
944
|
+
hasMore: false
|
|
945
|
+
};
|
|
946
|
+
const [lo, hi] = prefixRange(prefix);
|
|
947
|
+
const params = [
|
|
948
|
+
this.scope,
|
|
949
|
+
lo,
|
|
950
|
+
hi,
|
|
951
|
+
since
|
|
952
|
+
];
|
|
953
|
+
let sql = `SELECT path, cid, size, mtime, ctime, mime_type FROM ds_entries INDEXED BY idx_entries_kind
|
|
954
|
+
WHERE scope = ? AND path >= ? AND path < ? AND mtime > ? AND kind = 'file'`;
|
|
955
|
+
if (cursor) {
|
|
956
|
+
sql += ` AND path ${order === "asc" ? ">" : "<"} ?`;
|
|
957
|
+
params.push(cursor);
|
|
958
|
+
}
|
|
959
|
+
sql += ` ORDER BY path ${order} LIMIT ?`;
|
|
960
|
+
params.push(limit + 1);
|
|
961
|
+
const { results } = await this.conn.prepare(sql).bind(...params).all();
|
|
962
|
+
const hasMore = results.length > limit;
|
|
963
|
+
const selected = hasMore ? results.slice(0, limit) : results;
|
|
964
|
+
return {
|
|
965
|
+
entries: selected.map((r) => ({
|
|
966
|
+
path: r.path,
|
|
967
|
+
cid: r.cid,
|
|
968
|
+
size: r.size,
|
|
969
|
+
mtime: r.mtime,
|
|
970
|
+
ctime: r.ctime,
|
|
971
|
+
mimeType: r.mime_type ?? void 0
|
|
972
|
+
})),
|
|
973
|
+
hasMore,
|
|
974
|
+
cursor: hasMore && selected.length > 0 ? String(selected[selected.length - 1].path) : void 0
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
async getMaxSeq() {
|
|
978
|
+
const [row, compactedBelow] = await Promise.all([this.conn.prepare("SELECT MAX(seq) as maxSeq FROM ds_changelog WHERE scope = ?").bind(this.scope).first(), this.getCompactedBelowSeq()]);
|
|
979
|
+
return Math.max(row?.maxSeq ?? 0, compactedBelow);
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Read-auth reference check (design §4.1) — three-way, scoped, optionally
|
|
983
|
+
* prefix-local. See {@link TreeIndex.isReferenced}.
|
|
984
|
+
*/
|
|
985
|
+
async isReferenced(cid, pathPrefix = "") {
|
|
986
|
+
if (!cid) return false;
|
|
987
|
+
if (pathPrefix === "") return await this.conn.prepare(`SELECT 1 FROM (
|
|
988
|
+
SELECT 1 FROM ds_entries WHERE scope = ? AND cid = ?
|
|
989
|
+
UNION ALL
|
|
990
|
+
SELECT 1 FROM ds_changelog WHERE scope = ? AND cid = ?
|
|
991
|
+
UNION ALL
|
|
992
|
+
SELECT 1 FROM ds_changelog WHERE scope = ? AND old_cid = ?
|
|
993
|
+
) LIMIT 1`).bind(this.scope, cid, this.scope, cid, this.scope, cid).first() !== null;
|
|
994
|
+
const [lo, hi] = prefixRange(pathPrefix);
|
|
995
|
+
return await this.conn.prepare(`SELECT 1 FROM (
|
|
996
|
+
SELECT 1 FROM ds_entries WHERE scope = ? AND cid = ? AND path >= ? AND path < ?
|
|
997
|
+
UNION ALL
|
|
998
|
+
SELECT 1 FROM ds_changelog WHERE scope = ? AND cid = ? AND path >= ? AND path < ?
|
|
999
|
+
UNION ALL
|
|
1000
|
+
SELECT 1 FROM ds_changelog WHERE scope = ? AND old_cid = ? AND path >= ? AND path < ?
|
|
1001
|
+
) LIMIT 1`).bind(this.scope, cid, lo, hi, this.scope, cid, lo, hi, this.scope, cid, lo, hi).first() !== null;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Batch read-auth (design §7.2 / §14) — three-way-equivalent to per-cid
|
|
1005
|
+
* {@link isReferenced}, sharded under D1's 100-param cap (see
|
|
1006
|
+
* {@link D1_MANY_SHARD_SIZE}). Returns the referenced subset of `cids`.
|
|
1007
|
+
*/
|
|
1008
|
+
async isReferencedMany(cids, pathPrefix = "") {
|
|
1009
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
1010
|
+
const unique = [...new Set(cids.filter((c) => c))];
|
|
1011
|
+
if (unique.length === 0) return referenced;
|
|
1012
|
+
const usePrefix = pathPrefix !== "";
|
|
1013
|
+
const [lo, hi] = usePrefix ? prefixRange(pathPrefix) : ["", ""];
|
|
1014
|
+
for (let i = 0; i < unique.length; i += D1_MANY_SHARD_SIZE) {
|
|
1015
|
+
const shard = unique.slice(i, i + D1_MANY_SHARD_SIZE);
|
|
1016
|
+
const ph = shard.map(() => "?").join(",");
|
|
1017
|
+
const tail = usePrefix ? "AND path >= ? AND path < ?" : "";
|
|
1018
|
+
const sql = `SELECT cid AS c FROM ds_entries WHERE scope = ? AND cid IN (${ph}) ${tail}
|
|
1019
|
+
UNION
|
|
1020
|
+
SELECT cid AS c FROM ds_changelog WHERE scope = ? AND cid IN (${ph}) ${tail}
|
|
1021
|
+
UNION
|
|
1022
|
+
SELECT old_cid AS c FROM ds_changelog WHERE scope = ? AND old_cid IN (${ph}) ${tail}`;
|
|
1023
|
+
const bind = [];
|
|
1024
|
+
for (let b = 0; b < 3; b++) {
|
|
1025
|
+
bind.push(this.scope, ...shard);
|
|
1026
|
+
if (usePrefix) bind.push(lo, hi);
|
|
1027
|
+
}
|
|
1028
|
+
const { results } = await this.conn.prepare(sql).bind(...bind).all();
|
|
1029
|
+
for (const row of results) if (row.c) referenced.add(row.c);
|
|
1030
|
+
}
|
|
1031
|
+
return referenced;
|
|
1032
|
+
}
|
|
1033
|
+
async getChangelogSince(sinceSeq, limit) {
|
|
1034
|
+
const safeLimit = limit > 0 ? limit : 0;
|
|
1035
|
+
if (safeLimit === 0) return {
|
|
1036
|
+
entries: [],
|
|
1037
|
+
hasMore: false,
|
|
1038
|
+
cursor: sinceSeq
|
|
1039
|
+
};
|
|
1040
|
+
const { results } = await this.conn.prepare(`SELECT seq, scope, path, op, cid, old_cid, size, mtime, device_id
|
|
1041
|
+
FROM ds_changelog WHERE scope = ? AND seq > ? ORDER BY seq ASC LIMIT ?`).bind(this.scope, sinceSeq, safeLimit + 1).all();
|
|
1042
|
+
const hasMore = results.length > safeLimit;
|
|
1043
|
+
const page = hasMore ? results.slice(0, safeLimit) : results;
|
|
1044
|
+
return {
|
|
1045
|
+
entries: page,
|
|
1046
|
+
hasMore,
|
|
1047
|
+
cursor: page.length > 0 ? page[page.length - 1].seq : sinceSeq
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Ready dirty rows under `prefix` for this scope. `next_attempt_at <= now`
|
|
1052
|
+
* skips poison rows still in backoff; the `(scope, next_attempt_at,
|
|
1053
|
+
* updated_at)` index serves the ordered seek. Reads go through `this.conn`, so
|
|
1054
|
+
* a follower constructed primary-bound (`useReplica:false`, Phase 3) gets
|
|
1055
|
+
* read-your-write; a replica-routed instance would return replica state — the
|
|
1056
|
+
* runtime is responsible for pinning primary for the dirty follower.
|
|
1057
|
+
*/
|
|
1058
|
+
async listDirtyPaths(options) {
|
|
1059
|
+
const now = options.now ?? Date.now();
|
|
1060
|
+
const limit = options.limit > 0 ? options.limit : 0;
|
|
1061
|
+
if (limit === 0) return [];
|
|
1062
|
+
const [lo, hi] = prefixRange(options.prefix);
|
|
1063
|
+
const { results } = await this.conn.prepare(DIRTY_PATH_LIST_SQL).bind(this.scope, lo, hi, now, limit).all();
|
|
1064
|
+
return results.map((r) => ({
|
|
1065
|
+
path: r.path,
|
|
1066
|
+
latestSeq: Number(r.latest_seq),
|
|
1067
|
+
op: r.op,
|
|
1068
|
+
cid: r.cid,
|
|
1069
|
+
mtime: Number(r.mtime),
|
|
1070
|
+
attempts: Number(r.attempts)
|
|
1071
|
+
}));
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Guarded ack (design §8): delete the dirty row ONLY if its `latest_seq` is
|
|
1075
|
+
* still the one we processed. A concurrent write bumps latest_seq (trigger),
|
|
1076
|
+
* so a stale worker's DELETE affects 0 rows → returns false, and the newer
|
|
1077
|
+
* dirty row survives for the next tick. Writes go to primary (`this.db`).
|
|
1078
|
+
*/
|
|
1079
|
+
async ackDirtyPath(path, latestSeq) {
|
|
1080
|
+
return ((await this.db.prepare(DIRTY_PATH_ACK_SQL).bind(this.scope, path, latestSeq).run()).meta?.changes ?? 0) > 0;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Guarded failure record (design §8): bump attempts, stamp last_error +
|
|
1084
|
+
* next_attempt_at, SAME `latest_seq` guard so a fresh write's reset (trigger
|
|
1085
|
+
* sets attempts=0) is never clobbered by a late error from the prior attempt.
|
|
1086
|
+
*/
|
|
1087
|
+
async markDirtyPathError(path, latestSeq, error, nextAttemptAt) {
|
|
1088
|
+
return ((await this.db.prepare(DIRTY_PATH_MARK_ERROR_SQL).bind(error, nextAttemptAt, this.scope, path, latestSeq).run()).meta?.changes ?? 0) > 0;
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
//#endregion
|
|
1093
|
+
export { D1TreeIndex, MAX_ENTRIES_PER_SCOPE, ancestorDirs, buildDirChainStmt, nameOf, parentOf };
|
|
1094
|
+
//# sourceMappingURL=d1-tree-index.mjs.map
|