@mandujs/core 0.53.2 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +654 -654
- package/package.json +2 -2
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +394 -394
- package/src/a11y/types.ts +125 -125
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +122 -122
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +120 -120
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/bundler/__tests__/build-runner.ts +113 -113
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -10
- package/src/bundler/__tests__/hdr.test.ts +353 -353
- package/src/bundler/__tests__/hmr-client.test.ts +532 -532
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +519 -519
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +179 -179
- package/src/bundler/build.ts +55 -55
- package/src/bundler/dev.ts +42 -42
- package/src/bundler/manifest-schema.ts +301 -301
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +263 -263
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +155 -155
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +201 -201
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +10 -10
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +340 -340
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/router.ts +11 -11
- package/src/client/runtime.ts +47 -47
- package/src/client/serialize.ts +404 -404
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +3 -3
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +8 -8
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +482 -482
- package/src/db/index.ts +138 -138
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +182 -182
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/webview-fallback.test.ts +254 -254
- package/src/desktop/__tests__/window.test.ts +248 -248
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +320 -320
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +500 -500
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +450 -450
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/devtools/worker/worker-manager.ts +410 -410
- package/src/diagnose/__tests__/checks.test.ts +451 -380
- package/src/diagnose/checks.ts +832 -720
- package/src/diagnose/index.ts +17 -16
- package/src/diagnose/run.ts +93 -91
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +60 -60
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +454 -454
- package/src/filling/auth.ts +308 -308
- package/src/filling/cookie-codec.ts +299 -299
- package/src/filling/deps.ts +265 -265
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/index.ts +3 -3
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/index.ts +105 -105
- package/src/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -544
- package/src/kitchen/api/errors-grouping.ts +126 -0
- package/src/kitchen/kitchen-handler.ts +192 -63
- package/src/kitchen/kitchen-ui.ts +842 -464
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +32 -32
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +823 -823
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +559 -559
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.ts +6 -6
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +300 -300
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/router.ts +105 -105
- package/src/runtime/server.ts +67 -67
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/ssr.ts +24 -31
- package/src/runtime/streaming-ssr.ts +32 -37
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/watcher.ts +18 -18
package/src/runtime/ppr.ts
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu Partial Prerendering (PPR)
|
|
3
|
-
*
|
|
4
|
-
* Caches the static HTML shell (header, sidebar, layout) at build/first-request time,
|
|
5
|
-
* then injects fresh dynamic data (loader results) per request.
|
|
6
|
-
*
|
|
7
|
-
* Result: TTFB of a static page + freshness of a dynamic page.
|
|
8
|
-
*
|
|
9
|
-
* The shell is the expensive part (React render tree traversal).
|
|
10
|
-
* Data injection is cheap (JSON serialization into a script tag).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { escapeJsonForInlineScript } from "./escape";
|
|
14
|
-
import { serializeProps } from "../client/serialize";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* PPR shell marker injected at the end of cached HTML.
|
|
18
|
-
* Everything before this marker is the static shell; everything after
|
|
19
|
-
* (closing tags, data scripts) is regenerated per request.
|
|
20
|
-
*/
|
|
21
|
-
export const PPR_SHELL_MARKER = "<!--mandu:ppr-split-->";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Strip the closing </body></html> and any trailing whitespace from
|
|
25
|
-
* a full SSR HTML string, then append the shell marker.
|
|
26
|
-
* The result is safe to cache and later concatenated with fresh data.
|
|
27
|
-
*/
|
|
28
|
-
export function extractShellHtml(fullHtml: string): string {
|
|
29
|
-
// Remove trailing </body></html> (case-insensitive, whitespace-tolerant)
|
|
30
|
-
const trimmed = fullHtml.replace(/<\/body>\s*<\/html>\s*$/i, "");
|
|
31
|
-
return trimmed + PPR_SHELL_MARKER;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Build a streaming Response that:
|
|
36
|
-
* 1. Sends the cached shell immediately (near-zero TTFB)
|
|
37
|
-
* 2. Appends a script tag with fresh loader data
|
|
38
|
-
* 3. Closes with </body></html>
|
|
39
|
-
*/
|
|
40
|
-
export function createPPRResponse(
|
|
41
|
-
shellHtml: string,
|
|
42
|
-
routeId: string,
|
|
43
|
-
loaderData: unknown,
|
|
44
|
-
): Response {
|
|
45
|
-
const encoder = new TextEncoder();
|
|
46
|
-
|
|
47
|
-
// Build the data payload once; avoid doing work inside the stream callback
|
|
48
|
-
const serialized = serializeProps({ serverData: loaderData });
|
|
49
|
-
const escaped = escapeJsonForInlineScript(serialized);
|
|
50
|
-
const dataScript =
|
|
51
|
-
`<script>window.__MANDU_DATA__=window.__MANDU_DATA__||{};` +
|
|
52
|
-
`window.__MANDU_DATA__[${JSON.stringify(routeId)}]=${escaped}</script>`;
|
|
53
|
-
|
|
54
|
-
const stream = new ReadableStream<Uint8Array>({
|
|
55
|
-
start(controller) {
|
|
56
|
-
// (a) Cached shell -- everything up to the split marker (inclusive)
|
|
57
|
-
controller.enqueue(encoder.encode(shellHtml));
|
|
58
|
-
// (b) Fresh data script
|
|
59
|
-
controller.enqueue(encoder.encode(dataScript));
|
|
60
|
-
// (c) Close the document
|
|
61
|
-
controller.enqueue(encoder.encode("</body></html>"));
|
|
62
|
-
controller.close();
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
return new Response(stream, {
|
|
67
|
-
status: 200,
|
|
68
|
-
headers: {
|
|
69
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
70
|
-
"X-Content-Type-Options": "nosniff",
|
|
71
|
-
"X-Mandu-PPR": "shell-hit",
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu Partial Prerendering (PPR)
|
|
3
|
+
*
|
|
4
|
+
* Caches the static HTML shell (header, sidebar, layout) at build/first-request time,
|
|
5
|
+
* then injects fresh dynamic data (loader results) per request.
|
|
6
|
+
*
|
|
7
|
+
* Result: TTFB of a static page + freshness of a dynamic page.
|
|
8
|
+
*
|
|
9
|
+
* The shell is the expensive part (React render tree traversal).
|
|
10
|
+
* Data injection is cheap (JSON serialization into a script tag).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { escapeJsonForInlineScript } from "./escape";
|
|
14
|
+
import { serializeProps } from "../client/serialize";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* PPR shell marker injected at the end of cached HTML.
|
|
18
|
+
* Everything before this marker is the static shell; everything after
|
|
19
|
+
* (closing tags, data scripts) is regenerated per request.
|
|
20
|
+
*/
|
|
21
|
+
export const PPR_SHELL_MARKER = "<!--mandu:ppr-split-->";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Strip the closing </body></html> and any trailing whitespace from
|
|
25
|
+
* a full SSR HTML string, then append the shell marker.
|
|
26
|
+
* The result is safe to cache and later concatenated with fresh data.
|
|
27
|
+
*/
|
|
28
|
+
export function extractShellHtml(fullHtml: string): string {
|
|
29
|
+
// Remove trailing </body></html> (case-insensitive, whitespace-tolerant)
|
|
30
|
+
const trimmed = fullHtml.replace(/<\/body>\s*<\/html>\s*$/i, "");
|
|
31
|
+
return trimmed + PPR_SHELL_MARKER;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build a streaming Response that:
|
|
36
|
+
* 1. Sends the cached shell immediately (near-zero TTFB)
|
|
37
|
+
* 2. Appends a script tag with fresh loader data
|
|
38
|
+
* 3. Closes with </body></html>
|
|
39
|
+
*/
|
|
40
|
+
export function createPPRResponse(
|
|
41
|
+
shellHtml: string,
|
|
42
|
+
routeId: string,
|
|
43
|
+
loaderData: unknown,
|
|
44
|
+
): Response {
|
|
45
|
+
const encoder = new TextEncoder();
|
|
46
|
+
|
|
47
|
+
// Build the data payload once; avoid doing work inside the stream callback
|
|
48
|
+
const serialized = serializeProps({ serverData: loaderData });
|
|
49
|
+
const escaped = escapeJsonForInlineScript(serialized);
|
|
50
|
+
const dataScript =
|
|
51
|
+
`<script>window.__MANDU_DATA__=window.__MANDU_DATA__||{};` +
|
|
52
|
+
`window.__MANDU_DATA__[${JSON.stringify(routeId)}]=${escaped}</script>`;
|
|
53
|
+
|
|
54
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
55
|
+
start(controller) {
|
|
56
|
+
// (a) Cached shell -- everything up to the split marker (inclusive)
|
|
57
|
+
controller.enqueue(encoder.encode(shellHtml));
|
|
58
|
+
// (b) Fresh data script
|
|
59
|
+
controller.enqueue(encoder.encode(dataScript));
|
|
60
|
+
// (c) Close the document
|
|
61
|
+
controller.enqueue(encoder.encode("</body></html>"));
|
|
62
|
+
controller.close();
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return new Response(stream, {
|
|
67
|
+
status: 200,
|
|
68
|
+
headers: {
|
|
69
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
70
|
+
"X-Content-Type-Options": "nosniff",
|
|
71
|
+
"X-Mandu-PPR": "shell-hit",
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
package/src/runtime/registry.ts
CHANGED
|
@@ -1,171 +1,171 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime manifest registry.
|
|
3
|
-
*
|
|
4
|
-
* This module provides the **official** accessor for generated content at
|
|
5
|
-
* runtime. User code must NEVER `import` anything under `.mandu/generated/`
|
|
6
|
-
* or any path containing `/generated/` — the guard rule
|
|
7
|
-
* `INVALID_GENERATED_IMPORT` catches that at build time.
|
|
8
|
-
*
|
|
9
|
-
* Why the indirection?
|
|
10
|
-
*
|
|
11
|
-
* - **Hot reload** — in dev, generated modules are rebuilt and re-imported.
|
|
12
|
-
* A direct ESM import caches the first version; the registry re-reads the
|
|
13
|
-
* current manifest on every access.
|
|
14
|
-
* - **Determinism** — compiled binaries (`bun build --compile`) embed a
|
|
15
|
-
* fixed manifest. Direct imports would bypass the embedded copy and fail.
|
|
16
|
-
* - **ESM cache invalidation** — see #184. Transitive generated modules get
|
|
17
|
-
* stuck on stale copies when hot-reload fires; the registry's `getManifest`
|
|
18
|
-
* is the single choke point that the bundled importer invalidates cleanly.
|
|
19
|
-
*
|
|
20
|
-
* @see https://mandujs.com/docs/architect/generated-access
|
|
21
|
-
*/
|
|
22
|
-
import type { RoutesManifest, RouteSpec } from "../spec/schema";
|
|
23
|
-
|
|
24
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
25
|
-
// Generated artifact map
|
|
26
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Map of generated artifacts keyed by well-known names.
|
|
30
|
-
*
|
|
31
|
-
* Extend this interface (via module augmentation) in consumers that emit
|
|
32
|
-
* their own generated artifacts — collections, resources, db schemas, etc.
|
|
33
|
-
*
|
|
34
|
-
* @example Module augmentation
|
|
35
|
-
* ```ts
|
|
36
|
-
* declare module "@mandujs/core/runtime" {
|
|
37
|
-
* interface GeneratedRegistry {
|
|
38
|
-
* collections: Record<string, CollectionIndex>;
|
|
39
|
-
* }
|
|
40
|
-
* }
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export interface GeneratedRegistry {
|
|
44
|
-
/** Route manifest — the single source of truth for page/API routes. */
|
|
45
|
-
routes: RoutesManifest;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Union of well-known generated artifact keys. */
|
|
49
|
-
export type GeneratedKey = keyof GeneratedRegistry;
|
|
50
|
-
|
|
51
|
-
/** Typed accessor — narrows the return shape from the key. */
|
|
52
|
-
export type GeneratedShape<K extends GeneratedKey> = GeneratedRegistry[K];
|
|
53
|
-
|
|
54
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
55
|
-
// Global registry state
|
|
56
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* The live manifest, populated by `registerManifest()` (typically driven by
|
|
60
|
-
* `registerManifestHandlers()` from `@mandujs/cli`). Kept on `globalThis`
|
|
61
|
-
* so reloading the core module in dev does not lose registration.
|
|
62
|
-
*/
|
|
63
|
-
declare global {
|
|
64
|
-
// eslint-disable-next-line no-var
|
|
65
|
-
var __MANDU_MANIFEST__: Partial<GeneratedRegistry> | undefined;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function ensureGlobalSlot(): Partial<GeneratedRegistry> {
|
|
69
|
-
if (!globalThis.__MANDU_MANIFEST__) {
|
|
70
|
-
globalThis.__MANDU_MANIFEST__ = {};
|
|
71
|
-
}
|
|
72
|
-
return globalThis.__MANDU_MANIFEST__;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
76
|
-
// Public API
|
|
77
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Register a generated artifact under a well-known key.
|
|
81
|
-
*
|
|
82
|
-
* Called by the framework (typically from `registerManifestHandlers()` in
|
|
83
|
-
* `@mandujs/cli`) during server boot. User code normally does not call this
|
|
84
|
-
* directly — the only exception is tests that want to seed a manifest.
|
|
85
|
-
*
|
|
86
|
-
* @example Test setup
|
|
87
|
-
* ```ts
|
|
88
|
-
* import { registerManifest, clearGeneratedRegistry } from "@mandujs/core/runtime";
|
|
89
|
-
*
|
|
90
|
-
* beforeEach(() => clearGeneratedRegistry());
|
|
91
|
-
* registerManifest("routes", { version: 1, routes: [] });
|
|
92
|
-
* ```
|
|
93
|
-
*/
|
|
94
|
-
export function registerManifest<K extends GeneratedKey>(
|
|
95
|
-
key: K,
|
|
96
|
-
value: GeneratedShape<K>,
|
|
97
|
-
): void {
|
|
98
|
-
const slot = ensureGlobalSlot();
|
|
99
|
-
slot[key] = value;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Read a generated artifact by key. Throws a helpful error if the manifest
|
|
104
|
-
* has not been registered yet — this almost always means the server boot
|
|
105
|
-
* skipped `registerManifestHandlers()` or the test forgot to seed fixtures.
|
|
106
|
-
*
|
|
107
|
-
* @example Reading the route manifest
|
|
108
|
-
* ```ts
|
|
109
|
-
* import { getGenerated } from "@mandujs/core/runtime";
|
|
110
|
-
*
|
|
111
|
-
* const manifest = getGenerated("routes");
|
|
112
|
-
* for (const route of manifest.routes) {
|
|
113
|
-
* console.log(route.id, route.pattern);
|
|
114
|
-
* }
|
|
115
|
-
* ```
|
|
116
|
-
*
|
|
117
|
-
* @throws {Error} when the key has not been registered
|
|
118
|
-
*/
|
|
119
|
-
export function getGenerated<K extends GeneratedKey>(key: K): GeneratedShape<K> {
|
|
120
|
-
const slot = globalThis.__MANDU_MANIFEST__;
|
|
121
|
-
if (!slot || !(key in slot) || slot[key] === undefined) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
`[Mandu] Generated artifact "${String(key)}" not registered. ` +
|
|
124
|
-
`Call registerManifestHandlers() during server boot, or seed the ` +
|
|
125
|
-
`manifest with registerManifest("${String(key)}", …) in tests. ` +
|
|
126
|
-
`See https://mandujs.com/docs/architect/generated-access`,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
return slot[key] as GeneratedShape<K>;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Return the registered artifact, or `undefined` if absent. Prefer
|
|
134
|
-
* `getGenerated()` for the common case — this variant exists for hot paths
|
|
135
|
-
* where absence is not an error (e.g., optional collections).
|
|
136
|
-
*/
|
|
137
|
-
export function tryGetGenerated<K extends GeneratedKey>(
|
|
138
|
-
key: K,
|
|
139
|
-
): GeneratedShape<K> | undefined {
|
|
140
|
-
const slot = globalThis.__MANDU_MANIFEST__;
|
|
141
|
-
return slot?.[key] as GeneratedShape<K> | undefined;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Return the full routes manifest. Thin wrapper around `getGenerated("routes")`
|
|
146
|
-
* kept for call-site readability.
|
|
147
|
-
*
|
|
148
|
-
* @throws {Error} when no manifest has been registered yet
|
|
149
|
-
*/
|
|
150
|
-
export function getManifest(): RoutesManifest {
|
|
151
|
-
return getGenerated("routes");
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Find a single route by its stable ID. Returns `undefined` if no match.
|
|
156
|
-
* Use this instead of `manifest.routes.find(…)` at call sites that already
|
|
157
|
-
* want the readability of a named helper.
|
|
158
|
-
*/
|
|
159
|
-
export function getRouteById(id: string): RouteSpec | undefined {
|
|
160
|
-
const manifest = tryGetGenerated("routes");
|
|
161
|
-
if (!manifest) return undefined;
|
|
162
|
-
return manifest.routes.find((route) => route.id === id);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Clear all registered artifacts. Test-only — production code should never
|
|
167
|
-
* call this.
|
|
168
|
-
*/
|
|
169
|
-
export function clearGeneratedRegistry(): void {
|
|
170
|
-
globalThis.__MANDU_MANIFEST__ = {};
|
|
171
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Runtime manifest registry.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the **official** accessor for generated content at
|
|
5
|
+
* runtime. User code must NEVER `import` anything under `.mandu/generated/`
|
|
6
|
+
* or any path containing `/generated/` — the guard rule
|
|
7
|
+
* `INVALID_GENERATED_IMPORT` catches that at build time.
|
|
8
|
+
*
|
|
9
|
+
* Why the indirection?
|
|
10
|
+
*
|
|
11
|
+
* - **Hot reload** — in dev, generated modules are rebuilt and re-imported.
|
|
12
|
+
* A direct ESM import caches the first version; the registry re-reads the
|
|
13
|
+
* current manifest on every access.
|
|
14
|
+
* - **Determinism** — compiled binaries (`bun build --compile`) embed a
|
|
15
|
+
* fixed manifest. Direct imports would bypass the embedded copy and fail.
|
|
16
|
+
* - **ESM cache invalidation** — see #184. Transitive generated modules get
|
|
17
|
+
* stuck on stale copies when hot-reload fires; the registry's `getManifest`
|
|
18
|
+
* is the single choke point that the bundled importer invalidates cleanly.
|
|
19
|
+
*
|
|
20
|
+
* @see https://mandujs.com/docs/architect/generated-access
|
|
21
|
+
*/
|
|
22
|
+
import type { RoutesManifest, RouteSpec } from "../spec/schema";
|
|
23
|
+
|
|
24
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
25
|
+
// Generated artifact map
|
|
26
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Map of generated artifacts keyed by well-known names.
|
|
30
|
+
*
|
|
31
|
+
* Extend this interface (via module augmentation) in consumers that emit
|
|
32
|
+
* their own generated artifacts — collections, resources, db schemas, etc.
|
|
33
|
+
*
|
|
34
|
+
* @example Module augmentation
|
|
35
|
+
* ```ts
|
|
36
|
+
* declare module "@mandujs/core/runtime" {
|
|
37
|
+
* interface GeneratedRegistry {
|
|
38
|
+
* collections: Record<string, CollectionIndex>;
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export interface GeneratedRegistry {
|
|
44
|
+
/** Route manifest — the single source of truth for page/API routes. */
|
|
45
|
+
routes: RoutesManifest;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Union of well-known generated artifact keys. */
|
|
49
|
+
export type GeneratedKey = keyof GeneratedRegistry;
|
|
50
|
+
|
|
51
|
+
/** Typed accessor — narrows the return shape from the key. */
|
|
52
|
+
export type GeneratedShape<K extends GeneratedKey> = GeneratedRegistry[K];
|
|
53
|
+
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
55
|
+
// Global registry state
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The live manifest, populated by `registerManifest()` (typically driven by
|
|
60
|
+
* `registerManifestHandlers()` from `@mandujs/cli`). Kept on `globalThis`
|
|
61
|
+
* so reloading the core module in dev does not lose registration.
|
|
62
|
+
*/
|
|
63
|
+
declare global {
|
|
64
|
+
// eslint-disable-next-line no-var
|
|
65
|
+
var __MANDU_MANIFEST__: Partial<GeneratedRegistry> | undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function ensureGlobalSlot(): Partial<GeneratedRegistry> {
|
|
69
|
+
if (!globalThis.__MANDU_MANIFEST__) {
|
|
70
|
+
globalThis.__MANDU_MANIFEST__ = {};
|
|
71
|
+
}
|
|
72
|
+
return globalThis.__MANDU_MANIFEST__;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
76
|
+
// Public API
|
|
77
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Register a generated artifact under a well-known key.
|
|
81
|
+
*
|
|
82
|
+
* Called by the framework (typically from `registerManifestHandlers()` in
|
|
83
|
+
* `@mandujs/cli`) during server boot. User code normally does not call this
|
|
84
|
+
* directly — the only exception is tests that want to seed a manifest.
|
|
85
|
+
*
|
|
86
|
+
* @example Test setup
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { registerManifest, clearGeneratedRegistry } from "@mandujs/core/runtime";
|
|
89
|
+
*
|
|
90
|
+
* beforeEach(() => clearGeneratedRegistry());
|
|
91
|
+
* registerManifest("routes", { version: 1, routes: [] });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function registerManifest<K extends GeneratedKey>(
|
|
95
|
+
key: K,
|
|
96
|
+
value: GeneratedShape<K>,
|
|
97
|
+
): void {
|
|
98
|
+
const slot = ensureGlobalSlot();
|
|
99
|
+
slot[key] = value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Read a generated artifact by key. Throws a helpful error if the manifest
|
|
104
|
+
* has not been registered yet — this almost always means the server boot
|
|
105
|
+
* skipped `registerManifestHandlers()` or the test forgot to seed fixtures.
|
|
106
|
+
*
|
|
107
|
+
* @example Reading the route manifest
|
|
108
|
+
* ```ts
|
|
109
|
+
* import { getGenerated } from "@mandujs/core/runtime";
|
|
110
|
+
*
|
|
111
|
+
* const manifest = getGenerated("routes");
|
|
112
|
+
* for (const route of manifest.routes) {
|
|
113
|
+
* console.log(route.id, route.pattern);
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* @throws {Error} when the key has not been registered
|
|
118
|
+
*/
|
|
119
|
+
export function getGenerated<K extends GeneratedKey>(key: K): GeneratedShape<K> {
|
|
120
|
+
const slot = globalThis.__MANDU_MANIFEST__;
|
|
121
|
+
if (!slot || !(key in slot) || slot[key] === undefined) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`[Mandu] Generated artifact "${String(key)}" not registered. ` +
|
|
124
|
+
`Call registerManifestHandlers() during server boot, or seed the ` +
|
|
125
|
+
`manifest with registerManifest("${String(key)}", …) in tests. ` +
|
|
126
|
+
`See https://mandujs.com/docs/architect/generated-access`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return slot[key] as GeneratedShape<K>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Return the registered artifact, or `undefined` if absent. Prefer
|
|
134
|
+
* `getGenerated()` for the common case — this variant exists for hot paths
|
|
135
|
+
* where absence is not an error (e.g., optional collections).
|
|
136
|
+
*/
|
|
137
|
+
export function tryGetGenerated<K extends GeneratedKey>(
|
|
138
|
+
key: K,
|
|
139
|
+
): GeneratedShape<K> | undefined {
|
|
140
|
+
const slot = globalThis.__MANDU_MANIFEST__;
|
|
141
|
+
return slot?.[key] as GeneratedShape<K> | undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Return the full routes manifest. Thin wrapper around `getGenerated("routes")`
|
|
146
|
+
* kept for call-site readability.
|
|
147
|
+
*
|
|
148
|
+
* @throws {Error} when no manifest has been registered yet
|
|
149
|
+
*/
|
|
150
|
+
export function getManifest(): RoutesManifest {
|
|
151
|
+
return getGenerated("routes");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Find a single route by its stable ID. Returns `undefined` if no match.
|
|
156
|
+
* Use this instead of `manifest.routes.find(…)` at call sites that already
|
|
157
|
+
* want the readability of a named helper.
|
|
158
|
+
*/
|
|
159
|
+
export function getRouteById(id: string): RouteSpec | undefined {
|
|
160
|
+
const manifest = tryGetGenerated("routes");
|
|
161
|
+
if (!manifest) return undefined;
|
|
162
|
+
return manifest.routes.find((route) => route.id === id);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Clear all registered artifacts. Test-only — production code should never
|
|
167
|
+
* call this.
|
|
168
|
+
*/
|
|
169
|
+
export function clearGeneratedRegistry(): void {
|
|
170
|
+
globalThis.__MANDU_MANIFEST__ = {};
|
|
171
|
+
}
|