@askrjs/askr 0.0.42 → 0.0.44

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 (71) hide show
  1. package/dist/benchmark.js +2 -2
  2. package/dist/boot/index.d.ts.map +1 -1
  3. package/dist/boot/index.js +2 -0
  4. package/dist/boot/index.js.map +1 -1
  5. package/dist/common/route-activity.js +30 -0
  6. package/dist/common/route-activity.js.map +1 -0
  7. package/dist/common/router.d.ts +4 -2
  8. package/dist/common/router.d.ts.map +1 -1
  9. package/dist/common/ssr.d.ts +22 -1
  10. package/dist/common/ssr.d.ts.map +1 -1
  11. package/dist/common/ssr.js +9 -1
  12. package/dist/common/ssr.js.map +1 -1
  13. package/dist/data/index.d.ts +21 -2
  14. package/dist/data/index.d.ts.map +1 -1
  15. package/dist/data/index.js +56 -3
  16. package/dist/data/index.js.map +1 -1
  17. package/dist/data/invalidation-listeners.js +15 -0
  18. package/dist/data/invalidation-listeners.js.map +1 -0
  19. package/dist/renderer/dom.js +23 -15
  20. package/dist/renderer/dom.js.map +1 -1
  21. package/dist/renderer/evaluate.js +9 -13
  22. package/dist/renderer/evaluate.js.map +1 -1
  23. package/dist/renderer/for-commit.js +9 -0
  24. package/dist/renderer/for-commit.js.map +1 -1
  25. package/dist/resources/index.d.ts +2 -2
  26. package/dist/resources/index.js +2 -2
  27. package/dist/router/match.js +31 -6
  28. package/dist/router/match.js.map +1 -1
  29. package/dist/router/navigate.d.ts.map +1 -1
  30. package/dist/router/navigate.js +30 -4
  31. package/dist/router/navigate.js.map +1 -1
  32. package/dist/router/route.d.ts +10 -1
  33. package/dist/router/route.d.ts.map +1 -1
  34. package/dist/router/route.js +38 -8
  35. package/dist/router/route.js.map +1 -1
  36. package/dist/runtime/component.d.ts +7 -1
  37. package/dist/runtime/component.d.ts.map +1 -1
  38. package/dist/runtime/component.js +205 -44
  39. package/dist/runtime/component.js.map +1 -1
  40. package/dist/runtime/fastlane.js +6 -0
  41. package/dist/runtime/fastlane.js.map +1 -1
  42. package/dist/runtime/operations.d.ts +11 -3
  43. package/dist/runtime/operations.d.ts.map +1 -1
  44. package/dist/runtime/operations.js +161 -22
  45. package/dist/runtime/operations.js.map +1 -1
  46. package/dist/runtime/readable.d.ts +1 -0
  47. package/dist/runtime/readable.d.ts.map +1 -1
  48. package/dist/runtime/readable.js +7 -4
  49. package/dist/runtime/readable.js.map +1 -1
  50. package/dist/ssg/batch-render.js +27 -5
  51. package/dist/ssg/batch-render.js.map +1 -1
  52. package/dist/ssg/create-static-gen.d.ts.map +1 -1
  53. package/dist/ssg/create-static-gen.js +6 -4
  54. package/dist/ssg/create-static-gen.js.map +1 -1
  55. package/dist/ssg/index.d.ts +2 -1
  56. package/dist/ssg/resolve-ssg-data.js +18 -1
  57. package/dist/ssg/resolve-ssg-data.js.map +1 -1
  58. package/dist/ssg/types.d.ts +3 -0
  59. package/dist/ssg/types.d.ts.map +1 -1
  60. package/dist/ssr/context.d.ts +1 -2
  61. package/dist/ssr/context.d.ts.map +1 -1
  62. package/dist/ssr/index.d.ts +10 -11
  63. package/dist/ssr/index.d.ts.map +1 -1
  64. package/dist/ssr/index.js +104 -68
  65. package/dist/ssr/index.js.map +1 -1
  66. package/dist/ssr/render-resolved.d.ts +1 -1
  67. package/dist/testing/index.d.ts +53 -0
  68. package/dist/testing/index.d.ts.map +1 -0
  69. package/dist/testing/index.js +172 -0
  70. package/dist/testing/index.js.map +1 -0
  71. package/package.json +12 -4
@@ -1,6 +1,6 @@
1
1
  import { addPerfDuration, incrementPerfMetric } from "../runtime/perf-metrics.js";
2
2
  import { resolveRouteDescriptor } from "./route-utils.js";
3
- import { expandRoutes, resolveSsgData, validateRoutes } from "./resolve-ssg-data.js";
3
+ import { expandRoutes, resolveSsgData, resolveSsgRouteData, validateRoutes } from "./resolve-ssg-data.js";
4
4
  import { batchRenderRoutes } from "./batch-render.js";
5
5
  import { writeStaticFiles } from "./write-static-files.js";
6
6
  import { generateSSGResult, resultToMetadata, writeMetadata } from "./generate-metadata.js";
@@ -101,7 +101,8 @@ function createStaticGen(options) {
101
101
  const renderedResults = routesToRender.length > 0 ? await batchRenderRoutes(getRoutesToRender(routesToRender), {
102
102
  seed,
103
103
  dataMap,
104
- concurrency: resolvedConcurrency
104
+ concurrency: resolvedConcurrency,
105
+ document: options.document
105
106
  }) : [];
106
107
  addPerfDuration("ssgRenderTimeMs", performance.now() - renderStartTime);
107
108
  const renderedByRouteId = /* @__PURE__ */ new Map();
@@ -110,8 +111,9 @@ function createStaticGen(options) {
110
111
  const nextManifestRoutes = [];
111
112
  for (const entry of selected) {
112
113
  const { descriptor, previous, reason } = entry;
113
- const baseData = dataMap[descriptor.route.path] ?? dataMap[descriptor.path] ?? {};
114
- const resourceCount = Object.keys(baseData).length;
114
+ const resolvedData = resolveSsgRouteData(dataMap, descriptor.route.path, descriptor.path);
115
+ const baseData = resolvedData.hasData ? resolvedData.data : void 0;
116
+ const resourceCount = resolvedData.hasData && baseData ? Object.keys(baseData).length : 0;
115
117
  if (reason === "unchanged") {
116
118
  routeResultsById.set(descriptor.routeId, {
117
119
  path: descriptor.path,
@@ -1 +1 @@
1
- {"version":3,"file":"create-static-gen.js","names":[],"sources":["../../src/ssg/create-static-gen.ts"],"sourcesContent":["/**\n * Static Site Generation API\n *\n * Main orchestrator for batch rendering, file I/O, and metadata generation\n */\n\nimport type {\n RouteConfig,\n RouteRenderReason,\n RouteRenderResult,\n SSGGenerateOptions,\n SSGMode,\n SSGOptions,\n SSGResult,\n} from './types';\nimport {\n expandRoutes,\n resolveSsgData,\n validateRoutes,\n} from './resolve-ssg-data';\nimport { batchRenderRoutes } from './batch-render';\nimport { writeStaticFiles } from './write-static-files';\nimport {\n generateSSGResult,\n resultToMetadata,\n writeMetadata,\n} from './generate-metadata';\nimport {\n getExistingOutputFileSize,\n outputFileExists,\n readIncrementalManifest,\n type IncrementalManifest,\n type IncrementalManifestRouteEntry,\n writeIncrementalManifest,\n hashHtml,\n SSG_MANIFEST_SCHEMA_VERSION,\n} from './incremental-manifest';\nimport {\n resolveRouteDescriptor,\n type ResolvedRouteDescriptor,\n} from './route-utils';\nimport { addPerfDuration, incrementPerfMetric } from '../runtime/perf-metrics';\n\ntype AnyRouteConfig = RouteConfig<string>;\n\ntype StrictRouteConfig<TRoute extends AnyRouteConfig> = Omit<\n TRoute,\n 'params' | 'entries'\n> & {\n params?: RouteConfig<TRoute['path']>['params'];\n entries?: RouteConfig<TRoute['path']>['entries'];\n};\n\ntype StrictRouteConfigs<TRoutes extends readonly AnyRouteConfig[]> = {\n [TIndex in keyof TRoutes]: TRoutes[TIndex] extends AnyRouteConfig\n ? StrictRouteConfig<TRoutes[TIndex]>\n : never;\n};\n\ninterface SelectedRoute {\n descriptor: ResolvedRouteDescriptor;\n reason: RouteRenderReason;\n previous: IncrementalManifestRouteEntry | null;\n}\n\nfunction getRuntimeOnlyDiagnostic(\n descriptor: ResolvedRouteDescriptor\n): string | null {\n const { route } = descriptor;\n\n if (route.auth === true) {\n return `Skipped prerender for \"${descriptor.path}\": authenticated routes are runtime-only by default.`;\n }\n\n if (route.role) {\n return `Skipped prerender for \"${descriptor.path}\": role-gated routes are runtime-only by default.`;\n }\n\n if (route.permission) {\n return `Skipped prerender for \"${descriptor.path}\": permission-gated routes are runtime-only by default.`;\n }\n\n if (route.policies && route.policies.length > 0) {\n return `Skipped prerender for \"${descriptor.path}\": routes with custom policies are runtime-only by default.`;\n }\n\n return null;\n}\n\nfunction resolveParallelism(requested: number | 'auto' | undefined): number {\n if (requested !== 'auto') {\n return Math.max(1, requested ?? 1);\n }\n\n const maybeNavigator = globalThis as typeof globalThis & {\n navigator?: { hardwareConcurrency?: number };\n process?: { env?: Record<string, string | undefined> };\n };\n\n const envWorkers = Number(\n maybeNavigator.process?.env?.ASKR_SSG_WORKERS ??\n maybeNavigator.process?.env?.NUMBER_OF_PROCESSORS ??\n maybeNavigator.process?.env?.UV_THREADPOOL_SIZE\n );\n if (Number.isFinite(envWorkers) && envWorkers > 0) {\n return Math.max(1, Math.trunc(envWorkers));\n }\n\n if (typeof maybeNavigator.navigator?.hardwareConcurrency === 'number') {\n return Math.max(1, maybeNavigator.navigator.hardwareConcurrency);\n }\n\n return 1;\n}\n\n/**\n * Create a Static Site Generator\n *\n * Usage:\n * ```ts\n * const ssg = createStaticGen({\n * routes: [\n * { path: '/', component: HomePage },\n * { path: '/about', component: AboutPage },\n * ],\n * outputDir: './dist',\n * dataOverrides: {\n * '/api/posts': { posts: [...] }\n * }\n * });\n *\n * const result = await ssg.generate();\n * console.log(`Generated ${result.successful}/${result.totalRoutes} routes`);\n * ```\n */\nexport function createStaticGen<\n const TRoutes extends readonly AnyRouteConfig[],\n>(\n options: Omit<SSGOptions<TRoutes>, 'routes'> & {\n routes: TRoutes & StrictRouteConfigs<TRoutes>;\n }\n) {\n let result: SSGResult | null = null;\n const seed = options.seed ?? 12345;\n const resolvedParallelism = resolveParallelism(options.parallelism);\n const resolvedConcurrency = Math.max(\n 1,\n options.concurrency ?? resolvedParallelism\n );\n\n if (!Array.isArray(options.routes) || options.routes.length === 0) {\n throw new Error('routes array is required');\n }\n\n if (!options.outputDir || options.outputDir.trim().length === 0) {\n throw new Error('outputDir is required');\n }\n\n return {\n /**\n * Generate static HTML for all routes\n * Writes to outputDir with metadata.json\n * Returns detailed results\n */\n async generate(\n generateOptions: SSGGenerateOptions = {}\n ): Promise<SSGResult> {\n const routes = await expandRoutes(options.routes as RouteConfig[]);\n validateRoutes(routes);\n\n const changedKeys = dedupeStrings(generateOptions.changedKeys);\n const changedRoutes = dedupeStrings(generateOptions.changedRoutes);\n const requestedMode = generateOptions.mode ?? 'full';\n const previousManifest =\n !generateOptions.forceFull && requestedMode === 'incremental'\n ? readIncrementalManifest(options.outputDir, seed)\n : null;\n const effectiveMode: SSGMode =\n generateOptions.forceFull ||\n requestedMode !== 'incremental' ||\n previousManifest === null\n ? 'full'\n : 'incremental';\n\n // Resolve data\n const dataMap = resolveSsgData(routes, {\n dataOverrides: options.dataOverrides,\n });\n\n const descriptors = routes.map(resolveRouteDescriptor);\n const routeResultsById = new Map<string, RouteRenderResult>();\n const eligibleDescriptors: ResolvedRouteDescriptor[] = [];\n\n for (const descriptor of descriptors) {\n const diagnostic = getRuntimeOnlyDiagnostic(descriptor);\n if (diagnostic) {\n routeResultsById.set(descriptor.routeId, {\n path: descriptor.path,\n filePath: descriptor.filePath,\n html: '',\n fileSize: 0,\n renderDuration: 0,\n resourceCount: 0,\n status: 'skipped',\n reason: 'runtime-only',\n written: false,\n error: diagnostic,\n });\n continue;\n }\n\n eligibleDescriptors.push(descriptor);\n }\n\n const previousEntries = new Map(\n (previousManifest?.routes ?? []).map((entry) => [entry.routeId, entry])\n );\n const currentRouteIds = new Set(\n eligibleDescriptors.map((descriptor) => descriptor.routeId)\n );\n\n const selected: SelectedRoute[] = [];\n const routesToRender: SelectedRoute[] = [];\n for (let index = 0; index < eligibleDescriptors.length; index += 1) {\n const descriptor = eligibleDescriptors[index];\n const entry = selectRouteForGeneration(\n descriptor,\n previousEntries.get(descriptor.routeId) ?? null,\n effectiveMode,\n changedKeys,\n changedRoutes\n );\n selected.push(entry);\n if (entry.reason !== 'unchanged') {\n routesToRender.push(entry);\n }\n }\n const renderStartTime = performance.now();\n incrementPerfMetric('ssgWorkerCount', resolvedParallelism);\n const renderedResults =\n routesToRender.length > 0\n ? await batchRenderRoutes(getRoutesToRender(routesToRender), {\n seed,\n dataMap,\n concurrency: resolvedConcurrency,\n })\n : [];\n addPerfDuration('ssgRenderTimeMs', performance.now() - renderStartTime);\n const renderedByRouteId = new Map<string, RouteRenderResult>();\n for (let index = 0; index < renderedResults.length; index += 1) {\n renderedByRouteId.set(\n routesToRender[index].descriptor.routeId,\n renderedResults[index]\n );\n }\n\n let cacheHits = 0;\n const nextManifestRoutes: IncrementalManifestRouteEntry[] = [];\n\n for (const entry of selected) {\n const { descriptor, previous, reason } = entry;\n const baseData =\n dataMap[descriptor.route.path] ?? dataMap[descriptor.path] ?? {};\n const resourceCount = Object.keys(baseData).length;\n\n if (reason === 'unchanged') {\n routeResultsById.set(descriptor.routeId, {\n path: descriptor.path,\n filePath: descriptor.filePath,\n html: '',\n fileSize:\n previous?.htmlHash !== null && previous !== null\n ? getExistingOutputFileSize(\n options.outputDir,\n descriptor.filePath\n )\n : 0,\n renderDuration: 0,\n resourceCount,\n status: 'skipped',\n reason: 'unchanged',\n written: false,\n });\n\n if (previous) {\n nextManifestRoutes.push({\n ...previous,\n path: descriptor.path,\n filePath: descriptor.filePath,\n invalidationKeys: descriptor.invalidationKeys.slice(),\n });\n }\n continue;\n }\n\n const rendered = renderedByRouteId.get(descriptor.routeId);\n if (!rendered) {\n throw new Error(\n `Missing rendered result for route \"${descriptor.path}\"`\n );\n }\n\n const nextResult: RouteRenderResult = {\n ...rendered,\n path: descriptor.path,\n filePath: descriptor.filePath,\n resourceCount,\n reason,\n written: false,\n };\n\n if (nextResult.status === 'success') {\n const htmlDigest = hashHtml(nextResult.html);\n const shouldWrite =\n effectiveMode === 'full' ||\n !previous ||\n previous.htmlHash !== htmlDigest ||\n !outputFileExists(options.outputDir, descriptor.filePath);\n\n nextResult.written = shouldWrite;\n if (!shouldWrite) {\n cacheHits += 1;\n }\n\n nextManifestRoutes.push({\n routeId: descriptor.routeId,\n path: descriptor.path,\n filePath: descriptor.filePath,\n invalidationKeys: descriptor.invalidationKeys.slice(),\n htmlHash: htmlDigest,\n lastStatus: 'success',\n });\n } else {\n nextManifestRoutes.push({\n routeId: descriptor.routeId,\n path: descriptor.path,\n filePath: descriptor.filePath,\n invalidationKeys: descriptor.invalidationKeys.slice(),\n htmlHash: previous?.htmlHash ?? null,\n lastStatus: 'error',\n });\n }\n\n routeResultsById.set(descriptor.routeId, nextResult);\n }\n\n const removedResults = collectRemovedRouteResults(\n previousManifest,\n currentRouteIds\n );\n const routeResults: RouteRenderResult[] = [];\n for (let index = 0; index < descriptors.length; index += 1) {\n const routeResult = routeResultsById.get(descriptors[index].routeId);\n if (routeResult) {\n routeResults.push(routeResult);\n }\n }\n routeResults.push(...removedResults);\n\n // Write HTML files to disk and remove stale output\n const writeStartTime = performance.now();\n await writeStaticFiles(routeResults, options.outputDir, {\n concurrency: resolvedConcurrency,\n });\n addPerfDuration('ssgWriteTimeMs', performance.now() - writeStartTime);\n\n // Generate result object\n result = generateSSGResult(routeResults, {\n mode: effectiveMode,\n cacheHits,\n invalidatedKeys: changedKeys,\n invalidatedRoutes: changedRoutes,\n });\n\n // Write metadata\n const metadata = resultToMetadata(result);\n await writeMetadata(metadata, options.outputDir);\n\n await writeIncrementalManifest(\n {\n schemaVersion: SSG_MANIFEST_SCHEMA_VERSION,\n seed,\n mode: effectiveMode,\n routes: nextManifestRoutes,\n },\n options.outputDir\n );\n\n return result;\n },\n\n /**\n * Get effective config in a serializable form for diagnostics.\n */\n getConfig() {\n return {\n routeCount: options.routes.length,\n outputDir: options.outputDir,\n seed,\n concurrency: resolvedConcurrency,\n parallelism: resolvedParallelism,\n hasDataOverrides: !!options.dataOverrides,\n };\n },\n\n /**\n * Get the last generation result\n * Returns null if generate() hasn't been called\n */\n getResult(): SSGResult | null {\n return result;\n },\n };\n}\n\nfunction dedupeStrings(values?: string[]): string[] {\n return values ? Array.from(new Set(values)) : [];\n}\n\nfunction getRoutesToRender(selected: SelectedRoute[]): RouteConfig[] {\n const routes: RouteConfig[] = [];\n for (let index = 0; index < selected.length; index += 1) {\n routes.push(selected[index].descriptor.route);\n }\n return routes;\n}\n\nfunction selectRouteForGeneration(\n descriptor: ResolvedRouteDescriptor,\n previous: IncrementalManifestRouteEntry | null,\n mode: SSGMode,\n changedKeys: string[],\n changedRoutes: string[]\n): SelectedRoute {\n if (mode === 'full') {\n return { descriptor, reason: 'full', previous };\n }\n\n if (!previous) {\n return { descriptor, reason: 'new-route', previous };\n }\n\n if (previous.lastStatus === 'error' || previous.htmlHash === null) {\n return { descriptor, reason: 'new-route', previous };\n }\n\n if (descriptor.invalidationKeys.length === 0) {\n return { descriptor, reason: 'no-keys', previous };\n }\n\n if (changedRoutes.includes(descriptor.path)) {\n return { descriptor, reason: 'changed-route', previous };\n }\n\n if (descriptor.invalidationKeys.some((key) => changedKeys.includes(key))) {\n return { descriptor, reason: 'changed-key', previous };\n }\n\n return { descriptor, reason: 'unchanged', previous };\n}\n\nfunction collectRemovedRouteResults(\n manifest: IncrementalManifest | null,\n currentRouteIds: Set<string>\n): RouteRenderResult[] {\n if (!manifest) {\n return [];\n }\n\n const results: RouteRenderResult[] = [];\n for (let index = 0; index < manifest.routes.length; index += 1) {\n const entry = manifest.routes[index];\n if (currentRouteIds.has(entry.routeId)) {\n continue;\n }\n results.push({\n path: entry.path,\n filePath: entry.filePath,\n html: '',\n fileSize: 0,\n renderDuration: 0,\n resourceCount: 0,\n status: 'removed' as const,\n reason: 'deleted' as const,\n written: false,\n });\n }\n\n return results;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"create-static-gen.js","names":[],"sources":["../../src/ssg/create-static-gen.ts"],"sourcesContent":["/**\n * Static Site Generation API\n *\n * Main orchestrator for batch rendering, file I/O, and metadata generation\n */\n\nimport type {\n RouteConfig,\n RouteRenderReason,\n RouteRenderResult,\n SSGGenerateOptions,\n SSGMode,\n SSGOptions,\n SSGResult,\n} from './types';\nimport {\n expandRoutes,\n resolveSsgRouteData,\n resolveSsgData,\n validateRoutes,\n} from './resolve-ssg-data';\nimport { batchRenderRoutes } from './batch-render';\nimport { writeStaticFiles } from './write-static-files';\nimport {\n generateSSGResult,\n resultToMetadata,\n writeMetadata,\n} from './generate-metadata';\nimport {\n getExistingOutputFileSize,\n outputFileExists,\n readIncrementalManifest,\n type IncrementalManifest,\n type IncrementalManifestRouteEntry,\n writeIncrementalManifest,\n hashHtml,\n SSG_MANIFEST_SCHEMA_VERSION,\n} from './incremental-manifest';\nimport {\n resolveRouteDescriptor,\n type ResolvedRouteDescriptor,\n} from './route-utils';\nimport { addPerfDuration, incrementPerfMetric } from '../runtime/perf-metrics';\n\ntype AnyRouteConfig = RouteConfig<string>;\n\ntype StrictRouteConfig<TRoute extends AnyRouteConfig> = Omit<\n TRoute,\n 'params' | 'entries'\n> & {\n params?: RouteConfig<TRoute['path']>['params'];\n entries?: RouteConfig<TRoute['path']>['entries'];\n};\n\ntype StrictRouteConfigs<TRoutes extends readonly AnyRouteConfig[]> = {\n [TIndex in keyof TRoutes]: TRoutes[TIndex] extends AnyRouteConfig\n ? StrictRouteConfig<TRoutes[TIndex]>\n : never;\n};\n\ninterface SelectedRoute {\n descriptor: ResolvedRouteDescriptor;\n reason: RouteRenderReason;\n previous: IncrementalManifestRouteEntry | null;\n}\n\nfunction getRuntimeOnlyDiagnostic(\n descriptor: ResolvedRouteDescriptor\n): string | null {\n const { route } = descriptor;\n\n if (route.auth === true) {\n return `Skipped prerender for \"${descriptor.path}\": authenticated routes are runtime-only by default.`;\n }\n\n if (route.role) {\n return `Skipped prerender for \"${descriptor.path}\": role-gated routes are runtime-only by default.`;\n }\n\n if (route.permission) {\n return `Skipped prerender for \"${descriptor.path}\": permission-gated routes are runtime-only by default.`;\n }\n\n if (route.policies && route.policies.length > 0) {\n return `Skipped prerender for \"${descriptor.path}\": routes with custom policies are runtime-only by default.`;\n }\n\n return null;\n}\n\nfunction resolveParallelism(requested: number | 'auto' | undefined): number {\n if (requested !== 'auto') {\n return Math.max(1, requested ?? 1);\n }\n\n const maybeNavigator = globalThis as typeof globalThis & {\n navigator?: { hardwareConcurrency?: number };\n process?: { env?: Record<string, string | undefined> };\n };\n\n const envWorkers = Number(\n maybeNavigator.process?.env?.ASKR_SSG_WORKERS ??\n maybeNavigator.process?.env?.NUMBER_OF_PROCESSORS ??\n maybeNavigator.process?.env?.UV_THREADPOOL_SIZE\n );\n if (Number.isFinite(envWorkers) && envWorkers > 0) {\n return Math.max(1, Math.trunc(envWorkers));\n }\n\n if (typeof maybeNavigator.navigator?.hardwareConcurrency === 'number') {\n return Math.max(1, maybeNavigator.navigator.hardwareConcurrency);\n }\n\n return 1;\n}\n\n/**\n * Create a Static Site Generator\n *\n * Usage:\n * ```ts\n * const ssg = createStaticGen({\n * routes: [\n * { path: '/', component: HomePage },\n * { path: '/about', component: AboutPage },\n * ],\n * outputDir: './dist',\n * dataOverrides: {\n * '/api/posts': { posts: [...] }\n * }\n * });\n *\n * const result = await ssg.generate();\n * console.log(`Generated ${result.successful}/${result.totalRoutes} routes`);\n * ```\n */\nexport function createStaticGen<\n const TRoutes extends readonly AnyRouteConfig[],\n>(\n options: Omit<SSGOptions<TRoutes>, 'routes'> & {\n routes: TRoutes & StrictRouteConfigs<TRoutes>;\n }\n) {\n let result: SSGResult | null = null;\n const seed = options.seed ?? 12345;\n const resolvedParallelism = resolveParallelism(options.parallelism);\n const resolvedConcurrency = Math.max(\n 1,\n options.concurrency ?? resolvedParallelism\n );\n\n if (!Array.isArray(options.routes) || options.routes.length === 0) {\n throw new Error('routes array is required');\n }\n\n if (!options.outputDir || options.outputDir.trim().length === 0) {\n throw new Error('outputDir is required');\n }\n\n return {\n /**\n * Generate static HTML for all routes\n * Writes to outputDir with metadata.json\n * Returns detailed results\n */\n async generate(\n generateOptions: SSGGenerateOptions = {}\n ): Promise<SSGResult> {\n const routes = await expandRoutes(options.routes as RouteConfig[]);\n validateRoutes(routes);\n\n const changedKeys = dedupeStrings(generateOptions.changedKeys);\n const changedRoutes = dedupeStrings(generateOptions.changedRoutes);\n const requestedMode = generateOptions.mode ?? 'full';\n const previousManifest =\n !generateOptions.forceFull && requestedMode === 'incremental'\n ? readIncrementalManifest(options.outputDir, seed)\n : null;\n const effectiveMode: SSGMode =\n generateOptions.forceFull ||\n requestedMode !== 'incremental' ||\n previousManifest === null\n ? 'full'\n : 'incremental';\n\n // Resolve data\n const dataMap = resolveSsgData(routes, {\n dataOverrides: options.dataOverrides,\n });\n\n const descriptors = routes.map(resolveRouteDescriptor);\n const routeResultsById = new Map<string, RouteRenderResult>();\n const eligibleDescriptors: ResolvedRouteDescriptor[] = [];\n\n for (const descriptor of descriptors) {\n const diagnostic = getRuntimeOnlyDiagnostic(descriptor);\n if (diagnostic) {\n routeResultsById.set(descriptor.routeId, {\n path: descriptor.path,\n filePath: descriptor.filePath,\n html: '',\n fileSize: 0,\n renderDuration: 0,\n resourceCount: 0,\n status: 'skipped',\n reason: 'runtime-only',\n written: false,\n error: diagnostic,\n });\n continue;\n }\n\n eligibleDescriptors.push(descriptor);\n }\n\n const previousEntries = new Map(\n (previousManifest?.routes ?? []).map((entry) => [entry.routeId, entry])\n );\n const currentRouteIds = new Set(\n eligibleDescriptors.map((descriptor) => descriptor.routeId)\n );\n\n const selected: SelectedRoute[] = [];\n const routesToRender: SelectedRoute[] = [];\n for (let index = 0; index < eligibleDescriptors.length; index += 1) {\n const descriptor = eligibleDescriptors[index];\n const entry = selectRouteForGeneration(\n descriptor,\n previousEntries.get(descriptor.routeId) ?? null,\n effectiveMode,\n changedKeys,\n changedRoutes\n );\n selected.push(entry);\n if (entry.reason !== 'unchanged') {\n routesToRender.push(entry);\n }\n }\n const renderStartTime = performance.now();\n incrementPerfMetric('ssgWorkerCount', resolvedParallelism);\n const renderedResults =\n routesToRender.length > 0\n ? await batchRenderRoutes(getRoutesToRender(routesToRender), {\n seed,\n dataMap,\n concurrency: resolvedConcurrency,\n document: options.document,\n })\n : [];\n addPerfDuration('ssgRenderTimeMs', performance.now() - renderStartTime);\n const renderedByRouteId = new Map<string, RouteRenderResult>();\n for (let index = 0; index < renderedResults.length; index += 1) {\n renderedByRouteId.set(\n routesToRender[index].descriptor.routeId,\n renderedResults[index]\n );\n }\n\n let cacheHits = 0;\n const nextManifestRoutes: IncrementalManifestRouteEntry[] = [];\n\n for (const entry of selected) {\n const { descriptor, previous, reason } = entry;\n const resolvedData = resolveSsgRouteData(\n dataMap,\n descriptor.route.path,\n descriptor.path\n );\n const baseData = resolvedData.hasData ? resolvedData.data : undefined;\n const resourceCount =\n resolvedData.hasData && baseData ? Object.keys(baseData).length : 0;\n\n if (reason === 'unchanged') {\n routeResultsById.set(descriptor.routeId, {\n path: descriptor.path,\n filePath: descriptor.filePath,\n html: '',\n fileSize:\n previous?.htmlHash !== null && previous !== null\n ? getExistingOutputFileSize(\n options.outputDir,\n descriptor.filePath\n )\n : 0,\n renderDuration: 0,\n resourceCount,\n status: 'skipped',\n reason: 'unchanged',\n written: false,\n });\n\n if (previous) {\n nextManifestRoutes.push({\n ...previous,\n path: descriptor.path,\n filePath: descriptor.filePath,\n invalidationKeys: descriptor.invalidationKeys.slice(),\n });\n }\n continue;\n }\n\n const rendered = renderedByRouteId.get(descriptor.routeId);\n if (!rendered) {\n throw new Error(\n `Missing rendered result for route \"${descriptor.path}\"`\n );\n }\n\n const nextResult: RouteRenderResult = {\n ...rendered,\n path: descriptor.path,\n filePath: descriptor.filePath,\n resourceCount,\n reason,\n written: false,\n };\n\n if (nextResult.status === 'success') {\n const htmlDigest = hashHtml(nextResult.html);\n const shouldWrite =\n effectiveMode === 'full' ||\n !previous ||\n previous.htmlHash !== htmlDigest ||\n !outputFileExists(options.outputDir, descriptor.filePath);\n\n nextResult.written = shouldWrite;\n if (!shouldWrite) {\n cacheHits += 1;\n }\n\n nextManifestRoutes.push({\n routeId: descriptor.routeId,\n path: descriptor.path,\n filePath: descriptor.filePath,\n invalidationKeys: descriptor.invalidationKeys.slice(),\n htmlHash: htmlDigest,\n lastStatus: 'success',\n });\n } else {\n nextManifestRoutes.push({\n routeId: descriptor.routeId,\n path: descriptor.path,\n filePath: descriptor.filePath,\n invalidationKeys: descriptor.invalidationKeys.slice(),\n htmlHash: previous?.htmlHash ?? null,\n lastStatus: 'error',\n });\n }\n\n routeResultsById.set(descriptor.routeId, nextResult);\n }\n\n const removedResults = collectRemovedRouteResults(\n previousManifest,\n currentRouteIds\n );\n const routeResults: RouteRenderResult[] = [];\n for (let index = 0; index < descriptors.length; index += 1) {\n const routeResult = routeResultsById.get(descriptors[index].routeId);\n if (routeResult) {\n routeResults.push(routeResult);\n }\n }\n routeResults.push(...removedResults);\n\n // Write HTML files to disk and remove stale output\n const writeStartTime = performance.now();\n await writeStaticFiles(routeResults, options.outputDir, {\n concurrency: resolvedConcurrency,\n });\n addPerfDuration('ssgWriteTimeMs', performance.now() - writeStartTime);\n\n // Generate result object\n result = generateSSGResult(routeResults, {\n mode: effectiveMode,\n cacheHits,\n invalidatedKeys: changedKeys,\n invalidatedRoutes: changedRoutes,\n });\n\n // Write metadata\n const metadata = resultToMetadata(result);\n await writeMetadata(metadata, options.outputDir);\n\n await writeIncrementalManifest(\n {\n schemaVersion: SSG_MANIFEST_SCHEMA_VERSION,\n seed,\n mode: effectiveMode,\n routes: nextManifestRoutes,\n },\n options.outputDir\n );\n\n return result;\n },\n\n /**\n * Get effective config in a serializable form for diagnostics.\n */\n getConfig() {\n return {\n routeCount: options.routes.length,\n outputDir: options.outputDir,\n seed,\n concurrency: resolvedConcurrency,\n parallelism: resolvedParallelism,\n hasDataOverrides: !!options.dataOverrides,\n };\n },\n\n /**\n * Get the last generation result\n * Returns null if generate() hasn't been called\n */\n getResult(): SSGResult | null {\n return result;\n },\n };\n}\n\nfunction dedupeStrings(values?: string[]): string[] {\n return values ? Array.from(new Set(values)) : [];\n}\n\nfunction getRoutesToRender(selected: SelectedRoute[]): RouteConfig[] {\n const routes: RouteConfig[] = [];\n for (let index = 0; index < selected.length; index += 1) {\n routes.push(selected[index].descriptor.route);\n }\n return routes;\n}\n\nfunction selectRouteForGeneration(\n descriptor: ResolvedRouteDescriptor,\n previous: IncrementalManifestRouteEntry | null,\n mode: SSGMode,\n changedKeys: string[],\n changedRoutes: string[]\n): SelectedRoute {\n if (mode === 'full') {\n return { descriptor, reason: 'full', previous };\n }\n\n if (!previous) {\n return { descriptor, reason: 'new-route', previous };\n }\n\n if (previous.lastStatus === 'error' || previous.htmlHash === null) {\n return { descriptor, reason: 'new-route', previous };\n }\n\n if (descriptor.invalidationKeys.length === 0) {\n return { descriptor, reason: 'no-keys', previous };\n }\n\n if (changedRoutes.includes(descriptor.path)) {\n return { descriptor, reason: 'changed-route', previous };\n }\n\n if (descriptor.invalidationKeys.some((key) => changedKeys.includes(key))) {\n return { descriptor, reason: 'changed-key', previous };\n }\n\n return { descriptor, reason: 'unchanged', previous };\n}\n\nfunction collectRemovedRouteResults(\n manifest: IncrementalManifest | null,\n currentRouteIds: Set<string>\n): RouteRenderResult[] {\n if (!manifest) {\n return [];\n }\n\n const results: RouteRenderResult[] = [];\n for (let index = 0; index < manifest.routes.length; index += 1) {\n const entry = manifest.routes[index];\n if (currentRouteIds.has(entry.routeId)) {\n continue;\n }\n results.push({\n path: entry.path,\n filePath: entry.filePath,\n html: '',\n fileSize: 0,\n renderDuration: 0,\n resourceCount: 0,\n status: 'removed' as const,\n reason: 'deleted' as const,\n written: false,\n });\n }\n\n return results;\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { DocumentRenderArgs, DocumentRenderContext, DocumentRenderer } from "../common/ssr.js";
1
2
  import { DiscoveredResources, RouteConfig, RouteRenderReason, RouteRenderResult, RouteRenderStatus, SSGGenerateOptions, SSGMetadata, SSGMode, SSGOptions, SSGResult } from "./types.js";
2
3
  import { createStaticGen } from "./create-static-gen.js";
3
- export { type DiscoveredResources, type RouteConfig, type RouteRenderReason, type RouteRenderResult, type RouteRenderStatus, type SSGGenerateOptions, type SSGMetadata, type SSGMode, type SSGOptions, type SSGResult, createStaticGen };
4
+ export { type DiscoveredResources, type DocumentRenderArgs, type DocumentRenderContext, type DocumentRenderer, type RouteConfig, type RouteRenderReason, type RouteRenderResult, type RouteRenderStatus, type SSGGenerateOptions, type SSGMetadata, type SSGMode, type SSGOptions, type SSGResult, createStaticGen };
@@ -1,5 +1,8 @@
1
1
  import { interpolateRoutePath } from "./route-utils.js";
2
2
  //#region src/ssg/resolve-ssg-data.ts
3
+ function hasOwnRouteData(dataMap, path) {
4
+ return Object.prototype.hasOwnProperty.call(dataMap, path);
5
+ }
3
6
  /**
4
7
  * Resolve and validate data for SSG routes
5
8
  * Returns a map of route path -> SSRData
@@ -21,6 +24,20 @@ function resolveSsgData(routes, options = {}) {
21
24
  }
22
25
  return dataMap;
23
26
  }
27
+ function resolveSsgRouteData(dataMap, routePath, concretePath) {
28
+ if (hasOwnRouteData(dataMap, concretePath)) return {
29
+ hasData: true,
30
+ data: dataMap[concretePath]
31
+ };
32
+ if (hasOwnRouteData(dataMap, routePath)) return {
33
+ hasData: true,
34
+ data: dataMap[routePath]
35
+ };
36
+ return {
37
+ hasData: false,
38
+ data: void 0
39
+ };
40
+ }
24
41
  function isStringRecord(value) {
25
42
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
26
43
  for (const recordValue of Object.values(value)) if (typeof recordValue !== "string") return false;
@@ -83,6 +100,6 @@ function validateRoutes(routes) {
83
100
  }
84
101
  }
85
102
  //#endregion
86
- export { expandRoutes, resolveSsgData, validateRoutes };
103
+ export { expandRoutes, resolveSsgData, resolveSsgRouteData, validateRoutes };
87
104
 
88
105
  //# sourceMappingURL=resolve-ssg-data.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-ssg-data.js","names":[],"sources":["../../src/ssg/resolve-ssg-data.ts"],"sourcesContent":["/**\n * Data resolution for SSG\n *\n * Merges user-supplied data overrides with any auto-discovered resources.\n * In phase 1, primarily handles user-supplied data.\n */\n\nimport type { SSRData } from '../common/ssr';\nimport type { RouteConfig } from './types';\nimport { interpolateRoutePath } from './route-utils';\n\ninterface DataResolutionOptions {\n /** User-supplied data overrides per route path */\n dataOverrides?: Record<string, unknown>;\n}\n\n/**\n * Resolve and validate data for SSG routes\n * Returns a map of route path -> SSRData\n *\n * In phase 1: accepts user-supplied dataOverrides\n * In phase 2: can be extended to auto-discover resources\n */\nexport function resolveSsgData(\n routes: RouteConfig[],\n options: DataResolutionOptions = {}\n): Record<string, SSRData> {\n const { dataOverrides = {} } = options;\n const dataMap: Record<string, SSRData> = {};\n\n for (const route of routes) {\n const concretePath = interpolateRoutePath(route.path, route.params);\n const overridePath =\n concretePath in dataOverrides\n ? concretePath\n : route.path in dataOverrides\n ? route.path\n : null;\n\n // Check if user provided data for this route\n if (overridePath) {\n const data = dataOverrides[overridePath];\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n throw new Error(\n `data for route \"${overridePath}\" must be an object, got ${typeof data}`\n );\n }\n dataMap[overridePath] = data as SSRData;\n }\n // In phase 1, routes without data are rendered with no SSR data\n // Phase 2 can add auto-discovery here\n }\n\n return dataMap;\n}\n\nfunction isStringRecord(value: unknown): value is Record<string, string> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n\n for (const recordValue of Object.values(value)) {\n if (typeof recordValue !== 'string') {\n return false;\n }\n }\n\n return true;\n}\n\nfunction validateRouteShape(route: RouteConfig): void {\n if (typeof route.path !== 'string' || !route.path.startsWith('/')) {\n throw new Error(\n `route path must be a string starting with \"/\", got \"${route.path}\"`\n );\n }\n\n const handler = route.handler ?? route.component;\n if (typeof handler !== 'function') {\n throw new Error(\n `route handler must be a function for path \"${route.path}\"`\n );\n }\n\n if (route.params !== undefined && !isStringRecord(route.params)) {\n throw new Error(\n `route \"${route.path}\" params must be an object containing only string values`\n );\n }\n\n if (route.invalidationKeys !== undefined) {\n if (!Array.isArray(route.invalidationKeys)) {\n throw new Error(\n `route \"${route.path}\" invalidationKeys must be an array of strings`\n );\n }\n for (const key of route.invalidationKeys) {\n if (typeof key !== 'string' || key.length === 0) {\n throw new Error(\n `route \"${route.path}\" invalidationKeys must contain only non-empty strings`\n );\n }\n }\n }\n}\n\nfunction validateEntriesResult(\n route: RouteConfig,\n entries: unknown\n): Array<Record<string, string>> {\n if (!Array.isArray(entries)) {\n throw new Error(\n `route \"${route.path}\" entries must return an array of param maps`\n );\n }\n\n return entries.map((entry, index) => {\n if (!isStringRecord(entry)) {\n throw new Error(\n `route \"${route.path}\" entries[${index}] must be an object containing only string values`\n );\n }\n\n return entry;\n });\n}\n\nexport async function expandRoutes(\n routes: RouteConfig[]\n): Promise<RouteConfig[]> {\n if (!Array.isArray(routes)) {\n throw new Error('routes must be an array');\n }\n\n const expanded: RouteConfig[] = [];\n\n for (const route of routes) {\n validateRouteShape(route);\n\n if (route.params) {\n expanded.push({ ...route, entries: undefined });\n }\n\n if (!route.entries) {\n if (!route.params) {\n expanded.push(route);\n }\n continue;\n }\n\n const entries = validateEntriesResult(route, await route.entries());\n for (const params of entries) {\n expanded.push({ ...route, params, entries: undefined });\n }\n }\n\n return expanded;\n}\n\n/**\n * Validate routes are properly configured\n */\nexport function validateRoutes(routes: RouteConfig[]): void {\n if (!Array.isArray(routes)) {\n throw new Error('routes must be an array');\n }\n\n const seen = new Set<string>();\n\n for (const route of routes) {\n validateRouteShape(route);\n\n const key = `${route.path}::${JSON.stringify(route.params || {})}`;\n if (seen.has(key)) {\n throw new Error(\n `duplicate route entry detected for path \"${route.path}\"`\n );\n }\n seen.add(key);\n\n if (route.path.includes('{')) {\n const paramNames = Array.from(route.path.matchAll(/\\{([^}]+)\\}/g)).map(\n (m) => m[1]\n );\n if (!route.params) {\n throw new Error(\n `route \"${route.path}\" uses path parameters and requires params`\n );\n }\n for (const name of paramNames) {\n if (!(name in route.params)) {\n throw new Error(\n `route \"${route.path}\" missing required param \"${name}\"`\n );\n }\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolve-ssg-data.js","names":[],"sources":["../../src/ssg/resolve-ssg-data.ts"],"sourcesContent":["/**\n * Data resolution for SSG\n *\n * Merges user-supplied data overrides with any auto-discovered resources.\n * In phase 1, primarily handles user-supplied data.\n */\n\nimport type { SSRData } from '../common/ssr';\nimport type { RouteConfig } from './types';\nimport { interpolateRoutePath } from './route-utils';\n\ninterface DataResolutionOptions {\n /** User-supplied data overrides per route path */\n dataOverrides?: Record<string, unknown>;\n}\n\nexport interface ResolvedSsgRouteData {\n hasData: boolean;\n data?: SSRData;\n}\n\nfunction hasOwnRouteData(\n dataMap: Record<string, SSRData>,\n path: string\n): boolean {\n return Object.prototype.hasOwnProperty.call(dataMap, path);\n}\n\n/**\n * Resolve and validate data for SSG routes\n * Returns a map of route path -> SSRData\n *\n * In phase 1: accepts user-supplied dataOverrides\n * In phase 2: can be extended to auto-discover resources\n */\nexport function resolveSsgData(\n routes: RouteConfig[],\n options: DataResolutionOptions = {}\n): Record<string, SSRData> {\n const { dataOverrides = {} } = options;\n const dataMap: Record<string, SSRData> = {};\n\n for (const route of routes) {\n const concretePath = interpolateRoutePath(route.path, route.params);\n const overridePath =\n concretePath in dataOverrides\n ? concretePath\n : route.path in dataOverrides\n ? route.path\n : null;\n\n // Check if user provided data for this route\n if (overridePath) {\n const data = dataOverrides[overridePath];\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n throw new Error(\n `data for route \"${overridePath}\" must be an object, got ${typeof data}`\n );\n }\n dataMap[overridePath] = data as SSRData;\n }\n // In phase 1, routes without data are rendered with no SSR data\n // Phase 2 can add auto-discovery here\n }\n\n return dataMap;\n}\n\nexport function resolveSsgRouteData(\n dataMap: Record<string, SSRData>,\n routePath: string,\n concretePath: string\n): ResolvedSsgRouteData {\n if (hasOwnRouteData(dataMap, concretePath)) {\n return {\n hasData: true,\n data: dataMap[concretePath],\n };\n }\n\n if (hasOwnRouteData(dataMap, routePath)) {\n return {\n hasData: true,\n data: dataMap[routePath],\n };\n }\n\n return {\n hasData: false,\n data: undefined,\n };\n}\n\nfunction isStringRecord(value: unknown): value is Record<string, string> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n\n for (const recordValue of Object.values(value)) {\n if (typeof recordValue !== 'string') {\n return false;\n }\n }\n\n return true;\n}\n\nfunction validateRouteShape(route: RouteConfig): void {\n if (typeof route.path !== 'string' || !route.path.startsWith('/')) {\n throw new Error(\n `route path must be a string starting with \"/\", got \"${route.path}\"`\n );\n }\n\n const handler = route.handler ?? route.component;\n if (typeof handler !== 'function') {\n throw new Error(\n `route handler must be a function for path \"${route.path}\"`\n );\n }\n\n if (route.params !== undefined && !isStringRecord(route.params)) {\n throw new Error(\n `route \"${route.path}\" params must be an object containing only string values`\n );\n }\n\n if (route.invalidationKeys !== undefined) {\n if (!Array.isArray(route.invalidationKeys)) {\n throw new Error(\n `route \"${route.path}\" invalidationKeys must be an array of strings`\n );\n }\n for (const key of route.invalidationKeys) {\n if (typeof key !== 'string' || key.length === 0) {\n throw new Error(\n `route \"${route.path}\" invalidationKeys must contain only non-empty strings`\n );\n }\n }\n }\n}\n\nfunction validateEntriesResult(\n route: RouteConfig,\n entries: unknown\n): Array<Record<string, string>> {\n if (!Array.isArray(entries)) {\n throw new Error(\n `route \"${route.path}\" entries must return an array of param maps`\n );\n }\n\n return entries.map((entry, index) => {\n if (!isStringRecord(entry)) {\n throw new Error(\n `route \"${route.path}\" entries[${index}] must be an object containing only string values`\n );\n }\n\n return entry;\n });\n}\n\nexport async function expandRoutes(\n routes: RouteConfig[]\n): Promise<RouteConfig[]> {\n if (!Array.isArray(routes)) {\n throw new Error('routes must be an array');\n }\n\n const expanded: RouteConfig[] = [];\n\n for (const route of routes) {\n validateRouteShape(route);\n\n if (route.params) {\n expanded.push({ ...route, entries: undefined });\n }\n\n if (!route.entries) {\n if (!route.params) {\n expanded.push(route);\n }\n continue;\n }\n\n const entries = validateEntriesResult(route, await route.entries());\n for (const params of entries) {\n expanded.push({ ...route, params, entries: undefined });\n }\n }\n\n return expanded;\n}\n\n/**\n * Validate routes are properly configured\n */\nexport function validateRoutes(routes: RouteConfig[]): void {\n if (!Array.isArray(routes)) {\n throw new Error('routes must be an array');\n }\n\n const seen = new Set<string>();\n\n for (const route of routes) {\n validateRouteShape(route);\n\n const key = `${route.path}::${JSON.stringify(route.params || {})}`;\n if (seen.has(key)) {\n throw new Error(\n `duplicate route entry detected for path \"${route.path}\"`\n );\n }\n seen.add(key);\n\n if (route.path.includes('{')) {\n const paramNames = Array.from(route.path.matchAll(/\\{([^}]+)\\}/g)).map(\n (m) => m[1]\n );\n if (!route.params) {\n throw new Error(\n `route \"${route.path}\" uses path parameters and requires params`\n );\n }\n for (const name of paramNames) {\n if (!(name in route.params)) {\n throw new Error(\n `route \"${route.path}\" missing required param \"${name}\"`\n );\n }\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { ComponentFunction } from "../common/component.js";
2
+ import { DocumentRenderer } from "../common/ssr.js";
2
3
  import { RouteAuthMode, RouteHandler, RoutePathParams, RoutePolicy } from "../common/router.js";
3
4
 
4
5
  //#region src/ssg/types.d.ts
@@ -59,6 +60,8 @@ interface SSGOptions<TRoutes extends readonly RouteConfig[] = RouteConfig[]> {
59
60
  seed?: number;
60
61
  /** Optional override data for resources (route-keyed) */
61
62
  dataOverrides?: Record<string, unknown>;
63
+ /** Optional document wrapper for full HTML output */
64
+ document?: DocumentRenderer;
62
65
  /** Optional concurrency limit for rendering (default: 10) */
63
66
  concurrency?: number;
64
67
  /** Preferred render parallelism. `'auto'` resolves from the host machine. */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/ssg/types.ts"],"mappings":";;;;KAYY,OAAA;AAAA,KAEA,iBAAA;AAAA,KAEA,iBAAA;AAAA,KAUP,iBAAA,uCAAwD,IAAA,GACzD,MAAA,mBACA,eAAA,CAAgB,IAAA;AAdpB;;;;AAA6B;AAE7B;AAFA,UAsBiB,WAAA;;EAEf,IAAA,EAAM,IAAA;EAtBqB;EAwB3B,OAAA,GAAU,YAAA;EAdU;EAgBpB,SAAA,GAAY,iBAAA;EAhB+C;EAkB3D,KAAA,GAAQ,MAAA;EAhBU;EAkBlB,SAAA;EAlBiB;EAoBjB,IAAA,GAAO,aAAA;EAtBc;EAwBrB,IAAA;EAvBE;EAyBF,UAAA;EAxBkB;EA0BlB,QAAA,YAAoB,WAAA;EA1BE;EA4BtB,MAAA,GAAS,iBAAA,CAAkB,IAAA;EApBD;EAsB1B,gBAAA;EApBM;;;;;;;;;;;;EAiCN,OAAA,SACI,KAAA,CAAM,iBAAA,CAAkB,IAAA,KACxB,OAAA,CAAQ,KAAA,CAAM,iBAAA,CAAkB,IAAA;AAAA;;UAIrB,UAAA,0BACU,WAAA,KAAgB,WAAA;EAL9B;EAQX,MAAA,EAAQ,OAAA;EA3CR;EA6CA,SAAA;EA3CA;EA6CA,IAAA;EA3CA;EA6CA,aAAA,GAAgB,MAAA;EA3ChB;EA6CA,WAAA;EA3CA;EA6CA,WAAA;AAAA;;UAIe,kBAAA;EAzCf;EA2CA,IAAA,GAAO,OAAO;EAzCd;EA2CA,WAAA;EA3C2B;EA6C3B,aAAA;EA9BA;EAgCA,SAAA;AAAA;;UAIe,iBAAA;EAlCH;EAoCZ,IAAA;EApCoC;EAsCpC,QAAA;EAtCwC;EAwCxC,IAAA;EApCyB;EAsCzB,QAAA;EArCyB;EAuCzB,cAAA;EApCQ;EAsCR,aAAA;EAhCsB;EAkCtB,MAAA,EAAQ,iBAAA;EA3CR;EA6CA,MAAA,EAAQ,iBAAiB;EA7CgB;EA+CzC,OAAA;EA5CQ;EA8CR,KAAA;AAAA;;UAIe,SAAA;EA1Cf;EA4CA,WAAA;EA1CW;EA4CX,WAAA;EAxCe;EA0Cf,UAAA;;EAEA,MAAA;EA1CA;EA4CA,aAAA;EA1CA;EA4CA,IAAA,EAAM,OAAA;EAxCN;EA0CA,OAAA;EA1CS;EA4CT,OAAA;EAxCgC;EA0ChC,OAAA;EA1ByB;EA4BzB,SAAA;EAxCA;EA0CA,eAAA;EAtCA;EAwCA,iBAAA;EApCA;EAsCA,MAAA,EAAQ,iBAAiB;AAAA;;UAIV,WAAA,SAAoB,MAAA;EApCnC;EAsCA,WAAA;EApCK;EAsCL,WAAA;EAlCe;EAoCf,UAAA;;EAEA,MAAA;EApCA;EAsCA,aAAA;EAlCA;EAoCA,IAAA,EAAM,OAAA;EAhCN;EAkCA,OAAA;EAhCM;EAkCN,OAAA;EA9BA;EAgCA,OAAA;EA5BA;EA8BA,SAAA;EA1BA;EA4BA,eAAA;EA1BQ;EA4BR,iBAAA;EA5ByB;EA8BzB,MAAA,EAAQ,KAAA;IA1BmB,eA4BzB,IAAA,UAhBI;IAkBJ,QAAA,UAUQ;IARR,QAAA,UAhCiC;IAkCjC,cAAA,UAlCuC;IAoCvC,aAAA,UAlCF;IAoCE,MAAA,EAAQ,iBAAA,EAhCV;IAkCE,MAAA,EAAQ,iBAAA,EA9BV;IAgCE,OAAA,WA9BI;IAgCJ,KAAA;EAAA;AAAA;;UAKa,mBAAA;EAAA,CACd,GAAA;IACC,KAAA;IACA,YAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/ssg/types.ts"],"mappings":";;;;;KAaY,OAAA;AAAA,KAEA,iBAAA;AAAA,KAEA,iBAAA;AAAA,KAUP,iBAAA,uCAAwD,IAAA,GACzD,MAAA,mBACA,eAAA,CAAgB,IAAA;AAdpB;;;;AAA6B;AAE7B;AAFA,UAsBiB,WAAA;;EAEf,IAAA,EAAM,IAAA;EAtBqB;EAwB3B,OAAA,GAAU,YAAA;EAdU;EAgBpB,SAAA,GAAY,iBAAA;EAhB+C;EAkB3D,KAAA,GAAQ,MAAA;EAhBU;EAkBlB,SAAA;EAlBiB;EAoBjB,IAAA,GAAO,aAAA;EAtBc;EAwBrB,IAAA;EAvBE;EAyBF,UAAA;EAxBkB;EA0BlB,QAAA,YAAoB,WAAA;EA1BE;EA4BtB,MAAA,GAAS,iBAAA,CAAkB,IAAA;EApBD;EAsB1B,gBAAA;EApBM;;;;;;;;;;;;EAiCN,OAAA,SACI,KAAA,CAAM,iBAAA,CAAkB,IAAA,KACxB,OAAA,CAAQ,KAAA,CAAM,iBAAA,CAAkB,IAAA;AAAA;;UAIrB,UAAA,0BACU,WAAA,KAAgB,WAAA;EAL9B;EAQX,MAAA,EAAQ,OAAA;EA3CR;EA6CA,SAAA;EA3CA;EA6CA,IAAA;EA3CA;EA6CA,aAAA,GAAgB,MAAA;EA3ChB;EA6CA,QAAA,GAAW,gBAAA;EA3CX;EA6CA,WAAA;EA3CO;EA6CP,WAAA;AAAA;;UAIe,kBAAA;EAzCf;EA2CA,IAAA,GAAO,OAAO;EA3Ca;EA6C3B,WAAA;EA9BA;EAgCA,aAAA;EA/BU;EAiCV,SAAA;AAAA;;UAIe,iBAAA;EApCqB;EAsCpC,IAAA;EAtCwC;EAwCxC,QAAA;EApCyB;EAsCzB,IAAA;EArCyB;EAuCzB,QAAA;EApCQ;EAsCR,cAAA;EA9BW;EAgCX,aAAA;EAhC2B;EAkC3B,MAAA,EAAQ,iBAAA;EA7CiB;EA+CzB,MAAA,EAAQ,iBAAiB;EA5CzB;EA8CA,OAAA;EA5CA;EA8CA,KAAA;AAAA;;UAIe,SAAA;EA5CJ;EA8CX,WAAA;EA1CA;EA4CA,WAAA;EA5CW;EA8CX,UAAA;EA1CiC;EA4CjC,MAAA;EA1Cc;EA4Cd,aAAA;EA5CO;EA8CP,IAAA,EAAM,OAAA;EA1CN;EA4CA,OAAA;EA1CS;EA4CT,OAAA;EAxCe;EA0Cf,OAAA;;EAEA,SAAA;EA1CA;EA4CA,eAAA;EAxCA;EA0CA,iBAAA;EAtCA;EAwCA,MAAA,EAAQ,iBAAiB;AAAA;;UAIV,WAAA,SAAoB,MAAA;EAtC3B;EAwCR,WAAA;EApCA;EAsCA,WAAA;EAtCK;EAwCL,UAAA;EApCwB;EAsCxB,MAAA;EAZyB;EAczB,aAAA;EApCA;EAsCA,IAAA,EAAM,OAAA;EAlCN;EAoCA,OAAA;EAhCA;EAkCA,OAAA;EAhCA;EAkCA,OAAA;EA9BA;EAgCA,SAAA;EA5BA;EA8BA,eAAA;EA1BA;EA4BA,iBAAA;EA5ByB;EA8BzB,MAAA,EAAQ,KAAA;IA1BO,eA4Bb,IAAA,UA5ByB;IA8BzB,QAAA,UAQQ;IANR,QAAA,UANM;IAQN,cAAA,UAlCuC;IAoCvC,aAAA,UApCiC;IAsCjC,MAAA,EAAQ,iBAAA,EAlCV;IAoCE,MAAA,EAAQ,iBAAA,EAhCV;IAkCE,OAAA,WA9BF;IAgCE,KAAA;EAAA;AAAA;;UAKa,mBAAA;EAAA,CACd,GAAA;IACC,KAAA;IACA,YAAA;EAAA;AAAA"}
@@ -1,7 +1,6 @@
1
+ import { SSRData } from "../common/ssr.js";
1
2
  import { Route, RouteAuthOptions } from "../common/router.js";
2
3
  import { SSRDataMissingError } from "../common/ssr-errors.js";
3
- import { SSRData } from "../common/ssr.js";
4
-
5
4
  //#region src/ssr/context.d.ts
6
5
  interface RenderContext {
7
6
  url: string;
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","names":[],"sources":["../../src/ssr/context.ts"],"mappings":";;;;;UAgBiB,aAAA;EACf,GAAA;EACA,IAAA;EACA,IAAA,GAAO,OAAA;EACP,MAAA,GAAS,MAAA;EACT,MAAA,YAAkB,KAAA;EAClB,SAAA,GAAY,gBAAA;EACZ,MAAA,GAAS,WAAA;EACT,UAAA,GAAa,GAAA;EACb,aAAA,EAAe,KAAA;EAEf,UAAA;EACA,UAAA,EAAY,MAAA;AAAA;AAAA,KAIF,UAAA,GAAa,aAAa;AAAA,iBAoDtB,mBAAA,CACd,IAAA,WACA,IAAA;EACE,GAAA;EACA,IAAA,GAAO,OAAA;EACP,MAAA,GAAS,MAAA;EACT,MAAA,YAAkB,KAAA;EAClB,SAAA,GAAY,gBAAA;EACZ,MAAA,GAAS,WAAA;AAAA,IAEV,aAAA;;;;;iBAsBa,iBAAA,IAAqB,GAAA,EAAK,aAAA,EAAe,EAAA,QAAU,CAAA,GAAI,CAAA;;AAxFnD;AAIpB;;iBAuGgB,gBAAA,IAAoB,aAAa;AAAA,cASpC,aAAA,SAAa,gBAAmB;AAAA,cAChC,cAAA,SAAc,iBAAoB;AAAA,cAClC,oBAAA,SAAoB,gBAAmB;AAAA,iBAEpC,iBAAA,IAAqB,GAAA,EAAK,aAAA,EAAe,EAAA,QAAU,CAAA,GAAI,CAAA;;;;;iBASvD,mBAAA"}
1
+ {"version":3,"file":"context.d.ts","names":[],"sources":["../../src/ssr/context.ts"],"mappings":";;;;UAgBiB,aAAA;EACf,GAAA;EACA,IAAA;EACA,IAAA,GAAO,OAAA;EACP,MAAA,GAAS,MAAA;EACT,MAAA,YAAkB,KAAA;EAClB,SAAA,GAAY,gBAAA;EACZ,MAAA,GAAS,WAAA;EACT,UAAA,GAAa,GAAA;EACb,aAAA,EAAe,KAAA;EAEf,UAAA;EACA,UAAA,EAAY,MAAA;AAAA;AAAA,KAIF,UAAA,GAAa,aAAa;AAAA,iBAoDtB,mBAAA,CACd,IAAA,WACA,IAAA;EACE,GAAA;EACA,IAAA,GAAO,OAAA;EACP,MAAA,GAAS,MAAA;EACT,MAAA,YAAkB,KAAA;EAClB,SAAA,GAAY,gBAAA;EACZ,MAAA,GAAS,WAAA;AAAA,IAEV,aAAA;;;;;iBAsBa,iBAAA,IAAqB,GAAA,EAAK,aAAA,EAAe,EAAA,QAAU,CAAA,GAAI,CAAA;;AAxFnD;AAIpB;;iBAuGgB,gBAAA,IAAoB,aAAa;AAAA,cASpC,aAAA,SAAa,gBAAmB;AAAA,cAChC,cAAA,SAAc,iBAAoB;AAAA,cAClC,oBAAA,SAAoB,gBAAmB;AAAA,iBAEpC,iBAAA,IAAqB,GAAA,EAAK,aAAA,EAAe,EAAA,QAAU,CAAA,GAAI,CAAA;;;;;iBASvD,mBAAA"}
@@ -1,7 +1,7 @@
1
1
  import { JSXElement } from "../common/jsx.js";
2
+ import { DocumentRenderArgs, DocumentRenderContext, DocumentRenderer, SSRData } from "../common/ssr.js";
2
3
  import { RouteAuthOptions, RouteHandler, RouteManifest, RouteRequestResult } from "../common/router.js";
3
4
  import { SSRDataMissingError } from "../common/ssr-errors.js";
4
- import { SSRData } from "../common/ssr.js";
5
5
  import { SSRComponent, VNode } from "./types.js";
6
6
  import { renderResolvedToStringSync } from "./render-resolved.js";
7
7
 
@@ -41,21 +41,20 @@ type SSRRoute = {
41
41
  handler: RouteHandler;
42
42
  namespace?: string;
43
43
  };
44
- declare function renderToString(component: (props?: Record<string, unknown>) => VNode | JSXElement | string | number | null): string;
45
- declare function renderToString(opts: {
46
- url: string;
47
- routes: SSRRoute[];
48
- seed?: number;
49
- data?: SSRData;
50
- }): string;
51
- declare function renderToStream(opts: {
44
+ type RouteRenderOptions = {
52
45
  url: string;
53
46
  routes: SSRRoute[];
54
47
  seed?: number;
55
48
  data?: SSRData;
49
+ document?: DocumentRenderer;
50
+ };
51
+ type RouteStreamOptions = RouteRenderOptions & {
56
52
  onChunk(html: string): void;
57
53
  onComplete(): void;
58
- }): void;
54
+ };
55
+ declare function renderToString(component: (props?: Record<string, unknown>) => VNode | JSXElement | string | number | null): string;
56
+ declare function renderToString(opts: RouteRenderOptions): string;
57
+ declare function renderToStream(opts: RouteStreamOptions): void;
59
58
  //#endregion
60
- export { type SSRComponent, SSRDataMissingError, SSRRoute, type VNode, renderResolvedToStringSync, renderToStream, renderToString, renderToStringSync, resolveRequest };
59
+ export { type DocumentRenderArgs, type DocumentRenderContext, type DocumentRenderer, type SSRComponent, SSRDataMissingError, SSRRoute, type VNode, renderResolvedToStringSync, renderToStream, renderToString, renderToStringSync, resolveRequest };
61
60
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/ssr/index.ts"],"mappings":";;;;;;;;;;;;;iBAusCgB,kBAAA,CACd,SAAA,GACE,KAAA,GAAQ,MAAA,sBACL,KAAA,GAAQ,UAAA,iDACb,KAAA,GAAQ,MAAA,mBACR,OAAA;EAAY,IAAA;EAAe,IAAA,GAAO,OAAA;AAAA;AAAA,iBAgCd,cAAA,CACpB,IAAA;EAEM,GAAA;EACA,QAAA,EAAU,aAAA;EACV,MAAA,GAAS,KAAA;IACP,IAAA;IACA,OAAA,EAAS,YAAA;IACT,SAAA;EAAA;EAEF,IAAA,GAAO,gBAAA;EACP,MAAA,GAAS,WAAA;AAAA;EAGT,GAAA;EACA,QAAA,GAAW,aAAA;EACX,MAAA,EAAQ,KAAA;IACN,IAAA;IACA,OAAA,EAAS,YAAA;IACT,SAAA;EAAA;EAEF,IAAA,GAAO,gBAAA;EACP,MAAA,GAAS,WAAA;AAAA,IAEd,OAAA,CAAQ,kBAAA;AAAA,KAsCC,QAAA;EACV,IAAA;EACA,OAAA,EAAS,YAAY;EACrB,SAAA;AAAA;AAAA,iBAGc,cAAA,CACd,SAAA,GACE,KAAA,GAAQ,MAAA,sBACL,KAAA,GAAQ,UAAA;AAAA,iBAEC,cAAA,CAAe,IAAA;EAC7B,GAAA;EACA,MAAA,EAAQ,QAAA;EACR,IAAA;EACA,IAAA,GAAO,OAAO;AAAA;AAAA,iBAuBA,cAAA,CAAe,IAAA;EAC7B,GAAA;EACA,MAAA,EAAQ,QAAA;EACR,IAAA;EACA,IAAA,GAAO,OAAO;EACd,OAAA,CAAQ,IAAA;EACR,UAAA;AAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/ssr/index.ts"],"mappings":";;;;;;;;;;;;;iBAosCgB,kBAAA,CACd,SAAA,GACE,KAAA,GAAQ,MAAA,sBACL,KAAA,GAAQ,UAAA,iDACb,KAAA,GAAQ,MAAA,mBACR,OAAA;EAAY,IAAA;EAAe,IAAA,GAAO,OAAA;AAAA;AAAA,iBAgCd,cAAA,CACpB,IAAA;EAEM,GAAA;EACA,QAAA,EAAU,aAAA;EACV,MAAA,GAAS,KAAA;IACP,IAAA;IACA,OAAA,EAAS,YAAA;IACT,SAAA;EAAA;EAEF,IAAA,GAAO,gBAAA;EACP,MAAA,GAAS,WAAA;AAAA;EAGT,GAAA;EACA,QAAA,GAAW,aAAA;EACX,MAAA,EAAQ,KAAA;IACN,IAAA;IACA,OAAA,EAAS,YAAA;IACT,SAAA;EAAA;EAEF,IAAA,GAAO,gBAAA;EACP,MAAA,GAAS,WAAA;AAAA,IAEd,OAAA,CAAQ,kBAAA;AAAA,KAsCC,QAAA;EACV,IAAA;EACA,OAAA,EAAS,YAAY;EACrB,SAAA;AAAA;AAAA,KAGG,kBAAA;EACH,GAAA;EACA,MAAA,EAAQ,QAAA;EACR,IAAA;EACA,IAAA,GAAO,OAAA;EACP,QAAA,GAAW,gBAAA;AAAA;AAAA,KAGR,kBAAA,GAAqB,kBAAkB;EAC1C,OAAA,CAAQ,IAAA;EACR,UAAA;AAAA;AAAA,iBAiGc,cAAA,CACd,SAAA,GACE,KAAA,GAAQ,MAAA,sBACL,KAAA,GAAQ,UAAA;AAAA,iBAEC,cAAA,CAAe,IAAwB,EAAlB,kBAAkB;AAAA,iBAiBvC,cAAA,CAAe,IAAwB,EAAlB,kBAAkB"}
package/dist/ssr/index.js CHANGED
@@ -10,10 +10,10 @@ import { evaluateCaseState, evaluateShowState } from "../runtime/control.js";
10
10
  import { SSRDataMissingError } from "../common/ssr-errors.js";
11
11
  import { VOID_ELEMENTS, escapeText, styleObjToCss } from "./escape.js";
12
12
  import { createRenderContext, getRenderContext, throwSSRDataMissing, withRenderContext } from "./context.js";
13
- import { resolveRouteFromRoutes, resolveRouteRequest } from "../router/route.js";
13
+ import { _resolveRouteMatchFromRoutes, resolveRouteFromRoutes, resolveRouteRequest } from "../router/route.js";
14
14
  import "../jsx/index.js";
15
15
  import { DefaultPortal, disposeDefaultPortalScope } from "../foundations/structures/portal.js";
16
- import { SSR_RENDER_DATA_ATTR } from "../common/ssr.js";
16
+ import { SSR_RENDER_DATA_ATTR, renderDocument } from "../common/ssr.js";
17
17
  import { getCurrentRenderData, getNextKey, startRenderPhase, stopRenderPhase } from "./render-keys.js";
18
18
  import { installSSRBridge } from "../runtime/ssr-bridge.js";
19
19
  import { renderAttrs, renderAttrsDirect } from "./attrs.js";
@@ -65,24 +65,35 @@ function popSSRStrictPurityGuard() {
65
65
  /**
66
66
  * Synchronous rendering helpers (used for strictly synchronous SSR)
67
67
  */
68
- function renderChildSync(child, ctx) {
69
- if (typeof child === "string") return escapeText(child);
70
- if (typeof child === "number") return escapeText(String(child));
71
- if (child === null || child === void 0 || child === false) return "";
72
- if (child && typeof child === "object" && "type" in child) return renderNodeSync(child, ctx);
68
+ function renderRenderableSync(value, ctx) {
69
+ if (typeof value === "string") return escapeText(value);
70
+ if (typeof value === "number") return escapeText(String(value));
71
+ if (value === null || value === void 0 || value === false) return "";
72
+ if (Array.isArray(value)) return renderChildrenSync(value, ctx);
73
+ if (value && typeof value === "object" && "type" in value) return renderNodeSync(value, ctx);
73
74
  return "";
74
75
  }
75
- function renderChildSyncToSink(child, sink, ctx) {
76
- if (child === null || child === void 0 || child === false) return;
77
- if (typeof child === "string") {
78
- sink.write(escapeText(child));
76
+ function renderChildSync(child, ctx) {
77
+ return renderRenderableSync(child, ctx);
78
+ }
79
+ function renderRenderableSyncToSink(value, sink, ctx) {
80
+ if (value === null || value === void 0 || value === false) return;
81
+ if (typeof value === "string") {
82
+ sink.write(escapeText(value));
79
83
  return;
80
84
  }
81
- if (typeof child === "number") {
82
- sink.write(escapeText(String(child)));
85
+ if (typeof value === "number") {
86
+ sink.write(escapeText(String(value)));
83
87
  return;
84
88
  }
85
- if (child && typeof child === "object" && "type" in child) renderNodeSyncToSink(child, sink, ctx);
89
+ if (Array.isArray(value)) {
90
+ renderChildrenSyncToSink(value, sink, ctx);
91
+ return;
92
+ }
93
+ if (value && typeof value === "object" && "type" in value) renderNodeSyncToSink(value, sink, ctx);
94
+ }
95
+ function renderChildSyncToSink(child, sink, ctx) {
96
+ renderRenderableSyncToSink(child, sink, ctx);
86
97
  }
87
98
  function serializeHydrationRenderData(data) {
88
99
  if (!data || Object.keys(data).length === 0) return "";
@@ -195,19 +206,7 @@ function evaluateControlBoundaryChildren(node) {
195
206
  function renderChildrenSyncToSink(children, sink, ctx) {
196
207
  if (!children || !Array.isArray(children) || children.length === 0) return;
197
208
  if (children.length >= 32) {
198
- for (let i = 0; i < children.length; i++) {
199
- const child = children[i];
200
- if (child === null || child === void 0 || child === false) continue;
201
- if (typeof child === "string") {
202
- sink.write(escapeText(child));
203
- continue;
204
- }
205
- if (typeof child === "number") {
206
- sink.write(escapeText(String(child)));
207
- continue;
208
- }
209
- if (child && typeof child === "object" && "type" in child) renderNodeSyncToSink(child, sink, ctx);
210
- }
209
+ for (let i = 0; i < children.length; i++) renderRenderableSyncToSink(children[i], sink, ctx);
211
210
  return;
212
211
  }
213
212
  for (let i = 0; i < children.length; i++) renderChildSyncToSink(children[i], sink, ctx);
@@ -238,14 +237,10 @@ function renderNodeSync(node, ctx) {
238
237
  if (__SSR_DEBUG) try {
239
238
  logger.warn("[SSR] renderNodeSync type:", typeof type, type);
240
239
  } catch {}
241
- if (typeof type === "function") {
242
- const result = executeComponentSync(type, props, ctx);
243
- if (isPromiseLike(result)) throwSSRDataMissing();
244
- return renderNodeSync(result, ctx);
245
- }
240
+ if (typeof type === "function") return renderRenderableSync(inheritRenderableKey(node, executeComponentSync(type, props, ctx)), ctx);
246
241
  if (typeof type === "symbol") {
247
242
  if (type === Fragment) {
248
- const childrenArr = Array.isArray(node.children) ? node.children : Array.isArray(props?.children) ? props?.children : void 0;
243
+ const childrenArr = getRenderableChildren(node);
249
244
  /* istanbul ignore if - dev-only debug */
250
245
  if (__SSR_DEBUG) try {
251
246
  logger.warn("[SSR] fragment children length:", childrenArr?.length);
@@ -283,19 +278,19 @@ function renderNodeSync(node, ctx) {
283
278
  if (maybeDangerous !== void 0 && maybeDangerous !== null) {
284
279
  const { attrs, dangerousHtml } = renderAttrs(props, { returnDangerousHtml: true });
285
280
  if (dangerousHtml !== void 0) return `<${typeStr}${attrs}>${dangerousHtml}</${typeStr}>`;
286
- return `<${typeStr}${attrs}>${renderChildrenSync(node.children, ctx)}</${typeStr}>`;
281
+ return `<${typeStr}${attrs}>${renderChildrenSync(getRenderableChildren(node), ctx)}</${typeStr}>`;
287
282
  }
288
- return `<${typeStr}${renderAttrs(props)}>${renderChildrenSync(node.children, ctx)}</${typeStr}>`;
283
+ return `<${typeStr}${renderAttrs(props)}>${renderChildrenSync(getRenderableChildren(node), ctx)}</${typeStr}>`;
289
284
  }
290
285
  function renderNodeSyncToSink(node, sink, ctx) {
291
286
  const { type, props } = node;
292
287
  if (typeof type === "function") {
293
- renderNodeSyncToSink(inheritRenderableKey(node, executeComponentSync(type, props, ctx)), sink, ctx);
288
+ renderRenderableSyncToSink(inheritRenderableKey(node, executeComponentSync(type, props, ctx)), sink, ctx);
294
289
  return;
295
290
  }
296
291
  if (typeof type === "symbol") {
297
292
  if (type === Fragment) {
298
- renderChildrenSyncToSink(Array.isArray(node.children) ? node.children : Array.isArray(props?.children) ? props?.children : void 0, sink, ctx);
293
+ renderChildrenSyncToSink(getRenderableChildren(node), sink, ctx);
299
294
  return;
300
295
  }
301
296
  if (type === __CONTROL_BOUNDARY__) {
@@ -344,16 +339,11 @@ function renderNodeSyncToSink(node, sink, ctx) {
344
339
  renderAttrsDirect(props, sink);
345
340
  sink.write(">");
346
341
  if (dangerousHtml !== void 0) sink.write(dangerousHtml);
347
- else renderChildrenSyncToSink(node.children, sink, ctx);
342
+ else renderChildrenSyncToSink(getRenderableChildren(node), sink, ctx);
348
343
  sinkWrite3(sink, "</", typeStr, ">");
349
344
  return;
350
345
  }
351
- let children = node.children;
352
- if (children === void 0 && props?.children !== void 0) {
353
- const propsChildren = props.children;
354
- if (Array.isArray(propsChildren)) children = propsChildren;
355
- else if (propsChildren !== null && propsChildren !== false) children = [propsChildren];
356
- }
346
+ const children = getRenderableChildren(node);
357
347
  if (!children || Array.isArray(children) && children.length === 0) {
358
348
  sinkWrite2(sink, "<", typeStr);
359
349
  renderAttrsDirect(props, sink);
@@ -532,10 +522,11 @@ function verifyExpectedNode(node, state, ctx) {
532
522
  state.pendingText += String(node);
533
523
  return true;
534
524
  }
525
+ if (Array.isArray(node)) return verifyExpectedChildren(node, state, ctx);
535
526
  if (!node || typeof node !== "object" || !("type" in node)) return true;
536
527
  const vnode = node;
537
528
  const { type, props } = vnode;
538
- if (typeof type === "function") return verifyExpectedNode(inheritRenderableKey(vnode, executeComponentSync(type, props, ctx)), state, ctx);
529
+ if (typeof type === "function") return verifyRenderableNode(inheritRenderableKey(vnode, executeComponentSync(type, props, ctx)), state, ctx);
539
530
  if (typeof type === "symbol") {
540
531
  if (type === __CONTROL_BOUNDARY__) {
541
532
  const children = evaluateControlBoundaryChildren(vnode);
@@ -655,6 +646,64 @@ async function resolveRequest(opts) {
655
646
  params: resolved.params
656
647
  };
657
648
  }
649
+ function resolveSSRRouteRender(opts) {
650
+ const { url, routes, seed = 12345, data, document } = opts;
651
+ const routeTable = routes.map((route) => ({ ...route }));
652
+ const requestUrl = new URL(url, "http://localhost");
653
+ const matched = _resolveRouteMatchFromRoutes(requestUrl.pathname, routeTable);
654
+ if (!matched) throw new Error(`SSR: no route found for url: ${url}`);
655
+ const ctx = createRenderContext(seed, {
656
+ url,
657
+ data,
658
+ params: matched.params,
659
+ routes: routeTable
660
+ });
661
+ return {
662
+ url,
663
+ requestUrl,
664
+ route: matched.route,
665
+ params: matched.params,
666
+ seed,
667
+ data,
668
+ ctx,
669
+ document
670
+ };
671
+ }
672
+ function buildDocumentRenderArgs(resolved, appHtml) {
673
+ return {
674
+ appHtml,
675
+ context: {
676
+ mode: "ssr",
677
+ url: resolved.url,
678
+ pathname: resolved.requestUrl.pathname,
679
+ search: resolved.requestUrl.search,
680
+ hash: resolved.requestUrl.hash,
681
+ params: resolved.params,
682
+ data: resolved.data,
683
+ seed: resolved.seed,
684
+ route: {
685
+ path: resolved.route.path,
686
+ namespace: resolved.route.namespace
687
+ }
688
+ }
689
+ };
690
+ }
691
+ function renderResolvedRouteAppToSink(resolved, sink) {
692
+ const { ctx, data, route, params } = resolved;
693
+ withRenderContext(ctx, () => {
694
+ startRenderPhase(data || null);
695
+ try {
696
+ renderRenderableSyncToSink(executeComponentSync(route.handler, params, ctx), sink, ctx);
697
+ sink.write(serializeHydrationRenderData(data));
698
+ } finally {
699
+ try {
700
+ stopRenderPhase();
701
+ } finally {
702
+ disposeSSRTemporaryOwners(ctx);
703
+ }
704
+ }
705
+ });
706
+ }
658
707
  function renderToString(arg) {
659
708
  if (typeof arg === "function") return renderToStringSync(arg);
660
709
  const opts = arg;
@@ -675,29 +724,16 @@ function renderToStream(opts) {
675
724
  sink.end();
676
725
  }
677
726
  function renderToSinkInternal(opts) {
678
- const { url, routes, seed = 12345, data, sink } = opts;
679
- const routeTable = routes.map((route) => ({ ...route }));
680
- const resolved = resolveRouteFromRoutes(new URL(url, "http://localhost").pathname, routeTable);
681
- if (!resolved) throw new Error(`SSR: no route found for url: ${url}`);
682
- const ctx = createRenderContext(seed, {
683
- url,
684
- data,
685
- params: resolved.params,
686
- routes: routeTable
687
- });
688
- withRenderContext(ctx, () => {
689
- startRenderPhase(data || null);
690
- try {
691
- renderNodeSyncToSink(executeComponentSync(resolved.handler, resolved.params, ctx), sink, ctx);
692
- sink.write(serializeHydrationRenderData(data));
693
- } finally {
694
- try {
695
- stopRenderPhase();
696
- } finally {
697
- disposeSSRTemporaryOwners(ctx);
698
- }
699
- }
700
- });
727
+ const { sink, ...renderOptions } = opts;
728
+ const resolved = resolveSSRRouteRender(renderOptions);
729
+ if (!resolved.document) {
730
+ renderResolvedRouteAppToSink(resolved, sink);
731
+ return;
732
+ }
733
+ const appSink = new StringSink();
734
+ renderResolvedRouteAppToSink(resolved, appSink);
735
+ appSink.end();
736
+ sink.write(renderDocument(resolved.document, buildDocumentRenderArgs(resolved, appSink.toString()), "renderToString()/renderToStream()"));
701
737
  }
702
738
  //#endregion
703
739
  export { SSRDataMissingError, renderResolvedToStringSync, renderToStream, renderToString, renderToStringSync, resolveRequest };