@gobing-ai/ts-runtime 0.4.5 → 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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-runtime",
3
- "version": "0.4.5",
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.5",
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.5",
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
  };