125 lines
8.4 KiB
Markdown
125 lines
8.4 KiB
Markdown
# Drive Plugins
|
|
|
|
The drive core does five things: lifecycle, path resolution, the two indexes, the middleware chain, and capability fallbacks. Everything else is a plugin.
|
|
|
|
This document is the catalogue of plugins (shipped, planned, and conventional) and how they compose.
|
|
|
|
## The plugin contract
|
|
|
|
```ts
|
|
interface DrivePlugin {
|
|
readonly name: string;
|
|
readonly hooks: Record<string, PluginHook>;
|
|
readonly priority?: number; // higher first
|
|
}
|
|
|
|
type PluginHook = (context: unknown, next: NextFunction) => Promise<unknown>;
|
|
```
|
|
|
|
Plugins register hooks against operation names — `drive:read`, `drive:write`, `drive:delete`, `drive:stat`, `drive:list`, `drive:initialize`, `drive:dispose`. Hooks run in priority order (high → low) on the request path, then the same order in reverse on the response path. A hook can:
|
|
|
|
- transform the args before delegating (`await next()`),
|
|
- transform the result after delegating,
|
|
- short-circuit by *not* calling `next()` and returning a value directly,
|
|
- throw, propagating up the chain.
|
|
|
|
`useMiddleware(mw)` is a thin sugar for a plugin whose only hook is the universal `drive:*` interceptor.
|
|
|
|
## Plugin catalogue
|
|
|
|
| Plugin | Status | Responsibility |
|
|
| --- | --- | --- |
|
|
| `chunks` (`@suaveplan/drive-plugin-chunks`) | **shipped** | Splits files into signed, encrypted chunks. Owns the `manifest`. Owns layout selection (sparse-file vs chunk-keys). |
|
|
| `sharing` (`@suaveplan/drive-sharing`) | pending — `main` | Partition-based access control. Envelope encryption. Grant/revoke/rotate. |
|
|
| `react-sharing` (`@suaveplan/drive-react-sharing`) | pending — `main` | React UI for the sharing engine — `<ShareDialog>`, `<PartitionBadge>`, `useSharing()`. |
|
|
| `mesh` (`@suaveplan/drive-mesh`) | planned (genesis-original) | Per-file chunk scheduling: which peer to ask for which chunk. Bitset advertisement. Per-peer scoring. |
|
|
| `transport-https` (`@suaveplan/drive-transport-https`) | planned (genesis-original) | HTTPS chunk transport — server fallback when WebRTC isn't available. `GET /v1/chunks/<fileId>/<chunkId>` with Range. |
|
|
| `transport-webrtc` (`@suaveplan/drive-transport-webrtc`) | planned (genesis-original) | WebRTC datachannel chunk transport — browser↔browser. `[uint32 BE frameLen][uint8 frameType][payload]` framing. |
|
|
| `webrtc` (`@suaveplan/drive-webrtc`) | pending — `main` | A pre-packaged "browser drive" plugin: chunks + transport-webrtc + signalling, in one. |
|
|
| `cache` (`@suaveplan/cache`) | shipped | Read-through cache middleware. Drop in to cache decrypted plaintext. |
|
|
| `cow` (planned) | planned | Copy-on-write versioning. Multiple `version` numbers per `fileId`. Snapshot create/restore/diff. |
|
|
| `dedup` (planned) | planned | Content-addressed dedup. Hashes content, points multiple `fileLocId`s at the same chunks. |
|
|
| `metadata` (planned, see `streams-metadata`) | planned | Side-channel metadata extraction (EXIF/MIME/audio) on every write. |
|
|
|
|
## How the chain runs end-to-end
|
|
|
|
A typical browser drive:
|
|
|
|
```ts
|
|
const drive = new Drive(opfsStorage)
|
|
.usePlugin(chunksPlugin({ chunkSize: 65536, signing: { secretKey } }))
|
|
.usePlugin(sharingPlugin({ partitions, kms }))
|
|
.usePlugin(meshPlugin({ networkNode }))
|
|
.usePlugin(transportWebrtcPlugin({ signaller }))
|
|
.useMiddleware(loggingMiddleware);
|
|
```
|
|
|
|
Registration order matters:
|
|
|
|
```text
|
|
drive.read("/photo.jpg")
|
|
│
|
|
┌────────────▼────────────┐
|
|
│ logging (pre) │
|
|
│ chunks (pre) │ load manifest
|
|
│ sharing (pre) │ unwrap envelope key
|
|
│ mesh (pre) │ prepare peer scoring
|
|
│ transport (pre) │ open peer connections
|
|
└────────────┬────────────┘
|
|
▼
|
|
storage.get
|
|
│
|
|
┌────────────▼────────────┐
|
|
│ transport (post)│ fetch missing chunks via WebRTC
|
|
│ mesh (post) │ reassemble
|
|
│ sharing (post) │ decrypt envelope
|
|
│ chunks (post) │ verify+decrypt+concat
|
|
│ logging (post) │ log duration
|
|
└────────────┬────────────┘
|
|
▼
|
|
Uint8Array
|
|
```
|
|
|
|
Each layer is independent. Strip any one and the layers above keep working — `mesh` without `transport-webrtc` falls back to `transport-https`; `sharing` without `mesh` works on a single-node drive; `chunks` without `mesh` works with no network at all.
|
|
|
|
## Cross-layer ownership boundaries
|
|
|
|
The genesis design is meticulous about which layer owns what. The `drive-network-integration` change folder ([proposal](../../openspec/changes/drive-network-integration/proposal.md)) is the canonical resolution document; the summary is:
|
|
|
|
| Concern | Owner | Why |
|
|
| --- | --- | --- |
|
|
| Peer identity & lifecycle | `network-plugin-registry` | Peers exist independent of files; one registry per mesh. |
|
|
| Per-file chunk-availability bitsets | `drive-mesh` | Specific to a file's manifest; doesn't belong in the generic registry. |
|
|
| Per-file chunk scheduling (which peer to ask, in what order) | `drive-mesh` | Highly file-specific; uses peer health from registry but decides locally. |
|
|
| Mesh-wide chunk redundancy (N copies, prefetch, integrity) | `network-plugin-content-delivery` | Cuts across files; lives at the network layer because it's about peers, not files. |
|
|
| Cross-mesh forwarding for chunks | `network-plugin-relay` | Same as message relay; a chunk request is just another envelope. |
|
|
|
|
The interaction is event-driven via `NetworkBus`:
|
|
|
|
- `peer:joined` / `peer:left` / `peer:updated` from `network-plugin-registry`.
|
|
- `cd:advertise` from `drive-mesh` (this drive has these chunks).
|
|
- `cd:request` from `drive-mesh` (this drive needs this chunk).
|
|
- `cd:response` from `network-plugin-content-delivery` (here it is).
|
|
|
|
That keeps `drive-mesh` blissfully unaware of *which* transport is fetching the chunk; it just listens for `cd:response`.
|
|
|
|
## Conventions for writing a new plugin
|
|
|
|
1. **No drive imports.** Use `DrivePlugin`, `MiddlewareHandler`, and `MiddlewareContext` from `@suaveplan/types`. Never import `Drive` itself — that creates a circular dep.
|
|
2. **Idempotent install.** A plugin's install hook may run more than once if a drive is reinitialized. Track state on the plugin instance, not in module scope.
|
|
3. **Reverse dispose.** If your plugin allocates anything (sockets, workers, watchers), free it in `drive:dispose`. The drive guarantees this hook runs in reverse-registration order, so plugins added later get to clean up first.
|
|
4. **Surface errors as `DriveError`.** Wrap thrown errors as `DriveError(DRIVE.PLUGIN_FAILED, { cause })` so consumers see one error type at the boundary.
|
|
5. **Use `@suaveplan/types` for backend-agnostic capabilities.** If you need to know what the storage can do, read `storage.capabilities`. If you need to know what the network can do, read it through `NetworkBus` capability events. Don't hardcode.
|
|
|
|
## Why this is split, not a fat library
|
|
|
|
A drive that runs in a browser without WebRTC just doesn't install the WebRTC plugin. A server drive in a single-node deployment doesn't install `mesh`. A test drive doesn't install any plugin at all and uses `MemoryStorage` directly. None of those configurations cost extra bytes or extra surface area, because the core knows nothing about chunks, peers, or partitions — it just runs whatever middleware you registered.
|
|
|
|
This is the genesis bet: composability over completeness. The `enterprise-drive` monolith design tried the other way and ended up with a 2 MB package that imports half the platform on the import line. The split keeps every drive minimal.
|
|
|
|
## Reading further
|
|
|
|
- [`encryption.md`](./encryption.md) — the chunks plugin in detail.
|
|
- [`mesh-and-sharing.md`](./mesh-and-sharing.md) — the mesh and transport plugins.
|
|
- [`../network/plugins-catalog.md`](../network/plugins-catalog.md) — the network plugins these drive plugins talk to.
|