feat: add Node.js compatibility layer to Railway executor

Add multi-strategy import system to support Node.js packages in Deno:

1. Primary: Use npm: specifier for Node.js compatibility mode
2. Fallback: Use esm.sh with explicit esnext target

This allows packages like ai-sdk-tool-code-execution that depend on
Node.js built-ins (node:sqlite, undici) to work in the Deno runtime.

Also added deno.json with nodeModulesDir and BYONM support.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-12-04 15:18:38 +10:00
parent 934c8e9c0b
commit 8562eb5b38
2 changed files with 95 additions and 7 deletions

View file

@ -0,0 +1,12 @@
{
"compilerOptions": {
"allowJs": true,
"lib": ["deno.window"],
"strict": true
},
"nodeModulesDir": true,
"unstable": ["byonm"],
"imports": {
"zod-to-json-schema": "https://esm.sh/zod-to-json-schema@3.25.0"
}
}

View file

@ -38,11 +38,48 @@ async function loadAndDescribe(req: Request): Promise<Response> {
console.log(`✅ Cache hit: ${cacheKey}`);
toolModule = moduleCache.get(cacheKey);
} else {
// Dynamic import from esm.sh (Deno supports this natively!)
const url = importUrl || `https://esm.sh/${packageName}@${version}`;
console.log(`📦 Importing: ${url}`);
// Try multiple import strategies for maximum compatibility
// biome-ignore lint/suspicious/noImplicitAnyLet: Module type is dynamic
let module;
// biome-ignore lint/suspicious/noImplicitAnyLet: Error type is dynamic
let importError;
// Strategy 1: Use Deno's npm: specifier (Node.js compatibility mode)
try {
const npmUrl = `npm:${packageName}@${version}`;
console.log(`📦 Trying npm: specifier (Node.js compat): ${npmUrl}`);
module = await import(npmUrl);
console.log('✅ Successfully imported via npm: specifier');
} catch (error) {
console.log('❌ npm: import failed:', error.message);
importError = error;
// Strategy 2: Fall back to esm.sh with explicit Node target
try {
const url = importUrl || `https://esm.sh/${packageName}@${version}`;
// Force esm.sh to use Node.js target instead of Deno target
const esmUrl = url.includes('?') ? `${url}&target=esnext` : `${url}?target=esnext`;
console.log(`📦 Trying esm.sh with Node target: ${esmUrl}`);
module = await import(esmUrl);
console.log('✅ Successfully imported via esm.sh');
} catch (esmError) {
console.error('❌ Both import strategies failed');
console.error(' npm: error:', error.message);
console.error(' esm.sh error:', esmError.message);
return Response.json(
{
success: false,
error: `Failed to import package: ${esmError.message}`,
details: {
npmError: error.message,
esmError: esmError.message,
},
},
{ status: 500 }
);
}
}
const module = await import(url);
let rawExport = module[exportName];
if (!rawExport) {
@ -292,10 +329,49 @@ async function executeTool(req: Request): Promise<Response> {
console.log(`✅ Using cached tool: ${cacheKey}`);
toolModule = moduleCache.get(cacheKey);
} else {
const url = importUrl || `https://esm.sh/${packageName}@${version}`;
console.log(`📦 Importing for execution: ${url}`);
// Try multiple import strategies for maximum compatibility
// biome-ignore lint/suspicious/noImplicitAnyLet: Module type is dynamic
let module;
// biome-ignore lint/suspicious/noImplicitAnyLet: Error type is dynamic
let importError;
// Strategy 1: Use Deno's npm: specifier (Node.js compatibility mode)
try {
const npmUrl = `npm:${packageName}@${version}`;
console.log(`📦 Trying npm: specifier (Node.js compat): ${npmUrl}`);
module = await import(npmUrl);
console.log('✅ Successfully imported via npm: specifier');
} catch (error) {
console.log('❌ npm: import failed:', error.message);
importError = error;
// Strategy 2: Fall back to esm.sh with explicit Node target
try {
const url = importUrl || `https://esm.sh/${packageName}@${version}`;
// Force esm.sh to use Node.js target instead of Deno target
const esmUrl = url.includes('?') ? `${url}&target=esnext` : `${url}?target=esnext`;
console.log(`📦 Trying esm.sh with Node target: ${esmUrl}`);
module = await import(esmUrl);
console.log('✅ Successfully imported via esm.sh');
} catch (esmError) {
console.error('❌ Both import strategies failed');
console.error(' npm: error:', error.message);
console.error(' esm.sh error:', esmError.message);
return Response.json(
{
success: false,
error: `Failed to import package: ${esmError.message}`,
details: {
npmError: error.message,
esmError: esmError.message,
},
executionTimeMs: Date.now() - startTime,
},
{ status: 500 }
);
}
}
const module = await import(url);
let rawExport = module[exportName];
if (!rawExport) {