@kb-labs/workflow-artifacts 1.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.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # @kb-labs/workflow-artifacts
2
+
3
+ Artifact helpers for KB Labs workflow engine.
4
+
5
+ ## Vision & Purpose
6
+
7
+ **@kb-labs/workflow-artifacts** provides artifact helpers for the KB Labs workflow engine. It includes file system client for reading and writing workflow artifacts, artifact management, and path organization.
8
+
9
+ ### Core Goals
10
+
11
+ - **File System Client**: Operations for reading and writing workflow artifacts
12
+ - **Artifact Management**: Helpers for artifact paths and organization
13
+ - **Stream Support**: Support for streaming artifacts
14
+
15
+ ## Package Status
16
+
17
+ - **Version**: 0.1.0
18
+ - **Stage**: Stable
19
+ - **Status**: Production Ready ✅
20
+
21
+ ## Architecture
22
+
23
+ ### High-Level Overview
24
+
25
+ ```
26
+ Workflow Artifacts
27
+
28
+ ├──► File System Client
29
+ ├──► Artifact Types
30
+ └──► Path Management
31
+ ```
32
+
33
+ ### Key Components
34
+
35
+ 1. **FileSystemArtifactClient** (`fs-client.ts`): File system-based artifact client
36
+ 2. **Types** (`types.ts`): Artifact type definitions
37
+
38
+ ## ✨ Features
39
+
40
+ - **File system client** for artifacts
41
+ - **Read/write operations** for artifacts
42
+ - **Stream support** for large artifacts
43
+ - **Path validation** and security
44
+ - **List operations** for artifact discovery
45
+
46
+ ## 📦 API Reference
47
+
48
+ ### Main Exports
49
+
50
+ #### Client Classes
51
+
52
+ - `FileSystemArtifactClient`: File system-based artifact client
53
+ - `createFileSystemArtifactClient(options)`: Factory function
54
+
55
+ #### Types & Interfaces
56
+
57
+ - `ArtifactClient`: Artifact client interface
58
+ - `ArtifactInput`: Artifact input types
59
+ - `ArtifactOutput`: Artifact output type
60
+ - `ArtifactReference`: Artifact reference type
61
+
62
+ ### Types & Interfaces
63
+
64
+ #### `ArtifactClient`
65
+
66
+ ```typescript
67
+ interface ArtifactClient {
68
+ produce(path: string, input: ArtifactInput): Promise<void>;
69
+ consume(path: string): Promise<ArtifactOutput>;
70
+ list?(prefix?: string): Promise<ArtifactReference[]>;
71
+ basePath?: () => string;
72
+ }
73
+ ```
74
+
75
+ #### `ArtifactInput`
76
+
77
+ ```typescript
78
+ type ArtifactInput =
79
+ | string
80
+ | Buffer
81
+ | Uint8Array
82
+ | NodeJS.ReadableStream;
83
+ ```
84
+
85
+ #### `ArtifactReference`
86
+
87
+ ```typescript
88
+ interface ArtifactReference {
89
+ path: string;
90
+ size?: number;
91
+ contentType?: string;
92
+ modifiedAt?: Date;
93
+ }
94
+ ```
95
+
96
+ ## 🔧 Configuration
97
+
98
+ ### Configuration Options
99
+
100
+ #### FileSystemArtifactClientOptions
101
+
102
+ - **root**: Root directory for artifacts (required)
103
+ - **defaultContentType**: Default content type
104
+
105
+ ## 🔗 Dependencies
106
+
107
+ ### Runtime Dependencies
108
+
109
+ - `pathe` (`^1.1.2`): Path utilities
110
+
111
+ ### Development Dependencies
112
+
113
+ - `@kb-labs/devkit` (`link:../../../kb-labs-devkit`): DevKit presets
114
+ - `@types/node` (`^24.3.3`): Node.js types
115
+ - `tsup` (`^8.5.0`): TypeScript bundler
116
+ - `typescript` (`^5.6.3`): TypeScript compiler
117
+ - `vitest` (`^3.2.4`): Test runner
118
+
119
+ ## 🧪 Testing
120
+
121
+ ### Test Structure
122
+
123
+ ```
124
+ src/__tests__/
125
+ └── (tests to be added)
126
+ ```
127
+
128
+ ### Test Coverage
129
+
130
+ - **Current Coverage**: ~0% (tests to be added)
131
+ - **Target Coverage**: 90%
132
+
133
+ ## 📈 Performance
134
+
135
+ ### Performance Characteristics
136
+
137
+ - **Time Complexity**: O(1) for operations, O(n) for listing
138
+ - **Space Complexity**: O(1)
139
+ - **Bottlenecks**: File I/O operations
140
+
141
+ ## 🔒 Security
142
+
143
+ ### Security Considerations
144
+
145
+ - **Path Validation**: Path escaping prevention
146
+ - **Root Directory**: Artifacts restricted to root directory
147
+
148
+ ### Known Vulnerabilities
149
+
150
+ - None
151
+
152
+ ## 🐛 Known Issues & Limitations
153
+
154
+ ### Known Issues
155
+
156
+ - None currently
157
+
158
+ ### Limitations
159
+
160
+ - **File System Only**: Only file system implementation
161
+ - **No Remote Storage**: No S3/remote storage support
162
+
163
+ ### Future Improvements
164
+
165
+ - **Remote Storage**: S3/remote storage support
166
+ - **Compression**: Artifact compression support
167
+
168
+ ## 🔄 Migration & Breaking Changes
169
+
170
+ ### Migration from Previous Versions
171
+
172
+ No breaking changes in current version (0.1.0).
173
+
174
+ ### Breaking Changes in Future Versions
175
+
176
+ - None planned
177
+
178
+ ## 📚 Examples
179
+
180
+ ### Example 1: Create Artifact Client
181
+
182
+ ```typescript
183
+ import { createFileSystemArtifactClient } from '@kb-labs/workflow-artifacts';
184
+
185
+ const client = createFileSystemArtifactClient({
186
+ root: '/path/to/artifacts',
187
+ });
188
+ ```
189
+
190
+ ### Example 2: Read Artifact
191
+
192
+ ```typescript
193
+ const content = await client.consume('run-123/job-abc/artifact.txt');
194
+ ```
195
+
196
+ ### Example 3: Write Artifact
197
+
198
+ ```typescript
199
+ await client.produce('run-123/job-abc/artifact.txt', 'content');
200
+ ```
201
+
202
+ ### Example 4: List Artifacts
203
+
204
+ ```typescript
205
+ const artifacts = await client.list('run-123/');
206
+ ```
207
+
208
+ ## 🤝 Contributing
209
+
210
+ See [CONTRIBUTING.md](../../CONTRIBUTING.md) for development guidelines.
211
+
212
+ ## 📄 License
213
+
214
+ MIT © KB Labs
@@ -0,0 +1,31 @@
1
+ type ArtifactInput = string | Buffer | Uint8Array | NodeJS.ReadableStream;
2
+ type ArtifactOutput = Buffer;
3
+ interface ArtifactReference {
4
+ path: string;
5
+ size?: number;
6
+ contentType?: string;
7
+ modifiedAt?: Date;
8
+ }
9
+ interface ArtifactClient {
10
+ produce(path: string, input: ArtifactInput): Promise<void>;
11
+ consume(path: string): Promise<ArtifactOutput>;
12
+ list?(prefix?: string): Promise<ArtifactReference[]>;
13
+ basePath?: () => string;
14
+ }
15
+
16
+ interface FileSystemArtifactClientOptions {
17
+ root: string;
18
+ defaultContentType?: string;
19
+ }
20
+ declare class FileSystemArtifactClient implements ArtifactClient {
21
+ private readonly options;
22
+ constructor(options: FileSystemArtifactClientOptions);
23
+ basePath(): string;
24
+ produce(path: string, input: ArtifactInput): Promise<void>;
25
+ consume(path: string): Promise<ArtifactOutput>;
26
+ stream(path: string): Promise<NodeJS.ReadableStream>;
27
+ list(prefix?: string): Promise<ArtifactReference[]>;
28
+ }
29
+ declare function createFileSystemArtifactClient(root: string): FileSystemArtifactClient;
30
+
31
+ export { type ArtifactClient, type ArtifactInput, type ArtifactOutput, type ArtifactReference, FileSystemArtifactClient, type FileSystemArtifactClientOptions, createFileSystemArtifactClient };
package/dist/index.js ADDED
@@ -0,0 +1,84 @@
1
+ import { createReadStream, createWriteStream } from 'fs';
2
+ import { readFile, writeFile, readdir, stat, mkdir } from 'fs/promises';
3
+ import { pipeline } from 'stream/promises';
4
+ import { resolve, join, relative, dirname } from 'pathe';
5
+
6
+ // src/fs-client.ts
7
+ function ensureWithinRoot(root, targetPath) {
8
+ const normalizedRoot = root.endsWith("/") ? root : `${root}/`;
9
+ const target = resolve(root, targetPath);
10
+ if (target === normalizedRoot.slice(0, -1)) {
11
+ return target;
12
+ }
13
+ if (!target.startsWith(normalizedRoot)) {
14
+ throw new Error(`Artifact path escapes root: ${targetPath}`);
15
+ }
16
+ return target;
17
+ }
18
+ async function writeInput(destination, input) {
19
+ if (typeof input === "string" || input instanceof Buffer || input instanceof Uint8Array) {
20
+ await createParentDir(destination);
21
+ await writeFile(destination, input);
22
+ return;
23
+ }
24
+ if (input && typeof input.pipe === "function") {
25
+ await createParentDir(destination);
26
+ await pipeline(input, createWriteStream(destination));
27
+ return;
28
+ }
29
+ throw new Error("Unsupported artifact input type");
30
+ }
31
+ async function createParentDir(path) {
32
+ await mkdir(dirname(path), { recursive: true });
33
+ }
34
+ async function walk(root, prefix) {
35
+ const base = prefix ? ensureWithinRoot(root, prefix) : root;
36
+ const entries = await readdir(base, { withFileTypes: true });
37
+ const entryPromises = entries.map(async (entry) => {
38
+ const absolute = join(base, entry.name);
39
+ const rel = relative(root, absolute);
40
+ if (entry.isDirectory()) {
41
+ return walk(root, rel);
42
+ } else if (entry.isFile()) {
43
+ const s = await stat(absolute);
44
+ return [{
45
+ path: rel,
46
+ size: s.size,
47
+ modifiedAt: s.mtime
48
+ }];
49
+ }
50
+ return [];
51
+ });
52
+ const nestedResults = await Promise.all(entryPromises);
53
+ return nestedResults.flat();
54
+ }
55
+ var FileSystemArtifactClient = class {
56
+ constructor(options) {
57
+ this.options = options;
58
+ }
59
+ basePath() {
60
+ return this.options.root;
61
+ }
62
+ async produce(path, input) {
63
+ const target = ensureWithinRoot(this.options.root, path);
64
+ await writeInput(target, input);
65
+ }
66
+ async consume(path) {
67
+ const target = ensureWithinRoot(this.options.root, path);
68
+ return readFile(target);
69
+ }
70
+ async stream(path) {
71
+ const target = ensureWithinRoot(this.options.root, path);
72
+ return createReadStream(target);
73
+ }
74
+ async list(prefix) {
75
+ return walk(this.options.root, prefix);
76
+ }
77
+ };
78
+ function createFileSystemArtifactClient(root) {
79
+ return new FileSystemArtifactClient({ root });
80
+ }
81
+
82
+ export { FileSystemArtifactClient, createFileSystemArtifactClient };
83
+ //# sourceMappingURL=index.js.map
84
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/fs-client.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { createWriteStream, createReadStream } from 'node:fs'\nimport { mkdir, stat, readdir, readFile, writeFile } from 'node:fs/promises'\nimport { pipeline } from 'node:stream/promises'\nimport { dirname, resolve, join, relative } from 'pathe'\nimport type {\n ArtifactClient,\n ArtifactInput,\n ArtifactOutput,\n ArtifactReference,\n} from './types'\n\nfunction ensureWithinRoot(root: string, targetPath: string): string {\n const normalizedRoot = root.endsWith('/') ? root : `${root}/`\n const target = resolve(root, targetPath)\n if (target === normalizedRoot.slice(0, -1)) {\n return target\n }\n if (!target.startsWith(normalizedRoot)) {\n throw new Error(`Artifact path escapes root: ${targetPath}`)\n }\n return target\n}\n\nasync function writeInput(\n destination: string,\n input: ArtifactInput,\n): Promise<void> {\n if (\n typeof input === 'string' ||\n input instanceof Buffer ||\n input instanceof Uint8Array\n ) {\n await createParentDir(destination)\n await writeFile(destination, input)\n return\n }\n\n if (input && typeof input.pipe === 'function') {\n await createParentDir(destination)\n await pipeline(input, createWriteStream(destination))\n return\n }\n\n throw new Error('Unsupported artifact input type')\n}\n\nasync function createParentDir(path: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true })\n}\n\nasync function walk(root: string, prefix?: string): Promise<ArtifactReference[]> {\n const base = prefix ? ensureWithinRoot(root, prefix) : root\n const entries = await readdir(base, { withFileTypes: true })\n\n // Process all entries in parallel\n const entryPromises = entries.map(async (entry) => {\n const absolute = join(base, entry.name)\n const rel = relative(root, absolute)\n\n if (entry.isDirectory()) {\n return walk(root, rel)\n } else if (entry.isFile()) {\n const s = await stat(absolute)\n return [{\n path: rel,\n size: s.size,\n modifiedAt: s.mtime,\n }]\n }\n return []\n })\n\n const nestedResults = await Promise.all(entryPromises)\n return nestedResults.flat()\n}\n\nexport interface FileSystemArtifactClientOptions {\n root: string\n defaultContentType?: string\n}\n\nexport class FileSystemArtifactClient implements ArtifactClient {\n constructor(private readonly options: FileSystemArtifactClientOptions) {}\n\n basePath(): string {\n return this.options.root\n }\n\n async produce(path: string, input: ArtifactInput): Promise<void> {\n const target = ensureWithinRoot(this.options.root, path)\n await writeInput(target, input)\n }\n\n async consume(path: string): Promise<ArtifactOutput> {\n const target = ensureWithinRoot(this.options.root, path)\n return readFile(target)\n }\n\n async stream(path: string): Promise<NodeJS.ReadableStream> {\n const target = ensureWithinRoot(this.options.root, path)\n return createReadStream(target)\n }\n\n async list(prefix?: string): Promise<ArtifactReference[]> {\n return walk(this.options.root, prefix)\n }\n}\n\nexport function createFileSystemArtifactClient(\n root: string,\n): FileSystemArtifactClient {\n return new FileSystemArtifactClient({ root })\n}\n\n\n"]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@kb-labs/workflow-artifacts",
3
+ "version": "1.1.0",
4
+ "type": "module",
5
+ "description": "Artifact helpers for KB Labs workflow engine.",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "sideEffects": false,
21
+ "scripts": {
22
+ "clean": "rimraf dist",
23
+ "build": "pnpm clean && tsup --config tsup.config.ts",
24
+ "dev": "tsup --config tsup.config.ts --watch",
25
+ "type-check": "tsc --noEmit",
26
+ "test": "vitest run --passWithNoTests",
27
+ "lint": "eslint . --ignore-pattern 'dist/**'",
28
+ "lint:fix": "eslint . --fix --ignore-pattern 'dist/**'",
29
+ "test:watch": "vitest"
30
+ },
31
+ "dependencies": {
32
+ "pathe": "^1.1.2"
33
+ },
34
+ "devDependencies": {
35
+ "@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit",
36
+ "@types/node": "^24.3.3",
37
+ "@typescript-eslint/eslint-plugin": "^8",
38
+ "@typescript-eslint/parser": "^8",
39
+ "eslint": "^9",
40
+ "rimraf": "^6.0.1",
41
+ "tsup": "^8.5.0",
42
+ "typescript": "^5.6.3",
43
+ "vitest": "^3.2.4"
44
+ },
45
+ "engines": {
46
+ "node": ">=20.0.0",
47
+ "pnpm": ">=9.0.0"
48
+ }
49
+ }