/** * Layer 5: Compute — Deterministic transforms that run locally when possible, * delegate to server when not. * * | Transform | Local? | Example | * |---------------------|--------|--------------------------------| * | Markdown → HTML | Yes | Notes rendering | * | Search indexing | Yes | Build index from docs | * | Vote tallies | Yes | Derived from CRDT state | * | PDF generation | No | Server delegate (Typst) | * | Image thumbnailing | No | Server delegate (Sharp) | * * Server fallback: POST /:space/api/compute/:transformId */ // ============================================================================ // TYPES // ============================================================================ /** * A transform: a named, deterministic function that converts input → output. */ export interface Transform { /** Unique identifier (e.g. "markdown-to-html", "pdf-generate") */ id: string; /** Whether this transform can run in the browser */ canRunLocally: boolean; /** Execute the transform */ execute(input: In): Promise; } export interface ComputeEngineOptions { /** Base URL for server-side compute endpoint (e.g. "/demo/api/compute") */ serverBaseUrl?: string; /** Auth token for server requests */ authToken?: string; } // ============================================================================ // ComputeEngine // ============================================================================ export class ComputeEngine { #transforms = new Map>(); #serverBaseUrl: string | null; #authToken: string | null; constructor(opts: ComputeEngineOptions = {}) { this.#serverBaseUrl = opts.serverBaseUrl ?? null; this.#authToken = opts.authToken ?? null; } /** * Register a transform. */ register(transform: Transform): void { this.#transforms.set(transform.id, transform); } /** * Run a transform. Runs locally if possible, delegates to server otherwise. */ async run(transformId: string, input: In): Promise { const transform = this.#transforms.get(transformId); if (transform?.canRunLocally) { return transform.execute(input) as Promise; } if (transform && !transform.canRunLocally && this.#serverBaseUrl) { return this.#delegateToServer(transformId, input); } if (!transform && this.#serverBaseUrl) { // Transform not registered locally — try server return this.#delegateToServer(transformId, input); } throw new Error( `Transform "${transformId}" not available: ${ transform ? 'requires server but no serverBaseUrl configured' : 'not registered' }` ); } /** * Check if a transform is registered and can run locally. */ canRunLocally(transformId: string): boolean { const t = this.#transforms.get(transformId); return !!t && t.canRunLocally; } /** * Check if a transform is registered. */ has(transformId: string): boolean { return this.#transforms.has(transformId); } /** * List all registered transform IDs. */ list(): string[] { return Array.from(this.#transforms.keys()); } /** * Update auth token (e.g. after login/refresh). */ setAuthToken(token: string): void { this.#authToken = token; } /** * Update server base URL. */ setServerBaseUrl(url: string): void { this.#serverBaseUrl = url; } // ---------- Private ---------- async #delegateToServer(transformId: string, input: In): Promise { if (!this.#serverBaseUrl) { throw new Error('No server base URL configured for compute delegation'); } const url = `${this.#serverBaseUrl}/${transformId}`; const headers: Record = { 'Content-Type': 'application/json', }; if (this.#authToken) { headers['Authorization'] = `Bearer ${this.#authToken}`; } const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify({ input }), }); if (!response.ok) { throw new Error(`Compute server error: ${response.status} ${response.statusText}`); } const result = await response.json(); return result.output as Out; } } // ============================================================================ // BUILT-IN TRANSFORMS // ============================================================================ /** * Helper to create a local transform. */ export function localTransform( id: string, fn: (input: In) => Promise | Out ): Transform { return { id, canRunLocally: true, execute: async (input) => fn(input), }; } /** * Helper to declare a server-only transform (acts as a type contract). */ export function serverTransform(id: string): Transform { return { id, canRunLocally: false, execute: async () => { throw new Error(`Transform "${id}" must run on server`); }, }; }