@flowscripter/pluggable-io-framework-api 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -47
- package/dist/src/ChunkRef.d.ts +42 -24
- package/dist/src/ChunkRef.d.ts.map +1 -1
- package/dist/src/ChunkRef.js +39 -8
- package/dist/src/IOProvider.d.ts +16 -10
- package/dist/src/IOProvider.d.ts.map +1 -1
- package/dist/src/IOProviderFactory.d.ts +3 -6
- package/dist/src/IOProviderFactory.d.ts.map +1 -1
- package/dist/src/ItemProperties.d.ts +1 -2
- package/dist/src/ItemProperties.d.ts.map +1 -1
- package/dist/src/Part.d.ts +8 -6
- package/dist/src/Part.d.ts.map +1 -1
- package/dist/src/StreamDecorator.d.ts +17 -4
- package/dist/src/StreamDecorator.d.ts.map +1 -1
- package/dist/src/StreamDecorator.js +6 -1
- package/dist/src/StreamHandle.d.ts +9 -11
- package/dist/src/StreamHandle.d.ts.map +1 -1
- package/dist/src/TelemetryHooks.d.ts +3 -3
- package/package.json +2 -2
- package/src/ChunkRef.ts +81 -39
- package/src/IOProvider.ts +17 -10
- package/src/IOProviderFactory.ts +3 -6
- package/src/ItemProperties.ts +1 -2
- package/src/Part.ts +8 -6
- package/src/StreamDecorator.ts +31 -4
- package/src/StreamHandle.ts +9 -11
- package/src/TelemetryHooks.ts +3 -3
package/README.md
CHANGED
|
@@ -5,63 +5,47 @@
|
|
|
5
5
|
[](https://flowscripter.github.io/pluggable-io-framework-api/index.html)
|
|
6
6
|
[](https://github.com/flowscripter/pluggable-io-framework-api/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
-
> API
|
|
9
|
-
> source/sink provider plugins
|
|
8
|
+
> API for the https://github.com/flowscripter/pluggable-io-framework
|
|
10
9
|
|
|
11
10
|
## Key Features
|
|
12
11
|
|
|
13
12
|
- Defines the `IOProviderFactory`/`IOProvider` contract that source/sink
|
|
14
|
-
plugins (e.g. local filesystem, object storage) implement
|
|
13
|
+
plugins (e.g. local filesystem, object storage) implement which are then discovered and
|
|
15
14
|
loaded via
|
|
16
15
|
[dynamic-plugin-framework](https://github.com/flowscripter/dynamic-plugin-framework).
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
- `IOProvider`, `StreamHandle` and `Part` are tagged with the single
|
|
17
|
+
`ChunkKind` ("js" or "native") a provider natively produces/consumes -
|
|
18
|
+
streams are homogeneous, so consumers never test each chunk's kind. A
|
|
19
|
+
mismatch between two linked streams is decided once per link via
|
|
20
|
+
`adaptReadableStream`, not once per chunk.
|
|
21
|
+
- `JsChunk`/`NativeChunk`: a tagged-union stream payload that carries memory
|
|
22
|
+
ownership/origin with it, enabling zero-copy handoff to/from
|
|
22
23
|
Rust-FFI-backed providers and decorators, with a small adapter to the
|
|
23
24
|
standard Web Streams `ReadableStream<Uint8Array>`/`WritableStream<Uint8Array>`
|
|
24
25
|
for interop (`fetch`, `pipeTo`, etc.).
|
|
26
|
+
- Capabilities beyond plain streaming (e.g. `seekable`) are modeled as
|
|
27
|
+
small interfaces (`Seekable`, `RangeReadable`) with co-located type guards
|
|
28
|
+
(`isSeekable`, `isRangeReadable`).
|
|
25
29
|
- Well-known item properties (`size`, `lastModified`, `isFolder`,
|
|
26
|
-
`contentType`)
|
|
27
|
-
|
|
28
|
-
tags).
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
`contentType`) are default for every provider.
|
|
31
|
+
- A provider-specific
|
|
32
|
+
`properties` extension bag supports other properties (e.g. etag, storage class, custom tags).
|
|
33
|
+
- Provider config and per-item property schemas are defined with
|
|
34
|
+
[Zod](https://zod.dev).
|
|
35
|
+
- Multipart transfers are modeled as a stream of independently readable/writable
|
|
36
|
+
`Part` handles allowing parts to be processed concurrently.
|
|
37
|
+
- A global `TelemetryHooks` object is supplied once at
|
|
38
|
+
initialisation and every operation reports through it tagged with a
|
|
39
|
+
correlation ID.
|
|
40
|
+
- Disposal is `Symbol.asyncDispose` (TC39 explicit resource management) -
|
|
41
|
+
`await using provider = await factory.createProvider(config)` disposes
|
|
42
|
+
deterministically, including on thrown errors.
|
|
43
|
+
- See
|
|
36
44
|
[pluggable-io-framework](https://github.com/flowscripter/pluggable-io-framework)
|
|
37
45
|
for orchestration and
|
|
38
46
|
[pluggable-io-framework-plugin-filesystem](https://github.com/flowscripter/pluggable-io-framework-plugin-filesystem)
|
|
39
47
|
for a reference implementation.
|
|
40
48
|
|
|
41
|
-
## Bun Module Usage
|
|
42
|
-
|
|
43
|
-
Add the module:
|
|
44
|
-
|
|
45
|
-
`bun add @flowscripter/pluggable-io-framework-api`
|
|
46
|
-
|
|
47
|
-
Implement a provider factory:
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
import { z } from "zod";
|
|
51
|
-
import type { IOProviderFactory } from "@flowscripter/pluggable-io-framework-api";
|
|
52
|
-
|
|
53
|
-
const configSchema = z.object({ rootPath: z.string() });
|
|
54
|
-
const propertySchema = z.object({ etag: z.string().optional() });
|
|
55
|
-
|
|
56
|
-
const factory: IOProviderFactory<z.infer<typeof configSchema>> = {
|
|
57
|
-
configSchema,
|
|
58
|
-
propertySchema,
|
|
59
|
-
async createProvider(config) {
|
|
60
|
-
// return an IOProvider implementation
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
```
|
|
64
|
-
|
|
65
49
|
## Development
|
|
66
50
|
|
|
67
51
|
Install dependencies:
|
|
@@ -95,10 +79,10 @@ Generate HTML API Documentation:
|
|
|
95
79
|
```mermaid
|
|
96
80
|
classDiagram
|
|
97
81
|
IOProviderFactory --> IOProvider : creates
|
|
98
|
-
IOProvider --> StreamHandle : returns
|
|
99
|
-
IOProvider --> Part : returns (multipart)
|
|
100
|
-
StreamHandle -->
|
|
101
|
-
|
|
82
|
+
IOProvider --> StreamHandle : returns (kind K)
|
|
83
|
+
IOProvider --> Part : returns (multipart, kind K)
|
|
84
|
+
StreamHandle --> JsChunk : streams (kind "js")
|
|
85
|
+
StreamHandle --> NativeChunk : streams (kind "native")
|
|
102
86
|
IOProvider --> ItemProperties : returns
|
|
103
87
|
|
|
104
88
|
class IOProviderFactory {
|
|
@@ -107,7 +91,8 @@ classDiagram
|
|
|
107
91
|
+createProvider(config)
|
|
108
92
|
}
|
|
109
93
|
class IOProvider {
|
|
110
|
-
+
|
|
94
|
+
+kind: K
|
|
95
|
+
+[Symbol.asyncDispose]()
|
|
111
96
|
+list(path, options)
|
|
112
97
|
+getProperties(path)
|
|
113
98
|
+setProperties(path, properties)
|
package/dist/src/ChunkRef.d.ts
CHANGED
|
@@ -1,40 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* This is the actual unit flowing through {@link StreamHandle} and
|
|
5
|
-
* {@link Part} streams - not a TS-vs-Rust-specific type. A pure-TS pipeline
|
|
6
|
-
* is simply a stream of `kind: "js"` chunks end to end.
|
|
7
|
-
*
|
|
8
|
-
* Tagging origin lets a consumer choose a zero-copy pointer handoff when
|
|
9
|
-
* compatible (js -> rust always; rust -> rust always) or fall back to an
|
|
10
|
-
* explicit copy when required (rust -> js, since JS code cannot safely
|
|
11
|
-
* retain a raw pointer past the call that produced it).
|
|
12
|
-
*
|
|
13
|
-
* `attributes` is deliberately shaped close to the future Flowscripter
|
|
14
|
-
* runtime's Item (attributes + payload) so this type is a natural fit if/when
|
|
15
|
-
* an `adapt` operator wraps these streams later. It is unused today.
|
|
2
|
+
* The two possible chunk memory origins.
|
|
16
3
|
*/
|
|
17
|
-
export
|
|
18
|
-
|
|
4
|
+
export declare enum ChunkKind {
|
|
5
|
+
Js = "js",
|
|
6
|
+
Native = "native"
|
|
7
|
+
}
|
|
8
|
+
/** A chunk whose payload lives in a normal JS-managed Uint8Array. */
|
|
9
|
+
export interface JsChunk {
|
|
10
|
+
readonly kind: ChunkKind.Js;
|
|
19
11
|
readonly data: Uint8Array;
|
|
20
12
|
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
21
|
-
}
|
|
22
|
-
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A chunk whose payload lives in memory owned outside the JS heap (e.g. a
|
|
16
|
+
* Rust-allocated buffer). `release()` must be called once nothing needs the
|
|
17
|
+
* buffer - ownership/lifetime is explicit rather than GC'd, since JS code
|
|
18
|
+
* cannot safely retain a raw pointer past the point its owner frees it.
|
|
19
|
+
*/
|
|
20
|
+
export interface NativeChunk {
|
|
21
|
+
readonly kind: ChunkKind.Native;
|
|
23
22
|
readonly ptr: number;
|
|
24
23
|
readonly length: number;
|
|
25
24
|
release(): void;
|
|
26
25
|
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
27
|
-
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A chunk of stream payload, tagged with its memory origin/ownership.
|
|
29
|
+
*/
|
|
30
|
+
export type ChunkRef = JsChunk | NativeChunk;
|
|
31
|
+
/** The concrete chunk type produced by a stream tagged with a given kind. */
|
|
32
|
+
export type ChunkOfKind<K extends ChunkKind> = Extract<ChunkRef, {
|
|
33
|
+
kind: K;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Converts a single chunk to the target kind. Pure-TS code can only ever
|
|
37
|
+
* implement the identity case (same kind in, same kind out) - a real
|
|
38
|
+
* js<->native conversion needs FFI-capable pointer access and must be
|
|
39
|
+
* supplied by a runtime-specific package (e.g. via `bun:ffi`).
|
|
40
|
+
*/
|
|
41
|
+
export type ChunkConverter = (chunk: ChunkRef, toKind: ChunkKind) => ChunkRef;
|
|
42
|
+
/** Identity converter - only handles chunks already of the requested kind. */
|
|
43
|
+
export declare const identityChunkConverter: ChunkConverter;
|
|
28
44
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
45
|
+
* Adapts a homogeneous stream of one kind to another, using `convert`. When
|
|
46
|
+
* `fromKind === toKind` the stream is passed straight through untouched (no
|
|
47
|
+
* per-chunk work at all). This is the ONE place a kind mismatch is decided -
|
|
48
|
+
* once per stream link, not once per chunk.
|
|
31
49
|
*/
|
|
32
|
-
export declare function
|
|
50
|
+
export declare function adaptReadableStream<From extends ChunkKind, To extends ChunkKind>(stream: ReadableStream<ChunkOfKind<From>>, fromKind: From, toKind: To, convert?: ChunkConverter): ReadableStream<ChunkOfKind<To>>;
|
|
33
51
|
/**
|
|
34
52
|
* Adapter to the standard Web Streams interop surface (fetch, pipeTo
|
|
35
53
|
* external consumers). This is the one clearly-marked copy boundary -
|
|
36
54
|
* internal source/sink/decorator code speaks {@link ChunkRef} directly.
|
|
37
55
|
*/
|
|
38
|
-
export declare function toWebReadableStream(source: ReadableStream<
|
|
39
|
-
export declare function fromWebReadableStream(source: ReadableStream<Uint8Array>): ReadableStream<
|
|
56
|
+
export declare function toWebReadableStream(source: ReadableStream<JsChunk>): ReadableStream<Uint8Array>;
|
|
57
|
+
export declare function fromWebReadableStream(source: ReadableStream<Uint8Array>): ReadableStream<JsChunk>;
|
|
40
58
|
//# sourceMappingURL=ChunkRef.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChunkRef.d.ts","sourceRoot":"","sources":["../../src/ChunkRef.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ChunkRef.d.ts","sourceRoot":"","sources":["../../src/ChunkRef.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,oBAAY,SAAS;IACnB,EAAE,OAAO;IACT,MAAM,WAAW;CAClB;AAED,qEAAqE;AACrE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACzD;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,OAAO,IAAI,IAAI,CAAC;IAChB,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACzD;AAED;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,WAAW,CAAC;AAE7C,6EAA6E;AAC7E,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC;AAE9E;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,KAAK,QAAQ,CAAC;AAE9E,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,EAAE,cAOpC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,SAAS,SAAS,EAAE,EAAE,SAAS,SAAS,EAC9E,MAAM,EAAE,cAAc,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,EACzC,QAAQ,EAAE,IAAI,EACd,MAAM,EAAE,EAAE,EACV,OAAO,GAAE,cAAuC,GAC/C,cAAc,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAkBjC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,cAAc,CAAC,UAAU,CAAC,CAgB/F;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,CAejG"}
|
package/dist/src/ChunkRef.js
CHANGED
|
@@ -1,12 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* The only copy incurred is for `kind: "native"` chunks.
|
|
2
|
+
* The two possible chunk memory origins.
|
|
4
3
|
*/
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
export var ChunkKind;
|
|
5
|
+
(function (ChunkKind) {
|
|
6
|
+
ChunkKind["Js"] = "js";
|
|
7
|
+
ChunkKind["Native"] = "native";
|
|
8
|
+
})(ChunkKind || (ChunkKind = {}));
|
|
9
|
+
/** Identity converter - only handles chunks already of the requested kind. */
|
|
10
|
+
export const identityChunkConverter = (chunk, toKind) => {
|
|
11
|
+
if (chunk.kind === toKind) {
|
|
12
|
+
return chunk;
|
|
8
13
|
}
|
|
9
|
-
throw new Error(
|
|
14
|
+
throw new Error(`Cannot convert a "${chunk.kind}" chunk to "${toKind}" without an FFI-capable ChunkConverter`);
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Adapts a homogeneous stream of one kind to another, using `convert`. When
|
|
18
|
+
* `fromKind === toKind` the stream is passed straight through untouched (no
|
|
19
|
+
* per-chunk work at all). This is the ONE place a kind mismatch is decided -
|
|
20
|
+
* once per stream link, not once per chunk.
|
|
21
|
+
*/
|
|
22
|
+
export function adaptReadableStream(stream, fromKind, toKind, convert = identityChunkConverter) {
|
|
23
|
+
if (fromKind === toKind) {
|
|
24
|
+
return stream;
|
|
25
|
+
}
|
|
26
|
+
const reader = stream.getReader();
|
|
27
|
+
return new ReadableStream({
|
|
28
|
+
async pull(controller) {
|
|
29
|
+
const { done, value } = await reader.read();
|
|
30
|
+
if (done) {
|
|
31
|
+
controller.close();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
controller.enqueue(convert(value, toKind));
|
|
35
|
+
},
|
|
36
|
+
cancel(reason) {
|
|
37
|
+
return reader.cancel(reason);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
10
40
|
}
|
|
11
41
|
/**
|
|
12
42
|
* Adapter to the standard Web Streams interop surface (fetch, pipeTo
|
|
@@ -22,7 +52,8 @@ export function toWebReadableStream(source) {
|
|
|
22
52
|
controller.close();
|
|
23
53
|
return;
|
|
24
54
|
}
|
|
25
|
-
|
|
55
|
+
// value is statically a JsChunk here - no per-chunk kind check needed.
|
|
56
|
+
controller.enqueue(value.data);
|
|
26
57
|
},
|
|
27
58
|
cancel(reason) {
|
|
28
59
|
return reader.cancel(reason);
|
|
@@ -38,7 +69,7 @@ export function fromWebReadableStream(source) {
|
|
|
38
69
|
controller.close();
|
|
39
70
|
return;
|
|
40
71
|
}
|
|
41
|
-
controller.enqueue({ kind:
|
|
72
|
+
controller.enqueue({ kind: ChunkKind.Js, data: value });
|
|
42
73
|
},
|
|
43
74
|
cancel(reason) {
|
|
44
75
|
return reader.cancel(reason);
|
package/dist/src/IOProvider.d.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
+
import type { ChunkKind } from "./ChunkRef.ts";
|
|
1
2
|
import type { ItemProperties } from "./ItemProperties.ts";
|
|
2
3
|
import type { Part } from "./Part.ts";
|
|
3
4
|
import type { StreamHandle } from "./StreamHandle.ts";
|
|
4
5
|
/**
|
|
5
6
|
* A configured source/sink instance, as returned by
|
|
6
|
-
* {@link IOProviderFactory.createProvider}.
|
|
7
|
+
* {@link IOProviderFactory.createProvider}. `K` is the single
|
|
8
|
+
* {@link ChunkKind} this provider natively produces/consumes - e.g. a
|
|
9
|
+
* pure-TS filesystem plugin is `IOProvider<"js">`, a Rust-FFI-backed plugin
|
|
10
|
+
* is `IOProvider<"native">`.
|
|
7
11
|
*
|
|
8
|
-
* Disposal is
|
|
9
|
-
*
|
|
10
|
-
*
|
|
12
|
+
* Disposal is `Symbol.asyncDispose` (TC39 explicit resource management) -
|
|
13
|
+
* host code disposes deterministically via `await using provider = ...`,
|
|
14
|
+
* including on thrown errors, without needing a bespoke method name.
|
|
11
15
|
*/
|
|
12
|
-
export interface IOProvider {
|
|
13
|
-
|
|
16
|
+
export interface IOProvider<K extends ChunkKind = ChunkKind> {
|
|
17
|
+
/** The single chunk kind this provider natively produces/consumes. */
|
|
18
|
+
readonly kind: K;
|
|
19
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
14
20
|
list(path: string, options?: {
|
|
15
21
|
recursive?: boolean;
|
|
16
22
|
regex?: RegExp;
|
|
@@ -21,11 +27,11 @@ export interface IOProvider {
|
|
|
21
27
|
getProperties(path: string): Promise<ItemProperties>;
|
|
22
28
|
setProperties(path: string, properties: Partial<Record<string, unknown>>): Promise<void>;
|
|
23
29
|
delete(path: string): Promise<void>;
|
|
24
|
-
getReadableStream(path: string): Promise<StreamHandle
|
|
25
|
-
getWritableStream(path: string): Promise<StreamHandle
|
|
26
|
-
getMultipartReader(path: string): AsyncIterable<Part
|
|
30
|
+
getReadableStream(path: string): Promise<StreamHandle<K>>;
|
|
31
|
+
getWritableStream(path: string): Promise<StreamHandle<K>>;
|
|
32
|
+
getMultipartReader(path: string): AsyncIterable<Part<K>>;
|
|
27
33
|
getMultipartWriter(path: string): {
|
|
28
|
-
write(parts: AsyncIterable<Part
|
|
34
|
+
write(parts: AsyncIterable<Part<K>>): Promise<void>;
|
|
29
35
|
};
|
|
30
36
|
/**
|
|
31
37
|
* Self-reported direct-transfer eligibility - the provider owns what
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IOProvider.d.ts","sourceRoot":"","sources":["../../src/IOProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD
|
|
1
|
+
{"version":3,"file":"IOProvider.d.ts","sourceRoot":"","sources":["../../src/IOProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS;IACzD,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAEjB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,IAAI,CACF,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAChD,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC;IAC/D,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACrD,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAE1F;;;;OAIG;IACH,iBAAiB,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC;IAC/C,UAAU,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,UAAU,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ZodType } from "zod";
|
|
2
|
+
import type { ChunkKind } from "./ChunkRef.ts";
|
|
2
3
|
import type { IOProvider } from "./IOProvider.ts";
|
|
3
4
|
/**
|
|
4
5
|
* Extension point constant that a `dynamic-plugin-framework` `Plugin`'s
|
|
@@ -10,14 +11,10 @@ export declare const PLUGGABLE_IO_FRAMEWORK_PROVIDER_FACTORY_EXTENSION_POINT = "
|
|
|
10
11
|
* Returned (as `unknown`, cast at the extension point boundary) from
|
|
11
12
|
* `ExtensionFactory.create()` in a `dynamic-plugin-framework`
|
|
12
13
|
* `ExtensionDescriptor`.
|
|
13
|
-
*
|
|
14
|
-
* Config/property schema: Zod is the source of truth; JSON Schema can be
|
|
15
|
-
* derived via `zod-to-json-schema` for portability (hand-edited config
|
|
16
|
-
* files, non-TS validation).
|
|
17
14
|
*/
|
|
18
|
-
export interface IOProviderFactory<TConfig = unknown> {
|
|
15
|
+
export interface IOProviderFactory<TConfig = unknown, K extends ChunkKind = ChunkKind> {
|
|
19
16
|
readonly configSchema: ZodType<TConfig>;
|
|
20
17
|
readonly propertySchema: ZodType<Record<string, unknown>>;
|
|
21
|
-
createProvider(config: TConfig): Promise<IOProvider
|
|
18
|
+
createProvider(config: TConfig): Promise<IOProvider<K>>;
|
|
22
19
|
}
|
|
23
20
|
//# sourceMappingURL=IOProviderFactory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IOProviderFactory.d.ts","sourceRoot":"","sources":["../../src/IOProviderFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,uDAAuD,0DACX,CAAC;AAE1D
|
|
1
|
+
{"version":3,"file":"IOProviderFactory.d.ts","sourceRoot":"","sources":["../../src/IOProviderFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,uDAAuD,0DACX,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,iBAAiB,CAAC,OAAO,GAAG,OAAO,EAAE,CAAC,SAAS,SAAS,GAAG,SAAS;IACnF,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1D,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;CACzD"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Properties of a file/folder item. `size`, `lastModified`, `isFolder` and
|
|
3
3
|
* `contentType` are well-known, framework-guaranteed fields every provider
|
|
4
|
-
* must populate
|
|
5
|
-
* on them without knowing the provider. Anything provider-specific (etag,
|
|
4
|
+
* must populate. Anything provider-specific (etag,
|
|
6
5
|
* storage class, custom tags) goes in `properties`, validated against that
|
|
7
6
|
* provider's `propertySchema`.
|
|
8
7
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ItemProperties.d.ts","sourceRoot":"","sources":["../../src/ItemProperties.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ItemProperties.d.ts","sourceRoot":"","sources":["../../src/ItemProperties.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,IAAI,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACxD"}
|
package/dist/src/Part.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChunkKind, ChunkOfKind } from "./ChunkRef.ts";
|
|
2
2
|
/**
|
|
3
|
-
* One independently readable/writable part of a multipart transfer
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* One independently readable/writable part of a multipart transfer, carrying
|
|
4
|
+
* a homogeneous stream of a single declared {@link ChunkKind}. Parts may be
|
|
5
|
+
* processed concurrently (e.g. `Promise.all` over N parts); the provider
|
|
6
|
+
* assembles/commits them once all parts finish.
|
|
6
7
|
*/
|
|
7
|
-
export interface Part {
|
|
8
|
+
export interface Part<K extends ChunkKind = ChunkKind> {
|
|
8
9
|
readonly index: number;
|
|
9
10
|
readonly offset: number;
|
|
10
|
-
readonly
|
|
11
|
+
readonly kind: K;
|
|
12
|
+
readonly stream: ReadableStream<ChunkOfKind<K>> | WritableStream<ChunkOfKind<K>>;
|
|
11
13
|
complete(): Promise<void>;
|
|
12
14
|
}
|
|
13
15
|
//# sourceMappingURL=Part.d.ts.map
|
package/dist/src/Part.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Part.d.ts","sourceRoot":"","sources":["../../src/Part.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"Part.d.ts","sourceRoot":"","sources":["../../src/Part.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5D;;;;;GAKG;AACH,MAAM,WAAW,IAAI,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACjF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B"}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import type { ChunkKind, ChunkOfKind } from "./ChunkRef.ts";
|
|
1
2
|
import type { StreamHandle } from "./StreamHandle.ts";
|
|
3
|
+
/** Capability added by the `seekable` decorator: jump to an absolute offset. */
|
|
4
|
+
export interface Seekable {
|
|
5
|
+
seek(offset: number): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export declare function isSeekable<K extends ChunkKind>(handle: StreamHandle<K>): handle is StreamHandle<K> & Seekable;
|
|
8
|
+
/** Capability added by decorators that can serve an arbitrary byte range directly. */
|
|
9
|
+
export interface RangeReadable<K extends ChunkKind = ChunkKind> {
|
|
10
|
+
readRange(start: number, end: number): Promise<ReadableStream<ChunkOfKind<K>>>;
|
|
11
|
+
}
|
|
12
|
+
export declare function isRangeReadable<K extends ChunkKind>(handle: StreamHandle<K>): handle is StreamHandle<K> & RangeReadable<K>;
|
|
2
13
|
/**
|
|
3
14
|
* A decorator wraps a {@link StreamHandle} and returns an enhanced handle
|
|
4
|
-
* with additional
|
|
5
|
-
*
|
|
6
|
-
*
|
|
15
|
+
* with additional capabilities `C` (e.g. {@link Seekable}). When `C` is
|
|
16
|
+
* known at the call site (the common case - a specific decorator is applied
|
|
17
|
+
* directly), consumers get static typing with no runtime capability check
|
|
18
|
+
* needed. Where a handle arrives already decorated by unknown/dynamic
|
|
19
|
+
* decorators, use the `is*` type guards above instead.
|
|
7
20
|
*/
|
|
8
|
-
export type StreamDecorator = (handle: StreamHandle) => StreamHandle;
|
|
21
|
+
export type StreamDecorator<K extends ChunkKind = ChunkKind, C extends object = object> = (handle: StreamHandle<K>) => StreamHandle<K> & C;
|
|
9
22
|
//# sourceMappingURL=StreamDecorator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamDecorator.d.ts","sourceRoot":"","sources":["../../src/StreamDecorator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD
|
|
1
|
+
{"version":3,"file":"StreamDecorator.d.ts","sourceRoot":"","sources":["../../src/StreamDecorator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,gFAAgF;AAChF,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AAED,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,EAC5C,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,MAAM,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,QAAQ,CAEtC;AAED,sFAAsF;AACtF,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS;IAC5D,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAChF;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EACjD,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,MAAM,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAE9C;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,CACxF,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,KACpB,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC"}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChunkKind, ChunkOfKind } from "./ChunkRef.ts";
|
|
2
2
|
/**
|
|
3
|
-
* A handle to a readable or writable stream of
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* feature-detect via `typeof handle.seek === "function"` etc.
|
|
3
|
+
* A handle to a readable or writable stream of a single, declared
|
|
4
|
+
* {@link ChunkKind} - homogeneous, so consumers never need to test each
|
|
5
|
+
* chunk's kind. Plus optional capability methods for providers/decorators
|
|
6
|
+
* that support random-access beyond plain sequential read (e.g. `seekable`)
|
|
7
|
+
* - see {@link Seekable}/{@link isSeekable} in `StreamDecorator.ts`.
|
|
9
8
|
*/
|
|
10
|
-
export interface StreamHandle {
|
|
11
|
-
readonly
|
|
12
|
-
|
|
13
|
-
readRange?(start: number, end: number): Promise<ReadableStream<ChunkRef>>;
|
|
9
|
+
export interface StreamHandle<K extends ChunkKind = ChunkKind> {
|
|
10
|
+
readonly kind: K;
|
|
11
|
+
readonly stream: ReadableStream<ChunkOfKind<K>> | WritableStream<ChunkOfKind<K>>;
|
|
14
12
|
}
|
|
15
13
|
//# sourceMappingURL=StreamHandle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamHandle.d.ts","sourceRoot":"","sources":["../../src/StreamHandle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"StreamHandle.d.ts","sourceRoot":"","sources":["../../src/StreamHandle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS;IAC3D,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;CAClF"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global telemetry hooks supplied once at framework initialisation. Every
|
|
3
|
-
* operation is given a correlation
|
|
4
|
-
* tagged with that
|
|
3
|
+
* operation is given a correlation ID and reports through the same hooks,
|
|
4
|
+
* tagged with that ID/operation-type. Callers don't need to thread a
|
|
5
5
|
* callback through every call, but can still track individual operations
|
|
6
|
-
* via the
|
|
6
|
+
* via the ID in emitted events.
|
|
7
7
|
*/
|
|
8
8
|
export interface TelemetryHooks {
|
|
9
9
|
onProgress?(event: {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowscripter/pluggable-io-framework-api",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "API
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "API for the https://github.com/flowscripter/pluggable-io-framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
7
7
|
"bun",
|
package/src/ChunkRef.ts
CHANGED
|
@@ -1,44 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two possible chunk memory origins.
|
|
3
|
+
*/
|
|
4
|
+
export enum ChunkKind {
|
|
5
|
+
Js = "js",
|
|
6
|
+
Native = "native",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** A chunk whose payload lives in a normal JS-managed Uint8Array. */
|
|
10
|
+
export interface JsChunk {
|
|
11
|
+
readonly kind: ChunkKind.Js;
|
|
12
|
+
readonly data: Uint8Array;
|
|
13
|
+
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A chunk whose payload lives in memory owned outside the JS heap (e.g. a
|
|
18
|
+
* Rust-allocated buffer). `release()` must be called once nothing needs the
|
|
19
|
+
* buffer - ownership/lifetime is explicit rather than GC'd, since JS code
|
|
20
|
+
* cannot safely retain a raw pointer past the point its owner frees it.
|
|
21
|
+
*/
|
|
22
|
+
export interface NativeChunk {
|
|
23
|
+
readonly kind: ChunkKind.Native;
|
|
24
|
+
readonly ptr: number;
|
|
25
|
+
readonly length: number;
|
|
26
|
+
release(): void;
|
|
27
|
+
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
28
|
+
}
|
|
29
|
+
|
|
1
30
|
/**
|
|
2
31
|
* A chunk of stream payload, tagged with its memory origin/ownership.
|
|
3
|
-
*
|
|
4
|
-
* This is the actual unit flowing through {@link StreamHandle} and
|
|
5
|
-
* {@link Part} streams - not a TS-vs-Rust-specific type. A pure-TS pipeline
|
|
6
|
-
* is simply a stream of `kind: "js"` chunks end to end.
|
|
7
|
-
*
|
|
8
|
-
* Tagging origin lets a consumer choose a zero-copy pointer handoff when
|
|
9
|
-
* compatible (js -> rust always; rust -> rust always) or fall back to an
|
|
10
|
-
* explicit copy when required (rust -> js, since JS code cannot safely
|
|
11
|
-
* retain a raw pointer past the call that produced it).
|
|
12
|
-
*
|
|
13
|
-
* `attributes` is deliberately shaped close to the future Flowscripter
|
|
14
|
-
* runtime's Item (attributes + payload) so this type is a natural fit if/when
|
|
15
|
-
* an `adapt` operator wraps these streams later. It is unused today.
|
|
16
32
|
*/
|
|
17
|
-
export type ChunkRef =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
22
|
-
}
|
|
23
|
-
| {
|
|
24
|
-
readonly kind: "native";
|
|
25
|
-
readonly ptr: number;
|
|
26
|
-
readonly length: number;
|
|
27
|
-
release(): void;
|
|
28
|
-
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
29
|
-
};
|
|
33
|
+
export type ChunkRef = JsChunk | NativeChunk;
|
|
34
|
+
|
|
35
|
+
/** The concrete chunk type produced by a stream tagged with a given kind. */
|
|
36
|
+
export type ChunkOfKind<K extends ChunkKind> = Extract<ChunkRef, { kind: K }>;
|
|
30
37
|
|
|
31
38
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
39
|
+
* Converts a single chunk to the target kind. Pure-TS code can only ever
|
|
40
|
+
* implement the identity case (same kind in, same kind out) - a real
|
|
41
|
+
* js<->native conversion needs FFI-capable pointer access and must be
|
|
42
|
+
* supplied by a runtime-specific package (e.g. via `bun:ffi`).
|
|
34
43
|
*/
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
export type ChunkConverter = (chunk: ChunkRef, toKind: ChunkKind) => ChunkRef;
|
|
45
|
+
|
|
46
|
+
/** Identity converter - only handles chunks already of the requested kind. */
|
|
47
|
+
export const identityChunkConverter: ChunkConverter = (chunk, toKind) => {
|
|
48
|
+
if (chunk.kind === toKind) {
|
|
49
|
+
return chunk;
|
|
38
50
|
}
|
|
39
51
|
throw new Error(
|
|
40
|
-
|
|
52
|
+
`Cannot convert a "${chunk.kind}" chunk to "${toKind}" without an FFI-capable ChunkConverter`,
|
|
41
53
|
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Adapts a homogeneous stream of one kind to another, using `convert`. When
|
|
58
|
+
* `fromKind === toKind` the stream is passed straight through untouched (no
|
|
59
|
+
* per-chunk work at all). This is the ONE place a kind mismatch is decided -
|
|
60
|
+
* once per stream link, not once per chunk.
|
|
61
|
+
*/
|
|
62
|
+
export function adaptReadableStream<From extends ChunkKind, To extends ChunkKind>(
|
|
63
|
+
stream: ReadableStream<ChunkOfKind<From>>,
|
|
64
|
+
fromKind: From,
|
|
65
|
+
toKind: To,
|
|
66
|
+
convert: ChunkConverter = identityChunkConverter,
|
|
67
|
+
): ReadableStream<ChunkOfKind<To>> {
|
|
68
|
+
if ((fromKind as ChunkKind) === (toKind as ChunkKind)) {
|
|
69
|
+
return stream as unknown as ReadableStream<ChunkOfKind<To>>;
|
|
70
|
+
}
|
|
71
|
+
const reader = stream.getReader();
|
|
72
|
+
return new ReadableStream<ChunkOfKind<To>>({
|
|
73
|
+
async pull(controller) {
|
|
74
|
+
const { done, value } = await reader.read();
|
|
75
|
+
if (done) {
|
|
76
|
+
controller.close();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
controller.enqueue(convert(value, toKind) as ChunkOfKind<To>);
|
|
80
|
+
},
|
|
81
|
+
cancel(reason) {
|
|
82
|
+
return reader.cancel(reason);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
42
85
|
}
|
|
43
86
|
|
|
44
87
|
/**
|
|
@@ -46,7 +89,7 @@ export function toUint8Array(chunk: ChunkRef): Uint8Array {
|
|
|
46
89
|
* external consumers). This is the one clearly-marked copy boundary -
|
|
47
90
|
* internal source/sink/decorator code speaks {@link ChunkRef} directly.
|
|
48
91
|
*/
|
|
49
|
-
export function toWebReadableStream(source: ReadableStream<
|
|
92
|
+
export function toWebReadableStream(source: ReadableStream<JsChunk>): ReadableStream<Uint8Array> {
|
|
50
93
|
const reader = source.getReader();
|
|
51
94
|
return new ReadableStream<Uint8Array>({
|
|
52
95
|
async pull(controller) {
|
|
@@ -55,7 +98,8 @@ export function toWebReadableStream(source: ReadableStream<ChunkRef>): ReadableS
|
|
|
55
98
|
controller.close();
|
|
56
99
|
return;
|
|
57
100
|
}
|
|
58
|
-
|
|
101
|
+
// value is statically a JsChunk here - no per-chunk kind check needed.
|
|
102
|
+
controller.enqueue(value.data);
|
|
59
103
|
},
|
|
60
104
|
cancel(reason) {
|
|
61
105
|
return reader.cancel(reason);
|
|
@@ -63,18 +107,16 @@ export function toWebReadableStream(source: ReadableStream<ChunkRef>): ReadableS
|
|
|
63
107
|
});
|
|
64
108
|
}
|
|
65
109
|
|
|
66
|
-
export function fromWebReadableStream(
|
|
67
|
-
source: ReadableStream<Uint8Array>,
|
|
68
|
-
): ReadableStream<ChunkRef> {
|
|
110
|
+
export function fromWebReadableStream(source: ReadableStream<Uint8Array>): ReadableStream<JsChunk> {
|
|
69
111
|
const reader = source.getReader();
|
|
70
|
-
return new ReadableStream<
|
|
112
|
+
return new ReadableStream<JsChunk>({
|
|
71
113
|
async pull(controller) {
|
|
72
114
|
const { done, value } = await reader.read();
|
|
73
115
|
if (done) {
|
|
74
116
|
controller.close();
|
|
75
117
|
return;
|
|
76
118
|
}
|
|
77
|
-
controller.enqueue({ kind:
|
|
119
|
+
controller.enqueue({ kind: ChunkKind.Js, data: value });
|
|
78
120
|
},
|
|
79
121
|
cancel(reason) {
|
|
80
122
|
return reader.cancel(reason);
|
package/src/IOProvider.ts
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
+
import type { ChunkKind } from "./ChunkRef.ts";
|
|
1
2
|
import type { ItemProperties } from "./ItemProperties.ts";
|
|
2
3
|
import type { Part } from "./Part.ts";
|
|
3
4
|
import type { StreamHandle } from "./StreamHandle.ts";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* A configured source/sink instance, as returned by
|
|
7
|
-
* {@link IOProviderFactory.createProvider}.
|
|
8
|
+
* {@link IOProviderFactory.createProvider}. `K` is the single
|
|
9
|
+
* {@link ChunkKind} this provider natively produces/consumes - e.g. a
|
|
10
|
+
* pure-TS filesystem plugin is `IOProvider<"js">`, a Rust-FFI-backed plugin
|
|
11
|
+
* is `IOProvider<"native">`.
|
|
8
12
|
*
|
|
9
|
-
* Disposal is
|
|
10
|
-
*
|
|
11
|
-
*
|
|
13
|
+
* Disposal is `Symbol.asyncDispose` (TC39 explicit resource management) -
|
|
14
|
+
* host code disposes deterministically via `await using provider = ...`,
|
|
15
|
+
* including on thrown errors, without needing a bespoke method name.
|
|
12
16
|
*/
|
|
13
|
-
export interface IOProvider {
|
|
14
|
-
|
|
17
|
+
export interface IOProvider<K extends ChunkKind = ChunkKind> {
|
|
18
|
+
/** The single chunk kind this provider natively produces/consumes. */
|
|
19
|
+
readonly kind: K;
|
|
20
|
+
|
|
21
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
15
22
|
|
|
16
23
|
list(
|
|
17
24
|
path: string,
|
|
@@ -21,10 +28,10 @@ export interface IOProvider {
|
|
|
21
28
|
setProperties(path: string, properties: Partial<Record<string, unknown>>): Promise<void>;
|
|
22
29
|
delete(path: string): Promise<void>;
|
|
23
30
|
|
|
24
|
-
getReadableStream(path: string): Promise<StreamHandle
|
|
25
|
-
getWritableStream(path: string): Promise<StreamHandle
|
|
26
|
-
getMultipartReader(path: string): AsyncIterable<Part
|
|
27
|
-
getMultipartWriter(path: string): { write(parts: AsyncIterable<Part
|
|
31
|
+
getReadableStream(path: string): Promise<StreamHandle<K>>;
|
|
32
|
+
getWritableStream(path: string): Promise<StreamHandle<K>>;
|
|
33
|
+
getMultipartReader(path: string): AsyncIterable<Part<K>>;
|
|
34
|
+
getMultipartWriter(path: string): { write(parts: AsyncIterable<Part<K>>): Promise<void> };
|
|
28
35
|
|
|
29
36
|
/**
|
|
30
37
|
* Self-reported direct-transfer eligibility - the provider owns what
|
package/src/IOProviderFactory.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ZodType } from "zod";
|
|
2
|
+
import type { ChunkKind } from "./ChunkRef.ts";
|
|
2
3
|
import type { IOProvider } from "./IOProvider.ts";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -13,13 +14,9 @@ export const PLUGGABLE_IO_FRAMEWORK_PROVIDER_FACTORY_EXTENSION_POINT =
|
|
|
13
14
|
* Returned (as `unknown`, cast at the extension point boundary) from
|
|
14
15
|
* `ExtensionFactory.create()` in a `dynamic-plugin-framework`
|
|
15
16
|
* `ExtensionDescriptor`.
|
|
16
|
-
*
|
|
17
|
-
* Config/property schema: Zod is the source of truth; JSON Schema can be
|
|
18
|
-
* derived via `zod-to-json-schema` for portability (hand-edited config
|
|
19
|
-
* files, non-TS validation).
|
|
20
17
|
*/
|
|
21
|
-
export interface IOProviderFactory<TConfig = unknown> {
|
|
18
|
+
export interface IOProviderFactory<TConfig = unknown, K extends ChunkKind = ChunkKind> {
|
|
22
19
|
readonly configSchema: ZodType<TConfig>;
|
|
23
20
|
readonly propertySchema: ZodType<Record<string, unknown>>;
|
|
24
|
-
createProvider(config: TConfig): Promise<IOProvider
|
|
21
|
+
createProvider(config: TConfig): Promise<IOProvider<K>>;
|
|
25
22
|
}
|
package/src/ItemProperties.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Properties of a file/folder item. `size`, `lastModified`, `isFolder` and
|
|
3
3
|
* `contentType` are well-known, framework-guaranteed fields every provider
|
|
4
|
-
* must populate
|
|
5
|
-
* on them without knowing the provider. Anything provider-specific (etag,
|
|
4
|
+
* must populate. Anything provider-specific (etag,
|
|
6
5
|
* storage class, custom tags) goes in `properties`, validated against that
|
|
7
6
|
* provider's `propertySchema`.
|
|
8
7
|
*/
|
package/src/Part.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChunkKind, ChunkOfKind } from "./ChunkRef.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* One independently readable/writable part of a multipart transfer
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* One independently readable/writable part of a multipart transfer, carrying
|
|
5
|
+
* a homogeneous stream of a single declared {@link ChunkKind}. Parts may be
|
|
6
|
+
* processed concurrently (e.g. `Promise.all` over N parts); the provider
|
|
7
|
+
* assembles/commits them once all parts finish.
|
|
7
8
|
*/
|
|
8
|
-
export interface Part {
|
|
9
|
+
export interface Part<K extends ChunkKind = ChunkKind> {
|
|
9
10
|
readonly index: number;
|
|
10
11
|
readonly offset: number;
|
|
11
|
-
readonly
|
|
12
|
+
readonly kind: K;
|
|
13
|
+
readonly stream: ReadableStream<ChunkOfKind<K>> | WritableStream<ChunkOfKind<K>>;
|
|
12
14
|
complete(): Promise<void>;
|
|
13
15
|
}
|
package/src/StreamDecorator.ts
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
|
+
import type { ChunkKind, ChunkOfKind } from "./ChunkRef.ts";
|
|
1
2
|
import type { StreamHandle } from "./StreamHandle.ts";
|
|
2
3
|
|
|
4
|
+
/** Capability added by the `seekable` decorator: jump to an absolute offset. */
|
|
5
|
+
export interface Seekable {
|
|
6
|
+
seek(offset: number): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isSeekable<K extends ChunkKind>(
|
|
10
|
+
handle: StreamHandle<K>,
|
|
11
|
+
): handle is StreamHandle<K> & Seekable {
|
|
12
|
+
return typeof (handle as Partial<Seekable>).seek === "function";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Capability added by decorators that can serve an arbitrary byte range directly. */
|
|
16
|
+
export interface RangeReadable<K extends ChunkKind = ChunkKind> {
|
|
17
|
+
readRange(start: number, end: number): Promise<ReadableStream<ChunkOfKind<K>>>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isRangeReadable<K extends ChunkKind>(
|
|
21
|
+
handle: StreamHandle<K>,
|
|
22
|
+
): handle is StreamHandle<K> & RangeReadable<K> {
|
|
23
|
+
return typeof (handle as Partial<RangeReadable<K>>).readRange === "function";
|
|
24
|
+
}
|
|
25
|
+
|
|
3
26
|
/**
|
|
4
27
|
* A decorator wraps a {@link StreamHandle} and returns an enhanced handle
|
|
5
|
-
* with additional
|
|
6
|
-
*
|
|
7
|
-
*
|
|
28
|
+
* with additional capabilities `C` (e.g. {@link Seekable}). When `C` is
|
|
29
|
+
* known at the call site (the common case - a specific decorator is applied
|
|
30
|
+
* directly), consumers get static typing with no runtime capability check
|
|
31
|
+
* needed. Where a handle arrives already decorated by unknown/dynamic
|
|
32
|
+
* decorators, use the `is*` type guards above instead.
|
|
8
33
|
*/
|
|
9
|
-
export type StreamDecorator =
|
|
34
|
+
export type StreamDecorator<K extends ChunkKind = ChunkKind, C extends object = object> = (
|
|
35
|
+
handle: StreamHandle<K>,
|
|
36
|
+
) => StreamHandle<K> & C;
|
package/src/StreamHandle.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChunkKind, ChunkOfKind } from "./ChunkRef.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A handle to a readable or writable stream of
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* feature-detect via `typeof handle.seek === "function"` etc.
|
|
4
|
+
* A handle to a readable or writable stream of a single, declared
|
|
5
|
+
* {@link ChunkKind} - homogeneous, so consumers never need to test each
|
|
6
|
+
* chunk's kind. Plus optional capability methods for providers/decorators
|
|
7
|
+
* that support random-access beyond plain sequential read (e.g. `seekable`)
|
|
8
|
+
* - see {@link Seekable}/{@link isSeekable} in `StreamDecorator.ts`.
|
|
10
9
|
*/
|
|
11
|
-
export interface StreamHandle {
|
|
12
|
-
readonly
|
|
13
|
-
|
|
14
|
-
readRange?(start: number, end: number): Promise<ReadableStream<ChunkRef>>;
|
|
10
|
+
export interface StreamHandle<K extends ChunkKind = ChunkKind> {
|
|
11
|
+
readonly kind: K;
|
|
12
|
+
readonly stream: ReadableStream<ChunkOfKind<K>> | WritableStream<ChunkOfKind<K>>;
|
|
15
13
|
}
|
package/src/TelemetryHooks.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global telemetry hooks supplied once at framework initialisation. Every
|
|
3
|
-
* operation is given a correlation
|
|
4
|
-
* tagged with that
|
|
3
|
+
* operation is given a correlation ID and reports through the same hooks,
|
|
4
|
+
* tagged with that ID/operation-type. Callers don't need to thread a
|
|
5
5
|
* callback through every call, but can still track individual operations
|
|
6
|
-
* via the
|
|
6
|
+
* via the ID in emitted events.
|
|
7
7
|
*/
|
|
8
8
|
export interface TelemetryHooks {
|
|
9
9
|
onProgress?(event: {
|