@gobing-ai/ts-runtime 0.3.0 → 0.3.2
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 +234 -176
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -0
- package/dist/context.d.ts +28 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +45 -2
- package/dist/file-system-cf.d.ts +25 -0
- package/dist/file-system-cf.d.ts.map +1 -0
- package/dist/file-system-cf.js +59 -0
- package/dist/file-system-node.d.ts +29 -0
- package/dist/file-system-node.d.ts.map +1 -0
- package/dist/file-system-node.js +94 -0
- package/dist/file-system.d.ts +47 -0
- package/dist/file-system.d.ts.map +1 -0
- package/dist/file-system.js +0 -0
- package/dist/fs.d.ts +31 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +32 -19
- package/dist/index.d.ts +21 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -2
- package/dist/path.d.ts +12 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +65 -4
- package/dist/platform.d.ts +12 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +41 -0
- package/dist/plugin/capability-registry.d.ts +35 -0
- package/dist/plugin/capability-registry.d.ts.map +1 -0
- package/dist/plugin/capability-registry.js +43 -0
- package/dist/plugin/extension-loader.d.ts +66 -0
- package/dist/plugin/extension-loader.d.ts.map +1 -0
- package/dist/plugin/extension-loader.js +47 -0
- package/dist/plugin/extension-path.d.ts +15 -0
- package/dist/plugin/extension-path.d.ts.map +1 -0
- package/dist/plugin/extension-path.js +20 -0
- package/dist/plugin/index.d.ts +4 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +3 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +1 -0
- package/dist/process-executor.d.ts +77 -19
- package/dist/process-executor.d.ts.map +1 -1
- package/dist/process-executor.js +209 -37
- package/dist/runtime-cf.d.ts +6 -0
- package/dist/runtime-cf.d.ts.map +1 -0
- package/dist/runtime-cf.js +33 -0
- package/dist/runtime-factory.d.ts +24 -0
- package/dist/runtime-factory.d.ts.map +1 -0
- package/dist/runtime-factory.js +0 -0
- package/dist/runtime-node-bun.d.ts +8 -0
- package/dist/runtime-node-bun.d.ts.map +1 -0
- package/dist/runtime-node-bun.js +67 -0
- package/dist/schema-validation.d.ts +16 -0
- package/dist/schema-validation.d.ts.map +1 -1
- package/dist/schema-validation.js +9 -4
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/config.ts +16 -4
- package/src/context.ts +58 -4
- package/src/file-system-cf.ts +74 -0
- package/src/file-system-node.ts +122 -0
- package/src/file-system.ts +55 -0
- package/src/fs.ts +35 -18
- package/src/index.ts +57 -2
- package/src/path.ts +68 -4
- package/src/platform.ts +47 -0
- package/src/plugin/capability-registry.ts +58 -0
- package/src/plugin/extension-loader.ts +105 -0
- package/src/plugin/extension-path.ts +20 -0
- package/src/plugin/index.ts +3 -0
- package/src/plugin.ts +1 -0
- package/src/process-executor.ts +296 -58
- package/src/runtime-cf.ts +44 -0
- package/src/runtime-factory.ts +28 -0
- package/src/runtime-node-bun.ts +83 -0
- package/src/schema-validation.ts +20 -4
- 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 —
|
|
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
|
-
|
|
11
|
+
|
|
8
12
|
|
|
9
13
|
**Key abstractions:**
|
|
10
14
|
|
|
11
15
|
| Concept | Interface | Bun/Node impl | Cloudflare impl |
|
|
12
16
|
|---------|-----------|---------------|-----------------|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
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
|
|
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
|
-
+
|
|
40
|
-
+
|
|
41
|
-
+
|
|
42
|
-
+
|
|
43
|
-
+
|
|
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
|
|
52
|
-
+
|
|
53
|
-
+
|
|
54
|
-
+
|
|
55
|
-
+
|
|
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
|
|
66
|
-
+
|
|
67
|
-
+
|
|
68
|
-
+
|
|
69
|
-
+
|
|
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
|
|
54
|
+
class FileSystem {
|
|
80
55
|
<<interface>>
|
|
81
|
-
+
|
|
82
|
-
+
|
|
83
|
-
+
|
|
84
|
-
+
|
|
85
|
-
+
|
|
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
|
|
67
|
+
class createNodeFileSystem {
|
|
89
68
|
+readFile(path) string
|
|
90
69
|
+writeFile(path, content) void
|
|
91
|
-
+
|
|
92
|
-
+
|
|
93
|
-
+
|
|
70
|
+
+exists(path) boolean
|
|
71
|
+
+ensureDir(path) void
|
|
72
|
+
+stat(path) FileStat | null
|
|
73
|
+
+getProjectRoot() string
|
|
94
74
|
}
|
|
95
75
|
|
|
96
|
-
class
|
|
97
|
-
|
|
98
|
-
+
|
|
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
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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 {
|
|
341
|
+
import { assertRelativeExtensionPath } from '@gobing-ai/ts-runtime/plugin';
|
|
279
342
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
###
|
|
348
|
+
### 7. File system abstraction
|
|
286
349
|
|
|
287
|
-
|
|
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
|
|
389
|
+
### Basic setup (Node/Bun)
|
|
305
390
|
|
|
306
391
|
```ts
|
|
307
|
-
import {
|
|
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
|
-
|
|
396
|
+
fs.writeFile('hello.txt', 'Hello, world!');
|
|
318
397
|
```
|
|
319
398
|
|
|
320
399
|
### Cloudflare Workers
|
|
321
400
|
|
|
322
401
|
```ts
|
|
323
|
-
import {
|
|
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
|
-
|
|
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
|
-
`
|
|
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 {
|
|
445
|
+
import { ProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
365
446
|
|
|
366
|
-
const exec = new
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
400
|
-
|
|
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
|
-
|
|
403
|
-
|
|
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
|
-
|
|
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;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|