160 lines
8.8 KiB
Markdown
160 lines
8.8 KiB
Markdown
# @suaveplan/drive-uri - URI plugins (drive://, drive+mesh://, drive+template://) for @suaveplan/storage-uri's StorageUriResolver. Wraps a caller-supplied Drive instance in a StorageLike adapter so URI-addressed drive content resolves through the same dispatcher as storage adapters.
|
|
|
|
URI plugins (`drive://`, `drive+mesh://`, `drive+template://`) for
|
|
`@suaveplan/storage-uri`'s `StorageUriResolver`. Wraps a caller-supplied
|
|
`Drive` instance in a `StorageLike` adapter so URI-addressed drive content
|
|
resolves through the same dispatcher as `file://`, `https://`, `s3://`, and
|
|
the other storage adapters — without re-implementing chunk decoding, peer
|
|
discovery, or template validation. The drive itself remains the single
|
|
source of truth for those concerns; `drive-uri` is a routing layer.
|
|
|
|
<!-- begin:auto-docs -->
|
|
## Documentation tree
|
|
|
|
- [adapter](./src/adapter/drive-storage-like.md) — Defines `DriveStorageLikeAdapter`, the internal shim that exposes a `Drive` instance as a `StorageLike` so it can be re…
|
|
- [drive-storage-like](./src/adapter/drive-storage-like.md) — Defines `DriveStorageLikeAdapter`, the internal shim that exposes a `Drive` instance as a `StorageLike` so it can be re…
|
|
- [conformance](./src/conformance/conformance.md) — Re-exports the canonical URI-plugin conformance harness from `@suaveplan/storage-uri` (`runUriPluginConformance`, `UriP…
|
|
- [conformance](./src/conformance/conformance.md) — Re-exports the canonical URI-plugin conformance harness from `@suaveplan/storage-uri` (`runUriPluginConformance`, `UriP…
|
|
- [error-codes](./src/error-codes/error-codes.md) — Declares the seven `DRIVE_URI.*` error codes raised by `@suaveplan/drive-uri` plugins and the `DriveUriError` discrimin…
|
|
- [error-codes](./src/error-codes/error-codes.md) — Declares the seven `DRIVE_URI.*` error codes raised by `@suaveplan/drive-uri` plugins and the `DriveUriError` discrimin…
|
|
- [otel](./src/otel/otel.md) — OpenTelemetry instrumentation surface for `@suaveplan/drive-uri`. Exposes a meter named `@suaveplan/drive-uri` carrying…
|
|
- [otel](./src/otel/otel.md) — OpenTelemetry instrumentation surface for `@suaveplan/drive-uri`. Exposes a meter named `@suaveplan/drive-uri` carrying…
|
|
- [drive](./src/plugins/drive/drive.md) — `driveUriPlugin(options)` is a factory returning a `UriPlugin` registered against the `drive://` scheme. It wraps a con…
|
|
- [drive](./src/plugins/drive/drive.md) — `driveUriPlugin(options)` is a factory returning a `UriPlugin` registered against the `drive://` scheme. It wraps a con…
|
|
- [drive.streams](./src/plugins/drive/drive.streams.md) — `InstrumentedDriveAdapter` is the OTel-instrumented `StorageLike` shared by all three URI plugins (`drive://`, `drive+m…
|
|
- [drive-mesh](./src/plugins/drive-mesh/drive-mesh.md) — `driveMeshUriPlugin(options)` is a factory returning a `UriPlugin` registered against the `drive+mesh://` scheme. It pa…
|
|
- [drive-mesh](./src/plugins/drive-mesh/drive-mesh.md) — `driveMeshUriPlugin(options)` is a factory returning a `UriPlugin` registered against the `drive+mesh://` scheme. It pa…
|
|
- [drive-template](./src/plugins/drive-template/drive-template.md) — `driveTemplateUriPlugin(options)` is a factory returning a `UriPlugin` registered against the `drive+template://` schem…
|
|
- [drive-template](./src/plugins/drive-template/drive-template.md) — `driveTemplateUriPlugin(options)` is a factory returning a `UriPlugin` registered against the `drive+template://` schem…
|
|
<!-- end:auto-docs -->
|
|
|
|
## Install
|
|
|
|
```bash
|
|
bun add @suaveplan/drive-uri @suaveplan/storage-uri @suaveplan/drive
|
|
```
|
|
|
|
## Quick start
|
|
|
|
```ts
|
|
import { Drive } from "@suaveplan/drive";
|
|
import { createStorageUriResolver } from "@suaveplan/storage-uri";
|
|
import {
|
|
driveMeshUriPlugin,
|
|
driveTemplateUriPlugin,
|
|
driveUriPlugin,
|
|
} from "@suaveplan/drive-uri";
|
|
|
|
const localDrive = new Drive(localStorage);
|
|
await localDrive.initialize();
|
|
|
|
const meshDrive = await buildMeshConfiguredDrive(); // your wiring
|
|
|
|
const resolver = createStorageUriResolver([
|
|
driveUriPlugin({ drive: localDrive, driveId: "primary" }),
|
|
driveMeshUriPlugin({ drive: meshDrive, defaultSwarmId: "swarmA" }),
|
|
driveTemplateUriPlugin({ drive: meshDrive, templateProvider }),
|
|
]);
|
|
|
|
// Three different schemes, one resolver, one dispatch flow.
|
|
await resolver.read("drive://primary/folder/file.bin");
|
|
await resolver.open("drive+mesh://swarmA/raw/clip.mov");
|
|
await resolver.read("drive+template://templA/manifest/clip.jpg");
|
|
```
|
|
|
|
The three plugins share zero state by default — but you can pass the same
|
|
`Drive` to two factories so a hydration triggered by the template plugin
|
|
populates the file-location index that the mesh plugin then uses to fetch
|
|
chunks. That composition is the canonical pattern for "open a template,
|
|
then stream its files".
|
|
|
|
## Composition with storage-uri plugins
|
|
|
|
`drive-uri` plugins coexist with every other URI plugin. A single
|
|
`StorageUriResolver` can carry a mix of schemes without any code-path
|
|
divergence — each URI dispatches to the correct adapter based on its scheme
|
|
prefix, and no plugin observes another plugin's state.
|
|
|
|
```ts
|
|
const resolver = createStorageUriResolver([
|
|
fileUriPlugin({ rootDir: "/var/media" }),
|
|
httpsUriPlugin(httpStorage),
|
|
s3UriPlugin({ bucket: "uploads", region: "eu-west-1" }),
|
|
driveUriPlugin({ drive: localDrive, driveId: "primary" }),
|
|
driveMeshUriPlugin({ drive: meshDrive }),
|
|
driveTemplateUriPlugin({ drive: meshDrive, templateProvider }),
|
|
]);
|
|
|
|
const bytes = await resolver.read("drive://primary/folder/file.bin");
|
|
const headers = await resolver.read("https://cdn.example/img.png");
|
|
```
|
|
|
|
## Authoring a `TemplateProvider`
|
|
|
|
```ts
|
|
import type { TemplateProvider } from "@suaveplan/drive-uri";
|
|
import type { DriveTemplate } from "@suaveplan/drive-template";
|
|
|
|
export const httpTemplateProvider: TemplateProvider = {
|
|
async fetchTemplate(templateId: string, signal?: AbortSignal): Promise<DriveTemplate> {
|
|
const res = await fetch(`/api/templates/${templateId}`, { signal });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
return (await res.json()) as DriveTemplate;
|
|
},
|
|
};
|
|
```
|
|
|
|
The provider is invoked exactly once per `templateId` per plugin instance —
|
|
the hydration cache short-circuits subsequent lookups. On a transient
|
|
network failure, the cache entry is removed so a retry is possible.
|
|
|
|
## Errors
|
|
|
|
Every drive-uri failure surfaces as `DriveUriError` with a `DRIVE_URI.*`
|
|
code:
|
|
|
|
| Code | Meaning |
|
|
|---|---|
|
|
| `DRIVE_URI.INVALID_URI` | URI is malformed or has an unexpected scheme |
|
|
| `DRIVE_URI.DRIVE_ID_MISMATCH` | URI's driveId differs from plugin's configured driveId |
|
|
| `DRIVE_URI.TEMPLATE_NOT_AVAILABLE` | `templateProvider.fetchTemplate` rejected |
|
|
| `DRIVE_URI.TEMPLATE_INVALID` | `Drive.hydrateFrom` rejected (template validation failed) |
|
|
| `DRIVE_URI.WRITE_UNSUPPORTED` | Initial release rejects every write |
|
|
| `DRIVE_URI.CAPABILITY_MISSING` | Required option (drive, templateProvider) missing |
|
|
| `DRIVE_URI.ABORTED` | `AbortSignal` was already aborted at call time |
|
|
|
|
Wrapping rules are documented in the design doc — `templateProvider`
|
|
rejection becomes `TEMPLATE_NOT_AVAILABLE`; `Drive.hydrateFrom` rejection
|
|
(any `DTP_*` code) becomes `TEMPLATE_INVALID`. The original cause is
|
|
preserved on `err.cause` so operators can drill down to the underlying drive
|
|
failure.
|
|
|
|
## Observability
|
|
|
|
The package emits to the `@suaveplan/drive-uri` meter and tracer:
|
|
|
|
- `drive_uri.op_duration_ms{op, scheme, outcome}` — duration histogram
|
|
- `drive_uri.reads_total{scheme, outcome}`,
|
|
`drive_uri.writes_total{scheme, outcome}`,
|
|
`drive_uri.deletes_total{scheme, outcome}` — counters
|
|
- `drive_uri.errors_total{scheme, code}` — error attribution
|
|
- `drive_uri.template_hydrations_total{outcome}` — increments exactly once
|
|
per first-use template
|
|
|
|
Span attributes: `drive.uri` (query-stripped), `drive.uri.scheme`, and one of
|
|
`drive.uri.drive_id` / `drive.uri.swarm_id` / `drive.uri.template_id`
|
|
depending on the scheme.
|
|
|
|
## Modules
|
|
|
|
- [`src/error-codes/error-codes.md`](src/error-codes/error-codes.md) — `DRIVE_URI_CODES` + `DriveUriError`
|
|
- [`src/adapter/drive-storage-like.md`](src/adapter/drive-storage-like.md) — `DriveStorageLikeAdapter` + `isDriveNotFound`
|
|
- [`src/plugins/drive/drive.md`](src/plugins/drive/drive.md) — `driveUriPlugin`
|
|
- [`src/plugins/drive/drive.streams.md`](src/plugins/drive/drive.streams.md) — `InstrumentedDriveAdapter`
|
|
- [`src/plugins/drive-mesh/drive-mesh.md`](src/plugins/drive-mesh/drive-mesh.md) — `driveMeshUriPlugin`
|
|
- [`src/plugins/drive-template/drive-template.md`](src/plugins/drive-template/drive-template.md) — `driveTemplateUriPlugin`
|
|
- [`src/types/plugin-options.types.md`](src/types/plugin-options.types.md) — option-bag interfaces + `TemplateProvider`
|
|
- [`src/otel/otel.md`](src/otel/otel.md) — OpenTelemetry surface
|
|
- [`src/conformance/conformance.md`](src/conformance/conformance.md) — re-export of `runUriPluginConformance`
|
|
|