@gobing-ai/ts-runtime 0.2.7 → 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 +159 -20
- 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 +97 -13
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +42 -0
- package/dist/schema-validation.d.ts.map +1 -0
- package/dist/schema-validation.js +304 -0
- 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 +124 -25
- package/src/index.ts +2 -0
- package/src/path.ts +54 -0
- package/src/process-executor.ts +128 -0
- package/src/schema-validation.ts +442 -0
- 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`.
|
|
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';
|
|
@@ -174,9 +217,50 @@ const config = buildConfigFromYaml(yaml, {
|
|
|
174
217
|
// config.logging.level === 'debug' (from YAML)
|
|
175
218
|
```
|
|
176
219
|
|
|
177
|
-
### 4.
|
|
220
|
+
### 4. Structured config + JSON-schema validation
|
|
221
|
+
|
|
222
|
+
`loadStructuredConfig` reads a `.json`/`.yaml` file and — if it declares a top-level `$schema` — validates
|
|
223
|
+
it against that schema before returning. This powers schema-checked rule and workflow files in
|
|
224
|
+
`ts-rule-engine` and `ts-dual-workflow-engine`.
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { loadStructuredConfig } from '@gobing-ai/ts-runtime';
|
|
228
|
+
|
|
229
|
+
const config = await loadStructuredConfig('rules.yaml'); // validates against its $schema, throws on violation
|
|
230
|
+
const raw = await loadStructuredConfig('rules.yaml', { validateSchema: false }); // skip validation
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
A `StructuredConfigSchemaError` (with a `violations` array of `{ path, message }`) is thrown on any
|
|
234
|
+
schema violation.
|
|
178
235
|
|
|
179
|
-
|
|
236
|
+
#### `$schema` reference styles
|
|
237
|
+
|
|
238
|
+
There are three ways a config file can name its schema. **Prefer the bundled package specifier** — it is
|
|
239
|
+
the most secure and performant default:
|
|
240
|
+
|
|
241
|
+
| Style | Example | Resolution | Notes |
|
|
242
|
+
|-------|---------|------------|-------|
|
|
243
|
+
| **Package specifier** (recommended) | `$schema: "@gobing-ai/ts-rule-engine/schemas/rule-file.schema.json"` | Resolved through `node_modules` via the module resolver, then read from disk | No network, no path guessing; survives hoisting/pnpm/monorepo layouts. Schemas ship in each package's `schemas/` (declared in `files`). **Quote the value** — YAML treats a leading `@` as reserved. |
|
|
244
|
+
| Relative path | `$schema: ./schemas/rule-file.schema.json` | Resolved against the config file's directory | Fine for repo-local schemas; brittle if the config moves. |
|
|
245
|
+
| Remote URL | `$schema: https://json-schema.org/.../rule-file.schema.json` | Fetched over HTTP(S) — **off by default** | SSRF/DoS surface for third-party configs. Opt in with `{ allowRemote: true }` (5s timeout) or supply your own `fetch`. |
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
// Bundled package schema (default path — no extra options needed):
|
|
249
|
+
// $schema: "@gobing-ai/ts-rule-engine/schemas/rule-file.schema.json"
|
|
250
|
+
await loadStructuredConfig('rules.yaml');
|
|
251
|
+
|
|
252
|
+
// Remote schema is refused unless explicitly enabled:
|
|
253
|
+
await loadStructuredConfig('rules.yaml', { allowRemote: true }); // built-in fetch, time-bounded
|
|
254
|
+
await loadStructuredConfig('rules.yaml', { fetch: myFetch }); // or inject your own
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
> **Security:** remote schema fetching is disabled by default. Resolving a bundled schema from
|
|
258
|
+
> `node_modules` keeps validation entirely local — no outbound request, no dependency on a schema host's
|
|
259
|
+
> availability, and no chance for a malicious config to point validation at an internal URL.
|
|
260
|
+
|
|
261
|
+
### 5. File system abstraction
|
|
262
|
+
|
|
263
|
+
Most file operations go through the async `FileSystem` interface. Swap implementations for testing:
|
|
180
264
|
|
|
181
265
|
```ts
|
|
182
266
|
import { getFs } from '@gobing-ai/ts-runtime';
|
|
@@ -186,7 +270,19 @@ await fs.writeFile('output.json', JSON.stringify(data));
|
|
|
186
270
|
const content = await fs.readFile('output.json');
|
|
187
271
|
```
|
|
188
272
|
|
|
189
|
-
|
|
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
|
+
|
|
285
|
+
### 6. Graceful disposal
|
|
190
286
|
|
|
191
287
|
`RuntimeContext.dispose()` calls `dispose()` on every registered service that implements the pattern:
|
|
192
288
|
|
|
@@ -261,6 +357,9 @@ const config = buildConfigFromObject({
|
|
|
261
357
|
|
|
262
358
|
### Process execution
|
|
263
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
|
+
|
|
264
363
|
```ts
|
|
265
364
|
import { NodeProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
266
365
|
|
|
@@ -277,6 +376,46 @@ console.log(result.stdout);
|
|
|
277
376
|
console.log(`Duration: ${result.durationMs}ms`);
|
|
278
377
|
```
|
|
279
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
|
+
|
|
280
419
|
### SpanContext (for telemetry)
|
|
281
420
|
|
|
282
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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"}
|