refactor(executor): switch vercel template from Next.js to Deno runtime
- Replace Next.js app router with pure Deno API functions - Use vercel-deno@3.0.0 community runtime for native HTTP imports - Deno natively supports importing from esm.sh URLs - Simplified template: just api/health.ts and api/execute-tool.ts - Remove unnecessary React/Next.js dependencies - Update README with new architecture documentation This matches the Railway executor's Deno-based approach but runs on the user's own Vercel account. The Deno runtime enables dynamic imports from esm.sh without any special setup.
This commit is contained in:
parent
346a3f6da0
commit
300083aff9
11 changed files with 357 additions and 357 deletions
|
|
@ -1,112 +0,0 @@
|
|||
/**
|
||||
* Execute Tool Endpoint
|
||||
*
|
||||
* POST /api/execute-tool
|
||||
* Executes a TPMJS tool with the provided parameters
|
||||
*/
|
||||
|
||||
import { type ExecuteToolRequest, executeTool } from '@/lib/executor';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
export const maxDuration = 60; // 60 seconds max
|
||||
|
||||
/**
|
||||
* Verify API key if configured
|
||||
*/
|
||||
function verifyApiKey(request: NextRequest): boolean {
|
||||
const apiKey = process.env.EXECUTOR_API_KEY;
|
||||
|
||||
// If no API key is configured, allow all requests
|
||||
if (!apiKey) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const authHeader = request.headers.get('Authorization');
|
||||
if (!authHeader) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Expect "Bearer <api-key>" format
|
||||
const [type, token] = authHeader.split(' ');
|
||||
if (type !== 'Bearer' || token !== apiKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the request body
|
||||
*/
|
||||
function validateRequest(body: unknown): body is ExecuteToolRequest {
|
||||
if (!body || typeof body !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const req = body as Record<string, unknown>;
|
||||
|
||||
return (
|
||||
typeof req.packageName === 'string' &&
|
||||
req.packageName.length > 0 &&
|
||||
typeof req.name === 'string' &&
|
||||
req.name.length > 0 &&
|
||||
typeof req.params === 'object' &&
|
||||
req.params !== null
|
||||
);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||
// Verify API key if configured
|
||||
if (!verifyApiKey(request)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Unauthorized', executionTimeMs: 0 },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
// Validate request
|
||||
if (!validateRequest(body)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Invalid request. Required: packageName, name, params',
|
||||
executionTimeMs: 0,
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Execute the tool
|
||||
const result = await executeTool(body);
|
||||
|
||||
return NextResponse.json(result, {
|
||||
status: result.success ? 200 : 500,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
executionTimeMs: 0,
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle OPTIONS for CORS preflight
|
||||
export async function OPTIONS(): Promise<NextResponse> {
|
||||
return new NextResponse(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/**
|
||||
* Health Check Endpoint
|
||||
*
|
||||
* GET /api/health
|
||||
* Returns the health status of the executor
|
||||
*/
|
||||
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export const runtime = 'nodejs';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
interface HealthResponse {
|
||||
status: 'ok' | 'degraded' | 'error';
|
||||
version?: string;
|
||||
info?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export async function GET(): Promise<NextResponse<HealthResponse>> {
|
||||
return NextResponse.json({
|
||||
status: 'ok',
|
||||
version: '1.0.0',
|
||||
info: {
|
||||
runtime: 'vercel-serverless',
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Handle OPTIONS for CORS preflight
|
||||
export async function OPTIONS(): Promise<NextResponse> {
|
||||
return new NextResponse(null, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
export default function Home() {
|
||||
return (
|
||||
<div style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif' }}>
|
||||
<h1>TPMJS Executor</h1>
|
||||
<p>This is a TPMJS tool executor service.</p>
|
||||
<h2>Endpoints</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<code>GET /api/health</code> - Health check
|
||||
</li>
|
||||
<li>
|
||||
<code>POST /api/execute-tool</code> - Execute a tool
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<a href="https://tpmjs.com/docs/executors">Documentation</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue