Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,159 @@
# @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`

View File

@@ -0,0 +1,147 @@
{
"name": "@suaveplan/drive-uri",
"version": "0.1.0",
"type": "module",
"sideEffects": false,
"description": "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.",
"license": "MIT",
"author": "Suaveplan Team",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"./error-codes": {
"import": {
"types": "./dist/error-codes/index.d.ts",
"default": "./dist/error-codes/error-codes.js"
},
"require": {
"types": "./dist/error-codes/index.d.ts",
"default": "./dist/error-codes/error-codes.js"
}
},
"./adapter": {
"import": {
"types": "./dist/adapter/index.d.ts",
"default": "./dist/adapter/drive-storage-like.js"
},
"require": {
"types": "./dist/adapter/index.d.ts",
"default": "./dist/adapter/drive-storage-like.js"
}
},
"./plugins/drive": {
"import": {
"types": "./dist/plugins/drive/index.d.ts",
"default": "./dist/plugins/drive/drive.js"
},
"require": {
"types": "./dist/plugins/drive/index.d.ts",
"default": "./dist/plugins/drive/drive.js"
}
},
"./plugins/drive-mesh": {
"import": {
"types": "./dist/plugins/drive-mesh/index.d.ts",
"default": "./dist/plugins/drive-mesh/drive-mesh.js"
},
"require": {
"types": "./dist/plugins/drive-mesh/index.d.ts",
"default": "./dist/plugins/drive-mesh/drive-mesh.js"
}
},
"./plugins/drive-template": {
"import": {
"types": "./dist/plugins/drive-template/index.d.ts",
"default": "./dist/plugins/drive-template/drive-template.js"
},
"require": {
"types": "./dist/plugins/drive-template/index.d.ts",
"default": "./dist/plugins/drive-template/drive-template.js"
}
},
"./otel": {
"import": {
"types": "./dist/otel/index.d.ts",
"default": "./dist/otel/otel.js"
},
"require": {
"types": "./dist/otel/index.d.ts",
"default": "./dist/otel/otel.js"
}
},
"./conformance": {
"import": {
"types": "./dist/conformance/index.d.ts",
"default": "./dist/conformance/conformance.js"
},
"require": {
"types": "./dist/conformance/index.d.ts",
"default": "./dist/conformance/conformance.js"
}
},
"./types": {
"import": {
"types": "./dist/types/index.d.ts",
"default": "./dist/types/index.js"
},
"require": {
"types": "./dist/types/index.d.ts",
"default": "./dist/types/index.js"
}
}
},
"scripts": {
"build": "vite build",
"test": "vitest run --passWithNoTests",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage --passWithNoTests",
"lint": "bunx biome check src/",
"lint:fix": "bunx biome lint --fix --unsafe --max-diagnostics 500 src/",
"format": "bunx biome format --write --max-diagnostics 500 src/",
"check": "bunx biome check --write --unsafe --max-diagnostics 500 src/",
"typecheck": "bunx tsc --noEmit --composite false -p tsconfig.lib.json",
"docs:check": "bun run ../../../../scripts/docs-check.ts ."
},
"dependencies": {
"@opentelemetry/api": "catalog:observability",
"@suaveplan/drive": "workspace:^",
"@suaveplan/drive-template": "workspace:^",
"@suaveplan/error": "workspace:^",
"@suaveplan/storage-uri": "workspace:^",
"@suaveplan/types": "workspace:^",
"tslib": "catalog:build"
},
"devDependencies": {
"@suaveplan/storage": "workspace:^",
"@suaveplan/storage-file": "workspace:^",
"@suaveplan/testing": "workspace:^",
"@types/node": "catalog:",
"@vitest/coverage-istanbul": "catalog:testing",
"fast-check": "catalog:testing",
"typescript": "catalog:",
"vite": "catalog:build",
"vite-plugin-dts": "catalog:build",
"vitest": "catalog:testing"
},
"publishConfig": {
"registry": "https://npmjs.fq.io/",
"access": "public"
},
"files": [
"dist",
"src",
"README.md"
]
}

View File

@@ -0,0 +1,90 @@
# `@suaveplan/drive-uri/adapter`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Defines `DriveStorageLikeAdapter`, the internal shim that exposes a `Drive`
instance as a `StorageLike` so it can be registered as the `adapter` of a
`UriPlugin` consumed by `@suaveplan/storage-uri`'s `StorageUriResolver`. The
shim is the join point between the drive-tier API (which throws on missing
files) and the storage-tier contract (which returns `undefined` for missing
keys) — every other failure path is preserved unchanged so the resolver can
attach its own `STORAGE_URI.ADAPTER_ERROR` wrap.
A second export, `isDriveNotFound`, walks the `cause` chain of a thrown value
looking for any `SuaveplanError` whose `code` ends in `.NOT_FOUND`. This is
the only place that has to know about Drive's not-found vocabulary; every
plugin defers the translation to the shim.
## Features
- Implements every required member of `StorageLike` (`get`, `set`, `delete`,
`exists`, `list`, `clear`) plus the optional `createReadStream` extension
used by `StorageUriResolver.open()`'s native-stream fast path.
- Translates `Drive.read` not-found rejections into `undefined` from `get()`
so the resolver's existing `undefined → STORAGE_URI.NOT_FOUND` mapping
applies without modification.
- Rejects `clear()` with a `DriveUriError(WRITE_UNSUPPORTED)` whose message
names `Drive.delete(path)` as the supported alternative.
- Forwards `AbortSignal` to `Drive.createReadStream` so consumers can cancel
in-flight streams.
## Usage: Basic
```ts
import { DriveStorageLikeAdapter } from "@suaveplan/drive-uri/adapter";
const adapter = new DriveStorageLikeAdapter(drive);
const bytes = await adapter.get("folder/file.bin");
if (bytes === undefined) console.log("not found");
```
## Usage: Advanced
Composing the shim with a custom `UriPlugin` (this is essentially what
`driveUriPlugin` does internally):
```ts
import { DriveStorageLikeAdapter } from "@suaveplan/drive-uri/adapter";
import type { UriPlugin } from "@suaveplan/storage-uri";
function myPlugin(drive: Drive): UriPlugin {
const adapter = new DriveStorageLikeAdapter(drive);
return {
scheme: "my",
adapter,
parse(uri: string) {
const path = uri.slice("my://".length);
return { key: path };
},
};
}
```
## API Reference
- `class DriveStorageLikeAdapter implements StorageLike` — accepts a `Drive`
in its constructor. All `StorageLike` methods delegate to the bound drive.
- `DriveStorageLikeAdapter.createReadStream(key, signal?)` — forwards
directly to `Drive.createReadStream(key, signal)`.
- `function isDriveNotFound(err: unknown): boolean` — returns `true` when
`err` (or anything in its `cause` chain, up to 8 levels deep with cycle
detection) is a `SuaveplanError` whose `code` ends in `.NOT_FOUND`.
## Implementation Notes
- The shim does NOT own any chunk codec, manifest fetching, peer discovery,
or template validation — that all happens inside `Drive` via its installed
middleware (`chunksPlugin`, `drive-mesh`, etc.).
- `list()` accepts either of two shapes Drive may return (`{ keys }` vs.
`{ entries: [{ path }] }`) and normalises to the `StorageLike` shape,
preserving `continuationToken` when present.
- `clear()` is intentionally a hard error rather than a soft no-op — wiping a
Drive through the URI surface is a footgun, and a silent success would lose
data on misuse. The supported path is `resolver.remove(uri)` per file.
- `isDriveNotFound` includes cycle detection (depth-limit + self-reference
check) so a malicious or accidental self-referential `cause` chain cannot
cause an infinite loop.

View File

@@ -0,0 +1,245 @@
import { SuaveplanError } from "@suaveplan/error";
import { beforeEach, describe, expect, it } from "@suaveplan/testing/runner";
import {
asDrive,
FakeDrive,
FakeDriveError,
} from "../../tests/setup/fake-drive.js";
import { DRIVE_URI_CODES, DriveUriError } from "../error-codes/error-codes.js";
import {
DriveStorageLikeAdapter,
isDriveNotFound,
} from "./drive-storage-like.js";
class CustomNotInDriveError extends SuaveplanError<string> {}
describe("isDriveNotFound", () => {
it("matches any SuaveplanError whose code ends in .NOT_FOUND", {
timeout: 5000,
}, () => {
expect(
isDriveNotFound(new CustomNotInDriveError("DRIVE.NOT_FOUND", "x"))
).toBe(true);
expect(
isDriveNotFound(
new CustomNotInDriveError("SOMETHING_ELSE.NOT_FOUND", "x")
)
).toBe(true);
});
it("returns false for non-SuaveplanError values", { timeout: 5000 }, () => {
expect(isDriveNotFound(undefined)).toBe(false);
expect(isDriveNotFound(null)).toBe(false);
expect(isDriveNotFound("string")).toBe(false);
expect(isDriveNotFound(new Error("plain"))).toBe(false);
});
it("walks the cause chain", { timeout: 5000 }, () => {
const inner = new FakeDriveError("DRIVE.NOT_FOUND", "missing");
const outer = new Error("wrapped");
(outer as Error & { cause?: unknown }).cause = inner;
expect(isDriveNotFound(outer)).toBe(true);
});
it("aborts on a self-referential cause cycle", { timeout: 5000 }, () => {
const err = new Error("cyclic");
(err as Error & { cause?: unknown }).cause = err;
expect(isDriveNotFound(err)).toBe(false);
});
it("returns false when no cause member exists", { timeout: 5000 }, () => {
expect(isDriveNotFound({ message: "no cause", code: "X" })).toBe(false);
});
});
describe("DriveStorageLikeAdapter", () => {
let fake: FakeDrive;
let adapter: DriveStorageLikeAdapter;
beforeEach(() => {
fake = new FakeDrive({
contents: new Map([["a.bin", new Uint8Array([1, 2, 3])]]),
});
adapter = new DriveStorageLikeAdapter(asDrive(fake));
});
it("returns undefined for Drive not-found errors", {
timeout: 5000,
}, async () => {
const result = await adapter.get("missing.bin");
expect(result).toBeUndefined();
});
it("returns the bytes on a hit", { timeout: 5000 }, async () => {
const bytes = await adapter.get("a.bin");
expect(bytes).toEqual(new Uint8Array([1, 2, 3]));
});
it("re-throws non-not-found Drive errors unchanged", {
timeout: 5000,
}, async () => {
const failure = new FakeDriveError(
"DRIVE.PLUGIN_FAILED",
"plugin crashed"
);
const bad = new FakeDrive({ readError: () => failure });
const a = new DriveStorageLikeAdapter(asDrive(bad));
await expect(a.get("x")).rejects.toBe(failure);
});
it("forwards set, delete, exists, list", { timeout: 5000 }, async () => {
await adapter.set("b.bin", new Uint8Array([9]));
expect(fake.calls.write).toHaveLength(1);
expect(await adapter.exists("a.bin")).toBe(true);
expect(await adapter.exists("missing")).toBe(false);
const removed = await adapter.delete("b.bin");
expect(removed).toBe(true);
const listed = await adapter.list({ prefix: "a" });
expect(listed.keys).toContain("a.bin");
expect(listed.hasMore).toBe(false);
expect(typeof listed.total).toBe("number");
});
it("list with no options defaults prefix/limit to undefined", {
timeout: 5000,
}, async () => {
const result = await adapter.list();
expect(Array.isArray(result.keys)).toBe(true);
});
it("list forwards options.limit to the drive", {
timeout: 5000,
}, async () => {
const result = await adapter.list({ prefix: "a", limit: 10 });
expect(Array.isArray(result.keys)).toBe(true);
});
it("list copes with a drive returning entries-shape instead of keys-shape", {
timeout: 5000,
}, async () => {
const odd = {
async read(): Promise<Uint8Array> {
return new Uint8Array();
},
async write(): Promise<void> {},
async delete(): Promise<boolean> {
return false;
},
async stat(): Promise<unknown | null> {
return null;
},
async list(): Promise<unknown> {
return {
entries: [{ path: "p1" }, { path: "p2" }],
};
},
createReadStream(): ReadableStream<Uint8Array> {
return new ReadableStream();
},
};
const a = new DriveStorageLikeAdapter(
odd as unknown as Parameters<typeof DriveStorageLikeAdapter>[0]
);
const r = await a.list();
expect(r.keys).toEqual(["p1", "p2"]);
expect(r.total).toBe(2);
});
it("list copes with a drive returning neither shape", {
timeout: 5000,
}, async () => {
const oddler = {
async read(): Promise<Uint8Array> {
return new Uint8Array();
},
async write(): Promise<void> {},
async delete(): Promise<boolean> {
return false;
},
async stat(): Promise<unknown | null> {
return null;
},
async list(): Promise<unknown> {
return undefined;
},
createReadStream(): ReadableStream<Uint8Array> {
return new ReadableStream();
},
};
const a = new DriveStorageLikeAdapter(
oddler as unknown as Parameters<typeof DriveStorageLikeAdapter>[0]
);
const r = await a.list();
expect(r.keys).toEqual([]);
expect(r.total).toBe(0);
expect(r.hasMore).toBe(false);
});
it("list forwards continuationToken when the drive returns one", {
timeout: 5000,
}, async () => {
const cursored = {
async read(): Promise<Uint8Array> {
return new Uint8Array();
},
async write(): Promise<void> {},
async delete(): Promise<boolean> {
return false;
},
async stat(): Promise<unknown | null> {
return null;
},
async list(): Promise<unknown> {
return {
keys: ["a", "b"],
total: 99,
hasMore: true,
continuationToken: "next-page",
};
},
createReadStream(): ReadableStream<Uint8Array> {
return new ReadableStream();
},
};
const a = new DriveStorageLikeAdapter(
cursored as unknown as Parameters<typeof DriveStorageLikeAdapter>[0]
);
const r = await a.list();
expect(r.continuationToken).toBe("next-page");
expect(r.hasMore).toBe(true);
expect(r.total).toBe(99);
});
it("clear is unsupported and names the alternative", {
timeout: 5000,
}, async () => {
await expect(adapter.clear()).rejects.toBeInstanceOf(SuaveplanError);
try {
await adapter.clear();
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
expect((err as DriveUriError).code).toBe(
DRIVE_URI_CODES.WRITE_UNSUPPORTED
);
expect((err as DriveUriError).message).toContain(
"Drive.delete(path)"
);
}
});
it("createReadStream forwards to Drive.createReadStream", {
timeout: 5000,
}, async () => {
const stream = adapter.createReadStream("a.bin");
expect(fake.calls.createReadStream).toHaveLength(1);
// Drain it
const reader = stream.getReader();
const { value } = await reader.read();
expect(value).toEqual(new Uint8Array([1, 2, 3]));
await reader.read(); // close
reader.releaseLock();
});
});

View File

@@ -0,0 +1,139 @@
import type { Drive } from "@suaveplan/drive";
import { SuaveplanError } from "@suaveplan/error";
import type { StorageLike } from "@suaveplan/types";
import { DRIVE_URI_CODES, DriveUriError } from "../error-codes/error-codes.js";
/**
* Detect whether `err` (or anything in its `cause` chain) is a "not found"
* marker emitted by `Drive` or its installed middleware. Matches any
* `SuaveplanError` whose `code` ends in `.NOT_FOUND` (covers `DRIVE.NOT_FOUND`
* surfaced by template hydration as well as adapter-level `DRIVE.IO_FAILED`
* variants whose underlying cause is itself a not-found marker).
*/
export function isDriveNotFound(err: unknown): boolean {
let cursor: unknown = err;
for (
let depth = 0;
cursor !== undefined && cursor !== null && depth < 8;
depth++
) {
if (cursor instanceof SuaveplanError) {
if (cursor.code.endsWith(".NOT_FOUND")) return true;
}
if (
typeof cursor === "object" &&
cursor !== null &&
"cause" in cursor
) {
const next: unknown = (cursor as { cause: unknown }).cause;
if (next === cursor) return false;
cursor = next;
continue;
}
return false;
}
return false;
}
interface DriveStatLike {
readonly size?: number;
readonly modifiedAt?: number;
readonly createdAt?: number;
readonly checksum?: string;
}
/**
* Adapter that exposes a `Drive` instance as a `StorageLike` so it can be
* registered as the `adapter` on a `UriPlugin` consumed by
* `StorageUriResolver`.
*
* The shim translates `Drive.read` rejections whose `cause` chain indicates a
* not-found condition into `undefined` (matching `StorageLike.get`'s contract)
* — every other failure is re-thrown unchanged so the upstream resolver can
* wrap it as `STORAGE_URI.ADAPTER_ERROR`.
*
* `clear()` rejects with an explicit `SuaveplanError` pointing the caller at
* `Drive.delete(path)`. Wiping a Drive through the URI surface is an obvious
* footgun; the resolver's `remove(uri)` is the supported path.
*
* `createReadStream(key, signal)` forwards directly to
* `Drive.createReadStream` so `StorageUriResolver.open()` uses the native
* streaming path — `storage_uri.stream_fallback_total` is NOT incremented.
*/
export class DriveStorageLikeAdapter implements StorageLike {
public constructor(private readonly drive: Drive) {}
public async get(key: string): Promise<Uint8Array | undefined> {
try {
return await this.drive.read(key);
} catch (err) {
if (isDriveNotFound(err)) return undefined;
throw err;
}
}
public async set(key: string, value: Uint8Array): Promise<void> {
await this.drive.write(key, value);
}
public async delete(key: string): Promise<boolean> {
return this.drive.delete(key);
}
public async exists(key: string): Promise<boolean> {
const result = (await this.drive.stat(key)) as DriveStatLike | null;
return result !== null && result !== undefined;
}
public async list(options?: { prefix?: string; limit?: number }): Promise<{
keys: string[];
total: number;
hasMore: boolean;
continuationToken?: string | undefined;
}> {
const result = (await this.drive.list(options?.prefix, {
...(options?.limit !== undefined ? { limit: options.limit } : {}),
})) as
| {
keys?: string[];
entries?: { path: string }[];
total?: number;
hasMore?: boolean;
continuationToken?: string;
}
| undefined;
const keys = Array.isArray(result?.keys)
? (result?.keys as string[])
: Array.isArray(result?.entries)
? (result?.entries as { path: string }[]).map((e) => e.path)
: [];
return {
keys,
total:
typeof result?.total === "number" ? result.total : keys.length,
hasMore: result?.hasMore === true,
...(typeof result?.continuationToken === "string"
? { continuationToken: result.continuationToken }
: {}),
};
}
public async clear(): Promise<void> {
throw new DriveUriError(
DRIVE_URI_CODES.WRITE_UNSUPPORTED,
"clear() is not supported on Drive — use Drive.delete(path) (or resolver.remove(uri)) for targeted removal"
);
}
/**
* Forward to `Drive.createReadStream(key, signal)`. Used by
* `StorageUriResolver.open()`'s native-stream fast path so the resolver
* does not buffer the entire payload.
*/
public createReadStream(
key: string,
signal?: AbortSignal
): ReadableStream<Uint8Array> {
return this.drive.createReadStream(key, signal);
}
}

View File

@@ -0,0 +1,4 @@
export {
DriveStorageLikeAdapter,
isDriveNotFound,
} from "./drive-storage-like.js";

View File

@@ -0,0 +1,106 @@
# `@suaveplan/drive-uri/conformance`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Re-exports the canonical URI-plugin conformance harness from
`@suaveplan/storage-uri` (`runUriPluginConformance`, `UriPluginFixtures`,
`UriPluginConformanceFailure`). `drive-uri` does NOT author its own harness
— keeping the assertions in one place means every URI-plugin package in the
project (storage-file, storage-http, storage-cdn-*, drive-uri, …)
runs against an identical set of contract checks.
Each plugin's fixture set lives in its co-located test file; the harness
runs three classes of assertion per fixture: every `validUris` member is
accepted by `parse`, every `invalidUris` member throws
`STORAGE_URI.INVALID_URI`, and every `buildRoundTrip` entry round-trips
through `plugin.build`.
## Features
- Zero-runtime re-export — the conformance harness is a value, not a copy.
- Type-safe `UriPluginFixtures` shape.
- Strong-typed failure surface (`UriPluginConformanceFailure`).
- Compatible with both `drive`, `drive+mesh`, and `drive+template` plugins.
## Usage: Basic
```ts
import {
runUriPluginConformance,
type UriPluginFixtures,
} from "@suaveplan/drive-uri/conformance";
import { driveUriPlugin } from "@suaveplan/drive-uri";
const fixtures: UriPluginFixtures = {
validUris: ["drive://primary/a", "drive://primary/nested/file.bin"],
invalidUris: ["", "drive:", "drive://primary/has space"],
buildRoundTrip: [
{
key: "a",
meta: { driveId: "primary" },
uri: "drive://primary/a",
},
],
};
runUriPluginConformance(
driveUriPlugin({ drive, driveId: "primary" }),
fixtures,
);
```
## Usage: Advanced
Running the same fixture template across multiple driveIds parametrically:
```ts
import { runUriPluginConformance, type UriPluginFixtures } from "@suaveplan/drive-uri";
function fixturesFor(driveId: string): UriPluginFixtures {
return {
validUris: [`drive://${driveId}/a`, `drive://${driveId}/folder/b`],
invalidUris: ["", "drive:"],
buildRoundTrip: [
{
key: "a",
meta: { driveId },
uri: `drive://${driveId}/a`,
},
],
};
}
for (const id of ["primary", "secondary", "backup"]) {
runUriPluginConformance(
driveUriPlugin({ drive: drives[id], driveId: id }),
fixturesFor(id),
);
}
```
## API Reference
- `runUriPluginConformance(plugin, fixtures)` — throws
`UriPluginConformanceFailure` on the first mismatch. Re-export from
`@suaveplan/storage-uri/conformance`.
- `interface UriPluginFixtures` — `validUris`, `invalidUris`, optional
`buildRoundTrip`.
- `class UriPluginConformanceFailure extends Error` — the harness's failure
class.
## Implementation Notes
- The drive-uri tests assemble per-plugin fixture sets locally (one each for
`drive://`, `drive+mesh://`, `drive+template://`) and feed them into the
harness inside `src/conformance/conformance.test.ts`.
- The `invalidUris` set must produce `STORAGE_URI.INVALID_URI` from
`parseUri` — the drive-uri plugins authenticate the scheme themselves but
defer URI-hygiene checks (whitespace, NUL, length, scheme regex) to the
upstream `parseUri` helper.
- Round-trip fixtures only need to be supplied when the plugin implements
`build`; the harness skips that section when `buildRoundTrip` is empty
or omitted.

View File

@@ -0,0 +1,123 @@
import type { UriPluginFixtures } from "@suaveplan/storage-uri";
import { describe, expect, it } from "@suaveplan/testing/runner";
import { asDrive, FakeDrive } from "../../tests/setup/fake-drive.js";
import { driveUriPlugin } from "../plugins/drive/drive.js";
import { driveMeshUriPlugin } from "../plugins/drive-mesh/drive-mesh.js";
import { driveTemplateUriPlugin } from "../plugins/drive-template/drive-template.js";
import type { TemplateProvider } from "../types/plugin-options.types.js";
import { runUriPluginConformance } from "./conformance.js";
const fake = new FakeDrive();
const fakeProvider: TemplateProvider = {
async fetchTemplate(): Promise<never> {
return {} as never;
},
};
const driveFixtures: UriPluginFixtures = {
validUris: [
"drive://primary/a",
"drive://primary/nested/path/file.bin",
"drive://primary/with-dash_and.dot",
],
invalidUris: ["", "://no-scheme", "drive:", "drive://primary/with space"],
buildRoundTrip: [
{
key: "a",
meta: { driveId: "primary" },
uri: "drive://primary/a",
},
{
key: "nested/path/file.bin",
meta: { driveId: "primary" },
uri: "drive://primary/nested/path/file.bin",
},
],
};
const meshFixtures: UriPluginFixtures = {
validUris: [
"drive+mesh://swarmA/a",
"drive+mesh://swarmA/nested/path/file.bin",
"drive+mesh://swarmA/with-dash_and.dot",
],
invalidUris: [
"",
"drive:",
"drive+mesh://swarmA/path with space",
"drive+mesh:///x",
],
buildRoundTrip: [
{
key: "a",
meta: { swarmId: "swarmA" },
uri: "drive+mesh://swarmA/a",
},
{
key: "raw/clip.mov",
meta: { swarmId: "swarmA" },
uri: "drive+mesh://swarmA/raw/clip.mov",
},
],
};
const templateFixtures: UriPluginFixtures = {
validUris: [
"drive+template://templA/a",
"drive+template://templA/nested/path/file.bin",
"drive+template://templA/with-dash_and.dot",
],
invalidUris: [
"",
"drive:",
"drive+template://templA/path with space",
"drive+template:///x",
],
buildRoundTrip: [
{
key: "a",
meta: { templateId: "templA" },
uri: "drive+template://templA/a",
},
{
key: "manifest/clip.jpg",
meta: { templateId: "templA" },
uri: "drive+template://templA/manifest/clip.jpg",
},
],
};
describe("runUriPluginConformance", () => {
it("drive plugin satisfies runUriPluginConformance", {
timeout: 5000,
}, () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
expect(() =>
runUriPluginConformance(plugin, driveFixtures)
).not.toThrow();
});
it("drive+mesh plugin satisfies runUriPluginConformance", {
timeout: 5000,
}, () => {
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
expect(() =>
runUriPluginConformance(plugin, meshFixtures)
).not.toThrow();
});
it("drive+template plugin satisfies runUriPluginConformance", {
timeout: 5000,
}, () => {
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: fakeProvider,
});
expect(() =>
runUriPluginConformance(plugin, templateFixtures)
).not.toThrow();
});
});

View File

@@ -0,0 +1,14 @@
/**
* Re-export the canonical conformance harness from `@suaveplan/storage-uri`.
*
* `drive-uri` does not author its own conformance harness — every URI plugin
* package in the genesis monorepo runs against the same harness so the
* "round-trip parse/build + invalid URI rejection" contract stays in one
* place. Each drive-uri plugin's fixture set lives in its co-located test
* file and is fed into the harness from there.
*/
export {
runUriPluginConformance,
UriPluginConformanceFailure,
type UriPluginFixtures,
} from "@suaveplan/storage-uri";

View File

@@ -0,0 +1,5 @@
export type { UriPluginFixtures } from "./conformance.js";
export {
runUriPluginConformance,
UriPluginConformanceFailure,
} from "./conformance.js";

View File

@@ -0,0 +1,98 @@
# `@suaveplan/drive-uri/error-codes`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Declares the seven `DRIVE_URI.*` error codes raised by `@suaveplan/drive-uri`
plugins and the `DriveUriError` discriminant class used to surface them. The
codes form the public contract by which consumers programmatically distinguish
URI-parsing failures (`INVALID_URI`), authority mismatches
(`DRIVE_ID_MISMATCH`), template fetch/validation failures
(`TEMPLATE_NOT_AVAILABLE`, `TEMPLATE_INVALID`), unsupported writes
(`WRITE_UNSUPPORTED`), missing required wiring (`CAPABILITY_MISSING`), and
abort short-circuits (`ABORTED`).
Every code is produced by `@suaveplan/error`'s `defineCodes` so the literal
strings (`"DRIVE_URI.INVALID_URI"` etc.) are generated once and consumed via
the typed `DRIVE_URI_CODES` constant — never hand-typed. This keeps the
namespace honest under refactor and guarantees that `instanceof DriveUriError`
narrows the `code` discriminant to a small literal union.
## Features
- Seven namespaced codes registered via a single `defineCodes` call.
- `DriveUriError` carries optional discriminants (`uri`, `scheme`, `driveId`,
`swarmId`, `templateId`, `path`, `expected`, `actual`, `reason`) so
operators can correlate failures without parsing message strings.
- Original `cause` is preserved across re-wraps via the `SuaveplanError`
base class so `err.cause.code` still reads e.g. `DTP.TEMPLATE_EXPIRED`
after a `TEMPLATE_INVALID` wrap.
- The error class name is `"DriveUriError"` for log aggregation and
TypeScript narrowing.
## Usage: Basic
```ts
import { DRIVE_URI_CODES, DriveUriError } from "@suaveplan/drive-uri/error-codes";
throw new DriveUriError(
DRIVE_URI_CODES.DRIVE_ID_MISMATCH,
"URI driveId 'other' does not match plugin driveId 'primary'",
{ uri: "drive://other/x", expected: "primary", actual: "other" },
);
```
## Usage: Advanced
Walking the `cause` chain to surface the underlying drive failure code while
keeping the public `DRIVE_URI.*` shape:
```ts
import { DRIVE_URI_CODES, DriveUriError } from "@suaveplan/drive-uri";
try {
await drive.hydrateFrom(template);
} catch (cause) {
throw new DriveUriError(
DRIVE_URI_CODES.TEMPLATE_INVALID,
"hydrateFrom rejected for templateId 'templA'",
{ templateId: "templA", cause },
);
}
```
A consumer receives a `DriveUriError` whose `code === "DRIVE_URI.TEMPLATE_INVALID"`
and whose `cause.code === "DTP.TEMPLATE_EXPIRED"` (or whichever DTP code the
drive-template validator surfaced). Both are programmatically actionable.
## API Reference
- `DRIVE_URI_CODES` — frozen registry with seven keys: `INVALID_URI`,
`DRIVE_ID_MISMATCH`, `TEMPLATE_NOT_AVAILABLE`, `TEMPLATE_INVALID`,
`WRITE_UNSUPPORTED`, `CAPABILITY_MISSING`, `ABORTED`. Each value is the
namespaced string `DRIVE_URI.<REASON>`.
- `DriveUriErrorCode` — string-literal union over the registry values.
- `DriveUriErrorOptions` — extends `SuaveplanErrorOptions` with optional
`uri`, `scheme`, `driveId`, `swarmId`, `templateId`, `path`, `expected`,
`actual`, `reason` discriminants.
- `DriveUriError` — extends `SuaveplanError<DriveUriErrorCode>`. Constructor:
`(code, message, options?) => DriveUriError`. Exposes the discriminants as
readonly fields.
## Implementation Notes
- The registry is intentionally narrow. Codes like `STORAGE_URI.NOT_FOUND` are
emitted by the resolver, not by drive-uri — duplicating them here would
drift on rename. The codes that ARE in this registry are the ones drive-uri
raises before reaching the resolver, plus the ones it re-wraps to keep the
failure mode visible to dashboards.
- `DriveUriError` does NOT include `getMetadata`-style state — it is a plain
exception class. OpenTelemetry instrumentation lives in
`@suaveplan/drive-uri/otel`.
- The side-effect import at `src/index.ts` ensures the registry is registered
when consumers reach the package via the root entry; subpath consumers
reaching `./error-codes` directly register the codes on import via
`defineCodes`'s own side effect.

View File

@@ -0,0 +1,116 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
DRIVE_URI_CODES,
DriveUriError,
type DriveUriErrorCode,
} from "./error-codes.js";
describe("DRIVE_URI_CODES registry", () => {
it("exposes all seven namespaced codes", { timeout: 5000 }, () => {
const codes: DriveUriErrorCode[] = [
DRIVE_URI_CODES.INVALID_URI,
DRIVE_URI_CODES.DRIVE_ID_MISMATCH,
DRIVE_URI_CODES.TEMPLATE_NOT_AVAILABLE,
DRIVE_URI_CODES.TEMPLATE_INVALID,
DRIVE_URI_CODES.WRITE_UNSUPPORTED,
DRIVE_URI_CODES.CAPABILITY_MISSING,
DRIVE_URI_CODES.ABORTED,
];
expect(codes).toHaveLength(7);
for (const code of codes) {
expect(code.startsWith("DRIVE_URI.")).toBe(true);
}
});
});
describe("DriveUriError", () => {
it("populates every discriminant from the options bag", {
timeout: 5000,
}, () => {
const cause = new Error("underlying");
const err = new DriveUriError(
DRIVE_URI_CODES.DRIVE_ID_MISMATCH,
"mismatch",
{
uri: "drive://other/x",
scheme: "drive",
driveId: "primary",
swarmId: "swarmA",
templateId: "templA",
path: "x",
expected: "primary",
actual: "other",
reason: "test",
cause,
}
);
expect(err.code).toBe(DRIVE_URI_CODES.DRIVE_ID_MISMATCH);
expect(err.uri).toBe("drive://other/x");
expect(err.scheme).toBe("drive");
expect(err.driveId).toBe("primary");
expect(err.swarmId).toBe("swarmA");
expect(err.templateId).toBe("templA");
expect(err.path).toBe("x");
expect(err.expected).toBe("primary");
expect(err.actual).toBe("other");
expect(err.reason).toBe("test");
expect(err.cause).toBe(cause);
expect(err.name).toBe("DriveUriError");
});
it("leaves unspecified discriminants undefined", { timeout: 5000 }, () => {
const err = new DriveUriError(
DRIVE_URI_CODES.INVALID_URI,
"no options at all"
);
expect(err.uri).toBeUndefined();
expect(err.scheme).toBeUndefined();
expect(err.driveId).toBeUndefined();
expect(err.swarmId).toBeUndefined();
expect(err.templateId).toBeUndefined();
expect(err.path).toBeUndefined();
expect(err.expected).toBeUndefined();
expect(err.actual).toBeUndefined();
expect(err.reason).toBeUndefined();
});
it("preserves cause across re-wraps", { timeout: 5000 }, () => {
const inner = new Error("inner failure");
const middle = new DriveUriError(
DRIVE_URI_CODES.TEMPLATE_NOT_AVAILABLE,
"fetch failed",
{ cause: inner }
);
const outer = new DriveUriError(
DRIVE_URI_CODES.TEMPLATE_INVALID,
"rewrapped",
{ cause: middle }
);
expect(outer.cause).toBe(middle);
expect((outer.cause as DriveUriError).cause).toBe(inner);
});
it("is reachable for every registered code via a real throw", {
timeout: 5000,
}, () => {
const allCodes: DriveUriErrorCode[] = [
DRIVE_URI_CODES.INVALID_URI,
DRIVE_URI_CODES.DRIVE_ID_MISMATCH,
DRIVE_URI_CODES.TEMPLATE_NOT_AVAILABLE,
DRIVE_URI_CODES.TEMPLATE_INVALID,
DRIVE_URI_CODES.WRITE_UNSUPPORTED,
DRIVE_URI_CODES.CAPABILITY_MISSING,
DRIVE_URI_CODES.ABORTED,
];
for (const code of allCodes) {
let caught: DriveUriError | undefined;
try {
throw new DriveUriError(code, `test for ${code}`);
} catch (err) {
caught = err as DriveUriError;
}
expect(caught).toBeInstanceOf(DriveUriError);
expect(caught?.code).toBe(code);
}
});
});

View File

@@ -0,0 +1,79 @@
import {
defineCodes,
SuaveplanError,
type SuaveplanErrorOptions,
} from "@suaveplan/error";
/**
* Registry of every error code raised by `@suaveplan/drive-uri`.
*
* Every member is the fully-qualified string `DRIVE_URI.<REASON>` produced by
* `defineCodes`. Seven codes total — `INVALID_URI`, `DRIVE_ID_MISMATCH`,
* `TEMPLATE_NOT_AVAILABLE`, `TEMPLATE_INVALID`, `WRITE_UNSUPPORTED`,
* `CAPABILITY_MISSING`, `ABORTED` — all reachable from a real call site
* exercised by the test suite.
*/
export const DRIVE_URI_CODES = defineCodes("DRIVE_URI", [
"INVALID_URI",
"DRIVE_ID_MISMATCH",
"TEMPLATE_NOT_AVAILABLE",
"TEMPLATE_INVALID",
"WRITE_UNSUPPORTED",
"CAPABILITY_MISSING",
"ABORTED",
] as const);
export type DriveUriErrorCode =
(typeof DRIVE_URI_CODES)[keyof typeof DRIVE_URI_CODES];
export interface DriveUriErrorOptions extends SuaveplanErrorOptions {
uri?: string;
scheme?: string;
driveId?: string;
swarmId?: string;
templateId?: string;
path?: string;
expected?: string;
actual?: string;
reason?: string;
}
/**
* Structured error for drive-URI plugin operations.
*
* Carries optional discriminants (`uri`, `scheme`, `driveId`, `swarmId`,
* `templateId`, `path`, `expected`/`actual` for mismatches, `reason` for
* capability-missing failures) so operators can correlate a failure with the
* offending request without parsing message text. The original underlying
* cause (when re-wrapping a `Drive`, `DTP_*`, or `templateProvider` throwable)
* is preserved on `cause` via the base class.
*/
export class DriveUriError extends SuaveplanError<DriveUriErrorCode> {
public readonly uri: string | undefined;
public readonly scheme: string | undefined;
public readonly driveId: string | undefined;
public readonly swarmId: string | undefined;
public readonly templateId: string | undefined;
public readonly path: string | undefined;
public readonly expected: string | undefined;
public readonly actual: string | undefined;
public readonly reason: string | undefined;
public constructor(
code: DriveUriErrorCode,
message: string,
options?: DriveUriErrorOptions
) {
super(code, message, options);
this.uri = options?.uri;
this.scheme = options?.scheme;
this.driveId = options?.driveId;
this.swarmId = options?.swarmId;
this.templateId = options?.templateId;
this.path = options?.path;
this.expected = options?.expected;
this.actual = options?.actual;
this.reason = options?.reason;
this.name = "DriveUriError";
}
}

View File

@@ -0,0 +1,8 @@
export type {
DriveUriErrorCode,
DriveUriErrorOptions,
} from "./error-codes.js";
export {
DRIVE_URI_CODES,
DriveUriError,
} from "./error-codes.js";

View File

@@ -0,0 +1,44 @@
// Side-effect import: ensures the DRIVE_URI.* code registry registers even
// when subpath consumers never reach this barrel.
import "./error-codes/error-codes.js";
export {
DriveStorageLikeAdapter,
isDriveNotFound,
} from "./adapter/drive-storage-like.js";
export {
runUriPluginConformance,
UriPluginConformanceFailure,
type UriPluginFixtures,
} from "./conformance/conformance.js";
export {
DRIVE_URI_CODES,
DriveUriError,
type DriveUriErrorCode,
type DriveUriErrorOptions,
} from "./error-codes/error-codes.js";
export {
type DriveUriInstruments,
type DriveUriOp,
type DriveUriOutcome,
getInstruments,
type RecordOpInput,
recordOp,
recordTemplateHydration,
redactUri,
resetInstruments,
setDriveUriMeterProvider,
} from "./otel/otel.js";
export { driveUriPlugin } from "./plugins/drive/drive.js";
export {
type InstrumentedAdapterOptions,
InstrumentedDriveAdapter,
} from "./plugins/drive/drive.streams.js";
export { driveMeshUriPlugin } from "./plugins/drive-mesh/drive-mesh.js";
export { driveTemplateUriPlugin } from "./plugins/drive-template/drive-template.js";
export type {
DriveMeshUriPluginOptions,
DriveTemplateUriPluginOptions,
DriveUriPluginOptions,
TemplateProvider,
} from "./types/plugin-options.types.js";

View File

@@ -0,0 +1,14 @@
export type {
DriveUriInstruments,
DriveUriOp,
DriveUriOutcome,
RecordOpInput,
} from "./otel.js";
export {
getInstruments,
recordOp,
recordTemplateHydration,
redactUri,
resetInstruments,
setDriveUriMeterProvider,
} from "./otel.js";

View File

@@ -0,0 +1,92 @@
# `@suaveplan/drive-uri/otel`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
OpenTelemetry instrumentation surface for `@suaveplan/drive-uri`. Exposes a
meter named `@suaveplan/drive-uri` carrying the operation-level counters and
duration histogram described in the package spec, plus a tracer of the same
name for span emission. The `redactUri` helper strips query strings and
fragments from URI attributes so peer hints, seeds, or other secret material
never reach span backends.
Counters and histograms are lazily created on first access via
`getInstruments()` so the package has zero side effects at import time, and
`setDriveUriMeterProvider` lets tests inject a stub provider for assertions.
## Features
- Lazy instrument bag keyed by the active `MeterProvider`.
- Counters: `drive_uri.reads_total`, `drive_uri.writes_total`,
`drive_uri.deletes_total`, `drive_uri.errors_total`,
`drive_uri.template_hydrations_total`.
- Histogram: `drive_uri.op_duration_ms{op, scheme, outcome}`.
- Per-op routing helper `recordOp` that increments the correct success or
error counter and the duration histogram in one call. Writes increment
`writes_total` even on rejection so operator dashboards see misuse rate.
- `redactUri(uri)` strips everything from the first `?` or `#` onwards.
## Usage: Basic
```ts
import { recordOp, recordTemplateHydration } from "@suaveplan/drive-uri/otel";
recordOp({ op: "read", scheme: "drive", outcome: "success", durationMs: 12 });
recordTemplateHydration("success");
```
## Usage: Advanced
Test harness swapping in a stub `MeterProvider`:
```ts
import {
resetInstruments,
setDriveUriMeterProvider,
} from "@suaveplan/drive-uri/otel";
class StubProvider implements MeterProvider {
public meter = new StubMeter();
public getMeter() {
return this.meter;
}
}
const provider = new StubProvider();
setDriveUriMeterProvider(provider);
resetInstruments();
// ... run code under test, then inspect provider.meter ...
setDriveUriMeterProvider(undefined);
resetInstruments();
```
## API Reference
- `setDriveUriMeterProvider(provider | undefined)` — override or revert.
- `getInstruments(force = false)` — returns the cached bag or rebuilds.
- `resetInstruments()` — clears the cache (used between provider swaps).
- `recordOp({ op, scheme, outcome, durationMs, code? })` — one-shot
per-operation observation. `op ∈ {open|read|write|stat|exists|remove|list}`.
- `recordTemplateHydration(outcome)` — increments
`drive_uri.template_hydrations_total{outcome}` by one.
- `redactUri(uri): string` — strips `?...` and `#...` suffixes.
- Types: `DriveUriOp`, `DriveUriOutcome`, `DriveUriInstruments`,
`RecordOpInput`.
## Implementation Notes
- `writes_total` carries the `outcome` attribute (`"success"` or `"error"`),
letting dashboards split successful (currently impossible) writes from
misuse rejections.
- `errors_total` is keyed by `scheme` and `code` only — operators slice it
by code to attribute failures to root causes (`DRIVE_URI.TEMPLATE_INVALID`
vs. `DRIVE_URI.WRITE_UNSUPPORTED` etc.).
- The drive-uri tracer is independent of the resolver's `@suaveplan/storage-uri`
tracer — both spans fire on a single dispatch, giving operators a per-tier
view of the operation.
- `redactUri` is content-agnostic; it does NOT validate that the input is a
syntactically correct URI. The plugin layer is responsible for rejecting
malformed URIs before they reach the span attribute.

View File

@@ -0,0 +1,294 @@
import type {
Counter,
Histogram,
Meter,
MeterProvider,
} from "@opentelemetry/api";
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import {
afterEach,
beforeEach,
describe,
expect,
it,
} from "@suaveplan/testing/runner";
import { asDrive, FakeDrive } from "../../tests/setup/fake-drive.js";
import { driveUriPlugin } from "../plugins/drive/drive.js";
import { driveMeshUriPlugin } from "../plugins/drive-mesh/drive-mesh.js";
import { driveTemplateUriPlugin } from "../plugins/drive-template/drive-template.js";
import {
recordOp,
recordTemplateHydration,
redactUri,
resetInstruments,
setDriveUriMeterProvider,
} from "./otel.js";
interface Sample {
value: number;
attrs: Record<string, unknown> | undefined;
}
class StubCounter implements Counter {
public adds: Sample[] = [];
public add(v: number, attrs?: Record<string, unknown>): void {
this.adds.push({ value: v, attrs });
}
}
class StubHistogram implements Histogram {
public samples: Sample[] = [];
public record(v: number, attrs?: Record<string, unknown>): void {
this.samples.push({ value: v, attrs });
}
}
class StubMeter implements Meter {
public counters = new Map<string, StubCounter>();
public histograms = new Map<string, StubHistogram>();
public createCounter(name: string): Counter {
const c = new StubCounter();
this.counters.set(name, c);
return c;
}
public createHistogram(name: string): Histogram {
const h = new StubHistogram();
this.histograms.set(name, h);
return h;
}
public createUpDownCounter(): never {
throw new Error("unused");
}
public createObservableCounter(): never {
throw new Error("unused");
}
public createObservableUpDownCounter(): never {
throw new Error("unused");
}
public createObservableGauge(): never {
throw new Error("unused");
}
public addBatchObservableCallback(): void {}
public removeBatchObservableCallback(): void {}
public createGauge(): never {
throw new Error("unused");
}
}
class StubMeterProvider implements MeterProvider {
public meter = new StubMeter();
public getMeter(): Meter {
return this.meter;
}
}
describe("drive-uri otel", () => {
let provider: StubMeterProvider;
beforeEach(() => {
provider = new StubMeterProvider();
setDriveUriMeterProvider(provider);
resetInstruments();
});
afterEach(() => {
setDriveUriMeterProvider(undefined);
resetInstruments();
});
it("records duration histogram and reads counter on success", {
timeout: 5000,
}, async () => {
const fake = new FakeDrive({
contents: new Map([["x", new Uint8Array([1])]]),
});
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
const resolver = createStorageUriResolver([plugin]);
await resolver.read("drive://primary/x");
const dur = provider.meter.histograms.get("drive_uri.op_duration_ms");
expect(dur?.samples.length).toBeGreaterThan(0);
const sample = dur?.samples.find(
(s) => s.attrs?.["outcome"] === "success"
);
expect(sample?.attrs).toMatchObject({
op: "read",
scheme: "drive",
outcome: "success",
});
const reads = provider.meter.counters.get("drive_uri.reads_total");
expect(reads?.adds[0]?.attrs).toMatchObject({
scheme: "drive",
outcome: "success",
});
});
it("increments errors_total with the resolved DRIVE_URI code", {
timeout: 5000,
}, async () => {
const fake = new FakeDrive();
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: {
async fetchTemplate(): Promise<never> {
throw new Error("404");
},
},
});
const resolver = createStorageUriResolver([plugin]);
await expect(
resolver.read("drive+template://templA/x")
).rejects.toBeDefined();
const errs = provider.meter.counters.get("drive_uri.errors_total");
const match = errs?.adds.find(
(a) =>
a.attrs?.["code"] === "DRIVE_URI.TEMPLATE_NOT_AVAILABLE" &&
a.attrs?.["scheme"] === "drive+template"
);
expect(match).toBeDefined();
});
it("strips query strings from the drive.uri span attribute", {
timeout: 5000,
}, () => {
expect(redactUri("drive+mesh://swarmA/path?peers=abcdef")).toBe(
"drive+mesh://swarmA/path"
);
expect(redactUri("drive://primary/clip#frag")).toBe(
"drive://primary/clip"
);
expect(redactUri("drive://primary/clip")).toBe("drive://primary/clip");
});
it("increments template_hydrations_total exactly once per first-use template", {
timeout: 5000,
}, async () => {
const fake = new FakeDrive({
contents: new Map([
["x", new Uint8Array([1])],
["y", new Uint8Array([2])],
]),
});
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: {
async fetchTemplate(): Promise<unknown> {
return {};
},
} as unknown as Parameters<
typeof driveTemplateUriPlugin
>[0]["templateProvider"],
});
const resolver = createStorageUriResolver([plugin]);
await resolver.read("drive+template://templA/x");
await resolver.read("drive+template://templA/y");
const hyd = provider.meter.counters.get(
"drive_uri.template_hydrations_total"
);
expect(hyd?.adds).toHaveLength(1);
expect(hyd?.adds[0]?.attrs).toMatchObject({ outcome: "success" });
});
it("increments writes_total on rejected writes", {
timeout: 5000,
}, async () => {
const fake = new FakeDrive();
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
const adapter = plugin.adapter as {
set(key: string, value: Uint8Array): Promise<void>;
};
await expect(
adapter.set("x", new Uint8Array([1]))
).rejects.toBeDefined();
const writes = provider.meter.counters.get("drive_uri.writes_total");
expect(writes?.adds[0]?.attrs).toMatchObject({
op: "write",
scheme: "drive+mesh",
outcome: "error",
});
});
it("recordOp routes read-class ops to reads_total only on success", {
timeout: 5000,
}, () => {
for (const op of ["read", "open", "stat", "exists", "list"] as const) {
recordOp({
op,
scheme: "drive",
outcome: "success",
durationMs: 1,
});
}
expect(
provider.meter.counters.get("drive_uri.reads_total")?.adds
).toHaveLength(5);
});
it("recordOp records error outcome separately into errors_total", {
timeout: 5000,
}, () => {
recordOp({
op: "read",
scheme: "drive",
outcome: "error",
durationMs: 1,
});
const errs = provider.meter.counters.get("drive_uri.errors_total");
expect(errs?.adds[0]?.attrs).toMatchObject({ code: "UNKNOWN" });
});
it("recordOp handles not_found read outcomes without incrementing reads_total", {
timeout: 5000,
}, () => {
recordOp({
op: "read",
scheme: "drive",
outcome: "not_found",
durationMs: 1,
});
expect(
provider.meter.counters.get("drive_uri.reads_total")?.adds.length ??
0
).toBe(0);
expect(
provider.meter.counters.get("drive_uri.errors_total")?.adds.length
).toBe(1);
});
it("recordTemplateHydration records the supplied outcome attribute", {
timeout: 5000,
}, () => {
recordTemplateHydration("success");
recordTemplateHydration("error");
const hyd = provider.meter.counters.get(
"drive_uri.template_hydrations_total"
);
expect(hyd?.adds).toHaveLength(2);
expect(hyd?.adds[0]?.attrs).toMatchObject({ outcome: "success" });
expect(hyd?.adds[1]?.attrs).toMatchObject({ outcome: "error" });
});
it("getInstruments caches between calls and rebuilds on provider swap", {
timeout: 5000,
}, () => {
recordOp({
op: "read",
scheme: "x",
outcome: "success",
durationMs: 1,
});
const reads1 = provider.meter.counters.get("drive_uri.reads_total");
expect(reads1?.adds.length).toBe(1);
const newProvider = new StubMeterProvider();
setDriveUriMeterProvider(newProvider);
recordOp({
op: "read",
scheme: "x",
outcome: "success",
durationMs: 1,
});
expect(
newProvider.meter.counters.get("drive_uri.reads_total")?.adds.length
).toBe(1);
});
});

View File

@@ -0,0 +1,156 @@
import {
type Counter,
type Histogram,
type Meter,
type MeterProvider,
metrics,
type Tracer,
trace,
} from "@opentelemetry/api";
const METER_NAME = "@suaveplan/drive-uri";
const TRACER_NAME = "@suaveplan/drive-uri";
let overrideMeterProvider: MeterProvider | undefined;
/**
* Override the `MeterProvider` used to acquire the drive-uri meter. Pass
* `undefined` to revert to the global provider. Resets the cached instrument
* bag so the next access binds to the new provider.
*/
export function setDriveUriMeterProvider(
provider: MeterProvider | undefined
): void {
overrideMeterProvider = provider;
cachedInstruments = undefined;
}
export type DriveUriOp =
| "open"
| "read"
| "write"
| "stat"
| "exists"
| "remove"
| "list";
export type DriveUriOutcome = "success" | "not_found" | "error" | "aborted";
export interface DriveUriInstruments {
readonly tracer: Tracer;
readonly meter: Meter;
readonly readsTotal: Counter;
readonly writesTotal: Counter;
readonly deletesTotal: Counter;
readonly errorsTotal: Counter;
readonly templateHydrationsTotal: Counter;
readonly opDurationMs: Histogram;
}
let cachedInstruments: DriveUriInstruments | undefined;
/** Lazily build (or return cached) instruments bound to the active provider. */
export function getInstruments(force = false): DriveUriInstruments {
if (cachedInstruments !== undefined && !force) return cachedInstruments;
const tracer = trace.getTracer(TRACER_NAME);
const meter = (overrideMeterProvider ?? metrics).getMeter(METER_NAME);
cachedInstruments = {
tracer,
meter,
readsTotal: meter.createCounter("drive_uri.reads_total", {
description:
"Read operations dispatched via drive-uri, partitioned by scheme and outcome",
}),
writesTotal: meter.createCounter("drive_uri.writes_total", {
description:
"Write operations attempted via drive-uri (initial release rejects every write)",
}),
deletesTotal: meter.createCounter("drive_uri.deletes_total", {
description: "Delete operations attempted via drive-uri",
}),
errorsTotal: meter.createCounter("drive_uri.errors_total", {
description:
"Operations that ended in error, attributed by DRIVE_URI.* code",
}),
templateHydrationsTotal: meter.createCounter(
"drive_uri.template_hydrations_total",
{
description:
"Number of first-use template hydrations performed by drive+template plugin instances (cache hits do not increment)",
}
),
opDurationMs: meter.createHistogram("drive_uri.op_duration_ms", {
description: "Per-operation duration in milliseconds",
unit: "ms",
}),
};
return cachedInstruments;
}
/** Reset cached instruments — used by tests between provider swaps. */
export function resetInstruments(): void {
cachedInstruments = undefined;
}
const SUCCESS_COUNTER_OF: Record<DriveUriOp, "reads" | "writes" | "deletes"> = {
read: "reads",
open: "reads",
stat: "reads",
exists: "reads",
list: "reads",
write: "writes",
remove: "deletes",
};
export interface RecordOpInput {
readonly op: DriveUriOp;
readonly scheme: string;
readonly outcome: DriveUriOutcome;
readonly durationMs: number;
/** Resolved `DRIVE_URI.*` code when `outcome === "error"`. */
readonly code?: string;
}
/**
* Record one full observability bundle for a completed operation.
*
* Per spec, `drive_uri.writes_total{scheme, outcome: "error"}` increments
* even on rejection (so operators can see misuse rate); the success vs.
* error counter routing is handled below.
*/
export function recordOp(input: RecordOpInput): void {
const inst = getInstruments();
const { op, scheme, outcome, durationMs, code } = input;
inst.opDurationMs.record(durationMs, { op, scheme, outcome });
const counter = SUCCESS_COUNTER_OF[op];
const attrs = { op, scheme, outcome };
if (counter === "writes") {
inst.writesTotal.add(1, attrs);
} else if (counter === "deletes") {
inst.deletesTotal.add(1, attrs);
} else if (outcome === "success") {
inst.readsTotal.add(1, attrs);
}
if (outcome !== "success") {
inst.errorsTotal.add(1, { scheme, code: code ?? "UNKNOWN" });
}
}
/** Increment `drive_uri.template_hydrations_total{outcome}` by one. */
export function recordTemplateHydration(outcome: "success" | "error"): void {
getInstruments().templateHydrationsTotal.add(1, { outcome });
}
/**
* Strip the query string and fragment from a URI so span attributes never
* leak peer hints, seeds, or other sensitive material. Returns the input
* verbatim when no `?` or `#` is present.
*/
export function redactUri(uri: string): string {
const q = uri.indexOf("?");
const h = uri.indexOf("#");
let end = uri.length;
if (q >= 0) end = Math.min(end, q);
if (h >= 0) end = Math.min(end, h);
return uri.slice(0, end);
}

View File

@@ -0,0 +1,91 @@
# `@suaveplan/drive-uri/plugins/drive-mesh`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`driveMeshUriPlugin(options)` is a factory returning a `UriPlugin` registered
against the `drive+mesh://` scheme. It parses `drive+mesh://<swarmId>/<path>`
URIs, captures `swarmId` for instrumentation, and forwards `path` to the
consumer-supplied mesh-configured `Drive`. The plugin does NOT use `swarmId`
to choose a `Drive` or transport — peer routing remains entirely the
responsibility of the `Drive`'s installed transports (`drive-transport-https`,
`drive-webrtc`) and its `PeerRegistry`.
The captured `swarmId` surfaces on every emitted span via the
`drive.uri.swarm_id` attribute. When a span is active at the time of
`parse(uri)`, the attribute is written there (alongside `drive.uri.scheme`
and the redacted `drive.uri`); otherwise the parse step is silent.
## Features
- `parse(uri)` enforces `scheme === "drive+mesh"`, a non-empty authority, and
a non-empty path. All other URI hygiene (whitespace, NUL, scheme regex,
4096-char limit) is delegated to `parseUri` from `@suaveplan/storage-uri`.
- `build(key, meta?)` round-trips the URI back to its source shape, defaulting
the swarmId to `options.defaultSwarmId` (or `"default"`) when no meta is
supplied.
- Adapter inherits `InstrumentedDriveAdapter` so reads, writes, and errors
are recorded under the `drive+mesh` scheme attribute.
## Usage: Basic
```ts
import { Drive } from "@suaveplan/drive";
import { chunksPlugin } from "@suaveplan/drive-plugin-chunks";
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { driveMeshUriPlugin } from "@suaveplan/drive-uri/plugins/drive-mesh";
const meshDrive = new Drive(localStorage);
await meshDrive.initialize();
await meshDrive.usePlugin(chunksPlugin({ /* ... */ }));
const resolver = createStorageUriResolver([
driveMeshUriPlugin({ drive: meshDrive }),
]);
const bytes = await resolver.read("drive+mesh://swarmA/raw/clip.mov");
```
## Usage: Advanced
Mixing mesh and template plugins on the same mesh-configured Drive so a
single resolver dispatches both shapes through one drive instance:
```ts
import {
driveMeshUriPlugin,
driveTemplateUriPlugin,
} from "@suaveplan/drive-uri";
const resolver = createStorageUriResolver([
driveMeshUriPlugin({ drive: meshDrive, defaultSwarmId: "primary" }),
driveTemplateUriPlugin({ drive: meshDrive, templateProvider }),
]);
await resolver.read("drive+mesh://primary/folder/a.bin");
await resolver.read("drive+template://templA/folder/a.bin");
```
The two plugins share the same `Drive`, so a hydration triggered by the
template plugin populates the file-location index that the mesh plugin then
uses to fetch chunks. This is the canonical composition.
## API Reference
- `driveMeshUriPlugin(options: DriveMeshUriPluginOptions): UriPlugin` —
`options.drive` is required; `options.defaultSwarmId` is optional and
defaults to `"default"` for `build` output.
## Implementation Notes
- The plugin's `parse(uri)` attaches the swarmId to the active span via
`trace.getActiveSpan()`. When no span is active (e.g. plugin used
outside the resolver, in unit tests parsing URIs directly), the
attribute write is silently skipped — never throws.
- The `drive.uri` attribute is passed through `redactUri` so query strings
carrying peer hints or seeds are stripped before reaching span backends.
- Like `driveUriPlugin`, writes are rejected with `DRIVE_URI.WRITE_UNSUPPORTED`
per the per-release scope.

View File

@@ -0,0 +1,157 @@
import { trace } from "@opentelemetry/api";
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { beforeEach, describe, expect, it } from "@suaveplan/testing/runner";
import { asDrive, FakeDrive } from "../../../tests/setup/fake-drive.js";
import {
DRIVE_URI_CODES,
DriveUriError,
} from "../../error-codes/error-codes.js";
import { driveMeshUriPlugin } from "./drive-mesh.js";
interface SpanLike {
setAttribute(name: string, value: unknown): SpanLike;
end(): void;
readonly attributes: Map<string, unknown>;
}
function makeRecordingSpan(): SpanLike {
const attrs = new Map<string, unknown>();
return {
attributes: attrs,
setAttribute(name: string, value: unknown): SpanLike {
attrs.set(name, value);
return this;
},
end(): void {},
};
}
describe("driveMeshUriPlugin", () => {
let fake: FakeDrive;
beforeEach(() => {
fake = new FakeDrive({
contents: new Map([["raw/clip.mov", new Uint8Array([1, 2, 3])]]),
});
});
it("requires options.drive at construction", { timeout: 5000 }, () => {
expect(() =>
driveMeshUriPlugin({
drive: undefined as unknown as Parameters<
typeof driveMeshUriPlugin
>[0]["drive"],
})
).toThrow(DriveUriError);
});
it("rejects non-mesh schemes", { timeout: 5000 }, () => {
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
try {
plugin.parse("drive://primary/x");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
expect((err as DriveUriError).code).toBe(
DRIVE_URI_CODES.INVALID_URI
);
}
});
it("rejects URIs without an authority", { timeout: 5000 }, () => {
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
expect(() => plugin.parse("drive+mesh:///x")).toThrow(DriveUriError);
});
it("rejects URIs without a path", { timeout: 5000 }, () => {
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
expect(() => plugin.parse("drive+mesh://swarmA/")).toThrow(
DriveUriError
);
});
it("rejects URIs with no authority component (no //)", {
timeout: 5000,
}, () => {
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
// `drive+mesh:x` has scheme + path but no authority — covers the
// `parts.authority ?? ""` fallback branch.
expect(() => plugin.parse("drive+mesh:x")).toThrow(DriveUriError);
});
it("dispatches a drive+mesh:// URI to the configured Drive", {
timeout: 5000,
}, async () => {
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
const resolver = createStorageUriResolver([plugin]);
const bytes = await resolver.read("drive+mesh://swarmA/raw/clip.mov");
expect(bytes).toEqual(new Uint8Array([1, 2, 3]));
expect(fake.calls.read).toHaveLength(1);
expect(fake.calls.read[0]?.path).toBe("raw/clip.mov");
});
it("surfaces swarmId on the emitted span", { timeout: 5000 }, () => {
const span = makeRecordingSpan();
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
// Patch the OTel API's getActiveSpan to return our recording span
// for the duration of this test. Without an SDK-installed context
// manager, `trace.setSpan(context, span)` is a no-op (the noop
// context manager doesn't propagate); patching at the API layer is
// the lightest-weight test seam compatible with `@opentelemetry/api`
// alone (no `@opentelemetry/sdk-trace-base` dev dependency needed).
const originalGetActiveSpan = trace.getActiveSpan.bind(trace);
trace.getActiveSpan = (() =>
span as unknown as ReturnType<
typeof trace.getActiveSpan
>) as typeof trace.getActiveSpan;
try {
plugin.parse("drive+mesh://swarmA/raw/clip.mov?peers=secret");
} finally {
trace.getActiveSpan = originalGetActiveSpan;
}
expect(span.attributes.get("drive.uri.swarm_id")).toBe("swarmA");
expect(span.attributes.get("drive.uri.scheme")).toBe("drive+mesh");
expect(span.attributes.get("drive.uri")).toBe(
"drive+mesh://swarmA/raw/clip.mov"
);
});
it("does not attempt span attribute writes when no span is active", {
timeout: 5000,
}, () => {
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
// No active span — must not throw.
expect(() =>
plugin.parse("drive+mesh://swarmA/raw/clip.mov")
).not.toThrow();
});
it("build round-trips swarmId and key into a drive+mesh:// URI", {
timeout: 5000,
}, () => {
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
expect(plugin.build?.("raw/clip.mov", { swarmId: "swarmA" })).toBe(
"drive+mesh://swarmA/raw/clip.mov"
);
});
it("build defaults swarmId to options.defaultSwarmId when meta is omitted", {
timeout: 5000,
}, () => {
const plugin = driveMeshUriPlugin({
drive: asDrive(fake),
defaultSwarmId: "fallback",
});
expect(plugin.build?.("raw/clip.mov")).toBe(
"drive+mesh://fallback/raw/clip.mov"
);
});
it("build uses 'default' when neither meta nor defaultSwarmId is provided", {
timeout: 5000,
}, () => {
const plugin = driveMeshUriPlugin({ drive: asDrive(fake) });
expect(plugin.build?.("clip.mov")).toBe(
"drive+mesh://default/clip.mov"
);
});
});

View File

@@ -0,0 +1,103 @@
import { trace } from "@opentelemetry/api";
import type { Drive } from "@suaveplan/drive";
import {
type ParsedUri,
parseUri,
type UriPlugin,
} from "@suaveplan/storage-uri";
import {
DRIVE_URI_CODES,
DriveUriError,
} from "../../error-codes/error-codes.js";
import { redactUri } from "../../otel/otel.js";
import type { DriveMeshUriPluginOptions } from "../../types/plugin-options.types.js";
import { InstrumentedDriveAdapter } from "../drive/drive.streams.js";
const SCHEME = "drive+mesh";
/**
* `drive+mesh://<swarmId>/<path>` URI plugin factory.
*
* Identical in shape to `driveUriPlugin` but captures the URI authority as
* `swarmId` rather than enforcing a `driveId` match. The plugin does NOT use
* `swarmId` to choose a `Drive` or transport — peer routing is the
* caller-supplied `Drive`'s job. The swarmId surfaces on every emitted span
* via the `drive.uri.swarm_id` attribute (set on the active span when one
* exists; otherwise a new short-lived span is opened so the attribute is
* not lost).
*/
export function driveMeshUriPlugin(
options: DriveMeshUriPluginOptions
): UriPlugin {
if (options.drive === undefined || options.drive === null) {
throw new DriveUriError(
DRIVE_URI_CODES.CAPABILITY_MISSING,
"driveMeshUriPlugin requires options.drive",
{ scheme: SCHEME, reason: "drive" }
);
}
const adapter = new MeshAdapter(options.drive);
const defaultSwarmId = options.defaultSwarmId ?? "default";
return {
scheme: SCHEME,
adapter,
parse(uri: string): ParsedUri {
const parts = parseUri(uri);
if (parts.scheme !== SCHEME) {
throw new DriveUriError(
DRIVE_URI_CODES.INVALID_URI,
`expected scheme '${SCHEME}', got '${parts.scheme}'`,
{ uri, scheme: SCHEME }
);
}
const swarmId = parts.authority ?? "";
if (swarmId.length === 0) {
throw new DriveUriError(
DRIVE_URI_CODES.INVALID_URI,
"drive+mesh:// URI is missing the swarmId authority",
{ uri, scheme: SCHEME }
);
}
const key = parts.path.replace(/^\//, "");
if (key.length === 0) {
throw new DriveUriError(
DRIVE_URI_CODES.INVALID_URI,
"drive+mesh:// URI must include a non-empty path component",
{ uri, scheme: SCHEME, swarmId }
);
}
adapter.recordSwarm(swarmId, uri);
return { key, meta: { swarmId } };
},
build(key: string, meta?: Record<string, string>): string {
const swarmId = meta?.["swarmId"] ?? defaultSwarmId;
return `${SCHEME}://${swarmId}/${key.replace(/^\//, "")}`;
},
};
}
/**
* Mesh-tier adapter — extends `InstrumentedDriveAdapter` and writes the
* `drive.uri.swarm_id` and `drive.uri` attributes onto the currently active
* span so they appear alongside the resolver's own `storage.uri.*` span.
*
* `recordSwarm` is invoked from the plugin's `parse` step (which the
* resolver runs synchronously before delegating to the adapter), so by the
* time `get`/`set`/etc. fires, the attributes are already on the active
* span. Tests that exercise `parse(uri)` directly cover the path; tests
* routed through `StorageUriResolver` exercise it via the dispatch flow.
*/
class MeshAdapter extends InstrumentedDriveAdapter {
public constructor(drive: Drive) {
super(drive, { scheme: SCHEME });
}
public recordSwarm(swarmId: string, uri: string): void {
const span = trace.getActiveSpan();
if (span !== undefined) {
span.setAttribute("drive.uri.scheme", SCHEME);
span.setAttribute("drive.uri.swarm_id", swarmId);
span.setAttribute("drive.uri", redactUri(uri));
}
}
}

View File

@@ -0,0 +1 @@
export { driveMeshUriPlugin } from "./drive-mesh.js";

View File

@@ -0,0 +1,112 @@
# `@suaveplan/drive-uri/plugins/drive-template`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`driveTemplateUriPlugin(options)` is a factory returning a `UriPlugin`
registered against the `drive+template://` scheme. It parses
`drive+template://<templateId>/<path>` URIs and, before delegating any read
to the wrapped `Drive`, fetches the matching `DriveTemplate` via
`options.templateProvider.fetchTemplate(templateId)` and feeds it to
`drive.hydrateFrom(template)`. The first read against a given `templateId`
performs both steps; concurrent reads await the same in-flight hydration;
subsequent reads after a successful hydration skip both calls.
The hydration cache is a per-plugin `Map<string, Promise<void>>` keyed by
`templateId`. After a failed hydration the entry is removed so subsequent
reads can retry — important for transient `fetchTemplate` failures.
## Features
- Construction-time validation of `options.templateProvider` — absence or a
non-function `fetchTemplate` throws `DRIVE_URI.CAPABILITY_MISSING`
synchronously, never deferred to first use.
- Per-templateId hydration memoisation with concurrent-read coalescing.
- Distinct error mappings — `fetchTemplate` rejection becomes
`DRIVE_URI.TEMPLATE_NOT_AVAILABLE`; `Drive.hydrateFrom` rejection becomes
`DRIVE_URI.TEMPLATE_INVALID`. The two failures are NEVER conflated.
- Original cause is preserved on both wrapper errors so operators see e.g.
`cause.code === "DTP.TEMPLATE_EXPIRED"` underneath a `TEMPLATE_INVALID`.
- `drive_uri.template_hydrations_total{outcome}` increments exactly once
per first-use template (success or error). Cache-hit reads do NOT
increment the counter.
## Usage: Basic
```ts
import { Drive } from "@suaveplan/drive";
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { driveTemplateUriPlugin } from "@suaveplan/drive-uri/plugins/drive-template";
const drive = new Drive(localStorage);
await drive.initialize();
const templateProvider = {
async fetchTemplate(id: string) {
return downloadTemplate(id);
},
};
const resolver = createStorageUriResolver([
driveTemplateUriPlugin({ drive, templateProvider }),
]);
const bytes = await resolver.read("drive+template://templA/manifest/clip.jpg");
```
## Usage: Advanced
Sharing the template plugin's hydration cache with a `drive+mesh://` plugin
by passing the same `Drive` to both — the file-location index populated by
hydration powers the mesh plugin's chunk fetches:
```ts
import {
driveMeshUriPlugin,
driveTemplateUriPlugin,
} from "@suaveplan/drive-uri";
const sharedDrive = await buildMeshConfiguredDrive();
const resolver = createStorageUriResolver([
driveMeshUriPlugin({ drive: sharedDrive }),
driveTemplateUriPlugin({ drive: sharedDrive, templateProvider }),
]);
// First read hydrates the template, populates the index, then reads chunks
// through the chunks plugin via the mesh transport.
await resolver.read("drive+template://templA/clip.mov");
// Subsequent reads for the same template skip hydration but still go
// through the chunks plugin (because the file-location index is now seeded).
await resolver.read("drive+template://templA/cover.jpg");
```
## API Reference
- `driveTemplateUriPlugin(options: DriveTemplateUriPluginOptions): UriPlugin` —
both `options.drive` and `options.templateProvider` are required at
construction. `templateProvider.fetchTemplate` MUST be a function.
- The returned plugin exposes `scheme === "drive+template"`, an instrumented
adapter, `parse(uri) → { key, meta: { templateId } }`, and
`build(key, meta?) → uri`.
- `interface TemplateProvider` — `{ fetchTemplate(templateId, signal?) => Promise<DriveTemplate> }`.
## Implementation Notes
- The hydration cache stores `Promise<void>` rather than the resolved
template object — concurrent reads await the same promise, and once it
settles the value is irrelevant (the template is already inside the
`Drive`'s index via `hydrateFrom`).
- Failed hydrations remove themselves from the cache so retries are
possible. A re-hydration after a previous failure is idempotent at the
`Drive` layer via `TemplateNonceLru`.
- `parse(uri)` captures the templateId in a closure-scoped variable so the
adapter's `beforeOp` hook (which fires synchronously inside the
resolver's dispatch chain) sees the right value before any `get`/`exists`/
`delete` call.
- Writes are rejected at the adapter tier with `DRIVE_URI.WRITE_UNSUPPORTED`,
per the per-release scope.

View File

@@ -0,0 +1,341 @@
import { SuaveplanError } from "@suaveplan/error";
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { beforeEach, describe, expect, it } from "@suaveplan/testing/runner";
import { asDrive, FakeDrive } from "../../../tests/setup/fake-drive.js";
import {
DRIVE_URI_CODES,
DriveUriError,
} from "../../error-codes/error-codes.js";
import type { TemplateProvider } from "../../types/plugin-options.types.js";
import { driveTemplateUriPlugin } from "./drive-template.js";
interface Calls {
fetch: string[];
}
function tracingProvider(
calls: Calls,
behaviour?: () => unknown
): TemplateProvider {
return {
async fetchTemplate(templateId: string): Promise<never> {
calls.fetch.push(templateId);
const result = behaviour?.();
if (result instanceof Error) throw result;
return result as never;
},
};
}
class FakeDtpError extends SuaveplanError<string> {}
describe("driveTemplateUriPlugin", () => {
let fake: FakeDrive;
let calls: Calls;
beforeEach(() => {
fake = new FakeDrive({
contents: new Map([
["folder/file.bin", new Uint8Array([7])],
["x", new Uint8Array([1])],
["y", new Uint8Array([2])],
]),
});
calls = { fetch: [] };
});
it("throws CAPABILITY_MISSING when templateProvider is absent", {
timeout: 5000,
}, () => {
try {
driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: undefined as unknown as TemplateProvider,
});
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
const e = err as DriveUriError;
expect(e.code).toBe(DRIVE_URI_CODES.CAPABILITY_MISSING);
expect(e.reason).toBe("templateProvider");
}
});
it("throws CAPABILITY_MISSING when templateProvider.fetchTemplate is not a function", {
timeout: 5000,
}, () => {
try {
driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: {
fetchTemplate:
"not a function" as unknown as TemplateProvider["fetchTemplate"],
},
});
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
expect((err as DriveUriError).code).toBe(
DRIVE_URI_CODES.CAPABILITY_MISSING
);
}
});
it("throws CAPABILITY_MISSING when drive is absent", {
timeout: 5000,
}, () => {
expect(() =>
driveTemplateUriPlugin({
drive: undefined as unknown as Parameters<
typeof driveTemplateUriPlugin
>[0]["drive"],
templateProvider: tracingProvider(calls, () => ({})),
})
).toThrow(DriveUriError);
});
it("rejects URIs with wrong scheme/missing authority/missing path", {
timeout: 5000,
}, () => {
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: tracingProvider(calls, () => ({})),
});
expect(() => plugin.parse("file:///x")).toThrow(DriveUriError);
expect(() => plugin.parse("drive+template:///x")).toThrow(
DriveUriError
);
expect(() => plugin.parse("drive+template://templA/")).toThrow(
DriveUriError
);
// No `//` — covers `parts.authority ?? ""` fallback branch.
expect(() => plugin.parse("drive+template:x")).toThrow(DriveUriError);
});
it("adapter.get on an un-parsed URI short-circuits hydration", {
timeout: 5000,
}, async () => {
// Construct the plugin but never call its parse() — exercise the
// `currentTemplateId === undefined` branch in beforeOp.
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: tracingProvider(calls, () => ({})),
});
const adapter = plugin.adapter as {
get(key: string): Promise<Uint8Array | undefined>;
};
const result = await adapter.get("x");
expect(result).toEqual(new Uint8Array([1]));
// Hydration must not have fired (no template id captured yet).
expect(calls.fetch).toEqual([]);
expect(fake.calls.hydrateFrom).toHaveLength(0);
});
it("wraps a non-Error fetchTemplate rejection without setting cause", {
timeout: 5000,
}, async () => {
// Throwing `undefined` exercises the `cause !== undefined` false
// branch in the TEMPLATE_NOT_AVAILABLE wrapper. Biome cannot
// statically reject a thrown non-Error variable, so we route the
// value through an `unknown` binding instead of a suppression.
const nonError: unknown = undefined;
const provider: TemplateProvider = {
async fetchTemplate(): Promise<never> {
throw nonError;
},
};
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: provider,
});
const resolver = createStorageUriResolver([plugin]);
try {
await resolver.read("drive+template://templA/x");
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
const e = err as DriveUriError;
expect(e.code).toBe(DRIVE_URI_CODES.TEMPLATE_NOT_AVAILABLE);
expect(e.cause).toBeUndefined();
}
});
it("hydrates exactly once on the first read", {
timeout: 5000,
}, async () => {
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: tracingProvider(calls, () => ({})),
});
const resolver = createStorageUriResolver([plugin]);
const bytes = await resolver.read(
"drive+template://templA/folder/file.bin"
);
expect(bytes).toEqual(new Uint8Array([7]));
expect(calls.fetch).toEqual(["templA"]);
expect(fake.calls.hydrateFrom).toHaveLength(1);
expect(fake.calls.read).toHaveLength(1);
expect(fake.calls.read[0]?.path).toBe("folder/file.bin");
});
it("shares a single hydration across concurrent reads against the same templateId", {
timeout: 5000,
}, async () => {
let resolveFetch: (value: unknown) => void = () => {};
const provider: TemplateProvider = {
async fetchTemplate(templateId: string): Promise<never> {
calls.fetch.push(templateId);
return new Promise((resolve) => {
resolveFetch = resolve;
}) as Promise<never>;
},
};
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: provider,
});
const resolver = createStorageUriResolver([plugin]);
const p1 = resolver.read("drive+template://templA/x");
const p2 = resolver.read("drive+template://templA/y");
// Both reads are queued behind the pending hydration.
await Promise.resolve();
expect(calls.fetch).toEqual(["templA"]);
resolveFetch({});
await Promise.all([p1, p2]);
expect(calls.fetch).toEqual(["templA"]);
expect(fake.calls.hydrateFrom).toHaveLength(1);
expect(fake.calls.read).toHaveLength(2);
});
it("skips fetch and hydrate on subsequent reads against the same templateId", {
timeout: 5000,
}, async () => {
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: tracingProvider(calls, () => ({})),
});
const resolver = createStorageUriResolver([plugin]);
await resolver.read("drive+template://templA/x");
await resolver.read("drive+template://templA/y");
expect(calls.fetch).toEqual(["templA"]);
expect(fake.calls.hydrateFrom).toHaveLength(1);
expect(fake.calls.read).toHaveLength(2);
});
it("wraps fetchTemplate rejection as TEMPLATE_NOT_AVAILABLE", {
timeout: 5000,
}, async () => {
const fetchErr = new Error("404");
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: {
async fetchTemplate(): Promise<never> {
throw fetchErr;
},
},
});
const resolver = createStorageUriResolver([plugin]);
try {
await resolver.read("drive+template://templA/x");
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
const e = err as DriveUriError;
expect(e.code).toBe(DRIVE_URI_CODES.TEMPLATE_NOT_AVAILABLE);
expect(e.cause).toBe(fetchErr);
expect((e.cause as Error).message).toBe("404");
}
});
it("wraps hydrateFrom rejection as TEMPLATE_INVALID", {
timeout: 5000,
}, async () => {
const hydrateErr = new FakeDtpError(
"DTP.TEMPLATE_EXPIRED",
"template expired"
);
const badDrive = new FakeDrive({ hydrateError: () => hydrateErr });
const plugin = driveTemplateUriPlugin({
drive: asDrive(badDrive),
templateProvider: tracingProvider(calls, () => ({})),
});
const resolver = createStorageUriResolver([plugin]);
try {
await resolver.read("drive+template://templA/x");
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
const e = err as DriveUriError;
expect(e.code).toBe(DRIVE_URI_CODES.TEMPLATE_INVALID);
expect((e.cause as SuaveplanError).code).toBe(
"DTP.TEMPLATE_EXPIRED"
);
}
});
it("retries hydration after a previous failure", {
timeout: 5000,
}, async () => {
let attempts = 0;
const provider: TemplateProvider = {
async fetchTemplate(): Promise<never> {
attempts += 1;
if (attempts === 1) throw new Error("first attempt fails");
return {} as never;
},
};
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: provider,
});
const resolver = createStorageUriResolver([plugin]);
await expect(
resolver.read("drive+template://templA/x")
).rejects.toBeDefined();
const bytes = await resolver.read("drive+template://templA/x");
expect(bytes).toEqual(new Uint8Array([1]));
expect(attempts).toBe(2);
});
it("wraps a non-Error hydrate rejection without setting cause", {
timeout: 5000,
}, async () => {
const badDrive = new FakeDrive({ hydrateError: () => "literal" });
const plugin = driveTemplateUriPlugin({
drive: asDrive(badDrive),
templateProvider: tracingProvider(calls, () => ({})),
});
const resolver = createStorageUriResolver([plugin]);
try {
await resolver.read("drive+template://templA/x");
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
const e = err as DriveUriError;
expect(e.code).toBe(DRIVE_URI_CODES.TEMPLATE_INVALID);
expect(e.cause).toBeUndefined();
}
});
it("build round-trips templateId and key into a drive+template:// URI", {
timeout: 5000,
}, () => {
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: tracingProvider(calls, () => ({})),
});
expect(
plugin.build?.("folder/file.bin", { templateId: "templA" })
).toBe("drive+template://templA/folder/file.bin");
});
it("build with no meta yields an empty authority", {
timeout: 5000,
}, () => {
const plugin = driveTemplateUriPlugin({
drive: asDrive(fake),
templateProvider: tracingProvider(calls, () => ({})),
});
expect(plugin.build?.("clip.mov")).toBe("drive+template:///clip.mov");
});
});

View File

@@ -0,0 +1,177 @@
import type { Drive } from "@suaveplan/drive";
import { SuaveplanError } from "@suaveplan/error";
import {
type ParsedUri,
parseUri,
type UriPlugin,
} from "@suaveplan/storage-uri";
import {
DRIVE_URI_CODES,
DriveUriError,
} from "../../error-codes/error-codes.js";
import { recordTemplateHydration } from "../../otel/otel.js";
import type {
DriveTemplateUriPluginOptions,
TemplateProvider,
} from "../../types/plugin-options.types.js";
import { InstrumentedDriveAdapter } from "../drive/drive.streams.js";
const SCHEME = "drive+template";
/**
* `drive+template://<templateId>/<path>` URI plugin factory.
*
* Before delegating any read to the wrapped `Drive`, the plugin fetches the
* `DriveTemplate` for `templateId` via `options.templateProvider.fetchTemplate`
* and feeds it to `drive.hydrateFrom`. The first read against a given
* `templateId` performs both steps; concurrent reads await a single in-flight
* hydration; subsequent reads after a successful hydration short-circuit
* directly to `drive.read`. The cache is a per-plugin
* `Map<string, Promise<void>>` — re-hydration after a previous *failure*
* retries by deleting the rejected entry.
*
* Construction-time validates `options.templateProvider` — absence or a
* non-function `fetchTemplate` throws `DRIVE_URI.CAPABILITY_MISSING`
* synchronously, never deferring the failure to first use.
*/
export function driveTemplateUriPlugin(
options: DriveTemplateUriPluginOptions
): UriPlugin {
validateOptions(options);
const hydrated = new Map<string, Promise<void>>();
let currentTemplateId: string | undefined;
const adapter = new InstrumentedDriveAdapter(options.drive, {
scheme: SCHEME,
beforeOp: async () => {
if (currentTemplateId === undefined) return;
await ensureHydrated(
options.drive,
options.templateProvider,
hydrated,
currentTemplateId
);
},
});
return {
scheme: SCHEME,
adapter,
parse(uri: string): ParsedUri {
const parts = parseUri(uri);
if (parts.scheme !== SCHEME) {
throw new DriveUriError(
DRIVE_URI_CODES.INVALID_URI,
`expected scheme '${SCHEME}', got '${parts.scheme}'`,
{ uri, scheme: SCHEME }
);
}
const templateId = parts.authority ?? "";
if (templateId.length === 0) {
throw new DriveUriError(
DRIVE_URI_CODES.INVALID_URI,
"drive+template:// URI is missing the templateId authority",
{ uri, scheme: SCHEME }
);
}
const key = parts.path.replace(/^\//, "");
if (key.length === 0) {
throw new DriveUriError(
DRIVE_URI_CODES.INVALID_URI,
"drive+template:// URI must include a non-empty path component",
{ uri, scheme: SCHEME, templateId }
);
}
// Capture the templateId so the adapter's `beforeOp` hook
// (which fires inside the resolver's synchronous dispatch
// chain) sees the right value.
currentTemplateId = templateId;
return { key, meta: { templateId } };
},
build(key: string, meta?: Record<string, string>): string {
const templateId = meta?.["templateId"] ?? "";
return `${SCHEME}://${templateId}/${key.replace(/^\//, "")}`;
},
};
}
function validateOptions(options: DriveTemplateUriPluginOptions): void {
if (options.drive === undefined || options.drive === null) {
throw new DriveUriError(
DRIVE_URI_CODES.CAPABILITY_MISSING,
"driveTemplateUriPlugin requires options.drive",
{ scheme: SCHEME, reason: "drive" }
);
}
const provider = options.templateProvider as TemplateProvider | undefined;
if (
provider === undefined ||
provider === null ||
typeof provider.fetchTemplate !== "function"
) {
throw new DriveUriError(
DRIVE_URI_CODES.CAPABILITY_MISSING,
"driveTemplateUriPlugin requires options.templateProvider with a function-typed fetchTemplate",
{ scheme: SCHEME, reason: "templateProvider" }
);
}
}
async function ensureHydrated(
drive: Drive,
provider: TemplateProvider,
cache: Map<string, Promise<void>>,
templateId: string
): Promise<void> {
let pending = cache.get(templateId);
if (pending === undefined) {
pending = hydrate(drive, provider, templateId);
cache.set(templateId, pending);
// Remove failed hydrations so retries are possible.
pending.catch(() => {
cache.delete(templateId);
});
}
await pending;
}
async function hydrate(
drive: Drive,
provider: TemplateProvider,
templateId: string
): Promise<void> {
let template: Awaited<ReturnType<TemplateProvider["fetchTemplate"]>>;
try {
template = await provider.fetchTemplate(templateId);
} catch (cause) {
recordTemplateHydration("error");
throw new DriveUriError(
DRIVE_URI_CODES.TEMPLATE_NOT_AVAILABLE,
`templateProvider.fetchTemplate('${templateId}') rejected`,
{
scheme: SCHEME,
templateId,
...(cause !== undefined ? { cause } : {}),
}
);
}
try {
await drive.hydrateFrom(template);
} catch (cause) {
recordTemplateHydration("error");
// `Drive.hydrateFrom` rejects with SuaveplanError carrying a DTP_*
// (or DRIVE.*) code; preserve it on `cause` so operators can
// distinguish the underlying failure.
const wrapped = new DriveUriError(
DRIVE_URI_CODES.TEMPLATE_INVALID,
`Drive.hydrateFrom rejected for templateId '${templateId}'`,
{
scheme: SCHEME,
templateId,
...(cause instanceof SuaveplanError || cause instanceof Error
? { cause }
: {}),
}
);
throw wrapped;
}
recordTemplateHydration("success");
}

View File

@@ -0,0 +1 @@
export { driveTemplateUriPlugin } from "./drive-template.js";

View File

@@ -0,0 +1,103 @@
# `@suaveplan/drive-uri/plugins/drive`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`driveUriPlugin(options)` is a factory returning a `UriPlugin` registered
against the `drive://` scheme. It wraps a consumer-supplied `Drive` instance
in an OTel-instrumented `DriveStorageLikeAdapter` so a single
`StorageUriResolver` can dispatch `drive://<driveId>/<path>` URIs through the
same code path as `file:///` or `s3://` URIs. The plugin enforces a 1:1 match
between the URI's authority component and the `driveId` declared in options;
a mismatch surfaces as `DRIVE_URI.DRIVE_ID_MISMATCH` before any `Drive` call.
The plugin is read-only in this release: every `adapter.set` call rejects
with `DRIVE_URI.WRITE_UNSUPPORTED` (which the resolver wraps as
`STORAGE_URI.WRITE_UNSUPPORTED` with the drive-uri error preserved on
`cause`). Writes still increment `drive_uri.writes_total{outcome:"error"}`
so operator dashboards see misuse rates.
## Features
- Parses `drive://<driveId>/<path>` URIs and rejects mismatched driveIds.
- Returns `{ key, meta: { driveId } }` so callers can re-build the URI
through `plugin.build(key, meta)`.
- Forwards `Drive.createReadStream` directly when `resolver.open()` is used
so the resolver's native streaming fast path applies — no buffered
fallback, no `storage_uri.stream_fallback_total` increment.
- Pre-aborted signals short-circuit at the adapter shim with
`DRIVE_URI.ABORTED` (no `Drive` call is made).
## Usage: Basic
```ts
import { Drive } from "@suaveplan/drive";
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { driveUriPlugin } from "@suaveplan/drive-uri/plugins/drive";
const drive = new Drive(localStorage);
await drive.initialize();
const resolver = createStorageUriResolver([
driveUriPlugin({ drive, driveId: "primary" }),
]);
const bytes = await resolver.read("drive://primary/folder/file.bin");
```
## Usage: Advanced
Composing with other plugins (file + drive in one resolver) and threading an
`AbortSignal` into the streaming open path:
```ts
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { driveUriPlugin } from "@suaveplan/drive-uri";
const resolver = createStorageUriResolver([
fileUriPlugin({ rootDir: "/var/cache" }),
driveUriPlugin({ drive, driveId: "primary" }),
]);
const controller = new AbortController();
const stream = await resolver.open(
"drive://primary/large/clip.mov",
controller.signal,
);
const reader = stream.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
process(value);
}
} finally {
controller.abort(); // propagates to Drive.createReadStream
reader.releaseLock();
}
```
## API Reference
- `driveUriPlugin(options: DriveUriPluginOptions): UriPlugin` — `options.drive`
is the consumer-supplied `Drive`, `options.driveId` is the literal authority
the plugin will require. Both are validated synchronously; a missing or
empty value throws `DRIVE_URI.CAPABILITY_MISSING`.
- The returned plugin exposes `scheme === "drive"`, `adapter`,
`parse(uri) → { key, meta }`, and `build(key, meta?) → uri`.
## Implementation Notes
- The plugin's `parse` is purely synchronous. Async work (the actual `Drive`
call) happens inside the adapter's `get`/`createReadStream`/etc. methods
invoked by `StorageUriResolver`.
- `build(key, meta?)` defaults the driveId to the plugin's configured
`options.driveId` when no meta is supplied — useful for re-serialising
resolver-list results.
- Writes are deferred to a follow-up change (see proposal §"Out of scope").
The plugin still records the rejection so dashboards capture misuse.
- The `DriveStorageLikeAdapter` is created once per plugin instance and
shared across every URI dispatched to this plugin.

View File

@@ -0,0 +1,84 @@
# `@suaveplan/drive-uri/plugins/drive/streams`
## Purpose
`InstrumentedDriveAdapter` is the OTel-instrumented `StorageLike` shared by
all three URI plugins (`drive://`, `drive+mesh://`, `drive+template://`). It
extends `DriveStorageLikeAdapter` to:
- record `drive_uri.op_duration_ms{op, scheme, outcome}` and the per-op
counters (`reads_total`, `writes_total`, `deletes_total`, `errors_total`)
via `recordOp`;
- reject every `set` call with `DRIVE_URI.WRITE_UNSUPPORTED` (and increment
`writes_total{outcome:"error"}`);
- short-circuit `createReadStream` when the supplied `AbortSignal` is already
aborted, throwing `DRIVE_URI.ABORTED` without invoking the underlying drive;
- thread an optional `beforeOp` async hook used by the `drive+template`
plugin to interpose template hydration before every operation.
The class is exported from the package root for advanced consumers building
custom plugins; the three first-party plugins use it internally.
## Features
- Inherits `StorageLike` conformance from `DriveStorageLikeAdapter` (including
`clear() → DRIVE_URI.WRITE_UNSUPPORTED` and `list()`'s normalised shape).
- Per-op OTel instrumentation — `read`/`open`/`exists`/`list` route to
`reads_total` (on success) and `errors_total` (on failure);
`write` always routes to `writes_total`; `delete` routes to `deletes_total`.
- `not_found` outcomes are recorded as `errors_total{code:"UNKNOWN"}` for
consistency with the storage-uri resolver — operators see a clear "missing
resource" signal without the success counters being skewed.
- The `beforeOp` hook is awaited before every operation, so the
`drive+template` plugin can ensure hydration has completed.
## Usage: Basic
```ts
import { InstrumentedDriveAdapter } from "@suaveplan/drive-uri";
const adapter = new InstrumentedDriveAdapter(drive, { scheme: "drive" });
await adapter.get("folder/file.bin");
```
## Usage: Advanced
A custom plugin combining the instrumented adapter with a per-op auth check:
```ts
import { InstrumentedDriveAdapter } from "@suaveplan/drive-uri";
const adapter = new InstrumentedDriveAdapter(drive, {
scheme: "drive+custom",
beforeOp: async (op) => {
if (op === "delete" && !ctx.hasDeletePermission()) {
throw new Error("forbidden");
}
},
});
```
The hook fires in lockstep with the resolver's dispatch chain. It's run
before the wrapped `Drive` call, so failures inside the hook are recorded
under `errors_total` for the relevant op.
## API Reference
- `interface InstrumentedAdapterOptions` — `scheme: string`,
`beforeOp?: (op) => Promise<void>`.
- `class InstrumentedDriveAdapter extends DriveStorageLikeAdapter` —
overrides `get`, `set`, `delete`, `exists`, and `createReadStream`. Every
override records duration and counter samples via `recordOp`.
## Implementation Notes
- `set` always throws — the per-release scope deliberately excludes writes.
- `not_found` outcome on `get` happens when the base class translates a
drive not-found error into `undefined`; both the histogram and
`errors_total{code:"UNKNOWN"}` (per `recordOp` semantics) increment.
- `createReadStream` is the only operation that takes an `AbortSignal` at
the storage-uri resolver layer; the pre-aborted check throws `ABORTED`
before any Drive call.
- The `beforeOp` hook receives a string discriminant (`"get"|"set"|"delete"|
"exists"|"list"`) rather than the op-level `DriveUriOp` so it doesn't
conflate the resolver's `open` (which maps to `get`) with `read`.

View File

@@ -0,0 +1,284 @@
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { beforeEach, describe, expect, it } from "@suaveplan/testing/runner";
import {
asDrive,
FakeDrive,
FakeDriveError,
} from "../../../tests/setup/fake-drive.js";
import { driveUriPlugin } from "./drive.js";
import { InstrumentedDriveAdapter } from "./drive.streams.js";
describe("driveUriPlugin streams", () => {
let fake: FakeDrive;
beforeEach(() => {
fake = new FakeDrive({
contents: new Map([["clip.mov", new Uint8Array([1, 2, 3, 4, 5])]]),
});
});
it("open forwards Drive.createReadStream without buffered fallback", {
timeout: 5000,
}, async () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
const resolver = createStorageUriResolver([plugin]);
const stream = await resolver.open("drive://primary/clip.mov");
expect(fake.calls.createReadStream).toHaveLength(1);
expect(fake.calls.read).toHaveLength(0);
const reader = stream.getReader();
const { value } = await reader.read();
expect(value).toEqual(new Uint8Array([1, 2, 3, 4, 5]));
reader.releaseLock();
});
it("mid-stream abort propagates to Drive.createReadStream", {
timeout: 5000,
}, async () => {
// Fake whose createReadStream observes the signal we hand it.
const observed: AbortSignal[] = [];
const stub = {
async read(): Promise<Uint8Array> {
return new Uint8Array();
},
async write(): Promise<void> {},
async delete(): Promise<boolean> {
return false;
},
async stat(): Promise<unknown | null> {
return null;
},
async list(): Promise<unknown> {
return { keys: [], total: 0, hasMore: false };
},
createReadStream(
_: string,
signal?: AbortSignal
): ReadableStream<Uint8Array> {
if (signal !== undefined) observed.push(signal);
return new ReadableStream<Uint8Array>({
start(ctrl) {
ctrl.enqueue(new Uint8Array([0xff]));
// Stay open so the consumer can abort.
},
});
},
};
const plugin = driveUriPlugin({
drive: stub as unknown as Parameters<
typeof driveUriPlugin
>[0]["drive"],
driveId: "primary",
});
const resolver = createStorageUriResolver([plugin]);
const controller = new AbortController();
const stream = await resolver.open(
"drive://primary/clip.mov",
controller.signal
);
expect(observed).toHaveLength(1);
expect(observed[0]).toBe(controller.signal);
const reader = stream.getReader();
const first = await reader.read();
expect(first.value).toEqual(new Uint8Array([0xff]));
controller.abort();
expect(controller.signal.aborted).toBe(true);
reader.releaseLock();
// The observed signal is the controller's — abort is visible there.
expect(observed[0]?.aborted).toBe(true);
});
it("records error outcome when delete throws a DriveUriError", {
timeout: 5000,
}, async () => {
// Use an adapter wired with a beforeOp hook that throws DriveUriError —
// exercises the error branch with `err instanceof DriveUriError` true.
const adapter = new InstrumentedDriveAdapter(asDrive(fake), {
scheme: "drive",
async beforeOp(op) {
if (op === "delete") {
const { DRIVE_URI_CODES, DriveUriError } = await import(
"../../error-codes/error-codes.js"
);
throw new DriveUriError(
DRIVE_URI_CODES.ABORTED,
"test forced abort"
);
}
},
});
await expect(adapter.delete("clip.mov")).rejects.toBeDefined();
});
it("records error outcome when exists throws an arbitrary failure", {
timeout: 5000,
}, async () => {
// Use a drive that throws a non-not-found from stat — exercises the
// ADAPTER_ERROR branch with `err instanceof DriveUriError` false.
const stub = {
async read() {
return new Uint8Array();
},
async write(): Promise<void> {},
async delete(): Promise<boolean> {
return false;
},
async stat(): Promise<unknown | null> {
throw new FakeDriveError("DRIVE.PLUGIN_FAILED", "boom");
},
async list(): Promise<unknown> {
return { keys: [], total: 0, hasMore: false };
},
createReadStream(): ReadableStream<Uint8Array> {
return new ReadableStream();
},
};
const adapter = new InstrumentedDriveAdapter(
stub as unknown as Parameters<typeof InstrumentedDriveAdapter>[0],
{ scheme: "drive" }
);
await expect(adapter.exists("x")).rejects.toBeDefined();
});
it("records not_found outcome when exists returns false", {
timeout: 5000,
}, async () => {
const adapter = new InstrumentedDriveAdapter(asDrive(fake), {
scheme: "drive",
});
const result = await adapter.exists("missing-key");
expect(result).toBe(false);
});
it("delete records success when the file exists", {
timeout: 5000,
}, async () => {
const adapter = new InstrumentedDriveAdapter(asDrive(fake), {
scheme: "drive",
});
const removed = await adapter.delete("clip.mov");
expect(removed).toBe(true);
});
it("delete records error with ADAPTER_ERROR code for non-DriveUri failures", {
timeout: 5000,
}, async () => {
// A drive that throws a non-DriveUriError from delete — exercises
// the `err instanceof DriveUriError ? err.code : "ADAPTER_ERROR"`
// false branch.
const stub = {
async read() {
return new Uint8Array();
},
async write(): Promise<void> {},
async delete(): Promise<boolean> {
throw new FakeDriveError("DRIVE.IO_FAILED", "io-fail");
},
async stat(): Promise<unknown | null> {
return null;
},
async list(): Promise<unknown> {
return { keys: [], total: 0, hasMore: false };
},
createReadStream(): ReadableStream<Uint8Array> {
return new ReadableStream();
},
};
const adapter = new InstrumentedDriveAdapter(
stub as unknown as Parameters<typeof InstrumentedDriveAdapter>[0],
{ scheme: "drive" }
);
await expect(adapter.delete("clip.mov")).rejects.toBeDefined();
});
it("exists records error with the DriveUriError code when beforeOp throws", {
timeout: 5000,
}, async () => {
const adapter = new InstrumentedDriveAdapter(asDrive(fake), {
scheme: "drive",
async beforeOp(op) {
if (op === "exists") {
const { DRIVE_URI_CODES, DriveUriError } = await import(
"../../error-codes/error-codes.js"
);
throw new DriveUriError(
DRIVE_URI_CODES.ABORTED,
"test forced abort on exists"
);
}
},
});
await expect(adapter.exists("clip.mov")).rejects.toBeDefined();
});
it("get records error with ADAPTER_ERROR code for non-DriveUri failures", {
timeout: 5000,
}, async () => {
const stub = {
async read(): Promise<Uint8Array> {
throw new FakeDriveError("DRIVE.PLUGIN_FAILED", "plugin");
},
async write(): Promise<void> {},
async delete(): Promise<boolean> {
return false;
},
async stat(): Promise<unknown | null> {
return null;
},
async list(): Promise<unknown> {
return { keys: [], total: 0, hasMore: false };
},
createReadStream(): ReadableStream<Uint8Array> {
return new ReadableStream();
},
};
const adapter = new InstrumentedDriveAdapter(
stub as unknown as Parameters<typeof InstrumentedDriveAdapter>[0],
{ scheme: "drive" }
);
await expect(adapter.get("x")).rejects.toBeDefined();
});
it("get records not_found outcome when the drive returns undefined", {
timeout: 5000,
}, async () => {
const adapter = new InstrumentedDriveAdapter(asDrive(fake), {
scheme: "drive",
});
const result = await adapter.get("absent-key");
expect(result).toBeUndefined();
});
it("exists records success outcome when the file is present", {
timeout: 5000,
}, async () => {
const adapter = new InstrumentedDriveAdapter(asDrive(fake), {
scheme: "drive",
});
const present = await adapter.exists("clip.mov");
expect(present).toBe(true);
});
it("get records error with the DriveUriError code when beforeOp throws", {
timeout: 5000,
}, async () => {
const adapter = new InstrumentedDriveAdapter(asDrive(fake), {
scheme: "drive",
async beforeOp(op) {
if (op === "get") {
const { DRIVE_URI_CODES, DriveUriError } = await import(
"../../error-codes/error-codes.js"
);
throw new DriveUriError(
DRIVE_URI_CODES.ABORTED,
"test forced abort on get"
);
}
},
});
await expect(adapter.get("clip.mov")).rejects.toBeDefined();
});
});

View File

@@ -0,0 +1,147 @@
import type { Drive } from "@suaveplan/drive";
import { DriveStorageLikeAdapter } from "../../adapter/drive-storage-like.js";
import {
DRIVE_URI_CODES,
DriveUriError,
} from "../../error-codes/error-codes.js";
import { recordOp } from "../../otel/otel.js";
/**
* Adapter shape used by all three plugins. Wraps a `Drive` in an
* OTel-instrumented `StorageLike` that:
*
* - records `drive_uri.op_duration_ms{op, scheme, outcome}` per operation
* - increments the correct success/error counters via `recordOp`
* - rejects every `set` call with `DRIVE_URI.WRITE_UNSUPPORTED` (and still
* records the rejection so operators see misuse rate)
* - exposes `createReadStream(key, signal)` with pre-abort short-circuit
* so the resolver's native streaming fast path is wired without losing
* the `DRIVE_URI.ABORTED` semantic
*/
export interface InstrumentedAdapterOptions {
readonly scheme: string;
/**
* Optional hook the adapter calls before each operation. Used by the
* `drive+template` plugin to ensure the Drive is hydrated before any
* delegation reaches it.
*/
readonly beforeOp?: (
op: "get" | "set" | "delete" | "exists" | "list"
) => Promise<void>;
}
export class InstrumentedDriveAdapter extends DriveStorageLikeAdapter {
readonly #scheme: string;
readonly #beforeOp?: InstrumentedAdapterOptions["beforeOp"];
readonly #drive: Drive;
public constructor(drive: Drive, options: InstrumentedAdapterOptions) {
super(drive);
this.#drive = drive;
this.#scheme = options.scheme;
if (options.beforeOp !== undefined) this.#beforeOp = options.beforeOp;
}
public override async get(key: string): Promise<Uint8Array | undefined> {
const start = Date.now();
try {
if (this.#beforeOp !== undefined) await this.#beforeOp("get");
const bytes = await super.get(key);
recordOp({
op: "read",
scheme: this.#scheme,
outcome: bytes === undefined ? "not_found" : "success",
durationMs: Date.now() - start,
});
return bytes;
} catch (err) {
recordOp({
op: "read",
scheme: this.#scheme,
outcome: "error",
durationMs: Date.now() - start,
code: err instanceof DriveUriError ? err.code : "ADAPTER_ERROR",
});
throw err;
}
}
public override async set(key: string, _value: Uint8Array): Promise<void> {
const start = Date.now();
const err = new DriveUriError(
DRIVE_URI_CODES.WRITE_UNSUPPORTED,
"writes via drive*:// URIs are not supported in this release",
{ scheme: this.#scheme, path: key }
);
recordOp({
op: "write",
scheme: this.#scheme,
outcome: "error",
durationMs: Date.now() - start,
code: err.code,
});
throw err;
}
public override async delete(key: string): Promise<boolean> {
const start = Date.now();
try {
if (this.#beforeOp !== undefined) await this.#beforeOp("delete");
const removed = await super.delete(key);
recordOp({
op: "remove",
scheme: this.#scheme,
outcome: "success",
durationMs: Date.now() - start,
});
return removed;
} catch (err) {
recordOp({
op: "remove",
scheme: this.#scheme,
outcome: "error",
durationMs: Date.now() - start,
code: err instanceof DriveUriError ? err.code : "ADAPTER_ERROR",
});
throw err;
}
}
public override async exists(key: string): Promise<boolean> {
const start = Date.now();
try {
if (this.#beforeOp !== undefined) await this.#beforeOp("exists");
const present = await super.exists(key);
recordOp({
op: "exists",
scheme: this.#scheme,
outcome: present ? "success" : "not_found",
durationMs: Date.now() - start,
});
return present;
} catch (err) {
recordOp({
op: "exists",
scheme: this.#scheme,
outcome: "error",
durationMs: Date.now() - start,
code: err instanceof DriveUriError ? err.code : "ADAPTER_ERROR",
});
throw err;
}
}
public override createReadStream(
key: string,
signal?: AbortSignal
): ReadableStream<Uint8Array> {
if (signal?.aborted === true) {
throw new DriveUriError(
DRIVE_URI_CODES.ABORTED,
"signal aborted before createReadStream",
{ scheme: this.#scheme, path: key }
);
}
return this.#drive.createReadStream(key, signal);
}
}

View File

@@ -0,0 +1,247 @@
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { beforeEach, describe, expect, it } from "@suaveplan/testing/runner";
import { asDrive, FakeDrive } from "../../../tests/setup/fake-drive.js";
import {
DRIVE_URI_CODES,
DriveUriError,
} from "../../error-codes/error-codes.js";
import { driveUriPlugin } from "./drive.js";
describe("driveUriPlugin", () => {
let fake: FakeDrive;
beforeEach(() => {
fake = new FakeDrive({
contents: new Map([
["folder/file.bin", new Uint8Array([7, 8, 9])],
["clip.mov", new Uint8Array([1, 2])],
]),
});
});
it("validates options.driveId at construction", { timeout: 5000 }, () => {
expect(() =>
driveUriPlugin({ drive: asDrive(fake), driveId: "" })
).toThrow(DriveUriError);
});
it("validates options.drive at construction", { timeout: 5000 }, () => {
expect(() =>
driveUriPlugin({
drive: undefined as unknown as Parameters<
typeof driveUriPlugin
>[0]["drive"],
driveId: "primary",
})
).toThrow(DriveUriError);
});
it("rejects URIs whose scheme is not drive://", { timeout: 5000 }, () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
expect(() => plugin.parse("file:///x")).toThrow(DriveUriError);
});
it("rejects URIs missing the driveId authority", { timeout: 5000 }, () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
try {
plugin.parse("drive:///x");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
expect((err as DriveUriError).code).toBe(
DRIVE_URI_CODES.INVALID_URI
);
}
});
it("rejects URIs with no authority component (no //)", {
timeout: 5000,
}, () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
// `drive:x` has scheme + path but no authority — covers the
// `parts.authority ?? ""` fallback branch.
try {
plugin.parse("drive:x");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
expect((err as DriveUriError).code).toBe(
DRIVE_URI_CODES.INVALID_URI
);
}
});
it("rejects URIs with an empty path", { timeout: 5000 }, () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
try {
plugin.parse("drive://primary/");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
expect((err as DriveUriError).code).toBe(
DRIVE_URI_CODES.INVALID_URI
);
}
});
it("dispatches a drive:// URI to Drive.read", {
timeout: 5000,
}, async () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
const resolver = createStorageUriResolver([plugin]);
const bytes = await resolver.read("drive://primary/folder/file.bin");
expect(bytes).toEqual(new Uint8Array([7, 8, 9]));
expect(fake.calls.read).toHaveLength(1);
expect(fake.calls.read[0]?.path).toBe("folder/file.bin");
});
it("throws DRIVE_ID_MISMATCH when URI driveId differs from configured driveId", {
timeout: 5000,
}, async () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
const resolver = createStorageUriResolver([plugin]);
try {
await resolver.read("drive://other/x");
throw new Error("expected throw");
} catch (err) {
// Per fix-storage-uri-error-passthrough: SuaveplanError
// subclasses thrown by a plugin's adapter pass through the
// resolver unchanged, so the caller sees the DriveUriError
// directly with its original code.
expect(err).toBeInstanceOf(DriveUriError);
const e = err as DriveUriError;
expect(e.code).toBe(DRIVE_URI_CODES.DRIVE_ID_MISMATCH);
expect(e.uri).toBe("drive://other/x");
expect(e.expected).toBe("primary");
expect(e.actual).toBe("other");
}
});
it("build round-trips driveId and key into a drive:// URI", {
timeout: 5000,
}, () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
expect(plugin.build?.("folder/file.bin", { driveId: "primary" })).toBe(
"drive://primary/folder/file.bin"
);
});
it("build defaults to the configured driveId when meta is omitted", {
timeout: 5000,
}, () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
expect(plugin.build?.("clip.mov")).toBe("drive://primary/clip.mov");
});
it("build strips a leading slash from key", { timeout: 5000 }, () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
expect(plugin.build?.("/clip.mov", { driveId: "primary" })).toBe(
"drive://primary/clip.mov"
);
});
it("short-circuits with DRIVE_URI.ABORTED on a pre-aborted signal", {
timeout: 5000,
}, () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
const adapter = plugin.adapter as {
createReadStream(
key: string,
signal?: AbortSignal
): ReadableStream<Uint8Array>;
};
const signal = AbortSignal.abort();
try {
adapter.createReadStream("x", signal);
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
expect((err as DriveUriError).code).toBe(DRIVE_URI_CODES.ABORTED);
}
// Drive.createReadStream MUST NOT have been invoked.
expect(fake.calls.createReadStream).toHaveLength(0);
});
it("forwards a live AbortSignal unchanged to Drive.createReadStream", {
timeout: 5000,
}, () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
const adapter = plugin.adapter as {
createReadStream(
key: string,
signal?: AbortSignal
): ReadableStream<Uint8Array>;
};
const controller = new AbortController();
adapter.createReadStream("clip.mov", controller.signal);
expect(fake.calls.createReadStream).toHaveLength(1);
expect(fake.calls.createReadStream[0]?.signal).toBe(controller.signal);
});
it("rejects writes with DRIVE_URI.WRITE_UNSUPPORTED", {
timeout: 5000,
}, async () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
const resolver = createStorageUriResolver([plugin]);
try {
await resolver.write(
"drive://primary/x",
new Uint8Array([1, 2, 3])
);
throw new Error("expected throw");
} catch (err) {
// Per fix-storage-uri-error-passthrough: the DriveUriError
// passes through the resolver's write path unchanged.
expect(err).toBeInstanceOf(DriveUriError);
const e = err as DriveUriError;
expect(e.code).toBe(DRIVE_URI_CODES.WRITE_UNSUPPORTED);
expect(e.scheme).toBe("drive");
}
});
it("delete forwards through the adapter to Drive.delete", {
timeout: 5000,
}, async () => {
const plugin = driveUriPlugin({
drive: asDrive(fake),
driveId: "primary",
});
const resolver = createStorageUriResolver([plugin]);
const removed = await resolver.remove("drive://primary/clip.mov");
expect(removed).toBe(true);
expect(fake.calls.delete).toHaveLength(1);
});
});

View File

@@ -0,0 +1,99 @@
import {
type ParsedUri,
parseUri,
type UriPlugin,
} from "@suaveplan/storage-uri";
import {
DRIVE_URI_CODES,
DriveUriError,
} from "../../error-codes/error-codes.js";
import type { DriveUriPluginOptions } from "../../types/plugin-options.types.js";
import { InstrumentedDriveAdapter } from "./drive.streams.js";
const SCHEME = "drive";
/**
* `drive://<driveId>/<path>` URI plugin factory.
*
* Wraps the caller-supplied `Drive` in a `DriveStorageLikeAdapter` (with OTel
* instrumentation) and returns a `UriPlugin` registerable with
* `StorageUriResolver`. The plugin's `parse` enforces that the URI's authority
* equals `options.driveId` — mismatches throw `DRIVE_URI.DRIVE_ID_MISMATCH`
* synchronously, before any `Drive` call.
*
* The plugin is read-only. Every `adapter.set` rejects with
* `DRIVE_URI.WRITE_UNSUPPORTED` and increments
* `drive_uri.writes_total{scheme:"drive", outcome:"error"}` regardless of
* cause (so operator dashboards see the misuse rate).
*/
export function driveUriPlugin(options: DriveUriPluginOptions): UriPlugin {
validateOptions(options);
const adapter = new InstrumentedDriveAdapter(options.drive, {
scheme: SCHEME,
});
return {
scheme: SCHEME,
adapter,
parse(uri: string): ParsedUri {
const parts = parseUri(uri);
if (parts.scheme !== SCHEME) {
throw new DriveUriError(
DRIVE_URI_CODES.INVALID_URI,
`expected scheme '${SCHEME}', got '${parts.scheme}'`,
{ uri, scheme: SCHEME }
);
}
const driveId = parts.authority ?? "";
if (driveId.length === 0) {
throw new DriveUriError(
DRIVE_URI_CODES.INVALID_URI,
"drive:// URI is missing the driveId authority",
{ uri, scheme: SCHEME }
);
}
if (driveId !== options.driveId) {
throw new DriveUriError(
DRIVE_URI_CODES.DRIVE_ID_MISMATCH,
`URI driveId '${driveId}' does not match plugin driveId '${options.driveId}'`,
{
uri,
scheme: SCHEME,
driveId: options.driveId,
expected: options.driveId,
actual: driveId,
}
);
}
const key = parts.path.replace(/^\//, "");
if (key.length === 0) {
throw new DriveUriError(
DRIVE_URI_CODES.INVALID_URI,
"drive:// URI must include a non-empty path component",
{ uri, scheme: SCHEME, driveId }
);
}
return { key, meta: { driveId } };
},
build(key: string, meta?: Record<string, string>): string {
const driveId = meta?.["driveId"] ?? options.driveId;
return `${SCHEME}://${driveId}/${key.replace(/^\//, "")}`;
},
};
}
function validateOptions(options: DriveUriPluginOptions): void {
if (typeof options.driveId !== "string" || options.driveId.length === 0) {
throw new DriveUriError(
DRIVE_URI_CODES.CAPABILITY_MISSING,
"driveUriPlugin requires a non-empty options.driveId",
{ scheme: SCHEME, reason: "driveId" }
);
}
if (options.drive === undefined || options.drive === null) {
throw new DriveUriError(
DRIVE_URI_CODES.CAPABILITY_MISSING,
"driveUriPlugin requires options.drive",
{ scheme: SCHEME, reason: "drive" }
);
}
}

View File

@@ -0,0 +1,3 @@
export { driveUriPlugin } from "./drive.js";
export type { InstrumentedAdapterOptions } from "./drive.streams.js";
export { InstrumentedDriveAdapter } from "./drive.streams.js";

View File

@@ -0,0 +1,6 @@
export type {
DriveMeshUriPluginOptions,
DriveTemplateUriPluginOptions,
DriveUriPluginOptions,
TemplateProvider,
} from "./plugin-options.types.js";

View File

@@ -0,0 +1,95 @@
# `@suaveplan/drive-uri/types`
## Purpose
Shared option-bag interfaces consumed by the three URI plugin factories
exported from `@suaveplan/drive-uri`. Each interface follows AGENTS §11's
rule that nested object shapes must be named — every option referenced
across plugin boundaries (e.g. `TemplateProvider`) is a standalone interface,
never an inline object literal.
The types are intentionally minimal — drive-uri does not own the contract
of the `Drive` it wraps, nor the schema of the `DriveTemplate` it passes
through. Those types come from `@suaveplan/drive` and `@suaveplan/drive-template`
respectively and are referenced by name here.
## Features
- `TemplateProvider` — single-method interface that the `drive+template`
plugin consumes. Implementations may fetch templates over HTTP, gossip,
the upstream mesh, or an in-memory map (used in tests).
- `DriveUriPluginOptions` — minimum surface for the `drive` plugin:
`drive` + `driveId`.
- `DriveMeshUriPluginOptions` — `drive` + optional `defaultSwarmId`.
- `DriveTemplateUriPluginOptions` — `drive` + `templateProvider`.
## Usage: Basic
```ts
import type {
DriveUriPluginOptions,
TemplateProvider,
} from "@suaveplan/drive-uri";
const options: DriveUriPluginOptions = { drive, driveId: "primary" };
const provider: TemplateProvider = {
async fetchTemplate(templateId, signal) {
return downloadTemplate(templateId, signal);
},
};
```
## Usage: Advanced
A `TemplateProvider` backed by an HTTP fetch with caching, retry, and the
forwarded `AbortSignal`:
```ts
import type { TemplateProvider } from "@suaveplan/drive-uri";
import type { DriveTemplate } from "@suaveplan/drive-template";
const cache = new Map<string, Promise<DriveTemplate>>();
const provider: TemplateProvider = {
async fetchTemplate(templateId, signal) {
const cached = cache.get(templateId);
if (cached !== undefined) return cached;
const pending = (async () => {
const res = await fetch(`/api/templates/${templateId}`, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return (await res.json()) as DriveTemplate;
})();
cache.set(templateId, pending);
try {
return await pending;
} catch (err) {
cache.delete(templateId);
throw err;
}
},
};
```
## API Reference
- `interface TemplateProvider` — `fetchTemplate(templateId: string, signal?: AbortSignal): Promise<DriveTemplate>`.
- `interface DriveUriPluginOptions` — `readonly drive: Drive`, `readonly driveId: string`.
- `interface DriveMeshUriPluginOptions` — `readonly drive: Drive`,
`readonly defaultSwarmId?: string`.
- `interface DriveTemplateUriPluginOptions` — `readonly drive: Drive`,
`readonly templateProvider: TemplateProvider`.
## Implementation Notes
- Every interface is `readonly` for ergonomic safety — option bags are
one-shot construction inputs, not mutable state.
- `defaultSwarmId` on the mesh options is only used by `plugin.build(...)`
when no `meta.swarmId` is supplied — it never affects dispatch.
- `TemplateProvider.fetchTemplate` returns `DriveTemplate` (the canonical
type from `@suaveplan/drive-template`), not a serialized form. The
provider is responsible for any decoding/parsing; `drive-uri` passes the
return value straight to `Drive.hydrateFrom`.
- A future addition could be a `templateCache` option exposing the
hydration cache to the consumer, but per AGENTS §11 that requires ≥2
consumers — out of scope for the initial release.

View File

@@ -0,0 +1,49 @@
import type { Drive } from "@suaveplan/drive";
import type { DriveTemplate } from "@suaveplan/drive-template";
/**
* Provider injected into the `drive+template` plugin so the URI resolver can
* obtain a signed `DriveTemplate` for a given `templateId` without being
* coupled to a particular transport (HTTP, gossip, mesh, etc.). The provider
* is invoked at most once per templateId per plugin instance — the hydration
* cache short-circuits subsequent lookups.
*/
export interface TemplateProvider {
fetchTemplate(
templateId: string,
signal?: AbortSignal
): Promise<DriveTemplate>;
}
/**
* Options accepted by `driveUriPlugin`. The `Drive` instance is supplied by
* the consumer — `drive-uri` never constructs one. `driveId` is the literal
* authority component the plugin will require in every URI; mismatches throw
* `DRIVE_URI.DRIVE_ID_MISMATCH` synchronously from `parse`.
*/
export interface DriveUriPluginOptions {
readonly drive: Drive;
readonly driveId: string;
}
/**
* Options accepted by `driveMeshUriPlugin`. Unlike `DriveUriPluginOptions`,
* the mesh variant does NOT enforce a configured swarmId — the URI's
* authority is captured for instrumentation only and is forwarded to the
* `Drive`'s mesh transport via the consumer's wiring. `defaultSwarmId` is
* the value used by `build(key)` when no `meta.swarmId` is supplied.
*/
export interface DriveMeshUriPluginOptions {
readonly drive: Drive;
readonly defaultSwarmId?: string;
}
/**
* Options accepted by `driveTemplateUriPlugin`. Both `drive` and
* `templateProvider` are required at construction; absence of either
* throws `DRIVE_URI.CAPABILITY_MISSING`.
*/
export interface DriveTemplateUriPluginOptions {
readonly drive: Drive;
readonly templateProvider: TemplateProvider;
}

View File

@@ -0,0 +1,92 @@
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Drive } from "@suaveplan/drive";
import { createNodeFsStorage } from "@suaveplan/storage-file";
import {
createStorageUriResolver,
type UriPlugin,
} from "@suaveplan/storage-uri";
import {
afterEach,
beforeEach,
describe,
expect,
it,
} from "@suaveplan/testing/runner";
import { driveUriPlugin } from "../../src/plugins/drive/drive.js";
interface FileUriOptions {
readonly rootDir: string;
}
function fileUriPlugin(options: FileUriOptions): UriPlugin {
const adapter = createNodeFsStorage({
rootDir: options.rootDir,
mkdirp: true,
});
return {
scheme: "file",
adapter,
parse(uri: string) {
// file:///some/path -> key = "some/path"
const idx = uri.indexOf("://");
const after = uri.slice(idx + 3);
const pathStart = after.indexOf("/");
const path = pathStart >= 0 ? after.slice(pathStart + 1) : after;
return { key: path };
},
build(key: string) {
return `file:///${key.replace(/^\//, "")}`;
},
};
}
describe("compose-with-storage-uri integration", () => {
let rootDir: string;
let driveRoot: string;
let drive: Drive;
beforeEach(async () => {
rootDir = mkdtempSync(join(tmpdir(), "drive-uri-file-"));
driveRoot = mkdtempSync(join(tmpdir(), "drive-uri-drive-"));
const driveStorage = Object.assign(
createNodeFsStorage({ rootDir: driveRoot, mkdirp: true }),
{ id: "default" }
);
drive = new Drive(driveStorage);
await drive.initialize();
});
afterEach(async () => {
await drive.dispose();
rmSync(rootDir, { recursive: true, force: true });
rmSync(driveRoot, { recursive: true, force: true });
});
it("file:// and drive:// resolve through one StorageUriResolver", {
timeout: 30000,
}, async () => {
const resolver = createStorageUriResolver([
fileUriPlugin({ rootDir }),
driveUriPlugin({ drive, driveId: "primary" }),
]);
// Seed both backends with distinct bytes.
await drive.write("a.bin", new Uint8Array([0xaa]));
// Use the file plugin directly to write — the resolver rejects
// writes for drive:// (by design) so we exercise the file side via
// resolver.write to confirm it works.
await resolver.write("file:///b.bin", new Uint8Array([0xbb]));
const fileBytes = await resolver.read("file:///b.bin");
const driveBytes = await resolver.read("drive://primary/a.bin");
expect(Array.from(fileBytes)).toEqual([0xbb]);
expect(Array.from(driveBytes)).toEqual([0xaa]);
// Neither plugin sees the other's state.
expect(resolver.hasScheme("file")).toBe(true);
expect(resolver.hasScheme("drive")).toBe(true);
expect(resolver.schemeCount).toBe(2);
});
});

View File

@@ -0,0 +1,76 @@
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Drive } from "@suaveplan/drive";
import { createNodeFsStorage } from "@suaveplan/storage-file";
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import {
afterEach,
beforeEach,
describe,
expect,
it,
} from "@suaveplan/testing/runner";
import { driveUriPlugin } from "../../src/plugins/drive/drive.js";
describe("drive-roundtrip integration", () => {
let rootDir: string;
let drive: Drive;
beforeEach(async () => {
rootDir = mkdtempSync(join(tmpdir(), "drive-uri-roundtrip-"));
const fileStorage = createNodeFsStorage({ rootDir, mkdirp: true });
const storage = Object.assign(fileStorage, { id: "default" });
drive = new Drive(storage);
await drive.initialize();
});
afterEach(async () => {
await drive.dispose();
rmSync(rootDir, { recursive: true, force: true });
});
it("writes a file via Drive.write then reads it via resolver.read('drive://')", {
timeout: 30000,
}, async () => {
const plugin = driveUriPlugin({ drive, driveId: "test-drive" });
const resolver = createStorageUriResolver([plugin]);
const payload = new Uint8Array([0x4d, 0x4f, 0x56, 0x21]);
await drive.write("folder/clip.mov", payload);
const bytes = await resolver.read("drive://test-drive/folder/clip.mov");
expect(Array.from(bytes)).toEqual(Array.from(payload));
});
it("resolver.open streams the file via Drive.createReadStream", {
timeout: 30000,
}, async () => {
const plugin = driveUriPlugin({ drive, driveId: "test-drive" });
const resolver = createStorageUriResolver([plugin]);
const payload = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
await drive.write("clip.dat", payload);
const stream = await resolver.open("drive://test-drive/clip.dat");
const reader = stream.getReader();
const chunks: number[] = [];
for (;;) {
const { value, done } = await reader.read();
if (done) break;
if (value !== undefined) chunks.push(...value);
}
reader.releaseLock();
expect(chunks).toEqual(Array.from(payload));
});
it("resolver.remove deletes the file", { timeout: 30000 }, async () => {
const plugin = driveUriPlugin({ drive, driveId: "test-drive" });
const resolver = createStorageUriResolver([plugin]);
await drive.write("doomed.bin", new Uint8Array([42]));
const removed = await resolver.remove("drive://test-drive/doomed.bin");
expect(removed).toBe(true);
// A subsequent read should yield STORAGE_URI.NOT_FOUND (the
// adapter translates Drive's not-found into undefined, and the
// resolver maps undefined to NOT_FOUND).
await expect(
resolver.read("drive://test-drive/doomed.bin")
).rejects.toBeDefined();
});
});

View File

@@ -0,0 +1,96 @@
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Drive } from "@suaveplan/drive";
import { createNodeFsStorage } from "@suaveplan/storage-file";
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import {
afterEach,
beforeEach,
describe,
expect,
it,
} from "@suaveplan/testing/runner";
import {
DRIVE_URI_CODES,
DriveUriError,
} from "../../src/error-codes/error-codes.js";
import { driveTemplateUriPlugin } from "../../src/plugins/drive-template/drive-template.js";
describe("template-roundtrip integration", () => {
let rootDir: string;
let drive: Drive;
beforeEach(async () => {
rootDir = mkdtempSync(join(tmpdir(), "drive-uri-template-"));
const fileStorage = createNodeFsStorage({ rootDir, mkdirp: true });
const storage = Object.assign(fileStorage, { id: "default" });
drive = new Drive(storage);
await drive.initialize();
});
afterEach(async () => {
await drive.dispose();
rmSync(rootDir, { recursive: true, force: true });
});
it("real Drive.hydrateFrom rejects unsigned template as TEMPLATE_INVALID", {
timeout: 30000,
}, async () => {
const provider = {
async fetchTemplate(): Promise<unknown> {
// Returning an obviously-malformed template forces
// Drive.hydrateFrom's validation pipeline to reject.
return {};
},
};
const plugin = driveTemplateUriPlugin({
drive,
templateProvider: provider as unknown as Parameters<
typeof driveTemplateUriPlugin
>[0]["templateProvider"],
});
const resolver = createStorageUriResolver([plugin]);
try {
await resolver.read("drive+template://templA/x");
throw new Error("expected throw");
} catch (err) {
// SuaveplanError subclasses pass through the resolver
// unchanged, so the caller sees the DriveUriError directly.
expect(err).toBeInstanceOf(DriveUriError);
const e = err as DriveUriError;
expect(e.code).toBe(DRIVE_URI_CODES.TEMPLATE_INVALID);
// The inner cause is the real DTP_*/DRIVE error surfaced by
// Drive's hydrateFrom — confirming the wrapping path is wired
// through real package code, not a stub.
expect(e.cause).toBeDefined();
}
});
it("provider rejection becomes TEMPLATE_NOT_AVAILABLE via real Drive", {
timeout: 30000,
}, async () => {
const original = new Error("network down");
const provider = {
async fetchTemplate(): Promise<never> {
throw original;
},
};
const plugin = driveTemplateUriPlugin({
drive,
templateProvider: provider as unknown as Parameters<
typeof driveTemplateUriPlugin
>[0]["templateProvider"],
});
const resolver = createStorageUriResolver([plugin]);
try {
await resolver.read("drive+template://templA/x");
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(DriveUriError);
const e = err as DriveUriError;
expect(e.code).toBe(DRIVE_URI_CODES.TEMPLATE_NOT_AVAILABLE);
expect(e.cause).toBe(original);
}
});
});

View File

@@ -0,0 +1,151 @@
import type { Drive } from "@suaveplan/drive";
import type { DriveTemplate } from "@suaveplan/drive-template";
import { SuaveplanError } from "@suaveplan/error";
/**
* Real, hand-written `Drive`-shaped test double. NOT a mock — it is a real
* class implementing every method consumed by `@suaveplan/drive-uri`. Used
* for unit-tier tests where spinning up a full `Drive + storage-file +
* chunksPlugin` would be excessive; integration tests use the real `Drive`.
*
* Per CLAUDE.md rule 2 this is acceptable because:
* - the class is concrete, with deterministic behaviour
* - no `vi.mock`/`jest.mock` is involved
* - it implements the structural contract drive-uri consumes
*/
export class FakeDriveError extends SuaveplanError<string> {
public constructor(
code: string,
message: string,
options?: { cause?: unknown }
) {
super(code, message, options);
this.name = "FakeDriveError";
}
}
export interface FakeDriveCalls {
read: Array<{ path: string; options?: { signal?: AbortSignal } }>;
write: Array<{ path: string; data: Uint8Array }>;
delete: Array<{ path: string }>;
stat: Array<{ path: string }>;
list: Array<{ prefix?: string }>;
createReadStream: Array<{ path: string; signal?: AbortSignal }>;
hydrateFrom: Array<{
template: DriveTemplate;
options?: { signal?: AbortSignal };
}>;
}
export interface FakeDriveOptions {
readonly contents?: Map<string, Uint8Array>;
readonly readError?: () => unknown;
readonly hydrateError?: () => unknown;
}
export class FakeDrive {
public readonly clock = undefined;
public readonly rng = undefined;
public readonly logger = undefined;
public readonly events = undefined;
public readonly calls: FakeDriveCalls = {
read: [],
write: [],
delete: [],
stat: [],
list: [],
createReadStream: [],
hydrateFrom: [],
};
readonly #contents: Map<string, Uint8Array>;
readonly #options: FakeDriveOptions;
public constructor(options: FakeDriveOptions = {}) {
this.#contents = options.contents ?? new Map();
this.#options = options;
}
public async read(
path: string,
options?: { signal?: AbortSignal }
): Promise<Uint8Array> {
const entry: { path: string; options?: { signal?: AbortSignal } } = {
path,
};
if (options !== undefined) entry.options = options;
this.calls.read.push(entry);
if (this.#options.readError !== undefined) {
throw this.#options.readError();
}
const bytes = this.#contents.get(path);
if (bytes === undefined) {
throw new FakeDriveError(
"DRIVE.NOT_FOUND",
`no entry for '${path}'`
);
}
return bytes;
}
public async write(path: string, data: Uint8Array): Promise<void> {
this.calls.write.push({ path, data });
this.#contents.set(path, data);
}
public async delete(path: string): Promise<boolean> {
this.calls.delete.push({ path });
return this.#contents.delete(path);
}
public async stat(path: string): Promise<unknown | null> {
this.calls.stat.push({ path });
const bytes = this.#contents.get(path);
if (bytes === undefined) return null;
return { path, size: bytes.byteLength };
}
public async list(prefix?: string): Promise<unknown> {
this.calls.list.push({ ...(prefix !== undefined ? { prefix } : {}) });
const keys = Array.from(this.#contents.keys()).filter((k) =>
prefix === undefined || prefix === "" ? true : k.startsWith(prefix)
);
return { keys, total: keys.length, hasMore: false };
}
public createReadStream(
path: string,
signal?: AbortSignal
): ReadableStream<Uint8Array> {
const entry: { path: string; signal?: AbortSignal } = { path };
if (signal !== undefined) entry.signal = signal;
this.calls.createReadStream.push(entry);
const bytes = this.#contents.get(path);
return new ReadableStream<Uint8Array>({
start(ctrl) {
if (bytes !== undefined) ctrl.enqueue(bytes);
ctrl.close();
},
});
}
public async hydrateFrom(
template: DriveTemplate,
options?: { signal?: AbortSignal }
): Promise<{ filesAdded: number }> {
const entry: {
template: DriveTemplate;
options?: { signal?: AbortSignal };
} = { template };
if (options !== undefined) entry.options = options;
this.calls.hydrateFrom.push(entry);
if (this.#options.hydrateError !== undefined) {
throw this.#options.hydrateError();
}
return { filesAdded: 0 };
}
}
/** Returns a `Drive`-typed reference to the fake — narrows for ergonomic use. */
export function asDrive(fake: FakeDrive): Drive {
return fake as unknown as Drive;
}

View File

@@ -0,0 +1,5 @@
// Polyfills for test environment — none required; Node 18+ provides
// `URL`, `AbortController`, `ReadableStream`, and `Response` natively.
// File exists so vitest has a stable setup entry and to mirror sibling
// drive packages.
export {};

View File

@@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "./dist",
"lib": ["ES2022"],
"types": ["node"]
},
"include": ["src/**/*.ts", "tests/**/*.ts"],
"exclude": ["dist", "node_modules"]
}

View File

@@ -0,0 +1,12 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"lib": ["ES2022"],
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts", "src/**/*.bench.ts", "dist", "node_modules"]
}

View File

@@ -0,0 +1,38 @@
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";
export default defineConfig({
root: __dirname,
cacheDir: "../../../node_modules/.vite/packages/drive-uri",
plugins: [
dts({
entryRoot: "src",
tsconfigPath: "./tsconfig.lib.json",
insertTypesEntry: true,
}),
],
build: {
outDir: "./dist",
emptyOutDir: true,
reportCompressedSize: true,
sourcemap: true,
lib: { entry: "src/index.ts", formats: ["es"] },
rollupOptions: {
treeshake: true,
output: {
preserveModules: true,
preserveModulesRoot: "src",
entryFileNames: "[name].js",
},
external: [
"@opentelemetry/api",
"@suaveplan/drive",
"@suaveplan/drive-template",
"@suaveplan/error",
"@suaveplan/storage-uri",
"@suaveplan/types",
"tslib",
],
},
},
});

View File

@@ -0,0 +1,32 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: false,
environment: "node",
pool: "forks",
setupFiles: ["./tests/setup/polyfills.ts"],
include: ["src/**/*.test.ts", "tests/**/*.test.ts"],
exclude: ["dist/**", "node_modules/**"],
coverage: {
provider: "istanbul",
reporter: ["text", "html", "lcov"],
include: ["src/**/*.ts"],
exclude: [
"src/**/*.test.ts",
"src/**/*.types.ts",
"src/**/index.ts",
"src/**/*.bench.ts",
"src/__benchmarks__/**",
"tests/**",
],
thresholds: {
statements: 100,
branches: 100,
functions: 100,
lines: 100,
},
},
testTimeout: 30000,
},
});