@nifrajs/web 2.1.0 → 2.3.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 (194) hide show
  1. package/README.md +3 -3
  2. package/dist/build-vite.d.ts +68 -0
  3. package/dist/build-vite.d.ts.map +1 -0
  4. package/dist/build-vite.js +433 -0
  5. package/dist/build-vite.js.map +1 -0
  6. package/dist/build.d.ts +174 -65
  7. package/dist/build.d.ts.map +1 -1
  8. package/dist/build.js +376 -135
  9. package/dist/build.js.map +1 -1
  10. package/dist/bun-dev-entry.d.ts +83 -0
  11. package/dist/bun-dev-entry.d.ts.map +1 -0
  12. package/dist/bun-dev-entry.js +136 -0
  13. package/dist/bun-dev-entry.js.map +1 -0
  14. package/dist/chunk-names.js +1 -1
  15. package/dist/client.d.ts +3 -9
  16. package/dist/client.d.ts.map +1 -1
  17. package/dist/client.js +209 -27
  18. package/dist/client.js.map +1 -1
  19. package/dist/conformance.d.ts +2 -2
  20. package/dist/conformance.js +3 -3
  21. package/dist/deferred.d.ts +11 -11
  22. package/dist/deferred.d.ts.map +1 -1
  23. package/dist/deferred.js +16 -16
  24. package/dist/dev-error.d.ts +2 -2
  25. package/dist/dev-error.js +3 -3
  26. package/dist/dev-port.d.ts +36 -0
  27. package/dist/dev-port.d.ts.map +1 -0
  28. package/dist/dev-port.js +48 -0
  29. package/dist/dev-port.js.map +1 -0
  30. package/dist/dev.d.ts +62 -5
  31. package/dist/dev.d.ts.map +1 -1
  32. package/dist/dev.js +419 -60
  33. package/dist/dev.js.map +1 -1
  34. package/dist/draft.d.ts +44 -7
  35. package/dist/draft.d.ts.map +1 -1
  36. package/dist/draft.js +92 -9
  37. package/dist/draft.js.map +1 -1
  38. package/dist/fn-state.d.ts +56 -0
  39. package/dist/fn-state.d.ts.map +1 -0
  40. package/dist/fn-state.js +77 -0
  41. package/dist/fn-state.js.map +1 -0
  42. package/dist/fn.d.ts +111 -0
  43. package/dist/fn.d.ts.map +1 -0
  44. package/dist/fn.js +125 -0
  45. package/dist/fn.js.map +1 -0
  46. package/dist/fonts-google.d.ts +9 -9
  47. package/dist/fonts-google.js +10 -10
  48. package/dist/fonts.d.ts +7 -7
  49. package/dist/fonts.js +1 -1
  50. package/dist/forms.d.ts +8 -8
  51. package/dist/fs.d.ts +15 -2
  52. package/dist/fs.d.ts.map +1 -1
  53. package/dist/fs.js +8 -2
  54. package/dist/fs.js.map +1 -1
  55. package/dist/index.d.ts +266 -74
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +869 -179
  58. package/dist/index.js.map +1 -1
  59. package/dist/internal/head-attributes.d.ts +15 -0
  60. package/dist/internal/head-attributes.d.ts.map +1 -0
  61. package/dist/internal/head-attributes.js +99 -0
  62. package/dist/internal/head-attributes.js.map +1 -0
  63. package/dist/internal/script-types.d.ts +14 -0
  64. package/dist/internal/script-types.d.ts.map +1 -0
  65. package/dist/internal/script-types.js +17 -0
  66. package/dist/internal/script-types.js.map +1 -0
  67. package/dist/internal/server-boundary.d.ts +60 -0
  68. package/dist/internal/server-boundary.d.ts.map +1 -0
  69. package/dist/internal/server-boundary.js +174 -0
  70. package/dist/internal/server-boundary.js.map +1 -0
  71. package/dist/internal/timing-safe-equal.d.ts +16 -0
  72. package/dist/internal/timing-safe-equal.d.ts.map +1 -0
  73. package/dist/internal/timing-safe-equal.js +26 -0
  74. package/dist/internal/timing-safe-equal.js.map +1 -0
  75. package/dist/internal/vite-import.d.ts +63 -0
  76. package/dist/internal/vite-import.d.ts.map +1 -0
  77. package/dist/internal/vite-import.js +94 -0
  78. package/dist/internal/vite-import.js.map +1 -0
  79. package/dist/islands.d.ts +5 -5
  80. package/dist/islands.js +5 -5
  81. package/dist/isr.d.ts +15 -15
  82. package/dist/isr.d.ts.map +1 -1
  83. package/dist/isr.js +8 -21
  84. package/dist/isr.js.map +1 -1
  85. package/dist/llms-txt.d.ts.map +1 -1
  86. package/dist/llms-txt.js +12 -6
  87. package/dist/llms-txt.js.map +1 -1
  88. package/dist/manifest.d.ts +121 -40
  89. package/dist/manifest.d.ts.map +1 -1
  90. package/dist/manifest.js +136 -26
  91. package/dist/manifest.js.map +1 -1
  92. package/dist/module-graph.d.ts +91 -0
  93. package/dist/module-graph.d.ts.map +1 -0
  94. package/dist/module-graph.js +92 -0
  95. package/dist/module-graph.js.map +1 -0
  96. package/dist/navigation.d.ts +120 -5
  97. package/dist/navigation.d.ts.map +1 -1
  98. package/dist/navigation.js +37 -5
  99. package/dist/navigation.js.map +1 -1
  100. package/dist/plugins/css-modules.d.ts +3 -3
  101. package/dist/plugins/css-modules.d.ts.map +1 -1
  102. package/dist/plugins/css-modules.js +0 -0
  103. package/dist/plugins/css-modules.js.map +1 -1
  104. package/dist/plugins/kit.d.ts +8 -8
  105. package/dist/plugins/kit.js +9 -9
  106. package/dist/plugins/scss.js +3 -3
  107. package/dist/plugins/vite-leak-guard.d.ts +45 -0
  108. package/dist/plugins/vite-leak-guard.d.ts.map +1 -0
  109. package/dist/plugins/vite-leak-guard.js +67 -0
  110. package/dist/plugins/vite-leak-guard.js.map +1 -0
  111. package/dist/plugins/vite-server-fn.d.ts +23 -0
  112. package/dist/plugins/vite-server-fn.d.ts.map +1 -0
  113. package/dist/plugins/vite-server-fn.js +51 -0
  114. package/dist/plugins/vite-server-fn.js.map +1 -0
  115. package/dist/plugins/vite-server-only.d.ts +49 -0
  116. package/dist/plugins/vite-server-only.d.ts.map +1 -0
  117. package/dist/plugins/vite-server-only.js +57 -0
  118. package/dist/plugins/vite-server-only.js.map +1 -0
  119. package/dist/prerender.d.ts +5 -5
  120. package/dist/prerender.js +7 -7
  121. package/dist/public-dir.d.ts +36 -0
  122. package/dist/public-dir.d.ts.map +1 -0
  123. package/dist/public-dir.js +89 -0
  124. package/dist/public-dir.js.map +1 -0
  125. package/dist/query.d.ts +10 -10
  126. package/dist/query.js +15 -15
  127. package/dist/route-manifest.d.ts +91 -0
  128. package/dist/route-manifest.d.ts.map +1 -0
  129. package/dist/route-manifest.js +128 -0
  130. package/dist/route-manifest.js.map +1 -0
  131. package/dist/router.d.ts +94 -22
  132. package/dist/router.d.ts.map +1 -1
  133. package/dist/router.js +170 -58
  134. package/dist/router.js.map +1 -1
  135. package/dist/search.d.ts +94 -0
  136. package/dist/search.d.ts.map +1 -0
  137. package/dist/search.js +232 -0
  138. package/dist/search.js.map +1 -0
  139. package/dist/server-only.d.ts +6 -6
  140. package/dist/server-only.js +7 -7
  141. package/dist/service-worker.d.ts +56 -0
  142. package/dist/service-worker.d.ts.map +1 -0
  143. package/dist/service-worker.js +127 -0
  144. package/dist/service-worker.js.map +1 -0
  145. package/dist/vite.d.ts +25 -17
  146. package/dist/vite.d.ts.map +1 -1
  147. package/dist/vite.js +75 -33
  148. package/dist/vite.js.map +1 -1
  149. package/package.json +48 -4
  150. package/server-manifest.d.ts +31 -0
  151. package/src/build-vite.ts +553 -0
  152. package/src/build.ts +563 -172
  153. package/src/bun-dev-entry.ts +184 -0
  154. package/src/chunk-names.ts +1 -1
  155. package/src/client.ts +221 -30
  156. package/src/conformance.ts +3 -3
  157. package/src/deferred.ts +17 -17
  158. package/src/dev-error.ts +3 -3
  159. package/src/dev-port.ts +63 -0
  160. package/src/dev.ts +468 -66
  161. package/src/draft.ts +124 -12
  162. package/src/fn-state.ts +107 -0
  163. package/src/fn.ts +199 -0
  164. package/src/fonts-google.ts +13 -13
  165. package/src/fonts.ts +7 -7
  166. package/src/forms.ts +8 -8
  167. package/src/fs.ts +24 -6
  168. package/src/index.ts +1174 -225
  169. package/src/internal/head-attributes.ts +109 -0
  170. package/src/internal/script-types.ts +18 -0
  171. package/src/internal/server-boundary.ts +187 -0
  172. package/src/internal/timing-safe-equal.ts +24 -0
  173. package/src/internal/vite-import.ts +92 -0
  174. package/src/islands.ts +7 -7
  175. package/src/isr.ts +18 -30
  176. package/src/llms-txt.ts +13 -5
  177. package/src/manifest.ts +264 -48
  178. package/src/module-graph.ts +146 -0
  179. package/src/navigation.ts +151 -5
  180. package/src/plugins/css-modules.ts +0 -0
  181. package/src/plugins/kit.ts +11 -11
  182. package/src/plugins/scss.ts +3 -3
  183. package/src/plugins/vite-leak-guard.ts +110 -0
  184. package/src/plugins/vite-server-fn.ts +67 -0
  185. package/src/plugins/vite-server-only.ts +75 -0
  186. package/src/prerender.ts +10 -10
  187. package/src/public-dir.ts +108 -0
  188. package/src/query.ts +22 -22
  189. package/src/route-manifest.ts +240 -0
  190. package/src/router.ts +288 -74
  191. package/src/search.ts +297 -0
  192. package/src/server-only.ts +7 -7
  193. package/src/service-worker.ts +165 -0
  194. package/src/vite.ts +103 -49
package/dist/deferred.js CHANGED
@@ -2,13 +2,13 @@
2
2
  * Deferred loader data (`defer()`). A loader may mark slow values as deferred: the critical data
3
3
  * renders in the shell, the deferred data streams in behind a `<Suspense>` (the adapter's `<Await>`
4
4
  * consumes it) and resolves on the client **without a re-fetch**. This module owns the agnostic
5
- * protocol — the marker, the resolved/deferred split, and the tiny client registry that streamed
5
+ * protocol - the marker, the resolved/deferred split, and the tiny client registry that streamed
6
6
  * resolution scripts settle. The per-adapter `<Await>` lives in `@nifrajs/web-{solid,react}/client`.
7
7
  */
8
8
  import { plainJsonCodec } from "@nifrajs/core/transport-codec";
9
9
  /**
10
- * Mark a loader value as deferred — it streams in after the shell instead of blocking it. Works
11
- * **anywhere** in the loader's returned data — a top-level key, or nested in objects/arrays:
10
+ * Mark a loader value as deferred - it streams in after the shell instead of blocking it. Works
11
+ * **anywhere** in the loader's returned data - a top-level key, or nested in objects/arrays:
12
12
  *
13
13
  * return { user: await api.user.get(), feed: defer(api.user.feed()),
14
14
  * panels: [{ chart: defer(api.metrics()) }] }
@@ -32,8 +32,8 @@ function isDeferred(value) {
32
32
  /**
33
33
  * Split a loader (or action) result into the **component-facing** data (deferred values carry their
34
34
  * assigned `id` + promise, for `<Await>`) and the **client-serializable** data (deferred values
35
- * replaced with a `{__nifra_deferred: id}` placeholder — promises don't serialize). Walks the tree
36
- * **recursively** — `defer()` works at any depth, inside nested objects and arrays — assigning ids in
35
+ * replaced with a `{__nifra_deferred: id}` placeholder - promises don't serialize). Walks the tree
36
+ * **recursively** - `defer()` works at any depth, inside nested objects and arrays - assigning ids in
37
37
  * walk order. `idOffset` continues the id space when a page splits two results (loader data **and**
38
38
  * action data) into one shared client registry, so their ids don't collide. `deferred` lists the
39
39
  * promises for callers that await them. Recurses plain objects + arrays only; the data is expected to
@@ -42,7 +42,7 @@ function isDeferred(value) {
42
42
  export function prepareDeferred(data, idOffset = 0) {
43
43
  const deferred = [];
44
44
  // Structural sharing: a subtree with no deferred value returns the ORIGINAL node for
45
- // both sides (`fc`/`cl` identical to the input), so a deferred-free result — the common case — is
45
+ // both sides (`fc`/`cl` identical to the input), so a deferred-free result - the common case - is
46
46
  // returned by reference with no persisted clone, no downstream copy, and no throwaway clone
47
47
  // allocations. Only the path to a `defer()` marker is rebuilt; unchanged siblings are shared by
48
48
  // reference.
@@ -119,18 +119,18 @@ export function prepareDeferred(data, idOffset = 0) {
119
119
  : { forComponent: result.fc, forClient: result.cl, deferred };
120
120
  }
121
121
  /**
122
- * Stable, non-leaking payload streamed to the client when a deferred value rejects — never the raw
122
+ * Stable, non-leaking payload streamed to the client when a deferred value rejects - never the raw
123
123
  * error text. The real reason is logged server-side; `<Await>`'s `errorFallback` receives
124
124
  * this code. (A future typed `DeferredError` could opt into a public, intentional message.) Shared by
125
125
  * the NDJSON soft-nav transport and the full-document SSR path (`renderPage`'s `streamDocument`).
126
126
  */
127
127
  export const DEFERRED_ERROR_CODE = "deferred_error";
128
- // Shared, stateless — allocated once at module load, not per stream.
128
+ // Shared, stateless - allocated once at module load, not per stream.
129
129
  const NDJSON_ENCODER = new TextEncoder();
130
130
  /**
131
131
  * Stream a loader result as NDJSON for a client (soft) navigation: line 1 is the critical data with
132
132
  * `{__nifra_deferred: id}` placeholders (`forClient`), then one line per deferred as its promise
133
- * settles — `{"i": id, "v": value}` on resolve, `{"i": id, "e": "deferred_error"}` on reject (a
133
+ * settles - `{"i": id, "v": value}` on resolve, `{"i": id, "e": "deferred_error"}` on reject (a
134
134
  * rejection is data, not a stream error; the opaque code never leaks the raw reason). Closes when all
135
135
  * settle. The client (`defaultFetchData`) returns the
136
136
  * data after line 1 and settles `<Await>`'s markers as the resolution lines arrive. `JSON.stringify`
@@ -180,7 +180,7 @@ export const DEFERRED_RUNTIME = `(() => {
180
180
  return e;
181
181
  };
182
182
  window.__nifraDeferred = (id) => get(id).p;
183
- // Tag the promise (status/value/reason) — React's use() reads them and returns SYNCHRONOUSLY at
183
+ // Tag the promise (status/value/reason) - React's use() reads them and returns SYNCHRONOUSLY at
184
184
  // hydration, so it renders the resolved content directly into the <Suspense> boundary the server
185
185
  // streamed (no re-suspend, no fallback flash). r/j also wake awaiters (Solid uses its own _$HY).
186
186
  window.__nifraResolve = (id, v) => { const e = get(id); e.p.status = "fulfilled"; e.p.value = v; e.r(v); };
@@ -188,7 +188,7 @@ export const DEFERRED_RUNTIME = `(() => {
188
188
  })();`;
189
189
  /**
190
190
  * Source emitted into the generated client entry: maps serialized `{__nifra_deferred: id}` placeholders
191
- * (at any depth — nested objects/arrays) to the registry's promises, so the component receives real
191
+ * (at any depth - nested objects/arrays) to the registry's promises, so the component receives real
192
192
  * promises to `<Await>`. A no-op for data without placeholders (so non-deferred pages are unchanged).
193
193
  */
194
194
  export const MAP_DEFERRED_SOURCE = `const mapDeferred = (d) => {
@@ -199,18 +199,18 @@ export const MAP_DEFERRED_SOURCE = `const mapDeferred = (d) => {
199
199
  for (const k in d) out[k] = mapDeferred(d[k])
200
200
  return out
201
201
  }`;
202
- /** Replace `{__nifra_deferred: <number>}` placeholders (at any depth — nested objects/arrays) with
202
+ /** Replace `{__nifra_deferred: <number>}` placeholders (at any depth - nested objects/arrays) with
203
203
  * `Deferred` markers whose promises are held open in `pending` (settled later by
204
204
  * {@link parseNdjsonData}'s drain). Mirrors `prepareDeferred`'s recursive walk. */
205
205
  function markersFromPlaceholders(data, pending) {
206
- // Markers already created in THIS parse, keyed by id — so a repeated id aliases the first marker.
206
+ // Markers already created in THIS parse, keyed by id - so a repeated id aliases the first marker.
207
207
  const byId = new Map();
208
208
  const walk = (value) => {
209
209
  const id = value?.__nifra_deferred;
210
210
  if (value !== null && typeof value === "object" && typeof id === "number") {
211
211
  // The NDJSON stream is a trust boundary; the server assigns unique ids, but a corrupt/crafted
212
212
  // line-1 could repeat one. Alias a duplicate id to the first marker (same promise) instead of
213
- // overwriting its resolver in `pending` — otherwise the first marker would never settle (a
213
+ // overwriting its resolver in `pending` - otherwise the first marker would never settle (a
214
214
  // permanently-pending promise that leaks on abort). One resolve line then settles both.
215
215
  const seen = byId.get(id);
216
216
  if (seen !== undefined)
@@ -246,7 +246,7 @@ function markersFromPlaceholders(data, pending) {
246
246
  * the NDJSON body into a data object whose deferred markers settle as resolution lines arrive.
247
247
  * Returns after **line 1** so the router can apply the critical data + render immediately; the
248
248
  * markers' promises settle/reject in the background. If the stream ends (or `signal` aborts) with
249
- * markers unsettled, they reject — so `<Await>` never hangs.
249
+ * markers unsettled, they reject - so `<Await>` never hangs.
250
250
  */
251
251
  export async function parseNdjsonData(stream, signal, codec = plainJsonCodec) {
252
252
  const reader = stream.getReader();
@@ -297,7 +297,7 @@ export async function parseNdjsonData(stream, signal, codec = plainJsonCodec) {
297
297
  }
298
298
  }
299
299
  catch {
300
- // stream errored — leftovers are rejected below (unless this was an abort)
300
+ // stream errored - leftovers are rejected below (unless this was an abort)
301
301
  }
302
302
  finally {
303
303
  signal?.removeEventListener("abort", onAbort); // no-op if it already fired ({ once: true })
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dev-only error overlay. When a loader/action/render throws during `@nifrajs/web/vite` dev, this
3
- * renders a readable full-page overlay — the message, the source-mapped stack (Vite's
4
- * `ssrFixStacktrace` runs first), and the request line — instead of the bare `err.stack` text dump.
3
+ * renders a readable full-page overlay - the message, the source-mapped stack (Vite's
4
+ * `ssrFixStacktrace` runs first), and the request line - instead of the bare `err.stack` text dump.
5
5
  * Dev-only by construction: it's called solely from the Vite middleware's catch, never in
6
6
  * production (production maps errors to the `_error` route boundary).
7
7
  */
package/dist/dev-error.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dev-only error overlay. When a loader/action/render throws during `@nifrajs/web/vite` dev, this
3
- * renders a readable full-page overlay — the message, the source-mapped stack (Vite's
4
- * `ssrFixStacktrace` runs first), and the request line — instead of the bare `err.stack` text dump.
3
+ * renders a readable full-page overlay - the message, the source-mapped stack (Vite's
4
+ * `ssrFixStacktrace` runs first), and the request line - instead of the bare `err.stack` text dump.
5
5
  * Dev-only by construction: it's called solely from the Vite middleware's catch, never in
6
6
  * production (production maps errors to the `_error` route boundary).
7
7
  */
@@ -31,7 +31,7 @@ export function renderDevErrorOverlay(err, req) {
31
31
  const framesHtml = frames.length > 0
32
32
  ? `<ol class="frames">${frames.map((f) => `<li>${esc(f)}</li>`).join("")}</ol>`
33
33
  : `<p class="no-frames">No stack frames.</p>`;
34
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${name} — nifra dev</title>
34
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${name} - nifra dev</title>
35
35
  <style>
36
36
  :root { color-scheme: dark }
37
37
  body { margin: 0; background: #0b0d10; color: #e6e6e6; font: 14px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `@nifrajs/web/dev-port` - what a dev server says when it cannot bind.
3
+ *
4
+ * Both pipelines need this and neither should own it: the Vite server binds through Node's `http`
5
+ * (asynchronous `error` event), the Bun server binds through `Bun.serve` (synchronous throw). Two very
6
+ * different failure mechanics, one thing the developer has to be told.
7
+ *
8
+ * That message is worth this much care because the visible symptom is not the real one. When the port is
9
+ * taken, the EARLIER dev server is still answering on it, still serving the build it started with. Every
10
+ * subsequent edit appears to do nothing. What reaches the developer is "my changes stopped reaching SSR",
11
+ * which reads as broken HMR or a stale module graph and sends them into the module graph - not to the one
12
+ * process that never started. Naming the cause here is the entire fix.
13
+ *
14
+ * This module deliberately imports nothing: it is shared by a Node-http path and a Bun path, and the
15
+ * structural `ListenTarget` keeps it free of either runtime's types.
16
+ */
17
+ /** The `EADDRINUSE` explanation. Exported so tests pin the exact text a user will read. */
18
+ export declare function portInUseMessage(port: number): string;
19
+ /** True when an unknown thrown value carries the "address already in use" errno. */
20
+ export declare const isAddressInUse: (err: unknown) => boolean;
21
+ /** Map a bind failure to the port-collision explanation, leaving every other failure as itself. */
22
+ export declare const explainBindFailure: (err: unknown, port: number) => Error;
23
+ /** The bits of a Node server {@link listenOrExplain} touches - structural, so a test can fake it. */
24
+ export interface ListenTarget {
25
+ listen(port: number, cb: () => void): void;
26
+ once(event: "error", cb: (err: unknown) => void): void;
27
+ removeListener(event: "error", cb: (err: unknown) => void): void;
28
+ }
29
+ /**
30
+ * `listen`, but a bind failure becomes a readable nifra error instead of Node's raw internal throw.
31
+ *
32
+ * Without an `error` listener attached, Node throws from deep inside `node:events`, the new process dies
33
+ * in the background, and nothing connects that death to the stale page in front of you.
34
+ */
35
+ export declare function listenOrExplain(server: ListenTarget, port: number): Promise<void>;
36
+ //# sourceMappingURL=dev-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-port.d.ts","sourceRoot":"","sources":["../src/dev-port.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQrD;AAED,oFAAoF;AACpF,eAAO,MAAM,cAAc,GAAI,KAAK,OAAO,KAAG,OACa,CAAA;AAI3D,mGAAmG;AACnG,eAAO,MAAM,kBAAkB,GAAI,KAAK,OAAO,EAAE,MAAM,MAAM,KAAG,KACQ,CAAA;AAExE,qGAAqG;AACrG,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;IAC1C,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAA;IACtD,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAA;CACjE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWjF"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `@nifrajs/web/dev-port` - what a dev server says when it cannot bind.
3
+ *
4
+ * Both pipelines need this and neither should own it: the Vite server binds through Node's `http`
5
+ * (asynchronous `error` event), the Bun server binds through `Bun.serve` (synchronous throw). Two very
6
+ * different failure mechanics, one thing the developer has to be told.
7
+ *
8
+ * That message is worth this much care because the visible symptom is not the real one. When the port is
9
+ * taken, the EARLIER dev server is still answering on it, still serving the build it started with. Every
10
+ * subsequent edit appears to do nothing. What reaches the developer is "my changes stopped reaching SSR",
11
+ * which reads as broken HMR or a stale module graph and sends them into the module graph - not to the one
12
+ * process that never started. Naming the cause here is the entire fix.
13
+ *
14
+ * This module deliberately imports nothing: it is shared by a Node-http path and a Bun path, and the
15
+ * structural `ListenTarget` keeps it free of either runtime's types.
16
+ */
17
+ /** The `EADDRINUSE` explanation. Exported so tests pin the exact text a user will read. */
18
+ export function portInUseMessage(port) {
19
+ return (`[nifra] dev server can't start: port ${port} is already in use.\n` +
20
+ ` Most likely an earlier \`nifra dev\` is still running. It keeps serving the PREVIOUS build, so ` +
21
+ `every edit will look like it stops reaching SSR while the browser shows stale output.\n` +
22
+ ` Free the port: lsof -ti:${port} | xargs kill\n` +
23
+ ` Or use another: nifra dev --port ${port + 1}`);
24
+ }
25
+ /** True when an unknown thrown value carries the "address already in use" errno. */
26
+ export const isAddressInUse = (err) => err?.code === "EADDRINUSE";
27
+ const asError = (err) => (err instanceof Error ? err : new Error(String(err)));
28
+ /** Map a bind failure to the port-collision explanation, leaving every other failure as itself. */
29
+ export const explainBindFailure = (err, port) => isAddressInUse(err) ? new Error(portInUseMessage(port)) : asError(err);
30
+ /**
31
+ * `listen`, but a bind failure becomes a readable nifra error instead of Node's raw internal throw.
32
+ *
33
+ * Without an `error` listener attached, Node throws from deep inside `node:events`, the new process dies
34
+ * in the background, and nothing connects that death to the stale page in front of you.
35
+ */
36
+ export function listenOrExplain(server, port) {
37
+ return new Promise((resolve, reject) => {
38
+ const onError = (err) => reject(explainBindFailure(err, port));
39
+ server.once("error", onError);
40
+ server.listen(port, () => {
41
+ // Drop the guard once we're listening: leaving it attached would funnel a LATER server error into
42
+ // an already-settled promise, silently swallowing it instead of surfacing it.
43
+ server.removeListener("error", onError);
44
+ resolve();
45
+ });
46
+ });
47
+ }
48
+ //# sourceMappingURL=dev-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-port.js","sourceRoot":"","sources":["../src/dev-port.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,2FAA2F;AAC3F,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,CACL,wCAAwC,IAAI,uBAAuB;QACnE,mGAAmG;QACnG,yFAAyF;QACzF,8BAA8B,IAAI,iBAAiB;QACnD,sCAAsC,IAAI,GAAG,CAAC,EAAE,CACjD,CAAA;AACH,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,GAAY,EAAW,EAAE,CACrD,GAAiC,EAAE,IAAI,KAAK,YAAY,CAAA;AAE3D,MAAM,OAAO,GAAG,CAAC,GAAY,EAAS,EAAE,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAE9F,mGAAmG;AACnG,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAY,EAAE,IAAY,EAAS,EAAE,CACtE,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;AASxE;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,MAAoB,EAAE,IAAY;IAChE,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC3C,MAAM,OAAO,GAAG,CAAC,GAAY,EAAQ,EAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAA;QAC7E,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;YACvB,kGAAkG;YAClG,8EAA8E;YAC9E,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YACvC,OAAO,EAAE,CAAA;QACX,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC"}
package/dist/dev.d.ts CHANGED
@@ -1,25 +1,82 @@
1
1
  import { type BuildClientOptions } from "./build.js";
2
- /** Minimal app surface the dev server needs — `createWebApp(...)` satisfies it. */
2
+ /** Minimal app surface the dev server needs - `createWebApp(...)` satisfies it. */
3
3
  interface FetchApp {
4
4
  fetch(request: Request): Response | Promise<Response>;
5
5
  }
6
6
  export interface DevServerOptions extends Omit<BuildClientOptions, "minify"> {
7
7
  /**
8
- * Build the nifra app for the freshly-built client entry. `importQuery` changes on every
9
- * rebuild — pass it to `discoverRoutes(routesDir, { importQuery })` so SSR re-imports edited
10
- * route modules instead of Bun's cached copies.
8
+ * Build the nifra app for the current client entry. `importQuery` changes on every reload - pass it to
9
+ * `discoverRoutes(routesDir, { importQuery })` so SSR re-imports edited route modules instead of Bun's
10
+ * cached copies.
11
11
  */
12
12
  readonly createApp: (clientEntry: string, importQuery: string) => FetchApp | Promise<FetchApp>;
13
13
  /** Directories to watch (default: `[routesDir]`). */
14
14
  readonly watch?: readonly string[];
15
15
  /** Port to listen on (default {@link DEFAULT_DEV_PORT}). */
16
16
  readonly port?: number;
17
+ /** Directory of user-authored static files served at the root (default `"public"`). The SAME
18
+ * option the production build copies and serves, so dev and prod cannot drift. */
19
+ readonly publicDir?: string | false;
20
+ /**
21
+ * Run the client-leak guards on each change (default `true`).
22
+ *
23
+ * `buildClient` is what runs them - server-only code reaching the browser, `node:` builtins in client
24
+ * code - and Bun's dev server does its own bundling, so nothing would run them otherwise. They are
25
+ * security guards; a dev loop that stops enforcing them is how a leak reaches a deploy unnoticed. The
26
+ * pass runs in the background off the hot path, so HMR is never waiting on it, and only reports.
27
+ */
28
+ readonly guardLeaks?: boolean;
17
29
  }
18
30
  export interface DevServer {
19
31
  readonly port: number;
32
+ /**
33
+ * The URL pages point their client entry at - always {@link CLIENT_ENTRY_PATH}, never Bun's hashed
34
+ * chunk. Deliberately not the underlying chunk URL: that one moves on every rebuild, and anything
35
+ * holding onto it is holding onto a URL that will stop working.
36
+ */
37
+ readonly clientEntry: string;
20
38
  stop(): void;
21
39
  }
22
- /** Start the dev server: build → serve → watch → rebuild + reload on change. */
40
+ /**
41
+ * The stable URL every SSR'd page points its client entry at.
42
+ *
43
+ * It has to be stable, and Bun's own URL is not: that one is a content hash over the entire client graph,
44
+ * so it changes whenever anything the entry imports changes. This path redirects to whichever hashed
45
+ * chunk Bun is serving at the moment the browser asks, which is the only formulation that stays correct
46
+ * across a rebuild.
47
+ */
48
+ export declare const CLIENT_ENTRY_PATH = "/__nifra/client.js";
49
+ /**
50
+ * The throwaway HTML document. Its only job is to make Bun bundle the entry and assign it a URL - it is
51
+ * never shown to a user, so it carries no app markup. The `<div id="root">` is there purely so the
52
+ * document stands on its own if someone opens the probe path directly while debugging.
53
+ */
54
+ export declare function devHtml(entryHref: string): string;
55
+ interface WriteDevFilesOptions {
56
+ readonly routesDir: string;
57
+ readonly clientModule: string;
58
+ readonly entryPath: string;
59
+ readonly htmlPath: string;
60
+ }
61
+ /**
62
+ * Generate the client entry + the HTML route that carries it.
63
+ *
64
+ * Route specifiers are written **relative to the entry file**, not root-relative as the Vite path writes
65
+ * them: Vite resolves `/routes/x.tsx` against its configured root, while Bun's bundler resolves an import
66
+ * the way the runtime does, so a leading slash there would mean the filesystem root.
67
+ */
68
+ export declare function writeDevFiles(options: WriteDevFilesOptions): void;
69
+ /** `<link rel="stylesheet">` tags for Bun's extracted CSS, injected into each SSR'd page's `<head>`. */
70
+ export declare function styleTags(styles: readonly string[]): string;
71
+ /**
72
+ * Inject Bun's stylesheet links into an SSR'd document.
73
+ *
74
+ * Prefers `</head>`; falls back to prepending when a document has no head (a bare fragment from a custom
75
+ * renderer). Never appends blindly at the end - a stylesheet after `</body>` still applies but arrives
76
+ * after first paint, so the page flashes unstyled and dev stops resembling production.
77
+ */
78
+ export declare function injectStyles(html: string, styles: readonly string[]): string;
79
+ /** Start the Bun dev server: generate → bundle → serve → watch → hot-reload on change. */
23
80
  export declare function createDevServer(options: DevServerOptions): Promise<DevServer>;
24
81
  export {};
25
82
  //# sourceMappingURL=dev.d.ts.map
package/dist/dev.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,kBAAkB,EAAe,MAAM,YAAY,CAAA;AAGjE,mFAAmF;AACnF,UAAU,QAAQ;IAChB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACtD;AAED,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC;IAC1E;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9F,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,IAAI,IAAI,CAAA;CACb;AAOD,gFAAgF;AAChF,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CAoEnF"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"AAsDA,OAAO,EAAE,KAAK,kBAAkB,EAAe,MAAM,YAAY,CAAA;AAQjE,mFAAmF;AACnF,UAAU,QAAQ;IAChB,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CACtD;AAED,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,QAAQ,CAAC;IAC1E;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9F,qDAAqD;IACrD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;sFACkF;IAClF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,IAAI,IAAI,IAAI,CAAA;CACb;AAaD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,uBAAuB,CAAA;AAuCrD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAsBjE;AAED,wGAAwG;AACxG,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAQ3D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAM5E;AAED,0FAA0F;AAC1F,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CAwOnF"}