@needmoretruth/nmts-cli 0.17.2 → 0.34.2
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/AGENTS.md +125 -517
- package/CHANGELOG.md +104 -0
- package/README.ko.md +377 -469
- package/README.md +394 -474
- package/dist/account-proof.d.ts +29 -0
- package/dist/account-proof.js +6 -6
- package/dist/account.d.ts +18 -0
- package/dist/account.js +5 -5
- package/dist/agent-host.d.ts +71 -0
- package/dist/agent-host.js +169 -0
- package/dist/api-advice.d.ts +2 -0
- package/dist/api-advice.js +210 -0
- package/dist/api-key.d.ts +79 -0
- package/dist/api-key.js +7 -7
- package/dist/api-refusal.d.ts +14 -0
- package/dist/api-refusal.js +8 -0
- package/dist/api.d.ts +114 -0
- package/dist/api.js +59 -162
- package/dist/args.d.ts +160 -0
- package/dist/args.js +118 -30
- package/dist/artifact-about.d.ts +49 -0
- package/dist/autonomy.d.ts +39 -0
- package/dist/autonomy.js +110 -32
- package/dist/code-access.d.ts +29 -0
- package/dist/code-access.js +3 -3
- package/dist/code-vault.d.ts +38 -0
- package/dist/code-vault.js +8 -8
- package/dist/collision.d.ts +67 -0
- package/dist/collision.js +1 -1
- package/dist/commands/accept-terms.d.ts +11 -0
- package/dist/commands/accept-terms.js +106 -0
- package/dist/commands/balance.d.ts +7 -0
- package/dist/commands/balance.js +54 -1
- package/dist/commands/create-code-file.d.ts +24 -0
- package/dist/commands/create-code-file.js +93 -0
- package/dist/commands/create-link.d.ts +10 -0
- package/dist/commands/create-link.js +232 -0
- package/dist/commands/create.d.ts +20 -0
- package/dist/commands/create.js +43 -102
- package/dist/commands/credits.d.ts +9 -0
- package/dist/commands/credits.js +110 -0
- package/dist/commands/delete-account.d.ts +11 -0
- package/dist/commands/delete-account.js +63 -0
- package/dist/commands/deposit.d.ts +7 -0
- package/dist/commands/deposit.js +85 -0
- package/dist/commands/devices.d.ts +12 -0
- package/dist/commands/devices.js +144 -0
- package/dist/commands/documents.d.ts +35 -0
- package/dist/commands/documents.js +212 -0
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.js +10 -2
- package/dist/commands/erase.d.ts +13 -0
- package/dist/commands/erase.js +193 -0
- package/dist/commands/expiring.d.ts +19 -0
- package/dist/commands/extend.d.ts +33 -0
- package/dist/commands/extend.js +54 -15
- package/dist/commands/get.d.ts +18 -0
- package/dist/commands/key-manage.d.ts +11 -0
- package/dist/commands/key-manage.js +162 -0
- package/dist/commands/key.d.ts +28 -0
- package/dist/commands/key.js +249 -0
- package/dist/commands/kit.d.ts +11 -0
- package/dist/commands/kit.js +7 -7
- package/dist/commands/listfile.d.ts +13 -0
- package/dist/commands/listfile.js +12 -5
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.js +19 -19
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +1 -1
- package/dist/commands/losses.d.ts +12 -0
- package/dist/commands/losses.js +180 -0
- package/dist/commands/ls.d.ts +23 -0
- package/dist/commands/marks-dispatch.d.ts +2 -0
- package/dist/commands/marks-dispatch.js +51 -0
- package/dist/commands/marks.d.ts +46 -0
- package/dist/commands/marks.js +80 -0
- package/dist/commands/mcp.d.ts +24 -0
- package/dist/commands/mcp.js +70 -5
- package/dist/commands/mode.d.ts +8 -0
- package/dist/commands/mode.js +60 -24
- package/dist/commands/on-collision.d.ts +6 -0
- package/dist/commands/organise.d.ts +46 -0
- package/dist/commands/padding.d.ts +7 -0
- package/dist/commands/padding.js +114 -0
- package/dist/commands/public-code.d.ts +9 -0
- package/dist/commands/public-code.js +8 -8
- package/dist/commands/pull.d.ts +27 -0
- package/dist/commands/push-wallet.d.ts +19 -0
- package/dist/commands/push-wallet.js +69 -0
- package/dist/commands/push.d.ts +47 -0
- package/dist/commands/push.js +37 -7
- package/dist/commands/put-wallet.d.ts +77 -0
- package/dist/commands/put-wallet.js +316 -0
- package/dist/commands/put.d.ts +56 -0
- package/dist/commands/put.js +53 -6
- package/dist/commands/rebuild.d.ts +18 -0
- package/dist/commands/rebuild.js +108 -10
- package/dist/commands/receive.d.ts +24 -0
- package/dist/commands/recovery-dispatch.d.ts +2 -0
- package/dist/commands/recovery-dispatch.js +34 -0
- package/dist/commands/recovery-list.d.ts +11 -0
- package/dist/commands/recovery-list.js +6 -6
- package/dist/commands/recovery.d.ts +25 -0
- package/dist/commands/recovery.js +2 -2
- package/dist/commands/rollback.d.ts +9 -0
- package/dist/commands/rollback.js +118 -0
- package/dist/commands/s3.d.ts +23 -0
- package/dist/commands/s3.js +6 -6
- package/dist/commands/settings.d.ts +5 -0
- package/dist/commands/settings.js +21 -6
- package/dist/commands/share-dispatch.d.ts +2 -0
- package/dist/commands/share-dispatch.js +46 -0
- package/dist/commands/share.d.ts +14 -0
- package/dist/commands/share.js +13 -3
- package/dist/commands/shares-sent.d.ts +18 -0
- package/dist/commands/shares-sent.js +95 -0
- package/dist/commands/support.d.ts +24 -0
- package/dist/commands/support.js +321 -0
- package/dist/commands/sweep.d.ts +11 -0
- package/dist/commands/tip.d.ts +11 -0
- package/dist/commands/tip.js +86 -0
- package/dist/commands/trash.d.ts +17 -0
- package/dist/commands/trash.js +1 -1
- package/dist/commands/trial.d.ts +7 -0
- package/dist/commands/trial.js +16 -18
- package/dist/commands/unlock.d.ts +16 -0
- package/dist/commands/unlock.js +159 -0
- package/dist/commands/update.d.ts +42 -0
- package/dist/commands/update.js +1 -1
- package/dist/commands/usage.d.ts +8 -0
- package/dist/commands/verify.d.ts +18 -0
- package/dist/commands/verify.js +10 -6
- package/dist/commands/wallet-activity.d.ts +26 -0
- package/dist/commands/wallet-activity.js +117 -0
- package/dist/commands/wallet-donate.d.ts +26 -0
- package/dist/commands/wallet-donate.js +163 -0
- package/dist/commands/wallet-hall.d.ts +35 -0
- package/dist/commands/wallet-hall.js +214 -0
- package/dist/commands/wallet-send.d.ts +22 -0
- package/dist/commands/wallet-send.js +153 -0
- package/dist/commands/wallet-storage-ops.d.ts +24 -0
- package/dist/commands/wallet-storage-ops.js +215 -0
- package/dist/commands/wallet-storage.d.ts +18 -0
- package/dist/commands/wallet-storage.js +95 -0
- package/dist/commands/wallet-swap.d.ts +32 -0
- package/dist/commands/wallet-swap.js +287 -0
- package/dist/commands/wallet.d.ts +36 -0
- package/dist/commands/wallet.js +57 -15
- package/dist/commands/whoami.d.ts +10 -0
- package/dist/commands/whoami.js +24 -6
- package/dist/consent.d.ts +140 -0
- package/dist/consent.js +89 -30
- package/dist/credentials.d.ts +146 -0
- package/dist/credentials.js +10 -10
- package/dist/crypto-surface.d.ts +197 -0
- package/dist/crypto.d.ts +81 -0
- package/dist/crypto.js +13 -0
- package/dist/deposit.d.ts +25 -0
- package/dist/deposit.js +78 -0
- package/dist/download-part.d.ts +40 -0
- package/dist/download-sink.d.ts +76 -0
- package/dist/download.d.ts +79 -0
- package/dist/download.js +3 -3
- package/dist/drive-paths.d.ts +73 -0
- package/dist/environment.d.ts +62 -0
- package/dist/environment.js +29 -9
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +3 -3
- package/dist/exit.d.ts +44 -0
- package/dist/expiry.d.ts +58 -0
- package/dist/extend-budget.d.ts +35 -0
- package/dist/extend-budget.js +81 -0
- package/dist/extend-chain.d.ts +43 -0
- package/dist/extend-chain.js +40 -3
- package/dist/extend-plan.d.ts +148 -0
- package/dist/extend-plan.js +1 -1
- package/dist/gate.d.ts +18 -0
- package/dist/gate.js +100 -0
- package/dist/guards.d.ts +2 -0
- package/dist/help.d.ts +20 -0
- package/dist/help.js +199 -33
- package/dist/human-check.d.ts +23 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +49 -0
- package/dist/item-trash.d.ts +1 -0
- package/dist/kit-file.d.ts +56 -0
- package/dist/kit-file.js +10 -10
- package/dist/list-file.d.ts +88 -0
- package/dist/list-file.js +90 -7
- package/dist/list-view-find.d.ts +15 -0
- package/dist/list-view-order.d.ts +32 -0
- package/dist/main.d.ts +12 -0
- package/dist/main.js +112 -113
- package/dist/manifest-chunk-cache.d.ts +14 -0
- package/dist/manifest-chunk-cache.js +103 -0
- package/dist/manifest-chunk-flow.d.ts +81 -0
- package/dist/manifest-chunk-flow.js +307 -0
- package/dist/manifest-create.d.ts +23 -0
- package/dist/manifest-create.js +26 -30
- package/dist/manifest-write.d.ts +161 -0
- package/dist/manifest-write.js +42 -33
- package/dist/manifest.d.ts +82 -0
- package/dist/manifest.js +69 -21
- package/dist/mark-render.d.ts +26 -0
- package/dist/mcp-args.d.ts +17 -0
- package/dist/mcp-ask.d.ts +43 -0
- package/dist/mcp-ask.js +100 -0
- package/dist/mcp-tools/context.d.ts +46 -0
- package/dist/mcp-tools/credits.d.ts +3 -0
- package/dist/mcp-tools/credits.js +61 -0
- package/dist/mcp-tools/files.d.ts +3 -0
- package/dist/mcp-tools/files.js +34 -0
- package/dist/mcp-tools/organise.d.ts +3 -0
- package/dist/mcp-tools/organise.js +82 -2
- package/dist/mcp-tools/reads.d.ts +3 -0
- package/dist/mcp-tools/reads.js +132 -4
- package/dist/mcp-tools/share.d.ts +3 -0
- package/dist/mcp-tools/share.js +26 -7
- package/dist/mcp-tools/support.d.ts +3 -0
- package/dist/mcp-tools/support.js +140 -0
- package/dist/mcp-tools/tiers.d.ts +31 -0
- package/dist/mcp-tools/tiers.js +147 -0
- package/dist/mcp.d.ts +80 -0
- package/dist/mcp.js +110 -9
- package/dist/net-retry.d.ts +38 -0
- package/dist/network.d.ts +10 -0
- package/dist/notice.d.ts +7 -0
- package/dist/notice.js +7 -7
- package/dist/product.d.ts +29 -0
- package/dist/product.js +1 -1
- package/dist/progress.d.ts +37 -0
- package/dist/prompt.d.ts +31 -0
- package/dist/prompt.js +1 -1
- package/dist/rebuild-key-check.d.ts +26 -0
- package/dist/rebuild-key-check.js +90 -0
- package/dist/rebuild.d.ts +87 -0
- package/dist/rebuild.js +26 -4
- package/dist/recovery-assemble.d.ts +40 -0
- package/dist/recovery-assemble.js +1 -1
- package/dist/recovery-build.d.ts +40 -0
- package/dist/recovery-map-file.d.ts +53 -0
- package/dist/recovery-map-file.js +4 -4
- package/dist/recovery-map.d.ts +197 -0
- package/dist/recovery-release.d.ts +104 -0
- package/dist/recovery-seq.d.ts +10 -0
- package/dist/recovery-source.d.ts +50 -0
- package/dist/redact.d.ts +70 -0
- package/dist/redact.js +212 -0
- package/dist/registration.d.ts +30 -0
- package/dist/registration.js +5 -5
- package/dist/risk.d.ts +380 -0
- package/dist/risk.js +206 -0
- package/dist/run-log.d.ts +78 -0
- package/dist/run-log.js +301 -0
- package/dist/s3/listing.d.ts +47 -0
- package/dist/s3/multipart.d.ts +21 -0
- package/dist/s3/response-sink.d.ts +8 -0
- package/dist/s3/same-file.d.ts +54 -0
- package/dist/s3/server.d.ts +58 -0
- package/dist/s3/server.js +2 -2
- package/dist/s3/sigv4.d.ts +46 -0
- package/dist/s3/sigv4.js +1 -1
- package/dist/s3/staging.d.ts +10 -0
- package/dist/s3/xml.d.ts +28 -0
- package/dist/safe-path.d.ts +41 -0
- package/dist/seal.d.ts +120 -0
- package/dist/secret-reader.d.ts +28 -0
- package/dist/server.d.ts +9 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +1 -1
- package/dist/setup-questions.d.ts +7 -0
- package/dist/share.d.ts +108 -0
- package/dist/share.js +1 -1
- package/dist/shared/lib/crypto/size-padding.d.ts +55 -0
- package/dist/shared/lib/crypto/size-padding.js +5 -0
- package/dist/shared/lib/drive/manifest-chunks.d.ts +95 -0
- package/dist/shared/lib/drive/manifest-chunks.js +252 -0
- package/dist/shared/lib/drive/manifest-codec.d.ts +214 -0
- package/dist/shared/lib/drive/manifest-codec.js +7 -7
- package/dist/shared/lib/drive/manifest-index.d.ts +98 -0
- package/dist/shared/lib/drive/manifest-ops.d.ts +167 -0
- package/dist/shared/lib/drive/manifest-ops.js +9 -4
- package/dist/shared/lib/drive/manifest-pack.d.ts +65 -0
- package/dist/shared/lib/drive/manifest-pack.js +287 -0
- package/dist/shared/lib/drive/manifest-settings.d.ts +113 -0
- package/dist/shared/lib/drive/manifest-settings.js +87 -6
- package/dist/shared/lib/drive/name-conflict.d.ts +76 -0
- package/dist/shared/lib/drive/rebuild-verify.d.ts +73 -0
- package/dist/shared/lib/drive/rebuild-verify.js +112 -0
- package/dist/shared/lib/drive/unique-name.d.ts +9 -0
- package/dist/shared/lib/drive/zstd.d.ts +27 -0
- package/dist/shared/lib/drive/zstd.js +74 -0
- package/dist/shared/lib/extend/epochs.d.ts +205 -0
- package/dist/shared/lib/net/retry-budget.d.ts +79 -0
- package/dist/shared/lib/share/shared-file-info.d.ts +21 -0
- package/dist/shared/lib/storage-control/chain.d.ts +57 -0
- package/dist/shared/lib/storage-control/chain.js +82 -0
- package/dist/shared/lib/storage-control/plan.d.ts +40 -0
- package/dist/shared/lib/storage-control/plan.js +72 -0
- package/dist/shared/lib/storage-control/reuse.d.ts +72 -0
- package/dist/shared/lib/storage-control/reuse.js +95 -0
- package/dist/shared/lib/storage-network.d.ts +25 -0
- package/dist/shared/lib/upload/part-plan.d.ts +24 -0
- package/dist/shared/lib/wallet/activity.d.ts +138 -0
- package/dist/shared/lib/wallet/activity.js +208 -0
- package/dist/shared/lib/wallet/send-rules.d.ts +68 -0
- package/dist/shared/lib/wallet/send-rules.js +116 -0
- package/dist/shared/lib/wallet/swap-rules.d.ts +115 -0
- package/dist/shared/lib/wallet/swap-rules.js +199 -0
- package/dist/shared/lib/wallet/tip.d.ts +14 -0
- package/dist/shared/lib/wallet/tip.js +34 -0
- package/dist/shared/lib/wallet/venue-ids.d.ts +23 -0
- package/dist/shared/lib/wallet/venue-ids.js +53 -0
- package/dist/standing-tip.d.ts +24 -0
- package/dist/standing-tip.js +52 -0
- package/dist/stdout.d.ts +38 -0
- package/dist/storage-control-chain.d.ts +38 -0
- package/dist/storage-control-chain.js +78 -0
- package/dist/sui-rpc.d.ts +15 -0
- package/dist/sui-rpc.js +59 -0
- package/dist/support-attach.d.ts +24 -0
- package/dist/support-attach.js +72 -0
- package/dist/support-copy.d.ts +42 -0
- package/dist/support-copy.js +109 -0
- package/dist/support-wire.d.ts +49 -0
- package/dist/support-wire.js +194 -0
- package/dist/trash-sweep.d.ts +55 -0
- package/dist/units.d.ts +2 -0
- package/dist/update-check.d.ts +50 -0
- package/dist/update-source.d.ts +63 -0
- package/dist/upload-api.d.ts +3 -0
- package/dist/upload-file.d.ts +97 -0
- package/dist/upload-file.js +3 -2
- package/dist/upload-price.d.ts +50 -0
- package/dist/upload-steps.d.ts +26 -0
- package/dist/upload-steps.js +29 -9
- package/dist/upload-store.d.ts +182 -0
- package/dist/upload-wallet-chain.d.ts +54 -0
- package/dist/upload-wallet-chain.js +203 -0
- package/dist/upload-wallet-plan.d.ts +128 -0
- package/dist/upload-wallet-plan.js +184 -0
- package/dist/upload-wallet.d.ts +24 -0
- package/dist/upload-wallet.js +208 -0
- package/dist/upload-wire.d.ts +244 -0
- package/dist/upload.d.ts +18 -0
- package/dist/upload.js +10 -0
- package/dist/usage-report.d.ts +35 -0
- package/dist/wallet-activity-chain.d.ts +5 -0
- package/dist/wallet-activity-chain.js +31 -0
- package/dist/wallet-chain.d.ts +19 -0
- package/dist/wallet-chain.js +2 -2
- package/dist/wallet-grant.d.ts +50 -0
- package/dist/wallet-grant.js +218 -0
- package/dist/wallet-send-chain.d.ts +24 -0
- package/dist/wallet-send-chain.js +49 -0
- package/dist/wallet-sign.d.ts +133 -0
- package/dist/wallet-sign.js +317 -0
- package/dist/wallet-storage-chain.d.ts +3 -0
- package/dist/wallet-storage-chain.js +20 -0
- package/dist/wallet-swap-chain.d.ts +52 -0
- package/dist/wallet-swap-chain.js +186 -0
- package/dist/wallet-swap-quote.d.ts +22 -0
- package/dist/wallet-swap-quote.js +126 -0
- package/dist/wallet.d.ts +135 -0
- package/dist/wallet.js +5 -5
- package/dist/walrus-write.d.ts +56 -0
- package/dist/walrus-write.js +5 -4
- package/dist/walrus.d.ts +86 -0
- package/dist/walrus.js +48 -10
- package/dist/zstd-node.d.ts +11 -0
- package/dist/zstd-node.js +71 -0
- package/docs/commands/accept-terms.md +15 -0
- package/docs/commands/create.md +17 -0
- package/docs/commands/credits.md +34 -0
- package/docs/commands/delete-account.md +14 -0
- package/docs/commands/deposit.md +27 -0
- package/docs/commands/devices.md +9 -0
- package/docs/commands/documents.md +10 -0
- package/docs/commands/env.md +21 -0
- package/docs/commands/extend.md +27 -0
- package/docs/commands/folders.md +10 -0
- package/docs/commands/get.md +14 -0
- package/docs/commands/key.md +14 -0
- package/docs/commands/login.md +23 -0
- package/docs/commands/logout.md +11 -0
- package/docs/commands/losses.md +13 -0
- package/docs/commands/ls.md +16 -0
- package/docs/commands/marks.md +9 -0
- package/docs/commands/mcp.md +33 -0
- package/docs/commands/mode.md +29 -0
- package/docs/commands/on-collision.md +13 -0
- package/docs/commands/padding.md +13 -0
- package/docs/commands/public-code.md +9 -0
- package/docs/commands/pull.md +9 -0
- package/docs/commands/push.md +12 -0
- package/docs/commands/put.md +36 -0
- package/docs/commands/rebuild.md +13 -0
- package/docs/commands/recovery.md +18 -0
- package/docs/commands/s3.md +17 -0
- package/docs/commands/share.md +15 -0
- package/docs/commands/support.md +16 -0
- package/docs/commands/tip.md +21 -0
- package/docs/commands/trash.md +25 -0
- package/docs/commands/trial.md +12 -0
- package/docs/commands/unlock.md +25 -0
- package/docs/commands/update.md +14 -0
- package/docs/commands/usage.md +14 -0
- package/docs/commands/verify.md +18 -0
- package/docs/commands/wallet.md +67 -0
- package/docs/commands/whoami.md +12 -0
- package/package.json +67 -8
- package/server.json +28 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm.d.ts +23 -2
- package/vendor/nmts-crypto/nmts_crypto_wasm.js +44 -2
- package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm +0 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm.d.ts +1 -0
- package/vendor/nmts-crypto/package.json +1 -1
- package/dist/commands/consent.js +0 -77
- package/dist/extend-sign.js +0 -130
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// The CHUNKED file list — NCF-3 §6.3, format version 2.
|
|
2
|
+
// ⚠ PUBLISHED — copied byte-for-byte into the `nmts` command-line package; keep comments
|
|
3
|
+
// self-contained English.
|
|
4
|
+
//
|
|
5
|
+
// The index stays where the single sealed list was; the entries move into immutable chunks of at
|
|
6
|
+
// most 4 MiB sealed bytes, each named by the hash of its transport string, so an edit rewrites one
|
|
7
|
+
// chunk and the index instead of the whole list. This module is ONLY the byte format — entries ⇄
|
|
8
|
+
// the plaintext that gets sealed, for both halves. It performs no I/O, holds no key and never
|
|
9
|
+
// touches the network, exactly like `manifest-codec.ts` beside it, and for the same reason: the
|
|
10
|
+
// format has to be unit-testable without a browser and written down in one place.
|
|
11
|
+
//
|
|
12
|
+
// WHY TWO LABELS. The index and a chunk are the same shape of bytes to the server, so a chunk
|
|
13
|
+
// handed back where an index was asked for has to fail at the AEAD rather than at the parser.
|
|
14
|
+
// That is what `AAD_FILE_LIST_CHUNK` buys, and it is the whole reason the constant exists.
|
|
15
|
+
//
|
|
16
|
+
// WHAT THE INDEX PINS. The index is authenticated (envelope), continued (`p`), and it names every
|
|
17
|
+
// chunk by hash. A server that swaps, drops, duplicates or rolls a chunk back produces bytes
|
|
18
|
+
// whose hash is not the one the index named. So every check §6.1 makes on the one blob now
|
|
19
|
+
// covers the whole list — see `manifest-chunk-flow.ts`, which is where the checking happens.
|
|
20
|
+
import { decodeManifest, FLAG_GZIP, FLAG_RAW, fromWire, gunzip, gzip, manifestFingerprint, ManifestFormatError, toWire, withFlag, } from "./manifest-codec.js";
|
|
21
|
+
import { settingsFromWire, settingsToWire } from "./manifest-settings.js";
|
|
22
|
+
import { zstdCodec, zstdContentSize, ZSTD_LEVEL } from "./zstd.js";
|
|
23
|
+
/**
|
|
24
|
+
* Envelope AAD for ONE CHUNK of the chunked file list (NCF-3 §2, §6.3). Its own label, distinct
|
|
25
|
+
* from `AAD_FILE_LIST`, so a chunk can never be presented as an index or an index as a chunk —
|
|
26
|
+
* the server holds both as opaque bytes of the same shape.
|
|
27
|
+
*/
|
|
28
|
+
export const AAD_FILE_LIST_CHUNK = "nmts/v3/file-list-chunk";
|
|
29
|
+
/** The version an index and its chunks carry. Version 1 is the single blob `manifest-codec` reads. */
|
|
30
|
+
export const FILE_LIST_VERSION_CHUNKED = 2;
|
|
31
|
+
/** Compression flag 0x02 — zstd (NCF-3 §6.3.4). 0x00 and 0x01 are `manifest-codec`'s. */
|
|
32
|
+
export const FLAG_ZSTD = 0x02;
|
|
33
|
+
/**
|
|
34
|
+
* The plaintext bound for ONE CHUNK (NCF-3 §6.3.3).
|
|
35
|
+
*
|
|
36
|
+
* On the PLAINTEXT and not on the sealed size, so a writer can decide where to close a chunk
|
|
37
|
+
* before it compresses or seals anything: 3,900,000 bytes plus the flag byte plus the 72-byte
|
|
38
|
+
* envelope is under the server's 4 MiB ceiling by construction, and a compressed chunk is smaller
|
|
39
|
+
* still. The unused remainder is left unused — an entry is never split across two chunks.
|
|
40
|
+
*/
|
|
41
|
+
export const CHUNK_PLAIN_MAX = 3_900_000;
|
|
42
|
+
/**
|
|
43
|
+
* The plaintext bound for the INDEX.
|
|
44
|
+
*
|
|
45
|
+
* The index sits where the single blob sat, so it inherits that ceiling (16 MiB sealed · §8.291).
|
|
46
|
+
* It holds one ~110-byte row per chunk plus the account settings, so nothing an account can do
|
|
47
|
+
* brings it near this; the number exists so a reader has a bound to check a compressed frame
|
|
48
|
+
* against instead of trusting what the frame claims about itself.
|
|
49
|
+
*/
|
|
50
|
+
export const INDEX_PLAIN_MAX = 15_000_000;
|
|
51
|
+
/**
|
|
52
|
+
* A chunk's name. The SAME function the parent link uses, deliberately: both commit to the
|
|
53
|
+
* base64url transport string rather than the bytes it encodes, because the string is what travels
|
|
54
|
+
* and the encoding is canonical (fixed alphabet, no padding, one string per byte sequence).
|
|
55
|
+
*/
|
|
56
|
+
export const chunkFingerprint = manifestFingerprint;
|
|
57
|
+
// ------------------------------------------------------------------------------ compression
|
|
58
|
+
/**
|
|
59
|
+
* JSON → the plaintext to seal: `flag || payload` (NCF-3 §6.3.4).
|
|
60
|
+
*
|
|
61
|
+
* zstd when this build has an encoder, the platform's gzip when it does not, raw when it has
|
|
62
|
+
* neither. All three are readable by every version-2 reader, so the choice costs nobody anything.
|
|
63
|
+
*/
|
|
64
|
+
async function pack(json) {
|
|
65
|
+
const codec = zstdCodec();
|
|
66
|
+
if (codec) {
|
|
67
|
+
try {
|
|
68
|
+
return withFlag(FLAG_ZSTD, await codec.compress(json, ZSTD_LEVEL));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// An encoder that will not run must not cost somebody their save: gzip below writes a
|
|
72
|
+
// document every reader can open, and the only thing lost is ~9 % of the bytes.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const gz = await gzip(json);
|
|
76
|
+
return gz ? withFlag(FLAG_GZIP, gz) : withFlag(FLAG_RAW, json);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The sealed plaintext → the JSON bytes inside it.
|
|
80
|
+
*
|
|
81
|
+
* `maxOut` is the caller's bound (a chunk's or the index's). A zstd frame declares what it expands
|
|
82
|
+
* to, so a frame that claims more than the bound is refused HERE — before anything is allocated
|
|
83
|
+
* for it. Every refusal is a `ManifestFormatError` that says which one it was; none of them is
|
|
84
|
+
* ever an empty drive (§6.1's rule).
|
|
85
|
+
*/
|
|
86
|
+
async function unpack(body, maxOut) {
|
|
87
|
+
if (body.length < 1)
|
|
88
|
+
throw new ManifestFormatError("empty file list body");
|
|
89
|
+
const flag = body[0];
|
|
90
|
+
const rest = body.subarray(1);
|
|
91
|
+
if (flag === FLAG_RAW)
|
|
92
|
+
return rest;
|
|
93
|
+
if (flag === FLAG_GZIP) {
|
|
94
|
+
const out = await gunzip(rest);
|
|
95
|
+
if (!out)
|
|
96
|
+
throw new ManifestFormatError("the file list is gzipped and this platform cannot expand it");
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
if (flag === FLAG_ZSTD) {
|
|
100
|
+
const codec = zstdCodec();
|
|
101
|
+
if (!codec) {
|
|
102
|
+
throw new ManifestFormatError("the file list is zstd-compressed and this build has no zstd decoder");
|
|
103
|
+
}
|
|
104
|
+
const declared = zstdContentSize(rest);
|
|
105
|
+
if (declared === null) {
|
|
106
|
+
throw new ManifestFormatError("the zstd frame does not declare its size, so it cannot be bounded");
|
|
107
|
+
}
|
|
108
|
+
if (declared > maxOut) {
|
|
109
|
+
throw new ManifestFormatError(`the zstd frame claims ${declared} bytes, over the ${maxOut} bound`);
|
|
110
|
+
}
|
|
111
|
+
return codec.decompress(rest, maxOut);
|
|
112
|
+
}
|
|
113
|
+
throw new ManifestFormatError(`unknown file list compression flag ${String(flag)}`);
|
|
114
|
+
}
|
|
115
|
+
/** The parsed JSON object inside a sealed plaintext, or a `ManifestFormatError` saying why not. */
|
|
116
|
+
async function parse(body, maxOut) {
|
|
117
|
+
const json = await unpack(body, maxOut);
|
|
118
|
+
let parsed;
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(json));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
throw new ManifestFormatError("file list body is not valid JSON");
|
|
124
|
+
}
|
|
125
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
126
|
+
throw new ManifestFormatError("file list body is not an object");
|
|
127
|
+
}
|
|
128
|
+
return { ...parsed };
|
|
129
|
+
}
|
|
130
|
+
/** The sealed version this document carries — `1` or `2`, or a refusal naming what it said. */
|
|
131
|
+
function versionOf(doc) {
|
|
132
|
+
const v = doc.v;
|
|
133
|
+
if (typeof v !== "number")
|
|
134
|
+
throw new ManifestFormatError(`file list has no version (v=${String(v)})`);
|
|
135
|
+
return v;
|
|
136
|
+
}
|
|
137
|
+
/** The sealed store version, checked the way §6.1 requires: present, positive, and linked. */
|
|
138
|
+
function seqOf(doc, what) {
|
|
139
|
+
const seq = doc.seq;
|
|
140
|
+
if (typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 1) {
|
|
141
|
+
throw new ManifestFormatError(`${what} has no sealed version (seq=${String(seq)})`);
|
|
142
|
+
}
|
|
143
|
+
return seq;
|
|
144
|
+
}
|
|
145
|
+
// ------------------------------------------------------------------------------ the index
|
|
146
|
+
/** The index → the plaintext to seal under `AAD_FILE_LIST`. */
|
|
147
|
+
export async function encodeIndex(index) {
|
|
148
|
+
if (!Number.isSafeInteger(index.seq) || index.seq < 1) {
|
|
149
|
+
throw new ManifestFormatError(`file list seq must be a positive integer, got ${index.seq}`);
|
|
150
|
+
}
|
|
151
|
+
// Version 1 has nothing before it; every later version must name what it continued from, or the
|
|
152
|
+
// fork check has a hole exactly where a fork would be introduced (§6.1 check 3).
|
|
153
|
+
if (index.seq > 1 && !index.p) {
|
|
154
|
+
throw new ManifestFormatError(`file list seq ${index.seq} must name the version it was built on`);
|
|
155
|
+
}
|
|
156
|
+
const st = settingsToWire(index.settings);
|
|
157
|
+
const wire = {
|
|
158
|
+
v: FILE_LIST_VERSION_CHUNKED,
|
|
159
|
+
seq: index.seq,
|
|
160
|
+
...(index.p ? { p: index.p } : {}),
|
|
161
|
+
...(st ? { settings: st } : {}),
|
|
162
|
+
chunks: index.chunks.map((c) => ({ h: c.h, n: c.n, f: c.f, l: c.l })),
|
|
163
|
+
};
|
|
164
|
+
return pack(new TextEncoder().encode(JSON.stringify(wire)));
|
|
165
|
+
}
|
|
166
|
+
/** Sealed plaintext → the index. Throws `ManifestFormatError` on anything it cannot read. */
|
|
167
|
+
export async function decodeIndex(body) {
|
|
168
|
+
return indexFrom(await parse(body, INDEX_PLAIN_MAX));
|
|
169
|
+
}
|
|
170
|
+
function indexFrom(doc) {
|
|
171
|
+
const v = versionOf(doc);
|
|
172
|
+
if (v !== FILE_LIST_VERSION_CHUNKED) {
|
|
173
|
+
throw new ManifestFormatError(`unsupported file list version ${v}`);
|
|
174
|
+
}
|
|
175
|
+
const seq = seqOf(doc, "file list index");
|
|
176
|
+
const p = doc.p;
|
|
177
|
+
if (seq > 1 && typeof p !== "string") {
|
|
178
|
+
throw new ManifestFormatError(`file list index ${seq} does not say what it was built on`);
|
|
179
|
+
}
|
|
180
|
+
if (!Array.isArray(doc.chunks))
|
|
181
|
+
throw new ManifestFormatError("file list index names no chunks");
|
|
182
|
+
const chunks = [];
|
|
183
|
+
for (const raw of doc.chunks) {
|
|
184
|
+
// Every field is load-bearing: the hash IS the pin, and a row missing one of them would leave
|
|
185
|
+
// a chunk that could not be checked. Refusing the index is the only honest answer.
|
|
186
|
+
if (!raw || typeof raw !== "object")
|
|
187
|
+
throw new ManifestFormatError("file list index has a broken chunk row");
|
|
188
|
+
const row = { ...raw };
|
|
189
|
+
const { h, n, f, l } = row;
|
|
190
|
+
if (typeof h !== "string" || h === "")
|
|
191
|
+
throw new ManifestFormatError("a chunk row has no name");
|
|
192
|
+
if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 0) {
|
|
193
|
+
throw new ManifestFormatError(`chunk ${h} does not say how many entries it holds`);
|
|
194
|
+
}
|
|
195
|
+
if (typeof f !== "string" || typeof l !== "string") {
|
|
196
|
+
throw new ManifestFormatError(`chunk ${h} has no placement range`);
|
|
197
|
+
}
|
|
198
|
+
chunks.push({ h, n, f, l });
|
|
199
|
+
}
|
|
200
|
+
const settings = settingsFromWire(doc.settings);
|
|
201
|
+
return {
|
|
202
|
+
v: FILE_LIST_VERSION_CHUNKED,
|
|
203
|
+
seq,
|
|
204
|
+
...(typeof p === "string" ? { p } : {}),
|
|
205
|
+
...(settings ? { settings } : {}),
|
|
206
|
+
chunks,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// ------------------------------------------------------------------------------ one chunk
|
|
210
|
+
/** A chunk → the plaintext to seal under `AAD_FILE_LIST_CHUNK`. */
|
|
211
|
+
export async function encodeChunk(doc) {
|
|
212
|
+
if (!Number.isSafeInteger(doc.seq) || doc.seq < 1) {
|
|
213
|
+
throw new ManifestFormatError(`chunk seq must be a positive integer, got ${doc.seq}`);
|
|
214
|
+
}
|
|
215
|
+
const wire = {
|
|
216
|
+
v: FILE_LIST_VERSION_CHUNKED,
|
|
217
|
+
seq: doc.seq,
|
|
218
|
+
items: doc.items.map(toWire),
|
|
219
|
+
};
|
|
220
|
+
return pack(new TextEncoder().encode(JSON.stringify(wire)));
|
|
221
|
+
}
|
|
222
|
+
/** Sealed chunk plaintext → its entries. */
|
|
223
|
+
export async function decodeChunk(body) {
|
|
224
|
+
const doc = await parse(body, CHUNK_PLAIN_MAX);
|
|
225
|
+
const v = versionOf(doc);
|
|
226
|
+
if (v !== FILE_LIST_VERSION_CHUNKED)
|
|
227
|
+
throw new ManifestFormatError(`unsupported chunk version ${v}`);
|
|
228
|
+
const seq = seqOf(doc, "chunk");
|
|
229
|
+
if (!Array.isArray(doc.items))
|
|
230
|
+
throw new ManifestFormatError("chunk has no item list");
|
|
231
|
+
return { v: FILE_LIST_VERSION_CHUNKED, seq, items: doc.items.map(fromWire) };
|
|
232
|
+
}
|
|
233
|
+
// ------------------------------------------------------------------------------ either version
|
|
234
|
+
/**
|
|
235
|
+
* The reader's one door: sealed plaintext → whichever version it turned out to be.
|
|
236
|
+
*
|
|
237
|
+
* ⚠ A version-1 body is inflated twice — once here to read `v`, once by `decodeManifest`. That is
|
|
238
|
+
* deliberate rather than a duplicate validator: version 1's rules live in one place and stay
|
|
239
|
+
* there. The cost is bounded and self-extinguishing, because the first save after a version-1 load
|
|
240
|
+
* converts the account (§6.3.6) and no later read takes this branch.
|
|
241
|
+
*/
|
|
242
|
+
export async function decodeFileList(body) {
|
|
243
|
+
const doc = await parse(body, INDEX_PLAIN_MAX);
|
|
244
|
+
const v = versionOf(doc);
|
|
245
|
+
if (v === FILE_LIST_VERSION_CHUNKED)
|
|
246
|
+
return { v: FILE_LIST_VERSION_CHUNKED, index: indexFrom(doc) };
|
|
247
|
+
if (v === 1)
|
|
248
|
+
return { v: 1, manifest: await decodeManifest(body) };
|
|
249
|
+
// An unknown version means another device wrote a format this build predates. Refusing is the
|
|
250
|
+
// safe answer, and §6.1 says the caller must never render the refusal as an empty drive.
|
|
251
|
+
throw new ManifestFormatError(`unsupported file list version ${v}`);
|
|
252
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { TEXT_SCALE_DEFAULT_PCT, TEXT_SCALE_MAX_PCT, TEXT_SCALE_MIN_PCT, type AccountSettings } from "./manifest-settings.ts";
|
|
2
|
+
export { TEXT_SCALE_DEFAULT_PCT, TEXT_SCALE_MAX_PCT, TEXT_SCALE_MIN_PCT };
|
|
3
|
+
export type { AccountSettings };
|
|
4
|
+
/**
|
|
5
|
+
* One share this device made, kept where the server cannot reach it (`ManifestEntry.shares`).
|
|
6
|
+
*
|
|
7
|
+
* A receipt is written only AFTER the server's created row was checked to carry the very address
|
|
8
|
+
* the sender typed — so what is stored is the address she asked for, never one the server chose.
|
|
9
|
+
*/
|
|
10
|
+
export interface ShareReceipt {
|
|
11
|
+
/** Recipient address in WIRE form: exactly what the create call was checked against. */
|
|
12
|
+
address: string;
|
|
13
|
+
/** When this device wrote the receipt, ms since the Unix epoch. This browser's clock. */
|
|
14
|
+
at: number;
|
|
15
|
+
/**
|
|
16
|
+
* A revoke was sent for this receipt and the listing has not yet come back without the row.
|
|
17
|
+
*
|
|
18
|
+
* The receipt outlives the revoke ON PURPOSE: a revoke this side cannot verify is exactly the
|
|
19
|
+
* case worth keeping, and a listing that still carries the address is the only evidence the
|
|
20
|
+
* removal did not happen. Dropped once a listing no longer names it (`sharePrune`), which is
|
|
21
|
+
* what keeps this array from growing forever.
|
|
22
|
+
*/
|
|
23
|
+
revoked?: true;
|
|
24
|
+
}
|
|
25
|
+
/** One entry — a file or a folder — as the rest of the app sees it. */
|
|
26
|
+
export interface ManifestEntry {
|
|
27
|
+
/** Item id. Files: the id the server assigned at commit. Folders: client-generated. */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Parent folder id, or null for the drive root. */
|
|
30
|
+
parentId: string | null;
|
|
31
|
+
/** 0 folder · 1 file (the same numeric codes the items API uses). */
|
|
32
|
+
kind: number;
|
|
33
|
+
/** Plaintext name. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Plaintext size in bytes; 0 for folders. */
|
|
36
|
+
size: number;
|
|
37
|
+
/** Created, ms since the Unix epoch (UTC). */
|
|
38
|
+
createdAt: number;
|
|
39
|
+
/** Last modified, ms since epoch. */
|
|
40
|
+
updatedAt: number;
|
|
41
|
+
/** In the trash since this instant; absent = live. */
|
|
42
|
+
deletedAt?: number;
|
|
43
|
+
/** Wrapped file DEK (base64url NCF-3 §3 envelope, 104 bytes), files only. Carried verbatim. */
|
|
44
|
+
dekWrapped?: string;
|
|
45
|
+
/** Sealed content hash (base64url NCF-3 §3 envelope, 104 bytes), files only. Carried verbatim. */
|
|
46
|
+
contentHashCt?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Which storage network holds this file's bytes (`lib/storage-network.ts` codes), files only.
|
|
49
|
+
*
|
|
50
|
+
* ABSENT MEANS WALRUS — a fact, not a default: no other network has an upload path, so every
|
|
51
|
+
* entry written before this field is on Walrus by construction. Writing the field only when it
|
|
52
|
+
* is NOT Walrus keeps the common case free, which matters in a blob that is rewritten whole on
|
|
53
|
+
* every change and re-downloaded on every cold start.
|
|
54
|
+
*
|
|
55
|
+
* Here rather than read from the server per file because the drive list is built from THIS
|
|
56
|
+
* document alone — the server's item rows carry no placement, and a per-file round trip to
|
|
57
|
+
* learn where each one lives is what NMF-1 exists to avoid.
|
|
58
|
+
*
|
|
59
|
+
* ⚠ DISPLAY, NOT TRUTH. `file_parts.network` on the server is what recovery and the lifecycle
|
|
60
|
+
* sweep read. A stale tab that saves can drop this mark the same way it can drop a star (see
|
|
61
|
+
* MANIFEST_FORMAT_VERSION), which would show the wrong tier until the list is rebuilt — bad,
|
|
62
|
+
* but not lost bytes.
|
|
63
|
+
*/
|
|
64
|
+
network?: number;
|
|
65
|
+
/** Starred by the person: shows in Favourites wherever the file actually lives. Absent = not. */
|
|
66
|
+
favorite?: true;
|
|
67
|
+
/** Kept at the top of its own folder listing. Absent = not. Independent of `favorite`. */
|
|
68
|
+
pinned?: true;
|
|
69
|
+
/**
|
|
70
|
+
* MY OWN RECORD of who this file was shared with.
|
|
71
|
+
*
|
|
72
|
+
* WHY IT IS HERE. The recipients list on screen is the SERVER'S record: a row it joined through
|
|
73
|
+
* `shares.recipient_id`. A server that hides a row makes the sender believe she never shared;
|
|
74
|
+
* one that answers "deleted" without deleting makes her believe she took access back. Neither is
|
|
75
|
+
* catchable from a list only the server can produce. This is the one place the server cannot
|
|
76
|
+
* edit — the list is sealed under the account's own key — so a receipt written here is the only
|
|
77
|
+
* thing that can be held up against what the server says.
|
|
78
|
+
*
|
|
79
|
+
* ⚠ WHAT IT DOES NOT DO. A receipt proves the SHARE WAS MADE; it cannot revoke one, and it
|
|
80
|
+
* cannot make an adversarial server stop serving the ciphertext. It also cannot say a row with
|
|
81
|
+
* no receipt is fabricated: shares made before this field existed have none, and a build that
|
|
82
|
+
* does not read this field rewrites the list without it (the same limit every mark here has).
|
|
83
|
+
* That asymmetry is why only two mismatches are ever shown to the person, and neither of them
|
|
84
|
+
* is "the server invented a row" — see `share-receipts.ts`.
|
|
85
|
+
*
|
|
86
|
+
* Absent = this device has recorded nothing for this file, which is NOT "shared with nobody".
|
|
87
|
+
*/
|
|
88
|
+
shares?: ShareReceipt[];
|
|
89
|
+
/**
|
|
90
|
+
* Labels — the person's own labels for this item, stored as the label TEXT.
|
|
91
|
+
*
|
|
92
|
+
* There is deliberately no registry of labels: a label exists exactly as long as some item wears
|
|
93
|
+
* it. That is what keeps every edit replayable onto a list this device has not seen (two devices
|
|
94
|
+
* inventing the same label converge instead of colliding on an id), and it means a label can
|
|
95
|
+
* never outlive its last file as an empty row nobody can explain. Renaming one is a sweep across
|
|
96
|
+
* the entries that carry it (manifest-ops.ts `labelRename`).
|
|
97
|
+
*
|
|
98
|
+
* The text is plaintext INSIDE the sealed blob — the same protection the file names get, and the
|
|
99
|
+
* reason labels are not in the URL: a query string would hand the label to the server and the CDN
|
|
100
|
+
* log in the clear — the server is never handed a per-account value it could use as a handle.
|
|
101
|
+
*/
|
|
102
|
+
labels?: string[];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Account-level settings that live INSIDE the sealed list.
|
|
106
|
+
*
|
|
107
|
+
* Here and not in a server column because the server must not learn them: which accounts run
|
|
108
|
+
* developer mode, or read at what size, is exactly the sort of per-account profile it must never
|
|
109
|
+
* be handed. Here and not in device storage because they are account-level — one switch, every
|
|
110
|
+
* signed-in device.
|
|
111
|
+
*
|
|
112
|
+
* EVERY FIELD DEFAULTS BY ABSENCE, like the entry marks above it. ⚠ The honest limit is the same
|
|
113
|
+
* as a star's: an older build that SAVES rewrites the blob without the member it never read, so a
|
|
114
|
+
* stale tab can silently reset these. Turning them back on is the whole repair.
|
|
115
|
+
*/
|
|
116
|
+
/** The decoded manifest. */
|
|
117
|
+
export interface Manifest {
|
|
118
|
+
/** Format version — NMF-1 is `1`. Read this FIRST when handling a future format. */
|
|
119
|
+
v: number;
|
|
120
|
+
/**
|
|
121
|
+
* The store version this blob was SEALED at (NCF-3 §6.1, defect A3).
|
|
122
|
+
*
|
|
123
|
+
* The server also keeps this number in a column, and before NCF-3 that column was the only
|
|
124
|
+
* copy — so the server could hand back an older `(seq, ct)` pair and the client had no way to
|
|
125
|
+
* tell. Deleted files reappeared, recent uploads vanished, a rename undid itself. Sealing the
|
|
126
|
+
* number means the version and the contents are authenticated together.
|
|
127
|
+
*/
|
|
128
|
+
seq: number;
|
|
129
|
+
/**
|
|
130
|
+
* SHA-256 of the SEALED bytes this list was built from, base64url. Absent only for version 1.
|
|
131
|
+
*
|
|
132
|
+
* The version check alone narrows rollback; it does not close it. A server can pin a device that
|
|
133
|
+
* is merely *behind* by answering with the version it last saw, and it can show two devices two
|
|
134
|
+
* different forks indefinitely — "not lower than the highest I have seen" is satisfied by
|
|
135
|
+
* standing still. A link to the previous blob makes a fork visible to the NEXT reader on ANY
|
|
136
|
+
* device, because a list whose `prev` is not the blob that device actually opened cannot be a
|
|
137
|
+
* continuation of it.
|
|
138
|
+
*/
|
|
139
|
+
prev?: string;
|
|
140
|
+
entries: ManifestEntry[];
|
|
141
|
+
/** Account-level settings, absent when nothing was ever set. */
|
|
142
|
+
settings?: AccountSettings;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Current format version this build writes.
|
|
146
|
+
*
|
|
147
|
+
* STAYS 1 WHEN OPTIONAL FIELDS ARE ADDED (favorite · pinned · labels · account settings landed
|
|
148
|
+
* this way). The reader
|
|
149
|
+
* below refuses any version it does not know, so bumping would lock every already-open tab out of
|
|
150
|
+
* the drive to buy nothing: an older build ignores fields it has never heard of and renders the
|
|
151
|
+
* list correctly. The cost of that choice is real and bounded — an older build that SAVES rewrites
|
|
152
|
+
* the entries without the marks it dropped, so a stale tab can clear stars and labels. Bump only for
|
|
153
|
+
* a change an old build would MISREAD, not one it would merely not show.
|
|
154
|
+
*/
|
|
155
|
+
export declare const MANIFEST_FORMAT_VERSION = 1;
|
|
156
|
+
/** base64url SHA-256 of a sealed blob — the value a later list carries as its `prev`. */
|
|
157
|
+
export declare function manifestFingerprint(ct: string): Promise<string>;
|
|
158
|
+
export declare const FLAG_RAW = 0;
|
|
159
|
+
export declare const FLAG_GZIP = 1;
|
|
160
|
+
/** The compact per-entry shape actually stored. */
|
|
161
|
+
export interface WireEntry {
|
|
162
|
+
i: string;
|
|
163
|
+
p: string | null;
|
|
164
|
+
k: number;
|
|
165
|
+
n: string;
|
|
166
|
+
s: number;
|
|
167
|
+
c: number;
|
|
168
|
+
u: number;
|
|
169
|
+
d?: number;
|
|
170
|
+
w?: string;
|
|
171
|
+
h?: string;
|
|
172
|
+
f?: 1;
|
|
173
|
+
pn?: 1;
|
|
174
|
+
l?: string[];
|
|
175
|
+
sn?: number;
|
|
176
|
+
sh?: WireShareReceipt[];
|
|
177
|
+
}
|
|
178
|
+
/** One share receipt on the wire. Same short-key reason as the entry above it. */
|
|
179
|
+
interface WireShareReceipt {
|
|
180
|
+
/** address. */
|
|
181
|
+
a: string;
|
|
182
|
+
/** at. */
|
|
183
|
+
t: number;
|
|
184
|
+
/** revoked. */
|
|
185
|
+
r?: 1;
|
|
186
|
+
}
|
|
187
|
+
export declare function toWire(e: ManifestEntry): WireEntry;
|
|
188
|
+
export declare function fromWire(w: WireEntry): ManifestEntry;
|
|
189
|
+
/** Thrown when the plaintext is not a manifest this build can read. */
|
|
190
|
+
export declare class ManifestFormatError extends Error {
|
|
191
|
+
constructor(message: string);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Entries → the plaintext to seal.
|
|
195
|
+
*
|
|
196
|
+
* Compresses when the platform offers `CompressionStream`, which is the normal case and roughly
|
|
197
|
+
* halves the blob (names and repeated JSON keys compress well). When it does not, the raw form is
|
|
198
|
+
* written instead of failing: an older browser must still be able to save its drive.
|
|
199
|
+
*/
|
|
200
|
+
export declare function encodeManifest(entries: readonly ManifestEntry[], seq: number, prev?: string, settings?: AccountSettings): Promise<Uint8Array>;
|
|
201
|
+
/**
|
|
202
|
+
* Sealed plaintext → entries.
|
|
203
|
+
*
|
|
204
|
+
* Throws `ManifestFormatError` on anything it cannot read — including a version from the future.
|
|
205
|
+
* Callers must NOT treat a throw as "the drive is empty": it is the signal to try the retained
|
|
206
|
+
* previous version, because rendering an empty drive invites the user to re-upload everything.
|
|
207
|
+
*/
|
|
208
|
+
export declare function decodeManifest(body: Uint8Array): Promise<Manifest>;
|
|
209
|
+
/** Prepend the compression flag byte. */
|
|
210
|
+
export declare function withFlag(flag: number, body: Uint8Array): Uint8Array;
|
|
211
|
+
/** gzip, or null when the platform has no `CompressionStream`. */
|
|
212
|
+
export declare function gzip(bytes: Uint8Array): Promise<Uint8Array | null>;
|
|
213
|
+
/** gunzip, or null when the platform has no `DecompressionStream`. */
|
|
214
|
+
export declare function gunzip(bytes: Uint8Array): Promise<Uint8Array | null>;
|
|
@@ -38,9 +38,9 @@ export async function manifestFingerprint(ct) {
|
|
|
38
38
|
bin += String.fromCharCode(b);
|
|
39
39
|
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
40
40
|
}
|
|
41
|
-
const FLAG_RAW = 0x00;
|
|
42
|
-
const FLAG_GZIP = 0x01;
|
|
43
|
-
function toWire(e) {
|
|
41
|
+
export const FLAG_RAW = 0x00;
|
|
42
|
+
export const FLAG_GZIP = 0x01;
|
|
43
|
+
export function toWire(e) {
|
|
44
44
|
const w = {
|
|
45
45
|
i: e.id,
|
|
46
46
|
p: e.parentId,
|
|
@@ -75,7 +75,7 @@ function toWire(e) {
|
|
|
75
75
|
}
|
|
76
76
|
return w;
|
|
77
77
|
}
|
|
78
|
-
function fromWire(w) {
|
|
78
|
+
export function fromWire(w) {
|
|
79
79
|
const e = {
|
|
80
80
|
id: w.i,
|
|
81
81
|
parentId: w.p ?? null,
|
|
@@ -228,21 +228,21 @@ export async function decodeManifest(body) {
|
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
230
|
/** Prepend the compression flag byte. */
|
|
231
|
-
function withFlag(flag, body) {
|
|
231
|
+
export function withFlag(flag, body) {
|
|
232
232
|
const out = new Uint8Array(body.length + 1);
|
|
233
233
|
out[0] = flag;
|
|
234
234
|
out.set(body, 1);
|
|
235
235
|
return out;
|
|
236
236
|
}
|
|
237
237
|
/** gzip, or null when the platform has no `CompressionStream`. */
|
|
238
|
-
async function gzip(bytes) {
|
|
238
|
+
export async function gzip(bytes) {
|
|
239
239
|
const C = globalThis.CompressionStream;
|
|
240
240
|
if (!C)
|
|
241
241
|
return null;
|
|
242
242
|
return collect(streamThrough(bytes, new C("gzip")));
|
|
243
243
|
}
|
|
244
244
|
/** gunzip, or null when the platform has no `DecompressionStream`. */
|
|
245
|
-
async function gunzip(bytes) {
|
|
245
|
+
export async function gunzip(bytes) {
|
|
246
246
|
const D = globalThis.DecompressionStream;
|
|
247
247
|
if (!D)
|
|
248
248
|
return null;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ManifestEntry } from "./manifest-codec.ts";
|
|
2
|
+
/** Folder items use kind 0, files kind 1 — the same numbers the items API uses. */
|
|
3
|
+
export declare const KIND_FOLDER = 0;
|
|
4
|
+
export declare const KIND_FILE = 1;
|
|
5
|
+
export interface ManifestIndex {
|
|
6
|
+
/** Every entry, in the order the list was written. */
|
|
7
|
+
readonly all: readonly ManifestEntry[];
|
|
8
|
+
readonly byId: ReadonlyMap<string, ManifestEntry>;
|
|
9
|
+
/** Direct children by parent id (root under its own key). Values keep list order. */
|
|
10
|
+
readonly childrenByParent: ReadonlyMap<string, readonly ManifestEntry[]>;
|
|
11
|
+
}
|
|
12
|
+
/** Build the lookup structures for one version of the list. Cost is linear; do it once. */
|
|
13
|
+
export declare function buildIndex(entries: readonly ManifestEntry[]): ManifestIndex;
|
|
14
|
+
/**
|
|
15
|
+
* The instant this item became trash — its own, or the nearest trashed ancestor's.
|
|
16
|
+
* `null` means live. A parent chain that is broken or looping counts as live at the point it
|
|
17
|
+
* breaks: showing an item whose parent vanished is recoverable, hiding it silently is not.
|
|
18
|
+
*/
|
|
19
|
+
export declare function trashedAt(index: ManifestIndex, entry: ManifestEntry): number | null;
|
|
20
|
+
/** Live = not trashed itself and under no trashed ancestor. */
|
|
21
|
+
export declare function isLive(index: ManifestIndex, entry: ManifestEntry): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Does the ACCOUNT hold a file that whole-account export could actually write out?
|
|
24
|
+
*
|
|
25
|
+
* Asked by the export card, which offers itself on this and nothing else. The two wrong answers it
|
|
26
|
+
* replaces are both easy to reach: counting the CURRENT LEVEL hides the card from an account whose
|
|
27
|
+
* files all sit inside folders, and counting entries of any kind offers a download to a drive of
|
|
28
|
+
* empty folders — one that can only answer "there is nothing to download".
|
|
29
|
+
*
|
|
30
|
+
* ⚠ Trashed files are excluded on purpose. They are still stored and still paid for, but export
|
|
31
|
+
* writes the live drive, so a drive whose every file is in the trash has nothing to export.
|
|
32
|
+
*/
|
|
33
|
+
export declare function hasLiveFile(index: ManifestIndex): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Live children of a folder (`null` = drive root), in list order.
|
|
36
|
+
* Sorting belongs to the view: the same folder is shown by name, size and date in different places.
|
|
37
|
+
*/
|
|
38
|
+
export declare function childrenOf(index: ManifestIndex, parentId: string | null): readonly ManifestEntry[];
|
|
39
|
+
/**
|
|
40
|
+
* What the trash view shows: items the person deleted directly, newest first.
|
|
41
|
+
* A child whose parent is also trashed is deliberately absent — it is restored with its parent.
|
|
42
|
+
*/
|
|
43
|
+
export declare function trashRoots(index: ManifestIndex): readonly ManifestEntry[];
|
|
44
|
+
/**
|
|
45
|
+
* Folder names from the root down to (and excluding) this item.
|
|
46
|
+
* An entry whose chain is broken returns what could be resolved — callers render that as a
|
|
47
|
+
* partial path rather than claiming the item sits at the root.
|
|
48
|
+
*/
|
|
49
|
+
export declare function pathOf(index: ManifestIndex, entry: ManifestEntry): readonly string[];
|
|
50
|
+
/** Every descendant of a folder, live and trashed, depth-first. The folder itself is excluded. */
|
|
51
|
+
export declare function descendantsOf(index: ManifestIndex, folderId: string): readonly ManifestEntry[];
|
|
52
|
+
/**
|
|
53
|
+
* True when `folderId` is `candidateId` or sits underneath it.
|
|
54
|
+
* Move targets are checked with this: dropping a folder into its own subtree would detach that
|
|
55
|
+
* whole branch from the root, and nothing in the UI could reach it afterwards.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isSelfOrDescendant(index: ManifestIndex, candidateId: string, folderId: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Live items whose name contains `query`, case-insensitively.
|
|
60
|
+
*
|
|
61
|
+
* This search is COMPLETE — it reads the whole list from memory. That is a change worth knowing
|
|
62
|
+
* about: the old server-backed listing could only match what had been fetched, so "no results"
|
|
63
|
+
* was a claim the UI had to hedge. Here it is simply true.
|
|
64
|
+
*/
|
|
65
|
+
export declare function searchByName(index: ManifestIndex, query: string, limit?: number): readonly ManifestEntry[];
|
|
66
|
+
/**
|
|
67
|
+
* Live files the person starred, newest first.
|
|
68
|
+
*
|
|
69
|
+
* Files only: a folder is already reachable in the panel's tree, so starring one would put the same
|
|
70
|
+
* thing in two places and make "favourites" mean two different kinds of row.
|
|
71
|
+
*/
|
|
72
|
+
export declare function favoriteFiles(index: ManifestIndex): readonly ManifestEntry[];
|
|
73
|
+
/**
|
|
74
|
+
* Every label in use, with how many live files wear it, ordered by the person's own locale.
|
|
75
|
+
*
|
|
76
|
+
* Counting here rather than in the panel matters: the count is what tells someone a label still has
|
|
77
|
+
* files in it before they rename or clear it, and it must agree with what opening it shows.
|
|
78
|
+
*/
|
|
79
|
+
export declare function labelCounts(index: ManifestIndex): {
|
|
80
|
+
label: string;
|
|
81
|
+
count: number;
|
|
82
|
+
}[];
|
|
83
|
+
/** Live files wearing one label, newest first. */
|
|
84
|
+
export declare function filesWithLabel(index: ManifestIndex, label: string): readonly ManifestEntry[];
|
|
85
|
+
export interface DriveTotals {
|
|
86
|
+
/** Live files. Folders are not counted — they hold nothing. */
|
|
87
|
+
files: number;
|
|
88
|
+
/** Live folders. */
|
|
89
|
+
folders: number;
|
|
90
|
+
/** Bytes of live files. */
|
|
91
|
+
bytes: number;
|
|
92
|
+
/** Files sitting in the trash (still stored, still paid for). */
|
|
93
|
+
trashedFiles: number;
|
|
94
|
+
/** Bytes held by trashed files — the figure that explains "deleting did not free space yet". */
|
|
95
|
+
trashedBytes: number;
|
|
96
|
+
}
|
|
97
|
+
/** Whole-drive counts. Exact, because the list is complete by construction. */
|
|
98
|
+
export declare function totalsOf(index: ManifestIndex): DriveTotals;
|