@noego/wood 0.4.10 → 0.5.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 +118 -42
- package/dist/bridge/backend_bridge.cjs.map +1 -1
- package/dist/bridge/backend_bridge.js.map +1 -1
- package/dist/client/index.cjs +78 -21
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +18 -3
- package/dist/client/index.d.ts +18 -3
- package/dist/client/index.js +80 -22
- package/dist/client/index.js.map +1 -1
- package/dist/client/renderer_log_transport.cjs +151 -0
- package/dist/client/renderer_log_transport.cjs.map +1 -0
- package/dist/client/renderer_log_transport.d.cts +54 -0
- package/dist/client/renderer_log_transport.d.ts +54 -0
- package/dist/client/renderer_log_transport.js +123 -0
- package/dist/client/renderer_log_transport.js.map +1 -0
- package/dist/codegen/controller_registration_generator.cjs +13 -2
- package/dist/codegen/controller_registration_generator.cjs.map +1 -1
- package/dist/codegen/controller_registration_generator.d.cts +12 -1
- package/dist/codegen/controller_registration_generator.d.ts +12 -1
- package/dist/codegen/controller_registration_generator.js +10 -1
- package/dist/codegen/controller_registration_generator.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -0
- package/dist/codegen/index.d.ts +1 -0
- package/dist/codegen/preload_generator.cjs +13 -6
- package/dist/codegen/preload_generator.cjs.map +1 -1
- package/dist/codegen/preload_generator.d.cts +11 -2
- package/dist/codegen/preload_generator.d.ts +11 -2
- package/dist/codegen/preload_generator.js +13 -6
- package/dist/codegen/preload_generator.js.map +1 -1
- package/dist/codegen/renderer_app_generator.cjs +78 -42
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +78 -42
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/codegen/type_generator.cjs +1 -0
- package/dist/codegen/type_generator.cjs.map +1 -1
- package/dist/codegen/type_generator.js +1 -0
- package/dist/codegen/type_generator.js.map +1 -1
- package/dist/config/config_parser.cjs +95 -4
- package/dist/config/config_parser.cjs.map +1 -1
- package/dist/config/config_parser.d.cts +10 -2
- package/dist/config/config_parser.d.ts +10 -2
- package/dist/config/config_parser.js +92 -3
- package/dist/config/config_parser.js.map +1 -1
- package/dist/config/index.cjs +6 -2
- package/dist/config/index.cjs.map +1 -1
- package/dist/config/index.d.cts +2 -2
- package/dist/config/index.d.ts +2 -2
- package/dist/config/index.js +8 -2
- package/dist/config/index.js.map +1 -1
- package/dist/controller/controller_resolver.cjs +51 -3
- package/dist/controller/controller_resolver.cjs.map +1 -1
- package/dist/controller/controller_resolver.d.cts +27 -4
- package/dist/controller/controller_resolver.d.ts +27 -4
- package/dist/controller/controller_resolver.js +48 -2
- package/dist/controller/controller_resolver.js.map +1 -1
- package/dist/controller/index.cjs +6 -2
- package/dist/controller/index.cjs.map +1 -1
- package/dist/controller/index.d.cts +1 -1
- package/dist/controller/index.d.ts +1 -1
- package/dist/controller/index.js +4 -2
- package/dist/controller/index.js.map +1 -1
- package/dist/dev/wood_dev_server.cjs +4 -1
- package/dist/dev/wood_dev_server.cjs.map +1 -1
- package/dist/dev/wood_dev_server.js +5 -2
- package/dist/dev/wood_dev_server.js.map +1 -1
- package/dist/dev/yaml_watcher.cjs +1 -1
- package/dist/dev/yaml_watcher.cjs.map +1 -1
- package/dist/dev/yaml_watcher.d.cts +4 -0
- package/dist/dev/yaml_watcher.d.ts +4 -0
- package/dist/dev/yaml_watcher.js +1 -1
- package/dist/dev/yaml_watcher.js.map +1 -1
- package/dist/frontend/frontend_lifecycle.cjs +74 -0
- package/dist/frontend/frontend_lifecycle.cjs.map +1 -0
- package/dist/frontend/frontend_lifecycle.d.cts +50 -0
- package/dist/frontend/frontend_lifecycle.d.ts +50 -0
- package/dist/frontend/frontend_lifecycle.js +50 -0
- package/dist/frontend/frontend_lifecycle.js.map +1 -0
- package/dist/frontend/frontend_page.cjs +122 -0
- package/dist/frontend/frontend_page.cjs.map +1 -0
- package/dist/frontend/frontend_page.d.cts +62 -0
- package/dist/frontend/frontend_page.d.ts +62 -0
- package/dist/frontend/frontend_page.js +98 -0
- package/dist/frontend/frontend_page.js.map +1 -0
- package/dist/frontend/frontend_surface.cjs +249 -0
- package/dist/frontend/frontend_surface.cjs.map +1 -0
- package/dist/frontend/frontend_surface.d.cts +64 -0
- package/dist/frontend/frontend_surface.d.ts +64 -0
- package/dist/frontend/frontend_surface.js +224 -0
- package/dist/frontend/frontend_surface.js.map +1 -0
- package/dist/frontend/index.cjs +52 -0
- package/dist/frontend/index.cjs.map +1 -0
- package/dist/frontend/index.d.cts +10 -0
- package/dist/frontend/index.d.ts +10 -0
- package/dist/frontend/index.js +19 -0
- package/dist/frontend/index.js.map +1 -0
- package/dist/frontend/page_catalog.cjs +135 -0
- package/dist/frontend/page_catalog.cjs.map +1 -0
- package/dist/frontend/page_catalog.d.cts +56 -0
- package/dist/frontend/page_catalog.d.ts +56 -0
- package/dist/frontend/page_catalog.js +108 -0
- package/dist/frontend/page_catalog.js.map +1 -0
- package/dist/frontend/tracked_transport.cjs +55 -0
- package/dist/frontend/tracked_transport.cjs.map +1 -0
- package/dist/frontend/tracked_transport.d.cts +36 -0
- package/dist/frontend/tracked_transport.d.ts +36 -0
- package/dist/frontend/tracked_transport.js +31 -0
- package/dist/frontend/tracked_transport.js.map +1 -0
- package/dist/frontend/work_registry.cjs +153 -0
- package/dist/frontend/work_registry.cjs.map +1 -0
- package/dist/frontend/work_registry.d.cts +70 -0
- package/dist/frontend/work_registry.d.ts +70 -0
- package/dist/frontend/work_registry.js +128 -0
- package/dist/frontend/work_registry.js.map +1 -0
- package/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -7
- package/dist/index.d.ts +10 -7
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/main/index.cjs +10 -0
- package/dist/main/index.cjs.map +1 -1
- package/dist/main/index.d.cts +14 -2
- package/dist/main/index.d.ts +14 -2
- package/dist/main/index.js +16 -0
- package/dist/main/index.js.map +1 -1
- package/dist/middleware/middleware_resolver.cjs +22 -1
- package/dist/middleware/middleware_resolver.cjs.map +1 -1
- package/dist/middleware/middleware_resolver.d.cts +13 -1
- package/dist/middleware/middleware_resolver.d.ts +13 -1
- package/dist/middleware/middleware_resolver.js +22 -1
- package/dist/middleware/middleware_resolver.js.map +1 -1
- package/dist/navigation/index.cjs +3 -0
- package/dist/navigation/index.cjs.map +1 -1
- package/dist/navigation/index.d.cts +25 -1
- package/dist/navigation/index.d.ts +25 -1
- package/dist/navigation/index.js +2 -0
- package/dist/navigation/index.js.map +1 -1
- package/dist/{middleware/__fixtures__/middleware/auth.cjs → navigation/layout_preservation.cjs} +18 -20
- package/dist/navigation/layout_preservation.cjs.map +1 -0
- package/dist/navigation/layout_preservation.d.cts +2 -0
- package/dist/navigation/layout_preservation.d.ts +2 -0
- package/dist/navigation/layout_preservation.js +18 -0
- package/dist/navigation/layout_preservation.js.map +1 -0
- package/dist/parser/index.cjs +2 -0
- package/dist/parser/index.cjs.map +1 -1
- package/dist/parser/index.d.cts +1 -1
- package/dist/parser/index.d.ts +1 -1
- package/dist/parser/index.js +2 -1
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/operations_parser.cjs +6 -0
- package/dist/parser/operations_parser.cjs.map +1 -1
- package/dist/parser/operations_parser.d.cts +9 -2
- package/dist/parser/operations_parser.d.ts +9 -2
- package/dist/parser/operations_parser.js +5 -0
- package/dist/parser/operations_parser.js.map +1 -1
- package/dist/parser/views_parser.cjs.map +1 -1
- package/dist/parser/views_parser.js.map +1 -1
- package/dist/router/index.d.cts +1 -0
- package/dist/router/index.d.ts +1 -0
- package/dist/router/wood_router.cjs +35 -3
- package/dist/router/wood_router.cjs.map +1 -1
- package/dist/router/wood_router.d.cts +15 -0
- package/dist/router/wood_router.d.ts +15 -0
- package/dist/router/wood_router.js +36 -4
- package/dist/router/wood_router.js.map +1 -1
- package/dist/runtime/get_container.cjs +9 -34
- package/dist/runtime/get_container.cjs.map +1 -1
- package/dist/runtime/get_container.d.cts +24 -4
- package/dist/runtime/get_container.d.ts +24 -4
- package/dist/runtime/get_container.js +8 -23
- package/dist/runtime/get_container.js.map +1 -1
- package/dist/runtime/index.cjs +9 -0
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +5 -2
- package/dist/runtime/index.d.ts +5 -2
- package/dist/runtime/index.js +10 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/renderer_config_extension.cjs +50 -0
- package/dist/runtime/renderer_config_extension.cjs.map +1 -0
- package/dist/runtime/renderer_config_extension.d.cts +19 -0
- package/dist/runtime/renderer_config_extension.d.ts +19 -0
- package/dist/runtime/renderer_config_extension.js +24 -0
- package/dist/runtime/renderer_config_extension.js.map +1 -0
- package/dist/runtime/renderer_log_batch_receiver.cjs +73 -0
- package/dist/runtime/renderer_log_batch_receiver.cjs.map +1 -0
- package/dist/runtime/renderer_log_batch_receiver.d.cts +29 -0
- package/dist/runtime/renderer_log_batch_receiver.d.ts +29 -0
- package/dist/runtime/renderer_log_batch_receiver.js +48 -0
- package/dist/runtime/renderer_log_batch_receiver.js.map +1 -0
- package/dist/runtime/renderer_trace_batch_receiver_registry.d.cts +1 -1
- package/dist/runtime/renderer_trace_batch_receiver_registry.d.ts +1 -1
- package/dist/runtime/runtime.cjs +14 -0
- package/dist/runtime/runtime.cjs.map +1 -1
- package/dist/runtime/runtime.d.cts +2 -1
- package/dist/runtime/runtime.d.ts +2 -1
- package/dist/runtime/runtime.js +14 -0
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/stick/index.cjs.map +1 -1
- package/dist/stick/index.js.map +1 -1
- package/dist/testing/browser.cjs +104 -23
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +8 -3
- package/dist/testing/browser.d.ts +8 -3
- package/dist/testing/browser.js +102 -22
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/create_controller_harness.cjs +28 -8
- package/dist/testing/create_controller_harness.cjs.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +17 -2
- package/dist/testing/create_controller_harness.d.ts +17 -2
- package/dist/testing/create_controller_harness.js +28 -8
- package/dist/testing/create_controller_harness.js.map +1 -1
- package/dist/testing/create_router_bridge.d.cts +1 -0
- package/dist/testing/create_router_bridge.d.ts +1 -0
- package/dist/testing/create_test_router.d.cts +1 -0
- package/dist/testing/create_test_router.d.ts +1 -0
- package/dist/testing/index.cjs +31 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +20 -5
- package/dist/testing/index.d.ts +20 -5
- package/dist/testing/index.js +19 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/{controller/__fixtures__/controllers/not_a_class.cjs → testing/test_wood.cjs} +15 -7
- package/dist/testing/test_wood.cjs.map +1 -0
- package/dist/testing/test_wood.d.cts +16 -0
- package/dist/testing/test_wood.d.ts +16 -0
- package/dist/testing/test_wood.js +12 -0
- package/dist/testing/test_wood.js.map +1 -0
- package/dist/testing/test_wood_config.cjs +272 -0
- package/dist/testing/test_wood_config.cjs.map +1 -0
- package/dist/testing/test_wood_config.d.cts +111 -0
- package/dist/testing/test_wood_config.d.ts +111 -0
- package/dist/testing/test_wood_config.js +247 -0
- package/dist/testing/test_wood_config.js.map +1 -0
- package/dist/testing/wood_config_source.cjs +146 -0
- package/dist/testing/wood_config_source.cjs.map +1 -0
- package/dist/testing/wood_config_source.d.cts +36 -0
- package/dist/testing/wood_config_source.d.ts +36 -0
- package/dist/testing/wood_config_source.js +112 -0
- package/dist/testing/wood_config_source.js.map +1 -0
- package/dist/testing/wood_env_renderer.cjs +210 -0
- package/dist/testing/wood_env_renderer.cjs.map +1 -0
- package/dist/testing/wood_env_renderer.d.cts +74 -0
- package/dist/testing/wood_env_renderer.d.ts +74 -0
- package/dist/testing/wood_env_renderer.js +188 -0
- package/dist/testing/wood_env_renderer.js.map +1 -0
- package/dist/testing/wood_test_selection.cjs +94 -0
- package/dist/testing/wood_test_selection.cjs.map +1 -0
- package/dist/testing/wood_test_selection.d.cts +39 -0
- package/dist/testing/wood_test_selection.d.ts +39 -0
- package/dist/testing/wood_test_selection.js +68 -0
- package/dist/testing/wood_test_selection.js.map +1 -0
- package/dist/trace-testing/index.d.cts +1 -2
- package/dist/trace-testing/index.d.ts +1 -2
- package/dist/trace-testing/trace_recorder.cjs +1 -1
- package/dist/trace-testing/trace_recorder.cjs.map +1 -1
- package/dist/trace-testing/trace_recorder.d.cts +1 -2
- package/dist/trace-testing/trace_recorder.d.ts +1 -2
- package/dist/trace-testing/trace_recorder.js +1 -1
- package/dist/trace-testing/trace_recorder.js.map +1 -1
- package/dist/trace-transport/index.d.cts +1 -1
- package/dist/trace-transport/index.d.ts +1 -1
- package/dist/{trace_hub-Cob54qRx.d.cts → trace_hub-Cpo_nVze.d.cts} +21 -1
- package/dist/{trace_hub-Cob54qRx.d.ts → trace_hub-Cpo_nVze.d.ts} +21 -1
- package/dist/tracing/index.cjs +9 -2
- package/dist/tracing/index.cjs.map +1 -1
- package/dist/tracing/index.d.cts +3 -2
- package/dist/tracing/index.d.ts +3 -2
- package/dist/tracing/index.js +7 -1
- package/dist/tracing/index.js.map +1 -1
- package/dist/tracing/renderer_trace_batch_host.d.cts +1 -1
- package/dist/tracing/renderer_trace_batch_host.d.ts +1 -1
- package/dist/tracing/renderer_trace_batch_transport.cjs +48 -6
- package/dist/tracing/renderer_trace_batch_transport.cjs.map +1 -1
- package/dist/tracing/renderer_trace_batch_transport.d.cts +17 -1
- package/dist/tracing/renderer_trace_batch_transport.d.ts +17 -1
- package/dist/tracing/renderer_trace_batch_transport.js +48 -6
- package/dist/tracing/renderer_trace_batch_transport.js.map +1 -1
- package/dist/tracing/trace_batch_adapter.cjs +110 -0
- package/dist/tracing/trace_batch_adapter.cjs.map +1 -0
- package/dist/tracing/trace_batch_adapter.d.cts +36 -0
- package/dist/tracing/trace_batch_adapter.d.ts +36 -0
- package/dist/tracing/trace_batch_adapter.js +86 -0
- package/dist/tracing/trace_batch_adapter.js.map +1 -0
- package/dist/tracing/trace_commit_writer.d.cts +1 -1
- package/dist/tracing/trace_commit_writer.d.ts +1 -1
- package/dist/tracing/trace_hub.cjs +87 -20
- package/dist/tracing/trace_hub.cjs.map +1 -1
- package/dist/tracing/trace_hub.d.cts +1 -1
- package/dist/tracing/trace_hub.d.ts +1 -1
- package/dist/tracing/trace_hub.js +85 -20
- package/dist/tracing/trace_hub.js.map +1 -1
- package/dist/tracing/trace_provider.cjs +21 -6
- package/dist/tracing/trace_provider.cjs.map +1 -1
- package/dist/tracing/trace_provider.d.cts +13 -4
- package/dist/tracing/trace_provider.d.ts +13 -4
- package/dist/tracing/trace_provider.js +22 -7
- package/dist/tracing/trace_provider.js.map +1 -1
- package/dist/tracing/types/wood_trace_batch.types.cjs.map +1 -1
- package/dist/tracing/types/wood_trace_batch.types.d.cts +15 -1
- package/dist/tracing/types/wood_trace_batch.types.d.ts +15 -1
- package/dist/tracing/types/wood_trace_sink.types.d.cts +1 -1
- package/dist/tracing/types/wood_trace_sink.types.d.ts +1 -1
- package/dist/tracing/wood_trace_batch_contract.cjs +2 -0
- package/dist/tracing/wood_trace_batch_contract.cjs.map +1 -1
- package/dist/tracing/wood_trace_batch_contract.d.cts +1 -1
- package/dist/tracing/wood_trace_batch_contract.d.ts +1 -1
- package/dist/tracing/wood_trace_batch_contract.js +2 -0
- package/dist/tracing/wood_trace_batch_contract.js.map +1 -1
- package/dist/transport/build_transport_bridge.cjs +4 -2
- package/dist/transport/build_transport_bridge.cjs.map +1 -1
- package/dist/transport/build_transport_bridge.d.cts +7 -1
- package/dist/transport/build_transport_bridge.d.ts +7 -1
- package/dist/transport/build_transport_bridge.js +2 -1
- package/dist/transport/build_transport_bridge.js.map +1 -1
- package/dist/transport/in_process_router_transport.d.cts +1 -0
- package/dist/transport/in_process_router_transport.d.ts +1 -0
- package/dist/transport/index.d.cts +1 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/types/config.cjs.map +1 -1
- package/dist/types/config.d.cts +47 -1
- package/dist/types/config.d.ts +47 -1
- package/dist/types/context.cjs +12 -0
- package/dist/types/context.cjs.map +1 -1
- package/dist/types/context.d.cts +27 -1
- package/dist/types/context.d.ts +27 -1
- package/dist/types/context.js +4 -0
- package/dist/types/context.js.map +1 -1
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/operations.cjs.map +1 -1
- package/dist/types/operations.d.cts +2 -0
- package/dist/types/operations.d.ts +2 -0
- package/docs/axe.md +706 -0
- package/package.json +13 -5
- package/src/components/NavigationShell.scopes.browser.test.ts +363 -0
- package/src/components/NavigationShell.svelte +126 -49
- package/dist/controller/__fixtures__/controllers/chat.cjs +0 -32
- package/dist/controller/__fixtures__/controllers/chat.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/chat.d.cts +0 -11
- package/dist/controller/__fixtures__/controllers/chat.d.ts +0 -11
- package/dist/controller/__fixtures__/controllers/chat.js +0 -12
- package/dist/controller/__fixtures__/controllers/chat.js.map +0 -1
- package/dist/controller/__fixtures__/controllers/not_a_class.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/not_a_class.d.cts +0 -3
- package/dist/controller/__fixtures__/controllers/not_a_class.d.ts +0 -3
- package/dist/controller/__fixtures__/controllers/not_a_class.js +0 -5
- package/dist/controller/__fixtures__/controllers/not_a_class.js.map +0 -1
- package/dist/controller/__fixtures__/controllers/settings.controller.cjs +0 -32
- package/dist/controller/__fixtures__/controllers/settings.controller.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/settings.controller.d.cts +0 -12
- package/dist/controller/__fixtures__/controllers/settings.controller.d.ts +0 -12
- package/dist/controller/__fixtures__/controllers/settings.controller.js +0 -12
- package/dist/controller/__fixtures__/controllers/settings.controller.js.map +0 -1
- package/dist/controller/__fixtures__/controllers/test.cjs +0 -29
- package/dist/controller/__fixtures__/controllers/test.cjs.map +0 -1
- package/dist/controller/__fixtures__/controllers/test.d.cts +0 -7
- package/dist/controller/__fixtures__/controllers/test.d.ts +0 -7
- package/dist/controller/__fixtures__/controllers/test.js +0 -9
- package/dist/controller/__fixtures__/controllers/test.js.map +0 -1
- package/dist/loader/__fixtures__/layouts/app.load.cjs +0 -27
- package/dist/loader/__fixtures__/layouts/app.load.cjs.map +0 -1
- package/dist/loader/__fixtures__/layouts/app.load.d.cts +0 -5
- package/dist/loader/__fixtures__/layouts/app.load.d.ts +0 -5
- package/dist/loader/__fixtures__/layouts/app.load.js +0 -7
- package/dist/loader/__fixtures__/layouts/app.load.js.map +0 -1
- package/dist/loader/__fixtures__/views/test/page.load.cjs +0 -27
- package/dist/loader/__fixtures__/views/test/page.load.cjs.map +0 -1
- package/dist/loader/__fixtures__/views/test/page.load.d.cts +0 -6
- package/dist/loader/__fixtures__/views/test/page.load.d.ts +0 -6
- package/dist/loader/__fixtures__/views/test/page.load.js +0 -7
- package/dist/loader/__fixtures__/views/test/page.load.js.map +0 -1
- package/dist/middleware/__fixtures__/middleware/auth.cjs.map +0 -1
- package/dist/middleware/__fixtures__/middleware/auth.d.cts +0 -6
- package/dist/middleware/__fixtures__/middleware/auth.d.ts +0 -6
- package/dist/middleware/__fixtures__/middleware/auth.js +0 -19
- package/dist/middleware/__fixtures__/middleware/auth.js.map +0 -1
- package/dist/middleware/__fixtures__/middleware/sequence.cjs +0 -47
- package/dist/middleware/__fixtures__/middleware/sequence.cjs.map +0 -1
- package/dist/middleware/__fixtures__/middleware/sequence.d.cts +0 -7
- package/dist/middleware/__fixtures__/middleware/sequence.d.ts +0 -7
- package/dist/middleware/__fixtures__/middleware/sequence.js +0 -21
- package/dist/middleware/__fixtures__/middleware/sequence.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/router/wood_router.ts"],"sourcesContent":["import type { OperationDef, IpcContext, WoodMiddlewareContext } from '../types/index.cjs';\nimport { WoodValidationError } from '../types/index.cjs';\nimport { SchemaValidator } from '../validation/schema_validator.cjs';\nimport { MiddlewareResolver } from '../middleware/middleware_resolver.cjs';\nimport { buildMiddlewareContext, executeMiddleware } from '../middleware/middleware_pipeline.cjs';\nimport { ControllerResolver, buildControllerArgs } from '../controller/controller_resolver.cjs';\nimport { disposeControllerLifecycle } from '../controller/controller_lifecycle.cjs';\nimport { getLogger } from '@noego/logger';\nimport { publishMainTrace } from '../tracing/index.js';\n\nconst logger = getLogger('wood:router');\n\nfunction extractTraceConversationId(value: unknown): number | string | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n const record = value as Record<string, unknown>;\n if (typeof record.conversationId === 'number' || typeof record.conversationId === 'string') {\n return record.conversationId;\n }\n if (typeof record.threadId === 'number' || typeof record.threadId === 'string') {\n return record.threadId;\n }\n\n return undefined;\n}\n\n/** Reserved internal channels prefixed with __ to avoid collisions with user operations. */\nexport const RESERVED_CHANNELS = [\n '__load',\n '__navigate',\n '__window',\n '__debug',\n '__contextMenu',\n '__woodConfig',\n '__traceBatch',\n] as const;\nexport type ReservedChannel = (typeof RESERVED_CHANNELS)[number];\n\n/** Handler signature for reserved channels. */\nexport type ReservedChannelHandler = (data: unknown, event?: any) => Promise<unknown>;\n\nexport class WoodRouter {\n private operations = new Map<string, OperationDef>();\n private reservedHandlers = new Map<ReservedChannel, ReservedChannelHandler>();\n private validator: SchemaValidator;\n private middlewareResolver: MiddlewareResolver;\n private controllerResolver: ControllerResolver;\n private container: any; // IoC Container\n\n constructor(options: {\n container: any;\n controllersDir: string;\n middlewareDir: string;\n mode?: 'bundled' | 'dev';\n }) {\n this.container = options.container;\n this.validator = new SchemaValidator();\n this.middlewareResolver = new MiddlewareResolver(options.middlewareDir);\n this.controllerResolver = new ControllerResolver(options.controllersDir, {\n mode: options.mode,\n });\n }\n\n /**\n * Load operations from a parsed operations map.\n */\n loadOperations(operations: Map<string, OperationDef>): void {\n for (const [channel, opDef] of operations) {\n this.operations.set(channel, opDef);\n }\n logger.debug(`Loaded ${operations.size} operations`);\n }\n\n /**\n * Register a handler for a reserved channel (__load, __navigate, __window).\n * These channels are internal to Wood and serve as extension points for\n * the loader runner, navigation system, and window manager.\n */\n registerReservedHandler(channel: ReservedChannel, handler: ReservedChannelHandler): void {\n this.reservedHandlers.set(channel, handler);\n logger.debug(`Registered reserved handler for \"${channel}\"`);\n }\n\n /**\n * Register IPC handlers for all operations and reserved channels.\n * In production: registers with ipcMain.handle\n * In testing/bridge mode: handlers are callable via dispatch()\n */\n registerIpcHandlers(ipcMain: any): void {\n for (const channel of this.operations.keys()) {\n ipcMain.handle(channel, async (_event: any, data: unknown) => {\n return this.dispatch(channel, data, _event);\n });\n }\n\n // Register reserved channels\n for (const channel of RESERVED_CHANNELS) {\n ipcMain.handle(channel, async (_event: any, data: unknown) => {\n const handler = this.reservedHandlers.get(channel);\n if (!handler) {\n throw new Error(`No handler registered for reserved channel \"${channel}\"`);\n }\n return handler(data, _event);\n });\n }\n\n logger.debug(\n `Registered ${this.operations.size} operation handlers and ${RESERVED_CHANNELS.length} reserved channels`\n );\n }\n\n /**\n * The request lifecycle -- mirrors Dinner's pipeline.\n *\n * channel -> build context -> middleware -> validate -> resolve controller\n * -> create instance -> build args -> call action -> return result\n */\n async dispatch(channel: string, data: unknown, event?: any): Promise<unknown> {\n const opDef = this.operations.get(channel);\n if (!opDef) {\n throw new Error(`Unknown operation: \"${channel}\"`);\n }\n\n const conversationId = extractTraceConversationId(data);\n publishMainTrace('debug', {\n source: 'wood.router',\n type: 'dispatch.start',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n\n // 1. Build IPC context\n const ipcContext: IpcContext = {\n body: data ?? {},\n params: (data as any) ?? {},\n query: {},\n context: {},\n sender: event?.sender,\n windowId: undefined,\n };\n\n // 2. Execute middleware sequentially\n if (opDef.middleware.length > 0) {\n const mwContext = buildMiddlewareContext(ipcContext, channel);\n const middlewareFns = await Promise.all(\n opDef.middleware.map(spec => this.middlewareResolver.resolve(spec))\n );\n await executeMiddleware(middlewareFns, mwContext);\n }\n\n // 3. Validate input (if schema defined)\n if (opDef.inputSchema) {\n const result = this.validator.validate(\n channel,\n opDef.inputSchema as Record<string, unknown>,\n ipcContext.body\n );\n if (!result.valid) {\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.validation_error',\n message: 'Request validation failed',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n errorCount: result.errors?.length ?? 0,\n },\n });\n throw new WoodValidationError('Request validation failed', result.errors ?? []);\n }\n }\n\n // 4. Resolve controller class (cached)\n const ControllerClass = await this.controllerResolver.resolveClass(opDef.controller);\n\n // 5. Create instance via IoC (per-request scoped container)\n const scopedContainer = this.container.extend();\n const instance = await scopedContainer.instance(ControllerClass);\n\n // 6. Build controller args (fake req/res)\n const args = buildControllerArgs(ipcContext, scopedContainer);\n\n // 7. Call action, always dispose controller afterward\n const action = instance[opDef.action];\n let result: unknown;\n let actionError: unknown;\n\n try {\n if (typeof action !== 'function') {\n throw new Error(\n `Controller \"${opDef.controller}\" has no action \"${opDef.action}\"`\n );\n }\n\n result = await action.call(instance, args);\n publishMainTrace('debug', {\n source: 'wood.router',\n type: 'dispatch.success',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n } catch (error) {\n actionError = error;\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.error',\n message: error instanceof Error ? error.message : String(error),\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n } finally {\n try {\n await disposeControllerLifecycle(instance);\n } catch (disposeError) {\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.dispose.error',\n message: disposeError instanceof Error ? disposeError.message : String(disposeError),\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n\n if (!actionError) {\n actionError = disposeError;\n }\n }\n }\n\n if (actionError) {\n throw actionError;\n }\n\n return result;\n }\n\n /**\n * Pre-register a controller class by name so it is resolved from\n * the in-memory cache instead of the filesystem.\n * This is the public API used by the generated registration file.\n */\n registerController(name: string, ControllerClass: any): void {\n this.controllerResolver.register(name, ControllerClass);\n }\n\n /**\n * Register an import path for a controller. The controller will be lazily\n * imported from this path when first needed, avoiding circular dependencies.\n */\n registerControllerPath(name: string, importPath: string): void {\n this.controllerResolver.registerPath(name, importPath);\n }\n\n /**\n * Validate that every controller referenced in loaded operations is\n * pre-registered in the controller resolver. Call this after\n * loadOperations() + registerControllers() but before registerIpcHandlers().\n *\n * Throws with a clear error listing all missing controllers if any\n * operation references a controller that was not pre-registered.\n */\n validateControllers(): void {\n const missing: Array<{ channel: string; controller: string }> = [];\n\n for (const [channel, opDef] of this.operations) {\n if (!this.controllerResolver.has(opDef.controller)) {\n missing.push({ channel, controller: opDef.controller });\n }\n }\n\n if (missing.length > 0) {\n const details = missing\n .map(m => ` - channel \"${m.channel}\" requires controller \"${m.controller}\"`)\n .join('\\n');\n\n throw new Error(\n `WoodRouter: ${missing.length} operation(s) reference controllers that are not registered.\\n` +\n `This usually means controllers.generated.ts is missing or out of date.\\n` +\n `Run \"npx wood build\" or \"npx wood dev\" to regenerate it.\\n\\n` +\n `Missing controllers:\\n${details}`\n );\n }\n\n logger.info(`All ${this.operations.size} operations have registered controllers`);\n }\n\n /**\n * Eagerly compile every operation's inputSchema so a malformed schema fails\n * at startup (with the offending channel named) instead of throwing an opaque\n * ajv error on the first request to that channel. Call after loadOperations().\n */\n precompileSchemas(): void {\n let compiled = 0;\n for (const [channel, opDef] of this.operations) {\n if (opDef.inputSchema) {\n this.validator.precompile(channel, opDef.inputSchema as Record<string, unknown>);\n compiled += 1;\n }\n }\n logger.info(`Pre-compiled ${compiled} operation input schema(s)`);\n }\n\n /** Get all registered operation channels. */\n getChannels(): string[] {\n return Array.from(this.operations.keys());\n }\n\n /** Get a specific operation definition. */\n getOperation(channel: string): OperationDef | undefined {\n return this.operations.get(channel);\n }\n}\n"],"mappings":"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","names":["result"]}
|
|
1
|
+
{"version":3,"sources":["../../src/router/wood_router.ts"],"sourcesContent":["import type { OperationDef, IpcContext, WoodMiddlewareContext } from '../types/index.cjs';\nimport { WoodValidationError } from '../types/index.cjs';\nimport { SchemaValidator } from '../validation/schema_validator.cjs';\nimport { MiddlewareResolver } from '../middleware/middleware_resolver.cjs';\nimport { buildMiddlewareContext, executeMiddleware } from '../middleware/middleware_pipeline.cjs';\nimport { ControllerResolver, buildControllerArgs, buildOperationContext, usesOperationContext } from '../controller/controller_resolver.cjs';\nimport { disposeControllerLifecycle } from '../controller/controller_lifecycle.cjs';\nimport { getLogger } from '@noego/logger';\nimport { ExecutionContext } from '@noego/ioc';\nimport { publishMainTrace } from '../tracing/index.js';\n\nconst logger = getLogger('wood:router');\n\nfunction extractTraceConversationId(value: unknown): number | string | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n const record = value as Record<string, unknown>;\n if (typeof record.conversationId === 'number' || typeof record.conversationId === 'string') {\n return record.conversationId;\n }\n if (typeof record.threadId === 'number' || typeof record.threadId === 'string') {\n return record.threadId;\n }\n\n return undefined;\n}\n\n/** Reserved internal channels prefixed with __ to avoid collisions with user operations. */\nexport const RESERVED_CHANNELS = [\n '__load',\n '__navigate',\n '__window',\n '__debug',\n '__contextMenu',\n '__woodConfig',\n '__traceBatch',\n] as const;\nexport type ReservedChannel = (typeof RESERVED_CHANNELS)[number];\n\n/** Handler signature for reserved channels. */\nexport type ReservedChannelHandler = (data: unknown, event?: any) => Promise<unknown>;\n\n/**\n * Whether the scope (or an ancestor) already holds a registration for the\n * controller token. Containers without the query (fakes, older runtimes)\n * report false so the router registers as it always did.\n */\nfunction controllerAlreadyRegistered(scope: unknown, ControllerClass: unknown): boolean {\n const candidate = scope as { isRegistered?: (label: unknown) => boolean };\n return typeof candidate.isRegistered === 'function' && candidate.isRegistered(ControllerClass);\n}\n\nexport class WoodRouter {\n private operations = new Map<string, OperationDef>();\n private reservedHandlers = new Map<ReservedChannel, ReservedChannelHandler>();\n private validator: SchemaValidator;\n private middlewareResolver: MiddlewareResolver;\n private controllerResolver: ControllerResolver;\n private container: any; // IoC Container\n\n constructor(options: {\n container: any;\n controllersDir: string;\n middlewareDir: string;\n mode?: 'bundled' | 'dev';\n }) {\n this.container = options.container;\n this.validator = new SchemaValidator();\n this.middlewareResolver = new MiddlewareResolver(options.middlewareDir);\n this.controllerResolver = new ControllerResolver(options.controllersDir, {\n mode: options.mode,\n });\n }\n\n /**\n * Load operations from a parsed operations map.\n */\n loadOperations(operations: Map<string, OperationDef>): void {\n for (const [channel, opDef] of operations) {\n this.operations.set(channel, opDef);\n }\n logger.debug(`Loaded ${operations.size} operations`);\n }\n\n /**\n * Register a handler for a reserved channel (__load, __navigate, __window).\n * These channels are internal to Wood and serve as extension points for\n * the loader runner, navigation system, and window manager.\n */\n registerReservedHandler(channel: ReservedChannel, handler: ReservedChannelHandler): void {\n this.reservedHandlers.set(channel, handler);\n logger.debug(`Registered reserved handler for \"${channel}\"`);\n }\n\n /**\n * Register IPC handlers for all operations and reserved channels.\n * In production: registers with ipcMain.handle\n * In testing/bridge mode: handlers are callable via dispatch()\n */\n registerIpcHandlers(ipcMain: any): void {\n for (const channel of this.operations.keys()) {\n ipcMain.handle(channel, async (_event: any, data: unknown) => {\n return this.dispatch(channel, data, _event);\n });\n }\n\n // Register reserved channels\n for (const channel of RESERVED_CHANNELS) {\n ipcMain.handle(channel, async (_event: any, data: unknown) => {\n const handler = this.reservedHandlers.get(channel);\n if (!handler) {\n throw new Error(`No handler registered for reserved channel \"${channel}\"`);\n }\n return handler(data, _event);\n });\n }\n\n logger.debug(\n `Registered ${this.operations.size} operation handlers and ${RESERVED_CHANNELS.length} reserved channels`\n );\n }\n\n /**\n * The request lifecycle -- mirrors Dinner's pipeline.\n *\n * channel -> build context -> middleware -> validate -> resolve controller\n * -> create instance -> build args -> call action -> return result\n */\n async dispatch(channel: string, data: unknown, event?: any): Promise<unknown> {\n const opDef = this.operations.get(channel);\n if (!opDef) {\n throw new Error(`Unknown operation: \"${channel}\"`);\n }\n\n const conversationId = extractTraceConversationId(data);\n publishMainTrace('debug', {\n source: 'wood.router',\n type: 'dispatch.start',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n\n // 1. Build IPC context\n const ipcContext: IpcContext = {\n body: data ?? {},\n params: (data as any) ?? {},\n query: {},\n context: {},\n sender: event?.sender,\n windowId: undefined,\n };\n\n // 2. Execute middleware sequentially\n if (opDef.middleware.length > 0) {\n const mwContext = buildMiddlewareContext(ipcContext, channel);\n const middlewareFns = await Promise.all(\n opDef.middleware.map(spec => this.middlewareResolver.resolve(spec))\n );\n await executeMiddleware(middlewareFns, mwContext);\n }\n\n // 3. Validate input (if schema defined)\n if (opDef.inputSchema) {\n const result = this.validator.validate(\n channel,\n opDef.inputSchema as Record<string, unknown>,\n ipcContext.body\n );\n if (!result.valid) {\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.validation_error',\n message: 'Request validation failed',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n errorCount: result.errors?.length ?? 0,\n },\n });\n throw new WoodValidationError('Request validation failed', result.errors ?? []);\n }\n }\n\n // 4. Resolve controller class (cached)\n const ControllerClass = await this.controllerResolver.resolveClass(opDef.controller);\n\n // 5. Create instance via IoC (per-request scoped container). Plain\n // undecorated controller classes are part of Wood's contract, so the\n // class is registered on the scope first — a bare registration defers\n // to @Component options when the class is decorated. A registration\n // already present in the hierarchy (a root-level substitution of the\n // controller token, e.g. a test seam) is left untouched so it keeps\n // deciding how the controller resolves inside this dispatch scope.\n const scopedContainer = this.container.extend();\n if (!controllerAlreadyRegistered(scopedContainer, ControllerClass)\n && typeof scopedContainer.registerClass === 'function') {\n scopedContainer.registerClass(ControllerClass);\n }\n // Framework boundary (D25): the whole operation — controller\n // construction included — executes under the operation scope's context.\n // Constructors may invoke injected collaborators, and any caller context\n // (e.g. an in-process renderer harness bridging into this backend root)\n // must be severed exactly as the production IPC process hop severs it.\n const instance = await ExecutionContext.run(scopedContainer, () =>\n scopedContainer.instance(ControllerClass)\n );\n\n // 6. Build the action argument: native operation context for controllers\n // that opted in (static woodContext = 'operation'), legacy fake req/res\n // args for everything else (migration-only compat).\n const args = usesOperationContext(ControllerClass)\n ? buildOperationContext(channel, ipcContext)\n : buildControllerArgs(ipcContext, scopedContainer);\n\n // 7. Call action, always dispose controller afterward\n const action = instance[opDef.action];\n let result: unknown;\n let actionError: unknown;\n\n try {\n if (typeof action !== 'function') {\n throw new Error(\n `Controller \"${opDef.controller}\" has no action \"${opDef.action}\"`\n );\n }\n\n // Framework boundary: the dispatch executes under ITS operation scope\n // (D25 — boundaries establish context automatically). This also severs\n // any caller context, e.g. an in-process renderer harness bridging a\n // call into this backend root — the production equivalent is the IPC\n // process hop, which carries no context at all.\n result = await ExecutionContext.run(scopedContainer, () => action.call(instance, args));\n publishMainTrace('debug', {\n source: 'wood.router',\n type: 'dispatch.success',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n } catch (error) {\n actionError = error;\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.error',\n message: error instanceof Error ? error.message : String(error),\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n } finally {\n try {\n await disposeControllerLifecycle(instance);\n // One operation = one IoC scope: dispose the complete scoped graph\n // (services, state, resources), not only the controller instance.\n // The controller-lifecycle hook runs first so legacy dispose/destroy\n // conventions keep their ordering; scope disposal dedupes instances\n // it has already seen. Containers without dispose() (legacy fakes)\n // are tolerated during migration.\n if (typeof (scopedContainer as { dispose?: () => Promise<void> }).dispose === 'function') {\n await (scopedContainer as { dispose: () => Promise<void> }).dispose();\n }\n } catch (disposeError) {\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.dispose.error',\n message: disposeError instanceof Error ? disposeError.message : String(disposeError),\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n\n if (!actionError) {\n actionError = disposeError;\n }\n }\n }\n\n if (actionError) {\n throw actionError;\n }\n\n return result;\n }\n\n /**\n * Pre-register a controller class by name so it is resolved from\n * the in-memory cache instead of the filesystem.\n * This is the public API used by the generated registration file.\n */\n registerController(name: string, ControllerClass: any): void {\n this.controllerResolver.register(name, ControllerClass);\n }\n\n /**\n * Pre-register the executable for a production middleware identity so it\n * resolves from the registry instead of the filesystem — the bundled-mode\n * path for middleware, and the seam test environments use to replace an\n * identity's behavior without touching source topology.\n */\n registerMiddleware(spec: string, middleware: import('../types/index.cjs').WoodMiddlewareFn): void {\n this.middlewareResolver.register(spec, middleware);\n }\n\n /**\n * Register an import path for a controller. The controller will be lazily\n * imported from this path when first needed, avoiding circular dependencies.\n */\n registerControllerPath(name: string, importPath: string): void {\n this.controllerResolver.registerPath(name, importPath);\n }\n\n /**\n * Register a caller-owned lazy loader for a controller: the module is\n * imported through the CALLER's `() => import('…literal specifier…')` only\n * on first dispatch, preserving the caller's module identity for the\n * controller graph (thin production slices in tests).\n */\n registerControllerLoader(name: string, loader: () => Promise<unknown>): void {\n this.controllerResolver.registerLoader(name, loader);\n }\n\n /**\n * Validate that every controller referenced in loaded operations is\n * pre-registered in the controller resolver. Call this after\n * loadOperations() + registerControllers() but before registerIpcHandlers().\n *\n * Throws with a clear error listing all missing controllers if any\n * operation references a controller that was not pre-registered.\n */\n validateControllers(): void {\n const missing: Array<{ channel: string; controller: string }> = [];\n\n for (const [channel, opDef] of this.operations) {\n if (!this.controllerResolver.has(opDef.controller)) {\n missing.push({ channel, controller: opDef.controller });\n }\n }\n\n if (missing.length > 0) {\n const details = missing\n .map(m => ` - channel \"${m.channel}\" requires controller \"${m.controller}\"`)\n .join('\\n');\n\n throw new Error(\n `WoodRouter: ${missing.length} operation(s) reference controllers that are not registered.\\n` +\n `This usually means controllers.generated.ts is missing or out of date.\\n` +\n `Run \"npx wood build\" or \"npx wood dev\" to regenerate it.\\n\\n` +\n `Missing controllers:\\n${details}`\n );\n }\n\n logger.info(`All ${this.operations.size} operations have registered controllers`);\n }\n\n /**\n * Eagerly compile every operation's inputSchema so a malformed schema fails\n * at startup (with the offending channel named) instead of throwing an opaque\n * ajv error on the first request to that channel. Call after loadOperations().\n */\n precompileSchemas(): void {\n let compiled = 0;\n for (const [channel, opDef] of this.operations) {\n if (opDef.inputSchema) {\n this.validator.precompile(channel, opDef.inputSchema as Record<string, unknown>);\n compiled += 1;\n }\n }\n logger.info(`Pre-compiled ${compiled} operation input schema(s)`);\n }\n\n /** Get all registered operation channels. */\n getChannels(): string[] {\n return Array.from(this.operations.keys());\n }\n\n /** Get a specific operation definition. */\n getOperation(channel: string): OperationDef | undefined {\n return this.operations.get(channel);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,mBAAoC;AACpC,8BAAgC;AAChC,iCAAmC;AACnC,iCAA0D;AAC1D,iCAAqG;AACrG,kCAA2C;AAC3C,oBAA0B;AAC1B,iBAAiC;AACjC,qBAAiC;AAEjC,MAAM,aAAS,yBAAU,aAAa;AAEtC,SAAS,2BAA2B,OAA6C;AAC/E,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,WAAO;AAAA,EACT;AAEA,QAAM,SAAS;AACf,MAAI,OAAO,OAAO,mBAAmB,YAAY,OAAO,OAAO,mBAAmB,UAAU;AAC1F,WAAO,OAAO;AAAA,EAChB;AACA,MAAI,OAAO,OAAO,aAAa,YAAY,OAAO,OAAO,aAAa,UAAU;AAC9E,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO;AACT;AAGO,MAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAWA,SAAS,4BAA4B,OAAgB,iBAAmC;AACtF,QAAM,YAAY;AAClB,SAAO,OAAO,UAAU,iBAAiB,cAAc,UAAU,aAAa,eAAe;AAC/F;AAEO,MAAM,WAAW;AAAA;AAAA,EAQtB,YAAY,SAKT;AAZH,SAAQ,aAAa,oBAAI,IAA0B;AACnD,SAAQ,mBAAmB,oBAAI,IAA6C;AAY1E,SAAK,YAAY,QAAQ;AACzB,SAAK,YAAY,IAAI,wCAAgB;AACrC,SAAK,qBAAqB,IAAI,8CAAmB,QAAQ,aAAa;AACtE,SAAK,qBAAqB,IAAI,8CAAmB,QAAQ,gBAAgB;AAAA,MACvE,MAAM,QAAQ;AAAA,IAChB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,YAA6C;AAC1D,eAAW,CAAC,SAAS,KAAK,KAAK,YAAY;AACzC,WAAK,WAAW,IAAI,SAAS,KAAK;AAAA,IACpC;AACA,WAAO,MAAM,UAAU,WAAW,IAAI,aAAa;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,wBAAwB,SAA0B,SAAuC;AACvF,SAAK,iBAAiB,IAAI,SAAS,OAAO;AAC1C,WAAO,MAAM,oCAAoC,OAAO,GAAG;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAAoB,SAAoB;AACtC,eAAW,WAAW,KAAK,WAAW,KAAK,GAAG;AAC5C,cAAQ,OAAO,SAAS,OAAO,QAAa,SAAkB;AAC5D,eAAO,KAAK,SAAS,SAAS,MAAM,MAAM;AAAA,MAC5C,CAAC;AAAA,IACH;AAGA,eAAW,WAAW,mBAAmB;AACvC,cAAQ,OAAO,SAAS,OAAO,QAAa,SAAkB;AAC5D,cAAM,UAAU,KAAK,iBAAiB,IAAI,OAAO;AACjD,YAAI,CAAC,SAAS;AACZ,gBAAM,IAAI,MAAM,+CAA+C,OAAO,GAAG;AAAA,QAC3E;AACA,eAAO,QAAQ,MAAM,MAAM;AAAA,MAC7B,CAAC;AAAA,IACH;AAEA,WAAO;AAAA,MACL,cAAc,KAAK,WAAW,IAAI,2BAA2B,kBAAkB,MAAM;AAAA,IACvF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,SAAS,SAAiB,MAAe,OAA+B;AAC5E,UAAM,QAAQ,KAAK,WAAW,IAAI,OAAO;AACzC,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,uBAAuB,OAAO,GAAG;AAAA,IACnD;AAEA,UAAM,iBAAiB,2BAA2B,IAAI;AACtD,yCAAiB,SAAS;AAAA,MACxB,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,GAAI,mBAAmB,SAAY,EAAE,eAAe,IAAI,CAAC;AAAA,MACzD,SAAS;AAAA,QACP;AAAA,QACA,YAAY,MAAM;AAAA,QAClB,QAAQ,MAAM;AAAA,MAChB;AAAA,IACF,CAAC;AAGD,UAAM,aAAyB;AAAA,MAC7B,MAAM,QAAQ,CAAC;AAAA,MACf,QAAS,QAAgB,CAAC;AAAA,MAC1B,OAAO,CAAC;AAAA,MACR,SAAS,CAAC;AAAA,MACV,QAAQ,OAAO;AAAA,MACf,UAAU;AAAA,IACZ;AAGA,QAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,YAAM,gBAAY,mDAAuB,YAAY,OAAO;AAC5D,YAAM,gBAAgB,MAAM,QAAQ;AAAA,QAClC,MAAM,WAAW,IAAI,UAAQ,KAAK,mBAAmB,QAAQ,IAAI,CAAC;AAAA,MACpE;AACA,gBAAM,8CAAkB,eAAe,SAAS;AAAA,IAClD;AAGA,QAAI,MAAM,aAAa;AACrB,YAAMA,UAAS,KAAK,UAAU;AAAA,QAC5B;AAAA,QACA,MAAM;AAAA,QACN,WAAW;AAAA,MACb;AACA,UAAI,CAACA,QAAO,OAAO;AACjB,6CAAiB,SAAS;AAAA,UACxB,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS;AAAA,UACT,GAAI,mBAAmB,SAAY,EAAE,eAAe,IAAI,CAAC;AAAA,UACzD,SAAS;AAAA,YACP;AAAA,YACA,YAAY,MAAM;AAAA,YAClB,QAAQ,MAAM;AAAA,YACd,YAAYA,QAAO,QAAQ,UAAU;AAAA,UACvC;AAAA,QACF,CAAC;AACD,cAAM,IAAI,iCAAoB,6BAA6BA,QAAO,UAAU,CAAC,CAAC;AAAA,MAChF;AAAA,IACF;AAGA,UAAM,kBAAkB,MAAM,KAAK,mBAAmB,aAAa,MAAM,UAAU;AASnF,UAAM,kBAAkB,KAAK,UAAU,OAAO;AAC9C,QAAI,CAAC,4BAA4B,iBAAiB,eAAe,KAC5D,OAAO,gBAAgB,kBAAkB,YAAY;AACxD,sBAAgB,cAAc,eAAe;AAAA,IAC/C;AAMA,UAAM,WAAW,MAAM,4BAAiB;AAAA,MAAI;AAAA,MAAiB,MAC3D,gBAAgB,SAAS,eAAe;AAAA,IAC1C;AAKA,UAAM,WAAO,iDAAqB,eAAe,QAC7C,kDAAsB,SAAS,UAAU,QACzC,gDAAoB,YAAY,eAAe;AAGnD,UAAM,SAAS,SAAS,MAAM,MAAM;AACpC,QAAI;AACJ,QAAI;AAEJ,QAAI;AACF,UAAI,OAAO,WAAW,YAAY;AAChC,cAAM,IAAI;AAAA,UACR,eAAe,MAAM,UAAU,oBAAoB,MAAM,MAAM;AAAA,QACjE;AAAA,MACF;AAOA,eAAS,MAAM,4BAAiB,IAAI,iBAAiB,MAAM,OAAO,KAAK,UAAU,IAAI,CAAC;AACtF,2CAAiB,SAAS;AAAA,QACxB,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,GAAI,mBAAmB,SAAY,EAAE,eAAe,IAAI,CAAC;AAAA,QACzD,SAAS;AAAA,UACP;AAAA,UACA,YAAY,MAAM;AAAA,UAClB,QAAQ,MAAM;AAAA,QAChB;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,oBAAc;AACd,2CAAiB,SAAS;AAAA,QACxB,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,QAC9D,GAAI,mBAAmB,SAAY,EAAE,eAAe,IAAI,CAAC;AAAA,QACzD,SAAS;AAAA,UACP;AAAA,UACA,YAAY,MAAM;AAAA,UAClB,QAAQ,MAAM;AAAA,QAChB;AAAA,MACF,CAAC;AAAA,IACH,UAAE;AACA,UAAI;AACF,kBAAM,wDAA2B,QAAQ;AAOzC,YAAI,OAAQ,gBAAsD,YAAY,YAAY;AACxF,gBAAO,gBAAqD,QAAQ;AAAA,QACtE;AAAA,MACF,SAAS,cAAc;AACrB,6CAAiB,SAAS;AAAA,UACxB,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,SAAS,wBAAwB,QAAQ,aAAa,UAAU,OAAO,YAAY;AAAA,UACnF,GAAI,mBAAmB,SAAY,EAAE,eAAe,IAAI,CAAC;AAAA,UACzD,SAAS;AAAA,YACP;AAAA,YACA,YAAY,MAAM;AAAA,YAClB,QAAQ,MAAM;AAAA,UAChB;AAAA,QACF,CAAC;AAED,YAAI,CAAC,aAAa;AAChB,wBAAc;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AAEA,QAAI,aAAa;AACf,YAAM;AAAA,IACR;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAmB,MAAc,iBAA4B;AAC3D,SAAK,mBAAmB,SAAS,MAAM,eAAe;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAmB,MAAc,YAAiE;AAChG,SAAK,mBAAmB,SAAS,MAAM,UAAU;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,uBAAuB,MAAc,YAA0B;AAC7D,SAAK,mBAAmB,aAAa,MAAM,UAAU;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,yBAAyB,MAAc,QAAsC;AAC3E,SAAK,mBAAmB,eAAe,MAAM,MAAM;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,sBAA4B;AAC1B,UAAM,UAA0D,CAAC;AAEjE,eAAW,CAAC,SAAS,KAAK,KAAK,KAAK,YAAY;AAC9C,UAAI,CAAC,KAAK,mBAAmB,IAAI,MAAM,UAAU,GAAG;AAClD,gBAAQ,KAAK,EAAE,SAAS,YAAY,MAAM,WAAW,CAAC;AAAA,MACxD;AAAA,IACF;AAEA,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,UAAU,QACb,IAAI,OAAK,gBAAgB,EAAE,OAAO,0BAA0B,EAAE,UAAU,GAAG,EAC3E,KAAK,IAAI;AAEZ,YAAM,IAAI;AAAA,QACR,eAAe,QAAQ,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,EAGJ,OAAO;AAAA,MAClC;AAAA,IACF;AAEA,WAAO,KAAK,OAAO,KAAK,WAAW,IAAI,yCAAyC;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,oBAA0B;AACxB,QAAI,WAAW;AACf,eAAW,CAAC,SAAS,KAAK,KAAK,KAAK,YAAY;AAC9C,UAAI,MAAM,aAAa;AACrB,aAAK,UAAU,WAAW,SAAS,MAAM,WAAsC;AAC/E,oBAAY;AAAA,MACd;AAAA,IACF;AACA,WAAO,KAAK,gBAAgB,QAAQ,4BAA4B;AAAA,EAClE;AAAA;AAAA,EAGA,cAAwB;AACtB,WAAO,MAAM,KAAK,KAAK,WAAW,KAAK,CAAC;AAAA,EAC1C;AAAA;AAAA,EAGA,aAAa,SAA2C;AACtD,WAAO,KAAK,WAAW,IAAI,OAAO;AAAA,EACpC;AACF;","names":["result"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OperationDef } from '../types/operations.cjs';
|
|
2
|
+
import { WoodMiddlewareFn } from '../types/context.cjs';
|
|
2
3
|
|
|
3
4
|
/** Reserved internal channels prefixed with __ to avoid collisions with user operations. */
|
|
4
5
|
declare const RESERVED_CHANNELS: readonly ["__load", "__navigate", "__window", "__debug", "__contextMenu", "__woodConfig", "__traceBatch"];
|
|
@@ -47,11 +48,25 @@ declare class WoodRouter {
|
|
|
47
48
|
* This is the public API used by the generated registration file.
|
|
48
49
|
*/
|
|
49
50
|
registerController(name: string, ControllerClass: any): void;
|
|
51
|
+
/**
|
|
52
|
+
* Pre-register the executable for a production middleware identity so it
|
|
53
|
+
* resolves from the registry instead of the filesystem — the bundled-mode
|
|
54
|
+
* path for middleware, and the seam test environments use to replace an
|
|
55
|
+
* identity's behavior without touching source topology.
|
|
56
|
+
*/
|
|
57
|
+
registerMiddleware(spec: string, middleware: WoodMiddlewareFn): void;
|
|
50
58
|
/**
|
|
51
59
|
* Register an import path for a controller. The controller will be lazily
|
|
52
60
|
* imported from this path when first needed, avoiding circular dependencies.
|
|
53
61
|
*/
|
|
54
62
|
registerControllerPath(name: string, importPath: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Register a caller-owned lazy loader for a controller: the module is
|
|
65
|
+
* imported through the CALLER's `() => import('…literal specifier…')` only
|
|
66
|
+
* on first dispatch, preserving the caller's module identity for the
|
|
67
|
+
* controller graph (thin production slices in tests).
|
|
68
|
+
*/
|
|
69
|
+
registerControllerLoader(name: string, loader: () => Promise<unknown>): void;
|
|
55
70
|
/**
|
|
56
71
|
* Validate that every controller referenced in loaded operations is
|
|
57
72
|
* pre-registered in the controller resolver. Call this after
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OperationDef } from '../types/operations.js';
|
|
2
|
+
import { WoodMiddlewareFn } from '../types/context.js';
|
|
2
3
|
|
|
3
4
|
/** Reserved internal channels prefixed with __ to avoid collisions with user operations. */
|
|
4
5
|
declare const RESERVED_CHANNELS: readonly ["__load", "__navigate", "__window", "__debug", "__contextMenu", "__woodConfig", "__traceBatch"];
|
|
@@ -47,11 +48,25 @@ declare class WoodRouter {
|
|
|
47
48
|
* This is the public API used by the generated registration file.
|
|
48
49
|
*/
|
|
49
50
|
registerController(name: string, ControllerClass: any): void;
|
|
51
|
+
/**
|
|
52
|
+
* Pre-register the executable for a production middleware identity so it
|
|
53
|
+
* resolves from the registry instead of the filesystem — the bundled-mode
|
|
54
|
+
* path for middleware, and the seam test environments use to replace an
|
|
55
|
+
* identity's behavior without touching source topology.
|
|
56
|
+
*/
|
|
57
|
+
registerMiddleware(spec: string, middleware: WoodMiddlewareFn): void;
|
|
50
58
|
/**
|
|
51
59
|
* Register an import path for a controller. The controller will be lazily
|
|
52
60
|
* imported from this path when first needed, avoiding circular dependencies.
|
|
53
61
|
*/
|
|
54
62
|
registerControllerPath(name: string, importPath: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Register a caller-owned lazy loader for a controller: the module is
|
|
65
|
+
* imported through the CALLER's `() => import('…literal specifier…')` only
|
|
66
|
+
* on first dispatch, preserving the caller's module identity for the
|
|
67
|
+
* controller graph (thin production slices in tests).
|
|
68
|
+
*/
|
|
69
|
+
registerControllerLoader(name: string, loader: () => Promise<unknown>): void;
|
|
55
70
|
/**
|
|
56
71
|
* Validate that every controller referenced in loaded operations is
|
|
57
72
|
* pre-registered in the controller resolver. Call this after
|
|
@@ -2,9 +2,10 @@ import { WoodValidationError } from "../types/index.js";
|
|
|
2
2
|
import { SchemaValidator } from "../validation/schema_validator.js";
|
|
3
3
|
import { MiddlewareResolver } from "../middleware/middleware_resolver.js";
|
|
4
4
|
import { buildMiddlewareContext, executeMiddleware } from "../middleware/middleware_pipeline.js";
|
|
5
|
-
import { ControllerResolver, buildControllerArgs } from "../controller/controller_resolver.js";
|
|
5
|
+
import { ControllerResolver, buildControllerArgs, buildOperationContext, usesOperationContext } from "../controller/controller_resolver.js";
|
|
6
6
|
import { disposeControllerLifecycle } from "../controller/controller_lifecycle.js";
|
|
7
7
|
import { getLogger } from "@noego/logger";
|
|
8
|
+
import { ExecutionContext } from "@noego/ioc";
|
|
8
9
|
import { publishMainTrace } from "../tracing/index.js";
|
|
9
10
|
const logger = getLogger("wood:router");
|
|
10
11
|
function extractTraceConversationId(value) {
|
|
@@ -29,6 +30,10 @@ const RESERVED_CHANNELS = [
|
|
|
29
30
|
"__woodConfig",
|
|
30
31
|
"__traceBatch"
|
|
31
32
|
];
|
|
33
|
+
function controllerAlreadyRegistered(scope, ControllerClass) {
|
|
34
|
+
const candidate = scope;
|
|
35
|
+
return typeof candidate.isRegistered === "function" && candidate.isRegistered(ControllerClass);
|
|
36
|
+
}
|
|
32
37
|
class WoodRouter {
|
|
33
38
|
// IoC Container
|
|
34
39
|
constructor(options) {
|
|
@@ -144,8 +149,14 @@ class WoodRouter {
|
|
|
144
149
|
}
|
|
145
150
|
const ControllerClass = await this.controllerResolver.resolveClass(opDef.controller);
|
|
146
151
|
const scopedContainer = this.container.extend();
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
if (!controllerAlreadyRegistered(scopedContainer, ControllerClass) && typeof scopedContainer.registerClass === "function") {
|
|
153
|
+
scopedContainer.registerClass(ControllerClass);
|
|
154
|
+
}
|
|
155
|
+
const instance = await ExecutionContext.run(
|
|
156
|
+
scopedContainer,
|
|
157
|
+
() => scopedContainer.instance(ControllerClass)
|
|
158
|
+
);
|
|
159
|
+
const args = usesOperationContext(ControllerClass) ? buildOperationContext(channel, ipcContext) : buildControllerArgs(ipcContext, scopedContainer);
|
|
149
160
|
const action = instance[opDef.action];
|
|
150
161
|
let result;
|
|
151
162
|
let actionError;
|
|
@@ -155,7 +166,7 @@ class WoodRouter {
|
|
|
155
166
|
`Controller "${opDef.controller}" has no action "${opDef.action}"`
|
|
156
167
|
);
|
|
157
168
|
}
|
|
158
|
-
result = await action.call(instance, args);
|
|
169
|
+
result = await ExecutionContext.run(scopedContainer, () => action.call(instance, args));
|
|
159
170
|
publishMainTrace("debug", {
|
|
160
171
|
source: "wood.router",
|
|
161
172
|
type: "dispatch.success",
|
|
@@ -182,6 +193,9 @@ class WoodRouter {
|
|
|
182
193
|
} finally {
|
|
183
194
|
try {
|
|
184
195
|
await disposeControllerLifecycle(instance);
|
|
196
|
+
if (typeof scopedContainer.dispose === "function") {
|
|
197
|
+
await scopedContainer.dispose();
|
|
198
|
+
}
|
|
185
199
|
} catch (disposeError) {
|
|
186
200
|
publishMainTrace("error", {
|
|
187
201
|
source: "wood.router",
|
|
@@ -212,6 +226,15 @@ class WoodRouter {
|
|
|
212
226
|
registerController(name, ControllerClass) {
|
|
213
227
|
this.controllerResolver.register(name, ControllerClass);
|
|
214
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Pre-register the executable for a production middleware identity so it
|
|
231
|
+
* resolves from the registry instead of the filesystem — the bundled-mode
|
|
232
|
+
* path for middleware, and the seam test environments use to replace an
|
|
233
|
+
* identity's behavior without touching source topology.
|
|
234
|
+
*/
|
|
235
|
+
registerMiddleware(spec, middleware) {
|
|
236
|
+
this.middlewareResolver.register(spec, middleware);
|
|
237
|
+
}
|
|
215
238
|
/**
|
|
216
239
|
* Register an import path for a controller. The controller will be lazily
|
|
217
240
|
* imported from this path when first needed, avoiding circular dependencies.
|
|
@@ -219,6 +242,15 @@ class WoodRouter {
|
|
|
219
242
|
registerControllerPath(name, importPath) {
|
|
220
243
|
this.controllerResolver.registerPath(name, importPath);
|
|
221
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Register a caller-owned lazy loader for a controller: the module is
|
|
247
|
+
* imported through the CALLER's `() => import('…literal specifier…')` only
|
|
248
|
+
* on first dispatch, preserving the caller's module identity for the
|
|
249
|
+
* controller graph (thin production slices in tests).
|
|
250
|
+
*/
|
|
251
|
+
registerControllerLoader(name, loader) {
|
|
252
|
+
this.controllerResolver.registerLoader(name, loader);
|
|
253
|
+
}
|
|
222
254
|
/**
|
|
223
255
|
* Validate that every controller referenced in loaded operations is
|
|
224
256
|
* pre-registered in the controller resolver. Call this after
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/router/wood_router.ts"],"sourcesContent":["import type { OperationDef, IpcContext, WoodMiddlewareContext } from '../types/index.js';\nimport { WoodValidationError } from '../types/index.js';\nimport { SchemaValidator } from '../validation/schema_validator.js';\nimport { MiddlewareResolver } from '../middleware/middleware_resolver.js';\nimport { buildMiddlewareContext, executeMiddleware } from '../middleware/middleware_pipeline.js';\nimport { ControllerResolver, buildControllerArgs } from '../controller/controller_resolver.js';\nimport { disposeControllerLifecycle } from '../controller/controller_lifecycle.js';\nimport { getLogger } from '@noego/logger';\nimport { publishMainTrace } from '../tracing/index.js';\n\nconst logger = getLogger('wood:router');\n\nfunction extractTraceConversationId(value: unknown): number | string | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n const record = value as Record<string, unknown>;\n if (typeof record.conversationId === 'number' || typeof record.conversationId === 'string') {\n return record.conversationId;\n }\n if (typeof record.threadId === 'number' || typeof record.threadId === 'string') {\n return record.threadId;\n }\n\n return undefined;\n}\n\n/** Reserved internal channels prefixed with __ to avoid collisions with user operations. */\nexport const RESERVED_CHANNELS = [\n '__load',\n '__navigate',\n '__window',\n '__debug',\n '__contextMenu',\n '__woodConfig',\n '__traceBatch',\n] as const;\nexport type ReservedChannel = (typeof RESERVED_CHANNELS)[number];\n\n/** Handler signature for reserved channels. */\nexport type ReservedChannelHandler = (data: unknown, event?: any) => Promise<unknown>;\n\nexport class WoodRouter {\n private operations = new Map<string, OperationDef>();\n private reservedHandlers = new Map<ReservedChannel, ReservedChannelHandler>();\n private validator: SchemaValidator;\n private middlewareResolver: MiddlewareResolver;\n private controllerResolver: ControllerResolver;\n private container: any; // IoC Container\n\n constructor(options: {\n container: any;\n controllersDir: string;\n middlewareDir: string;\n mode?: 'bundled' | 'dev';\n }) {\n this.container = options.container;\n this.validator = new SchemaValidator();\n this.middlewareResolver = new MiddlewareResolver(options.middlewareDir);\n this.controllerResolver = new ControllerResolver(options.controllersDir, {\n mode: options.mode,\n });\n }\n\n /**\n * Load operations from a parsed operations map.\n */\n loadOperations(operations: Map<string, OperationDef>): void {\n for (const [channel, opDef] of operations) {\n this.operations.set(channel, opDef);\n }\n logger.debug(`Loaded ${operations.size} operations`);\n }\n\n /**\n * Register a handler for a reserved channel (__load, __navigate, __window).\n * These channels are internal to Wood and serve as extension points for\n * the loader runner, navigation system, and window manager.\n */\n registerReservedHandler(channel: ReservedChannel, handler: ReservedChannelHandler): void {\n this.reservedHandlers.set(channel, handler);\n logger.debug(`Registered reserved handler for \"${channel}\"`);\n }\n\n /**\n * Register IPC handlers for all operations and reserved channels.\n * In production: registers with ipcMain.handle\n * In testing/bridge mode: handlers are callable via dispatch()\n */\n registerIpcHandlers(ipcMain: any): void {\n for (const channel of this.operations.keys()) {\n ipcMain.handle(channel, async (_event: any, data: unknown) => {\n return this.dispatch(channel, data, _event);\n });\n }\n\n // Register reserved channels\n for (const channel of RESERVED_CHANNELS) {\n ipcMain.handle(channel, async (_event: any, data: unknown) => {\n const handler = this.reservedHandlers.get(channel);\n if (!handler) {\n throw new Error(`No handler registered for reserved channel \"${channel}\"`);\n }\n return handler(data, _event);\n });\n }\n\n logger.debug(\n `Registered ${this.operations.size} operation handlers and ${RESERVED_CHANNELS.length} reserved channels`\n );\n }\n\n /**\n * The request lifecycle -- mirrors Dinner's pipeline.\n *\n * channel -> build context -> middleware -> validate -> resolve controller\n * -> create instance -> build args -> call action -> return result\n */\n async dispatch(channel: string, data: unknown, event?: any): Promise<unknown> {\n const opDef = this.operations.get(channel);\n if (!opDef) {\n throw new Error(`Unknown operation: \"${channel}\"`);\n }\n\n const conversationId = extractTraceConversationId(data);\n publishMainTrace('debug', {\n source: 'wood.router',\n type: 'dispatch.start',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n\n // 1. Build IPC context\n const ipcContext: IpcContext = {\n body: data ?? {},\n params: (data as any) ?? {},\n query: {},\n context: {},\n sender: event?.sender,\n windowId: undefined,\n };\n\n // 2. Execute middleware sequentially\n if (opDef.middleware.length > 0) {\n const mwContext = buildMiddlewareContext(ipcContext, channel);\n const middlewareFns = await Promise.all(\n opDef.middleware.map(spec => this.middlewareResolver.resolve(spec))\n );\n await executeMiddleware(middlewareFns, mwContext);\n }\n\n // 3. Validate input (if schema defined)\n if (opDef.inputSchema) {\n const result = this.validator.validate(\n channel,\n opDef.inputSchema as Record<string, unknown>,\n ipcContext.body\n );\n if (!result.valid) {\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.validation_error',\n message: 'Request validation failed',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n errorCount: result.errors?.length ?? 0,\n },\n });\n throw new WoodValidationError('Request validation failed', result.errors ?? []);\n }\n }\n\n // 4. Resolve controller class (cached)\n const ControllerClass = await this.controllerResolver.resolveClass(opDef.controller);\n\n // 5. Create instance via IoC (per-request scoped container)\n const scopedContainer = this.container.extend();\n const instance = await scopedContainer.instance(ControllerClass);\n\n // 6. Build controller args (fake req/res)\n const args = buildControllerArgs(ipcContext, scopedContainer);\n\n // 7. Call action, always dispose controller afterward\n const action = instance[opDef.action];\n let result: unknown;\n let actionError: unknown;\n\n try {\n if (typeof action !== 'function') {\n throw new Error(\n `Controller \"${opDef.controller}\" has no action \"${opDef.action}\"`\n );\n }\n\n result = await action.call(instance, args);\n publishMainTrace('debug', {\n source: 'wood.router',\n type: 'dispatch.success',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n } catch (error) {\n actionError = error;\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.error',\n message: error instanceof Error ? error.message : String(error),\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n } finally {\n try {\n await disposeControllerLifecycle(instance);\n } catch (disposeError) {\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.dispose.error',\n message: disposeError instanceof Error ? disposeError.message : String(disposeError),\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n\n if (!actionError) {\n actionError = disposeError;\n }\n }\n }\n\n if (actionError) {\n throw actionError;\n }\n\n return result;\n }\n\n /**\n * Pre-register a controller class by name so it is resolved from\n * the in-memory cache instead of the filesystem.\n * This is the public API used by the generated registration file.\n */\n registerController(name: string, ControllerClass: any): void {\n this.controllerResolver.register(name, ControllerClass);\n }\n\n /**\n * Register an import path for a controller. The controller will be lazily\n * imported from this path when first needed, avoiding circular dependencies.\n */\n registerControllerPath(name: string, importPath: string): void {\n this.controllerResolver.registerPath(name, importPath);\n }\n\n /**\n * Validate that every controller referenced in loaded operations is\n * pre-registered in the controller resolver. Call this after\n * loadOperations() + registerControllers() but before registerIpcHandlers().\n *\n * Throws with a clear error listing all missing controllers if any\n * operation references a controller that was not pre-registered.\n */\n validateControllers(): void {\n const missing: Array<{ channel: string; controller: string }> = [];\n\n for (const [channel, opDef] of this.operations) {\n if (!this.controllerResolver.has(opDef.controller)) {\n missing.push({ channel, controller: opDef.controller });\n }\n }\n\n if (missing.length > 0) {\n const details = missing\n .map(m => ` - channel \"${m.channel}\" requires controller \"${m.controller}\"`)\n .join('\\n');\n\n throw new Error(\n `WoodRouter: ${missing.length} operation(s) reference controllers that are not registered.\\n` +\n `This usually means controllers.generated.ts is missing or out of date.\\n` +\n `Run \"npx wood build\" or \"npx wood dev\" to regenerate it.\\n\\n` +\n `Missing controllers:\\n${details}`\n );\n }\n\n logger.info(`All ${this.operations.size} operations have registered controllers`);\n }\n\n /**\n * Eagerly compile every operation's inputSchema so a malformed schema fails\n * at startup (with the offending channel named) instead of throwing an opaque\n * ajv error on the first request to that channel. Call after loadOperations().\n */\n precompileSchemas(): void {\n let compiled = 0;\n for (const [channel, opDef] of this.operations) {\n if (opDef.inputSchema) {\n this.validator.precompile(channel, opDef.inputSchema as Record<string, unknown>);\n compiled += 1;\n }\n }\n logger.info(`Pre-compiled ${compiled} operation input schema(s)`);\n }\n\n /** Get all registered operation channels. */\n getChannels(): string[] {\n return Array.from(this.operations.keys());\n }\n\n /** Get a specific operation definition. */\n getOperation(channel: string): OperationDef | undefined {\n return this.operations.get(channel);\n }\n}\n"],"mappings":"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","names":["result"]}
|
|
1
|
+
{"version":3,"sources":["../../src/router/wood_router.ts"],"sourcesContent":["import type { OperationDef, IpcContext, WoodMiddlewareContext } from '../types/index.js';\nimport { WoodValidationError } from '../types/index.js';\nimport { SchemaValidator } from '../validation/schema_validator.js';\nimport { MiddlewareResolver } from '../middleware/middleware_resolver.js';\nimport { buildMiddlewareContext, executeMiddleware } from '../middleware/middleware_pipeline.js';\nimport { ControllerResolver, buildControllerArgs, buildOperationContext, usesOperationContext } from '../controller/controller_resolver.js';\nimport { disposeControllerLifecycle } from '../controller/controller_lifecycle.js';\nimport { getLogger } from '@noego/logger';\nimport { ExecutionContext } from '@noego/ioc';\nimport { publishMainTrace } from '../tracing/index.js';\n\nconst logger = getLogger('wood:router');\n\nfunction extractTraceConversationId(value: unknown): number | string | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n const record = value as Record<string, unknown>;\n if (typeof record.conversationId === 'number' || typeof record.conversationId === 'string') {\n return record.conversationId;\n }\n if (typeof record.threadId === 'number' || typeof record.threadId === 'string') {\n return record.threadId;\n }\n\n return undefined;\n}\n\n/** Reserved internal channels prefixed with __ to avoid collisions with user operations. */\nexport const RESERVED_CHANNELS = [\n '__load',\n '__navigate',\n '__window',\n '__debug',\n '__contextMenu',\n '__woodConfig',\n '__traceBatch',\n] as const;\nexport type ReservedChannel = (typeof RESERVED_CHANNELS)[number];\n\n/** Handler signature for reserved channels. */\nexport type ReservedChannelHandler = (data: unknown, event?: any) => Promise<unknown>;\n\n/**\n * Whether the scope (or an ancestor) already holds a registration for the\n * controller token. Containers without the query (fakes, older runtimes)\n * report false so the router registers as it always did.\n */\nfunction controllerAlreadyRegistered(scope: unknown, ControllerClass: unknown): boolean {\n const candidate = scope as { isRegistered?: (label: unknown) => boolean };\n return typeof candidate.isRegistered === 'function' && candidate.isRegistered(ControllerClass);\n}\n\nexport class WoodRouter {\n private operations = new Map<string, OperationDef>();\n private reservedHandlers = new Map<ReservedChannel, ReservedChannelHandler>();\n private validator: SchemaValidator;\n private middlewareResolver: MiddlewareResolver;\n private controllerResolver: ControllerResolver;\n private container: any; // IoC Container\n\n constructor(options: {\n container: any;\n controllersDir: string;\n middlewareDir: string;\n mode?: 'bundled' | 'dev';\n }) {\n this.container = options.container;\n this.validator = new SchemaValidator();\n this.middlewareResolver = new MiddlewareResolver(options.middlewareDir);\n this.controllerResolver = new ControllerResolver(options.controllersDir, {\n mode: options.mode,\n });\n }\n\n /**\n * Load operations from a parsed operations map.\n */\n loadOperations(operations: Map<string, OperationDef>): void {\n for (const [channel, opDef] of operations) {\n this.operations.set(channel, opDef);\n }\n logger.debug(`Loaded ${operations.size} operations`);\n }\n\n /**\n * Register a handler for a reserved channel (__load, __navigate, __window).\n * These channels are internal to Wood and serve as extension points for\n * the loader runner, navigation system, and window manager.\n */\n registerReservedHandler(channel: ReservedChannel, handler: ReservedChannelHandler): void {\n this.reservedHandlers.set(channel, handler);\n logger.debug(`Registered reserved handler for \"${channel}\"`);\n }\n\n /**\n * Register IPC handlers for all operations and reserved channels.\n * In production: registers with ipcMain.handle\n * In testing/bridge mode: handlers are callable via dispatch()\n */\n registerIpcHandlers(ipcMain: any): void {\n for (const channel of this.operations.keys()) {\n ipcMain.handle(channel, async (_event: any, data: unknown) => {\n return this.dispatch(channel, data, _event);\n });\n }\n\n // Register reserved channels\n for (const channel of RESERVED_CHANNELS) {\n ipcMain.handle(channel, async (_event: any, data: unknown) => {\n const handler = this.reservedHandlers.get(channel);\n if (!handler) {\n throw new Error(`No handler registered for reserved channel \"${channel}\"`);\n }\n return handler(data, _event);\n });\n }\n\n logger.debug(\n `Registered ${this.operations.size} operation handlers and ${RESERVED_CHANNELS.length} reserved channels`\n );\n }\n\n /**\n * The request lifecycle -- mirrors Dinner's pipeline.\n *\n * channel -> build context -> middleware -> validate -> resolve controller\n * -> create instance -> build args -> call action -> return result\n */\n async dispatch(channel: string, data: unknown, event?: any): Promise<unknown> {\n const opDef = this.operations.get(channel);\n if (!opDef) {\n throw new Error(`Unknown operation: \"${channel}\"`);\n }\n\n const conversationId = extractTraceConversationId(data);\n publishMainTrace('debug', {\n source: 'wood.router',\n type: 'dispatch.start',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n\n // 1. Build IPC context\n const ipcContext: IpcContext = {\n body: data ?? {},\n params: (data as any) ?? {},\n query: {},\n context: {},\n sender: event?.sender,\n windowId: undefined,\n };\n\n // 2. Execute middleware sequentially\n if (opDef.middleware.length > 0) {\n const mwContext = buildMiddlewareContext(ipcContext, channel);\n const middlewareFns = await Promise.all(\n opDef.middleware.map(spec => this.middlewareResolver.resolve(spec))\n );\n await executeMiddleware(middlewareFns, mwContext);\n }\n\n // 3. Validate input (if schema defined)\n if (opDef.inputSchema) {\n const result = this.validator.validate(\n channel,\n opDef.inputSchema as Record<string, unknown>,\n ipcContext.body\n );\n if (!result.valid) {\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.validation_error',\n message: 'Request validation failed',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n errorCount: result.errors?.length ?? 0,\n },\n });\n throw new WoodValidationError('Request validation failed', result.errors ?? []);\n }\n }\n\n // 4. Resolve controller class (cached)\n const ControllerClass = await this.controllerResolver.resolveClass(opDef.controller);\n\n // 5. Create instance via IoC (per-request scoped container). Plain\n // undecorated controller classes are part of Wood's contract, so the\n // class is registered on the scope first — a bare registration defers\n // to @Component options when the class is decorated. A registration\n // already present in the hierarchy (a root-level substitution of the\n // controller token, e.g. a test seam) is left untouched so it keeps\n // deciding how the controller resolves inside this dispatch scope.\n const scopedContainer = this.container.extend();\n if (!controllerAlreadyRegistered(scopedContainer, ControllerClass)\n && typeof scopedContainer.registerClass === 'function') {\n scopedContainer.registerClass(ControllerClass);\n }\n // Framework boundary (D25): the whole operation — controller\n // construction included — executes under the operation scope's context.\n // Constructors may invoke injected collaborators, and any caller context\n // (e.g. an in-process renderer harness bridging into this backend root)\n // must be severed exactly as the production IPC process hop severs it.\n const instance = await ExecutionContext.run(scopedContainer, () =>\n scopedContainer.instance(ControllerClass)\n );\n\n // 6. Build the action argument: native operation context for controllers\n // that opted in (static woodContext = 'operation'), legacy fake req/res\n // args for everything else (migration-only compat).\n const args = usesOperationContext(ControllerClass)\n ? buildOperationContext(channel, ipcContext)\n : buildControllerArgs(ipcContext, scopedContainer);\n\n // 7. Call action, always dispose controller afterward\n const action = instance[opDef.action];\n let result: unknown;\n let actionError: unknown;\n\n try {\n if (typeof action !== 'function') {\n throw new Error(\n `Controller \"${opDef.controller}\" has no action \"${opDef.action}\"`\n );\n }\n\n // Framework boundary: the dispatch executes under ITS operation scope\n // (D25 — boundaries establish context automatically). This also severs\n // any caller context, e.g. an in-process renderer harness bridging a\n // call into this backend root — the production equivalent is the IPC\n // process hop, which carries no context at all.\n result = await ExecutionContext.run(scopedContainer, () => action.call(instance, args));\n publishMainTrace('debug', {\n source: 'wood.router',\n type: 'dispatch.success',\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n } catch (error) {\n actionError = error;\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.error',\n message: error instanceof Error ? error.message : String(error),\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n } finally {\n try {\n await disposeControllerLifecycle(instance);\n // One operation = one IoC scope: dispose the complete scoped graph\n // (services, state, resources), not only the controller instance.\n // The controller-lifecycle hook runs first so legacy dispose/destroy\n // conventions keep their ordering; scope disposal dedupes instances\n // it has already seen. Containers without dispose() (legacy fakes)\n // are tolerated during migration.\n if (typeof (scopedContainer as { dispose?: () => Promise<void> }).dispose === 'function') {\n await (scopedContainer as { dispose: () => Promise<void> }).dispose();\n }\n } catch (disposeError) {\n publishMainTrace('error', {\n source: 'wood.router',\n type: 'dispatch.dispose.error',\n message: disposeError instanceof Error ? disposeError.message : String(disposeError),\n ...(conversationId !== undefined ? { conversationId } : {}),\n payload: {\n channel,\n controller: opDef.controller,\n action: opDef.action,\n },\n });\n\n if (!actionError) {\n actionError = disposeError;\n }\n }\n }\n\n if (actionError) {\n throw actionError;\n }\n\n return result;\n }\n\n /**\n * Pre-register a controller class by name so it is resolved from\n * the in-memory cache instead of the filesystem.\n * This is the public API used by the generated registration file.\n */\n registerController(name: string, ControllerClass: any): void {\n this.controllerResolver.register(name, ControllerClass);\n }\n\n /**\n * Pre-register the executable for a production middleware identity so it\n * resolves from the registry instead of the filesystem — the bundled-mode\n * path for middleware, and the seam test environments use to replace an\n * identity's behavior without touching source topology.\n */\n registerMiddleware(spec: string, middleware: import('../types/index.js').WoodMiddlewareFn): void {\n this.middlewareResolver.register(spec, middleware);\n }\n\n /**\n * Register an import path for a controller. The controller will be lazily\n * imported from this path when first needed, avoiding circular dependencies.\n */\n registerControllerPath(name: string, importPath: string): void {\n this.controllerResolver.registerPath(name, importPath);\n }\n\n /**\n * Register a caller-owned lazy loader for a controller: the module is\n * imported through the CALLER's `() => import('…literal specifier…')` only\n * on first dispatch, preserving the caller's module identity for the\n * controller graph (thin production slices in tests).\n */\n registerControllerLoader(name: string, loader: () => Promise<unknown>): void {\n this.controllerResolver.registerLoader(name, loader);\n }\n\n /**\n * Validate that every controller referenced in loaded operations is\n * pre-registered in the controller resolver. Call this after\n * loadOperations() + registerControllers() but before registerIpcHandlers().\n *\n * Throws with a clear error listing all missing controllers if any\n * operation references a controller that was not pre-registered.\n */\n validateControllers(): void {\n const missing: Array<{ channel: string; controller: string }> = [];\n\n for (const [channel, opDef] of this.operations) {\n if (!this.controllerResolver.has(opDef.controller)) {\n missing.push({ channel, controller: opDef.controller });\n }\n }\n\n if (missing.length > 0) {\n const details = missing\n .map(m => ` - channel \"${m.channel}\" requires controller \"${m.controller}\"`)\n .join('\\n');\n\n throw new Error(\n `WoodRouter: ${missing.length} operation(s) reference controllers that are not registered.\\n` +\n `This usually means controllers.generated.ts is missing or out of date.\\n` +\n `Run \"npx wood build\" or \"npx wood dev\" to regenerate it.\\n\\n` +\n `Missing controllers:\\n${details}`\n );\n }\n\n logger.info(`All ${this.operations.size} operations have registered controllers`);\n }\n\n /**\n * Eagerly compile every operation's inputSchema so a malformed schema fails\n * at startup (with the offending channel named) instead of throwing an opaque\n * ajv error on the first request to that channel. Call after loadOperations().\n */\n precompileSchemas(): void {\n let compiled = 0;\n for (const [channel, opDef] of this.operations) {\n if (opDef.inputSchema) {\n this.validator.precompile(channel, opDef.inputSchema as Record<string, unknown>);\n compiled += 1;\n }\n }\n logger.info(`Pre-compiled ${compiled} operation input schema(s)`);\n }\n\n /** Get all registered operation channels. */\n getChannels(): string[] {\n return Array.from(this.operations.keys());\n }\n\n /** Get a specific operation definition. */\n getOperation(channel: string): OperationDef | undefined {\n return this.operations.get(channel);\n }\n}\n"],"mappings":"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","names":["result"]}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,27 +15,23 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
var get_container_exports = {};
|
|
30
20
|
__export(get_container_exports, {
|
|
31
21
|
clearContainerRegistryForTests: () => clearContainerRegistryForTests,
|
|
22
|
+
createWoodRoot: () => createWoodRoot,
|
|
32
23
|
getContainer: () => getContainer
|
|
33
24
|
});
|
|
34
25
|
module.exports = __toCommonJS(get_container_exports);
|
|
35
|
-
var
|
|
36
|
-
var import_node_path = __toESM(require("node:path"), 1);
|
|
26
|
+
var import_ioc = require("@noego/ioc");
|
|
37
27
|
var import_logger = require("@noego/logger");
|
|
38
|
-
const import_meta = {};
|
|
39
28
|
const logger = (0, import_logger.getLogger)("wood:runtime:container");
|
|
40
29
|
const CONTAINER_REGISTRY_SYMBOL = /* @__PURE__ */ Symbol.for("@noego/wood.container.registry");
|
|
30
|
+
function createWoodRoot(options = {}) {
|
|
31
|
+
const root = (0, import_ioc.createContainer)();
|
|
32
|
+
options.configure?.(root);
|
|
33
|
+
return root;
|
|
34
|
+
}
|
|
41
35
|
function registry() {
|
|
42
36
|
const globalObj = globalThis;
|
|
43
37
|
if (!globalObj[CONTAINER_REGISTRY_SYMBOL]) {
|
|
@@ -46,26 +40,6 @@ function registry() {
|
|
|
46
40
|
}
|
|
47
41
|
return globalObj[CONTAINER_REGISTRY_SYMBOL];
|
|
48
42
|
}
|
|
49
|
-
function createContainerInstance(scope) {
|
|
50
|
-
const projectRoot = process.cwd();
|
|
51
|
-
try {
|
|
52
|
-
const consumerRequire = (0, import_node_module.createRequire)(import_node_path.default.resolve(projectRoot, "package.json"));
|
|
53
|
-
const consumerIoc = consumerRequire("@noego/ioc");
|
|
54
|
-
if (consumerIoc.Container) {
|
|
55
|
-
logger.info("container factory resolved from consumer app", { scope, source: "Container" });
|
|
56
|
-
return new consumerIoc.Container();
|
|
57
|
-
}
|
|
58
|
-
} catch (err) {
|
|
59
|
-
logger.warn("consumer @noego/ioc resolution failed, using Wood fallback", {
|
|
60
|
-
scope,
|
|
61
|
-
error: err instanceof Error ? err.message : String(err)
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
const fallbackRequire = (0, import_node_module.createRequire)(import_meta.url);
|
|
65
|
-
const fallbackIoc = fallbackRequire("@noego/ioc");
|
|
66
|
-
logger.info("container factory resolved from Wood fallback", { scope });
|
|
67
|
-
return new fallbackIoc.Container();
|
|
68
|
-
}
|
|
69
43
|
function getContainer(options = {}) {
|
|
70
44
|
const scope = options.scope ?? process.cwd();
|
|
71
45
|
const containers = registry();
|
|
@@ -74,7 +48,7 @@ function getContainer(options = {}) {
|
|
|
74
48
|
logger.info("container reused", { scope });
|
|
75
49
|
return existing;
|
|
76
50
|
}
|
|
77
|
-
const created =
|
|
51
|
+
const created = createWoodRoot();
|
|
78
52
|
containers.set(scope, created);
|
|
79
53
|
logger.info("container created", { scope });
|
|
80
54
|
if (options.configure) {
|
|
@@ -91,6 +65,7 @@ function clearContainerRegistryForTests() {
|
|
|
91
65
|
// Annotate the CommonJS export names for ESM import in node:
|
|
92
66
|
0 && (module.exports = {
|
|
93
67
|
clearContainerRegistryForTests,
|
|
68
|
+
createWoodRoot,
|
|
94
69
|
getContainer
|
|
95
70
|
});
|
|
96
71
|
//# sourceMappingURL=get_container.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/runtime/get_container.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/get_container.ts"],"sourcesContent":["import { createContainer } from '@noego/ioc';\nimport type { IContainer } from '@noego/ioc';\nimport { getLogger } from '@noego/logger';\n\nconst logger = getLogger('wood:runtime:container');\n\nconst CONTAINER_REGISTRY_SYMBOL = Symbol.for('@noego/wood.container.registry');\n\nexport interface GetContainerOptions {\n /** Scope key for the container instance. Defaults to process.cwd(). */\n scope?: string;\n /** Called only when a new scoped container is created. */\n configure?: (container: any) => void;\n}\n\nexport interface CreateWoodRootOptions {\n /** Applied to the fresh root before it is returned. */\n configure?: (container: IContainer) => void;\n}\n\n/**\n * Create an explicit Wood composition root: a fresh real `@noego/ioc`\n * container owned by the caller. This is the canonical construction path —\n * runtime boot, tests, and tools receive/create a root explicitly and pass\n * it down; operation/renderer scopes are children of it.\n *\n * Decorator metadata uses `Symbol.for(...)` keys and the global\n * reflect-metadata registry, so consumer-decorated classes resolve\n * correctly regardless of which package instance created the container —\n * the old `createRequire` consumer-package resolution is unnecessary.\n */\nexport function createWoodRoot(options: CreateWoodRootOptions = {}): IContainer {\n const root = createContainer() as IContainer;\n options.configure?.(root);\n return root;\n}\n\ntype ContainerRegistry = Map<string, any>;\n\nfunction registry(): ContainerRegistry {\n const globalObj = globalThis as typeof globalThis & {\n [CONTAINER_REGISTRY_SYMBOL]?: ContainerRegistry;\n };\n\n if (!globalObj[CONTAINER_REGISTRY_SYMBOL]) {\n globalObj[CONTAINER_REGISTRY_SYMBOL] = new Map<string, any>();\n logger.info('container registry created', { symbol: CONTAINER_REGISTRY_SYMBOL.toString() });\n }\n\n return globalObj[CONTAINER_REGISTRY_SYMBOL]!;\n}\n\n/**\n * COMPAT: return a process-global scoped container from the registry.\n *\n * The canonical architecture owns an explicit root (see createWoodRoot);\n * this global accessor remains as a runtime-boundary convenience while\n * consumers migrate. Note its known trap: `configure` runs only on first\n * creation for a scope key.\n */\nexport function getContainer(options: GetContainerOptions = {}): any {\n const scope = options.scope ?? process.cwd();\n const containers = registry();\n\n const existing = containers.get(scope);\n if (existing) {\n logger.info('container reused', { scope });\n return existing;\n }\n\n const created = createWoodRoot();\n containers.set(scope, created);\n logger.info('container created', { scope });\n\n if (options.configure) {\n options.configure(created);\n logger.info('container configured', { scope });\n }\n\n return created;\n}\n\nexport function clearContainerRegistryForTests(): void {\n const containers = registry();\n containers.clear();\n logger.info('container registry cleared');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAgC;AAEhC,oBAA0B;AAE1B,MAAM,aAAS,yBAAU,wBAAwB;AAEjD,MAAM,4BAA4B,uBAAO,IAAI,gCAAgC;AAyBtE,SAAS,eAAe,UAAiC,CAAC,GAAe;AAC9E,QAAM,WAAO,4BAAgB;AAC7B,UAAQ,YAAY,IAAI;AACxB,SAAO;AACT;AAIA,SAAS,WAA8B;AACrC,QAAM,YAAY;AAIlB,MAAI,CAAC,UAAU,yBAAyB,GAAG;AACzC,cAAU,yBAAyB,IAAI,oBAAI,IAAiB;AAC5D,WAAO,KAAK,8BAA8B,EAAE,QAAQ,0BAA0B,SAAS,EAAE,CAAC;AAAA,EAC5F;AAEA,SAAO,UAAU,yBAAyB;AAC5C;AAUO,SAAS,aAAa,UAA+B,CAAC,GAAQ;AACnE,QAAM,QAAQ,QAAQ,SAAS,QAAQ,IAAI;AAC3C,QAAM,aAAa,SAAS;AAE5B,QAAM,WAAW,WAAW,IAAI,KAAK;AACrC,MAAI,UAAU;AACZ,WAAO,KAAK,oBAAoB,EAAE,MAAM,CAAC;AACzC,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,eAAe;AAC/B,aAAW,IAAI,OAAO,OAAO;AAC7B,SAAO,KAAK,qBAAqB,EAAE,MAAM,CAAC;AAE1C,MAAI,QAAQ,WAAW;AACrB,YAAQ,UAAU,OAAO;AACzB,WAAO,KAAK,wBAAwB,EAAE,MAAM,CAAC;AAAA,EAC/C;AAEA,SAAO;AACT;AAEO,SAAS,iCAAuC;AACrD,QAAM,aAAa,SAAS;AAC5B,aAAW,MAAM;AACjB,SAAO,KAAK,4BAA4B;AAC1C;","names":[]}
|
|
@@ -1,16 +1,36 @@
|
|
|
1
|
+
import { IContainer } from '@noego/ioc';
|
|
2
|
+
|
|
1
3
|
interface GetContainerOptions {
|
|
2
4
|
/** Scope key for the container instance. Defaults to process.cwd(). */
|
|
3
5
|
scope?: string;
|
|
4
6
|
/** Called only when a new scoped container is created. */
|
|
5
7
|
configure?: (container: any) => void;
|
|
6
8
|
}
|
|
9
|
+
interface CreateWoodRootOptions {
|
|
10
|
+
/** Applied to the fresh root before it is returned. */
|
|
11
|
+
configure?: (container: IContainer) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create an explicit Wood composition root: a fresh real `@noego/ioc`
|
|
15
|
+
* container owned by the caller. This is the canonical construction path —
|
|
16
|
+
* runtime boot, tests, and tools receive/create a root explicitly and pass
|
|
17
|
+
* it down; operation/renderer scopes are children of it.
|
|
18
|
+
*
|
|
19
|
+
* Decorator metadata uses `Symbol.for(...)` keys and the global
|
|
20
|
+
* reflect-metadata registry, so consumer-decorated classes resolve
|
|
21
|
+
* correctly regardless of which package instance created the container —
|
|
22
|
+
* the old `createRequire` consumer-package resolution is unnecessary.
|
|
23
|
+
*/
|
|
24
|
+
declare function createWoodRoot(options?: CreateWoodRootOptions): IContainer;
|
|
7
25
|
/**
|
|
8
|
-
*
|
|
26
|
+
* COMPAT: return a process-global scoped container from the registry.
|
|
9
27
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
28
|
+
* The canonical architecture owns an explicit root (see createWoodRoot);
|
|
29
|
+
* this global accessor remains as a runtime-boundary convenience while
|
|
30
|
+
* consumers migrate. Note its known trap: `configure` runs only on first
|
|
31
|
+
* creation for a scope key.
|
|
12
32
|
*/
|
|
13
33
|
declare function getContainer(options?: GetContainerOptions): any;
|
|
14
34
|
declare function clearContainerRegistryForTests(): void;
|
|
15
35
|
|
|
16
|
-
export { type GetContainerOptions, clearContainerRegistryForTests, getContainer };
|
|
36
|
+
export { type CreateWoodRootOptions, type GetContainerOptions, clearContainerRegistryForTests, createWoodRoot, getContainer };
|
|
@@ -1,16 +1,36 @@
|
|
|
1
|
+
import { IContainer } from '@noego/ioc';
|
|
2
|
+
|
|
1
3
|
interface GetContainerOptions {
|
|
2
4
|
/** Scope key for the container instance. Defaults to process.cwd(). */
|
|
3
5
|
scope?: string;
|
|
4
6
|
/** Called only when a new scoped container is created. */
|
|
5
7
|
configure?: (container: any) => void;
|
|
6
8
|
}
|
|
9
|
+
interface CreateWoodRootOptions {
|
|
10
|
+
/** Applied to the fresh root before it is returned. */
|
|
11
|
+
configure?: (container: IContainer) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create an explicit Wood composition root: a fresh real `@noego/ioc`
|
|
15
|
+
* container owned by the caller. This is the canonical construction path —
|
|
16
|
+
* runtime boot, tests, and tools receive/create a root explicitly and pass
|
|
17
|
+
* it down; operation/renderer scopes are children of it.
|
|
18
|
+
*
|
|
19
|
+
* Decorator metadata uses `Symbol.for(...)` keys and the global
|
|
20
|
+
* reflect-metadata registry, so consumer-decorated classes resolve
|
|
21
|
+
* correctly regardless of which package instance created the container —
|
|
22
|
+
* the old `createRequire` consumer-package resolution is unnecessary.
|
|
23
|
+
*/
|
|
24
|
+
declare function createWoodRoot(options?: CreateWoodRootOptions): IContainer;
|
|
7
25
|
/**
|
|
8
|
-
*
|
|
26
|
+
* COMPAT: return a process-global scoped container from the registry.
|
|
9
27
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
28
|
+
* The canonical architecture owns an explicit root (see createWoodRoot);
|
|
29
|
+
* this global accessor remains as a runtime-boundary convenience while
|
|
30
|
+
* consumers migrate. Note its known trap: `configure` runs only on first
|
|
31
|
+
* creation for a scope key.
|
|
12
32
|
*/
|
|
13
33
|
declare function getContainer(options?: GetContainerOptions): any;
|
|
14
34
|
declare function clearContainerRegistryForTests(): void;
|
|
15
35
|
|
|
16
|
-
export { type GetContainerOptions, clearContainerRegistryForTests, getContainer };
|
|
36
|
+
export { type CreateWoodRootOptions, type GetContainerOptions, clearContainerRegistryForTests, createWoodRoot, getContainer };
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from "node:path";
|
|
1
|
+
import { createContainer } from "@noego/ioc";
|
|
3
2
|
import { getLogger } from "@noego/logger";
|
|
4
3
|
const logger = getLogger("wood:runtime:container");
|
|
5
4
|
const CONTAINER_REGISTRY_SYMBOL = /* @__PURE__ */ Symbol.for("@noego/wood.container.registry");
|
|
5
|
+
function createWoodRoot(options = {}) {
|
|
6
|
+
const root = createContainer();
|
|
7
|
+
options.configure?.(root);
|
|
8
|
+
return root;
|
|
9
|
+
}
|
|
6
10
|
function registry() {
|
|
7
11
|
const globalObj = globalThis;
|
|
8
12
|
if (!globalObj[CONTAINER_REGISTRY_SYMBOL]) {
|
|
@@ -11,26 +15,6 @@ function registry() {
|
|
|
11
15
|
}
|
|
12
16
|
return globalObj[CONTAINER_REGISTRY_SYMBOL];
|
|
13
17
|
}
|
|
14
|
-
function createContainerInstance(scope) {
|
|
15
|
-
const projectRoot = process.cwd();
|
|
16
|
-
try {
|
|
17
|
-
const consumerRequire = createRequire(path.resolve(projectRoot, "package.json"));
|
|
18
|
-
const consumerIoc = consumerRequire("@noego/ioc");
|
|
19
|
-
if (consumerIoc.Container) {
|
|
20
|
-
logger.info("container factory resolved from consumer app", { scope, source: "Container" });
|
|
21
|
-
return new consumerIoc.Container();
|
|
22
|
-
}
|
|
23
|
-
} catch (err) {
|
|
24
|
-
logger.warn("consumer @noego/ioc resolution failed, using Wood fallback", {
|
|
25
|
-
scope,
|
|
26
|
-
error: err instanceof Error ? err.message : String(err)
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
const fallbackRequire = createRequire(import.meta.url);
|
|
30
|
-
const fallbackIoc = fallbackRequire("@noego/ioc");
|
|
31
|
-
logger.info("container factory resolved from Wood fallback", { scope });
|
|
32
|
-
return new fallbackIoc.Container();
|
|
33
|
-
}
|
|
34
18
|
function getContainer(options = {}) {
|
|
35
19
|
const scope = options.scope ?? process.cwd();
|
|
36
20
|
const containers = registry();
|
|
@@ -39,7 +23,7 @@ function getContainer(options = {}) {
|
|
|
39
23
|
logger.info("container reused", { scope });
|
|
40
24
|
return existing;
|
|
41
25
|
}
|
|
42
|
-
const created =
|
|
26
|
+
const created = createWoodRoot();
|
|
43
27
|
containers.set(scope, created);
|
|
44
28
|
logger.info("container created", { scope });
|
|
45
29
|
if (options.configure) {
|
|
@@ -55,6 +39,7 @@ function clearContainerRegistryForTests() {
|
|
|
55
39
|
}
|
|
56
40
|
export {
|
|
57
41
|
clearContainerRegistryForTests,
|
|
42
|
+
createWoodRoot,
|
|
58
43
|
getContainer
|
|
59
44
|
};
|
|
60
45
|
//# sourceMappingURL=get_container.js.map
|