@mandujs/core 0.21.0 → 0.22.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.
Files changed (122) hide show
  1. package/package.json +94 -69
  2. package/src/auth/__tests__/login.test.ts +419 -0
  3. package/src/auth/__tests__/password.test.ts +122 -0
  4. package/src/auth/__tests__/reset.test.ts +296 -0
  5. package/src/auth/__tests__/tokens.test.ts +274 -0
  6. package/src/auth/__tests__/verification.test.ts +274 -0
  7. package/src/auth/index.ts +76 -0
  8. package/src/auth/login.ts +225 -0
  9. package/src/auth/password.ts +120 -0
  10. package/src/auth/reset.ts +243 -0
  11. package/src/auth/tokens.ts +612 -0
  12. package/src/auth/verification.ts +253 -0
  13. package/src/bundler/__tests__/cli-bench-utils.test.ts +149 -0
  14. package/src/bundler/__tests__/cold-start.test.ts +504 -0
  15. package/src/bundler/__tests__/csp-nonce.test.ts +278 -0
  16. package/src/bundler/__tests__/dev-reliability.test.ts +619 -0
  17. package/src/bundler/__tests__/extended-watch.test.ts +710 -0
  18. package/src/bundler/__tests__/fast-refresh.test.ts +596 -0
  19. package/src/bundler/__tests__/hdr.test.ts +353 -0
  20. package/src/bundler/__tests__/hmr-client.test.ts +532 -0
  21. package/src/bundler/__tests__/manifest-schema.test.ts +266 -0
  22. package/src/bundler/__tests__/prod-smoke.test.ts +138 -0
  23. package/src/bundler/__tests__/slot-dispatch.test.ts +573 -0
  24. package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -0
  25. package/src/bundler/__tests__/vendor-cache.test.ts +455 -0
  26. package/src/bundler/build.test.ts +8 -1
  27. package/src/bundler/build.ts +310 -18
  28. package/src/bundler/css.ts +326 -323
  29. package/src/bundler/dev.ts +1611 -59
  30. package/src/bundler/fast-refresh-plugin.ts +307 -0
  31. package/src/bundler/hmr-types.ts +252 -0
  32. package/src/bundler/manifest-schema.ts +301 -0
  33. package/src/bundler/safe-build.test.ts +128 -0
  34. package/src/bundler/safe-build.ts +77 -0
  35. package/src/bundler/scenario-matrix.ts +229 -0
  36. package/src/bundler/types.ts +11 -0
  37. package/src/bundler/vendor-cache-types.ts +130 -0
  38. package/src/bundler/vendor-cache.ts +526 -0
  39. package/src/client/router.ts +214 -56
  40. package/src/db/__tests__/db.test.ts +485 -0
  41. package/src/db/index.ts +513 -0
  42. package/src/db/migrations/__tests__/runner.test.ts +661 -0
  43. package/src/db/migrations/history-table.ts +345 -0
  44. package/src/db/migrations/lock.ts +269 -0
  45. package/src/db/migrations/runner.ts +633 -0
  46. package/src/desktop/__tests__/smoke.test.ts +100 -0
  47. package/src/desktop/__tests__/window.test.ts +172 -0
  48. package/src/desktop/__tests__/worker.test.ts +266 -0
  49. package/src/desktop/index.ts +43 -0
  50. package/src/desktop/types.ts +158 -0
  51. package/src/desktop/window.ts +492 -0
  52. package/src/desktop/worker.ts +180 -0
  53. package/src/email/__tests__/email.test.ts +355 -0
  54. package/src/email/index.ts +282 -0
  55. package/src/email/resend.ts +163 -0
  56. package/src/email/smtp.ts +64 -0
  57. package/src/filling/__tests__/session-sqlite.test.ts +454 -0
  58. package/src/filling/context.ts +72 -78
  59. package/src/filling/cookie-codec.ts +299 -0
  60. package/src/filling/deps.ts +25 -1
  61. package/src/filling/filling.ts +28 -3
  62. package/src/filling/session-sqlite.ts +617 -0
  63. package/src/filling/session.ts +265 -216
  64. package/src/guard/decision-memory.test.ts +52 -22
  65. package/src/id/__tests__/id.test.ts +120 -0
  66. package/src/id/index.ts +105 -0
  67. package/src/kitchen/index.ts +2 -2
  68. package/src/kitchen/kitchen-handler.ts +86 -0
  69. package/src/kitchen/stream/activity-sse.ts +2 -1
  70. package/src/middleware/csrf.ts +328 -0
  71. package/src/middleware/index.ts +40 -0
  72. package/src/middleware/oauth/__tests__/oauth.test.ts +574 -0
  73. package/src/middleware/oauth/index.ts +505 -0
  74. package/src/middleware/oauth/providers.ts +115 -0
  75. package/src/middleware/rate-limit/__tests__/rate-limit.test.ts +642 -0
  76. package/src/middleware/rate-limit/index.ts +522 -0
  77. package/src/middleware/rate-limit/sqlite-store.ts +382 -0
  78. package/src/middleware/secure/__tests__/secure.test.ts +360 -0
  79. package/src/middleware/secure/csp.ts +193 -0
  80. package/src/middleware/secure/index.ts +417 -0
  81. package/src/middleware/session.ts +174 -0
  82. package/src/observability/event-bus.ts +81 -79
  83. package/src/paths.ts +37 -0
  84. package/src/perf/hmr-markers.ts +215 -0
  85. package/src/perf/index.ts +104 -0
  86. package/src/resource/__tests__/generator.test.ts +603 -2
  87. package/src/resource/ddl/__tests__/diff.test.ts +639 -0
  88. package/src/resource/ddl/__tests__/emit.test.ts +799 -0
  89. package/src/resource/ddl/__tests__/snapshot.test.ts +499 -0
  90. package/src/resource/ddl/diff.ts +392 -0
  91. package/src/resource/ddl/emit.ts +548 -0
  92. package/src/resource/ddl/persistence-types.ts +218 -0
  93. package/src/resource/ddl/snapshot.ts +447 -0
  94. package/src/resource/ddl/type-map.ts +223 -0
  95. package/src/resource/ddl/types.ts +232 -0
  96. package/src/resource/generator-repo.ts +610 -0
  97. package/src/resource/generator-schema.ts +476 -0
  98. package/src/resource/generator.ts +117 -1
  99. package/src/resource/index.ts +17 -1
  100. package/src/resource/schema.ts +30 -0
  101. package/src/router/fs-scanner.ts +3 -0
  102. package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -0
  103. package/src/runtime/__tests__/hdr-client.test.ts +223 -0
  104. package/src/runtime/__tests__/http-errors.test.ts +117 -0
  105. package/src/runtime/__tests__/not-found.test.ts +152 -0
  106. package/src/runtime/boundary.tsx +21 -1
  107. package/src/runtime/fast-refresh-runtime.ts +322 -0
  108. package/src/runtime/fast-refresh-types.ts +128 -0
  109. package/src/runtime/hmr-client.ts +409 -0
  110. package/src/runtime/http-errors.ts +113 -0
  111. package/src/runtime/index.ts +6 -0
  112. package/src/runtime/logger.ts +678 -677
  113. package/src/runtime/not-found.ts +93 -0
  114. package/src/runtime/redirect.ts +133 -0
  115. package/src/runtime/server.ts +518 -20
  116. package/src/runtime/ssr.ts +340 -10
  117. package/src/runtime/streaming-ssr.ts +222 -19
  118. package/src/scheduler/__tests__/scheduler.test.ts +514 -0
  119. package/src/scheduler/index.ts +343 -0
  120. package/src/storage/s3/__tests__/s3.test.ts +479 -0
  121. package/src/storage/s3/index.ts +412 -0
  122. package/src/testing/index.ts +58 -0
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Phase 7.0 — 36-scenario HMR test matrix (3 project forms × 12 change kinds)
3
+ *
4
+ * This module is the SINGLE SOURCE OF TRUTH for the matrix that:
5
+ * - Agent E (R2) builds the Playwright test harness around
6
+ * - Agent F (R3) asserts latency targets against
7
+ * - Any future regression test iterates over
8
+ *
9
+ * The matrix intentionally enumerates every combination even when a cell
10
+ * is trivially n/a (e.g. `island.client.tsx` in a pure-SSG project with
11
+ * zero islands) — `expectedBehavior: "n/a"` keeps the matrix square so
12
+ * the iteration loop doesn't need per-cell conditionals.
13
+ *
14
+ * References:
15
+ * docs/bun/phase-7-team-plan.md §3.3
16
+ * docs/bun/phase-7-diagnostics/performance-reliability.md §4 (매트릭스 초안)
17
+ */
18
+
19
+ // ============================================
20
+ // Dimensions
21
+ // ============================================
22
+
23
+ /**
24
+ * Three representative project shapes. Each R2 E fixture corresponds to
25
+ * one of these.
26
+ */
27
+ export const PROJECT_FORMS = [
28
+ "pure-ssg", // no hydration, SSR → HTML only
29
+ "hybrid", // some pages with islands, some SSR-only
30
+ "full-interactive", // every route has at least one island
31
+ ] as const;
32
+
33
+ export type ProjectForm = (typeof PROJECT_FORMS)[number];
34
+
35
+ /**
36
+ * File change kinds the matrix covers. Order matters only for stable
37
+ * report output — the iterating test runner relies on `for (const k of
38
+ * CHANGE_KINDS)`.
39
+ *
40
+ * Kept in sync with `docs/bun/phase-7-diagnostics/performance-reliability.md §4`.
41
+ */
42
+ export const CHANGE_KINDS = [
43
+ "app/page.tsx",
44
+ "app/slot.ts",
45
+ "app/layout.tsx",
46
+ "app/contract.ts", // Agent D new coverage
47
+ "spec/resource.ts", // Agent D new coverage
48
+ "app/middleware.ts", // Agent D new coverage
49
+ "island.client.tsx",
50
+ "src/shared/**",
51
+ "src/top-level.ts", // Agent A new coverage (B1 fix)
52
+ "css",
53
+ "mandu.config.ts", // Agent D new coverage
54
+ ".env", // Agent D new coverage
55
+ ] as const;
56
+
57
+ export type ChangeKind = (typeof CHANGE_KINDS)[number];
58
+
59
+ // ============================================
60
+ // Cell shape
61
+ // ============================================
62
+
63
+ /**
64
+ * Every matrix cell's expected outcome. The test harness picks the
65
+ * right assertion strategy based on this field.
66
+ *
67
+ * - `"island-update"` : expect `island-update` WS message; DOM patch
68
+ * without full navigation; form/scroll preserved.
69
+ * - `"full-reload"` : expect `full-reload` or `reload` WS message;
70
+ * fresh HTML; previous state cleared.
71
+ * - `"prerender-regen"` : Pure-SSR common-dir path; expect #188 fix —
72
+ * new HTML reachable after reload.
73
+ * - `"css-update"` : expect `css-update`; <link> re-timestamped; no
74
+ * full reload.
75
+ * - `"server-restart"` : expect `mandu.config.ts` / `.env` auto-restart;
76
+ * dev server responds on same port after delay.
77
+ * - `"code-regen"` : expect contract/resource artifact regenerated
78
+ * AND handler re-registered (no browser event
79
+ * needed unless UI-visible).
80
+ * - `"n/a"` : combination impossible in that project form
81
+ * (e.g. island change in pure-ssg).
82
+ */
83
+ export type ExpectedBehavior =
84
+ | "island-update"
85
+ | "full-reload"
86
+ | "prerender-regen"
87
+ | "css-update"
88
+ | "server-restart"
89
+ | "code-regen"
90
+ | "n/a";
91
+
92
+ /**
93
+ * A single matrix cell. Agent E materializes one Playwright test per
94
+ * cell; Agent F measures `REBUILD_TOTAL` on the same cells and asserts
95
+ * latency against the target.
96
+ */
97
+ export interface ScenarioCell {
98
+ projectForm: ProjectForm;
99
+ changeKind: ChangeKind;
100
+ expectedBehavior: ExpectedBehavior;
101
+ /**
102
+ * Target `REBUILD_TOTAL` P95 in ms. `null` for `n/a` cells or
103
+ * `server-restart` (restart time varies by project startup).
104
+ */
105
+ latencyTargetMs: number | null;
106
+ }
107
+
108
+ // ============================================
109
+ // The 36 cells
110
+ // ============================================
111
+
112
+ /** Total cell count — mirrored in tests so they fail loudly if the
113
+ * matrix shape changes.
114
+ *
115
+ * NOTE: Declared BEFORE `SCENARIO_CELLS` so `buildMatrix()` — which runs
116
+ * during the `SCENARIO_CELLS` initializer and references this constant
117
+ * for its sanity check — does not hit a TDZ ReferenceError. The pair is
118
+ * still conceptually "matrix shape" metadata; they're co-located. */
119
+ export const EXPECTED_CELL_COUNT = PROJECT_FORMS.length * CHANGE_KINDS.length;
120
+
121
+ /**
122
+ * Pre-computed matrix. Derived once at module load so importers don't
123
+ * pay the lookup cost per test.
124
+ */
125
+ export const SCENARIO_CELLS: readonly ScenarioCell[] = buildMatrix();
126
+
127
+ /** Look up a single cell. O(n) — matrix is small. */
128
+ export function findCell(
129
+ projectForm: ProjectForm,
130
+ changeKind: ChangeKind,
131
+ ): ScenarioCell {
132
+ const cell = SCENARIO_CELLS.find(
133
+ (c) => c.projectForm === projectForm && c.changeKind === changeKind,
134
+ );
135
+ if (!cell) {
136
+ throw new Error(
137
+ `No scenario cell for ${projectForm} × ${changeKind}. Add it to scenario-matrix.ts.`,
138
+ );
139
+ }
140
+ return cell;
141
+ }
142
+
143
+ // ============================================
144
+ // Matrix construction
145
+ // ============================================
146
+
147
+ function buildMatrix(): ScenarioCell[] {
148
+ const cells: ScenarioCell[] = [];
149
+ for (const projectForm of PROJECT_FORMS) {
150
+ for (const changeKind of CHANGE_KINDS) {
151
+ cells.push({
152
+ projectForm,
153
+ changeKind,
154
+ expectedBehavior: classifyBehavior(projectForm, changeKind),
155
+ latencyTargetMs: classifyTarget(projectForm, changeKind),
156
+ });
157
+ }
158
+ }
159
+ if (cells.length !== EXPECTED_CELL_COUNT) {
160
+ throw new Error(
161
+ `scenario-matrix.ts built ${cells.length} cells, expected ${EXPECTED_CELL_COUNT}.`,
162
+ );
163
+ }
164
+ return cells;
165
+ }
166
+
167
+ function classifyBehavior(
168
+ projectForm: ProjectForm,
169
+ changeKind: ChangeKind,
170
+ ): ExpectedBehavior {
171
+ // Island changes only apply to forms with islands.
172
+ if (changeKind === "island.client.tsx") {
173
+ return projectForm === "pure-ssg" ? "n/a" : "island-update";
174
+ }
175
+
176
+ // CSS is always style-swap.
177
+ if (changeKind === "css") return "css-update";
178
+
179
+ // Config / env always restart.
180
+ if (changeKind === "mandu.config.ts" || changeKind === ".env") {
181
+ return "server-restart";
182
+ }
183
+
184
+ // Contract / resource trigger code-gen + handler re-register.
185
+ if (changeKind === "app/contract.ts" || changeKind === "spec/resource.ts") {
186
+ return "code-regen";
187
+ }
188
+
189
+ // src/shared + src/top-level in pure-SSG needs the #188 prerender path.
190
+ if (
191
+ projectForm === "pure-ssg" &&
192
+ (changeKind === "src/shared/**" || changeKind === "src/top-level.ts")
193
+ ) {
194
+ return "prerender-regen";
195
+ }
196
+
197
+ // Everything else is a full reload (SSR-rendered page, layout, slot,
198
+ // middleware, common deps in hybrid/interactive projects). Full reload
199
+ // is the honest fallback — SSR has no React state to preserve.
200
+ return "full-reload";
201
+ }
202
+
203
+ function classifyTarget(
204
+ projectForm: ProjectForm,
205
+ changeKind: ChangeKind,
206
+ ): number | null {
207
+ const behavior = classifyBehavior(projectForm, changeKind);
208
+ switch (behavior) {
209
+ case "n/a":
210
+ return null;
211
+ case "island-update":
212
+ return 50; // HMR_PERF_TARGETS.ISLAND_REBUILD_P95_MS
213
+ case "css-update":
214
+ return 100; // HMR_PERF_TARGETS.CSS_REBUILD_P95_MS
215
+ case "server-restart":
216
+ return null; // project-dependent, not an HMR target
217
+ case "code-regen":
218
+ return 400; // regen is heavier than a plain rebuild
219
+ case "prerender-regen":
220
+ return 400; // fan-out across all prerendered routes
221
+ case "full-reload":
222
+ // src/shared in hybrid/interactive → common-dir rebuild (400ms),
223
+ // otherwise a single SSR page (200ms).
224
+ if (changeKind === "src/shared/**" || changeKind === "src/top-level.ts") {
225
+ return 400; // HMR_PERF_TARGETS.COMMON_DIR_REBUILD_P95_MS
226
+ }
227
+ return 200; // HMR_PERF_TARGETS.SSR_REBUILD_P95_MS
228
+ }
229
+ }
@@ -73,6 +73,17 @@ export interface BundleManifest {
73
73
  vendor: string;
74
74
  /** Client-side Router 런타임 */
75
75
  router?: string;
76
+ /**
77
+ * Phase 7.1 B-2: dev-only Fast Refresh assets. Absent in prod
78
+ * manifests so the HTML preamble short-circuits without trying to
79
+ * load a nonexistent URL.
80
+ */
81
+ fastRefresh?: {
82
+ /** Bundled `react-refresh/runtime` (e.g. `/.mandu/client/_vendor-react-refresh.js`). */
83
+ runtime: string;
84
+ /** Mandu's glue module exposing `installGlobal` / `__MANDU_HMR__`. */
85
+ glue: string;
86
+ };
76
87
  };
77
88
  /** Import map for bare specifiers (react, react-dom, etc.) */
78
89
  importMap?: {
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Phase 7.2 — Tier 2 vendor shim disk cache contract.
3
+ *
4
+ * Cold start breakdown (R0.3 diagnostic): the largest remaining cost is
5
+ * `buildVendorShims` rebuilding `_vendor-react.js`, `_vendor-react-dom.js`,
6
+ * `_vendor-react-refresh.js`, and `_fast-refresh-runtime.js` on every
7
+ * `mandu dev` invocation. These outputs are **deterministic per React /
8
+ * React-DOM / react-refresh / Bun version combination**, so Agent A's
9
+ * Phase 7.2.S2 caches them on disk and reuses them across boots.
10
+ *
11
+ * This file is the CONTRACT between the cache writer (Agent A in
12
+ * `bundler/vendor-cache.ts`) and any reader (bench scripts, audit
13
+ * tools). Do NOT add logic here — pure types only.
14
+ *
15
+ * Cache key composition (hash input, order-preserved):
16
+ * 1. Bun runtime version (`Bun.version`)
17
+ * 2. React package version (from the project's resolved `react/package.json`)
18
+ * 3. React-DOM package version
19
+ * 4. react-refresh package version
20
+ * 5. Mandu version (`@mandujs/core/package.json`) — shim source evolves
21
+ *
22
+ * Any miss invalidates every entry — we do not support per-entry
23
+ * invalidation to keep the manifest trivial (N=4 shims isn't worth it).
24
+ *
25
+ * References:
26
+ * docs/bun/phase-7-2-team-plan.md §2.2
27
+ * docs/bun/phase-7-1-diagnostics/cold-start-breakdown.md (source of the ~150ms target)
28
+ */
29
+
30
+ // ============================================
31
+ // Manifest shape
32
+ // ============================================
33
+
34
+ /**
35
+ * Single cache entry — one `_vendor-*.js` output.
36
+ */
37
+ export interface VendorCacheEntry {
38
+ /** Relative path inside `VENDOR_CACHE_DIR`. No leading slash. */
39
+ path: string;
40
+ /** Bytes on disk. Verification hint — mismatch → invalidate. */
41
+ size: number;
42
+ /** SHA-256 of the cached content. Tamper detection. */
43
+ hash: string;
44
+ }
45
+
46
+ /**
47
+ * `.mandu/vendor-cache/vendor-cache.json` — loaded at boot to decide
48
+ * cache hit vs miss.
49
+ */
50
+ export interface VendorCacheManifest {
51
+ /** Format version. Bump on breaking manifest changes. */
52
+ version: 1;
53
+
54
+ /** `Bun.version` at write time. Any mismatch → wholesale miss. */
55
+ bunVersion: string;
56
+
57
+ /**
58
+ * Resolved versions of the four npm packages that feed the shims.
59
+ * Order matches the hash input above.
60
+ */
61
+ reactVersion: string;
62
+ reactDomVersion: string;
63
+ reactRefreshVersion: string;
64
+
65
+ /** `@mandujs/core` version — shim source code evolves with Mandu. */
66
+ manduCoreVersion: string;
67
+
68
+ /**
69
+ * Map from shim logical id (e.g. `"react"`, `"react-dom"`,
70
+ * `"react-refresh-runtime"`, `"fast-refresh-glue"`) to on-disk entry.
71
+ * Writer is expected to include exactly the four keys above.
72
+ */
73
+ entries: Record<string, VendorCacheEntry>;
74
+
75
+ /** ISO 8601 timestamp. Provenance only — not used by hit/miss logic. */
76
+ generatedAt: string;
77
+ }
78
+
79
+ // ============================================
80
+ // Constants — location + filename
81
+ // ============================================
82
+
83
+ /**
84
+ * Relative to project root. All cache files go here.
85
+ *
86
+ * Kept inside `.mandu/` (already gitignored) so cache survives branch
87
+ * switches but not `git clean -fdx`. Intentional: heavy operations
88
+ * re-run on hard clean, routine workflow stays warm.
89
+ */
90
+ export const VENDOR_CACHE_DIR = ".mandu/vendor-cache";
91
+
92
+ /** Filename of the manifest inside `VENDOR_CACHE_DIR`. */
93
+ export const VENDOR_CACHE_FILENAME = "vendor-cache.json";
94
+
95
+ /**
96
+ * Safety limit — if the cache grows past this, invalidate and rebuild.
97
+ * The four shims total ~25 KB today; 10 MB is overly generous but
98
+ * catches pathological disk corruption.
99
+ */
100
+ export const VENDOR_CACHE_MAX_BYTES = 10 * 1024 * 1024;
101
+
102
+ // ============================================
103
+ // Hit/miss result — returned by the cache reader
104
+ // ============================================
105
+
106
+ /**
107
+ * Branded result so callers (build.ts) don't need to re-read the
108
+ * manifest to know why.
109
+ */
110
+ export type VendorCacheResult =
111
+ | { kind: "hit"; manifest: VendorCacheManifest }
112
+ | {
113
+ kind: "miss";
114
+ reason:
115
+ | "no-manifest" // first boot
116
+ | "version-mismatch" // any of the 5 version fields changed
117
+ | "missing-entry" // manifest references a file that doesn't exist
118
+ | "hash-mismatch" // tamper / partial write
119
+ | "size-mismatch" // same as above, cheaper first-pass check
120
+ | "format-version"; // manifest.version !== 1
121
+ /** Populated when reason === "version-mismatch" — which field. */
122
+ mismatchedField?: keyof Pick<
123
+ VendorCacheManifest,
124
+ | "bunVersion"
125
+ | "reactVersion"
126
+ | "reactDomVersion"
127
+ | "reactRefreshVersion"
128
+ | "manduCoreVersion"
129
+ >;
130
+ };