@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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { WoodRpcTransport } from '../transport/wood_rpc_transport.cjs';
|
|
2
|
+
import { WorkRegistry } from './work_registry.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Work-tracking decorator over any `WoodRpcTransport` (18A D-FAS-01 §2.2).
|
|
6
|
+
*
|
|
7
|
+
* Every client invocation registers finite owned work at the transport seam;
|
|
8
|
+
* every queued event delivery to a frontend listener is finite owned work
|
|
9
|
+
* (promise-returning listeners are awaited). A subscription itself is a
|
|
10
|
+
* resident resource — it never blocks settling and is removed when the
|
|
11
|
+
* owning page/application lifecycle ends.
|
|
12
|
+
*
|
|
13
|
+
* This is a wrapper, not a modification: `InProcessRouterTransport` and the
|
|
14
|
+
* shared bridge builders are untouched, so existing harness users see no
|
|
15
|
+
* behavior change.
|
|
16
|
+
*
|
|
17
|
+
* Honesty note: backend work that continues after `invoke` resolves (e.g. a
|
|
18
|
+
* stream emitting after the dispatch returned) is only observable when its
|
|
19
|
+
* emissions arrive as tracked event deliveries. `settle()` drains queued
|
|
20
|
+
* deliveries; it does not claim to await a backend's private timeline —
|
|
21
|
+
* tests script the external boundary or await a terminal event.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
declare class TrackedTransport implements WoodRpcTransport {
|
|
25
|
+
private readonly inner;
|
|
26
|
+
private readonly registry;
|
|
27
|
+
/** Captures the owning lifecycle identity at call/subscription time. */
|
|
28
|
+
private readonly currentOwner;
|
|
29
|
+
constructor(inner: WoodRpcTransport, registry: WorkRegistry,
|
|
30
|
+
/** Captures the owning lifecycle identity at call/subscription time. */
|
|
31
|
+
currentOwner: () => string);
|
|
32
|
+
invoke(channel: string, payload?: unknown): Promise<unknown>;
|
|
33
|
+
subscribe(channel: string, listener: (payload: unknown) => void): () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { TrackedTransport };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { WoodRpcTransport } from '../transport/wood_rpc_transport.js';
|
|
2
|
+
import { WorkRegistry } from './work_registry.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Work-tracking decorator over any `WoodRpcTransport` (18A D-FAS-01 §2.2).
|
|
6
|
+
*
|
|
7
|
+
* Every client invocation registers finite owned work at the transport seam;
|
|
8
|
+
* every queued event delivery to a frontend listener is finite owned work
|
|
9
|
+
* (promise-returning listeners are awaited). A subscription itself is a
|
|
10
|
+
* resident resource — it never blocks settling and is removed when the
|
|
11
|
+
* owning page/application lifecycle ends.
|
|
12
|
+
*
|
|
13
|
+
* This is a wrapper, not a modification: `InProcessRouterTransport` and the
|
|
14
|
+
* shared bridge builders are untouched, so existing harness users see no
|
|
15
|
+
* behavior change.
|
|
16
|
+
*
|
|
17
|
+
* Honesty note: backend work that continues after `invoke` resolves (e.g. a
|
|
18
|
+
* stream emitting after the dispatch returned) is only observable when its
|
|
19
|
+
* emissions arrive as tracked event deliveries. `settle()` drains queued
|
|
20
|
+
* deliveries; it does not claim to await a backend's private timeline —
|
|
21
|
+
* tests script the external boundary or await a terminal event.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
declare class TrackedTransport implements WoodRpcTransport {
|
|
25
|
+
private readonly inner;
|
|
26
|
+
private readonly registry;
|
|
27
|
+
/** Captures the owning lifecycle identity at call/subscription time. */
|
|
28
|
+
private readonly currentOwner;
|
|
29
|
+
constructor(inner: WoodRpcTransport, registry: WorkRegistry,
|
|
30
|
+
/** Captures the owning lifecycle identity at call/subscription time. */
|
|
31
|
+
currentOwner: () => string);
|
|
32
|
+
invoke(channel: string, payload?: unknown): Promise<unknown>;
|
|
33
|
+
subscribe(channel: string, listener: (payload: unknown) => void): () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { TrackedTransport };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class TrackedTransport {
|
|
2
|
+
constructor(inner, registry, currentOwner) {
|
|
3
|
+
this.inner = inner;
|
|
4
|
+
this.registry = registry;
|
|
5
|
+
this.currentOwner = currentOwner;
|
|
6
|
+
}
|
|
7
|
+
invoke(channel, payload) {
|
|
8
|
+
return this.registry.track(this.inner.invoke(channel, payload), {
|
|
9
|
+
label: `invoke ${channel}`,
|
|
10
|
+
kind: "invoke",
|
|
11
|
+
owner: this.currentOwner()
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
subscribe(channel, listener) {
|
|
15
|
+
const owner = this.currentOwner();
|
|
16
|
+
return this.inner.subscribe(channel, (payload) => {
|
|
17
|
+
const result = listener(payload);
|
|
18
|
+
if (result && typeof result.then === "function") {
|
|
19
|
+
this.registry.track(result, {
|
|
20
|
+
label: `event ${channel}`,
|
|
21
|
+
kind: "event-delivery",
|
|
22
|
+
owner
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
TrackedTransport
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=tracked_transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/frontend/tracked_transport.ts"],"sourcesContent":["/**\n * Work-tracking decorator over any `WoodRpcTransport` (18A D-FAS-01 §2.2).\n *\n * Every client invocation registers finite owned work at the transport seam;\n * every queued event delivery to a frontend listener is finite owned work\n * (promise-returning listeners are awaited). A subscription itself is a\n * resident resource — it never blocks settling and is removed when the\n * owning page/application lifecycle ends.\n *\n * This is a wrapper, not a modification: `InProcessRouterTransport` and the\n * shared bridge builders are untouched, so existing harness users see no\n * behavior change.\n *\n * Honesty note: backend work that continues after `invoke` resolves (e.g. a\n * stream emitting after the dispatch returned) is only observable when its\n * emissions arrive as tracked event deliveries. `settle()` drains queued\n * deliveries; it does not claim to await a backend's private timeline —\n * tests script the external boundary or await a terminal event.\n */\nimport type { WoodRpcTransport } from '../transport/wood_rpc_transport.js';\nimport type { WorkRegistry } from './work_registry.js';\n\nexport class TrackedTransport implements WoodRpcTransport {\n constructor(\n private readonly inner: WoodRpcTransport,\n private readonly registry: WorkRegistry,\n /** Captures the owning lifecycle identity at call/subscription time. */\n private readonly currentOwner: () => string,\n ) {}\n\n invoke(channel: string, payload?: unknown): Promise<unknown> {\n return this.registry.track(this.inner.invoke(channel, payload), {\n label: `invoke ${channel}`,\n kind: 'invoke',\n owner: this.currentOwner(),\n });\n }\n\n subscribe(channel: string, listener: (payload: unknown) => void): () => void {\n // Owner is fixed at subscription time: deliveries belong to the\n // subscribing page, not to whichever context happens to be active\n // when the backend pushes.\n const owner = this.currentOwner();\n return this.inner.subscribe(channel, (payload) => {\n // Deliver SYNCHRONOUSLY — identical to the production bridge path.\n // Deferring delivery to a microtask would reorder produce/consume\n // loops the product paces at the event seam. Only an asynchronous\n // continuation (a promise-returning listener) becomes tracked work.\n const result = listener(payload) as unknown;\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n this.registry.track(result as Promise<unknown>, {\n label: `event ${channel}`,\n kind: 'event-delivery',\n owner,\n });\n }\n });\n }\n}\n"],"mappings":"AAsBO,MAAM,iBAA6C;AAAA,EACxD,YACmB,OACA,UAEA,cACjB;AAJiB;AACA;AAEA;AAAA,EAChB;AAAA,EAEH,OAAO,SAAiB,SAAqC;AAC3D,WAAO,KAAK,SAAS,MAAM,KAAK,MAAM,OAAO,SAAS,OAAO,GAAG;AAAA,MAC9D,OAAO,UAAU,OAAO;AAAA,MACxB,MAAM;AAAA,MACN,OAAO,KAAK,aAAa;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EAEA,UAAU,SAAiB,UAAkD;AAI3E,UAAM,QAAQ,KAAK,aAAa;AAChC,WAAO,KAAK,MAAM,UAAU,SAAS,CAAC,YAAY;AAKhD,YAAM,SAAS,SAAS,OAAO;AAC/B,UAAI,UAAU,OAAQ,OAA4B,SAAS,YAAY;AACrE,aAAK,SAAS,MAAM,QAA4B;AAAA,UAC9C,OAAO,SAAS,OAAO;AAAA,UACvB,MAAM;AAAA,UACN;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var work_registry_exports = {};
|
|
20
|
+
__export(work_registry_exports, {
|
|
21
|
+
FrontendWorkError: () => FrontendWorkError,
|
|
22
|
+
WorkRegistry: () => WorkRegistry
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(work_registry_exports);
|
|
25
|
+
const MAX_DRAIN_PASSES = 100;
|
|
26
|
+
class FrontendWorkError extends Error {
|
|
27
|
+
constructor(message, failures = []) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.failures = failures;
|
|
30
|
+
this.name = "FrontendWorkError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function describe(work) {
|
|
34
|
+
return `[${work.kind}] ${work.label} (owner: ${work.owner})`;
|
|
35
|
+
}
|
|
36
|
+
class WorkRegistry {
|
|
37
|
+
constructor() {
|
|
38
|
+
this.nextId = 1;
|
|
39
|
+
/** Total registrations ever — the fixed-point signal for draining. */
|
|
40
|
+
this.registrations = 0;
|
|
41
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
42
|
+
this.failures = [];
|
|
43
|
+
/** How many failures a completed drain has already reported. */
|
|
44
|
+
this.reportedFailures = 0;
|
|
45
|
+
this.drainChain = Promise.resolve();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Register finite owned work. Returns a promise with the work's own
|
|
49
|
+
* settlement semantics — callers may await it directly; the registry
|
|
50
|
+
* additionally observes completion (success or failure) for draining.
|
|
51
|
+
*/
|
|
52
|
+
track(work, options) {
|
|
53
|
+
const descriptor = {
|
|
54
|
+
id: this.nextId++,
|
|
55
|
+
label: options.label,
|
|
56
|
+
kind: options.kind ?? "tracked",
|
|
57
|
+
owner: options.owner ?? "application"
|
|
58
|
+
};
|
|
59
|
+
this.registrations += 1;
|
|
60
|
+
const promise = typeof work === "function" ? work() : work;
|
|
61
|
+
const observed = promise.then(
|
|
62
|
+
() => void 0,
|
|
63
|
+
(error) => {
|
|
64
|
+
this.failures.push({ ...descriptor, error });
|
|
65
|
+
}
|
|
66
|
+
).finally(() => {
|
|
67
|
+
this.inFlight.delete(descriptor.id);
|
|
68
|
+
});
|
|
69
|
+
this.inFlight.set(descriptor.id, { descriptor, observed });
|
|
70
|
+
return promise;
|
|
71
|
+
}
|
|
72
|
+
/** Descriptors of currently incomplete owned work. */
|
|
73
|
+
pending() {
|
|
74
|
+
return [...this.inFlight.values()].map((entry) => entry.descriptor);
|
|
75
|
+
}
|
|
76
|
+
/** Every owned-work failure observed so far (accumulates across drains). */
|
|
77
|
+
errors() {
|
|
78
|
+
return [...this.failures];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Fixed-point drain: await all pending owned work, run the adapter flush
|
|
82
|
+
* hooks, repeat until one full pass observes no pending work and causes
|
|
83
|
+
* no new registrations. Concurrent drains queue — each caller gets its
|
|
84
|
+
* own complete pass over whatever work exists when its turn arrives.
|
|
85
|
+
*
|
|
86
|
+
* Rejects with `FrontendWorkError` naming the owners when owned work
|
|
87
|
+
* failed since the previous drain, or when the pass bound is exceeded
|
|
88
|
+
* (repeating owned work), listing pending and repeating labels.
|
|
89
|
+
*/
|
|
90
|
+
drain(flushHooks = []) {
|
|
91
|
+
const run = this.drainChain.then(() => this.drainOnce(flushHooks));
|
|
92
|
+
this.drainChain = run.then(
|
|
93
|
+
() => void 0,
|
|
94
|
+
() => void 0
|
|
95
|
+
);
|
|
96
|
+
return run;
|
|
97
|
+
}
|
|
98
|
+
async drainOnce(flushHooks) {
|
|
99
|
+
const seenPerLabel = /* @__PURE__ */ new Map();
|
|
100
|
+
for (let pass = 1; pass <= MAX_DRAIN_PASSES; pass++) {
|
|
101
|
+
const registrationsBefore = this.registrations;
|
|
102
|
+
const snapshot = [...this.inFlight.values()];
|
|
103
|
+
for (const { descriptor } of snapshot) {
|
|
104
|
+
seenPerLabel.set(descriptor.label, (seenPerLabel.get(descriptor.label) ?? 0) + 1);
|
|
105
|
+
}
|
|
106
|
+
await Promise.all(snapshot.map((entry) => entry.observed));
|
|
107
|
+
for (const hook of flushHooks) {
|
|
108
|
+
await hook();
|
|
109
|
+
}
|
|
110
|
+
await Promise.resolve();
|
|
111
|
+
await Promise.resolve();
|
|
112
|
+
const stable = this.inFlight.size === 0 && this.registrations === registrationsBefore;
|
|
113
|
+
if (stable) {
|
|
114
|
+
this.throwNewFailures();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const pendingLines = this.pending().map(describe);
|
|
119
|
+
const repeating = [...seenPerLabel.entries()].filter(([, count]) => count > 3).map(([label, count]) => `${label} (seen in ${count} passes)`);
|
|
120
|
+
throw new FrontendWorkError(
|
|
121
|
+
`settle did not converge within ${MAX_DRAIN_PASSES} drain passes.
|
|
122
|
+
` + (pendingLines.length ? `Still pending:
|
|
123
|
+
${pendingLines.join("\n ")}
|
|
124
|
+
` : "") + (repeating.length ? `Repeating owned work:
|
|
125
|
+
${repeating.join("\n ")}
|
|
126
|
+
` : "") + "Owned work keeps creating more owned work \u2014 this names the owners; it is not a timeout.",
|
|
127
|
+
this.takeNewFailures()
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
takeNewFailures() {
|
|
131
|
+
const fresh = this.failures.slice(this.reportedFailures);
|
|
132
|
+
this.reportedFailures = this.failures.length;
|
|
133
|
+
return fresh;
|
|
134
|
+
}
|
|
135
|
+
throwNewFailures() {
|
|
136
|
+
const fresh = this.takeNewFailures();
|
|
137
|
+
if (!fresh.length) return;
|
|
138
|
+
const lines = fresh.map(
|
|
139
|
+
(failure) => `${describe(failure)}: ${failure.error instanceof Error ? failure.error.message : String(failure.error)}`
|
|
140
|
+
);
|
|
141
|
+
throw new FrontendWorkError(
|
|
142
|
+
`owned frontend work failed:
|
|
143
|
+
${lines.join("\n ")}`,
|
|
144
|
+
fresh
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
149
|
+
0 && (module.exports = {
|
|
150
|
+
FrontendWorkError,
|
|
151
|
+
WorkRegistry
|
|
152
|
+
});
|
|
153
|
+
//# sourceMappingURL=work_registry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/frontend/work_registry.ts"],"sourcesContent":["/**\n * Owned-work registry for the frontend application slice (18A D-FAS-01).\n *\n * `act()` and `settle()` drain framework-owned finite work registered here\n * through production seams (transport invokes, event deliveries, navigation\n * transitions, tracked lifecycle tasks). The registry never patches global\n * async primitives and never guesses at event-loop emptiness: work exists\n * only because an owner registered it, and a drain is a fixed point —\n * repeated passes until one complete pass creates no new owned work.\n *\n * Floating promises and bare timers are explicitly outside the guarantee;\n * they are product lifecycle bugs, not framework blind spots to paper over.\n */\n\nexport type WorkKind =\n | 'invoke'\n | 'event-delivery'\n | 'navigation'\n | 'initialize'\n | 'tracked'\n | (string & {});\n\nexport interface WorkOptions {\n /** Human-readable diagnostic label, e.g. `invoke messages.send`. */\n label: string;\n kind?: WorkKind;\n /** Owning lifecycle identity, e.g. `page main.chat_dom` or `application`. */\n owner?: string;\n}\n\nexport interface WorkDescriptor {\n readonly id: number;\n readonly label: string;\n readonly kind: WorkKind;\n readonly owner: string;\n}\n\nexport interface WorkFailure extends WorkDescriptor {\n readonly error: unknown;\n}\n\n/** Safety bound on drain passes — a diagnostic limit, not the readiness mechanism. */\nconst MAX_DRAIN_PASSES = 100;\n\nexport class FrontendWorkError extends Error {\n constructor(message: string, readonly failures: readonly WorkFailure[] = []) {\n super(message);\n this.name = 'FrontendWorkError';\n }\n}\n\nfunction describe(work: WorkDescriptor): string {\n return `[${work.kind}] ${work.label} (owner: ${work.owner})`;\n}\n\nexport class WorkRegistry {\n private nextId = 1;\n /** Total registrations ever — the fixed-point signal for draining. */\n private registrations = 0;\n private readonly inFlight = new Map<number, { descriptor: WorkDescriptor; observed: Promise<void> }>();\n private readonly failures: WorkFailure[] = [];\n /** How many failures a completed drain has already reported. */\n private reportedFailures = 0;\n private drainChain: Promise<void> = Promise.resolve();\n\n /**\n * Register finite owned work. Returns a promise with the work's own\n * settlement semantics — callers may await it directly; the registry\n * additionally observes completion (success or failure) for draining.\n */\n track<T>(work: Promise<T> | (() => Promise<T>), options: WorkOptions): Promise<T> {\n const descriptor: WorkDescriptor = {\n id: this.nextId++,\n label: options.label,\n kind: options.kind ?? 'tracked',\n owner: options.owner ?? 'application',\n };\n\n this.registrations += 1;\n const promise = typeof work === 'function' ? work() : work;\n\n // Observe settlement without altering the caller's promise semantics.\n const observed = promise.then(\n () => undefined,\n (error) => {\n this.failures.push({ ...descriptor, error });\n },\n ).finally(() => {\n this.inFlight.delete(descriptor.id);\n });\n\n this.inFlight.set(descriptor.id, { descriptor, observed });\n return promise;\n }\n\n /** Descriptors of currently incomplete owned work. */\n pending(): WorkDescriptor[] {\n return [...this.inFlight.values()].map((entry) => entry.descriptor);\n }\n\n /** Every owned-work failure observed so far (accumulates across drains). */\n errors(): readonly WorkFailure[] {\n return [...this.failures];\n }\n\n /**\n * Fixed-point drain: await all pending owned work, run the adapter flush\n * hooks, repeat until one full pass observes no pending work and causes\n * no new registrations. Concurrent drains queue — each caller gets its\n * own complete pass over whatever work exists when its turn arrives.\n *\n * Rejects with `FrontendWorkError` naming the owners when owned work\n * failed since the previous drain, or when the pass bound is exceeded\n * (repeating owned work), listing pending and repeating labels.\n */\n drain(flushHooks: ReadonlyArray<() => void | Promise<void>> = []): Promise<void> {\n const run = this.drainChain.then(() => this.drainOnce(flushHooks));\n // Keep the chain alive regardless of this drain's outcome.\n this.drainChain = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n private async drainOnce(flushHooks: ReadonlyArray<() => void | Promise<void>>): Promise<void> {\n const seenPerLabel = new Map<string, number>();\n\n for (let pass = 1; pass <= MAX_DRAIN_PASSES; pass++) {\n const registrationsBefore = this.registrations;\n const snapshot = [...this.inFlight.values()];\n\n for (const { descriptor } of snapshot) {\n seenPerLabel.set(descriptor.label, (seenPerLabel.get(descriptor.label) ?? 0) + 1);\n }\n\n // Observed promises never reject (failures are recorded by track());\n // awaiting them cannot abort the pass mid-way.\n await Promise.all(snapshot.map((entry) => entry.observed));\n\n for (const hook of flushHooks) {\n await hook();\n }\n\n // Give completion continuations a microtask to register follow-on\n // owned work before the fixed point is tested.\n await Promise.resolve();\n await Promise.resolve();\n\n const stable = this.inFlight.size === 0 && this.registrations === registrationsBefore;\n if (stable) {\n this.throwNewFailures();\n return;\n }\n }\n\n const pendingLines = this.pending().map(describe);\n const repeating = [...seenPerLabel.entries()]\n .filter(([, count]) => count > 3)\n .map(([label, count]) => `${label} (seen in ${count} passes)`);\n\n throw new FrontendWorkError(\n `settle did not converge within ${MAX_DRAIN_PASSES} drain passes.\\n` +\n (pendingLines.length ? `Still pending:\\n ${pendingLines.join('\\n ')}\\n` : '') +\n (repeating.length ? `Repeating owned work:\\n ${repeating.join('\\n ')}\\n` : '') +\n 'Owned work keeps creating more owned work — this names the owners; it is not a timeout.',\n this.takeNewFailures(),\n );\n }\n\n private takeNewFailures(): WorkFailure[] {\n const fresh = this.failures.slice(this.reportedFailures);\n this.reportedFailures = this.failures.length;\n return fresh;\n }\n\n private throwNewFailures(): void {\n const fresh = this.takeNewFailures();\n if (!fresh.length) return;\n const lines = fresh.map(\n (failure) =>\n `${describe(failure)}: ${failure.error instanceof Error ? failure.error.message : String(failure.error)}`,\n );\n throw new FrontendWorkError(\n `owned frontend work failed:\\n ${lines.join('\\n ')}`,\n fresh,\n );\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owned-work registry for the frontend application slice (18A D-FAS-01).
|
|
3
|
+
*
|
|
4
|
+
* `act()` and `settle()` drain framework-owned finite work registered here
|
|
5
|
+
* through production seams (transport invokes, event deliveries, navigation
|
|
6
|
+
* transitions, tracked lifecycle tasks). The registry never patches global
|
|
7
|
+
* async primitives and never guesses at event-loop emptiness: work exists
|
|
8
|
+
* only because an owner registered it, and a drain is a fixed point —
|
|
9
|
+
* repeated passes until one complete pass creates no new owned work.
|
|
10
|
+
*
|
|
11
|
+
* Floating promises and bare timers are explicitly outside the guarantee;
|
|
12
|
+
* they are product lifecycle bugs, not framework blind spots to paper over.
|
|
13
|
+
*/
|
|
14
|
+
type WorkKind = 'invoke' | 'event-delivery' | 'navigation' | 'initialize' | 'tracked' | (string & {});
|
|
15
|
+
interface WorkOptions {
|
|
16
|
+
/** Human-readable diagnostic label, e.g. `invoke messages.send`. */
|
|
17
|
+
label: string;
|
|
18
|
+
kind?: WorkKind;
|
|
19
|
+
/** Owning lifecycle identity, e.g. `page main.chat_dom` or `application`. */
|
|
20
|
+
owner?: string;
|
|
21
|
+
}
|
|
22
|
+
interface WorkDescriptor {
|
|
23
|
+
readonly id: number;
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly kind: WorkKind;
|
|
26
|
+
readonly owner: string;
|
|
27
|
+
}
|
|
28
|
+
interface WorkFailure extends WorkDescriptor {
|
|
29
|
+
readonly error: unknown;
|
|
30
|
+
}
|
|
31
|
+
declare class FrontendWorkError extends Error {
|
|
32
|
+
readonly failures: readonly WorkFailure[];
|
|
33
|
+
constructor(message: string, failures?: readonly WorkFailure[]);
|
|
34
|
+
}
|
|
35
|
+
declare class WorkRegistry {
|
|
36
|
+
private nextId;
|
|
37
|
+
/** Total registrations ever — the fixed-point signal for draining. */
|
|
38
|
+
private registrations;
|
|
39
|
+
private readonly inFlight;
|
|
40
|
+
private readonly failures;
|
|
41
|
+
/** How many failures a completed drain has already reported. */
|
|
42
|
+
private reportedFailures;
|
|
43
|
+
private drainChain;
|
|
44
|
+
/**
|
|
45
|
+
* Register finite owned work. Returns a promise with the work's own
|
|
46
|
+
* settlement semantics — callers may await it directly; the registry
|
|
47
|
+
* additionally observes completion (success or failure) for draining.
|
|
48
|
+
*/
|
|
49
|
+
track<T>(work: Promise<T> | (() => Promise<T>), options: WorkOptions): Promise<T>;
|
|
50
|
+
/** Descriptors of currently incomplete owned work. */
|
|
51
|
+
pending(): WorkDescriptor[];
|
|
52
|
+
/** Every owned-work failure observed so far (accumulates across drains). */
|
|
53
|
+
errors(): readonly WorkFailure[];
|
|
54
|
+
/**
|
|
55
|
+
* Fixed-point drain: await all pending owned work, run the adapter flush
|
|
56
|
+
* hooks, repeat until one full pass observes no pending work and causes
|
|
57
|
+
* no new registrations. Concurrent drains queue — each caller gets its
|
|
58
|
+
* own complete pass over whatever work exists when its turn arrives.
|
|
59
|
+
*
|
|
60
|
+
* Rejects with `FrontendWorkError` naming the owners when owned work
|
|
61
|
+
* failed since the previous drain, or when the pass bound is exceeded
|
|
62
|
+
* (repeating owned work), listing pending and repeating labels.
|
|
63
|
+
*/
|
|
64
|
+
drain(flushHooks?: ReadonlyArray<() => void | Promise<void>>): Promise<void>;
|
|
65
|
+
private drainOnce;
|
|
66
|
+
private takeNewFailures;
|
|
67
|
+
private throwNewFailures;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { FrontendWorkError, type WorkDescriptor, type WorkFailure, type WorkKind, type WorkOptions, WorkRegistry };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owned-work registry for the frontend application slice (18A D-FAS-01).
|
|
3
|
+
*
|
|
4
|
+
* `act()` and `settle()` drain framework-owned finite work registered here
|
|
5
|
+
* through production seams (transport invokes, event deliveries, navigation
|
|
6
|
+
* transitions, tracked lifecycle tasks). The registry never patches global
|
|
7
|
+
* async primitives and never guesses at event-loop emptiness: work exists
|
|
8
|
+
* only because an owner registered it, and a drain is a fixed point —
|
|
9
|
+
* repeated passes until one complete pass creates no new owned work.
|
|
10
|
+
*
|
|
11
|
+
* Floating promises and bare timers are explicitly outside the guarantee;
|
|
12
|
+
* they are product lifecycle bugs, not framework blind spots to paper over.
|
|
13
|
+
*/
|
|
14
|
+
type WorkKind = 'invoke' | 'event-delivery' | 'navigation' | 'initialize' | 'tracked' | (string & {});
|
|
15
|
+
interface WorkOptions {
|
|
16
|
+
/** Human-readable diagnostic label, e.g. `invoke messages.send`. */
|
|
17
|
+
label: string;
|
|
18
|
+
kind?: WorkKind;
|
|
19
|
+
/** Owning lifecycle identity, e.g. `page main.chat_dom` or `application`. */
|
|
20
|
+
owner?: string;
|
|
21
|
+
}
|
|
22
|
+
interface WorkDescriptor {
|
|
23
|
+
readonly id: number;
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly kind: WorkKind;
|
|
26
|
+
readonly owner: string;
|
|
27
|
+
}
|
|
28
|
+
interface WorkFailure extends WorkDescriptor {
|
|
29
|
+
readonly error: unknown;
|
|
30
|
+
}
|
|
31
|
+
declare class FrontendWorkError extends Error {
|
|
32
|
+
readonly failures: readonly WorkFailure[];
|
|
33
|
+
constructor(message: string, failures?: readonly WorkFailure[]);
|
|
34
|
+
}
|
|
35
|
+
declare class WorkRegistry {
|
|
36
|
+
private nextId;
|
|
37
|
+
/** Total registrations ever — the fixed-point signal for draining. */
|
|
38
|
+
private registrations;
|
|
39
|
+
private readonly inFlight;
|
|
40
|
+
private readonly failures;
|
|
41
|
+
/** How many failures a completed drain has already reported. */
|
|
42
|
+
private reportedFailures;
|
|
43
|
+
private drainChain;
|
|
44
|
+
/**
|
|
45
|
+
* Register finite owned work. Returns a promise with the work's own
|
|
46
|
+
* settlement semantics — callers may await it directly; the registry
|
|
47
|
+
* additionally observes completion (success or failure) for draining.
|
|
48
|
+
*/
|
|
49
|
+
track<T>(work: Promise<T> | (() => Promise<T>), options: WorkOptions): Promise<T>;
|
|
50
|
+
/** Descriptors of currently incomplete owned work. */
|
|
51
|
+
pending(): WorkDescriptor[];
|
|
52
|
+
/** Every owned-work failure observed so far (accumulates across drains). */
|
|
53
|
+
errors(): readonly WorkFailure[];
|
|
54
|
+
/**
|
|
55
|
+
* Fixed-point drain: await all pending owned work, run the adapter flush
|
|
56
|
+
* hooks, repeat until one full pass observes no pending work and causes
|
|
57
|
+
* no new registrations. Concurrent drains queue — each caller gets its
|
|
58
|
+
* own complete pass over whatever work exists when its turn arrives.
|
|
59
|
+
*
|
|
60
|
+
* Rejects with `FrontendWorkError` naming the owners when owned work
|
|
61
|
+
* failed since the previous drain, or when the pass bound is exceeded
|
|
62
|
+
* (repeating owned work), listing pending and repeating labels.
|
|
63
|
+
*/
|
|
64
|
+
drain(flushHooks?: ReadonlyArray<() => void | Promise<void>>): Promise<void>;
|
|
65
|
+
private drainOnce;
|
|
66
|
+
private takeNewFailures;
|
|
67
|
+
private throwNewFailures;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { FrontendWorkError, type WorkDescriptor, type WorkFailure, type WorkKind, type WorkOptions, WorkRegistry };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const MAX_DRAIN_PASSES = 100;
|
|
2
|
+
class FrontendWorkError extends Error {
|
|
3
|
+
constructor(message, failures = []) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.failures = failures;
|
|
6
|
+
this.name = "FrontendWorkError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function describe(work) {
|
|
10
|
+
return `[${work.kind}] ${work.label} (owner: ${work.owner})`;
|
|
11
|
+
}
|
|
12
|
+
class WorkRegistry {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.nextId = 1;
|
|
15
|
+
/** Total registrations ever — the fixed-point signal for draining. */
|
|
16
|
+
this.registrations = 0;
|
|
17
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
18
|
+
this.failures = [];
|
|
19
|
+
/** How many failures a completed drain has already reported. */
|
|
20
|
+
this.reportedFailures = 0;
|
|
21
|
+
this.drainChain = Promise.resolve();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Register finite owned work. Returns a promise with the work's own
|
|
25
|
+
* settlement semantics — callers may await it directly; the registry
|
|
26
|
+
* additionally observes completion (success or failure) for draining.
|
|
27
|
+
*/
|
|
28
|
+
track(work, options) {
|
|
29
|
+
const descriptor = {
|
|
30
|
+
id: this.nextId++,
|
|
31
|
+
label: options.label,
|
|
32
|
+
kind: options.kind ?? "tracked",
|
|
33
|
+
owner: options.owner ?? "application"
|
|
34
|
+
};
|
|
35
|
+
this.registrations += 1;
|
|
36
|
+
const promise = typeof work === "function" ? work() : work;
|
|
37
|
+
const observed = promise.then(
|
|
38
|
+
() => void 0,
|
|
39
|
+
(error) => {
|
|
40
|
+
this.failures.push({ ...descriptor, error });
|
|
41
|
+
}
|
|
42
|
+
).finally(() => {
|
|
43
|
+
this.inFlight.delete(descriptor.id);
|
|
44
|
+
});
|
|
45
|
+
this.inFlight.set(descriptor.id, { descriptor, observed });
|
|
46
|
+
return promise;
|
|
47
|
+
}
|
|
48
|
+
/** Descriptors of currently incomplete owned work. */
|
|
49
|
+
pending() {
|
|
50
|
+
return [...this.inFlight.values()].map((entry) => entry.descriptor);
|
|
51
|
+
}
|
|
52
|
+
/** Every owned-work failure observed so far (accumulates across drains). */
|
|
53
|
+
errors() {
|
|
54
|
+
return [...this.failures];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Fixed-point drain: await all pending owned work, run the adapter flush
|
|
58
|
+
* hooks, repeat until one full pass observes no pending work and causes
|
|
59
|
+
* no new registrations. Concurrent drains queue — each caller gets its
|
|
60
|
+
* own complete pass over whatever work exists when its turn arrives.
|
|
61
|
+
*
|
|
62
|
+
* Rejects with `FrontendWorkError` naming the owners when owned work
|
|
63
|
+
* failed since the previous drain, or when the pass bound is exceeded
|
|
64
|
+
* (repeating owned work), listing pending and repeating labels.
|
|
65
|
+
*/
|
|
66
|
+
drain(flushHooks = []) {
|
|
67
|
+
const run = this.drainChain.then(() => this.drainOnce(flushHooks));
|
|
68
|
+
this.drainChain = run.then(
|
|
69
|
+
() => void 0,
|
|
70
|
+
() => void 0
|
|
71
|
+
);
|
|
72
|
+
return run;
|
|
73
|
+
}
|
|
74
|
+
async drainOnce(flushHooks) {
|
|
75
|
+
const seenPerLabel = /* @__PURE__ */ new Map();
|
|
76
|
+
for (let pass = 1; pass <= MAX_DRAIN_PASSES; pass++) {
|
|
77
|
+
const registrationsBefore = this.registrations;
|
|
78
|
+
const snapshot = [...this.inFlight.values()];
|
|
79
|
+
for (const { descriptor } of snapshot) {
|
|
80
|
+
seenPerLabel.set(descriptor.label, (seenPerLabel.get(descriptor.label) ?? 0) + 1);
|
|
81
|
+
}
|
|
82
|
+
await Promise.all(snapshot.map((entry) => entry.observed));
|
|
83
|
+
for (const hook of flushHooks) {
|
|
84
|
+
await hook();
|
|
85
|
+
}
|
|
86
|
+
await Promise.resolve();
|
|
87
|
+
await Promise.resolve();
|
|
88
|
+
const stable = this.inFlight.size === 0 && this.registrations === registrationsBefore;
|
|
89
|
+
if (stable) {
|
|
90
|
+
this.throwNewFailures();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const pendingLines = this.pending().map(describe);
|
|
95
|
+
const repeating = [...seenPerLabel.entries()].filter(([, count]) => count > 3).map(([label, count]) => `${label} (seen in ${count} passes)`);
|
|
96
|
+
throw new FrontendWorkError(
|
|
97
|
+
`settle did not converge within ${MAX_DRAIN_PASSES} drain passes.
|
|
98
|
+
` + (pendingLines.length ? `Still pending:
|
|
99
|
+
${pendingLines.join("\n ")}
|
|
100
|
+
` : "") + (repeating.length ? `Repeating owned work:
|
|
101
|
+
${repeating.join("\n ")}
|
|
102
|
+
` : "") + "Owned work keeps creating more owned work \u2014 this names the owners; it is not a timeout.",
|
|
103
|
+
this.takeNewFailures()
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
takeNewFailures() {
|
|
107
|
+
const fresh = this.failures.slice(this.reportedFailures);
|
|
108
|
+
this.reportedFailures = this.failures.length;
|
|
109
|
+
return fresh;
|
|
110
|
+
}
|
|
111
|
+
throwNewFailures() {
|
|
112
|
+
const fresh = this.takeNewFailures();
|
|
113
|
+
if (!fresh.length) return;
|
|
114
|
+
const lines = fresh.map(
|
|
115
|
+
(failure) => `${describe(failure)}: ${failure.error instanceof Error ? failure.error.message : String(failure.error)}`
|
|
116
|
+
);
|
|
117
|
+
throw new FrontendWorkError(
|
|
118
|
+
`owned frontend work failed:
|
|
119
|
+
${lines.join("\n ")}`,
|
|
120
|
+
fresh
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
FrontendWorkError,
|
|
126
|
+
WorkRegistry
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=work_registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/frontend/work_registry.ts"],"sourcesContent":["/**\n * Owned-work registry for the frontend application slice (18A D-FAS-01).\n *\n * `act()` and `settle()` drain framework-owned finite work registered here\n * through production seams (transport invokes, event deliveries, navigation\n * transitions, tracked lifecycle tasks). The registry never patches global\n * async primitives and never guesses at event-loop emptiness: work exists\n * only because an owner registered it, and a drain is a fixed point —\n * repeated passes until one complete pass creates no new owned work.\n *\n * Floating promises and bare timers are explicitly outside the guarantee;\n * they are product lifecycle bugs, not framework blind spots to paper over.\n */\n\nexport type WorkKind =\n | 'invoke'\n | 'event-delivery'\n | 'navigation'\n | 'initialize'\n | 'tracked'\n | (string & {});\n\nexport interface WorkOptions {\n /** Human-readable diagnostic label, e.g. `invoke messages.send`. */\n label: string;\n kind?: WorkKind;\n /** Owning lifecycle identity, e.g. `page main.chat_dom` or `application`. */\n owner?: string;\n}\n\nexport interface WorkDescriptor {\n readonly id: number;\n readonly label: string;\n readonly kind: WorkKind;\n readonly owner: string;\n}\n\nexport interface WorkFailure extends WorkDescriptor {\n readonly error: unknown;\n}\n\n/** Safety bound on drain passes — a diagnostic limit, not the readiness mechanism. */\nconst MAX_DRAIN_PASSES = 100;\n\nexport class FrontendWorkError extends Error {\n constructor(message: string, readonly failures: readonly WorkFailure[] = []) {\n super(message);\n this.name = 'FrontendWorkError';\n }\n}\n\nfunction describe(work: WorkDescriptor): string {\n return `[${work.kind}] ${work.label} (owner: ${work.owner})`;\n}\n\nexport class WorkRegistry {\n private nextId = 1;\n /** Total registrations ever — the fixed-point signal for draining. */\n private registrations = 0;\n private readonly inFlight = new Map<number, { descriptor: WorkDescriptor; observed: Promise<void> }>();\n private readonly failures: WorkFailure[] = [];\n /** How many failures a completed drain has already reported. */\n private reportedFailures = 0;\n private drainChain: Promise<void> = Promise.resolve();\n\n /**\n * Register finite owned work. Returns a promise with the work's own\n * settlement semantics — callers may await it directly; the registry\n * additionally observes completion (success or failure) for draining.\n */\n track<T>(work: Promise<T> | (() => Promise<T>), options: WorkOptions): Promise<T> {\n const descriptor: WorkDescriptor = {\n id: this.nextId++,\n label: options.label,\n kind: options.kind ?? 'tracked',\n owner: options.owner ?? 'application',\n };\n\n this.registrations += 1;\n const promise = typeof work === 'function' ? work() : work;\n\n // Observe settlement without altering the caller's promise semantics.\n const observed = promise.then(\n () => undefined,\n (error) => {\n this.failures.push({ ...descriptor, error });\n },\n ).finally(() => {\n this.inFlight.delete(descriptor.id);\n });\n\n this.inFlight.set(descriptor.id, { descriptor, observed });\n return promise;\n }\n\n /** Descriptors of currently incomplete owned work. */\n pending(): WorkDescriptor[] {\n return [...this.inFlight.values()].map((entry) => entry.descriptor);\n }\n\n /** Every owned-work failure observed so far (accumulates across drains). */\n errors(): readonly WorkFailure[] {\n return [...this.failures];\n }\n\n /**\n * Fixed-point drain: await all pending owned work, run the adapter flush\n * hooks, repeat until one full pass observes no pending work and causes\n * no new registrations. Concurrent drains queue — each caller gets its\n * own complete pass over whatever work exists when its turn arrives.\n *\n * Rejects with `FrontendWorkError` naming the owners when owned work\n * failed since the previous drain, or when the pass bound is exceeded\n * (repeating owned work), listing pending and repeating labels.\n */\n drain(flushHooks: ReadonlyArray<() => void | Promise<void>> = []): Promise<void> {\n const run = this.drainChain.then(() => this.drainOnce(flushHooks));\n // Keep the chain alive regardless of this drain's outcome.\n this.drainChain = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n private async drainOnce(flushHooks: ReadonlyArray<() => void | Promise<void>>): Promise<void> {\n const seenPerLabel = new Map<string, number>();\n\n for (let pass = 1; pass <= MAX_DRAIN_PASSES; pass++) {\n const registrationsBefore = this.registrations;\n const snapshot = [...this.inFlight.values()];\n\n for (const { descriptor } of snapshot) {\n seenPerLabel.set(descriptor.label, (seenPerLabel.get(descriptor.label) ?? 0) + 1);\n }\n\n // Observed promises never reject (failures are recorded by track());\n // awaiting them cannot abort the pass mid-way.\n await Promise.all(snapshot.map((entry) => entry.observed));\n\n for (const hook of flushHooks) {\n await hook();\n }\n\n // Give completion continuations a microtask to register follow-on\n // owned work before the fixed point is tested.\n await Promise.resolve();\n await Promise.resolve();\n\n const stable = this.inFlight.size === 0 && this.registrations === registrationsBefore;\n if (stable) {\n this.throwNewFailures();\n return;\n }\n }\n\n const pendingLines = this.pending().map(describe);\n const repeating = [...seenPerLabel.entries()]\n .filter(([, count]) => count > 3)\n .map(([label, count]) => `${label} (seen in ${count} passes)`);\n\n throw new FrontendWorkError(\n `settle did not converge within ${MAX_DRAIN_PASSES} drain passes.\\n` +\n (pendingLines.length ? `Still pending:\\n ${pendingLines.join('\\n ')}\\n` : '') +\n (repeating.length ? `Repeating owned work:\\n ${repeating.join('\\n ')}\\n` : '') +\n 'Owned work keeps creating more owned work — this names the owners; it is not a timeout.',\n this.takeNewFailures(),\n );\n }\n\n private takeNewFailures(): WorkFailure[] {\n const fresh = this.failures.slice(this.reportedFailures);\n this.reportedFailures = this.failures.length;\n return fresh;\n }\n\n private throwNewFailures(): void {\n const fresh = this.takeNewFailures();\n if (!fresh.length) return;\n const lines = fresh.map(\n (failure) =>\n `${describe(failure)}: ${failure.error instanceof Error ? failure.error.message : String(failure.error)}`,\n );\n throw new FrontendWorkError(\n `owned frontend work failed:\\n ${lines.join('\\n ')}`,\n fresh,\n );\n }\n}\n"],"mappings":"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","names":[]}
|