@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 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
+ ```
@@ -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"}
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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"}