@gobing-ai/ts-runtime 0.2.8 → 0.2.9
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 +116 -18
- package/dist/config.d.ts +10 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +35 -58
- package/dist/context.d.ts +1 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -2
- package/dist/fs.d.ts +15 -0
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +37 -64
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/path.d.ts +7 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +55 -0
- package/dist/process-executor.d.ts +36 -0
- package/dist/process-executor.d.ts.map +1 -1
- package/dist/process-executor.js +74 -0
- package/dist/schema-validation.d.ts +1 -1
- package/dist/schema-validation.d.ts.map +1 -1
- package/dist/schema-validation.js +70 -21
- package/dist/types.d.ts +0 -11
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/config.ts +41 -57
- package/src/context.ts +2 -4
- package/src/fs.ts +51 -59
- package/src/index.ts +1 -0
- package/src/path.ts +54 -0
- package/src/process-executor.ts +128 -0
- package/src/schema-validation.ts +84 -16
- package/src/types.ts +0 -10
package/README.md
CHANGED
|
@@ -4,14 +4,17 @@ Runtime abstraction layer — environment detection, file system, process execut
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
`ts-runtime` decouples application code from platform specifics. Instead of importing `node:fs` or `node:child_process` directly, consumers go through interfaces (`FileSystem`, `ProcessExecutor`) 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`.
|
|
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`.
|
|
8
8
|
|
|
9
9
|
**Key abstractions:**
|
|
10
10
|
|
|
11
11
|
| Concept | Interface | Bun/Node impl | Cloudflare impl |
|
|
12
12
|
|---------|-----------|---------------|-----------------|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
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` | — |
|
|
15
18
|
| Configuration | `Config` (Zod schema) | YAML + env vars | YAML + env vars |
|
|
16
19
|
| Context | `RuntimeContext` | service locator | service locator |
|
|
17
20
|
| Tracing | `SpanContext` | `{ traceId, spanId }` | `{ traceId, spanId }` |
|
|
@@ -20,15 +23,6 @@ Runtime abstraction layer — environment detection, file system, process execut
|
|
|
20
23
|
|
|
21
24
|
```mermaid
|
|
22
25
|
classDiagram
|
|
23
|
-
class RuntimeFactory {
|
|
24
|
-
<<interface>>
|
|
25
|
-
+RuntimeName runtimeName
|
|
26
|
-
+RuntimeCapabilities capabilities
|
|
27
|
-
+createFileSystem() FileSystem
|
|
28
|
-
+loadConfig() Promise~Config~
|
|
29
|
-
+createContext() RuntimeContext
|
|
30
|
-
}
|
|
31
|
-
|
|
32
26
|
class RuntimeContext {
|
|
33
27
|
+RuntimeScope scope
|
|
34
28
|
+RuntimeName runtimeName
|
|
@@ -82,6 +76,23 @@ classDiagram
|
|
|
82
76
|
+createLogStream(path) LogStream
|
|
83
77
|
}
|
|
84
78
|
|
|
79
|
+
class SyncFileSystem {
|
|
80
|
+
<<interface>>
|
|
81
|
+
+readFile(path) string
|
|
82
|
+
+writeFile(path, content) void
|
|
83
|
+
+mkdir(path) void
|
|
84
|
+
+readDir(path) string[]
|
|
85
|
+
+unlink(path) void
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class NodeSyncFileSystem {
|
|
89
|
+
+readFile(path) string
|
|
90
|
+
+writeFile(path, content) void
|
|
91
|
+
+mkdir(path) void
|
|
92
|
+
+readDir(path) string[]
|
|
93
|
+
+unlink(path) void
|
|
94
|
+
}
|
|
95
|
+
|
|
85
96
|
class ProcessExecutor {
|
|
86
97
|
<<interface>>
|
|
87
98
|
+run(options) Promise~ProcessResult~
|
|
@@ -91,6 +102,35 @@ classDiagram
|
|
|
91
102
|
+run(options) Promise~ProcessResult~
|
|
92
103
|
}
|
|
93
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
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class PipeProcess {
|
|
124
|
+
<<interface>>
|
|
125
|
+
+pid number?
|
|
126
|
+
+stdout ReadableStream?
|
|
127
|
+
+stderr ReadableStream?
|
|
128
|
+
+exited Promise~number?~
|
|
129
|
+
+writeStdin(input) void
|
|
130
|
+
+endStdin() void
|
|
131
|
+
+kill(signal?) void
|
|
132
|
+
}
|
|
133
|
+
|
|
94
134
|
class Config {
|
|
95
135
|
+AppConfig app
|
|
96
136
|
+DatabaseConfig database
|
|
@@ -107,20 +147,23 @@ classDiagram
|
|
|
107
147
|
|
|
108
148
|
FileSystem <|.. NodeFileSystem : implements
|
|
109
149
|
FileSystem <|.. CloudflareFileSystem : implements
|
|
150
|
+
SyncFileSystem <|.. NodeSyncFileSystem : implements
|
|
110
151
|
ProcessExecutor <|.. NodeProcessExecutor : implements
|
|
152
|
+
SyncProcessExecutor <|.. BunSyncProcessExecutor : implements
|
|
153
|
+
PipeProcessSpawner <|.. BunPipeProcessSpawner : implements
|
|
154
|
+
BunPipeProcessSpawner --> PipeProcess : creates
|
|
111
155
|
RuntimeContext --> FileSystem : "fileSystem"
|
|
112
156
|
RuntimeContext --> Config : "config"
|
|
113
157
|
RuntimeContext --> ProcessExecutor : "processExecutor"
|
|
114
|
-
RuntimeFactory --> RuntimeContext : creates
|
|
115
|
-
RuntimeFactory --> FileSystem : creates
|
|
116
|
-
RuntimeFactory --> Config : loadConfig()
|
|
117
158
|
```
|
|
118
159
|
|
|
119
160
|
## How It Works
|
|
120
161
|
|
|
121
|
-
### 1. Runtime
|
|
162
|
+
### 1. Runtime selection
|
|
122
163
|
|
|
123
|
-
|
|
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):
|
|
124
167
|
|
|
125
168
|
```ts
|
|
126
169
|
import { createRuntimeContext } from '@gobing-ai/ts-runtime';
|
|
@@ -217,7 +260,7 @@ await loadStructuredConfig('rules.yaml', { fetch: myFetch }); // or in
|
|
|
217
260
|
|
|
218
261
|
### 5. File system abstraction
|
|
219
262
|
|
|
220
|
-
|
|
263
|
+
Most file operations go through the async `FileSystem` interface. Swap implementations for testing:
|
|
221
264
|
|
|
222
265
|
```ts
|
|
223
266
|
import { getFs } from '@gobing-ai/ts-runtime';
|
|
@@ -227,6 +270,18 @@ await fs.writeFile('output.json', JSON.stringify(data));
|
|
|
227
270
|
const content = await fs.readFile('output.json');
|
|
228
271
|
```
|
|
229
272
|
|
|
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`.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { NodeSyncFileSystem } from '@gobing-ai/ts-runtime';
|
|
279
|
+
|
|
280
|
+
const fs = new NodeSyncFileSystem();
|
|
281
|
+
fs.writeFile('agents/coder.yaml', 'id: coder\n');
|
|
282
|
+
const files = fs.readDir('agents');
|
|
283
|
+
```
|
|
284
|
+
|
|
230
285
|
### 6. Graceful disposal
|
|
231
286
|
|
|
232
287
|
`RuntimeContext.dispose()` calls `dispose()` on every registered service that implements the pattern:
|
|
@@ -302,6 +357,9 @@ const config = buildConfigFromObject({
|
|
|
302
357
|
|
|
303
358
|
### Process execution
|
|
304
359
|
|
|
360
|
+
`NodeProcessExecutor` is the default buffered async executor. It returns a `ProcessResult` and only
|
|
361
|
+
throws on non-zero exits when `rejectOnError` is set.
|
|
362
|
+
|
|
305
363
|
```ts
|
|
306
364
|
import { NodeProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
307
365
|
|
|
@@ -318,6 +376,46 @@ console.log(result.stdout);
|
|
|
318
376
|
console.log(`Duration: ${result.durationMs}ms`);
|
|
319
377
|
```
|
|
320
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
|
+
|
|
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
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
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.
|
|
401
|
+
|
|
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
|
+
```
|
|
416
|
+
|
|
417
|
+
Cloudflare Workers do not expose process execution; do not register process executors there.
|
|
418
|
+
|
|
321
419
|
### SpanContext (for telemetry)
|
|
322
420
|
|
|
323
421
|
```ts
|
package/dist/config.d.ts
CHANGED
|
@@ -29,6 +29,15 @@ export declare const configSchema: z.ZodObject<{
|
|
|
29
29
|
}, z.core.$strip>>;
|
|
30
30
|
}, z.core.$strip>;
|
|
31
31
|
export type Config = z.output<typeof configSchema>;
|
|
32
|
+
/** Raised when a YAML text cannot be parsed as a plain object. */
|
|
33
|
+
export declare class YamlParseError extends Error {
|
|
34
|
+
readonly innerError?: unknown | undefined;
|
|
35
|
+
constructor(message: string, innerError?: unknown | undefined);
|
|
36
|
+
}
|
|
37
|
+
/** Parse YAML text into a plain object. Returns `{}` for null/undefined/empty input. */
|
|
38
|
+
export declare function parseYamlObject(text: string): Record<string, unknown>;
|
|
39
|
+
/** Serialize a plain object to a YAML string. */
|
|
40
|
+
export declare function stringifyYamlObject(value: Record<string, unknown>): string;
|
|
32
41
|
export declare class ConfigLoadError extends Error {
|
|
33
42
|
readonly issues: ZodIssue[];
|
|
34
43
|
constructor(message: string, issues?: ZodIssue[]);
|
|
@@ -37,11 +46,9 @@ export declare function getNodeEnv(): string;
|
|
|
37
46
|
export declare function isTestEnv(): boolean;
|
|
38
47
|
export declare function getProcessEnv(): Record<string, string | undefined>;
|
|
39
48
|
export declare function getDatabaseUrl(): string | undefined;
|
|
49
|
+
/** Node-bun only: interpolates `${VAR}` from `process.env` (see note above). */
|
|
40
50
|
export declare function interpolateEnv(value: string): string;
|
|
41
51
|
export declare function interpolateTree(value: unknown): unknown;
|
|
42
|
-
export declare function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
|
|
43
|
-
export declare function flattenKeys(obj: Record<string, unknown>, prefix?: string): Record<string, string>;
|
|
44
|
-
export declare function deFlattenKeys(entries: Record<string, string>): Record<string, unknown>;
|
|
45
52
|
export declare function buildConfigFromObject(raw: Record<string, unknown>, options?: {
|
|
46
53
|
overrides?: Partial<Config>;
|
|
47
54
|
}): Config;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { deepMerge } from '@gobing-ai/ts-utils';
|
|
2
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
export const configSchema = z.object({
|
|
4
5
|
app: z
|
|
@@ -24,6 +25,35 @@ export const configSchema = z.object({
|
|
|
24
25
|
.default({ level: 'info', console: true, file: false, json: false }),
|
|
25
26
|
});
|
|
26
27
|
const ENV_INTERPOLATION_RE = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
28
|
+
/** Raised when a YAML text cannot be parsed as a plain object. */
|
|
29
|
+
export class YamlParseError extends Error {
|
|
30
|
+
innerError;
|
|
31
|
+
constructor(message, innerError) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.innerError = innerError;
|
|
34
|
+
this.name = 'YamlParseError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Parse YAML text into a plain object. Returns `{}` for null/undefined/empty input. */
|
|
38
|
+
export function parseYamlObject(text) {
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = parseYaml(text);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new YamlParseError(`YAML parsing failed: ${error.message}`, error instanceof Error ? error : undefined);
|
|
45
|
+
}
|
|
46
|
+
if (parsed === null || parsed === undefined)
|
|
47
|
+
return {};
|
|
48
|
+
if (typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
49
|
+
throw new YamlParseError('YAML must parse to an object');
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
/** Serialize a plain object to a YAML string. */
|
|
54
|
+
export function stringifyYamlObject(value) {
|
|
55
|
+
return stringifyYaml(value);
|
|
56
|
+
}
|
|
27
57
|
export class ConfigLoadError extends Error {
|
|
28
58
|
issues;
|
|
29
59
|
constructor(message, issues = []) {
|
|
@@ -32,6 +62,8 @@ export class ConfigLoadError extends Error {
|
|
|
32
62
|
this.issues = issues;
|
|
33
63
|
}
|
|
34
64
|
}
|
|
65
|
+
// These accessors read `process.env` directly and are node-bun only (ADR-008). On
|
|
66
|
+
// `cloudflare-workers` there is no `process`; inject config explicitly rather than calling these.
|
|
35
67
|
export function getNodeEnv() {
|
|
36
68
|
return process.env.NODE_ENV ?? 'development';
|
|
37
69
|
}
|
|
@@ -44,6 +76,7 @@ export function getProcessEnv() {
|
|
|
44
76
|
export function getDatabaseUrl() {
|
|
45
77
|
return process.env.DATABASE_URL;
|
|
46
78
|
}
|
|
79
|
+
/** Node-bun only: interpolates `${VAR}` from `process.env` (see note above). */
|
|
47
80
|
export function interpolateEnv(value) {
|
|
48
81
|
return value.replace(ENV_INTERPOLATION_RE, (_match, name) => process.env[name] ?? `\${${name}}`);
|
|
49
82
|
}
|
|
@@ -57,48 +90,6 @@ export function interpolateTree(value) {
|
|
|
57
90
|
}
|
|
58
91
|
return value;
|
|
59
92
|
}
|
|
60
|
-
export function deepMerge(target, source) {
|
|
61
|
-
const result = { ...target };
|
|
62
|
-
for (const [key, value] of Object.entries(source)) {
|
|
63
|
-
if (isPlainObject(value) && isPlainObject(result[key])) {
|
|
64
|
-
result[key] = deepMerge(result[key], value);
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
result[key] = value;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return result;
|
|
71
|
-
}
|
|
72
|
-
export function flattenKeys(obj, prefix = '') {
|
|
73
|
-
const result = {};
|
|
74
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
75
|
-
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
76
|
-
if (isPlainObject(value)) {
|
|
77
|
-
Object.assign(result, flattenKeys(value, fullKey));
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
result[fullKey] = JSON.stringify(value);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
export function deFlattenKeys(entries) {
|
|
86
|
-
const result = {};
|
|
87
|
-
for (const [key, rawValue] of Object.entries(entries)) {
|
|
88
|
-
const parts = key.split('.');
|
|
89
|
-
let current = result;
|
|
90
|
-
for (const part of parts.slice(0, -1)) {
|
|
91
|
-
if (!isPlainObject(current[part]))
|
|
92
|
-
current[part] = {};
|
|
93
|
-
current = current[part];
|
|
94
|
-
}
|
|
95
|
-
const last = parts.at(-1);
|
|
96
|
-
if (last === undefined)
|
|
97
|
-
continue;
|
|
98
|
-
current[last] = parseConfigValue(rawValue);
|
|
99
|
-
}
|
|
100
|
-
return result;
|
|
101
|
-
}
|
|
102
93
|
export function buildConfigFromObject(raw, options = {}) {
|
|
103
94
|
const interpolated = interpolateTree(raw);
|
|
104
95
|
const merged = options.overrides
|
|
@@ -112,13 +103,7 @@ export function buildConfigFromObject(raw, options = {}) {
|
|
|
112
103
|
}
|
|
113
104
|
export function parseConfigYaml(yamlText) {
|
|
114
105
|
try {
|
|
115
|
-
|
|
116
|
-
if (parsed === null || parsed === undefined)
|
|
117
|
-
return {};
|
|
118
|
-
if (!isPlainObject(parsed)) {
|
|
119
|
-
throw new ConfigLoadError('Config YAML must parse to an object');
|
|
120
|
-
}
|
|
121
|
-
return parsed;
|
|
106
|
+
return parseYamlObject(yamlText);
|
|
122
107
|
}
|
|
123
108
|
catch (error) {
|
|
124
109
|
if (error instanceof ConfigLoadError)
|
|
@@ -129,14 +114,6 @@ export function parseConfigYaml(yamlText) {
|
|
|
129
114
|
export function buildConfigFromYaml(yamlText, options = {}) {
|
|
130
115
|
return buildConfigFromObject(parseConfigYaml(yamlText), options);
|
|
131
116
|
}
|
|
132
|
-
function parseConfigValue(value) {
|
|
133
|
-
try {
|
|
134
|
-
return JSON.parse(value);
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return value;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
117
|
function isPlainObject(value) {
|
|
141
118
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
142
119
|
}
|
package/dist/context.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Config } from './config';
|
|
2
2
|
import type { FileSystem } from './fs';
|
|
3
|
-
import type { RuntimeCapabilities,
|
|
3
|
+
import type { RuntimeCapabilities, RuntimeName } from './types';
|
|
4
4
|
export type RuntimeScope = 'process' | 'server-request' | 'scheduled-event' | 'test';
|
|
5
5
|
export interface RuntimeServiceMap {
|
|
6
6
|
config: Config;
|
|
@@ -12,7 +12,6 @@ export interface RuntimeContextOptions<TServices extends RuntimeServiceMap = Run
|
|
|
12
12
|
runtimeName?: RuntimeName;
|
|
13
13
|
capabilities?: RuntimeCapabilities;
|
|
14
14
|
services?: Partial<TServices>;
|
|
15
|
-
factory?: RuntimeFactory;
|
|
16
15
|
}
|
|
17
16
|
export declare class RuntimeContext<TServices extends RuntimeServiceMap = RuntimeServiceMap> {
|
|
18
17
|
readonly scope: RuntimeScope;
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEhE,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,MAAM,CAAC;AAErF,MAAM,WAAW,iBAAiB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,UAAU,CAAC;IACvB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB,CAAC,SAAS,SAAS,iBAAiB,GAAG,iBAAiB;IAC1F,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;CACjC;AAED,qBAAa,cAAc,CAAC,SAAS,SAAS,iBAAiB,GAAG,iBAAiB;IAC/E,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,QAAQ,mDAA0D;gBAE/D,OAAO,GAAE,qBAAqB,CAAC,SAAS,CAAM;IAqB1D,QAAQ,CAAC,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI;IAKxE,GAAG,CAAC,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS;IAIhE,OAAO,CAAC,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IAQxD,GAAG,CAAC,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO;IAIzC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAejC;AAMD,wBAAgB,oBAAoB,CAAC,SAAS,SAAS,iBAAiB,GAAG,iBAAiB,EACxF,OAAO,GAAE,qBAAqB,CAAC,SAAS,CAAM,GAC/C,cAAc,CAAC,SAAS,CAAC,CAE3B"}
|
package/dist/context.js
CHANGED
|
@@ -7,10 +7,9 @@ export class RuntimeContext {
|
|
|
7
7
|
services = new Map();
|
|
8
8
|
constructor(options = {}) {
|
|
9
9
|
this.scope = options.scope ?? 'process';
|
|
10
|
-
this.runtimeName = options.runtimeName ??
|
|
10
|
+
this.runtimeName = options.runtimeName ?? 'node-bun';
|
|
11
11
|
this.capabilities =
|
|
12
12
|
options.capabilities ??
|
|
13
|
-
options.factory?.capabilities ??
|
|
14
13
|
{
|
|
15
14
|
hasFilesystem: true,
|
|
16
15
|
hasProcessExecution: true,
|
package/dist/fs.d.ts
CHANGED
|
@@ -22,6 +22,13 @@ export interface FileSystem {
|
|
|
22
22
|
rename(src: string, dest: string): Promise<void>;
|
|
23
23
|
createLogStream(path: string): LogStream;
|
|
24
24
|
}
|
|
25
|
+
export interface SyncFileSystem {
|
|
26
|
+
readFile(path: string): string;
|
|
27
|
+
writeFile(path: string, content: string): void;
|
|
28
|
+
mkdir(path: string): void;
|
|
29
|
+
readDir(path: string): string[];
|
|
30
|
+
unlink(path: string): void;
|
|
31
|
+
}
|
|
25
32
|
export declare class NodeFileSystem implements FileSystem {
|
|
26
33
|
readFile(path: string): Promise<string>;
|
|
27
34
|
writeFile(path: string, content: string): Promise<void>;
|
|
@@ -36,6 +43,13 @@ export declare class NodeFileSystem implements FileSystem {
|
|
|
36
43
|
rename(src: string, dest: string): Promise<void>;
|
|
37
44
|
createLogStream(path: string): LogStream;
|
|
38
45
|
}
|
|
46
|
+
export declare class NodeSyncFileSystem implements SyncFileSystem {
|
|
47
|
+
readFile(path: string): string;
|
|
48
|
+
writeFile(path: string, content: string): void;
|
|
49
|
+
mkdir(path: string): void;
|
|
50
|
+
readDir(path: string): string[];
|
|
51
|
+
unlink(path: string): void;
|
|
52
|
+
}
|
|
39
53
|
export declare class CloudflareFileSystem implements FileSystem {
|
|
40
54
|
readFile(path: string): Promise<string>;
|
|
41
55
|
writeFile(path: string, _content: string): Promise<void>;
|
|
@@ -53,6 +67,7 @@ export declare class CloudflareFileSystem implements FileSystem {
|
|
|
53
67
|
export declare function setFileSystem(fileSystem: FileSystem): () => void;
|
|
54
68
|
export declare function getFs(): FileSystem;
|
|
55
69
|
export declare function ensureDirForFile(path: string, fs?: FileSystem): Promise<void>;
|
|
70
|
+
export declare function ensureDirForFileSync(path: string, fs: SyncFileSystem): void;
|
|
56
71
|
export declare function atomicWriteFile(path: string, content: string, fs?: FileSystem): Promise<void>;
|
|
57
72
|
export declare function atomicWriteJson(path: string, value: unknown, fs?: FileSystem): Promise<void>;
|
|
58
73
|
export declare function readJsonFile<T = unknown>(path: string, fs?: FileSystem): Promise<T>;
|
package/dist/fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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"}
|
package/dist/fs.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirnamePath, getProcessCwd, joinPath, resolvePath } from './path.js';
|
|
1
3
|
let fsPromisesModule = null;
|
|
2
4
|
let fsModule = null;
|
|
3
5
|
function nodeFsPromises() {
|
|
@@ -76,37 +78,52 @@ export class NodeFileSystem {
|
|
|
76
78
|
return new LazyNodeLogStream(path);
|
|
77
79
|
}
|
|
78
80
|
}
|
|
81
|
+
export class NodeSyncFileSystem {
|
|
82
|
+
readFile(path) {
|
|
83
|
+
return readFileSync(path, 'utf-8');
|
|
84
|
+
}
|
|
85
|
+
writeFile(path, content) {
|
|
86
|
+
ensureDirForFileSync(path, this);
|
|
87
|
+
writeFileSync(path, content, 'utf-8');
|
|
88
|
+
}
|
|
89
|
+
mkdir(path) {
|
|
90
|
+
mkdirSync(path, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
readDir(path) {
|
|
93
|
+
return readdirSync(path);
|
|
94
|
+
}
|
|
95
|
+
unlink(path) {
|
|
96
|
+
rmSync(path, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
79
99
|
class LazyNodeLogStream {
|
|
80
100
|
ready;
|
|
81
101
|
ended = false;
|
|
82
|
-
|
|
102
|
+
// Single serialized chain: every write/end is appended here, so the underlying stream observes
|
|
103
|
+
// them in call order regardless of how the resolving microtasks interleave. A per-write `shift()`
|
|
104
|
+
// off a shared buffer (the previous approach) could reorder writes that arrived in the same tick.
|
|
105
|
+
tail;
|
|
83
106
|
constructor(path) {
|
|
84
107
|
this.ready = nodeFs().then(({ createWriteStream, mkdirSync }) => {
|
|
85
108
|
mkdirSync(dirnamePath(path), { recursive: true });
|
|
86
109
|
const stream = createWriteStream(path, { flags: 'a' });
|
|
87
|
-
for (const chunk of this.pending.splice(0))
|
|
88
|
-
stream.write(chunk);
|
|
89
|
-
if (this.ended)
|
|
90
|
-
stream.end();
|
|
91
110
|
return {
|
|
92
111
|
write: (chunk) => stream.write(chunk),
|
|
93
112
|
end: () => stream.end(),
|
|
94
113
|
};
|
|
95
114
|
});
|
|
115
|
+
this.tail = this.ready;
|
|
96
116
|
}
|
|
97
117
|
write(chunk) {
|
|
98
118
|
if (this.ended)
|
|
99
119
|
return;
|
|
100
|
-
this.
|
|
101
|
-
void this.ready.then((stream) => {
|
|
102
|
-
const next = this.pending.shift();
|
|
103
|
-
if (next !== undefined)
|
|
104
|
-
stream.write(next);
|
|
105
|
-
});
|
|
120
|
+
this.tail = this.tail.then(() => this.ready.then((stream) => stream.write(chunk)));
|
|
106
121
|
}
|
|
107
122
|
end() {
|
|
123
|
+
if (this.ended)
|
|
124
|
+
return;
|
|
108
125
|
this.ended = true;
|
|
109
|
-
|
|
126
|
+
this.tail = this.tail.then(() => this.ready.then((stream) => stream.end()));
|
|
110
127
|
}
|
|
111
128
|
}
|
|
112
129
|
const CLOUDFLARE_FS_ERROR = 'FileSystem is not available on Cloudflare Workers. Use D1, KV, or R2.';
|
|
@@ -165,9 +182,12 @@ export function getFs() {
|
|
|
165
182
|
export async function ensureDirForFile(path, fs = getFs()) {
|
|
166
183
|
await fs.mkdir(dirnamePath(path));
|
|
167
184
|
}
|
|
185
|
+
export function ensureDirForFileSync(path, fs) {
|
|
186
|
+
fs.mkdir(dirnamePath(path));
|
|
187
|
+
}
|
|
168
188
|
export async function atomicWriteFile(path, content, fs = getFs()) {
|
|
169
189
|
await ensureDirForFile(path, fs);
|
|
170
|
-
const tempPath = `${path}.${getProcessPid()}.${
|
|
190
|
+
const tempPath = `${path}.${getProcessPid()}.${uniqueToken()}.tmp`;
|
|
171
191
|
await fs.writeFile(tempPath, content);
|
|
172
192
|
await fs.rename(tempPath, path);
|
|
173
193
|
}
|
|
@@ -223,55 +243,8 @@ function hasBunFile(path) {
|
|
|
223
243
|
function getProcessPid() {
|
|
224
244
|
return globalThis.process?.pid ?? 0;
|
|
225
245
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return path.replaceAll('\\', '/');
|
|
231
|
-
}
|
|
232
|
-
function isAbsolutePath(path) {
|
|
233
|
-
return path.startsWith('/') || /^[A-Za-z]:\//.test(normalizeSeparators(path));
|
|
234
|
-
}
|
|
235
|
-
function dirnamePath(path) {
|
|
236
|
-
const input = normalizeSeparators(path);
|
|
237
|
-
if (/^\/+$/.test(input))
|
|
238
|
-
return '/';
|
|
239
|
-
const normalized = input.replace(/\/+$/, '');
|
|
240
|
-
if (normalized === '' || normalized === '/')
|
|
241
|
-
return normalized || '.';
|
|
242
|
-
const index = normalized.lastIndexOf('/');
|
|
243
|
-
if (index < 0)
|
|
244
|
-
return '.';
|
|
245
|
-
if (index === 0)
|
|
246
|
-
return '/';
|
|
247
|
-
return normalized.slice(0, index);
|
|
248
|
-
}
|
|
249
|
-
function joinPath(...segments) {
|
|
250
|
-
const filtered = segments.filter((segment) => segment.length > 0).map(normalizeSeparators);
|
|
251
|
-
if (filtered.length === 0)
|
|
252
|
-
return '.';
|
|
253
|
-
const absolute = isAbsolutePath(filtered[0] ?? '');
|
|
254
|
-
const joined = filtered.join('/').replace(/\/+/g, '/');
|
|
255
|
-
return absolute ? joined : joined.replace(/^\//, '');
|
|
256
|
-
}
|
|
257
|
-
function resolvePath(...segments) {
|
|
258
|
-
const candidates = segments.length === 0 ? [getProcessCwd()] : segments;
|
|
259
|
-
let resolved = '';
|
|
260
|
-
for (const segment of candidates.map(normalizeSeparators)) {
|
|
261
|
-
if (segment.length === 0)
|
|
262
|
-
continue;
|
|
263
|
-
resolved = isAbsolutePath(segment) ? segment : joinPath(resolved || getProcessCwd(), segment);
|
|
264
|
-
}
|
|
265
|
-
const parts = [];
|
|
266
|
-
const absolute = isAbsolutePath(resolved);
|
|
267
|
-
for (const part of resolved.split('/')) {
|
|
268
|
-
if (part === '' || part === '.')
|
|
269
|
-
continue;
|
|
270
|
-
if (part === '..') {
|
|
271
|
-
parts.pop();
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
parts.push(part);
|
|
275
|
-
}
|
|
276
|
-
return `${absolute ? '/' : ''}${parts.join('/')}` || (absolute ? '/' : '.');
|
|
246
|
+
// Two writers to the same path in the same millisecond must not share a temp name, or one clobbers
|
|
247
|
+
// the other before rename. randomUUID disambiguates; Date.now keeps names sortable for debugging.
|
|
248
|
+
function uniqueToken() {
|
|
249
|
+
return `${Date.now()}.${crypto.randomUUID()}`;
|
|
277
250
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,MAAM,CAAC;AACrB,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,MAAM,CAAC;AACrB,cAAc,QAAQ,CAAC;AACvB,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/path.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function normalizeSeparators(path: string): string;
|
|
2
|
+
export declare function isAbsolutePath(path: string): boolean;
|
|
3
|
+
export declare function dirnamePath(path: string): string;
|
|
4
|
+
export declare function joinPath(...segments: string[]): string;
|
|
5
|
+
export declare function resolvePath(...segments: string[]): string;
|
|
6
|
+
export declare function getProcessCwd(): string;
|
|
7
|
+
//# sourceMappingURL=path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAIA,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAShD;AAED,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMtD;AAED,wBAAgB,WAAW,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAkBzD;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|