@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 @@
|
|
|
1
|
+
{"version":3,"file":"d1-tree-index.mjs","names":["sql","hasMore","results","selected","countRow"],"sources":["../src/d1-tree-index.ts"],"sourcesContent":["/**\n * D1TreeIndex — D1 (SQLite) implementation of TreeIndex.\n *\n * Uses the `entries` table (v2 schema) with parent/name/kind columns\n * for efficient directory operations. WITHOUT ROWID for direct PK lookup.\n *\n * Each instance is scoped to a (scope) partition.\n */\n\nimport {\n CHANGELOG_INDEX_SQL,\n CHANGELOG_TABLE_SQL,\n PREFIX_WATERMARK_BUMP_SQL,\n PREFIX_WATERMARK_READ_SQL,\n PREFIX_WATERMARK_TABLE_SQL,\n SCOPE_META_FILE_COUNT_DELETE_ONE_SQL,\n SCOPE_META_FILE_COUNT_DELETE_RANGE_SQL,\n SCOPE_META_FILE_COUNT_TRIGGER_SQL,\n SCOPE_META_TABLE_SQL,\n SCOPE_META_TOTAL_BYTES_COLUMN_SQL,\n SCOPE_META_TOTAL_BYTES_DELETE_ONE_SQL,\n SCOPE_META_TOTAL_BYTES_DELETE_RANGE_SQL,\n SCOPE_META_TOTAL_BYTES_TRIGGER_SQL,\n watermarkPrefix,\n watermarkWritePrefix,\n} from \"./changelog-schema.js\";\nimport { validateDeviceId } from \"./changelog-write.js\";\nimport {\n ENTRIES_CONTENT_TYPE_COLUMN_SQL,\n ENTRIES_CONTENT_TYPE_INDEX_DROP_SQL,\n ENTRIES_DEDUP_INDEX_SQL,\n ENTRIES_DEDUP_KEY_COLUMN_SQL,\n ENTRIES_DEDUP_SCOPE_COLUMN_SQL,\n ENTRIES_INDEX_SQL,\n ENTRIES_INLINE_CONTENT_COLUMN_SQL,\n ENTRIES_METADATA_COLUMN_SQL,\n ENTRIES_TABLE_SQL,\n} from \"./d1-schema.js\";\nimport type { DirtyPathRow, ListDirtyPathOptions } from \"./dirty-path-queue.js\";\nimport {\n DIRTY_PATH_ACK_SQL,\n DIRTY_PATH_INDEX_SQL,\n DIRTY_PATH_LIST_SQL,\n DIRTY_PATH_MARK_ERROR_SQL,\n DIRTY_PATH_TABLE_SQL,\n DIRTY_PATH_TRIGGER_SQL,\n} from \"./dirty-path-schema.js\";\nimport {\n type InlineContentOptions,\n type ResolvedInlineConfig,\n resolveInlineConfig,\n resolveInlineContent,\n} from \"./inline-content.js\";\nimport { prefixRange } from \"./prefix-range.js\";\nimport { resolveParentDirStamp } from \"./timestamp.js\";\nimport {\n type BatchDeleteEntry,\n type BatchDeleteEntryResult,\n type BatchIndexContext,\n type BatchPutEntry,\n type BatchPutEntryResult,\n buildKeysetCursor,\n buildOrderedQueryParts,\n type ChangelogEntry,\n type PutOpts,\n parseTreeCursor,\n parseTreeEntryMetadata,\n STORAGE_QUOTA_EXCEEDED_CODE,\n serializeTreeEntryMetadata,\n storageQuotaError,\n type TreeEntry,\n type TreeIndex,\n type TreeIndexListEntriesResult,\n type TreeIndexListOptions,\n type TreeIndexListResult,\n treeEntryMetadataEqual,\n} from \"./tree-index.js\";\nimport {\n buildChildrenFrom,\n buildTreeQueryResultFromSql,\n buildTreeQueryStatements,\n mapAggregateRows,\n type TreeQueryResult,\n type TreeQuerySpec,\n} from \"./tree-query.js\";\n\n// ========== Path helpers ==========\n\n/** Extract parent directory path from a file/dir path */\nexport function parentOf(path: string): string {\n if (!path) return \"\";\n const clean = path.endsWith(\"/\") ? path.slice(0, -1) : path;\n const lastSlash = clean.lastIndexOf(\"/\");\n if (lastSlash <= 0) return \"\";\n return clean.slice(0, lastSlash + 1);\n}\n\n/** Extract basename from a file/dir path */\nexport function nameOf(path: string): string {\n if (!path) return \"\";\n const clean = path.endsWith(\"/\") ? path.slice(0, -1) : path;\n const lastSlash = clean.lastIndexOf(\"/\");\n return lastSlash < 0 ? clean : clean.slice(lastSlash + 1);\n}\n\n/** Collect all ancestor directory paths from a file path (deepest first) */\nexport function ancestorDirs(path: string): string[] {\n const dirs: string[] = [];\n let current = path;\n while (current.includes(\"/\")) {\n const slashIdx = current.lastIndexOf(\"/\");\n const dirPath = current.slice(0, slashIdx + 1);\n if (!dirPath || dirPath === \"/\") break;\n dirs.push(dirPath);\n current = dirPath.slice(0, -1);\n }\n return dirs;\n}\n\n/**\n * Convert a prefix string into a [lo, hi) half-open range for SQL:\n * WHERE path >= lo AND path < hi\n * This replaces LIKE patterns to avoid SQLite \"pattern too complex\" errors on D1.\n *\n * Re-exported from the shared {@link prefix-range} module (single source, also\n * used by `d1-resolve-store` / `sqlite-resolve-store` / the dirty-path queue) so\n * existing `import { prefixRange } from \"./d1-tree-index.js\"` call sites (e.g.\n * `sqlite-tree-index.ts`) keep working. Imported at the top of this file for\n * internal use; re-exported here to preserve the historical export surface.\n */\nexport { prefixRange };\n\n/**\n * Parent-dir-chain statements (Rule R2, feed-sort-createdat design §5.2).\n *\n * `DIR_INSERT_IGNORE_SQL` is the legacy \"scaffold a dir if absent\" — used for\n * the whole ancestor chain and for a direct parent whose write carries no\n * application `createdAt`. `DIR_STAMP_UPSERT_SQL` additionally overwrites the\n * existing dir row's ctime/mtime — used ONLY for the DIRECT parent when the\n * write carries a valid createdAt, so the feed's `orderBy ctime` (which sorts\n * the DIR row `e.ctime`) reflects the application createdAt. Both take the\n * SAME 6 binds: (scope, path, parent, name, ctime, mtime).\n */\nconst DIR_COLS = \"(scope, path, parent, name, kind, size, ctime, mtime)\";\nexport const DIR_INSERT_IGNORE_SQL = `INSERT OR IGNORE INTO ds_entries ${DIR_COLS}\n VALUES (?, ?, ?, ?, 'dir', 0, ?, ?)`;\nexport const DIR_STAMP_UPSERT_SQL = `INSERT INTO ds_entries ${DIR_COLS}\n VALUES (?, ?, ?, ?, 'dir', 0, ?, ?)\n ON CONFLICT(scope, path) DO UPDATE SET ctime = excluded.ctime, mtime = excluded.mtime`;\n\n/**\n * Build the dir-chain statement for one `dirPath`: a Rule-R2 stamping UPSERT\n * when `stamp` is non-null (the direct parent of a createdAt-bearing write),\n * else the scaffold-only `INSERT OR IGNORE` at insert time `now`. Backend-\n * agnostic `{ sql, args }`; the D1 caller wraps with `prepare().bind()`, the\n * SQLite caller pushes it as-is.\n */\nexport function buildDirChainStmt(\n scope: string,\n dirPath: string,\n stamp: { ctime: number; mtime: number } | null,\n now: number,\n): { sql: string; args: Array<string | number> } {\n const sql = stamp ? DIR_STAMP_UPSERT_SQL : DIR_INSERT_IGNORE_SQL;\n const { ctime, mtime } = stamp ?? { ctime: now, mtime: now };\n return { sql, args: [scope, dirPath, parentOf(dirPath), nameOf(dirPath), ctime, mtime] };\n}\n\n/** Maximum file entries per scope (files only, excludes dirs) */\nexport const MAX_ENTRIES_PER_SCOPE = 1_000_000;\n\n/**\n * `isReferencedMany` IN-shard size for D1 (design §7.2, Finding 5). D1 caps a\n * query at 100 bound parameters. The three-way UNION binds each cid 3× plus\n * scope per branch (3 total), and 2 more per branch (lo/hi) when prefix-local:\n * - whole-scope: 3N + 3 ≤ 100 → N ≤ 32\n * - prefix-local: 3N + 3 + 6 ≤ 100 → N ≤ 30\n * 28 is the conservative single value covering both branches (never 30+).\n */\nexport const D1_MANY_SHARD_SIZE = 28;\n\nexport class D1TreeIndex implements TreeIndex {\n private readonly conn: D1Database | D1DatabaseSession;\n private readonly maxEntriesPerScope: number;\n /** upload-limits Phase 5 byte quota (0 = disabled, the opt-in default). */\n private readonly maxBytesPerScope: number;\n private readonly inline: ResolvedInlineConfig;\n\n constructor(\n private db: D1Database,\n private scope: string,\n opts?: {\n useReplica?: boolean;\n maxEntriesPerScope?: number;\n maxBytesPerScope?: number;\n } & InlineContentOptions,\n ) {\n this.conn =\n opts?.useReplica && typeof db.withSession === \"function\"\n ? db.withSession(\"first-unconstrained\")\n : db;\n this.maxEntriesPerScope = opts?.maxEntriesPerScope ?? MAX_ENTRIES_PER_SCOPE;\n // 0 / unset → byte quota OFF (opt-in, aligns with maxBytesPerScope default).\n this.maxBytesPerScope =\n typeof opts?.maxBytesPerScope === \"number\" && opts.maxBytesPerScope > 0\n ? opts.maxBytesPerScope\n : 0;\n this.inline = resolveInlineConfig(opts);\n }\n\n /**\n * Create entries table + indexes (v2 schema) AND the Phase 2 changelog table.\n * Production: schema created by `wrangler d1 migrations apply`.\n * Tests: call this in beforeEach with in-memory DB.\n *\n * Changelog DDL is sourced from `changelog-schema.ts` to keep the four\n * runtimes (D1, desktop, Swift, Kotlin) byte-equivalent — see\n * `scripts/check-changelog-ddl-parity.ts`. Schema-only here; producers come\n * online in Task 2.4.\n */\n async ensureTable(): Promise<void> {\n await this.db.batch([\n this.db.prepare(ENTRIES_TABLE_SQL),\n ...ENTRIES_INDEX_SQL.map((sql) => this.db.prepare(sql)),\n this.db.prepare(CHANGELOG_TABLE_SQL),\n ...CHANGELOG_INDEX_SQL.map((sql) => this.db.prepare(sql)),\n // scope_meta + the file_count triggers must be created together and\n // BEFORE any entries write fires a trigger: the trigger body upserts\n // into scope_meta, so the table has to exist first. (Production gets\n // this via migration 0104; ensureTable covers fresh test DBs / first\n // deploy. No backfill here — a fresh DB has no rows to seed.)\n this.db.prepare(SCOPE_META_TABLE_SQL),\n ...SCOPE_META_FILE_COUNT_TRIGGER_SQL.map((sql) => this.db.prepare(sql)),\n // prefix_watermark (Live Update Resilience §11.4) — D1-only, bumped in\n // the put/delete batches below. Production gets it via migration; this\n // covers fresh test DBs / first deploy.\n this.db.prepare(PREFIX_WATERMARK_TABLE_SQL),\n // ds_dirty_path (index-follower-dirty-path) + its AFTER INSERT trigger.\n // Must be created BEFORE any changelog write so the trigger catches it —\n // ensureTable runs before any put/delete on a fresh DB, so ordering holds.\n // Production CF gets the table via the Phase-4 numbered migration; this\n // ensureTable path covers fresh test/dev DBs (same fresh-DB-fallback role\n // it already plays for changelog / scope_meta / prefix_watermark).\n this.db.prepare(DIRTY_PATH_TABLE_SQL),\n ...DIRTY_PATH_INDEX_SQL.map((sql) => this.db.prepare(sql)),\n ...DIRTY_PATH_TRIGGER_SQL.map((sql) => this.db.prepare(sql)),\n ]);\n // Inline-content column (Layer 0, additive): probe-then-ALTER, mirroring\n // D1ResolveStore.ensureColumns. Production deploys apply the generated\n // 0102 migration instead; this runtime guard covers tests + fresh-DB\n // bootstrap paths that only call ensureTable.\n const { results } = await this.db\n .prepare(\"SELECT name FROM pragma_table_info('ds_entries')\")\n .all<{ name: string }>();\n const colNames = new Set<string>(results.map((r) => r.name));\n if (!colNames.has(\"content\")) {\n try {\n await this.db.prepare(ENTRIES_INLINE_CONTENT_COLUMN_SQL).run();\n } catch (err) {\n // Concurrent ensureTable calls can race the ALTER (no IF NOT EXISTS)\n // — the loser's \"duplicate column name\" is benign.\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"duplicate column name\")) throw err;\n }\n }\n // Application metadata column (planning/agent-hub/metadata-foundation.md):\n // same additive ALTER pattern. Production gets 0105 migration.\n if (!colNames.has(\"metadata\")) {\n try {\n await this.db.prepare(ENTRIES_METADATA_COLUMN_SQL).run();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"duplicate column name\")) throw err;\n }\n }\n // Dedup-on-write columns + partial UNIQUE index (#1159): same additive\n // ALTER pattern. The index is created AFTER both columns exist and is\n // idempotent (IF NOT EXISTS). Production gets the generated 0117 migration.\n if (!colNames.has(\"dedup_scope\")) {\n try {\n await this.db.prepare(ENTRIES_DEDUP_SCOPE_COLUMN_SQL).run();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"duplicate column name\")) throw err;\n }\n }\n if (!colNames.has(\"dedup_key\")) {\n try {\n await this.db.prepare(ENTRIES_DEDUP_KEY_COLUMN_SQL).run();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"duplicate column name\")) throw err;\n }\n }\n await this.db.prepare(ENTRIES_DEDUP_INDEX_SQL).run();\n // Content-type virtual generated column (#1293): additive ALTER pattern.\n // Virtual generated columns are permitted in ALTER TABLE ADD COLUMN (SQLite\n // explicitly allows them; stored ones are not). The column is RETAINED but\n // its backing index is NOT — see below. Production gets the generated 0120\n // migration.\n if (!colNames.has(\"content_type\")) {\n try {\n await this.db.prepare(ENTRIES_CONTENT_TYPE_COLUMN_SQL).run();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"duplicate column name\")) throw err;\n }\n }\n // Drop the unused idx_entries_content_type (#1367): no query ever emitted\n // `WHERE content_type = ?`, so it only taxed every write. Idempotent\n // one-time cleanup for DBs a prior ensureTable already indexed; no-op after.\n // Schema-probe-skipped production isolates get migration 0121 instead.\n await this.db.prepare(ENTRIES_CONTENT_TYPE_INDEX_DROP_SQL).run();\n // upload-limits Phase 5 byte quota: add `scope_meta.total_bytes` (NOT in the\n // CREATE TABLE — added by ALTER only, so the prod migration works on fresh +\n // existing DBs alike) THEN create its triggers. Order matters: the triggers\n // reference total_bytes, so the column must exist first. Production gets the\n // generated migration; this covers fresh test/dev DBs. ensureTable runs to\n // completion before the first write, so the column + triggers are in place.\n const scopeMetaCols = await this.db\n .prepare(\"SELECT name FROM pragma_table_info('ds_scope_meta')\")\n .all<{ name: string }>();\n if (!new Set(scopeMetaCols.results.map((r) => r.name)).has(\"total_bytes\")) {\n try {\n await this.db.prepare(SCOPE_META_TOTAL_BYTES_COLUMN_SQL).run();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"duplicate column name\")) throw err;\n }\n }\n await this.db.batch(SCOPE_META_TOTAL_BYTES_TRIGGER_SQL.map((sql) => this.db.prepare(sql)));\n }\n\n // ========== Core CRUD ==========\n\n async get(path: string, opts?: { includeContent?: boolean }): Promise<TreeEntry | null> {\n // The content column is projected ONLY for inline-enabled indexes so a\n // default-off index keeps issuing byte-identical legacy SQL (and never\n // touches a not-yet-migrated database's missing column). `includeContent`\n // (default true) additionally lets metadata-only callers (stat / list /\n // readMeta) skip marshalling the inline copy (avg ~800B/row) — the hottest\n // point-read in the space stops dragging content it never reads.\n const contentCol =\n (opts?.includeContent ?? true) && this.inline.inlineCache === \"on\" ? \", content\" : \"\";\n const row = await this.conn\n .prepare(\n `SELECT cid, size, mtime, ctime, mime_type, metadata${contentCol} FROM ds_entries WHERE scope = ? AND path = ?`,\n )\n .bind(this.scope, path)\n .first<{\n cid: string | null;\n size: number;\n mtime: number;\n ctime: number;\n mime_type: string | null;\n metadata: string | null;\n content?: string | null;\n }>();\n if (!row || !row.cid) return null;\n const entry: TreeEntry = {\n cid: row.cid,\n size: row.size,\n mtime: row.mtime,\n ctime: row.ctime,\n mimeType: row.mime_type ?? undefined,\n };\n if (row.content != null) entry.content = row.content;\n const meta = parseTreeEntryMetadata(row.metadata);\n if (meta) entry.metadata = meta;\n return entry;\n }\n\n async getDirTimes(dirPath: string): Promise<{ ctime: number; mtime: number } | null> {\n // No `cid` filter (dir rows are cid-less, unlike get()); the trailing-slash\n // dirPath is the scaffolded directory row key (ancestorDirs form).\n const row = await this.conn\n .prepare(\"SELECT ctime, mtime FROM ds_entries WHERE scope = ? AND path = ?\")\n .bind(this.scope, dirPath)\n .first<{ ctime: number; mtime: number }>();\n return row ? { ctime: row.ctime, mtime: row.mtime } : null;\n }\n\n /** Inline content this entry would store — parity with the loop reference impl. */\n inlineForEntry(entry: TreeEntry): Promise<string | null> {\n return resolveInlineContent(entry, this.inline);\n }\n\n // ========== Dedup-on-write secondary index (#1159) ==========\n\n /** Point seek on the `(scope, dedup_scope, dedup_key)` partial UNIQUE index. */\n async findByDedupKey(\n dedupScope: string,\n dedupKey: string,\n ): Promise<({ path: string } & TreeEntry) | null> {\n const row = await this.conn\n .prepare(\n `SELECT path, cid, size, mtime, ctime, mime_type, metadata\n FROM ds_entries WHERE scope = ? AND dedup_scope = ? AND dedup_key = ? LIMIT 1`,\n )\n .bind(this.scope, dedupScope, dedupKey)\n .first<{\n path: string;\n cid: string | null;\n size: number;\n mtime: number;\n ctime: number;\n mime_type: string | null;\n metadata: string | null;\n }>();\n if (!row || !row.cid) return null;\n const entry: { path: string } & TreeEntry = {\n path: row.path,\n cid: row.cid,\n size: row.size,\n mtime: row.mtime,\n ctime: row.ctime,\n mimeType: row.mime_type ?? undefined,\n };\n const meta = parseTreeEntryMetadata(row.metadata);\n if (meta) entry.metadata = meta;\n return entry;\n }\n\n /** Index-only backfill: set the dedup columns without touching content/changelog. */\n async setDedupKey(\n path: string,\n dedupScope: string,\n dedupKey: string,\n opts: PutOpts,\n ): Promise<void> {\n validateDeviceId(opts.deviceId);\n await this.db\n .prepare(\"UPDATE ds_entries SET dedup_scope = ?, dedup_key = ? WHERE scope = ? AND path = ?\")\n .bind(dedupScope, dedupKey, this.scope, path)\n .run();\n }\n\n /**\n * Batch metadata read — one chunked `IN (…)` query per ≤90 paths (D1's\n * 100-bind limit minus the scope bind, design §4.3). Result map carries an\n * entry for EVERY requested path (`null` when absent), keyed verbatim.\n */\n async getMany(paths: string[]): Promise<Map<string, TreeEntry | null>> {\n const map = new Map<string, TreeEntry | null>();\n for (const path of paths) map.set(path, null);\n const contentCol = this.inline.inlineCache === \"on\" ? \", content\" : \"\";\n for (let offset = 0; offset < paths.length; offset += 90) {\n const chunk = paths.slice(offset, offset + 90);\n const { results } = await this.conn\n .prepare(\n `SELECT path, cid, size, mtime, ctime, mime_type, metadata${contentCol} FROM ds_entries\n WHERE scope = ? AND kind = 'file' AND path IN (${chunk.map(() => \"?\").join(\",\")})`,\n )\n .bind(this.scope, ...chunk)\n .all<{\n path: string;\n cid: string | null;\n size: number;\n mtime: number;\n ctime: number;\n mime_type: string | null;\n metadata: string | null;\n content?: string | null;\n }>();\n for (const row of results) {\n if (!row.cid) continue;\n const entry: TreeEntry = {\n cid: row.cid,\n size: row.size,\n mtime: row.mtime,\n ctime: row.ctime,\n mimeType: row.mime_type ?? undefined,\n };\n if (row.content != null) entry.content = row.content;\n const meta = parseTreeEntryMetadata(row.metadata);\n if (meta) entry.metadata = meta;\n map.set(row.path, entry);\n }\n }\n return map;\n }\n\n /**\n * Merged batch put (afs-rpc-batch §4.3 / §6.3) — the CF subrequest-budget\n * fast path: ONE preread (caller-supplied or chunked IN), ONE capacity\n * read, ONE atomic `db.batch` carrying every surviving entry's\n * changelog+upsert (+1 watermark bump per write), then ONE watermark\n * readback per distinct collection root to attribute per-entry seqs\n * (`liveSeq[i] = final − N + 1 + i`, skip/conflict entries take no seq).\n *\n * Statement order inside the batch mirrors single `put`: dirs first, then\n * per entry changelog INSERT (correlated old_cid subquery reads the\n * pre-upsert cid) BEFORE its upsert — D1 preserves batch statement order.\n */\n async batchPutEntries(\n entries: BatchPutEntry[],\n ctx?: BatchIndexContext,\n ): Promise<BatchPutEntryResult[]> {\n if (entries.length === 0) return [];\n for (const e of entries) validateDeviceId(e.opts.deviceId);\n\n const preread = ctx?.preread ?? (await this.getMany(entries.map((e) => e.path)));\n const results: BatchPutEntryResult[] = new Array(entries.length);\n\n // Classify: conflict / skip-if-unchanged / live — same rules as put().\n const live: number[] = [];\n let newFileCount = 0;\n for (let i = 0; i < entries.length; i++) {\n const { path, entry, opts } = entries[i]!;\n const existing = preread.get(path) ?? null;\n if (opts.ifMatch !== undefined && (!existing || existing.cid !== opts.ifMatch)) {\n results[i] = {\n path,\n conflict: {\n currentCid: existing?.cid ?? null,\n currentMtime: existing?.mtime ?? null,\n currentDeviceId: null,\n },\n };\n continue;\n }\n if (\n existing &&\n existing.cid === entry.cid &&\n existing.mimeType === entry.mimeType &&\n treeEntryMetadataEqual(existing.metadata, entry.metadata)\n ) {\n // Metadata-aware skip (did-space-metadata G4): a metadata-only change\n // keeps the same cid — fall through to the UPSERT so it lands.\n // Skip only when the inline copy is already in sync (write-through\n // re-fill — same exception as single put()): an out-of-sync inline\n // copy must fall through to the UPSERT so it self-repairs.\n if (this.inline.inlineCache !== \"on\") {\n results[i] = { path, oldCid: existing.cid, skipped: true };\n continue;\n }\n const wouldBeInline = await resolveInlineContent(entry, this.inline);\n if ((existing.content ?? null) === wouldBeInline) {\n results[i] = { path, oldCid: existing.cid, skipped: true };\n continue;\n }\n // out of sync — treat as live so the batch UPSERT re-fills it.\n }\n if (!existing) newFileCount++;\n live.push(i);\n }\n\n // Capacity guard — ONE counter read for the whole batch; entries beyond\n // the remaining capacity fail per entry with the single-put message.\n if (newFileCount > 0) {\n const currentCount = await this.getScopeFileCount();\n let remaining = this.maxEntriesPerScope - currentCount;\n if (remaining < newFileCount) {\n const surviving: number[] = [];\n for (const i of live) {\n const isNew = !(preread.get(entries[i]!.path) ?? null);\n if (isNew) {\n if (remaining > 0) {\n remaining--;\n surviving.push(i);\n } else {\n results[i] = {\n path: entries[i]!.path,\n error: `Storage quota exceeded: scope \"${this.scope}\" has reached the ${this.maxEntriesPerScope} file limit`,\n code: STORAGE_QUOTA_EXCEEDED_CODE,\n };\n }\n } else {\n surviving.push(i);\n }\n }\n live.length = 0;\n live.push(...surviving);\n }\n }\n // upload-limits Phase 5 byte quota (opt-in). ONE counter read for the batch;\n // accumulate the NET byte delta per surviving entry (overwrite-aware) and\n // reject those that push the running total over the cap — same per-entry\n // degrade shape as the file-count guard above.\n if (this.maxBytesPerScope > 0 && live.length > 0) {\n const total = await this.getScopeTotalBytes();\n let used = 0;\n const surviving: number[] = [];\n for (const i of live) {\n const e = entries[i]!;\n const newBytes = e.entry.size - (preread.get(e.path)?.size ?? 0);\n if (newBytes > 0 && total + used + newBytes > this.maxBytesPerScope) {\n results[i] = {\n path: e.path,\n error: `Storage quota exceeded: scope \"${this.scope}\" has reached the ${this.maxBytesPerScope}-byte limit`,\n code: STORAGE_QUOTA_EXCEEDED_CODE,\n };\n } else {\n if (newBytes > 0) used += newBytes;\n surviving.push(i);\n }\n }\n live.length = 0;\n live.push(...surviving);\n }\n if (live.length === 0) return results;\n\n const now = Date.now();\n const stmts: D1PreparedStatement[] = [];\n\n // Parent dir chain — union across live entries. Rule R2 (feed-sort §5.2):\n // a direct parent whose write carries a createdAt is stamped (UPSERT\n // ctime/mtime); last writer per direct-parent wins (LWW by entry order);\n // ancestors and createdAt-less direct parents stay INSERT OR IGNORE.\n const dirStamps = new Map<string, { ctime: number; mtime: number }>();\n const allDirs = new Set<string>();\n for (const i of live) {\n const path = entries[i]!.path;\n const stamp = resolveParentDirStamp(entries[i]!.entry.metadata);\n if (stamp) dirStamps.set(parentOf(path), stamp);\n for (const dirPath of ancestorDirs(path)) {\n if (allDirs.has(dirPath)) break;\n allDirs.add(dirPath);\n }\n }\n for (const dirPath of allDirs) {\n const { sql, args } = buildDirChainStmt(\n this.scope,\n dirPath,\n dirStamps.get(dirPath) ?? null,\n now,\n );\n stmts.push(this.db.prepare(sql).bind(...args));\n }\n\n // Per entry: changelog INSERT (correlated old_cid) then file upsert, then\n // its own watermark bump (+1 per write — single-put parity, NOT putBatch's\n // once-per-root). Track per-prefix write order for seq attribution.\n const writesByPrefix = new Map<string, number[]>();\n for (const i of live) {\n const { path, entry, opts } = entries[i]!;\n stmts.push(\n this.db\n .prepare(\n `INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)\n VALUES (?, ?, 'put', ?, (SELECT cid FROM ds_entries WHERE scope = ? AND path = ?), ?, ?, ?)`,\n )\n .bind(\n this.scope,\n path,\n entry.cid,\n this.scope,\n path,\n entry.size,\n entry.mtime,\n opts.deviceId,\n ),\n );\n stmts.push(await this.buildFileUpsert(path, entry));\n const wmPrefix = watermarkWritePrefix(path);\n if (wmPrefix !== null) {\n stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));\n const order = writesByPrefix.get(wmPrefix) ?? [];\n order.push(i);\n writesByPrefix.set(wmPrefix, order);\n }\n }\n\n await this.db.batch(stmts);\n\n // Watermark readback per distinct root → back-compute per-entry seqs.\n for (const [wmPrefix, order] of writesByPrefix) {\n const row = await this.conn\n .prepare(PREFIX_WATERMARK_READ_SQL)\n .bind(this.scope, wmPrefix)\n .first<{ version: number }>();\n const final = row?.version ?? 0;\n order.forEach((i, j) => {\n const existing = preread.get(entries[i]!.path) ?? null;\n results[i] = {\n path: entries[i]!.path,\n ...(existing ? { oldCid: existing.cid } : {}),\n liveSeq: final - order.length + 1 + j,\n };\n });\n }\n // Root-level writes (no collection root): result without liveSeq.\n for (const i of live) {\n if (!results[i]) {\n const existing = preread.get(entries[i]!.path) ?? null;\n results[i] = { path: entries[i]!.path, ...(existing ? { oldCid: existing.cid } : {}) };\n }\n }\n return results;\n }\n\n /**\n * Merged batch delete — same shape as {@link batchPutEntries} without the\n * R2 leg: ONE preread, ONE atomic `db.batch` (per entry: changelog INSERT\n * SELECT + file_count decrement + DELETE, +1 watermark bump), then empty-\n * parent cleanup over the DISTINCT ancestor chain. FILE entries only.\n */\n async batchDeleteEntries(\n entries: BatchDeleteEntry[],\n ctx?: BatchIndexContext,\n ): Promise<BatchDeleteEntryResult[]> {\n if (entries.length === 0) return [];\n for (const e of entries) validateDeviceId(e.opts.deviceId);\n\n const preread = ctx?.preread ?? (await this.getMany(entries.map((e) => e.path)));\n const results: BatchDeleteEntryResult[] = new Array(entries.length);\n const live: number[] = [];\n for (let i = 0; i < entries.length; i++) {\n const { path, opts } = entries[i]!;\n const existing = preread.get(path) ?? null;\n if (opts.ifMatch !== undefined && (!existing || existing.cid !== opts.ifMatch)) {\n results[i] = {\n path,\n conflict: {\n currentCid: existing?.cid ?? null,\n currentMtime: existing?.mtime ?? null,\n currentDeviceId: null,\n },\n };\n continue;\n }\n if (!existing) {\n results[i] = { path, notFound: true };\n continue;\n }\n live.push(i);\n }\n if (live.length === 0) return results;\n\n const stmts: D1PreparedStatement[] = [];\n const writesByPrefix = new Map<string, number[]>();\n for (const i of live) {\n const { path, opts } = entries[i]!;\n stmts.push(\n this.db\n .prepare(\n `INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)\n SELECT ?, ?, 'delete', NULL, cid, NULL, ?, ?\n FROM ds_entries WHERE scope = ? AND path = ? AND cid IS NOT NULL`,\n )\n .bind(this.scope, path, opts.mtime ?? Date.now(), opts.deviceId, this.scope, path),\n );\n stmts.push(\n this.db.prepare(SCOPE_META_FILE_COUNT_DELETE_ONE_SQL).bind(this.scope, this.scope, path),\n );\n // upload-limits Phase 5: refund the deleted file's size from total_bytes\n // (else the byte quota would monotonically climb). BEFORE the DELETE so the\n // subquery sees the pre-delete row. Bind order: (scope, path, scope).\n stmts.push(\n this.db.prepare(SCOPE_META_TOTAL_BYTES_DELETE_ONE_SQL).bind(this.scope, path, this.scope),\n );\n stmts.push(\n this.db\n .prepare(\"DELETE FROM ds_entries WHERE scope = ? AND path = ?\")\n .bind(this.scope, path),\n );\n const wmPrefix = watermarkWritePrefix(path);\n if (wmPrefix !== null) {\n stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));\n const order = writesByPrefix.get(wmPrefix) ?? [];\n order.push(i);\n writesByPrefix.set(wmPrefix, order);\n }\n }\n\n await this.db.batch(stmts);\n\n for (const [wmPrefix, order] of writesByPrefix) {\n const row = await this.conn\n .prepare(PREFIX_WATERMARK_READ_SQL)\n .bind(this.scope, wmPrefix)\n .first<{ version: number }>();\n const final = row?.version ?? 0;\n order.forEach((i, j) => {\n results[i] = {\n path: entries[i]!.path,\n deletedCid: (preread.get(entries[i]!.path) ?? null)?.cid,\n liveSeq: final - order.length + 1 + j,\n };\n });\n }\n for (const i of live) {\n if (!results[i]) {\n results[i] = {\n path: entries[i]!.path,\n deletedCid: (preread.get(entries[i]!.path) ?? null)?.cid,\n };\n }\n }\n\n // Empty-parent cleanup over the DISTINCT ancestor chains (deepest first) —\n // single-delete parity at deduped cost.\n const dirs = new Set<string>();\n for (const i of live) {\n for (const dirPath of ancestorDirs(entries[i]!.path)) dirs.add(dirPath);\n }\n const ordered = [...dirs].sort((a, b) => b.length - a.length);\n for (const dirPath of ordered) {\n const hasChildren = await this.db\n .prepare(\"SELECT 1 FROM ds_entries WHERE scope = ? AND parent = ? LIMIT 1\")\n .bind(this.scope, dirPath)\n .first();\n // `continue`, NOT cleanEmptyParents' `break`: this set is a UNION of\n // several paths' ancestor chains — a non-empty dir here says nothing\n // about dirs from a DIFFERENT deleted path's chain.\n if (hasChildren) continue;\n await this.db\n .prepare(\"DELETE FROM ds_entries WHERE scope = ? AND path = ?\")\n .bind(this.scope, dirPath)\n .run();\n }\n return results;\n }\n\n async put(path: string, entry: TreeEntry, opts: PutOpts): Promise<{ oldCid?: string }> {\n validateDeviceId(opts.deviceId);\n\n const _parent = parentOf(path);\n const _name = nameOf(path);\n const now = Date.now();\n\n const existing = await this.get(path);\n // Skip-if-unchanged: re-writing identical content at an existing path is a\n // no-op. `cid` IS the content hash (and the optimistic-concurrency version\n // stat() surfaces), so a matching cid+mimeType means nothing changed —\n // writing a changelog row + re-UPSERTing entries would burn D1 rows_written\n // and emit a phantom \"change\" other devices would pull for nothing. Returns\n // the same oldCid the full path would. (R2 object self-heal already ran in\n // CloudflareDIDSpace.internalWrite before this call.)\n //\n // Inline write-through re-fill: a same-cid re-write\n // still refreshes the inline `content` cache WHEN it is out of sync — a row\n // whose cid matches but whose inline copy was lost (legacy/unfilled, or an\n // externally voided copy) would otherwise never self-repair, leaving an\n // includeContent list permanently missing that entry. The common case\n // (inline already correct) still short-circuits, so D1 rows_written cost is\n // unchanged for normal same-cid re-writes; the extra UPSERT is paid ONLY\n // when the cache genuinely needs repair. (Reads still verify the inline cid\n // and fall back to R2, so a cache miss was never a correctness bug — but a\n // never-refilled local-only inline copy IS, once the object is GC'd.)\n if (\n existing &&\n existing.cid === entry.cid &&\n existing.mimeType === entry.mimeType &&\n treeEntryMetadataEqual(existing.metadata, entry.metadata)\n ) {\n // Metadata-aware skip (did-space-metadata G4): a metadata-only change\n // keeps the same cid — fall through to the UPSERT so it lands.\n if (this.inline.inlineCache !== \"on\") return { oldCid: existing.cid };\n const wouldBeInline = await resolveInlineContent(entry, this.inline);\n if ((existing.content ?? null) === wouldBeInline) return { oldCid: existing.cid };\n // Inline copy out of sync — fall through to the UPSERT to re-fill/clear it.\n }\n if (!existing) {\n // Capacity guard reads the trigger-maintained counter (O(1), one row)\n // instead of `count(\"\")` — a full-scope `COUNT(*)` whose per-write cost\n // grew with scope size and turned bulk sync into an O(N²) rows_read\n // blow-up. See `scope_meta.file_count` + its triggers in\n // changelog-schema.ts.\n const currentCount = await this.getScopeFileCount();\n if (currentCount >= this.maxEntriesPerScope) {\n throw storageQuotaError(\n `Storage quota exceeded: scope \"${this.scope}\" has reached the ${this.maxEntriesPerScope} file limit`,\n );\n }\n }\n // upload-limits Phase 5 byte quota (opt-in; OFF when maxBytesPerScope===0).\n // Guard the NET byte delta so an overwrite that GROWS past the quota is also\n // rejected: newBytes = size - oldSize (oldSize 0 for a new file). O(1) read\n // of the trigger-maintained total (never SUM(size) — that's the rejected 503\n // anti-pattern). Covers ALL writes through this provider (WS, HTTP, native\n // sync, CLI) — the one quota that DOES belong in the shared layer (design §5).\n if (this.maxBytesPerScope > 0) {\n const newBytes = entry.size - (existing?.size ?? 0);\n if (newBytes > 0) {\n const total = await this.getScopeTotalBytes();\n if (total + newBytes > this.maxBytesPerScope) {\n throw storageQuotaError(\n `Storage quota exceeded: scope \"${this.scope}\" has reached the ${this.maxBytesPerScope}-byte limit`,\n );\n }\n }\n }\n\n // Build batch: parent directory chain (INSERT OR IGNORE) + file entry\n // + (optional) changelog INSERT — all atomic.\n const stmts: D1PreparedStatement[] = [];\n\n // Ensure parent directories exist. Rule R2 (feed-sort §5.2): the DIRECT\n // parent's ctime/mtime are stamped from this write's application createdAt\n // (when present) so the feed's `orderBy ctime` reflects createdAt;\n // ancestors stay scaffold-only INSERT OR IGNORE.\n const dirStamp = resolveParentDirStamp(entry.metadata);\n const directParent = parentOf(path);\n for (const dirPath of ancestorDirs(path)) {\n const { sql, args } = buildDirChainStmt(\n this.scope,\n dirPath,\n dirStamp && dirPath === directParent ? dirStamp : null,\n now,\n );\n stmts.push(this.db.prepare(sql).bind(...args));\n }\n\n // Changelog row. We use a correlated subquery for `old_cid` so the\n // lookup runs INSIDE the atomic batch — this eliminates the race between\n // a caller-side `this.get()` and a concurrent writer landing its own\n // batch before ours commits. The subquery reads `entries.cid` as it\n // exists BEFORE the entries UPSERT below; that ordering is critical —\n // see the comment after this block.\n if (!opts.skipChangelog) {\n stmts.push(\n this.db\n .prepare(\n `INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)\n VALUES (?, ?, 'put', ?, (SELECT cid FROM ds_entries WHERE scope = ? AND path = ?), ?, ?, ?)`,\n )\n .bind(\n this.scope,\n path,\n entry.cid,\n this.scope,\n path,\n entry.size,\n entry.mtime,\n opts.deviceId,\n ),\n );\n }\n\n // File entry UPSERT — must come AFTER the changelog INSERT so the\n // correlated subquery above reads the pre-write cid, not the post-write\n // one. D1's batch preserves statement order.\n stmts.push(await this.buildFileUpsert(path, entry));\n\n // prefix_watermark bump (Live Update Resilience §11.4): one row per\n // collection root, +1 per write inside it, in the SAME atomic batch as the\n // changelog row. Skipped for changelog-less writes (sync-internal) and\n // root-level files (no collection root).\n if (!opts.skipChangelog) {\n const wmPrefix = watermarkWritePrefix(path);\n if (wmPrefix !== null) {\n stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));\n }\n }\n\n await this.db.batch(stmts);\n return { oldCid: existing?.cid };\n }\n\n /**\n * Build the file-row UPSERT. Inline-enabled indexes always write the\n * content column (inline copy or NULL — an overwrite without inline content\n * MUST clear any prior copy so the column never goes stale relative to the\n * new cid); default-off indexes keep the byte-identical legacy statement\n * (and never touch a not-yet-migrated database's missing column).\n */\n private async buildFileUpsert(path: string, entry: TreeEntry): Promise<D1PreparedStatement> {\n const metadataValue = serializeTreeEntryMetadata(entry.metadata);\n // Dedup-on-write columns (#1159). COALESCE on overwrite so a plain edit\n // (no dedup fields) never drops a dedup row out of the index; a dedup write\n // / reindex supplies them and they win.\n const dedupScopeValue = entry.dedupScope ?? null;\n const dedupKeyValue = entry.dedupKey ?? null;\n if (this.inline.inlineCache !== \"on\") {\n return this.db\n .prepare(\n `INSERT INTO ds_entries (scope, path, parent, name, kind, cid, size, mime_type, ctime, mtime, metadata, dedup_scope, dedup_key)\n VALUES (?, ?, ?, ?, 'file', ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(scope, path) DO UPDATE SET\n cid = excluded.cid,\n size = excluded.size,\n mtime = excluded.mtime,\n mime_type = excluded.mime_type,\n metadata = excluded.metadata,\n dedup_scope = COALESCE(excluded.dedup_scope, ds_entries.dedup_scope),\n dedup_key = COALESCE(excluded.dedup_key, ds_entries.dedup_key)`,\n )\n .bind(\n this.scope,\n path,\n parentOf(path),\n nameOf(path),\n entry.cid,\n entry.size,\n entry.mimeType ?? null,\n entry.ctime,\n entry.mtime,\n metadataValue,\n dedupScopeValue,\n dedupKeyValue,\n );\n }\n const inlineContent = await resolveInlineContent(entry, this.inline);\n return this.db\n .prepare(\n `INSERT INTO ds_entries (scope, path, parent, name, kind, cid, size, mime_type, ctime, mtime, content, metadata, dedup_scope, dedup_key)\n VALUES (?, ?, ?, ?, 'file', ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(scope, path) DO UPDATE SET\n cid = excluded.cid,\n size = excluded.size,\n mtime = excluded.mtime,\n mime_type = excluded.mime_type,\n content = excluded.content,\n metadata = excluded.metadata,\n dedup_scope = COALESCE(excluded.dedup_scope, ds_entries.dedup_scope),\n dedup_key = COALESCE(excluded.dedup_key, ds_entries.dedup_key)`,\n )\n .bind(\n this.scope,\n path,\n parentOf(path),\n nameOf(path),\n entry.cid,\n entry.size,\n entry.mimeType ?? null,\n entry.ctime,\n entry.mtime,\n inlineContent,\n metadataValue,\n dedupScopeValue,\n dedupKeyValue,\n );\n }\n\n async delete(path: string, opts: PutOpts): Promise<void> {\n validateDeviceId(opts.deviceId);\n\n // Two-statement atomic batch:\n // 1. INSERT INTO ds_changelog ... SELECT cid FROM ds_entries ...\n // The SELECT runs INSIDE the batch so we observe the current cid\n // even under concurrent writers. If no row exists the INSERT SELECT\n // produces zero rows (0 changelog entries) — the delete is still\n // issued but the changelog stays clean for no-op deletes.\n // 2. DELETE FROM ds_entries\n //\n // `SELECT ... FROM ds_entries WHERE cid IS NOT NULL` guarantees we never\n // log a phantom delete for a dir row (cid NULL) or a non-existent row.\n const stmts: D1PreparedStatement[] = [];\n\n if (!opts.skipChangelog) {\n stmts.push(\n this.db\n .prepare(\n `INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)\n SELECT ?, ?, 'delete', NULL, cid, NULL, ?, ?\n FROM ds_entries WHERE scope = ? AND path = ? AND cid IS NOT NULL`,\n )\n // T1.5: client mtime when supplied (echo prevention + LWW), else server clock.\n .bind(this.scope, path, opts.mtime ?? Date.now(), opts.deviceId, this.scope, path),\n );\n }\n\n // Decrement file_count iff `path` is currently a file. MUST precede the\n // DELETE so the EXISTS subquery sees the pre-delete row. (Increment is a\n // trigger; decrement is explicit so the entries DELETE's `meta.changes`\n // stays trigger-free — see changelog-schema.ts.)\n stmts.push(\n this.db.prepare(SCOPE_META_FILE_COUNT_DELETE_ONE_SQL).bind(this.scope, this.scope, path),\n );\n // upload-limits Phase 5: refund the deleted file's size (before the DELETE).\n stmts.push(\n this.db.prepare(SCOPE_META_TOTAL_BYTES_DELETE_ONE_SQL).bind(this.scope, path, this.scope),\n );\n\n stmts.push(\n this.db.prepare(\"DELETE FROM ds_entries WHERE scope = ? AND path = ?\").bind(this.scope, path),\n );\n\n // Watermark bump: a delete inside a collection root is a change the\n // collection's listers must reconcile (same gate as put).\n if (!opts.skipChangelog) {\n const wmPrefix = watermarkWritePrefix(path);\n if (wmPrefix !== null) {\n stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));\n }\n }\n\n await this.db.batch(stmts);\n\n // Clean up empty parent directories — runs outside the main batch because\n // it issues its own SELECT-then-DELETE loop. Parent-dir cleanup is NOT\n // recorded in the changelog (dirs are implementation scaffolding).\n await this.cleanEmptyParents(path);\n }\n\n /** Walk up parent chain, removing directory entries that have no remaining children */\n private async cleanEmptyParents(path: string): Promise<void> {\n for (const dirPath of ancestorDirs(path)) {\n const hasChildren = await this.db\n .prepare(\"SELECT 1 FROM ds_entries WHERE scope = ? AND parent = ? LIMIT 1\")\n .bind(this.scope, dirPath)\n .first();\n\n if (hasChildren) break;\n\n await this.db\n .prepare(\"DELETE FROM ds_entries WHERE scope = ? AND path = ?\")\n .bind(this.scope, dirPath)\n .run();\n }\n }\n\n // ========== List operations ==========\n\n // orderBy whitelist — runtime validation prevents SQL injection\n private static readonly VALID_ORDER_BY: Record<string, string> = {\n path: \"path\",\n mtime: \"mtime\",\n size: \"size\",\n ctime: \"ctime\",\n mimeType: \"mime_type\",\n name: \"name\",\n };\n\n /** The only nullable sortable column — NULLs sort last (see tree-index.ts). */\n private static readonly NULLABLE_ORDER_COLUMNS = new Set([\"mime_type\"]);\n\n async list(prefix: string, options?: TreeIndexListOptions): Promise<TreeIndexListResult> {\n const limit = options?.limit ?? 1000;\n const orderByKey = options?.orderBy ?? \"path\";\n const order = options?.order ?? \"asc\";\n const cursor = options?.cursor;\n\n const col = D1TreeIndex.VALID_ORDER_BY[orderByKey];\n if (!col) throw new Error(`Invalid orderBy: ${orderByKey}`);\n\n if (limit === 0) {\n return { entries: [], hasMore: false };\n }\n\n const [lo, hi] = prefixRange(prefix);\n const params: unknown[] = [this.scope, lo, hi];\n\n let sql = `SELECT path, ${col} as _sort FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'`;\n\n if (cursor) {\n const op = order === \"asc\" ? \">\" : \"<\";\n sql += ` AND ${col} ${op} ?`;\n params.push(cursor);\n }\n\n sql += ` ORDER BY ${col} ${order} LIMIT ?`;\n params.push(limit + 1);\n\n const { results } = await this.conn\n .prepare(sql)\n .bind(...params)\n .all<{ path: string; _sort: unknown }>();\n const hasMore = results.length > limit;\n const selected = hasMore ? results.slice(0, limit) : results;\n\n return {\n entries: selected.map((r) => ({ path: r.path })),\n hasMore,\n cursor:\n hasMore && selected.length > 0 ? String(selected[selected.length - 1]!._sort) : undefined,\n };\n }\n\n async listEntries(\n prefix: string,\n options?: TreeIndexListOptions,\n ): Promise<TreeIndexListEntriesResult> {\n const limit = options?.limit ?? 1000;\n const hasOrderBy = options?.orderBy !== undefined;\n const orderByKey = options?.orderBy ?? \"path\";\n const order = options?.order === \"desc\" ? \"desc\" : \"asc\";\n\n const col = D1TreeIndex.VALID_ORDER_BY[orderByKey];\n if (!col) throw new Error(`Invalid orderBy: ${orderByKey}`);\n\n if (limit === 0) {\n return { entries: [], hasMore: false };\n }\n\n const parsed = parseTreeCursor(options?.cursor, hasOrderBy);\n const [lo, hi] = prefixRange(prefix);\n const params: unknown[] = [this.scope, lo, hi];\n\n // includeContent is opt-in projection — the default SELECT never touches\n // the content column (zero-regression for every existing list path).\n const contentCol = options?.includeContent ? \", content\" : \"\";\n // Projecting non-indexed columns (cid/size/mtime/...) makes the planner\n // default to the PRIMARY KEY path-range scan instead of idx_entries_kind\n // (scope, kind, path) — reading every dir row in the range too (measured\n // on production D1: 5.6x rows read vs rows returned). Only force the hint\n // when the sort column is `path` itself, matching the index's own order —\n // other orderBy columns still need a manual sort either way.\n const indexHint = col === \"path\" ? \" INDEXED BY idx_entries_kind\" : \"\";\n let sql = `SELECT path, cid, size, mtime, ctime, mime_type${contentCol}, ${col} as _sort FROM ds_entries${indexHint} WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'`;\n\n if (hasOrderBy) {\n // Keyset resume on (col, path) — path is the unique tiebreak. NULL\n // mime_type sorts last in both directions. See tree-index.ts helpers.\n const parts = buildOrderedQueryParts({\n col,\n nullable: D1TreeIndex.NULLABLE_ORDER_COLUMNS.has(col),\n order,\n tieCol: \"path\",\n cursor: parsed.kind === \"v2\" ? parsed.cursor : undefined,\n });\n if (parts.cursorWhereSql) {\n sql += ` AND ${parts.cursorWhereSql}`;\n params.push(...parts.cursorArgs);\n }\n sql += ` ORDER BY ${parts.orderSql} LIMIT ?`;\n } else {\n // Legacy path — byte-identical to the pre-orderBy behavior.\n if (parsed.kind === \"legacy\") {\n const op = order === \"asc\" ? \">\" : \"<\";\n sql += ` AND ${col} ${op} ?`;\n params.push(parsed.value);\n }\n sql += ` ORDER BY ${col} ${order} LIMIT ?`;\n }\n params.push(limit + 1);\n\n const { results } = await this.conn\n .prepare(sql)\n .bind(...params)\n .all<{\n path: string;\n cid: string;\n size: number;\n mtime: number;\n ctime: number;\n mime_type: string | null;\n content?: string | null;\n _sort: unknown;\n }>();\n const hasMore = results.length > limit;\n const selected = hasMore ? results.slice(0, limit) : results;\n const last = selected[selected.length - 1];\n\n return {\n entries: selected.map((r) => {\n const e: { path: string } & TreeEntry = {\n path: r.path,\n cid: r.cid,\n size: r.size,\n mtime: r.mtime,\n ctime: r.ctime,\n mimeType: r.mime_type ?? undefined,\n };\n if (options?.includeContent && r.content != null) e.content = r.content;\n return e;\n }),\n hasMore,\n cursor:\n !hasMore || !last\n ? undefined\n : hasOrderBy\n ? buildKeysetCursor((last._sort as string | number | null) ?? null, last.path)\n : String(last._sort),\n };\n }\n\n async hasChildren(prefix: string): Promise<boolean> {\n const [lo, hi] = prefixRange(prefix);\n const row = await this.conn\n .prepare(\n \"SELECT 1 FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file' LIMIT 1\",\n )\n .bind(this.scope, lo, hi)\n .first();\n return row !== null;\n }\n\n async getAndHasChildren(\n filePath: string,\n dirPrefix: string,\n ): Promise<[TreeEntry | null, boolean]> {\n const contentCol = this.inline.inlineCache === \"on\" ? \", content\" : \"\";\n const [lo, hi] = prefixRange(dirPrefix);\n const batchResults = await this.db.batch([\n this.db\n .prepare(\n `SELECT cid, size, mtime, ctime, mime_type, metadata${contentCol} FROM ds_entries WHERE scope = ? AND path = ?`,\n )\n .bind(this.scope, filePath),\n this.db\n .prepare(\n \"SELECT 1 FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file' LIMIT 1\",\n )\n .bind(this.scope, lo, hi),\n ]);\n const getResult = batchResults[0]!;\n const hasChildResult = batchResults[1]!;\n\n const row = getResult.results[0] as\n | {\n cid: string | null;\n size: number;\n mtime: number;\n ctime: number;\n mime_type: string | null;\n metadata: string | null;\n content?: string | null;\n }\n | undefined;\n\n let entry: TreeEntry | null = null;\n if (row?.cid) {\n entry = {\n cid: row.cid,\n size: row.size,\n mtime: row.mtime,\n ctime: row.ctime,\n mimeType: row.mime_type ?? undefined,\n };\n if (row.content != null) entry.content = row.content;\n const meta = parseTreeEntryMetadata(row.metadata);\n if (meta) entry.metadata = meta;\n }\n\n return [entry, (hasChildResult.results?.length ?? 0) > 0];\n }\n\n async count(prefix: string): Promise<number> {\n const [lo, hi] = prefixRange(prefix);\n const row = await this.conn\n .prepare(\n \"SELECT COUNT(*) as cnt FROM ds_entries WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'\",\n )\n .bind(this.scope, lo, hi)\n .first<{ cnt: number }>();\n return row?.cnt ?? 0;\n }\n\n /**\n * O(1) read of the trigger-maintained file count for this scope, used by the\n * `put()` capacity guard. A missing `scope_meta` row means \"no files counted\n * yet\" → 0, which is also the correct answer for a brand-new scope and the\n * safe (permissive) fallback for a scope not yet backfilled. This replaces\n * the per-write full-scope `count(\"\")` scan — the source of the O(N²)\n * `rows_read` blow-up that exhausted the D1 free tier during bulk sync.\n */\n private async getScopeFileCount(): Promise<number> {\n const row = await this.conn\n .prepare(\"SELECT file_count FROM ds_scope_meta WHERE scope = ?\")\n .bind(this.scope)\n .first<{ file_count: number }>();\n return row?.file_count ?? 0;\n }\n\n /**\n * O(1) read of the trigger-maintained byte total for this scope, used by the\n * Phase 5 byte-quota guard (only when `maxBytesPerScope > 0`). Same O(1)\n * contract as {@link getScopeFileCount} — never a `SELECT SUM(size)` scan.\n * Missing row → 0 (new/un-backfilled scope; permissive).\n */\n private async getScopeTotalBytes(): Promise<number> {\n const row = await this.conn\n .prepare(\"SELECT total_bytes FROM ds_scope_meta WHERE scope = ?\")\n .bind(this.scope)\n .first<{ total_bytes: number }>();\n return row?.total_bytes ?? 0;\n }\n\n async getCompactedBelowSeq(): Promise<number> {\n const row = await this.conn\n .prepare(\"SELECT compacted_below_seq FROM ds_scope_meta WHERE scope = ?\")\n .bind(this.scope)\n .first<{ compacted_below_seq: number }>();\n return row?.compacted_below_seq ?? 0;\n }\n\n async deleteByPrefix(prefix: string, opts: PutOpts): Promise<number> {\n validateDeviceId(opts.deviceId);\n\n const [lo, hi] = prefixRange(prefix);\n\n // Atomic batch:\n // 1. INSERT INTO ds_changelog SELECT ... FROM ds_entries WHERE prefix-range\n // (captures every file row's prior cid in one statement — no pre-\n // query, no race with concurrent writers)\n // 2. DELETE FROM ds_entries WHERE prefix-range\n //\n // Dir rows (cid IS NULL) are not logged — the WHERE clause filters them.\n const stmts: D1PreparedStatement[] = [];\n\n if (!opts.skipChangelog) {\n // T1.5: client mtime when supplied (echo prevention + LWW), else server clock.\n const now = opts.mtime ?? Date.now();\n stmts.push(\n this.db\n .prepare(\n `INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)\n SELECT ?, path, 'delete', NULL, cid, NULL, ?, ?\n FROM ds_entries INDEXED BY idx_entries_kind\n WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file' AND cid IS NOT NULL`,\n )\n .bind(this.scope, now, opts.deviceId, this.scope, lo, hi),\n );\n }\n\n // Decrement file_count by the number of file rows in the range. MUST\n // precede the DELETE so the COUNT sees the pre-delete rows, and MUST NOT\n // be the last statement so the DELETE's `meta.changes` (read below) stays\n // the verbatim deleted-row count. See changelog-schema.ts for why the\n // decrement is explicit instead of an AFTER-DELETE trigger.\n stmts.push(\n this.db.prepare(SCOPE_META_FILE_COUNT_DELETE_RANGE_SQL).bind(this.scope, lo, hi, this.scope),\n );\n // upload-limits Phase 5: refund Σ size of the deleted range from total_bytes.\n // Also BEFORE the DELETE (and not last) so the SUM sees pre-delete rows and\n // the DELETE's meta.changes stays the verbatim deleted-row count.\n stmts.push(\n this.db.prepare(SCOPE_META_TOTAL_BYTES_DELETE_RANGE_SQL).bind(this.scope, lo, hi, this.scope),\n );\n\n // Watermark bump: deleting a subtree under a collection root is a change\n // its listers must reconcile. Unlike a single-file write (depth >= 2), a\n // prefix delete uses the first segment of the prefix directly — clearing an\n // entire collection root (`items/`) still bumps `items`. MUST precede the\n // DELETE so the DELETE stays the last statement (its meta.changes is the\n // returned deleted-row count).\n if (!opts.skipChangelog) {\n const wmPrefix = watermarkPrefix(prefix);\n if (wmPrefix !== null) {\n stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));\n }\n }\n\n stmts.push(\n this.db\n .prepare(\"DELETE FROM ds_entries WHERE scope = ? AND path >= ? AND path < ?\")\n .bind(this.scope, lo, hi),\n );\n\n const batchResults = await this.db.batch(stmts);\n // The DELETE is the LAST statement in the batch. batchResults[-1] holds\n // its meta.changes count which we return verbatim (includes dir rows —\n // matches pre-2.4a behaviour so test counts do not regress).\n const deleteResult = batchResults[batchResults.length - 1] as D1Result<unknown>;\n return deleteResult.meta.changes;\n }\n\n // ========== v2 methods (Phase 0) ==========\n\n async listChildren(\n parent: string,\n options?: TreeIndexListOptions,\n ): Promise<TreeIndexListEntriesResult> {\n const limit = options?.limit ?? 1000;\n const order = options?.order === \"desc\" ? \"desc\" : \"asc\";\n\n if (limit === 0) {\n return { entries: [], hasMore: false };\n }\n\n const hasOrderBy = options?.orderBy !== undefined;\n const parsed = parseTreeCursor(options?.cursor, hasOrderBy);\n const params: unknown[] = [this.scope, parent];\n\n type ChildRow = {\n path: string;\n cid: string | null;\n size: number;\n mtime: number;\n ctime: number;\n mime_type: string | null;\n content?: string | null;\n name: string;\n _sort: string | number | null;\n };\n const includeContent = options?.includeContent === true;\n // Candidates FROM clause comes from the SHARED builder (tree-query.ts).\n // Dir-per-record joins moved to the query engine's doc form (T4.3) —\n // listChildren is always join-free now.\n const t = \"e.\";\n const from = buildChildrenFrom(undefined);\n const fromClause = from.sql;\n const contentCol = includeContent ? `, ${t}content` : \"\";\n params.unshift(...from.args);\n const mapChild = (r: ChildRow) => {\n const e: { path: string } & TreeEntry = {\n path: r.path,\n cid: r.cid ?? \"\",\n size: r.size,\n mtime: r.mtime,\n ctime: r.ctime,\n mimeType: r.mime_type ?? undefined,\n };\n if (includeContent && r.content != null) e.content = r.content;\n return e;\n };\n\n if (hasOrderBy) {\n const col = D1TreeIndex.VALID_ORDER_BY[options!.orderBy!];\n if (!col) throw new Error(`Invalid orderBy: ${options?.orderBy}`);\n // Keyset resume on (col, name) — name is unique within a parent. NULL\n // mime_type sorts last in both directions. See tree-index.ts helpers.\n const parts = buildOrderedQueryParts({\n col: `${t}${col}`,\n nullable: D1TreeIndex.NULLABLE_ORDER_COLUMNS.has(col),\n order,\n tieCol: `${t}name`,\n cursor: parsed.kind === \"v2\" ? parsed.cursor : undefined,\n });\n let sql = `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 = ?`;\n if (parts.cursorWhereSql) {\n sql += ` AND ${parts.cursorWhereSql}`;\n params.push(...parts.cursorArgs);\n }\n sql += ` ORDER BY ${parts.orderSql} LIMIT ?`;\n params.push(limit + 1);\n\n const { results } = await this.conn\n .prepare(sql)\n .bind(...params)\n .all<ChildRow>();\n const hasMore = results.length > limit;\n const selected = hasMore ? results.slice(0, limit) : results;\n const last = selected[selected.length - 1];\n return {\n entries: selected.map(mapChild),\n hasMore,\n cursor: hasMore && last ? buildKeysetCursor(last._sort ?? null, last.name) : undefined,\n };\n }\n\n // Legacy default path — byte-identical: ORDER BY name, plain name cursor.\n 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 = ?`;\n if (parsed.kind === \"legacy\") {\n const op = order === \"asc\" ? \">\" : \"<\";\n sql += ` AND ${t}name ${op} ?`;\n params.push(parsed.value);\n }\n sql += ` ORDER BY ${t}name ${order} LIMIT ?`;\n params.push(limit + 1);\n\n const { results } = await this.conn\n .prepare(sql)\n .bind(...params)\n .all<ChildRow>();\n const hasMore = results.length > limit;\n const selected = hasMore ? results.slice(0, limit) : results;\n\n return {\n entries: selected.map(mapChild),\n hasMore,\n cursor:\n hasMore && selected.length > 0 ? String(selected[selected.length - 1]!.name) : undefined,\n };\n }\n\n async countChildren(parent: string): Promise<number> {\n const row = await this.conn\n .prepare(\"SELECT COUNT(*) as cnt FROM ds_entries WHERE scope = ? AND parent = ?\")\n .bind(this.scope, parent)\n .first<{ cnt: number }>();\n return row?.cnt ?? 0;\n }\n\n /**\n * Collection query (afs-collection-query Phase 2) — same shared compiler +\n * result builder as SQLiteTreeIndex (tree-query.ts), executed over the\n * replica-aware `this.conn`: query is read-class and follows the SAME\n * replica routing policy as list (AFS_D1_READ_REPLICA gate upstream picks\n * the session; no separate consistency standard — design T2.1).\n */\n async query(parent: string, spec: TreeQuerySpec): Promise<TreeQueryResult> {\n const statements = buildTreeQueryStatements({ scope: this.scope, parent, spec });\n\n if (statements.aggregate) {\n const [aggRes, countRow] = await Promise.all([\n this.conn\n .prepare(statements.aggregate.select.sql)\n .bind(...statements.aggregate.select.args)\n .all<Record<string, unknown>>(),\n this.conn\n .prepare(statements.aggregate.countGroups.sql)\n .bind(...statements.aggregate.countGroups.args)\n .first<{ cnt: number }>(),\n ]);\n const aggregateGroups = mapAggregateRows(\n aggRes.results,\n statements.aggregate.groupKeys,\n statements.aggregate.aggregations,\n );\n const total = statements.aggregate.groupKeys.length === 0 ? 1 : (countRow?.cnt ?? 0);\n return { rows: [], total, aggregateGroups };\n }\n\n const [rowsRes, countRow, groupsRes, seekRow] = await Promise.all([\n this.conn\n .prepare(statements.rows.sql)\n .bind(...statements.rows.args)\n .all<Record<string, unknown>>(),\n // skipTotal: no count statement was built — skip the full-directory scan\n // and report total: -1 below.\n statements.count\n ? this.conn\n .prepare(statements.count.sql)\n .bind(...statements.count.args)\n .first<{ cnt: number }>()\n : Promise.resolve(undefined),\n statements.groups\n ? this.conn\n .prepare(statements.groups.sql)\n .bind(...statements.groups.args)\n .all<{ t: unknown; v: unknown; cnt: unknown }>()\n : Promise.resolve(undefined),\n statements.seek\n ? this.conn\n .prepare(statements.seek.sql)\n .bind(...statements.seek.args)\n .first<{ rn: number }>()\n : Promise.resolve(undefined),\n ]);\n\n const seekOffset = statements.seek ? (seekRow?.rn ?? -1) : undefined;\n\n return buildTreeQueryResultFromSql({\n rawRows: rowsRes.results,\n total: statements.count ? (countRow?.cnt ?? 0) : -1,\n groupRows: groupsRes?.results,\n spec,\n orderField: statements.order.field,\n seekOffset,\n });\n }\n\n /**\n * Page-bounded grouped child counts: ONE chunked `parent IN (…)` aggregate\n * per ≤90 parents (D1's 100-bind limit minus the scope bind — same chunking\n * as `getMany`), index-only seeks on `idx_entries_children(scope, parent,\n * name)`. rows_read is bounded by the listed page's direct grandchildren.\n *\n * Predecessor (`countChildrenGrouped(prefix)`) used a `parent >= lo AND\n * parent < hi` range — a scan/group over EVERY descendant under the listed\n * prefix on EVERY list. Once the aside user space grew to ~1.8k dirs, each\n * list of any ancestor dir read ~3.8k rows; at list frequency that burned\n * ~100M D1 rows_read/day (42× the free tier).\n */\n async countChildrenMany(parents: string[]): Promise<Map<string, number>> {\n const map = new Map<string, number>();\n for (let offset = 0; offset < parents.length; offset += 90) {\n const chunk = parents.slice(offset, offset + 90);\n const { results } = await this.conn\n .prepare(\n `SELECT parent, COUNT(*) as cnt FROM ds_entries WHERE scope = ? AND parent IN (${chunk.map(() => \"?\").join(\",\")}) GROUP BY parent`,\n )\n .bind(this.scope, ...chunk)\n .all<{ parent: string; cnt: number }>();\n for (const row of results) map.set(row.parent, row.cnt);\n }\n return map;\n }\n\n async aggregate(prefix: string): Promise<{ fileCount: number; totalSize: number }> {\n const [lo, hi] = prefixRange(prefix);\n const row = await this.conn\n .prepare(\n `SELECT COUNT(*) as file_count, COALESCE(SUM(size), 0) as total_size\n FROM ds_entries INDEXED BY idx_entries_kind WHERE scope = ? AND path >= ? AND path < ? AND kind = 'file'`,\n )\n .bind(this.scope, lo, hi)\n .first<{ file_count: number; total_size: number }>();\n return {\n fileCount: row?.file_count ?? 0,\n totalSize: row?.total_size ?? 0,\n };\n }\n\n async putBatch(entries: Array<{ path: string; entry: TreeEntry }>, opts: PutOpts): Promise<void> {\n if (entries.length === 0) return;\n\n validateDeviceId(opts.deviceId);\n\n const now = Date.now();\n const stmts: D1PreparedStatement[] = [];\n\n // Collect all unique parent dirs across all entries. Rule R2 (feed-sort\n // §5.2): direct parents of createdAt-bearing writes get a ctime/mtime\n // stamp (LWW by entry order); ancestors stay scaffold-only.\n const dirStamps = new Map<string, { ctime: number; mtime: number }>();\n const allDirs = new Set<string>();\n for (const { path, entry } of entries) {\n const stamp = resolveParentDirStamp(entry.metadata);\n if (stamp) dirStamps.set(parentOf(path), stamp);\n for (const dirPath of ancestorDirs(path)) {\n if (allDirs.has(dirPath)) break;\n allDirs.add(dirPath);\n }\n }\n\n // INSERT OR IGNORE (scaffold) or stamping UPSERT (direct parent) per dir.\n for (const dirPath of allDirs) {\n const { sql, args } = buildDirChainStmt(\n this.scope,\n dirPath,\n dirStamps.get(dirPath) ?? null,\n now,\n );\n stmts.push(this.db.prepare(sql).bind(...args));\n }\n\n // Per entry: (a) changelog INSERT with correlated subquery (reads prior\n // cid), then (b) file UPSERT. Order is critical — see the single-put\n // implementation for the rationale.\n for (const { path, entry } of entries) {\n if (!opts.skipChangelog) {\n stmts.push(\n this.db\n .prepare(\n `INSERT INTO ds_changelog (scope, path, op, cid, old_cid, size, mtime, device_id)\n VALUES (?, ?, 'put', ?, (SELECT cid FROM ds_entries WHERE scope = ? AND path = ?), ?, ?, ?)`,\n )\n .bind(\n this.scope,\n path,\n entry.cid,\n this.scope,\n path,\n entry.size,\n entry.mtime,\n opts.deviceId,\n ),\n );\n }\n\n stmts.push(await this.buildFileUpsert(path, entry));\n }\n\n // Watermark: one bump per DISTINCT collection root touched (a bulk write of\n // N files into one collection bumps its version by 1, not N — listers only\n // need \"did it change\", not a per-file count).\n if (!opts.skipChangelog) {\n const wmPrefixes = new Set<string>();\n for (const { path } of entries) {\n const wmPrefix = watermarkWritePrefix(path);\n if (wmPrefix !== null) wmPrefixes.add(wmPrefix);\n }\n for (const wmPrefix of wmPrefixes) {\n stmts.push(this.db.prepare(PREFIX_WATERMARK_BUMP_SQL).bind(this.scope, wmPrefix));\n }\n }\n\n await this.db.batch(stmts);\n }\n\n /**\n * Read the per-prefix watermark version (Live Update Resilience §11.4).\n * Reduces `prefix` to its first path segment (collection root) and returns\n * that row's `version`, or 0 when no row exists. Replica-aware via `this.conn`\n * — but reconcile passes a primary-pinned index for read-your-watermark\n * (DEC-5; see CloudflareDIDSpace.getInstanceSpace consistency option).\n */\n async getPrefixWatermark(prefix: string): Promise<number | null> {\n const wmPrefix = watermarkPrefix(prefix);\n if (wmPrefix === null) return null;\n const row = await this.conn\n .prepare(PREFIX_WATERMARK_READ_SQL)\n .bind(this.scope, wmPrefix)\n .first<{ version: number }>();\n return row?.version ?? 0;\n }\n\n async listModifiedSince(\n prefix: string,\n since: number,\n options?: TreeIndexListOptions,\n ): Promise<TreeIndexListEntriesResult> {\n const limit = options?.limit ?? 1000;\n const order = options?.order ?? \"asc\";\n const cursor = options?.cursor;\n\n if (limit === 0) {\n return { entries: [], hasMore: false };\n }\n\n const [lo, hi] = prefixRange(prefix);\n const params: unknown[] = [this.scope, lo, hi, since];\n\n let sql = `SELECT path, cid, size, mtime, ctime, mime_type FROM ds_entries INDEXED BY idx_entries_kind\n WHERE scope = ? AND path >= ? AND path < ? AND mtime > ? AND kind = 'file'`;\n\n if (cursor) {\n const op = order === \"asc\" ? \">\" : \"<\";\n sql += ` AND path ${op} ?`;\n params.push(cursor);\n }\n\n sql += ` ORDER BY path ${order} LIMIT ?`;\n params.push(limit + 1);\n\n const { results } = await this.conn\n .prepare(sql)\n .bind(...params)\n .all<{\n path: string;\n cid: string;\n size: number;\n mtime: number;\n ctime: number;\n mime_type: string | null;\n }>();\n\n const hasMore = results.length > limit;\n const selected = hasMore ? results.slice(0, limit) : results;\n\n return {\n entries: selected.map((r) => ({\n path: r.path,\n cid: r.cid,\n size: r.size,\n mtime: r.mtime,\n ctime: r.ctime,\n mimeType: r.mime_type ?? undefined,\n })),\n hasMore,\n cursor:\n hasMore && selected.length > 0 ? String(selected[selected.length - 1]!.path) : undefined,\n };\n }\n\n async getMaxSeq(): Promise<number> {\n const [row, compactedBelow] = await Promise.all([\n this.conn\n .prepare(\"SELECT MAX(seq) as maxSeq FROM ds_changelog WHERE scope = ?\")\n .bind(this.scope)\n .first<{ maxSeq: number | null }>(),\n this.getCompactedBelowSeq(),\n ]);\n return Math.max(row?.maxSeq ?? 0, compactedBelow);\n }\n\n /**\n * Read-auth reference check (design §4.1) — three-way, scoped, optionally\n * prefix-local. See {@link TreeIndex.isReferenced}.\n */\n async isReferenced(cid: string, pathPrefix = \"\"): Promise<boolean> {\n if (!cid) return false;\n // Whole-scope (pathPrefix \"\") applies NO path filter. We can't reuse the\n // `prefixRange(\"\")` [\"\", \"\\xff\"] range here: its `\\xff` sentinel is a single\n // code point whose UTF-8 encoding (0xC3 0xBF) sorts BEFORE any path whose\n // first byte exceeds 0xC3 (Cyrillic/CJK/emoji filenames), which would wrongly\n // exclude them. A bare scope+cid match is both correct and faster.\n if (pathPrefix === \"\") {\n const row = await this.conn\n .prepare(\n `SELECT 1 FROM (\n SELECT 1 FROM ds_entries WHERE scope = ? AND cid = ?\n UNION ALL\n SELECT 1 FROM ds_changelog WHERE scope = ? AND cid = ?\n UNION ALL\n SELECT 1 FROM ds_changelog WHERE scope = ? AND old_cid = ?\n ) LIMIT 1`,\n )\n .bind(this.scope, cid, this.scope, cid, this.scope, cid)\n .first();\n return row !== null;\n }\n const [lo, hi] = prefixRange(pathPrefix);\n const row = await this.conn\n .prepare(\n `SELECT 1 FROM (\n SELECT 1 FROM ds_entries WHERE scope = ? AND cid = ? AND path >= ? AND path < ?\n UNION ALL\n SELECT 1 FROM ds_changelog WHERE scope = ? AND cid = ? AND path >= ? AND path < ?\n UNION ALL\n SELECT 1 FROM ds_changelog WHERE scope = ? AND old_cid = ? AND path >= ? AND path < ?\n ) LIMIT 1`,\n )\n .bind(this.scope, cid, lo, hi, this.scope, cid, lo, hi, this.scope, cid, lo, hi)\n .first();\n return row !== null;\n }\n\n /**\n * Batch read-auth (design §7.2 / §14) — three-way-equivalent to per-cid\n * {@link isReferenced}, sharded under D1's 100-param cap (see\n * {@link D1_MANY_SHARD_SIZE}). Returns the referenced subset of `cids`.\n */\n async isReferencedMany(cids: string[], pathPrefix = \"\"): Promise<Set<string>> {\n const referenced = new Set<string>();\n const unique = [...new Set(cids.filter((c) => c))];\n if (unique.length === 0) return referenced;\n const usePrefix = pathPrefix !== \"\";\n const [lo, hi] = usePrefix ? prefixRange(pathPrefix) : [\"\", \"\"];\n for (let i = 0; i < unique.length; i += D1_MANY_SHARD_SIZE) {\n const shard = unique.slice(i, i + D1_MANY_SHARD_SIZE);\n const ph = shard.map(() => \"?\").join(\",\");\n const tail = usePrefix ? \"AND path >= ? AND path < ?\" : \"\";\n const sql = `SELECT cid AS c FROM ds_entries WHERE scope = ? AND cid IN (${ph}) ${tail}\n UNION\n SELECT cid AS c FROM ds_changelog WHERE scope = ? AND cid IN (${ph}) ${tail}\n UNION\n SELECT old_cid AS c FROM ds_changelog WHERE scope = ? AND old_cid IN (${ph}) ${tail}`;\n const bind: unknown[] = [];\n for (let b = 0; b < 3; b++) {\n bind.push(this.scope, ...shard);\n if (usePrefix) bind.push(lo, hi);\n }\n const { results } = await this.conn\n .prepare(sql)\n .bind(...bind)\n .all<{ c: string }>();\n for (const row of results) if (row.c) referenced.add(row.c);\n }\n return referenced;\n }\n\n async getChangelogSince(\n sinceSeq: number,\n limit: number,\n ): Promise<{ entries: ChangelogEntry[]; hasMore: boolean; cursor: number }> {\n const safeLimit = limit > 0 ? limit : 0;\n if (safeLimit === 0) return { entries: [], hasMore: false, cursor: sinceSeq };\n\n // Fetch limit+1 to detect hasMore without a second COUNT round-trip.\n const { results } = await this.conn\n .prepare(\n `SELECT seq, scope, path, op, cid, old_cid, size, mtime, device_id\n FROM ds_changelog WHERE scope = ? AND seq > ? ORDER BY seq ASC LIMIT ?`,\n )\n .bind(this.scope, sinceSeq, safeLimit + 1)\n .all<ChangelogEntry>();\n\n const hasMore = results.length > safeLimit;\n const page = hasMore ? results.slice(0, safeLimit) : results;\n const cursor = page.length > 0 ? page[page.length - 1]!.seq : sinceSeq;\n return { entries: page, hasMore, cursor };\n }\n\n // ========== Source-side dirty queue (index-follower-dirty-path §7) ==========\n\n /**\n * Ready dirty rows under `prefix` for this scope. `next_attempt_at <= now`\n * skips poison rows still in backoff; the `(scope, next_attempt_at,\n * updated_at)` index serves the ordered seek. Reads go through `this.conn`, so\n * a follower constructed primary-bound (`useReplica:false`, Phase 3) gets\n * read-your-write; a replica-routed instance would return replica state — the\n * runtime is responsible for pinning primary for the dirty follower.\n */\n async listDirtyPaths(options: ListDirtyPathOptions): Promise<DirtyPathRow[]> {\n const now = options.now ?? Date.now();\n const limit = options.limit > 0 ? options.limit : 0;\n if (limit === 0) return [];\n const [lo, hi] = prefixRange(options.prefix);\n const { results } = await this.conn\n .prepare(DIRTY_PATH_LIST_SQL)\n .bind(this.scope, lo, hi, now, limit)\n .all<{\n path: string;\n latest_seq: number;\n op: \"put\" | \"delete\";\n cid: string | null;\n mtime: number;\n attempts: number;\n }>();\n return results.map((r) => ({\n path: r.path,\n latestSeq: Number(r.latest_seq),\n op: r.op,\n cid: r.cid,\n mtime: Number(r.mtime),\n attempts: Number(r.attempts),\n }));\n }\n\n /**\n * Guarded ack (design §8): delete the dirty row ONLY if its `latest_seq` is\n * still the one we processed. A concurrent write bumps latest_seq (trigger),\n * so a stale worker's DELETE affects 0 rows → returns false, and the newer\n * dirty row survives for the next tick. Writes go to primary (`this.db`).\n */\n async ackDirtyPath(path: string, latestSeq: number): Promise<boolean> {\n const res = await this.db.prepare(DIRTY_PATH_ACK_SQL).bind(this.scope, path, latestSeq).run();\n return (res.meta?.changes ?? 0) > 0;\n }\n\n /**\n * Guarded failure record (design §8): bump attempts, stamp last_error +\n * next_attempt_at, SAME `latest_seq` guard so a fresh write's reset (trigger\n * sets attempts=0) is never clobbered by a late error from the prior attempt.\n */\n async markDirtyPathError(\n path: string,\n latestSeq: number,\n error: string,\n nextAttemptAt: number,\n ): Promise<boolean> {\n const res = await this.db\n .prepare(DIRTY_PATH_MARK_ERROR_SQL)\n .bind(error, nextAttemptAt, this.scope, path, latestSeq)\n .run();\n return (res.meta?.changes ?? 0) > 0;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const require_did_utils = require('./did-utils.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/data-backend.ts
|
|
4
|
+
var DIDSpaceBlockletDataBackend = class {
|
|
5
|
+
didSpace;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.didSpace = options.didSpace;
|
|
8
|
+
}
|
|
9
|
+
/** Resolve the system-role instance space (content tree, mounted at `/`). */
|
|
10
|
+
instanceSpace(blockletDid) {
|
|
11
|
+
return this.didSpace.getInstanceSpace({
|
|
12
|
+
instanceDid: require_did_utils.normalizeId(blockletDid),
|
|
13
|
+
role: "system"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the runtime-internal config fragment root (`/`), or null if the
|
|
18
|
+
* backend has none. /files-prefix-removal: config moved out of the role-AFS
|
|
19
|
+
* into a sibling fragment, reached via `getConfigSpace` instead of the old
|
|
20
|
+
* `system/config` sub-mount.
|
|
21
|
+
*/
|
|
22
|
+
async configSpace(blockletDid) {
|
|
23
|
+
if (typeof this.didSpace.getConfigSpace !== "function") return null;
|
|
24
|
+
return this.didSpace.getConfigSpace(require_did_utils.normalizeId(blockletDid));
|
|
25
|
+
}
|
|
26
|
+
async getInstanceAFS(blockletDid) {
|
|
27
|
+
return this.instanceSpace(blockletDid);
|
|
28
|
+
}
|
|
29
|
+
async removeData(blockletDid) {
|
|
30
|
+
const appAFS = await this.instanceSpace(blockletDid);
|
|
31
|
+
try {
|
|
32
|
+
const files = (await appAFS.list("/", { maxDepth: 99 })).data.filter((e) => e.meta?.childrenCount === void 0 || e.meta.childrenCount === null);
|
|
33
|
+
for (const file of files) try {
|
|
34
|
+
await appAFS.delete(file.path);
|
|
35
|
+
} catch {}
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
async readMountOverrides(blockletDid) {
|
|
39
|
+
const cfg = await this.configSpace(blockletDid);
|
|
40
|
+
if (!cfg) return [];
|
|
41
|
+
const configPath = "/mounts.toml";
|
|
42
|
+
try {
|
|
43
|
+
const result = await cfg.read(configPath);
|
|
44
|
+
const content = String(result.data?.content ?? "");
|
|
45
|
+
if (!content.trim()) return [];
|
|
46
|
+
return this.parseMountsToml(content);
|
|
47
|
+
} catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async writeMountOverrides(blockletDid, overrides) {
|
|
52
|
+
if (overrides.length === 0) return;
|
|
53
|
+
const cfg = await this.configSpace(blockletDid);
|
|
54
|
+
if (!cfg) return;
|
|
55
|
+
const configPath = "/mounts.toml";
|
|
56
|
+
const existing = await this.readMountOverrides(blockletDid);
|
|
57
|
+
const byTarget = new Map(existing.map((o) => [o.target, o]));
|
|
58
|
+
for (const override of overrides) {
|
|
59
|
+
const entry = {
|
|
60
|
+
target: override.target,
|
|
61
|
+
uri: override.uri
|
|
62
|
+
};
|
|
63
|
+
if (override.options && Object.keys(override.options).length > 0) entry.options = { ...override.options };
|
|
64
|
+
byTarget.set(override.target, entry);
|
|
65
|
+
}
|
|
66
|
+
const content = this.stringifyMountsToml([...byTarget.values()]);
|
|
67
|
+
await cfg.write(configPath, { content });
|
|
68
|
+
}
|
|
69
|
+
async removeMountOverrides(blockletDid, targets) {
|
|
70
|
+
if (targets.length === 0) return;
|
|
71
|
+
const targetSet = new Set(targets);
|
|
72
|
+
const remaining = (await this.readMountOverrides(blockletDid)).filter((override) => !targetSet.has(override.target));
|
|
73
|
+
const cfg = await this.configSpace(blockletDid);
|
|
74
|
+
if (!cfg) return;
|
|
75
|
+
const configPath = "/mounts.toml";
|
|
76
|
+
if (remaining.length === 0) try {
|
|
77
|
+
await cfg.delete(configPath);
|
|
78
|
+
return;
|
|
79
|
+
} catch {}
|
|
80
|
+
await cfg.write(configPath, { content: this.stringifyMountsToml(remaining) });
|
|
81
|
+
}
|
|
82
|
+
parseMountsToml(content) {
|
|
83
|
+
try {
|
|
84
|
+
const result = [];
|
|
85
|
+
const lines = content.split("\n");
|
|
86
|
+
let current = null;
|
|
87
|
+
let inOptions = false;
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
const trimmed = line.trim();
|
|
90
|
+
if (trimmed === "[[mounts]]") {
|
|
91
|
+
if (current?.target && current.uri) result.push({
|
|
92
|
+
target: current.target,
|
|
93
|
+
uri: current.uri,
|
|
94
|
+
...current.options ? { options: current.options } : {}
|
|
95
|
+
});
|
|
96
|
+
current = {};
|
|
97
|
+
inOptions = false;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!current) continue;
|
|
101
|
+
if (trimmed === "[mounts.options]") {
|
|
102
|
+
inOptions = true;
|
|
103
|
+
if (!current.options) current.options = {};
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const match = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
|
107
|
+
if (match) {
|
|
108
|
+
const [, key, rawVal] = match;
|
|
109
|
+
let value;
|
|
110
|
+
const v = rawVal.trim();
|
|
111
|
+
if (v.startsWith("\"") && v.endsWith("\"")) value = v.slice(1, -1);
|
|
112
|
+
else if (v === "true") value = true;
|
|
113
|
+
else if (v === "false") value = false;
|
|
114
|
+
else if (v.startsWith("[") && v.endsWith("]")) value = v.slice(1, -1).split(",").map((s) => s.trim().replace(/^"|"$/g, "")).filter(Boolean);
|
|
115
|
+
else if (!Number.isNaN(Number(v))) value = Number(v);
|
|
116
|
+
else value = v;
|
|
117
|
+
if (inOptions && current.options) current.options[key] = value;
|
|
118
|
+
else if (key === "path") current.target = value;
|
|
119
|
+
else if (key === "uri") current.uri = value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (current?.target && current.uri) result.push({
|
|
123
|
+
target: current.target,
|
|
124
|
+
uri: current.uri,
|
|
125
|
+
...current.options ? { options: current.options } : {}
|
|
126
|
+
});
|
|
127
|
+
return result;
|
|
128
|
+
} catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
stringifyMountsToml(overrides) {
|
|
133
|
+
return overrides.map((o) => {
|
|
134
|
+
let block = `[[mounts]]\npath = "${o.target}"\nuri = "${o.uri}"`;
|
|
135
|
+
if (o.options && Object.keys(o.options).length > 0) {
|
|
136
|
+
block += "\n\n[mounts.options]";
|
|
137
|
+
for (const [k, v] of Object.entries(o.options)) if (typeof v === "string") block += `\n${k} = "${v}"`;
|
|
138
|
+
else if (typeof v === "number" || typeof v === "boolean") block += `\n${k} = ${v}`;
|
|
139
|
+
else if (Array.isArray(v)) block += `\n${k} = [${v.map((i) => `"${i}"`).join(", ")}]`;
|
|
140
|
+
}
|
|
141
|
+
return block;
|
|
142
|
+
}).join("\n\n");
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
exports.DIDSpaceBlockletDataBackend = DIDSpaceBlockletDataBackend;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DIDSpaceLike as DIDSpaceLike$1 } from "./did-space-like.cjs";
|
|
2
|
+
import { AFS, BlockletDataBackend, MountOverride } from "@aigne/afs";
|
|
3
|
+
|
|
4
|
+
//#region src/data-backend.d.ts
|
|
5
|
+
interface DIDSpaceBlockletDataBackendOptions {
|
|
6
|
+
didSpace: DIDSpaceLike$1;
|
|
7
|
+
}
|
|
8
|
+
declare class DIDSpaceBlockletDataBackend implements BlockletDataBackend {
|
|
9
|
+
private readonly didSpace;
|
|
10
|
+
constructor(options: DIDSpaceBlockletDataBackendOptions);
|
|
11
|
+
/** Resolve the system-role instance space (content tree, mounted at `/`). */
|
|
12
|
+
private instanceSpace;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the runtime-internal config fragment root (`/`), or null if the
|
|
15
|
+
* backend has none. /files-prefix-removal: config moved out of the role-AFS
|
|
16
|
+
* into a sibling fragment, reached via `getConfigSpace` instead of the old
|
|
17
|
+
* `system/config` sub-mount.
|
|
18
|
+
*/
|
|
19
|
+
private configSpace;
|
|
20
|
+
getInstanceAFS(blockletDid: string): Promise<AFS>;
|
|
21
|
+
removeData(blockletDid: string): Promise<void>;
|
|
22
|
+
readMountOverrides(blockletDid: string): Promise<MountOverride[]>;
|
|
23
|
+
writeMountOverrides(blockletDid: string, overrides: MountOverride[]): Promise<void>;
|
|
24
|
+
removeMountOverrides(blockletDid: string, targets: string[]): Promise<void>;
|
|
25
|
+
private parseMountsToml;
|
|
26
|
+
private stringifyMountsToml;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { DIDSpaceBlockletDataBackend, DIDSpaceBlockletDataBackendOptions };
|
|
30
|
+
//# sourceMappingURL=data-backend.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-backend.d.cts","names":[],"sources":["../src/data-backend.ts"],"mappings":";;;;UAYiB,kCAAA;EACf,QAAA,EAAU,cAAA;AAAA;AAAA,cAGC,2BAAA,YAAuC,mBAAA;EAAA,iBACjC,QAAA;cAEL,OAAA,EAAS,kCAAA;EA6BkB;EAAA,QAxB/B,aAAA;EA8CuC;;;;;;EAAA,QAjCjC,WAAA;EAOR,cAAA,CAAe,WAAA,WAAsB,OAAA,CAAQ,GAAA;EAI7C,UAAA,CAAW,WAAA,WAAsB,OAAA;EAsBjC,kBAAA,CAAmB,WAAA,WAAsB,OAAA,CAAQ,aAAA;EAgBjD,mBAAA,CAAoB,WAAA,UAAqB,SAAA,EAAW,aAAA,KAAkB,OAAA;EAuBtE,oBAAA,CAAqB,WAAA,UAAqB,OAAA,aAAoB,OAAA;EAAA,QAyB5D,eAAA;EAAA,QAmEA,mBAAA;AAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DIDSpaceLike as DIDSpaceLike$1 } from "./did-space-like.mjs";
|
|
2
|
+
import { AFS, BlockletDataBackend, MountOverride } from "@aigne/afs";
|
|
3
|
+
|
|
4
|
+
//#region src/data-backend.d.ts
|
|
5
|
+
interface DIDSpaceBlockletDataBackendOptions {
|
|
6
|
+
didSpace: DIDSpaceLike$1;
|
|
7
|
+
}
|
|
8
|
+
declare class DIDSpaceBlockletDataBackend implements BlockletDataBackend {
|
|
9
|
+
private readonly didSpace;
|
|
10
|
+
constructor(options: DIDSpaceBlockletDataBackendOptions);
|
|
11
|
+
/** Resolve the system-role instance space (content tree, mounted at `/`). */
|
|
12
|
+
private instanceSpace;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the runtime-internal config fragment root (`/`), or null if the
|
|
15
|
+
* backend has none. /files-prefix-removal: config moved out of the role-AFS
|
|
16
|
+
* into a sibling fragment, reached via `getConfigSpace` instead of the old
|
|
17
|
+
* `system/config` sub-mount.
|
|
18
|
+
*/
|
|
19
|
+
private configSpace;
|
|
20
|
+
getInstanceAFS(blockletDid: string): Promise<AFS>;
|
|
21
|
+
removeData(blockletDid: string): Promise<void>;
|
|
22
|
+
readMountOverrides(blockletDid: string): Promise<MountOverride[]>;
|
|
23
|
+
writeMountOverrides(blockletDid: string, overrides: MountOverride[]): Promise<void>;
|
|
24
|
+
removeMountOverrides(blockletDid: string, targets: string[]): Promise<void>;
|
|
25
|
+
private parseMountsToml;
|
|
26
|
+
private stringifyMountsToml;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { DIDSpaceBlockletDataBackend, DIDSpaceBlockletDataBackendOptions };
|
|
30
|
+
//# sourceMappingURL=data-backend.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-backend.d.mts","names":[],"sources":["../src/data-backend.ts"],"mappings":";;;;UAYiB,kCAAA;EACf,QAAA,EAAU,cAAA;AAAA;AAAA,cAGC,2BAAA,YAAuC,mBAAA;EAAA,iBACjC,QAAA;cAEL,OAAA,EAAS,kCAAA;EA6BkB;EAAA,QAxB/B,aAAA;EA8CuC;;;;;;EAAA,QAjCjC,WAAA;EAOR,cAAA,CAAe,WAAA,WAAsB,OAAA,CAAQ,GAAA;EAI7C,UAAA,CAAW,WAAA,WAAsB,OAAA;EAsBjC,kBAAA,CAAmB,WAAA,WAAsB,OAAA,CAAQ,aAAA;EAgBjD,mBAAA,CAAoB,WAAA,UAAqB,SAAA,EAAW,aAAA,KAAkB,OAAA;EAuBtE,oBAAA,CAAqB,WAAA,UAAqB,OAAA,aAAoB,OAAA;EAAA,QAyB5D,eAAA;EAAA,QAmEA,mBAAA;AAAA"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { normalizeId } from "./did-utils.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/data-backend.ts
|
|
4
|
+
var DIDSpaceBlockletDataBackend = class {
|
|
5
|
+
didSpace;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.didSpace = options.didSpace;
|
|
8
|
+
}
|
|
9
|
+
/** Resolve the system-role instance space (content tree, mounted at `/`). */
|
|
10
|
+
instanceSpace(blockletDid) {
|
|
11
|
+
return this.didSpace.getInstanceSpace({
|
|
12
|
+
instanceDid: normalizeId(blockletDid),
|
|
13
|
+
role: "system"
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the runtime-internal config fragment root (`/`), or null if the
|
|
18
|
+
* backend has none. /files-prefix-removal: config moved out of the role-AFS
|
|
19
|
+
* into a sibling fragment, reached via `getConfigSpace` instead of the old
|
|
20
|
+
* `system/config` sub-mount.
|
|
21
|
+
*/
|
|
22
|
+
async configSpace(blockletDid) {
|
|
23
|
+
if (typeof this.didSpace.getConfigSpace !== "function") return null;
|
|
24
|
+
return this.didSpace.getConfigSpace(normalizeId(blockletDid));
|
|
25
|
+
}
|
|
26
|
+
async getInstanceAFS(blockletDid) {
|
|
27
|
+
return this.instanceSpace(blockletDid);
|
|
28
|
+
}
|
|
29
|
+
async removeData(blockletDid) {
|
|
30
|
+
const appAFS = await this.instanceSpace(blockletDid);
|
|
31
|
+
try {
|
|
32
|
+
const files = (await appAFS.list("/", { maxDepth: 99 })).data.filter((e) => e.meta?.childrenCount === void 0 || e.meta.childrenCount === null);
|
|
33
|
+
for (const file of files) try {
|
|
34
|
+
await appAFS.delete(file.path);
|
|
35
|
+
} catch {}
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
async readMountOverrides(blockletDid) {
|
|
39
|
+
const cfg = await this.configSpace(blockletDid);
|
|
40
|
+
if (!cfg) return [];
|
|
41
|
+
const configPath = "/mounts.toml";
|
|
42
|
+
try {
|
|
43
|
+
const result = await cfg.read(configPath);
|
|
44
|
+
const content = String(result.data?.content ?? "");
|
|
45
|
+
if (!content.trim()) return [];
|
|
46
|
+
return this.parseMountsToml(content);
|
|
47
|
+
} catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async writeMountOverrides(blockletDid, overrides) {
|
|
52
|
+
if (overrides.length === 0) return;
|
|
53
|
+
const cfg = await this.configSpace(blockletDid);
|
|
54
|
+
if (!cfg) return;
|
|
55
|
+
const configPath = "/mounts.toml";
|
|
56
|
+
const existing = await this.readMountOverrides(blockletDid);
|
|
57
|
+
const byTarget = new Map(existing.map((o) => [o.target, o]));
|
|
58
|
+
for (const override of overrides) {
|
|
59
|
+
const entry = {
|
|
60
|
+
target: override.target,
|
|
61
|
+
uri: override.uri
|
|
62
|
+
};
|
|
63
|
+
if (override.options && Object.keys(override.options).length > 0) entry.options = { ...override.options };
|
|
64
|
+
byTarget.set(override.target, entry);
|
|
65
|
+
}
|
|
66
|
+
const content = this.stringifyMountsToml([...byTarget.values()]);
|
|
67
|
+
await cfg.write(configPath, { content });
|
|
68
|
+
}
|
|
69
|
+
async removeMountOverrides(blockletDid, targets) {
|
|
70
|
+
if (targets.length === 0) return;
|
|
71
|
+
const targetSet = new Set(targets);
|
|
72
|
+
const remaining = (await this.readMountOverrides(blockletDid)).filter((override) => !targetSet.has(override.target));
|
|
73
|
+
const cfg = await this.configSpace(blockletDid);
|
|
74
|
+
if (!cfg) return;
|
|
75
|
+
const configPath = "/mounts.toml";
|
|
76
|
+
if (remaining.length === 0) try {
|
|
77
|
+
await cfg.delete(configPath);
|
|
78
|
+
return;
|
|
79
|
+
} catch {}
|
|
80
|
+
await cfg.write(configPath, { content: this.stringifyMountsToml(remaining) });
|
|
81
|
+
}
|
|
82
|
+
parseMountsToml(content) {
|
|
83
|
+
try {
|
|
84
|
+
const result = [];
|
|
85
|
+
const lines = content.split("\n");
|
|
86
|
+
let current = null;
|
|
87
|
+
let inOptions = false;
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
const trimmed = line.trim();
|
|
90
|
+
if (trimmed === "[[mounts]]") {
|
|
91
|
+
if (current?.target && current.uri) result.push({
|
|
92
|
+
target: current.target,
|
|
93
|
+
uri: current.uri,
|
|
94
|
+
...current.options ? { options: current.options } : {}
|
|
95
|
+
});
|
|
96
|
+
current = {};
|
|
97
|
+
inOptions = false;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!current) continue;
|
|
101
|
+
if (trimmed === "[mounts.options]") {
|
|
102
|
+
inOptions = true;
|
|
103
|
+
if (!current.options) current.options = {};
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const match = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
|
107
|
+
if (match) {
|
|
108
|
+
const [, key, rawVal] = match;
|
|
109
|
+
let value;
|
|
110
|
+
const v = rawVal.trim();
|
|
111
|
+
if (v.startsWith("\"") && v.endsWith("\"")) value = v.slice(1, -1);
|
|
112
|
+
else if (v === "true") value = true;
|
|
113
|
+
else if (v === "false") value = false;
|
|
114
|
+
else if (v.startsWith("[") && v.endsWith("]")) value = v.slice(1, -1).split(",").map((s) => s.trim().replace(/^"|"$/g, "")).filter(Boolean);
|
|
115
|
+
else if (!Number.isNaN(Number(v))) value = Number(v);
|
|
116
|
+
else value = v;
|
|
117
|
+
if (inOptions && current.options) current.options[key] = value;
|
|
118
|
+
else if (key === "path") current.target = value;
|
|
119
|
+
else if (key === "uri") current.uri = value;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (current?.target && current.uri) result.push({
|
|
123
|
+
target: current.target,
|
|
124
|
+
uri: current.uri,
|
|
125
|
+
...current.options ? { options: current.options } : {}
|
|
126
|
+
});
|
|
127
|
+
return result;
|
|
128
|
+
} catch {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
stringifyMountsToml(overrides) {
|
|
133
|
+
return overrides.map((o) => {
|
|
134
|
+
let block = `[[mounts]]\npath = "${o.target}"\nuri = "${o.uri}"`;
|
|
135
|
+
if (o.options && Object.keys(o.options).length > 0) {
|
|
136
|
+
block += "\n\n[mounts.options]";
|
|
137
|
+
for (const [k, v] of Object.entries(o.options)) if (typeof v === "string") block += `\n${k} = "${v}"`;
|
|
138
|
+
else if (typeof v === "number" || typeof v === "boolean") block += `\n${k} = ${v}`;
|
|
139
|
+
else if (Array.isArray(v)) block += `\n${k} = [${v.map((i) => `"${i}"`).join(", ")}]`;
|
|
140
|
+
}
|
|
141
|
+
return block;
|
|
142
|
+
}).join("\n\n");
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
export { DIDSpaceBlockletDataBackend };
|
|
148
|
+
//# sourceMappingURL=data-backend.mjs.map
|