@noego/wood 0.6.2 → 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.
- package/README.md +7 -0
- package/dist/browser-test-setup.cjs +29 -0
- package/dist/browser-test-setup.cjs.map +1 -0
- package/dist/browser-test-setup.d.cts +8 -0
- package/dist/browser-test-setup.d.ts +8 -0
- package/dist/browser-test-setup.js +9 -0
- package/dist/browser-test-setup.js.map +1 -0
- package/dist/client/global_polyfill.d.cts +1 -0
- package/dist/client/global_polyfill.d.ts +1 -0
- package/dist/client/index.cjs +7 -0
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +7 -4
- package/dist/client/index.d.ts +7 -4
- package/dist/client/index.js +6 -1
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/renderer_app_generator.cjs +97 -24
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +97 -24
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/codegen/type_generator.cjs +9 -0
- package/dist/codegen/type_generator.cjs.map +1 -1
- package/dist/codegen/type_generator.js +9 -0
- package/dist/codegen/type_generator.js.map +1 -1
- package/dist/config/config_parser.cjs +10 -0
- package/dist/config/config_parser.cjs.map +1 -1
- package/dist/config/config_parser.js +10 -0
- package/dist/config/config_parser.js.map +1 -1
- package/dist/fetch.cjs +38 -0
- package/dist/fetch.cjs.map +1 -0
- package/dist/fetch.d.cts +7 -0
- package/dist/fetch.d.ts +7 -0
- package/dist/fetch.js +12 -0
- package/dist/fetch.js.map +1 -0
- package/dist/frontend/frontend_page.cjs +3 -3
- package/dist/frontend/frontend_page.cjs.map +1 -1
- package/dist/frontend/frontend_page.d.cts +1 -1
- package/dist/frontend/frontend_page.d.ts +1 -1
- package/dist/frontend/frontend_page.js +3 -3
- package/dist/frontend/frontend_page.js.map +1 -1
- package/dist/frontend/frontend_surface.cjs +31 -17
- package/dist/frontend/frontend_surface.cjs.map +1 -1
- package/dist/frontend/frontend_surface.d.cts +12 -2
- package/dist/frontend/frontend_surface.d.ts +12 -2
- package/dist/frontend/frontend_surface.js +37 -17
- package/dist/frontend/frontend_surface.js.map +1 -1
- package/dist/frontend/index.d.cts +2 -0
- package/dist/frontend/index.d.ts +2 -0
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/loader/index.cjs +19 -2
- package/dist/loader/index.cjs.map +1 -1
- package/dist/loader/index.d.cts +1 -0
- package/dist/loader/index.d.ts +1 -0
- package/dist/loader/index.js +19 -1
- package/dist/loader/index.js.map +1 -1
- package/dist/loader/loader_orchestration.cjs +82 -0
- package/dist/loader/loader_orchestration.cjs.map +1 -0
- package/dist/loader/loader_orchestration.d.cts +66 -0
- package/dist/loader/loader_orchestration.d.ts +66 -0
- package/dist/loader/loader_orchestration.js +51 -0
- package/dist/loader/loader_orchestration.js.map +1 -0
- package/dist/loader/loader_runner.cjs +46 -41
- package/dist/loader/loader_runner.cjs.map +1 -1
- package/dist/loader/loader_runner.d.cts +12 -6
- package/dist/loader/loader_runner.d.ts +12 -6
- package/dist/loader/loader_runner.js +50 -41
- package/dist/loader/loader_runner.js.map +1 -1
- package/dist/native_browser.types-BY2daUOS.d.ts +189 -0
- package/dist/native_browser.types-vWXtE4rM.d.cts +189 -0
- package/dist/navigation/index.cjs +15 -2
- package/dist/navigation/index.cjs.map +1 -1
- package/dist/navigation/index.d.cts +2 -0
- package/dist/navigation/index.d.ts +2 -0
- package/dist/navigation/index.js +8 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/runtime/get_container.cjs +2 -0
- package/dist/runtime/get_container.cjs.map +1 -1
- package/dist/runtime/get_container.js +2 -0
- package/dist/runtime/get_container.js.map +1 -1
- package/dist/runtime/index.cjs +5 -0
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +2 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/runtime_capabilities.cjs +35 -0
- package/dist/runtime/runtime_capabilities.cjs.map +1 -0
- package/dist/runtime/runtime_capabilities.d.cts +6 -0
- package/dist/runtime/runtime_capabilities.d.ts +6 -0
- package/dist/runtime/runtime_capabilities.js +11 -0
- package/dist/runtime/runtime_capabilities.js.map +1 -0
- package/dist/runtime/runtime_module.cjs +2 -0
- package/dist/runtime/runtime_module.cjs.map +1 -1
- package/dist/runtime/runtime_module.js +2 -0
- package/dist/runtime/runtime_module.js.map +1 -1
- package/dist/test-wood.cjs +150 -0
- package/dist/test-wood.cjs.map +1 -0
- package/dist/test-wood.d.cts +62 -0
- package/dist/test-wood.d.ts +62 -0
- package/dist/test-wood.js +115 -0
- package/dist/test-wood.js.map +1 -0
- package/dist/testing/browser.cjs +92 -163
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +14 -13
- package/dist/testing/browser.d.ts +14 -13
- package/dist/testing/browser.js +88 -162
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +2 -1
- package/dist/testing/create_controller_harness.d.ts +2 -1
- package/dist/testing/create_router_bridge.cjs +19 -15
- package/dist/testing/create_router_bridge.cjs.map +1 -1
- package/dist/testing/create_router_bridge.d.cts +8 -1
- package/dist/testing/create_router_bridge.d.ts +8 -1
- package/dist/testing/create_router_bridge.js +18 -15
- package/dist/testing/create_router_bridge.js.map +1 -1
- package/dist/testing/index.cjs +12 -4
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +11 -5
- package/dist/testing/index.d.ts +11 -5
- package/dist/testing/index.js +9 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/native_browser/browser_pool.cjs +31 -0
- package/dist/testing/native_browser/browser_pool.cjs.map +1 -0
- package/dist/testing/native_browser/browser_pool.d.cts +1 -0
- package/dist/testing/native_browser/browser_pool.d.ts +1 -0
- package/dist/testing/native_browser/browser_pool.js +6 -0
- package/dist/testing/native_browser/browser_pool.js.map +1 -0
- package/dist/testing/native_browser/happy_dom_tab.cjs +107 -0
- package/dist/testing/native_browser/happy_dom_tab.cjs.map +1 -0
- package/dist/testing/native_browser/happy_dom_tab.d.cts +35 -0
- package/dist/testing/native_browser/happy_dom_tab.d.ts +35 -0
- package/dist/testing/native_browser/happy_dom_tab.js +73 -0
- package/dist/testing/native_browser/happy_dom_tab.js.map +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.cjs +29 -0
- package/dist/testing/native_browser/happy_dom_transfer.cjs.map +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.d.cts +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.d.ts +1 -0
- package/dist/testing/native_browser/happy_dom_transfer.js +5 -0
- package/dist/testing/native_browser/happy_dom_transfer.js.map +1 -0
- package/dist/testing/native_browser/host_transport.cjs +89 -0
- package/dist/testing/native_browser/host_transport.cjs.map +1 -0
- package/dist/testing/native_browser/host_transport.d.cts +44 -0
- package/dist/testing/native_browser/host_transport.d.ts +44 -0
- package/dist/testing/native_browser/host_transport.js +65 -0
- package/dist/testing/native_browser/host_transport.js.map +1 -0
- package/dist/testing/native_browser/native_browser.types.cjs +17 -0
- package/dist/testing/native_browser/native_browser.types.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser.types.d.cts +31 -0
- package/dist/testing/native_browser/native_browser.types.d.ts +31 -0
- package/dist/testing/native_browser/native_browser.types.js +1 -0
- package/dist/testing/native_browser/native_browser.types.js.map +1 -0
- package/dist/testing/native_browser/native_browser_environment.cjs +143 -0
- package/dist/testing/native_browser/native_browser_environment.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser_environment.d.cts +36 -0
- package/dist/testing/native_browser/native_browser_environment.d.ts +36 -0
- package/dist/testing/native_browser/native_browser_environment.js +109 -0
- package/dist/testing/native_browser/native_browser_environment.js.map +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.cjs +31 -0
- package/dist/testing/native_browser/native_browser_lifecycle.cjs.map +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.d.cts +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.d.ts +1 -0
- package/dist/testing/native_browser/native_browser_lifecycle.js +6 -0
- package/dist/testing/native_browser/native_browser_lifecycle.js.map +1 -0
- package/dist/testing/native_browser/playwright_tab.cjs +124 -0
- package/dist/testing/native_browser/playwright_tab.cjs.map +1 -0
- package/dist/testing/native_browser/playwright_tab.d.cts +35 -0
- package/dist/testing/native_browser/playwright_tab.d.ts +35 -0
- package/dist/testing/native_browser/playwright_tab.js +100 -0
- package/dist/testing/native_browser/playwright_tab.js.map +1 -0
- package/dist/testing/native_browser/renderer_compilation.cjs +149 -0
- package/dist/testing/native_browser/renderer_compilation.cjs.map +1 -0
- package/dist/testing/native_browser/renderer_compilation.d.cts +35 -0
- package/dist/testing/native_browser/renderer_compilation.d.ts +35 -0
- package/dist/testing/native_browser/renderer_compilation.js +115 -0
- package/dist/testing/native_browser/renderer_compilation.js.map +1 -0
- package/dist/testing/native_browser/renderer_entry.cjs +148 -0
- package/dist/testing/native_browser/renderer_entry.cjs.map +1 -0
- package/dist/testing/native_browser/renderer_entry.d.cts +13 -0
- package/dist/testing/native_browser/renderer_entry.d.ts +13 -0
- package/dist/testing/native_browser/renderer_entry.js +114 -0
- package/dist/testing/native_browser/renderer_entry.js.map +1 -0
- package/dist/testing/registration_only_root.cjs +61 -5
- package/dist/testing/registration_only_root.cjs.map +1 -1
- package/dist/testing/registration_only_root.d.cts +25 -4
- package/dist/testing/registration_only_root.d.ts +25 -4
- package/dist/testing/registration_only_root.js +60 -5
- package/dist/testing/registration_only_root.js.map +1 -1
- package/dist/testing/runner_importer.cjs +75 -0
- package/dist/testing/runner_importer.cjs.map +1 -0
- package/dist/testing/runner_importer.d.cts +8 -0
- package/dist/testing/runner_importer.d.ts +8 -0
- package/dist/testing/runner_importer.js +41 -0
- package/dist/testing/runner_importer.js.map +1 -0
- package/dist/testing/test_wood_config.cjs +387 -272
- package/dist/testing/test_wood_config.cjs.map +1 -1
- package/dist/testing/test_wood_config.d.cts +18 -123
- package/dist/testing/test_wood_config.d.ts +18 -123
- package/dist/testing/test_wood_config.js +396 -270
- package/dist/testing/test_wood_config.js.map +1 -1
- package/dist/testing/wood_config_source.cjs +12 -0
- package/dist/testing/wood_config_source.cjs.map +1 -1
- package/dist/testing/wood_config_source.d.cts +10 -0
- package/dist/testing/wood_config_source.d.ts +10 -0
- package/dist/testing/wood_config_source.js +12 -0
- package/dist/testing/wood_config_source.js.map +1 -1
- package/dist/testing/wood_env_renderer.cjs +28 -3
- package/dist/testing/wood_env_renderer.cjs.map +1 -1
- package/dist/testing/wood_env_renderer.d.cts +12 -1
- package/dist/testing/wood_env_renderer.d.ts +12 -1
- package/dist/testing/wood_env_renderer.js +28 -3
- package/dist/testing/wood_env_renderer.js.map +1 -1
- package/dist/testing/wood_lazy_runtime.cjs +109 -0
- package/dist/testing/wood_lazy_runtime.cjs.map +1 -0
- package/dist/testing/wood_lazy_runtime.d.cts +36 -0
- package/dist/testing/wood_lazy_runtime.d.ts +36 -0
- package/dist/testing/wood_lazy_runtime.js +84 -0
- package/dist/testing/wood_lazy_runtime.js.map +1 -0
- package/dist/testing/wood_test_selection.cjs +34 -55
- package/dist/testing/wood_test_selection.cjs.map +1 -1
- package/dist/testing/wood_test_selection.d.cts +18 -24
- package/dist/testing/wood_test_selection.d.ts +18 -24
- package/dist/testing/wood_test_selection.js +30 -54
- package/dist/testing/wood_test_selection.js.map +1 -1
- package/dist/testing-api.cjs +17 -0
- package/dist/testing-api.cjs.map +1 -0
- package/dist/testing-api.d.cts +18 -0
- package/dist/testing-api.d.ts +18 -0
- package/dist/testing-api.js +1 -0
- package/dist/testing-api.js.map +1 -0
- package/dist/types/config.cjs.map +1 -1
- package/dist/types/config.d.cts +4 -0
- package/dist/types/config.d.ts +4 -0
- package/docs/native-browser-testing.md +164 -0
- package/package.json +31 -5
- package/src/components/NavigationShell.browser.test.ts +10 -10
- package/src/components/NavigationShell.cleanup.browser.test.ts +57 -0
- package/src/components/NavigationShell.scopes.browser.test.ts +5 -5
- package/src/components/NavigationShell.svelte +50 -24
|
@@ -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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|