@noego/wood 0.6.1 → 0.7.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 (247) hide show
  1. package/README.md +7 -0
  2. package/dist/browser-test-setup.cjs +29 -0
  3. package/dist/browser-test-setup.cjs.map +1 -0
  4. package/dist/browser-test-setup.d.cts +8 -0
  5. package/dist/browser-test-setup.d.ts +8 -0
  6. package/dist/browser-test-setup.js +9 -0
  7. package/dist/browser-test-setup.js.map +1 -0
  8. package/dist/client/global_polyfill.d.cts +1 -0
  9. package/dist/client/global_polyfill.d.ts +1 -0
  10. package/dist/client/index.cjs +7 -0
  11. package/dist/client/index.cjs.map +1 -1
  12. package/dist/client/index.d.cts +8 -4
  13. package/dist/client/index.d.ts +8 -4
  14. package/dist/client/index.js +6 -1
  15. package/dist/client/index.js.map +1 -1
  16. package/dist/codegen/renderer_app_generator.cjs +97 -24
  17. package/dist/codegen/renderer_app_generator.cjs.map +1 -1
  18. package/dist/codegen/renderer_app_generator.js +97 -24
  19. package/dist/codegen/renderer_app_generator.js.map +1 -1
  20. package/dist/codegen/type_generator.cjs +9 -0
  21. package/dist/codegen/type_generator.cjs.map +1 -1
  22. package/dist/codegen/type_generator.js +9 -0
  23. package/dist/codegen/type_generator.js.map +1 -1
  24. package/dist/config/config_parser.cjs +10 -0
  25. package/dist/config/config_parser.cjs.map +1 -1
  26. package/dist/config/config_parser.js +10 -0
  27. package/dist/config/config_parser.js.map +1 -1
  28. package/dist/fetch.cjs +38 -0
  29. package/dist/fetch.cjs.map +1 -0
  30. package/dist/fetch.d.cts +7 -0
  31. package/dist/fetch.d.ts +7 -0
  32. package/dist/fetch.js +12 -0
  33. package/dist/fetch.js.map +1 -0
  34. package/dist/frontend/frontend_page.cjs +3 -3
  35. package/dist/frontend/frontend_page.cjs.map +1 -1
  36. package/dist/frontend/frontend_page.d.cts +1 -1
  37. package/dist/frontend/frontend_page.d.ts +1 -1
  38. package/dist/frontend/frontend_page.js +3 -3
  39. package/dist/frontend/frontend_page.js.map +1 -1
  40. package/dist/frontend/frontend_surface.cjs +31 -17
  41. package/dist/frontend/frontend_surface.cjs.map +1 -1
  42. package/dist/frontend/frontend_surface.d.cts +13 -2
  43. package/dist/frontend/frontend_surface.d.ts +13 -2
  44. package/dist/frontend/frontend_surface.js +37 -17
  45. package/dist/frontend/frontend_surface.js.map +1 -1
  46. package/dist/frontend/index.d.cts +3 -0
  47. package/dist/frontend/index.d.ts +3 -0
  48. package/dist/index.cjs +5 -0
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.d.cts +27 -1
  51. package/dist/index.d.ts +27 -1
  52. package/dist/index.js +3 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/loader/index.cjs +19 -2
  55. package/dist/loader/index.cjs.map +1 -1
  56. package/dist/loader/index.d.cts +1 -0
  57. package/dist/loader/index.d.ts +1 -0
  58. package/dist/loader/index.js +19 -1
  59. package/dist/loader/index.js.map +1 -1
  60. package/dist/loader/loader_orchestration.cjs +82 -0
  61. package/dist/loader/loader_orchestration.cjs.map +1 -0
  62. package/dist/loader/loader_orchestration.d.cts +66 -0
  63. package/dist/loader/loader_orchestration.d.ts +66 -0
  64. package/dist/loader/loader_orchestration.js +51 -0
  65. package/dist/loader/loader_orchestration.js.map +1 -0
  66. package/dist/loader/loader_runner.cjs +46 -41
  67. package/dist/loader/loader_runner.cjs.map +1 -1
  68. package/dist/loader/loader_runner.d.cts +12 -6
  69. package/dist/loader/loader_runner.d.ts +12 -6
  70. package/dist/loader/loader_runner.js +50 -41
  71. package/dist/loader/loader_runner.js.map +1 -1
  72. package/dist/native_browser.types-BY2daUOS.d.ts +189 -0
  73. package/dist/native_browser.types-vWXtE4rM.d.cts +189 -0
  74. package/dist/navigation/index.cjs +15 -2
  75. package/dist/navigation/index.cjs.map +1 -1
  76. package/dist/navigation/index.d.cts +6 -28
  77. package/dist/navigation/index.d.ts +6 -28
  78. package/dist/navigation/index.js +8 -1
  79. package/dist/navigation/index.js.map +1 -1
  80. package/dist/navigation/layout_preservation.cjs.map +1 -1
  81. package/dist/navigation/layout_preservation.d.cts +27 -2
  82. package/dist/navigation/layout_preservation.d.ts +27 -2
  83. package/dist/navigation/layout_preservation.js.map +1 -1
  84. package/dist/runtime/get_container.cjs +2 -0
  85. package/dist/runtime/get_container.cjs.map +1 -1
  86. package/dist/runtime/get_container.js +2 -0
  87. package/dist/runtime/get_container.js.map +1 -1
  88. package/dist/runtime/index.cjs +5 -0
  89. package/dist/runtime/index.cjs.map +1 -1
  90. package/dist/runtime/index.d.cts +2 -0
  91. package/dist/runtime/index.d.ts +2 -0
  92. package/dist/runtime/index.js +3 -0
  93. package/dist/runtime/index.js.map +1 -1
  94. package/dist/runtime/runtime_capabilities.cjs +35 -0
  95. package/dist/runtime/runtime_capabilities.cjs.map +1 -0
  96. package/dist/runtime/runtime_capabilities.d.cts +6 -0
  97. package/dist/runtime/runtime_capabilities.d.ts +6 -0
  98. package/dist/runtime/runtime_capabilities.js +11 -0
  99. package/dist/runtime/runtime_capabilities.js.map +1 -0
  100. package/dist/runtime/runtime_module.cjs +2 -0
  101. package/dist/runtime/runtime_module.cjs.map +1 -1
  102. package/dist/runtime/runtime_module.js +2 -0
  103. package/dist/runtime/runtime_module.js.map +1 -1
  104. package/dist/test-wood.cjs +150 -0
  105. package/dist/test-wood.cjs.map +1 -0
  106. package/dist/test-wood.d.cts +62 -0
  107. package/dist/test-wood.d.ts +62 -0
  108. package/dist/test-wood.js +115 -0
  109. package/dist/test-wood.js.map +1 -0
  110. package/dist/testing/browser.cjs +92 -163
  111. package/dist/testing/browser.cjs.map +1 -1
  112. package/dist/testing/browser.d.cts +14 -13
  113. package/dist/testing/browser.d.ts +14 -13
  114. package/dist/testing/browser.js +88 -162
  115. package/dist/testing/browser.js.map +1 -1
  116. package/dist/testing/create_controller_harness.d.cts +3 -1
  117. package/dist/testing/create_controller_harness.d.ts +3 -1
  118. package/dist/testing/create_router_bridge.cjs +19 -15
  119. package/dist/testing/create_router_bridge.cjs.map +1 -1
  120. package/dist/testing/create_router_bridge.d.cts +8 -1
  121. package/dist/testing/create_router_bridge.d.ts +8 -1
  122. package/dist/testing/create_router_bridge.js +18 -15
  123. package/dist/testing/create_router_bridge.js.map +1 -1
  124. package/dist/testing/index.cjs +12 -4
  125. package/dist/testing/index.cjs.map +1 -1
  126. package/dist/testing/index.d.cts +12 -5
  127. package/dist/testing/index.d.ts +12 -5
  128. package/dist/testing/index.js +9 -3
  129. package/dist/testing/index.js.map +1 -1
  130. package/dist/testing/native_browser/browser_pool.cjs +31 -0
  131. package/dist/testing/native_browser/browser_pool.cjs.map +1 -0
  132. package/dist/testing/native_browser/browser_pool.d.cts +1 -0
  133. package/dist/testing/native_browser/browser_pool.d.ts +1 -0
  134. package/dist/testing/native_browser/browser_pool.js +6 -0
  135. package/dist/testing/native_browser/browser_pool.js.map +1 -0
  136. package/dist/testing/native_browser/happy_dom_tab.cjs +107 -0
  137. package/dist/testing/native_browser/happy_dom_tab.cjs.map +1 -0
  138. package/dist/testing/native_browser/happy_dom_tab.d.cts +35 -0
  139. package/dist/testing/native_browser/happy_dom_tab.d.ts +35 -0
  140. package/dist/testing/native_browser/happy_dom_tab.js +73 -0
  141. package/dist/testing/native_browser/happy_dom_tab.js.map +1 -0
  142. package/dist/testing/native_browser/happy_dom_transfer.cjs +29 -0
  143. package/dist/testing/native_browser/happy_dom_transfer.cjs.map +1 -0
  144. package/dist/testing/native_browser/happy_dom_transfer.d.cts +1 -0
  145. package/dist/testing/native_browser/happy_dom_transfer.d.ts +1 -0
  146. package/dist/testing/native_browser/happy_dom_transfer.js +5 -0
  147. package/dist/testing/native_browser/happy_dom_transfer.js.map +1 -0
  148. package/dist/testing/native_browser/host_transport.cjs +89 -0
  149. package/dist/testing/native_browser/host_transport.cjs.map +1 -0
  150. package/dist/testing/native_browser/host_transport.d.cts +44 -0
  151. package/dist/testing/native_browser/host_transport.d.ts +44 -0
  152. package/dist/testing/native_browser/host_transport.js +65 -0
  153. package/dist/testing/native_browser/host_transport.js.map +1 -0
  154. package/dist/testing/native_browser/native_browser.types.cjs +17 -0
  155. package/dist/testing/native_browser/native_browser.types.cjs.map +1 -0
  156. package/dist/testing/native_browser/native_browser.types.d.cts +31 -0
  157. package/dist/testing/native_browser/native_browser.types.d.ts +31 -0
  158. package/dist/testing/native_browser/native_browser.types.js +1 -0
  159. package/dist/testing/native_browser/native_browser.types.js.map +1 -0
  160. package/dist/testing/native_browser/native_browser_environment.cjs +143 -0
  161. package/dist/testing/native_browser/native_browser_environment.cjs.map +1 -0
  162. package/dist/testing/native_browser/native_browser_environment.d.cts +36 -0
  163. package/dist/testing/native_browser/native_browser_environment.d.ts +36 -0
  164. package/dist/testing/native_browser/native_browser_environment.js +109 -0
  165. package/dist/testing/native_browser/native_browser_environment.js.map +1 -0
  166. package/dist/testing/native_browser/native_browser_lifecycle.cjs +31 -0
  167. package/dist/testing/native_browser/native_browser_lifecycle.cjs.map +1 -0
  168. package/dist/testing/native_browser/native_browser_lifecycle.d.cts +1 -0
  169. package/dist/testing/native_browser/native_browser_lifecycle.d.ts +1 -0
  170. package/dist/testing/native_browser/native_browser_lifecycle.js +6 -0
  171. package/dist/testing/native_browser/native_browser_lifecycle.js.map +1 -0
  172. package/dist/testing/native_browser/playwright_tab.cjs +124 -0
  173. package/dist/testing/native_browser/playwright_tab.cjs.map +1 -0
  174. package/dist/testing/native_browser/playwright_tab.d.cts +35 -0
  175. package/dist/testing/native_browser/playwright_tab.d.ts +35 -0
  176. package/dist/testing/native_browser/playwright_tab.js +100 -0
  177. package/dist/testing/native_browser/playwright_tab.js.map +1 -0
  178. package/dist/testing/native_browser/renderer_compilation.cjs +149 -0
  179. package/dist/testing/native_browser/renderer_compilation.cjs.map +1 -0
  180. package/dist/testing/native_browser/renderer_compilation.d.cts +35 -0
  181. package/dist/testing/native_browser/renderer_compilation.d.ts +35 -0
  182. package/dist/testing/native_browser/renderer_compilation.js +115 -0
  183. package/dist/testing/native_browser/renderer_compilation.js.map +1 -0
  184. package/dist/testing/native_browser/renderer_entry.cjs +148 -0
  185. package/dist/testing/native_browser/renderer_entry.cjs.map +1 -0
  186. package/dist/testing/native_browser/renderer_entry.d.cts +13 -0
  187. package/dist/testing/native_browser/renderer_entry.d.ts +13 -0
  188. package/dist/testing/native_browser/renderer_entry.js +114 -0
  189. package/dist/testing/native_browser/renderer_entry.js.map +1 -0
  190. package/dist/testing/registration_only_root.cjs +61 -5
  191. package/dist/testing/registration_only_root.cjs.map +1 -1
  192. package/dist/testing/registration_only_root.d.cts +25 -4
  193. package/dist/testing/registration_only_root.d.ts +25 -4
  194. package/dist/testing/registration_only_root.js +60 -5
  195. package/dist/testing/registration_only_root.js.map +1 -1
  196. package/dist/testing/runner_importer.cjs +75 -0
  197. package/dist/testing/runner_importer.cjs.map +1 -0
  198. package/dist/testing/runner_importer.d.cts +8 -0
  199. package/dist/testing/runner_importer.d.ts +8 -0
  200. package/dist/testing/runner_importer.js +41 -0
  201. package/dist/testing/runner_importer.js.map +1 -0
  202. package/dist/testing/test_wood_config.cjs +387 -272
  203. package/dist/testing/test_wood_config.cjs.map +1 -1
  204. package/dist/testing/test_wood_config.d.cts +19 -123
  205. package/dist/testing/test_wood_config.d.ts +19 -123
  206. package/dist/testing/test_wood_config.js +396 -270
  207. package/dist/testing/test_wood_config.js.map +1 -1
  208. package/dist/testing/wood_config_source.cjs +12 -0
  209. package/dist/testing/wood_config_source.cjs.map +1 -1
  210. package/dist/testing/wood_config_source.d.cts +10 -0
  211. package/dist/testing/wood_config_source.d.ts +10 -0
  212. package/dist/testing/wood_config_source.js +12 -0
  213. package/dist/testing/wood_config_source.js.map +1 -1
  214. package/dist/testing/wood_env_renderer.cjs +28 -3
  215. package/dist/testing/wood_env_renderer.cjs.map +1 -1
  216. package/dist/testing/wood_env_renderer.d.cts +13 -1
  217. package/dist/testing/wood_env_renderer.d.ts +13 -1
  218. package/dist/testing/wood_env_renderer.js +28 -3
  219. package/dist/testing/wood_env_renderer.js.map +1 -1
  220. package/dist/testing/wood_lazy_runtime.cjs +109 -0
  221. package/dist/testing/wood_lazy_runtime.cjs.map +1 -0
  222. package/dist/testing/wood_lazy_runtime.d.cts +36 -0
  223. package/dist/testing/wood_lazy_runtime.d.ts +36 -0
  224. package/dist/testing/wood_lazy_runtime.js +84 -0
  225. package/dist/testing/wood_lazy_runtime.js.map +1 -0
  226. package/dist/testing/wood_test_selection.cjs +34 -55
  227. package/dist/testing/wood_test_selection.cjs.map +1 -1
  228. package/dist/testing/wood_test_selection.d.cts +18 -24
  229. package/dist/testing/wood_test_selection.d.ts +18 -24
  230. package/dist/testing/wood_test_selection.js +30 -54
  231. package/dist/testing/wood_test_selection.js.map +1 -1
  232. package/dist/testing-api.cjs +17 -0
  233. package/dist/testing-api.cjs.map +1 -0
  234. package/dist/testing-api.d.cts +18 -0
  235. package/dist/testing-api.d.ts +18 -0
  236. package/dist/testing-api.js +1 -0
  237. package/dist/testing-api.js.map +1 -0
  238. package/dist/types/config.cjs.map +1 -1
  239. package/dist/types/config.d.cts +4 -0
  240. package/dist/types/config.d.ts +4 -0
  241. package/docs/native-browser-testing.md +164 -0
  242. package/package.json +31 -5
  243. package/src/components/NavigationShell.browser.test.ts +10 -10
  244. package/src/components/NavigationShell.cleanup.browser.test.ts +57 -0
  245. package/src/components/NavigationShell.scopes.browser.test.ts +5 -5
  246. package/src/components/NavigationShell.svelte +50 -24
  247. package/src/navigation/layout_preservation.ts +3 -1
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var renderer_entry_exports = {};
30
+ __export(renderer_entry_exports, {
31
+ nativeRendererEntry: () => nativeRendererEntry
32
+ });
33
+ module.exports = __toCommonJS(renderer_entry_exports);
34
+ var import_node_module = require("node:module");
35
+ var import_node_path = __toESM(require("node:path"), 1);
36
+ function nativeRendererEntry(source) {
37
+ const operations = [...source.operations];
38
+ const reflection = (0, import_node_module.createRequire)(import_node_path.default.join(source.configDir, "package.json")).resolve("reflect-metadata");
39
+ const rendererCompose = source.hookModule(source.config.renderer.compose);
40
+ const composeImport = rendererCompose ? `import configureRenderer from ${JSON.stringify(rendererCompose)};` : "const configureRenderer = null;";
41
+ return `import ${JSON.stringify(reflection)};
42
+ import { flushSync, tick, unmount } from 'svelte';
43
+ import { buildTransportBridge } from '@noego/wood/transport';
44
+ import { getContainer, TraceProviderConfiguration, flushRendererLogs } from '@noego/wood/client';
45
+ import * as generated from './app.ts';
46
+ ${composeImport}
47
+
48
+ const route = window.__woodNativeRoute;
49
+ const callbacks = new Map();
50
+ const subscriptionChanges = new Set();
51
+ const requests = new Set();
52
+ let mounted;
53
+ let closing;
54
+ function track(set, task) {
55
+ const promise = Promise.resolve(task);
56
+ set.add(promise);
57
+ promise.then(() => set.delete(promise), () => set.delete(promise));
58
+ return promise;
59
+ }
60
+ async function subscriptionsReady() {
61
+ while (subscriptionChanges.size) await Promise.all([...subscriptionChanges]);
62
+ }
63
+ const transport = {
64
+ invoke(channel, payload) {
65
+ return track(requests, (async () => {
66
+ await subscriptionsReady();
67
+ return window.__woodNativeInvoke(channel, payload === undefined ? {} : payload);
68
+ })());
69
+ },
70
+ subscribe(channel, callback) {
71
+ let listeners = callbacks.get(channel);
72
+ if (!listeners) {
73
+ listeners = new Set();
74
+ callbacks.set(channel, listeners);
75
+ track(subscriptionChanges, window.__woodNativeSubscribe(channel, true));
76
+ }
77
+ listeners.add(callback);
78
+ return () => {
79
+ listeners.delete(callback);
80
+ if (listeners.size === 0) {
81
+ callbacks.delete(channel);
82
+ track(subscriptionChanges, window.__woodNativeSubscribe(channel, false));
83
+ }
84
+ };
85
+ },
86
+ };
87
+ window.__woodNativeEmit = (channel, payload) => {
88
+ for (const callback of callbacks.get(channel) ?? []) callback(payload);
89
+ };
90
+ const bridge = buildTransportBridge(new Map(${JSON.stringify(operations)}), transport).bridge;
91
+ bridge.__window = { current: async () => ({ok: true, windowId: route.page.split('.')[0], defaultRoute: route.page}) };
92
+ bridge.__woodConfig = async () => ({trace: {enabled: true, minLevel: 'debug'}});
93
+ bridge.__trace = payload => track(requests, window.__woodNativeTrace(payload));
94
+ // These are preload infrastructure channels, never product operation replacements.
95
+ bridge.__logBatch = batch => track(requests, window.__woodNativeLog(batch));
96
+ window.__NOEGO_WOOD_BRIDGE__ = bridge;
97
+
98
+ window.__woodNativeFlush = async () => {
99
+ await tick();
100
+ flushSync();
101
+ await subscriptionsReady();
102
+ flushRendererLogs();
103
+ while (requests.size) await Promise.all([...requests]);
104
+ await tick();
105
+ flushSync();
106
+ };
107
+ window.__woodNativeDispose = () => closing ??= (async () => {
108
+ const errors = [];
109
+ const attempt = async fn => {try {await fn();} catch (error) {errors.push(error);}};
110
+ await attempt(() => mounted?.disposeNavigation?.());
111
+ await attempt(() => mounted && unmount(mounted));
112
+ await attempt(() => window.__woodNativeFlush());
113
+ await attempt(() => getContainer().dispose());
114
+ callbacks.clear();
115
+ if (errors.length) {
116
+ const describe = (error, depth = 0) => {
117
+ const message = error?.message ?? String(error);
118
+ const nested = error?.errors ?? error?.failures?.map(failure => failure.error);
119
+ return depth < 4 && Array.isArray(nested)
120
+ ? message + ': ' + nested.map(value => describe(value, depth + 1)).join('; ')
121
+ : message;
122
+ };
123
+ throw new AggregateError(errors, 'Native Wood renderer teardown failed: ' + errors.map(error => describe(error)).join('; '));
124
+ }
125
+ })();
126
+ window.__woodNativeReady = (async () => {
127
+ const container = getContainer();
128
+ if (configureRenderer) await configureRenderer(container);
129
+ const config = await container.get(TraceProviderConfiguration);
130
+ config.configureRuntime('renderer');
131
+ config.configureEnabled(true);
132
+ config.configureMinLevel('debug');
133
+ mounted = await generated.app(document.getElementById('app'), {params: route.params, query: route.query});
134
+ await tick();
135
+ flushSync();
136
+ // Subscribe before open() resolves: do not invent event replay to hide a race.
137
+ await subscriptionsReady();
138
+ })();
139
+ // A host always awaits readiness. Attach a handler now so an immediate mount
140
+ // failure cannot become an unhandled rejection before the host reads it.
141
+ window.__woodNativeReady.catch(() => {});
142
+ `;
143
+ }
144
+ // Annotate the CommonJS export names for ESM import in node:
145
+ 0 && (module.exports = {
146
+ nativeRendererEntry
147
+ });
148
+ //# sourceMappingURL=renderer_entry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/testing/native_browser/renderer_entry.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type { WoodConfigSource } from '../wood_config_source.js';\n\n/**\n * Identical renderer bootstrap for Chromium and happy-dom's isolated VM realm.\n * Production codegen owns controllers, layouts, data loaders and navigation.\n * Only the host transport differs. No components or Node globals are faked.\n */\nexport function nativeRendererEntry(source: WoodConfigSource): string {\n const operations = [...source.operations];\n const reflection = createRequire(path.join(source.configDir, 'package.json')).resolve('reflect-metadata');\n const rendererCompose = source.hookModule(source.config.renderer.compose);\n const composeImport = rendererCompose\n ? `import configureRenderer from ${JSON.stringify(rendererCompose)};`\n : 'const configureRenderer = null;';\n return `import ${JSON.stringify(reflection)};\nimport { flushSync, tick, unmount } from 'svelte';\nimport { buildTransportBridge } from '@noego/wood/transport';\nimport { getContainer, TraceProviderConfiguration, flushRendererLogs } from '@noego/wood/client';\nimport * as generated from './app.ts';\n${composeImport}\n\nconst route = window.__woodNativeRoute;\nconst callbacks = new Map();\nconst subscriptionChanges = new Set();\nconst requests = new Set();\nlet mounted;\nlet closing;\nfunction track(set, task) {\n const promise = Promise.resolve(task);\n set.add(promise);\n promise.then(() => set.delete(promise), () => set.delete(promise));\n return promise;\n}\nasync function subscriptionsReady() {\n while (subscriptionChanges.size) await Promise.all([...subscriptionChanges]);\n}\nconst transport = {\n invoke(channel, payload) {\n return track(requests, (async () => {\n await subscriptionsReady();\n return window.__woodNativeInvoke(channel, payload === undefined ? {} : payload);\n })());\n },\n subscribe(channel, callback) {\n let listeners = callbacks.get(channel);\n if (!listeners) {\n listeners = new Set();\n callbacks.set(channel, listeners);\n track(subscriptionChanges, window.__woodNativeSubscribe(channel, true));\n }\n listeners.add(callback);\n return () => {\n listeners.delete(callback);\n if (listeners.size === 0) {\n callbacks.delete(channel);\n track(subscriptionChanges, window.__woodNativeSubscribe(channel, false));\n }\n };\n },\n};\nwindow.__woodNativeEmit = (channel, payload) => {\n for (const callback of callbacks.get(channel) ?? []) callback(payload);\n};\nconst bridge = buildTransportBridge(new Map(${JSON.stringify(operations)}), transport).bridge;\nbridge.__window = { current: async () => ({ok: true, windowId: route.page.split('.')[0], defaultRoute: route.page}) };\nbridge.__woodConfig = async () => ({trace: {enabled: true, minLevel: 'debug'}});\nbridge.__trace = payload => track(requests, window.__woodNativeTrace(payload));\n// These are preload infrastructure channels, never product operation replacements.\nbridge.__logBatch = batch => track(requests, window.__woodNativeLog(batch));\nwindow.__NOEGO_WOOD_BRIDGE__ = bridge;\n\nwindow.__woodNativeFlush = async () => {\n await tick();\n flushSync();\n await subscriptionsReady();\n flushRendererLogs();\n while (requests.size) await Promise.all([...requests]);\n await tick();\n flushSync();\n};\nwindow.__woodNativeDispose = () => closing ??= (async () => {\n const errors = [];\n const attempt = async fn => {try {await fn();} catch (error) {errors.push(error);}};\n await attempt(() => mounted?.disposeNavigation?.());\n await attempt(() => mounted && unmount(mounted));\n await attempt(() => window.__woodNativeFlush());\n await attempt(() => getContainer().dispose());\n callbacks.clear();\n if (errors.length) {\n const describe = (error, depth = 0) => {\n const message = error?.message ?? String(error);\n const nested = error?.errors ?? error?.failures?.map(failure => failure.error);\n return depth < 4 && Array.isArray(nested)\n ? message + ': ' + nested.map(value => describe(value, depth + 1)).join('; ')\n : message;\n };\n throw new AggregateError(errors, 'Native Wood renderer teardown failed: ' + errors.map(error => describe(error)).join('; '));\n }\n})();\nwindow.__woodNativeReady = (async () => {\n const container = getContainer();\n if (configureRenderer) await configureRenderer(container);\n const config = await container.get(TraceProviderConfiguration);\n config.configureRuntime('renderer');\n config.configureEnabled(true);\n config.configureMinLevel('debug');\n mounted = await generated.app(document.getElementById('app'), {params: route.params, query: route.query});\n await tick();\n flushSync();\n // Subscribe before open() resolves: do not invent event replay to hide a race.\n await subscriptionsReady();\n})();\n// A host always awaits readiness. Attach a handler now so an immediate mount\n// failure cannot become an unhandled rejection before the host reads it.\nwindow.__woodNativeReady.catch(() => {});\n`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAA8B;AAC9B,uBAAiB;AAQV,SAAS,oBAAoB,QAAkC;AACpE,QAAM,aAAa,CAAC,GAAG,OAAO,UAAU;AACxC,QAAM,iBAAa,kCAAc,iBAAAA,QAAK,KAAK,OAAO,WAAW,cAAc,CAAC,EAAE,QAAQ,kBAAkB;AACxG,QAAM,kBAAkB,OAAO,WAAW,OAAO,OAAO,SAAS,OAAO;AACxE,QAAM,gBAAgB,kBAClB,iCAAiC,KAAK,UAAU,eAAe,CAAC,MAChE;AACJ,SAAO,UAAU,KAAK,UAAU,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAK3C,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8CA4C+B,KAAK,UAAU,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqDxE;","names":["path"]}
@@ -0,0 +1,13 @@
1
+ import { WoodConfigSource } from '../wood_config_source.cjs';
2
+ import '../../types/config.cjs';
3
+ import '../../types/operations.cjs';
4
+ import '../../types/views.cjs';
5
+
6
+ /**
7
+ * Identical renderer bootstrap for Chromium and happy-dom's isolated VM realm.
8
+ * Production codegen owns controllers, layouts, data loaders and navigation.
9
+ * Only the host transport differs. No components or Node globals are faked.
10
+ */
11
+ declare function nativeRendererEntry(source: WoodConfigSource): string;
12
+
13
+ export { nativeRendererEntry };
@@ -0,0 +1,13 @@
1
+ import { WoodConfigSource } from '../wood_config_source.js';
2
+ import '../../types/config.js';
3
+ import '../../types/operations.js';
4
+ import '../../types/views.js';
5
+
6
+ /**
7
+ * Identical renderer bootstrap for Chromium and happy-dom's isolated VM realm.
8
+ * Production codegen owns controllers, layouts, data loaders and navigation.
9
+ * Only the host transport differs. No components or Node globals are faked.
10
+ */
11
+ declare function nativeRendererEntry(source: WoodConfigSource): string;
12
+
13
+ export { nativeRendererEntry };
@@ -0,0 +1,114 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ function nativeRendererEntry(source) {
4
+ const operations = [...source.operations];
5
+ const reflection = createRequire(path.join(source.configDir, "package.json")).resolve("reflect-metadata");
6
+ const rendererCompose = source.hookModule(source.config.renderer.compose);
7
+ const composeImport = rendererCompose ? `import configureRenderer from ${JSON.stringify(rendererCompose)};` : "const configureRenderer = null;";
8
+ return `import ${JSON.stringify(reflection)};
9
+ import { flushSync, tick, unmount } from 'svelte';
10
+ import { buildTransportBridge } from '@noego/wood/transport';
11
+ import { getContainer, TraceProviderConfiguration, flushRendererLogs } from '@noego/wood/client';
12
+ import * as generated from './app.ts';
13
+ ${composeImport}
14
+
15
+ const route = window.__woodNativeRoute;
16
+ const callbacks = new Map();
17
+ const subscriptionChanges = new Set();
18
+ const requests = new Set();
19
+ let mounted;
20
+ let closing;
21
+ function track(set, task) {
22
+ const promise = Promise.resolve(task);
23
+ set.add(promise);
24
+ promise.then(() => set.delete(promise), () => set.delete(promise));
25
+ return promise;
26
+ }
27
+ async function subscriptionsReady() {
28
+ while (subscriptionChanges.size) await Promise.all([...subscriptionChanges]);
29
+ }
30
+ const transport = {
31
+ invoke(channel, payload) {
32
+ return track(requests, (async () => {
33
+ await subscriptionsReady();
34
+ return window.__woodNativeInvoke(channel, payload === undefined ? {} : payload);
35
+ })());
36
+ },
37
+ subscribe(channel, callback) {
38
+ let listeners = callbacks.get(channel);
39
+ if (!listeners) {
40
+ listeners = new Set();
41
+ callbacks.set(channel, listeners);
42
+ track(subscriptionChanges, window.__woodNativeSubscribe(channel, true));
43
+ }
44
+ listeners.add(callback);
45
+ return () => {
46
+ listeners.delete(callback);
47
+ if (listeners.size === 0) {
48
+ callbacks.delete(channel);
49
+ track(subscriptionChanges, window.__woodNativeSubscribe(channel, false));
50
+ }
51
+ };
52
+ },
53
+ };
54
+ window.__woodNativeEmit = (channel, payload) => {
55
+ for (const callback of callbacks.get(channel) ?? []) callback(payload);
56
+ };
57
+ const bridge = buildTransportBridge(new Map(${JSON.stringify(operations)}), transport).bridge;
58
+ bridge.__window = { current: async () => ({ok: true, windowId: route.page.split('.')[0], defaultRoute: route.page}) };
59
+ bridge.__woodConfig = async () => ({trace: {enabled: true, minLevel: 'debug'}});
60
+ bridge.__trace = payload => track(requests, window.__woodNativeTrace(payload));
61
+ // These are preload infrastructure channels, never product operation replacements.
62
+ bridge.__logBatch = batch => track(requests, window.__woodNativeLog(batch));
63
+ window.__NOEGO_WOOD_BRIDGE__ = bridge;
64
+
65
+ window.__woodNativeFlush = async () => {
66
+ await tick();
67
+ flushSync();
68
+ await subscriptionsReady();
69
+ flushRendererLogs();
70
+ while (requests.size) await Promise.all([...requests]);
71
+ await tick();
72
+ flushSync();
73
+ };
74
+ window.__woodNativeDispose = () => closing ??= (async () => {
75
+ const errors = [];
76
+ const attempt = async fn => {try {await fn();} catch (error) {errors.push(error);}};
77
+ await attempt(() => mounted?.disposeNavigation?.());
78
+ await attempt(() => mounted && unmount(mounted));
79
+ await attempt(() => window.__woodNativeFlush());
80
+ await attempt(() => getContainer().dispose());
81
+ callbacks.clear();
82
+ if (errors.length) {
83
+ const describe = (error, depth = 0) => {
84
+ const message = error?.message ?? String(error);
85
+ const nested = error?.errors ?? error?.failures?.map(failure => failure.error);
86
+ return depth < 4 && Array.isArray(nested)
87
+ ? message + ': ' + nested.map(value => describe(value, depth + 1)).join('; ')
88
+ : message;
89
+ };
90
+ throw new AggregateError(errors, 'Native Wood renderer teardown failed: ' + errors.map(error => describe(error)).join('; '));
91
+ }
92
+ })();
93
+ window.__woodNativeReady = (async () => {
94
+ const container = getContainer();
95
+ if (configureRenderer) await configureRenderer(container);
96
+ const config = await container.get(TraceProviderConfiguration);
97
+ config.configureRuntime('renderer');
98
+ config.configureEnabled(true);
99
+ config.configureMinLevel('debug');
100
+ mounted = await generated.app(document.getElementById('app'), {params: route.params, query: route.query});
101
+ await tick();
102
+ flushSync();
103
+ // Subscribe before open() resolves: do not invent event replay to hide a race.
104
+ await subscriptionsReady();
105
+ })();
106
+ // A host always awaits readiness. Attach a handler now so an immediate mount
107
+ // failure cannot become an unhandled rejection before the host reads it.
108
+ window.__woodNativeReady.catch(() => {});
109
+ `;
110
+ }
111
+ export {
112
+ nativeRendererEntry
113
+ };
114
+ //# sourceMappingURL=renderer_entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/testing/native_browser/renderer_entry.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type { WoodConfigSource } from '../wood_config_source.js';\n\n/**\n * Identical renderer bootstrap for Chromium and happy-dom's isolated VM realm.\n * Production codegen owns controllers, layouts, data loaders and navigation.\n * Only the host transport differs. No components or Node globals are faked.\n */\nexport function nativeRendererEntry(source: WoodConfigSource): string {\n const operations = [...source.operations];\n const reflection = createRequire(path.join(source.configDir, 'package.json')).resolve('reflect-metadata');\n const rendererCompose = source.hookModule(source.config.renderer.compose);\n const composeImport = rendererCompose\n ? `import configureRenderer from ${JSON.stringify(rendererCompose)};`\n : 'const configureRenderer = null;';\n return `import ${JSON.stringify(reflection)};\nimport { flushSync, tick, unmount } from 'svelte';\nimport { buildTransportBridge } from '@noego/wood/transport';\nimport { getContainer, TraceProviderConfiguration, flushRendererLogs } from '@noego/wood/client';\nimport * as generated from './app.ts';\n${composeImport}\n\nconst route = window.__woodNativeRoute;\nconst callbacks = new Map();\nconst subscriptionChanges = new Set();\nconst requests = new Set();\nlet mounted;\nlet closing;\nfunction track(set, task) {\n const promise = Promise.resolve(task);\n set.add(promise);\n promise.then(() => set.delete(promise), () => set.delete(promise));\n return promise;\n}\nasync function subscriptionsReady() {\n while (subscriptionChanges.size) await Promise.all([...subscriptionChanges]);\n}\nconst transport = {\n invoke(channel, payload) {\n return track(requests, (async () => {\n await subscriptionsReady();\n return window.__woodNativeInvoke(channel, payload === undefined ? {} : payload);\n })());\n },\n subscribe(channel, callback) {\n let listeners = callbacks.get(channel);\n if (!listeners) {\n listeners = new Set();\n callbacks.set(channel, listeners);\n track(subscriptionChanges, window.__woodNativeSubscribe(channel, true));\n }\n listeners.add(callback);\n return () => {\n listeners.delete(callback);\n if (listeners.size === 0) {\n callbacks.delete(channel);\n track(subscriptionChanges, window.__woodNativeSubscribe(channel, false));\n }\n };\n },\n};\nwindow.__woodNativeEmit = (channel, payload) => {\n for (const callback of callbacks.get(channel) ?? []) callback(payload);\n};\nconst bridge = buildTransportBridge(new Map(${JSON.stringify(operations)}), transport).bridge;\nbridge.__window = { current: async () => ({ok: true, windowId: route.page.split('.')[0], defaultRoute: route.page}) };\nbridge.__woodConfig = async () => ({trace: {enabled: true, minLevel: 'debug'}});\nbridge.__trace = payload => track(requests, window.__woodNativeTrace(payload));\n// These are preload infrastructure channels, never product operation replacements.\nbridge.__logBatch = batch => track(requests, window.__woodNativeLog(batch));\nwindow.__NOEGO_WOOD_BRIDGE__ = bridge;\n\nwindow.__woodNativeFlush = async () => {\n await tick();\n flushSync();\n await subscriptionsReady();\n flushRendererLogs();\n while (requests.size) await Promise.all([...requests]);\n await tick();\n flushSync();\n};\nwindow.__woodNativeDispose = () => closing ??= (async () => {\n const errors = [];\n const attempt = async fn => {try {await fn();} catch (error) {errors.push(error);}};\n await attempt(() => mounted?.disposeNavigation?.());\n await attempt(() => mounted && unmount(mounted));\n await attempt(() => window.__woodNativeFlush());\n await attempt(() => getContainer().dispose());\n callbacks.clear();\n if (errors.length) {\n const describe = (error, depth = 0) => {\n const message = error?.message ?? String(error);\n const nested = error?.errors ?? error?.failures?.map(failure => failure.error);\n return depth < 4 && Array.isArray(nested)\n ? message + ': ' + nested.map(value => describe(value, depth + 1)).join('; ')\n : message;\n };\n throw new AggregateError(errors, 'Native Wood renderer teardown failed: ' + errors.map(error => describe(error)).join('; '));\n }\n})();\nwindow.__woodNativeReady = (async () => {\n const container = getContainer();\n if (configureRenderer) await configureRenderer(container);\n const config = await container.get(TraceProviderConfiguration);\n config.configureRuntime('renderer');\n config.configureEnabled(true);\n config.configureMinLevel('debug');\n mounted = await generated.app(document.getElementById('app'), {params: route.params, query: route.query});\n await tick();\n flushSync();\n // Subscribe before open() resolves: do not invent event replay to hide a race.\n await subscriptionsReady();\n})();\n// A host always awaits readiness. Attach a handler now so an immediate mount\n// failure cannot become an unhandled rejection before the host reads it.\nwindow.__woodNativeReady.catch(() => {});\n`;\n}\n"],"mappings":"AAAA,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AAQV,SAAS,oBAAoB,QAAkC;AACpE,QAAM,aAAa,CAAC,GAAG,OAAO,UAAU;AACxC,QAAM,aAAa,cAAc,KAAK,KAAK,OAAO,WAAW,cAAc,CAAC,EAAE,QAAQ,kBAAkB;AACxG,QAAM,kBAAkB,OAAO,WAAW,OAAO,OAAO,SAAS,OAAO;AACxE,QAAM,gBAAgB,kBAClB,iCAAiC,KAAK,UAAU,eAAe,CAAC,MAChE;AACJ,SAAO,UAAU,KAAK,UAAU,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAK3C,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8CA4C+B,KAAK,UAAU,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqDxE;","names":[]}
@@ -18,14 +18,34 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var registration_only_root_exports = {};
20
20
  __export(registration_only_root_exports, {
21
+ APPLICATION_TEST_MARK: () => APPLICATION_TEST_MARK,
21
22
  RegistrationOnlyRoot: () => RegistrationOnlyRoot,
22
23
  TestWoodCleanupError: () => TestWoodCleanupError,
23
24
  buildRecordedEnvironment: () => buildRecordedEnvironment,
24
25
  cleanupAll: () => cleanupAll
25
26
  });
26
27
  module.exports = __toCommonJS(registration_only_root_exports);
28
+ var import_ioc = require("@noego/ioc");
27
29
  var import_get_container = require("../runtime/get_container.js");
28
30
  var import_test_wood = require("./test_wood.cjs");
31
+ const APPLICATION_TEST_MARK = /* @__PURE__ */ Symbol.for("@noego/testing.application.v1");
32
+ function refine(binding, lifetime) {
33
+ if (lifetime === "singleton") binding.singleton();
34
+ else if (lifetime === "scoped") binding.scoped();
35
+ else if (lifetime === "transient") binding.transient();
36
+ }
37
+ function lifetimeOf(options) {
38
+ switch (options?.loadAs) {
39
+ case import_ioc.LoadAs.Singleton:
40
+ return "singleton";
41
+ case import_ioc.LoadAs.Scoped:
42
+ return "scoped";
43
+ case import_ioc.LoadAs.Transient:
44
+ return "transient";
45
+ default:
46
+ return void 0;
47
+ }
48
+ }
29
49
  function errorMessage(error) {
30
50
  const message = error instanceof Error ? error.message : String(error);
31
51
  const nested = error?.failures;
@@ -54,12 +74,24 @@ async function cleanupAll(message, actions) {
54
74
  if (failures.length) throw new TestWoodCleanupError(message, failures);
55
75
  }
56
76
  class RegistrationOnlyRoot {
57
- constructor(hookName) {
77
+ /**
78
+ * @param hookName the app hook that may register on this root
79
+ * @param applicationTest the config path identifying the application test;
80
+ * registered on the ACTUAL root under the shared
81
+ * `@noego/testing.application.v1` marker before any hook runs, so
82
+ * frameworks reading the IoC async-context ABI (e.g. Proper's migration
83
+ * guard) recognize Wood startup/invocation as an application test.
84
+ * Production roots are never marked.
85
+ */
86
+ constructor(hookName, applicationTest) {
58
87
  this.hookName = hookName;
59
88
  this.owners = /* @__PURE__ */ new Set();
60
89
  this.actualRoot = (0, import_get_container.createWoodRoot)();
61
90
  this.target = null;
62
91
  this.replayed = false;
92
+ /** Every production registration as a declarative binding (the shared overlay catalog input). */
93
+ this.declarations = [];
94
+ this.actualRoot.registerFunction(APPLICATION_TEST_MARK, () => applicationTest, { owned: false });
63
95
  const registrationOnly = this;
64
96
  this.root = new Proxy(this.actualRoot, {
65
97
  get(_target, property) {
@@ -67,6 +99,8 @@ class RegistrationOnlyRoot {
67
99
  return (classDefinition, options) => {
68
100
  registrationOnly.assertRecording("registerClass");
69
101
  registrationOnly.declare(classDefinition);
102
+ const lifetime = lifetimeOf(options);
103
+ registrationOnly.declarations.push((bind) => refine(bind(classDefinition).toSelf(), lifetime));
70
104
  registrationOnly.actualRoot.registerClass(classDefinition, options);
71
105
  };
72
106
  }
@@ -74,9 +108,10 @@ class RegistrationOnlyRoot {
74
108
  return (token, factory, options) => {
75
109
  registrationOnly.assertRecording("registerFunction");
76
110
  registrationOnly.declare(token);
77
- for (const dependency of options?.param ?? []) {
78
- registrationOnly.declare(dependency);
79
- }
111
+ const dependencies = [...options?.param ?? []];
112
+ for (const dependency of dependencies) registrationOnly.declare(dependency);
113
+ const lifetime = lifetimeOf(options);
114
+ registrationOnly.declarations.push((bind) => refine(bind(token).toFactory(factory, dependencies), lifetime));
80
115
  registrationOnly.actualRoot.registerFunction(token, factory, options);
81
116
  };
82
117
  }
@@ -114,6 +149,25 @@ class RegistrationOnlyRoot {
114
149
  this.declare(injected?.has(index) ? injected.get(index) : design[index]);
115
150
  }
116
151
  }
152
+ /**
153
+ * Declare a token this root binds later (at runtime initialization) as a
154
+ * provider so the shared overlay catalog can resolve replacements for it.
155
+ * The placeholder factory is description only and never resolves.
156
+ */
157
+ declareBinding(token, lifetime) {
158
+ this.declare(token);
159
+ const hookName = this.hookName;
160
+ this.declarations.push((bind) => refine(bind(token).toFactory(() => {
161
+ throw new import_test_wood.TestWoodError(`${hookName} declared-only binding ${String(token)} has no production factory`);
162
+ }, []), lifetime));
163
+ }
164
+ /** The recorded production registrations as a declarative module (description only, never built). */
165
+ module() {
166
+ const declarations = [...this.declarations];
167
+ return (0, import_ioc.defineModule)(`@noego/wood/testing/${this.hookName}`, (bind) => {
168
+ for (const declaration of declarations) declaration(bind);
169
+ });
170
+ }
117
171
  replay(root) {
118
172
  if (this.replayed) throw new import_test_wood.TestWoodError(`${this.hookName} registrations were already replayed`);
119
173
  this.replayed = true;
@@ -131,11 +185,12 @@ class RegistrationOnlyRoot {
131
185
  }
132
186
  }
133
187
  }
134
- async function buildRecordedEnvironment(builder, recorded, owner) {
188
+ async function buildRecordedEnvironment(builder, recorded, owner, borrowedTokens = []) {
135
189
  const partial = { root: recorded.actualRoot };
136
190
  try {
137
191
  const environment = await builder.build({
138
192
  root: recorded.actualRoot,
193
+ borrowedTokens,
139
194
  prepare: (freshRoot) => {
140
195
  partial.root = freshRoot;
141
196
  recorded.replay(freshRoot);
@@ -160,6 +215,7 @@ async function buildRecordedEnvironment(builder, recorded, owner) {
160
215
  }
161
216
  // Annotate the CommonJS export names for ESM import in node:
162
217
  0 && (module.exports = {
218
+ APPLICATION_TEST_MARK,
163
219
  RegistrationOnlyRoot,
164
220
  TestWoodCleanupError,
165
221
  buildRecordedEnvironment,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/registration_only_root.ts"],"sourcesContent":["import type { IContainer } from '@noego/ioc';\nimport { createWoodRoot } from '../runtime/get_container.js';\nimport type { TestEnvironment, TestIocBuilder } from '@noego/testing';\nimport { TestWoodError } from './test_wood.cjs';\n\n\nexport interface CleanupFailure {\n readonly owner: string;\n readonly error: unknown;\n}\n\nfunction errorMessage(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n const nested = (error as { failures?: Array<{ error: unknown }> } | null)?.failures;\n return Array.isArray(nested) ? message + ': ' + nested.map(failure => errorMessage(failure.error)).join('; ') : message;\n}\n\n/**\n * Error raised after every requested owner has been given a chance to clean up.\n * The structured failures are retained for assertions and callers that need to\n * inspect the original errors.\n */\nexport class TestWoodCleanupError extends TestWoodError {\n readonly failures: readonly CleanupFailure[];\n\n constructor(message: string, failures: readonly CleanupFailure[], cause?: unknown) {\n super(\n `${message} (${failures.length} failure(s)):\\n ` +\n failures.map(({ owner, error }) => `${owner}: ${errorMessage(error)}`).join('\\n '),\n );\n this.name = 'TestWoodCleanupError';\n this.failures = failures;\n if (cause !== undefined) (this as Error & { cause?: unknown }).cause = cause;\n }\n}\n\n/** Run every cleanup action in order, collecting rather than short-circuiting. */\nexport async function cleanupAll(\n message: string,\n actions: ReadonlyArray<readonly [owner: string, action: () => void | Promise<void>]>,\n): Promise<void> {\n const failures: CleanupFailure[] = [];\n for (const [owner, action] of actions) {\n try {\n await action();\n } catch (error) {\n failures.push({ owner, error });\n }\n }\n if (failures.length) throw new TestWoodCleanupError(message, failures);\n}\n\n/**\n * A registration-only view of the real root owned by this environment.\n *\n * Registrations land immediately on that root; shared @noego/testing applies\n * scenario overlays to it without replay. Resolution is unavailable until it has been\n * built and the test overlays and method decorator are installed.\n */\nexport class RegistrationOnlyRoot {\n readonly owners = new Set<unknown>();\n readonly root: IContainer;\n\n readonly actualRoot = createWoodRoot();\n private target: IContainer | null = null;\n private replayed = false;\n\n constructor(private readonly hookName: string) {\n const registrationOnly = this;\n this.root = new Proxy(this.actualRoot, {\n get(_target, property) {\n if (property === 'registerClass') {\n return (classDefinition: new (...args: any[]) => unknown, options?: unknown): void => {\n registrationOnly.assertRecording('registerClass');\n registrationOnly.declare(classDefinition);\n registrationOnly.actualRoot.registerClass(classDefinition, options as never);\n };\n }\n if (property === 'registerFunction') {\n return (token: unknown, factory: (...args: any[]) => unknown, options?: unknown): void => {\n registrationOnly.assertRecording('registerFunction');\n registrationOnly.declare(token);\n for (const dependency of (options as { param?: unknown[] } | undefined)?.param ?? []) {\n registrationOnly.declare(dependency);\n }\n registrationOnly.actualRoot.registerFunction(token, factory, options as never);\n };\n }\n if (property === 'isRegistered') {\n return (token: unknown): boolean =>\n registrationOnly.target?.isRegistered(token) ?? registrationOnly.owners.has(token);\n }\n if (property === 'isDisposed') {\n return (): boolean => registrationOnly.target?.isDisposed() ?? false;\n }\n\n const live = registrationOnly.target;\n if (live) {\n const value = Reflect.get(live as object, property, live);\n return typeof value === 'function' ? value.bind(live) : value;\n }\n\n return (..._args: unknown[]): never => {\n const method = String(property);\n throw new TestWoodError(\n `${registrationOnly.hookName} may only register dependencies before test overlays; ` +\n `${method}() attempted eager resolution or root mutation. Move resolution to start(), ` +\n 'a page lifecycle, or the built test environment.',\n );\n };\n },\n });\n }\n\n declare(token: unknown): void {\n if (this.owners.has(token)) return;\n this.owners.add(token);\n if (typeof token !== 'function') return;\n const injected = (Reflect as any).getMetadata?.(\n Symbol.for('ioc:inject:tokens'),\n token,\n ) as Map<number, unknown> | undefined;\n const design = (Reflect as any).getMetadata?.('design:paramtypes', token) ?? [];\n const indices = new Set<number>([...design.keys(), ...(injected?.keys() ?? [])]);\n for (const index of indices) {\n this.declare(injected?.has(index) ? injected.get(index) : design[index]);\n }\n }\n\n replay(root: IContainer): void {\n if (this.replayed) throw new TestWoodError(`${this.hookName} registrations were already replayed`);\n this.replayed = true;\n if (root !== this.actualRoot) throw new TestWoodError('testWood requires @noego/testing with build({ root }) support; update the shared testing package.');\n }\n\n bind(root: IContainer): void {\n if (!this.replayed) throw new TestWoodError(`${this.hookName} registrations must be replayed before binding`);\n this.target = root;\n }\n\n private assertRecording(method: string): void {\n if (this.replayed) {\n throw new TestWoodError(\n `${this.hookName} cannot call ${method}() after test overlays have been installed`,\n );\n }\n }\n}\n\n/**\n * Materialize shared @noego/testing overlays on the same production root,\n * disposing the partially built root if materialization fails.\n */\nexport async function buildRecordedEnvironment(\n builder: TestIocBuilder,\n recorded: RegistrationOnlyRoot,\n owner: string,\n): Promise<TestEnvironment> {\n const partial: { root?: IContainer } = { root: recorded.actualRoot };\n try {\n const environment = await builder.build({\n root: recorded.actualRoot,\n prepare: (freshRoot) => {\n partial.root = freshRoot;\n recorded.replay(freshRoot);\n },\n });\n recorded.bind(environment.root);\n return environment;\n } catch (error) {\n const failures: CleanupFailure[] = [];\n if (partial.root) {\n try {\n await partial.root.dispose();\n } catch (cleanupError) {\n failures.push({ owner: `${owner} root rollback`, error: cleanupError });\n }\n }\n if (failures.length) {\n throw new TestWoodCleanupError(`${owner} build failed and rollback failed`, failures, error);\n }\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/testing/registration_only_root.ts"],"sourcesContent":["import type { ApplicationModule, BindFunction, BindingRefinement, IContainer, Lifetime } from '@noego/ioc';\nimport { defineModule, LoadAs } from '@noego/ioc';\nimport { createWoodRoot } from '../runtime/get_container.js';\nimport type { TestEnvironment, TestIocBuilder } from '@noego/testing';\nimport { TestWoodError } from './test_wood.cjs';\n\ntype ContainerOptions = { loadAs?: LoadAs; param?: unknown[] };\n\n/** Shared application-test marker (same symbol `@noego/app` registers). */\nexport const APPLICATION_TEST_MARK = Symbol.for('@noego/testing.application.v1');\n\nfunction refine(binding: BindingRefinement, lifetime: Lifetime | undefined): void {\n if (lifetime === 'singleton') binding.singleton();\n else if (lifetime === 'scoped') binding.scoped();\n else if (lifetime === 'transient') binding.transient();\n}\n\nfunction lifetimeOf(options: Partial<ContainerOptions> | undefined): Lifetime | undefined {\n switch (options?.loadAs) {\n case LoadAs.Singleton: return 'singleton';\n case LoadAs.Scoped: return 'scoped';\n case LoadAs.Transient: return 'transient';\n default: return undefined;\n }\n}\n\n\nexport interface CleanupFailure {\n readonly owner: string;\n readonly error: unknown;\n}\n\nfunction errorMessage(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n const nested = (error as { failures?: Array<{ error: unknown }> } | null)?.failures;\n return Array.isArray(nested) ? message + ': ' + nested.map(failure => errorMessage(failure.error)).join('; ') : message;\n}\n\n/**\n * Error raised after every requested owner has been given a chance to clean up.\n * The structured failures are retained for assertions and callers that need to\n * inspect the original errors.\n */\nexport class TestWoodCleanupError extends TestWoodError {\n readonly failures: readonly CleanupFailure[];\n\n constructor(message: string, failures: readonly CleanupFailure[], cause?: unknown) {\n super(\n `${message} (${failures.length} failure(s)):\\n ` +\n failures.map(({ owner, error }) => `${owner}: ${errorMessage(error)}`).join('\\n '),\n );\n this.name = 'TestWoodCleanupError';\n this.failures = failures;\n if (cause !== undefined) (this as Error & { cause?: unknown }).cause = cause;\n }\n}\n\n/** Run every cleanup action in order, collecting rather than short-circuiting. */\nexport async function cleanupAll(\n message: string,\n actions: ReadonlyArray<readonly [owner: string, action: () => void | Promise<void>]>,\n): Promise<void> {\n const failures: CleanupFailure[] = [];\n for (const [owner, action] of actions) {\n try {\n await action();\n } catch (error) {\n failures.push({ owner, error });\n }\n }\n if (failures.length) throw new TestWoodCleanupError(message, failures);\n}\n\n/**\n * A registration-only view of the real root owned by this environment.\n *\n * Registrations land immediately on that root; shared @noego/testing applies\n * scenario overlays to it without replay. Resolution is unavailable until it has been\n * built and the test overlays and method decorator are installed.\n */\nexport class RegistrationOnlyRoot {\n readonly owners = new Set<unknown>();\n readonly root: IContainer;\n\n readonly actualRoot = createWoodRoot();\n private target: IContainer | null = null;\n private replayed = false;\n /** Every production registration as a declarative binding (the shared overlay catalog input). */\n private readonly declarations: Array<(bind: BindFunction) => void> = [];\n\n /**\n * @param hookName the app hook that may register on this root\n * @param applicationTest the config path identifying the application test;\n * registered on the ACTUAL root under the shared\n * `@noego/testing.application.v1` marker before any hook runs, so\n * frameworks reading the IoC async-context ABI (e.g. Proper's migration\n * guard) recognize Wood startup/invocation as an application test.\n * Production roots are never marked.\n */\n constructor(private readonly hookName: string, applicationTest: string) {\n this.actualRoot.registerFunction(APPLICATION_TEST_MARK, () => applicationTest, { owned: false });\n const registrationOnly = this;\n this.root = new Proxy(this.actualRoot, {\n get(_target, property) {\n if (property === 'registerClass') {\n return (classDefinition: new (...args: any[]) => unknown, options?: unknown): void => {\n registrationOnly.assertRecording('registerClass');\n registrationOnly.declare(classDefinition);\n const lifetime = lifetimeOf(options as Partial<ContainerOptions> | undefined);\n registrationOnly.declarations.push((bind) => refine(bind(classDefinition).toSelf(), lifetime));\n registrationOnly.actualRoot.registerClass(classDefinition, options as never);\n };\n }\n if (property === 'registerFunction') {\n return (token: unknown, factory: (...args: any[]) => unknown, options?: unknown): void => {\n registrationOnly.assertRecording('registerFunction');\n registrationOnly.declare(token);\n const dependencies = [...((options as { param?: unknown[] } | undefined)?.param ?? [])];\n for (const dependency of dependencies) registrationOnly.declare(dependency);\n const lifetime = lifetimeOf(options as Partial<ContainerOptions> | undefined);\n registrationOnly.declarations.push((bind) => refine(bind(token).toFactory(factory, dependencies), lifetime));\n registrationOnly.actualRoot.registerFunction(token, factory, options as never);\n };\n }\n if (property === 'isRegistered') {\n return (token: unknown): boolean =>\n registrationOnly.target?.isRegistered(token) ?? registrationOnly.owners.has(token);\n }\n if (property === 'isDisposed') {\n return (): boolean => registrationOnly.target?.isDisposed() ?? false;\n }\n\n const live = registrationOnly.target;\n if (live) {\n const value = Reflect.get(live as object, property, live);\n return typeof value === 'function' ? value.bind(live) : value;\n }\n\n return (..._args: unknown[]): never => {\n const method = String(property);\n throw new TestWoodError(\n `${registrationOnly.hookName} may only register dependencies before test overlays; ` +\n `${method}() attempted eager resolution or root mutation. Move resolution to start(), ` +\n 'a page lifecycle, or the built test environment.',\n );\n };\n },\n });\n }\n\n declare(token: unknown): void {\n if (this.owners.has(token)) return;\n this.owners.add(token);\n if (typeof token !== 'function') return;\n const injected = (Reflect as any).getMetadata?.(\n Symbol.for('ioc:inject:tokens'),\n token,\n ) as Map<number, unknown> | undefined;\n const design = (Reflect as any).getMetadata?.('design:paramtypes', token) ?? [];\n const indices = new Set<number>([...design.keys(), ...(injected?.keys() ?? [])]);\n for (const index of indices) {\n this.declare(injected?.has(index) ? injected.get(index) : design[index]);\n }\n }\n\n /**\n * Declare a token this root binds later (at runtime initialization) as a\n * provider so the shared overlay catalog can resolve replacements for it.\n * The placeholder factory is description only and never resolves.\n */\n declareBinding(token: unknown, lifetime: Lifetime): void {\n this.declare(token);\n const hookName = this.hookName;\n this.declarations.push((bind) => refine(bind(token).toFactory(() => {\n throw new TestWoodError(`${hookName} declared-only binding ${String(token)} has no production factory`);\n }, []), lifetime));\n }\n\n /** The recorded production registrations as a declarative module (description only, never built). */\n module(): ApplicationModule {\n const declarations = [...this.declarations];\n return defineModule(`@noego/wood/testing/${this.hookName}`, (bind) => {\n for (const declaration of declarations) declaration(bind);\n });\n }\n\n replay(root: IContainer): void {\n if (this.replayed) throw new TestWoodError(`${this.hookName} registrations were already replayed`);\n this.replayed = true;\n if (root !== this.actualRoot) throw new TestWoodError('testWood requires @noego/testing with build({ root }) support; update the shared testing package.');\n }\n\n bind(root: IContainer): void {\n if (!this.replayed) throw new TestWoodError(`${this.hookName} registrations must be replayed before binding`);\n this.target = root;\n }\n\n private assertRecording(method: string): void {\n if (this.replayed) {\n throw new TestWoodError(\n `${this.hookName} cannot call ${method}() after test overlays have been installed`,\n );\n }\n }\n}\n\n/**\n * Materialize shared @noego/testing overlays on the same production root,\n * disposing the partially built root if materialization fails.\n */\nexport async function buildRecordedEnvironment(\n builder: TestIocBuilder,\n recorded: RegistrationOnlyRoot,\n owner: string,\n borrowedTokens: readonly unknown[] = [],\n): Promise<TestEnvironment> {\n const partial: { root?: IContainer } = { root: recorded.actualRoot };\n try {\n const environment = await builder.build({\n root: recorded.actualRoot,\n borrowedTokens,\n prepare: (freshRoot) => {\n partial.root = freshRoot;\n recorded.replay(freshRoot);\n },\n });\n recorded.bind(environment.root);\n return environment;\n } catch (error) {\n const failures: CleanupFailure[] = [];\n if (partial.root) {\n try {\n await partial.root.dispose();\n } catch (cleanupError) {\n failures.push({ owner: `${owner} root rollback`, error: cleanupError });\n }\n }\n if (failures.length) {\n throw new TestWoodCleanupError(`${owner} build failed and rollback failed`, failures, error);\n }\n throw error;\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,9 @@
1
- import { IContainer } from '@noego/ioc';
1
+ import { IContainer, Lifetime, ApplicationModule } from '@noego/ioc';
2
2
  import { TestIocBuilder, TestEnvironment } from '@noego/testing';
3
3
  import { TestWoodError } from './test_wood.cjs';
4
4
 
5
+ /** Shared application-test marker (same symbol `@noego/app` registers). */
6
+ declare const APPLICATION_TEST_MARK: unique symbol;
5
7
  interface CleanupFailure {
6
8
  readonly owner: string;
7
9
  readonly error: unknown;
@@ -31,8 +33,27 @@ declare class RegistrationOnlyRoot {
31
33
  readonly actualRoot: IContainer;
32
34
  private target;
33
35
  private replayed;
34
- constructor(hookName: string);
36
+ /** Every production registration as a declarative binding (the shared overlay catalog input). */
37
+ private readonly declarations;
38
+ /**
39
+ * @param hookName the app hook that may register on this root
40
+ * @param applicationTest the config path identifying the application test;
41
+ * registered on the ACTUAL root under the shared
42
+ * `@noego/testing.application.v1` marker before any hook runs, so
43
+ * frameworks reading the IoC async-context ABI (e.g. Proper's migration
44
+ * guard) recognize Wood startup/invocation as an application test.
45
+ * Production roots are never marked.
46
+ */
47
+ constructor(hookName: string, applicationTest: string);
35
48
  declare(token: unknown): void;
49
+ /**
50
+ * Declare a token this root binds later (at runtime initialization) as a
51
+ * provider so the shared overlay catalog can resolve replacements for it.
52
+ * The placeholder factory is description only and never resolves.
53
+ */
54
+ declareBinding(token: unknown, lifetime: Lifetime): void;
55
+ /** The recorded production registrations as a declarative module (description only, never built). */
56
+ module(): ApplicationModule;
36
57
  replay(root: IContainer): void;
37
58
  bind(root: IContainer): void;
38
59
  private assertRecording;
@@ -41,6 +62,6 @@ declare class RegistrationOnlyRoot {
41
62
  * Materialize shared @noego/testing overlays on the same production root,
42
63
  * disposing the partially built root if materialization fails.
43
64
  */
44
- declare function buildRecordedEnvironment(builder: TestIocBuilder, recorded: RegistrationOnlyRoot, owner: string): Promise<TestEnvironment>;
65
+ declare function buildRecordedEnvironment(builder: TestIocBuilder, recorded: RegistrationOnlyRoot, owner: string, borrowedTokens?: readonly unknown[]): Promise<TestEnvironment>;
45
66
 
46
- export { type CleanupFailure, RegistrationOnlyRoot, TestWoodCleanupError, buildRecordedEnvironment, cleanupAll };
67
+ export { APPLICATION_TEST_MARK, type CleanupFailure, RegistrationOnlyRoot, TestWoodCleanupError, buildRecordedEnvironment, cleanupAll };
@@ -1,7 +1,9 @@
1
- import { IContainer } from '@noego/ioc';
1
+ import { IContainer, Lifetime, ApplicationModule } from '@noego/ioc';
2
2
  import { TestIocBuilder, TestEnvironment } from '@noego/testing';
3
3
  import { TestWoodError } from './test_wood.js';
4
4
 
5
+ /** Shared application-test marker (same symbol `@noego/app` registers). */
6
+ declare const APPLICATION_TEST_MARK: unique symbol;
5
7
  interface CleanupFailure {
6
8
  readonly owner: string;
7
9
  readonly error: unknown;
@@ -31,8 +33,27 @@ declare class RegistrationOnlyRoot {
31
33
  readonly actualRoot: IContainer;
32
34
  private target;
33
35
  private replayed;
34
- constructor(hookName: string);
36
+ /** Every production registration as a declarative binding (the shared overlay catalog input). */
37
+ private readonly declarations;
38
+ /**
39
+ * @param hookName the app hook that may register on this root
40
+ * @param applicationTest the config path identifying the application test;
41
+ * registered on the ACTUAL root under the shared
42
+ * `@noego/testing.application.v1` marker before any hook runs, so
43
+ * frameworks reading the IoC async-context ABI (e.g. Proper's migration
44
+ * guard) recognize Wood startup/invocation as an application test.
45
+ * Production roots are never marked.
46
+ */
47
+ constructor(hookName: string, applicationTest: string);
35
48
  declare(token: unknown): void;
49
+ /**
50
+ * Declare a token this root binds later (at runtime initialization) as a
51
+ * provider so the shared overlay catalog can resolve replacements for it.
52
+ * The placeholder factory is description only and never resolves.
53
+ */
54
+ declareBinding(token: unknown, lifetime: Lifetime): void;
55
+ /** The recorded production registrations as a declarative module (description only, never built). */
56
+ module(): ApplicationModule;
36
57
  replay(root: IContainer): void;
37
58
  bind(root: IContainer): void;
38
59
  private assertRecording;
@@ -41,6 +62,6 @@ declare class RegistrationOnlyRoot {
41
62
  * Materialize shared @noego/testing overlays on the same production root,
42
63
  * disposing the partially built root if materialization fails.
43
64
  */
44
- declare function buildRecordedEnvironment(builder: TestIocBuilder, recorded: RegistrationOnlyRoot, owner: string): Promise<TestEnvironment>;
65
+ declare function buildRecordedEnvironment(builder: TestIocBuilder, recorded: RegistrationOnlyRoot, owner: string, borrowedTokens?: readonly unknown[]): Promise<TestEnvironment>;
45
66
 
46
- export { type CleanupFailure, RegistrationOnlyRoot, TestWoodCleanupError, buildRecordedEnvironment, cleanupAll };
67
+ export { APPLICATION_TEST_MARK, type CleanupFailure, RegistrationOnlyRoot, TestWoodCleanupError, buildRecordedEnvironment, cleanupAll };