@zenithbuild/cli 0.7.11 → 0.7.12

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 (87) hide show
  1. package/README.md +10 -1
  2. package/dist/adapters/adapter-netlify-static.d.ts +2 -5
  3. package/dist/adapters/adapter-netlify.d.ts +2 -5
  4. package/dist/adapters/adapter-netlify.js +22 -5
  5. package/dist/adapters/adapter-types.d.ts +32 -13
  6. package/dist/adapters/adapter-types.js +0 -59
  7. package/dist/adapters/adapter-vercel-static.d.ts +2 -5
  8. package/dist/adapters/adapter-vercel.d.ts +2 -5
  9. package/dist/adapters/adapter-vercel.js +21 -6
  10. package/dist/adapters/copy-hosted-page-runtime.d.ts +2 -1
  11. package/dist/adapters/copy-hosted-page-runtime.js +68 -3
  12. package/dist/adapters/resolve-adapter.d.ts +6 -4
  13. package/dist/build/expression-rewrites.d.ts +3 -1
  14. package/dist/build/expression-rewrites.js +14 -2
  15. package/dist/build/page-component-loop.d.ts +1 -0
  16. package/dist/build/page-component-loop.js +66 -6
  17. package/dist/build/page-ir-normalization.js +7 -0
  18. package/dist/build/page-loop-state.d.ts +2 -1
  19. package/dist/build/page-loop-state.js +9 -2
  20. package/dist/build/page-loop.js +10 -1
  21. package/dist/build/scoped-expression-context.d.ts +5 -0
  22. package/dist/build/scoped-expression-context.js +133 -0
  23. package/dist/build/type-declarations.d.ts +2 -1
  24. package/dist/build/type-declarations.js +31 -1
  25. package/dist/build-output-manifest.d.ts +10 -6
  26. package/dist/build-output-manifest.js +4 -1
  27. package/dist/build.js +11 -2
  28. package/dist/component-instance-ir.js +1 -0
  29. package/dist/component-occurrences.d.ts +9 -0
  30. package/dist/component-occurrences.js +18 -0
  31. package/dist/config-plugins.d.ts +12 -0
  32. package/dist/config-plugins.js +100 -0
  33. package/dist/config.d.ts +1 -0
  34. package/dist/config.js +56 -5
  35. package/dist/dev-server/request-handler.js +46 -4
  36. package/dist/dev-server.js +92 -4
  37. package/dist/global-middleware-runtime-source.d.ts +15 -0
  38. package/dist/global-middleware-runtime-source.js +62 -0
  39. package/dist/global-middleware.d.ts +13 -0
  40. package/dist/global-middleware.js +252 -0
  41. package/dist/manifest.d.ts +9 -1
  42. package/dist/manifest.js +66 -26
  43. package/dist/preview/request-handler.js +78 -5
  44. package/dist/preview/server-runner.d.ts +7 -2
  45. package/dist/preview/server-runner.js +19 -6
  46. package/dist/preview/server-script-runner-template.js +97 -29
  47. package/dist/route-classification.d.ts +2 -1
  48. package/dist/route-classification.js +6 -2
  49. package/dist/scoped-server-data/analyze-owner-file.d.ts +3 -0
  50. package/dist/scoped-server-data/analyze-owner-file.js +149 -0
  51. package/dist/scoped-server-data/diagnostics.d.ts +18 -0
  52. package/dist/scoped-server-data/diagnostics.js +32 -0
  53. package/dist/scoped-server-data/lowering.d.ts +27 -0
  54. package/dist/scoped-server-data/lowering.js +242 -0
  55. package/dist/scoped-server-data/manifest-integration.d.ts +4 -0
  56. package/dist/scoped-server-data/manifest-integration.js +125 -0
  57. package/dist/scoped-server-data/owner-scanner.d.ts +6 -0
  58. package/dist/scoped-server-data/owner-scanner.js +55 -0
  59. package/dist/scoped-server-data/parse-owner-server-block.d.ts +12 -0
  60. package/dist/scoped-server-data/parse-owner-server-block.js +35 -0
  61. package/dist/scoped-server-data/runtime.d.ts +24 -0
  62. package/dist/scoped-server-data/runtime.js +121 -0
  63. package/dist/scoped-server-data/serialization-set.d.ts +2 -0
  64. package/dist/scoped-server-data/serialization-set.js +52 -0
  65. package/dist/scoped-server-data/static-props.d.ts +12 -0
  66. package/dist/scoped-server-data/static-props.js +307 -0
  67. package/dist/scoped-server-data/type-declarations.d.ts +10 -0
  68. package/dist/scoped-server-data/type-declarations.js +368 -0
  69. package/dist/scoped-server-data/types.d.ts +74 -0
  70. package/dist/scoped-server-data/types.js +1 -0
  71. package/dist/server-contract/auth-control-flow.d.ts +1 -0
  72. package/dist/server-contract/auth-control-flow.js +10 -0
  73. package/dist/server-contract/resolve.d.ts +19 -0
  74. package/dist/server-contract/resolve.js +85 -13
  75. package/dist/server-contract/resolved-envelope.d.ts +9 -0
  76. package/dist/server-contract/resolved-envelope.js +14 -0
  77. package/dist/server-contract/stage.js +1 -10
  78. package/dist/server-module-output.d.ts +9 -0
  79. package/dist/server-module-output.js +250 -0
  80. package/dist/server-output.d.ts +7 -1
  81. package/dist/server-output.js +138 -179
  82. package/dist/server-runtime/matched-route-pipeline.d.ts +1 -0
  83. package/dist/server-runtime/matched-route-pipeline.js +1 -0
  84. package/dist/server-runtime/node-server.js +21 -1
  85. package/dist/server-runtime/route-render.d.ts +12 -3
  86. package/dist/server-runtime/route-render.js +67 -13
  87. package/package.json +3 -3
@@ -0,0 +1,4 @@
1
+ import type { AnalyzeRouteScopedServerMetadataOptions, AnalyzeRouteScopedServerMetadataResult, ScopedServerDiagnostic } from './types.js';
2
+ export type { AnalyzeRouteScopedServerMetadataOptions, AnalyzeRouteScopedServerMetadataResult, ManifestScopedServerDataEntry } from './types.js';
3
+ export declare function analyzeRouteScopedServerMetadata(options: AnalyzeRouteScopedServerMetadataOptions): AnalyzeRouteScopedServerMetadataResult;
4
+ export declare function assertNoScopedServerBuildErrors(diagnostics: ScopedServerDiagnostic[], contextFile: string): void;
@@ -0,0 +1,125 @@
1
+ import { resolve } from 'node:path';
2
+ import { collectExpandedComponentOccurrences } from '../component-occurrences.js';
3
+ import { scanRouteScopedServerOwners, toOwnerKey } from './owner-scanner.js';
4
+ import { sortScopedServerDiagnostics } from './diagnostics.js';
5
+ import { parseScopedComponentStaticProps } from './static-props.js';
6
+ export function analyzeRouteScopedServerMetadata(options) {
7
+ const pageSource = String(options.pageSource || '');
8
+ const pageFile = resolve(String(options.pageFile || ''));
9
+ const srcDir = resolve(String(options.srcDir || ''));
10
+ const registry = options.registry;
11
+ const compilerOpts = options.compilerOpts || {};
12
+ const scanResult = scanRouteScopedServerOwners({
13
+ pageSource,
14
+ pageFile,
15
+ registry,
16
+ srcDir,
17
+ compilerOpts
18
+ });
19
+ const componentOccurrences = collectExpandedComponentOccurrences(pageSource, registry, pageFile);
20
+ const occurrenceCountByPath = buildOccurrenceCountByPath(componentOccurrences);
21
+ const occurrenceDetails = buildScopedComponentOccurrenceDetails({
22
+ componentOccurrences,
23
+ owners: scanResult.owners,
24
+ srcDir
25
+ });
26
+ const scopedServerData = scanResult.owners.map((owner) => toManifestScopedServerDataEntry(owner, occurrenceCountByPath, occurrenceDetails.byOwnerKey));
27
+ return {
28
+ hasScopedServerData: scopedServerData.length > 0,
29
+ scopedServerData,
30
+ diagnostics: sortScopedServerDiagnostics([
31
+ ...scanResult.diagnostics,
32
+ ...occurrenceDetails.diagnostics
33
+ ])
34
+ };
35
+ }
36
+ export function assertNoScopedServerBuildErrors(diagnostics, contextFile) {
37
+ const errors = diagnostics.filter((item) => item.severity === 'error');
38
+ if (errors.length === 0) {
39
+ return;
40
+ }
41
+ const first = errors[0];
42
+ throw new Error(`[zenith] Build failed for ${contextFile}: ${first.code} ${first.message} (${first.filePath})`);
43
+ }
44
+ function toManifestScopedServerDataEntry(owner, occurrenceCountByPath, occurrenceDetailsByOwnerKey) {
45
+ const instanceStrategy = resolveInstanceStrategy(owner, occurrenceCountByPath);
46
+ const entry = {
47
+ ownerKind: owner.ownerKind,
48
+ ownerKey: owner.ownerKey,
49
+ syntax: owner.syntax,
50
+ exportName: owner.exportName,
51
+ instanceStrategy
52
+ };
53
+ if (owner.syntax === 'variables' && owner.serializedVariableNames.length > 0) {
54
+ entry.serializedVariableNames = [...owner.serializedVariableNames];
55
+ }
56
+ if (owner.ownerKind === 'component') {
57
+ const occurrences = occurrenceDetailsByOwnerKey.get(owner.ownerKey) || [];
58
+ if (instanceStrategy === 'per-instance') {
59
+ entry.instances = occurrences.map(({ key, occurrenceId, props }) => ({
60
+ key,
61
+ occurrenceId,
62
+ props
63
+ }));
64
+ }
65
+ else if (occurrences.length === 1 && Object.keys(occurrences[0].props).length > 0) {
66
+ entry.props = occurrences[0].props;
67
+ }
68
+ }
69
+ return entry;
70
+ }
71
+ function buildScopedComponentOccurrenceDetails({ componentOccurrences, owners, srcDir }) {
72
+ const componentOwnerByKey = new Map();
73
+ for (const owner of owners) {
74
+ if (owner.ownerKind === 'component') {
75
+ componentOwnerByKey.set(owner.ownerKey, owner);
76
+ }
77
+ }
78
+ const byOwnerKey = new Map();
79
+ const occurrenceCountByOwnerKey = new Map();
80
+ const diagnostics = [];
81
+ for (const occurrence of componentOccurrences) {
82
+ if (typeof occurrence.componentPath !== 'string' || occurrence.componentPath.length === 0) {
83
+ continue;
84
+ }
85
+ const ownerKey = toOwnerKey(occurrence.componentPath, srcDir);
86
+ if (!componentOwnerByKey.has(ownerKey)) {
87
+ continue;
88
+ }
89
+ const index = occurrenceCountByOwnerKey.get(ownerKey) || 0;
90
+ occurrenceCountByOwnerKey.set(ownerKey, index + 1);
91
+ const occurrenceId = `o${index}`;
92
+ const parsed = parseScopedComponentStaticProps({
93
+ attrs: occurrence.attrs,
94
+ ownerKey,
95
+ contextFile: occurrence.ownerPath || occurrence.componentPath,
96
+ occurrenceId
97
+ });
98
+ diagnostics.push(...parsed.diagnostics);
99
+ if (!byOwnerKey.has(ownerKey)) {
100
+ byOwnerKey.set(ownerKey, []);
101
+ }
102
+ byOwnerKey.get(ownerKey)?.push({
103
+ key: `component:${ownerKey}:${occurrenceId}`,
104
+ occurrenceId,
105
+ props: parsed.props
106
+ });
107
+ }
108
+ return { byOwnerKey, diagnostics };
109
+ }
110
+ function resolveInstanceStrategy(owner, occurrenceCountByPath) {
111
+ if (owner.ownerKind === 'layout') {
112
+ return 'singleton';
113
+ }
114
+ const resolvedPath = resolve(owner.ownerPath);
115
+ const count = occurrenceCountByPath.get(resolvedPath) ?? 1;
116
+ return count > 1 ? 'per-instance' : 'singleton';
117
+ }
118
+ function buildOccurrenceCountByPath(componentOccurrences) {
119
+ const occurrenceCountByPath = new Map();
120
+ for (const occurrence of componentOccurrences) {
121
+ const key = occurrence.componentPath || occurrence.name;
122
+ occurrenceCountByPath.set(key, (occurrenceCountByPath.get(key) || 0) + 1);
123
+ }
124
+ return occurrenceCountByPath;
125
+ }
@@ -0,0 +1,6 @@
1
+ import type { ScanRouteScopedServerOwnersOptions, ScanRouteScopedServerOwnersResult } from './types.js';
2
+ export type { CompilerOptsLike, OwnerFileAnalysisResult, ScanRouteScopedServerOwnersOptions, ScanRouteScopedServerOwnersResult, ScopedServerDataOwner, ScopedServerDataOwnerBase, ScopedServerDiagnostic, ScopedServerOwnerKind, ScopedServerOwnerSyntax } from './types.js';
3
+ export { analyzeOwnerServerFile } from './analyze-owner-file.js';
4
+ export { createScopedServerDiagnostic, SCOPED_SERVER_DIAGNOSTIC, sortScopedServerDiagnostics } from './diagnostics.js';
5
+ export declare function scanRouteScopedServerOwners(options: ScanRouteScopedServerOwnersOptions): ScanRouteScopedServerOwnersResult;
6
+ export declare function toOwnerKey(ownerPath: string, srcDir: string): string;
@@ -0,0 +1,55 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { relative, resolve } from 'node:path';
3
+ import { collectReachableOwnerPaths } from '../component-occurrences.js';
4
+ import { extractTemplate, isDocumentMode } from '../resolve-components.js';
5
+ import { analyzeOwnerServerFile } from './analyze-owner-file.js';
6
+ import { createScopedServerDiagnostic, SCOPED_SERVER_DIAGNOSTIC, sortScopedServerDiagnostics } from './diagnostics.js';
7
+ export { analyzeOwnerServerFile } from './analyze-owner-file.js';
8
+ export { createScopedServerDiagnostic, SCOPED_SERVER_DIAGNOSTIC, sortScopedServerDiagnostics } from './diagnostics.js';
9
+ export function scanRouteScopedServerOwners(options) {
10
+ const pageSource = String(options.pageSource || '');
11
+ const pageFile = String(options.pageFile || '');
12
+ const registry = options.registry;
13
+ const srcDir = resolve(String(options.srcDir || ''));
14
+ const compilerOpts = options.compilerOpts || {};
15
+ const owners = [];
16
+ const diagnostics = [];
17
+ diagnostics.push(...detectCompetingDocumentRoots(pageSource, registry, pageFile));
18
+ const ownerPaths = collectReachableOwnerPaths(pageSource, registry, pageFile).sort();
19
+ for (const ownerPath of ownerPaths) {
20
+ const ownerSource = readFileSync(ownerPath, 'utf8');
21
+ const result = analyzeOwnerServerFile(ownerSource, ownerPath, compilerOpts);
22
+ diagnostics.push(...result.diagnostics);
23
+ if (result.owner) {
24
+ owners.push({
25
+ ...result.owner,
26
+ ownerKey: toOwnerKey(ownerPath, srcDir)
27
+ });
28
+ }
29
+ }
30
+ return {
31
+ owners,
32
+ diagnostics: sortScopedServerDiagnostics(diagnostics)
33
+ };
34
+ }
35
+ function detectCompetingDocumentRoots(pageSource, registry, pageFile) {
36
+ const diagnostics = [];
37
+ let documentRootCount = 0;
38
+ if (isDocumentMode(extractTemplate(pageSource))) {
39
+ documentRootCount += 1;
40
+ }
41
+ for (const ownerPath of collectReachableOwnerPaths(pageSource, registry, pageFile)) {
42
+ const ownerTemplate = extractTemplate(readFileSync(ownerPath, 'utf8'));
43
+ if (isDocumentMode(ownerTemplate)) {
44
+ documentRootCount += 1;
45
+ }
46
+ }
47
+ if (documentRootCount > 1) {
48
+ diagnostics.push(createScopedServerDiagnostic(SCOPED_SERVER_DIAGNOSTIC.COMPETING_DOCUMENT_ROOTS, 'error', 'Page render must resolve to exactly one document root.', pageFile));
49
+ }
50
+ return diagnostics;
51
+ }
52
+ export function toOwnerKey(ownerPath, srcDir) {
53
+ const rel = relative(srcDir, resolve(ownerPath)).replace(/\\/g, '/');
54
+ return rel.startsWith('src/') ? rel : `src/${rel}`;
55
+ }
@@ -0,0 +1,12 @@
1
+ import type { CompilerOptsLike, ScriptBlockPartition } from './types.js';
2
+ interface ScriptBlockMatch extends ScriptBlockPartition {
3
+ full: string;
4
+ index: number;
5
+ }
6
+ export declare function findScriptBlocks(source: string): ScriptBlockMatch[];
7
+ export declare function partitionScriptBlocks(source: string): {
8
+ serverBlocks: ScriptBlockPartition[];
9
+ clientBlocks: ScriptBlockPartition[];
10
+ };
11
+ export declare function serverBlockRequiresLangTs(attrs: string, compilerOpts?: CompilerOptsLike): boolean;
12
+ export {};
@@ -0,0 +1,35 @@
1
+ export function findScriptBlocks(source) {
2
+ const scriptRe = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
3
+ const blocks = [];
4
+ for (const match of String(source || '').matchAll(scriptRe)) {
5
+ blocks.push({
6
+ attrs: String(match[1] || ''),
7
+ body: String(match[2] || ''),
8
+ full: String(match[0] || ''),
9
+ index: typeof match.index === 'number' ? match.index : -1
10
+ });
11
+ }
12
+ return blocks;
13
+ }
14
+ export function partitionScriptBlocks(source) {
15
+ const serverBlocks = [];
16
+ const clientBlocks = [];
17
+ for (const block of findScriptBlocks(source)) {
18
+ if (/\bserver\b/i.test(block.attrs)) {
19
+ serverBlocks.push({ attrs: block.attrs, body: block.body });
20
+ }
21
+ else {
22
+ clientBlocks.push({ attrs: block.attrs, body: block.body });
23
+ }
24
+ }
25
+ return { serverBlocks, clientBlocks };
26
+ }
27
+ export function serverBlockRequiresLangTs(attrs, compilerOpts = {}) {
28
+ const hasLangTs = /\blang\s*=\s*["']ts["']/i.test(attrs);
29
+ if (hasLangTs) {
30
+ return false;
31
+ }
32
+ const hasLangJs = /\blang\s*=\s*["'](?:js|javascript)["']/i.test(attrs);
33
+ const hasAnyLang = /\blang\s*=/i.test(attrs);
34
+ return !compilerOpts.typescriptDefault || hasLangJs || hasAnyLang;
35
+ }
@@ -0,0 +1,24 @@
1
+ import type { ManifestScopedServerDataEntry } from './types.js';
2
+ type ScopedEntry = ManifestScopedServerDataEntry & {
3
+ module?: string;
4
+ };
5
+ type ScopedModule = {
6
+ data?: unknown;
7
+ };
8
+ type ScopedModuleLoader = (entry: ScopedEntry) => Promise<ScopedModule>;
9
+ export interface ExecuteScopedServerDataOptions {
10
+ route: {
11
+ path?: string;
12
+ route_kind?: string | null;
13
+ prerender?: boolean;
14
+ has_scoped_server_data?: boolean;
15
+ scoped_server_data?: ScopedEntry[];
16
+ };
17
+ ctx: unknown;
18
+ serverDir?: string | null;
19
+ loadModule?: ScopedModuleLoader;
20
+ }
21
+ export declare function hasExecutableScopedServerData(route: ExecuteScopedServerDataOptions['route']): boolean;
22
+ export declare function mergeScopedSsrPayload(routePayload: unknown, scopedPayload: Record<string, Record<string, unknown>>): Record<string, unknown>;
23
+ export declare function executeScopedServerData(options: ExecuteScopedServerDataOptions): Promise<Record<string, Record<string, unknown>>>;
24
+ export {};
@@ -0,0 +1,121 @@
1
+ import { isAbsolute, resolve, sep } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { assertJsonSerializable } from '../server-contract/json-serializable.js';
4
+ const INVALID_SCOPED_MODULE_PATH = '[Zenith:ScopedServerData] Invalid scoped server data module path.';
5
+ export function hasExecutableScopedServerData(route) {
6
+ return route?.route_kind !== 'resource' &&
7
+ route?.prerender !== true &&
8
+ route?.has_scoped_server_data === true &&
9
+ Array.isArray(route?.scoped_server_data) &&
10
+ route.scoped_server_data.length > 0;
11
+ }
12
+ export function mergeScopedSsrPayload(routePayload, scopedPayload) {
13
+ const routeData = isPlainRecord(routePayload) ? routePayload : {};
14
+ if (Object.keys(scopedPayload || {}).length === 0) {
15
+ return routeData;
16
+ }
17
+ return {
18
+ ...routeData,
19
+ route: routeData,
20
+ scoped: scopedPayload
21
+ };
22
+ }
23
+ export async function executeScopedServerData(options) {
24
+ const route = options.route || {};
25
+ if (!hasExecutableScopedServerData(route)) {
26
+ return {};
27
+ }
28
+ const scoped = {};
29
+ const entries = route.scoped_server_data || [];
30
+ for (const entry of entries) {
31
+ const workItems = scopedPayloadWorkItems(entry);
32
+ const mod = await loadScopedModule(entry, options);
33
+ const dataFn = mod?.data;
34
+ if (typeof dataFn !== 'function') {
35
+ throw new Error(`[Zenith:ScopedServerData] Scoped server data module "${entry.module || entry.ownerKey}" must export data(ctx, props).`);
36
+ }
37
+ for (const item of workItems) {
38
+ const props = normalizeStaticProps(item.props, entry.ownerKey);
39
+ const result = await dataFn(options.ctx, props);
40
+ if (isRouteResultLike(result)) {
41
+ throw new Error(`[Zenith:ScopedServerData] Scoped server data owner "${entry.ownerKey}" must return a plain serializable object, not a route result.`);
42
+ }
43
+ assertJsonSerializable(result, `${entry.ownerKey}: scoped data return`);
44
+ scoped[item.key] = result;
45
+ }
46
+ }
47
+ return scoped;
48
+ }
49
+ async function loadScopedModule(entry, options) {
50
+ if (typeof options.loadModule === 'function') {
51
+ return options.loadModule(entry);
52
+ }
53
+ if (typeof options.serverDir !== 'string' || options.serverDir.length === 0) {
54
+ throw new Error('[Zenith:ScopedServerData] Cannot execute scoped server data without a server module root.');
55
+ }
56
+ const modulePath = resolveScopedServerModulePath(options.serverDir, entry.module);
57
+ return import(pathToFileURL(modulePath).href);
58
+ }
59
+ function scopedPayloadWorkItems(entry) {
60
+ const ownerKey = String(entry?.ownerKey || '');
61
+ if (entry?.instanceStrategy === 'per-instance') {
62
+ if (!Array.isArray(entry.instances) || entry.instances.length === 0) {
63
+ throw new Error(`[Zenith:ScopedServerData] Per-instance scoped server data owner "${ownerKey}" is missing instance metadata.`);
64
+ }
65
+ return entry.instances.map((instance) => {
66
+ const key = typeof instance?.key === 'string' && instance.key.length > 0
67
+ ? instance.key
68
+ : '';
69
+ if (!key.startsWith(`component:${ownerKey}:`)) {
70
+ throw new Error(`[Zenith:ScopedServerData] Invalid scoped server data instance key for "${ownerKey}".`);
71
+ }
72
+ return { key, props: instance.props || {} };
73
+ });
74
+ }
75
+ if (entry?.ownerKind === 'layout') {
76
+ return [{ key: `layout:${ownerKey}`, props: {} }];
77
+ }
78
+ if (entry?.ownerKind === 'component') {
79
+ return [{ key: `component:${ownerKey}`, props: entry.props || {} }];
80
+ }
81
+ throw new Error(`[Zenith:ScopedServerData] Unsupported scoped server data owner kind "${String(entry?.ownerKind || '')}".`);
82
+ }
83
+ function normalizeStaticProps(value, ownerKey) {
84
+ if (value == null) {
85
+ return {};
86
+ }
87
+ if (!isPlainRecord(value)) {
88
+ throw new Error(`[Zenith:ScopedServerData] Scoped server data props for "${ownerKey}" must be a plain object.`);
89
+ }
90
+ assertJsonSerializable(value, `${ownerKey}: scoped data props`);
91
+ return value;
92
+ }
93
+ function resolveScopedServerModulePath(serverDir, modulePath) {
94
+ const raw = String(modulePath || '');
95
+ if (!raw || isAbsolute(raw) || /^[A-Za-z]:[\\/]/.test(raw)) {
96
+ throw new Error(INVALID_SCOPED_MODULE_PATH);
97
+ }
98
+ const normalized = raw.replace(/\\/g, '/');
99
+ if (!normalized.startsWith('scoped/') || normalized.split('/').some((part) => part === '..' || part === '.')) {
100
+ throw new Error(INVALID_SCOPED_MODULE_PATH);
101
+ }
102
+ const scopedRoot = resolve(serverDir, 'scoped');
103
+ const outputPath = resolve(serverDir, normalized);
104
+ if (outputPath !== scopedRoot && !outputPath.startsWith(`${scopedRoot}${sep}`)) {
105
+ throw new Error(INVALID_SCOPED_MODULE_PATH);
106
+ }
107
+ return outputPath;
108
+ }
109
+ function isRouteResultLike(value) {
110
+ return Boolean(value &&
111
+ typeof value === 'object' &&
112
+ !Array.isArray(value) &&
113
+ typeof value.kind === 'string');
114
+ }
115
+ function isPlainRecord(value) {
116
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
117
+ return false;
118
+ }
119
+ const proto = Object.getPrototypeOf(value);
120
+ return proto === null || proto === Object.prototype || proto?.constructor?.name === 'Object';
121
+ }
@@ -0,0 +1,2 @@
1
+ export declare function collectTemplateRootBindingRefs(template: string): Set<string>;
2
+ export declare function computeSerializationSet(declaredNames: string[], template: string): string[];
@@ -0,0 +1,52 @@
1
+ const IDENT_START = /[A-Za-z_$]/;
2
+ const IDENT_PART = /[A-Za-z0-9_$]/;
3
+ export function collectTemplateRootBindingRefs(template) {
4
+ const refs = new Set();
5
+ const source = String(template || '');
6
+ let cursor = 0;
7
+ while (cursor < source.length) {
8
+ const open = source.indexOf('{', cursor);
9
+ if (open < 0) {
10
+ break;
11
+ }
12
+ const close = source.indexOf('}', open + 1);
13
+ if (close < 0) {
14
+ break;
15
+ }
16
+ const expr = source.slice(open + 1, close).trim();
17
+ const root = readExpressionRoot(expr);
18
+ if (root) {
19
+ refs.add(root);
20
+ }
21
+ cursor = close + 1;
22
+ }
23
+ return refs;
24
+ }
25
+ function readExpressionRoot(expr) {
26
+ if (!expr || expr.startsWith('//')) {
27
+ return null;
28
+ }
29
+ let i = 0;
30
+ while (i < expr.length && /\s/.test(expr[i] ?? '')) {
31
+ i += 1;
32
+ }
33
+ if (!IDENT_START.test(expr[i] ?? '')) {
34
+ return null;
35
+ }
36
+ let end = i + 1;
37
+ while (end < expr.length && IDENT_PART.test(expr[end] ?? '')) {
38
+ end += 1;
39
+ }
40
+ return expr.slice(i, end);
41
+ }
42
+ export function computeSerializationSet(declaredNames, template) {
43
+ const refs = collectTemplateRootBindingRefs(template);
44
+ const declared = new Set(declaredNames);
45
+ const serialized = [];
46
+ for (const name of refs) {
47
+ if (declared.has(name)) {
48
+ serialized.push(name);
49
+ }
50
+ }
51
+ return serialized.sort();
52
+ }
@@ -0,0 +1,12 @@
1
+ import type { ScopedServerDiagnostic, ScopedServerStaticProps } from './types.js';
2
+ export interface ParseScopedComponentStaticPropsOptions {
3
+ attrs: string;
4
+ ownerKey: string;
5
+ contextFile: string;
6
+ occurrenceId?: string;
7
+ }
8
+ export interface ParseScopedComponentStaticPropsResult {
9
+ props: ScopedServerStaticProps;
10
+ diagnostics: ScopedServerDiagnostic[];
11
+ }
12
+ export declare function parseScopedComponentStaticProps(options: ParseScopedComponentStaticPropsOptions): ParseScopedComponentStaticPropsResult;