@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.
Files changed (142) hide show
  1. package/README.md +31 -0
  2. package/dist/client/index.cjs +18 -11
  3. package/dist/client/index.cjs.map +1 -1
  4. package/dist/client/index.d.cts +21 -1
  5. package/dist/client/index.d.ts +21 -1
  6. package/dist/client/index.js +17 -11
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/codegen/renderer_app_generator.cjs +2 -9
  9. package/dist/codegen/renderer_app_generator.cjs.map +1 -1
  10. package/dist/codegen/renderer_app_generator.js +2 -9
  11. package/dist/codegen/renderer_app_generator.js.map +1 -1
  12. package/dist/controller/cleanup.cjs +39 -0
  13. package/dist/controller/cleanup.cjs.map +1 -0
  14. package/dist/controller/cleanup.d.cts +4 -0
  15. package/dist/controller/cleanup.d.ts +4 -0
  16. package/dist/controller/cleanup.js +15 -0
  17. package/dist/controller/cleanup.js.map +1 -0
  18. package/dist/controller/controller_lifecycle.cjs +26 -2
  19. package/dist/controller/controller_lifecycle.cjs.map +1 -1
  20. package/dist/controller/controller_lifecycle.d.cts +3 -1
  21. package/dist/controller/controller_lifecycle.d.ts +3 -1
  22. package/dist/controller/controller_lifecycle.js +24 -1
  23. package/dist/controller/controller_lifecycle.js.map +1 -1
  24. package/dist/frontend/frontend_lifecycle.cjs +5 -7
  25. package/dist/frontend/frontend_lifecycle.cjs.map +1 -1
  26. package/dist/frontend/frontend_lifecycle.d.cts +1 -14
  27. package/dist/frontend/frontend_lifecycle.d.ts +1 -14
  28. package/dist/frontend/frontend_lifecycle.js +5 -7
  29. package/dist/frontend/frontend_lifecycle.js.map +1 -1
  30. package/dist/frontend/frontend_surface.cjs +130 -104
  31. package/dist/frontend/frontend_surface.cjs.map +1 -1
  32. package/dist/frontend/frontend_surface.js +131 -105
  33. package/dist/frontend/frontend_surface.js.map +1 -1
  34. package/dist/frontend/work_registry.cjs +29 -4
  35. package/dist/frontend/work_registry.cjs.map +1 -1
  36. package/dist/frontend/work_registry.d.cts +2 -0
  37. package/dist/frontend/work_registry.d.ts +2 -0
  38. package/dist/frontend/work_registry.js +29 -4
  39. package/dist/frontend/work_registry.js.map +1 -1
  40. package/dist/index.d.cts +2 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/ipc/index.cjs +6 -2
  43. package/dist/ipc/index.cjs.map +1 -1
  44. package/dist/ipc/index.d.cts +9 -0
  45. package/dist/ipc/index.d.ts +9 -0
  46. package/dist/ipc/index.js +6 -2
  47. package/dist/ipc/index.js.map +1 -1
  48. package/dist/main/index.cjs +53 -35
  49. package/dist/main/index.cjs.map +1 -1
  50. package/dist/main/index.d.cts +11 -2
  51. package/dist/main/index.d.ts +11 -2
  52. package/dist/main/index.js +56 -40
  53. package/dist/main/index.js.map +1 -1
  54. package/dist/main/startup_failure_handler.cjs +67 -0
  55. package/dist/main/startup_failure_handler.cjs.map +1 -0
  56. package/dist/main/startup_failure_handler.d.cts +16 -0
  57. package/dist/main/startup_failure_handler.d.ts +16 -0
  58. package/dist/main/startup_failure_handler.js +43 -0
  59. package/dist/main/startup_failure_handler.js.map +1 -0
  60. package/dist/router/index.d.cts +3 -0
  61. package/dist/router/index.d.ts +3 -0
  62. package/dist/router/wood_router.cjs +204 -123
  63. package/dist/router/wood_router.cjs.map +1 -1
  64. package/dist/router/wood_router.d.cts +20 -0
  65. package/dist/router/wood_router.d.ts +20 -0
  66. package/dist/router/wood_router.js +206 -125
  67. package/dist/router/wood_router.js.map +1 -1
  68. package/dist/runtime/index.cjs +13 -0
  69. package/dist/runtime/index.cjs.map +1 -1
  70. package/dist/runtime/index.d.cts +8 -4
  71. package/dist/runtime/index.d.ts +8 -4
  72. package/dist/runtime/index.js +8 -0
  73. package/dist/runtime/index.js.map +1 -1
  74. package/dist/runtime/renderer_config_extension.cjs +1 -2
  75. package/dist/runtime/renderer_config_extension.cjs.map +1 -1
  76. package/dist/runtime/renderer_config_extension.d.cts +1 -1
  77. package/dist/runtime/renderer_config_extension.d.ts +1 -1
  78. package/dist/runtime/renderer_config_extension.js +1 -2
  79. package/dist/runtime/renderer_config_extension.js.map +1 -1
  80. package/dist/runtime/renderer_log_dispatcher.cjs +2 -0
  81. package/dist/runtime/renderer_log_dispatcher.cjs.map +1 -1
  82. package/dist/runtime/renderer_log_dispatcher.d.cts +1 -1
  83. package/dist/runtime/renderer_log_dispatcher.d.ts +1 -1
  84. package/dist/runtime/renderer_log_dispatcher.js +1 -0
  85. package/dist/runtime/renderer_log_dispatcher.js.map +1 -1
  86. package/dist/runtime/runtime.cjs +107 -57
  87. package/dist/runtime/runtime.cjs.map +1 -1
  88. package/dist/runtime/runtime.d.cts +12 -0
  89. package/dist/runtime/runtime.d.ts +12 -0
  90. package/dist/runtime/runtime.js +110 -59
  91. package/dist/runtime/runtime.js.map +1 -1
  92. package/dist/runtime/runtime_module.cjs +58 -0
  93. package/dist/runtime/runtime_module.cjs.map +1 -0
  94. package/dist/runtime/runtime_module.d.cts +22 -0
  95. package/dist/runtime/runtime_module.d.ts +22 -0
  96. package/dist/runtime/runtime_module.js +30 -0
  97. package/dist/runtime/runtime_module.js.map +1 -0
  98. package/dist/testing/create_controller_harness.d.cts +3 -0
  99. package/dist/testing/create_controller_harness.d.ts +3 -0
  100. package/dist/testing/create_router_bridge.d.cts +3 -0
  101. package/dist/testing/create_router_bridge.d.ts +3 -0
  102. package/dist/testing/create_test_router.d.cts +3 -0
  103. package/dist/testing/create_test_router.d.ts +3 -0
  104. package/dist/testing/index.cjs +3 -0
  105. package/dist/testing/index.cjs.map +1 -1
  106. package/dist/testing/index.d.cts +6 -1
  107. package/dist/testing/index.d.ts +6 -1
  108. package/dist/testing/index.js +2 -0
  109. package/dist/testing/index.js.map +1 -1
  110. package/dist/testing/registration_only_root.cjs +168 -0
  111. package/dist/testing/registration_only_root.cjs.map +1 -0
  112. package/dist/testing/registration_only_root.d.cts +46 -0
  113. package/dist/testing/registration_only_root.d.ts +46 -0
  114. package/dist/testing/registration_only_root.js +141 -0
  115. package/dist/testing/registration_only_root.js.map +1 -0
  116. package/dist/testing/test_wood_config.cjs +382 -70
  117. package/dist/testing/test_wood_config.cjs.map +1 -1
  118. package/dist/testing/test_wood_config.d.cts +25 -1
  119. package/dist/testing/test_wood_config.d.ts +25 -1
  120. package/dist/testing/test_wood_config.js +388 -71
  121. package/dist/testing/test_wood_config.js.map +1 -1
  122. package/dist/testing/wood_dom_animation.cjs +213 -0
  123. package/dist/testing/wood_dom_animation.cjs.map +1 -0
  124. package/dist/testing/wood_dom_animation.d.cts +89 -0
  125. package/dist/testing/wood_dom_animation.d.ts +89 -0
  126. package/dist/testing/wood_dom_animation.js +185 -0
  127. package/dist/testing/wood_dom_animation.js.map +1 -0
  128. package/dist/testing/wood_env_renderer.cjs +81 -32
  129. package/dist/testing/wood_env_renderer.cjs.map +1 -1
  130. package/dist/testing/wood_env_renderer.d.cts +32 -1
  131. package/dist/testing/wood_env_renderer.d.ts +32 -1
  132. package/dist/testing/wood_env_renderer.js +84 -32
  133. package/dist/testing/wood_env_renderer.js.map +1 -1
  134. package/dist/transport/in_process_router_transport.d.cts +3 -0
  135. package/dist/transport/in_process_router_transport.d.ts +3 -0
  136. package/dist/transport/index.d.cts +3 -0
  137. package/dist/transport/index.d.ts +3 -0
  138. package/package.json +8 -6
  139. package/src/components/NavigationShell.svelte +12 -7
  140. package/src/controller/cleanup.ts +8 -0
  141. package/src/controller/controller_lifecycle.ts +24 -0
  142. package/src/navigation/layout_preservation.ts +45 -0
@@ -1,5 +1,6 @@
1
1
  import { testWood, TestWoodBuilder } from "./test_wood_config.js";
2
2
  import { TestWoodError, WOOD_FRONTEND_BRIDGE } from "./test_wood.js";
3
+ import { TestWoodCleanupError } from "./registration_only_root.js";
3
4
  import { FrontendLifecycleOwner, FrontendWorkError, PageCatalogError, PageCatalog, WorkRegistry, TrackedTransport, createFrontendSurface, parseViewsSource, pageIdentity } from "../frontend/index.js";
4
5
  import { createTestContainer } from "./create_test_container.js";
5
6
  import {
@@ -52,6 +53,7 @@ export {
52
53
  PageCatalogError,
53
54
  RouterBridgeEvents,
54
55
  TestWoodBuilder,
56
+ TestWoodCleanupError,
55
57
  TestWoodError,
56
58
  TraceRecorder,
57
59
  TrackedTransport,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/index.ts"],"sourcesContent":["// Canonical entry point — config-driven, real-IoC testWood(configPath).\nexport { testWood, TestWoodBuilder } from './test_wood_config.js';\nexport type {\n TestWoodEnvironment,\n WoodComposeContext,\n WoodComposeMain,\n WoodComposeResult,\n} from './test_wood_config.js';\nexport type { WoodTestSelection, WoodMainSelection, WoodDomMode } from './wood_test_selection.js';\nexport type { WoodPageHost, WoodMountedPage, WoodRendererHooks } from './wood_env_renderer.js';\nexport type { ModuleImporter } from './wood_config_source.js';\nexport { TestWoodError, WOOD_FRONTEND_BRIDGE } from './test_wood.js';\nexport { FrontendLifecycleOwner, FrontendWorkError, PageCatalogError, PageCatalog, WorkRegistry, TrackedTransport, createFrontendSurface, parseViewsSource, pageIdentity } from '../frontend/index.js';\nexport type { FrontendApplicationSurface, FrontendOpenOptions, FrontendPage, FrontendLifecycle, RendererSelection, ViewsSource, CreateFrontendSurfaceOptions, WorkFailure, WorkDescriptor } from '../frontend/index.js';\n\n/** @deprecated createTestContainer's fake container bypasses real dependency\n * resolution and is retained only for parser/compiler fixtures that do not\n * claim production-equivalent IoC behavior. Canonical tests use testWood /\n * @noego/testing's testIoc, which build fresh REAL @noego/ioc roots. */\nexport { createTestContainer } from './create_test_container.js';\nexport {\n createControllerHarness,\n type ControllerHarnessResult,\n type CreateControllerHarnessOptions,\n} from './create_controller_harness.js';\nexport { createTestRouter, type TestRouterResult } from './create_test_router.js';\nexport { mockElectron, getElectronMockFactory } from './mock_electron.js';\nexport {\n createRouterBridge,\n RouterBridgeEvents,\n type RouterBridgeResult,\n} from './create_router_bridge.js';\nexport {\n browser,\n createBrowserBridgeFixture,\n startSharedBrowserViteServer,\n type MountedWoodBridge,\n type MountedWoodComponent,\n type MountedWoodNavigation,\n type MountedWoodRoute,\n type MountedWoodSurface,\n type MountedWoodTree,\n type MountComponentOptions,\n type MountRouteOptions,\n type MountTreeOptions,\n type WoodBridgeFixtureContext,\n type WoodBridgeFixtureInput,\n type WoodBridgeRecordedCall,\n type WoodBridgeRecordedEvent,\n type WoodRpcManifestEntryInput,\n type WoodBrowserExpect,\n type WoodHitTestResult,\n type WoodRectSnapshot,\n type WoodBrowserHarness,\n type WoodBrowserHarnessConfig,\n type WoodBrowserPageContext,\n type WoodBrowserTraceConfig,\n type WoodViewport,\n} from './browser.js';\nexport {\n automationAssertions,\n visible,\n hidden,\n insideViewport,\n notClipped,\n notOverlapping,\n hitTestReachable,\n sharedEdges,\n evenSpacing,\n stableAfterAnimation,\n rectsOverlap,\n gaps,\n sharedRectEdges,\n evenSpacingRects,\n type AssertionVerdict,\n type AutomationTarget,\n type AutomationRectTarget,\n type GeometryVerdict,\n type RectAxis,\n type RectBox,\n type RectEdge,\n} from './automation_assertions.js';\nexport {\n InProcessRouterTransport,\n LoopbackRemoteTransport,\n LoopbackRemoteTransportServer,\n assertLoopbackSerializable,\n LoopbackSerializationError,\n buildTransportBridge,\n type WoodRpcTransport,\n type WoodChannelContract,\n type WoodChannelMapLike,\n type WoodChannelInput,\n type WoodChannelOutput,\n type TypedCall,\n type TransportBridgeResult,\n} from '../transport/index.js';\nexport { TraceRecorder } from '../trace-testing/index.js';\nexport type {\n TraceDispatchResult,\n TraceEvent,\n TraceInput,\n TraceWaitOptions,\n} from '../tracing/index.js';\n"],"mappings":"AACA,SAAS,UAAU,uBAAuB;AAU1C,SAAS,eAAe,4BAA4B;AACpD,SAAS,wBAAwB,mBAAmB,kBAAkB,aAAa,cAAc,kBAAkB,uBAAuB,kBAAkB,oBAAoB;AAOhL,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,OAGK;AACP,SAAS,wBAA+C;AACxD,SAAS,cAAc,8BAA8B;AACrD;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAuBK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AACP,SAAS,qBAAqB;","names":[]}
1
+ {"version":3,"sources":["../../src/testing/index.ts"],"sourcesContent":["// Canonical entry point — config-driven, real-IoC testWood(configPath).\nexport { testWood, TestWoodBuilder } from './test_wood_config.js';\nexport type {\n TestWoodEnvironment,\n WoodComposeContext,\n WoodComposeMain,\n WoodComposeResult,\n WoodRootComposer,\n} from './test_wood_config.js';\nexport type { WoodTestSelection, WoodMainSelection, WoodDomMode } from './wood_test_selection.js';\nexport type { WoodPageHost, WoodMountedPage, WoodRendererHooks } from './wood_env_renderer.js';\nexport type { ModuleImporter } from './wood_config_source.js';\nexport { TestWoodError, WOOD_FRONTEND_BRIDGE } from './test_wood.js';\nexport { TestWoodCleanupError, type CleanupFailure } from './registration_only_root.js';\nexport { FrontendLifecycleOwner, FrontendWorkError, PageCatalogError, PageCatalog, WorkRegistry, TrackedTransport, createFrontendSurface, parseViewsSource, pageIdentity } from '../frontend/index.js';\nexport type { FrontendApplicationSurface, FrontendOpenOptions, FrontendPage, FrontendLifecycle, RendererSelection, ViewsSource, CreateFrontendSurfaceOptions, WorkFailure, WorkDescriptor } from '../frontend/index.js';\n\n/** @deprecated createTestContainer's fake container bypasses real dependency\n * resolution and is retained only for parser/compiler fixtures that do not\n * claim production-equivalent IoC behavior. Canonical tests use testWood /\n * @noego/testing's testIoc, which build fresh REAL @noego/ioc roots. */\nexport { createTestContainer } from './create_test_container.js';\nexport {\n createControllerHarness,\n type ControllerHarnessResult,\n type CreateControllerHarnessOptions,\n} from './create_controller_harness.js';\nexport { createTestRouter, type TestRouterResult } from './create_test_router.js';\nexport { mockElectron, getElectronMockFactory } from './mock_electron.js';\nexport {\n createRouterBridge,\n RouterBridgeEvents,\n type RouterBridgeResult,\n} from './create_router_bridge.js';\nexport {\n browser,\n createBrowserBridgeFixture,\n startSharedBrowserViteServer,\n type MountedWoodBridge,\n type MountedWoodComponent,\n type MountedWoodNavigation,\n type MountedWoodRoute,\n type MountedWoodSurface,\n type MountedWoodTree,\n type MountComponentOptions,\n type MountRouteOptions,\n type MountTreeOptions,\n type WoodBridgeFixtureContext,\n type WoodBridgeFixtureInput,\n type WoodBridgeRecordedCall,\n type WoodBridgeRecordedEvent,\n type WoodRpcManifestEntryInput,\n type WoodBrowserExpect,\n type WoodHitTestResult,\n type WoodRectSnapshot,\n type WoodBrowserHarness,\n type WoodBrowserHarnessConfig,\n type WoodBrowserPageContext,\n type WoodBrowserTraceConfig,\n type WoodViewport,\n} from './browser.js';\nexport {\n automationAssertions,\n visible,\n hidden,\n insideViewport,\n notClipped,\n notOverlapping,\n hitTestReachable,\n sharedEdges,\n evenSpacing,\n stableAfterAnimation,\n rectsOverlap,\n gaps,\n sharedRectEdges,\n evenSpacingRects,\n type AssertionVerdict,\n type AutomationTarget,\n type AutomationRectTarget,\n type GeometryVerdict,\n type RectAxis,\n type RectBox,\n type RectEdge,\n} from './automation_assertions.js';\nexport {\n InProcessRouterTransport,\n LoopbackRemoteTransport,\n LoopbackRemoteTransportServer,\n assertLoopbackSerializable,\n LoopbackSerializationError,\n buildTransportBridge,\n type WoodRpcTransport,\n type WoodChannelContract,\n type WoodChannelMapLike,\n type WoodChannelInput,\n type WoodChannelOutput,\n type TypedCall,\n type TransportBridgeResult,\n} from '../transport/index.js';\nexport { TraceRecorder } from '../trace-testing/index.js';\nexport type {\n TraceDispatchResult,\n TraceEvent,\n TraceInput,\n TraceWaitOptions,\n} from '../tracing/index.js';\n"],"mappings":"AACA,SAAS,UAAU,uBAAuB;AAW1C,SAAS,eAAe,4BAA4B;AACpD,SAAS,4BAAiD;AAC1D,SAAS,wBAAwB,mBAAmB,kBAAkB,aAAa,cAAc,kBAAkB,uBAAuB,kBAAkB,oBAAoB;AAOhL,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,OAGK;AACP,SAAS,wBAA+C;AACxD,SAAS,cAAc,8BAA8B;AACrD;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAuBK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AACP,SAAS,qBAAqB;","names":[]}
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var registration_only_root_exports = {};
20
+ __export(registration_only_root_exports, {
21
+ RegistrationOnlyRoot: () => RegistrationOnlyRoot,
22
+ TestWoodCleanupError: () => TestWoodCleanupError,
23
+ buildRecordedEnvironment: () => buildRecordedEnvironment,
24
+ cleanupAll: () => cleanupAll
25
+ });
26
+ module.exports = __toCommonJS(registration_only_root_exports);
27
+ var import_get_container = require("../runtime/get_container.js");
28
+ var import_test_wood = require("./test_wood.cjs");
29
+ function errorMessage(error) {
30
+ const message = error instanceof Error ? error.message : String(error);
31
+ const nested = error?.failures;
32
+ return Array.isArray(nested) ? message + ": " + nested.map((failure) => errorMessage(failure.error)).join("; ") : message;
33
+ }
34
+ class TestWoodCleanupError extends import_test_wood.TestWoodError {
35
+ constructor(message, failures, cause) {
36
+ super(
37
+ `${message} (${failures.length} failure(s)):
38
+ ` + failures.map(({ owner, error }) => `${owner}: ${errorMessage(error)}`).join("\n ")
39
+ );
40
+ this.name = "TestWoodCleanupError";
41
+ this.failures = failures;
42
+ if (cause !== void 0) this.cause = cause;
43
+ }
44
+ }
45
+ async function cleanupAll(message, actions) {
46
+ const failures = [];
47
+ for (const [owner, action] of actions) {
48
+ try {
49
+ await action();
50
+ } catch (error) {
51
+ failures.push({ owner, error });
52
+ }
53
+ }
54
+ if (failures.length) throw new TestWoodCleanupError(message, failures);
55
+ }
56
+ class RegistrationOnlyRoot {
57
+ constructor(hookName) {
58
+ this.hookName = hookName;
59
+ this.owners = /* @__PURE__ */ new Set();
60
+ this.actualRoot = (0, import_get_container.createWoodRoot)();
61
+ this.target = null;
62
+ this.replayed = false;
63
+ const registrationOnly = this;
64
+ this.root = new Proxy(this.actualRoot, {
65
+ get(_target, property) {
66
+ if (property === "registerClass") {
67
+ return (classDefinition, options) => {
68
+ registrationOnly.assertRecording("registerClass");
69
+ registrationOnly.declare(classDefinition);
70
+ registrationOnly.actualRoot.registerClass(classDefinition, options);
71
+ };
72
+ }
73
+ if (property === "registerFunction") {
74
+ return (token, factory, options) => {
75
+ registrationOnly.assertRecording("registerFunction");
76
+ registrationOnly.declare(token);
77
+ for (const dependency of options?.param ?? []) {
78
+ registrationOnly.declare(dependency);
79
+ }
80
+ registrationOnly.actualRoot.registerFunction(token, factory, options);
81
+ };
82
+ }
83
+ if (property === "isRegistered") {
84
+ return (token) => registrationOnly.target?.isRegistered(token) ?? registrationOnly.owners.has(token);
85
+ }
86
+ if (property === "isDisposed") {
87
+ return () => registrationOnly.target?.isDisposed() ?? false;
88
+ }
89
+ const live = registrationOnly.target;
90
+ if (live) {
91
+ const value = Reflect.get(live, property, live);
92
+ return typeof value === "function" ? value.bind(live) : value;
93
+ }
94
+ return (..._args) => {
95
+ const method = String(property);
96
+ throw new import_test_wood.TestWoodError(
97
+ `${registrationOnly.hookName} may only register dependencies before test overlays; ${method}() attempted eager resolution or root mutation. Move resolution to start(), a page lifecycle, or the built test environment.`
98
+ );
99
+ };
100
+ }
101
+ });
102
+ }
103
+ declare(token) {
104
+ if (this.owners.has(token)) return;
105
+ this.owners.add(token);
106
+ if (typeof token !== "function") return;
107
+ const injected = Reflect.getMetadata?.(
108
+ /* @__PURE__ */ Symbol.for("ioc:inject:tokens"),
109
+ token
110
+ );
111
+ const design = Reflect.getMetadata?.("design:paramtypes", token) ?? [];
112
+ const indices = /* @__PURE__ */ new Set([...design.keys(), ...injected?.keys() ?? []]);
113
+ for (const index of indices) {
114
+ this.declare(injected?.has(index) ? injected.get(index) : design[index]);
115
+ }
116
+ }
117
+ replay(root) {
118
+ if (this.replayed) throw new import_test_wood.TestWoodError(`${this.hookName} registrations were already replayed`);
119
+ this.replayed = true;
120
+ if (root !== this.actualRoot) throw new import_test_wood.TestWoodError("testWood requires @noego/testing with build({ root }) support; update the shared testing package.");
121
+ }
122
+ bind(root) {
123
+ if (!this.replayed) throw new import_test_wood.TestWoodError(`${this.hookName} registrations must be replayed before binding`);
124
+ this.target = root;
125
+ }
126
+ assertRecording(method) {
127
+ if (this.replayed) {
128
+ throw new import_test_wood.TestWoodError(
129
+ `${this.hookName} cannot call ${method}() after test overlays have been installed`
130
+ );
131
+ }
132
+ }
133
+ }
134
+ async function buildRecordedEnvironment(builder, recorded, owner) {
135
+ const partial = { root: recorded.actualRoot };
136
+ try {
137
+ const environment = await builder.build({
138
+ root: recorded.actualRoot,
139
+ prepare: (freshRoot) => {
140
+ partial.root = freshRoot;
141
+ recorded.replay(freshRoot);
142
+ }
143
+ });
144
+ recorded.bind(environment.root);
145
+ return environment;
146
+ } catch (error) {
147
+ const failures = [];
148
+ if (partial.root) {
149
+ try {
150
+ await partial.root.dispose();
151
+ } catch (cleanupError) {
152
+ failures.push({ owner: `${owner} root rollback`, error: cleanupError });
153
+ }
154
+ }
155
+ if (failures.length) {
156
+ throw new TestWoodCleanupError(`${owner} build failed and rollback failed`, failures, error);
157
+ }
158
+ throw error;
159
+ }
160
+ }
161
+ // Annotate the CommonJS export names for ESM import in node:
162
+ 0 && (module.exports = {
163
+ RegistrationOnlyRoot,
164
+ TestWoodCleanupError,
165
+ buildRecordedEnvironment,
166
+ cleanupAll
167
+ });
168
+ //# sourceMappingURL=registration_only_root.cjs.map
@@ -0,0 +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":[]}
@@ -0,0 +1,46 @@
1
+ import { IContainer } from '@noego/ioc';
2
+ import { TestIocBuilder, TestEnvironment } from '@noego/testing';
3
+ import { TestWoodError } from './test_wood.cjs';
4
+
5
+ interface CleanupFailure {
6
+ readonly owner: string;
7
+ readonly error: unknown;
8
+ }
9
+ /**
10
+ * Error raised after every requested owner has been given a chance to clean up.
11
+ * The structured failures are retained for assertions and callers that need to
12
+ * inspect the original errors.
13
+ */
14
+ declare class TestWoodCleanupError extends TestWoodError {
15
+ readonly failures: readonly CleanupFailure[];
16
+ constructor(message: string, failures: readonly CleanupFailure[], cause?: unknown);
17
+ }
18
+ /** Run every cleanup action in order, collecting rather than short-circuiting. */
19
+ declare function cleanupAll(message: string, actions: ReadonlyArray<readonly [owner: string, action: () => void | Promise<void>]>): Promise<void>;
20
+ /**
21
+ * A registration-only view of the real root owned by this environment.
22
+ *
23
+ * Registrations land immediately on that root; shared @noego/testing applies
24
+ * scenario overlays to it without replay. Resolution is unavailable until it has been
25
+ * built and the test overlays and method decorator are installed.
26
+ */
27
+ declare class RegistrationOnlyRoot {
28
+ private readonly hookName;
29
+ readonly owners: Set<unknown>;
30
+ readonly root: IContainer;
31
+ readonly actualRoot: IContainer;
32
+ private target;
33
+ private replayed;
34
+ constructor(hookName: string);
35
+ declare(token: unknown): void;
36
+ replay(root: IContainer): void;
37
+ bind(root: IContainer): void;
38
+ private assertRecording;
39
+ }
40
+ /**
41
+ * Materialize shared @noego/testing overlays on the same production root,
42
+ * disposing the partially built root if materialization fails.
43
+ */
44
+ declare function buildRecordedEnvironment(builder: TestIocBuilder, recorded: RegistrationOnlyRoot, owner: string): Promise<TestEnvironment>;
45
+
46
+ export { type CleanupFailure, RegistrationOnlyRoot, TestWoodCleanupError, buildRecordedEnvironment, cleanupAll };
@@ -0,0 +1,46 @@
1
+ import { IContainer } from '@noego/ioc';
2
+ import { TestIocBuilder, TestEnvironment } from '@noego/testing';
3
+ import { TestWoodError } from './test_wood.js';
4
+
5
+ interface CleanupFailure {
6
+ readonly owner: string;
7
+ readonly error: unknown;
8
+ }
9
+ /**
10
+ * Error raised after every requested owner has been given a chance to clean up.
11
+ * The structured failures are retained for assertions and callers that need to
12
+ * inspect the original errors.
13
+ */
14
+ declare class TestWoodCleanupError extends TestWoodError {
15
+ readonly failures: readonly CleanupFailure[];
16
+ constructor(message: string, failures: readonly CleanupFailure[], cause?: unknown);
17
+ }
18
+ /** Run every cleanup action in order, collecting rather than short-circuiting. */
19
+ declare function cleanupAll(message: string, actions: ReadonlyArray<readonly [owner: string, action: () => void | Promise<void>]>): Promise<void>;
20
+ /**
21
+ * A registration-only view of the real root owned by this environment.
22
+ *
23
+ * Registrations land immediately on that root; shared @noego/testing applies
24
+ * scenario overlays to it without replay. Resolution is unavailable until it has been
25
+ * built and the test overlays and method decorator are installed.
26
+ */
27
+ declare class RegistrationOnlyRoot {
28
+ private readonly hookName;
29
+ readonly owners: Set<unknown>;
30
+ readonly root: IContainer;
31
+ readonly actualRoot: IContainer;
32
+ private target;
33
+ private replayed;
34
+ constructor(hookName: string);
35
+ declare(token: unknown): void;
36
+ replay(root: IContainer): void;
37
+ bind(root: IContainer): void;
38
+ private assertRecording;
39
+ }
40
+ /**
41
+ * Materialize shared @noego/testing overlays on the same production root,
42
+ * disposing the partially built root if materialization fails.
43
+ */
44
+ declare function buildRecordedEnvironment(builder: TestIocBuilder, recorded: RegistrationOnlyRoot, owner: string): Promise<TestEnvironment>;
45
+
46
+ export { type CleanupFailure, RegistrationOnlyRoot, TestWoodCleanupError, buildRecordedEnvironment, cleanupAll };
@@ -0,0 +1,141 @@
1
+ import { createWoodRoot } from "../runtime/get_container.js";
2
+ import { TestWoodError } from "./test_wood.js";
3
+ function errorMessage(error) {
4
+ const message = error instanceof Error ? error.message : String(error);
5
+ const nested = error?.failures;
6
+ return Array.isArray(nested) ? message + ": " + nested.map((failure) => errorMessage(failure.error)).join("; ") : message;
7
+ }
8
+ class TestWoodCleanupError extends TestWoodError {
9
+ constructor(message, failures, cause) {
10
+ super(
11
+ `${message} (${failures.length} failure(s)):
12
+ ` + failures.map(({ owner, error }) => `${owner}: ${errorMessage(error)}`).join("\n ")
13
+ );
14
+ this.name = "TestWoodCleanupError";
15
+ this.failures = failures;
16
+ if (cause !== void 0) this.cause = cause;
17
+ }
18
+ }
19
+ async function cleanupAll(message, actions) {
20
+ const failures = [];
21
+ for (const [owner, action] of actions) {
22
+ try {
23
+ await action();
24
+ } catch (error) {
25
+ failures.push({ owner, error });
26
+ }
27
+ }
28
+ if (failures.length) throw new TestWoodCleanupError(message, failures);
29
+ }
30
+ class RegistrationOnlyRoot {
31
+ constructor(hookName) {
32
+ this.hookName = hookName;
33
+ this.owners = /* @__PURE__ */ new Set();
34
+ this.actualRoot = createWoodRoot();
35
+ this.target = null;
36
+ this.replayed = false;
37
+ const registrationOnly = this;
38
+ this.root = new Proxy(this.actualRoot, {
39
+ get(_target, property) {
40
+ if (property === "registerClass") {
41
+ return (classDefinition, options) => {
42
+ registrationOnly.assertRecording("registerClass");
43
+ registrationOnly.declare(classDefinition);
44
+ registrationOnly.actualRoot.registerClass(classDefinition, options);
45
+ };
46
+ }
47
+ if (property === "registerFunction") {
48
+ return (token, factory, options) => {
49
+ registrationOnly.assertRecording("registerFunction");
50
+ registrationOnly.declare(token);
51
+ for (const dependency of options?.param ?? []) {
52
+ registrationOnly.declare(dependency);
53
+ }
54
+ registrationOnly.actualRoot.registerFunction(token, factory, options);
55
+ };
56
+ }
57
+ if (property === "isRegistered") {
58
+ return (token) => registrationOnly.target?.isRegistered(token) ?? registrationOnly.owners.has(token);
59
+ }
60
+ if (property === "isDisposed") {
61
+ return () => registrationOnly.target?.isDisposed() ?? false;
62
+ }
63
+ const live = registrationOnly.target;
64
+ if (live) {
65
+ const value = Reflect.get(live, property, live);
66
+ return typeof value === "function" ? value.bind(live) : value;
67
+ }
68
+ return (..._args) => {
69
+ const method = String(property);
70
+ throw new TestWoodError(
71
+ `${registrationOnly.hookName} may only register dependencies before test overlays; ${method}() attempted eager resolution or root mutation. Move resolution to start(), a page lifecycle, or the built test environment.`
72
+ );
73
+ };
74
+ }
75
+ });
76
+ }
77
+ declare(token) {
78
+ if (this.owners.has(token)) return;
79
+ this.owners.add(token);
80
+ if (typeof token !== "function") return;
81
+ const injected = Reflect.getMetadata?.(
82
+ /* @__PURE__ */ Symbol.for("ioc:inject:tokens"),
83
+ token
84
+ );
85
+ const design = Reflect.getMetadata?.("design:paramtypes", token) ?? [];
86
+ const indices = /* @__PURE__ */ new Set([...design.keys(), ...injected?.keys() ?? []]);
87
+ for (const index of indices) {
88
+ this.declare(injected?.has(index) ? injected.get(index) : design[index]);
89
+ }
90
+ }
91
+ replay(root) {
92
+ if (this.replayed) throw new TestWoodError(`${this.hookName} registrations were already replayed`);
93
+ this.replayed = true;
94
+ if (root !== this.actualRoot) throw new TestWoodError("testWood requires @noego/testing with build({ root }) support; update the shared testing package.");
95
+ }
96
+ bind(root) {
97
+ if (!this.replayed) throw new TestWoodError(`${this.hookName} registrations must be replayed before binding`);
98
+ this.target = root;
99
+ }
100
+ assertRecording(method) {
101
+ if (this.replayed) {
102
+ throw new TestWoodError(
103
+ `${this.hookName} cannot call ${method}() after test overlays have been installed`
104
+ );
105
+ }
106
+ }
107
+ }
108
+ async function buildRecordedEnvironment(builder, recorded, owner) {
109
+ const partial = { root: recorded.actualRoot };
110
+ try {
111
+ const environment = await builder.build({
112
+ root: recorded.actualRoot,
113
+ prepare: (freshRoot) => {
114
+ partial.root = freshRoot;
115
+ recorded.replay(freshRoot);
116
+ }
117
+ });
118
+ recorded.bind(environment.root);
119
+ return environment;
120
+ } catch (error) {
121
+ const failures = [];
122
+ if (partial.root) {
123
+ try {
124
+ await partial.root.dispose();
125
+ } catch (cleanupError) {
126
+ failures.push({ owner: `${owner} root rollback`, error: cleanupError });
127
+ }
128
+ }
129
+ if (failures.length) {
130
+ throw new TestWoodCleanupError(`${owner} build failed and rollback failed`, failures, error);
131
+ }
132
+ throw error;
133
+ }
134
+ }
135
+ export {
136
+ RegistrationOnlyRoot,
137
+ TestWoodCleanupError,
138
+ buildRecordedEnvironment,
139
+ cleanupAll
140
+ };
141
+ //# sourceMappingURL=registration_only_root.js.map
@@ -0,0 +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.js';\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":[]}