0fab4ce17d
Phase 0 of the delta-sync plan. Re-uploading a file the user already has (another device, a restore, a duplicate) used to transfer every byte just for the server to discard them as a dedup hit. The frontend now computes the file's BLAKE3 locally and, on a hit, registers the file with a single ~150-byte metadata call. Server — POST /api/files/by-hash: - All checks live in the application service per the AuthZ rule: Create permission on the target folder via the authorization engine, hash ownership via the existing user-scoped query (a non-owned hash returns 404 — same shape as "no such blob" — and emits an instant_upload.rejected audit event), quota on the logical size. - On success: one ref_count bump + the existing save_file_with_blob row registration (compensation included); is_new_blob=false so lifecycle hooks skip thumbnail regeneration. ~10 ms warm. - The storage-usage service is now built before the application services and injected, instead of only living on AppState. Client — WASM BLAKE3 + worker: - wasm/oxicloud-hash: the exact same blake3 crate the server uses, compiled with WASM SIMD128 (~660 MB/s measured) so browser hashes match server content addresses bit for bit. Built by scripts/build-wasm.sh; the artifacts (45 KB wasm + 8 KB glue) are vendored like pdf.js — no npm dependencies, no wasm toolchain needed for regular builds. - static/js/workers/hashWorker.js streams the File in 8 MiB slices off the main thread (constant RAM at any file size). - features/files/instantUpload.js orchestrates: threshold (8 MiB — below it the round-trips cost more than the bytes), user-scoped /api/dedup/check, by-hash registration, and silent fallback to the normal byte upload on any miss, race or unsupported environment. Wired into both uploadFiles and uploadFolderEntries. - biome.json vendors exclusion fixed to cover nested directories (previous vendors were .mjs and never matched the *.js include). Verified end-to-end against PostgreSQL 16: node-driven WASM hash equals the server's content_hash for a 20 MB file; by-hash returns 201 in ~10 ms warm with a 151-byte request (vs 20,971,873 bytes for the byte upload); the copy downloads byte-identical and the manifest ref_count goes 1→2; a second user probing the same hash gets exists:false and 404 plus the audit line; duplicate name → 409, malformed hash → 400; worker and wasm are served with correct MIME (application/wasm). https://claude.ai/code/session_01WdNenpnujNR2sc32XVvwfS
64 lines
1.5 KiB
JSON
64 lines
1.5 KiB
JSON
{
|
|
"assist": {
|
|
"actions": {
|
|
"source": {
|
|
"organizeImports": "on"
|
|
}
|
|
}
|
|
},
|
|
"files": {
|
|
"includes": ["static/**/*.js", "static/**/*.css", "static/**/*.json", "!static/js/vendors"]
|
|
},
|
|
"formatter": {
|
|
"enabled": true,
|
|
"indentStyle": "space",
|
|
"indentWidth": 4,
|
|
"lineWidth": 160,
|
|
"lineEnding": "lf"
|
|
},
|
|
"linter": {
|
|
"enabled": true,
|
|
"rules": {
|
|
"recommended": true,
|
|
"correctness": {
|
|
"noUnusedVariables": "warn",
|
|
"noUndeclaredVariables": "error",
|
|
"noUnreachable": "warn",
|
|
"noUnsafeFinally": "error"
|
|
},
|
|
"nursery": {
|
|
"useExplicitType": "error"
|
|
},
|
|
"security": {
|
|
"noGlobalEval": "error"
|
|
},
|
|
"style": {
|
|
"noDescendingSpecificity": "off"
|
|
},
|
|
"complexity": {
|
|
"noImportantStyles": "off"
|
|
}
|
|
}
|
|
},
|
|
"javascript": {
|
|
"formatter": {
|
|
"indentStyle": "space",
|
|
"indentWidth": 4,
|
|
"quoteStyle": "single",
|
|
"semicolons": "always",
|
|
"trailingCommas": "none",
|
|
"bracketSameLine": false,
|
|
"bracketSpacing": true,
|
|
"operatorLinebreak": "after"
|
|
}
|
|
},
|
|
"css": {
|
|
"linter": {
|
|
"enabled": true
|
|
},
|
|
"formatter": {
|
|
"enabled": true
|
|
}
|
|
}
|
|
}
|