@warlock.js/web 5.0.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 (182) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +125 -0
  4. package/esm/build/contribution.d.mts +55 -0
  5. package/esm/build/contribution.mjs +132 -0
  6. package/esm/build/contribution.mjs.map +1 -0
  7. package/esm/build/discover-pages.mjs +294 -0
  8. package/esm/build/discover-pages.mjs.map +1 -0
  9. package/esm/build/generate-client-registry.mjs +123 -0
  10. package/esm/build/generate-client-registry.mjs.map +1 -0
  11. package/esm/build/generate-pages-barrel.mjs +330 -0
  12. package/esm/build/generate-pages-barrel.mjs.map +1 -0
  13. package/esm/build/read-route-exports.mjs +212 -0
  14. package/esm/build/read-route-exports.mjs.map +1 -0
  15. package/esm/client/build-hydrated-tree.mjs +113 -0
  16. package/esm/client/build-hydrated-tree.mjs.map +1 -0
  17. package/esm/client/hydrate-page.mjs +73 -0
  18. package/esm/client/hydrate-page.mjs.map +1 -0
  19. package/esm/client/navigation/current-route.d.mts +91 -0
  20. package/esm/client/navigation/current-route.mjs +66 -0
  21. package/esm/client/navigation/current-route.mjs.map +1 -0
  22. package/esm/client/navigation/fetch-page-data.mjs +97 -0
  23. package/esm/client/navigation/fetch-page-data.mjs.map +1 -0
  24. package/esm/client/navigation/navigation-root.mjs +196 -0
  25. package/esm/client/navigation/navigation-root.mjs.map +1 -0
  26. package/esm/client/navigation/prefetch.mjs +110 -0
  27. package/esm/client/navigation/prefetch.mjs.map +1 -0
  28. package/esm/client/navigation/refresh.d.mts +40 -0
  29. package/esm/client/navigation/refresh.mjs +146 -0
  30. package/esm/client/navigation/refresh.mjs.map +1 -0
  31. package/esm/client/navigation/verbs.d.mts +55 -0
  32. package/esm/client/navigation/verbs.mjs +66 -0
  33. package/esm/client/navigation/verbs.mjs.map +1 -0
  34. package/esm/client/runtime/index.d.mts +4 -0
  35. package/esm/client/runtime/index.mjs +4 -0
  36. package/esm/client/runtime/manifest.d.mts +8 -0
  37. package/esm/client/runtime/manifest.mjs +77 -0
  38. package/esm/client/runtime/manifest.mjs.map +1 -0
  39. package/esm/client/runtime/matcher.d.mts +7 -0
  40. package/esm/client/runtime/matcher.mjs +169 -0
  41. package/esm/client/runtime/matcher.mjs.map +1 -0
  42. package/esm/client/runtime/types.d.mts +22 -0
  43. package/esm/components/default-app.mjs +32 -0
  44. package/esm/components/default-app.mjs.map +1 -0
  45. package/esm/components/document-context.d.mts +1 -0
  46. package/esm/components/document-context.mjs +24 -0
  47. package/esm/components/document-context.mjs.map +1 -0
  48. package/esm/components/head.d.mts +14 -0
  49. package/esm/components/head.mjs +63 -0
  50. package/esm/components/head.mjs.map +1 -0
  51. package/esm/components/link.d.mts +157 -0
  52. package/esm/components/link.mjs +194 -0
  53. package/esm/components/link.mjs.map +1 -0
  54. package/esm/components/scripts.d.mts +14 -0
  55. package/esm/components/scripts.mjs +21 -0
  56. package/esm/components/scripts.mjs.map +1 -0
  57. package/esm/connector/index.d.mts +6 -0
  58. package/esm/connector/index.mjs +7 -0
  59. package/esm/context.d.mts +107 -0
  60. package/esm/hydration/index.d.mts +1 -0
  61. package/esm/hydration/index.mjs +33 -0
  62. package/esm/hydration/index.mjs.map +1 -0
  63. package/esm/hydration-payload.d.mts +1 -0
  64. package/esm/hydration-payload.mjs +72 -0
  65. package/esm/hydration-payload.mjs.map +1 -0
  66. package/esm/index.d.mts +35 -0
  67. package/esm/index.mjs +12 -0
  68. package/esm/loaders.d.mts +47 -0
  69. package/esm/metadata.d.mts +54 -0
  70. package/esm/props.d.mts +42 -0
  71. package/esm/route.d.mts +30 -0
  72. package/esm/routing/compose-route-path.mjs +33 -0
  73. package/esm/routing/compose-route-path.mjs.map +1 -0
  74. package/esm/routing/data-request.mjs +52 -0
  75. package/esm/routing/data-request.mjs.map +1 -0
  76. package/esm/routing/layout-policy.mjs +78 -0
  77. package/esm/routing/layout-policy.mjs.map +1 -0
  78. package/esm/routing/navigator.mjs +21 -0
  79. package/esm/routing/navigator.mjs.map +1 -0
  80. package/esm/routing/query-string.d.mts +250 -0
  81. package/esm/routing/query-string.mjs +340 -0
  82. package/esm/routing/query-string.mjs.map +1 -0
  83. package/esm/routing/route-identity.mjs +68 -0
  84. package/esm/routing/route-identity.mjs.map +1 -0
  85. package/esm/routing/route-table.d.mts +34 -0
  86. package/esm/routing/route-table.mjs +182 -0
  87. package/esm/routing/route-table.mjs.map +1 -0
  88. package/esm/routing/router-events.d.mts +159 -0
  89. package/esm/routing/router-events.mjs +83 -0
  90. package/esm/routing/router-events.mjs.map +1 -0
  91. package/esm/server/buffered-response.mjs +115 -0
  92. package/esm/server/buffered-response.mjs.map +1 -0
  93. package/esm/server/build-hydration-payload.mjs +27 -0
  94. package/esm/server/build-hydration-payload.mjs.map +1 -0
  95. package/esm/server/client-asset-url-prefix.d.mts +22 -0
  96. package/esm/server/client-asset-url-prefix.mjs +23 -0
  97. package/esm/server/client-asset-url-prefix.mjs.map +1 -0
  98. package/esm/server/create-page-module-loader.mjs +49 -0
  99. package/esm/server/create-page-module-loader.mjs.map +1 -0
  100. package/esm/server/create-page-route-handler.mjs +107 -0
  101. package/esm/server/create-page-route-handler.mjs.map +1 -0
  102. package/esm/server/dev-server.mjs +167 -0
  103. package/esm/server/dev-server.mjs.map +1 -0
  104. package/esm/server/execute-page-request.d.mts +1 -0
  105. package/esm/server/execute-page-request.mjs +166 -0
  106. package/esm/server/execute-page-request.mjs.map +1 -0
  107. package/esm/server/execute-page-request.types.d.mts +1 -0
  108. package/esm/server/hydration-client-url.d.mts +37 -0
  109. package/esm/server/hydration-client-url.mjs +101 -0
  110. package/esm/server/hydration-client-url.mjs.map +1 -0
  111. package/esm/server/index.mjs +13 -0
  112. package/esm/server/install-page-routes-from-manifest.mjs +133 -0
  113. package/esm/server/install-page-routes-from-manifest.mjs.map +1 -0
  114. package/esm/server/install-page-routes.d.mts +1 -0
  115. package/esm/server/install-page-routes.mjs +6 -0
  116. package/esm/server/install-production-page-routes.mjs +55 -0
  117. package/esm/server/install-production-page-routes.mjs.map +1 -0
  118. package/esm/server/match-page-route.mjs +48 -0
  119. package/esm/server/match-page-route.mjs.map +1 -0
  120. package/esm/server/page-context.d.mts +1 -0
  121. package/esm/server/page-context.mjs +33 -0
  122. package/esm/server/page-context.mjs.map +1 -0
  123. package/esm/server/page-manifest.d.mts +102 -0
  124. package/esm/server/page-manifest.mjs +31 -0
  125. package/esm/server/page-manifest.mjs.map +1 -0
  126. package/esm/server/render-page.mjs +214 -0
  127. package/esm/server/render-page.mjs.map +1 -0
  128. package/esm/server/resolve-page-metadata.mjs +33 -0
  129. package/esm/server/resolve-page-metadata.mjs.map +1 -0
  130. package/esm/server/resolve-validation-data.mjs +27 -0
  131. package/esm/server/resolve-validation-data.mjs.map +1 -0
  132. package/esm/server/settle-page-response.d.mts +1 -0
  133. package/esm/server/settle-page-response.mjs +90 -0
  134. package/esm/server/settle-page-response.mjs.map +1 -0
  135. package/esm/server/stylesheet-urls.mjs +65 -0
  136. package/esm/server/stylesheet-urls.mjs.map +1 -0
  137. package/esm/server/web-connector-factory.d.mts +25 -0
  138. package/esm/server/web-connector-factory.mjs +121 -0
  139. package/esm/server/web-connector-factory.mjs.map +1 -0
  140. package/esm/server/web-connector.d.mts +34 -0
  141. package/esm/server/web-connector.mjs +516 -0
  142. package/esm/server/web-connector.mjs.map +1 -0
  143. package/esm/shared.d.mts +22 -0
  144. package/esm/shared.mjs +223 -0
  145. package/esm/shared.mjs.map +1 -0
  146. package/esm/validation.d.mts +31 -0
  147. package/esm/vite/app-convention-aliases.mjs +43 -0
  148. package/esm/vite/app-convention-aliases.mjs.map +1 -0
  149. package/esm/vite/build-client.d.mts +38 -0
  150. package/esm/vite/build-client.mjs +72 -0
  151. package/esm/vite/build-client.mjs.map +1 -0
  152. package/esm/vite/gate-a-resolve.d.mts +57 -0
  153. package/esm/vite/gate-a-resolve.mjs +834 -0
  154. package/esm/vite/gate-a-resolve.mjs.map +1 -0
  155. package/esm/vite/gate-b-secrets.d.mts +57 -0
  156. package/esm/vite/gate-b-secrets.mjs +299 -0
  157. package/esm/vite/gate-b-secrets.mjs.map +1 -0
  158. package/esm/vite/gate-c-verify.d.mts +118 -0
  159. package/esm/vite/gate-c-verify.mjs +263 -0
  160. package/esm/vite/gate-c-verify.mjs.map +1 -0
  161. package/esm/vite/hydration-entries.d.mts +17 -0
  162. package/esm/vite/hydration-entries.mjs +45 -0
  163. package/esm/vite/hydration-entries.mjs.map +1 -0
  164. package/esm/vite/index.d.mts +126 -0
  165. package/esm/vite/index.mjs +146 -0
  166. package/esm/vite/index.mjs.map +1 -0
  167. package/esm/vite/page-registry-plugin.d.mts +43 -0
  168. package/esm/vite/page-registry-plugin.mjs +138 -0
  169. package/esm/vite/page-registry-plugin.mjs.map +1 -0
  170. package/esm/vite/projection.d.mts +31 -0
  171. package/esm/vite/projection.mjs +384 -0
  172. package/esm/vite/projection.mjs.map +1 -0
  173. package/llms-full.txt +1041 -0
  174. package/llms.txt +21 -0
  175. package/package.json +77 -0
  176. package/skills/add-web-to-an-app/SKILL.md +103 -0
  177. package/skills/create-a-page/SKILL.md +141 -0
  178. package/skills/load-page-data/SKILL.md +183 -0
  179. package/skills/navigate-on-the-client/SKILL.md +182 -0
  180. package/skills/serve-styles/SKILL.md +145 -0
  181. package/skills/use-layouts/SKILL.md +121 -0
  182. package/skills/write-the-root/SKILL.md +131 -0
package/esm/shared.mjs ADDED
@@ -0,0 +1,223 @@
1
+ //#region ../web/src/shared.ts
2
+ /**
3
+ * Per-request scope records, keyed by the request's own store object — the
4
+ * exact idiom of request-memo.ts:10: the store object is fresh per request
5
+ * (RequestContext.buildStore), so two concurrent requests cannot share or even
6
+ * see each other's entries, and everything is GC-eligible when the request's
7
+ * ALS frame ends.
8
+ */
9
+ const sharedScopes = /* @__PURE__ */ new WeakMap();
10
+ let resolveSharedStore;
11
+ /**
12
+ * Boot-time wiring, called once by the pipeline before any request runs.
13
+ * Returns the previously connected resolver so a caller (tests, mainly) can
14
+ * restore it.
15
+ */
16
+ function connectSharedStore(resolve) {
17
+ const previous = resolveSharedStore;
18
+ resolveSharedStore = resolve;
19
+ return previous;
20
+ }
21
+ function currentStore(access) {
22
+ if (!resolveSharedStore) throw new Error(`Cannot ${access}: \`shared\` is not connected to a request store (web/src/shared.ts). \`shared\` is request-scoped — its data lives in core's per-request AsyncLocalStorage store, and this module holds only a resolver, never data. Fix: the server bootstrap must call connectSharedStore(() => requestContext.getStore()) before any request runs; in a unit test, connect a resolver for the context the test runs in.`);
23
+ const store = resolveSharedStore();
24
+ if (!store) throw new Error(`Cannot ${access}: \`shared\` was accessed outside a request context (web/src/shared.ts). \`shared\` is per-request — this happens at module load, in a background job, or from a late timer/dangling promise that outlived its request. There is deliberately NO fallback to a process-wide object: that would leak one user's data into another's response. Fix: move this access into code the request pipeline runs (middleware, a loader, a component render), or pass the value you need explicitly.`);
25
+ return store;
26
+ }
27
+ function scopeOf(store, access) {
28
+ const scope = sharedScopes.get(store);
29
+ if (!scope) throw new Error(`Cannot ${access}: this request has no \`shared\` scope yet (web/src/shared.ts). The per-request target is created by the pipeline at stage 2 via enterSharedScope(store); this access ran inside the request context but before that point. Fix: move the access after pipeline stage 2 (any middleware/loader/render code qualifies), or — if you are the pipeline — call enterSharedScope(store) first.`);
30
+ return scope;
31
+ }
32
+ function requireScope(access) {
33
+ return scopeOf(currentStore(access), access);
34
+ }
35
+ function sealedWriteError(action, key) {
36
+ return /* @__PURE__ */ new Error(`Cannot ${action} \`shared.${String(key)}\`: shared is SEALED for this request (web/src/shared.ts). Writes are middleware work and happen before the seal; after the seal the payload is committed to the page and any further write would silently diverge server from client. Reads keep working. Fix: move this write into middleware (before the pipeline's seal stage), or if the value is render-time state, it does not belong in \`shared\`.`);
37
+ }
38
+ /**
39
+ * Pipeline stage 2: create THE per-request target. Once per request — the
40
+ * store object is the request's identity (fresh per request), so a second call
41
+ * for the same store is a pipeline bug, not a merge.
42
+ *
43
+ * Returns the raw target; the pipeline may hold it, but app code goes through
44
+ * `shared`.
45
+ */
46
+ function enterSharedScope(store) {
47
+ if (sharedScopes.has(store)) throw new Error("enterSharedScope() was called twice for the same request store (web/src/shared.ts). Each request gets exactly one `shared` target, created once at pipeline stage 2. Fix: enter the scope once per request; to reach the existing target, use `shared` inside the request context instead.");
48
+ const target = {};
49
+ sharedScopes.set(store, {
50
+ target,
51
+ sealed: false
52
+ });
53
+ return target;
54
+ }
55
+ function rejectAtGate(path, offender) {
56
+ throw new Error(`sealShared(): \`${path}\` is ${offender} (web/src/shared.ts prototype gate — runs in production AND dev). \`shared\` is the audit surface serialized into the page: every value must be plain data (scalars, arrays, plain objects) or carry a \`toJSON()\` serialization contract (a Resource). A Date/Map/Set/class instance smuggles prototype state past that audit and does not survive serialization intact. Fix: store plain data — an ISO string instead of a Date, an object or array instead of a Map/Set, a Resource (or its \`.toJSON()\` output) instead of a model or class instance.`);
57
+ }
58
+ /**
59
+ * The prototype gate. Precedence mirrors `Response.parse` exactly so the gate
60
+ * and the normalization that follows it agree on every value: toJSON wins over
61
+ * the plain-object branch (response.ts:301-305 vs :319) and is NOT descended —
62
+ * a Resource's field list is its own contract. Date is
63
+ * checked BEFORE toJSON because `Date.prototype.toJSON` exists and Dates are
64
+ * rejected regardless.
65
+ */
66
+ function assertClientSafe(value, path) {
67
+ if (value === null || value === void 0) return;
68
+ const valueType = typeof value;
69
+ if (valueType === "function") rejectAtGate(path, "a function");
70
+ if (valueType !== "object") return;
71
+ if (value instanceof Date) rejectAtGate(path, "a Date");
72
+ if (value instanceof Map) rejectAtGate(path, "a Map");
73
+ if (value instanceof Set) rejectAtGate(path, "a Set");
74
+ if (typeof value.toJSON === "function") return;
75
+ if (Array.isArray(value)) {
76
+ value.forEach((item, index) => assertClientSafe(item, `${path}[${index}]`));
77
+ return;
78
+ }
79
+ const proto = Object.getPrototypeOf(value);
80
+ if (proto === Object.prototype || proto === null) {
81
+ for (const key of Object.keys(value)) assertClientSafe(value[key], `${path}.${key}`);
82
+ return;
83
+ }
84
+ rejectAtGate(path, `a class instance (${proto?.constructor?.name ?? "unknown class"})`);
85
+ }
86
+ function deepFreeze(value) {
87
+ if (!value || typeof value !== "object" || Object.isFrozen(value)) return;
88
+ Object.freeze(value);
89
+ for (const key of Object.keys(value)) deepFreeze(value[key]);
90
+ }
91
+ let browserSharedSnapshot;
92
+ let browserSharedInstalled = false;
93
+ function freezeBrowserSnapshot(value, seen) {
94
+ if (seen.has(value)) return;
95
+ seen.add(value);
96
+ for (const key of Object.keys(value)) {
97
+ const child = value[key];
98
+ if (child !== null && typeof child === "object") freezeBrowserSnapshot(child, seen);
99
+ }
100
+ Object.freeze(value);
101
+ }
102
+ /**
103
+ * Install the browser's one readonly `shared` snapshot. An object value is
104
+ * recursively frozen in place and retained wholesale so every consumer
105
+ * observes the same identity; this never reads or writes the server ALS scope.
106
+ */
107
+ function installBrowserSharedSnapshot(value) {
108
+ if (value !== null && typeof value === "object") freezeBrowserSnapshot(value, /* @__PURE__ */ new Set());
109
+ browserSharedSnapshot = value;
110
+ browserSharedInstalled = true;
111
+ }
112
+ /** Compatibility alias; browser snapshot ownership lives in the installer above. */
113
+ function hydrateShared(value) {
114
+ installBrowserSharedSnapshot(value);
115
+ }
116
+ function readBrowserSharedSnapshot() {
117
+ if (!browserSharedInstalled) throw new Error("Cannot read `shared` via useShared(): the browser snapshot has not been installed (web/src/shared.ts). Hydration must validate the complete #__WARLOCK_DATA__ payload and call installBrowserSharedSnapshot() before constructing the React tree.");
118
+ return browserSharedSnapshot;
119
+ }
120
+ /**
121
+ * The settle-stage seal, exported for the pipeline. Order is load-bearing:
122
+ *
123
+ * 1. PROTOTYPE GATE (pre-parse) — production and dev, offending key named
124
+ * with its full path. Fail-fast courtesy: catches most violations before
125
+ * spending a parse pass on them.
126
+ * 2. `Response.parse` NORMALIZATION — via the store's own response instance
127
+ * (core/src/http/response.ts:297, the public surface). parse mutates the
128
+ * target IN PLACE (response.ts:327), which is exactly why it MUST precede
129
+ * the freeze: freeze-then-parse throws on the first key parse writes
130
+ * in place.
131
+ * 3. PROTOTYPE GATE AGAIN (post-parse) — closes the re-entry window the first
132
+ * gate cannot see: `Response.parse` never re-parses a `toJSON()` result
133
+ * (it only normalizes the value handed to it), so a Resource whose `toJSON()` returns a
134
+ * Date/Map/Set/class instance re-enters the payload AFTER the first gate
135
+ * already ran — the first gate inspects the pre-parse value (a plain
136
+ * object with a callable `toJSON`, correctly not descended) and parse then
137
+ * puts back exactly what that gate would have rejected. Only a gate that
138
+ * runs on the POST-parse target — the object that actually ships — can
139
+ * catch it. This is the only gate that matters for correctness; the first
140
+ * is a fail-fast courtesy that never gets to be wrong on its own.
141
+ * 4. DEEP FREEZE — dev only (env read at seal time). Prod skips the freeze but
142
+ * NOT the sealed-write throw below; freezing is defense-in-depth for direct
143
+ * target references, the throw is the contract.
144
+ * 5. Sealed flag — writes/deletes/defines through `shared` now throw naming
145
+ * the key; reads keep working.
146
+ *
147
+ * `store` defaults to the current request's store; the pipeline holds the
148
+ * store either way, since it entered the scope with it. Returns the sealed
149
+ * target — serialize THAT, not a re-read of the proxy.
150
+ */
151
+ async function sealShared(store) {
152
+ const scopeStore = store ?? currentStore("seal `shared`");
153
+ const scope = scopeOf(scopeStore, "seal `shared`");
154
+ if (scope.sealed) throw new Error("sealShared() was called twice for the same request (web/src/shared.ts). The seal is the pipeline's settle stage and runs once, after middleware writes and before serialization. Fix: seal once per request.");
155
+ assertClientSafe(scope.target, "shared");
156
+ const response = scopeStore.response;
157
+ if (!response || typeof response.parse !== "function") throw new Error("sealShared() found no `response.parse` on the request store (web/src/shared.ts). Sealing normalizes `shared` through core's public `Response.parse` (core/src/http/response.ts:297), reached via the store's own `response` — the store the pipeline entered the scope with must be core's request store (`{ request, response }`, request-context.ts:10-13). Fix: pass that store (or run sealShared() inside the request context that carries it).");
158
+ await response.parse(scope.target);
159
+ assertClientSafe(scope.target, "shared");
160
+ if (import.meta.env?.DEV) deepFreeze(scope.target);
161
+ scope.sealed = true;
162
+ return scope.target;
163
+ }
164
+ /**
165
+ * The WRITABLE per-request payload — middleware's half of the contract.
166
+ *
167
+ * Every `shared.x = …` at a call site reads like a global write; it is not —
168
+ * each trap below resolves the CURRENT request's target through the connected
169
+ * store resolver, on every single access — two concurrent requests writing
170
+ * `shared.locale` write to two different objects.
171
+ */
172
+ const shared = new Proxy({}, {
173
+ get(_stub, key) {
174
+ return requireScope(`read \`shared.${String(key)}\``).target[key];
175
+ },
176
+ set(_stub, key, value) {
177
+ const scope = requireScope(`write \`shared.${String(key)}\``);
178
+ if (scope.sealed) throw sealedWriteError("write", key);
179
+ scope.target[key] = value;
180
+ return true;
181
+ },
182
+ has(_stub, key) {
183
+ return key in requireScope(`check \`shared.${String(key)}\``).target;
184
+ },
185
+ deleteProperty(_stub, key) {
186
+ const scope = requireScope(`delete \`shared.${String(key)}\``);
187
+ if (scope.sealed) throw sealedWriteError("delete", key);
188
+ delete scope.target[key];
189
+ return true;
190
+ },
191
+ ownKeys() {
192
+ return Reflect.ownKeys(requireScope("enumerate `shared`").target);
193
+ },
194
+ getOwnPropertyDescriptor(_stub, key) {
195
+ const descriptor = Object.getOwnPropertyDescriptor(requireScope(`describe \`shared.${String(key)}\``).target, key);
196
+ if (!descriptor) return void 0;
197
+ return {
198
+ ...descriptor,
199
+ configurable: true
200
+ };
201
+ },
202
+ defineProperty(_stub, key, descriptor) {
203
+ const scope = requireScope(`define \`shared.${String(key)}\``);
204
+ if (scope.sealed) throw sealedWriteError("define", key);
205
+ Reflect.defineProperty(scope.target, key, descriptor);
206
+ return true;
207
+ }
208
+ });
209
+ /**
210
+ * The READ half for components at depth. On the server it preserves the live
211
+ * ALS proxy behavior. In the browser it returns the exact recursively frozen
212
+ * object installed from the validated hydration payload, never an empty or
213
+ * process-wide fallback.
214
+ */
215
+ function useShared() {
216
+ if (typeof window !== "undefined") return readBrowserSharedSnapshot();
217
+ requireScope("read `shared` via useShared()");
218
+ return shared;
219
+ }
220
+
221
+ //#endregion
222
+ export { connectSharedStore, enterSharedScope, hydrateShared, sealShared, shared, useShared };
223
+ //# sourceMappingURL=shared.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.mjs","names":[],"sources":["../../../../../../web/src/shared.ts"],"sourcesContent":["import type { SharedContext } from \"./index\";\r\n\r\n/**\r\n * The runtime behind `shared` — the per-request, ALS-backed audit surface.\r\n *\r\n * `shared` is syntactically global for DX but semantically request-scoped: the\r\n * exported value is a Proxy, and EVERY trap re-resolves the current request's\r\n * target through core's AsyncLocalStorage store. This module holds a resolver,\r\n * never data — nothing here may cache a target, because a cached target\r\n * reintroduces the cross-request leak in the one form that still passes a\r\n * single-request test. And it throws rather than falling\r\n * back: outside a live request there is no process-wide object to land on —\r\n * that is `useRequestStore()`'s `|| {}` shape (request-context.ts:74-76),\r\n * deliberately not copied here; the pattern followed instead is\r\n * `requestMemo()`'s raw-store-or-throw (core/src/http/context/request-memo.ts:29-40).\r\n *\r\n * WHY A RESOLVER AND NOT `import { requestContext } from \"@warlock.js/core\"`:\r\n * web does not (and must not yet) depend on core — no core dep in\r\n * web/package.json, no tsconfig path, and no built esm/ in this checkout\r\n * (context.ts:96-98 records the same fact for types). So the PIPELINE, which\r\n * lives where `requestContext` is in scope, owns the wiring end to end:\r\n *\r\n * connectSharedStore(() => requestContext.getStore()); // boot, once\r\n * enterSharedScope(store); // stage 2, per request\r\n * await sealShared(); // settle stage: gate → parse → freeze\r\n *\r\n * shared.ts never opens an ALS scope itself.\r\n */\r\n\r\n/**\r\n * Untyped view of the per-request target. `SharedContext` declares the\r\n * framework keys (`can`, `locale`, `dir`, `nonce`) the pipeline's middleware\r\n * writes into the target before any app code reads them, so the runtime writes\r\n * through this shape and the public surface casts at the boundary — through\r\n * `unknown`, because the two types share no declared members.\r\n */\r\ntype SharedTarget = Record<string | symbol, unknown>;\r\n\r\n/**\r\n * The pipeline's per-request store, structurally. At runtime this IS core's\r\n * `RequestContextStore` (`{ request, response }`, request-context.ts:10-13);\r\n * only `response.parse` is named here because it is the one member seal needs —\r\n * core's `Response.parse` is public (core/src/http/response.ts:297) and the\r\n * store already carries the instance, which is how seal reaches it without web\r\n * importing core.\r\n */\r\nexport interface SharedStore {\r\n response?: { parse(value: unknown): Promise<unknown> };\r\n}\r\n\r\n/**\r\n * Returns the CURRENT request's store, or undefined outside a request. The\r\n * pipeline connects `() => requestContext.getStore()`.\r\n */\r\nexport type SharedStoreResolver = () => SharedStore | undefined;\r\n\r\ntype SharedScope = {\r\n target: SharedTarget;\r\n sealed: boolean;\r\n};\r\n\r\n/**\r\n * Per-request scope records, keyed by the request's own store object — the\r\n * exact idiom of request-memo.ts:10: the store object is fresh per request\r\n * (RequestContext.buildStore), so two concurrent requests cannot share or even\r\n * see each other's entries, and everything is GC-eligible when the request's\r\n * ALS frame ends.\r\n */\r\nconst sharedScopes = new WeakMap<SharedStore, SharedScope>();\r\n\r\nlet resolveSharedStore: SharedStoreResolver | undefined;\r\n\r\n/**\r\n * Boot-time wiring, called once by the pipeline before any request runs.\r\n * Returns the previously connected resolver so a caller (tests, mainly) can\r\n * restore it.\r\n */\r\nexport function connectSharedStore(\r\n resolve: SharedStoreResolver | undefined,\r\n): SharedStoreResolver | undefined {\r\n const previous = resolveSharedStore;\r\n resolveSharedStore = resolve;\r\n return previous;\r\n}\r\n\r\nfunction currentStore(access: string): SharedStore {\r\n if (!resolveSharedStore) {\r\n throw new Error(\r\n `Cannot ${access}: \\`shared\\` is not connected to a request store ` +\r\n \"(web/src/shared.ts). `shared` is request-scoped — its data lives in \" +\r\n \"core's per-request AsyncLocalStorage store, and this module holds only \" +\r\n \"a resolver, never data. Fix: the server bootstrap must call \" +\r\n \"connectSharedStore(() => requestContext.getStore()) before any request \" +\r\n \"runs; in a unit test, connect a resolver for the context the test runs in.\",\r\n );\r\n }\r\n\r\n const store = resolveSharedStore();\r\n\r\n if (!store) {\r\n throw new Error(\r\n `Cannot ${access}: \\`shared\\` was accessed outside a request context ` +\r\n \"(web/src/shared.ts). `shared` is per-request — this happens at module \" +\r\n \"load, in a background job, or from a late timer/dangling promise that \" +\r\n \"outlived its request. There is deliberately NO fallback to a \" +\r\n \"process-wide object: that would leak one user's data into another's \" +\r\n \"response. Fix: move this access into code the request pipeline runs \" +\r\n \"(middleware, a loader, a component render), or pass the value you need \" +\r\n \"explicitly.\",\r\n );\r\n }\r\n\r\n return store;\r\n}\r\n\r\nfunction scopeOf(store: SharedStore, access: string): SharedScope {\r\n const scope = sharedScopes.get(store);\r\n\r\n if (!scope) {\r\n throw new Error(\r\n `Cannot ${access}: this request has no \\`shared\\` scope yet ` +\r\n \"(web/src/shared.ts). The per-request target is created by the pipeline \" +\r\n \"at stage 2 via enterSharedScope(store); this access ran inside the \" +\r\n \"request context but before that point. Fix: move the access after \" +\r\n \"pipeline stage 2 (any middleware/loader/render code qualifies), or — if \" +\r\n \"you are the pipeline — call enterSharedScope(store) first.\",\r\n );\r\n }\r\n\r\n return scope;\r\n}\r\n\r\nfunction requireScope(access: string): SharedScope {\r\n return scopeOf(currentStore(access), access);\r\n}\r\n\r\nfunction sealedWriteError(action: string, key: string | symbol): Error {\r\n return new Error(\r\n `Cannot ${action} \\`shared.${String(key)}\\`: shared is SEALED for this ` +\r\n \"request (web/src/shared.ts). Writes are middleware work and happen \" +\r\n \"before the seal; after the seal the payload is committed to the page and \" +\r\n \"any further write would silently diverge server from client. Reads keep \" +\r\n \"working. Fix: move this write into middleware (before the pipeline's \" +\r\n \"seal stage), or if the value is render-time state, it does not belong in \" +\r\n \"`shared`.\",\r\n );\r\n}\r\n\r\n/**\r\n * Pipeline stage 2: create THE per-request target. Once per request — the\r\n * store object is the request's identity (fresh per request), so a second call\r\n * for the same store is a pipeline bug, not a merge.\r\n *\r\n * Returns the raw target; the pipeline may hold it, but app code goes through\r\n * `shared`.\r\n */\r\nexport function enterSharedScope(store: SharedStore): SharedContext {\r\n if (sharedScopes.has(store)) {\r\n throw new Error(\r\n \"enterSharedScope() was called twice for the same request store \" +\r\n \"(web/src/shared.ts). Each request gets exactly one `shared` target, \" +\r\n \"created once at pipeline stage 2. Fix: enter the scope once per \" +\r\n \"request; to reach the existing target, use `shared` inside the \" +\r\n \"request context instead.\",\r\n );\r\n }\r\n\r\n const target: SharedTarget = {};\r\n\r\n sharedScopes.set(store, { target, sealed: false });\r\n\r\n return target as unknown as SharedContext;\r\n}\r\n\r\nfunction rejectAtGate(path: string, offender: string): never {\r\n throw new Error(\r\n `sealShared(): \\`${path}\\` is ${offender} (web/src/shared.ts prototype ` +\r\n \"gate — runs in production AND dev). `shared` is the audit surface \" +\r\n \"serialized into the page: every value must be plain data (scalars, \" +\r\n \"arrays, plain objects) or carry a `toJSON()` serialization contract \" +\r\n \"(a Resource). A Date/Map/Set/class instance smuggles prototype state \" +\r\n \"past that audit and does not survive serialization intact. Fix: store \" +\r\n \"plain data — an ISO string instead of a Date, an object or array \" +\r\n \"instead of a Map/Set, a Resource (or its `.toJSON()` output) instead \" +\r\n \"of a model or class instance.\",\r\n );\r\n}\r\n\r\n/**\r\n * The prototype gate. Precedence mirrors `Response.parse` exactly so the gate\r\n * and the normalization that follows it agree on every value: toJSON wins over\r\n * the plain-object branch (response.ts:301-305 vs :319) and is NOT descended —\r\n * a Resource's field list is its own contract. Date is\r\n * checked BEFORE toJSON because `Date.prototype.toJSON` exists and Dates are\r\n * rejected regardless.\r\n */\r\nfunction assertClientSafe(value: unknown, path: string): void {\r\n if (value === null || value === undefined) return;\r\n\r\n const valueType = typeof value;\r\n\r\n if (valueType === \"function\") rejectAtGate(path, \"a function\");\r\n if (valueType !== \"object\") return;\r\n\r\n if (value instanceof Date) rejectAtGate(path, \"a Date\");\r\n if (value instanceof Map) rejectAtGate(path, \"a Map\");\r\n if (value instanceof Set) rejectAtGate(path, \"a Set\");\r\n\r\n if (typeof (value as { toJSON?: unknown }).toJSON === \"function\") return;\r\n\r\n if (Array.isArray(value)) {\r\n value.forEach((item, index) => assertClientSafe(item, `${path}[${index}]`));\r\n return;\r\n }\r\n\r\n const proto = Object.getPrototypeOf(value);\r\n\r\n if (proto === Object.prototype || proto === null) {\r\n for (const key of Object.keys(value)) {\r\n assertClientSafe((value as SharedTarget)[key], `${path}.${key}`);\r\n }\r\n return;\r\n }\r\n\r\n const name = (proto?.constructor?.name as string | undefined) ?? \"unknown class\";\r\n rejectAtGate(path, `a class instance (${name})`);\r\n}\r\n\r\nfunction deepFreeze(value: unknown): void {\r\n if (!value || typeof value !== \"object\" || Object.isFrozen(value)) return;\r\n\r\n Object.freeze(value);\r\n\r\n for (const key of Object.keys(value)) {\r\n deepFreeze((value as SharedTarget)[key]);\r\n }\r\n}\r\n\r\nlet browserSharedSnapshot: Readonly<SharedContext> | undefined;\r\nlet browserSharedInstalled = false;\r\n\r\nfunction freezeBrowserSnapshot(value: object, seen: Set<object>): void {\r\n if (seen.has(value)) return;\r\n\r\n seen.add(value);\r\n\r\n for (const key of Object.keys(value)) {\r\n const child = (value as SharedTarget)[key];\r\n\r\n if (child !== null && typeof child === \"object\") {\r\n freezeBrowserSnapshot(child, seen);\r\n }\r\n }\r\n\r\n Object.freeze(value);\r\n}\r\n\r\n/**\r\n * Install the browser's one readonly `shared` snapshot. An object value is\r\n * recursively frozen in place and retained wholesale so every consumer\r\n * observes the same identity; this never reads or writes the server ALS scope.\r\n */\r\nexport function installBrowserSharedSnapshot(value: unknown): void {\r\n if (value !== null && typeof value === \"object\") {\r\n freezeBrowserSnapshot(value, new Set<object>());\r\n }\r\n\r\n browserSharedSnapshot = value as Readonly<SharedContext>;\r\n browserSharedInstalled = true;\r\n}\r\n\r\n/** Compatibility alias; browser snapshot ownership lives in the installer above. */\r\nexport function hydrateShared(value: unknown): void {\r\n installBrowserSharedSnapshot(value);\r\n}\r\n\r\nfunction readBrowserSharedSnapshot(): Readonly<SharedContext> {\r\n if (!browserSharedInstalled) {\r\n throw new Error(\r\n \"Cannot read `shared` via useShared(): the browser snapshot has not been \" +\r\n \"installed (web/src/shared.ts). Hydration must validate the complete \" +\r\n \"#__WARLOCK_DATA__ payload and call installBrowserSharedSnapshot() \" +\r\n \"before constructing the React tree.\",\r\n );\r\n }\r\n\r\n return browserSharedSnapshot as Readonly<SharedContext>;\r\n}\r\n\r\n/**\r\n * The settle-stage seal, exported for the pipeline. Order is load-bearing:\r\n *\r\n * 1. PROTOTYPE GATE (pre-parse) — production and dev, offending key named\r\n * with its full path. Fail-fast courtesy: catches most violations before\r\n * spending a parse pass on them.\r\n * 2. `Response.parse` NORMALIZATION — via the store's own response instance\r\n * (core/src/http/response.ts:297, the public surface). parse mutates the\r\n * target IN PLACE (response.ts:327), which is exactly why it MUST precede\r\n * the freeze: freeze-then-parse throws on the first key parse writes\r\n * in place.\r\n * 3. PROTOTYPE GATE AGAIN (post-parse) — closes the re-entry window the first\r\n * gate cannot see: `Response.parse` never re-parses a `toJSON()` result\r\n * (it only normalizes the value handed to it), so a Resource whose `toJSON()` returns a\r\n * Date/Map/Set/class instance re-enters the payload AFTER the first gate\r\n * already ran — the first gate inspects the pre-parse value (a plain\r\n * object with a callable `toJSON`, correctly not descended) and parse then\r\n * puts back exactly what that gate would have rejected. Only a gate that\r\n * runs on the POST-parse target — the object that actually ships — can\r\n * catch it. This is the only gate that matters for correctness; the first\r\n * is a fail-fast courtesy that never gets to be wrong on its own.\r\n * 4. DEEP FREEZE — dev only (env read at seal time). Prod skips the freeze but\r\n * NOT the sealed-write throw below; freezing is defense-in-depth for direct\r\n * target references, the throw is the contract.\r\n * 5. Sealed flag — writes/deletes/defines through `shared` now throw naming\r\n * the key; reads keep working.\r\n *\r\n * `store` defaults to the current request's store; the pipeline holds the\r\n * store either way, since it entered the scope with it. Returns the sealed\r\n * target — serialize THAT, not a re-read of the proxy.\r\n */\r\nexport async function sealShared(store?: SharedStore): Promise<Readonly<SharedContext>> {\r\n const scopeStore = store ?? currentStore(\"seal `shared`\");\r\n const scope = scopeOf(scopeStore, \"seal `shared`\");\r\n\r\n if (scope.sealed) {\r\n throw new Error(\r\n \"sealShared() was called twice for the same request (web/src/shared.ts). \" +\r\n \"The seal is the pipeline's settle stage and runs once, after \" +\r\n \"middleware writes and before serialization. Fix: seal once per \" +\r\n \"request.\",\r\n );\r\n }\r\n\r\n assertClientSafe(scope.target, \"shared\");\r\n\r\n const response = scopeStore.response;\r\n\r\n if (!response || typeof response.parse !== \"function\") {\r\n throw new Error(\r\n \"sealShared() found no `response.parse` on the request store \" +\r\n \"(web/src/shared.ts). Sealing normalizes `shared` through core's \" +\r\n \"public `Response.parse` (core/src/http/response.ts:297), reached via \" +\r\n \"the store's own `response` — the store the pipeline entered the scope \" +\r\n \"with must be core's request store (`{ request, response }`, \" +\r\n \"request-context.ts:10-13). Fix: pass that store (or run sealShared() \" +\r\n \"inside the request context that carries it).\",\r\n );\r\n }\r\n\r\n await response.parse(scope.target);\r\n\r\n assertClientSafe(scope.target, \"shared\");\r\n\r\n /*\r\n OPTIONAL CHAINING IS LOAD-BEARING, and the fallback that used to sit here\r\n is deliberately gone.\r\n\r\n `import.meta.env` is injected by a BUNDLER. When this package is compiled\r\n into an app's server bundle — what happens in this checkout — esbuild's\r\n `define` (build/generate-pages-barrel.ts:59-62) replaces the whole\r\n expression and the question never arises. When `@warlock.js/web` is\r\n INSTALLED, it is external to that bundle, nothing replaces anything, and\r\n plain `import.meta.env.DEV` threw `Cannot read properties of undefined`\r\n on EVERY page render. `?.` is what makes the absent case falsy instead\r\n of fatal.\r\n\r\n A `globalThis.process?.env?.NODE_ENV` fallback was tried and REJECTED:\r\n Gate B matches `process.env` only when the object is the bare identifier\r\n `process` (vite/gate-b-secrets.ts:100), so routing through `globalThis`\r\n walks straight past the secret-leak gate. A dev-only freeze is not worth\r\n teaching the codebase the shape that evades that check.\r\n\r\n The cost is honest and small: an installed package running under\r\n `NODE_ENV=development` outside a bundler does not freeze. `DEV` is in Gate\r\n B's own allowlist (gate-b-secrets.ts:44), so this form stays permitted in\r\n client-bound code.\r\n */\r\n if (import.meta.env?.DEV) {\r\n deepFreeze(scope.target);\r\n }\r\n\r\n scope.sealed = true;\r\n\r\n return scope.target as Readonly<SharedContext>;\r\n}\r\n\r\n/**\r\n * The WRITABLE per-request payload — middleware's half of the contract.\r\n *\r\n * Every `shared.x = …` at a call site reads like a global write; it is not —\r\n * each trap below resolves the CURRENT request's target through the connected\r\n * store resolver, on every single access — two concurrent requests writing\r\n * `shared.locale` write to two different objects.\r\n */\r\nexport const shared: SharedContext = new Proxy({} as SharedTarget, {\r\n get(_stub, key) {\r\n return requireScope(`read \\`shared.${String(key)}\\``).target[key];\r\n },\r\n\r\n set(_stub, key, value) {\r\n const scope = requireScope(`write \\`shared.${String(key)}\\``);\r\n\r\n if (scope.sealed) throw sealedWriteError(\"write\", key);\r\n\r\n scope.target[key] = value;\r\n\r\n return true;\r\n },\r\n\r\n has(_stub, key) {\r\n return key in requireScope(`check \\`shared.${String(key)}\\``).target;\r\n },\r\n\r\n deleteProperty(_stub, key) {\r\n const scope = requireScope(`delete \\`shared.${String(key)}\\``);\r\n\r\n if (scope.sealed) throw sealedWriteError(\"delete\", key);\r\n\r\n delete scope.target[key];\r\n\r\n return true;\r\n },\r\n\r\n ownKeys() {\r\n return Reflect.ownKeys(requireScope(\"enumerate `shared`\").target);\r\n },\r\n\r\n getOwnPropertyDescriptor(_stub, key) {\r\n const descriptor = Object.getOwnPropertyDescriptor(\r\n requireScope(`describe \\`shared.${String(key)}\\``).target,\r\n key,\r\n );\r\n\r\n if (!descriptor) return undefined;\r\n\r\n // The proxy's book-keeping target is the empty stub, so a frozen real\r\n // target's non-configurable descriptors would violate proxy invariants if\r\n // reported as-is; configurable:true is the honest report for the proxy\r\n // surface (the sealed-write throw is what enforces immutability).\r\n return { ...descriptor, configurable: true };\r\n },\r\n\r\n defineProperty(_stub, key, descriptor) {\r\n const scope = requireScope(`define \\`shared.${String(key)}\\``);\r\n\r\n if (scope.sealed) throw sealedWriteError(\"define\", key);\r\n\r\n Reflect.defineProperty(scope.target, key, descriptor);\r\n\r\n return true;\r\n },\r\n}) as unknown as SharedContext;\r\n\r\n/**\r\n * The READ half for components at depth. On the server it preserves the live\r\n * ALS proxy behavior. In the browser it returns the exact recursively frozen\r\n * object installed from the validated hydration payload, never an empty or\r\n * process-wide fallback.\r\n */\r\nexport function useShared(): Readonly<SharedContext> {\r\n if (typeof window !== \"undefined\") return readBrowserSharedSnapshot();\r\n\r\n requireScope(\"read `shared` via useShared()\");\r\n\r\n return shared as Readonly<SharedContext>;\r\n}\r\n"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { Infer } from "@warlock.js/seal";
2
+
3
+ //#region ../web/src/validation.d.ts
4
+ /**
5
+ * The page's `validation` export: a Seal schema plus what to validate it
6
+ * against. `schema` is kept structural (`unknown`) at the constraint so the
7
+ * generic carries the CONCRETE validator type through — the inference happens
8
+ * in `ValidatedOutput`, not here.
9
+ */
10
+ type PageValidation = {
11
+ schema?: unknown;
12
+ validating?: readonly string[];
13
+ };
14
+ /**
15
+ * What `request.validated()` hands back: `Infer.Output`, not bare `Infer`.
16
+ * Bare `Infer<T>` is `Infer.Input<T>` — the shape a CALLER sends, where
17
+ * `.default()` makes a key optional. `validated()` describes the shape AFTER
18
+ * defaults are injected, so `.default()` fields stay required
19
+ * (v5/app/src/app/auth/schema/login.schema.ts:29-38,
20
+ * seal/src/types/inference-types.ts:98-102).
21
+ *
22
+ * A page with no `validation` export (`PageLoader<undefined, …>`,
23
+ * product-details.page.tsx:69) validates nothing: `validated()` answers the
24
+ * empty object, which mirrors core's runtime (`request.ts:611-617`).
25
+ */
26
+ type ValidatedOutput<TValidation> = TValidation extends {
27
+ schema: infer TSchema;
28
+ } ? Infer.Output<TSchema> : Record<string, never>;
29
+ //#endregion
30
+ export { PageValidation, ValidatedOutput };
31
+ //# sourceMappingURL=validation.d.mts.map
@@ -0,0 +1,43 @@
1
+ import path from "node:path";
2
+
3
+ //#region ../web/src/vite/app-convention-aliases.ts
4
+ /**
5
+ * The app-tree import convention — `web/*` and `app/*` — expressed as Vite
6
+ * aliases.
7
+ *
8
+ * These mirror the `paths` an application's own `tsconfig.json` declares. Vite
9
+ * does not read tsconfig `paths` on its own, and no `vite-tsconfig-paths`
10
+ * plugin is installed in this workspace, so every pipeline that resolves app
11
+ * source has to be told about them explicitly.
12
+ *
13
+ * WHY THIS IS A SHARED FUNCTION AND NOT TWO LITERALS
14
+ *
15
+ * It used to be two literals. The dev server had them; the production build did
16
+ * not — it passed `options.aliases ?? {}` — so `warlock dev` resolved
17
+ * `web/components/...` and the production client build died on the first page
18
+ * it met:
19
+ *
20
+ * [vite]: Rollup failed to resolve import "web/components/checkbox-input"
21
+ *
22
+ * Dev worked and production could not build at all, for as long as nobody ran
23
+ * the production client build to completion. Copying the pair to a second call
24
+ * site would have fixed that instance and guaranteed the next one, so there is
25
+ * exactly one definition and both pipelines read it.
26
+ *
27
+ * @param appSrcRoot Absolute path to the application's source root — the
28
+ * directory holding `web/` and `app/`. Both callers default it to
29
+ * `<appRoot>/src`.
30
+ */
31
+ function appConventionAliases(appSrcRoot) {
32
+ return [{
33
+ find: /^web\//,
34
+ replacement: `${path.join(appSrcRoot, "web")}/`
35
+ }, {
36
+ find: /^app\//,
37
+ replacement: `${path.join(appSrcRoot, "app")}/`
38
+ }];
39
+ }
40
+
41
+ //#endregion
42
+ export { appConventionAliases };
43
+ //# sourceMappingURL=app-convention-aliases.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-convention-aliases.mjs","names":[],"sources":["../../../../../../../web/src/vite/app-convention-aliases.ts"],"sourcesContent":["import path from \"node:path\";\n\n/**\n * One entry of Vite's array-form `resolve.alias`.\n *\n * Declared structurally rather than imported from `vite` so this module has no\n * dependency on the optional peer: it is read by the dev server's bootstrap and\n * by the production build contribution, and neither should pull Vite in merely\n * to learn what the app-convention aliases are.\n */\nexport type AppConventionAlias = {\n find: RegExp;\n replacement: string;\n};\n\n/**\n * The app-tree import convention — `web/*` and `app/*` — expressed as Vite\n * aliases.\n *\n * These mirror the `paths` an application's own `tsconfig.json` declares. Vite\n * does not read tsconfig `paths` on its own, and no `vite-tsconfig-paths`\n * plugin is installed in this workspace, so every pipeline that resolves app\n * source has to be told about them explicitly.\n *\n * WHY THIS IS A SHARED FUNCTION AND NOT TWO LITERALS\n *\n * It used to be two literals. The dev server had them; the production build did\n * not — it passed `options.aliases ?? {}` — so `warlock dev` resolved\n * `web/components/...` and the production client build died on the first page\n * it met:\n *\n * [vite]: Rollup failed to resolve import \"web/components/checkbox-input\"\n *\n * Dev worked and production could not build at all, for as long as nobody ran\n * the production client build to completion. Copying the pair to a second call\n * site would have fixed that instance and guaranteed the next one, so there is\n * exactly one definition and both pipelines read it.\n *\n * @param appSrcRoot Absolute path to the application's source root — the\n * directory holding `web/` and `app/`. Both callers default it to\n * `<appRoot>/src`.\n */\nexport function appConventionAliases(appSrcRoot: string): AppConventionAlias[] {\n return [\n { find: /^web\\//, replacement: `${path.join(appSrcRoot, \"web\")}/` },\n { find: /^app\\//, replacement: `${path.join(appSrcRoot, \"app\")}/` },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAgB,qBAAqB,YAA0C;CAC7E,OAAO,CACL;EAAE,MAAM;EAAU,aAAa,GAAG,KAAK,KAAK,YAAY,KAAK,EAAE;CAAG,GAClE;EAAE,MAAM;EAAU,aAAa,GAAG,KAAK,KAAK,YAAY,KAAK,EAAE;CAAG,CACpE;AACF"}
@@ -0,0 +1,38 @@
1
+ import { HydrationClientEntry } from "./hydration-entries.mjs";
2
+ import { AliasOptions, PluginOption, Rollup } from "vite";
3
+
4
+ //#region ../web/src/vite/build-client.d.ts
5
+ interface BuildHydrationClientOptions {
6
+ /** `@warlock.js/web` root containing the packaged or checkout hydration entry. */
7
+ webRoot: string;
8
+ /**
9
+ * Absolute output directory for the client bundle — REQUIRED.
10
+ *
11
+ * Was hardcoded to `<webRoot>/dist/client`, which wrote the artifacts into
12
+ * the framework package rather than the app's build output. The caller owns
13
+ * the build layout (`<outdir>/client`, contract §1), so it passes the
14
+ * directory; `manifestPath` is derived from it.
15
+ */
16
+ outDir: string;
17
+ /** The caller-composed projection and boundary-gate plugin pipeline. */
18
+ plugins: readonly PluginOption[];
19
+ /** The caller-owned application and workspace source aliases. */
20
+ resolveAliases: AliasOptions;
21
+ /** Optional peers that must remain external to this bundler pipeline. */
22
+ external?: Rollup.ExternalOption;
23
+ }
24
+ type HydrationClientBuildOutput = Rollup.RollupOutput | Rollup.RollupOutput[];
25
+ interface BuildHydrationClientResult {
26
+ entry: HydrationClientEntry;
27
+ outDir: string;
28
+ manifestPath: string;
29
+ output: HydrationClientBuildOutput;
30
+ }
31
+ /**
32
+ * Builds the one browser hydration entry. Vite is an optional peer and is
33
+ * imported only after the caller explicitly invokes this build operation.
34
+ */
35
+ declare function buildHydrationClient(options: BuildHydrationClientOptions): Promise<BuildHydrationClientResult>;
36
+ //#endregion
37
+ export { BuildHydrationClientOptions, BuildHydrationClientResult, HydrationClientBuildOutput, buildHydrationClient };
38
+ //# sourceMappingURL=build-client.d.mts.map
@@ -0,0 +1,72 @@
1
+ import { createHydrationClientEntry } from "./hydration-entries.mjs";
2
+ import { statSync } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ //#region ../web/src/vite/build-client.ts
6
+ function assertEntryFile(entry) {
7
+ let entryStat;
8
+ try {
9
+ entryStat = statSync(entry.sourcePath);
10
+ } catch (error) {
11
+ throw new Error(`Cannot build the hydration client: entry "${entry.sourcePath}" is missing or unreadable.`, { cause: error });
12
+ }
13
+ if (!entryStat.isFile()) throw new Error(`Cannot build the hydration client: entry "${entry.sourcePath}" is not a file.`);
14
+ }
15
+ function assertBuildOptions(options) {
16
+ if (!options || typeof options !== "object") throw new TypeError("Cannot build the hydration client: options are required.");
17
+ if (!Array.isArray(options.plugins) || options.plugins.length === 0) throw new TypeError("Cannot build the hydration client: the caller must provide its composed boundary plugins.");
18
+ if (options.resolveAliases === void 0 || options.resolveAliases === null) throw new TypeError("Cannot build the hydration client: the caller must provide its resolve aliases.");
19
+ if (Array.isArray(options.resolveAliases) && options.resolveAliases.length === 0 || !Array.isArray(options.resolveAliases) && Object.keys(options.resolveAliases).length === 0) throw new TypeError("Cannot build the hydration client: the resolve alias table is empty. App source that imports via `web/*` or `app/*` cannot resolve without it.");
20
+ if (typeof options.outDir !== "string" || options.outDir.trim().length === 0) throw new TypeError("Cannot build the hydration client: the caller must provide an absolute outDir.");
21
+ }
22
+ /**
23
+ * Builds the one browser hydration entry. Vite is an optional peer and is
24
+ * imported only after the caller explicitly invokes this build operation.
25
+ */
26
+ async function buildHydrationClient(options) {
27
+ assertBuildOptions(options);
28
+ const entry = createHydrationClientEntry(options.webRoot);
29
+ assertEntryFile(entry);
30
+ const outDir = path.resolve(options.outDir);
31
+ const manifestPath = path.join(outDir, ".vite/manifest.json");
32
+ const { build } = await import("vite");
33
+ const viteResult = await build({
34
+ root: options.webRoot,
35
+ appType: "custom",
36
+ configFile: false,
37
+ plugins: [...options.plugins],
38
+ resolve: { alias: options.resolveAliases },
39
+ build: {
40
+ copyPublicDir: false,
41
+ emptyOutDir: true,
42
+ manifest: true,
43
+ outDir,
44
+ target: "es2022",
45
+ rollupOptions: {
46
+ external: options.external,
47
+ input: { [entry.name]: entry.sourcePath },
48
+ output: {
49
+ assetFileNames: "assets/[name]-[hash][extname]",
50
+ chunkFileNames: "assets/[name]-[hash].js",
51
+ entryFileNames: "assets/[name]-[hash].js",
52
+ format: "es"
53
+ }
54
+ },
55
+ watch: null
56
+ }
57
+ });
58
+ if (!Array.isArray(viteResult) && !("output" in viteResult)) {
59
+ await viteResult.close();
60
+ throw new Error("Cannot build the hydration client: Vite unexpectedly returned a watcher.");
61
+ }
62
+ return {
63
+ entry,
64
+ outDir,
65
+ manifestPath,
66
+ output: viteResult
67
+ };
68
+ }
69
+
70
+ //#endregion
71
+ export { buildHydrationClient };
72
+ //# sourceMappingURL=build-client.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-client.mjs","names":[],"sources":["../../../../../../../web/src/vite/build-client.ts"],"sourcesContent":["import { statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport type { AliasOptions, PluginOption, Rollup } from \"vite\";\nimport { createHydrationClientEntry, type HydrationClientEntry } from \"./hydration-entries\";\n\nexport interface BuildHydrationClientOptions {\n /** `@warlock.js/web` root containing the packaged or checkout hydration entry. */\n webRoot: string;\n /**\n * Absolute output directory for the client bundle — REQUIRED.\n *\n * Was hardcoded to `<webRoot>/dist/client`, which wrote the artifacts into\n * the framework package rather than the app's build output. The caller owns\n * the build layout (`<outdir>/client`, contract §1), so it passes the\n * directory; `manifestPath` is derived from it.\n */\n outDir: string;\n /** The caller-composed projection and boundary-gate plugin pipeline. */\n plugins: readonly PluginOption[];\n /** The caller-owned application and workspace source aliases. */\n resolveAliases: AliasOptions;\n /** Optional peers that must remain external to this bundler pipeline. */\n external?: Rollup.ExternalOption;\n}\n\nexport type HydrationClientBuildOutput = Rollup.RollupOutput | Rollup.RollupOutput[];\n\nexport interface BuildHydrationClientResult {\n entry: HydrationClientEntry;\n outDir: string;\n manifestPath: string;\n output: HydrationClientBuildOutput;\n}\n\nfunction assertEntryFile(entry: HydrationClientEntry): void {\n let entryStat: ReturnType<typeof statSync>;\n\n try {\n entryStat = statSync(entry.sourcePath);\n } catch (error) {\n throw new Error(\n `Cannot build the hydration client: entry \"${entry.sourcePath}\" is missing or unreadable.`,\n { cause: error },\n );\n }\n\n if (!entryStat.isFile()) {\n throw new Error(`Cannot build the hydration client: entry \"${entry.sourcePath}\" is not a file.`);\n }\n}\n\nfunction assertBuildOptions(options: BuildHydrationClientOptions): void {\n if (!options || typeof options !== \"object\") {\n throw new TypeError(\"Cannot build the hydration client: options are required.\");\n }\n\n if (!Array.isArray(options.plugins) || options.plugins.length === 0) {\n throw new TypeError(\n \"Cannot build the hydration client: the caller must provide its composed boundary plugins.\",\n );\n }\n\n if (options.resolveAliases === undefined || options.resolveAliases === null) {\n throw new TypeError(\n \"Cannot build the hydration client: the caller must provide its resolve aliases.\",\n );\n }\n\n // An EMPTY table is rejected too, and that is the whole point of this branch.\n //\n // The not-null check above looks like it already covers a missing alias table.\n // It did not: the call site read `options.aliases ?? {}`, and `??` manufactures\n // an empty object that is neither undefined nor null — so the guard passed and\n // the build proceeded misconfigured, failing much later with a bare Rollup\n // \"failed to resolve import\" that named none of this.\n //\n // An app whose source uses alias imports cannot be built with zero aliases, so\n // absent and present-but-empty are the same error and must be reported the same\n // way, here, by name.\n if (\n (Array.isArray(options.resolveAliases) && options.resolveAliases.length === 0) ||\n (!Array.isArray(options.resolveAliases) && Object.keys(options.resolveAliases).length === 0)\n ) {\n throw new TypeError(\n \"Cannot build the hydration client: the resolve alias table is empty. \" +\n \"App source that imports via `web/*` or `app/*` cannot resolve without it.\",\n );\n }\n\n if (typeof options.outDir !== \"string\" || options.outDir.trim().length === 0) {\n throw new TypeError(\n \"Cannot build the hydration client: the caller must provide an absolute outDir.\",\n );\n }\n}\n\n/**\n * Builds the one browser hydration entry. Vite is an optional peer and is\n * imported only after the caller explicitly invokes this build operation.\n */\nexport async function buildHydrationClient(\n options: BuildHydrationClientOptions,\n): Promise<BuildHydrationClientResult> {\n assertBuildOptions(options);\n\n const entry = createHydrationClientEntry(options.webRoot);\n assertEntryFile(entry);\n\n const outDir = path.resolve(options.outDir);\n const manifestPath = path.join(outDir, \".vite/manifest.json\");\n const { build } = await import(\"vite\");\n const viteResult = await build({\n root: options.webRoot,\n appType: \"custom\",\n configFile: false,\n plugins: [...options.plugins],\n resolve: { alias: options.resolveAliases },\n build: {\n copyPublicDir: false,\n emptyOutDir: true,\n manifest: true,\n outDir,\n target: \"es2022\",\n rollupOptions: {\n external: options.external,\n input: { [entry.name]: entry.sourcePath },\n output: {\n assetFileNames: \"assets/[name]-[hash][extname]\",\n chunkFileNames: \"assets/[name]-[hash].js\",\n entryFileNames: \"assets/[name]-[hash].js\",\n format: \"es\",\n },\n },\n watch: null,\n },\n });\n\n if (!Array.isArray(viteResult) && !(\"output\" in viteResult)) {\n await viteResult.close();\n throw new Error(\"Cannot build the hydration client: Vite unexpectedly returned a watcher.\");\n }\n\n return { entry, outDir, manifestPath, output: viteResult };\n}\n"],"mappings":";;;;;AAkCA,SAAS,gBAAgB,OAAmC;CAC1D,IAAI;CAEJ,IAAI;EACF,YAAY,SAAS,MAAM,UAAU;CACvC,SAAS,OAAO;EACd,MAAM,IAAI,MACR,6CAA6C,MAAM,WAAW,8BAC9D,EAAE,OAAO,MAAM,CACjB;CACF;CAEA,IAAI,CAAC,UAAU,OAAO,GACpB,MAAM,IAAI,MAAM,6CAA6C,MAAM,WAAW,iBAAiB;AAEnG;AAEA,SAAS,mBAAmB,SAA4C;CACtE,IAAI,CAAC,WAAW,OAAO,YAAY,UACjC,MAAM,IAAI,UAAU,0DAA0D;CAGhF,IAAI,CAAC,MAAM,QAAQ,QAAQ,OAAO,KAAK,QAAQ,QAAQ,WAAW,GAChE,MAAM,IAAI,UACR,2FACF;CAGF,IAAI,QAAQ,mBAAmB,UAAa,QAAQ,mBAAmB,MACrE,MAAM,IAAI,UACR,iFACF;CAcF,IACG,MAAM,QAAQ,QAAQ,cAAc,KAAK,QAAQ,eAAe,WAAW,KAC3E,CAAC,MAAM,QAAQ,QAAQ,cAAc,KAAK,OAAO,KAAK,QAAQ,cAAc,CAAC,CAAC,WAAW,GAE1F,MAAM,IAAI,UACR,gJAEF;CAGF,IAAI,OAAO,QAAQ,WAAW,YAAY,QAAQ,OAAO,KAAK,CAAC,CAAC,WAAW,GACzE,MAAM,IAAI,UACR,gFACF;AAEJ;;;;;AAMA,eAAsB,qBACpB,SACqC;CACrC,mBAAmB,OAAO;CAE1B,MAAM,QAAQ,2BAA2B,QAAQ,OAAO;CACxD,gBAAgB,KAAK;CAErB,MAAM,SAAS,KAAK,QAAQ,QAAQ,MAAM;CAC1C,MAAM,eAAe,KAAK,KAAK,QAAQ,qBAAqB;CAC5D,MAAM,EAAE,UAAU,MAAM,OAAO;CAC/B,MAAM,aAAa,MAAM,MAAM;EAC7B,MAAM,QAAQ;EACd,SAAS;EACT,YAAY;EACZ,SAAS,CAAC,GAAG,QAAQ,OAAO;EAC5B,SAAS,EAAE,OAAO,QAAQ,eAAe;EACzC,OAAO;GACL,eAAe;GACf,aAAa;GACb,UAAU;GACV;GACA,QAAQ;GACR,eAAe;IACb,UAAU,QAAQ;IAClB,OAAO,GAAG,MAAM,OAAO,MAAM,WAAW;IACxC,QAAQ;KACN,gBAAgB;KAChB,gBAAgB;KAChB,gBAAgB;KAChB,QAAQ;IACV;GACF;GACA,OAAO;EACT;CACF,CAAC;CAED,IAAI,CAAC,MAAM,QAAQ,UAAU,KAAK,EAAE,YAAY,aAAa;EAC3D,MAAM,WAAW,MAAM;EACvB,MAAM,IAAI,MAAM,0EAA0E;CAC5F;CAEA,OAAO;EAAE;EAAO;EAAQ;EAAc,QAAQ;CAAW;AAC3D"}
@@ -0,0 +1,57 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region ../web/src/vite/gate-a-resolve.d.ts
4
+ type WarlockEnvironment = "server" | "universal" | "client";
5
+ interface EnvironmentClassifierOptions {
6
+ /**
7
+ * Force these governed-scope package names (e.g. `@warlock.js/core`) to
8
+ * classify as `"server"` regardless of what `warlock.environment` their
9
+ * `package.json` declares. Defaults to the live marker resolution (workspace
10
+ * `package.json` lookup, falling back to `node_modules`). Only meant for
11
+ * tests that need a deterministic classification independent of the host
12
+ * filesystem.
13
+ */
14
+ serverPackages?: Iterable<string>;
15
+ /**
16
+ * The app's project root. Defines what counts as APP SOURCE (see
17
+ * `isAppSourcePath`), which two rules depend on: rule 4 ("outside
18
+ * `$module/web/`") and rule 3's plain-`server/`-folder half. A dependency's
19
+ * own internal file layout — reached once resolution has left the app root,
20
+ * or through a `node_modules/` segment — is exempt from both, though rules
21
+ * 1, 2 and the `.server` half of rule 3 still apply to it. Defaults to
22
+ * `process.cwd()`, matching Vite's own default `root`.
23
+ */
24
+ appRoot?: string;
25
+ }
26
+ type GateAOptions = EnvironmentClassifierOptions;
27
+ interface EnvironmentClassifier {
28
+ appRoot: string;
29
+ environmentOf(pkgName: string): WarlockEnvironment;
30
+ /**
31
+ * Maps an absolute, already-resolved file path (e.g. a Rollup
32
+ * `OutputChunk.moduleIds` entry) back to the governed-scope package name
33
+ * that owns it — a `node_modules` dependency, or a workspace member's own
34
+ * directory PROVIDED the path is outside `appRoot`. The `appRoot` carve-out
35
+ * matters: a fixture/app can physically live inside a workspace package's
36
+ * own directory tree (e.g. this repo's own `web/__tests__/` fixtures live
37
+ * under the `web` package) without thereby "importing" that package — the
38
+ * app's own source is never a dependency edge onto itself, no matter where
39
+ * on disk it happens to sit (same distinction Gate A's rule 2 already makes
40
+ * via `isInsideAppRoot` for the importer side). `undefined` when the path
41
+ * isn't a governed-scope dependency at all. Exists so Gate C
42
+ * (`gate-c-verify.ts`) can classify modules already sitting in the EMITTED
43
+ * bundle graph using this exact same marker logic, instead of hand-rolling
44
+ * a second classification scheme — Gate C re-derives, it does not
45
+ * duplicate.
46
+ */
47
+ packageNameForFilePath(absPath: string): string | undefined;
48
+ }
49
+ /**
50
+ * The client-build Vite plugin. `vite` is only imported for its types
51
+ * (`import type`), so this module carries no runtime dependency on `vite`
52
+ * being installed — it is a `peerDependenciesMeta.optional` peer.
53
+ */
54
+ declare function gateAResolve(options?: GateAOptions): Plugin;
55
+ //#endregion
56
+ export { EnvironmentClassifier, EnvironmentClassifierOptions, WarlockEnvironment, gateAResolve };
57
+ //# sourceMappingURL=gate-a-resolve.d.mts.map