@flowscripter/pluggable-io-framework-api 0.1.0

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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/dist/index.d.ts +9 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +8 -0
  6. package/dist/src/ChunkRef.d.ts +40 -0
  7. package/dist/src/ChunkRef.d.ts.map +1 -0
  8. package/dist/src/ChunkRef.js +47 -0
  9. package/dist/src/IOProvider.d.ts +39 -0
  10. package/dist/src/IOProvider.d.ts.map +1 -0
  11. package/dist/src/IOProvider.js +1 -0
  12. package/dist/src/IOProviderFactory.d.ts +23 -0
  13. package/dist/src/IOProviderFactory.d.ts.map +1 -0
  14. package/dist/src/IOProviderFactory.js +6 -0
  15. package/dist/src/ItemProperties.d.ts +16 -0
  16. package/dist/src/ItemProperties.d.ts.map +1 -0
  17. package/dist/src/ItemProperties.js +1 -0
  18. package/dist/src/Part.d.ts +13 -0
  19. package/dist/src/Part.d.ts.map +1 -0
  20. package/dist/src/Part.js +1 -0
  21. package/dist/src/StreamDecorator.d.ts +9 -0
  22. package/dist/src/StreamDecorator.d.ts.map +1 -0
  23. package/dist/src/StreamDecorator.js +1 -0
  24. package/dist/src/StreamHandle.d.ts +15 -0
  25. package/dist/src/StreamHandle.d.ts.map +1 -0
  26. package/dist/src/StreamHandle.js +1 -0
  27. package/dist/src/TelemetryHooks.d.ts +21 -0
  28. package/dist/src/TelemetryHooks.d.ts.map +1 -0
  29. package/dist/src/TelemetryHooks.js +1 -0
  30. package/index.ts +8 -0
  31. package/package.json +60 -0
  32. package/src/ChunkRef.ts +83 -0
  33. package/src/IOProvider.ts +37 -0
  34. package/src/IOProviderFactory.ts +25 -0
  35. package/src/ItemProperties.ts +15 -0
  36. package/src/Part.ts +13 -0
  37. package/src/StreamDecorator.ts +9 -0
  38. package/src/StreamHandle.ts +15 -0
  39. package/src/TelemetryHooks.ts +16 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flowscripter
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # pluggable-io-framework-api
2
+
3
+ [![version](https://img.shields.io/github/v/release/flowscripter/pluggable-io-framework-api?sort=semver)](https://github.com/flowscripter/pluggable-io-framework-api/releases)
4
+ [![build](https://img.shields.io/github/actions/workflow/status/flowscripter/pluggable-io-framework-api/release-bun-library.yml)](https://github.com/flowscripter/pluggable-io-framework-api/actions/workflows/release-bun-library.yml)
5
+ [![docs](https://img.shields.io/badge/docs-API-blue)](https://flowscripter.github.io/pluggable-io-framework-api/index.html)
6
+ [![license: MIT](https://img.shields.io/github/license/flowscripter/pluggable-io-framework-api)](https://github.com/flowscripter/pluggable-io-framework-api/blob/main/LICENSE)
7
+
8
+ > API contracts for [pluggable-io-framework](https://github.com/flowscripter/pluggable-io-framework)
9
+ > source/sink provider plugins
10
+
11
+ ## Key Features
12
+
13
+ - Defines the `IOProviderFactory`/`IOProvider` contract that source/sink
14
+ plugins (e.g. local filesystem, object storage) implement, discovered and
15
+ loaded via
16
+ [dynamic-plugin-framework](https://github.com/flowscripter/dynamic-plugin-framework).
17
+ - Config and per-item property schemas are defined with
18
+ [Zod](https://zod.dev) (source of truth), portable to JSON Schema via
19
+ `zod-to-json-schema` for hand-edited config files or non-TS validation.
20
+ - `ChunkRef`: a tagged-union stream payload (`js` vs `native`) that carries
21
+ memory ownership/origin with it, enabling zero-copy handoff to/from
22
+ Rust-FFI-backed providers and decorators, with a small adapter to the
23
+ standard Web Streams `ReadableStream<Uint8Array>`/`WritableStream<Uint8Array>`
24
+ for interop (`fetch`, `pipeTo`, etc.).
25
+ - Well-known item properties (`size`, `lastModified`, `isFolder`,
26
+ `contentType`) guaranteed by every provider, plus a provider-specific
27
+ `properties` extension bag for anything else (etag, storage class, custom
28
+ tags).
29
+ - Multipart transfer modeled as a stream of independently readable/writable
30
+ `Part` handles, so parts can be processed concurrently.
31
+ - Telemetry as a global `TelemetryHooks` object supplied once at
32
+ initialisation; every operation reports through it tagged with a
33
+ correlation id.
34
+ - Pure TypeScript, minimal dependencies, no concrete provider
35
+ implementations - see
36
+ [pluggable-io-framework](https://github.com/flowscripter/pluggable-io-framework)
37
+ for orchestration and
38
+ [pluggable-io-framework-plugin-filesystem](https://github.com/flowscripter/pluggable-io-framework-plugin-filesystem)
39
+ for a reference implementation.
40
+
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
+ ## Development
66
+
67
+ Install dependencies:
68
+
69
+ `bun install`
70
+
71
+ Build (produces `dist/` for Node.js and TypeScript consumers; Bun uses raw source directly):
72
+
73
+ `bun run build`
74
+
75
+ Test:
76
+
77
+ `bun test`
78
+
79
+ Format:
80
+
81
+ `bunx oxfmt`
82
+
83
+ Lint:
84
+
85
+ `bunx oxlint index.ts src/ tests/`
86
+
87
+ Generate HTML API Documentation:
88
+
89
+ `bunx typedoc index.ts`
90
+
91
+ ## Documentation
92
+
93
+ ### Overview
94
+
95
+ ```mermaid
96
+ classDiagram
97
+ IOProviderFactory --> IOProvider : creates
98
+ IOProvider --> StreamHandle : returns
99
+ IOProvider --> Part : returns (multipart)
100
+ StreamHandle --> ChunkRef : streams
101
+ Part --> ChunkRef : streams
102
+ IOProvider --> ItemProperties : returns
103
+
104
+ class IOProviderFactory {
105
+ +configSchema
106
+ +propertySchema
107
+ +createProvider(config)
108
+ }
109
+ class IOProvider {
110
+ +dispose()
111
+ +list(path, options)
112
+ +getProperties(path)
113
+ +setProperties(path, properties)
114
+ +delete(path)
115
+ +getReadableStream(path)
116
+ +getWritableStream(path)
117
+ +getMultipartReader(path)
118
+ +getMultipartWriter(path)
119
+ +canDirectTransfer(other)
120
+ +directCopy(sourcePath, destPath)
121
+ +directMove(sourcePath, destPath)
122
+ }
123
+ ```
124
+
125
+ ### API
126
+
127
+ Link to auto-generated API docs:
128
+
129
+ [API Documentation](https://flowscripter.github.io/pluggable-io-framework-api/index.html)
130
+
131
+ ## License
132
+
133
+ MIT © Flowscripter
@@ -0,0 +1,9 @@
1
+ export * from "./src/ChunkRef.ts";
2
+ export * from "./src/IOProvider.ts";
3
+ export * from "./src/IOProviderFactory.ts";
4
+ export * from "./src/ItemProperties.ts";
5
+ export * from "./src/Part.ts";
6
+ export * from "./src/StreamDecorator.ts";
7
+ export * from "./src/StreamHandle.ts";
8
+ export * from "./src/TelemetryHooks.ts";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC;AAC9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./src/ChunkRef.js";
2
+ export * from "./src/IOProvider.js";
3
+ export * from "./src/IOProviderFactory.js";
4
+ export * from "./src/ItemProperties.js";
5
+ export * from "./src/Part.js";
6
+ export * from "./src/StreamDecorator.js";
7
+ export * from "./src/StreamHandle.js";
8
+ export * from "./src/TelemetryHooks.js";
@@ -0,0 +1,40 @@
1
+ /**
2
+ * 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
+ */
17
+ export type ChunkRef = {
18
+ readonly kind: "js";
19
+ readonly data: Uint8Array;
20
+ readonly attributes?: Readonly<Record<string, unknown>>;
21
+ } | {
22
+ readonly kind: "native";
23
+ readonly ptr: number;
24
+ readonly length: number;
25
+ release(): void;
26
+ readonly attributes?: Readonly<Record<string, unknown>>;
27
+ };
28
+ /**
29
+ * Copy a chunk's bytes into a plain Uint8Array, regardless of origin.
30
+ * The only copy incurred is for `kind: "native"` chunks.
31
+ */
32
+ export declare function toUint8Array(chunk: ChunkRef): Uint8Array;
33
+ /**
34
+ * Adapter to the standard Web Streams interop surface (fetch, pipeTo
35
+ * external consumers). This is the one clearly-marked copy boundary -
36
+ * internal source/sink/decorator code speaks {@link ChunkRef} directly.
37
+ */
38
+ export declare function toWebReadableStream(source: ReadableStream<ChunkRef>): ReadableStream<Uint8Array>;
39
+ export declare function fromWebReadableStream(source: ReadableStream<Uint8Array>): ReadableStream<ChunkRef>;
40
+ //# sourceMappingURL=ChunkRef.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChunkRef.d.ts","sourceRoot":"","sources":["../../src/ChunkRef.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,QAAQ,GAChB;IACE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACzD,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,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,CAAC;AAEN;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,UAAU,CAOxD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC,UAAU,CAAC,CAehG;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACjC,cAAc,CAAC,QAAQ,CAAC,CAe1B"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Copy a chunk's bytes into a plain Uint8Array, regardless of origin.
3
+ * The only copy incurred is for `kind: "native"` chunks.
4
+ */
5
+ export function toUint8Array(chunk) {
6
+ if (chunk.kind === "js") {
7
+ return chunk.data;
8
+ }
9
+ throw new Error("Copying a native ChunkRef to Uint8Array requires an FFI-capable runtime helper - not implemented in pluggable-io-framework-api");
10
+ }
11
+ /**
12
+ * Adapter to the standard Web Streams interop surface (fetch, pipeTo
13
+ * external consumers). This is the one clearly-marked copy boundary -
14
+ * internal source/sink/decorator code speaks {@link ChunkRef} directly.
15
+ */
16
+ export function toWebReadableStream(source) {
17
+ const reader = source.getReader();
18
+ return new ReadableStream({
19
+ async pull(controller) {
20
+ const { done, value } = await reader.read();
21
+ if (done) {
22
+ controller.close();
23
+ return;
24
+ }
25
+ controller.enqueue(toUint8Array(value));
26
+ },
27
+ cancel(reason) {
28
+ return reader.cancel(reason);
29
+ },
30
+ });
31
+ }
32
+ export function fromWebReadableStream(source) {
33
+ const reader = source.getReader();
34
+ return new ReadableStream({
35
+ async pull(controller) {
36
+ const { done, value } = await reader.read();
37
+ if (done) {
38
+ controller.close();
39
+ return;
40
+ }
41
+ controller.enqueue({ kind: "js", data: value });
42
+ },
43
+ cancel(reason) {
44
+ return reader.cancel(reason);
45
+ },
46
+ });
47
+ }
@@ -0,0 +1,39 @@
1
+ import type { ItemProperties } from "./ItemProperties.ts";
2
+ import type { Part } from "./Part.ts";
3
+ import type { StreamHandle } from "./StreamHandle.ts";
4
+ /**
5
+ * A configured source/sink instance, as returned by
6
+ * {@link IOProviderFactory.createProvider}.
7
+ *
8
+ * Disposal is the explicit {@link IOProvider.dispose} method - not a reliance
9
+ * on `Symbol.asyncDispose` - so plugins have an unambiguous, discoverable
10
+ * contract for releasing connections/handles.
11
+ */
12
+ export interface IOProvider {
13
+ dispose(): Promise<void>;
14
+ list(path: string, options?: {
15
+ recursive?: boolean;
16
+ regex?: RegExp;
17
+ }): AsyncIterable<{
18
+ path: string;
19
+ properties: ItemProperties;
20
+ }>;
21
+ getProperties(path: string): Promise<ItemProperties>;
22
+ setProperties(path: string, properties: Partial<Record<string, unknown>>): Promise<void>;
23
+ delete(path: string): Promise<void>;
24
+ getReadableStream(path: string): Promise<StreamHandle>;
25
+ getWritableStream(path: string): Promise<StreamHandle>;
26
+ getMultipartReader(path: string): AsyncIterable<Part>;
27
+ getMultipartWriter(path: string): {
28
+ write(parts: AsyncIterable<Part>): Promise<void>;
29
+ };
30
+ /**
31
+ * Self-reported direct-transfer eligibility - the provider owns what
32
+ * "same" means for its backend (e.g. same mount for filesystem, same
33
+ * bucket+region+credentials for object storage).
34
+ */
35
+ canDirectTransfer?(other: IOProvider): boolean;
36
+ directCopy?(sourcePath: string, destPath: string): Promise<void>;
37
+ directMove?(sourcePath: string, destPath: string): Promise<void>;
38
+ }
39
+ //# sourceMappingURL=IOProvider.d.ts.map
@@ -0,0 +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;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzB,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;IACvD,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACvD,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACtD,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAEvF;;;;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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { ZodType } from "zod";
2
+ import type { IOProvider } from "./IOProvider.ts";
3
+ /**
4
+ * Extension point constant that a `dynamic-plugin-framework` `Plugin`'s
5
+ * `ExtensionDescriptor.extensionPoint` must match to be discovered as a
6
+ * pluggable-io-framework source/sink provider.
7
+ */
8
+ export declare const PLUGGABLE_IO_FRAMEWORK_PROVIDER_FACTORY_EXTENSION_POINT = "@flowscripter/pluggable-io-framework/provider-factory";
9
+ /**
10
+ * Returned (as `unknown`, cast at the extension point boundary) from
11
+ * `ExtensionFactory.create()` in a `dynamic-plugin-framework`
12
+ * `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
+ */
18
+ export interface IOProviderFactory<TConfig = unknown> {
19
+ readonly configSchema: ZodType<TConfig>;
20
+ readonly propertySchema: ZodType<Record<string, unknown>>;
21
+ createProvider(config: TConfig): Promise<IOProvider>;
22
+ }
23
+ //# sourceMappingURL=IOProviderFactory.d.ts.map
@@ -0,0 +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;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB,CAAC,OAAO,GAAG,OAAO;IAClD,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;CACtD"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Extension point constant that a `dynamic-plugin-framework` `Plugin`'s
3
+ * `ExtensionDescriptor.extensionPoint` must match to be discovered as a
4
+ * pluggable-io-framework source/sink provider.
5
+ */
6
+ export const PLUGGABLE_IO_FRAMEWORK_PROVIDER_FACTORY_EXTENSION_POINT = "@flowscripter/pluggable-io-framework/provider-factory";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Properties of a file/folder item. `size`, `lastModified`, `isFolder` and
3
+ * `contentType` are well-known, framework-guaranteed fields every provider
4
+ * must populate so generic consumers (CLI, copy/move orchestration) can rely
5
+ * on them without knowing the provider. Anything provider-specific (etag,
6
+ * storage class, custom tags) goes in `properties`, validated against that
7
+ * provider's `propertySchema`.
8
+ */
9
+ export interface ItemProperties {
10
+ readonly size: number | undefined;
11
+ readonly lastModified: Date | undefined;
12
+ readonly isFolder: boolean;
13
+ readonly contentType?: string;
14
+ readonly properties: Readonly<Record<string, unknown>>;
15
+ }
16
+ //# sourceMappingURL=ItemProperties.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ItemProperties.d.ts","sourceRoot":"","sources":["../../src/ItemProperties.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ import type { ChunkRef } from "./ChunkRef.ts";
2
+ /**
3
+ * One independently readable/writable part of a multipart transfer.
4
+ * Parts may be processed concurrently (e.g. `Promise.all` over N parts);
5
+ * the provider assembles/commits them once all parts finish.
6
+ */
7
+ export interface Part {
8
+ readonly index: number;
9
+ readonly offset: number;
10
+ readonly stream: ReadableStream<ChunkRef> | WritableStream<ChunkRef>;
11
+ complete(): Promise<void>;
12
+ }
13
+ //# sourceMappingURL=Part.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Part.d.ts","sourceRoot":"","sources":["../../src/Part.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACrE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { StreamHandle } from "./StreamHandle.ts";
2
+ /**
3
+ * A decorator wraps a {@link StreamHandle} and returns an enhanced handle
4
+ * with additional capability methods (e.g. `seekable` adds `seek`/
5
+ * `readRange`; `locally-cached` transparently serves repeated reads from a
6
+ * local cache).
7
+ */
8
+ export type StreamDecorator = (handle: StreamHandle) => StreamHandle;
9
+ //# sourceMappingURL=StreamDecorator.d.ts.map
@@ -0,0 +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;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,YAAY,KAAK,YAAY,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import type { ChunkRef } from "./ChunkRef.ts";
2
+ /**
3
+ * A handle to a readable or writable stream of {@link ChunkRef}s, plus
4
+ * optional capability methods for providers/decorators that support
5
+ * random-access beyond plain sequential read (e.g. `seekable`).
6
+ *
7
+ * Capability methods are present only when supported - consumers
8
+ * feature-detect via `typeof handle.seek === "function"` etc.
9
+ */
10
+ export interface StreamHandle {
11
+ readonly stream: ReadableStream<ChunkRef> | WritableStream<ChunkRef>;
12
+ seek?(offset: number): Promise<void>;
13
+ readRange?(start: number, end: number): Promise<ReadableStream<ChunkRef>>;
14
+ }
15
+ //# sourceMappingURL=StreamHandle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamHandle.d.ts","sourceRoot":"","sources":["../../src/StreamHandle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACrE,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,SAAS,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;CAC3E"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Global telemetry hooks supplied once at framework initialisation. Every
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
+ * callback through every call, but can still track individual operations
6
+ * via the id in emitted events.
7
+ */
8
+ export interface TelemetryHooks {
9
+ onProgress?(event: {
10
+ operationId: string;
11
+ type: string;
12
+ bytesProcessed: number;
13
+ totalBytes?: number;
14
+ }): void;
15
+ onMetric?(event: {
16
+ name: string;
17
+ value: number;
18
+ tags?: Record<string, string>;
19
+ }): void;
20
+ }
21
+ //# sourceMappingURL=TelemetryHooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TelemetryHooks.d.ts","sourceRoot":"","sources":["../../src/TelemetryHooks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,CAAC,KAAK,EAAE;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,cAAc,EAAE,MAAM,CAAC;QACvB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG,IAAI,CAAC;IACT,QAAQ,CAAC,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;CACxF"}
@@ -0,0 +1 @@
1
+ export {};
package/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./src/ChunkRef.ts";
2
+ export * from "./src/IOProvider.ts";
3
+ export * from "./src/IOProviderFactory.ts";
4
+ export * from "./src/ItemProperties.ts";
5
+ export * from "./src/Part.ts";
6
+ export * from "./src/StreamDecorator.ts";
7
+ export * from "./src/StreamHandle.ts";
8
+ export * from "./src/TelemetryHooks.ts";
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@flowscripter/pluggable-io-framework-api",
3
+ "version": "0.1.0",
4
+ "description": "API contracts for pluggable-io-framework source/sink provider plugins",
5
+ "keywords": [
6
+ "api",
7
+ "bun",
8
+ "io",
9
+ "plugin"
10
+ ],
11
+ "homepage": "https://github.com/flowscripter/pluggable-io-framework-api#readme",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/flowscripter/pluggable-io-framework-api.git"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src",
20
+ "index.ts",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "type": "module",
25
+ "main": "dist/index.js",
26
+ "module": "dist/index.js",
27
+ "types": "dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "bun": "./index.ts",
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.js"
33
+ }
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.build.json",
40
+ "test": "bun test"
41
+ },
42
+ "dependencies": {
43
+ "zod": "^3.24.0",
44
+ "zod-to-json-schema": "^3.24.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/bun": "^1.3.14",
48
+ "oxfmt": "0.57.0",
49
+ "oxlint": "1.72.0"
50
+ },
51
+ "peerDependencies": {
52
+ "typescript": "^6.0.3"
53
+ },
54
+ "typedocOptions": {
55
+ "readme": "none",
56
+ "exclude": [
57
+ "tests/**"
58
+ ]
59
+ }
60
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * 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
+ */
17
+ export type ChunkRef =
18
+ | {
19
+ readonly kind: "js";
20
+ readonly data: Uint8Array;
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
+ };
30
+
31
+ /**
32
+ * Copy a chunk's bytes into a plain Uint8Array, regardless of origin.
33
+ * The only copy incurred is for `kind: "native"` chunks.
34
+ */
35
+ export function toUint8Array(chunk: ChunkRef): Uint8Array {
36
+ if (chunk.kind === "js") {
37
+ return chunk.data;
38
+ }
39
+ throw new Error(
40
+ "Copying a native ChunkRef to Uint8Array requires an FFI-capable runtime helper - not implemented in pluggable-io-framework-api",
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Adapter to the standard Web Streams interop surface (fetch, pipeTo
46
+ * external consumers). This is the one clearly-marked copy boundary -
47
+ * internal source/sink/decorator code speaks {@link ChunkRef} directly.
48
+ */
49
+ export function toWebReadableStream(source: ReadableStream<ChunkRef>): ReadableStream<Uint8Array> {
50
+ const reader = source.getReader();
51
+ return new ReadableStream<Uint8Array>({
52
+ async pull(controller) {
53
+ const { done, value } = await reader.read();
54
+ if (done) {
55
+ controller.close();
56
+ return;
57
+ }
58
+ controller.enqueue(toUint8Array(value));
59
+ },
60
+ cancel(reason) {
61
+ return reader.cancel(reason);
62
+ },
63
+ });
64
+ }
65
+
66
+ export function fromWebReadableStream(
67
+ source: ReadableStream<Uint8Array>,
68
+ ): ReadableStream<ChunkRef> {
69
+ const reader = source.getReader();
70
+ return new ReadableStream<ChunkRef>({
71
+ async pull(controller) {
72
+ const { done, value } = await reader.read();
73
+ if (done) {
74
+ controller.close();
75
+ return;
76
+ }
77
+ controller.enqueue({ kind: "js", data: value });
78
+ },
79
+ cancel(reason) {
80
+ return reader.cancel(reason);
81
+ },
82
+ });
83
+ }
@@ -0,0 +1,37 @@
1
+ import type { ItemProperties } from "./ItemProperties.ts";
2
+ import type { Part } from "./Part.ts";
3
+ import type { StreamHandle } from "./StreamHandle.ts";
4
+
5
+ /**
6
+ * A configured source/sink instance, as returned by
7
+ * {@link IOProviderFactory.createProvider}.
8
+ *
9
+ * Disposal is the explicit {@link IOProvider.dispose} method - not a reliance
10
+ * on `Symbol.asyncDispose` - so plugins have an unambiguous, discoverable
11
+ * contract for releasing connections/handles.
12
+ */
13
+ export interface IOProvider {
14
+ dispose(): Promise<void>;
15
+
16
+ list(
17
+ path: string,
18
+ options?: { recursive?: boolean; regex?: RegExp },
19
+ ): AsyncIterable<{ path: string; properties: ItemProperties }>;
20
+ getProperties(path: string): Promise<ItemProperties>;
21
+ setProperties(path: string, properties: Partial<Record<string, unknown>>): Promise<void>;
22
+ delete(path: string): Promise<void>;
23
+
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>): Promise<void> };
28
+
29
+ /**
30
+ * Self-reported direct-transfer eligibility - the provider owns what
31
+ * "same" means for its backend (e.g. same mount for filesystem, same
32
+ * bucket+region+credentials for object storage).
33
+ */
34
+ canDirectTransfer?(other: IOProvider): boolean;
35
+ directCopy?(sourcePath: string, destPath: string): Promise<void>;
36
+ directMove?(sourcePath: string, destPath: string): Promise<void>;
37
+ }
@@ -0,0 +1,25 @@
1
+ import type { ZodType } from "zod";
2
+ import type { IOProvider } from "./IOProvider.ts";
3
+
4
+ /**
5
+ * Extension point constant that a `dynamic-plugin-framework` `Plugin`'s
6
+ * `ExtensionDescriptor.extensionPoint` must match to be discovered as a
7
+ * pluggable-io-framework source/sink provider.
8
+ */
9
+ export const PLUGGABLE_IO_FRAMEWORK_PROVIDER_FACTORY_EXTENSION_POINT =
10
+ "@flowscripter/pluggable-io-framework/provider-factory";
11
+
12
+ /**
13
+ * Returned (as `unknown`, cast at the extension point boundary) from
14
+ * `ExtensionFactory.create()` in a `dynamic-plugin-framework`
15
+ * `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
+ */
21
+ export interface IOProviderFactory<TConfig = unknown> {
22
+ readonly configSchema: ZodType<TConfig>;
23
+ readonly propertySchema: ZodType<Record<string, unknown>>;
24
+ createProvider(config: TConfig): Promise<IOProvider>;
25
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Properties of a file/folder item. `size`, `lastModified`, `isFolder` and
3
+ * `contentType` are well-known, framework-guaranteed fields every provider
4
+ * must populate so generic consumers (CLI, copy/move orchestration) can rely
5
+ * on them without knowing the provider. Anything provider-specific (etag,
6
+ * storage class, custom tags) goes in `properties`, validated against that
7
+ * provider's `propertySchema`.
8
+ */
9
+ export interface ItemProperties {
10
+ readonly size: number | undefined;
11
+ readonly lastModified: Date | undefined;
12
+ readonly isFolder: boolean;
13
+ readonly contentType?: string;
14
+ readonly properties: Readonly<Record<string, unknown>>;
15
+ }
package/src/Part.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { ChunkRef } from "./ChunkRef.ts";
2
+
3
+ /**
4
+ * One independently readable/writable part of a multipart transfer.
5
+ * Parts may be processed concurrently (e.g. `Promise.all` over N parts);
6
+ * the provider assembles/commits them once all parts finish.
7
+ */
8
+ export interface Part {
9
+ readonly index: number;
10
+ readonly offset: number;
11
+ readonly stream: ReadableStream<ChunkRef> | WritableStream<ChunkRef>;
12
+ complete(): Promise<void>;
13
+ }
@@ -0,0 +1,9 @@
1
+ import type { StreamHandle } from "./StreamHandle.ts";
2
+
3
+ /**
4
+ * A decorator wraps a {@link StreamHandle} and returns an enhanced handle
5
+ * with additional capability methods (e.g. `seekable` adds `seek`/
6
+ * `readRange`; `locally-cached` transparently serves repeated reads from a
7
+ * local cache).
8
+ */
9
+ export type StreamDecorator = (handle: StreamHandle) => StreamHandle;
@@ -0,0 +1,15 @@
1
+ import type { ChunkRef } from "./ChunkRef.ts";
2
+
3
+ /**
4
+ * A handle to a readable or writable stream of {@link ChunkRef}s, plus
5
+ * optional capability methods for providers/decorators that support
6
+ * random-access beyond plain sequential read (e.g. `seekable`).
7
+ *
8
+ * Capability methods are present only when supported - consumers
9
+ * feature-detect via `typeof handle.seek === "function"` etc.
10
+ */
11
+ export interface StreamHandle {
12
+ readonly stream: ReadableStream<ChunkRef> | WritableStream<ChunkRef>;
13
+ seek?(offset: number): Promise<void>;
14
+ readRange?(start: number, end: number): Promise<ReadableStream<ChunkRef>>;
15
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Global telemetry hooks supplied once at framework initialisation. Every
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
+ * callback through every call, but can still track individual operations
6
+ * via the id in emitted events.
7
+ */
8
+ export interface TelemetryHooks {
9
+ onProgress?(event: {
10
+ operationId: string;
11
+ type: string;
12
+ bytesProcessed: number;
13
+ totalBytes?: number;
14
+ }): void;
15
+ onMetric?(event: { name: string; value: number; tags?: Record<string, string> }): void;
16
+ }