Skip to content

Cross-runtime shim layer for Node.js compat (Phase 1)#491

Open
evantahler wants to merge 2 commits into
mainfrom
node-compat
Open

Cross-runtime shim layer for Node.js compat (Phase 1)#491
evantahler wants to merge 2 commits into
mainfrom
node-compat

Conversation

@evantahler

Copy link
Copy Markdown
Member

What & why

Phase 1 of making the keryx framework run on both Bun and Node.js (tracking issue #488). This PR is pure groundwork — it introduces a cross-runtime shim layer and moves all non-server framework code off Bun-only globals, with no behavior change on Bun.

Changes

  • New packages/keryx/util/runtime.ts — the single abstraction over Bun-only primitives:
    • sleep · glob/globSync (tinyglobby) · sha256Hex (node:crypto) · readFileText/readFileJson/writeFile/fileExists/mimeType (node:fs/promises + mime) · spawnProcess (node:child_process) · isBun · packageRunner
  • Swapped 17 source files off Bun.*:
    • Bun.envprocess.env (a global on both runtimes — no import, no scaffold-rewrite needed)
    • Bun.Glob, Bun.CryptoHasher, Bun.file/Bun.write → shim helpers
    • initializers/db.ts drizzle-kit $ shell → spawnProcess(packageRunner, …)
    • util/webStaticFiles.ts now streams via node:fs + Readable.toWeb, with ETag from fs.stat
  • Added deps tinyglobby + mime; version bump 0.31.0 → 0.31.1.

fileExists deliberately matches Bun.file(p).exists() semantics (regular files only, not directories) so the static-file index.html directory-fallback keeps working — caught by the existing static-file tests.

Out of scope (later phases)

The only Bun.* left in source is the server transport (servers/web.ts, util/webSocket.ts), deferred to the srvx + crossws port (Phase 3). Build step (Phase 4), Node test run + CI matrix (Phase 5/6), and docs (Phase 7) follow.

Verification

  • tsc --noEmit: clean · biome check: clean
  • Full framework suite: 54 files / 777 tests pass on Bun.

Part of #488

🤖 Generated with Claude Code

evantahler and others added 2 commits June 18, 2026 04:02
Phase 1 of dual-runtime (Bun + Node.js) support. Introduces
packages/keryx/util/runtime.ts as the single abstraction over Bun-only
primitives and swaps all non-server framework source onto it:

- sleep, glob/globSync (tinyglobby), sha256Hex (node:crypto),
  readFileText/readFileJson/writeFile/fileExists/mimeType
  (node:fs/promises + mime), spawnProcess (node:child_process)
- Bun.env -> process.env (a global on both runtimes)
- initializers/db.ts drizzle-kit shell ($) -> spawnProcess(packageRunner)
- webStaticFiles.ts streams via node:fs + Readable.toWeb; ETag from fs.stat

fileExists matches Bun.file(p).exists() semantics (regular files only, not
directories) so the static-file index.html fallback keeps working.

No behavior change on Bun: tsc + biome clean, full suite (54 files / 777
tests) passes. The only remaining Bun.* in source is the server transport
(servers/web.ts, util/webSocket.ts), deferred to the srvx + crossws port.

Part of #488

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2 of dual-runtime support. globLoader and API.loadLocalConfig now
match both TypeScript sources (.ts/.tsx — the dev/source layout) and
compiled JavaScript (.js/.mjs/.cjs — the built dist/ layout a plain-Node
consumer runs), skip declaration files (.d.ts), and dedupe by basename so
a stray compiled .js next to its .ts source can't double-instantiate.

In dev only .ts is present, so loading order/behavior is unchanged: full
suite (54 files / 777 tests) still passes on Bun.

Asset + scaffold-source path resolution (packageRoot/scaffoldSourceRoot)
is deferred to the build-step PR, where it can be validated against the
real tsup output layout.

Part of #488

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant