@canmi/seam-react 0.2.14 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/scripts/build-skeletons.mjs +51 -506
- package/scripts/skeleton/cache.mjs +139 -0
- package/scripts/skeleton/layout.mjs +109 -0
- package/scripts/skeleton/process.mjs +188 -0
- package/scripts/skeleton/render.mjs +160 -0
- package/scripts/skeleton/schema.mjs +120 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/* packages/client/react/scripts/skeleton/schema.mjs */
|
|
2
|
+
|
|
3
|
+
import { buildSentinelData } from "@canmi/seam-react";
|
|
4
|
+
import {
|
|
5
|
+
collectStructuralAxes,
|
|
6
|
+
cartesianProduct,
|
|
7
|
+
buildVariantSentinel,
|
|
8
|
+
} from "../variant-generator.mjs";
|
|
9
|
+
import {
|
|
10
|
+
generateMockFromSchema,
|
|
11
|
+
flattenLoaderMock,
|
|
12
|
+
deepMerge,
|
|
13
|
+
collectHtmlPaths,
|
|
14
|
+
createAccessTracker,
|
|
15
|
+
checkFieldAccess,
|
|
16
|
+
} from "../mock-generator.mjs";
|
|
17
|
+
import { SeamBuildError, guardedRender, stripResourceHints } from "./render.mjs";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Merge loader procedure schemas from manifest into a combined page schema.
|
|
21
|
+
* Each loader contributes its output schema fields to the top-level properties.
|
|
22
|
+
*/
|
|
23
|
+
function buildPageSchema(route, manifest) {
|
|
24
|
+
if (!manifest) return null;
|
|
25
|
+
|
|
26
|
+
const properties = {};
|
|
27
|
+
|
|
28
|
+
for (const [loaderKey, loaderDef] of Object.entries(route.loaders || {})) {
|
|
29
|
+
const procName = loaderDef.procedure;
|
|
30
|
+
const proc = manifest.procedures?.[procName];
|
|
31
|
+
if (!proc?.output) continue;
|
|
32
|
+
|
|
33
|
+
// Always nest under the loader key so axis paths (e.g. "user.bio")
|
|
34
|
+
// align with sentinel data paths built from mock (e.g. sentinel.user.bio).
|
|
35
|
+
properties[loaderKey] = proc.output;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const result = {};
|
|
39
|
+
if (Object.keys(properties).length > 0) result.properties = properties;
|
|
40
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve mock data for a route: auto-generate from schema when available,
|
|
45
|
+
* then deep-merge any user-provided partial mock on top.
|
|
46
|
+
*/
|
|
47
|
+
function resolveRouteMock(route, manifest) {
|
|
48
|
+
const pageSchema = buildPageSchema(route, manifest);
|
|
49
|
+
|
|
50
|
+
if (pageSchema) {
|
|
51
|
+
const keyedMock = generateMockFromSchema(pageSchema);
|
|
52
|
+
const autoMock = flattenLoaderMock(keyedMock);
|
|
53
|
+
return route.mock ? deepMerge(autoMock, route.mock) : autoMock;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// No manifest (frontend-only mode) — mock is required
|
|
57
|
+
if (route.mock) return route.mock;
|
|
58
|
+
|
|
59
|
+
throw new SeamBuildError(
|
|
60
|
+
`[seam] error: Mock data required for route "${route.path}"\n\n` +
|
|
61
|
+
" No procedure manifest found \u2014 cannot auto-generate mock data.\n" +
|
|
62
|
+
" Provide mock data in your route definition:\n\n" +
|
|
63
|
+
" defineRoutes([{\n" +
|
|
64
|
+
` path: "${route.path}",\n` +
|
|
65
|
+
" component: YourComponent,\n" +
|
|
66
|
+
' mock: { user: { name: "..." }, repos: [...] }\n' +
|
|
67
|
+
" }])\n\n" +
|
|
68
|
+
" Or switch to fullstack mode with typed Procedures\n" +
|
|
69
|
+
" to enable automatic mock generation from schema.",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Render a route: generate variants from structural axes, plus a mock-data render for CTR check.
|
|
75
|
+
* @param {{ buildWarnings: string[], seenWarnings: Set<string> }} ctx - shared warning state
|
|
76
|
+
*/
|
|
77
|
+
function renderRoute(route, manifest, i18nValue, ctx) {
|
|
78
|
+
const mock = resolveRouteMock(route, manifest);
|
|
79
|
+
const pageSchema = buildPageSchema(route, manifest);
|
|
80
|
+
const htmlPaths = pageSchema ? collectHtmlPaths(pageSchema) : new Set();
|
|
81
|
+
const baseSentinel = buildSentinelData(mock, "", htmlPaths);
|
|
82
|
+
const axes = pageSchema ? collectStructuralAxes(pageSchema, mock) : [];
|
|
83
|
+
const combos = cartesianProduct(axes);
|
|
84
|
+
|
|
85
|
+
const variants = combos.map((variant) => {
|
|
86
|
+
const sentinel = buildVariantSentinel(baseSentinel, mock, variant);
|
|
87
|
+
const html = guardedRender(route.path, route.component, sentinel, i18nValue, ctx);
|
|
88
|
+
return { variant, html };
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Render with real mock data for CTR equivalence check.
|
|
92
|
+
// Wrap mock with Proxy to track field accesses and detect schema mismatches.
|
|
93
|
+
const accessed = new Set();
|
|
94
|
+
const trackedMock = createAccessTracker(mock, accessed);
|
|
95
|
+
const mockHtml = stripResourceHints(
|
|
96
|
+
guardedRender(route.path, route.component, trackedMock, i18nValue, ctx),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const fieldWarnings = checkFieldAccess(accessed, pageSchema, route.path);
|
|
100
|
+
for (const w of fieldWarnings) {
|
|
101
|
+
const msg = `[seam] warning: ${w}`;
|
|
102
|
+
if (!ctx.seenWarnings.has(msg)) {
|
|
103
|
+
ctx.seenWarnings.add(msg);
|
|
104
|
+
ctx.buildWarnings.push(msg);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
path: route.path,
|
|
110
|
+
loaders: route.loaders,
|
|
111
|
+
layout: route._layoutId || undefined,
|
|
112
|
+
axes,
|
|
113
|
+
variants,
|
|
114
|
+
mockHtml,
|
|
115
|
+
mock,
|
|
116
|
+
pageSchema,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { buildPageSchema, resolveRouteMock, renderRoute };
|