@noego/wood 0.5.1 → 0.6.1
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 +31 -0
- package/dist/client/index.cjs +30 -11
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +33 -1
- package/dist/client/index.d.ts +33 -1
- package/dist/client/index.js +28 -11
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/preload_generator.cjs +44 -5
- package/dist/codegen/preload_generator.cjs.map +1 -1
- package/dist/codegen/preload_generator.js +44 -5
- package/dist/codegen/preload_generator.js.map +1 -1
- package/dist/codegen/renderer_app_generator.cjs +2 -9
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +2 -9
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/controller/cleanup.cjs +39 -0
- package/dist/controller/cleanup.cjs.map +1 -0
- package/dist/controller/cleanup.d.cts +4 -0
- package/dist/controller/cleanup.d.ts +4 -0
- package/dist/controller/cleanup.js +15 -0
- package/dist/controller/cleanup.js.map +1 -0
- package/dist/controller/controller_lifecycle.cjs +26 -2
- package/dist/controller/controller_lifecycle.cjs.map +1 -1
- package/dist/controller/controller_lifecycle.d.cts +3 -1
- package/dist/controller/controller_lifecycle.d.ts +3 -1
- package/dist/controller/controller_lifecycle.js +24 -1
- package/dist/controller/controller_lifecycle.js.map +1 -1
- package/dist/frontend/frontend_lifecycle.cjs +5 -7
- package/dist/frontend/frontend_lifecycle.cjs.map +1 -1
- package/dist/frontend/frontend_lifecycle.d.cts +1 -14
- package/dist/frontend/frontend_lifecycle.d.ts +1 -14
- package/dist/frontend/frontend_lifecycle.js +5 -7
- package/dist/frontend/frontend_lifecycle.js.map +1 -1
- package/dist/frontend/frontend_surface.cjs +130 -104
- package/dist/frontend/frontend_surface.cjs.map +1 -1
- package/dist/frontend/frontend_surface.js +131 -105
- package/dist/frontend/frontend_surface.js.map +1 -1
- package/dist/frontend/work_registry.cjs +29 -4
- package/dist/frontend/work_registry.cjs.map +1 -1
- package/dist/frontend/work_registry.d.cts +2 -0
- package/dist/frontend/work_registry.d.ts +2 -0
- package/dist/frontend/work_registry.js +29 -4
- package/dist/frontend/work_registry.js.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/ipc/index.cjs +6 -2
- package/dist/ipc/index.cjs.map +1 -1
- package/dist/ipc/index.d.cts +9 -0
- package/dist/ipc/index.d.ts +9 -0
- package/dist/ipc/index.js +6 -2
- package/dist/ipc/index.js.map +1 -1
- package/dist/main/index.cjs +81 -82
- package/dist/main/index.cjs.map +1 -1
- package/dist/main/index.d.cts +13 -4
- package/dist/main/index.d.ts +13 -4
- package/dist/main/index.js +89 -87
- package/dist/main/index.js.map +1 -1
- package/dist/main/shutdown_coordinator.cjs +153 -0
- package/dist/main/shutdown_coordinator.cjs.map +1 -0
- package/dist/main/shutdown_coordinator.d.cts +81 -0
- package/dist/main/shutdown_coordinator.d.ts +81 -0
- package/dist/main/shutdown_coordinator.js +127 -0
- package/dist/main/shutdown_coordinator.js.map +1 -0
- package/dist/main/startup_failure_handler.cjs +67 -0
- package/dist/main/startup_failure_handler.cjs.map +1 -0
- package/dist/main/startup_failure_handler.d.cts +16 -0
- package/dist/main/startup_failure_handler.d.ts +16 -0
- package/dist/main/startup_failure_handler.js +43 -0
- package/dist/main/startup_failure_handler.js.map +1 -0
- package/dist/router/index.d.cts +3 -0
- package/dist/router/index.d.ts +3 -0
- package/dist/router/wood_router.cjs +204 -123
- package/dist/router/wood_router.cjs.map +1 -1
- package/dist/router/wood_router.d.cts +20 -0
- package/dist/router/wood_router.d.ts +20 -0
- package/dist/router/wood_router.js +206 -125
- package/dist/router/wood_router.js.map +1 -1
- package/dist/runtime/index.cjs +13 -0
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +8 -4
- package/dist/runtime/index.d.ts +8 -4
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/renderer_config_extension.cjs +1 -2
- package/dist/runtime/renderer_config_extension.cjs.map +1 -1
- package/dist/runtime/renderer_config_extension.d.cts +1 -1
- package/dist/runtime/renderer_config_extension.d.ts +1 -1
- package/dist/runtime/renderer_config_extension.js +1 -2
- package/dist/runtime/renderer_config_extension.js.map +1 -1
- package/dist/runtime/renderer_log_dispatcher.cjs +2 -0
- package/dist/runtime/renderer_log_dispatcher.cjs.map +1 -1
- package/dist/runtime/renderer_log_dispatcher.d.cts +1 -1
- package/dist/runtime/renderer_log_dispatcher.d.ts +1 -1
- package/dist/runtime/renderer_log_dispatcher.js +1 -0
- package/dist/runtime/renderer_log_dispatcher.js.map +1 -1
- package/dist/runtime/runtime.cjs +107 -57
- package/dist/runtime/runtime.cjs.map +1 -1
- package/dist/runtime/runtime.d.cts +12 -0
- package/dist/runtime/runtime.d.ts +12 -0
- package/dist/runtime/runtime.js +110 -59
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/runtime_module.cjs +58 -0
- package/dist/runtime/runtime_module.cjs.map +1 -0
- package/dist/runtime/runtime_module.d.cts +22 -0
- package/dist/runtime/runtime_module.d.ts +22 -0
- package/dist/runtime/runtime_module.js +30 -0
- package/dist/runtime/runtime_module.js.map +1 -0
- package/dist/testing/create_controller_harness.d.cts +3 -0
- package/dist/testing/create_controller_harness.d.ts +3 -0
- package/dist/testing/create_router_bridge.d.cts +3 -0
- package/dist/testing/create_router_bridge.d.ts +3 -0
- package/dist/testing/create_test_router.d.cts +3 -0
- package/dist/testing/create_test_router.d.ts +3 -0
- package/dist/testing/index.cjs +3 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +6 -1
- package/dist/testing/index.d.ts +6 -1
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/registration_only_root.cjs +168 -0
- package/dist/testing/registration_only_root.cjs.map +1 -0
- package/dist/testing/registration_only_root.d.cts +46 -0
- package/dist/testing/registration_only_root.d.ts +46 -0
- package/dist/testing/registration_only_root.js +141 -0
- package/dist/testing/registration_only_root.js.map +1 -0
- package/dist/testing/test_wood_config.cjs +382 -70
- package/dist/testing/test_wood_config.cjs.map +1 -1
- package/dist/testing/test_wood_config.d.cts +25 -1
- package/dist/testing/test_wood_config.d.ts +25 -1
- package/dist/testing/test_wood_config.js +388 -71
- package/dist/testing/test_wood_config.js.map +1 -1
- package/dist/testing/wood_dom_animation.cjs +213 -0
- package/dist/testing/wood_dom_animation.cjs.map +1 -0
- package/dist/testing/wood_dom_animation.d.cts +89 -0
- package/dist/testing/wood_dom_animation.d.ts +89 -0
- package/dist/testing/wood_dom_animation.js +185 -0
- package/dist/testing/wood_dom_animation.js.map +1 -0
- package/dist/testing/wood_env_renderer.cjs +81 -32
- package/dist/testing/wood_env_renderer.cjs.map +1 -1
- package/dist/testing/wood_env_renderer.d.cts +32 -1
- package/dist/testing/wood_env_renderer.d.ts +32 -1
- package/dist/testing/wood_env_renderer.js +84 -32
- package/dist/testing/wood_env_renderer.js.map +1 -1
- package/dist/transport/in_process_router_transport.d.cts +3 -0
- package/dist/transport/in_process_router_transport.d.ts +3 -0
- package/dist/transport/index.d.cts +3 -0
- package/dist/transport/index.d.ts +3 -0
- package/package.json +7 -6
- package/src/components/NavigationShell.svelte +12 -7
- package/src/controller/cleanup.ts +8 -0
- package/src/controller/controller_lifecycle.ts +24 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { disposeControllerLifecycle, guardControllerLifecycleOnce } from "../controller/controller_lifecycle.js";
|
|
1
2
|
import path from "node:path";
|
|
2
|
-
import { ExecutionContext } from "@noego/ioc";
|
|
3
|
+
import { ExecutionContext, flattenModule } from "@noego/ioc";
|
|
3
4
|
import { testIoc, ENV_REGISTRY } from "@noego/testing";
|
|
4
|
-
import { WoodRouter } from "../
|
|
5
|
-
import {
|
|
5
|
+
import { installWoodRuntime, WoodRouter } from "../runtime/runtime_module.js";
|
|
6
|
+
import { Navigation } from "../navigation/index.js";
|
|
7
|
+
import { TestWoodError, WOOD_FRONTEND_BRIDGE } from "./test_wood.js";
|
|
6
8
|
import {
|
|
7
9
|
defaultImporter,
|
|
8
10
|
importDefaultExport,
|
|
@@ -16,6 +18,12 @@ import {
|
|
|
16
18
|
import {
|
|
17
19
|
buildRendererTier
|
|
18
20
|
} from "./wood_env_renderer.js";
|
|
21
|
+
import {
|
|
22
|
+
buildRecordedEnvironment,
|
|
23
|
+
cleanupAll,
|
|
24
|
+
RegistrationOnlyRoot,
|
|
25
|
+
TestWoodCleanupError
|
|
26
|
+
} from "./registration_only_root.js";
|
|
19
27
|
const CONTAINER_REGISTRY_SYMBOL = /* @__PURE__ */ Symbol.for("@noego/wood.container.registry");
|
|
20
28
|
function globalRegistrySize() {
|
|
21
29
|
const registry = globalThis[CONTAINER_REGISTRY_SYMBOL];
|
|
@@ -26,23 +34,158 @@ function tierMissing(tier, member) {
|
|
|
26
34
|
`the ${member} is not available: this environment was built without { ${tier} } \u2014 add it to select()`
|
|
27
35
|
);
|
|
28
36
|
}
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
function recordComposer(compose) {
|
|
38
|
+
if (typeof compose !== "function") throw new TestWoodError("owner composition expects a callback");
|
|
39
|
+
const calls = [];
|
|
40
|
+
const root = {
|
|
41
|
+
use: (preset) => {
|
|
42
|
+
calls.push({ method: "use", args: [preset], flat: false });
|
|
43
|
+
return root;
|
|
44
|
+
},
|
|
45
|
+
classes: (config) => {
|
|
46
|
+
calls.push({ method: "classes", args: [config], flat: false });
|
|
47
|
+
return root;
|
|
48
|
+
},
|
|
49
|
+
functions: (config) => {
|
|
50
|
+
calls.push({ method: "functions", args: [config], flat: false });
|
|
51
|
+
return root;
|
|
52
|
+
},
|
|
53
|
+
values: (config) => {
|
|
54
|
+
calls.push({ method: "values", args: [config], flat: false });
|
|
55
|
+
return root;
|
|
56
|
+
},
|
|
57
|
+
methods: (config) => {
|
|
58
|
+
calls.push({ method: "methods", args: [config], flat: false });
|
|
59
|
+
return root;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
compose(root);
|
|
63
|
+
return calls;
|
|
64
|
+
}
|
|
65
|
+
function applyCalls(builder, calls) {
|
|
66
|
+
let next = builder;
|
|
67
|
+
for (const call of [...calls].sort((a, b) => a.order - b.order)) {
|
|
68
|
+
next = next[call.method](...call.args);
|
|
69
|
+
}
|
|
70
|
+
return next;
|
|
71
|
+
}
|
|
72
|
+
function ownerTokens(builder) {
|
|
73
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
74
|
+
const visit = (token) => {
|
|
75
|
+
if (tokens.has(token)) return;
|
|
76
|
+
tokens.add(token);
|
|
77
|
+
if (typeof token !== "function") return;
|
|
78
|
+
const injected = Reflect.getMetadata?.(/* @__PURE__ */ Symbol.for("ioc:inject:tokens"), token);
|
|
79
|
+
const design = Reflect.getMetadata?.("design:paramtypes", token) ?? [];
|
|
80
|
+
const indices = /* @__PURE__ */ new Set([...design.keys(), ...injected?.keys() ?? []]);
|
|
81
|
+
for (const index of indices) visit(injected?.has(index) ? injected.get(index) : design[index]);
|
|
82
|
+
};
|
|
83
|
+
for (const write of builder.writes) {
|
|
84
|
+
if (write.op === "use") {
|
|
85
|
+
for (const registration of flattenModule(write.module)) {
|
|
86
|
+
visit(registration.token);
|
|
87
|
+
if (registration.kind === "class") visit(registration.implementation);
|
|
88
|
+
for (const dependency of registration.dependencies) visit(dependency);
|
|
89
|
+
}
|
|
90
|
+
} else if (write.op !== "methods") {
|
|
91
|
+
visit(write.key);
|
|
92
|
+
if (write.op === "classes") visit(write.implementation);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return tokens;
|
|
96
|
+
}
|
|
97
|
+
function tokenLabel(token) {
|
|
98
|
+
if (typeof token === "function" && token.name) return token.name;
|
|
99
|
+
return String(token);
|
|
100
|
+
}
|
|
101
|
+
function callEntries(call) {
|
|
102
|
+
applyCalls(testIoc(), [{ ...call, order: 0 }]);
|
|
103
|
+
const config = call.args[0];
|
|
104
|
+
if (Array.isArray(config) || config instanceof Map) return [...config];
|
|
105
|
+
return Reflect.ownKeys(config).map((key) => [key, config[key]]);
|
|
106
|
+
}
|
|
107
|
+
function resolveOwnerToken(key, exact, main, renderer, method) {
|
|
108
|
+
let token = key;
|
|
109
|
+
if (!exact && typeof key === "string") {
|
|
110
|
+
const matches = [.../* @__PURE__ */ new Set([...main, ...renderer])].filter(
|
|
111
|
+
(candidate) => candidate === key || typeof candidate === "function" && candidate.name === key
|
|
112
|
+
);
|
|
113
|
+
if (matches.length > 1) {
|
|
114
|
+
throw new TestWoodError(`Ambiguous ${method} replacement name "${key}"; use an exact token`);
|
|
115
|
+
}
|
|
116
|
+
token = matches[0] ?? key;
|
|
117
|
+
}
|
|
118
|
+
const ownsMain = main.has(token);
|
|
119
|
+
const ownsRenderer = renderer.has(token);
|
|
120
|
+
if (!ownsMain && !ownsRenderer) {
|
|
121
|
+
throw new TestWoodError(
|
|
122
|
+
`Unknown ${method} replacement ${tokenLabel(token)} in the selected roots; register it with .main() or .renderer()`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (ownsMain && ownsRenderer) {
|
|
126
|
+
throw new TestWoodError(
|
|
127
|
+
`Ambiguous ${method} replacement ${tokenLabel(token)} belongs to main and renderer; target .main() or .renderer() explicitly`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return { token, owner: ownsMain ? "main" : "renderer" };
|
|
131
|
+
}
|
|
132
|
+
function routeCalls(calls, main, renderer, target) {
|
|
133
|
+
for (const call of calls) {
|
|
134
|
+
if (!call.flat || call.method !== "classes") continue;
|
|
135
|
+
const exact = Array.isArray(call.args[0]) || call.args[0] instanceof Map;
|
|
136
|
+
for (const [key, implementation] of callEntries(call)) {
|
|
137
|
+
const routed = resolveOwnerToken(key, exact || typeof key !== "string", main, renderer, call.method);
|
|
138
|
+
(routed.owner === "main" ? main : renderer).add(implementation);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const result = [];
|
|
142
|
+
for (const call of calls) {
|
|
143
|
+
if (!call.flat) {
|
|
144
|
+
if (target === "main") result.push(call);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const exact = Array.isArray(call.args[0]) || call.args[0] instanceof Map;
|
|
148
|
+
for (const [key, value] of callEntries(call)) {
|
|
149
|
+
const routed = resolveOwnerToken(key, exact || typeof key !== "string", main, renderer, call.method);
|
|
150
|
+
if (routed.owner === target) {
|
|
151
|
+
result.push({ ...call, flat: false, args: [[[routed.token, value]]] });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
async function loadController(source, importer, reference, owner) {
|
|
158
|
+
const file = owner === "main" ? source.mainControllerFile(reference) : source.rendererControllerFile(reference);
|
|
159
|
+
const controller = await importDefaultExport(
|
|
160
|
+
importer,
|
|
161
|
+
file,
|
|
162
|
+
`${owner} controller "${reference}"`
|
|
163
|
+
);
|
|
164
|
+
if (typeof controller !== "function") {
|
|
165
|
+
throw new TestWoodError(`${owner} controller "${reference}" must default-export a class (${file})`);
|
|
166
|
+
}
|
|
167
|
+
return controller;
|
|
168
|
+
}
|
|
169
|
+
async function loadMainControllers(source, operations, importer) {
|
|
170
|
+
const controllers = /* @__PURE__ */ new Map();
|
|
37
171
|
for (const operation of operations.values()) {
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
172
|
+
if (!controllers.has(operation.controller)) {
|
|
173
|
+
controllers.set(operation.controller, await loadController(source, importer, operation.controller, "main"));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return controllers;
|
|
177
|
+
}
|
|
178
|
+
async function loadRendererControllers(source, views, importer) {
|
|
179
|
+
const controllers = /* @__PURE__ */ new Map();
|
|
180
|
+
const references = /* @__PURE__ */ new Set();
|
|
181
|
+
for (const view of views) {
|
|
182
|
+
if (view.controller) references.add(view.controller);
|
|
183
|
+
for (const controller of view.layoutControllers ?? []) if (controller) references.add(controller);
|
|
184
|
+
}
|
|
185
|
+
for (const reference of references) {
|
|
186
|
+
controllers.set(reference, await loadController(source, importer, reference, "renderer"));
|
|
42
187
|
}
|
|
43
|
-
|
|
44
|
-
router.validateControllers();
|
|
45
|
-
return router;
|
|
188
|
+
return controllers;
|
|
46
189
|
}
|
|
47
190
|
async function runCompose(root, source, context) {
|
|
48
191
|
const composePath = source.hookModule(source.config.main.testing?.compose);
|
|
@@ -73,16 +216,8 @@ function assertNoGlobalContainerLeak(before, source) {
|
|
|
73
216
|
);
|
|
74
217
|
}
|
|
75
218
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const operations = selectOperations(source.operations, selection.main);
|
|
79
|
-
const catalog = selection.pages ? selectCatalog(source.views(), selection.pages) : null;
|
|
80
|
-
const registryBefore = globalRegistrySize();
|
|
81
|
-
let composed = null;
|
|
82
|
-
let renderer = null;
|
|
83
|
-
const controllerScopes = [];
|
|
84
|
-
let router = null;
|
|
85
|
-
const context = {
|
|
219
|
+
function createComposeContext(source, state, selection, operations, live) {
|
|
220
|
+
return {
|
|
86
221
|
configPath: source.configPath,
|
|
87
222
|
configDir: source.configDir,
|
|
88
223
|
config: source.config,
|
|
@@ -90,47 +225,189 @@ async function assembleEnvironment(state, selection) {
|
|
|
90
225
|
operations,
|
|
91
226
|
importer: state.importer,
|
|
92
227
|
call: async (operation, input) => {
|
|
93
|
-
if (!router
|
|
94
|
-
|
|
95
|
-
|
|
228
|
+
if (!live.router || !live.env) {
|
|
229
|
+
throw new TestWoodError("compose context call() is available from start() onward, not during compose");
|
|
230
|
+
}
|
|
231
|
+
const router = live.router;
|
|
232
|
+
return await ExecutionContext.run(
|
|
233
|
+
live.env.root,
|
|
234
|
+
() => router.dispatch(operation, input)
|
|
235
|
+
);
|
|
96
236
|
}
|
|
97
237
|
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
238
|
+
}
|
|
239
|
+
function createAssemblyDisposer(live, mainRegistrations, rendererRegistrations, controllerScopes) {
|
|
240
|
+
let closing = null;
|
|
241
|
+
return () => closing ?? (closing = cleanupAll("testWood cleanup failed", [
|
|
242
|
+
["renderer", async () => {
|
|
243
|
+
if (live.renderer) await live.renderer.dispose();
|
|
244
|
+
}],
|
|
245
|
+
...[...controllerScopes].reverse().map(
|
|
246
|
+
(scope, index) => [`controller scope ${controllerScopes.length - index}`, () => scope.dispose()]
|
|
247
|
+
),
|
|
248
|
+
["compose drain", async () => {
|
|
249
|
+
await live.composed?.drain?.();
|
|
250
|
+
}],
|
|
251
|
+
["main root", async () => {
|
|
252
|
+
if (live.env) await disposeRoot(live.env);
|
|
253
|
+
else await mainRegistrations.actualRoot.dispose();
|
|
254
|
+
}],
|
|
255
|
+
["unbuilt renderer root", async () => {
|
|
256
|
+
if (!live.renderer && rendererRegistrations) await rendererRegistrations.actualRoot.dispose();
|
|
257
|
+
}],
|
|
258
|
+
["compose external dispose", async () => {
|
|
259
|
+
await live.composed?.dispose?.();
|
|
260
|
+
}]
|
|
261
|
+
]));
|
|
262
|
+
}
|
|
263
|
+
async function loadAssemblyControllers(inputs) {
|
|
264
|
+
const main = await loadMainControllers(inputs.source, inputs.operations, inputs.state.importer);
|
|
265
|
+
const renderer = inputs.catalog ? await loadRendererControllers(inputs.source, inputs.catalog.all(), inputs.state.importer) : /* @__PURE__ */ new Map();
|
|
266
|
+
return { main, renderer };
|
|
267
|
+
}
|
|
268
|
+
function installAssemblyRuntime(inputs, mainControllers) {
|
|
269
|
+
installWoodRuntime(inputs.mainRegistrations.root, {
|
|
270
|
+
controllersDir: inputs.source.mainControllersDir,
|
|
271
|
+
middlewareDir: inputs.source.mainMiddlewareDir,
|
|
272
|
+
mode: "dev",
|
|
273
|
+
registerRuntime: (configuredRouter) => {
|
|
274
|
+
for (const [identity, controller] of mainControllers) {
|
|
275
|
+
configuredRouter.registerControllerLoader(identity, () => Promise.resolve({ default: controller }));
|
|
276
|
+
}
|
|
277
|
+
configuredRouter.loadOperations(inputs.operations);
|
|
101
278
|
}
|
|
102
279
|
});
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
280
|
+
}
|
|
281
|
+
async function configureRendererRegistrations(inputs) {
|
|
282
|
+
const registrations = inputs.rendererRegistrations;
|
|
283
|
+
if (!registrations) return;
|
|
284
|
+
registrations.declare(Navigation);
|
|
285
|
+
registrations.declare(WOOD_FRONTEND_BRIDGE);
|
|
286
|
+
await ExecutionContext.run(
|
|
287
|
+
registrations.root,
|
|
288
|
+
() => inputs.live.composed?.configureRendererRoot?.(registrations.root)
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
function registerControllersBeforeOverlays(inputs, mainControllers, rendererControllers) {
|
|
292
|
+
for (const controller of mainControllers.values()) {
|
|
293
|
+
inputs.mainRegistrations.declare(controller);
|
|
294
|
+
if (!inputs.mainRegistrations.actualRoot.isRegistered(controller)) {
|
|
295
|
+
inputs.mainRegistrations.root.registerClass(controller);
|
|
112
296
|
}
|
|
297
|
+
}
|
|
298
|
+
for (const controller of rendererControllers.values()) {
|
|
299
|
+
inputs.rendererRegistrations?.declare(controller);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function buildAssemblyIoc(inputs) {
|
|
303
|
+
const explicitMain = inputs.state.mainCalls.filter((call) => !call.flat);
|
|
304
|
+
const mainOwners = /* @__PURE__ */ new Set([
|
|
305
|
+
...inputs.mainRegistrations.owners,
|
|
306
|
+
...ownerTokens(applyCalls(testIoc(), explicitMain))
|
|
307
|
+
]);
|
|
308
|
+
const rendererOwners = /* @__PURE__ */ new Set([
|
|
309
|
+
...inputs.rendererRegistrations?.owners ?? [],
|
|
310
|
+
...ownerTokens(applyCalls(testIoc(), inputs.state.rendererCalls))
|
|
311
|
+
]);
|
|
312
|
+
const routedMain = routeCalls(inputs.state.mainCalls, mainOwners, rendererOwners, "main");
|
|
313
|
+
const routedRenderer = routeCalls(inputs.state.mainCalls, mainOwners, rendererOwners, "renderer");
|
|
314
|
+
return {
|
|
315
|
+
main: applyCalls(testIoc(), routedMain),
|
|
316
|
+
renderer: applyCalls(testIoc(), [...inputs.state.rendererCalls, ...routedRenderer])
|
|
113
317
|
};
|
|
318
|
+
}
|
|
319
|
+
async function materializeEnvironment(inputs) {
|
|
320
|
+
const controllers = await loadAssemblyControllers(inputs);
|
|
321
|
+
inputs.live.composed = await runCompose(inputs.mainRegistrations.root, inputs.source, inputs.context);
|
|
322
|
+
installAssemblyRuntime(inputs, controllers.main);
|
|
323
|
+
await configureRendererRegistrations(inputs);
|
|
324
|
+
registerControllersBeforeOverlays(inputs, controllers.main, controllers.renderer);
|
|
325
|
+
const ioc = buildAssemblyIoc(inputs);
|
|
326
|
+
inputs.live.env = await buildRecordedEnvironment(ioc.main, inputs.mainRegistrations, "main");
|
|
327
|
+
const root = inputs.live.env.root;
|
|
328
|
+
inputs.live.router = await Promise.resolve(
|
|
329
|
+
ExecutionContext.run(root, () => root.get(WoodRouter))
|
|
330
|
+
);
|
|
331
|
+
await ExecutionContext.run(root, () => inputs.live.composed?.start?.());
|
|
332
|
+
assertNoGlobalContainerLeak(inputs.registryBefore, inputs.source);
|
|
333
|
+
if (inputs.catalog && inputs.rendererRegistrations) {
|
|
334
|
+
inputs.live.renderer = await buildRendererTier({
|
|
335
|
+
source: inputs.source,
|
|
336
|
+
router: inputs.live.router,
|
|
337
|
+
catalog: inputs.catalog,
|
|
338
|
+
operations: inputs.operations,
|
|
339
|
+
importer: inputs.state.importer,
|
|
340
|
+
hooks: inputs.live.composed,
|
|
341
|
+
dom: inputs.selection.dom,
|
|
342
|
+
ioc: ioc.renderer,
|
|
343
|
+
registrations: inputs.rendererRegistrations,
|
|
344
|
+
controllerClasses: controllers.renderer
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return environmentSurface({
|
|
348
|
+
root,
|
|
349
|
+
env: inputs.live.env,
|
|
350
|
+
router: inputs.live.router,
|
|
351
|
+
operations: inputs.operations,
|
|
352
|
+
renderer: inputs.live.renderer,
|
|
353
|
+
controllerScopes: inputs.controllerScopes,
|
|
354
|
+
dispose: inputs.dispose
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
async function rollbackEnvironment(error, dispose) {
|
|
114
358
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
359
|
+
await dispose();
|
|
360
|
+
} catch (cleanupError) {
|
|
361
|
+
const failures = [
|
|
362
|
+
...error instanceof TestWoodCleanupError ? error.failures : [],
|
|
363
|
+
...cleanupError instanceof TestWoodCleanupError ? cleanupError.failures : [{ owner: "build rollback", error: cleanupError }]
|
|
364
|
+
];
|
|
365
|
+
throw new TestWoodCleanupError("testWood build failed and rollback failed", failures, error);
|
|
366
|
+
}
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
async function assembleEnvironment(state, selection) {
|
|
370
|
+
const source = loadWoodConfigSource(state.configPath);
|
|
371
|
+
const operations = selectOperations(source.operations, selection.main);
|
|
372
|
+
const catalog = selection.pages ? selectCatalog(source.views(), selection.pages) : null;
|
|
373
|
+
if (!catalog && state.rendererCalls.length) {
|
|
374
|
+
throw new TestWoodError(".renderer() composition requires a selected pages tier");
|
|
375
|
+
}
|
|
376
|
+
const registryBefore = globalRegistrySize();
|
|
377
|
+
const mainRegistrations = new RegistrationOnlyRoot("main.testing.compose");
|
|
378
|
+
const rendererRegistrations = catalog ? new RegistrationOnlyRoot("configureRendererRoot") : null;
|
|
379
|
+
const controllerScopes = [];
|
|
380
|
+
const live = {
|
|
381
|
+
composed: null,
|
|
382
|
+
env: null,
|
|
383
|
+
renderer: null,
|
|
384
|
+
router: null
|
|
385
|
+
};
|
|
386
|
+
const context = createComposeContext(source, state, selection, operations, live);
|
|
387
|
+
const dispose = createAssemblyDisposer(
|
|
388
|
+
live,
|
|
389
|
+
mainRegistrations,
|
|
390
|
+
rendererRegistrations,
|
|
391
|
+
controllerScopes
|
|
392
|
+
);
|
|
393
|
+
const inputs = {
|
|
394
|
+
state,
|
|
395
|
+
selection,
|
|
396
|
+
source,
|
|
397
|
+
operations,
|
|
398
|
+
catalog,
|
|
399
|
+
registryBefore,
|
|
400
|
+
mainRegistrations,
|
|
401
|
+
rendererRegistrations,
|
|
402
|
+
controllerScopes,
|
|
403
|
+
live,
|
|
404
|
+
context,
|
|
405
|
+
dispose
|
|
406
|
+
};
|
|
407
|
+
try {
|
|
408
|
+
return await materializeEnvironment(inputs);
|
|
130
409
|
} catch (error) {
|
|
131
|
-
|
|
132
|
-
});
|
|
133
|
-
throw error;
|
|
410
|
+
return rollbackEnvironment(error, dispose);
|
|
134
411
|
}
|
|
135
412
|
}
|
|
136
413
|
function environmentSurface(parts) {
|
|
@@ -152,9 +429,18 @@ function environmentSurface(parts) {
|
|
|
152
429
|
},
|
|
153
430
|
controller: async (controller) => {
|
|
154
431
|
const scope = root.extend();
|
|
155
|
-
|
|
432
|
+
const ownsController = !root.isRegistered(controller);
|
|
433
|
+
let instance;
|
|
434
|
+
controllerScopes.push({ dispose: () => ExecutionContext.run(scope, () => cleanupAll("controller cleanup failed", [
|
|
435
|
+
["controller", async () => {
|
|
436
|
+
if (ownsController) await disposeControllerLifecycle(instance);
|
|
437
|
+
}],
|
|
438
|
+
["scope", () => scope.dispose()]
|
|
439
|
+
])) });
|
|
156
440
|
if (!scope.isRegistered(controller)) scope.registerClass(controller);
|
|
157
|
-
|
|
441
|
+
instance = await ExecutionContext.run(scope, () => scope.get(controller));
|
|
442
|
+
guardControllerLifecycleOnce(instance);
|
|
443
|
+
return instance;
|
|
158
444
|
},
|
|
159
445
|
get: async (token) => await root.get(token),
|
|
160
446
|
get frontend() {
|
|
@@ -181,7 +467,19 @@ function environmentSurface(parts) {
|
|
|
181
467
|
// Method expectations only: `settle()` is the completion contract for
|
|
182
468
|
// owned renderer work, and a test may legitimately verify while a
|
|
183
469
|
// deliberately deferred stream is still in flight.
|
|
184
|
-
verify: () =>
|
|
470
|
+
verify: async () => {
|
|
471
|
+
const failures = [];
|
|
472
|
+
await env.verify().catch((error) => failures.push({ owner: "main", error }));
|
|
473
|
+
if (renderer) {
|
|
474
|
+
await renderer.verify().catch((error) => failures.push({ owner: "renderer", error }));
|
|
475
|
+
}
|
|
476
|
+
if (failures.length === 1) throw failures[0].error;
|
|
477
|
+
if (failures.length) {
|
|
478
|
+
throw new TestWoodError(
|
|
479
|
+
"testWood verification failed in both roots:\n " + failures.map(({ owner, error }) => `${owner}: ${error instanceof Error ? error.message : String(error)}`).join("\n ")
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
},
|
|
185
483
|
dispose,
|
|
186
484
|
[ENV_REGISTRY]: env[ENV_REGISTRY]
|
|
187
485
|
};
|
|
@@ -197,7 +495,8 @@ class TestWoodBuilder {
|
|
|
197
495
|
return new TestWoodBuilder({
|
|
198
496
|
configPath: path.resolve(configPath),
|
|
199
497
|
selection: null,
|
|
200
|
-
|
|
498
|
+
mainCalls: [],
|
|
499
|
+
rendererCalls: [],
|
|
201
500
|
importer: defaultImporter
|
|
202
501
|
});
|
|
203
502
|
}
|
|
@@ -211,21 +510,33 @@ class TestWoodBuilder {
|
|
|
211
510
|
if (typeof importer !== "function") throw new TestWoodError("importer() expects a function");
|
|
212
511
|
return this.derive({ importer });
|
|
213
512
|
}
|
|
214
|
-
|
|
513
|
+
/** Explicitly compose or replace dependencies on the main-process root. */
|
|
514
|
+
main(compose) {
|
|
515
|
+
return this.append("main", recordComposer(compose));
|
|
516
|
+
}
|
|
517
|
+
/** Explicitly compose or replace dependencies on the renderer root. */
|
|
518
|
+
renderer(compose) {
|
|
519
|
+
return this.append("renderer", recordComposer(compose));
|
|
520
|
+
}
|
|
521
|
+
/** Reusable presets retain the historical main-root default. */
|
|
215
522
|
use(preset) {
|
|
216
|
-
return this.
|
|
523
|
+
return this.append("main", [{ method: "use", args: [preset], flat: false }]);
|
|
217
524
|
}
|
|
525
|
+
/** Exact-token replacement routed to the sole declared selected owner. */
|
|
218
526
|
classes(config) {
|
|
219
|
-
return this.
|
|
527
|
+
return this.append("main", [{ method: "classes", args: [config], flat: true }]);
|
|
220
528
|
}
|
|
529
|
+
/** Exact-token replacement routed to the sole declared selected owner. */
|
|
221
530
|
functions(config) {
|
|
222
|
-
return this.
|
|
531
|
+
return this.append("main", [{ method: "functions", args: [config], flat: true }]);
|
|
223
532
|
}
|
|
533
|
+
/** Exact-token replacement routed to the sole declared selected owner. */
|
|
224
534
|
values(config) {
|
|
225
|
-
return this.
|
|
535
|
+
return this.append("main", [{ method: "values", args: [config], flat: true }]);
|
|
226
536
|
}
|
|
537
|
+
/** Exact-token method seams routed to the sole declared selected owner. */
|
|
227
538
|
methods(config) {
|
|
228
|
-
return this.
|
|
539
|
+
return this.append("main", [{ method: "methods", args: [config], flat: true }]);
|
|
229
540
|
}
|
|
230
541
|
async build() {
|
|
231
542
|
if (!this.state.selection) {
|
|
@@ -233,6 +544,12 @@ class TestWoodBuilder {
|
|
|
233
544
|
}
|
|
234
545
|
return assembleEnvironment(this.state, resolveSelection(this.state.selection));
|
|
235
546
|
}
|
|
547
|
+
append(owner, calls) {
|
|
548
|
+
const start = this.state.mainCalls.length + this.state.rendererCalls.length;
|
|
549
|
+
const ordered = calls.map((call, index) => ({ ...call, order: start + index }));
|
|
550
|
+
applyCalls(testIoc(), ordered);
|
|
551
|
+
return owner === "main" ? this.derive({ mainCalls: [...this.state.mainCalls, ...ordered] }) : this.derive({ rendererCalls: [...this.state.rendererCalls, ...ordered] });
|
|
552
|
+
}
|
|
236
553
|
derive(patch) {
|
|
237
554
|
return new TestWoodBuilder({ ...this.state, ...patch });
|
|
238
555
|
}
|