@pwfabric/runtime 0.0.0-stage → 1.0.0-rc.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/LICENSE +90 -0
- package/README.md +13 -2
- package/dist/__tests__/dom-test-setup.d.ts +21 -0
- package/dist/__tests__/dom-test-setup.d.ts.map +1 -0
- package/dist/__tests__/dom-test-setup.js +36 -0
- package/dist/__tests__/dom-test-setup.js.map +1 -0
- package/dist/a11y/announcer.d.ts +66 -0
- package/dist/a11y/announcer.d.ts.map +1 -0
- package/dist/a11y/announcer.js +140 -0
- package/dist/a11y/announcer.js.map +1 -0
- package/dist/a11y/contrast.d.ts +55 -0
- package/dist/a11y/contrast.d.ts.map +1 -0
- package/dist/a11y/contrast.js +93 -0
- package/dist/a11y/contrast.js.map +1 -0
- package/dist/a11y/focus.d.ts +124 -0
- package/dist/a11y/focus.d.ts.map +1 -0
- package/dist/a11y/focus.js +282 -0
- package/dist/a11y/focus.js.map +1 -0
- package/dist/a11y/index.d.ts +22 -0
- package/dist/a11y/index.d.ts.map +1 -0
- package/dist/a11y/index.js +18 -0
- package/dist/a11y/index.js.map +1 -0
- package/dist/a11y/keyboard.d.ts +145 -0
- package/dist/a11y/keyboard.d.ts.map +1 -0
- package/dist/a11y/keyboard.js +317 -0
- package/dist/a11y/keyboard.js.map +1 -0
- package/dist/adapters/default-image.d.ts +26 -0
- package/dist/adapters/default-image.d.ts.map +1 -0
- package/dist/adapters/default-image.js +37 -0
- package/dist/adapters/default-image.js.map +1 -0
- package/dist/adapters/index.d.ts +28 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +28 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/next-image.d.ts +39 -0
- package/dist/adapters/next-image.d.ts.map +1 -0
- package/dist/adapters/next-image.js +124 -0
- package/dist/adapters/next-image.js.map +1 -0
- package/dist/adapters/types.d.ts +62 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +11 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/analytics/index.d.ts +15 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/index.js +15 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/analytics/tracker.d.ts +66 -0
- package/dist/analytics/tracker.d.ts.map +1 -0
- package/dist/analytics/tracker.js +251 -0
- package/dist/analytics/tracker.js.map +1 -0
- package/dist/analytics/types.d.ts +131 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/analytics/types.js +11 -0
- package/dist/analytics/types.js.map +1 -0
- package/dist/auth/index.d.ts +16 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +14 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/provider.d.ts +133 -0
- package/dist/auth/provider.d.ts.map +1 -0
- package/dist/auth/provider.js +473 -0
- package/dist/auth/provider.js.map +1 -0
- package/dist/auth/types.d.ts +118 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +9 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/binding/collection.d.ts +191 -0
- package/dist/binding/collection.d.ts.map +1 -0
- package/dist/binding/collection.js +358 -0
- package/dist/binding/collection.js.map +1 -0
- package/dist/binding/datasource.d.ts +320 -0
- package/dist/binding/datasource.d.ts.map +1 -0
- package/dist/binding/datasource.js +556 -0
- package/dist/binding/datasource.js.map +1 -0
- package/dist/binding/engine.d.ts +66 -0
- package/dist/binding/engine.d.ts.map +1 -0
- package/dist/binding/engine.js +268 -0
- package/dist/binding/engine.js.map +1 -0
- package/dist/binding/expression.d.ts +151 -0
- package/dist/binding/expression.d.ts.map +1 -0
- package/dist/binding/expression.js +787 -0
- package/dist/binding/expression.js.map +1 -0
- package/dist/binding/index.d.ts +20 -0
- package/dist/binding/index.d.ts.map +1 -0
- package/dist/binding/index.js +14 -0
- package/dist/binding/index.js.map +1 -0
- package/dist/binding/two-way.d.ts +150 -0
- package/dist/binding/two-way.d.ts.map +1 -0
- package/dist/binding/two-way.js +462 -0
- package/dist/binding/two-way.js.map +1 -0
- package/dist/binding/types.d.ts +120 -0
- package/dist/binding/types.d.ts.map +1 -0
- package/dist/binding/types.js +9 -0
- package/dist/binding/types.js.map +1 -0
- package/dist/blocks/__test-fixtures__/block-fixtures.d.ts +40 -0
- package/dist/blocks/__test-fixtures__/block-fixtures.d.ts.map +1 -0
- package/dist/blocks/__test-fixtures__/block-fixtures.js +506 -0
- package/dist/blocks/__test-fixtures__/block-fixtures.js.map +1 -0
- package/dist/blocks/component-types.d.ts +82 -0
- package/dist/blocks/component-types.d.ts.map +1 -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 +169 -0
- package/dist/blocks/composition-rules.d.ts.map +1 -0
- package/dist/blocks/composition-rules.js +391 -0
- package/dist/blocks/composition-rules.js.map +1 -0
- package/dist/blocks/hooks.d.ts +32 -0
- package/dist/blocks/hooks.d.ts.map +1 -0
- package/dist/blocks/hooks.js +42 -0
- package/dist/blocks/hooks.js.map +1 -0
- package/dist/blocks/index.d.ts +16 -0
- package/dist/blocks/index.d.ts.map +1 -0
- package/dist/blocks/index.js +24 -0
- package/dist/blocks/index.js.map +1 -0
- package/dist/blocks/registry.d.ts +85 -0
- package/dist/blocks/registry.d.ts.map +1 -0
- package/dist/blocks/registry.js +136 -0
- package/dist/blocks/registry.js.map +1 -0
- package/dist/blocks/rule-engine.d.ts +132 -0
- package/dist/blocks/rule-engine.d.ts.map +1 -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 +14 -0
- package/dist/blocks/surface-event-context.d.ts.map +1 -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 +14 -0
- package/dist/blocks/surface-id-context.d.ts.map +1 -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 +42 -0
- package/dist/bus.d.ts.map +1 -0
- package/dist/bus.js +158 -0
- package/dist/bus.js.map +1 -0
- package/dist/cache/index.d.ts +10 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +10 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/memory.d.ts +33 -0
- package/dist/cache/memory.d.ts.map +1 -0
- package/dist/cache/memory.js +138 -0
- package/dist/cache/memory.js.map +1 -0
- package/dist/cache/types.d.ts +63 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +9 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/capability/index.d.ts +73 -0
- package/dist/capability/index.d.ts.map +1 -0
- package/dist/capability/index.js +87 -0
- package/dist/capability/index.js.map +1 -0
- package/dist/capability/kernel-capabilities.d.ts +32 -0
- package/dist/capability/kernel-capabilities.d.ts.map +1 -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 +72 -0
- package/dist/capability/platform-capabilities.d.ts.map +1 -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 +71 -0
- package/dist/capability/type-mapping.d.ts.map +1 -0
- package/dist/capability/type-mapping.js +107 -0
- package/dist/capability/type-mapping.js.map +1 -0
- package/dist/capability/validation.d.ts +47 -0
- package/dist/capability/validation.d.ts.map +1 -0
- package/dist/capability/validation.js +54 -0
- package/dist/capability/validation.js.map +1 -0
- package/dist/connectors/declared-connector.d.ts +49 -0
- package/dist/connectors/declared-connector.d.ts.map +1 -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 +51 -0
- package/dist/connectors/engine-registry.d.ts.map +1 -0
- package/dist/connectors/engine-registry.js +135 -0
- package/dist/connectors/engine-registry.js.map +1 -0
- package/dist/connectors/index.d.ts +15 -0
- package/dist/connectors/index.d.ts.map +1 -0
- package/dist/connectors/index.js +14 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/module-loader.d.ts +71 -0
- package/dist/connectors/module-loader.d.ts.map +1 -0
- package/dist/connectors/module-loader.js +143 -0
- package/dist/connectors/module-loader.js.map +1 -0
- package/dist/connectors/types.d.ts +175 -0
- package/dist/connectors/types.d.ts.map +1 -0
- package/dist/connectors/types.js +23 -0
- package/dist/connectors/types.js.map +1 -0
- package/dist/context.d.ts +28 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +38 -0
- package/dist/context.js.map +1 -0
- package/dist/data/DataCapabilityProvider.d.ts +40 -0
- package/dist/data/DataCapabilityProvider.d.ts.map +1 -0
- package/dist/data/DataCapabilityProvider.js +58 -0
- package/dist/data/DataCapabilityProvider.js.map +1 -0
- package/dist/data/index.d.ts +18 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +18 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/manager.d.ts +41 -0
- package/dist/data/manager.d.ts.map +1 -0
- package/dist/data/manager.js +442 -0
- package/dist/data/manager.js.map +1 -0
- package/dist/data/types.d.ts +219 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/data/types.js +2 -0
- package/dist/data/types.js.map +1 -0
- package/dist/entity/aggregate.d.ts +9 -0
- package/dist/entity/aggregate.d.ts.map +1 -0
- package/dist/entity/aggregate.js +24 -0
- package/dist/entity/aggregate.js.map +1 -0
- package/dist/entity/apply-query.d.ts +59 -0
- package/dist/entity/apply-query.d.ts.map +1 -0
- package/dist/entity/apply-query.js +264 -0
- package/dist/entity/apply-query.js.map +1 -0
- package/dist/entity/behavior.d.ts +95 -0
- package/dist/entity/behavior.d.ts.map +1 -0
- package/dist/entity/behavior.js +78 -0
- package/dist/entity/behavior.js.map +1 -0
- package/dist/entity/connector.d.ts +142 -0
- package/dist/entity/connector.d.ts.map +1 -0
- package/dist/entity/connector.js +89 -0
- package/dist/entity/connector.js.map +1 -0
- package/dist/entity/connectors/index.d.ts +13 -0
- package/dist/entity/connectors/index.d.ts.map +1 -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 +52 -0
- package/dist/entity/connectors/static.d.ts.map +1 -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 +51 -0
- package/dist/entity/entity-datasource.d.ts.map +1 -0
- package/dist/entity/entity-datasource.js +33 -0
- package/dist/entity/entity-datasource.js.map +1 -0
- package/dist/entity/facade.d.ts +71 -0
- package/dist/entity/facade.d.ts.map +1 -0
- package/dist/entity/facade.js +70 -0
- package/dist/entity/facade.js.map +1 -0
- package/dist/entity/http-connector.d.ts +124 -0
- package/dist/entity/http-connector.d.ts.map +1 -0
- package/dist/entity/http-connector.js +295 -0
- package/dist/entity/http-connector.js.map +1 -0
- package/dist/entity/index.d.ts +39 -0
- package/dist/entity/index.d.ts.map +1 -0
- package/dist/entity/index.js +31 -0
- package/dist/entity/index.js.map +1 -0
- package/dist/entity/postgres-connector.d.ts +65 -0
- package/dist/entity/postgres-connector.d.ts.map +1 -0
- package/dist/entity/postgres-connector.js +239 -0
- package/dist/entity/postgres-connector.js.map +1 -0
- package/dist/entity/registry.d.ts +60 -0
- package/dist/entity/registry.d.ts.map +1 -0
- package/dist/entity/registry.js +90 -0
- package/dist/entity/registry.js.map +1 -0
- package/dist/entity/store.d.ts +67 -0
- package/dist/entity/store.d.ts.map +1 -0
- package/dist/entity/store.js +349 -0
- package/dist/entity/store.js.map +1 -0
- package/dist/entity/types.d.ts +181 -0
- package/dist/entity/types.d.ts.map +1 -0
- package/dist/entity/types.js +10 -0
- package/dist/entity/types.js.map +1 -0
- package/dist/execution-context.d.ts +89 -0
- package/dist/execution-context.d.ts.map +1 -0
- package/dist/execution-context.js +146 -0
- package/dist/execution-context.js.map +1 -0
- package/dist/executor.d.ts +28 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +33 -0
- package/dist/executor.js.map +1 -0
- package/dist/forms/form-entity-bridge.d.ts +95 -0
- package/dist/forms/form-entity-bridge.d.ts.map +1 -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 +35 -0
- package/dist/forms/form-write-target.d.ts.map +1 -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 +23 -0
- package/dist/forms/index.d.ts.map +1 -0
- package/dist/forms/index.js +25 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/forms/named-control-values.d.ts +28 -0
- package/dist/forms/named-control-values.d.ts.map +1 -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 +56 -0
- package/dist/forms/processor.d.ts.map +1 -0
- package/dist/forms/processor.js +154 -0
- package/dist/forms/processor.js.map +1 -0
- package/dist/forms/types.d.ts +175 -0
- package/dist/forms/types.d.ts.map +1 -0
- package/dist/forms/types.js +33 -0
- package/dist/forms/types.js.map +1 -0
- package/dist/forms/validator.d.ts +62 -0
- package/dist/forms/validator.d.ts.map +1 -0
- package/dist/forms/validator.js +218 -0
- package/dist/forms/validator.js.map +1 -0
- package/dist/graph/index.d.ts +11 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +9 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/resolver.d.ts +67 -0
- package/dist/graph/resolver.d.ts.map +1 -0
- package/dist/graph/resolver.js +243 -0
- package/dist/graph/resolver.js.map +1 -0
- package/dist/graph/types.d.ts +126 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +9 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/hooks/useInstalledAtoms.d.ts +97 -0
- package/dist/hooks/useInstalledAtoms.d.ts.map +1 -0
- package/dist/hooks/useInstalledAtoms.js +92 -0
- package/dist/hooks/useInstalledAtoms.js.map +1 -0
- package/dist/i18n/index.d.ts +16 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +14 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/translator.d.ts +77 -0
- package/dist/i18n/translator.d.ts.map +1 -0
- package/dist/i18n/translator.js +357 -0
- package/dist/i18n/translator.js.map +1 -0
- package/dist/i18n/types.d.ts +94 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +9 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel.d.ts +48 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +258 -0
- package/dist/kernel.js.map +1 -0
- package/dist/perf/budget.d.ts +93 -0
- package/dist/perf/budget.d.ts.map +1 -0
- package/dist/perf/budget.js +122 -0
- package/dist/perf/budget.js.map +1 -0
- package/dist/perf/index.d.ts +17 -0
- package/dist/perf/index.d.ts.map +1 -0
- package/dist/perf/index.js +13 -0
- package/dist/perf/index.js.map +1 -0
- package/dist/perf/render-timer.d.ts +91 -0
- package/dist/perf/render-timer.d.ts.map +1 -0
- package/dist/perf/render-timer.js +136 -0
- package/dist/perf/render-timer.js.map +1 -0
- package/dist/perf/reporter.d.ts +127 -0
- package/dist/perf/reporter.d.ts.map +1 -0
- package/dist/perf/reporter.js +241 -0
- package/dist/perf/reporter.js.map +1 -0
- package/dist/perf/web-vitals.d.ts +112 -0
- package/dist/perf/web-vitals.d.ts.map +1 -0
- package/dist/perf/web-vitals.js +268 -0
- package/dist/perf/web-vitals.js.map +1 -0
- package/dist/registry.d.ts +64 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +126 -0
- package/dist/registry.js.map +1 -0
- package/dist/render/BehaviorSurfaceProvider.d.ts +66 -0
- package/dist/render/BehaviorSurfaceProvider.d.ts.map +1 -0
- package/dist/render/BehaviorSurfaceProvider.js +185 -0
- package/dist/render/BehaviorSurfaceProvider.js.map +1 -0
- package/dist/render/BlockErrorBoundary.d.ts +27 -0
- package/dist/render/BlockErrorBoundary.d.ts.map +1 -0
- package/dist/render/BlockErrorBoundary.js +41 -0
- package/dist/render/BlockErrorBoundary.js.map +1 -0
- package/dist/render/DataBoundSurfaceRenderer.d.ts +79 -0
- package/dist/render/DataBoundSurfaceRenderer.d.ts.map +1 -0
- package/dist/render/DataBoundSurfaceRenderer.js +998 -0
- package/dist/render/DataBoundSurfaceRenderer.js.map +1 -0
- package/dist/render/DynamicBlockWrapper.d.ts +24 -0
- package/dist/render/DynamicBlockWrapper.d.ts.map +1 -0
- package/dist/render/DynamicBlockWrapper.js +117 -0
- package/dist/render/DynamicBlockWrapper.js.map +1 -0
- package/dist/render/appearance-resolver.d.ts +51 -0
- package/dist/render/appearance-resolver.d.ts.map +1 -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 +70 -0
- package/dist/render/capability-gate.d.ts.map +1 -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 +42 -0
- package/dist/render/conditional-states-css.d.ts.map +1 -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 +32 -0
- package/dist/render/connections-context.d.ts.map +1 -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 +37 -0
- package/dist/render/dark-theme-css.d.ts.map +1 -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 +192 -0
- package/dist/render/data-binding-types.d.ts.map +1 -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 +31 -0
- package/dist/render/data-cache.d.ts.map +1 -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 +18 -0
- package/dist/render/derive-dark-theme.d.ts.map +1 -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 +78 -0
- package/dist/render/dynamic-artifact-context.d.ts.map +1 -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 +81 -0
- package/dist/render/dynamic-block-loader.d.ts.map +1 -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 +19 -0
- package/dist/render/endpoint.d.ts.map +1 -0
- package/dist/render/endpoint.js +109 -0
- package/dist/render/endpoint.js.map +1 -0
- package/dist/render/entity-facade-context.d.ts +35 -0
- package/dist/render/entity-facade-context.d.ts.map +1 -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 +42 -0
- package/dist/render/group-states-css.d.ts.map +1 -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 +9 -0
- package/dist/render/hooks/useSurfaceEvents.d.ts.map +1 -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 +9 -0
- package/dist/render/hooks/useSurfaceId.d.ts.map +1 -0
- package/dist/render/hooks/useSurfaceId.js +9 -0
- package/dist/render/hooks/useSurfaceId.js.map +1 -0
- package/dist/render/index.d.ts +49 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +61 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/interaction-rule-engine.d.ts +13 -0
- package/dist/render/interaction-rule-engine.d.ts.map +1 -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 +32 -0
- package/dist/render/keyframes-css.d.ts.map +1 -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 +4 -0
- package/dist/render/query-state.d.ts.map +1 -0
- package/dist/render/query-state.js +70 -0
- package/dist/render/query-state.js.map +1 -0
- package/dist/render/renderer.d.ts +45 -0
- package/dist/render/renderer.d.ts.map +1 -0
- package/dist/render/renderer.js +937 -0
- package/dist/render/renderer.js.map +1 -0
- package/dist/render/resolve-expressions.d.ts +38 -0
- package/dist/render/resolve-expressions.d.ts.map +1 -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 +43 -0
- package/dist/render/responsive-style-css.d.ts.map +1 -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 +50 -0
- package/dist/render/responsive.d.ts.map +1 -0
- package/dist/render/responsive.js +99 -0
- package/dist/render/responsive.js.map +1 -0
- package/dist/render/scoped-css.d.ts +72 -0
- package/dist/render/scoped-css.d.ts.map +1 -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 +70 -0
- package/dist/render/shell-selection.d.ts.map +1 -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 +33 -0
- package/dist/render/states-css.d.ts.map +1 -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 +19 -0
- package/dist/render/surface-data-context.d.ts.map +1 -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 +20 -0
- package/dist/render/surface-event-bus.d.ts.map +1 -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 +12 -0
- package/dist/render/surface-event-context.d.ts.map +1 -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 +9 -0
- package/dist/render/surface-id-context.d.ts.map +1 -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 +15 -0
- package/dist/render/theme-utils.d.ts.map +1 -0
- package/dist/render/theme-utils.js +15 -0
- package/dist/render/theme-utils.js.map +1 -0
- package/dist/render/types.d.ts +283 -0
- package/dist/render/types.d.ts.map +1 -0
- package/dist/render/types.js +9 -0
- package/dist/render/types.js.map +1 -0
- package/dist/render/visibility-css.d.ts +43 -0
- package/dist/render/visibility-css.d.ts.map +1 -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 +35 -0
- package/dist/render/width-scope.d.ts.map +1 -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 +34 -0
- package/dist/render/write-only-sources.d.ts.map +1 -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 +15 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +15 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/routing/router.d.ts +43 -0
- package/dist/routing/router.d.ts.map +1 -0
- package/dist/routing/router.js +218 -0
- package/dist/routing/router.js.map +1 -0
- package/dist/routing/types.d.ts +125 -0
- package/dist/routing/types.d.ts.map +1 -0
- package/dist/routing/types.js +9 -0
- package/dist/routing/types.js.map +1 -0
- package/dist/seo/generator.d.ts +40 -0
- package/dist/seo/generator.d.ts.map +1 -0
- package/dist/seo/generator.js +236 -0
- package/dist/seo/generator.js.map +1 -0
- package/dist/seo/index.d.ts +15 -0
- package/dist/seo/index.d.ts.map +1 -0
- package/dist/seo/index.js +15 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/seo/types.d.ts +182 -0
- package/dist/seo/types.d.ts.map +1 -0
- package/dist/seo/types.js +9 -0
- package/dist/seo/types.js.map +1 -0
- package/dist/state/index.d.ts +16 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +16 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/manager.d.ts +34 -0
- package/dist/state/manager.d.ts.map +1 -0
- package/dist/state/manager.js +81 -0
- package/dist/state/manager.js.map +1 -0
- package/dist/state/store.d.ts +41 -0
- package/dist/state/store.d.ts.map +1 -0
- package/dist/state/store.js +145 -0
- package/dist/state/store.js.map +1 -0
- package/dist/state/types.d.ts +108 -0
- package/dist/state/types.d.ts.map +1 -0
- package/dist/state/types.js +9 -0
- package/dist/state/types.js.map +1 -0
- package/dist/surface/expression.d.ts +66 -0
- package/dist/surface/expression.d.ts.map +1 -0
- package/dist/surface/expression.js +179 -0
- package/dist/surface/expression.js.map +1 -0
- package/dist/surface/index.d.ts +33 -0
- package/dist/surface/index.d.ts.map +1 -0
- package/dist/surface/index.js +25 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/surface/interpreter.d.ts +97 -0
- package/dist/surface/interpreter.d.ts.map +1 -0
- package/dist/surface/interpreter.js +231 -0
- package/dist/surface/interpreter.js.map +1 -0
- package/dist/surface/layout.d.ts +55 -0
- package/dist/surface/layout.d.ts.map +1 -0
- package/dist/surface/layout.js +288 -0
- package/dist/surface/layout.js.map +1 -0
- package/dist/surface/output.d.ts +91 -0
- package/dist/surface/output.d.ts.map +1 -0
- package/dist/surface/output.js +104 -0
- package/dist/surface/output.js.map +1 -0
- package/dist/surface/primitives.d.ts +181 -0
- package/dist/surface/primitives.d.ts.map +1 -0
- package/dist/surface/primitives.js +339 -0
- package/dist/surface/primitives.js.map +1 -0
- package/dist/surface/renderer.d.ts +97 -0
- package/dist/surface/renderer.d.ts.map +1 -0
- package/dist/surface/renderer.js +190 -0
- package/dist/surface/renderer.js.map +1 -0
- package/dist/surface/schemas.d.ts +182 -0
- package/dist/surface/schemas.d.ts.map +1 -0
- package/dist/surface/schemas.js +142 -0
- package/dist/surface/schemas.js.map +1 -0
- package/dist/surface/transforms.d.ts +145 -0
- package/dist/surface/transforms.d.ts.map +1 -0
- package/dist/surface/transforms.js +120 -0
- package/dist/surface/transforms.js.map +1 -0
- package/dist/surface/types.d.ts +161 -0
- package/dist/surface/types.d.ts.map +1 -0
- package/dist/surface/types.js +51 -0
- package/dist/surface/types.js.map +1 -0
- package/dist/surface/validation.d.ts +124 -0
- package/dist/surface/validation.d.ts.map +1 -0
- package/dist/surface/validation.js +147 -0
- package/dist/surface/validation.js.map +1 -0
- package/dist/validator.d.ts +56 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +100 -0
- package/dist/validator.js.map +1 -0
- package/package.json +105 -3
|
@@ -0,0 +1,998 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Data-Bound Surface Renderer
|
|
4
|
+
*
|
|
5
|
+
* Wrapper component that resolves dataSources, state, and {{expression}} props
|
|
6
|
+
* before passing concrete values to the existing SurfaceRenderer.
|
|
7
|
+
*
|
|
8
|
+
* Architecture (post-refactor):
|
|
9
|
+
* Surface JSON (dataSources + state + {{expr}} in props)
|
|
10
|
+
* -> DataBoundSurfaceRenderer
|
|
11
|
+
* -> DataCapabilityProvider (creates DataService instance)
|
|
12
|
+
* -> DataBoundInner
|
|
13
|
+
* -> useSurfaceData: maps SurfaceDataSourceDefinition → DataSourceDefinition,
|
|
14
|
+
* registers with DataService, fetches via DataService, manages React state
|
|
15
|
+
* -> SurfaceRenderer (unchanged)
|
|
16
|
+
* -> Block components (unchanged)
|
|
17
|
+
*
|
|
18
|
+
* Backward compatible: if no dataSources, passes through with zero overhead.
|
|
19
|
+
*
|
|
20
|
+
* @module @pwfabric/runtime/render/DataBoundSurfaceRenderer
|
|
21
|
+
*/
|
|
22
|
+
import * as React from 'react';
|
|
23
|
+
import { SurfaceRenderer } from './renderer.js';
|
|
24
|
+
import { resolveBlockExpressions } from './resolve-expressions.js';
|
|
25
|
+
import { endpointMissingPathValue, resolveEndpoint } from './endpoint.js';
|
|
26
|
+
import { resolveQueryState } from './query-state.js';
|
|
27
|
+
import { dataSourceCacheKey } from './data-cache.js';
|
|
28
|
+
import { DataCapabilityProvider, useDataService } from '../data/DataCapabilityProvider.js';
|
|
29
|
+
import { createEntityDataSource } from '../entity/entity-datasource.js';
|
|
30
|
+
import { aggregateThrough } from '../entity/aggregate.js';
|
|
31
|
+
import { SurfaceDataContext } from './surface-data-context.js';
|
|
32
|
+
import { PLATFORM_PERSISTENCE_CAPABILITY_ID } from '@pwfabric/contracts';
|
|
33
|
+
import { isCapability } from '../capability/type-mapping.js';
|
|
34
|
+
import { writeOnlySourceIds } from './write-only-sources.js';
|
|
35
|
+
// ============================================================
|
|
36
|
+
// extract dataSources + initialState from persistence config
|
|
37
|
+
// ============================================================
|
|
38
|
+
/**
|
|
39
|
+
* consolidated these two seeds under the platform/persistence
|
|
40
|
+
* capability config. DataBoundSurfaceRenderer is the sole consumer.
|
|
41
|
+
*/
|
|
42
|
+
function extractPersistenceSeeds(surface) {
|
|
43
|
+
const binding = surface.capabilities?.find((c) => isCapability(c, PLATFORM_PERSISTENCE_CAPABILITY_ID));
|
|
44
|
+
const cfg = binding?.config;
|
|
45
|
+
return {
|
|
46
|
+
dataSources: cfg?.dataSources,
|
|
47
|
+
initialState: cfg?.initialState,
|
|
48
|
+
// Declared behaviors (shipped by a pattern / authored in JSON). The
|
|
49
|
+
// renderer merges these with host-provided behaviors (the host's store) so a pack
|
|
50
|
+
// can carry its own controls without the host knowing about them.
|
|
51
|
+
behaviors: cfg?.behaviors,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A source that could not be served but declares a `fallback`: the caller
|
|
56
|
+
* binds `data` AND records `message` under `__errors.<id>`, so the surface
|
|
57
|
+
* renders honestly (sample data + error flag) instead of empty or crashed.
|
|
58
|
+
*/
|
|
59
|
+
class SourceFallback extends Error {
|
|
60
|
+
data;
|
|
61
|
+
constructor(message, data) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.data = data;
|
|
64
|
+
this.name = 'SourceFallback';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A surface whose ONLY dynamic behaviour is writing (platform/entities
|
|
69
|
+
* formBindings, no persistence dataSources) still needs the full data-bound
|
|
70
|
+
* path: the form→entity bridge dispatches statusKeys through setState and the
|
|
71
|
+
* success/error UI hangs off `visibleWhen: "{{state.x}}"` — both live in
|
|
72
|
+
* DataBoundInner's state machinery. Before this check, such a surface fell
|
|
73
|
+
* into the zero-overhead passthrough where visibleWhen was never evaluated
|
|
74
|
+
* and, worse, submits were silently lost (proven live against a real
|
|
75
|
+
* SharePoint list: two submits on a formBindings-only page never reached the
|
|
76
|
+
* connector; adding any read source made the identical form work).
|
|
77
|
+
*/
|
|
78
|
+
function hasEntityFormBindings(surface) {
|
|
79
|
+
const entityCap = surface.capabilities?.find((c) => isCapability(c, 'platform/entities'));
|
|
80
|
+
const formBindings = entityCap?.config
|
|
81
|
+
?.formBindings;
|
|
82
|
+
return Array.isArray(formBindings) && formBindings.length > 0;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The actions a block's `action` prop names, in order.
|
|
86
|
+
*
|
|
87
|
+
* One action or a list of them (`[setState step, setState stepError '']`) —
|
|
88
|
+
* a wizard's "Next" has to move the step AND clear the previous step's error
|
|
89
|
+
* in one click. Anything that is not a surface action (a form's `action` is
|
|
90
|
+
* its submit URL string) names none.
|
|
91
|
+
*/
|
|
92
|
+
export function blockActionsOf(value) {
|
|
93
|
+
if (Array.isArray(value))
|
|
94
|
+
return value.filter(isSurfaceAction);
|
|
95
|
+
return isSurfaceAction(value) ? [value] : [];
|
|
96
|
+
}
|
|
97
|
+
function blockTreeHasAction(blocks) {
|
|
98
|
+
if (!blocks)
|
|
99
|
+
return false;
|
|
100
|
+
for (const block of blocks) {
|
|
101
|
+
if (blockActionsOf(block.props?.action).length > 0)
|
|
102
|
+
return true;
|
|
103
|
+
if (blockTreeHasAction(block.children))
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Does any block carry a condition or a repeat the renderer has to evaluate?
|
|
110
|
+
*
|
|
111
|
+
* `visibleWhen`/`if` and `repeat` are resolved ONLY on the data-bound path.
|
|
112
|
+
* A static page — no source, no state, no action — takes this path too: on
|
|
113
|
+
* the passthrough a condition would never be read, so the shell's empty
|
|
114
|
+
* brand logo would draw as "⊟ <name> logo", and a thank-you gated on
|
|
115
|
+
* `{{state.submitted}}` would show before anything was submitted.
|
|
116
|
+
* An empty condition string is no condition, as in resolveBlock.
|
|
117
|
+
*/
|
|
118
|
+
function blockTreeHasCondition(blocks) {
|
|
119
|
+
if (!blocks)
|
|
120
|
+
return false;
|
|
121
|
+
for (const block of blocks) {
|
|
122
|
+
const b = block;
|
|
123
|
+
for (const expr of [b.visibleWhen, b.if, b.repeat]) {
|
|
124
|
+
if (typeof expr === 'string' && expr.trim())
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (blockTreeHasCondition(block.children))
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Does this surface need the data-bound path — state, expression resolution,
|
|
134
|
+
* the action dispatcher — or can it go straight to SurfaceRenderer?
|
|
135
|
+
*
|
|
136
|
+
* ONE ANSWER FOR EVERY HOST. The renderer's own passthrough check and embed's
|
|
137
|
+
* `needsDataBoundRenderer` used to disagree: embed counted `initialState`, the
|
|
138
|
+
* renderer did not. So a wizard page carrying nothing but `initialState` and
|
|
139
|
+
* buttons with `action` props reached the renderer, fell into the passthrough,
|
|
140
|
+
* and there `visibleWhen` was never evaluated, `repeat` never expanded and no
|
|
141
|
+
* `action` was ever dispatched — the buttons were dead. A block `action` alone
|
|
142
|
+
* needs the path too: it is dispatched nowhere else. So does a block
|
|
143
|
+
* `visibleWhen`/`if`/`repeat` (see blockTreeHasCondition).
|
|
144
|
+
*
|
|
145
|
+
* Host-provided behaviors are a prop, not part of the surface, and are
|
|
146
|
+
* checked by the renderer on top of this.
|
|
147
|
+
*/
|
|
148
|
+
export function surfaceNeedsDataBinding(surface) {
|
|
149
|
+
const { dataSources, initialState, behaviors } = extractPersistenceSeeds(surface);
|
|
150
|
+
return (Boolean(dataSources && dataSources.length > 0) ||
|
|
151
|
+
initialState !== undefined ||
|
|
152
|
+
Boolean(behaviors && behaviors.length > 0) ||
|
|
153
|
+
hasEntityFormBindings(surface) ||
|
|
154
|
+
blockTreeHasAction(surface.blocks) ||
|
|
155
|
+
blockTreeHasCondition(surface.blocks));
|
|
156
|
+
}
|
|
157
|
+
// ============================================================
|
|
158
|
+
// SURFACE → RUNTIME SOURCE MAPPING
|
|
159
|
+
// ============================================================
|
|
160
|
+
/**
|
|
161
|
+
* Maps a JSON-safe SurfaceDataSourceDefinition to a runtime DataSourceDefinition.
|
|
162
|
+
* Returns null for 'state' kind (handled locally via React state, not DataService).
|
|
163
|
+
*/
|
|
164
|
+
function mapSurfaceSourceToDefinition(source, currentState) {
|
|
165
|
+
switch (source.kind) {
|
|
166
|
+
case 'static':
|
|
167
|
+
return {
|
|
168
|
+
id: source.id,
|
|
169
|
+
kind: 'static',
|
|
170
|
+
staticData: source.staticData,
|
|
171
|
+
};
|
|
172
|
+
case 'state':
|
|
173
|
+
// State sources read from React state, not managed by DataService
|
|
174
|
+
return null;
|
|
175
|
+
case 'rest': {
|
|
176
|
+
if (!source.endpoint)
|
|
177
|
+
return null;
|
|
178
|
+
// `{{state.x}}` placeholders resolve against current state, each value
|
|
179
|
+
// URL-encoded in a mixed endpoint — see resolveEndpoint for why.
|
|
180
|
+
const endpoint = resolveEndpoint(source.endpoint, currentState);
|
|
181
|
+
return {
|
|
182
|
+
id: source.id,
|
|
183
|
+
kind: 'rest',
|
|
184
|
+
endpoint,
|
|
185
|
+
method: source.method ?? 'GET',
|
|
186
|
+
...(source.headers ? { headers: source.headers } : {}),
|
|
187
|
+
// carry the identity the source names. This branch
|
|
188
|
+
// copied endpoint/method/headers and stopped, so a rest source could
|
|
189
|
+
// declare an identity the fetch would never see.
|
|
190
|
+
...(source.authProviderRef ? { authProviderRef: source.authProviderRef } : {}),
|
|
191
|
+
// Same omission class as the line above: the source says where its
|
|
192
|
+
// list is and the definition has to carry it, or `fetchRest` hands the
|
|
193
|
+
// binding context a whole JSON body.
|
|
194
|
+
...(source.returns !== undefined ? { returns: source.returns } : {}),
|
|
195
|
+
// The entity branch resolves `{{state.x}}` inside the query and hands
|
|
196
|
+
// it to the facade; a rest source declared the same query and lost it
|
|
197
|
+
// here. Resolved the same way, applied by the data service after the
|
|
198
|
+
// fetch.
|
|
199
|
+
...(source.query
|
|
200
|
+
? { query: resolveQueryState(source.query, currentState) }
|
|
201
|
+
: {}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
default:
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** JSON of a source DEFINITION, computed once per definition object. */
|
|
209
|
+
const definitionJsonCache = new WeakMap();
|
|
210
|
+
let unserialisable = 0;
|
|
211
|
+
function safeJson(value) {
|
|
212
|
+
try {
|
|
213
|
+
return JSON.stringify(value) ?? 'undefined';
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// A value that cannot be serialised can never be proven unchanged.
|
|
217
|
+
return `#unserialisable-${++unserialisable}`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function definitionJson(source) {
|
|
221
|
+
let json = definitionJsonCache.get(source);
|
|
222
|
+
if (json === undefined) {
|
|
223
|
+
json = safeJson(source);
|
|
224
|
+
definitionJsonCache.set(source, json);
|
|
225
|
+
}
|
|
226
|
+
return json;
|
|
227
|
+
}
|
|
228
|
+
/** A REST source whose required path value is empty is not fetched. */
|
|
229
|
+
function isIdleSource(source, state) {
|
|
230
|
+
return source.kind === 'rest' && Boolean(source.endpoint) && endpointMissingPathValue(source.endpoint, state);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* EVERYTHING THAT DECIDES WHAT A SOURCE FETCHES, AS ONE STRING.
|
|
234
|
+
*
|
|
235
|
+
* The definition itself plus whatever it reads from state, RESOLVED: the REST
|
|
236
|
+
* address, the query with its `{{state.*}}` placeholders filled in, a state
|
|
237
|
+
* source's value. A source is refetched when — and only when — this changes.
|
|
238
|
+
*
|
|
239
|
+
* Refetching every source on every `setState` would make a search box writing
|
|
240
|
+
* `state.searchDraft` on each keystroke refetch the product list AND the
|
|
241
|
+
* cart, flashing both loading states, and a menu toggle refetch the whole
|
|
242
|
+
* page. Dependency tracking on the resolved request is exact where
|
|
243
|
+
* key-name extraction is not: `{{state.a ? state.b : state.c}}` depends on `c`
|
|
244
|
+
* only while `a` is false, and the resolved value knows that.
|
|
245
|
+
*/
|
|
246
|
+
function requestKeyOf(source, state) {
|
|
247
|
+
const def = definitionJson(source);
|
|
248
|
+
switch (source.kind) {
|
|
249
|
+
case 'rest': {
|
|
250
|
+
if (!source.endpoint)
|
|
251
|
+
return def;
|
|
252
|
+
if (isIdleSource(source, state))
|
|
253
|
+
return `${def}|idle`;
|
|
254
|
+
const query = source.query ? safeJson(resolveQueryState(source.query, state)) : '';
|
|
255
|
+
return `${def}|${resolveEndpoint(source.endpoint, state)}|${query}`;
|
|
256
|
+
}
|
|
257
|
+
case 'entity': {
|
|
258
|
+
const query = source.query ? safeJson(resolveQueryState(source.query, state)) : '';
|
|
259
|
+
// The resolved aggregate too: a counter whose filter reads
|
|
260
|
+
// `{{state.status}}` must ask again when the status changes.
|
|
261
|
+
const aggregate = source.aggregate ? safeJson(resolveQueryState(source.aggregate, state)) : '';
|
|
262
|
+
return `${def}|${query}|${aggregate}`;
|
|
263
|
+
}
|
|
264
|
+
case 'state':
|
|
265
|
+
return `${def}|${safeJson(state[source.id])}`;
|
|
266
|
+
default:
|
|
267
|
+
return def;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function useSurfaceData(dataSources, initialState, interactionMode = 'live', entityFacade, connections, dataCache,
|
|
271
|
+
/** Sources only a form writes to (see write-only-sources): settled, never fetched. */
|
|
272
|
+
writeOnly) {
|
|
273
|
+
const dataService = useDataService();
|
|
274
|
+
const writeOnlyRef = React.useRef(writeOnly);
|
|
275
|
+
writeOnlyRef.current = writeOnly;
|
|
276
|
+
const dataCacheRef = React.useRef(dataCache);
|
|
277
|
+
dataCacheRef.current = dataCache;
|
|
278
|
+
// State store
|
|
279
|
+
const [surfaceState, setSurfaceState] = React.useState(() => ({
|
|
280
|
+
...initialState,
|
|
281
|
+
}));
|
|
282
|
+
// Source data store — seeded from the host's cache when it has yesterday's
|
|
283
|
+
// rows for a source, so the first frame is not an empty table. The seed is
|
|
284
|
+
// marked loading: the fetch that follows is what makes it today's.
|
|
285
|
+
const [sourceData, setSourceData] = React.useState(() => {
|
|
286
|
+
if (!dataCache || !dataSources)
|
|
287
|
+
return {};
|
|
288
|
+
const seeded = {};
|
|
289
|
+
for (const source of dataSources) {
|
|
290
|
+
const key = dataSourceCacheKey(source, initialState ?? {});
|
|
291
|
+
if (!key)
|
|
292
|
+
continue;
|
|
293
|
+
const hit = dataCache.get(key);
|
|
294
|
+
if (hit !== undefined) {
|
|
295
|
+
seeded[source.id] = { data: hit, loading: true, error: null, key: requestKeyOf(source, initialState ?? {}) };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return seeded;
|
|
299
|
+
});
|
|
300
|
+
// Track whether initial fetch is complete. A page whose every fetched
|
|
301
|
+
// source came out of the cache counts as loaded: the gate has nothing to
|
|
302
|
+
// wait for that the visitor has not already seen.
|
|
303
|
+
const [initialLoaded, setInitialLoaded] = React.useState(() => {
|
|
304
|
+
if (!dataCache || !dataSources)
|
|
305
|
+
return false;
|
|
306
|
+
const fetched = dataSources.filter((s) => dataSourceCacheKey(s, initialState ?? {}) !== undefined);
|
|
307
|
+
return fetched.length > 0 && fetched.every((s) => dataCache.get(dataSourceCacheKey(s, initialState ?? {})) !== undefined);
|
|
308
|
+
});
|
|
309
|
+
// Stable ref to current state for use in fetch callbacks
|
|
310
|
+
const stateRef = React.useRef(surfaceState);
|
|
311
|
+
stateRef.current = surfaceState;
|
|
312
|
+
// Stable ref to sources
|
|
313
|
+
const sourcesRef = React.useRef(dataSources);
|
|
314
|
+
sourcesRef.current = dataSources;
|
|
315
|
+
// Stable ref to the entity facade (kind:'entity' sources query through it)
|
|
316
|
+
const entityFacadeRef = React.useRef(entityFacade);
|
|
317
|
+
entityFacadeRef.current = entityFacade;
|
|
318
|
+
// Stable ref to the connections context (kind:'entity'
|
|
319
|
+
// sources with a `connector` route through the engine registry)
|
|
320
|
+
const connectionsRef = React.useRef(connections);
|
|
321
|
+
connectionsRef.current = connections;
|
|
322
|
+
/** The request each source was last fetched (or marked idle) for. */
|
|
323
|
+
const requestKeysRef = React.useRef({});
|
|
324
|
+
/** The in-flight fetch of each source — a newer one aborts it. */
|
|
325
|
+
const controllersRef = React.useRef(new Map());
|
|
326
|
+
// ---- setState action ----
|
|
327
|
+
// Writing the value a key already holds changes nothing, so it renders
|
|
328
|
+
// nothing and fetches nothing.
|
|
329
|
+
const setStateValue = React.useCallback((key, value) => {
|
|
330
|
+
setSurfaceState((prev) => {
|
|
331
|
+
if (prev[key] === value)
|
|
332
|
+
return prev;
|
|
333
|
+
return { ...prev, [key]: value };
|
|
334
|
+
});
|
|
335
|
+
}, []);
|
|
336
|
+
// ---- (Re-)register a source's definition for the current state ----
|
|
337
|
+
// Registered even when the source is idle, so a form or a behavior writing
|
|
338
|
+
// to it through the data service still finds it.
|
|
339
|
+
const registerSource = React.useCallback((source, state) => {
|
|
340
|
+
if (dataService.hasSource(source.id))
|
|
341
|
+
dataService.unregisterSource(source.id);
|
|
342
|
+
const definition = mapSurfaceSourceToDefinition(source, state);
|
|
343
|
+
if (definition)
|
|
344
|
+
dataService.registerSource(definition);
|
|
345
|
+
}, [dataService]);
|
|
346
|
+
// ---- Fetch a single source ----
|
|
347
|
+
const fetchSource = React.useCallback(async (source, state, signal) => {
|
|
348
|
+
const failed = (err, status = null) => ({
|
|
349
|
+
kind: 'error',
|
|
350
|
+
message: err instanceof Error ? err.message : String(err),
|
|
351
|
+
status,
|
|
352
|
+
// fallback declared → bind it; the error stays visible next to it
|
|
353
|
+
...(err instanceof SourceFallback ? { fallback: { data: err.data } } : {}),
|
|
354
|
+
});
|
|
355
|
+
// State sources read from React state directly
|
|
356
|
+
if (source.kind === 'state') {
|
|
357
|
+
return { kind: 'ok', data: state[source.id], status: null, meta: null };
|
|
358
|
+
}
|
|
359
|
+
// Entity sources list records via the platform/entities facade.
|
|
360
|
+
// Returns the items array so {{<id>}} binds directly to the list (e.g. a
|
|
361
|
+
// table's `data`). Refreshed by refreshAll after a form→entity create.
|
|
362
|
+
if (source.kind === 'entity') {
|
|
363
|
+
if (!source.entityType)
|
|
364
|
+
return { kind: 'ok', data: [], status: null, meta: null };
|
|
365
|
+
// A source that names a `connector` routes through the
|
|
366
|
+
// host-provided engine registry when that connection is registered
|
|
367
|
+
// there. Registered-but-failed surfaces the reason as an honest
|
|
368
|
+
// per-source error (`__errors.<id>`), never a crash; an unregistered
|
|
369
|
+
// connection (or no registry at all) falls through to today's
|
|
370
|
+
// default facade path.
|
|
371
|
+
// `{{state.x}}` inside the query resolves against CURRENT state, so
|
|
372
|
+
// a pagination block writing `state.page` or a segmented-control
|
|
373
|
+
// writing `state.severity` reaches `query.pagination` / `query.filter`.
|
|
374
|
+
// Before this, only REST endpoints saw state; connector queries went raw.
|
|
375
|
+
const query = source.query ? resolveQueryState(source.query, state) : undefined;
|
|
376
|
+
// An AGGREGATE source asks for one number, never the rows. Its
|
|
377
|
+
// filter resolves against state like `query` does, and the answer
|
|
378
|
+
// binds `{ fn, field?, value, count }` (`{{<id>.value}}`), exempt
|
|
379
|
+
// from the list wrap below. A counter on an intake collection is
|
|
380
|
+
// allowed what a list of it is not, and a public one no longer hands
|
|
381
|
+
// every row to a stranger so the page can count them.
|
|
382
|
+
const aggregate = source.aggregate
|
|
383
|
+
? resolveQueryState(source.aggregate, state)
|
|
384
|
+
: undefined;
|
|
385
|
+
const entityType = source.entityType;
|
|
386
|
+
// Any failure below binds `fallback` (when declared) and still reports
|
|
387
|
+
// the error — see SourceFallback.
|
|
388
|
+
const fail = (message) => {
|
|
389
|
+
if (source.fallback !== undefined)
|
|
390
|
+
throw new SourceFallback(message, source.fallback);
|
|
391
|
+
throw new Error(message);
|
|
392
|
+
};
|
|
393
|
+
try {
|
|
394
|
+
const engines = connectionsRef.current?.engines;
|
|
395
|
+
if (source.connector && engines) {
|
|
396
|
+
const status = engines.getStatus(source.connector);
|
|
397
|
+
if (status.state !== 'unregistered') {
|
|
398
|
+
const engine = await engines.resolve(source.connector);
|
|
399
|
+
if (engine) {
|
|
400
|
+
if (aggregate) {
|
|
401
|
+
const agg = await aggregateThrough(engine, entityType, aggregate);
|
|
402
|
+
if (agg.success)
|
|
403
|
+
return { kind: 'ok', data: agg.result, status: null, meta: null };
|
|
404
|
+
return fail(agg.error);
|
|
405
|
+
}
|
|
406
|
+
const res = await engine.query(entityType, (query ?? {}));
|
|
407
|
+
if (res.success)
|
|
408
|
+
return { kind: 'ok', data: res.result.items, status: null, meta: null };
|
|
409
|
+
return fail(res.error);
|
|
410
|
+
}
|
|
411
|
+
const failedStatus = engines.getStatus(source.connector);
|
|
412
|
+
return fail(failedStatus.state === 'failed'
|
|
413
|
+
? failedStatus.reason
|
|
414
|
+
: `Connector engine '${source.connector}' unavailable`);
|
|
415
|
+
}
|
|
416
|
+
// Declared connector, nothing registered: a pattern shipped without
|
|
417
|
+
// its connection. With a fallback this is the "sample data" path;
|
|
418
|
+
// without one, today's facade route stays.
|
|
419
|
+
if (source.fallback !== undefined) {
|
|
420
|
+
return fail(`Connector '${source.connector}' is not connected`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const facade = entityFacadeRef.current;
|
|
424
|
+
if (!facade) {
|
|
425
|
+
return source.fallback !== undefined
|
|
426
|
+
? fail('No entity facade available')
|
|
427
|
+
: {
|
|
428
|
+
kind: 'ok',
|
|
429
|
+
data: aggregate
|
|
430
|
+
? { fn: aggregate.fn, ...(aggregate.field ? { field: aggregate.field } : {}), value: null, count: 0 }
|
|
431
|
+
: [],
|
|
432
|
+
status: null,
|
|
433
|
+
meta: null,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
if (aggregate) {
|
|
437
|
+
if (typeof facade.resolveAggregate === 'function') {
|
|
438
|
+
// A count that cannot be served is an error, not a 0: bind
|
|
439
|
+
// `fallback` and report it, same as a failed list.
|
|
440
|
+
try {
|
|
441
|
+
const agg = await facade.resolveAggregate(entityType, aggregate);
|
|
442
|
+
return { kind: 'ok', data: agg, status: null, meta: null };
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// A facade from before `resolveAggregate` lists; the rows it
|
|
449
|
+
// returns are reduced here, same as a list-only engine.
|
|
450
|
+
const viaList = await aggregateThrough({ query: async (t, q) => ({ success: true, result: await facade.resolveBinding(t, q) }) }, entityType, aggregate);
|
|
451
|
+
if (!viaList.success)
|
|
452
|
+
return fail(viaList.error);
|
|
453
|
+
return { kind: 'ok', data: viaList.result, status: null, meta: null };
|
|
454
|
+
}
|
|
455
|
+
const eds = createEntityDataSource(facade, {
|
|
456
|
+
id: source.id,
|
|
457
|
+
entityType: source.entityType,
|
|
458
|
+
...(query ? { query } : {}),
|
|
459
|
+
});
|
|
460
|
+
try {
|
|
461
|
+
const result = await eds.fetch();
|
|
462
|
+
return { kind: 'ok', data: result.items, status: null, meta: null };
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
return failed(err);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// Everything else goes through the DataService, registered for THIS state.
|
|
473
|
+
registerSource(source, state);
|
|
474
|
+
const result = await dataService.fetchData(source.id, {
|
|
475
|
+
signal,
|
|
476
|
+
...(source.headers ? { headers: source.headers } : {}),
|
|
477
|
+
});
|
|
478
|
+
if (result.success) {
|
|
479
|
+
return {
|
|
480
|
+
kind: 'ok',
|
|
481
|
+
data: result.data,
|
|
482
|
+
status: result.metadata.status ?? null,
|
|
483
|
+
meta: result.metadata.envelope ?? null,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return failed(new Error(result.error.message), result.error.status ?? null);
|
|
487
|
+
}, [dataService, registerSource]);
|
|
488
|
+
// ---- Fetch a set of sources for the current state ----
|
|
489
|
+
const fetchSources = React.useCallback(async (sources) => {
|
|
490
|
+
const state = stateRef.current;
|
|
491
|
+
const idle = [];
|
|
492
|
+
const writeOnlySettled = [];
|
|
493
|
+
const jobs = [];
|
|
494
|
+
for (const source of sources) {
|
|
495
|
+
controllersRef.current.get(source.id)?.abort();
|
|
496
|
+
controllersRef.current.delete(source.id);
|
|
497
|
+
const key = requestKeyOf(source, state);
|
|
498
|
+
requestKeysRef.current[source.id] = key;
|
|
499
|
+
if (isIdleSource(source, state)) {
|
|
500
|
+
idle.push(source);
|
|
501
|
+
registerSource(source, state);
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
// A form's write target nothing reads: no list request — not on
|
|
505
|
+
// mount, not after a submit's refreshAll. Still registered, so the
|
|
506
|
+
// form's write through the data service finds it.
|
|
507
|
+
if (writeOnlyRef.current?.has(source.id) === true) {
|
|
508
|
+
writeOnlySettled.push(source);
|
|
509
|
+
registerSource(source, state);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
const controller = new AbortController();
|
|
513
|
+
controllersRef.current.set(source.id, controller);
|
|
514
|
+
jobs.push({ source, controller, key });
|
|
515
|
+
}
|
|
516
|
+
// Idle sources settle at once: no data, not loading, no error. The data
|
|
517
|
+
// of whatever was shown before belongs to another request, so it goes.
|
|
518
|
+
// Fetched sources show their loading flag and keep their data until the
|
|
519
|
+
// answer arrives (a filter change does not blank the list meanwhile).
|
|
520
|
+
setSourceData((prev) => {
|
|
521
|
+
const next = { ...prev };
|
|
522
|
+
for (const source of idle) {
|
|
523
|
+
next[source.id] = { data: null, loading: false, error: null, status: null, meta: null, idle: true, key: requestKeysRef.current[source.id] };
|
|
524
|
+
}
|
|
525
|
+
for (const source of writeOnlySettled) {
|
|
526
|
+
next[source.id] = { data: null, loading: false, error: null, status: null, meta: null, key: requestKeysRef.current[source.id] };
|
|
527
|
+
}
|
|
528
|
+
for (const { source } of jobs) {
|
|
529
|
+
const p = prev[source.id];
|
|
530
|
+
next[source.id] = {
|
|
531
|
+
data: p?.data ?? null,
|
|
532
|
+
loading: true,
|
|
533
|
+
error: null,
|
|
534
|
+
status: p?.status ?? null,
|
|
535
|
+
meta: p?.meta ?? null,
|
|
536
|
+
...(p?.key !== undefined ? { key: p.key } : {}),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
return next;
|
|
540
|
+
});
|
|
541
|
+
await Promise.all(jobs.map(async ({ source, controller, key }) => {
|
|
542
|
+
let outcome;
|
|
543
|
+
try {
|
|
544
|
+
outcome = await fetchSource(source, state, controller.signal);
|
|
545
|
+
}
|
|
546
|
+
catch (err) {
|
|
547
|
+
outcome = { kind: 'error', message: err instanceof Error ? err.message : String(err), status: null };
|
|
548
|
+
}
|
|
549
|
+
if (controller.signal.aborted)
|
|
550
|
+
return;
|
|
551
|
+
controllersRef.current.delete(source.id);
|
|
552
|
+
setSourceData((prev) => {
|
|
553
|
+
const next = { ...prev };
|
|
554
|
+
if (outcome.kind === 'ok') {
|
|
555
|
+
next[source.id] = { data: outcome.data, loading: false, error: null, status: outcome.status, meta: outcome.meta, key };
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
const p = prev[source.id];
|
|
559
|
+
// ON ERROR, DATA FROM ANOTHER REQUEST IS CLEARED: a search
|
|
560
|
+
// for cart 999 must not leave cart 1 on screen under the error.
|
|
561
|
+
// The SAME request failing again (a refresh, a poll) keeps its
|
|
562
|
+
// last good answer — that answer is still the right record.
|
|
563
|
+
const keep = p?.key === key;
|
|
564
|
+
next[source.id] = {
|
|
565
|
+
data: outcome.fallback ? outcome.fallback.data : keep ? (p?.data ?? null) : null,
|
|
566
|
+
loading: false,
|
|
567
|
+
error: outcome.message,
|
|
568
|
+
status: outcome.status,
|
|
569
|
+
meta: keep ? (p?.meta ?? null) : null,
|
|
570
|
+
...(keep ? { key } : {}),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
return next;
|
|
574
|
+
});
|
|
575
|
+
if (outcome.kind === 'ok') {
|
|
576
|
+
const cacheKey = dataSourceCacheKey(source, state);
|
|
577
|
+
if (cacheKey)
|
|
578
|
+
dataCacheRef.current?.set(cacheKey, outcome.data);
|
|
579
|
+
}
|
|
580
|
+
}));
|
|
581
|
+
// A pass whose every fetch was superseded settles nothing; the pass
|
|
582
|
+
// that superseded it will.
|
|
583
|
+
if (jobs.length > 0 && jobs.every((j) => j.controller.signal.aborted))
|
|
584
|
+
return;
|
|
585
|
+
setInitialLoaded(true);
|
|
586
|
+
}, [fetchSource, registerSource]);
|
|
587
|
+
// ---- Fetch every source (refreshAll) ----
|
|
588
|
+
const fetchAllSources = React.useCallback(async () => {
|
|
589
|
+
const sources = sourcesRef.current;
|
|
590
|
+
if (!sources || sources.length === 0) {
|
|
591
|
+
setInitialLoaded(true);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
await fetchSources(sources);
|
|
595
|
+
}, [fetchSources]);
|
|
596
|
+
// ---- Refresh a single source ----
|
|
597
|
+
const refreshSource = React.useCallback(async (sourceId) => {
|
|
598
|
+
const source = sourcesRef.current?.find((s) => s.id === sourceId);
|
|
599
|
+
if (!source)
|
|
600
|
+
return;
|
|
601
|
+
await fetchSources([source]);
|
|
602
|
+
}, [fetchSources]);
|
|
603
|
+
// ---- Initial fetch + refetch of the sources whose request changed ----
|
|
604
|
+
React.useEffect(() => {
|
|
605
|
+
const sources = dataSources ?? [];
|
|
606
|
+
const present = new Set(sources.map((s) => s.id));
|
|
607
|
+
for (const id of Object.keys(requestKeysRef.current)) {
|
|
608
|
+
if (present.has(id))
|
|
609
|
+
continue;
|
|
610
|
+
delete requestKeysRef.current[id];
|
|
611
|
+
controllersRef.current.get(id)?.abort();
|
|
612
|
+
controllersRef.current.delete(id);
|
|
613
|
+
if (dataService.hasSource(id))
|
|
614
|
+
dataService.unregisterSource(id);
|
|
615
|
+
}
|
|
616
|
+
if (sources.length === 0) {
|
|
617
|
+
setInitialLoaded(true);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const changed = sources.filter((s) => requestKeyOf(s, surfaceState) !== requestKeysRef.current[s.id]);
|
|
621
|
+
if (changed.length > 0)
|
|
622
|
+
void fetchSources(changed);
|
|
623
|
+
}, [dataSources, surfaceState, fetchSources, dataService]);
|
|
624
|
+
// ---- Unmount: abort what is in flight, forget what was fetched ----
|
|
625
|
+
// Forgetting matters under StrictMode's mount → unmount → mount: the second
|
|
626
|
+
// mount must fetch again, not believe the aborted first pass succeeded.
|
|
627
|
+
React.useEffect(() => {
|
|
628
|
+
const controllers = controllersRef.current;
|
|
629
|
+
return () => {
|
|
630
|
+
for (const controller of controllers.values())
|
|
631
|
+
controller.abort();
|
|
632
|
+
controllers.clear();
|
|
633
|
+
requestKeysRef.current = {};
|
|
634
|
+
for (const source of sourcesRef.current ?? []) {
|
|
635
|
+
if (dataService.hasSource(source.id))
|
|
636
|
+
dataService.unregisterSource(source.id);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
}, [dataService]);
|
|
640
|
+
// ---- Auto-refresh intervals ----
|
|
641
|
+
React.useEffect(() => {
|
|
642
|
+
// Inert (editor/preview): never start auto-refresh timers.
|
|
643
|
+
if (!dataSources || interactionMode === 'inert')
|
|
644
|
+
return;
|
|
645
|
+
const intervals = [];
|
|
646
|
+
for (const source of dataSources) {
|
|
647
|
+
if (source.refreshInterval && source.refreshInterval > 0) {
|
|
648
|
+
const interval = setInterval(() => {
|
|
649
|
+
void refreshSource(source.id);
|
|
650
|
+
}, source.refreshInterval);
|
|
651
|
+
intervals.push(interval);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return () => {
|
|
655
|
+
for (const interval of intervals) {
|
|
656
|
+
clearInterval(interval);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
}, [dataSources, refreshSource, interactionMode]);
|
|
660
|
+
// ---- Build expression context ----
|
|
661
|
+
const expressionData = React.useMemo(() => {
|
|
662
|
+
const ctx = { state: surfaceState };
|
|
663
|
+
// Per-source loading flags for skeleton UIs: `{{__loading.<sourceId>}}` /
|
|
664
|
+
// `{{__anyLoading}}`. A declared source with no sourceData entry yet has
|
|
665
|
+
// not even started its first fetch (the fetch effect runs after first
|
|
666
|
+
// paint) — report it as loading so a skeleton shows from frame one
|
|
667
|
+
// instead of flashing the block's empty state. `state`-kind sources read
|
|
668
|
+
// synchronously from React state and are never "loading".
|
|
669
|
+
const loadingMap = {};
|
|
670
|
+
let anyLoading = false;
|
|
671
|
+
// Per-source error messages — `{{__errors.<sourceId>}}` — so a block can
|
|
672
|
+
// distinguish "fetch failed" from "fetched, genuinely empty" instead of
|
|
673
|
+
// showing a false empty-state. The value is the error MESSAGE string
|
|
674
|
+
// (truthy check + tooltip content in one binding); null when healthy.
|
|
675
|
+
const errorMap = {};
|
|
676
|
+
let anyError = false;
|
|
677
|
+
// `{{__status.<id>}}` — the HTTP status of the last answer (200, 404, …);
|
|
678
|
+
// null for a network failure, an idle source or a non-HTTP kind.
|
|
679
|
+
// `{{__notFound.<id>}}` — status 404, so "no such order" and "connection
|
|
680
|
+
// error" can be told apart. `{{__idle.<id>}}` — a REST source
|
|
681
|
+
// not fetched because a required path value is empty.
|
|
682
|
+
// `{{__meta.<id>}}` — the envelope `returns` unwrapped the list from
|
|
683
|
+
// (`total`, `skip`, `limit` …); null when there is none.
|
|
684
|
+
const statusMap = {};
|
|
685
|
+
const notFoundMap = {};
|
|
686
|
+
const idleMap = {};
|
|
687
|
+
const metaMap = {};
|
|
688
|
+
for (const source of dataSources ?? []) {
|
|
689
|
+
const entry = sourceData[source.id];
|
|
690
|
+
const loading = source.kind === 'state' || writeOnly?.has(source.id) === true ? false : entry ? entry.loading : true;
|
|
691
|
+
loadingMap[source.id] = loading;
|
|
692
|
+
if (loading)
|
|
693
|
+
anyLoading = true;
|
|
694
|
+
const error = entry?.error ?? null;
|
|
695
|
+
errorMap[source.id] = error;
|
|
696
|
+
if (error)
|
|
697
|
+
anyError = true;
|
|
698
|
+
const status = entry?.status ?? null;
|
|
699
|
+
statusMap[source.id] = status;
|
|
700
|
+
notFoundMap[source.id] = status === 404;
|
|
701
|
+
idleMap[source.id] = entry?.idle === true;
|
|
702
|
+
metaMap[source.id] = entry?.meta ?? null;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* A LIST SOURCE ALWAYS BINDS AS A LIST.
|
|
706
|
+
*
|
|
707
|
+
* The entity path returns `result.items` — an array. The rest path
|
|
708
|
+
* returned the whole JSON body, or, since `returns` landed, the plucked
|
|
709
|
+
* value. So the same block bound to `{{data.Product}}` received an array
|
|
710
|
+
* from our store and an object from anyone else's, and swapping a model
|
|
711
|
+
* from platform to a connection quietly changed what every bound block
|
|
712
|
+
* got. One shape.
|
|
713
|
+
*
|
|
714
|
+
* `static` and `state` are the documented exceptions and stay untouched:
|
|
715
|
+
* a state source is routinely a scalar, and wrapping it would break every
|
|
716
|
+
* shipped surface that reads one.
|
|
717
|
+
*/
|
|
718
|
+
const LIST_KINDS = new Set(['rest', 'entity', 'graphql']);
|
|
719
|
+
const kindById = new Map((dataSources ?? []).map((s) => [s.id, s.kind]));
|
|
720
|
+
// An AGGREGATE source is one number, not a list of one: wrapping
|
|
721
|
+
// `{ value }` into `[{ value }]` would still read through the single-
|
|
722
|
+
// element fallthrough, but `.length` would lie and `count` would hide.
|
|
723
|
+
// Entity-only: that is the one branch that reads `aggregate`; a rest
|
|
724
|
+
// source carrying the key still fetches a list and binds as one.
|
|
725
|
+
const aggregateIds = new Set((dataSources ?? []).filter((s) => s.kind === 'entity' && s.aggregate !== undefined).map((s) => s.id));
|
|
726
|
+
for (const [key, value] of Object.entries(sourceData)) {
|
|
727
|
+
const listy = LIST_KINDS.has(kindById.get(key) ?? '') && !aggregateIds.has(key);
|
|
728
|
+
ctx[key] =
|
|
729
|
+
listy && value.data != null && !Array.isArray(value.data) ? [value.data] : value.data;
|
|
730
|
+
}
|
|
731
|
+
ctx['__loading'] = loadingMap;
|
|
732
|
+
ctx['__anyLoading'] = anyLoading;
|
|
733
|
+
ctx['__errors'] = errorMap;
|
|
734
|
+
ctx['__anyError'] = anyError;
|
|
735
|
+
ctx['__status'] = statusMap;
|
|
736
|
+
ctx['__notFound'] = notFoundMap;
|
|
737
|
+
ctx['__idle'] = idleMap;
|
|
738
|
+
ctx['__meta'] = metaMap;
|
|
739
|
+
return ctx;
|
|
740
|
+
}, [surfaceState, sourceData, dataSources, writeOnly]);
|
|
741
|
+
return {
|
|
742
|
+
expressionData,
|
|
743
|
+
surfaceState,
|
|
744
|
+
initialLoaded,
|
|
745
|
+
setStateValue,
|
|
746
|
+
refreshSource,
|
|
747
|
+
fetchAllSources,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
// ============================================================
|
|
751
|
+
// canonicalizeEventType
|
|
752
|
+
// ============================================================
|
|
753
|
+
/**
|
|
754
|
+
* Normalize a stored behavior trigger string to canonical colon form.
|
|
755
|
+
* - Already has `:` → return lowercased/trimmed as-is
|
|
756
|
+
* - Has `.` → replace first `.` with `:`
|
|
757
|
+
* - Otherwise → return trimmed lowercased (best-effort; won't match well-formed events)
|
|
758
|
+
*/
|
|
759
|
+
export function canonicalizeEventType(raw) {
|
|
760
|
+
const s = raw.trim().toLowerCase();
|
|
761
|
+
if (s.includes(':'))
|
|
762
|
+
return s;
|
|
763
|
+
if (s.includes('.'))
|
|
764
|
+
return s.replace('.', ':');
|
|
765
|
+
return s;
|
|
766
|
+
}
|
|
767
|
+
// ============================================================
|
|
768
|
+
// isSurfaceAction
|
|
769
|
+
// ============================================================
|
|
770
|
+
export function isSurfaceAction(value) {
|
|
771
|
+
if (!value || typeof value !== 'object')
|
|
772
|
+
return false;
|
|
773
|
+
const obj = value;
|
|
774
|
+
return (obj.type === 'setState' ||
|
|
775
|
+
obj.type === 'refresh' ||
|
|
776
|
+
obj.type === 'refreshAll' ||
|
|
777
|
+
obj.type === 'showBlock' ||
|
|
778
|
+
obj.type === 'hideBlock' ||
|
|
779
|
+
obj.type === 'navigate');
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Guard the `navigate` action against XSS: only http(s) absolute URLs and
|
|
783
|
+
* relative/anchor URLs may be navigated to. A scheme like `javascript:`,
|
|
784
|
+
* `data:`, or `vbscript:` is rejected so a stored behavior cannot execute
|
|
785
|
+
* script in a published-surface visitor's context. Control characters are
|
|
786
|
+
* stripped first to defeat `java\tscript:`-style scheme smuggling.
|
|
787
|
+
*/
|
|
788
|
+
export function isSafeNavigateUrl(url) {
|
|
789
|
+
if (typeof url !== 'string')
|
|
790
|
+
return false;
|
|
791
|
+
const stripped = url.replace(/[^!-~]/g, '');
|
|
792
|
+
if (stripped === '')
|
|
793
|
+
return false;
|
|
794
|
+
const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(stripped);
|
|
795
|
+
if (!scheme)
|
|
796
|
+
return true; // relative path / query / hash / protocol-relative
|
|
797
|
+
const s = (scheme[1] ?? '').toLowerCase();
|
|
798
|
+
return s === 'http' || s === 'https';
|
|
799
|
+
}
|
|
800
|
+
// ============================================================
|
|
801
|
+
// DataBoundSurfaceRenderer COMPONENT
|
|
802
|
+
// ============================================================
|
|
803
|
+
/**
|
|
804
|
+
* Data-bound surface renderer.
|
|
805
|
+
*
|
|
806
|
+
* If nothing on the surface is dynamic (see surfaceNeedsDataBinding), renders
|
|
807
|
+
* SurfaceRenderer directly (zero overhead).
|
|
808
|
+
* Otherwise, wraps with DataCapabilityProvider for DataService context, then
|
|
809
|
+
* fetches data, manages state, resolves expressions, and intercepts events.
|
|
810
|
+
*/
|
|
811
|
+
export function DataBoundSurfaceRenderer(props) {
|
|
812
|
+
const { surface, onEvent, behaviors: hostBehaviors, onNavigateAction, dataServiceConfig, ...rest } = props;
|
|
813
|
+
const { blocks, appearance, capabilities } = surface;
|
|
814
|
+
// dataSources + initialState + declared behaviors live under platform/persistence config
|
|
815
|
+
const { dataSources, initialState, behaviors: declaredBehaviors } = extractPersistenceSeeds(surface);
|
|
816
|
+
// Declared (JSON) first, host (its store) after — same id in both: host wins.
|
|
817
|
+
const behaviors = React.useMemo(() => {
|
|
818
|
+
if (!declaredBehaviors?.length)
|
|
819
|
+
return hostBehaviors;
|
|
820
|
+
if (!hostBehaviors?.length)
|
|
821
|
+
return declaredBehaviors;
|
|
822
|
+
const hostIds = new Set(hostBehaviors.map((b) => b.id));
|
|
823
|
+
return [...declaredBehaviors.filter((b) => !hostIds.has(b.id)), ...hostBehaviors];
|
|
824
|
+
}, [declaredBehaviors, hostBehaviors]);
|
|
825
|
+
const hasBehaviors = Boolean(behaviors && behaviors.length > 0);
|
|
826
|
+
// Nothing dynamic — no sources, no state, no behaviors, no form bindings, no
|
|
827
|
+
// block action: pass through directly (zero overhead). Write-only surfaces
|
|
828
|
+
// (formBindings without any read source) and state-only wizards must NOT
|
|
829
|
+
// take it — see surfaceNeedsDataBinding() and hasEntityFormBindings().
|
|
830
|
+
if (!hasBehaviors && !surfaceNeedsDataBinding(surface)) {
|
|
831
|
+
const passthrough = {
|
|
832
|
+
// `id` FORWARDED, not dropped. SurfaceRenderer derives its scope from
|
|
833
|
+
// `surface.id`: with an id the per-surface rules key off the attribute
|
|
834
|
+
// VALUE (`[data-pw-surface="<id>"]`), without one they fall back to the
|
|
835
|
+
// value-less `[data-pw-surface]` and every surface in a document shares
|
|
836
|
+
// them. Rebuilding the surface object here silently erased the id, so the
|
|
837
|
+
// scoping degraded on this path while looking correct in the source.
|
|
838
|
+
...(surface.id !== undefined ? { id: surface.id } : {}),
|
|
839
|
+
blocks,
|
|
840
|
+
...(appearance !== undefined ? { appearance } : {}),
|
|
841
|
+
...(capabilities !== undefined ? { capabilities } : {}),
|
|
842
|
+
};
|
|
843
|
+
return onEvent ? (_jsx(SurfaceRenderer, { surface: passthrough, onEvent: onEvent, ...rest })) : (_jsx(SurfaceRenderer, { surface: passthrough, ...rest }));
|
|
844
|
+
}
|
|
845
|
+
// Wrap with DataCapabilityProvider to provide DataService context
|
|
846
|
+
return (_jsx(DataCapabilityProvider, { ...(dataServiceConfig ? { config: dataServiceConfig } : {}), children: onEvent ? (_jsx(DataBoundInner, { ...(surface.id !== undefined ? { surfaceId: surface.id } : {}), blocks: blocks, dataSources: dataSources ?? [], initialState: initialState, appearance: appearance, capabilities: capabilities, ...(behaviors !== undefined ? { behaviors } : {}), ...(onNavigateAction !== undefined ? { onNavigateAction } : {}), onEvent: onEvent, rendererProps: rest })) : (_jsx(DataBoundInner, { ...(surface.id !== undefined ? { surfaceId: surface.id } : {}), blocks: blocks, dataSources: dataSources ?? [], initialState: initialState, appearance: appearance, capabilities: capabilities, ...(behaviors !== undefined ? { behaviors } : {}), ...(onNavigateAction !== undefined ? { onNavigateAction } : {}), rendererProps: rest })) }));
|
|
847
|
+
}
|
|
848
|
+
function DataBoundInner({ surfaceId, blocks, dataSources, initialState, appearance, capabilities, onEvent, behaviors, onNavigateAction, rendererProps, }) {
|
|
849
|
+
const interactionMode = rendererProps.interactionMode ?? 'live';
|
|
850
|
+
const entityFacade = rendererProps.entityFacade;
|
|
851
|
+
// Same pass-through route as entityFacade: declared on
|
|
852
|
+
// DataBoundSurfaceRendererProps, travels inside rendererProps.
|
|
853
|
+
const loadingStrategy = rendererProps.loadingStrategy ?? 'gate';
|
|
854
|
+
// Connections context travels the same pass-through route
|
|
855
|
+
// (declared on SurfaceRendererProps, so it also reaches SurfaceRenderer's
|
|
856
|
+
// SurfaceConnectionsContext provider via the rendererProps spread below).
|
|
857
|
+
const connections = rendererProps.connections;
|
|
858
|
+
const dataCache = rendererProps.dataCache;
|
|
859
|
+
// Keyed by the ids, so a re-render that changes nothing hands useSurfaceData
|
|
860
|
+
// the same set (its expression context depends on it).
|
|
861
|
+
const writeOnlyKey = React.useMemo(() => [...writeOnlySourceIds({ blocks, dataSources, capabilities, behaviors })].sort().join('\n'), [blocks, dataSources, capabilities, behaviors]);
|
|
862
|
+
const writeOnly = React.useMemo(() => new Set(writeOnlyKey === '' ? [] : writeOnlyKey.split('\n')), [writeOnlyKey]);
|
|
863
|
+
const { expressionData, initialLoaded, setStateValue, refreshSource, fetchAllSources } = useSurfaceData(dataSources, initialState, interactionMode, entityFacade, connections, dataCache, writeOnly);
|
|
864
|
+
// Resolve expressions in blocks
|
|
865
|
+
const resolvedBlocks = React.useMemo(() => resolveBlockExpressions(blocks, expressionData), [blocks, expressionData]);
|
|
866
|
+
// Shared action dispatcher — handles all six action types
|
|
867
|
+
const dispatchAction = React.useCallback((action) => {
|
|
868
|
+
switch (action.type) {
|
|
869
|
+
case 'setState':
|
|
870
|
+
setStateValue(action.key, action.value);
|
|
871
|
+
break;
|
|
872
|
+
case 'refresh':
|
|
873
|
+
refreshSource(action.sourceId);
|
|
874
|
+
break;
|
|
875
|
+
case 'refreshAll':
|
|
876
|
+
fetchAllSources();
|
|
877
|
+
break;
|
|
878
|
+
case 'showBlock':
|
|
879
|
+
setStateValue('__show:' + action.blockId, true);
|
|
880
|
+
break;
|
|
881
|
+
case 'hideBlock':
|
|
882
|
+
setStateValue('__show:' + action.blockId, false);
|
|
883
|
+
break;
|
|
884
|
+
case 'navigate':
|
|
885
|
+
// Give the host a chance to handle this in place first (e.g. a
|
|
886
|
+
// connected-preview mode swapping to a sibling page) before falling
|
|
887
|
+
// through to a real hard navigation.
|
|
888
|
+
if (onNavigateAction?.(action.url))
|
|
889
|
+
break;
|
|
890
|
+
if (isSafeNavigateUrl(action.url)) {
|
|
891
|
+
window.location.assign(action.url);
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
console.warn('[surface] blocked unsafe navigate URL:', action.url);
|
|
895
|
+
}
|
|
896
|
+
break;
|
|
897
|
+
}
|
|
898
|
+
}, [setStateValue, refreshSource, fetchAllSources, onNavigateAction]);
|
|
899
|
+
// Event interceptor
|
|
900
|
+
const handleEvent = React.useCallback((event) => {
|
|
901
|
+
// Inert (editor/preview): skip block-dispatched surface actions (state
|
|
902
|
+
// mutations / data refreshes) so the surface doesn't mutate its own state
|
|
903
|
+
// while authoring. `onEvent` is a pure notification and STILL fires below
|
|
904
|
+
// — only the real data side-effects are gated.
|
|
905
|
+
if (interactionMode !== 'inert') {
|
|
906
|
+
// Check if the event payload contains a surface action
|
|
907
|
+
const payload = event.payload;
|
|
908
|
+
const action = payload?.action;
|
|
909
|
+
if (isSurfaceAction(action)) {
|
|
910
|
+
dispatchAction(action);
|
|
911
|
+
}
|
|
912
|
+
// `canonicalType` is normally stamped by the bus bridge (renderer.tsx's
|
|
913
|
+
// busOnEvent), but some deployed block bundles still call
|
|
914
|
+
// `context.onEvent` directly (the pre-bus legacy path) — for those,
|
|
915
|
+
// `canonicalType` is absent. Block-scoped click events reaching here
|
|
916
|
+
// always mean a real click occurred, so treat a canonicalType-less
|
|
917
|
+
// 'click' as 'block:click' rather than silently dropping it.
|
|
918
|
+
const fired = event.canonicalType ?? (event.type === 'click' && event.blockId ? 'block:click' : '');
|
|
919
|
+
// A block's own `action` prop (one action or a list, run in order) is
|
|
920
|
+
// what its CLICK does — and only its click. It used to run on every
|
|
921
|
+
// event the block emitted, so a block that also reported hover, focus
|
|
922
|
+
// or a value change would have fired its click action on each of them.
|
|
923
|
+
// Looked up in the RESOLVED tree, so a repeat clone (`id--N`) carries
|
|
924
|
+
// its own row's values.
|
|
925
|
+
if (fired === 'block:click') {
|
|
926
|
+
blockActionsOf(findBlockAction(resolvedBlocks, event.blockId)).forEach(dispatchAction);
|
|
927
|
+
}
|
|
928
|
+
// Execute stored behaviors whose trigger matches this canonical event.
|
|
929
|
+
if (behaviors && behaviors.length > 0) {
|
|
930
|
+
for (const b of behaviors) {
|
|
931
|
+
if (canonicalizeEventType(b.event) !== fired)
|
|
932
|
+
continue;
|
|
933
|
+
if (b.targetBlockId && b.targetBlockId !== event.blockId)
|
|
934
|
+
continue;
|
|
935
|
+
b.actions.forEach(dispatchAction);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
// Forward to original handler — a pure notification, always fires (the
|
|
940
|
+
// host decides what side-effects, if any, to run).
|
|
941
|
+
onEvent?.(event);
|
|
942
|
+
}, [interactionMode, onEvent, dispatchAction, resolvedBlocks, behaviors]);
|
|
943
|
+
// Loading state — 'gate' strategy only (the historical default): the whole
|
|
944
|
+
// block tree is withheld until the first fetch pass settles. 'immediate'
|
|
945
|
+
// skips this and renders the tree at once; unresolved bindings evaluate to
|
|
946
|
+
// undefined and fill in per source (see loadingStrategy prop docs).
|
|
947
|
+
if (!initialLoaded && loadingStrategy !== 'immediate') {
|
|
948
|
+
return (_jsx("div", { "data-surface-loading": true, style: {
|
|
949
|
+
display: 'flex',
|
|
950
|
+
alignItems: 'center',
|
|
951
|
+
justifyContent: 'center',
|
|
952
|
+
padding: '48px',
|
|
953
|
+
// Theme-tokened with the old hardcoded values as fallbacks, so the
|
|
954
|
+
// gate no longer paints light-theme blue on a dark-themed surface.
|
|
955
|
+
color: 'var(--pw-color-text-muted, #666)',
|
|
956
|
+
}, children: _jsxs("div", { style: { textAlign: 'center' }, children: [_jsx("div", { style: {
|
|
957
|
+
width: '32px',
|
|
958
|
+
height: '32px',
|
|
959
|
+
border: '3px solid var(--pw-color-border, #e5e7eb)',
|
|
960
|
+
borderTopColor: 'var(--pw-color-primary, #3b82f6)',
|
|
961
|
+
borderRadius: '50%',
|
|
962
|
+
animation: 'spin 0.8s linear infinite',
|
|
963
|
+
margin: '0 auto 12px',
|
|
964
|
+
} }), "Loading data..."] }) }));
|
|
965
|
+
}
|
|
966
|
+
const resolvedSurface = {
|
|
967
|
+
// See the passthrough branch above — same reason, same one-line omission.
|
|
968
|
+
...(surfaceId !== undefined ? { id: surfaceId } : {}),
|
|
969
|
+
blocks: resolvedBlocks,
|
|
970
|
+
...(appearance !== undefined ? { appearance } : {}),
|
|
971
|
+
...(capabilities !== undefined ? { capabilities } : {}),
|
|
972
|
+
};
|
|
973
|
+
return (
|
|
974
|
+
// What is bound right now, for the providers under SurfaceRenderer that
|
|
975
|
+
// must act on it — the form writer decides create-vs-update from the
|
|
976
|
+
// record a source has loaded, and could not see one before this.
|
|
977
|
+
_jsx(SurfaceDataContext.Provider, { value: expressionData, children: _jsx(SurfaceRenderer, { surface: resolvedSurface, onEvent: handleEvent, ...rendererProps }) }));
|
|
978
|
+
}
|
|
979
|
+
// ============================================================
|
|
980
|
+
// HELPERS
|
|
981
|
+
// ============================================================
|
|
982
|
+
/**
|
|
983
|
+
* Find the action prop of a block by ID in the resolved block tree.
|
|
984
|
+
*/
|
|
985
|
+
function findBlockAction(blocks, blockId) {
|
|
986
|
+
for (const block of blocks) {
|
|
987
|
+
if (block.id === blockId) {
|
|
988
|
+
return block.props?.action;
|
|
989
|
+
}
|
|
990
|
+
if (block.children) {
|
|
991
|
+
const found = findBlockAction(block.children, blockId);
|
|
992
|
+
if (found !== undefined)
|
|
993
|
+
return found;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return undefined;
|
|
997
|
+
}
|
|
998
|
+
//# sourceMappingURL=DataBoundSurfaceRenderer.js.map
|