@gobing-ai/ts-runtime 0.4.4 → 0.4.6

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 CHANGED
@@ -17,7 +17,7 @@ and Cloudflare Workers through a factory pattern that auto-detects the runtime.
17
17
  | Runtime factory | `RuntimeFactory` → `loadRuntimeFactory()` | `nodeBunFactory` | `cloudflareWorkersFactory` |
18
18
  | File system | `FileSystem` | `createNodeFileSystem()` (sync `node:fs`) | `createCfFileSystem()` (stub) |
19
19
  | Process execution | `ProcessExecutor` (class) | `run()` via execa, `runStreaming()` via `Bun.spawn` | throws |
20
- | SQL database | `createDbAdapter(config)` → `DbAdapter` | Bun SQLite via `@gobing-ai/ts-db` | throws `D1NotConfiguredError` (D1 round pending) |
20
+ | SQL database | `createDbAdapter(config)` → `DbAdapter` | Bun SQLite via `@gobing-ai/ts-db` (optional peer) | throws `D1NotConfiguredError` (D1 round pending) |
21
21
  | Configuration | `Config` (Zod schema) | YAML + env vars | CONFIG_YAML blob + env vars |
22
22
  | Context | `RuntimeContext` | service locator | service locator |
23
23
  | Path utilities | `SEP`, `basenamePath`, `dirnamePath`, `joinPath`, `resolvePath`, `relativePath`, … | runtime-portable (zero `node:*`) | runtime-portable (zero `node:*`) |
@@ -392,11 +392,25 @@ On Cloudflare Workers, `capabilities.hasSqlDatabase` is `false` and `createDbAda
392
392
  `D1NotConfiguredError` — the method exists on the interface so consumer code is forward-compatible and
393
393
  needs no change when the D1 round ships.
394
394
 
395
- **Dependency note:** `@gobing-ai/ts-db` is not a static dependency of `ts-runtime` (it would create a
396
- cycle, since `ts-db` depends on `ts-runtime`). The factory interface uses a structural
397
- `RuntimeDbAdapter` type (defined locally) that a ts-db `DbAdapter` satisfies via structural subtyping;
398
- `nodeBunFactory.createDbAdapter` loads `ts-db` via a dynamic `import()` at runtime. Consumers that call
399
- `createDbAdapter` must have `@gobing-ai/ts-db` in their dependency graph.
395
+ **Dependency note (optional peer):** `@gobing-ai/ts-db` is declared as an **optional
396
+ `peerDependency`** of `ts-runtime` (ADR-012 addendum), not a regular dependency a regular entry
397
+ would create a manifest cycle (`ts-db` depends on `ts-runtime`) and force-install `ts-db` (plus its
398
+ `drizzle-orm` peer) on every consumer, including Workers bundles and apps that never touch SQL.
399
+ The factory interface uses a structural `RuntimeDbAdapter` type (defined locally) that a ts-db
400
+ `DbAdapter` satisfies via structural subtyping; `nodeBunFactory.createDbAdapter` loads `ts-db` via a
401
+ literal dynamic `import()` at runtime.
402
+
403
+ **Who must install it:** only consumers that call `nodeBunFactory.createDbAdapter` on Node/Bun —
404
+ install `@gobing-ai/ts-db` yourself (`bun add @gobing-ai/ts-db`). Workers consumers do not need it
405
+ (`capabilities.hasSqlDatabase` is `false`; the method throws `D1NotConfiguredError`).
406
+
407
+ **Bundling (`Bun --compile` / esbuild / Vite):** the literal specifier keeps `ts-db` bundler-visible,
408
+ so `Bun --compile` can fold it into a standalone binary. If you bundle for a different runtime, mark
409
+ `@gobing-ai/ts-db` `external` to preserve the dynamic import.
410
+
411
+ **Failure mode:** if `@gobing-ai/ts-db` is absent or exports no `createDbAdapter` (incompatible
412
+ version), `createDbAdapter` throws a typed `DbModuleNotInstalledError` (with the underlying resolution
413
+ error chained as `cause`) instead of a raw `MODULE_NOT_FOUND`.
400
414
 
401
415
  ### 9. Graceful disposal
402
416
 
@@ -11,4 +11,17 @@
11
11
  export declare class D1NotConfiguredError extends Error {
12
12
  constructor(message?: string);
13
13
  }
14
+ /**
15
+ * Thrown by {@link RuntimeFactory.createDbAdapter} on `node-bun` when the
16
+ * optional peer `@gobing-ai/ts-db` is not installed.
17
+ *
18
+ * `ts-db` is an **optional peerDependency** of `ts-runtime` (ADR-012 addendum):
19
+ * it cannot be a regular dependency because `ts-db` depends on `ts-runtime`
20
+ * (manifest cycle). Consumers who call `nodeBunFactory.createDbAdapter` must
21
+ * install `@gobing-ai/ts-db` themselves. This error surfaces a missing module
22
+ * as an actionable, typed failure instead of a raw `MODULE_NOT_FOUND`.
23
+ */
24
+ export declare class DbModuleNotInstalledError extends Error {
25
+ constructor(message?: string, options?: ErrorOptions);
26
+ }
14
27
  //# sourceMappingURL=db-errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"db-errors.d.ts","sourceRoot":"","sources":["../src/db-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;gBAC/B,OAAO,SAAiE;CAIvF"}
1
+ {"version":3,"file":"db-errors.d.ts","sourceRoot":"","sources":["../src/db-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;gBAC/B,OAAO,SAAiE;CAIvF;AAED;;;;;;;;;GASG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;gBAE5C,OAAO,SAAoO,EAC3O,OAAO,CAAC,EAAE,YAAY;CAK7B"}
package/dist/db-errors.js CHANGED
@@ -14,3 +14,19 @@ export class D1NotConfiguredError extends Error {
14
14
  this.name = 'D1NotConfiguredError';
15
15
  }
16
16
  }
17
+ /**
18
+ * Thrown by {@link RuntimeFactory.createDbAdapter} on `node-bun` when the
19
+ * optional peer `@gobing-ai/ts-db` is not installed.
20
+ *
21
+ * `ts-db` is an **optional peerDependency** of `ts-runtime` (ADR-012 addendum):
22
+ * it cannot be a regular dependency because `ts-db` depends on `ts-runtime`
23
+ * (manifest cycle). Consumers who call `nodeBunFactory.createDbAdapter` must
24
+ * install `@gobing-ai/ts-db` themselves. This error surfaces a missing module
25
+ * as an actionable, typed failure instead of a raw `MODULE_NOT_FOUND`.
26
+ */
27
+ export class DbModuleNotInstalledError extends Error {
28
+ constructor(message = '@gobing-ai/ts-db is not installed. It is an optional peer of @gobing-ai/ts-runtime, required only for createDbAdapter on node-bun. Install it (`bun add @gobing-ai/ts-db`) or, when bundling, mark `@gobing-ai/ts-db` external.', options) {
29
+ super(message, options);
30
+ this.name = 'DbModuleNotInstalledError';
31
+ }
32
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './config';
2
2
  export * from './context';
3
3
  export { createRuntimeContextFromFactory } from './context';
4
- export { D1NotConfiguredError } from './db-errors';
4
+ export { D1NotConfiguredError, DbModuleNotInstalledError } from './db-errors';
5
5
  export type { FileStat, FileSystem } from './file-system';
6
6
  export { createCfFileSystem } from './file-system-cf';
7
7
  export { createNodeFileSystem, findProjectRoot } from './file-system-node';
@@ -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,OAAO,EAAE,+BAA+B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EACH,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,aAAa,GAChB,MAAM,MAAM,CAAC;AACd,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACjG,YAAY,EACR,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,aAAa,EACb,UAAU,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC;AAIxB,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAExG;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,cAAc,oBAAoB,EAAE,sBAAsB,CAAC,CAAC;AAE3G;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC,cAAc,oBAAoB,EAAE,qBAAqB,CAAC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,+BAA+B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC9E,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EACH,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,aAAa,GAChB,MAAM,MAAM,CAAC;AACd,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACjG,YAAY,EACR,YAAY,EACZ,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,aAAa,EACb,UAAU,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC1E,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC;AAIxB,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAExG;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,cAAc,oBAAoB,EAAE,sBAAsB,CAAC,CAAC;AAE3G;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC,cAAc,oBAAoB,EAAE,qBAAqB,CAAC,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './config.js';
2
2
  export * from './context.js';
3
3
  export { createRuntimeContextFromFactory } from './context.js';
4
- export { D1NotConfiguredError } from './db-errors.js';
4
+ export { D1NotConfiguredError, DbModuleNotInstalledError } from './db-errors.js';
5
5
  export { createCfFileSystem } from './file-system-cf.js';
6
6
  export { createNodeFileSystem, findProjectRoot } from './file-system-node.js';
7
7
  export { atomicWriteFile, atomicWriteJson, createLogStream, ensureDirForFile, readJsonFile, walkDir, writeJsonFile, } from './fs.js';
package/dist/path.d.ts CHANGED
@@ -8,6 +8,8 @@ export declare function isAbsolutePath(path: string): boolean;
8
8
  export declare function dirnamePath(path: string): string;
9
9
  /** Return the last segment of a path. Optionally strip a trailing extension. */
10
10
  export declare function basenamePath(p: string, ext?: string): string;
11
+ /** Convert a file:// URL into the portable path format used by this package. */
12
+ export declare function fileUrlToPath(url: string): string;
11
13
  /** Compute a platform-independent relative path from `from` to `to`. Both paths should be absolute. */
12
14
  export declare function relativePath(from: string, to: string): string;
13
15
  /** Joins path segments with `/`, normalizing separators and collapsing redundant slashes. */
@@ -1 +1 @@
1
- {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAIA,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,oFAAoF;AACpF,eAAO,MAAM,GAAG,EAAE,MACgF,CAAC;AAEnG,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAmBD,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWhD;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED,uGAAuG;AACvG,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAkB7D;AAED,6FAA6F;AAC7F,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMtD;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAqBzD;AAED,qFAAqF;AACrF,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAIA,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,oFAAoF;AACpF,eAAO,MAAM,GAAG,EAAE,MACgF,CAAC;AAEnG,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAmBD,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWhD;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAkBjD;AAED,uGAAuG;AACvG,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAkB7D;AAED,6FAA6F;AAC7F,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMtD;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAqBzD;AAED,qFAAqF;AACrF,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
package/dist/path.js CHANGED
@@ -54,6 +54,24 @@ export function basenamePath(p, ext) {
54
54
  }
55
55
  return base;
56
56
  }
57
+ /** Convert a file:// URL into the portable path format used by this package. */
58
+ export function fileUrlToPath(url) {
59
+ const parsed = new URL(url);
60
+ if (parsed.protocol !== 'file:') {
61
+ throw new Error(`Expected file URL, got "${parsed.protocol}"`);
62
+ }
63
+ // Encoded separators would decode into extra path segments (e.g. "%2F..%2F"
64
+ // becoming "/../"), silently changing path semantics — reject like node:url does.
65
+ if (/%2f|%5c/i.test(parsed.pathname)) {
66
+ throw new Error('File URL path must not include encoded "/" or "\\" characters');
67
+ }
68
+ const pathname = decodeURIComponent(parsed.pathname);
69
+ const localPath = /^\/[A-Za-z]:\//.test(pathname) ? pathname.slice(1) : pathname;
70
+ if (parsed.hostname.length > 0 && parsed.hostname !== 'localhost') {
71
+ return normalizeSeparators(`//${parsed.hostname}${localPath}`);
72
+ }
73
+ return normalizeSeparators(localPath);
74
+ }
57
75
  /** Compute a platform-independent relative path from `from` to `to`. Both paths should be absolute. */
58
76
  export function relativePath(from, to) {
59
77
  const fromParsed = pathParts(resolvePath(from));
@@ -1 +1 @@
1
- {"version":3,"file":"runtime-node-bun.d.ts","sourceRoot":"","sources":["../src/runtime-node-bun.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAUxD,oEAAoE;AACpE,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,cA8B5B,CAAC"}
1
+ {"version":3,"file":"runtime-node-bun.d.ts","sourceRoot":"","sources":["../src/runtime-node-bun.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAUxD,oEAAoE;AACpE,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,cAwC5B,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { parse as parseYaml } from 'yaml';
2
2
  import { buildConfigFromObject, getProcessEnv } from './config.js';
3
+ import { DbModuleNotInstalledError } from './db-errors.js';
3
4
  import { createNodeFileSystem } from './file-system-node.js';
4
5
  import { ProcessExecutor } from './process-executor.js';
5
6
  // Lazy re-initialisable singleton for test isolation.
@@ -30,14 +31,25 @@ export const nodeBunFactory = {
30
31
  return loadNodeConfig(options);
31
32
  },
32
33
  async createDbAdapter(config) {
33
- // Dynamic import via a variable specifier keeps ts-db out of the
34
- // static dependency graph (it depends on ts-runtime, so a static dep
35
- // would cycle). The variable prevents tsc from type-resolving the
36
- // module at build time (ts-db builds after ts-runtime); at runtime
37
- // Bun resolves it via the workspace + tsconfig paths. Connection only
38
- // the caller owns schema/migrations.
34
+ // Dynamic import via a variable specifier this prevents tsc from
35
+ // statically resolving @gobing-ai/ts-db at build time. ts-db builds
36
+ // AFTER ts-runtime (it depends on ts-runtime), so a literal specifier
37
+ // or `typeof import()` annotation triggers TS2307 in a clean CI
38
+ // checkout where no prebuilt dist/ exists yet. At runtime Bun resolves
39
+ // the module via the workspace symlink.
40
+ // ts-db is an optional peerDependency (ADR-012 addendum) — if the
41
+ // consumer has not installed it, surface a typed error.
39
42
  const moduleSpecifier = '@gobing-ai/ts-db';
40
- const mod = (await import(moduleSpecifier));
43
+ let mod;
44
+ try {
45
+ mod = (await import(moduleSpecifier));
46
+ }
47
+ catch (cause) {
48
+ throw new DbModuleNotInstalledError(undefined, { cause });
49
+ }
50
+ if (typeof mod.createDbAdapter !== 'function') {
51
+ throw new DbModuleNotInstalledError('@gobing-ai/ts-db is installed but does not export createDbAdapter — the installed version may be incompatible or partial.');
52
+ }
41
53
  return mod.createDbAdapter({ driver: 'bun-sqlite', url: config.url });
42
54
  },
43
55
  };
@@ -24,7 +24,8 @@ export interface StructuredConfigLoadOptions {
24
24
  /**
25
25
  * Allow `http(s)://` `$schema` refs. Off by default: remote fetches are an SSRF/DoS surface when
26
26
  * configs are authored by third parties. Prefer bundled package-specifier refs (resolved from
27
- * `node_modules`). Supplying `fetch` explicitly also opts into remote resolution.
27
+ * `node_modules`). Must be combined with an explicit `fetch` pass your own time-bounded
28
+ * or APIClient-backed fetch implementation.
28
29
  */
29
30
  allowRemote?: boolean;
30
31
  fetch?: (input: string) => Promise<Response>;
@@ -1 +1 @@
1
- {"version":3,"file":"schema-validation.d.ts","sourceRoot":"","sources":["../src/schema-validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAUhD,4GAA4G;AAC5G,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxC,oBAAoB,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IAC5C,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACtC;AAED,oGAAoG;AACpG,MAAM,WAAW,2BAA2B;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACtD;;;;;OAKG;IACH,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;CAC7C;AAED,+GAA+G;AAC/G,qBAAa,2BAA4B,SAAQ,KAAK;IAG9C,QAAQ,CAAC,UAAU,EAAE,SAAS,mBAAmB,EAAE;gBADnD,OAAO,EAAE,MAAM,EACN,UAAU,GAAE,SAAS,mBAAmB,EAAO;CAK/D;AAED,6GAA6G;AAC7G,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,2BAAgC,GAAG,OAAO,CAAC,OAAO,CAAC,CAGpH;AAED,qGAAqG;AACrG,wBAAsB,qBAAqB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED,qHAAqH;AACrH,wBAAsB,0BAA0B,CAC5C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED,wHAAwH;AACxH,wBAAgB,kBAAkB,CAC9B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,IAAI,SAAK,EACT,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAM,EACrC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GAC1C,mBAAmB,EAAE,CAwDvB"}
1
+ {"version":3,"file":"schema-validation.d.ts","sourceRoot":"","sources":["../src/schema-validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAOhD,4GAA4G;AAC5G,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,MAAM,WAAW,UAAU;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACxC,oBAAoB,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IAC5C,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACtC;AAED,oGAAoG;AACpG,MAAM,WAAW,2BAA2B;IACxC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACtD;;;;;OAKG;IACH,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;CAC7C;AAED,+GAA+G;AAC/G,qBAAa,2BAA4B,SAAQ,KAAK;IAG9C,QAAQ,CAAC,UAAU,EAAE,SAAS,mBAAmB,EAAE;gBADnD,OAAO,EAAE,MAAM,EACN,UAAU,GAAE,SAAS,mBAAmB,EAAO;CAK/D;AAED,6GAA6G;AAC7G,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,2BAAgC,GAAG,OAAO,CAAC,OAAO,CAAC,CAGpH;AAED,qGAAqG;AACrG,wBAAsB,qBAAqB,CACvC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED,qHAAqH;AACrH,wBAAsB,0BAA0B,CAC5C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,2BAAgC,GAC1C,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAED,wHAAwH;AACxH,wBAAgB,kBAAkB,CAC9B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,IAAI,SAAK,EACT,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAM,EACrC,QAAQ,GAAE,WAAW,CAAC,MAAM,CAAa,GAC1C,mBAAmB,EAAE,CAwDvB"}
@@ -1,8 +1,6 @@
1
1
  import { parse as parseYaml } from 'yaml';
2
2
  import { createNodeFileSystem } from './file-system-node.js';
3
3
  import { dirnamePath, getProcessCwd, isAbsolutePath, joinPath } from './path.js';
4
- /** Default time budget for a single remote schema fetch. */
5
- const REMOTE_SCHEMA_FETCH_TIMEOUT_MS = 5_000;
6
4
  /** Upper bound on a remote schema body. A timeout alone lets a slow multi-GB drip exhaust memory. */
7
5
  const REMOTE_SCHEMA_MAX_BYTES = 5 * 1024 * 1024;
8
6
  /** Error thrown when structured config validation fails, carrying the list of {@link JsonSchemaViolation}s. */
@@ -206,9 +204,9 @@ function splitPackageSpecifier(specifier) {
206
204
  }
207
205
  async function readSchema(schemaLocation, options) {
208
206
  if (isRemoteRef(schemaLocation)) {
209
- const fetchFn = options.fetch ?? (options.allowRemote ? boundedFetch : undefined);
207
+ const fetchFn = options.fetch;
210
208
  if (fetchFn === undefined) {
211
- throw new StructuredConfigSchemaError(`Refusing to fetch remote JSON schema "${schemaLocation}": pass { allowRemote: true } or a fetch implementation to opt in`);
209
+ throw new StructuredConfigSchemaError(`Refusing to fetch remote JSON schema "${schemaLocation}": pass a fetch implementation to opt in`);
212
210
  }
213
211
  const response = await fetchFn(schemaLocation);
214
212
  if (!response.ok) {
@@ -259,10 +257,6 @@ async function readBoundedBody(response, schemaLocation) {
259
257
  return new TextDecoder().decode(merged);
260
258
  }
261
259
  const defaultResolve = typeof Bun !== 'undefined' ? (specifier, from) => Bun.resolveSync(specifier, from) : undefined;
262
- /** Default remote fetch, time-bounded so a slow/hung schema host cannot stall config loading. */
263
- function boundedFetch(input) {
264
- return globalThis.fetch(input, { signal: AbortSignal.timeout(REMOTE_SCHEMA_FETCH_TIMEOUT_MS) });
265
- }
266
260
  function isObject(value) {
267
261
  return typeof value === 'object' && value !== null && !Array.isArray(value);
268
262
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-runtime",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "@gobing-ai/ts-runtime — Runtime abstractions for Bun, Node, and Cloudflare Workers.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -58,15 +58,23 @@
58
58
  "release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-runtime-v<version> && git push --tags' && exit 1"
59
59
  },
60
60
  "dependencies": {
61
- "@gobing-ai/ts-utils": "^0.4.4",
61
+ "@gobing-ai/ts-utils": "^0.4.6",
62
62
  "execa": "^9.5.0",
63
63
  "yaml": "^2.7.0",
64
64
  "zod": "^4.1.0"
65
65
  },
66
66
  "devDependencies": {
67
- "@gobing-ai/ts-db": "^0.4.4",
67
+ "@gobing-ai/ts-db": "^0.4.6",
68
68
  "@types/bun": "1.3.14"
69
69
  },
70
+ "peerDependencies": {
71
+ "@gobing-ai/ts-db": "^0.4.6"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "@gobing-ai/ts-db": {
75
+ "optional": true
76
+ }
77
+ },
70
78
  "publishConfig": {
71
79
  "access": "public"
72
80
  }
package/src/db-errors.ts CHANGED
@@ -14,3 +14,23 @@ export class D1NotConfiguredError extends Error {
14
14
  this.name = 'D1NotConfiguredError';
15
15
  }
16
16
  }
17
+
18
+ /**
19
+ * Thrown by {@link RuntimeFactory.createDbAdapter} on `node-bun` when the
20
+ * optional peer `@gobing-ai/ts-db` is not installed.
21
+ *
22
+ * `ts-db` is an **optional peerDependency** of `ts-runtime` (ADR-012 addendum):
23
+ * it cannot be a regular dependency because `ts-db` depends on `ts-runtime`
24
+ * (manifest cycle). Consumers who call `nodeBunFactory.createDbAdapter` must
25
+ * install `@gobing-ai/ts-db` themselves. This error surfaces a missing module
26
+ * as an actionable, typed failure instead of a raw `MODULE_NOT_FOUND`.
27
+ */
28
+ export class DbModuleNotInstalledError extends Error {
29
+ constructor(
30
+ message = '@gobing-ai/ts-db is not installed. It is an optional peer of @gobing-ai/ts-runtime, required only for createDbAdapter on node-bun. Install it (`bun add @gobing-ai/ts-db`) or, when bundling, mark `@gobing-ai/ts-db` external.',
31
+ options?: ErrorOptions,
32
+ ) {
33
+ super(message, options);
34
+ this.name = 'DbModuleNotInstalledError';
35
+ }
36
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './config';
2
2
  export * from './context';
3
3
  export { createRuntimeContextFromFactory } from './context';
4
- export { D1NotConfiguredError } from './db-errors';
4
+ export { D1NotConfiguredError, DbModuleNotInstalledError } from './db-errors';
5
5
  export type { FileStat, FileSystem } from './file-system';
6
6
  export { createCfFileSystem } from './file-system-cf';
7
7
  export { createNodeFileSystem, findProjectRoot } from './file-system-node';
package/src/path.ts CHANGED
@@ -58,6 +58,27 @@ export function basenamePath(p: string, ext?: string): string {
58
58
  return base;
59
59
  }
60
60
 
61
+ /** Convert a file:// URL into the portable path format used by this package. */
62
+ export function fileUrlToPath(url: string): string {
63
+ const parsed = new URL(url);
64
+ if (parsed.protocol !== 'file:') {
65
+ throw new Error(`Expected file URL, got "${parsed.protocol}"`);
66
+ }
67
+
68
+ // Encoded separators would decode into extra path segments (e.g. "%2F..%2F"
69
+ // becoming "/../"), silently changing path semantics — reject like node:url does.
70
+ if (/%2f|%5c/i.test(parsed.pathname)) {
71
+ throw new Error('File URL path must not include encoded "/" or "\\" characters');
72
+ }
73
+
74
+ const pathname = decodeURIComponent(parsed.pathname);
75
+ const localPath = /^\/[A-Za-z]:\//.test(pathname) ? pathname.slice(1) : pathname;
76
+ if (parsed.hostname.length > 0 && parsed.hostname !== 'localhost') {
77
+ return normalizeSeparators(`//${parsed.hostname}${localPath}`);
78
+ }
79
+ return normalizeSeparators(localPath);
80
+ }
81
+
61
82
  /** Compute a platform-independent relative path from `from` to `to`. Both paths should be absolute. */
62
83
  export function relativePath(from: string, to: string): string {
63
84
  const fromParsed = pathParts(resolvePath(from));
@@ -1,6 +1,7 @@
1
1
  import { parse as parseYaml } from 'yaml';
2
2
  import type { Config } from './config';
3
3
  import { buildConfigFromObject, getProcessEnv } from './config';
4
+ import { DbModuleNotInstalledError } from './db-errors';
4
5
  import type { FileSystem } from './file-system';
5
6
  import { createNodeFileSystem } from './file-system-node';
6
7
  import { ProcessExecutor, type ProcessExecutorConfig } from './process-executor';
@@ -40,16 +41,26 @@ export const nodeBunFactory: RuntimeFactory = {
40
41
  },
41
42
 
42
43
  async createDbAdapter(config: DatabaseConfig): Promise<RuntimeDbAdapter> {
43
- // Dynamic import via a variable specifier keeps ts-db out of the
44
- // static dependency graph (it depends on ts-runtime, so a static dep
45
- // would cycle). The variable prevents tsc from type-resolving the
46
- // module at build time (ts-db builds after ts-runtime); at runtime
47
- // Bun resolves it via the workspace + tsconfig paths. Connection only
48
- // the caller owns schema/migrations.
44
+ // Dynamic import via a variable specifier this prevents tsc from
45
+ // statically resolving @gobing-ai/ts-db at build time. ts-db builds
46
+ // AFTER ts-runtime (it depends on ts-runtime), so a literal specifier
47
+ // or `typeof import()` annotation triggers TS2307 in a clean CI
48
+ // checkout where no prebuilt dist/ exists yet. At runtime Bun resolves
49
+ // the module via the workspace symlink.
50
+ // ts-db is an optional peerDependency (ADR-012 addendum) — if the
51
+ // consumer has not installed it, surface a typed error.
49
52
  const moduleSpecifier = '@gobing-ai/ts-db';
50
- const mod = (await import(moduleSpecifier)) as {
51
- createDbAdapter: (config: { driver: 'bun-sqlite'; url?: string }) => RuntimeDbAdapter;
52
- };
53
+ let mod: { createDbAdapter: (config: { driver: 'bun-sqlite'; url?: string }) => RuntimeDbAdapter };
54
+ try {
55
+ mod = (await import(moduleSpecifier)) as typeof mod;
56
+ } catch (cause) {
57
+ throw new DbModuleNotInstalledError(undefined, { cause });
58
+ }
59
+ if (typeof mod.createDbAdapter !== 'function') {
60
+ throw new DbModuleNotInstalledError(
61
+ '@gobing-ai/ts-db is installed but does not export createDbAdapter — the installed version may be incompatible or partial.',
62
+ );
63
+ }
53
64
  return mod.createDbAdapter({ driver: 'bun-sqlite', url: config.url });
54
65
  },
55
66
  };
@@ -3,9 +3,6 @@ import type { FileSystem } from './file-system';
3
3
  import { createNodeFileSystem } from './file-system-node';
4
4
  import { dirnamePath, getProcessCwd, isAbsolutePath, joinPath } from './path';
5
5
 
6
- /** Default time budget for a single remote schema fetch. */
7
- const REMOTE_SCHEMA_FETCH_TIMEOUT_MS = 5_000;
8
-
9
6
  /** Upper bound on a remote schema body. A timeout alone lets a slow multi-GB drip exhaust memory. */
10
7
  const REMOTE_SCHEMA_MAX_BYTES = 5 * 1024 * 1024;
11
8
 
@@ -36,7 +33,8 @@ export interface StructuredConfigLoadOptions {
36
33
  /**
37
34
  * Allow `http(s)://` `$schema` refs. Off by default: remote fetches are an SSRF/DoS surface when
38
35
  * configs are authored by third parties. Prefer bundled package-specifier refs (resolved from
39
- * `node_modules`). Supplying `fetch` explicitly also opts into remote resolution.
36
+ * `node_modules`). Must be combined with an explicit `fetch` pass your own time-bounded
37
+ * or APIClient-backed fetch implementation.
40
38
  */
41
39
  allowRemote?: boolean;
42
40
  fetch?: (input: string) => Promise<Response>;
@@ -346,10 +344,10 @@ function splitPackageSpecifier(specifier: string): { pkg: string; subpath: strin
346
344
 
347
345
  async function readSchema(schemaLocation: string, options: StructuredConfigLoadOptions): Promise<string> {
348
346
  if (isRemoteRef(schemaLocation)) {
349
- const fetchFn = options.fetch ?? (options.allowRemote ? boundedFetch : undefined);
347
+ const fetchFn = options.fetch;
350
348
  if (fetchFn === undefined) {
351
349
  throw new StructuredConfigSchemaError(
352
- `Refusing to fetch remote JSON schema "${schemaLocation}": pass { allowRemote: true } or a fetch implementation to opt in`,
350
+ `Refusing to fetch remote JSON schema "${schemaLocation}": pass a fetch implementation to opt in`,
353
351
  );
354
352
  }
355
353
  const response = await fetchFn(schemaLocation);
@@ -411,11 +409,6 @@ async function readBoundedBody(response: Response, schemaLocation: string): Prom
411
409
  const defaultResolve: ((specifier: string, from: string) => string) | undefined =
412
410
  typeof Bun !== 'undefined' ? (specifier, from) => Bun.resolveSync(specifier, from) : undefined;
413
411
 
414
- /** Default remote fetch, time-bounded so a slow/hung schema host cannot stall config loading. */
415
- function boundedFetch(input: string): Promise<Response> {
416
- return globalThis.fetch(input, { signal: AbortSignal.timeout(REMOTE_SCHEMA_FETCH_TIMEOUT_MS) });
417
- }
418
-
419
412
  function isObject(value: unknown): value is Record<string, unknown> {
420
413
  return typeof value === 'object' && value !== null && !Array.isArray(value);
421
414
  }