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