@pwfabric/runtime 0.0.0-stage → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +90 -0
- package/README.md +43 -2
- package/dist/a11y/announcer.d.ts +65 -0
- package/dist/a11y/announcer.js +140 -0
- package/dist/a11y/announcer.js.map +1 -0
- package/dist/a11y/contrast.d.ts +54 -0
- package/dist/a11y/contrast.js +93 -0
- package/dist/a11y/contrast.js.map +1 -0
- package/dist/a11y/focus.d.ts +123 -0
- package/dist/a11y/focus.js +282 -0
- package/dist/a11y/focus.js.map +1 -0
- package/dist/a11y/index.d.ts +21 -0
- package/dist/a11y/index.js +18 -0
- package/dist/a11y/index.js.map +1 -0
- package/dist/a11y/keyboard.d.ts +144 -0
- package/dist/a11y/keyboard.js +317 -0
- package/dist/a11y/keyboard.js.map +1 -0
- package/dist/adapters/default-image.d.ts +25 -0
- package/dist/adapters/default-image.js +37 -0
- package/dist/adapters/default-image.js.map +1 -0
- package/dist/adapters/index.d.ts +27 -0
- package/dist/adapters/index.js +28 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/next-image.d.ts +38 -0
- package/dist/adapters/next-image.js +124 -0
- package/dist/adapters/next-image.js.map +1 -0
- package/dist/adapters/types.d.ts +61 -0
- package/dist/adapters/types.js +11 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/analytics/index.d.ts +14 -0
- package/dist/analytics/index.js +15 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/tracker.d.ts +65 -0
- package/dist/analytics/tracker.js +251 -0
- package/dist/analytics/tracker.js.map +1 -0
- package/dist/analytics/types.d.ts +130 -0
- package/dist/analytics/types.js +11 -0
- package/dist/analytics/types.js.map +1 -0
- package/dist/auth/index.d.ts +15 -0
- package/dist/auth/index.js +14 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/provider.d.ts +132 -0
- package/dist/auth/provider.js +473 -0
- package/dist/auth/provider.js.map +1 -0
- package/dist/auth/types.d.ts +117 -0
- package/dist/auth/types.js +9 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/binding/collection.d.ts +190 -0
- package/dist/binding/collection.js +358 -0
- package/dist/binding/collection.js.map +1 -0
- package/dist/binding/datasource.d.ts +319 -0
- package/dist/binding/datasource.js +556 -0
- package/dist/binding/datasource.js.map +1 -0
- package/dist/binding/engine.d.ts +65 -0
- package/dist/binding/engine.js +268 -0
- package/dist/binding/engine.js.map +1 -0
- package/dist/binding/expression.d.ts +150 -0
- package/dist/binding/expression.js +787 -0
- package/dist/binding/expression.js.map +1 -0
- package/dist/binding/index.d.ts +19 -0
- package/dist/binding/index.js +14 -0
- package/dist/binding/index.js.map +1 -0
- package/dist/binding/two-way.d.ts +149 -0
- package/dist/binding/two-way.js +462 -0
- package/dist/binding/two-way.js.map +1 -0
- package/dist/binding/types.d.ts +119 -0
- package/dist/binding/types.js +9 -0
- package/dist/binding/types.js.map +1 -0
- package/dist/blocks/component-types.d.ts +81 -0
- package/dist/blocks/component-types.js +11 -0
- package/dist/blocks/component-types.js.map +1 -0
- package/dist/blocks/composition-rules.d.ts +168 -0
- package/dist/blocks/composition-rules.js +391 -0
- package/dist/blocks/composition-rules.js.map +1 -0
- package/dist/blocks/hooks.d.ts +31 -0
- package/dist/blocks/hooks.js +42 -0
- package/dist/blocks/hooks.js.map +1 -0
- package/dist/blocks/index.d.ts +14 -0
- package/dist/blocks/index.js +23 -0
- package/dist/blocks/index.js.map +1 -0
- package/dist/blocks/registry.d.ts +84 -0
- package/dist/blocks/registry.js +136 -0
- package/dist/blocks/registry.js.map +1 -0
- package/dist/blocks/rule-engine.d.ts +131 -0
- package/dist/blocks/rule-engine.js +321 -0
- package/dist/blocks/rule-engine.js.map +1 -0
- package/dist/blocks/surface-event-context.d.ts +13 -0
- package/dist/blocks/surface-event-context.js +14 -0
- package/dist/blocks/surface-event-context.js.map +1 -0
- package/dist/blocks/surface-id-context.d.ts +13 -0
- package/dist/blocks/surface-id-context.js +15 -0
- package/dist/blocks/surface-id-context.js.map +1 -0
- package/dist/bus.d.ts +41 -0
- package/dist/bus.js +158 -0
- package/dist/bus.js.map +1 -0
- package/dist/cache/index.d.ts +9 -0
- package/dist/cache/index.js +10 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/memory.d.ts +32 -0
- package/dist/cache/memory.js +138 -0
- package/dist/cache/memory.js.map +1 -0
- package/dist/cache/types.d.ts +62 -0
- package/dist/cache/types.js +9 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/capability/index.d.ts +72 -0
- package/dist/capability/index.js +87 -0
- package/dist/capability/index.js.map +1 -0
- package/dist/capability/kernel-capabilities.d.ts +31 -0
- package/dist/capability/kernel-capabilities.js +36 -0
- package/dist/capability/kernel-capabilities.js.map +1 -0
- package/dist/capability/platform-capabilities.d.ts +71 -0
- package/dist/capability/platform-capabilities.js +267 -0
- package/dist/capability/platform-capabilities.js.map +1 -0
- package/dist/capability/type-mapping.d.ts +70 -0
- package/dist/capability/type-mapping.js +107 -0
- package/dist/capability/type-mapping.js.map +1 -0
- package/dist/capability/validation.d.ts +46 -0
- package/dist/capability/validation.js +54 -0
- package/dist/capability/validation.js.map +1 -0
- package/dist/connectors/declared-connector.d.ts +48 -0
- package/dist/connectors/declared-connector.js +333 -0
- package/dist/connectors/declared-connector.js.map +1 -0
- package/dist/connectors/engine-registry.d.ts +50 -0
- package/dist/connectors/engine-registry.js +135 -0
- package/dist/connectors/engine-registry.js.map +1 -0
- package/dist/connectors/index.d.ts +14 -0
- package/dist/connectors/index.js +14 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/module-loader.d.ts +70 -0
- package/dist/connectors/module-loader.js +143 -0
- package/dist/connectors/module-loader.js.map +1 -0
- package/dist/connectors/types.d.ts +174 -0
- package/dist/connectors/types.js +23 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/context.d.ts +27 -0
- package/dist/context.js +38 -0
- package/dist/context.js.map +1 -0
- package/dist/data/DataCapabilityProvider.d.ts +39 -0
- package/dist/data/DataCapabilityProvider.js +58 -0
- package/dist/data/DataCapabilityProvider.js.map +1 -0
- package/dist/data/index.d.ts +17 -0
- package/dist/data/index.js +18 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/manager.d.ts +40 -0
- package/dist/data/manager.js +442 -0
- package/dist/data/manager.js.map +1 -0
- package/dist/data/types.d.ts +218 -0
- package/dist/data/types.js +2 -0
- package/dist/data/types.js.map +1 -0
- package/dist/entity/aggregate.d.ts +8 -0
- package/dist/entity/aggregate.js +24 -0
- package/dist/entity/aggregate.js.map +1 -0
- package/dist/entity/apply-query.d.ts +58 -0
- package/dist/entity/apply-query.js +264 -0
- package/dist/entity/apply-query.js.map +1 -0
- package/dist/entity/behavior.d.ts +94 -0
- package/dist/entity/behavior.js +78 -0
- package/dist/entity/behavior.js.map +1 -0
- package/dist/entity/connector.d.ts +141 -0
- package/dist/entity/connector.js +89 -0
- package/dist/entity/connector.js.map +1 -0
- package/dist/entity/connectors/index.d.ts +12 -0
- package/dist/entity/connectors/index.js +11 -0
- package/dist/entity/connectors/index.js.map +1 -0
- package/dist/entity/connectors/static.d.ts +51 -0
- package/dist/entity/connectors/static.js +245 -0
- package/dist/entity/connectors/static.js.map +1 -0
- package/dist/entity/entity-datasource.d.ts +50 -0
- package/dist/entity/entity-datasource.js +33 -0
- package/dist/entity/entity-datasource.js.map +1 -0
- package/dist/entity/facade.d.ts +70 -0
- package/dist/entity/facade.js +70 -0
- package/dist/entity/facade.js.map +1 -0
- package/dist/entity/http-connector.d.ts +123 -0
- package/dist/entity/http-connector.js +295 -0
- package/dist/entity/http-connector.js.map +1 -0
- package/dist/entity/index.d.ts +38 -0
- package/dist/entity/index.js +31 -0
- package/dist/entity/index.js.map +1 -0
- package/dist/entity/postgres-connector.d.ts +64 -0
- package/dist/entity/postgres-connector.js +239 -0
- package/dist/entity/postgres-connector.js.map +1 -0
- package/dist/entity/registry.d.ts +59 -0
- package/dist/entity/registry.js +90 -0
- package/dist/entity/registry.js.map +1 -0
- package/dist/entity/store.d.ts +66 -0
- package/dist/entity/store.js +349 -0
- package/dist/entity/store.js.map +1 -0
- package/dist/entity/types.d.ts +180 -0
- package/dist/entity/types.js +10 -0
- package/dist/entity/types.js.map +1 -0
- package/dist/execution-context.d.ts +88 -0
- package/dist/execution-context.js +146 -0
- package/dist/execution-context.js.map +1 -0
- package/dist/executor.d.ts +27 -0
- package/dist/executor.js +33 -0
- package/dist/executor.js.map +1 -0
- package/dist/forms/form-entity-bridge.d.ts +94 -0
- package/dist/forms/form-entity-bridge.js +96 -0
- package/dist/forms/form-entity-bridge.js.map +1 -0
- package/dist/forms/form-write-target.d.ts +34 -0
- package/dist/forms/form-write-target.js +81 -0
- package/dist/forms/form-write-target.js.map +1 -0
- package/dist/forms/index.d.ts +22 -0
- package/dist/forms/index.js +25 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/forms/named-control-values.d.ts +27 -0
- package/dist/forms/named-control-values.js +42 -0
- package/dist/forms/named-control-values.js.map +1 -0
- package/dist/forms/processor.d.ts +55 -0
- package/dist/forms/processor.js +154 -0
- package/dist/forms/processor.js.map +1 -0
- package/dist/forms/types.d.ts +174 -0
- package/dist/forms/types.js +33 -0
- package/dist/forms/types.js.map +1 -0
- package/dist/forms/validator.d.ts +61 -0
- package/dist/forms/validator.js +218 -0
- package/dist/forms/validator.js.map +1 -0
- package/dist/graph/index.d.ts +10 -0
- package/dist/graph/index.js +9 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/resolver.d.ts +66 -0
- package/dist/graph/resolver.js +243 -0
- package/dist/graph/resolver.js.map +1 -0
- package/dist/graph/types.d.ts +125 -0
- package/dist/graph/types.js +9 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/hooks/useInstalledAtoms.d.ts +96 -0
- package/dist/hooks/useInstalledAtoms.js +92 -0
- package/dist/hooks/useInstalledAtoms.js.map +1 -0
- package/dist/i18n/index.d.ts +15 -0
- package/dist/i18n/index.js +14 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/translator.d.ts +76 -0
- package/dist/i18n/translator.js +357 -0
- package/dist/i18n/translator.js.map +1 -0
- package/dist/i18n/types.d.ts +93 -0
- package/dist/i18n/types.js +9 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel.d.ts +47 -0
- package/dist/kernel.js +258 -0
- package/dist/kernel.js.map +1 -0
- package/dist/perf/budget.d.ts +92 -0
- package/dist/perf/budget.js +122 -0
- package/dist/perf/budget.js.map +1 -0
- package/dist/perf/index.d.ts +16 -0
- package/dist/perf/index.js +13 -0
- package/dist/perf/index.js.map +1 -0
- package/dist/perf/render-timer.d.ts +90 -0
- package/dist/perf/render-timer.js +136 -0
- package/dist/perf/render-timer.js.map +1 -0
- package/dist/perf/reporter.d.ts +126 -0
- package/dist/perf/reporter.js +241 -0
- package/dist/perf/reporter.js.map +1 -0
- package/dist/perf/web-vitals.d.ts +111 -0
- package/dist/perf/web-vitals.js +268 -0
- package/dist/perf/web-vitals.js.map +1 -0
- package/dist/registry.d.ts +63 -0
- package/dist/registry.js +126 -0
- package/dist/registry.js.map +1 -0
- package/dist/render/BehaviorSurfaceProvider.d.ts +65 -0
- package/dist/render/BehaviorSurfaceProvider.js +185 -0
- package/dist/render/BehaviorSurfaceProvider.js.map +1 -0
- package/dist/render/BlockErrorBoundary.d.ts +26 -0
- package/dist/render/BlockErrorBoundary.js +41 -0
- package/dist/render/BlockErrorBoundary.js.map +1 -0
- package/dist/render/DataBoundSurfaceRenderer.d.ts +78 -0
- package/dist/render/DataBoundSurfaceRenderer.js +998 -0
- package/dist/render/DataBoundSurfaceRenderer.js.map +1 -0
- package/dist/render/DynamicBlockWrapper.d.ts +23 -0
- package/dist/render/DynamicBlockWrapper.js +117 -0
- package/dist/render/DynamicBlockWrapper.js.map +1 -0
- package/dist/render/appearance-resolver.d.ts +50 -0
- package/dist/render/appearance-resolver.js +78 -0
- package/dist/render/appearance-resolver.js.map +1 -0
- package/dist/render/capability-gate.d.ts +69 -0
- package/dist/render/capability-gate.js +94 -0
- package/dist/render/capability-gate.js.map +1 -0
- package/dist/render/conditional-states-css.d.ts +41 -0
- package/dist/render/conditional-states-css.js +82 -0
- package/dist/render/conditional-states-css.js.map +1 -0
- package/dist/render/connections-context.d.ts +31 -0
- package/dist/render/connections-context.js +34 -0
- package/dist/render/connections-context.js.map +1 -0
- package/dist/render/dark-theme-css.d.ts +36 -0
- package/dist/render/dark-theme-css.js +67 -0
- package/dist/render/dark-theme-css.js.map +1 -0
- package/dist/render/data-binding-types.d.ts +191 -0
- package/dist/render/data-binding-types.js +11 -0
- package/dist/render/data-binding-types.js.map +1 -0
- package/dist/render/data-cache.d.ts +30 -0
- package/dist/render/data-cache.js +37 -0
- package/dist/render/data-cache.js.map +1 -0
- package/dist/render/derive-dark-theme.d.ts +17 -0
- package/dist/render/derive-dark-theme.js +18 -0
- package/dist/render/derive-dark-theme.js.map +1 -0
- package/dist/render/dynamic-artifact-context.d.ts +77 -0
- package/dist/render/dynamic-artifact-context.js +89 -0
- package/dist/render/dynamic-artifact-context.js.map +1 -0
- package/dist/render/dynamic-block-loader.d.ts +80 -0
- package/dist/render/dynamic-block-loader.js +197 -0
- package/dist/render/dynamic-block-loader.js.map +1 -0
- package/dist/render/endpoint.d.ts +18 -0
- package/dist/render/endpoint.js +109 -0
- package/dist/render/endpoint.js.map +1 -0
- package/dist/render/entity-facade-context.d.ts +34 -0
- package/dist/render/entity-facade-context.js +37 -0
- package/dist/render/entity-facade-context.js.map +1 -0
- package/dist/render/group-states-css.d.ts +41 -0
- package/dist/render/group-states-css.js +73 -0
- package/dist/render/group-states-css.js.map +1 -0
- package/dist/render/hooks/useSurfaceEvents.d.ts +8 -0
- package/dist/render/hooks/useSurfaceEvents.js +9 -0
- package/dist/render/hooks/useSurfaceEvents.js.map +1 -0
- package/dist/render/hooks/useSurfaceId.d.ts +8 -0
- package/dist/render/hooks/useSurfaceId.js +9 -0
- package/dist/render/hooks/useSurfaceId.js.map +1 -0
- package/dist/render/index.d.ts +48 -0
- package/dist/render/index.js +61 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/interaction-rule-engine.d.ts +12 -0
- package/dist/render/interaction-rule-engine.js +47 -0
- package/dist/render/interaction-rule-engine.js.map +1 -0
- package/dist/render/keyframes-css.d.ts +31 -0
- package/dist/render/keyframes-css.js +44 -0
- package/dist/render/keyframes-css.js.map +1 -0
- package/dist/render/query-state.d.ts +3 -0
- package/dist/render/query-state.js +70 -0
- package/dist/render/query-state.js.map +1 -0
- package/dist/render/renderer.d.ts +44 -0
- package/dist/render/renderer.js +937 -0
- package/dist/render/renderer.js.map +1 -0
- package/dist/render/resolve-expressions.d.ts +37 -0
- package/dist/render/resolve-expressions.js +309 -0
- package/dist/render/resolve-expressions.js.map +1 -0
- package/dist/render/responsive-style-css.d.ts +42 -0
- package/dist/render/responsive-style-css.js +123 -0
- package/dist/render/responsive-style-css.js.map +1 -0
- package/dist/render/responsive.d.ts +49 -0
- package/dist/render/responsive.js +99 -0
- package/dist/render/responsive.js.map +1 -0
- package/dist/render/scoped-css.d.ts +71 -0
- package/dist/render/scoped-css.js +102 -0
- package/dist/render/scoped-css.js.map +1 -0
- package/dist/render/shell-selection.d.ts +69 -0
- package/dist/render/shell-selection.js +117 -0
- package/dist/render/shell-selection.js.map +1 -0
- package/dist/render/states-css.d.ts +32 -0
- package/dist/render/states-css.js +63 -0
- package/dist/render/states-css.js.map +1 -0
- package/dist/render/surface-data-context.d.ts +18 -0
- package/dist/render/surface-data-context.js +21 -0
- package/dist/render/surface-data-context.js.map +1 -0
- package/dist/render/surface-event-bus.d.ts +19 -0
- package/dist/render/surface-event-bus.js +68 -0
- package/dist/render/surface-event-bus.js.map +1 -0
- package/dist/render/surface-event-context.d.ts +11 -0
- package/dist/render/surface-event-context.js +12 -0
- package/dist/render/surface-event-context.js.map +1 -0
- package/dist/render/surface-id-context.d.ts +8 -0
- package/dist/render/surface-id-context.js +9 -0
- package/dist/render/surface-id-context.js.map +1 -0
- package/dist/render/theme-utils.d.ts +14 -0
- package/dist/render/theme-utils.js +15 -0
- package/dist/render/theme-utils.js.map +1 -0
- package/dist/render/types.d.ts +282 -0
- package/dist/render/types.js +9 -0
- package/dist/render/types.js.map +1 -0
- package/dist/render/visibility-css.d.ts +42 -0
- package/dist/render/visibility-css.js +119 -0
- package/dist/render/visibility-css.js.map +1 -0
- package/dist/render/width-scope.d.ts +34 -0
- package/dist/render/width-scope.js +44 -0
- package/dist/render/width-scope.js.map +1 -0
- package/dist/render/write-only-sources.d.ts +33 -0
- package/dist/render/write-only-sources.js +99 -0
- package/dist/render/write-only-sources.js.map +1 -0
- package/dist/routing/index.d.ts +14 -0
- package/dist/routing/index.js +15 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/routing/router.d.ts +42 -0
- package/dist/routing/router.js +218 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/routing/types.d.ts +124 -0
- package/dist/routing/types.js +9 -0
- package/dist/routing/types.js.map +1 -0
- package/dist/seo/generator.d.ts +39 -0
- package/dist/seo/generator.js +236 -0
- package/dist/seo/generator.js.map +1 -0
- package/dist/seo/index.d.ts +14 -0
- package/dist/seo/index.js +15 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/seo/types.d.ts +181 -0
- package/dist/seo/types.js +9 -0
- package/dist/seo/types.js.map +1 -0
- package/dist/state/index.d.ts +15 -0
- package/dist/state/index.js +16 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/manager.d.ts +33 -0
- package/dist/state/manager.js +81 -0
- package/dist/state/manager.js.map +1 -0
- package/dist/state/store.d.ts +40 -0
- package/dist/state/store.js +145 -0
- package/dist/state/store.js.map +1 -0
- package/dist/state/types.d.ts +107 -0
- package/dist/state/types.js +9 -0
- package/dist/state/types.js.map +1 -0
- package/dist/surface/expression.d.ts +65 -0
- package/dist/surface/expression.js +179 -0
- package/dist/surface/expression.js.map +1 -0
- package/dist/surface/index.d.ts +32 -0
- package/dist/surface/index.js +25 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/surface/interpreter.d.ts +96 -0
- package/dist/surface/interpreter.js +231 -0
- package/dist/surface/interpreter.js.map +1 -0
- package/dist/surface/layout.d.ts +54 -0
- package/dist/surface/layout.js +288 -0
- package/dist/surface/layout.js.map +1 -0
- package/dist/surface/output.d.ts +90 -0
- package/dist/surface/output.js +104 -0
- package/dist/surface/output.js.map +1 -0
- package/dist/surface/primitives.d.ts +180 -0
- package/dist/surface/primitives.js +339 -0
- package/dist/surface/primitives.js.map +1 -0
- package/dist/surface/renderer.d.ts +96 -0
- package/dist/surface/renderer.js +190 -0
- package/dist/surface/renderer.js.map +1 -0
- package/dist/surface/schemas.d.ts +181 -0
- package/dist/surface/schemas.js +142 -0
- package/dist/surface/schemas.js.map +1 -0
- package/dist/surface/transforms.d.ts +144 -0
- package/dist/surface/transforms.js +120 -0
- package/dist/surface/transforms.js.map +1 -0
- package/dist/surface/types.d.ts +160 -0
- package/dist/surface/types.js +51 -0
- package/dist/surface/types.js.map +1 -0
- package/dist/surface/validation.d.ts +123 -0
- package/dist/surface/validation.js +147 -0
- package/dist/surface/validation.js.map +1 -0
- package/dist/validator.d.ts +55 -0
- package/dist/validator.js +100 -0
- package/dist/validator.js.map +1 -0
- package/package.json +106 -3
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-way Data Binding
|
|
3
|
+
*
|
|
4
|
+
* P1-5: Extends the binding system with write-back capability.
|
|
5
|
+
* UI changes propagate back to the data source, enabling form inputs,
|
|
6
|
+
* editable fields, and other interactive elements to automatically
|
|
7
|
+
* update the data model.
|
|
8
|
+
*
|
|
9
|
+
* @module @pwfabric/runtime/binding/two-way
|
|
10
|
+
*/
|
|
11
|
+
import { resolvePath } from './expression.js';
|
|
12
|
+
/**
|
|
13
|
+
* Parse a dot-notation path into segments for traversal
|
|
14
|
+
*/
|
|
15
|
+
function parsePath(path) {
|
|
16
|
+
const segments = [];
|
|
17
|
+
const regex = /(\w+)|\[(\d+)\]/g;
|
|
18
|
+
let match;
|
|
19
|
+
while ((match = regex.exec(path)) !== null) {
|
|
20
|
+
if (match[1] !== undefined) {
|
|
21
|
+
segments.push({ type: 'property', key: match[1] });
|
|
22
|
+
}
|
|
23
|
+
else if (match[2] !== undefined) {
|
|
24
|
+
segments.push({ type: 'index', index: parseInt(match[2], 10) });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return segments;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Set a value at a dot-notation path in an object, creating
|
|
31
|
+
* intermediate objects/arrays as needed.
|
|
32
|
+
*
|
|
33
|
+
* Mutates the target object in place.
|
|
34
|
+
*/
|
|
35
|
+
function setAtPath(obj, path, value) {
|
|
36
|
+
const segments = parsePath(path);
|
|
37
|
+
if (segments.length === 0)
|
|
38
|
+
return;
|
|
39
|
+
let current = obj;
|
|
40
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
41
|
+
const segment = segments[i];
|
|
42
|
+
if (segment === undefined)
|
|
43
|
+
return;
|
|
44
|
+
if (segment.type === 'property') {
|
|
45
|
+
if (typeof current !== 'object' || current === null)
|
|
46
|
+
return;
|
|
47
|
+
const record = current;
|
|
48
|
+
if (record[segment.key] === undefined || record[segment.key] === null) {
|
|
49
|
+
// Look ahead to decide whether to create an object or array
|
|
50
|
+
const next = segments[i + 1];
|
|
51
|
+
record[segment.key] = next && next.type === 'index' ? [] : {};
|
|
52
|
+
}
|
|
53
|
+
current = record[segment.key];
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
if (!Array.isArray(current))
|
|
57
|
+
return;
|
|
58
|
+
if (current[segment.index] === undefined || current[segment.index] === null) {
|
|
59
|
+
const next = segments[i + 1];
|
|
60
|
+
current[segment.index] = next && next.type === 'index' ? [] : {};
|
|
61
|
+
}
|
|
62
|
+
current = current[segment.index];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const last = segments[segments.length - 1];
|
|
66
|
+
if (last === undefined)
|
|
67
|
+
return;
|
|
68
|
+
if (last.type === 'property') {
|
|
69
|
+
if (typeof current === 'object' && current !== null) {
|
|
70
|
+
;
|
|
71
|
+
current[last.key] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
if (Array.isArray(current)) {
|
|
76
|
+
current[last.index] = value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Deep-clone a plain object/array structure using structured clone
|
|
82
|
+
* semantics (JSON round-trip for simplicity and zero dependencies).
|
|
83
|
+
*/
|
|
84
|
+
function deepClone(value) {
|
|
85
|
+
if (value === undefined || value === null)
|
|
86
|
+
return value;
|
|
87
|
+
if (typeof value !== 'object')
|
|
88
|
+
return value;
|
|
89
|
+
return JSON.parse(JSON.stringify(value));
|
|
90
|
+
}
|
|
91
|
+
// ============================================================
|
|
92
|
+
// TWO-WAY BINDING INSTANCE (INTERNAL)
|
|
93
|
+
// ============================================================
|
|
94
|
+
/**
|
|
95
|
+
* Internal class that implements a single two-way binding
|
|
96
|
+
*/
|
|
97
|
+
class TwoWayBindingInstance {
|
|
98
|
+
path;
|
|
99
|
+
_value;
|
|
100
|
+
_initialValue;
|
|
101
|
+
_error;
|
|
102
|
+
_isDirty = false;
|
|
103
|
+
_isValidating = false;
|
|
104
|
+
_disposed = false;
|
|
105
|
+
config;
|
|
106
|
+
manager;
|
|
107
|
+
debounceTimer;
|
|
108
|
+
constructor(config, initialValue, manager) {
|
|
109
|
+
this.path = config.path;
|
|
110
|
+
this.config = config;
|
|
111
|
+
this.manager = manager;
|
|
112
|
+
// Apply toView transform for initial value
|
|
113
|
+
const viewValue = config.transform?.toView
|
|
114
|
+
? config.transform.toView(initialValue)
|
|
115
|
+
: initialValue;
|
|
116
|
+
this._value = viewValue;
|
|
117
|
+
this._initialValue = deepClone(viewValue);
|
|
118
|
+
}
|
|
119
|
+
get value() {
|
|
120
|
+
return this._value;
|
|
121
|
+
}
|
|
122
|
+
get error() {
|
|
123
|
+
return this._error;
|
|
124
|
+
}
|
|
125
|
+
get isDirty() {
|
|
126
|
+
return this._isDirty;
|
|
127
|
+
}
|
|
128
|
+
get isValidating() {
|
|
129
|
+
return this._isValidating;
|
|
130
|
+
}
|
|
131
|
+
setValue(newValue) {
|
|
132
|
+
if (this._disposed)
|
|
133
|
+
return;
|
|
134
|
+
const oldValue = this._value;
|
|
135
|
+
// Run validation if configured
|
|
136
|
+
if (this.config.validate) {
|
|
137
|
+
this._isValidating = true;
|
|
138
|
+
const result = this.config.validate(newValue);
|
|
139
|
+
this._isValidating = false;
|
|
140
|
+
if (!result.valid) {
|
|
141
|
+
this._error = result.error;
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Clear any previous error
|
|
146
|
+
this._error = undefined;
|
|
147
|
+
// Update the local value
|
|
148
|
+
this._value = newValue;
|
|
149
|
+
// Track dirty state
|
|
150
|
+
this._isDirty = !isEqual(newValue, this._initialValue);
|
|
151
|
+
// Debounced write-back to model
|
|
152
|
+
this.scheduleWriteBack(newValue, oldValue);
|
|
153
|
+
}
|
|
154
|
+
reset() {
|
|
155
|
+
if (this._disposed)
|
|
156
|
+
return;
|
|
157
|
+
const oldValue = this._value;
|
|
158
|
+
this._value = deepClone(this._initialValue);
|
|
159
|
+
this._isDirty = false;
|
|
160
|
+
this._error = undefined;
|
|
161
|
+
this.clearDebounce();
|
|
162
|
+
// Write the reset value back to model immediately
|
|
163
|
+
const modelValue = this.config.transform?.toModel
|
|
164
|
+
? this.config.transform.toModel(this._value)
|
|
165
|
+
: this._value;
|
|
166
|
+
this.manager.setValueInternal(this.path, modelValue, oldValue);
|
|
167
|
+
}
|
|
168
|
+
dispose() {
|
|
169
|
+
if (this._disposed)
|
|
170
|
+
return;
|
|
171
|
+
this._disposed = true;
|
|
172
|
+
this.clearDebounce();
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Called by the manager when the model value changes externally
|
|
176
|
+
*/
|
|
177
|
+
updateFromModel(modelValue) {
|
|
178
|
+
if (this._disposed)
|
|
179
|
+
return;
|
|
180
|
+
const viewValue = this.config.transform?.toView
|
|
181
|
+
? this.config.transform.toView(modelValue)
|
|
182
|
+
: modelValue;
|
|
183
|
+
this._value = viewValue;
|
|
184
|
+
this._isDirty = !isEqual(viewValue, this._initialValue);
|
|
185
|
+
}
|
|
186
|
+
scheduleWriteBack(newValue, oldValue) {
|
|
187
|
+
this.clearDebounce();
|
|
188
|
+
const delay = this.config.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
189
|
+
if (delay <= 0) {
|
|
190
|
+
// Write immediately
|
|
191
|
+
this.writeBack(newValue, oldValue);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
this.debounceTimer = setTimeout(() => {
|
|
195
|
+
this.debounceTimer = undefined;
|
|
196
|
+
this.writeBack(newValue, oldValue);
|
|
197
|
+
}, delay);
|
|
198
|
+
}
|
|
199
|
+
writeBack(newValue, oldValue) {
|
|
200
|
+
if (this._disposed)
|
|
201
|
+
return;
|
|
202
|
+
// Apply toModel transform before writing back
|
|
203
|
+
const modelValue = this.config.transform?.toModel
|
|
204
|
+
? this.config.transform.toModel(newValue)
|
|
205
|
+
: newValue;
|
|
206
|
+
this.manager.setValueInternal(this.path, modelValue, oldValue);
|
|
207
|
+
}
|
|
208
|
+
clearDebounce() {
|
|
209
|
+
if (this.debounceTimer !== undefined) {
|
|
210
|
+
clearTimeout(this.debounceTimer);
|
|
211
|
+
this.debounceTimer = undefined;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// ============================================================
|
|
216
|
+
// CONSTANTS
|
|
217
|
+
// ============================================================
|
|
218
|
+
/** Default debounce delay in milliseconds */
|
|
219
|
+
const DEFAULT_DEBOUNCE_MS = 300;
|
|
220
|
+
// ============================================================
|
|
221
|
+
// UTILITY
|
|
222
|
+
// ============================================================
|
|
223
|
+
/**
|
|
224
|
+
* Shallow equality check suitable for primitive and simple object values.
|
|
225
|
+
* Uses JSON serialization for object comparison (safe for plain data).
|
|
226
|
+
*/
|
|
227
|
+
function isEqual(a, b) {
|
|
228
|
+
if (a === b)
|
|
229
|
+
return true;
|
|
230
|
+
if (a === null || b === null)
|
|
231
|
+
return false;
|
|
232
|
+
if (a === undefined || b === undefined)
|
|
233
|
+
return false;
|
|
234
|
+
if (typeof a !== typeof b)
|
|
235
|
+
return false;
|
|
236
|
+
if (typeof a === 'object') {
|
|
237
|
+
try {
|
|
238
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
// ============================================================
|
|
247
|
+
// TWO-WAY BINDING MANAGER
|
|
248
|
+
// ============================================================
|
|
249
|
+
/**
|
|
250
|
+
* Manages two-way data bindings.
|
|
251
|
+
*
|
|
252
|
+
* Provides a central data store with path-based access, change listeners,
|
|
253
|
+
* validation, transforms, and batch update support. Each call to `bind()`
|
|
254
|
+
* creates a `TwoWayBinding` instance that can read/write a specific path
|
|
255
|
+
* in the data model.
|
|
256
|
+
*/
|
|
257
|
+
export class TwoWayBindingManager {
|
|
258
|
+
bindings = new Map();
|
|
259
|
+
data;
|
|
260
|
+
listeners = new Map();
|
|
261
|
+
batchDepth = 0;
|
|
262
|
+
pendingNotifications = [];
|
|
263
|
+
constructor(initialData) {
|
|
264
|
+
this.data = initialData ? deepClone(initialData) : {};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Create a two-way binding for a specific data path.
|
|
268
|
+
*
|
|
269
|
+
* Returns a `TwoWayBinding` instance that tracks the value at the path,
|
|
270
|
+
* supports validation and transforms, and writes changes back to the
|
|
271
|
+
* data model with optional debouncing.
|
|
272
|
+
*/
|
|
273
|
+
bind(config) {
|
|
274
|
+
// Dispose any existing binding at this path
|
|
275
|
+
const existing = this.bindings.get(config.path);
|
|
276
|
+
if (existing) {
|
|
277
|
+
existing.dispose();
|
|
278
|
+
}
|
|
279
|
+
const currentValue = resolvePath(this.data, config.path);
|
|
280
|
+
const instance = new TwoWayBindingInstance(config, currentValue, this);
|
|
281
|
+
this.bindings.set(config.path, instance);
|
|
282
|
+
return instance;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get the current value at a data path
|
|
286
|
+
*/
|
|
287
|
+
getValue(path) {
|
|
288
|
+
return resolvePath(this.data, path);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Set a value at a data path, triggering change listeners
|
|
292
|
+
*/
|
|
293
|
+
setValue(path, value) {
|
|
294
|
+
const oldValue = resolvePath(this.data, path);
|
|
295
|
+
// Write to data store
|
|
296
|
+
setAtPath(this.data, path, value);
|
|
297
|
+
// Update any binding watching this path
|
|
298
|
+
const binding = this.bindings.get(path);
|
|
299
|
+
if (binding) {
|
|
300
|
+
binding.updateFromModel(value);
|
|
301
|
+
}
|
|
302
|
+
// Notify listeners
|
|
303
|
+
this.notifyChange(path, value, oldValue);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Subscribe to changes at a data path.
|
|
307
|
+
*
|
|
308
|
+
* @returns Unsubscribe function
|
|
309
|
+
*/
|
|
310
|
+
onChange(path, listener) {
|
|
311
|
+
let pathListeners = this.listeners.get(path);
|
|
312
|
+
if (!pathListeners) {
|
|
313
|
+
pathListeners = new Set();
|
|
314
|
+
this.listeners.set(path, pathListeners);
|
|
315
|
+
}
|
|
316
|
+
pathListeners.add(listener);
|
|
317
|
+
return () => {
|
|
318
|
+
pathListeners.delete(listener);
|
|
319
|
+
if (pathListeners.size === 0) {
|
|
320
|
+
this.listeners.delete(path);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Batch multiple updates together.
|
|
326
|
+
*
|
|
327
|
+
* Listener notifications are deferred until the outermost batch
|
|
328
|
+
* completes, then fired once per changed path.
|
|
329
|
+
*/
|
|
330
|
+
batch(updates) {
|
|
331
|
+
this.batchDepth++;
|
|
332
|
+
try {
|
|
333
|
+
updates();
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
this.batchDepth--;
|
|
337
|
+
if (this.batchDepth === 0) {
|
|
338
|
+
this.flushPendingNotifications();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Get all bindings that have been modified from their initial values
|
|
344
|
+
*/
|
|
345
|
+
getDirtyBindings() {
|
|
346
|
+
const dirty = [];
|
|
347
|
+
for (const binding of this.bindings.values()) {
|
|
348
|
+
if (binding.isDirty) {
|
|
349
|
+
dirty.push(binding);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return dirty;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Reset all bindings to their initial values
|
|
356
|
+
*/
|
|
357
|
+
resetAll() {
|
|
358
|
+
for (const binding of this.bindings.values()) {
|
|
359
|
+
binding.reset();
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Dispose all bindings and clear all listeners
|
|
364
|
+
*/
|
|
365
|
+
dispose() {
|
|
366
|
+
for (const binding of this.bindings.values()) {
|
|
367
|
+
binding.dispose();
|
|
368
|
+
}
|
|
369
|
+
this.bindings.clear();
|
|
370
|
+
this.listeners.clear();
|
|
371
|
+
this.pendingNotifications.length = 0;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Internal method called by binding instances to write back to the model.
|
|
375
|
+
* Not part of the public API.
|
|
376
|
+
*/
|
|
377
|
+
setValueInternal(path, modelValue, oldViewValue) {
|
|
378
|
+
const oldModelValue = resolvePath(this.data, path);
|
|
379
|
+
// Write to data store
|
|
380
|
+
setAtPath(this.data, path, modelValue);
|
|
381
|
+
// Notify listeners with model values
|
|
382
|
+
this.notifyChange(path, modelValue, oldModelValue ?? oldViewValue);
|
|
383
|
+
}
|
|
384
|
+
// ============================================================
|
|
385
|
+
// PRIVATE
|
|
386
|
+
// ============================================================
|
|
387
|
+
notifyChange(path, newValue, oldValue) {
|
|
388
|
+
if (this.batchDepth > 0) {
|
|
389
|
+
// During batch, accumulate notifications (last write wins per path)
|
|
390
|
+
const existingIdx = this.pendingNotifications.findIndex((n) => n.path === path);
|
|
391
|
+
if (existingIdx >= 0) {
|
|
392
|
+
const existing = this.pendingNotifications[existingIdx];
|
|
393
|
+
if (existing) {
|
|
394
|
+
// Keep the original oldValue, update newValue
|
|
395
|
+
this.pendingNotifications[existingIdx] = {
|
|
396
|
+
path,
|
|
397
|
+
newValue,
|
|
398
|
+
oldValue: existing.oldValue,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
this.pendingNotifications.push({ path, newValue, oldValue });
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
this.fireListeners(path, newValue, oldValue);
|
|
408
|
+
}
|
|
409
|
+
flushPendingNotifications() {
|
|
410
|
+
// Copy and clear to handle re-entrant batch calls
|
|
411
|
+
const pending = [...this.pendingNotifications];
|
|
412
|
+
this.pendingNotifications.length = 0;
|
|
413
|
+
for (const { path, newValue, oldValue } of pending) {
|
|
414
|
+
this.fireListeners(path, newValue, oldValue);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
fireListeners(path, newValue, oldValue) {
|
|
418
|
+
const pathListeners = this.listeners.get(path);
|
|
419
|
+
if (!pathListeners)
|
|
420
|
+
return;
|
|
421
|
+
for (const listener of pathListeners) {
|
|
422
|
+
try {
|
|
423
|
+
listener(newValue, oldValue);
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// Swallow errors from listeners to prevent cascade failures
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// ============================================================
|
|
432
|
+
// FACTORY
|
|
433
|
+
// ============================================================
|
|
434
|
+
/**
|
|
435
|
+
* Create a two-way binding manager
|
|
436
|
+
*
|
|
437
|
+
* @param initialData - Optional initial data to populate the model
|
|
438
|
+
* @returns A new TwoWayBindingManager instance
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* ```ts
|
|
442
|
+
* const manager = createTwoWayBindingManager({
|
|
443
|
+
* user: { name: 'John', email: 'john@example.com' }
|
|
444
|
+
* })
|
|
445
|
+
*
|
|
446
|
+
* const nameBinding = manager.bind<string>({
|
|
447
|
+
* path: 'user.name',
|
|
448
|
+
* debounceMs: 200,
|
|
449
|
+
* validate: (v) => ({
|
|
450
|
+
* valid: typeof v === 'string' && v.length > 0,
|
|
451
|
+
* error: 'Name is required',
|
|
452
|
+
* }),
|
|
453
|
+
* })
|
|
454
|
+
*
|
|
455
|
+
* nameBinding.setValue('Jane')
|
|
456
|
+
* // After debounce, manager.getValue('user.name') === 'Jane'
|
|
457
|
+
* ```
|
|
458
|
+
*/
|
|
459
|
+
export function createTwoWayBindingManager(initialData) {
|
|
460
|
+
return new TwoWayBindingManager(initialData);
|
|
461
|
+
}
|
|
462
|
+
//# sourceMappingURL=two-way.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"two-way.js","sourceRoot":"","sources":["../../src/binding/two-way.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Two-way Data Binding\n *\n * P1-5: Extends the binding system with write-back capability.\n * UI changes propagate back to the data source, enabling form inputs,\n * editable fields, and other interactive elements to automatically\n * update the data model.\n *\n * @module @pwfabric/runtime/binding/two-way\n */\n\nimport { resolvePath } from './expression.js'\n\n// ============================================================\n// TYPES\n// ============================================================\n\n/**\n * Configuration for a two-way binding\n */\nexport interface TwoWayBindingConfig {\n /** Data path using dot-notation (e.g., \"user.profile.name\") */\n readonly path: string\n /** Debounce delay in ms for write-back (default: 300) */\n readonly debounceMs?: number\n /** Optional validation function run before setting value */\n readonly validate?: (value: unknown) => ValidationResult\n /** Optional transform functions between UI and model */\n readonly transform?: {\n readonly toModel?: (uiValue: unknown) => unknown\n readonly toView?: (modelValue: unknown) => unknown\n }\n}\n\n/**\n * Result of value validation\n */\nexport interface ValidationResult {\n readonly valid: boolean\n readonly error?: string\n}\n\n/**\n * A two-way binding instance for a specific data path\n */\nexport interface TwoWayBinding<T = unknown> {\n /** The data path this binding targets */\n readonly path: string\n /** Current value at the path */\n readonly value: T\n /** Current validation error, if any */\n readonly error?: string | undefined\n /** Whether the value has changed from the initial value */\n readonly isDirty: boolean\n /** Whether validation is currently running */\n readonly isValidating: boolean\n /** Set a new value (triggers validation, debounced write-back) */\n setValue(newValue: T): void\n /** Reset value to the initial value */\n reset(): void\n /** Dispose the binding and clean up timers */\n dispose(): void\n}\n\n/**\n * Listener called when a value changes at a path\n */\nexport type ChangeListener<T = unknown> = (newValue: T, oldValue: T) => void\n\n// ============================================================\n// PATH UTILITIES\n// ============================================================\n\n/**\n * Parsed segment of a data path\n */\ntype PathSegment = { type: 'property'; key: string } | { type: 'index'; index: number }\n\n/**\n * Parse a dot-notation path into segments for traversal\n */\nfunction parsePath(path: string): PathSegment[] {\n const segments: PathSegment[] = []\n const regex = /(\\w+)|\\[(\\d+)\\]/g\n let match: RegExpExecArray | null\n\n while ((match = regex.exec(path)) !== null) {\n if (match[1] !== undefined) {\n segments.push({ type: 'property', key: match[1] })\n } else if (match[2] !== undefined) {\n segments.push({ type: 'index', index: parseInt(match[2], 10) })\n }\n }\n\n return segments\n}\n\n/**\n * Set a value at a dot-notation path in an object, creating\n * intermediate objects/arrays as needed.\n *\n * Mutates the target object in place.\n */\nfunction setAtPath(obj: Record<string, unknown>, path: string, value: unknown): void {\n const segments = parsePath(path)\n if (segments.length === 0) return\n\n let current: unknown = obj\n\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i]\n if (segment === undefined) return\n\n if (segment.type === 'property') {\n if (typeof current !== 'object' || current === null) return\n const record = current as Record<string, unknown>\n if (record[segment.key] === undefined || record[segment.key] === null) {\n // Look ahead to decide whether to create an object or array\n const next = segments[i + 1]\n record[segment.key] = next && next.type === 'index' ? [] : {}\n }\n current = record[segment.key]\n } else {\n if (!Array.isArray(current)) return\n if (current[segment.index] === undefined || current[segment.index] === null) {\n const next = segments[i + 1]\n current[segment.index] = next && next.type === 'index' ? [] : {}\n }\n current = current[segment.index]\n }\n }\n\n const last = segments[segments.length - 1]\n if (last === undefined) return\n\n if (last.type === 'property') {\n if (typeof current === 'object' && current !== null) {\n ;(current as Record<string, unknown>)[last.key] = value\n }\n } else {\n if (Array.isArray(current)) {\n current[last.index] = value\n }\n }\n}\n\n/**\n * Deep-clone a plain object/array structure using structured clone\n * semantics (JSON round-trip for simplicity and zero dependencies).\n */\nfunction deepClone<T>(value: T): T {\n if (value === undefined || value === null) return value\n if (typeof value !== 'object') return value\n return JSON.parse(JSON.stringify(value)) as T\n}\n\n// ============================================================\n// TWO-WAY BINDING INSTANCE (INTERNAL)\n// ============================================================\n\n/**\n * Internal class that implements a single two-way binding\n */\nclass TwoWayBindingInstance<T = unknown> implements TwoWayBinding<T> {\n readonly path: string\n\n private _value: T\n private _initialValue: T\n private _error: string | undefined\n private _isDirty = false\n private _isValidating = false\n private _disposed = false\n\n private readonly config: TwoWayBindingConfig\n private readonly manager: TwoWayBindingManager\n private debounceTimer: ReturnType<typeof setTimeout> | undefined\n\n constructor(config: TwoWayBindingConfig, initialValue: T, manager: TwoWayBindingManager) {\n this.path = config.path\n this.config = config\n this.manager = manager\n\n // Apply toView transform for initial value\n const viewValue = config.transform?.toView\n ? (config.transform.toView(initialValue) as T)\n : initialValue\n\n this._value = viewValue\n this._initialValue = deepClone(viewValue)\n }\n\n get value(): T {\n return this._value\n }\n\n get error(): string | undefined {\n return this._error\n }\n\n get isDirty(): boolean {\n return this._isDirty\n }\n\n get isValidating(): boolean {\n return this._isValidating\n }\n\n setValue(newValue: T): void {\n if (this._disposed) return\n\n const oldValue = this._value\n\n // Run validation if configured\n if (this.config.validate) {\n this._isValidating = true\n const result = this.config.validate(newValue)\n this._isValidating = false\n\n if (!result.valid) {\n this._error = result.error\n return\n }\n }\n\n // Clear any previous error\n this._error = undefined\n\n // Update the local value\n this._value = newValue\n\n // Track dirty state\n this._isDirty = !isEqual(newValue, this._initialValue)\n\n // Debounced write-back to model\n this.scheduleWriteBack(newValue, oldValue)\n }\n\n reset(): void {\n if (this._disposed) return\n\n const oldValue = this._value\n this._value = deepClone(this._initialValue)\n this._isDirty = false\n this._error = undefined\n this.clearDebounce()\n\n // Write the reset value back to model immediately\n const modelValue = this.config.transform?.toModel\n ? this.config.transform.toModel(this._value)\n : this._value\n\n this.manager.setValueInternal(this.path, modelValue, oldValue)\n }\n\n dispose(): void {\n if (this._disposed) return\n this._disposed = true\n this.clearDebounce()\n }\n\n /**\n * Called by the manager when the model value changes externally\n */\n updateFromModel(modelValue: unknown): void {\n if (this._disposed) return\n\n const viewValue = this.config.transform?.toView\n ? (this.config.transform.toView(modelValue) as T)\n : (modelValue as T)\n\n this._value = viewValue\n this._isDirty = !isEqual(viewValue, this._initialValue)\n }\n\n private scheduleWriteBack(newValue: T, oldValue: T): void {\n this.clearDebounce()\n\n const delay = this.config.debounceMs ?? DEFAULT_DEBOUNCE_MS\n\n if (delay <= 0) {\n // Write immediately\n this.writeBack(newValue, oldValue)\n return\n }\n\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = undefined\n this.writeBack(newValue, oldValue)\n }, delay)\n }\n\n private writeBack(newValue: T, oldValue: T): void {\n if (this._disposed) return\n\n // Apply toModel transform before writing back\n const modelValue = this.config.transform?.toModel\n ? this.config.transform.toModel(newValue)\n : newValue\n\n this.manager.setValueInternal(this.path, modelValue, oldValue)\n }\n\n private clearDebounce(): void {\n if (this.debounceTimer !== undefined) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = undefined\n }\n }\n}\n\n// ============================================================\n// CONSTANTS\n// ============================================================\n\n/** Default debounce delay in milliseconds */\nconst DEFAULT_DEBOUNCE_MS = 300\n\n// ============================================================\n// UTILITY\n// ============================================================\n\n/**\n * Shallow equality check suitable for primitive and simple object values.\n * Uses JSON serialization for object comparison (safe for plain data).\n */\nfunction isEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true\n if (a === null || b === null) return false\n if (a === undefined || b === undefined) return false\n if (typeof a !== typeof b) return false\n if (typeof a === 'object') {\n try {\n return JSON.stringify(a) === JSON.stringify(b)\n } catch {\n return false\n }\n }\n return false\n}\n\n// ============================================================\n// TWO-WAY BINDING MANAGER\n// ============================================================\n\n/**\n * Manages two-way data bindings.\n *\n * Provides a central data store with path-based access, change listeners,\n * validation, transforms, and batch update support. Each call to `bind()`\n * creates a `TwoWayBinding` instance that can read/write a specific path\n * in the data model.\n */\nexport class TwoWayBindingManager {\n private readonly bindings = new Map<string, TwoWayBindingInstance>()\n private data: Record<string, unknown>\n private readonly listeners = new Map<string, Set<ChangeListener>>()\n private batchDepth = 0\n private readonly pendingNotifications: {\n path: string\n newValue: unknown\n oldValue: unknown\n }[] = []\n\n constructor(initialData?: Record<string, unknown>) {\n this.data = initialData ? deepClone(initialData) : {}\n }\n\n /**\n * Create a two-way binding for a specific data path.\n *\n * Returns a `TwoWayBinding` instance that tracks the value at the path,\n * supports validation and transforms, and writes changes back to the\n * data model with optional debouncing.\n */\n bind<T = unknown>(config: TwoWayBindingConfig): TwoWayBinding<T> {\n // Dispose any existing binding at this path\n const existing = this.bindings.get(config.path)\n if (existing) {\n existing.dispose()\n }\n\n const currentValue = resolvePath(this.data, config.path) as T\n const instance = new TwoWayBindingInstance<T>(config, currentValue, this)\n\n this.bindings.set(config.path, instance as TwoWayBindingInstance)\n\n return instance\n }\n\n /**\n * Get the current value at a data path\n */\n getValue(path: string): unknown {\n return resolvePath(this.data, path)\n }\n\n /**\n * Set a value at a data path, triggering change listeners\n */\n setValue(path: string, value: unknown): void {\n const oldValue = resolvePath(this.data, path)\n\n // Write to data store\n setAtPath(this.data, path, value)\n\n // Update any binding watching this path\n const binding = this.bindings.get(path)\n if (binding) {\n binding.updateFromModel(value)\n }\n\n // Notify listeners\n this.notifyChange(path, value, oldValue)\n }\n\n /**\n * Subscribe to changes at a data path.\n *\n * @returns Unsubscribe function\n */\n onChange<T = unknown>(path: string, listener: ChangeListener<T>): () => void {\n let pathListeners = this.listeners.get(path)\n if (!pathListeners) {\n pathListeners = new Set<ChangeListener>()\n this.listeners.set(path, pathListeners)\n }\n\n pathListeners.add(listener as ChangeListener)\n\n return () => {\n pathListeners.delete(listener as ChangeListener)\n if (pathListeners.size === 0) {\n this.listeners.delete(path)\n }\n }\n }\n\n /**\n * Batch multiple updates together.\n *\n * Listener notifications are deferred until the outermost batch\n * completes, then fired once per changed path.\n */\n batch(updates: () => void): void {\n this.batchDepth++\n\n try {\n updates()\n } finally {\n this.batchDepth--\n\n if (this.batchDepth === 0) {\n this.flushPendingNotifications()\n }\n }\n }\n\n /**\n * Get all bindings that have been modified from their initial values\n */\n getDirtyBindings(): readonly TwoWayBinding[] {\n const dirty: TwoWayBinding[] = []\n\n for (const binding of this.bindings.values()) {\n if (binding.isDirty) {\n dirty.push(binding)\n }\n }\n\n return dirty\n }\n\n /**\n * Reset all bindings to their initial values\n */\n resetAll(): void {\n for (const binding of this.bindings.values()) {\n binding.reset()\n }\n }\n\n /**\n * Dispose all bindings and clear all listeners\n */\n dispose(): void {\n for (const binding of this.bindings.values()) {\n binding.dispose()\n }\n this.bindings.clear()\n this.listeners.clear()\n this.pendingNotifications.length = 0\n }\n\n /**\n * Internal method called by binding instances to write back to the model.\n * Not part of the public API.\n */\n setValueInternal(path: string, modelValue: unknown, oldViewValue: unknown): void {\n const oldModelValue = resolvePath(this.data, path)\n\n // Write to data store\n setAtPath(this.data, path, modelValue)\n\n // Notify listeners with model values\n this.notifyChange(path, modelValue, oldModelValue ?? oldViewValue)\n }\n\n // ============================================================\n // PRIVATE\n // ============================================================\n\n private notifyChange(path: string, newValue: unknown, oldValue: unknown): void {\n if (this.batchDepth > 0) {\n // During batch, accumulate notifications (last write wins per path)\n const existingIdx = this.pendingNotifications.findIndex((n) => n.path === path)\n if (existingIdx >= 0) {\n const existing = this.pendingNotifications[existingIdx]\n if (existing) {\n // Keep the original oldValue, update newValue\n this.pendingNotifications[existingIdx] = {\n path,\n newValue,\n oldValue: existing.oldValue,\n }\n }\n } else {\n this.pendingNotifications.push({ path, newValue, oldValue })\n }\n return\n }\n\n this.fireListeners(path, newValue, oldValue)\n }\n\n private flushPendingNotifications(): void {\n // Copy and clear to handle re-entrant batch calls\n const pending = [...this.pendingNotifications]\n this.pendingNotifications.length = 0\n\n for (const { path, newValue, oldValue } of pending) {\n this.fireListeners(path, newValue, oldValue)\n }\n }\n\n private fireListeners(path: string, newValue: unknown, oldValue: unknown): void {\n const pathListeners = this.listeners.get(path)\n if (!pathListeners) return\n\n for (const listener of pathListeners) {\n try {\n listener(newValue, oldValue)\n } catch {\n // Swallow errors from listeners to prevent cascade failures\n }\n }\n }\n}\n\n// ============================================================\n// FACTORY\n// ============================================================\n\n/**\n * Create a two-way binding manager\n *\n * @param initialData - Optional initial data to populate the model\n * @returns A new TwoWayBindingManager instance\n *\n * @example\n * ```ts\n * const manager = createTwoWayBindingManager({\n * user: { name: 'John', email: 'john@example.com' }\n * })\n *\n * const nameBinding = manager.bind<string>({\n * path: 'user.name',\n * debounceMs: 200,\n * validate: (v) => ({\n * valid: typeof v === 'string' && v.length > 0,\n * error: 'Name is required',\n * }),\n * })\n *\n * nameBinding.setValue('Jane')\n * // After debounce, manager.getValue('user.name') === 'Jane'\n * ```\n */\nexport function createTwoWayBindingManager(\n initialData?: Record<string, unknown>\n): TwoWayBindingManager {\n return new TwoWayBindingManager(initialData)\n}\n"]}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding System Types
|
|
3
|
+
*
|
|
4
|
+
* Types for capability-to-surface binding
|
|
5
|
+
*
|
|
6
|
+
* @module @pwfabric/runtime/binding/types
|
|
7
|
+
*/
|
|
8
|
+
import type { CapabilityId, SurfaceId, ExecutionResult } from '@pwfabric/contracts';
|
|
9
|
+
/**
|
|
10
|
+
* Binding state
|
|
11
|
+
*/
|
|
12
|
+
export type BindingState = 'unbound' | 'binding' | 'bound' | 'error' | 'stale';
|
|
13
|
+
/**
|
|
14
|
+
* Binding configuration
|
|
15
|
+
*/
|
|
16
|
+
export interface BindingConfig {
|
|
17
|
+
readonly capabilityId: CapabilityId;
|
|
18
|
+
readonly alias?: string;
|
|
19
|
+
readonly transform?: RuntimeBindingTransform;
|
|
20
|
+
readonly cache?: BindingCacheConfig;
|
|
21
|
+
readonly retry?: RetryConfig;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Transform configuration for binding input/output (runtime version)
|
|
25
|
+
* Named differently from contracts BindingTransform to avoid export collision
|
|
26
|
+
*/
|
|
27
|
+
export interface RuntimeBindingTransform {
|
|
28
|
+
readonly input?: (data: unknown) => unknown;
|
|
29
|
+
readonly output?: (data: unknown) => unknown;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Cache configuration for binding (runtime version)
|
|
33
|
+
* Named differently from contracts CacheConfig to avoid export collision
|
|
34
|
+
*/
|
|
35
|
+
export interface BindingCacheConfig {
|
|
36
|
+
readonly enabled: boolean;
|
|
37
|
+
readonly ttl?: number;
|
|
38
|
+
readonly key?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Retry configuration
|
|
42
|
+
*/
|
|
43
|
+
export interface RetryConfig {
|
|
44
|
+
readonly maxAttempts: number;
|
|
45
|
+
readonly backoff: 'fixed' | 'exponential';
|
|
46
|
+
readonly delayMs: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A bound capability instance
|
|
50
|
+
*/
|
|
51
|
+
export interface BoundCapability<TInput = unknown, TOutput = unknown> {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly capabilityId: CapabilityId;
|
|
54
|
+
readonly state: BindingState;
|
|
55
|
+
readonly config: BindingConfig;
|
|
56
|
+
readonly lastExecuted?: number;
|
|
57
|
+
readonly lastResult?: ExecutionResult<TOutput>;
|
|
58
|
+
readonly error?: BindingError;
|
|
59
|
+
/**
|
|
60
|
+
* Execute the bound capability
|
|
61
|
+
*/
|
|
62
|
+
execute(input: TInput): Promise<ExecutionResult<TOutput>>;
|
|
63
|
+
/**
|
|
64
|
+
* Refresh the binding (re-execute with last input)
|
|
65
|
+
*/
|
|
66
|
+
refresh(): Promise<ExecutionResult<TOutput>>;
|
|
67
|
+
/**
|
|
68
|
+
* Invalidate cached data
|
|
69
|
+
*/
|
|
70
|
+
invalidate(): void;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Binding error
|
|
74
|
+
*/
|
|
75
|
+
export interface BindingError {
|
|
76
|
+
readonly type: 'resolution_failed' | 'execution_failed' | 'transform_failed' | 'cache_failed';
|
|
77
|
+
readonly message: string;
|
|
78
|
+
readonly cause?: unknown;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Context for binding resolution
|
|
82
|
+
*/
|
|
83
|
+
export interface BindingContext {
|
|
84
|
+
readonly surfaceId: SurfaceId;
|
|
85
|
+
readonly auth?: AuthContext;
|
|
86
|
+
readonly data: Record<string, unknown>;
|
|
87
|
+
readonly bindings: Map<string, BoundCapability>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Authentication context
|
|
91
|
+
*/
|
|
92
|
+
export interface AuthContext {
|
|
93
|
+
readonly isAuthenticated: boolean;
|
|
94
|
+
readonly userId?: string;
|
|
95
|
+
readonly worldId?: string;
|
|
96
|
+
readonly scopes?: readonly string[];
|
|
97
|
+
readonly token?: string;
|
|
98
|
+
readonly expiresAt?: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Result of binding resolution
|
|
102
|
+
*/
|
|
103
|
+
export type BindingResult<T = unknown> = {
|
|
104
|
+
readonly success: true;
|
|
105
|
+
readonly binding: BoundCapability<unknown, T>;
|
|
106
|
+
} | {
|
|
107
|
+
readonly success: false;
|
|
108
|
+
readonly error: BindingError;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Result of binding all capabilities for a surface
|
|
112
|
+
*/
|
|
113
|
+
export type SurfaceBindingResult = {
|
|
114
|
+
readonly success: true;
|
|
115
|
+
readonly context: BindingContext;
|
|
116
|
+
} | {
|
|
117
|
+
readonly success: false;
|
|
118
|
+
readonly errors: readonly BindingError[];
|
|
119
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/binding/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG","sourcesContent":["/**\n * Binding System Types\n *\n * Types for capability-to-surface binding\n *\n * @module @pwfabric/runtime/binding/types\n */\n\nimport type { CapabilityId, SurfaceId, ExecutionResult } from '@pwfabric/contracts'\n\n// ============================================================\n// BINDING TYPES\n// ============================================================\n\n/**\n * Binding state\n */\nexport type BindingState = 'unbound' | 'binding' | 'bound' | 'error' | 'stale'\n\n/**\n * Binding configuration\n */\nexport interface BindingConfig {\n readonly capabilityId: CapabilityId\n readonly alias?: string\n readonly transform?: RuntimeBindingTransform\n readonly cache?: BindingCacheConfig\n readonly retry?: RetryConfig\n}\n\n/**\n * Transform configuration for binding input/output (runtime version)\n * Named differently from contracts BindingTransform to avoid export collision\n */\nexport interface RuntimeBindingTransform {\n readonly input?: (data: unknown) => unknown\n readonly output?: (data: unknown) => unknown\n}\n\n/**\n * Cache configuration for binding (runtime version)\n * Named differently from contracts CacheConfig to avoid export collision\n */\nexport interface BindingCacheConfig {\n readonly enabled: boolean\n readonly ttl?: number\n readonly key?: string\n}\n\n/**\n * Retry configuration\n */\nexport interface RetryConfig {\n readonly maxAttempts: number\n readonly backoff: 'fixed' | 'exponential'\n readonly delayMs: number\n}\n\n// ============================================================\n// BINDING INSTANCE\n// ============================================================\n\n/**\n * A bound capability instance\n */\nexport interface BoundCapability<TInput = unknown, TOutput = unknown> {\n readonly id: string\n readonly capabilityId: CapabilityId\n readonly state: BindingState\n readonly config: BindingConfig\n readonly lastExecuted?: number\n readonly lastResult?: ExecutionResult<TOutput>\n readonly error?: BindingError\n\n /**\n * Execute the bound capability\n */\n execute(input: TInput): Promise<ExecutionResult<TOutput>>\n\n /**\n * Refresh the binding (re-execute with last input)\n */\n refresh(): Promise<ExecutionResult<TOutput>>\n\n /**\n * Invalidate cached data\n */\n invalidate(): void\n}\n\n/**\n * Binding error\n */\nexport interface BindingError {\n readonly type: 'resolution_failed' | 'execution_failed' | 'transform_failed' | 'cache_failed'\n readonly message: string\n readonly cause?: unknown\n}\n\n// ============================================================\n// BINDING CONTEXT\n// ============================================================\n\n/**\n * Context for binding resolution\n */\nexport interface BindingContext {\n readonly surfaceId: SurfaceId\n readonly auth?: AuthContext\n readonly data: Record<string, unknown>\n readonly bindings: Map<string, BoundCapability>\n}\n\n/**\n * Authentication context\n */\nexport interface AuthContext {\n readonly isAuthenticated: boolean\n readonly userId?: string\n readonly worldId?: string\n readonly scopes?: readonly string[]\n readonly token?: string\n readonly expiresAt?: number\n}\n\n// ============================================================\n// BINDING RESULT\n// ============================================================\n\n/**\n * Result of binding resolution\n */\nexport type BindingResult<T = unknown> =\n | { readonly success: true; readonly binding: BoundCapability<unknown, T> }\n | { readonly success: false; readonly error: BindingError }\n\n/**\n * Result of binding all capabilities for a surface\n */\nexport type SurfaceBindingResult =\n | { readonly success: true; readonly context: BindingContext }\n | { readonly success: false; readonly errors: readonly BindingError[] }\n"]}
|