@gobing-ai/ts-runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +288 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +156 -0
- package/dist/context.d.ts +30 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +66 -0
- package/dist/fs.d.ts +64 -0
- package/dist/fs.d.ts.map +1 -0
- package/dist/fs.js +166 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +304 -0
- package/dist/process-executor.d.ts +41 -0
- package/dist/process-executor.d.ts.map +1 -0
- package/dist/process-executor.js +70 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +0 -0
- package/package.json +45 -0
- package/src/config.ts +169 -0
- package/src/context.ts +97 -0
- package/src/fs.ts +239 -0
- package/src/index.ts +5 -0
- package/src/process-executor.ts +118 -0
- package/src/types.ts +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# @gobing-ai/ts-runtime
|
|
2
|
+
|
|
3
|
+
Runtime abstraction layer — environment detection, file system, process execution, configuration, and context management. Works on Bun/Node and Cloudflare Workers.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
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`.
|
|
8
|
+
|
|
9
|
+
**Key abstractions:**
|
|
10
|
+
|
|
11
|
+
| Concept | Interface | Bun/Node impl | Cloudflare impl |
|
|
12
|
+
|---------|-----------|---------------|-----------------|
|
|
13
|
+
| File system | `FileSystem` | `NodeFileSystem` | `CloudflareFileSystem` (stub) |
|
|
14
|
+
| Process execution | `ProcessExecutor` | `NodeProcessExecutor` | — |
|
|
15
|
+
| Configuration | `Config` (Zod schema) | YAML + env vars | YAML + env vars |
|
|
16
|
+
| Context | `RuntimeContext` | service locator | service locator |
|
|
17
|
+
| Tracing | `SpanContext` | `{ traceId, spanId }` | `{ traceId, spanId }` |
|
|
18
|
+
|
|
19
|
+
## Architecture
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
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
|
+
class RuntimeContext {
|
|
33
|
+
+RuntimeScope scope
|
|
34
|
+
+RuntimeName runtimeName
|
|
35
|
+
+RuntimeCapabilities capabilities
|
|
36
|
+
+register(key, service) void
|
|
37
|
+
+get(key) T | undefined
|
|
38
|
+
+require(key) T
|
|
39
|
+
+has(key) boolean
|
|
40
|
+
+dispose() Promise~void~
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class FileSystem {
|
|
44
|
+
<<interface>>
|
|
45
|
+
+readFile(path) Promise~string~
|
|
46
|
+
+writeFile(path, content) Promise~void~
|
|
47
|
+
+exists(path) Promise~boolean~
|
|
48
|
+
+mkdir(path) Promise~void~
|
|
49
|
+
+readDir(path) Promise~string[]~
|
|
50
|
+
+unlink(path) Promise~void~
|
|
51
|
+
+stat(path) Promise~FileStat | null~
|
|
52
|
+
+realpath(path) Promise~string~
|
|
53
|
+
+copy(src, dest) Promise~void~
|
|
54
|
+
+rename(src, dest) Promise~void~
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class NodeFileSystem {
|
|
58
|
+
+readFile(path) Promise~string~
|
|
59
|
+
+writeFile(path, content) Promise~void~
|
|
60
|
+
+exists(path) Promise~boolean~
|
|
61
|
+
+mkdir(path) Promise~void~
|
|
62
|
+
+readDir(path) Promise~string[]~
|
|
63
|
+
+unlink(path) Promise~void~
|
|
64
|
+
+stat(path) Promise~FileStat | null~
|
|
65
|
+
+realpath(path) Promise~string~
|
|
66
|
+
+copy(src, dest) Promise~void~
|
|
67
|
+
+rename(src, dest) Promise~void~
|
|
68
|
+
+createLogStream(path) LogStream
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class CloudflareFileSystem {
|
|
72
|
+
+readFile(path) Promise~string~
|
|
73
|
+
+writeFile(path, content) Promise~void~
|
|
74
|
+
+exists(path) Promise~boolean~
|
|
75
|
+
+mkdir(path) Promise~void~
|
|
76
|
+
+readDir(path) Promise~string[]~
|
|
77
|
+
+unlink(path) Promise~void~
|
|
78
|
+
+stat(path) Promise~FileStat | null~
|
|
79
|
+
+realpath(path) Promise~string~
|
|
80
|
+
+copy(src, dest) Promise~void~
|
|
81
|
+
+rename(src, dest) Promise~void~
|
|
82
|
+
+createLogStream(path) LogStream
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class ProcessExecutor {
|
|
86
|
+
<<interface>>
|
|
87
|
+
+run(options) Promise~ProcessResult~
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
class NodeProcessExecutor {
|
|
91
|
+
+run(options) Promise~ProcessResult~
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class Config {
|
|
95
|
+
+AppConfig app
|
|
96
|
+
+DatabaseConfig database
|
|
97
|
+
+LoggingConfig logging
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class SpanContext {
|
|
101
|
+
<<interface>>
|
|
102
|
+
+string traceId
|
|
103
|
+
+string spanId
|
|
104
|
+
+Record~string,string~ baggage?
|
|
105
|
+
+Record~string,string|number|boolean~ attributes?
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
FileSystem <|.. NodeFileSystem : implements
|
|
109
|
+
FileSystem <|.. CloudflareFileSystem : implements
|
|
110
|
+
ProcessExecutor <|.. NodeProcessExecutor : implements
|
|
111
|
+
RuntimeContext --> FileSystem : "fileSystem"
|
|
112
|
+
RuntimeContext --> Config : "config"
|
|
113
|
+
RuntimeContext --> ProcessExecutor : "processExecutor"
|
|
114
|
+
RuntimeFactory --> RuntimeContext : creates
|
|
115
|
+
RuntimeFactory --> FileSystem : creates
|
|
116
|
+
RuntimeFactory --> Config : loadConfig()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## How It Works
|
|
120
|
+
|
|
121
|
+
### 1. Runtime detection
|
|
122
|
+
|
|
123
|
+
At startup, a `RuntimeFactory` is selected based on the environment:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { createRuntimeContext } from '@gobing-ai/ts-runtime';
|
|
127
|
+
|
|
128
|
+
const ctx = createRuntimeContext({
|
|
129
|
+
runtimeName: 'node-bun',
|
|
130
|
+
capabilities: {
|
|
131
|
+
hasFilesystem: true,
|
|
132
|
+
hasProcessExecution: true,
|
|
133
|
+
hasPersistentStorage: true,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 2. Service registration
|
|
139
|
+
|
|
140
|
+
Services are registered into `RuntimeContext` and accessed by key:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
ctx.register('db', drizzleAdapter);
|
|
144
|
+
ctx.register('cache', redisClient);
|
|
145
|
+
|
|
146
|
+
const db = ctx.require('db'); // throws if missing
|
|
147
|
+
const cache = ctx.get('cache'); // undefined if missing
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 3. Configuration
|
|
151
|
+
|
|
152
|
+
Config is loaded from YAML with environment variable interpolation and Zod validation:
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
# config.yaml
|
|
156
|
+
app:
|
|
157
|
+
name: my-app
|
|
158
|
+
env: ${APP_ENV}
|
|
159
|
+
port: ${PORT}
|
|
160
|
+
database:
|
|
161
|
+
url: ${DATABASE_URL}
|
|
162
|
+
logging:
|
|
163
|
+
level: debug
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { buildConfigFromYaml } from '@gobing-ai/ts-runtime';
|
|
168
|
+
|
|
169
|
+
const yaml = await fs.readFile('config.yaml');
|
|
170
|
+
const config = buildConfigFromYaml(yaml, {
|
|
171
|
+
overrides: { app: { port: 8080 } },
|
|
172
|
+
});
|
|
173
|
+
// config.app.port === 8080 (overridden)
|
|
174
|
+
// config.logging.level === 'debug' (from YAML)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 4. File system abstraction
|
|
178
|
+
|
|
179
|
+
All file operations go through the `FileSystem` interface. Swap implementations for testing:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { getFs } from '@gobing-ai/ts-runtime';
|
|
183
|
+
|
|
184
|
+
const fs = getFs();
|
|
185
|
+
await fs.writeFile('output.json', JSON.stringify(data));
|
|
186
|
+
const content = await fs.readFile('output.json');
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 5. Graceful disposal
|
|
190
|
+
|
|
191
|
+
`RuntimeContext.dispose()` calls `dispose()` on every registered service that implements the pattern:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
process.on('SIGTERM', async () => {
|
|
195
|
+
await ctx.dispose();
|
|
196
|
+
process.exit(0);
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Usage
|
|
201
|
+
|
|
202
|
+
### Install
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
bun add @gobing-ai/ts-runtime
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Basic setup (Bun/Node)
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import { createRuntimeContext, getFs, NodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
212
|
+
|
|
213
|
+
const ctx = createRuntimeContext({
|
|
214
|
+
runtimeName: 'node-bun',
|
|
215
|
+
services: {
|
|
216
|
+
fileSystem: new NodeFileSystem(),
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const fs = ctx.require('fileSystem');
|
|
221
|
+
await fs.writeFile('hello.txt', 'Hello, world!');
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Cloudflare Workers
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { createRuntimeContext, CloudflareFileSystem } from '@gobing-ai/ts-runtime';
|
|
228
|
+
|
|
229
|
+
export default {
|
|
230
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
231
|
+
const ctx = createRuntimeContext({
|
|
232
|
+
runtimeName: 'cloudflare-workers',
|
|
233
|
+
capabilities: {
|
|
234
|
+
hasFilesystem: false,
|
|
235
|
+
hasProcessExecution: false,
|
|
236
|
+
hasPersistentStorage: false,
|
|
237
|
+
},
|
|
238
|
+
services: {
|
|
239
|
+
fileSystem: new CloudflareFileSystem(),
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
// ...
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Config with env interpolation
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { buildConfigFromYaml, buildConfigFromObject } from '@gobing-ai/ts-runtime';
|
|
251
|
+
|
|
252
|
+
// From YAML file
|
|
253
|
+
const config = buildConfigFromYaml(yamlText);
|
|
254
|
+
|
|
255
|
+
// From plain object (programmatic config)
|
|
256
|
+
const config = buildConfigFromObject({
|
|
257
|
+
app: { name: 'api', env: 'production', port: 3000 },
|
|
258
|
+
database: { url: ':memory:' },
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Process execution
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { NodeProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
266
|
+
|
|
267
|
+
const exec = new NodeProcessExecutor({ defaultTimeout: 30_000 });
|
|
268
|
+
|
|
269
|
+
const result = await exec.run({
|
|
270
|
+
command: 'git',
|
|
271
|
+
args: ['status', '--short'],
|
|
272
|
+
cwd: '/path/to/repo',
|
|
273
|
+
rejectOnError: true,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
console.log(result.stdout);
|
|
277
|
+
console.log(`Duration: ${result.durationMs}ms`);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### SpanContext (for telemetry)
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import type { SpanContext } from '@gobing-ai/ts-runtime';
|
|
284
|
+
|
|
285
|
+
function processRequest(ctx: SpanContext) {
|
|
286
|
+
console.log(`Trace: ${ctx.traceId}, Span: ${ctx.spanId}`);
|
|
287
|
+
}
|
|
288
|
+
```
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ZodIssue, z } from 'zod';
|
|
2
|
+
export declare const configSchema: z.ZodObject<{
|
|
3
|
+
app: z.ZodDefault<z.ZodObject<{
|
|
4
|
+
name: z.ZodDefault<z.ZodString>;
|
|
5
|
+
env: z.ZodDefault<z.ZodEnum<{
|
|
6
|
+
development: "development";
|
|
7
|
+
staging: "staging";
|
|
8
|
+
production: "production";
|
|
9
|
+
test: "test";
|
|
10
|
+
}>>;
|
|
11
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
}, z.core.$strip>>;
|
|
13
|
+
database: z.ZodDefault<z.ZodObject<{
|
|
14
|
+
url: z.ZodDefault<z.ZodString>;
|
|
15
|
+
}, z.core.$strip>>;
|
|
16
|
+
logging: z.ZodDefault<z.ZodObject<{
|
|
17
|
+
level: z.ZodDefault<z.ZodEnum<{
|
|
18
|
+
error: "error";
|
|
19
|
+
trace: "trace";
|
|
20
|
+
debug: "debug";
|
|
21
|
+
info: "info";
|
|
22
|
+
warn: "warn";
|
|
23
|
+
fatal: "fatal";
|
|
24
|
+
}>>;
|
|
25
|
+
console: z.ZodDefault<z.ZodBoolean>;
|
|
26
|
+
file: z.ZodDefault<z.ZodBoolean>;
|
|
27
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
28
|
+
json: z.ZodDefault<z.ZodBoolean>;
|
|
29
|
+
}, z.core.$strip>>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
export type Config = z.output<typeof configSchema>;
|
|
32
|
+
export declare class ConfigLoadError extends Error {
|
|
33
|
+
readonly issues: ZodIssue[];
|
|
34
|
+
constructor(message: string, issues?: ZodIssue[]);
|
|
35
|
+
}
|
|
36
|
+
export declare function getNodeEnv(): string;
|
|
37
|
+
export declare function isTestEnv(): boolean;
|
|
38
|
+
export declare function getDatabaseUrl(): string | undefined;
|
|
39
|
+
export declare function interpolateEnv(value: string): string;
|
|
40
|
+
export declare function interpolateTree(value: unknown): unknown;
|
|
41
|
+
export declare function deepMerge(target: Record<string, unknown>, source: Record<string, unknown>): Record<string, unknown>;
|
|
42
|
+
export declare function flattenKeys(obj: Record<string, unknown>, prefix?: string): Record<string, string>;
|
|
43
|
+
export declare function deFlattenKeys(entries: Record<string, string>): Record<string, unknown>;
|
|
44
|
+
export declare function buildConfigFromObject(raw: Record<string, unknown>, options?: {
|
|
45
|
+
overrides?: Partial<Config>;
|
|
46
|
+
}): Config;
|
|
47
|
+
export declare function parseConfigYaml(yamlText: string): Record<string, unknown>;
|
|
48
|
+
export declare function buildConfigFromYaml(yamlText: string, options?: {
|
|
49
|
+
overrides?: Partial<Config>;
|
|
50
|
+
}): Config;
|
|
51
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,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,qBAAa,eAAgB,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,QAAQ,EAAO;CAKvD;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,SAAS,IAAI,OAAO,CAEnC;AAED,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAEnD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOvD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUnH;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,SAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAW7F;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAetF;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,CAYzE;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
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { parse as parseYaml } from 'yaml';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export const configSchema = z.object({
|
|
4
|
+
app: z
|
|
5
|
+
.object({
|
|
6
|
+
name: z.string().default('app'),
|
|
7
|
+
env: z.enum(['development', 'staging', 'production', 'test']).default('development'),
|
|
8
|
+
port: z.number().int().positive().default(3000),
|
|
9
|
+
})
|
|
10
|
+
.default({ name: 'app', env: 'development', port: 3000 }),
|
|
11
|
+
database: z
|
|
12
|
+
.object({
|
|
13
|
+
url: z.string().default(':memory:'),
|
|
14
|
+
})
|
|
15
|
+
.default({ url: ':memory:' }),
|
|
16
|
+
logging: z
|
|
17
|
+
.object({
|
|
18
|
+
level: z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']).default('info'),
|
|
19
|
+
console: z.boolean().default(true),
|
|
20
|
+
file: z.boolean().default(false),
|
|
21
|
+
filePath: z.string().optional(),
|
|
22
|
+
json: z.boolean().default(false),
|
|
23
|
+
})
|
|
24
|
+
.default({ level: 'info', console: true, file: false, json: false }),
|
|
25
|
+
});
|
|
26
|
+
const ENV_INTERPOLATION_RE = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
|
|
27
|
+
export class ConfigLoadError extends Error {
|
|
28
|
+
issues;
|
|
29
|
+
constructor(message, issues = []) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'ConfigLoadError';
|
|
32
|
+
this.issues = issues;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function getNodeEnv() {
|
|
36
|
+
return process.env.NODE_ENV ?? 'development';
|
|
37
|
+
}
|
|
38
|
+
export function isTestEnv() {
|
|
39
|
+
return getNodeEnv() === 'test';
|
|
40
|
+
}
|
|
41
|
+
export function getDatabaseUrl() {
|
|
42
|
+
return process.env.DATABASE_URL;
|
|
43
|
+
}
|
|
44
|
+
export function interpolateEnv(value) {
|
|
45
|
+
return value.replace(ENV_INTERPOLATION_RE, (_match, name) => process.env[name] ?? `\${${name}}`);
|
|
46
|
+
}
|
|
47
|
+
export function interpolateTree(value) {
|
|
48
|
+
if (typeof value === 'string')
|
|
49
|
+
return interpolateEnv(value);
|
|
50
|
+
if (Array.isArray(value))
|
|
51
|
+
return value.map(interpolateTree);
|
|
52
|
+
if (isPlainObject(value)) {
|
|
53
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, interpolateTree(child)]));
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
export function deepMerge(target, source) {
|
|
58
|
+
const result = { ...target };
|
|
59
|
+
for (const [key, value] of Object.entries(source)) {
|
|
60
|
+
if (isPlainObject(value) && isPlainObject(result[key])) {
|
|
61
|
+
result[key] = deepMerge(result[key], value);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
result[key] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
export function flattenKeys(obj, prefix = '') {
|
|
70
|
+
const result = {};
|
|
71
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
72
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
73
|
+
if (isPlainObject(value)) {
|
|
74
|
+
Object.assign(result, flattenKeys(value, fullKey));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
result[fullKey] = JSON.stringify(value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
export function deFlattenKeys(entries) {
|
|
83
|
+
const result = {};
|
|
84
|
+
for (const [key, rawValue] of Object.entries(entries)) {
|
|
85
|
+
const parts = key.split('.');
|
|
86
|
+
let current = result;
|
|
87
|
+
for (const part of parts.slice(0, -1)) {
|
|
88
|
+
if (!isPlainObject(current[part]))
|
|
89
|
+
current[part] = {};
|
|
90
|
+
current = current[part];
|
|
91
|
+
}
|
|
92
|
+
const last = parts.at(-1);
|
|
93
|
+
if (last === undefined)
|
|
94
|
+
continue;
|
|
95
|
+
current[last] = parseConfigValue(rawValue);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
export function buildConfigFromObject(raw, options = {}) {
|
|
100
|
+
const interpolated = interpolateTree(raw);
|
|
101
|
+
const merged = options.overrides
|
|
102
|
+
? deepMerge(interpolated, options.overrides)
|
|
103
|
+
: interpolated;
|
|
104
|
+
const result = configSchema.safeParse(merged);
|
|
105
|
+
if (!result.success) {
|
|
106
|
+
throw new ConfigLoadError('Config validation failed', result.error.issues);
|
|
107
|
+
}
|
|
108
|
+
return deepFreeze(result.data);
|
|
109
|
+
}
|
|
110
|
+
export function parseConfigYaml(yamlText) {
|
|
111
|
+
try {
|
|
112
|
+
const parsed = parseYaml(yamlText);
|
|
113
|
+
if (parsed === null || parsed === undefined)
|
|
114
|
+
return {};
|
|
115
|
+
if (!isPlainObject(parsed)) {
|
|
116
|
+
throw new ConfigLoadError('Config YAML must parse to an object');
|
|
117
|
+
}
|
|
118
|
+
return parsed;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof ConfigLoadError)
|
|
122
|
+
throw error;
|
|
123
|
+
throw new ConfigLoadError(`Config YAML parsing failed: ${error.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export function buildConfigFromYaml(yamlText, options = {}) {
|
|
127
|
+
return buildConfigFromObject(parseConfigYaml(yamlText), options);
|
|
128
|
+
}
|
|
129
|
+
function parseConfigValue(value) {
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(value);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function isPlainObject(value) {
|
|
138
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
139
|
+
}
|
|
140
|
+
function deepFreeze(obj) {
|
|
141
|
+
Object.freeze(obj);
|
|
142
|
+
for (const value of Object.values(obj)) {
|
|
143
|
+
if (Array.isArray(value)) {
|
|
144
|
+
Object.freeze(value);
|
|
145
|
+
for (const item of value) {
|
|
146
|
+
if (isPlainObject(item) && !Object.isFrozen(item)) {
|
|
147
|
+
deepFreeze(item);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else if (isPlainObject(value) && !Object.isFrozen(value)) {
|
|
152
|
+
deepFreeze(value);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return obj;
|
|
156
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Config } from './config';
|
|
2
|
+
import type { FileSystem } from './fs';
|
|
3
|
+
import type { RuntimeCapabilities, RuntimeFactory, RuntimeName } from './types';
|
|
4
|
+
export type RuntimeScope = 'process' | 'server-request' | 'scheduled-event' | 'test';
|
|
5
|
+
export interface RuntimeServiceMap {
|
|
6
|
+
config: Config;
|
|
7
|
+
fileSystem: FileSystem;
|
|
8
|
+
[serviceName: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface RuntimeContextOptions<TServices extends RuntimeServiceMap = RuntimeServiceMap> {
|
|
11
|
+
scope?: RuntimeScope;
|
|
12
|
+
runtimeName?: RuntimeName;
|
|
13
|
+
capabilities?: RuntimeCapabilities;
|
|
14
|
+
services?: Partial<TServices>;
|
|
15
|
+
factory?: RuntimeFactory;
|
|
16
|
+
}
|
|
17
|
+
export declare class RuntimeContext<TServices extends RuntimeServiceMap = RuntimeServiceMap> {
|
|
18
|
+
readonly scope: RuntimeScope;
|
|
19
|
+
readonly runtimeName: RuntimeName;
|
|
20
|
+
readonly capabilities: RuntimeCapabilities;
|
|
21
|
+
readonly services: Map<keyof TServices, TServices[keyof TServices]>;
|
|
22
|
+
constructor(options?: RuntimeContextOptions<TServices>);
|
|
23
|
+
register<K extends keyof TServices>(key: K, service: TServices[K]): this;
|
|
24
|
+
get<K extends keyof TServices>(key: K): TServices[K] | undefined;
|
|
25
|
+
require<K extends keyof TServices>(key: K): TServices[K];
|
|
26
|
+
has<K extends keyof TServices>(key: K): boolean;
|
|
27
|
+
dispose(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export declare function createRuntimeContext<TServices extends RuntimeServiceMap = RuntimeServiceMap>(options?: RuntimeContextOptions<TServices>): RuntimeContext<TServices>;
|
|
30
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +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,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEhF,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;IAC9B,OAAO,CAAC,EAAE,cAAc,CAAC;CAC5B;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;IAsB1D,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
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { buildConfigFromObject } from './config.js';
|
|
2
|
+
import { getFs } from './fs.js';
|
|
3
|
+
export class RuntimeContext {
|
|
4
|
+
scope;
|
|
5
|
+
runtimeName;
|
|
6
|
+
capabilities;
|
|
7
|
+
services = new Map();
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.scope = options.scope ?? 'process';
|
|
10
|
+
this.runtimeName = options.runtimeName ?? options.factory?.runtimeName ?? 'node-bun';
|
|
11
|
+
this.capabilities =
|
|
12
|
+
options.capabilities ??
|
|
13
|
+
options.factory?.capabilities ??
|
|
14
|
+
{
|
|
15
|
+
hasFilesystem: true,
|
|
16
|
+
hasProcessExecution: true,
|
|
17
|
+
hasPersistentStorage: true,
|
|
18
|
+
};
|
|
19
|
+
this.register('config', (options.services?.config ?? buildConfigFromObject({})));
|
|
20
|
+
this.register('fileSystem', (options.services?.fileSystem ?? getFs()));
|
|
21
|
+
for (const [key, value] of Object.entries(options.services ?? {})) {
|
|
22
|
+
if (value !== undefined) {
|
|
23
|
+
this.register(key, value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
register(key, service) {
|
|
28
|
+
this.services.set(key, service);
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
get(key) {
|
|
32
|
+
return this.services.get(key);
|
|
33
|
+
}
|
|
34
|
+
require(key) {
|
|
35
|
+
const service = this.get(key);
|
|
36
|
+
if (service === undefined) {
|
|
37
|
+
throw new Error(`Runtime service "${String(key)}" is unavailable for runtime ${this.runtimeName}.`);
|
|
38
|
+
}
|
|
39
|
+
return service;
|
|
40
|
+
}
|
|
41
|
+
has(key) {
|
|
42
|
+
return this.services.has(key);
|
|
43
|
+
}
|
|
44
|
+
async dispose() {
|
|
45
|
+
const errors = [];
|
|
46
|
+
for (const service of this.services.values()) {
|
|
47
|
+
if (isDisposable(service)) {
|
|
48
|
+
try {
|
|
49
|
+
await service.dispose();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
errors.push(error instanceof Error ? error : new Error(String(error)));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (errors.length > 0) {
|
|
57
|
+
throw new Error(`Failed to dispose some services:\n${errors.map((e) => `- ${e.message}`).join('\n')}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isDisposable(value) {
|
|
62
|
+
return typeof value === 'object' && value !== null && 'dispose' in value && typeof value.dispose === 'function';
|
|
63
|
+
}
|
|
64
|
+
export function createRuntimeContext(options = {}) {
|
|
65
|
+
return new RuntimeContext(options);
|
|
66
|
+
}
|
package/dist/fs.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export interface FileStat {
|
|
2
|
+
isFile(): boolean;
|
|
3
|
+
isDirectory(): boolean;
|
|
4
|
+
size: number;
|
|
5
|
+
mtimeMs: number;
|
|
6
|
+
}
|
|
7
|
+
export interface LogStream {
|
|
8
|
+
write(chunk: string): void;
|
|
9
|
+
end(): void;
|
|
10
|
+
}
|
|
11
|
+
export interface FileSystem {
|
|
12
|
+
readFile(path: string): Promise<string>;
|
|
13
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
14
|
+
appendFile(path: string, content: string): Promise<void>;
|
|
15
|
+
mkdir(path: string): Promise<void>;
|
|
16
|
+
exists(path: string): Promise<boolean>;
|
|
17
|
+
readDir(path: string): Promise<string[]>;
|
|
18
|
+
unlink(path: string): Promise<void>;
|
|
19
|
+
stat(path: string): Promise<FileStat | null>;
|
|
20
|
+
realpath(path: string): Promise<string>;
|
|
21
|
+
copy(src: string, dest: string): Promise<void>;
|
|
22
|
+
rename(src: string, dest: string): Promise<void>;
|
|
23
|
+
createLogStream(path: string): LogStream;
|
|
24
|
+
}
|
|
25
|
+
export declare class NodeFileSystem implements FileSystem {
|
|
26
|
+
readFile(path: string): Promise<string>;
|
|
27
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
28
|
+
appendFile(path: string, content: string): Promise<void>;
|
|
29
|
+
mkdir(path: string): Promise<void>;
|
|
30
|
+
exists(path: string): Promise<boolean>;
|
|
31
|
+
readDir(path: string): Promise<string[]>;
|
|
32
|
+
unlink(path: string): Promise<void>;
|
|
33
|
+
stat(path: string): Promise<FileStat | null>;
|
|
34
|
+
realpath(path: string): Promise<string>;
|
|
35
|
+
copy(src: string, dest: string): Promise<void>;
|
|
36
|
+
rename(src: string, dest: string): Promise<void>;
|
|
37
|
+
createLogStream(path: string): LogStream;
|
|
38
|
+
}
|
|
39
|
+
export declare class CloudflareFileSystem implements FileSystem {
|
|
40
|
+
readFile(path: string): Promise<string>;
|
|
41
|
+
writeFile(path: string, _content: string): Promise<void>;
|
|
42
|
+
appendFile(path: string, _content: string): Promise<void>;
|
|
43
|
+
mkdir(_path: string): Promise<void>;
|
|
44
|
+
exists(_path: string): Promise<boolean>;
|
|
45
|
+
readDir(path: string): Promise<string[]>;
|
|
46
|
+
unlink(path: string): Promise<void>;
|
|
47
|
+
stat(_path: string): Promise<FileStat | null>;
|
|
48
|
+
realpath(path: string): Promise<string>;
|
|
49
|
+
copy(src: string, _dest: string): Promise<void>;
|
|
50
|
+
rename(src: string, _dest: string): Promise<void>;
|
|
51
|
+
createLogStream(path: string): LogStream;
|
|
52
|
+
}
|
|
53
|
+
export declare function setFileSystem(fileSystem: FileSystem): () => void;
|
|
54
|
+
export declare function getFs(): FileSystem;
|
|
55
|
+
export declare function ensureDirForFile(path: string, fs?: FileSystem): Promise<void>;
|
|
56
|
+
export declare function atomicWriteFile(path: string, content: string, fs?: FileSystem): Promise<void>;
|
|
57
|
+
export declare function atomicWriteJson(path: string, value: unknown, fs?: FileSystem): Promise<void>;
|
|
58
|
+
export declare function readJsonFile<T = unknown>(path: string, fs?: FileSystem): Promise<T>;
|
|
59
|
+
export declare function writeJsonFile(path: string, value: unknown, fs?: FileSystem): Promise<void>;
|
|
60
|
+
export declare function walkDir(path: string, fs?: FileSystem): Promise<string[]>;
|
|
61
|
+
export declare function getProjectRoot(startDir?: string): string;
|
|
62
|
+
export declare function resolveProjectPath(...segments: string[]): string;
|
|
63
|
+
export declare function createLogStream(path: string, fs?: FileSystem): LogStream;
|
|
64
|
+
//# sourceMappingURL=fs.d.ts.map
|
package/dist/fs.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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"}
|