@broadpaper/server 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5GQOM7MG.js → chunk-UGT7XIEH.js} +3 -3
- package/dist/chunk-UGT7XIEH.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunk-5GQOM7MG.js.map +0 -1
|
@@ -316,9 +316,9 @@ function createRenderHandler(options = {}) {
|
|
|
316
316
|
backends: service.backends,
|
|
317
317
|
defaultBackend: service.defaultBackend,
|
|
318
318
|
...service.load,
|
|
319
|
-
version: options.version ?? "0.
|
|
319
|
+
version: options.version ?? "0.2.0",
|
|
320
320
|
protocol: PROTOCOL_VERSION,
|
|
321
|
-
license: { state: license.state,
|
|
321
|
+
license: { state: license.state, plan: license.plan, validUntil: license.validUntil?.toISOString() ?? null }
|
|
322
322
|
};
|
|
323
323
|
sendJson(res, 200, body);
|
|
324
324
|
return true;
|
|
@@ -395,4 +395,4 @@ export {
|
|
|
395
395
|
createRenderHandler,
|
|
396
396
|
createRenderServer
|
|
397
397
|
};
|
|
398
|
-
//# sourceMappingURL=chunk-
|
|
398
|
+
//# sourceMappingURL=chunk-UGT7XIEH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/service.ts","../src/protocol.ts","../src/http.ts"],"sourcesContent":["/**\n * The render service, with the transport left out.\n *\n * `@broadpaper/pdf` could already serve HTTP, but only over Chromium — which\n * means a 700 MB browser on every machine that renders, and nothing at all on\n * a runtime that cannot start one. This service picks a backend instead: the\n * browserless engine by default, Chromium when a caller wants the browser's own\n * text shaping. Both are handed pages BroadPaper's paginator has already\n * decided, so the choice changes fidelity, not pagination.\n *\n * Nothing here knows about HTTP. `http.ts` is a thin adapter over it, which is\n * what lets a host mount the same service inside Express, Fastify, Hono or a\n * serverless handler rather than running a second process.\n */\nimport { createRegistry } from \"@broadpaper/blocks\";\nimport { migrateTemplate, parseTemplate } from \"@broadpaper/core\";\nimport type { BlockDefinition, BlockRegistry, ReportTemplate } from \"@broadpaper/core\";\nimport { renderPdfPaginated, type FormeFont } from \"@broadpaper/forme\";\nimport type { BatchRequest, BatchResponse, RenderBackend, RenderErrorCode, RenderFont, RenderRequest } from \"./protocol.js\";\n\nexport class RenderError extends Error {\n constructor(\n message: string,\n readonly code: RenderErrorCode,\n readonly status: number\n ) {\n super(message);\n this.name = \"RenderError\";\n }\n}\n\n/**\n * What a Chromium backend has to provide.\n *\n * Injected rather than imported, so this package never depends on Playwright\n * and a service that only wants the browserless engine does not drag a browser\n * into its image.\n */\nexport interface ChromiumBackend {\n render(opts: {\n template: unknown;\n data?: unknown;\n theme?: unknown;\n dataSources?: unknown;\n now?: Date;\n locale?: string;\n currency?: string;\n timeZone?: string;\n metadata?: unknown;\n network?: { allowedHosts?: string[]; blockPrivateNetworks?: boolean };\n customBlocksScript?: string;\n timeoutMs?: number;\n maxPages?: number;\n }): Promise<{ pdf: Uint8Array; pages: number; warnings: Array<string | { message: string }>; durationMs: number }>;\n close?(): Promise<void>;\n}\n\nexport interface RenderServiceOptions {\n /** Which backend to use when a request does not name one. Default \"forme\". */\n defaultBackend?: RenderBackend;\n /**\n * Backends a request may ask for. Defaults to whatever is available. Narrow\n * it when you do not want callers choosing the expensive one.\n */\n allowedBackends?: RenderBackend[];\n /** Custom blocks for the browserless backend: the host's own code, in process. */\n blocks?: Array<BlockDefinition<never>>;\n /** Custom blocks bundle for Chromium (an IIFE calling `BroadPaper.registerBlocks`). */\n customBlocksScript?: string;\n /** Fonts embedded in every render. A request may add to these, never replace them. */\n fonts?: RenderFont[];\n /** Chromium backend. Without one, only the browserless backend is available. */\n chromium?: ChromiumBackend;\n /** Concurrent renders. Default 4. */\n concurrency?: number;\n /** How long a request may wait for a slot before being refused. Default 30s. */\n queueTimeoutMs?: number;\n /** Per-render timeout. Default 60s. */\n timeoutMs?: number;\n /** Refuse documents longer than this. Default 500 pages. */\n maxPages?: number;\n /** Hosts remote assets may be fetched from. A request may narrow this, never widen it. */\n allowedHosts?: string[];\n /** Injected Forme entry point, so a host can pick the node, browser or worker build. */\n renderer?: (doc: Record<string, unknown>) => Promise<{ pdf: Uint8Array; layout?: unknown; warnings?: string[] }>;\n log?: (message: string, detail?: Record<string, unknown>) => void;\n}\n\nexport interface RenderOutcome {\n pdf: Uint8Array;\n pages: number;\n warnings: string[];\n durationMs: number;\n backend: RenderBackend;\n}\n\nconst DEFAULTS = { concurrency: 4, queueTimeoutMs: 30_000, timeoutMs: 60_000, maxPages: 500 };\n\nexport class RenderService {\n private readonly registry: BlockRegistry;\n private readonly opts: RenderServiceOptions & Required<Pick<RenderServiceOptions, \"concurrency\" | \"queueTimeoutMs\" | \"timeoutMs\" | \"maxPages\">>;\n private active = 0;\n private readonly waiting: Array<{ resolve: () => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }> = [];\n\n constructor(options: RenderServiceOptions = {}) {\n this.opts = { ...DEFAULTS, ...options };\n this.registry = createRegistry((options.blocks ?? []) as never);\n }\n\n /** Backends this service can actually use, in preference order. */\n get backends(): RenderBackend[] {\n const all: RenderBackend[] = this.opts.chromium ? [\"forme\", \"chromium\"] : [\"forme\"];\n const allowed = this.opts.allowedBackends;\n return allowed ? all.filter((b) => allowed.includes(b)) : all;\n }\n\n get defaultBackend(): RenderBackend {\n const wanted = this.opts.defaultBackend ?? \"forme\";\n return this.backends.includes(wanted) ? wanted : this.backends[0] ?? \"forme\";\n }\n\n get load(): { active: number; queued: number } {\n return { active: this.active, queued: this.waiting.length };\n }\n\n /**\n * Renders one document.\n *\n * Throws {@link RenderError} for anything a caller can act on — a backend\n * they may not use, a queue that never cleared, a document longer than the\n * service permits — carrying the code and status the wire protocol names.\n */\n async render(request: RenderRequest): Promise<RenderOutcome> {\n if (!request || typeof request !== \"object\" || !request.template) throw new RenderError(\"Missing template\", \"BAD_REQUEST\", 400);\n const template = this.parse(request.template);\n const backend = this.pickBackend(request.backend);\n await this.acquire();\n const started = Date.now();\n try {\n const checked = { ...request, template };\n const out = await this.withTimeout(backend === \"chromium\" ? this.renderChromium(checked) : this.renderForme(checked), backend);\n this.opts.log?.(\"render\", { backend, pages: out.pages, ms: out.durationMs, warnings: out.warnings.length });\n return out;\n } catch (e) {\n const err = e as RenderError;\n this.opts.log?.(\"render failed\", { backend, code: err.code ?? \"RENDER_FAILED\", message: err.message, ms: Date.now() - started });\n throw err;\n } finally {\n this.release();\n }\n }\n\n /**\n * Renders one template against many data sets — a monthly run, a mail merge.\n *\n * One failure does not fail the batch: each result carries its own outcome,\n * because a caller sending five hundred clients wants the four hundred and\n * ninety-nine that worked and a list of the one that did not.\n */\n async renderBatch(request: BatchRequest): Promise<BatchResponse> {\n if (!Array.isArray(request?.items) || request.items.length === 0) throw new RenderError(\"A batch needs at least one item\", \"BAD_REQUEST\", 400);\n const started = Date.now();\n const backend = this.pickBackend(request.backend);\n const { items, ...shared } = request;\n const results: BatchResponse[\"results\"] = await Promise.all(\n items.map(async (item) => {\n try {\n const out = await this.render({ ...shared, ...item, backend } as RenderRequest);\n return { id: item.id, ok: true as const, pdfBase64: Buffer.from(out.pdf).toString(\"base64\"), pages: out.pages, warnings: out.warnings, durationMs: out.durationMs };\n } catch (e) {\n const err = e as RenderError;\n return { id: item.id, ok: false as const, error: err.message, code: err.code ?? \"RENDER_FAILED\" };\n }\n })\n );\n return { results, backend, durationMs: Date.now() - started };\n }\n\n async close(): Promise<void> {\n for (const w of this.waiting.splice(0)) {\n clearTimeout(w.timer);\n w.reject(new RenderError(\"The service is shutting down.\", \"BUSY\", 503));\n }\n await this.opts.chromium?.close?.();\n }\n\n /* ------------------------------ internals ------------------------------ */\n\n /**\n * Migrates and validates the incoming template.\n *\n * A service takes JSON from over the wire — hand-written, generated, saved by\n * a version of the designer nobody here has seen. Handing that straight to\n * the renderer turns a missing `styles` block into \"Cannot read properties of\n * undefined (reading 'height')\", which tells the caller nothing at all.\n * Parsing first fills in every default the schema declares and names the\n * field when it cannot.\n */\n private parse(input: RenderRequest[\"template\"]): ReportTemplate {\n const migrated = migrateTemplate(input as never);\n const parsed = parseTemplate(migrated.template);\n if (!parsed.ok || !parsed.template) {\n const detail = parsed.errors\n .slice(0, 5)\n .map((e) => `${e.path || \"(root)\"}: ${e.message}`)\n .join(\"; \");\n throw new RenderError(`The template is not valid. ${detail}${parsed.errors.length > 5 ? ` (+${parsed.errors.length - 5} more)` : \"\"}`, \"BAD_REQUEST\", 400);\n }\n return parsed.template;\n }\n\n private pickBackend(requested: RenderBackend | undefined): RenderBackend {\n if (!requested) return this.defaultBackend;\n if (!this.backends.includes(requested)) {\n throw new RenderError(`Backend \"${requested}\" is not available here. Available: ${this.backends.join(\", \")}.`, \"BACKEND_UNAVAILABLE\", 400);\n }\n return requested;\n }\n\n /**\n * Narrows the network policy. A request may restrict what a render fetches;\n * it may not reach anywhere the service was not already willing to go.\n */\n private allowedHosts(requested: string[] | undefined): string[] {\n const server = this.opts.allowedHosts ?? [];\n if (!requested) return server;\n return server.includes(\"*\") ? requested : requested.filter((h) => server.includes(h));\n }\n\n private fonts(request: RenderRequest): FormeFont[] {\n return [...(this.opts.fonts ?? []), ...(request.fonts ?? [])].map((f) => ({\n family: f.family,\n // Base64 for a client that cannot share a filesystem with the service.\n src: f.bytes ? new Uint8Array(Buffer.from(f.bytes, \"base64\")) : f.src ?? \"\",\n weight: f.weight ?? 400,\n style: f.style ?? \"normal\"\n }));\n }\n\n private async renderForme(request: RenderRequest): Promise<RenderOutcome> {\n const fonts = this.fonts(request);\n const result = await renderPdfPaginated({\n template: request.template,\n registry: this.registry,\n data: request.data,\n theme: request.theme,\n dataSources: request.dataSources,\n now: request.now ? new Date(request.now) : undefined,\n locale: request.locale,\n currency: request.currency,\n timeZone: request.timeZone,\n metadata: request.metadata,\n ...(this.opts.renderer ? { renderer: this.opts.renderer } : {}),\n ...(fonts.length ? { fonts } : {}),\n ...(request.tagged ? { tagged: true } : {}),\n ...(request.pdfUa ? { pdfUa: true } : {}),\n ...(request.pdfA ? { pdfA: request.pdfA } : {})\n });\n if (result.paged.totalPages > this.opts.maxPages) {\n throw new RenderError(`Document is ${result.paged.totalPages} pages; this service allows ${this.opts.maxPages}.`, \"TOO_MANY_PAGES\", 413);\n }\n return { pdf: result.pdf, pages: result.paged.totalPages, warnings: result.warnings, durationMs: result.durationMs, backend: \"forme\" };\n }\n\n private async renderChromium(request: RenderRequest): Promise<RenderOutcome> {\n const result = await this.opts.chromium!.render({\n template: request.template,\n data: request.data,\n theme: request.theme,\n dataSources: request.dataSources,\n now: request.now ? new Date(request.now) : undefined,\n locale: request.locale,\n currency: request.currency,\n timeZone: request.timeZone,\n metadata: request.metadata,\n network: { allowedHosts: this.allowedHosts(request.network?.allowedHosts) },\n customBlocksScript: this.opts.customBlocksScript,\n timeoutMs: this.opts.timeoutMs,\n maxPages: this.opts.maxPages\n });\n return {\n pdf: result.pdf,\n pages: result.pages,\n warnings: result.warnings.map((w) => (typeof w === \"string\" ? w : w.message)),\n durationMs: result.durationMs,\n backend: \"chromium\"\n };\n }\n\n private async withTimeout(work: Promise<RenderOutcome>, backend: RenderBackend): Promise<RenderOutcome> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n work,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new RenderError(`Render timed out after ${this.opts.timeoutMs} ms on the ${backend} backend.`, \"TIMEOUT\", 504)), this.opts.timeoutMs);\n })\n ]);\n } catch (e) {\n if (e instanceof RenderError) throw e;\n throw new RenderError((e as Error).message || \"Render failed\", \"RENDER_FAILED\", 500);\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n\n /**\n * Waits for a slot, but not forever. An unbounded queue turns one busy minute\n * into a pile of work whose callers have all given up, and then does it\n * anyway.\n */\n private acquire(): Promise<void> {\n if (this.active < this.opts.concurrency) {\n this.active++;\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n const entry = {\n resolve: () => {\n this.active++;\n resolve();\n },\n reject,\n timer: setTimeout(() => {\n const i = this.waiting.indexOf(entry);\n if (i >= 0) this.waiting.splice(i, 1);\n reject(new RenderError(`Waited ${this.opts.queueTimeoutMs} ms for a render slot.`, \"BUSY\", 503));\n }, this.opts.queueTimeoutMs)\n };\n this.waiting.push(entry);\n });\n }\n\n private release(): void {\n this.active--;\n const next = this.waiting.shift();\n if (!next) return;\n clearTimeout(next.timer);\n next.resolve();\n }\n}\n","/**\n * The wire protocol of the render service.\n *\n * This file is the contract between the service, the browser client and the\n * .NET client under `dotnet/`. Every field here is JSON — a template, a theme\n * and a data set are all host-defined documents — so the same shapes are\n * expressible in any language, and the .NET types mirror these names exactly.\n *\n * Anything added here must be optional, because a client is always older than\n * the service it is talking to.\n */\nimport type { DataSource, ReportData, ReportTemplate, Theme, AnyVersionTemplate } from \"@broadpaper/core\";\nimport type { LicenseState, Plan } from \"@broadpaper/license\";\n\n/** Which engine draws the pages. BroadPaper decides the pages either way. */\nexport type RenderBackend = \"forme\" | \"chromium\";\n\nexport interface RenderMetadata {\n title?: string;\n author?: string;\n subject?: string;\n keywords?: string[];\n /** Document language, e.g. \"en-GB\". Required for PDF/UA. */\n lang?: string;\n}\n\n/** A font to embed. Without one the engine falls back to its standard faces. */\nexport interface RenderFont {\n family: string;\n /** A path the service can read, a `data:` URI, or base64 in `bytes`. */\n src?: string;\n /** Base64-encoded TTF/OTF. Use this from a client that cannot share a filesystem. */\n bytes?: string;\n weight?: number;\n style?: \"normal\" | \"italic\";\n}\n\nexport interface RenderRequest {\n /** The saved template. Older schema versions are migrated on the way in. */\n template: ReportTemplate | AnyVersionTemplate;\n /** Runtime data, keyed by data source id. */\n data?: ReportData;\n theme?: Partial<Theme>;\n dataSources?: DataSource[];\n /** ISO-8601. Fixes the clock, which is one of the conditions for byte-identical output. */\n now?: string;\n locale?: string;\n currency?: string;\n timeZone?: string;\n metadata?: RenderMetadata;\n /** Overrides the service default, when the service permits it. */\n backend?: RenderBackend;\n fonts?: RenderFont[];\n /** Tagged (accessible) PDF. */\n tagged?: boolean;\n /** PDF/UA-1 conformance. Needs an embeddable font. */\n pdfUa?: boolean;\n /** PDF/A conformance level. Needs an embeddable font. */\n pdfA?: \"2b\" | \"2u\" | \"2a\" | \"3b\" | \"3u\" | \"3a\";\n /** Hosts remote assets may be fetched from. May narrow the service's list, never widen it. */\n network?: { allowedHosts?: string[] };\n /** \"pdf\" returns the bytes; \"json\" returns {@link RenderJsonResponse}. Default \"pdf\". */\n format?: \"pdf\" | \"json\";\n}\n\n/** One entry of a batch: the same template rendered against different data. */\nexport interface BatchItem {\n /** Echoed back on the result, so a caller can match them up. */\n id: string;\n data?: ReportData;\n theme?: Partial<Theme>;\n metadata?: RenderMetadata;\n now?: string;\n locale?: string;\n currency?: string;\n timeZone?: string;\n}\n\nexport interface BatchRequest extends Omit<RenderRequest, \"data\" | \"format\"> {\n items: BatchItem[];\n}\n\nexport interface RenderJsonResponse {\n pdfBase64: string;\n pages: number;\n warnings: string[];\n durationMs: number;\n backend: RenderBackend;\n}\n\nexport interface BatchResponse {\n results: Array<\n | { id: string; ok: true; pdfBase64: string; pages: number; warnings: string[]; durationMs: number }\n | { id: string; ok: false; error: string; code: RenderErrorCode }\n >;\n backend: RenderBackend;\n durationMs: number;\n}\n\nexport interface HealthResponse {\n ok: boolean;\n /** Backends this service can actually use right now. */\n backends: RenderBackend[];\n defaultBackend: RenderBackend;\n active: number;\n queued: number;\n version: string;\n /** The protocol revision, so a client can refuse a service it does not understand. */\n protocol: number;\n /**\n * What this service's PDFs will come out as.\n *\n * Reported because the alternative is an operator discovering the answer in a\n * finance report a customer has already been sent. `state: \"unlicensed\"` is a\n * perfectly good answer in a development environment and a serious one in\n * production, and only the operator can tell which they are looking at.\n */\n license?: {\n state: LicenseState;\n /** Null unless the licence is being honoured. */\n plan: Plan | null;\n /** ISO 8601. Null when there is no licence, or when it has no end date. */\n validUntil: string | null;\n };\n}\n\nexport type RenderErrorCode =\n | \"BAD_REQUEST\"\n | \"UNAUTHORISED\"\n | \"PAYLOAD_TOO_LARGE\"\n | \"BACKEND_UNAVAILABLE\"\n | \"TIMEOUT\"\n | \"TOO_MANY_PAGES\"\n | \"BUSY\"\n | \"RENDER_FAILED\";\n\nexport interface ErrorResponse {\n error: string;\n code: RenderErrorCode;\n}\n\n/** Bumped when a change would break an older client. */\nexport const PROTOCOL_VERSION = 1;\n\n/** Response headers a client can read without parsing the body. */\nexport const HEADERS = {\n pages: \"x-broadpaper-pages\",\n warnings: \"x-broadpaper-warnings\",\n backend: \"x-broadpaper-backend\",\n durationMs: \"x-broadpaper-duration-ms\"\n} as const;\n","/**\n * HTTP in front of the render service.\n *\n * Two layers, on purpose. `createRenderHandler` is a plain Node\n * `(req, res) => Promise<boolean>` that returns whether it handled the request,\n * so it drops into an existing Express, Fastify, Connect or raw `http` app —\n * most teams already have a service and do not want a second one to deploy.\n * `createRenderServer` is that handler with a listener around it, for the teams\n * that do.\n *\n * The routes are the whole API surface:\n *\n * POST /render → PDF bytes, or JSON with `format: \"json\"`\n * POST /render/batch → one template, many data sets, JSON\n * GET /health → readiness, backends, load, protocol version\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { resolveLicenseStatus } from \"@broadpaper/license\";\nimport { RenderError, RenderService, type RenderServiceOptions } from \"./service.js\";\nimport { HEADERS, PROTOCOL_VERSION, type BatchRequest, type ErrorResponse, type HealthResponse, type RenderRequest } from \"./protocol.js\";\n\nexport interface RenderHandlerOptions extends RenderServiceOptions {\n /** Shared-secret bearer token. Strongly recommended anywhere but a laptop. */\n token?: string;\n /** Maximum request body size in bytes. Default 25 MB. */\n maxBodyBytes?: number;\n /**\n * Origins allowed to call this from a browser. `\"*\"` is the default because\n * the token is what actually guards the endpoint; set it when you want the\n * browser to enforce an origin too.\n */\n cors?: string | false;\n /** Mount point, e.g. \"/api/pdf\". Default \"\". */\n basePath?: string;\n /** Reported by `/health`, so an operator can see what is deployed. */\n version?: string;\n}\n\nexport interface RenderHandler {\n (req: IncomingMessage, res: ServerResponse): Promise<boolean>;\n service: RenderService;\n close(): Promise<void>;\n}\n\nfunction readJson(req: IncomingMessage, limit: number): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on(\"data\", (c: Buffer) => {\n size += c.length;\n if (size > limit) {\n // Stop reading, but do not destroy the socket: a caller who is told\n // \"connection reset\" has to guess why, and 413 is the whole point of\n // having a limit. The response carries `connection: close`, which is\n // what lets the socket go without draining the rest of the body.\n req.pause();\n reject(new RenderError(`Request body is larger than ${limit} bytes.`, \"PAYLOAD_TOO_LARGE\", 413));\n return;\n }\n chunks.push(c);\n });\n req.on(\"end\", () => {\n try {\n resolve(JSON.parse(Buffer.concat(chunks).toString(\"utf8\")));\n } catch {\n reject(new RenderError(\"Body is not valid JSON.\", \"BAD_REQUEST\", 400));\n }\n });\n req.on(\"error\", (e) => reject(new RenderError(e.message, \"BAD_REQUEST\", 400)));\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const json = JSON.stringify(body);\n res.writeHead(status, { \"content-type\": \"application/json; charset=utf-8\", \"content-length\": Buffer.byteLength(json) });\n res.end(json);\n}\n\nfunction fail(res: ServerResponse, e: unknown): void {\n const err = e instanceof RenderError ? e : new RenderError((e as Error)?.message || \"Render failed\", \"RENDER_FAILED\", 500);\n // The rest of an over-long body is never going to be read, so the connection\n // cannot be reused for the next request on it.\n if (err.code === \"PAYLOAD_TOO_LARGE\") res.setHeader(\"connection\", \"close\");\n const body: ErrorResponse = { error: err.message, code: err.code };\n sendJson(res, err.status, body);\n}\n\n/** A filename a browser will accept, from whatever the caller called the report. */\nfunction filename(title: string | undefined): string {\n const safe = (title ?? \"report\").replace(/[^\\w.-]+/g, \"_\").replace(/^_+|_+$/g, \"\");\n return `${safe || \"report\"}.pdf`;\n}\n\n/**\n * Builds the request handler. Returns `false` when the request was not one of\n * ours, so a host can fall through to its own routes.\n */\nexport function createRenderHandler(options: RenderHandlerOptions = {}): RenderHandler {\n const service = new RenderService(options);\n const limit = options.maxBodyBytes ?? 25 * 1024 * 1024;\n const base = (options.basePath ?? \"\").replace(/\\/$/, \"\");\n const cors = options.cors === undefined ? \"*\" : options.cors;\n\n const handler = (async (req: IncomingMessage, res: ServerResponse): Promise<boolean> => {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n if (base && !url.pathname.startsWith(base)) return false;\n const path = url.pathname.slice(base.length) || \"/\";\n const known = path === \"/health\" || path === \"/render\" || path === \"/render/batch\";\n if (!known) return false;\n\n res.setHeader(\"x-content-type-options\", \"nosniff\");\n if (cors) res.setHeader(\"access-control-allow-origin\", cors);\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, { \"access-control-allow-headers\": \"authorization, content-type\", \"access-control-allow-methods\": \"POST, GET, OPTIONS\", \"access-control-max-age\": \"600\" });\n res.end();\n return true;\n }\n\n if (path === \"/health\") {\n if (req.method !== \"GET\") {\n fail(res, new RenderError(\"Use GET for /health.\", \"BAD_REQUEST\", 405));\n return true;\n }\n const license = await resolveLicenseStatus();\n const body: HealthResponse = {\n ok: true,\n backends: service.backends,\n defaultBackend: service.defaultBackend,\n ...service.load,\n version: options.version ?? \"0.2.0\",\n protocol: PROTOCOL_VERSION,\n license: { state: license.state, plan: license.plan, validUntil: license.validUntil?.toISOString() ?? null }\n };\n sendJson(res, 200, body);\n return true;\n }\n\n if (req.method !== \"POST\") {\n fail(res, new RenderError(`Use POST for ${path}.`, \"BAD_REQUEST\", 405));\n return true;\n }\n // Checked before the body is read, so an unauthorised caller cannot make\n // the service buffer 25 MB for them.\n if (options.token && req.headers.authorization !== `Bearer ${options.token}`) {\n fail(res, new RenderError(\"Missing or invalid bearer token.\", \"UNAUTHORISED\", 401));\n return true;\n }\n\n try {\n const body = await readJson(req, limit);\n if (path === \"/render/batch\") {\n sendJson(res, 200, await service.renderBatch(body as BatchRequest));\n return true;\n }\n\n const request = body as RenderRequest;\n const out = await service.render(request);\n res.setHeader(HEADERS.pages, String(out.pages));\n res.setHeader(HEADERS.warnings, String(out.warnings.length));\n res.setHeader(HEADERS.backend, out.backend);\n res.setHeader(HEADERS.durationMs, String(out.durationMs));\n // Warnings matter enough to be readable without asking for JSON, but a\n // header is not the place for a paragraph of them.\n if (out.warnings.length && request.format !== \"json\") res.setHeader(\"x-broadpaper-warning-1\", out.warnings[0]!.slice(0, 300).replace(/[^\\x20-\\x7e]/g, \" \"));\n\n if (request.format === \"json\") {\n sendJson(res, 200, { pdfBase64: Buffer.from(out.pdf).toString(\"base64\"), pages: out.pages, warnings: out.warnings, durationMs: out.durationMs, backend: out.backend });\n return true;\n }\n res.writeHead(200, {\n \"content-type\": \"application/pdf\",\n \"content-length\": out.pdf.byteLength,\n \"content-disposition\": `inline; filename=\"${filename(request.metadata?.title)}\"`\n });\n res.end(Buffer.from(out.pdf));\n return true;\n } catch (e) {\n fail(res, e);\n return true;\n }\n }) as RenderHandler;\n\n handler.service = service;\n handler.close = () => service.close();\n return handler;\n}\n\nexport interface RenderServer {\n server: Server;\n port: number;\n service: RenderService;\n close(): Promise<void>;\n}\n\n/** The handler, with a listener around it. */\nexport async function createRenderServer(options: RenderHandlerOptions & { port?: number; host?: string } = {}): Promise<RenderServer> {\n const handler = createRenderHandler(options);\n const server = createServer((req, res) => {\n void handler(req, res).then((handled) => {\n if (!handled) sendJson(res, 404, { error: \"Not found\", code: \"BAD_REQUEST\" } satisfies ErrorResponse);\n });\n });\n const port = options.port ?? 4780;\n await new Promise<void>((resolve) => server.listen(port, options.host ?? \"0.0.0.0\", resolve));\n const address = server.address();\n const actual = typeof address === \"object\" && address ? address.port : port;\n options.log?.(`listening on http://${options.host ?? \"0.0.0.0\"}:${actual}${options.basePath ?? \"\"}`, { backends: handler.service.backends });\n return {\n server,\n port: actual,\n service: handler.service,\n close: async () => {\n await new Promise<void>((resolve) => server.close(() => resolve()));\n await handler.close();\n }\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createRenderServer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UGT7XIEH.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { resolveLicenseStatus } from "@broadpaper/license";
|
|
@@ -24,7 +24,7 @@ var server = await createRenderServer({
|
|
|
24
24
|
});
|
|
25
25
|
if (!env.BROADPAPER_TOKEN) console.warn("[broadpaper] no BROADPAPER_TOKEN set \u2014 the render endpoint is open to anyone who can reach it");
|
|
26
26
|
var license = await resolveLicenseStatus();
|
|
27
|
-
console.log(`[broadpaper] licence: ${license.state}${license.
|
|
27
|
+
console.log(`[broadpaper] licence: ${license.state}${license.plan ? ` (${license.plan})` : ""}${license.validUntil ? `, expires ${license.validUntil.toISOString().slice(0, 10)}` : ""}`);
|
|
28
28
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
29
29
|
process.on(signal, () => {
|
|
30
30
|
console.log(`[broadpaper] ${signal}, closing`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `broadpaper-server` — the render service as a container entry point.\n *\n * Everything is an environment variable because that is what a container\n * orchestrator can set; anything more elaborate belongs in a host that imports\n * `createRenderHandler` and configures it in code.\n */\nimport { resolveLicenseStatus } from \"@broadpaper/license\";\nimport { createRenderServer } from \"./http.js\";\n\nconst env = process.env;\nconst list = (v: string | undefined) => (v ?? \"\").split(\",\").map((s) => s.trim()).filter(Boolean);\n\nconst server = await createRenderServer({\n port: Number(env.PORT ?? env.BROADPAPER_PORT ?? 4780),\n host: env.HOST ?? \"0.0.0.0\",\n token: env.BROADPAPER_TOKEN,\n basePath: env.BROADPAPER_BASE_PATH ?? \"\",\n concurrency: Number(env.BROADPAPER_CONCURRENCY ?? 4),\n queueTimeoutMs: Number(env.BROADPAPER_QUEUE_TIMEOUT_MS ?? 30_000),\n timeoutMs: Number(env.BROADPAPER_TIMEOUT_MS ?? 60_000),\n maxPages: Number(env.BROADPAPER_MAX_PAGES ?? 500),\n maxBodyBytes: Number(env.BROADPAPER_MAX_BODY_BYTES ?? 25 * 1024 * 1024),\n allowedHosts: list(env.BROADPAPER_ALLOWED_HOSTS),\n cors: env.BROADPAPER_CORS === \"off\" ? false : env.BROADPAPER_CORS ?? \"*\",\n version: env.BROADPAPER_VERSION,\n log: (message, detail) => console.log(`[broadpaper] ${message}${detail ? \" \" + JSON.stringify(detail) : \"\"}`)\n});\n\nif (!env.BROADPAPER_TOKEN) console.warn(\"[broadpaper] no BROADPAPER_TOKEN set — the render endpoint is open to anyone who can reach it\");\n\n// Said at boot, not on the first render: an operator reads the startup log, and\n// \"every PDF this service produces is watermarked\" is not something to find out\n// from a customer. The licence itself is picked up from BROADPAPER_LICENSE.\nconst license = await resolveLicenseStatus();\nconsole.log(`[broadpaper] licence: ${license.state}${license.
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `broadpaper-server` — the render service as a container entry point.\n *\n * Everything is an environment variable because that is what a container\n * orchestrator can set; anything more elaborate belongs in a host that imports\n * `createRenderHandler` and configures it in code.\n */\nimport { resolveLicenseStatus } from \"@broadpaper/license\";\nimport { createRenderServer } from \"./http.js\";\n\nconst env = process.env;\nconst list = (v: string | undefined) => (v ?? \"\").split(\",\").map((s) => s.trim()).filter(Boolean);\n\nconst server = await createRenderServer({\n port: Number(env.PORT ?? env.BROADPAPER_PORT ?? 4780),\n host: env.HOST ?? \"0.0.0.0\",\n token: env.BROADPAPER_TOKEN,\n basePath: env.BROADPAPER_BASE_PATH ?? \"\",\n concurrency: Number(env.BROADPAPER_CONCURRENCY ?? 4),\n queueTimeoutMs: Number(env.BROADPAPER_QUEUE_TIMEOUT_MS ?? 30_000),\n timeoutMs: Number(env.BROADPAPER_TIMEOUT_MS ?? 60_000),\n maxPages: Number(env.BROADPAPER_MAX_PAGES ?? 500),\n maxBodyBytes: Number(env.BROADPAPER_MAX_BODY_BYTES ?? 25 * 1024 * 1024),\n allowedHosts: list(env.BROADPAPER_ALLOWED_HOSTS),\n cors: env.BROADPAPER_CORS === \"off\" ? false : env.BROADPAPER_CORS ?? \"*\",\n version: env.BROADPAPER_VERSION,\n log: (message, detail) => console.log(`[broadpaper] ${message}${detail ? \" \" + JSON.stringify(detail) : \"\"}`)\n});\n\nif (!env.BROADPAPER_TOKEN) console.warn(\"[broadpaper] no BROADPAPER_TOKEN set — the render endpoint is open to anyone who can reach it\");\n\n// Said at boot, not on the first render: an operator reads the startup log, and\n// \"every PDF this service produces is watermarked\" is not something to find out\n// from a customer. The licence itself is picked up from BROADPAPER_LICENSE.\nconst license = await resolveLicenseStatus();\nconsole.log(`[broadpaper] licence: ${license.state}${license.plan ? ` (${license.plan})` : \"\"}${license.validUntil ? `, expires ${license.validUntil.toISOString().slice(0, 10)}` : \"\"}`);\n\nfor (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.on(signal, () => {\n console.log(`[broadpaper] ${signal}, closing`);\n void server.close().then(() => process.exit(0));\n });\n}\n"],"mappings":";;;;;;AAQA,SAAS,4BAA4B;AAGrC,IAAM,MAAM,QAAQ;AACpB,IAAM,OAAO,CAAC,OAA2B,KAAK,IAAI,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAEhG,IAAM,SAAS,MAAM,mBAAmB;AAAA,EACtC,MAAM,OAAO,IAAI,QAAQ,IAAI,mBAAmB,IAAI;AAAA,EACpD,MAAM,IAAI,QAAQ;AAAA,EAClB,OAAO,IAAI;AAAA,EACX,UAAU,IAAI,wBAAwB;AAAA,EACtC,aAAa,OAAO,IAAI,0BAA0B,CAAC;AAAA,EACnD,gBAAgB,OAAO,IAAI,+BAA+B,GAAM;AAAA,EAChE,WAAW,OAAO,IAAI,yBAAyB,GAAM;AAAA,EACrD,UAAU,OAAO,IAAI,wBAAwB,GAAG;AAAA,EAChD,cAAc,OAAO,IAAI,6BAA6B,KAAK,OAAO,IAAI;AAAA,EACtE,cAAc,KAAK,IAAI,wBAAwB;AAAA,EAC/C,MAAM,IAAI,oBAAoB,QAAQ,QAAQ,IAAI,mBAAmB;AAAA,EACrE,SAAS,IAAI;AAAA,EACb,KAAK,CAAC,SAAS,WAAW,QAAQ,IAAI,gBAAgB,OAAO,GAAG,SAAS,MAAM,KAAK,UAAU,MAAM,IAAI,EAAE,EAAE;AAC9G,CAAC;AAED,IAAI,CAAC,IAAI,iBAAkB,SAAQ,KAAK,oGAA+F;AAKvI,IAAM,UAAU,MAAM,qBAAqB;AAC3C,QAAQ,IAAI,yBAAyB,QAAQ,KAAK,GAAG,QAAQ,OAAO,KAAK,QAAQ,IAAI,MAAM,EAAE,GAAG,QAAQ,aAAa,aAAa,QAAQ,WAAW,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE;AAExL,WAAW,UAAU,CAAC,UAAU,SAAS,GAAY;AACnD,UAAQ,GAAG,QAAQ,MAAM;AACvB,YAAQ,IAAI,gBAAgB,MAAM,WAAW;AAC7C,SAAK,OAAO,MAAM,EAAE,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAC;AAAA,EAChD,CAAC;AACH;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReportData, Theme, ReportTemplate, AnyVersionTemplate, DataSource, BlockDefinition } from '@broadpaper/core';
|
|
2
|
-
import { LicenseState,
|
|
3
|
-
export { BroadPaper, BroadPaperConfig,
|
|
2
|
+
import { LicenseState, Plan } from '@broadpaper/license';
|
|
3
|
+
export { BroadPaper, BroadPaperConfig, LicenseState, LicenseStatus, Plan, configure, resolveLicenseStatus } from '@broadpaper/license';
|
|
4
4
|
import { IncomingMessage, ServerResponse, Server } from 'node:http';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -124,9 +124,9 @@ interface HealthResponse {
|
|
|
124
124
|
license?: {
|
|
125
125
|
state: LicenseState;
|
|
126
126
|
/** Null unless the licence is being honoured. */
|
|
127
|
-
|
|
128
|
-
/** ISO 8601
|
|
129
|
-
|
|
127
|
+
plan: Plan | null;
|
|
128
|
+
/** ISO 8601. Null when there is no licence, or when it has no end date. */
|
|
129
|
+
validUntil: string | null;
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
type RenderErrorCode = "BAD_REQUEST" | "UNAUTHORISED" | "PAYLOAD_TOO_LARGE" | "BACKEND_UNAVAILABLE" | "TIMEOUT" | "TOO_MANY_PAGES" | "BUSY" | "RENDER_FAILED";
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @broadpaper/server — render BroadPaper templates to PDF on a server.\n *\n * The browserless engine by default, so this runs in a plain Node image with\n * no browser to install; Chromium is available by injecting it, for the cases\n * that want the browser's own text shaping.\n *\n * import { createRenderServer } from \"@broadpaper/server\";\n * const server = await createRenderServer({ token: process.env.TOKEN });\n *\n * Or mounted inside an app you already have:\n *\n * const render = createRenderHandler({ basePath: \"/api/pdf\" });\n * http.createServer(async (req, res) => { if (!(await render(req, res))) myApp(req, res); });\n */\nexport { RenderService, RenderError } from \"./service.js\";\nexport type { RenderServiceOptions, RenderOutcome, ChromiumBackend } from \"./service.js\";\nexport { createRenderHandler, createRenderServer } from \"./http.js\";\nexport type { RenderHandler, RenderHandlerOptions, RenderServer } from \"./http.js\";\nexport { PROTOCOL_VERSION, HEADERS } from \"./protocol.js\";\nexport type {\n RenderBackend,\n RenderRequest,\n RenderMetadata,\n RenderFont,\n RenderJsonResponse,\n BatchItem,\n BatchRequest,\n BatchResponse,\n HealthResponse,\n ErrorResponse,\n RenderErrorCode\n} from \"./protocol.js\";\n\n/**\n * Licensing.\n *\n * A render service renders under *its own* licence, set once at startup from\n * `BROADPAPER_LICENSE` or here in code. A licence is deliberately not part of\n * the render request: the service decides what its output is, not its callers.\n */\nexport { BroadPaper, configure, resolveLicenseStatus } from \"@broadpaper/license\";\nexport type { BroadPaperConfig,
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @broadpaper/server — render BroadPaper templates to PDF on a server.\n *\n * The browserless engine by default, so this runs in a plain Node image with\n * no browser to install; Chromium is available by injecting it, for the cases\n * that want the browser's own text shaping.\n *\n * import { createRenderServer } from \"@broadpaper/server\";\n * const server = await createRenderServer({ token: process.env.TOKEN });\n *\n * Or mounted inside an app you already have:\n *\n * const render = createRenderHandler({ basePath: \"/api/pdf\" });\n * http.createServer(async (req, res) => { if (!(await render(req, res))) myApp(req, res); });\n */\nexport { RenderService, RenderError } from \"./service.js\";\nexport type { RenderServiceOptions, RenderOutcome, ChromiumBackend } from \"./service.js\";\nexport { createRenderHandler, createRenderServer } from \"./http.js\";\nexport type { RenderHandler, RenderHandlerOptions, RenderServer } from \"./http.js\";\nexport { PROTOCOL_VERSION, HEADERS } from \"./protocol.js\";\nexport type {\n RenderBackend,\n RenderRequest,\n RenderMetadata,\n RenderFont,\n RenderJsonResponse,\n BatchItem,\n BatchRequest,\n BatchResponse,\n HealthResponse,\n ErrorResponse,\n RenderErrorCode\n} from \"./protocol.js\";\n\n/**\n * Licensing.\n *\n * A render service renders under *its own* licence, set once at startup from\n * `BROADPAPER_LICENSE` or here in code. A licence is deliberately not part of\n * the render request: the service decides what its output is, not its callers.\n */\nexport { BroadPaper, configure, resolveLicenseStatus } from \"@broadpaper/license\";\nexport type { BroadPaperConfig, LicenseState, LicenseStatus, Plan } from \"@broadpaper/license\";\n"],"mappings":";;;;;;;;;;AAyCA,SAAS,YAAY,WAAW,4BAA4B;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@broadpaper/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Server-side PDF rendering for BroadPaper: an HTTP render service and a mountable handler, browserless by default.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
},
|
|
24
24
|
"sideEffects": false,
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@broadpaper/blocks": "0.
|
|
27
|
-
"@broadpaper/core": "0.
|
|
28
|
-
"@broadpaper/
|
|
29
|
-
"@broadpaper/
|
|
26
|
+
"@broadpaper/blocks": "0.2.0",
|
|
27
|
+
"@broadpaper/core": "0.2.0",
|
|
28
|
+
"@broadpaper/forme": "0.2.0",
|
|
29
|
+
"@broadpaper/license": "0.2.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsup src/index.ts src/cli.ts --format esm --dts --sourcemap --clean --external @broadpaper/core --external @broadpaper/blocks --external @broadpaper/forme --external @broadpaper/license --external @formepdf/core",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/service.ts","../src/protocol.ts","../src/http.ts"],"sourcesContent":["/**\n * The render service, with the transport left out.\n *\n * `@broadpaper/pdf` could already serve HTTP, but only over Chromium — which\n * means a 700 MB browser on every machine that renders, and nothing at all on\n * a runtime that cannot start one. This service picks a backend instead: the\n * browserless engine by default, Chromium when a caller wants the browser's own\n * text shaping. Both are handed pages BroadPaper's paginator has already\n * decided, so the choice changes fidelity, not pagination.\n *\n * Nothing here knows about HTTP. `http.ts` is a thin adapter over it, which is\n * what lets a host mount the same service inside Express, Fastify, Hono or a\n * serverless handler rather than running a second process.\n */\nimport { createRegistry } from \"@broadpaper/blocks\";\nimport { migrateTemplate, parseTemplate } from \"@broadpaper/core\";\nimport type { BlockDefinition, BlockRegistry, ReportTemplate } from \"@broadpaper/core\";\nimport { renderPdfPaginated, type FormeFont } from \"@broadpaper/forme\";\nimport type { BatchRequest, BatchResponse, RenderBackend, RenderErrorCode, RenderFont, RenderRequest } from \"./protocol.js\";\n\nexport class RenderError extends Error {\n constructor(\n message: string,\n readonly code: RenderErrorCode,\n readonly status: number\n ) {\n super(message);\n this.name = \"RenderError\";\n }\n}\n\n/**\n * What a Chromium backend has to provide.\n *\n * Injected rather than imported, so this package never depends on Playwright\n * and a service that only wants the browserless engine does not drag a browser\n * into its image.\n */\nexport interface ChromiumBackend {\n render(opts: {\n template: unknown;\n data?: unknown;\n theme?: unknown;\n dataSources?: unknown;\n now?: Date;\n locale?: string;\n currency?: string;\n timeZone?: string;\n metadata?: unknown;\n network?: { allowedHosts?: string[]; blockPrivateNetworks?: boolean };\n customBlocksScript?: string;\n timeoutMs?: number;\n maxPages?: number;\n }): Promise<{ pdf: Uint8Array; pages: number; warnings: Array<string | { message: string }>; durationMs: number }>;\n close?(): Promise<void>;\n}\n\nexport interface RenderServiceOptions {\n /** Which backend to use when a request does not name one. Default \"forme\". */\n defaultBackend?: RenderBackend;\n /**\n * Backends a request may ask for. Defaults to whatever is available. Narrow\n * it when you do not want callers choosing the expensive one.\n */\n allowedBackends?: RenderBackend[];\n /** Custom blocks for the browserless backend: the host's own code, in process. */\n blocks?: Array<BlockDefinition<never>>;\n /** Custom blocks bundle for Chromium (an IIFE calling `BroadPaper.registerBlocks`). */\n customBlocksScript?: string;\n /** Fonts embedded in every render. A request may add to these, never replace them. */\n fonts?: RenderFont[];\n /** Chromium backend. Without one, only the browserless backend is available. */\n chromium?: ChromiumBackend;\n /** Concurrent renders. Default 4. */\n concurrency?: number;\n /** How long a request may wait for a slot before being refused. Default 30s. */\n queueTimeoutMs?: number;\n /** Per-render timeout. Default 60s. */\n timeoutMs?: number;\n /** Refuse documents longer than this. Default 500 pages. */\n maxPages?: number;\n /** Hosts remote assets may be fetched from. A request may narrow this, never widen it. */\n allowedHosts?: string[];\n /** Injected Forme entry point, so a host can pick the node, browser or worker build. */\n renderer?: (doc: Record<string, unknown>) => Promise<{ pdf: Uint8Array; layout?: unknown; warnings?: string[] }>;\n log?: (message: string, detail?: Record<string, unknown>) => void;\n}\n\nexport interface RenderOutcome {\n pdf: Uint8Array;\n pages: number;\n warnings: string[];\n durationMs: number;\n backend: RenderBackend;\n}\n\nconst DEFAULTS = { concurrency: 4, queueTimeoutMs: 30_000, timeoutMs: 60_000, maxPages: 500 };\n\nexport class RenderService {\n private readonly registry: BlockRegistry;\n private readonly opts: RenderServiceOptions & Required<Pick<RenderServiceOptions, \"concurrency\" | \"queueTimeoutMs\" | \"timeoutMs\" | \"maxPages\">>;\n private active = 0;\n private readonly waiting: Array<{ resolve: () => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }> = [];\n\n constructor(options: RenderServiceOptions = {}) {\n this.opts = { ...DEFAULTS, ...options };\n this.registry = createRegistry((options.blocks ?? []) as never);\n }\n\n /** Backends this service can actually use, in preference order. */\n get backends(): RenderBackend[] {\n const all: RenderBackend[] = this.opts.chromium ? [\"forme\", \"chromium\"] : [\"forme\"];\n const allowed = this.opts.allowedBackends;\n return allowed ? all.filter((b) => allowed.includes(b)) : all;\n }\n\n get defaultBackend(): RenderBackend {\n const wanted = this.opts.defaultBackend ?? \"forme\";\n return this.backends.includes(wanted) ? wanted : this.backends[0] ?? \"forme\";\n }\n\n get load(): { active: number; queued: number } {\n return { active: this.active, queued: this.waiting.length };\n }\n\n /**\n * Renders one document.\n *\n * Throws {@link RenderError} for anything a caller can act on — a backend\n * they may not use, a queue that never cleared, a document longer than the\n * service permits — carrying the code and status the wire protocol names.\n */\n async render(request: RenderRequest): Promise<RenderOutcome> {\n if (!request || typeof request !== \"object\" || !request.template) throw new RenderError(\"Missing template\", \"BAD_REQUEST\", 400);\n const template = this.parse(request.template);\n const backend = this.pickBackend(request.backend);\n await this.acquire();\n const started = Date.now();\n try {\n const checked = { ...request, template };\n const out = await this.withTimeout(backend === \"chromium\" ? this.renderChromium(checked) : this.renderForme(checked), backend);\n this.opts.log?.(\"render\", { backend, pages: out.pages, ms: out.durationMs, warnings: out.warnings.length });\n return out;\n } catch (e) {\n const err = e as RenderError;\n this.opts.log?.(\"render failed\", { backend, code: err.code ?? \"RENDER_FAILED\", message: err.message, ms: Date.now() - started });\n throw err;\n } finally {\n this.release();\n }\n }\n\n /**\n * Renders one template against many data sets — a monthly run, a mail merge.\n *\n * One failure does not fail the batch: each result carries its own outcome,\n * because a caller sending five hundred clients wants the four hundred and\n * ninety-nine that worked and a list of the one that did not.\n */\n async renderBatch(request: BatchRequest): Promise<BatchResponse> {\n if (!Array.isArray(request?.items) || request.items.length === 0) throw new RenderError(\"A batch needs at least one item\", \"BAD_REQUEST\", 400);\n const started = Date.now();\n const backend = this.pickBackend(request.backend);\n const { items, ...shared } = request;\n const results: BatchResponse[\"results\"] = await Promise.all(\n items.map(async (item) => {\n try {\n const out = await this.render({ ...shared, ...item, backend } as RenderRequest);\n return { id: item.id, ok: true as const, pdfBase64: Buffer.from(out.pdf).toString(\"base64\"), pages: out.pages, warnings: out.warnings, durationMs: out.durationMs };\n } catch (e) {\n const err = e as RenderError;\n return { id: item.id, ok: false as const, error: err.message, code: err.code ?? \"RENDER_FAILED\" };\n }\n })\n );\n return { results, backend, durationMs: Date.now() - started };\n }\n\n async close(): Promise<void> {\n for (const w of this.waiting.splice(0)) {\n clearTimeout(w.timer);\n w.reject(new RenderError(\"The service is shutting down.\", \"BUSY\", 503));\n }\n await this.opts.chromium?.close?.();\n }\n\n /* ------------------------------ internals ------------------------------ */\n\n /**\n * Migrates and validates the incoming template.\n *\n * A service takes JSON from over the wire — hand-written, generated, saved by\n * a version of the designer nobody here has seen. Handing that straight to\n * the renderer turns a missing `styles` block into \"Cannot read properties of\n * undefined (reading 'height')\", which tells the caller nothing at all.\n * Parsing first fills in every default the schema declares and names the\n * field when it cannot.\n */\n private parse(input: RenderRequest[\"template\"]): ReportTemplate {\n const migrated = migrateTemplate(input as never);\n const parsed = parseTemplate(migrated.template);\n if (!parsed.ok || !parsed.template) {\n const detail = parsed.errors\n .slice(0, 5)\n .map((e) => `${e.path || \"(root)\"}: ${e.message}`)\n .join(\"; \");\n throw new RenderError(`The template is not valid. ${detail}${parsed.errors.length > 5 ? ` (+${parsed.errors.length - 5} more)` : \"\"}`, \"BAD_REQUEST\", 400);\n }\n return parsed.template;\n }\n\n private pickBackend(requested: RenderBackend | undefined): RenderBackend {\n if (!requested) return this.defaultBackend;\n if (!this.backends.includes(requested)) {\n throw new RenderError(`Backend \"${requested}\" is not available here. Available: ${this.backends.join(\", \")}.`, \"BACKEND_UNAVAILABLE\", 400);\n }\n return requested;\n }\n\n /**\n * Narrows the network policy. A request may restrict what a render fetches;\n * it may not reach anywhere the service was not already willing to go.\n */\n private allowedHosts(requested: string[] | undefined): string[] {\n const server = this.opts.allowedHosts ?? [];\n if (!requested) return server;\n return server.includes(\"*\") ? requested : requested.filter((h) => server.includes(h));\n }\n\n private fonts(request: RenderRequest): FormeFont[] {\n return [...(this.opts.fonts ?? []), ...(request.fonts ?? [])].map((f) => ({\n family: f.family,\n // Base64 for a client that cannot share a filesystem with the service.\n src: f.bytes ? new Uint8Array(Buffer.from(f.bytes, \"base64\")) : f.src ?? \"\",\n weight: f.weight ?? 400,\n style: f.style ?? \"normal\"\n }));\n }\n\n private async renderForme(request: RenderRequest): Promise<RenderOutcome> {\n const fonts = this.fonts(request);\n const result = await renderPdfPaginated({\n template: request.template,\n registry: this.registry,\n data: request.data,\n theme: request.theme,\n dataSources: request.dataSources,\n now: request.now ? new Date(request.now) : undefined,\n locale: request.locale,\n currency: request.currency,\n timeZone: request.timeZone,\n metadata: request.metadata,\n ...(this.opts.renderer ? { renderer: this.opts.renderer } : {}),\n ...(fonts.length ? { fonts } : {}),\n ...(request.tagged ? { tagged: true } : {}),\n ...(request.pdfUa ? { pdfUa: true } : {}),\n ...(request.pdfA ? { pdfA: request.pdfA } : {})\n });\n if (result.paged.totalPages > this.opts.maxPages) {\n throw new RenderError(`Document is ${result.paged.totalPages} pages; this service allows ${this.opts.maxPages}.`, \"TOO_MANY_PAGES\", 413);\n }\n return { pdf: result.pdf, pages: result.paged.totalPages, warnings: result.warnings, durationMs: result.durationMs, backend: \"forme\" };\n }\n\n private async renderChromium(request: RenderRequest): Promise<RenderOutcome> {\n const result = await this.opts.chromium!.render({\n template: request.template,\n data: request.data,\n theme: request.theme,\n dataSources: request.dataSources,\n now: request.now ? new Date(request.now) : undefined,\n locale: request.locale,\n currency: request.currency,\n timeZone: request.timeZone,\n metadata: request.metadata,\n network: { allowedHosts: this.allowedHosts(request.network?.allowedHosts) },\n customBlocksScript: this.opts.customBlocksScript,\n timeoutMs: this.opts.timeoutMs,\n maxPages: this.opts.maxPages\n });\n return {\n pdf: result.pdf,\n pages: result.pages,\n warnings: result.warnings.map((w) => (typeof w === \"string\" ? w : w.message)),\n durationMs: result.durationMs,\n backend: \"chromium\"\n };\n }\n\n private async withTimeout(work: Promise<RenderOutcome>, backend: RenderBackend): Promise<RenderOutcome> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n work,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new RenderError(`Render timed out after ${this.opts.timeoutMs} ms on the ${backend} backend.`, \"TIMEOUT\", 504)), this.opts.timeoutMs);\n })\n ]);\n } catch (e) {\n if (e instanceof RenderError) throw e;\n throw new RenderError((e as Error).message || \"Render failed\", \"RENDER_FAILED\", 500);\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n\n /**\n * Waits for a slot, but not forever. An unbounded queue turns one busy minute\n * into a pile of work whose callers have all given up, and then does it\n * anyway.\n */\n private acquire(): Promise<void> {\n if (this.active < this.opts.concurrency) {\n this.active++;\n return Promise.resolve();\n }\n return new Promise<void>((resolve, reject) => {\n const entry = {\n resolve: () => {\n this.active++;\n resolve();\n },\n reject,\n timer: setTimeout(() => {\n const i = this.waiting.indexOf(entry);\n if (i >= 0) this.waiting.splice(i, 1);\n reject(new RenderError(`Waited ${this.opts.queueTimeoutMs} ms for a render slot.`, \"BUSY\", 503));\n }, this.opts.queueTimeoutMs)\n };\n this.waiting.push(entry);\n });\n }\n\n private release(): void {\n this.active--;\n const next = this.waiting.shift();\n if (!next) return;\n clearTimeout(next.timer);\n next.resolve();\n }\n}\n","/**\n * The wire protocol of the render service.\n *\n * This file is the contract between the service, the browser client and the\n * .NET client under `dotnet/`. Every field here is JSON — a template, a theme\n * and a data set are all host-defined documents — so the same shapes are\n * expressible in any language, and the .NET types mirror these names exactly.\n *\n * Anything added here must be optional, because a client is always older than\n * the service it is talking to.\n */\nimport type { DataSource, ReportData, ReportTemplate, Theme, AnyVersionTemplate } from \"@broadpaper/core\";\nimport type { Entitlement, LicenseState } from \"@broadpaper/license\";\n\n/** Which engine draws the pages. BroadPaper decides the pages either way. */\nexport type RenderBackend = \"forme\" | \"chromium\";\n\nexport interface RenderMetadata {\n title?: string;\n author?: string;\n subject?: string;\n keywords?: string[];\n /** Document language, e.g. \"en-GB\". Required for PDF/UA. */\n lang?: string;\n}\n\n/** A font to embed. Without one the engine falls back to its standard faces. */\nexport interface RenderFont {\n family: string;\n /** A path the service can read, a `data:` URI, or base64 in `bytes`. */\n src?: string;\n /** Base64-encoded TTF/OTF. Use this from a client that cannot share a filesystem. */\n bytes?: string;\n weight?: number;\n style?: \"normal\" | \"italic\";\n}\n\nexport interface RenderRequest {\n /** The saved template. Older schema versions are migrated on the way in. */\n template: ReportTemplate | AnyVersionTemplate;\n /** Runtime data, keyed by data source id. */\n data?: ReportData;\n theme?: Partial<Theme>;\n dataSources?: DataSource[];\n /** ISO-8601. Fixes the clock, which is one of the conditions for byte-identical output. */\n now?: string;\n locale?: string;\n currency?: string;\n timeZone?: string;\n metadata?: RenderMetadata;\n /** Overrides the service default, when the service permits it. */\n backend?: RenderBackend;\n fonts?: RenderFont[];\n /** Tagged (accessible) PDF. */\n tagged?: boolean;\n /** PDF/UA-1 conformance. Needs an embeddable font. */\n pdfUa?: boolean;\n /** PDF/A conformance level. Needs an embeddable font. */\n pdfA?: \"2b\" | \"2u\" | \"2a\" | \"3b\" | \"3u\" | \"3a\";\n /** Hosts remote assets may be fetched from. May narrow the service's list, never widen it. */\n network?: { allowedHosts?: string[] };\n /** \"pdf\" returns the bytes; \"json\" returns {@link RenderJsonResponse}. Default \"pdf\". */\n format?: \"pdf\" | \"json\";\n}\n\n/** One entry of a batch: the same template rendered against different data. */\nexport interface BatchItem {\n /** Echoed back on the result, so a caller can match them up. */\n id: string;\n data?: ReportData;\n theme?: Partial<Theme>;\n metadata?: RenderMetadata;\n now?: string;\n locale?: string;\n currency?: string;\n timeZone?: string;\n}\n\nexport interface BatchRequest extends Omit<RenderRequest, \"data\" | \"format\"> {\n items: BatchItem[];\n}\n\nexport interface RenderJsonResponse {\n pdfBase64: string;\n pages: number;\n warnings: string[];\n durationMs: number;\n backend: RenderBackend;\n}\n\nexport interface BatchResponse {\n results: Array<\n | { id: string; ok: true; pdfBase64: string; pages: number; warnings: string[]; durationMs: number }\n | { id: string; ok: false; error: string; code: RenderErrorCode }\n >;\n backend: RenderBackend;\n durationMs: number;\n}\n\nexport interface HealthResponse {\n ok: boolean;\n /** Backends this service can actually use right now. */\n backends: RenderBackend[];\n defaultBackend: RenderBackend;\n active: number;\n queued: number;\n version: string;\n /** The protocol revision, so a client can refuse a service it does not understand. */\n protocol: number;\n /**\n * What this service's PDFs will come out as.\n *\n * Reported because the alternative is an operator discovering the answer in a\n * finance report a customer has already been sent. `state: \"unlicensed\"` is a\n * perfectly good answer in a development environment and a serious one in\n * production, and only the operator can tell which they are looking at.\n */\n license?: {\n state: LicenseState;\n /** Null unless the licence is being honoured. */\n entitlement: Entitlement | null;\n /** ISO 8601, when a licence was readable. */\n expiresAt: string | null;\n };\n}\n\nexport type RenderErrorCode =\n | \"BAD_REQUEST\"\n | \"UNAUTHORISED\"\n | \"PAYLOAD_TOO_LARGE\"\n | \"BACKEND_UNAVAILABLE\"\n | \"TIMEOUT\"\n | \"TOO_MANY_PAGES\"\n | \"BUSY\"\n | \"RENDER_FAILED\";\n\nexport interface ErrorResponse {\n error: string;\n code: RenderErrorCode;\n}\n\n/** Bumped when a change would break an older client. */\nexport const PROTOCOL_VERSION = 1;\n\n/** Response headers a client can read without parsing the body. */\nexport const HEADERS = {\n pages: \"x-broadpaper-pages\",\n warnings: \"x-broadpaper-warnings\",\n backend: \"x-broadpaper-backend\",\n durationMs: \"x-broadpaper-duration-ms\"\n} as const;\n","/**\n * HTTP in front of the render service.\n *\n * Two layers, on purpose. `createRenderHandler` is a plain Node\n * `(req, res) => Promise<boolean>` that returns whether it handled the request,\n * so it drops into an existing Express, Fastify, Connect or raw `http` app —\n * most teams already have a service and do not want a second one to deploy.\n * `createRenderServer` is that handler with a listener around it, for the teams\n * that do.\n *\n * The routes are the whole API surface:\n *\n * POST /render → PDF bytes, or JSON with `format: \"json\"`\n * POST /render/batch → one template, many data sets, JSON\n * GET /health → readiness, backends, load, protocol version\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { resolveLicenseStatus } from \"@broadpaper/license\";\nimport { RenderError, RenderService, type RenderServiceOptions } from \"./service.js\";\nimport { HEADERS, PROTOCOL_VERSION, type BatchRequest, type ErrorResponse, type HealthResponse, type RenderRequest } from \"./protocol.js\";\n\nexport interface RenderHandlerOptions extends RenderServiceOptions {\n /** Shared-secret bearer token. Strongly recommended anywhere but a laptop. */\n token?: string;\n /** Maximum request body size in bytes. Default 25 MB. */\n maxBodyBytes?: number;\n /**\n * Origins allowed to call this from a browser. `\"*\"` is the default because\n * the token is what actually guards the endpoint; set it when you want the\n * browser to enforce an origin too.\n */\n cors?: string | false;\n /** Mount point, e.g. \"/api/pdf\". Default \"\". */\n basePath?: string;\n /** Reported by `/health`, so an operator can see what is deployed. */\n version?: string;\n}\n\nexport interface RenderHandler {\n (req: IncomingMessage, res: ServerResponse): Promise<boolean>;\n service: RenderService;\n close(): Promise<void>;\n}\n\nfunction readJson(req: IncomingMessage, limit: number): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on(\"data\", (c: Buffer) => {\n size += c.length;\n if (size > limit) {\n // Stop reading, but do not destroy the socket: a caller who is told\n // \"connection reset\" has to guess why, and 413 is the whole point of\n // having a limit. The response carries `connection: close`, which is\n // what lets the socket go without draining the rest of the body.\n req.pause();\n reject(new RenderError(`Request body is larger than ${limit} bytes.`, \"PAYLOAD_TOO_LARGE\", 413));\n return;\n }\n chunks.push(c);\n });\n req.on(\"end\", () => {\n try {\n resolve(JSON.parse(Buffer.concat(chunks).toString(\"utf8\")));\n } catch {\n reject(new RenderError(\"Body is not valid JSON.\", \"BAD_REQUEST\", 400));\n }\n });\n req.on(\"error\", (e) => reject(new RenderError(e.message, \"BAD_REQUEST\", 400)));\n });\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n const json = JSON.stringify(body);\n res.writeHead(status, { \"content-type\": \"application/json; charset=utf-8\", \"content-length\": Buffer.byteLength(json) });\n res.end(json);\n}\n\nfunction fail(res: ServerResponse, e: unknown): void {\n const err = e instanceof RenderError ? e : new RenderError((e as Error)?.message || \"Render failed\", \"RENDER_FAILED\", 500);\n // The rest of an over-long body is never going to be read, so the connection\n // cannot be reused for the next request on it.\n if (err.code === \"PAYLOAD_TOO_LARGE\") res.setHeader(\"connection\", \"close\");\n const body: ErrorResponse = { error: err.message, code: err.code };\n sendJson(res, err.status, body);\n}\n\n/** A filename a browser will accept, from whatever the caller called the report. */\nfunction filename(title: string | undefined): string {\n const safe = (title ?? \"report\").replace(/[^\\w.-]+/g, \"_\").replace(/^_+|_+$/g, \"\");\n return `${safe || \"report\"}.pdf`;\n}\n\n/**\n * Builds the request handler. Returns `false` when the request was not one of\n * ours, so a host can fall through to its own routes.\n */\nexport function createRenderHandler(options: RenderHandlerOptions = {}): RenderHandler {\n const service = new RenderService(options);\n const limit = options.maxBodyBytes ?? 25 * 1024 * 1024;\n const base = (options.basePath ?? \"\").replace(/\\/$/, \"\");\n const cors = options.cors === undefined ? \"*\" : options.cors;\n\n const handler = (async (req: IncomingMessage, res: ServerResponse): Promise<boolean> => {\n const url = new URL(req.url ?? \"/\", \"http://localhost\");\n if (base && !url.pathname.startsWith(base)) return false;\n const path = url.pathname.slice(base.length) || \"/\";\n const known = path === \"/health\" || path === \"/render\" || path === \"/render/batch\";\n if (!known) return false;\n\n res.setHeader(\"x-content-type-options\", \"nosniff\");\n if (cors) res.setHeader(\"access-control-allow-origin\", cors);\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204, { \"access-control-allow-headers\": \"authorization, content-type\", \"access-control-allow-methods\": \"POST, GET, OPTIONS\", \"access-control-max-age\": \"600\" });\n res.end();\n return true;\n }\n\n if (path === \"/health\") {\n if (req.method !== \"GET\") {\n fail(res, new RenderError(\"Use GET for /health.\", \"BAD_REQUEST\", 405));\n return true;\n }\n const license = await resolveLicenseStatus();\n const body: HealthResponse = {\n ok: true,\n backends: service.backends,\n defaultBackend: service.defaultBackend,\n ...service.load,\n version: options.version ?? \"0.1.1\",\n protocol: PROTOCOL_VERSION,\n license: { state: license.state, entitlement: license.entitlement, expiresAt: license.expiresAt?.toISOString() ?? null }\n };\n sendJson(res, 200, body);\n return true;\n }\n\n if (req.method !== \"POST\") {\n fail(res, new RenderError(`Use POST for ${path}.`, \"BAD_REQUEST\", 405));\n return true;\n }\n // Checked before the body is read, so an unauthorised caller cannot make\n // the service buffer 25 MB for them.\n if (options.token && req.headers.authorization !== `Bearer ${options.token}`) {\n fail(res, new RenderError(\"Missing or invalid bearer token.\", \"UNAUTHORISED\", 401));\n return true;\n }\n\n try {\n const body = await readJson(req, limit);\n if (path === \"/render/batch\") {\n sendJson(res, 200, await service.renderBatch(body as BatchRequest));\n return true;\n }\n\n const request = body as RenderRequest;\n const out = await service.render(request);\n res.setHeader(HEADERS.pages, String(out.pages));\n res.setHeader(HEADERS.warnings, String(out.warnings.length));\n res.setHeader(HEADERS.backend, out.backend);\n res.setHeader(HEADERS.durationMs, String(out.durationMs));\n // Warnings matter enough to be readable without asking for JSON, but a\n // header is not the place for a paragraph of them.\n if (out.warnings.length && request.format !== \"json\") res.setHeader(\"x-broadpaper-warning-1\", out.warnings[0]!.slice(0, 300).replace(/[^\\x20-\\x7e]/g, \" \"));\n\n if (request.format === \"json\") {\n sendJson(res, 200, { pdfBase64: Buffer.from(out.pdf).toString(\"base64\"), pages: out.pages, warnings: out.warnings, durationMs: out.durationMs, backend: out.backend });\n return true;\n }\n res.writeHead(200, {\n \"content-type\": \"application/pdf\",\n \"content-length\": out.pdf.byteLength,\n \"content-disposition\": `inline; filename=\"${filename(request.metadata?.title)}\"`\n });\n res.end(Buffer.from(out.pdf));\n return true;\n } catch (e) {\n fail(res, e);\n return true;\n }\n }) as RenderHandler;\n\n handler.service = service;\n handler.close = () => service.close();\n return handler;\n}\n\nexport interface RenderServer {\n server: Server;\n port: number;\n service: RenderService;\n close(): Promise<void>;\n}\n\n/** The handler, with a listener around it. */\nexport async function createRenderServer(options: RenderHandlerOptions & { port?: number; host?: string } = {}): Promise<RenderServer> {\n const handler = createRenderHandler(options);\n const server = createServer((req, res) => {\n void handler(req, res).then((handled) => {\n if (!handled) sendJson(res, 404, { error: \"Not found\", code: \"BAD_REQUEST\" } satisfies ErrorResponse);\n });\n });\n const port = options.port ?? 4780;\n await new Promise<void>((resolve) => server.listen(port, options.host ?? \"0.0.0.0\", resolve));\n const address = server.address();\n const actual = typeof address === \"object\" && address ? address.port : port;\n options.log?.(`listening on http://${options.host ?? \"0.0.0.0\"}:${actual}${options.basePath ?? \"\"}`, { backends: handler.service.backends });\n return {\n server,\n port: actual,\n service: handler.service,\n close: async () => {\n await new Promise<void>((resolve) => server.close(() => resolve()));\n await handler.close();\n }\n };\n}\n"],"mappings":"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","names":[]}
|