@gobing-ai/ts-runtime 0.3.1 → 0.3.3

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 (61) hide show
  1. package/README.md +234 -176
  2. package/dist/config.d.ts +13 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +14 -0
  5. package/dist/context.d.ts +28 -1
  6. package/dist/context.d.ts.map +1 -1
  7. package/dist/context.js +45 -2
  8. package/dist/file-system-cf.d.ts +25 -0
  9. package/dist/file-system-cf.d.ts.map +1 -0
  10. package/dist/file-system-cf.js +59 -0
  11. package/dist/file-system-node.d.ts +29 -0
  12. package/dist/file-system-node.d.ts.map +1 -0
  13. package/dist/file-system-node.js +94 -0
  14. package/dist/file-system.d.ts +47 -0
  15. package/dist/file-system.d.ts.map +1 -0
  16. package/dist/file-system.js +0 -0
  17. package/dist/fs.d.ts +31 -1
  18. package/dist/fs.d.ts.map +1 -1
  19. package/dist/fs.js +32 -19
  20. package/dist/index.d.ts +21 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +10 -2
  23. package/dist/path.d.ts +12 -0
  24. package/dist/path.d.ts.map +1 -1
  25. package/dist/path.js +65 -4
  26. package/dist/platform.d.ts +12 -0
  27. package/dist/platform.d.ts.map +1 -0
  28. package/dist/platform.js +41 -0
  29. package/dist/process-executor.d.ts +77 -19
  30. package/dist/process-executor.d.ts.map +1 -1
  31. package/dist/process-executor.js +209 -37
  32. package/dist/runtime-cf.d.ts +6 -0
  33. package/dist/runtime-cf.d.ts.map +1 -0
  34. package/dist/runtime-cf.js +33 -0
  35. package/dist/runtime-factory.d.ts +24 -0
  36. package/dist/runtime-factory.d.ts.map +1 -0
  37. package/dist/runtime-factory.js +0 -0
  38. package/dist/runtime-node-bun.d.ts +8 -0
  39. package/dist/runtime-node-bun.d.ts.map +1 -0
  40. package/dist/runtime-node-bun.js +67 -0
  41. package/dist/schema-validation.d.ts +16 -0
  42. package/dist/schema-validation.d.ts.map +1 -1
  43. package/dist/schema-validation.js +9 -4
  44. package/dist/types.d.ts +4 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/package.json +2 -2
  47. package/src/config.ts +16 -4
  48. package/src/context.ts +58 -4
  49. package/src/file-system-cf.ts +74 -0
  50. package/src/file-system-node.ts +122 -0
  51. package/src/file-system.ts +55 -0
  52. package/src/fs.ts +35 -18
  53. package/src/index.ts +57 -2
  54. package/src/path.ts +68 -4
  55. package/src/platform.ts +47 -0
  56. package/src/process-executor.ts +296 -58
  57. package/src/runtime-cf.ts +44 -0
  58. package/src/runtime-factory.ts +28 -0
  59. package/src/runtime-node-bun.ts +83 -0
  60. package/src/schema-validation.ts +20 -4
  61. package/src/types.ts +4 -0
package/README.md CHANGED
@@ -1,123 +1,90 @@
1
1
  # @gobing-ai/ts-runtime
2
2
 
3
- Runtime abstraction layer — environment detection, file system, process execution, configuration, and context management. Works on Bun/Node and Cloudflare Workers.
3
+ Runtime abstraction layer — platform detection, file system, process execution, configuration,
4
+ context management, path utilities, and a shared plugin/capability core. Works on Node.js, Bun,
5
+ and Cloudflare Workers through a factory pattern that auto-detects the runtime.
6
+
7
+ `ts-runtime` decouples application code from platform specifics. Instead of importing `node:fs` or `node:child_process` directly, consumers use interfaces (`FileSystem`, `ProcessExecutor`) resolved by `loadRuntimeFactory()` at startup. The factory auto-detects the platform and wires the correct implementations — `createNodeFileSystem` (real `node:fs`) for Node/Bun, `createCfFileSystem` (stub) for Workers. Node filesystem modules are loaded lazily, so importing the package remains safe for Worker bundles.
4
8
 
5
9
  ## Overview
6
10
 
7
- `ts-runtime` decouples application code from platform specifics. Instead of importing `node:fs` or `node:child_process` directly, consumers go through interfaces (`FileSystem`, `SyncFileSystem`, `ProcessExecutor`, `SyncProcessExecutor`, `PipeProcessSpawner`) that resolve to the correct implementation at startup based on `RuntimeContext`. Node filesystem modules are loaded lazily by `NodeFileSystem`, so importing the package remains safe for Worker bundles that select `CloudflareFileSystem`.
11
+
8
12
 
9
13
  **Key abstractions:**
10
14
 
11
15
  | Concept | Interface | Bun/Node impl | Cloudflare impl |
12
16
  |---------|-----------|---------------|-----------------|
13
- | Async file system | `FileSystem` | `NodeFileSystem` | `CloudflareFileSystem` (unsupported filesystem facade) |
14
- | Sync file system | `SyncFileSystem` | `NodeSyncFileSystem` | |
15
- | Buffered process execution | `ProcessExecutor` | `NodeProcessExecutor` | |
16
- | Sync process execution | `SyncProcessExecutor` | `BunSyncProcessExecutor` | |
17
- | Pipe process spawning | `PipeProcessSpawner` | `BunPipeProcessSpawner` | — |
18
- | Configuration | `Config` (Zod schema) | YAML + env vars | YAML + env vars |
17
+ | Runtime factory | `RuntimeFactory` `loadRuntimeFactory()` | `nodeBunFactory` | `cloudflareWorkersFactory` |
18
+ | File system | `FileSystem` | `createNodeFileSystem()` (sync `node:fs`) | `createCfFileSystem()` (stub) |
19
+ | Process execution | `ProcessExecutor` (class) | `run()` via execa, `runStreaming()` via `Bun.spawn` | throws |
20
+ | Configuration | `Config` (Zod schema) | YAML + env vars | CONFIG_YAML blob + env vars |
19
21
  | Context | `RuntimeContext` | service locator | service locator |
22
+ | Path utilities | `SEP`, `basenamePath`, `dirnamePath`, `joinPath`, `resolvePath`, `relativePath`, … | runtime-portable (zero `node:*`) | runtime-portable (zero `node:*`) |
23
+ | Schema validation | `loadStructuredConfig` | JSON-Schema + YAML | JSON-Schema + YAML |
24
+ | Plugin core (`./plugin`) | `CapabilityRegistry`, `loadExtensionModules` | trust-gated module loading | — |
20
25
  | Tracing | `SpanContext` | `{ traceId, spanId }` | `{ traceId, spanId }` |
21
26
 
22
27
  ## Architecture
23
28
 
24
29
  ```mermaid
25
30
  classDiagram
26
- class RuntimeContext {
27
- +RuntimeScope scope
28
- +RuntimeName runtimeName
29
- +RuntimeCapabilities capabilities
30
- +register(key, service) void
31
- +get(key) T | undefined
32
- +require(key) T
33
- +has(key) boolean
34
- +dispose() Promise~void~
35
- }
36
-
37
- class FileSystem {
31
+ class RuntimeFactory {
38
32
  <<interface>>
39
- +readFile(path) Promise~string~
40
- +writeFile(path, content) Promise~void~
41
- +exists(path) Promise~boolean~
42
- +mkdir(path) Promise~void~
43
- +readDir(path) Promise~string[]~
44
- +unlink(path) Promise~void~
45
- +stat(path) Promise~FileStat | null~
46
- +realpath(path) Promise~string~
47
- +copy(src, dest) Promise~void~
48
- +rename(src, dest) Promise~void~
33
+ +string runtimeName
34
+ +RuntimeCapabilities capabilities
35
+ +createFileSystem() FileSystem
36
+ +createProcessExecutor() ProcessExecutor
37
+ +loadConfig() Promise~Config~
49
38
  }
50
39
 
51
- class NodeFileSystem {
52
- +readFile(path) Promise~string~
53
- +writeFile(path, content) Promise~void~
54
- +exists(path) Promise~boolean~
55
- +mkdir(path) Promise~void~
56
- +readDir(path) Promise~string[]~
57
- +unlink(path) Promise~void~
58
- +stat(path) Promise~FileStat | null~
59
- +realpath(path) Promise~string~
60
- +copy(src, dest) Promise~void~
61
- +rename(src, dest) Promise~void~
62
- +createLogStream(path) LogStream
40
+ class nodeBunFactory {
41
+ +string runtimeName
42
+ +createFileSystem() createNodeFileSystem()
43
+ +createProcessExecutor() ProcessExecutor
44
+ +loadConfig() Promise~Config~
63
45
  }
64
46
 
65
- class CloudflareFileSystem {
66
- +readFile(path) Promise~string~
67
- +writeFile(path, content) Promise~void~
68
- +exists(path) Promise~boolean~
69
- +mkdir(path) Promise~void~
70
- +readDir(path) Promise~string[]~
71
- +unlink(path) Promise~void~
72
- +stat(path) Promise~FileStat | null~
73
- +realpath(path) Promise~string~
74
- +copy(src, dest) Promise~void~
75
- +rename(src, dest) Promise~void~
76
- +createLogStream(path) LogStream
47
+ class cloudflareWorkersFactory {
48
+ +string runtimeName
49
+ +createFileSystem() createCfFileSystem()
50
+ +createProcessExecutor() never
51
+ +loadConfig() Promise~Config~
77
52
  }
78
53
 
79
- class SyncFileSystem {
54
+ class FileSystem {
80
55
  <<interface>>
81
- +readFile(path) string
82
- +writeFile(path, content) void
83
- +mkdir(path) void
84
- +readDir(path) string[]
85
- +unlink(path) void
56
+ +exists(path) boolean~|~Promise~boolean~
57
+ +readFile(path) string~|~Promise~string~
58
+ +writeFile(path, content) void~|~Promise~void~
59
+ +ensureDir(path) void~|~Promise~void~
60
+ +readDir(path) string[]~|~Promise~string[]~
61
+ +deleteFile(path) void~|~Promise~void~
62
+ +stat(path) FileStat~|~null~|~Promise~FileStat|~
63
+ +resolve(...segments) string
64
+ +getProjectRoot() string
86
65
  }
87
66
 
88
- class NodeSyncFileSystem {
67
+ class createNodeFileSystem {
89
68
  +readFile(path) string
90
69
  +writeFile(path, content) void
91
- +mkdir(path) void
92
- +readDir(path) string[]
93
- +unlink(path) void
70
+ +exists(path) boolean
71
+ +ensureDir(path) void
72
+ +stat(path) FileStat | null
73
+ +getProjectRoot() string
94
74
  }
95
75
 
96
- class ProcessExecutor {
97
- <<interface>>
98
- +run(options) Promise~ProcessResult~
76
+ class createCfFileSystem {
77
+ +readFile(path) never
78
+ +writeFile(path, content) never
79
+ +exists(path) false
80
+ +ensureDir(path) void
81
+ +stat(path) null
82
+ +getProjectRoot() '/bundle'
99
83
  }
100
84
 
101
- class NodeProcessExecutor {
85
+ class ProcessExecutor {
102
86
  +run(options) Promise~ProcessResult~
103
- }
104
-
105
- class SyncProcessExecutor {
106
- <<interface>>
107
- +runSync(options) ProcessResult
108
- }
109
-
110
- class BunSyncProcessExecutor {
111
- +runSync(options) ProcessResult
112
- }
113
-
114
- class PipeProcessSpawner {
115
- <<interface>>
116
- +spawn(options) PipeProcess
117
- }
118
-
119
- class BunPipeProcessSpawner {
120
- +spawn(options) PipeProcess
87
+ +runStreaming(options) PipeProcess
121
88
  }
122
89
 
123
90
  class PipeProcess {
@@ -131,6 +98,17 @@ classDiagram
131
98
  +kill(signal?) void
132
99
  }
133
100
 
101
+ class RuntimeContext {
102
+ +RuntimeScope scope
103
+ +RuntimeName runtimeName
104
+ +RuntimeCapabilities capabilities
105
+ +register(key, service) void
106
+ +get(key) T | undefined
107
+ +require(key) T
108
+ +has(key) boolean
109
+ +dispose() Promise~void~
110
+ }
111
+
134
112
  class Config {
135
113
  +AppConfig app
136
114
  +DatabaseConfig database
@@ -145,13 +123,14 @@ classDiagram
145
123
  +Record~string,string|number|boolean~ attributes?
146
124
  }
147
125
 
148
- FileSystem <|.. NodeFileSystem : implements
149
- FileSystem <|.. CloudflareFileSystem : implements
150
- SyncFileSystem <|.. NodeSyncFileSystem : implements
151
- ProcessExecutor <|.. NodeProcessExecutor : implements
152
- SyncProcessExecutor <|.. BunSyncProcessExecutor : implements
153
- PipeProcessSpawner <|.. BunPipeProcessSpawner : implements
154
- BunPipeProcessSpawner --> PipeProcess : creates
126
+ RuntimeFactory <|.. nodeBunFactory : implements
127
+ RuntimeFactory <|.. cloudflareWorkersFactory : implements
128
+ FileSystem <|.. createNodeFileSystem : implements
129
+ FileSystem <|.. createCfFileSystem : implements
130
+ ProcessExecutor --> PipeProcess : creates
131
+ nodeBunFactory --> createNodeFileSystem : creates
132
+ nodeBunFactory --> ProcessExecutor : creates
133
+ cloudflareWorkersFactory --> createCfFileSystem : creates
155
134
  RuntimeContext --> FileSystem : "fileSystem"
156
135
  RuntimeContext --> Config : "config"
157
136
  RuntimeContext --> ProcessExecutor : "processExecutor"
@@ -161,20 +140,29 @@ classDiagram
161
140
 
162
141
  ### 1. Runtime selection
163
142
 
164
- The active `FileSystem` is a process-global swapped via `setFileSystem` (default: `NodeFileSystem`);
165
- `getFs()` returns it. A `RuntimeContext` is constructed directly with the scope, capabilities, and
166
- services for the current environment — there is no factory abstraction (see ADR-008):
143
+ `loadRuntimeFactory()` auto-detects the platform and returns a factory that creates the correct
144
+ FileSystem, ProcessExecutor, and Config. Use `createRuntimeContextFromFactory()` to wire
145
+ everything automatically:
146
+
147
+ ```ts
148
+ import { createRuntimeContextFromFactory } from '@gobing-ai/ts-runtime';
149
+
150
+ const ctx = await createRuntimeContextFromFactory();
151
+ // ctx.runtimeName → 'node-bun' (auto-detected)
152
+ // ctx.capabilities → { hasFilesystem: true, hasProcessExecution: true }
153
+ // ctx.require('fileSystem') → NodeFileSystem
154
+ // ctx.require('config') → Config from config.yaml
155
+ ```
156
+
157
+ For manual control or synchronous code, `createRuntimeContext()` is still available
158
+ (deprecated — kept for backward compatibility):
167
159
 
168
160
  ```ts
169
161
  import { createRuntimeContext } from '@gobing-ai/ts-runtime';
170
162
 
171
163
  const ctx = createRuntimeContext({
172
164
  runtimeName: 'node-bun',
173
- capabilities: {
174
- hasFilesystem: true,
175
- hasProcessExecution: true,
176
- hasPersistentStorage: true,
177
- },
165
+ capabilities: { hasFilesystem: true, hasProcessExecution: true, hasPersistentStorage: true },
178
166
  });
179
167
  ```
180
168
 
@@ -258,35 +246,132 @@ await loadStructuredConfig('rules.yaml', { fetch: myFetch }); // or in
258
246
  > `node_modules` keeps validation entirely local — no outbound request, no dependency on a schema host's
259
247
  > availability, and no chance for a malicious config to point validation at an internal URL.
260
248
 
261
- ### 5. File system abstraction
262
249
 
263
- Most file operations go through the async `FileSystem` interface. Swap implementations for testing:
250
+ ### 5. Path utilities
251
+
252
+ Runtime-portable path math that avoids `node:path` so the same logic works on Cloudflare Workers
253
+ (ADR-008). All functions use POSIX-style separators; Windows drive paths are normalized and treated
254
+ as absolute.
255
+
256
+ ```ts
257
+ import {
258
+ SEP, basenamePath, dirnamePath, isAbsolutePath,
259
+ joinPath, normalizeSeparators, relativePath, resolvePath,
260
+ getProcessCwd,
261
+ } from '@gobing-ai/ts-runtime';
262
+
263
+ normalizeSeparators('C:\\Users\\x\\file'); // 'C:/Users/x/file'
264
+ SEP; // '/' on POSIX, '\\' on Windows
265
+ isAbsolutePath('/abs'); // true
266
+ dirnamePath('/a/b/c.ts'); // '/a/b'
267
+ basenamePath('/a/b/c.ts'); // 'c.ts'
268
+ basenamePath('/a/b/c.ts', '.ts'); // 'c'
269
+ joinPath('/a', 'b', 'c'); // '/a/b/c'
270
+ resolvePath('/a/b', '../c'); // '/a/c'
271
+ relativePath('/a/x', '/a/y'); // '../y'
272
+ getProcessCwd(); // process.cwd() or '/' on Workers
273
+ ```
274
+
275
+ ### 6. Plugin core (`@gobing-ai/ts-runtime/plugin`)
276
+
277
+ The `./plugin` subpath exposes a generic, domain-agnostic plugin/capability core used by both
278
+ `ts-rule-engine` and `ts-dual-workflow-engine` (ADR-010). It shares the mechanism — a typed registry
279
+ with origin metadata, a trust-gated extension loader, and a path guard — without knowing anything
280
+ about evaluators, resolvers, actions, or guards. Each engine owns its domain-specific kinds,
281
+ schemas, error types, and override semantics.
282
+ #### Capability registry
264
283
 
265
284
  ```ts
266
- import { getFs } from '@gobing-ai/ts-runtime';
285
+ import { CapabilityRegistry } from '@gobing-ai/ts-runtime/plugin';
286
+
287
+ interface Widget { execute(): void; }
288
+ const registry = new CapabilityRegistry<Widget>('widget');
267
289
 
268
- const fs = getFs();
269
- await fs.writeFile('output.json', JSON.stringify(data));
270
- const content = await fs.readFile('output.json');
290
+ // Register built-ins and extensions with origin metadata.
291
+ registry.register('core', coreWidget, 'builtin');
292
+ registry.register('ext', extWidget, 'extension'); // default
293
+
294
+ registry.has('core'); // true
295
+ registry.list(); // ['core', 'ext'] (insertion order)
296
+ registry.get('core').execute(); // ok
297
+ registry.get('missing'); // throws: Unknown widget: missing
298
+
299
+ // Introspect origin without throwing.
300
+ registry.getEntry('core')?.origin; // 'builtin'
301
+ registry.entries(); // [['core', { capability, origin: 'builtin' }], ...]
271
302
  ```
272
303
 
273
- Use `SyncFileSystem` only for APIs that must stay synchronous, such as config discovery at module
274
- boundaries or compatibility wrappers. The sync seam still keeps direct `node:fs` access inside
275
- `ts-runtime`.
304
+ #### Extension loading (trust-gated)
305
+
306
+ Extension modules are arbitrary code — the loader is disabled by default and fails closed:
307
+
308
+ ```ts
309
+ import { loadExtensionModules, type ExtensionRef } from '@gobing-ai/ts-runtime/plugin';
310
+
311
+ const refs: ExtensionRef<'actions'>[] = [{
312
+ kind: 'actions',
313
+ path: './ext/slack.ts',
314
+ baseDir: '/abs/path/to/configs',
315
+ sourceName: 'my-config',
316
+ }];
317
+
318
+ // Throws before any import: "declares actions extension … but extensions are disabled"
319
+ await loadExtensionModules(refs, { moduleLoader: (p) => import(p) }, register);
320
+
321
+ // Explicitly opt in.
322
+ await loadExtensionModules(refs, {
323
+ allowExtensions: true,
324
+ moduleLoader: (p) => import(p),
325
+ }, (ref, extension) => {
326
+ // Engine-provided callback — the loader never chooses a target registry.
327
+ myHost.actions.register(extension.name, extension, 'extension');
328
+ });
329
+ ```
330
+
331
+ The loader validates both the default export and the named `extension` export. Invalid modules
332
+ throw with source name, kind, and path context. `moduleLoader` is required — the shared core has
333
+ no ambient `import()` capability of its own.
334
+
335
+ #### Path guard
336
+
337
+ `assertRelativeExtensionPath` rejects absolute paths and `..` traversal at load time, independent
338
+ of any engine's zod schema (defense in depth):
276
339
 
277
340
  ```ts
278
- import { NodeSyncFileSystem } from '@gobing-ai/ts-runtime';
341
+ import { assertRelativeExtensionPath } from '@gobing-ai/ts-runtime/plugin';
279
342
 
280
- const fs = new NodeSyncFileSystem();
281
- fs.writeFile('agents/coder.yaml', 'id: coder\n');
282
- const files = fs.readDir('agents');
343
+ assertRelativeExtensionPath('./ext/my.ts'); // ok
344
+ assertRelativeExtensionPath('/etc/evil.ts'); // throws: must be relative
345
+ assertRelativeExtensionPath('../escape.ts'); // throws: must not contain ".."
283
346
  ```
284
347
 
285
- ### 6. Graceful disposal
348
+ ### 7. File system abstraction
286
349
 
287
- `RuntimeContext.dispose()` calls `dispose()` on every registered service that implements the pattern:
350
+ Use `createNodeFileSystem()` for a real `node:fs`-backed filesystem (Node/Bun) or
351
+ `createCfFileSystem()` for a Cloudflare Workers stub:
288
352
 
289
353
  ```ts
354
+ import { createNodeFileSystem, createCfFileSystem } from '@gobing-ai/ts-runtime';
355
+
356
+ // Node.js / Bun — real filesystem (sync node:fs)
357
+ const fs = createNodeFileSystem('/path/to/project');
358
+ const content = fs.readFile('src/index.ts'); // string
359
+ fs.writeFile('output.json', JSON.stringify(data));
360
+ fs.ensureDir('tmp/cache'); // recursive mkdir
361
+
362
+ // Cloudflare Workers — stub (throws on mutating ops)
363
+ const cffs = createCfFileSystem();
364
+ cffs.getProjectRoot(); // '/bundle'
365
+ cffs.readFile('/x'); // throws: "use D1, KV, or R2"
366
+ ```
367
+
368
+ The old `getFs()` / `setFileSystem` global swap and `SyncFileSystem` are marked `@deprecated` —
369
+ use `createNodeFileSystem()` or `ctx.require('fileSystem')` instead.
370
+
371
+ ### 8. Graceful disposal
372
+
373
+ `RuntimeContext.dispose()` calls `dispose()` on every registered service that implements the pattern:
374
+
290
375
  process.on('SIGTERM', async () => {
291
376
  await ctx.dispose();
292
377
  process.exit(0);
@@ -301,39 +386,27 @@ process.on('SIGTERM', async () => {
301
386
  bun add @gobing-ai/ts-runtime
302
387
  ```
303
388
 
304
- ### Basic setup (Bun/Node)
389
+ ### Basic setup (Node/Bun)
305
390
 
306
391
  ```ts
307
- import { createRuntimeContext, getFs, NodeFileSystem } from '@gobing-ai/ts-runtime';
308
-
309
- const ctx = createRuntimeContext({
310
- runtimeName: 'node-bun',
311
- services: {
312
- fileSystem: new NodeFileSystem(),
313
- },
314
- });
392
+ import { createRuntimeContextFromFactory } from '@gobing-ai/ts-runtime';
315
393
 
394
+ const ctx = await createRuntimeContextFromFactory();
316
395
  const fs = ctx.require('fileSystem');
317
- await fs.writeFile('hello.txt', 'Hello, world!');
396
+ fs.writeFile('hello.txt', 'Hello, world!');
318
397
  ```
319
398
 
320
399
  ### Cloudflare Workers
321
400
 
322
401
  ```ts
323
- import { createRuntimeContext, CloudflareFileSystem } from '@gobing-ai/ts-runtime';
402
+ import { createCfFileSystem, createRuntimeContext } from '@gobing-ai/ts-runtime';
324
403
 
325
404
  export default {
326
405
  async fetch(request: Request, env: Env): Promise<Response> {
327
406
  const ctx = createRuntimeContext({
328
407
  runtimeName: 'cloudflare-workers',
329
- capabilities: {
330
- hasFilesystem: false,
331
- hasProcessExecution: false,
332
- hasPersistentStorage: false,
333
- },
334
- services: {
335
- fileSystem: new CloudflareFileSystem(),
336
- },
408
+ capabilities: { hasFilesystem: false, hasProcessExecution: false, hasPersistentStorage: false },
409
+ services: { fileSystem: createCfFileSystem() },
337
410
  });
338
411
  // ...
339
412
  },
@@ -348,6 +421,15 @@ import { buildConfigFromYaml, buildConfigFromObject } from '@gobing-ai/ts-runtim
348
421
  // From YAML file
349
422
  const config = buildConfigFromYaml(yamlText);
350
423
 
424
+ // From plain object (programmatic config)
425
+ const config = buildConfigFromObject({
426
+ app: { name: 'api', env: 'production', port: 3000 },
427
+ database: { url: ':memory:' },
428
+ });
429
+ ```
430
+ // From YAML file
431
+ const config = buildConfigFromYaml(yamlText);
432
+
351
433
  // From plain object (programmatic config)
352
434
  const config = buildConfigFromObject({
353
435
  app: { name: 'api', env: 'production', port: 3000 },
@@ -357,14 +439,14 @@ const config = buildConfigFromObject({
357
439
 
358
440
  ### Process execution
359
441
 
360
- `NodeProcessExecutor` is the default buffered async executor. It returns a `ProcessResult` and only
361
- throws on non-zero exits when `rejectOnError` is set.
442
+ `ProcessExecutor` is a single class wrapping `execa` (buffered) and `Bun.spawn` (streaming):
362
443
 
363
444
  ```ts
364
- import { NodeProcessExecutor } from '@gobing-ai/ts-runtime';
445
+ import { ProcessExecutor } from '@gobing-ai/ts-runtime';
365
446
 
366
- const exec = new NodeProcessExecutor({ defaultTimeout: 30_000 });
447
+ const exec = new ProcessExecutor({ defaultTimeout: 30_000 });
367
448
 
449
+ // Buffered — captures stdout/stderr, no throw on non-zero
368
450
  const result = await exec.run({
369
451
  command: 'git',
370
452
  args: ['status', '--short'],
@@ -372,49 +454,25 @@ const result = await exec.run({
372
454
  rejectOnError: true,
373
455
  });
374
456
 
375
- console.log(result.stdout);
457
+ console.log(result.stdout); // 'M src/index.ts\n'
376
458
  console.log(`Duration: ${result.durationMs}ms`);
377
- ```
378
-
379
- Use `BunSyncProcessExecutor` for synchronous command probes where the caller needs an immediate
380
- result, for example checking git state while building a prompt preamble.
381
-
382
- ```ts
383
- import { BunSyncProcessExecutor } from '@gobing-ai/ts-runtime';
384
459
 
385
- const exec = new BunSyncProcessExecutor();
386
-
387
- const branch = exec.runSync({
388
- command: 'git',
389
- args: ['branch', '--show-current'],
390
- cwd: '/path/to/repo',
391
- rejectOnError: false,
392
- });
393
-
394
- if (branch.exitCode === 0) {
395
- console.log(branch.stdout);
396
- }
460
+ // Streaming interactive subprocess with stdin control
461
+ const proc = exec.runStreaming({ command: 'cat' });
462
+ proc.writeStdin('hello\n');
463
+ proc.endStdin();
464
+ const exitCode = await proc.exited; // 0
397
465
  ```
398
466
 
399
- Use `BunPipeProcessSpawner` when the host needs a long-running subprocess with writable stdin and
400
- readable stdout/stderr streams. This is the primitive used by team-mode agent processes.
467
+ `rejectOnError: true` throws on non-zero exits. `OutputPolicy` controls
468
+ buffered vs streamed output. `ProcessOptions` supports timeout, env, cwd,
469
+ maxOutput, and forceBuffered.
401
470
 
402
- ```ts
403
- import { BunPipeProcessSpawner } from '@gobing-ai/ts-runtime';
404
-
405
- const process = new BunPipeProcessSpawner().spawn({
406
- command: 'codex',
407
- args: ['exec', 'Wait for task messages.'],
408
- cwd: '/path/to/repo',
409
- });
410
-
411
- process.writeStdin('[task from=operator id=msg-1] Inspect packages/runtime\n');
412
- process.endStdin();
413
-
414
- const exitCode = await process.exited;
415
- ```
471
+ Cloudflare Workers do not expose process execution; check
472
+ `factory.capabilities.hasProcessExecution` first.
416
473
 
417
- Cloudflare Workers do not expose process execution; do not register process executors there.
474
+ Old classes (`NodeProcessExecutor`, `BunSyncProcessExecutor`, `BunPipeProcessSpawner`)
475
+ are kept as deprecated backward-compatible wrappers.
418
476
 
419
477
  ### SpanContext (for telemetry)
420
478
 
package/dist/config.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type ZodIssue, z } from 'zod';
2
+ /** Zod schema for the application configuration object, providing defaults for app, database, and logging sections. */
2
3
  export declare const configSchema: z.ZodObject<{
3
4
  app: z.ZodDefault<z.ZodObject<{
4
5
  name: z.ZodDefault<z.ZodString>;
@@ -28,6 +29,7 @@ export declare const configSchema: z.ZodObject<{
28
29
  json: z.ZodDefault<z.ZodBoolean>;
29
30
  }, z.core.$strip>>;
30
31
  }, z.core.$strip>;
32
+ /** Inferred TypeScript type of a validated configuration object, derived from {@link configSchema}. */
31
33
  export type Config = z.output<typeof configSchema>;
32
34
  /** Raised when a YAML text cannot be parsed as a plain object. */
33
35
  export declare class YamlParseError extends Error {
@@ -38,21 +40,32 @@ export declare class YamlParseError extends Error {
38
40
  export declare function parseYamlObject(text: string): Record<string, unknown>;
39
41
  /** Serialize a plain object to a YAML string. */
40
42
  export declare function stringifyYamlObject(value: Record<string, unknown>): string;
43
+ /** Error thrown when configuration validation fails, carrying the Zod validation issues for diagnostics. */
41
44
  export declare class ConfigLoadError extends Error {
42
45
  readonly issues: ZodIssue[];
43
46
  constructor(message: string, issues?: ZodIssue[]);
44
47
  }
48
+ /** Returns the value of `process.env.NODE_ENV`, or `"development"` as default. Node/Bun only. */
45
49
  export declare function getNodeEnv(): string;
50
+ /** Returns `true` when `NODE_ENV` is `"test"`. Node/Bun only. */
46
51
  export declare function isTestEnv(): boolean;
52
+ /** Returns `process.env` as a plain object. Node/Bun only. */
47
53
  export declare function getProcessEnv(): Record<string, string | undefined>;
54
+ /** Returns `process.env.DATABASE_URL`, or `undefined` if not set. Node/Bun only. */
48
55
  export declare function getDatabaseUrl(): string | undefined;
56
+ /** Returns the user's home directory (`HOME`, falling back to `USERPROFILE` on Windows), or `undefined` if unset. Node/Bun only. */
57
+ export declare function getHomeDir(): string | undefined;
49
58
  /** Node-bun only: interpolates `${VAR}` from `process.env` (see note above). */
50
59
  export declare function interpolateEnv(value: string): string;
60
+ /** Recursively interpolates `${VAR}` environment variables in all string leaves of a nested object or array. Node/Bun only. */
51
61
  export declare function interpolateTree(value: unknown): unknown;
62
+ /** Interpolates env vars, merges overrides, validates against {@link configSchema}, and returns a frozen {@link Config}. */
52
63
  export declare function buildConfigFromObject(raw: Record<string, unknown>, options?: {
53
64
  overrides?: Partial<Config>;
54
65
  }): Config;
66
+ /** Parses a YAML configuration string into a raw object, throwing {@link ConfigLoadError} on failure. */
55
67
  export declare function parseConfigYaml(yamlText: string): Record<string, unknown>;
68
+ /** Parses YAML text and builds a validated {@link Config}, equivalent to `buildConfigFromObject(parseConfigYaml(…))`. */
56
69
  export declare function buildConfigFromYaml(yamlText: string, options?: {
57
70
  overrides?: Partial<Config>;
58
71
  }): Config;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAEvC,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsBvB,CAAC;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAInD,kEAAkE;AAClE,qBAAa,cAAe,SAAQ,KAAK;IAGjC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO;gBAD7B,OAAO,EAAE,MAAM,EACN,UAAU,CAAC,EAAE,OAAO,YAAA;CAKpC;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAerE;AAED,iDAAiD;AACjD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE1E;AAED,qBAAa,eAAgB,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,QAAQ,EAAO;CAKvD;AAKD,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAElE;AAED,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAEnD;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOvD;AAED,wBAAgB,qBAAqB,CACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;CAAO,GAC9C,MAAM,CAUR;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;CAAO,GAAG,MAAM,CAE3G"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAEvC,uHAAuH;AACvH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsBvB,CAAC;AAEH,uGAAuG;AACvG,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,YAAY,CAAC,CAAC;AAInD,kEAAkE;AAClE,qBAAa,cAAe,SAAQ,KAAK;IAGjC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO;gBAD7B,OAAO,EAAE,MAAM,EACN,UAAU,CAAC,EAAE,OAAO,YAAA;CAKpC;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAerE;AAED,iDAAiD;AACjD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAE1E;AAED,4GAA4G;AAC5G,qBAAa,eAAgB,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,QAAQ,EAAO;CAKvD;AAKD,iGAAiG;AACjG,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AACD,iEAAiE;AACjE,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAElE;AAED,oFAAoF;AACpF,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAEnD;AAED,oIAAoI;AACpI,wBAAgB,UAAU,IAAI,MAAM,GAAG,SAAS,CAE/C;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,+HAA+H;AAC/H,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOvD;AACD,4HAA4H;AAC5H,wBAAgB,qBAAqB,CACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;CAAO,GAC9C,MAAM,CAUR;AACD,yGAAyG;AACzG,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzE;AACD,yHAAyH;AACzH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;CAAO,GAAG,MAAM,CAE3G"}