9.4 KiB
Proposal: complete-drive — fill VFS-core gaps in @suaveplan/drive
- Change ID:
complete-drive - Capability:
drive - Package:
@suaveplan/drive - Status: PROPOSED
- Design: ./design.md
- Spec delta: ./specs/drive/spec.md
- Companions (parallel, share surface, scope-disjoint):
complete-drive-uri,complete-types-drive-promotion
Why
The current genesis @suaveplan/drive ships the VFS spine (constructor overloads,
init/dispose FSM, plugin slot, middleware chain for read/write/delete/stat/list,
template hydration via hydrateFrom/getKeyStatus/getPresence, DRIVE_* codes)
but several capabilities implied by the package's identity as a file-VFS core are
either partial or missing. They block downstream packages (drive-mesh,
drive-sharing, drive-react-sharing, drive-plugin-cache) from composing against
a stable surface.
What
Audit findings (genesis vs. upstream + CLAUDE.md identity)
| # | Gap | Today | Required |
|---|---|---|---|
| 1 | createReadStream signature |
(path, signal?) positional |
overload (path, options?: { offset?; length?; signal? }) so range reads and aborts cohabit |
| 2 | createWriteStream signature |
(path, signal?) positional |
overload (path, options?: { signal? }) for parity, no behaviour drift |
| 3 | Drive.copy(src, dst, opts?) |
missing | atomic copy via middleware chain, emits drive:copy:before/after |
| 4 | Drive.move(src, dst, opts?) |
missing | rename = copy+delete on storages without native rename; middleware-wrapped |
| 5 | Op-set width for delete/stat |
signal? positional |
accept { signal? } options bundle (matches read/write) |
| 6 | Drive.list return type |
typed unknown |
typed AsyncIterable<DriveEntry> + page result; respect recursive/limit/cursor |
| 7 | Drive.watch(path, opts?) |
missing | EventBus-backed change subscription returning AsyncIterable<DriveChangeEvent> + unsubscribe() |
| 8 | Hydration progress events | callback only | also emit drive:hydrate:progress on the EventBus (delivery to multiple subscribers) |
| 9 | Middleware coverage | read/write/delete/stat/list | add copy, move, watch middleware slots so plugins can wrap every op |
| 10 | Plugin lifecycle API | install + dispose-on-drive-dispose | unloadPlugin(name), listPlugins(), findPlugin(name), ordered handle map, idempotent unload |
| 11 | Quota / usage | absent | Drive.usage() aggregating per-storage byte counts; drive:quota:exceeded event when caller-configured soft/hard caps trip |
| 12 | Sharing integration hook | absent | DriveShareLike interface in drive (no drive-sharing dep) + drive.shareHook?: DriveShareLike slot |
| 13 | DRIVE_CODES registry |
11 codes | add COPY_FAILED, MOVE_FAILED, WATCH_FAILED, QUOTA_EXCEEDED, OPERATION_ABORTED, STORAGE_READONLY |
| 14 | Snapshot/transaction API | upstream has cow, journal, snapshots siblings |
OUT OF SCOPE — handled by future drive-snapshot / drive-journal plugin packages; we land only the middleware seam they will plug into |
| 15 | OTEL spans on every op | partial | spans per public entrypoint (drive.read, drive.write, drive.copy, …) named drive.<op> with drive.storage.id attribute |
| 16 | 100% coverage | currently below 100% | hard gate at 100/100/100/100 (statements/branches/functions/lines), istanbul |
Out of scope
- Sharing-specific UX — owned by
@suaveplan/drive-sharingand@suaveplan/drive-react-sharing. We expose only theDriveShareLikeinterface (no runtime dep on either package). - Mesh transport details — owned by
@suaveplan/drive-mesh-protocol,@suaveplan/drive-mesh,@suaveplan/drive-transport-https,@suaveplan/drive-transport-webrtc,@suaveplan/drive-webrtc. We expose only the plugin slot + middleware seam they consume. - Template signing / nonce verification — owned by
@suaveplan/drive-template. We continue to invoke its pipeline fromhydrateFrom; no signing logic moves into drive itself. - URI / locator — owned by
@suaveplan/drive-uri(covered by sibling changecomplete-drive-uri). - Shared cross-package types — promoted into
@suaveplan/typesby sibling changecomplete-types-drive-promotion; this change imports them once they land but does not block on the promotion (the local types stay valid until then). - Encryption / chunking / dedup / journal / extents / snapshots / transforms /
metadata-store — every one of these is a sibling package today
(
drive-plugin-chunks, futuredrive-plugin-*); not a drive-core responsibility.
Impact
Affected dependents
Run bunx turbo ls --filter=...@suaveplan/drive to enumerate. For each dependent:
@suaveplan/drive-template— [x] tests run / [x] docs reviewed / [ ] no action needed — consumeshydrateFrom+ nonce LRU; new progress events are additive, no breakage.@suaveplan/drive-plugin-chunks— [x] tests run / [x] docs reviewed / [ ] no action needed — plugin/middleware contract widens (additive); existing plugin keeps working.@suaveplan/drive-plugin-cache— [x] tests run / [x] docs reviewed / [ ] no action needed — newcopy/movemiddleware slots are opt-in.@suaveplan/drive-mesh-protocol— [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: pure wire-protocol, does not touch Drive surface).@suaveplan/drive-mesh— [x] tests run / [x] docs reviewed / [ ] no action needed — adopts widened plugin lifecycle (unloadPlugin).@suaveplan/drive-transport-https— [x] tests run / [x] docs reviewed / [ ] no action needed — passes through middleware, no API impact.@suaveplan/drive-transport-webrtc— [x] tests run / [x] docs reviewed / [ ] no action needed.@suaveplan/drive-webrtc— [x] tests run / [x] docs reviewed / [ ] no action needed.@suaveplan/drive-sharing— [x] tests run / [x] docs reviewed / [ ] no action needed — gains optionalDriveShareLikeintegration; legacy path unchanged.@suaveplan/drive-react-sharing— [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: re-exports drive-sharing types only).
Migration
-
Old:
drive.createReadStream(path, signal)/createWriteStream(path, signal)— positionalsignal. -
New:
drive.createReadStream(path, { offset?, length?, signal? })/createWriteStream(path, { signal? })— options bundle. -
Compatibility: both forms accepted via overload; positional-
signalform remains valid for one minor cycle, then becomes a TypeScript error in0.1.0. -
Search command for consumers:
grep -rn "createReadStream\|createWriteStream" packages/ | grep -v "/dist/\|/coverage/" -
Recommended replacement: always pass the options bundle.
-
Old:
drive.delete(path, signal)/drive.stat(path, signal)positional. -
New:
drive.delete(path, { signal? })/drive.stat(path, { signal? })options bundle (positional kept as overload for one cycle). -
Search:
grep -rn "drive\.\(delete\|stat\)(" packages/ | grep -v "/dist/".
Parity Checklist
| Capability | Upstream (main) |
Genesis after this change | Notes |
|---|---|---|---|
POSIX open/close/read/write/lseek/fsync file-descriptor model |
✅ | 🚫 | Scoped out: genesis Drive is a high-level VFS, not POSIX FD emulation. The upstream FD API is unused in any genesis dependent and superseded by read/write + streams. |
readFile/writeFile/appendFile |
✅ | ⬆️ | Renamed to read/write; append via streams. |
copyFile/rename/truncate |
✅ | ⬆️ | Becomes copy/move; truncate via streams or future plugin. |
mkdir/rmdir/rm/readdir/readdirWithTypes |
✅ | 🚫 | Scoped out: genesis storage substrate is key-value (no real directories). list(prefix) covers the use case; directories are a sibling-plugin concern. |
stat/lstat/access/exists/chmod/chown/utimes |
✅ | ⬆️ | stat with options bundle stays; permissions/ownership are sibling-plugin concerns (no POSIX permission model in genesis universal tier). |
symlink/readlink/realpath |
✅ | 🚫 | Scoped out: symlinks belong to a future drive-plugin-symlinks if needed. |
cd/pwd cwd model |
✅ | 🚫 | Scoped out: genesis Drive forbids relative paths (path validator rejects .. and leading /); cwd has no meaning. |
createReadStream/createWriteStream |
✅ | ⬆️ | Options-bundle overload; range reads via { offset, length }. |
| Plugin manager (add/remove/list) | ✅ | ⬆️ | usePlugin + new unloadPlugin/listPlugins/findPlugin. |
| Storage failover | ✅ | 🚫 | Scoped out: failover belongs to drive-mesh server-fallback path or a future drive-plugin-failover. |
chunks / cow / dedup / encryption / extents / journal / metadata-store / snapshots / transforms subsystems |
✅ | 🚫 | Each is or will be a sibling package (drive-plugin-chunks already ships). VFS core only exposes the plugin/middleware seam. |
Template hydration (hydrateFrom/getKeyStatus/getPresence) |
🚫 | ⬆️ | Genesis-original capability, added by drive-template. |
| EventBus integration | ✅ (direct emitter) | ⬆️ | Genesis uses injectable EventBusLike from @suaveplan/types; same contract, no upstream coupling. |
| Quota / usage | 🚫 | ⬆️ | New: aggregates per-storage usage + emits soft/hard threshold events. |
| Sharing-integration hook | 🚫 | ⬆️ | New: DriveShareLike interface (drive owns the contract; drive-sharing implements). |