rspace-online/server/local-first/compute-server.ts

91 lines
2.8 KiB
TypeScript

/**
* Layer 5: Compute (Server) — Server-side transform runner.
*
* Handles POST /:space/api/compute/:transformId requests from clients
* that can't run a transform locally (e.g. PDF generation, image thumbnailing).
*
* Usage in server/index.ts:
* import { createComputeRouter, registerServerTransform } from './local-first/compute-server';
* const computeRouter = createComputeRouter();
* app.route('/:space/api/compute', computeRouter);
*/
import { Hono } from 'hono';
// ============================================================================
// TYPES
// ============================================================================
export interface ServerTransform<In = unknown, Out = unknown> {
id: string;
/** Execute the transform on the server */
execute(input: In, context: TransformContext): Promise<Out>;
}
export interface TransformContext {
space: string;
/** Auth claims from EncryptID JWT (null if unauthenticated) */
claims: Record<string, unknown> | null;
}
// ============================================================================
// REGISTRY
// ============================================================================
const transforms = new Map<string, ServerTransform<any, any>>();
export function registerServerTransform<In, Out>(transform: ServerTransform<In, Out>): void {
transforms.set(transform.id, transform);
}
export function getServerTransform(id: string): ServerTransform | undefined {
return transforms.get(id);
}
// ============================================================================
// HONO ROUTER
// ============================================================================
/**
* Create a Hono router that handles compute requests.
* Mount at /:space/api/compute
*/
export function createComputeRouter(): Hono {
const router = new Hono();
// List available transforms
router.get('/', (c) => {
const list = Array.from(transforms.keys());
return c.json({ transforms: list });
});
// Execute a transform
router.post('/:transformId', async (c) => {
const transformId = c.req.param('transformId');
const transform = transforms.get(transformId);
if (!transform) {
return c.json({ error: `Transform "${transformId}" not found` }, 404);
}
try {
const body = await c.req.json();
const input = body.input;
const context: TransformContext = {
space: c.req.param('space') || 'demo',
claims: (c as any).get?.('claims') ?? null,
};
const output = await transform.execute(input, context);
return c.json({ output });
} catch (e) {
const message = e instanceof Error ? e.message : 'Transform execution failed';
console.error(`[ComputeServer] Transform "${transformId}" failed:`, e);
return c.json({ error: message }, 500);
}
});
return router;
}