import type { ComputeAppConfig, ComputeConfig } from "./types.ts"; type SerializableComputeConfig = { region?: ComputeConfig["region"]; app?: ComputeAppConfig; apps?: Record; }; /** * Renders a `prisma.compute.ts` source file for a config — the scaffolding * counterpart of `loadComputeConfig`. Output round-trips through * `normalizeComputeConfig` and is stable for committing to a repository. */ export function serializeComputeConfig( config: ComputeConfig, options?: { /** Import specifier for the typed helper. Defaults to this SDK. */ importFrom?: string; }, ): string { const importFrom = options?.importFrom ?? "@prisma/compute-sdk/config"; const body = renderValue(serializableComputeConfig(config), 0); return [ `import { defineComputeConfig } from ${JSON.stringify(importFrom)};`, "", `export default defineComputeConfig(${body});`, "", ].join("\n"); } /** * Renders a `prisma.compute.json` source file for a config — the same * serialization as `serializeComputeConfig` in the dependency-free static * format. Output round-trips through `normalizeComputeConfig` and is stable * for committing to a repository. */ export function serializeComputeConfigJson( config: ComputeConfig, options?: { /** * `$schema` URL emitted for editor validation. Omitted by default: * generated configs must not point editors at a URL that does not * resolve, so pass `COMPUTE_CONFIG_JSON_SCHEMA_URL` explicitly once the * schema is actually published there. */ schemaUrl?: string | null; }, ): string { const schemaUrl = options?.schemaUrl ?? null; const serializable = { ...(schemaUrl === null ? {} : { $schema: schemaUrl }), ...serializableComputeConfig(config), }; return `${JSON.stringify(serializable, null, 2)}\n`; } function serializableComputeConfig( config: ComputeConfig, ): SerializableComputeConfig { const serializable: SerializableComputeConfig = {}; if (config.region !== undefined) { serializable.region = config.region; } if ("app" in config && config.app !== undefined) { serializable.app = serializableComputeAppConfig(config.app); } else { serializable.apps = Object.fromEntries( Object.entries( (config as { apps: Record }).apps, ).map(([key, app]) => [key, serializableComputeAppConfig(app)]), ); } return serializable; } function serializableComputeAppConfig(app: ComputeAppConfig): ComputeAppConfig { return { name: app.name, region: app.region, root: app.root, framework: app.framework, entry: app.entry, httpPort: app.httpPort, env: app.env, build: app.build, }; } const IDENTIFIER_KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/; function renderValue(value: unknown, depth: number): string { if (value === null) { return "null"; } switch (typeof value) { case "string": return JSON.stringify(value); case "number": case "boolean": return String(value); case "object": break; default: throw new Error( `Cannot serialize a ${typeof value} value in a compute config.`, ); } if (Array.isArray(value)) { return `[${value.map((entry) => renderValue(entry, depth)).join(", ")}]`; } const entries = Object.entries(value as Record).filter( ([, entry]) => entry !== undefined, ); if (entries.length === 0) { return "{}"; } const indent = " ".repeat(depth + 1); const closingIndent = " ".repeat(depth); const rendered = entries.map(([key, entry]) => { // "__proto__" needs a computed key: in an object literal both the bare // and the quoted form set the prototype instead of defining a property. const renderedKey = key === "__proto__" ? '["__proto__"]' : IDENTIFIER_KEY.test(key) ? key : JSON.stringify(key); return `${indent}${renderedKey}: ${renderValue(entry, depth + 1)},`; }); return `{\n${rendered.join("\n")}\n${closingIndent}}`; }