@demiurgejs/core 0.2.0-beta.2 → 0.2.0-beta.3

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.
Files changed (106) hide show
  1. package/dist/adapter/testing.js.map +1 -1
  2. package/dist/browser/file-router.d.ts.map +1 -1
  3. package/dist/chunks/{document-shell-D1xo6cEA.js → document-shell-WoM8yZlR.js} +2 -2
  4. package/dist/chunks/{document-shell-D1xo6cEA.js.map → document-shell-WoM8yZlR.js.map} +1 -1
  5. package/dist/chunks/{image-codec-BGTx_t5K.js → image-codec-Di53WpcH.js} +2 -2
  6. package/dist/chunks/{image-codec-BGTx_t5K.js.map → image-codec-Di53WpcH.js.map} +1 -1
  7. package/dist/chunks/{images-B9MqcjSA.js → images-Edar3eU1.js} +2 -2
  8. package/dist/chunks/{images-B9MqcjSA.js.map → images-Edar3eU1.js.map} +1 -1
  9. package/dist/chunks/{request-metadata-D5NrT8ks.js → request-metadata-DaIYq4Ea.js} +214 -179
  10. package/dist/chunks/request-metadata-DaIYq4Ea.js.map +1 -0
  11. package/dist/chunks/{streaming-BQ1bykDo.js → streaming-D0fGAuiO.js} +5 -5
  12. package/dist/chunks/streaming-D0fGAuiO.js.map +1 -0
  13. package/dist/chunks/{testing-B_iMFYz_.js → testing-CRflAw4X.js} +1228 -1074
  14. package/dist/chunks/testing-CRflAw4X.js.map +1 -0
  15. package/dist/data/cache.d.ts +8 -0
  16. package/dist/data/cache.d.ts.map +1 -1
  17. package/dist/data/idempotency.d.ts.map +1 -1
  18. package/dist/data/index.d.ts +3 -1
  19. package/dist/data/index.d.ts.map +1 -1
  20. package/dist/data/testing.d.ts.map +1 -1
  21. package/dist/data/testing.js +94 -72
  22. package/dist/data/testing.js.map +1 -1
  23. package/dist/data/tiered.d.ts +7 -0
  24. package/dist/data/tiered.d.ts.map +1 -0
  25. package/dist/document/deferred-scripts.d.ts.map +1 -1
  26. package/dist/document/hydration.d.ts.map +1 -1
  27. package/dist/document/index.d.ts +1 -0
  28. package/dist/document/index.d.ts.map +1 -1
  29. package/dist/document/trusted-types.d.ts +10 -0
  30. package/dist/document/trusted-types.d.ts.map +1 -0
  31. package/dist/edge/index.js +3 -3
  32. package/dist/index.d.ts +6 -6
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +768 -432
  35. package/dist/index.js.map +1 -1
  36. package/dist/internal/testing.js +9 -9
  37. package/dist/kv/index.d.ts +2 -0
  38. package/dist/kv/index.d.ts.map +1 -1
  39. package/dist/kv/index.js +71 -48
  40. package/dist/kv/index.js.map +1 -1
  41. package/dist/kv/rate-limit-store.d.ts +8 -0
  42. package/dist/kv/rate-limit-store.d.ts.map +1 -0
  43. package/dist/kv/store.d.ts.map +1 -1
  44. package/dist/node/http.d.ts.map +1 -1
  45. package/dist/node/image.d.ts.map +1 -1
  46. package/dist/node/index.d.ts.map +1 -1
  47. package/dist/node/index.js +3 -3
  48. package/dist/node/index.js.map +1 -1
  49. package/dist/node/static.d.ts.map +1 -1
  50. package/dist/node/streaming.d.ts.map +1 -1
  51. package/dist/platform/images.d.ts.map +1 -1
  52. package/dist/platform/index.d.ts +5 -1
  53. package/dist/platform/index.d.ts.map +1 -1
  54. package/dist/platform/observability.d.ts +2 -1
  55. package/dist/platform/observability.d.ts.map +1 -1
  56. package/dist/platform/web-vitals-client.d.ts +32 -0
  57. package/dist/platform/web-vitals-client.d.ts.map +1 -0
  58. package/dist/platform/web-vitals.d.ts +78 -0
  59. package/dist/platform/web-vitals.d.ts.map +1 -0
  60. package/dist/redis/index.d.ts +2 -0
  61. package/dist/redis/index.d.ts.map +1 -1
  62. package/dist/redis/index.js +84 -46
  63. package/dist/redis/index.js.map +1 -1
  64. package/dist/redis/rate-limit-store.d.ts +8 -0
  65. package/dist/redis/rate-limit-store.d.ts.map +1 -0
  66. package/dist/redis/store.d.ts.map +1 -1
  67. package/dist/route/action.d.ts.map +1 -1
  68. package/dist/route/middleware.d.ts.map +1 -1
  69. package/dist/route/response.d.ts.map +1 -1
  70. package/dist/router/index.d.ts +1 -1
  71. package/dist/router/index.d.ts.map +1 -1
  72. package/dist/router/manifest.d.ts +1 -0
  73. package/dist/router/manifest.d.ts.map +1 -1
  74. package/dist/security/cookies.d.ts +26 -0
  75. package/dist/security/cookies.d.ts.map +1 -0
  76. package/dist/security/env.d.ts.map +1 -1
  77. package/dist/security/fetch-metadata.d.ts +29 -0
  78. package/dist/security/fetch-metadata.d.ts.map +1 -0
  79. package/dist/security/index.d.ts +5 -1
  80. package/dist/security/index.d.ts.map +1 -1
  81. package/dist/security/policy.d.ts.map +1 -1
  82. package/dist/security/rate-limit.d.ts +6 -3
  83. package/dist/security/rate-limit.d.ts.map +1 -1
  84. package/dist/security/request.d.ts.map +1 -1
  85. package/dist/security/types.d.ts +72 -3
  86. package/dist/security/types.d.ts.map +1 -1
  87. package/dist/server/index.d.ts +1 -1
  88. package/dist/server/index.d.ts.map +1 -1
  89. package/dist/server/request-handler.d.ts +6 -2
  90. package/dist/server/request-handler.d.ts.map +1 -1
  91. package/dist/static/index.d.ts.map +1 -1
  92. package/dist/static/index.js +4 -4
  93. package/dist/static/index.js.map +1 -1
  94. package/dist/vite/index.d.ts +3 -0
  95. package/dist/vite/index.d.ts.map +1 -1
  96. package/dist/vite/index.js +1030 -666
  97. package/dist/vite/index.js.map +1 -1
  98. package/dist/vite/plugin.d.ts +2 -0
  99. package/dist/vite/plugin.d.ts.map +1 -1
  100. package/dist/vite/policy-verification.d.ts.map +1 -1
  101. package/dist/vite/route-audit.d.ts +50 -0
  102. package/dist/vite/route-audit.d.ts.map +1 -0
  103. package/package.json +1 -1
  104. package/dist/chunks/request-metadata-D5NrT8ks.js.map +0 -1
  105. package/dist/chunks/streaming-BQ1bykDo.js.map +0 -1
  106. package/dist/chunks/testing-B_iMFYz_.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sources":["../../src/adapter/testing.ts"],"sourcesContent":["import { mkdtemp, readFile, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n verifyCacheStoreContract,\n verifyCacheStoreRefreshContract,\n type CacheStoreFactory,\n} from \"../data/testing\";\nimport type { Adapter, AdapterCapability } from \"./index\";\n\ntype MaybePromise<T> = T | Promise<T>;\n\n// A host answers a protocol upgrade before it has an HTTP response body, and\n// the Response constructor rejects status 101. A handshake is reported as a\n// plain status and header pair instead.\nexport type AdapterUpgradeResult = {\n headers?: Record<string, string>;\n status: number;\n};\n\nexport type AdapterBackgroundHost = {\n shutdown: () => Promise<void>;\n waitUntil: (promise: Promise<unknown>) => void;\n};\n\nexport type AdapterStaticOutputEntry = {\n file: string;\n headers: Record<string, string>;\n pathname: string;\n};\n\nexport type AdapterStaticOutput = {\n entries: readonly AdapterStaticOutputEntry[];\n};\n\n// One probe per capability. A probe runs the adapter through the smallest\n// deployment that can prove the capability, and the contract reads the result.\n// Every probe may be called more than once.\nexport type AdapterContractProbes = {\n backgroundLifetime?: () => MaybePromise<AdapterBackgroundHost>;\n crossOriginIsolationHeaders?: () => MaybePromise<Response>;\n nonceInjection?: () => MaybePromise<Response>;\n sharedCache?: CacheStoreFactory;\n staticOutput?: (outDir: string) => MaybePromise<AdapterStaticOutput>;\n streaming?: () => MaybePromise<Response>;\n webSocket?: (request: Request) => MaybePromise<AdapterUpgradeResult>;\n webTransport?: (request: Request) => MaybePromise<AdapterUpgradeResult>;\n};\n\nexport const adapterContractOrigin = \"https://adapter.contract.test\";\n\nconst foreignOrigin = \"https://foreign.contract.test\";\n\nconst contractCapabilities = [\n \"backgroundLifetime\",\n \"crossOriginIsolationHeaders\",\n \"nonceInjection\",\n \"sharedCache\",\n \"staticOutput\",\n \"streaming\",\n \"webSocket\",\n \"webTransport\",\n] as const satisfies readonly AdapterCapability[];\n\nconst noncePattern = /'nonce-([^']+)'/;\n\nexport async function verifyAdapterContract(\n adapter: Adapter,\n probes: AdapterContractProbes,\n) {\n assert(\n typeof adapter.name === \"string\" && adapter.name.length > 0,\n \"an adapter must carry a name\",\n );\n\n for (const capability of contractCapabilities) {\n const declared = adapter.capabilities[capability];\n const probe = probes[capability];\n\n assert(\n typeof declared === \"boolean\",\n `an adapter must declare capability \"${capability}\" as a boolean`,\n );\n assert(\n !declared || Boolean(probe),\n `capability \"${capability}\" is declared true and needs a probe that proves it`,\n );\n assert(\n declared || !probe,\n `capability \"${capability}\" has a probe but the adapter declares it false`,\n );\n }\n\n if (probes.backgroundLifetime) {\n await verifyBackgroundLifetimeContract(probes.backgroundLifetime);\n }\n\n if (probes.crossOriginIsolationHeaders) {\n await verifyCrossOriginIsolationContract(probes.crossOriginIsolationHeaders);\n }\n\n if (probes.nonceInjection) {\n await verifyNonceInjectionContract(probes.nonceInjection);\n }\n\n if (probes.sharedCache) {\n await verifySharedCacheContract(probes.sharedCache);\n }\n\n if (probes.staticOutput) {\n await verifyStaticOutputContract(probes.staticOutput);\n }\n\n if (probes.streaming) {\n await verifyStreamingContract(probes.streaming);\n }\n\n if (probes.webSocket) {\n await verifyUpgradeContract(probes.webSocket, \"websocket\", 101);\n }\n\n if (probes.webTransport) {\n await verifyUpgradeContract(probes.webTransport, \"webtransport\", 200);\n }\n}\n\n// A streamed body reaches the client before the render finishes, so it arrives\n// as more than one chunk. A buffered response fails this, and a client that\n// disconnects early must not fault the host.\nasync function verifyStreamingContract(\n probe: NonNullable<AdapterContractProbes[\"streaming\"]>,\n) {\n const response = await probe();\n\n assert(\n Boolean(response.body),\n \"a streaming response must expose a readable body stream\",\n );\n\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n let chunks = 0;\n let body = \"\";\n\n while (true) {\n const chunk = await reader.read();\n\n if (chunk.done) {\n break;\n }\n\n chunks += 1;\n body += decoder.decode(chunk.value, { stream: true });\n }\n\n body += decoder.decode();\n\n assert(body.length > 0, \"a streaming response must deliver a body\");\n assert(\n chunks > 1,\n \"a streaming response must deliver its body in more than one chunk\",\n );\n\n const cancelled = await probe();\n\n try {\n await cancelled.body?.cancel();\n } catch (error) {\n throw contractError(\n `a streaming response body must accept cancellation, and it failed with ${describe(error)}`,\n );\n }\n}\n\n// A nonce is single use. Two responses cannot share one, the document must\n// carry the value the header declares, and a shared cache must not store it.\nasync function verifyNonceInjectionContract(\n probe: NonNullable<AdapterContractProbes[\"nonceInjection\"]>,\n) {\n const first = await probe();\n const second = await probe();\n const firstNonce = readCspNonce(first);\n const secondNonce = readCspNonce(second);\n\n await second.body?.cancel();\n\n assert(\n Boolean(firstNonce),\n \"a nonce-injecting response must declare a content-security-policy nonce source\",\n );\n assert(\n Boolean(secondNonce) && secondNonce !== firstNonce,\n \"every response must carry a freshly generated nonce\",\n );\n\n const document = await first.text();\n\n assert(\n document.includes(`nonce=\"${firstNonce}\"`),\n \"the rendered document must carry the nonce its header declares\",\n );\n\n const cacheControl = (first.headers.get(\"cache-control\") ?? \"\").toLowerCase();\n\n assert(\n cacheControl.includes(\"no-store\") || cacheControl.includes(\"private\"),\n \"a nonce-backed document must not be a shared cache representation\",\n );\n}\n\n// Cross-origin isolation needs both headers to survive the host response path.\n// A resource policy travels with them and keeps the isolated document from\n// leaking as a subresource.\nasync function verifyCrossOriginIsolationContract(\n probe: NonNullable<AdapterContractProbes[\"crossOriginIsolationHeaders\"]>,\n) {\n const response = await probe();\n const embedderPolicy = response.headers.get(\"cross-origin-embedder-policy\");\n\n await response.body?.cancel();\n\n assert(\n response.headers.get(\"cross-origin-opener-policy\") === \"same-origin\",\n \"an isolated response must carry cross-origin-opener-policy same-origin\",\n );\n assert(\n embedderPolicy === \"require-corp\" || embedderPolicy === \"credentialless\",\n \"an isolated response must carry cross-origin-embedder-policy require-corp or credentialless\",\n );\n assert(\n Boolean(response.headers.get(\"cross-origin-resource-policy\")),\n \"an isolated response must carry a cross-origin-resource-policy\",\n );\n}\n\n// Background work outlives the response that started it. Shutdown waits for\n// that work, a failed task never fails shutdown, and a repeated shutdown is\n// safe.\nasync function verifyBackgroundLifetimeContract(\n probe: NonNullable<AdapterContractProbes[\"backgroundLifetime\"]>,\n) {\n const host = await probe();\n let release = () => {};\n let settled = false;\n const work = new Promise<void>((resolveWork) => {\n release = () => {\n settled = true;\n resolveWork();\n };\n });\n\n host.waitUntil(work);\n\n let stopped = false;\n const shutdown = host.shutdown().then(() => {\n stopped = true;\n });\n\n await tick();\n\n assert(\n !stopped,\n \"shutdown must not complete while background work is pending\",\n );\n\n release();\n await shutdown;\n\n assert(\n settled && stopped,\n \"shutdown must complete once background work settles\",\n );\n\n const failingHost = await probe();\n failingHost.waitUntil(\n Promise.reject(new Error(\"Adapter contract background failure.\")),\n );\n\n try {\n await failingHost.shutdown();\n await failingHost.shutdown();\n } catch (error) {\n throw contractError(\n `failed background work must not fail shutdown, and it failed with ${describe(error)}`,\n );\n }\n}\n\n// A shared cache is the framework cache store contract seen from a host. The\n// refresh contract applies to a store that publishes the lease methods.\nasync function verifySharedCacheContract(\n probe: NonNullable<AdapterContractProbes[\"sharedCache\"]>,\n) {\n await verifyCacheStoreContract(probe);\n\n const store = await probe();\n\n if (store.acquireRefreshLease) {\n await verifyCacheStoreRefreshContract(probe);\n }\n}\n\n// A build artifact is a file on disk, and it holds no per-request state. The\n// same build run twice writes the same files, which is what makes a deployment\n// reproducible.\nasync function verifyStaticOutputContract(\n probe: NonNullable<AdapterContractProbes[\"staticOutput\"]>,\n) {\n const outDir = await mkdtemp(join(tmpdir(), \"demiurge-adapter-contract-\"));\n\n try {\n const first = await probe(outDir);\n\n assert(\n first.entries.length > 0,\n \"a static build must produce at least one output entry\",\n );\n\n const files = new Set<string>();\n const pathnames = new Set<string>();\n const contents = new Map<string, string>();\n\n for (const entry of first.entries) {\n const file = JSON.stringify(entry.file);\n\n assert(\n isContainedFile(entry.file),\n `every output file must stay inside the output directory, and ${file} does not`,\n );\n assert(\n !files.has(entry.file),\n `every output entry must own one file, and ${file} repeats`,\n );\n assert(\n !pathnames.has(entry.pathname),\n `every output entry must own one pathname, and ${JSON.stringify(entry.pathname)} repeats`,\n );\n\n files.add(entry.file);\n pathnames.add(entry.pathname);\n\n const headers = lowercaseHeaders(entry.headers);\n\n assert(\n headers[\"set-cookie\"] === undefined,\n `a build artifact must not carry per-request state, and ${file} sets a cookie`,\n );\n assert(\n !noncePattern.test(headers[\"content-security-policy\"] ?? \"\"),\n `a build artifact must not depend on a per-request nonce, and ${file} declares one`,\n );\n\n contents.set(entry.file, await readOutputFile(outDir, entry.file));\n }\n\n const second = await probe(outDir);\n\n assert(\n JSON.stringify(second.entries) === JSON.stringify(first.entries),\n \"a repeated static build must produce the same output entries\",\n );\n\n for (const entry of second.entries) {\n assert(\n await readOutputFile(outDir, entry.file) === contents.get(entry.file),\n `a repeated static build must write the same file, and ${JSON.stringify(entry.file)} changed`,\n );\n }\n } finally {\n await rm(outDir, { force: true, recursive: true });\n }\n}\n\n// A host that accepts a connection upgrade also has to refuse one. An origin\n// it does not own and a request without the upgrade never reach the accepted\n// handshake.\nasync function verifyUpgradeContract(\n probe: (request: Request) => MaybePromise<AdapterUpgradeResult>,\n protocol: \"websocket\" | \"webtransport\",\n acceptedStatus: 101 | 200,\n) {\n const accepted = await probe(\n upgradeRequest(protocol, adapterContractOrigin),\n );\n const negotiated = lowercaseHeaders(accepted.headers ?? {}).upgrade;\n\n assert(\n accepted.status === acceptedStatus,\n `an accepted ${protocol} handshake must answer with status ${acceptedStatus}`,\n );\n assert(\n negotiated?.toLowerCase() === protocol,\n `an accepted ${protocol} handshake must confirm the negotiated protocol`,\n );\n\n const foreign = await probe(upgradeRequest(protocol, foreignOrigin));\n\n assert(\n foreign.status >= 400 && foreign.status < 500,\n `a ${protocol} handshake from an origin the host does not own must be refused`,\n );\n\n const plain = await probe(\n new Request(`${adapterContractOrigin}/`, {\n headers: { origin: adapterContractOrigin },\n }),\n );\n\n assert(\n plain.status !== acceptedStatus,\n `a request without a ${protocol} upgrade must not receive a handshake`,\n );\n}\n\nfunction upgradeRequest(protocol: string, origin: string) {\n return new Request(`${adapterContractOrigin}/`, {\n headers: {\n connection: \"Upgrade\",\n origin,\n upgrade: protocol,\n },\n });\n}\n\nfunction readCspNonce(response: Response) {\n const csp = response.headers.get(\"content-security-policy\") ?? \"\";\n\n return noncePattern.exec(csp)?.[1];\n}\n\nasync function readOutputFile(outDir: string, file: string) {\n try {\n return await readFile(join(outDir, file), \"utf8\");\n } catch {\n throw contractError(\n `every output entry must write its file, and ${JSON.stringify(file)} is missing`,\n );\n }\n}\n\nfunction isContainedFile(file: string) {\n return Boolean(file) &&\n !file.startsWith(\"/\") &&\n !file.startsWith(\"\\\\\") &&\n !file.split(/[\\\\/]/).includes(\"..\");\n}\n\nfunction lowercaseHeaders(headers: Record<string, string>) {\n return Object.fromEntries(\n Object.entries(headers).map(([name, value]) => [\n name.toLowerCase(),\n value,\n ]),\n ) as Record<string, string | undefined>;\n}\n\nfunction tick() {\n return new Promise((resolveTick) => setTimeout(resolveTick, 0));\n}\n\nfunction describe(error: unknown) {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction assert(condition: boolean, requirement: string): asserts condition {\n if (!condition) {\n throw contractError(requirement);\n }\n}\n\nfunction contractError(requirement: string) {\n return new Error(`Adapter contract failed: ${requirement}.`);\n}\n"],"names":["adapterContractOrigin","foreignOrigin","contractCapabilities","noncePattern","verifyAdapterContract","adapter","probes","assert","capability","declared","probe","verifyBackgroundLifetimeContract","verifyCrossOriginIsolationContract","verifyNonceInjectionContract","verifySharedCacheContract","verifyStaticOutputContract","verifyStreamingContract","verifyUpgradeContract","response","reader","decoder","chunks","body","chunk","cancelled","error","contractError","describe","first","second","firstNonce","readCspNonce","secondNonce","document","cacheControl","embedderPolicy","host","release","settled","work","resolveWork","stopped","shutdown","tick","failingHost","verifyCacheStoreContract","verifyCacheStoreRefreshContract","outDir","mkdtemp","join","tmpdir","files","pathnames","contents","entry","file","isContainedFile","headers","lowercaseHeaders","readOutputFile","rm","protocol","acceptedStatus","accepted","upgradeRequest","negotiated","foreign","plain","origin","csp","readFile","name","value","resolveTick","condition","requirement"],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","sources":["../../src/adapter/testing.ts"],"sourcesContent":["import { mkdtemp, readFile, rm } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n verifyCacheStoreContract,\n verifyCacheStoreRefreshContract,\n type CacheStoreFactory,\n} from \"../data/testing\";\nimport type { Adapter, AdapterCapability } from \"./index\";\n\ntype MaybePromise<T> = T | Promise<T>;\n\n// A host answers a protocol upgrade before it has an HTTP response body, and\n// the Response constructor rejects status 101. A handshake is reported as a\n// plain status and header pair instead.\nexport type AdapterUpgradeResult = {\n headers?: Record<string, string>;\n status: number;\n};\n\nexport type AdapterBackgroundHost = {\n shutdown: () => Promise<void>;\n waitUntil: (promise: Promise<unknown>) => void;\n};\n\nexport type AdapterStaticOutputEntry = {\n file: string;\n headers: Record<string, string>;\n pathname: string;\n};\n\nexport type AdapterStaticOutput = {\n entries: readonly AdapterStaticOutputEntry[];\n};\n\n// One probe per capability. A probe runs the adapter through the smallest\n// deployment that can prove the capability, and the contract reads the result.\n// Every probe may be called more than once.\nexport type AdapterContractProbes = {\n backgroundLifetime?: () => MaybePromise<AdapterBackgroundHost>;\n crossOriginIsolationHeaders?: () => MaybePromise<Response>;\n nonceInjection?: () => MaybePromise<Response>;\n sharedCache?: CacheStoreFactory;\n staticOutput?: (outDir: string) => MaybePromise<AdapterStaticOutput>;\n streaming?: () => MaybePromise<Response>;\n webSocket?: (request: Request) => MaybePromise<AdapterUpgradeResult>;\n webTransport?: (request: Request) => MaybePromise<AdapterUpgradeResult>;\n};\n\nexport const adapterContractOrigin = \"https://adapter.contract.test\";\n\nconst foreignOrigin = \"https://foreign.contract.test\";\n\nconst contractCapabilities = [\n \"backgroundLifetime\",\n \"crossOriginIsolationHeaders\",\n \"nonceInjection\",\n \"sharedCache\",\n \"staticOutput\",\n \"streaming\",\n \"webSocket\",\n \"webTransport\",\n] as const satisfies readonly AdapterCapability[];\n\nconst noncePattern = /'nonce-([^']+)'/;\n\nexport async function verifyAdapterContract(\n adapter: Adapter,\n probes: AdapterContractProbes,\n) {\n assert(\n typeof adapter.name === \"string\" && adapter.name.length > 0,\n \"an adapter must carry a name\",\n );\n\n for (const capability of contractCapabilities) {\n const declared = adapter.capabilities[capability];\n const probe = probes[capability];\n\n assert(\n typeof declared === \"boolean\",\n `an adapter must declare capability \"${capability}\" as a boolean`,\n );\n assert(\n !declared || Boolean(probe),\n `capability \"${capability}\" is declared true and needs a probe that proves it`,\n );\n assert(\n declared || !probe,\n `capability \"${capability}\" has a probe but the adapter declares it false`,\n );\n }\n\n if (probes.backgroundLifetime) {\n await verifyBackgroundLifetimeContract(probes.backgroundLifetime);\n }\n\n if (probes.crossOriginIsolationHeaders) {\n await verifyCrossOriginIsolationContract(probes.crossOriginIsolationHeaders);\n }\n\n if (probes.nonceInjection) {\n await verifyNonceInjectionContract(probes.nonceInjection);\n }\n\n if (probes.sharedCache) {\n await verifySharedCacheContract(probes.sharedCache);\n }\n\n if (probes.staticOutput) {\n await verifyStaticOutputContract(probes.staticOutput);\n }\n\n if (probes.streaming) {\n await verifyStreamingContract(probes.streaming);\n }\n\n if (probes.webSocket) {\n await verifyUpgradeContract(probes.webSocket, \"websocket\", 101);\n }\n\n if (probes.webTransport) {\n await verifyUpgradeContract(probes.webTransport, \"webtransport\", 200);\n }\n}\n\n// A streamed body reaches the client before the render finishes, so it arrives\n// as more than one chunk. A buffered response fails this, and a client that\n// disconnects early must not fault the host.\nasync function verifyStreamingContract(\n probe: NonNullable<AdapterContractProbes[\"streaming\"]>,\n) {\n const response = await probe();\n\n assert(\n Boolean(response.body),\n \"a streaming response must expose a readable body stream\",\n );\n\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n let chunks = 0;\n let body = \"\";\n\n while (true) {\n const chunk = await reader.read();\n\n if (chunk.done) {\n break;\n }\n\n chunks += 1;\n body += decoder.decode(chunk.value, { stream: true });\n }\n\n body += decoder.decode();\n\n assert(body.length > 0, \"a streaming response must deliver a body\");\n assert(\n chunks > 1,\n \"a streaming response must deliver its body in more than one chunk\",\n );\n\n const cancelled = await probe();\n\n try {\n await cancelled.body?.cancel();\n } catch (error) {\n throw contractError(\n `a streaming response body must accept cancellation, and it failed with ${describe(error)}`,\n );\n }\n}\n\n// A nonce is single use. Two responses cannot share one, the document must\n// carry the value the header declares, and a shared cache must not store it.\nasync function verifyNonceInjectionContract(\n probe: NonNullable<AdapterContractProbes[\"nonceInjection\"]>,\n) {\n const first = await probe();\n const second = await probe();\n const firstNonce = readCspNonce(first);\n const secondNonce = readCspNonce(second);\n\n await second.body?.cancel();\n\n assert(\n Boolean(firstNonce),\n \"a nonce-injecting response must declare a content-security-policy nonce source\",\n );\n assert(\n Boolean(secondNonce) && secondNonce !== firstNonce,\n \"every response must carry a freshly generated nonce\",\n );\n\n const document = await first.text();\n\n assert(\n document.includes(`nonce=\"${firstNonce}\"`),\n \"the rendered document must carry the nonce its header declares\",\n );\n\n const cacheControl = (first.headers.get(\"cache-control\") ?? \"\").toLowerCase();\n\n assert(\n cacheControl.includes(\"no-store\") || cacheControl.includes(\"private\"),\n \"a nonce-backed document must not be a shared cache representation\",\n );\n}\n\n// Cross-origin isolation needs both headers to survive the host response path.\n// A resource policy travels with them and keeps the isolated document from\n// leaking as a subresource.\nasync function verifyCrossOriginIsolationContract(\n probe: NonNullable<AdapterContractProbes[\"crossOriginIsolationHeaders\"]>,\n) {\n const response = await probe();\n const embedderPolicy = response.headers.get(\"cross-origin-embedder-policy\");\n\n await response.body?.cancel();\n\n assert(\n response.headers.get(\"cross-origin-opener-policy\") === \"same-origin\",\n \"an isolated response must carry cross-origin-opener-policy same-origin\",\n );\n assert(\n embedderPolicy === \"require-corp\" || embedderPolicy === \"credentialless\",\n \"an isolated response must carry cross-origin-embedder-policy require-corp or credentialless\",\n );\n assert(\n Boolean(response.headers.get(\"cross-origin-resource-policy\")),\n \"an isolated response must carry a cross-origin-resource-policy\",\n );\n}\n\n// Background work outlives the response that started it. Shutdown waits for\n// that work, a failed task never fails shutdown, and a repeated shutdown is\n// safe.\nasync function verifyBackgroundLifetimeContract(\n probe: NonNullable<AdapterContractProbes[\"backgroundLifetime\"]>,\n) {\n const host = await probe();\n let release = () => {};\n let settled = false;\n const work = new Promise<void>((resolveWork) => {\n release = () => {\n settled = true;\n resolveWork();\n };\n });\n\n host.waitUntil(work);\n\n let stopped = false;\n const shutdown = host.shutdown().then(() => {\n stopped = true;\n });\n\n await tick();\n\n assert(\n !stopped,\n \"shutdown must not complete while background work is pending\",\n );\n\n release();\n await shutdown;\n\n assert(\n settled && stopped,\n \"shutdown must complete once background work settles\",\n );\n\n const failingHost = await probe();\n failingHost.waitUntil(\n Promise.reject(new Error(\"Adapter contract background failure.\")),\n );\n\n try {\n await failingHost.shutdown();\n await failingHost.shutdown();\n } catch (error) {\n throw contractError(\n `failed background work must not fail shutdown, and it failed with ${describe(error)}`,\n );\n }\n}\n\n// A shared cache is the framework cache store contract seen from a host. The\n// refresh contract applies to a store that publishes the lease methods.\nasync function verifySharedCacheContract(\n probe: NonNullable<AdapterContractProbes[\"sharedCache\"]>,\n) {\n await verifyCacheStoreContract(probe);\n\n const store = await probe();\n\n if (store.acquireRefreshLease) {\n await verifyCacheStoreRefreshContract(probe);\n }\n}\n\n// A build artifact is a file on disk, and it holds no per-request state. The\n// same build run twice writes the same files, which is what makes a deployment\n// reproducible.\nasync function verifyStaticOutputContract(\n probe: NonNullable<AdapterContractProbes[\"staticOutput\"]>,\n) {\n const outDir = await mkdtemp(join(tmpdir(), \"demiurge-adapter-contract-\"));\n\n try {\n const first = await probe(outDir);\n\n assert(\n first.entries.length > 0,\n \"a static build must produce at least one output entry\",\n );\n\n const files = new Set<string>();\n const pathnames = new Set<string>();\n const contents = new Map<string, string>();\n\n for (const entry of first.entries) {\n const file = JSON.stringify(entry.file);\n\n assert(\n isContainedFile(entry.file),\n `every output file must stay inside the output directory, and ${file} does not`,\n );\n assert(\n !files.has(entry.file),\n `every output entry must own one file, and ${file} repeats`,\n );\n assert(\n !pathnames.has(entry.pathname),\n `every output entry must own one pathname, and ${JSON.stringify(entry.pathname)} repeats`,\n );\n\n files.add(entry.file);\n pathnames.add(entry.pathname);\n\n const headers = lowercaseHeaders(entry.headers);\n\n assert(\n headers[\"set-cookie\"] === undefined,\n `a build artifact must not carry per-request state, and ${file} sets a cookie`,\n );\n assert(\n !noncePattern.test(headers[\"content-security-policy\"] ?? \"\"),\n `a build artifact must not depend on a per-request nonce, and ${file} declares one`,\n );\n\n contents.set(entry.file, await readOutputFile(outDir, entry.file));\n }\n\n const second = await probe(outDir);\n\n assert(\n JSON.stringify(second.entries) === JSON.stringify(first.entries),\n \"a repeated static build must produce the same output entries\",\n );\n\n for (const entry of second.entries) {\n assert(\n await readOutputFile(outDir, entry.file) === contents.get(entry.file),\n `a repeated static build must write the same file, and ${JSON.stringify(entry.file)} changed`,\n );\n }\n } finally {\n await rm(outDir, { force: true, recursive: true });\n }\n}\n\n// A host that accepts a connection upgrade also has to refuse one. An origin\n// it does not own and a request without the upgrade never reach the accepted\n// handshake.\nasync function verifyUpgradeContract(\n probe: (request: Request) => MaybePromise<AdapterUpgradeResult>,\n protocol: \"websocket\" | \"webtransport\",\n acceptedStatus: 101 | 200,\n) {\n const accepted = await probe(\n upgradeRequest(protocol, adapterContractOrigin),\n );\n const negotiated = lowercaseHeaders(accepted.headers ?? {}).upgrade;\n\n assert(\n accepted.status === acceptedStatus,\n `an accepted ${protocol} handshake must answer with status ${acceptedStatus}`,\n );\n assert(\n negotiated?.toLowerCase() === protocol,\n `an accepted ${protocol} handshake must confirm the negotiated protocol`,\n );\n\n const foreign = await probe(upgradeRequest(protocol, foreignOrigin));\n\n assert(\n foreign.status >= 400 && foreign.status < 500,\n `a ${protocol} handshake from an origin the host does not own must be refused`,\n );\n\n const plain = await probe(\n new Request(`${adapterContractOrigin}/`, {\n headers: { origin: adapterContractOrigin },\n }),\n );\n\n assert(\n plain.status !== acceptedStatus,\n `a request without a ${protocol} upgrade must not receive a handshake`,\n );\n}\n\nfunction upgradeRequest(protocol: string, origin: string) {\n return new Request(`${adapterContractOrigin}/`, {\n headers: {\n connection: \"Upgrade\",\n origin,\n upgrade: protocol,\n },\n });\n}\n\nfunction readCspNonce(response: Response) {\n const csp = response.headers.get(\"content-security-policy\") ?? \"\";\n\n return noncePattern.exec(csp)?.[1];\n}\n\nasync function readOutputFile(outDir: string, file: string) {\n try {\n return await readFile(join(outDir, file), \"utf8\");\n } catch {\n throw contractError(\n `every output entry must write its file, and ${JSON.stringify(file)} is missing`,\n );\n }\n}\n\nfunction isContainedFile(file: string) {\n return Boolean(file) &&\n !file.startsWith(\"/\") &&\n !file.startsWith(\"\\\\\") &&\n !file.split(/[\\\\/]/).includes(\"..\");\n}\n\nfunction lowercaseHeaders(headers: Record<string, string>) {\n // TYPE-EVIDENCE: Object.fromEntries builds a record with string keys and string values. The cast widens the values to allow undefined.\n return Object.fromEntries(\n Object.entries(headers).map(([name, value]) => [\n name.toLowerCase(),\n value,\n ]),\n ) as Record<string, string | undefined>;\n}\n\nfunction tick() {\n return new Promise((resolveTick) => setTimeout(resolveTick, 0));\n}\n\nfunction describe(error: unknown) {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction assert(condition: boolean, requirement: string): asserts condition {\n if (!condition) {\n throw contractError(requirement);\n }\n}\n\nfunction contractError(requirement: string) {\n return new Error(`Adapter contract failed: ${requirement}.`);\n}\n"],"names":["adapterContractOrigin","foreignOrigin","contractCapabilities","noncePattern","verifyAdapterContract","adapter","probes","assert","capability","declared","probe","verifyBackgroundLifetimeContract","verifyCrossOriginIsolationContract","verifyNonceInjectionContract","verifySharedCacheContract","verifyStaticOutputContract","verifyStreamingContract","verifyUpgradeContract","response","reader","decoder","chunks","body","chunk","cancelled","error","contractError","describe","first","second","firstNonce","readCspNonce","secondNonce","document","cacheControl","embedderPolicy","host","release","settled","work","resolveWork","stopped","shutdown","tick","failingHost","verifyCacheStoreContract","verifyCacheStoreRefreshContract","outDir","mkdtemp","join","tmpdir","files","pathnames","contents","entry","file","isContainedFile","headers","lowercaseHeaders","readOutputFile","rm","protocol","acceptedStatus","accepted","upgradeRequest","negotiated","foreign","plain","origin","csp","readFile","name","value","resolveTick","condition","requirement"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"file-router.d.ts","sourceRoot":"","sources":["../../src/browser/file-router.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEb,SAAS,EAQV,MAAM,OAAO,CAAC;AACf,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EAIL,KAAK,aAAa,EAElB,KAAK,aAAa,EACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EAUL,KAAK,iBAAiB,EACvB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAQ,KAAK,OAAO,EAAmB,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAE9E,MAAM,MAAM,oBAAoB,GAAG,CACjC,OAAO,EAAE,OAAO,KACb,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAE/B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,UAAU,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACtC,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,QAAQ,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;CACzC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,4FA8H1D;AA0BD,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,iBAAiB,EACjB,cAAc,CACf,GAAG;IACF,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,iBAoDxE;AAED,KAAK,iBAAiB,GAAG,IAAI,CAC3B,cAAc,kBAAkB,CAAC,EACjC,YAAY,GAAG,aAAa,CAC7B,CAAC;AAEF,KAAK,oBAAoB,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG;IACvD,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,qBAiBjE;AAED,wBAAgB,IAAI,CAAC,KAAK,CAAC,GAAG,SAAS,OAAO,EAC5C,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAsBF;AA4LD,KAAK,SAAS,GAAG;IACf,UAAU,EAAE,UAAU,GAAG,QAAQ,CAAC;IAClC,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC"}
1
+ {"version":3,"file":"file-router.d.ts","sourceRoot":"","sources":["../../src/browser/file-router.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEb,SAAS,EAQV,MAAM,OAAO,CAAC;AACf,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EAIL,KAAK,aAAa,EAElB,KAAK,aAAa,EACnB,MAAM,UAAU,CAAC;AAClB,OAAO,EAUL,KAAK,iBAAiB,EACvB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAQ,KAAK,OAAO,EAAmB,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAE9E,MAAM,MAAM,oBAAoB,GAAG,CACjC,OAAO,EAAE,OAAO,KACb,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAE/B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,UAAU,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACtC,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,QAAQ,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;CACzC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,4FA8H1D;AA0BD,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,iBAAiB,EACjB,cAAc,CACf,GAAG;IACF,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,iBAoDxE;AAED,KAAK,iBAAiB,GAAG,IAAI,CAC3B,cAAc,kBAAkB,CAAC,EACjC,YAAY,GAAG,aAAa,CAC7B,CAAC;AAEF,KAAK,oBAAoB,GAAG,OAAO,CAAC,iBAAiB,CAAC,GAAG;IACvD,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,oBAAoB,qBAkBjE;AAED,wBAAgB,IAAI,CAAC,KAAK,CAAC,GAAG,SAAS,OAAO,EAC5C,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAuBF;AA+LD,KAAK,SAAS,GAAG;IACf,UAAU,EAAE,UAAU,GAAG,QAAQ,CAAC;IAClC,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { r as l } from "./request-metadata-D5NrT8ks.js";
1
+ import { r as l } from "./request-metadata-DaIYq4Ea.js";
2
2
  const t = '<template data-demiurge-stream-root=""></template>';
3
3
  async function i(r, e) {
4
4
  const s = l({
@@ -32,4 +32,4 @@ async function i(r, e) {
32
32
  export {
33
33
  i as c
34
34
  };
35
- //# sourceMappingURL=document-shell-D1xo6cEA.js.map
35
+ //# sourceMappingURL=document-shell-WoM8yZlR.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"document-shell-D1xo6cEA.js","sources":["../../src/server/document-shell.ts"],"sourcesContent":["import { renderDocumentShell } from \"../document/render\";\nimport type { ScriptTag } from \"../document/scripts\";\nimport type { LoadedRouteMatch } from \"../router\";\nimport type { SsrRenderOptions } from \"./ssr\";\n\n// A streaming render sends the document head before the body exists. A\n// transform that rewrites the document therefore needs a placeholder where the\n// streamed body will go, and it has to leave that placeholder in place.\nexport const STREAM_ROOT_MARKER =\n '<template data-demiurge-stream-root=\"\"></template>';\n\nexport type DocumentShell = {\n prefix: string;\n suffix: string;\n};\n\nexport async function createDocumentShell(\n match: LoadedRouteMatch,\n options: SsrRenderOptions & { scripts?: ScriptTag[] },\n): Promise<DocumentShell> {\n const shell = renderDocumentShell({\n body: { data: match.data, navigation: options.navigation },\n entrySrc: options.clientEntry,\n lang: options.lang,\n links: match.links,\n metadata: match.metadata,\n nonce: options.nonce,\n scripts: options.scripts ?? match.scripts,\n styles: options.styles,\n title: options.title,\n });\n\n if (!options.transformDocument) {\n return shell;\n }\n\n const transformed = await options.transformDocument(\n `${shell.prefix}${STREAM_ROOT_MARKER}${shell.suffix}`,\n );\n const markerIndex = transformed.indexOf(STREAM_ROOT_MARKER);\n\n if (\n markerIndex === -1 ||\n transformed.indexOf(\n STREAM_ROOT_MARKER,\n markerIndex + STREAM_ROOT_MARKER.length,\n ) !== -1\n ) {\n throw new Error(\n \"Demiurge streaming document transform must preserve the root marker exactly once.\",\n );\n }\n\n return {\n prefix: transformed.slice(0, markerIndex),\n suffix: transformed.slice(markerIndex + STREAM_ROOT_MARKER.length),\n };\n}\n"],"names":["STREAM_ROOT_MARKER","createDocumentShell","match","options","shell","renderDocumentShell","transformed","markerIndex"],"mappings":";AAQO,MAAMA,IACX;AAOF,eAAsBC,EACpBC,GACAC,GACwB;AACxB,QAAMC,IAAQC,EAAoB;AAAA,IAChC,MAAM,EAAE,MAAMH,EAAM,MAAM,YAAYC,EAAQ,WAAA;AAAA,IAC9C,UAAUA,EAAQ;AAAA,IAClB,MAAMA,EAAQ;AAAA,IACd,OAAOD,EAAM;AAAA,IACb,UAAUA,EAAM;AAAA,IAChB,OAAOC,EAAQ;AAAA,IACf,SAASA,EAAQ,WAAWD,EAAM;AAAA,IAClC,QAAQC,EAAQ;AAAA,IAChB,OAAOA,EAAQ;AAAA,EAAA,CAChB;AAED,MAAI,CAACA,EAAQ;AACX,WAAOC;AAGT,QAAME,IAAc,MAAMH,EAAQ;AAAA,IAChC,GAAGC,EAAM,MAAM,GAAGJ,CAAkB,GAAGI,EAAM,MAAM;AAAA,EAAA,GAE/CG,IAAcD,EAAY,QAAQN,CAAkB;AAE1D,MACEO,MAAgB,MAChBD,EAAY;AAAA,IACVN;AAAA,IACAO,IAAcP,EAAmB;AAAA,EAAA,MAC7B;AAEN,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,SAAO;AAAA,IACL,QAAQM,EAAY,MAAM,GAAGC,CAAW;AAAA,IACxC,QAAQD,EAAY,MAAMC,IAAcP,EAAmB,MAAM;AAAA,EAAA;AAErE;"}
1
+ {"version":3,"file":"document-shell-WoM8yZlR.js","sources":["../../src/server/document-shell.ts"],"sourcesContent":["import { renderDocumentShell } from \"../document/render\";\nimport type { ScriptTag } from \"../document/scripts\";\nimport type { LoadedRouteMatch } from \"../router\";\nimport type { SsrRenderOptions } from \"./ssr\";\n\n// A streaming render sends the document head before the body exists. A\n// transform that rewrites the document therefore needs a placeholder where the\n// streamed body will go, and it has to leave that placeholder in place.\nexport const STREAM_ROOT_MARKER =\n '<template data-demiurge-stream-root=\"\"></template>';\n\nexport type DocumentShell = {\n prefix: string;\n suffix: string;\n};\n\nexport async function createDocumentShell(\n match: LoadedRouteMatch,\n options: SsrRenderOptions & { scripts?: ScriptTag[] },\n): Promise<DocumentShell> {\n const shell = renderDocumentShell({\n body: { data: match.data, navigation: options.navigation },\n entrySrc: options.clientEntry,\n lang: options.lang,\n links: match.links,\n metadata: match.metadata,\n nonce: options.nonce,\n scripts: options.scripts ?? match.scripts,\n styles: options.styles,\n title: options.title,\n });\n\n if (!options.transformDocument) {\n return shell;\n }\n\n const transformed = await options.transformDocument(\n `${shell.prefix}${STREAM_ROOT_MARKER}${shell.suffix}`,\n );\n const markerIndex = transformed.indexOf(STREAM_ROOT_MARKER);\n\n if (\n markerIndex === -1 ||\n transformed.indexOf(\n STREAM_ROOT_MARKER,\n markerIndex + STREAM_ROOT_MARKER.length,\n ) !== -1\n ) {\n throw new Error(\n \"Demiurge streaming document transform must preserve the root marker exactly once.\",\n );\n }\n\n return {\n prefix: transformed.slice(0, markerIndex),\n suffix: transformed.slice(markerIndex + STREAM_ROOT_MARKER.length),\n };\n}\n"],"names":["STREAM_ROOT_MARKER","createDocumentShell","match","options","shell","renderDocumentShell","transformed","markerIndex"],"mappings":";AAQO,MAAMA,IACX;AAOF,eAAsBC,EACpBC,GACAC,GACwB;AACxB,QAAMC,IAAQC,EAAoB;AAAA,IAChC,MAAM,EAAE,MAAMH,EAAM,MAAM,YAAYC,EAAQ,WAAA;AAAA,IAC9C,UAAUA,EAAQ;AAAA,IAClB,MAAMA,EAAQ;AAAA,IACd,OAAOD,EAAM;AAAA,IACb,UAAUA,EAAM;AAAA,IAChB,OAAOC,EAAQ;AAAA,IACf,SAASA,EAAQ,WAAWD,EAAM;AAAA,IAClC,QAAQC,EAAQ;AAAA,IAChB,OAAOA,EAAQ;AAAA,EAAA,CAChB;AAED,MAAI,CAACA,EAAQ;AACX,WAAOC;AAGT,QAAME,IAAc,MAAMH,EAAQ;AAAA,IAChC,GAAGC,EAAM,MAAM,GAAGJ,CAAkB,GAAGI,EAAM,MAAM;AAAA,EAAA,GAE/CG,IAAcD,EAAY,QAAQN,CAAkB;AAE1D,MACEO,MAAgB,MAChBD,EAAY;AAAA,IACVN;AAAA,IACAO,IAAcP,EAAmB;AAAA,EAAA,MAC7B;AAEN,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAIJ,SAAO;AAAA,IACL,QAAQM,EAAY,MAAM,GAAGC,CAAW;AAAA,IACxC,QAAQD,EAAY,MAAMC,IAAcP,EAAmB,MAAM;AAAA,EAAA;AAErE;"}
@@ -1,7 +1,7 @@
1
1
  import { createHash as d } from "node:crypto";
2
2
  import { readFile as h, mkdir as p, writeFile as F } from "node:fs/promises";
3
3
  import { resolve as u, join as w, relative as b, sep as $ } from "node:path";
4
- import { d as v, f as S, a as N, r as E, b as J, c as O, i as j } from "./images-B9MqcjSA.js";
4
+ import { d as v, f as S, a as N, r as E, b as J, c as O, i as j } from "./images-Edar3eU1.js";
5
5
  const y = 5 * 1024 * 1024;
6
6
  async function H(e) {
7
7
  const t = e.fonts ?? [];
@@ -138,4 +138,4 @@ export {
138
138
  H as r,
139
139
  q as t
140
140
  };
141
- //# sourceMappingURL=image-codec-BGTx_t5K.js.map
141
+ //# sourceMappingURL=image-codec-Di53WpcH.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-codec-BGTx_t5K.js","sources":["../../src/platform/font-assets.ts","../../src/platform/image-codec.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\nimport {\n defaultFontPath,\n fontAssetFileName,\n fontFormat,\n fontMediaType,\n fontStylesheetUrl,\n renderFontFaceCss,\n type FontContribution,\n type FontDefinition,\n} from \"./fonts\";\n\nexport type FontAssetFetch = (\n input: string,\n init?: { signal?: AbortSignal },\n) => Promise<Response>;\n\nexport type FontAssetOptions = {\n basePath?: string;\n // Where a downloaded font file stays between builds. A build that already\n // downloaded a font reads it back and makes no network request.\n cacheDir?: string;\n fetch?: FontAssetFetch;\n fonts?: FontContribution;\n // The project directory that every local font source resolves against.\n root: string;\n};\n\nexport type FontAsset = {\n body: Uint8Array;\n contentType: string;\n // The output path, relative to the directory that the build publishes.\n file: string;\n url: string;\n};\n\nconst maximumFontBytes = 5 * 1024 * 1024;\n\n// One font declaration becomes one published file plus one stylesheet. The\n// build writes these files, and the Node handler serves the same set from\n// memory. Both derive every URL from the declaration, so they cannot drift.\nexport async function resolveFontAssets(\n options: FontAssetOptions,\n): Promise<FontAsset[]> {\n const fonts = options.fonts ?? [];\n\n if (fonts.length === 0) {\n return [];\n }\n\n const basePath = options.basePath ?? defaultFontPath;\n const root = resolve(options.root);\n const cacheDir = resolve(\n options.cacheDir ?? join(root, \"node_modules\", \".demiurge\", \"fonts\"),\n );\n const load = options.fetch ?? ((input, init) => fetch(input, init));\n const published = new Map<string, string>();\n const assets: FontAsset[] = [];\n\n for (const definition of fonts) {\n if (!definition.selfHost) {\n continue;\n }\n\n const name = fontAssetFileName(definition);\n const claimed = published.get(name);\n\n if (claimed !== undefined) {\n if (claimed !== definition.src) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} declares two sources that publish the same file ${JSON.stringify(name)}. Give one of them a different weight or style.`,\n );\n }\n\n continue;\n }\n\n published.set(name, definition.src);\n assets.push({\n body: await readFontBody(definition, { cacheDir, load, root }),\n contentType: fontMediaType(definition.src),\n file: outputFile(basePath, name),\n url: `${basePath}/${name}`,\n });\n }\n\n const stylesheet = `${renderFontFaceCss(fonts, basePath)}\\n`;\n\n assets.push({\n body: new TextEncoder().encode(stylesheet),\n contentType: \"text/css; charset=utf-8\",\n file: outputFile(basePath, \"fonts.css\"),\n url: fontStylesheetUrl(basePath),\n });\n\n return assets;\n}\n\nasync function readFontBody(\n definition: FontDefinition,\n context: { cacheDir: string; load: FontAssetFetch; root: string },\n) {\n const body = definition.source === \"google\"\n ? await downloadFontFile(definition, context)\n : await readLocalFontFile(definition, context.root);\n\n if (body.byteLength === 0) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} resolved to an empty file.`,\n );\n }\n\n if (body.byteLength > maximumFontBytes) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} is larger than the ${maximumFontBytes} byte limit.`,\n );\n }\n\n return body;\n}\n\nasync function readLocalFontFile(definition: FontDefinition, root: string) {\n const file = resolve(root, definition.src.replace(/^\\/+/, \"\"));\n\n if (relative(root, file).split(sep).includes(\"..\")) {\n throw new Error(\n `Font source escaped the project directory: ${JSON.stringify(definition.src)}.`,\n );\n }\n\n try {\n return new Uint8Array(await readFile(file));\n } catch (error) {\n throw new Error(\n `Demiurge could not read the font file ${JSON.stringify(definition.src)} for family ${JSON.stringify(definition.family)}.`,\n { cause: error },\n );\n }\n}\n\n// A downloaded font stays in the cache directory. The first build reaches the\n// font host once, and every later build reads the file from disk. A build that\n// runs without network access therefore keeps working.\nasync function downloadFontFile(\n definition: FontDefinition,\n context: { cacheDir: string; load: FontAssetFetch; root: string },\n) {\n const digest = createHash(\"sha256\").update(definition.src).digest(\"hex\");\n const cacheFile = join(\n context.cacheDir,\n `${digest.slice(0, 32)}.${fontFormat(definition.src)}`,\n );\n\n try {\n return new Uint8Array(await readFile(cacheFile));\n } catch {\n // The cache holds no copy yet, so the font host answers this once.\n }\n\n let response: Response;\n\n try {\n response = await context.load(definition.src);\n } catch (error) {\n throw new Error(\n `Demiurge could not download the font for family ${JSON.stringify(definition.family)} from ${JSON.stringify(definition.src)}.`,\n { cause: error },\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `The font host answered ${response.status} for family ${JSON.stringify(definition.family)} at ${JSON.stringify(definition.src)}.`,\n );\n }\n\n const body = new Uint8Array(await response.arrayBuffer());\n\n await mkdir(context.cacheDir, { recursive: true });\n await writeFile(cacheFile, body);\n\n return body;\n}\n\nfunction outputFile(basePath: string, name: string) {\n return `${basePath.replace(/^\\/+/, \"\")}/${name}`;\n}\n","import type { ImageFormat } from \"./images\";\nimport { imageSourceExtension, type ImageVariantDescriptor } from \"./image-url\";\n\nexport type EncodedImage = {\n body: Uint8Array;\n contentType: string;\n};\n\ntype SharpModule = {\n default: (input: Uint8Array) => SharpPipeline;\n};\n\ntype SharpPipeline = {\n avif: (options: { quality: number }) => SharpPipeline;\n jpeg: (options: { quality: number }) => SharpPipeline;\n png: () => SharpPipeline;\n resize: (options: {\n width: number;\n withoutEnlargement: boolean;\n }) => SharpPipeline;\n toBuffer: () => Promise<Buffer>;\n webp: (options: { quality: number }) => SharpPipeline;\n};\n\nconst contentTypes: Record<Exclude<ImageFormat, \"auto\">, string> = {\n avif: \"image/avif\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n webp: \"image/webp\",\n};\n\nconst defaultQuality = 75;\n\nexport function imageContentType(format: Exclude<ImageFormat, \"auto\">) {\n return contentTypes[format];\n}\n\n// `auto` lets the browser take the smallest format that it accepts. A client\n// that sends no usable `accept` header keeps the source format.\nexport function negotiateImageFormat(\n descriptor: ImageVariantDescriptor,\n accept: string | null,\n): Exclude<ImageFormat, \"auto\"> {\n if (descriptor.format !== \"auto\") {\n return descriptor.format;\n }\n\n const accepted = (accept ?? \"\").toLowerCase();\n\n if (accepted.includes(\"image/avif\")) {\n return \"avif\";\n }\n\n if (accepted.includes(\"image/webp\")) {\n return \"webp\";\n }\n\n return sourceFormat(descriptor.src);\n}\n\nexport function sourceFormat(src: string): Exclude<ImageFormat, \"auto\"> {\n const extension = imageSourceExtension(src);\n\n if (extension === \"avif\") {\n return \"avif\";\n }\n\n if (extension === \"webp\") {\n return \"webp\";\n }\n\n if (extension === \"png\") {\n return \"png\";\n }\n\n return \"jpeg\";\n}\n\nexport async function transformImage(\n source: Uint8Array,\n options: {\n format: Exclude<ImageFormat, \"auto\">;\n quality?: number;\n width: number;\n },\n): Promise<EncodedImage> {\n const sharp = await loadImageCodec();\n const quality = options.quality ?? defaultQuality;\n const resized = sharp(source).resize({\n width: options.width,\n withoutEnlargement: true,\n });\n\n return {\n body: new Uint8Array(await encode(resized, options.format, quality)),\n contentType: contentTypes[options.format],\n };\n}\n\nfunction encode(\n pipeline: SharpPipeline,\n format: Exclude<ImageFormat, \"auto\">,\n quality: number,\n) {\n if (format === \"png\") {\n return pipeline.png().toBuffer();\n }\n\n if (format === \"jpeg\") {\n return pipeline.jpeg({ quality }).toBuffer();\n }\n\n if (format === \"avif\") {\n return pipeline.avif({ quality }).toBuffer();\n }\n\n return pipeline.webp({ quality }).toBuffer();\n}\n\n// `sharp` is an optional peer dependency. Only an application that optimizes\n// an image installs it, the same rule that ADR 0006 states for a host\n// adapter dependency.\nasync function loadImageCodec() {\n let module: SharpModule;\n\n try {\n module = (await import(\"sharp\")) as unknown as SharpModule;\n } catch (error) {\n throw new Error(\n \"Demiurge image optimization requires the optional peer dependency sharp. Install sharp in the application.\",\n { cause: error },\n );\n }\n\n return module.default;\n}\n"],"names":["maximumFontBytes","resolveFontAssets","options","fonts","basePath","defaultFontPath","root","resolve","cacheDir","join","load","input","init","published","assets","definition","name","fontAssetFileName","claimed","readFontBody","fontMediaType","outputFile","stylesheet","renderFontFaceCss","fontStylesheetUrl","context","body","downloadFontFile","readLocalFontFile","file","relative","sep","readFile","error","digest","createHash","cacheFile","fontFormat","response","mkdir","writeFile","contentTypes","defaultQuality","negotiateImageFormat","descriptor","accept","accepted","sourceFormat","src","extension","imageSourceExtension","transformImage","source","sharp","loadImageCodec","quality","resized","encode","pipeline","format","module"],"mappings":"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"}
1
+ {"version":3,"file":"image-codec-Di53WpcH.js","sources":["../../src/platform/font-assets.ts","../../src/platform/image-codec.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\nimport {\n defaultFontPath,\n fontAssetFileName,\n fontFormat,\n fontMediaType,\n fontStylesheetUrl,\n renderFontFaceCss,\n type FontContribution,\n type FontDefinition,\n} from \"./fonts\";\n\nexport type FontAssetFetch = (\n input: string,\n init?: { signal?: AbortSignal },\n) => Promise<Response>;\n\nexport type FontAssetOptions = {\n basePath?: string;\n // Where a downloaded font file stays between builds. A build that already\n // downloaded a font reads it back and makes no network request.\n cacheDir?: string;\n fetch?: FontAssetFetch;\n fonts?: FontContribution;\n // The project directory that every local font source resolves against.\n root: string;\n};\n\nexport type FontAsset = {\n body: Uint8Array;\n contentType: string;\n // The output path, relative to the directory that the build publishes.\n file: string;\n url: string;\n};\n\nconst maximumFontBytes = 5 * 1024 * 1024;\n\n// One font declaration becomes one published file plus one stylesheet. The\n// build writes these files, and the Node handler serves the same set from\n// memory. Both derive every URL from the declaration, so they cannot drift.\nexport async function resolveFontAssets(\n options: FontAssetOptions,\n): Promise<FontAsset[]> {\n const fonts = options.fonts ?? [];\n\n if (fonts.length === 0) {\n return [];\n }\n\n const basePath = options.basePath ?? defaultFontPath;\n const root = resolve(options.root);\n const cacheDir = resolve(\n options.cacheDir ?? join(root, \"node_modules\", \".demiurge\", \"fonts\"),\n );\n const load = options.fetch ?? ((input, init) => fetch(input, init));\n const published = new Map<string, string>();\n const assets: FontAsset[] = [];\n\n for (const definition of fonts) {\n if (!definition.selfHost) {\n continue;\n }\n\n const name = fontAssetFileName(definition);\n const claimed = published.get(name);\n\n if (claimed !== undefined) {\n if (claimed !== definition.src) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} declares two sources that publish the same file ${JSON.stringify(name)}. Give one of them a different weight or style.`,\n );\n }\n\n continue;\n }\n\n published.set(name, definition.src);\n assets.push({\n body: await readFontBody(definition, { cacheDir, load, root }),\n contentType: fontMediaType(definition.src),\n file: outputFile(basePath, name),\n url: `${basePath}/${name}`,\n });\n }\n\n const stylesheet = `${renderFontFaceCss(fonts, basePath)}\\n`;\n\n assets.push({\n body: new TextEncoder().encode(stylesheet),\n contentType: \"text/css; charset=utf-8\",\n file: outputFile(basePath, \"fonts.css\"),\n url: fontStylesheetUrl(basePath),\n });\n\n return assets;\n}\n\nasync function readFontBody(\n definition: FontDefinition,\n context: { cacheDir: string; load: FontAssetFetch; root: string },\n) {\n const body = definition.source === \"google\"\n ? await downloadFontFile(definition, context)\n : await readLocalFontFile(definition, context.root);\n\n if (body.byteLength === 0) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} resolved to an empty file.`,\n );\n }\n\n if (body.byteLength > maximumFontBytes) {\n throw new Error(\n `Font family ${JSON.stringify(definition.family)} is larger than the ${maximumFontBytes} byte limit.`,\n );\n }\n\n return body;\n}\n\nasync function readLocalFontFile(definition: FontDefinition, root: string) {\n const file = resolve(root, definition.src.replace(/^\\/+/, \"\"));\n\n if (relative(root, file).split(sep).includes(\"..\")) {\n throw new Error(\n `Font source escaped the project directory: ${JSON.stringify(definition.src)}.`,\n );\n }\n\n try {\n return new Uint8Array(await readFile(file));\n } catch (error) {\n throw new Error(\n `Demiurge could not read the font file ${JSON.stringify(definition.src)} for family ${JSON.stringify(definition.family)}.`,\n { cause: error },\n );\n }\n}\n\n// A downloaded font stays in the cache directory. The first build reaches the\n// font host once, and every later build reads the file from disk. A build that\n// runs without network access therefore keeps working.\nasync function downloadFontFile(\n definition: FontDefinition,\n context: { cacheDir: string; load: FontAssetFetch; root: string },\n) {\n const digest = createHash(\"sha256\").update(definition.src).digest(\"hex\");\n const cacheFile = join(\n context.cacheDir,\n `${digest.slice(0, 32)}.${fontFormat(definition.src)}`,\n );\n\n try {\n return new Uint8Array(await readFile(cacheFile));\n } catch {\n // The cache holds no copy yet, so the font host answers this once.\n }\n\n let response: Response;\n\n try {\n response = await context.load(definition.src);\n } catch (error) {\n throw new Error(\n `Demiurge could not download the font for family ${JSON.stringify(definition.family)} from ${JSON.stringify(definition.src)}.`,\n { cause: error },\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `The font host answered ${response.status} for family ${JSON.stringify(definition.family)} at ${JSON.stringify(definition.src)}.`,\n );\n }\n\n const body = new Uint8Array(await response.arrayBuffer());\n\n await mkdir(context.cacheDir, { recursive: true });\n await writeFile(cacheFile, body);\n\n return body;\n}\n\nfunction outputFile(basePath: string, name: string) {\n return `${basePath.replace(/^\\/+/, \"\")}/${name}`;\n}\n","import type { ImageFormat } from \"./images\";\nimport { imageSourceExtension, type ImageVariantDescriptor } from \"./image-url\";\n\nexport type EncodedImage = {\n body: Uint8Array;\n contentType: string;\n};\n\ntype SharpModule = {\n default: (input: Uint8Array) => SharpPipeline;\n};\n\ntype SharpPipeline = {\n avif: (options: { quality: number }) => SharpPipeline;\n jpeg: (options: { quality: number }) => SharpPipeline;\n png: () => SharpPipeline;\n resize: (options: {\n width: number;\n withoutEnlargement: boolean;\n }) => SharpPipeline;\n toBuffer: () => Promise<Buffer>;\n webp: (options: { quality: number }) => SharpPipeline;\n};\n\nconst contentTypes: Record<Exclude<ImageFormat, \"auto\">, string> = {\n avif: \"image/avif\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n webp: \"image/webp\",\n};\n\nconst defaultQuality = 75;\n\nexport function imageContentType(format: Exclude<ImageFormat, \"auto\">) {\n return contentTypes[format];\n}\n\n// `auto` lets the browser take the smallest format that it accepts. A client\n// that sends no usable `accept` header keeps the source format.\nexport function negotiateImageFormat(\n descriptor: ImageVariantDescriptor,\n accept: string | null,\n): Exclude<ImageFormat, \"auto\"> {\n if (descriptor.format !== \"auto\") {\n return descriptor.format;\n }\n\n const accepted = (accept ?? \"\").toLowerCase();\n\n if (accepted.includes(\"image/avif\")) {\n return \"avif\";\n }\n\n if (accepted.includes(\"image/webp\")) {\n return \"webp\";\n }\n\n return sourceFormat(descriptor.src);\n}\n\nexport function sourceFormat(src: string): Exclude<ImageFormat, \"auto\"> {\n const extension = imageSourceExtension(src);\n\n if (extension === \"avif\") {\n return \"avif\";\n }\n\n if (extension === \"webp\") {\n return \"webp\";\n }\n\n if (extension === \"png\") {\n return \"png\";\n }\n\n return \"jpeg\";\n}\n\nexport async function transformImage(\n source: Uint8Array,\n options: {\n format: Exclude<ImageFormat, \"auto\">;\n quality?: number;\n width: number;\n },\n): Promise<EncodedImage> {\n const sharp = await loadImageCodec();\n const quality = options.quality ?? defaultQuality;\n const resized = sharp(source).resize({\n width: options.width,\n withoutEnlargement: true,\n });\n\n return {\n body: new Uint8Array(await encode(resized, options.format, quality)),\n contentType: contentTypes[options.format],\n };\n}\n\nfunction encode(\n pipeline: SharpPipeline,\n format: Exclude<ImageFormat, \"auto\">,\n quality: number,\n) {\n if (format === \"png\") {\n return pipeline.png().toBuffer();\n }\n\n if (format === \"jpeg\") {\n return pipeline.jpeg({ quality }).toBuffer();\n }\n\n if (format === \"avif\") {\n return pipeline.avif({ quality }).toBuffer();\n }\n\n return pipeline.webp({ quality }).toBuffer();\n}\n\n// `sharp` is an optional peer dependency. Only an application that optimizes\n// an image installs it, the same rule that ADR 0006 states for a host\n// adapter dependency.\nasync function loadImageCodec() {\n let module: SharpModule;\n\n try {\n // TYPE-EVIDENCE: sharp ships no types. The local type describes the methods the codec calls.\n module = (await import(\"sharp\")) as SharpModule;\n } catch (error) {\n throw new Error(\n \"Demiurge image optimization requires the optional peer dependency sharp. Install sharp in the application.\",\n { cause: error },\n );\n }\n\n return module.default;\n}\n"],"names":["maximumFontBytes","resolveFontAssets","options","fonts","basePath","defaultFontPath","root","resolve","cacheDir","join","load","input","init","published","assets","definition","name","fontAssetFileName","claimed","readFontBody","fontMediaType","outputFile","stylesheet","renderFontFaceCss","fontStylesheetUrl","context","body","downloadFontFile","readLocalFontFile","file","relative","sep","readFile","error","digest","createHash","cacheFile","fontFormat","response","mkdir","writeFile","contentTypes","defaultQuality","negotiateImageFormat","descriptor","accept","accepted","sourceFormat","src","extension","imageSourceExtension","transformImage","source","sharp","loadImageCodec","quality","resized","encode","pipeline","format","module"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { p as q } from "./request-metadata-D5NrT8ks.js";
1
+ import { p as q } from "./request-metadata-DaIYq4Ea.js";
2
2
  const u = "/_demiurge/font", j = {
3
3
  otf: "otf",
4
4
  ttf: "ttf",
@@ -336,4 +336,4 @@ export {
336
336
  F as s,
337
337
  X as t
338
338
  };
339
- //# sourceMappingURL=images-B9MqcjSA.js.map
339
+ //# sourceMappingURL=images-Edar3eU1.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"images-B9MqcjSA.js","sources":["../../src/platform/fonts.ts","../../src/platform/image-url.ts","../../src/platform/images.ts"],"sourcesContent":["import { preload } from \"../document/links\";\nimport type { LinkTag } from \"../document\";\n\nexport type FontStyle = \"normal\" | \"italic\" | \"oblique\";\n\nexport type FontFormat = \"otf\" | \"ttf\" | \"woff\" | \"woff2\";\n\nexport type FontDefinition = {\n display: \"auto\" | \"block\" | \"fallback\" | \"optional\" | \"swap\";\n family: string;\n kind: \"font\";\n // A self-hosted font is published from the application origin, so `'self'`\n // covers it. A font left on a third-party host stays a `font-src` entry and\n // a privacy question, and `fontSources` makes that cost visible.\n selfHost: boolean;\n source: \"google\" | \"local\";\n src: string;\n style: FontStyle;\n weight: number | `${number} ${number}`;\n};\n\nexport type LocalFontOptions = {\n display?: FontDefinition[\"display\"];\n name: string;\n src: string;\n style?: FontStyle;\n weight?: FontDefinition[\"weight\"];\n};\n\nexport type GoogleFontOptions = {\n display?: FontDefinition[\"display\"];\n family: string;\n selfHost?: boolean;\n src: string;\n style?: FontStyle;\n weight?: FontDefinition[\"weight\"];\n};\n\nexport type FontContribution = readonly FontDefinition[];\n\n// The framework publishes every self-hosted font under one path. The build\n// writes the files, and the Node handler serves the same URLs at request time.\nexport const defaultFontPath = \"/_demiurge/font\";\n\nconst fontFormats: Record<string, FontFormat> = {\n otf: \"otf\",\n ttf: \"ttf\",\n woff: \"woff\",\n woff2: \"woff2\",\n};\n\nconst fontFaceFormats: Record<FontFormat, string> = {\n otf: \"opentype\",\n ttf: \"truetype\",\n woff: \"woff\",\n woff2: \"woff2\",\n};\n\nconst fontMediaTypes: Record<FontFormat, string> = {\n otf: \"font/otf\",\n ttf: \"font/ttf\",\n woff: \"font/woff\",\n woff2: \"font/woff2\",\n};\n\nexport const font = {\n // A Google font needs the file URL that the Google stylesheet points at.\n // The framework reads no third-party stylesheet, because that request is\n // the privacy cost self-hosting removes.\n google(options: GoogleFontOptions): FontDefinition {\n const src = options.src.trim();\n\n if (!isRemoteSource(src)) {\n throw new Error(\n `Google font ${JSON.stringify(options.family)} needs an https font file URL as its source.`,\n );\n }\n\n assertKnownFormat(src);\n\n return {\n display: options.display ?? \"swap\",\n family: options.family,\n kind: \"font\",\n selfHost: options.selfHost ?? true,\n source: \"google\",\n src,\n style: options.style ?? \"normal\",\n weight: options.weight ?? 400,\n };\n },\n // A local font names a file in the project. The build reads that file and\n // publishes it, so the file does not have to sit in the public directory.\n local(options: LocalFontOptions): FontDefinition {\n const src = options.src.trim();\n\n if (!src) {\n throw new Error(\"Local font source must not be empty.\");\n }\n\n if (isRemoteSource(src)) {\n throw new Error(\n `Local font ${JSON.stringify(options.name)} must name a project file rather than a URL.`,\n );\n }\n\n assertKnownFormat(src);\n\n return {\n display: options.display ?? \"swap\",\n family: options.name,\n kind: \"font\",\n selfHost: true,\n source: \"local\",\n src,\n style: options.style ?? \"normal\",\n weight: options.weight ?? 400,\n };\n },\n};\n\nexport function defineFonts(fonts: FontContribution): FontContribution {\n return [...fonts];\n}\n\n// The published file name derives from the declaration alone. The renderer\n// and the build therefore agree on every URL without sharing any state.\nexport function fontAssetFileName(definition: FontDefinition) {\n const family = slug(definition.family);\n const weight = slug(String(definition.weight));\n\n return `${family}-${weight}-${definition.style}.${fontFormat(definition.src)}`;\n}\n\nexport function fontAssetUrl(\n definition: FontDefinition,\n basePath: string = defaultFontPath,\n) {\n return definition.selfHost\n ? `${basePath}/${fontAssetFileName(definition)}`\n : definition.src;\n}\n\nexport function fontStylesheetUrl(basePath: string = defaultFontPath) {\n return `${basePath}/fonts.css`;\n}\n\nexport function fontMediaType(file: string) {\n return fontMediaTypes[fontFormat(file)];\n}\n\nexport function fontFormat(file: string): FontFormat {\n const name = file.split(\"?\")[0]!.split(\"#\")[0]!.split(\"/\").at(-1) ?? \"\";\n const extension = name.includes(\".\")\n ? name.split(\".\").at(-1)!.toLowerCase()\n : \"\";\n const format = fontFormats[extension];\n\n if (!format) {\n throw new Error(\n `Font source ${JSON.stringify(file)} has no known font extension.`,\n );\n }\n\n return format;\n}\n\n// The stylesheet and the preload links are the whole document contribution.\n// An application spreads them into `links` in its root layout.\nexport function fontLinks(\n fonts: FontContribution,\n basePath: string = defaultFontPath,\n): LinkTag[] {\n if (fonts.length === 0) {\n return [];\n }\n\n return [\n {\n href: fontStylesheetUrl(basePath),\n kind: \"link\" as const,\n rel: \"stylesheet\",\n },\n ...fontPreloadLinks(fonts, basePath),\n ];\n}\n\nexport function fontPreloadLinks(\n fonts: FontContribution,\n basePath: string = defaultFontPath,\n): LinkTag[] {\n return fonts.map((definition) =>\n preload(fontAssetUrl(definition, basePath), {\n as: \"font\",\n // A font request runs in CORS mode even on the same origin. A preload\n // without this attribute fetches the file twice.\n crossOrigin: \"anonymous\",\n type: fontMediaType(definition.src),\n })\n );\n}\n\nexport function renderFontFaceCss(\n fonts: FontContribution,\n basePath: string = defaultFontPath,\n) {\n return fonts\n .map((definition) => {\n const url = fontAssetUrl(definition, basePath);\n\n return [\n \"@font-face {\",\n ` font-family: \"${escapeCss(definition.family)}\";`,\n ` src: url(\"${escapeCss(url)}\") format(\"${\n fontFaceFormats[fontFormat(definition.src)]\n }\");`,\n ` font-style: ${definition.style};`,\n ` font-weight: ${definition.weight};`,\n ` font-display: ${definition.display};`,\n \"}\",\n ].join(\"\\n\");\n })\n .join(\"\\n\");\n}\n\n// A self-hosted font set collapses to `'self'`, which every framework preset\n// already declares. A third-party host has to be named here instead.\nexport function fontSources(fonts: FontContribution): string[] {\n const origins = new Set<string>();\n\n for (const definition of fonts) {\n if (!definition.selfHost) {\n origins.add(new URL(definition.src).origin);\n }\n }\n\n return [\"'self'\", ...[...origins].sort()];\n}\n\nfunction isRemoteSource(src: string) {\n return /^https?:\\/\\//i.test(src);\n}\n\nfunction assertKnownFormat(src: string) {\n fontFormat(src);\n}\n\nfunction slug(value: string) {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\") || \"font\";\n}\n\nfunction escapeCss(value: string) {\n return value.replace(/[\\\\\"]/g, \"\\\\$&\");\n}\n","import type { ImageFormat } from \"./images\";\n\nexport type ImageSourceKind = \"local\" | \"remote\";\n\n// The descriptor is the complete input of one variant. The build pipeline and\n// the runtime optimizer both derive a file name and an encoded body from it.\nexport type ImageVariantDescriptor = {\n format: ImageFormat;\n quality?: number;\n sourceKind: ImageSourceKind;\n src: string;\n width: number;\n};\n\nexport const defaultImageOptimizerPath = \"/_demiurge/image\";\n\nconst formatExtensions: Record<Exclude<ImageFormat, \"auto\">, string> = {\n avif: \"avif\",\n jpeg: \"jpg\",\n png: \"png\",\n webp: \"webp\",\n};\n\nconst extensionFormats: Record<string, Exclude<ImageFormat, \"auto\">> = {\n avif: \"avif\",\n jpeg: \"jpeg\",\n jpg: \"jpeg\",\n png: \"png\",\n webp: \"webp\",\n};\n\nconst variantNamePattern = /^(.+)\\.w(\\d{1,5})(?:\\.q(\\d{1,3}))?\\.([a-z0-9]+)$/;\n\nexport function createImageOptimizerUrl(\n descriptor: ImageVariantDescriptor,\n optimizerPath: string,\n) {\n const params = new URLSearchParams({\n src: descriptor.src,\n w: String(descriptor.width),\n });\n\n if (descriptor.quality !== undefined) {\n params.set(\"q\", String(descriptor.quality));\n }\n\n if (descriptor.format !== \"auto\") {\n params.set(\"f\", descriptor.format);\n }\n\n return `${optimizerPath}?${params.toString()}`;\n}\n\n// A static build has no optimizer process, so the variant path must describe\n// the complete transform. The build reads the path back and emits the file,\n// and no shared state has to cross the build and render boundary.\nexport function createImageVariantPath(\n descriptor: ImageVariantDescriptor,\n optimizerPath: string,\n) {\n if (descriptor.sourceKind === \"remote\") {\n throw new Error(\n `Image source ${JSON.stringify(descriptor.src)} is remote. A static image loader can only emit a local image.`,\n );\n }\n\n const quality = descriptor.quality === undefined\n ? \"\"\n : `.q${descriptor.quality}`;\n\n return `${optimizerPath}${descriptor.src}.w${descriptor.width}${quality}.${\n resolveVariantExtension(descriptor)\n }`;\n}\n\nexport function parseImageVariantPath(\n pathname: string,\n optimizerPath: string,\n): ImageVariantDescriptor | undefined {\n if (!pathname.startsWith(`${optimizerPath}/`)) {\n return undefined;\n }\n\n const match = variantNamePattern.exec(pathname.slice(optimizerPath.length));\n\n if (!match) {\n return undefined;\n }\n\n const [, src, rawWidth, rawQuality, extension] = match;\n const format = extensionFormats[extension!];\n const width = Number(rawWidth);\n const quality = rawQuality === undefined ? undefined : Number(rawQuality);\n\n if (\n !format ||\n !imageSourceExtension(src!) ||\n width <= 0 ||\n (quality !== undefined && (quality < 1 || quality > 100))\n ) {\n return undefined;\n }\n\n return { format, quality, sourceKind: \"local\", src: src!, width };\n}\n\nexport function resolveVariantExtension(descriptor: ImageVariantDescriptor) {\n if (descriptor.format !== \"auto\") {\n return formatExtensions[descriptor.format];\n }\n\n const extension = imageSourceExtension(descriptor.src);\n\n if (!extension) {\n throw new Error(\n `Image source ${JSON.stringify(descriptor.src)} has no known image extension. Declare an explicit format.`,\n );\n }\n\n return extension === \"jpeg\" ? \"jpg\" : extension;\n}\n\nexport function imageSourceExtension(src: string) {\n const name = src.split(\"?\")[0]!.split(\"#\")[0]!.split(\"/\").at(-1) ?? \"\";\n const extension = name.includes(\".\")\n ? name.split(\".\").at(-1)!.toLowerCase()\n : \"\";\n\n return extension in extensionFormats ? extension : undefined;\n}\n\n// The build and the development server both find variants by reading the\n// documents that the application rendered. A variant that no document\n// references is a variant that nothing can request.\nexport function collectImageVariantPaths(html: string, optimizerPath: string) {\n const pattern = new RegExp(\n `${escapeRegExp(optimizerPath)}/[^\"'\\\\s,)<>]+`,\n \"g\",\n );\n\n return [...new Set(html.match(pattern) ?? [])];\n}\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","import {\n createImageOptimizerUrl,\n createImageVariantPath,\n defaultImageOptimizerPath,\n type ImageVariantDescriptor,\n} from \"./image-url\";\n\n// `optimizer` points every variant at a request-time endpoint. `static`\n// points every variant at a file that the static build emits.\nexport type ImageLoader = \"optimizer\" | \"static\";\n\nexport type ImageFormat = \"auto\" | \"avif\" | \"webp\" | \"jpeg\" | \"png\";\n\nexport type RemoteImagePattern = {\n hostname: string;\n pathname?: string;\n port?: string;\n protocol?: \"http:\" | \"https:\";\n};\n\nexport type ImagePolicy = {\n loader?: ImageLoader;\n local?: boolean;\n optimizerPath?: string;\n remote?: readonly (string | RemoteImagePattern)[];\n};\n\nexport type ImageTransformOptions = {\n alt: string;\n format?: ImageFormat;\n height: number;\n priority?: boolean;\n quality?: number;\n sizes?: string;\n src: string;\n width: number;\n widths?: readonly number[];\n};\n\nexport type ImageVariant = {\n format: ImageFormat;\n height: number;\n src: string;\n width: number;\n};\n\nexport type ImageTransformPlan = {\n alt: string;\n decoding: \"async\";\n fetchPriority?: \"high\";\n height: number;\n loading: \"eager\" | \"lazy\";\n sizes?: string;\n source: {\n kind: \"local\" | \"remote\";\n src: string;\n };\n src: string;\n srcSet: string;\n variants: ImageVariant[];\n width: number;\n};\n\nexport function defineImages(policy: ImagePolicy): ImagePolicy {\n return policy;\n}\n\nexport function isAllowedImageSource(src: string, policy: ImagePolicy = {}) {\n return classifyImageSource(src, policy) !== null;\n}\n\nexport function planImageTransform(\n options: ImageTransformOptions,\n policy: ImagePolicy = {},\n): ImageTransformPlan {\n const source = classifyImageSource(options.src, policy);\n\n if (!source) {\n throw new Error(`Image source \"${options.src}\" is not allowed by the image policy.`);\n }\n\n validateImageDimension(\"width\", options.width);\n validateImageDimension(\"height\", options.height);\n validateImageQuality(options.quality);\n\n const format = options.format ?? \"auto\";\n const loader = policy.loader ?? \"optimizer\";\n const optimizerPath = policy.optimizerPath ?? defaultImageOptimizerPath;\n const widths = normalizeVariantWidths(options.width, options.widths);\n const variants = widths.map((width) => {\n const height = Math.round((options.height / options.width) * width);\n const descriptor: ImageVariantDescriptor = {\n format,\n quality: options.quality,\n sourceKind: source.kind,\n src: source.kind === \"remote\" ? source.src : options.src,\n width,\n };\n\n return {\n format,\n height,\n src: loader === \"static\"\n ? createImageVariantPath(descriptor, optimizerPath)\n : createImageOptimizerUrl(descriptor, optimizerPath),\n width,\n };\n });\n const primaryVariant = variants[0];\n\n return {\n alt: options.alt,\n decoding: \"async\",\n fetchPriority: options.priority ? \"high\" : undefined,\n height: options.height,\n loading: options.priority ? \"eager\" : \"lazy\",\n sizes: options.sizes,\n source,\n src: primaryVariant.src,\n srcSet: variants\n .map((variant) => `${variant.src} ${variant.width}w`)\n .join(\", \"),\n variants,\n width: options.width,\n };\n}\n\nfunction classifyImageSource(\n src: string,\n policy: ImagePolicy,\n): ImageTransformPlan[\"source\"] | null {\n if (src.startsWith(\"/\") && !src.startsWith(\"//\")) {\n return policy.local === false ? null : { kind: \"local\", src };\n }\n\n let url: URL;\n\n try {\n url = new URL(src);\n } catch {\n return null;\n }\n\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n return null;\n }\n\n return isAllowedRemoteImage(url, policy.remote ?? [])\n ? { kind: \"remote\", src: url.toString() }\n : null;\n}\n\nfunction isAllowedRemoteImage(\n url: URL,\n remotes: readonly (string | RemoteImagePattern)[],\n) {\n return remotes.some((remote) =>\n typeof remote === \"string\"\n ? matchesRemoteOrigin(url, remote)\n : matchesRemotePattern(url, remote),\n );\n}\n\nfunction matchesRemoteOrigin(url: URL, origin: string) {\n try {\n return url.origin === new URL(origin).origin;\n } catch {\n return false;\n }\n}\n\nfunction matchesRemotePattern(url: URL, pattern: RemoteImagePattern) {\n if (pattern.protocol && url.protocol !== pattern.protocol) {\n return false;\n }\n\n if (url.hostname !== pattern.hostname) {\n return false;\n }\n\n if (pattern.port !== undefined && url.port !== pattern.port) {\n return false;\n }\n\n return pattern.pathname\n ? matchesPathnamePattern(url.pathname, pattern.pathname)\n : true;\n}\n\nfunction matchesPathnamePattern(pathname: string, pattern: string) {\n if (pattern.endsWith(\"*\")) {\n return pathname.startsWith(pattern.slice(0, -1));\n }\n\n return pathname === pattern;\n}\n\nfunction validateImageDimension(name: \"height\" | \"width\", value: number) {\n if (!Number.isInteger(value) || value <= 0) {\n throw new Error(`Image ${name} must be a positive integer.`);\n }\n}\n\nfunction validateImageQuality(quality: number | undefined) {\n if (quality === undefined) {\n return;\n }\n\n if (!Number.isInteger(quality) || quality < 1 || quality > 100) {\n throw new Error(\"Image quality must be an integer between 1 and 100.\");\n }\n}\n\nfunction normalizeVariantWidths(width: number, widths: readonly number[] | undefined) {\n const candidates = widths ?? [width, width * 2];\n const uniqueWidths = [...new Set(candidates)];\n\n for (const candidate of uniqueWidths) {\n validateImageDimension(\"width\", candidate);\n }\n\n return uniqueWidths.sort((left, right) => left - right);\n}\n\nexport type ImageRequestRejection = {\n reason: string;\n status: 400 | 403;\n};\n\nexport type ImageRequestResult =\n | { descriptor: ImageVariantDescriptor; ok: true }\n | { ok: false; rejection: ImageRequestRejection };\n\nconst imageFormats = new Set<ImageFormat>([\n \"avif\",\n \"jpeg\",\n \"png\",\n \"webp\",\n]);\n\n// The runtime optimizer reads a query that any client can write. It repeats\n// the policy check that the planner made, so an unallowed source never\n// reaches the file system or the network.\nexport function parseImageOptimizerRequest(\n url: URL,\n policy: ImagePolicy = {},\n): ImageRequestResult {\n const src = url.searchParams.get(\"src\");\n\n if (!src) {\n return reject(400, \"The image request must declare a src parameter.\");\n }\n\n const source = classifyImageSource(src, policy);\n\n if (!source) {\n return reject(403, \"The image policy does not allow this image source.\");\n }\n\n const width = Number(url.searchParams.get(\"w\"));\n\n if (!Number.isInteger(width) || width <= 0) {\n return reject(400, \"The image width must be a positive integer.\");\n }\n\n const rawQuality = url.searchParams.get(\"q\");\n const quality = rawQuality === null ? undefined : Number(rawQuality);\n\n if (\n quality !== undefined &&\n (!Number.isInteger(quality) || quality < 1 || quality > 100)\n ) {\n return reject(400, \"The image quality must be an integer from 1 through 100.\");\n }\n\n const rawFormat = url.searchParams.get(\"f\");\n\n if (rawFormat !== null && !imageFormats.has(rawFormat as ImageFormat)) {\n return reject(400, \"The image format is not supported.\");\n }\n\n return {\n descriptor: {\n format: (rawFormat as ImageFormat | null) ?? \"auto\",\n quality,\n sourceKind: source.kind,\n src: source.src,\n width,\n },\n ok: true,\n };\n}\n\nfunction reject(status: 400 | 403, reason: string): ImageRequestResult {\n return { ok: false, rejection: { reason, status } };\n}\n"],"names":["defaultFontPath","fontFormats","fontFaceFormats","fontMediaTypes","font","options","src","isRemoteSource","assertKnownFormat","defineFonts","fonts","fontAssetFileName","definition","family","slug","weight","fontFormat","fontAssetUrl","basePath","fontStylesheetUrl","fontMediaType","file","name","extension","format","fontLinks","fontPreloadLinks","preload","renderFontFaceCss","url","escapeCss","fontSources","origins","value","defaultImageOptimizerPath","formatExtensions","extensionFormats","variantNamePattern","createImageOptimizerUrl","descriptor","optimizerPath","params","createImageVariantPath","quality","resolveVariantExtension","parseImageVariantPath","pathname","match","rawWidth","rawQuality","width","imageSourceExtension","collectImageVariantPaths","html","pattern","escapeRegExp","defineImages","policy","isAllowedImageSource","classifyImageSource","planImageTransform","source","validateImageDimension","validateImageQuality","loader","variants","normalizeVariantWidths","height","primaryVariant","variant","isAllowedRemoteImage","remotes","remote","matchesRemoteOrigin","matchesRemotePattern","origin","matchesPathnamePattern","widths","candidates","uniqueWidths","candidate","left","right","imageFormats","parseImageOptimizerRequest","reject","rawFormat","status","reason"],"mappings":"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"}
1
+ {"version":3,"file":"images-Edar3eU1.js","sources":["../../src/platform/fonts.ts","../../src/platform/image-url.ts","../../src/platform/images.ts"],"sourcesContent":["import { preload } from \"../document/links\";\nimport type { LinkTag } from \"../document\";\n\nexport type FontStyle = \"normal\" | \"italic\" | \"oblique\";\n\nexport type FontFormat = \"otf\" | \"ttf\" | \"woff\" | \"woff2\";\n\nexport type FontDefinition = {\n display: \"auto\" | \"block\" | \"fallback\" | \"optional\" | \"swap\";\n family: string;\n kind: \"font\";\n // A self-hosted font is published from the application origin, so `'self'`\n // covers it. A font left on a third-party host stays a `font-src` entry and\n // a privacy question, and `fontSources` makes that cost visible.\n selfHost: boolean;\n source: \"google\" | \"local\";\n src: string;\n style: FontStyle;\n weight: number | `${number} ${number}`;\n};\n\nexport type LocalFontOptions = {\n display?: FontDefinition[\"display\"];\n name: string;\n src: string;\n style?: FontStyle;\n weight?: FontDefinition[\"weight\"];\n};\n\nexport type GoogleFontOptions = {\n display?: FontDefinition[\"display\"];\n family: string;\n selfHost?: boolean;\n src: string;\n style?: FontStyle;\n weight?: FontDefinition[\"weight\"];\n};\n\nexport type FontContribution = readonly FontDefinition[];\n\n// The framework publishes every self-hosted font under one path. The build\n// writes the files, and the Node handler serves the same URLs at request time.\nexport const defaultFontPath = \"/_demiurge/font\";\n\nconst fontFormats: Record<string, FontFormat> = {\n otf: \"otf\",\n ttf: \"ttf\",\n woff: \"woff\",\n woff2: \"woff2\",\n};\n\nconst fontFaceFormats: Record<FontFormat, string> = {\n otf: \"opentype\",\n ttf: \"truetype\",\n woff: \"woff\",\n woff2: \"woff2\",\n};\n\nconst fontMediaTypes: Record<FontFormat, string> = {\n otf: \"font/otf\",\n ttf: \"font/ttf\",\n woff: \"font/woff\",\n woff2: \"font/woff2\",\n};\n\nexport const font = {\n // A Google font needs the file URL that the Google stylesheet points at.\n // The framework reads no third-party stylesheet, because that request is\n // the privacy cost self-hosting removes.\n google(options: GoogleFontOptions): FontDefinition {\n const src = options.src.trim();\n\n if (!isRemoteSource(src)) {\n throw new Error(\n `Google font ${JSON.stringify(options.family)} needs an https font file URL as its source.`,\n );\n }\n\n assertKnownFormat(src);\n\n return {\n display: options.display ?? \"swap\",\n family: options.family,\n kind: \"font\",\n selfHost: options.selfHost ?? true,\n source: \"google\",\n src,\n style: options.style ?? \"normal\",\n weight: options.weight ?? 400,\n };\n },\n // A local font names a file in the project. The build reads that file and\n // publishes it, so the file does not have to sit in the public directory.\n local(options: LocalFontOptions): FontDefinition {\n const src = options.src.trim();\n\n if (!src) {\n throw new Error(\"Local font source must not be empty.\");\n }\n\n if (isRemoteSource(src)) {\n throw new Error(\n `Local font ${JSON.stringify(options.name)} must name a project file rather than a URL.`,\n );\n }\n\n assertKnownFormat(src);\n\n return {\n display: options.display ?? \"swap\",\n family: options.name,\n kind: \"font\",\n selfHost: true,\n source: \"local\",\n src,\n style: options.style ?? \"normal\",\n weight: options.weight ?? 400,\n };\n },\n};\n\nexport function defineFonts(fonts: FontContribution): FontContribution {\n return [...fonts];\n}\n\n// The published file name derives from the declaration alone. The renderer\n// and the build therefore agree on every URL without sharing any state.\nexport function fontAssetFileName(definition: FontDefinition) {\n const family = slug(definition.family);\n const weight = slug(String(definition.weight));\n\n return `${family}-${weight}-${definition.style}.${fontFormat(definition.src)}`;\n}\n\nexport function fontAssetUrl(\n definition: FontDefinition,\n basePath: string = defaultFontPath,\n) {\n return definition.selfHost\n ? `${basePath}/${fontAssetFileName(definition)}`\n : definition.src;\n}\n\nexport function fontStylesheetUrl(basePath: string = defaultFontPath) {\n return `${basePath}/fonts.css`;\n}\n\nexport function fontMediaType(file: string) {\n return fontMediaTypes[fontFormat(file)];\n}\n\nexport function fontFormat(file: string): FontFormat {\n const name = file.split(\"?\")[0]!.split(\"#\")[0]!.split(\"/\").at(-1) ?? \"\";\n const extension = name.includes(\".\")\n ? name.split(\".\").at(-1)!.toLowerCase()\n : \"\";\n const format = fontFormats[extension];\n\n if (!format) {\n throw new Error(\n `Font source ${JSON.stringify(file)} has no known font extension.`,\n );\n }\n\n return format;\n}\n\n// The stylesheet and the preload links are the whole document contribution.\n// An application spreads them into `links` in its root layout.\nexport function fontLinks(\n fonts: FontContribution,\n basePath: string = defaultFontPath,\n): LinkTag[] {\n if (fonts.length === 0) {\n return [];\n }\n\n return [\n {\n href: fontStylesheetUrl(basePath),\n kind: \"link\" as const,\n rel: \"stylesheet\",\n },\n ...fontPreloadLinks(fonts, basePath),\n ];\n}\n\nexport function fontPreloadLinks(\n fonts: FontContribution,\n basePath: string = defaultFontPath,\n): LinkTag[] {\n return fonts.map((definition) =>\n preload(fontAssetUrl(definition, basePath), {\n as: \"font\",\n // A font request runs in CORS mode even on the same origin. A preload\n // without this attribute fetches the file twice.\n crossOrigin: \"anonymous\",\n type: fontMediaType(definition.src),\n })\n );\n}\n\nexport function renderFontFaceCss(\n fonts: FontContribution,\n basePath: string = defaultFontPath,\n) {\n return fonts\n .map((definition) => {\n const url = fontAssetUrl(definition, basePath);\n\n return [\n \"@font-face {\",\n ` font-family: \"${escapeCss(definition.family)}\";`,\n ` src: url(\"${escapeCss(url)}\") format(\"${\n fontFaceFormats[fontFormat(definition.src)]\n }\");`,\n ` font-style: ${definition.style};`,\n ` font-weight: ${definition.weight};`,\n ` font-display: ${definition.display};`,\n \"}\",\n ].join(\"\\n\");\n })\n .join(\"\\n\");\n}\n\n// A self-hosted font set collapses to `'self'`, which every framework preset\n// already declares. A third-party host has to be named here instead.\nexport function fontSources(fonts: FontContribution): string[] {\n const origins = new Set<string>();\n\n for (const definition of fonts) {\n if (!definition.selfHost) {\n origins.add(new URL(definition.src).origin);\n }\n }\n\n return [\"'self'\", ...[...origins].sort()];\n}\n\nfunction isRemoteSource(src: string) {\n return /^https?:\\/\\//i.test(src);\n}\n\nfunction assertKnownFormat(src: string) {\n fontFormat(src);\n}\n\nfunction slug(value: string) {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\") || \"font\";\n}\n\nfunction escapeCss(value: string) {\n return value.replace(/[\\\\\"]/g, \"\\\\$&\");\n}\n","import type { ImageFormat } from \"./images\";\n\nexport type ImageSourceKind = \"local\" | \"remote\";\n\n// The descriptor is the complete input of one variant. The build pipeline and\n// the runtime optimizer both derive a file name and an encoded body from it.\nexport type ImageVariantDescriptor = {\n format: ImageFormat;\n quality?: number;\n sourceKind: ImageSourceKind;\n src: string;\n width: number;\n};\n\nexport const defaultImageOptimizerPath = \"/_demiurge/image\";\n\nconst formatExtensions: Record<Exclude<ImageFormat, \"auto\">, string> = {\n avif: \"avif\",\n jpeg: \"jpg\",\n png: \"png\",\n webp: \"webp\",\n};\n\nconst extensionFormats: Record<string, Exclude<ImageFormat, \"auto\">> = {\n avif: \"avif\",\n jpeg: \"jpeg\",\n jpg: \"jpeg\",\n png: \"png\",\n webp: \"webp\",\n};\n\nconst variantNamePattern = /^(.+)\\.w(\\d{1,5})(?:\\.q(\\d{1,3}))?\\.([a-z0-9]+)$/;\n\nexport function createImageOptimizerUrl(\n descriptor: ImageVariantDescriptor,\n optimizerPath: string,\n) {\n const params = new URLSearchParams({\n src: descriptor.src,\n w: String(descriptor.width),\n });\n\n if (descriptor.quality !== undefined) {\n params.set(\"q\", String(descriptor.quality));\n }\n\n if (descriptor.format !== \"auto\") {\n params.set(\"f\", descriptor.format);\n }\n\n return `${optimizerPath}?${params.toString()}`;\n}\n\n// A static build has no optimizer process, so the variant path must describe\n// the complete transform. The build reads the path back and emits the file,\n// and no shared state has to cross the build and render boundary.\nexport function createImageVariantPath(\n descriptor: ImageVariantDescriptor,\n optimizerPath: string,\n) {\n if (descriptor.sourceKind === \"remote\") {\n throw new Error(\n `Image source ${JSON.stringify(descriptor.src)} is remote. A static image loader can only emit a local image.`,\n );\n }\n\n const quality = descriptor.quality === undefined\n ? \"\"\n : `.q${descriptor.quality}`;\n\n return `${optimizerPath}${descriptor.src}.w${descriptor.width}${quality}.${\n resolveVariantExtension(descriptor)\n }`;\n}\n\nexport function parseImageVariantPath(\n pathname: string,\n optimizerPath: string,\n): ImageVariantDescriptor | undefined {\n if (!pathname.startsWith(`${optimizerPath}/`)) {\n return undefined;\n }\n\n const match = variantNamePattern.exec(pathname.slice(optimizerPath.length));\n\n if (!match) {\n return undefined;\n }\n\n const [, src, rawWidth, rawQuality, extension] = match;\n const format = extensionFormats[extension!];\n const width = Number(rawWidth);\n const quality = rawQuality === undefined ? undefined : Number(rawQuality);\n\n if (\n !format ||\n !imageSourceExtension(src!) ||\n width <= 0 ||\n (quality !== undefined && (quality < 1 || quality > 100))\n ) {\n return undefined;\n }\n\n return { format, quality, sourceKind: \"local\", src: src!, width };\n}\n\nexport function resolveVariantExtension(descriptor: ImageVariantDescriptor) {\n if (descriptor.format !== \"auto\") {\n return formatExtensions[descriptor.format];\n }\n\n const extension = imageSourceExtension(descriptor.src);\n\n if (!extension) {\n throw new Error(\n `Image source ${JSON.stringify(descriptor.src)} has no known image extension. Declare an explicit format.`,\n );\n }\n\n return extension === \"jpeg\" ? \"jpg\" : extension;\n}\n\nexport function imageSourceExtension(src: string) {\n const name = src.split(\"?\")[0]!.split(\"#\")[0]!.split(\"/\").at(-1) ?? \"\";\n const extension = name.includes(\".\")\n ? name.split(\".\").at(-1)!.toLowerCase()\n : \"\";\n\n return extension in extensionFormats ? extension : undefined;\n}\n\n// The build and the development server both find variants by reading the\n// documents that the application rendered. A variant that no document\n// references is a variant that nothing can request.\nexport function collectImageVariantPaths(html: string, optimizerPath: string) {\n const pattern = new RegExp(\n `${escapeRegExp(optimizerPath)}/[^\"'\\\\s,)<>]+`,\n \"g\",\n );\n\n return [...new Set(html.match(pattern) ?? [])];\n}\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","import {\n createImageOptimizerUrl,\n createImageVariantPath,\n defaultImageOptimizerPath,\n type ImageVariantDescriptor,\n} from \"./image-url\";\n\n// `optimizer` points every variant at a request-time endpoint. `static`\n// points every variant at a file that the static build emits.\nexport type ImageLoader = \"optimizer\" | \"static\";\n\nexport type ImageFormat = \"auto\" | \"avif\" | \"webp\" | \"jpeg\" | \"png\";\n\nexport type RemoteImagePattern = {\n hostname: string;\n pathname?: string;\n port?: string;\n protocol?: \"http:\" | \"https:\";\n};\n\nexport type ImagePolicy = {\n loader?: ImageLoader;\n local?: boolean;\n optimizerPath?: string;\n remote?: readonly (string | RemoteImagePattern)[];\n};\n\nexport type ImageTransformOptions = {\n alt: string;\n format?: ImageFormat;\n height: number;\n priority?: boolean;\n quality?: number;\n sizes?: string;\n src: string;\n width: number;\n widths?: readonly number[];\n};\n\nexport type ImageVariant = {\n format: ImageFormat;\n height: number;\n src: string;\n width: number;\n};\n\nexport type ImageTransformPlan = {\n alt: string;\n decoding: \"async\";\n fetchPriority?: \"high\";\n height: number;\n loading: \"eager\" | \"lazy\";\n sizes?: string;\n source: {\n kind: \"local\" | \"remote\";\n src: string;\n };\n src: string;\n srcSet: string;\n variants: ImageVariant[];\n width: number;\n};\n\nexport function defineImages(policy: ImagePolicy): ImagePolicy {\n return policy;\n}\n\nexport function isAllowedImageSource(src: string, policy: ImagePolicy = {}) {\n return classifyImageSource(src, policy) !== null;\n}\n\nexport function planImageTransform(\n options: ImageTransformOptions,\n policy: ImagePolicy = {},\n): ImageTransformPlan {\n const source = classifyImageSource(options.src, policy);\n\n if (!source) {\n throw new Error(`Image source \"${options.src}\" is not allowed by the image policy.`);\n }\n\n validateImageDimension(\"width\", options.width);\n validateImageDimension(\"height\", options.height);\n validateImageQuality(options.quality);\n\n const format = options.format ?? \"auto\";\n const loader = policy.loader ?? \"optimizer\";\n const optimizerPath = policy.optimizerPath ?? defaultImageOptimizerPath;\n const widths = normalizeVariantWidths(options.width, options.widths);\n const variants = widths.map((width) => {\n const height = Math.round((options.height / options.width) * width);\n const descriptor: ImageVariantDescriptor = {\n format,\n quality: options.quality,\n sourceKind: source.kind,\n src: source.kind === \"remote\" ? source.src : options.src,\n width,\n };\n\n return {\n format,\n height,\n src: loader === \"static\"\n ? createImageVariantPath(descriptor, optimizerPath)\n : createImageOptimizerUrl(descriptor, optimizerPath),\n width,\n };\n });\n const primaryVariant = variants[0];\n\n return {\n alt: options.alt,\n decoding: \"async\",\n fetchPriority: options.priority ? \"high\" : undefined,\n height: options.height,\n loading: options.priority ? \"eager\" : \"lazy\",\n sizes: options.sizes,\n source,\n src: primaryVariant.src,\n srcSet: variants\n .map((variant) => `${variant.src} ${variant.width}w`)\n .join(\", \"),\n variants,\n width: options.width,\n };\n}\n\nfunction classifyImageSource(\n src: string,\n policy: ImagePolicy,\n): ImageTransformPlan[\"source\"] | null {\n if (src.startsWith(\"/\") && !src.startsWith(\"//\")) {\n return policy.local === false ? null : { kind: \"local\", src };\n }\n\n let url: URL;\n\n try {\n url = new URL(src);\n } catch {\n return null;\n }\n\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n return null;\n }\n\n return isAllowedRemoteImage(url, policy.remote ?? [])\n ? { kind: \"remote\", src: url.toString() }\n : null;\n}\n\nfunction isAllowedRemoteImage(\n url: URL,\n remotes: readonly (string | RemoteImagePattern)[],\n) {\n return remotes.some((remote) =>\n typeof remote === \"string\"\n ? matchesRemoteOrigin(url, remote)\n : matchesRemotePattern(url, remote),\n );\n}\n\nfunction matchesRemoteOrigin(url: URL, origin: string) {\n try {\n return url.origin === new URL(origin).origin;\n } catch {\n return false;\n }\n}\n\nfunction matchesRemotePattern(url: URL, pattern: RemoteImagePattern) {\n if (pattern.protocol && url.protocol !== pattern.protocol) {\n return false;\n }\n\n if (url.hostname !== pattern.hostname) {\n return false;\n }\n\n if (pattern.port !== undefined && url.port !== pattern.port) {\n return false;\n }\n\n return pattern.pathname\n ? matchesPathnamePattern(url.pathname, pattern.pathname)\n : true;\n}\n\nfunction matchesPathnamePattern(pathname: string, pattern: string) {\n if (pattern.endsWith(\"*\")) {\n return pathname.startsWith(pattern.slice(0, -1));\n }\n\n return pathname === pattern;\n}\n\nfunction validateImageDimension(name: \"height\" | \"width\", value: number) {\n if (!Number.isInteger(value) || value <= 0) {\n throw new Error(`Image ${name} must be a positive integer.`);\n }\n}\n\nfunction validateImageQuality(quality: number | undefined) {\n if (quality === undefined) {\n return;\n }\n\n if (!Number.isInteger(quality) || quality < 1 || quality > 100) {\n throw new Error(\"Image quality must be an integer between 1 and 100.\");\n }\n}\n\nfunction normalizeVariantWidths(width: number, widths: readonly number[] | undefined) {\n const candidates = widths ?? [width, width * 2];\n const uniqueWidths = [...new Set(candidates)];\n\n for (const candidate of uniqueWidths) {\n validateImageDimension(\"width\", candidate);\n }\n\n return uniqueWidths.sort((left, right) => left - right);\n}\n\nexport type ImageRequestRejection = {\n reason: string;\n status: 400 | 403;\n};\n\nexport type ImageRequestResult =\n | { descriptor: ImageVariantDescriptor; ok: true }\n | { ok: false; rejection: ImageRequestRejection };\n\nconst imageFormats = new Set<ImageFormat>([\n \"avif\",\n \"jpeg\",\n \"png\",\n \"webp\",\n]);\n\n// The runtime optimizer reads a query that any client can write. It repeats\n// the policy check that the planner made, so an unallowed source never\n// reaches the file system or the network.\nexport function parseImageOptimizerRequest(\n url: URL,\n policy: ImagePolicy = {},\n): ImageRequestResult {\n const src = url.searchParams.get(\"src\");\n\n if (!src) {\n return reject(400, \"The image request must declare a src parameter.\");\n }\n\n const source = classifyImageSource(src, policy);\n\n if (!source) {\n return reject(403, \"The image policy does not allow this image source.\");\n }\n\n const width = Number(url.searchParams.get(\"w\"));\n\n if (!Number.isInteger(width) || width <= 0) {\n return reject(400, \"The image width must be a positive integer.\");\n }\n\n const rawQuality = url.searchParams.get(\"q\");\n const quality = rawQuality === null ? undefined : Number(rawQuality);\n\n if (\n quality !== undefined &&\n (!Number.isInteger(quality) || quality < 1 || quality > 100)\n ) {\n return reject(400, \"The image quality must be an integer from 1 through 100.\");\n }\n\n const rawFormat = url.searchParams.get(\"f\");\n\n // TYPE-EVIDENCE: the set membership check confirms the raw format is a known image format. The cast matches that check.\n if (rawFormat !== null && !imageFormats.has(rawFormat as ImageFormat)) {\n return reject(400, \"The image format is not supported.\");\n }\n\n // TYPE-EVIDENCE: the raw format value is a string that names an image format or is null. The cast labels it with the format union.\n return {\n descriptor: {\n format: (rawFormat as ImageFormat | null) ?? \"auto\",\n quality,\n sourceKind: source.kind,\n src: source.src,\n width,\n },\n ok: true,\n };\n}\n\nfunction reject(status: 400 | 403, reason: string): ImageRequestResult {\n return { ok: false, rejection: { reason, status } };\n}\n"],"names":["defaultFontPath","fontFormats","fontFaceFormats","fontMediaTypes","font","options","src","isRemoteSource","assertKnownFormat","defineFonts","fonts","fontAssetFileName","definition","family","slug","weight","fontFormat","fontAssetUrl","basePath","fontStylesheetUrl","fontMediaType","file","name","extension","format","fontLinks","fontPreloadLinks","preload","renderFontFaceCss","url","escapeCss","fontSources","origins","value","defaultImageOptimizerPath","formatExtensions","extensionFormats","variantNamePattern","createImageOptimizerUrl","descriptor","optimizerPath","params","createImageVariantPath","quality","resolveVariantExtension","parseImageVariantPath","pathname","match","rawWidth","rawQuality","width","imageSourceExtension","collectImageVariantPaths","html","pattern","escapeRegExp","defineImages","policy","isAllowedImageSource","classifyImageSource","planImageTransform","source","validateImageDimension","validateImageQuality","loader","variants","normalizeVariantWidths","height","primaryVariant","variant","isAllowedRemoteImage","remotes","remote","matchesRemoteOrigin","matchesRemotePattern","origin","matchesPathnamePattern","widths","candidates","uniqueWidths","candidate","left","right","imageFormats","parseImageOptimizerRequest","reject","rawFormat","status","reason"],"mappings":"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"}