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:
parent
934c8e9c0b
commit
8562eb5b38
2 changed files with 95 additions and 7 deletions
12
apps/railway-executor/deno.json
Normal file
12
apps/railway-executor/deno.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,11 +38,48 @@ async function loadAndDescribe(req: Request): Promise<Response> {
|
||||||
console.log(`✅ Cache hit: ${cacheKey}`);
|
console.log(`✅ Cache hit: ${cacheKey}`);
|
||||||
toolModule = moduleCache.get(cacheKey);
|
toolModule = moduleCache.get(cacheKey);
|
||||||
} else {
|
} else {
|
||||||
// Dynamic import from esm.sh (Deno supports this natively!)
|
// Try multiple import strategies for maximum compatibility
|
||||||
const url = importUrl || `https://esm.sh/${packageName}@${version}`;
|
// biome-ignore lint/suspicious/noImplicitAnyLet: Module type is dynamic
|
||||||
console.log(`📦 Importing: ${url}`);
|
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];
|
let rawExport = module[exportName];
|
||||||
|
|
||||||
if (!rawExport) {
|
if (!rawExport) {
|
||||||
|
|
@ -292,10 +329,49 @@ async function executeTool(req: Request): Promise<Response> {
|
||||||
console.log(`✅ Using cached tool: ${cacheKey}`);
|
console.log(`✅ Using cached tool: ${cacheKey}`);
|
||||||
toolModule = moduleCache.get(cacheKey);
|
toolModule = moduleCache.get(cacheKey);
|
||||||
} else {
|
} else {
|
||||||
const url = importUrl || `https://esm.sh/${packageName}@${version}`;
|
// Try multiple import strategies for maximum compatibility
|
||||||
console.log(`📦 Importing for execution: ${url}`);
|
// 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];
|
let rawExport = module[exportName];
|
||||||
|
|
||||||
if (!rawExport) {
|
if (!rawExport) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue