@ogygia/core 0.12.3 → 0.13.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 (226) hide show
  1. package/dist/LateIsland.svelte +3 -2
  2. package/dist/LateIslandAwait.svelte +6 -2
  3. package/dist/Region.svelte +22 -33
  4. package/dist/boundary.d.ts +0 -2
  5. package/dist/boundary.js +8 -6
  6. package/dist/compiler/debarrel/barrel.d.ts +15 -8
  7. package/dist/compiler/debarrel/barrel.js +92 -45
  8. package/dist/compiler/debarrel/plugin.js +41 -17
  9. package/dist/compiler/dev/dev-hmr.d.ts +3 -0
  10. package/dist/compiler/dev/dev-hmr.js +3 -0
  11. package/dist/compiler/driver.js +11 -4
  12. package/dist/compiler/flags.js +21 -7
  13. package/dist/compiler/fouc-css.js +5 -1
  14. package/dist/compiler/kit.js +5 -1
  15. package/dist/compiler/link/build-output.js +12 -3
  16. package/dist/compiler/macros/bake.js +18 -4
  17. package/dist/compiler/macros/dollar.js +6 -1
  18. package/dist/compiler/macros/source.js +6 -1
  19. package/dist/compiler/macros/store.js +10 -2
  20. package/dist/compiler/macros/wire.js +6 -1
  21. package/dist/compiler/parse/scan.js +10 -3
  22. package/dist/compiler/region/transform.js +188 -74
  23. package/dist/context-bridge.js +13 -5
  24. package/dist/context.d.ts +9 -0
  25. package/dist/context.js +30 -1
  26. package/dist/document.d.ts +1 -1
  27. package/dist/document.js +7 -1
  28. package/dist/early-holes-head.d.ts +5 -0
  29. package/dist/early-holes-head.js +20 -0
  30. package/dist/early-holes.d.ts +34 -0
  31. package/dist/early-holes.js +51 -0
  32. package/dist/entry-hydrate.js +9 -12
  33. package/dist/events/bus.js +7 -2
  34. package/dist/events/schema.d.ts +2 -2
  35. package/dist/fn-transport.js +4 -3
  36. package/dist/hooks.js +94 -163
  37. package/dist/insights/page-assets.js +11 -3
  38. package/dist/insights/page-insights.d.ts +0 -2
  39. package/dist/insights/page-insights.js +14 -10
  40. package/dist/insights/session-insights.js +2 -2
  41. package/dist/internal-instruments.d.ts +3 -1
  42. package/dist/internal-instruments.js +3 -1
  43. package/dist/internal-server.d.ts +3 -2
  44. package/dist/internal-server.js +3 -2
  45. package/dist/internal.d.ts +1 -2
  46. package/dist/internal.js +1 -2
  47. package/dist/keep-fallback.d.ts +0 -2
  48. package/dist/keep-fallback.js +4 -5
  49. package/dist/live-transport.js +3 -2
  50. package/dist/messages/build.d.ts +585 -0
  51. package/dist/messages/build.js +800 -0
  52. package/dist/messages/core.d.ts +464 -0
  53. package/dist/messages/core.js +843 -0
  54. package/dist/messages/define.d.ts +62 -0
  55. package/dist/messages/define.js +49 -0
  56. package/dist/messages/index.d.ts +6 -0
  57. package/dist/messages/index.js +6 -0
  58. package/dist/messages/island.d.ts +4 -0
  59. package/dist/messages/island.js +34 -0
  60. package/dist/messages/lazy.d.ts +3 -0
  61. package/dist/messages/lazy.js +22 -0
  62. package/dist/messages/prose.d.ts +15 -0
  63. package/dist/messages/prose.js +43 -0
  64. package/dist/messages/server.d.ts +142 -0
  65. package/dist/messages/server.js +246 -0
  66. package/dist/nested.d.ts +3 -0
  67. package/dist/nested.js +8 -1
  68. package/dist/plugin/css.js +14 -13
  69. package/dist/plugin/facts.d.ts +2 -0
  70. package/dist/plugin/facts.js +10 -6
  71. package/dist/plugin/hooks.d.ts +7 -17
  72. package/dist/plugin/hooks.js +32 -4
  73. package/dist/plugin/index.d.ts +9 -2
  74. package/dist/plugin/request-scope.d.ts +20 -0
  75. package/dist/plugin/request-scope.js +149 -0
  76. package/dist/plugin/server.d.ts +20 -2
  77. package/dist/plugin/server.js +98 -28
  78. package/dist/plugin/testing.d.ts +6 -0
  79. package/dist/plugin/testing.js +14 -2
  80. package/dist/policies.d.ts +21 -0
  81. package/dist/policies.js +66 -0
  82. package/dist/preference.js +6 -1
  83. package/dist/preload.js +6 -9
  84. package/dist/ref-watch.d.ts +0 -10
  85. package/dist/ref-watch.js +14 -1
  86. package/dist/ref.js +17 -5
  87. package/dist/region-snippet.js +9 -3
  88. package/dist/region.js +2 -1
  89. package/dist/report-thrown.d.ts +9 -0
  90. package/dist/report-thrown.js +15 -0
  91. package/dist/rules.d.ts +17 -0
  92. package/dist/rules.js +57 -0
  93. package/dist/runtime/beacon.d.ts +0 -4
  94. package/dist/runtime/beacon.js +58 -43
  95. package/dist/runtime/boot-link.js +19 -2
  96. package/dist/runtime/client-hooks.js +2 -1
  97. package/dist/runtime/concurrency.d.ts +16 -5
  98. package/dist/runtime/concurrency.js +123 -7
  99. package/dist/runtime/core.d.ts +3 -1
  100. package/dist/runtime/core.js +718 -384
  101. package/dist/runtime/devalue-lane.d.ts +13 -0
  102. package/dist/runtime/devalue-lane.js +49 -0
  103. package/dist/runtime/disclose-version.d.ts +1 -0
  104. package/dist/runtime/disclose-version.js +14 -0
  105. package/dist/runtime/early-holes.d.ts +4 -0
  106. package/dist/runtime/early-holes.js +40 -0
  107. package/dist/runtime/entry-locations.js +40 -49
  108. package/dist/runtime/foreign-props.d.ts +0 -7
  109. package/dist/runtime/foreign-props.js +2 -1
  110. package/dist/runtime/form-continuity.d.ts +2 -2
  111. package/dist/runtime/form-continuity.js +30 -8
  112. package/dist/runtime/frame-nav.d.ts +7 -1
  113. package/dist/runtime/frame-nav.js +84 -18
  114. package/dist/runtime/frame-store.d.ts +25 -16
  115. package/dist/runtime/frame-store.js +124 -98
  116. package/dist/runtime/frames.js +8 -4
  117. package/dist/runtime/hole-facts.js +6 -7
  118. package/dist/runtime/hole-fetch.d.ts +17 -0
  119. package/dist/runtime/hole-fetch.js +47 -0
  120. package/dist/runtime/hydrate-core.d.ts +4 -4
  121. package/dist/runtime/hydrate-core.js +80 -64
  122. package/dist/runtime/import-again.d.ts +33 -0
  123. package/dist/runtime/import-again.js +217 -0
  124. package/dist/runtime/interaction-replay.d.ts +5 -3
  125. package/dist/runtime/interaction-replay.js +6 -6
  126. package/dist/runtime/interaction.d.ts +14 -3
  127. package/dist/runtime/interaction.js +46 -23
  128. package/dist/runtime/kit-page-thread.svelte.d.ts +2 -1
  129. package/dist/runtime/kit-page-thread.svelte.js +44 -20
  130. package/dist/runtime/lakes.d.ts +1 -1
  131. package/dist/runtime/lakes.js +18 -5
  132. package/dist/runtime/live.d.ts +1 -2
  133. package/dist/runtime/live.js +6 -8
  134. package/dist/runtime/load-scheduler.d.ts +29 -18
  135. package/dist/runtime/load-scheduler.js +236 -136
  136. package/dist/runtime/nav-handle.js +25 -6
  137. package/dist/runtime/navigation.d.ts +24 -0
  138. package/dist/runtime/navigation.js +24 -0
  139. package/dist/runtime/ownership.d.ts +1 -1
  140. package/dist/runtime/page-defer.js +3 -3
  141. package/dist/runtime/parse-html.d.ts +15 -4
  142. package/dist/runtime/parse-html.js +24 -5
  143. package/dist/runtime/pushable-props.svelte.d.ts +21 -0
  144. package/dist/runtime/pushable-props.svelte.js +37 -0
  145. package/dist/runtime/region-endpoint-url.js +4 -6
  146. package/dist/runtime/region-machines.d.ts +111 -0
  147. package/dist/runtime/region-machines.js +97 -0
  148. package/dist/runtime/region-sheets.d.ts +31 -0
  149. package/dist/runtime/region-sheets.js +49 -0
  150. package/dist/runtime/restore-report.d.ts +1 -0
  151. package/dist/runtime/restore-report.js +36 -0
  152. package/dist/runtime/restore.d.ts +5 -2
  153. package/dist/runtime/restore.js +33 -14
  154. package/dist/runtime/router-nav.d.ts +32 -12
  155. package/dist/runtime/router-nav.js +320 -228
  156. package/dist/runtime/router.d.ts +83 -19
  157. package/dist/runtime/router.js +297 -129
  158. package/dist/runtime/rules.d.ts +35 -0
  159. package/dist/runtime/rules.js +137 -0
  160. package/dist/runtime/schedule.d.ts +6 -3
  161. package/dist/runtime/schedule.js +172 -57
  162. package/dist/runtime/seeds.d.ts +5 -1
  163. package/dist/runtime/seeds.js +11 -5
  164. package/dist/runtime/session.d.ts +46 -7
  165. package/dist/runtime/session.js +78 -10
  166. package/dist/runtime/slots.d.ts +53 -20
  167. package/dist/runtime/slots.js +2 -3
  168. package/dist/runtime/soft-refresh.d.ts +16 -0
  169. package/dist/runtime/soft-refresh.js +14 -0
  170. package/dist/runtime/svelte-warnings.d.ts +4 -10
  171. package/dist/runtime/svelte-warnings.js +7 -4
  172. package/dist/runtime/wire-classes.js +21 -8
  173. package/dist/runtime/wire-format.js +6 -3
  174. package/dist/script.js +8 -7
  175. package/dist/seed-ref-reviver.d.ts +0 -9
  176. package/dist/seed-ref-reviver.js +2 -1
  177. package/dist/seed-refs.d.ts +46 -36
  178. package/dist/seed-refs.js +131 -92
  179. package/dist/server/document-tail.d.ts +6 -0
  180. package/dist/server/document-tail.js +7 -1
  181. package/dist/server/early-holes-csp.d.ts +14 -0
  182. package/dist/server/early-holes-csp.js +73 -0
  183. package/dist/server/page-facts.d.ts +4 -1
  184. package/dist/server/page-facts.js +4 -1
  185. package/dist/server/page-seed.d.ts +2 -2
  186. package/dist/server/page-seed.js +104 -11
  187. package/dist/server/page-stream.d.ts +14 -1
  188. package/dist/server/page-stream.js +119 -2
  189. package/dist/server/personal-response.d.ts +0 -5
  190. package/dist/server/personal-response.js +6 -2
  191. package/dist/server/props-wire.js +5 -1
  192. package/dist/server/region-endpoint.js +10 -2
  193. package/dist/server/render-cache.d.ts +24 -20
  194. package/dist/server/render-cache.js +99 -40
  195. package/dist/server/render-page.js +46 -15
  196. package/dist/server/request-stats.d.ts +10 -8
  197. package/dist/server/request-stats.js +20 -12
  198. package/dist/server/rules.d.ts +11 -0
  199. package/dist/server/rules.js +64 -0
  200. package/dist/server/signing-key.js +31 -18
  201. package/dist/server/stream-regions.d.ts +14 -1
  202. package/dist/server/stream-regions.js +38 -2
  203. package/dist/shared-state.js +5 -1
  204. package/dist/shims/app-navigation.js +3 -2
  205. package/dist/shims/page-store.svelte.js +16 -2
  206. package/dist/source.js +7 -2
  207. package/dist/store-transport.js +12 -5
  208. package/dist/transport.js +3 -2
  209. package/dist/vite/index.js +17 -10
  210. package/dist/vite/kit-dirs.js +7 -1
  211. package/dist/vite/options.d.ts +2 -1
  212. package/dist/vite/options.js +11 -20
  213. package/dist/vite/package-files.js +13 -3
  214. package/dist/vite/paths.d.ts +2 -0
  215. package/dist/vite/paths.js +4 -4
  216. package/dist/vite/plugin-api.js +42 -14
  217. package/dist/vite/style-preprocess.js +10 -3
  218. package/package.json +8 -1
  219. package/dist/KeepHost.svelte +0 -19
  220. package/dist/KeepHost.svelte.d.ts +0 -10
  221. package/dist/LiveHost.svelte +0 -19
  222. package/dist/LiveHost.svelte.d.ts +0 -10
  223. package/dist/NestedProvider.svelte +0 -13
  224. package/dist/NestedProvider.svelte.d.ts +0 -8
  225. package/dist/runtime/page-cache.d.ts +0 -23
  226. package/dist/runtime/page-cache.js +0 -74
@@ -7,15 +7,16 @@
7
7
  * Where the app runs Svelte's async mode, the build imports LateIslandAwait.svelte in this one's
8
8
  * place, which holds the navigation until the code is in (vite/late-island.ts).
9
9
  */
10
+ import { OgygiaError } from './messages/island.js';
10
11
  /** @type {{ load: () => Promise<import('svelte').Component>, props: Record<string, unknown>, children?: import('svelte').Snippet }} */
11
12
  let { load, props, children } = $props();
12
13
  /** @type {import('svelte').Component | undefined} */
13
14
  let Component = $state(undefined);
14
- // (a failed load renders nothing)
15
+ // (a failed load renders nothing, and is said)
15
16
  // svelte-ignore state_referenced_locally
16
17
  load().then(
17
18
  (c) => (Component = c),
18
- () => {}
19
+ (error) => console.error(new OgygiaError('island_load_failed', { entry: null }), error)
19
20
  );
20
21
  </script>
21
22
 
@@ -6,12 +6,16 @@
6
6
  * (an island nested in it awaits in the same batch). Without async mode this file does not compile,
7
7
  * which is why it is never the default.
8
8
  */
9
+ import { OgygiaError } from './messages/island.js';
9
10
  /** @type {{ load: () => Promise<import('svelte').Component>, props: Record<string, unknown>, children?: import('svelte').Snippet }} */
10
11
  let { load, props, children } = $props();
11
- // (a failed load renders nothing)
12
+ // (a failed load renders nothing, and is said)
12
13
  // one load per island, read once on purpose (as in LateIsland.svelte)
13
14
  // svelte-ignore state_referenced_locally
14
- const Component = await load().catch(() => undefined);
15
+ const Component = await load().catch((error) => {
16
+ console.error(new OgygiaError('island_load_failed', { entry: null }), error);
17
+ return undefined;
18
+ });
15
19
  </script>
16
20
 
17
21
  {#if Component}<Component {...props}>{@render children?.()}</Component>{/if}
@@ -62,7 +62,8 @@
62
62
  import { document_tail, island_graph_script, modulepreload_tag } from './server/document-tail.js';
63
63
  import { plan_props_wire, props_sidecar } from './server/props-wire.js';
64
64
  import { region_css_tag } from './server/region-css.js';
65
- import { escape_amp, escape_amp_quot, escape_attr } from './escape.js';
65
+ import { escape_amp_quot, escape_attr } from './escape.js';
66
+ import { warn } from './messages/island.js';
66
67
  import {
67
68
  isNested,
68
69
  setNested,
@@ -73,7 +74,8 @@
73
74
  documentIsCsrTrue,
74
75
  claimRuntimeEmit,
75
76
  claim_region_css,
76
- claim_kit_island
77
+ claim_kit_island,
78
+ note_early_hole
77
79
  } from './context.js';
78
80
  import {
79
81
  prepare_region_props,
@@ -216,10 +218,7 @@
216
218
  // the promise and its stylesheet resolve). The model is "loading UI lives on the region" — say so.
217
219
  // svelte-ignore state_referenced_locally
218
220
  if (of_is_promise && !placeholder && !children && import.meta.env && import.meta.env.DEV) {
219
- console.warn(
220
- '[ogygia] <Region of={promise}> has no `placeholder` — the region will sit empty until the ' +
221
- 'promise resolves and its stylesheet loads. Add {#snippet placeholder()}…{/snippet} for the wait.'
222
- );
221
+ warn('region_promise_no_placeholder');
223
222
  }
224
223
  // A promise is PromiseRegion's (the template hands it over): how it waits — in the render where the
225
224
  // app runs async mode, a late slot on a streamed document, after hydration otherwise — is decided
@@ -409,7 +408,7 @@
409
408
  entry === '?' ? '' : entry
410
409
  );
411
410
  } catch {
412
- /* isolated render without a live page — the recorder is unset there anyway */
411
+ // ok: an isolated render without a live page; the recorder is unset there anyway
413
412
  }
414
413
  });
415
414
  }
@@ -419,11 +418,8 @@
419
418
  // eliminated in builds via the `import.meta.env.DEV` guard.
420
419
  if (nested && (is_island || is_server) && import.meta.env && import.meta.env.DEV) {
421
420
  const entry = untrack(() => (is_server ? __entry : island_entry));
422
- console.warn(
423
- is_server
424
- ? `[ogygia] nested server island "${entry}" is inside another island; it renders inline as frozen HTML (no JS ships; islands inside it wake on their own) — 'deferred' ignored.`
425
- : `[ogygia] nested island "${entry}" is inside another island; it hydrates with its parent (strategy ignored).`
426
- );
421
+ if (is_server) warn('nested_server_island_inline', { entry: entry || '' });
422
+ else warn('nested_island_with_parent', { entry: entry || '' });
427
423
  }
428
424
  // Freeze bare snippet PROPS (named-snippet props) to static region snippets (server) so the island
429
425
  // BODY and the serialized PAYLOAD render byte-for-byte identically — hydration then adopts the frozen
@@ -752,7 +748,7 @@
752
748
  props: { children: ogygiaFallback },
753
749
  context: region_contexts
754
750
  });
755
- return () => void unmount(app);
751
+ return () => void unmount(app); // ok: unmount's promise is its outros ending; it never rejects
756
752
  }
757
753
  );
758
754
  // A hole NESTED in an island renders in the page pass (`deferred` ignored, the nested rule) — as a
@@ -816,16 +812,13 @@
816
812
  ? []
817
813
  : graph_hrefs(__module, server_region_entry, null)
818
814
  );
819
- const server_fetch_preload = $derived.by(() => {
820
- // Only `defer: 'load'`: start the endpoint fetch during HTML parse (warms the per-hole load).
821
- if (nested || building || __defer !== 'load' || !server_endpoint) return '';
822
- const href_attr = escape_amp(server_endpoint);
823
- return (
824
- LT + 'link rel="preload" as="fetch" crossorigin="anonymous" href="' + href_attr + '"' + GT
825
- );
826
- });
827
- // The fetch preload STAYS in the head: it starts the hole's content request during the HTML parse
828
- // (content, not island code). The island graph rides the tail.
815
+ // Only `defer: 'load'`: its request starts during the HTML parse, from the head's early start
816
+ // (early-holes.ts), which the handle or `document()` writes once for every such hole. The island
817
+ // graph rides the tail.
818
+ if (typeof window === 'undefined' && is_server && !nested && !building)
819
+ untrack(() => {
820
+ if (__defer === 'load' && server_endpoint) note_early_hole(server_endpoint);
821
+ });
829
822
  const server_graph_tail =
830
823
  !!tail &&
831
824
  untrack(() => {
@@ -833,10 +826,9 @@
833
826
  return true;
834
827
  });
835
828
  const server_preload = $derived(
836
- server_fetch_preload +
837
- (server_graph_tail || !server_graph_hrefs.length
838
- ? ''
839
- : island_graph_script(new Map([[server_region_entry, server_graph_hrefs]])))
829
+ server_graph_tail || !server_graph_hrefs.length
830
+ ? ''
831
+ : island_graph_script(new Map([[server_region_entry, server_graph_hrefs]]))
840
832
  );
841
833
 
842
834
  // ───────────────────────────────────────────────────────────── lake branch ──
@@ -877,9 +869,7 @@
877
869
  // client-renders from scratch has nothing. (Inside an island an empty element is the lifted
878
870
  // lake, which the runtime fills right after.)
879
871
  if (is_csr && !el.firstChild && import.meta.env && import.meta.env.DEV)
880
- console.warn(
881
- `[ogygia] lake "${__entry}" was mounted by Kit on the client with no server HTML to adopt (a client-side navigation, or a hydration failure — look for an error above). The runtime restores it from its server HTML when this document rendered it; otherwise it stays empty. A lake is server HTML: keep chrome lakes in a layout that persists across navigations, or serve that route csr=false.`
882
- );
872
+ warn('lake_mounted_without_html', { entry: __entry || '' });
883
873
  }
884
874
  );
885
875
 
@@ -1083,7 +1073,7 @@
1083
1073
  });
1084
1074
  }
1085
1075
  } catch {
1086
- /* devtools emit must never break a render */
1076
+ // ok: a devtools emit never breaks a render; the event is left out of the timeline
1087
1077
  }
1088
1078
  });
1089
1079
  }
@@ -1119,8 +1109,7 @@
1119
1109
  margin={root_margin || undefined}
1120
1110
  data-ogygia-keep={__keep || undefined}
1121
1111
  data-og-fp={island_fp || undefined}
1122
- src={island_src || undefined}
1123
- >{#if Component}<Component {...island_props_body} />{/if}</ogygia-region
1112
+ src={island_src || undefined}><Component {...island_props_body} /></ogygia-region
1124
1113
  ><!-- svelte-ignore hydration_html_changed -->{@html island_props_inline}{/if}
1125
1114
  {:else if is_server}
1126
1115
  {@const Component = __component}
@@ -23,8 +23,6 @@ export declare function configure_boundary(p: BoundaryPolicy): void;
23
23
  export declare function classify_boundary(value: unknown, key?: string): BoundaryFinding[];
24
24
  /** The findings that make a value undroppable-into-HTML (used to explain a dropped context key). */
25
25
  export declare function boundary_problems(value: unknown, key?: string): BoundaryFinding[];
26
- /** Format one finding as the console line for a dropped/degraded context key. */
27
- export declare function format_boundary_finding(key: string, f: BoundaryFinding): string;
28
26
  /**
29
27
  * The `import.meta.og.$` rewrite target for NON-function values — the universal boundary
30
28
  * assertion. Classifies the value AT THE MARK (creation), so a refusal (DOM node, bare
package/dist/boundary.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { is_derived_like, is_store } from "./store-transport.js";
2
2
  import { wire } from "./live-transport.js";
3
+ import { OgygiaError } from "./messages/island.js";
3
4
  // src/boundary.ts
4
5
  /**
5
6
  * Boundary classifier — guard layer of the transportable seam.
@@ -105,10 +106,6 @@ function classify_boundary(value, key = "") {
105
106
  function boundary_problems(value, key = "") {
106
107
  return classify_boundary(value, key).filter((f) => f.kind === "refuse" || f.kind === "warn");
107
108
  }
108
- /** Format one finding as the console line for a dropped/degraded context key. */
109
- function format_boundary_finding(key, f) {
110
- return `[ogygia] context '${key}'${f.path ? ` @ ${f.path}` : ""}: ${f.detail}`;
111
- }
112
109
  /**
113
110
  * The `import.meta.og.$` rewrite target for NON-function values — the universal boundary
114
111
  * assertion. Classifies the value AT THE MARK (creation), so a refusal (DOM node, bare
@@ -120,8 +117,13 @@ function __og_boundary(value, site) {
120
117
  const refusals = classify_boundary(value).filter((f) => f.kind === "refuse");
121
118
  if (refusals.length) {
122
119
  const f = refusals[0];
123
- throw new Error(`[ogygia] ${site} — og.$ value${f.path ? ` @ ${f.path}` : ""}: ${f.detail}` + (refusals.length > 1 ? ` (+${refusals.length - 1} more)` : ""));
120
+ throw new OgygiaError("boundary_refused", {
121
+ site,
122
+ path: f.path || void 0,
123
+ detail: f.detail,
124
+ more: refusals.length - 1
125
+ });
124
126
  }
125
127
  return value;
126
128
  }
127
- export { __og_boundary, boundary_problems, classify_boundary, configure_boundary, format_boundary_finding };
129
+ export { __og_boundary, boundary_problems, classify_boundary, configure_boundary };
@@ -1,9 +1,11 @@
1
1
  import { ModuleShape } from "./parse.js";
2
- export interface Host {
2
+ /** `Caller`: who asked for the map (`map(id, spec, caller)`), handed to every resolve of its walk —
3
+ * the Vite plugin's transform context, whose own `resolve` knows its environment's conditions. */
4
+ export interface Host<Caller = void> {
3
5
  /** The module's source, or null when it cannot be read (virtual, binary, missing). */
4
6
  read(id: string): string | null;
5
- /** Resolve `spec` from `importer` to a file id; null when Vite cannot. */
6
- resolve(spec: string, importer: string): Promise<string | null>;
7
+ /** Resolve `spec` from `importer` to a file id, as `caller` resolves it; null when Vite cannot. */
8
+ resolve(spec: string, importer: string, caller: Caller): Promise<string | null>;
7
9
  /** May this resolved module be treated as a barrel at all? (matchers + node_modules policy) */
8
10
  candidate(id: string, spec: string): boolean;
9
11
  /** Is this module forced even though impure? */
@@ -39,17 +41,22 @@ export interface ExportMap {
39
41
  * cached only once the walk that owns them has closed the ring (the set is empty there) */
40
42
  cycles: Set<string>;
41
43
  }
42
- export declare class BarrelIndex {
44
+ /** One index caches the maps its callers' walks built, so every caller of one index must resolve
45
+ * alike: the Vite plugin keeps one index per environment (a leaf may resolve to another file for
46
+ * the client than for the server). */
47
+ export declare class BarrelIndex<Caller = void> {
43
48
  #private;
44
- constructor(host: Host);
45
- /** Drop every cached map/shape that depends on `id` (the file changed). */
49
+ constructor(host: Host<Caller>);
50
+ /** Drop every cached map/shape that depends on `id` (the file changed), and every build in flight
51
+ * that has met it so far (one it meets later reads the file as it is now). */
46
52
  invalidate(id: string): void;
47
53
  /** The parsed shape of a module (cached), or null when it cannot be read/parsed. */
48
54
  shape(id: string): ModuleShape | null;
49
55
  /**
50
56
  * The export map of a barrel, or null when the module is not a barrel the plugin may rewrite
51
57
  * (not a candidate, impure and not forced, kept, unreadable). `spec` is the specifier the
52
- * importer wrote — matchers see both. Concurrent callers share one build.
58
+ * importer wrote — matchers see both. Concurrent callers share one build; a build resolves as
59
+ * the `caller` that started it.
53
60
  */
54
- map(id: string, spec: string): Promise<ExportMap | null>;
61
+ map(id: string, spec: string, caller: Caller): Promise<ExportMap | null>;
55
62
  }
@@ -1,4 +1,5 @@
1
1
  import { analyze_exports } from "./parse.js";
2
+ import { STALE, flights } from "@ogygia/machine";
2
3
  // src/compiler/debarrel/barrel.ts
3
4
  /**
4
5
  * The barrel index: for a resolved module id, the map `exported name → leaf` — the module and
@@ -11,8 +12,10 @@ import { analyze_exports } from "./parse.js";
11
12
  * with `this.resolve`.
12
13
  *
13
14
  * CONCURRENCY: a bundler transforms hundreds of importers at once, and most of them ask for the same
14
- * few barrels. One build per barrel is kept in flight (`#pending`) and every concurrent caller awaits
15
- * it. A cycle is detected against the WALK STACK of the build that is running (a → b → a), never
15
+ * few barrels. The barrels are one flights keyed by id: one build per barrel is in flight and every
16
+ * concurrent caller awaits it. An edit (`invalidate`) drops a barrel whose build or map depends on
17
+ * the changed file: a build in flight then ends STALE, stores nothing, and its callers build again
18
+ * from the files as they are now (dev: a barrel saved while a transform resolves through it). A cycle is detected against the WALK STACK of the build that is running (a → b → a), never
16
19
  * against another build's in-flight state; when a walk meets a barrel some other walk is still
17
20
  * building, it does not wait (two walks waiting on each other would never finish) — it leaves that
18
21
  * name unresolved and marks its own map PROVISIONAL, so the map is served once but not cached, and
@@ -21,19 +24,37 @@ import { analyze_exports } from "./parse.js";
21
24
  const STRIP_QUERY_RE = /[?#].*$/;
22
25
  const NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
23
26
  const PARSABLE_RE = /\.(?:[cm]?[jt]sx?|svelte\.[jt]s)$/;
27
+ /** One index caches the maps its callers' walks built, so every caller of one index must resolve
28
+ * alike: the Vite plugin keeps one index per environment (a leaf may resolve to another file for
29
+ * the client than for the server). */
24
30
  var BarrelIndex = class {
25
31
  #host;
26
- #maps = /* @__PURE__ */ new Map();
32
+ /** One build per barrel: its map is kept until an edit to the barrel or to one of its deps
33
+ * (null: not a barrel we rewrite); a provisional map, or one that lacks a ring's names, is
34
+ * served to the callers of its build, not kept. */
35
+ #barrels = flights.keyed()({
36
+ name: "debarrel-barrel",
37
+ key: ["id"],
38
+ ignore: { build: "what the ask that starts the flight found of the file: a barrel is built once, as that caller resolves" },
39
+ run: async ({ id, build }) => build === null ? null : this.#build_of(id, build.shape, build.walk),
40
+ keep: (map) => map !== null && (map.provisional || map.cycles.size > 0) ? 0 : Infinity
41
+ });
42
+ /** The map each build in flight has met so far (its deps among it), for `invalidate`. */
43
+ #building = /* @__PURE__ */ new Map();
27
44
  #shapes = /* @__PURE__ */ new Map();
28
- #pending = /* @__PURE__ */ new Map();
29
45
  constructor(host) {
30
46
  this.#host = host;
31
47
  }
32
- /** Drop every cached map/shape that depends on `id` (the file changed). */
48
+ /** Drop every cached map/shape that depends on `id` (the file changed), and every build in flight
49
+ * that has met it so far (one it meets later reads the file as it is now). */
33
50
  invalidate(id) {
34
51
  const clean = id.replace(STRIP_QUERY_RE, "");
35
52
  this.#shapes.delete(clean);
36
- for (const [k, m] of this.#maps) if (k === clean || m && m.deps.has(clean)) this.#maps.delete(k);
53
+ this.#barrels.drop(({ id: k }, entry) => {
54
+ if (k === clean) return true;
55
+ const map = entry.kind === "kept" ? entry.answer : this.#building.get(k);
56
+ return map != null && map.deps.has(clean);
57
+ });
37
58
  }
38
59
  /** The parsed shape of a module (cached), or null when it cannot be read/parsed. */
39
60
  shape(id) {
@@ -50,44 +71,45 @@ var BarrelIndex = class {
50
71
  /**
51
72
  * The export map of a barrel, or null when the module is not a barrel the plugin may rewrite
52
73
  * (not a candidate, impure and not forced, kept, unreadable). `spec` is the specifier the
53
- * importer wrote — matchers see both. Concurrent callers share one build.
74
+ * importer wrote — matchers see both. Concurrent callers share one build; a build resolves as
75
+ * the `caller` that started it.
54
76
  */
55
- async map(id, spec) {
77
+ async map(id, spec, caller) {
56
78
  const clean = id.replace(STRIP_QUERY_RE, "");
57
79
  if (clean !== id) return null;
58
80
  if (this.#host.kept(clean, spec)) return null;
59
81
  if (!this.#host.candidate(clean, spec)) return null;
60
- if (this.#maps.has(clean)) return this.#maps.get(clean);
61
- const pending = this.#pending.get(clean);
62
- if (pending) return pending;
63
- const shape = this.shape(clean);
64
- if (!shape) {
65
- this.#maps.set(clean, null);
66
- return null;
67
- }
68
- if (!shape.pure && !this.#host.forced(clean, spec)) {
69
- this.#maps.set(clean, null);
70
- return null;
82
+ for (;;) {
83
+ const got = await this.#barrels.get(this.#ask(clean, spec, caller));
84
+ if (got !== STALE) return got;
71
85
  }
72
- return this.#start(clean, shape, /* @__PURE__ */ new Set());
73
86
  }
74
- /** Begin one build for `id`, register it as in flight, cache the result unless provisional. */
75
- #start(id, shape, walk) {
76
- const run = (async () => {
77
- try {
78
- const built = await this.#build(id, shape, walk);
79
- if (!built.provisional && built.cycles.size === 0) this.#maps.set(id, built);
80
- return built;
81
- } finally {
82
- this.#pending.delete(id);
87
+ /** What a caller of `map()` asks for: the barrel's build, or (it is not one) null. Only the ask
88
+ * that starts the barrel's flight runs; a built or building barrel is not looked at again. */
89
+ #ask(id, spec, caller) {
90
+ if (this.#barrels.peek({ id }) !== void 0) return {
91
+ id,
92
+ build: null
93
+ };
94
+ const shape = this.shape(id);
95
+ if (!shape || !shape.pure && !this.#host.forced(id, spec)) return {
96
+ id,
97
+ build: null
98
+ };
99
+ return {
100
+ id,
101
+ build: {
102
+ shape,
103
+ walk: {
104
+ stack: /* @__PURE__ */ new Set(),
105
+ caller
106
+ }
83
107
  }
84
- })();
85
- this.#pending.set(id, run);
86
- return run;
108
+ };
87
109
  }
88
- async #build(id, shape, outer) {
89
- const walk = new Set(outer);
90
- walk.add(id);
110
+ /** One build of `id` (a barrel's flight): kept once built unless provisional; STALE to its
111
+ * callers when an edit drops it first (it stores nothing then). */
112
+ async #build_of(id, shape, walk) {
91
113
  const map = {
92
114
  id,
93
115
  entries: /* @__PURE__ */ new Map(),
@@ -98,6 +120,19 @@ var BarrelIndex = class {
98
120
  provisional: false,
99
121
  cycles: /* @__PURE__ */ new Set()
100
122
  };
123
+ this.#building.set(id, map);
124
+ try {
125
+ return await this.#build(map, shape, walk);
126
+ } finally {
127
+ if (this.#building.get(id) === map) this.#building.delete(id);
128
+ }
129
+ }
130
+ async #build(map, shape, outer) {
131
+ const id = map.id;
132
+ const walk = {
133
+ stack: /* @__PURE__ */ new Set([...outer.stack, id]),
134
+ caller: outer.caller
135
+ };
101
136
  const stars = [];
102
137
  const explicit = /* @__PURE__ */ new Set();
103
138
  for (const rec of shape.exports) if (rec.kind === "own") {
@@ -110,7 +145,7 @@ var BarrelIndex = class {
110
145
  else map.opaque = true;
111
146
  } else if (rec.kind === "star_ns") {
112
147
  explicit.add(rec.exported);
113
- const target = await this.#host.resolve(rec.source, id);
148
+ const target = await this.#host.resolve(rec.source, id, walk.caller);
114
149
  if (target) map.entries.set(rec.exported, {
115
150
  id: target.replace(STRIP_QUERY_RE, ""),
116
151
  namespace: true
@@ -121,7 +156,7 @@ var BarrelIndex = class {
121
156
  type_only: rec.type_only
122
157
  });
123
158
  for (const star of stars) {
124
- const target = await this.#host.resolve(star.source, id);
159
+ const target = await this.#host.resolve(star.source, id, walk.caller);
125
160
  if (!target || target !== target.replace(STRIP_QUERY_RE, "")) {
126
161
  map.opaque = true;
127
162
  continue;
@@ -148,7 +183,7 @@ var BarrelIndex = class {
148
183
  }
149
184
  /** The leaf behind `import { imported } from source` written inside barrel `from`. */
150
185
  async #leaf(from, source, imported, map, walk) {
151
- const target = await this.#host.resolve(source, from);
186
+ const target = await this.#host.resolve(source, from, walk.caller);
152
187
  if (!target) return null;
153
188
  if (target !== target.replace(STRIP_QUERY_RE, "")) return null;
154
189
  map.deps.add(target);
@@ -172,22 +207,34 @@ var BarrelIndex = class {
172
207
  */
173
208
  async #through(id, parent, walk) {
174
209
  if (this.#host.kept(id, id)) return null;
175
- if (this.#maps.has(id)) {
176
- const m = this.#maps.get(id) ?? null;
210
+ const s = this.#barrels.peek({ id });
211
+ if (s?.kind === "kept") {
212
+ const m = s.answer;
177
213
  if (m) for (const d of m.deps) parent.deps.add(d);
178
214
  return m;
179
215
  }
180
- if (walk.has(id)) {
216
+ if (walk.stack.has(id)) {
181
217
  if (id !== parent.id) parent.cycles.add(id);
182
218
  return null;
183
219
  }
184
- if (this.#pending.has(id)) {
220
+ if (s !== void 0) {
185
221
  parent.provisional = true;
186
222
  return null;
187
223
  }
188
224
  const shape = this.shape(id);
189
225
  if (!shape || !shape.pure) return null;
190
- const built = await this.#start(id, shape, walk);
226
+ const built = await this.#barrels.get({
227
+ id,
228
+ build: {
229
+ shape,
230
+ walk
231
+ }
232
+ });
233
+ if (built === STALE) {
234
+ parent.provisional = true;
235
+ return null;
236
+ }
237
+ if (built === null) return null;
191
238
  if (built.provisional) parent.provisional = true;
192
239
  for (const c of built.cycles) if (c !== parent.id) parent.cycles.add(c);
193
240
  for (const d of built.deps) parent.deps.add(d);
@@ -218,13 +265,13 @@ var BarrelIndex = class {
218
265
  if (leaf) out.set(rec.exported, leaf);
219
266
  else parent.opaque = true;
220
267
  } else if (rec.kind === "star_ns") {
221
- const t = await this.#host.resolve(rec.source, target);
268
+ const t = await this.#host.resolve(rec.source, target, walk.caller);
222
269
  if (t) out.set(rec.exported, {
223
270
  id: t.replace(STRIP_QUERY_RE, ""),
224
271
  namespace: true
225
272
  });
226
273
  } else {
227
- const t = await this.#host.resolve(rec.source, target);
274
+ const t = await this.#host.resolve(rec.source, target, walk.caller);
228
275
  if (!t) {
229
276
  parent.opaque = true;
230
277
  continue;
@@ -4,6 +4,7 @@ import { rewrite_module } from "./rewrite.js";
4
4
  import { rewrite_svelte } from "./svelte.js";
5
5
  import { is_script_request } from "../script-request.js";
6
6
  import { strip_bom } from "../bom.js";
7
+ import { line, warn } from "../../messages/build.js";
7
8
  import { readFileSync, statSync } from "node:fs";
8
9
  import { searchForWorkspaceRoot } from "vite";
9
10
  // src/compiler/debarrel/plugin.ts
@@ -40,7 +41,6 @@ function debarrel(options = {}, internal = {}) {
40
41
  return { skip: (decl) => decl.attribute_keys.some((k) => skip_keys.has(k)) || !!locals && locals.size > 0 && decl.specs.some((s) => locals.has(s.local)) };
41
42
  };
42
43
  let root = process.cwd().replace(BACKSLASH_G, "/");
43
- let resolver = null;
44
44
  let log = (msg) => console.log(msg);
45
45
  let command = "serve";
46
46
  const mtimes = /* @__PURE__ */ new Map();
@@ -57,23 +57,31 @@ function debarrel(options = {}, internal = {}) {
57
57
  });
58
58
  let report = fresh_report();
59
59
  const warned = /* @__PURE__ */ new Set();
60
+ const indexes = /* @__PURE__ */ new Map();
61
+ const index_of = (environment) => {
62
+ let index = indexes.get(environment);
63
+ if (index === void 0) indexes.set(environment, index = new BarrelIndex(host));
64
+ return index;
65
+ };
66
+ const invalidate = (id) => {
67
+ for (const index of indexes.values()) index.invalidate(id);
68
+ };
60
69
  const host = {
61
70
  read(id) {
62
71
  try {
63
72
  const st = statSync(id);
64
73
  const prev = mtimes.get(id);
65
- if (prev !== void 0 && prev !== st.mtimeMs) index.invalidate(id);
74
+ if (prev !== void 0 && prev !== st.mtimeMs) invalidate(id);
66
75
  mtimes.set(id, st.mtimeMs);
67
76
  return strip_bom(readFileSync(id, "utf8"));
68
77
  } catch {
69
78
  return null;
70
79
  }
71
80
  },
72
- async resolve(spec, importer) {
73
- if (!resolver) return null;
81
+ async resolve(spec, importer, caller) {
74
82
  let r;
75
83
  try {
76
- r = await resolver.resolve(spec, importer, { skipSelf: true });
84
+ r = await caller.resolve(spec, importer, { skipSelf: true });
77
85
  } catch {
78
86
  return null;
79
87
  }
@@ -89,7 +97,6 @@ function debarrel(options = {}, internal = {}) {
89
97
  kept: (id, spec) => o.keep.length > 0 && matches_any(o.keep, id, spec),
90
98
  follow_packages: o.follow_packages
91
99
  };
92
- const index = new BarrelIndex(host);
93
100
  const is_importer = (id) => {
94
101
  if (id.includes("\0")) return false;
95
102
  if (!is_script_request(id)) return false;
@@ -108,22 +115,20 @@ function debarrel(options = {}, internal = {}) {
108
115
  command = config.command;
109
116
  log = (msg) => config.logger.info(msg);
110
117
  },
111
- buildStart() {
112
- resolver = this;
113
- },
114
118
  async transform(raw, id) {
115
119
  if (!is_importer(id)) return null;
116
120
  const code = strip_bom(raw);
117
- resolver = this;
121
+ const caller = this;
122
+ const index = index_of(this.environment.name);
118
123
  const t0 = performance.now();
119
124
  if (report.files === 0) report.first_ms = t0;
120
125
  report.files++;
121
126
  const importer = id.replace(QUERY_RE, "");
122
127
  const seen = [];
123
128
  const lookup = async (spec) => {
124
- const resolved = await host.resolve(spec, importer);
129
+ const resolved = await host.resolve(spec, importer, caller);
125
130
  if (!resolved) return null;
126
- const map = await index.map(resolved, spec);
131
+ const map = await index.map(resolved, spec, caller);
127
132
  if (map) seen.push(map);
128
133
  return map;
129
134
  };
@@ -132,7 +137,11 @@ function debarrel(options = {}, internal = {}) {
132
137
  report.skipped.add(importer);
133
138
  if (warned.has(importer)) return;
134
139
  warned.add(importer);
135
- console.warn(`[ogygia] barrels: skipped ${short(importer, root)}: ${why.code}${why.line ? ` at ${why.line}:${why.column ?? 1}` : ""} — its imports stay on their barrels`);
140
+ warn("barrels_component_skipped", {
141
+ file: short(importer, root),
142
+ reason: why.code,
143
+ at: why.line ? `${why.line}:${why.column ?? 1}` : ""
144
+ });
136
145
  }) : await rewrite_module(code, importer, lookup, policy);
137
146
  for (const map of seen) for (const d of map.deps) if (!d.includes("\0")) this.addWatchFile(d);
138
147
  report.last_ms = performance.now();
@@ -149,14 +158,19 @@ function debarrel(options = {}, internal = {}) {
149
158
  b.importers.add(importer);
150
159
  report.barrels.set(m.barrel, b);
151
160
  }
152
- if (o.debug) for (const m of result.moves) log(`[ogygia] barrels: ${short(importer, root)}: ${m.names.join(", ")} ← ${short(m.barrel, root)} → ${m.leaves.map((l) => short(l, root)).join(", ")}`);
161
+ if (o.debug) for (const m of result.moves) log(line("barrels_rewrite", {
162
+ file: short(importer, root),
163
+ names: m.names.join(", "),
164
+ barrel: short(m.barrel, root),
165
+ leaves: m.leaves.map((l) => short(l, root)).join(", ")
166
+ }));
153
167
  return {
154
168
  code: result.code,
155
169
  map: result.map
156
170
  };
157
171
  },
158
172
  watchChange(id) {
159
- index.invalidate(id.replace(BACKSLASH_G, "/"));
173
+ invalidate(id.replace(BACKSLASH_G, "/"));
160
174
  },
161
175
  closeBundle() {
162
176
  if (o.report && command === "build") log(format_report(report, root, o.report === "all" ? Infinity : 8));
@@ -173,8 +187,18 @@ const n = (v) => v.toLocaleString("en-US");
173
187
  function format_report(r, root, limit) {
174
188
  const span_s = ((r.last_ms - r.first_ms) / 1e3).toFixed(2);
175
189
  const skipped = r.skipped?.size ? [` ${n(r.skipped.size)} component${r.skipped.size === 1 ? "" : "s"} skipped (could not be parsed; their imports stay on their barrels): ${[...r.skipped].slice(0, limit).map((id) => short(id, root)).join(", ")}${r.skipped.size > limit ? ` and ${n(r.skipped.size - limit)} more` : ""}`] : [];
176
- if (r.importers === 0) return [`[ogygia] barrels: ${n(r.files)} files scanned, no barrel imports to rewrite (${span_s} s)`, ...skipped].join("\n");
177
- const head = `[ogygia] barrels: ${n(r.importers)} of ${n(r.files)} files rewritten — ${n(r.imports)} barrel imports → leaves (${n(r.names)} names), ${n(r.barrels.size)} barrels bypassed, ${span_s} s first to last transform`;
190
+ if (r.importers === 0) return [line("barrels_report_none", {
191
+ files: n(r.files),
192
+ seconds: span_s
193
+ }), ...skipped].join("\n");
194
+ const head = line("barrels_report", {
195
+ importers: n(r.importers),
196
+ files: n(r.files),
197
+ imports: n(r.imports),
198
+ names: n(r.names),
199
+ barrels: n(r.barrels.size),
200
+ seconds: span_s
201
+ });
178
202
  const sorted = [...r.barrels.entries()].sort((a, b) => b[1].names - a[1].names);
179
203
  const top = sorted.slice(0, limit);
180
204
  const width = Math.max(...top.map(([, b]) => n(b.names).length));
@@ -20,6 +20,9 @@
20
20
  * it blanks the page; a MutationObserver would also delete FOUC styles the SPA router merges
21
21
  * in on navigation.
22
22
  *
23
+ * Its `hot.on` listeners are its module run's, and need no scope: Vite drops a replaced module's
24
+ * `hot.on` listeners itself (the client's `ctxToListenersMap`), and a full reload ends the page.
25
+ *
23
26
  * @internal Emitted by the plugin; exported for unit tests.
24
27
  */
25
28
  export declare function dev_hmr_client_source(): string;
@@ -21,6 +21,9 @@
21
21
  * it blanks the page; a MutationObserver would also delete FOUC styles the SPA router merges
22
22
  * in on navigation.
23
23
  *
24
+ * Its `hot.on` listeners are its module run's, and need no scope: Vite drops a replaced module's
25
+ * `hot.on` listeners itself (the client's `ctxToListenersMap`), and a full reload ends the page.
26
+ *
24
27
  * @internal Emitted by the plugin; exported for unit tests.
25
28
  */
26
29
  function dev_hmr_client_source() {