@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,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Module
|
|
3
|
+
*
|
|
4
|
+
* Data source resolution, fetch integration, and data binding
|
|
5
|
+
*
|
|
6
|
+
* Exports:
|
|
7
|
+
* - Types: DataSourceDefinition, DataFetchResult, DataBinding, LiveDataBinding, etc.
|
|
8
|
+
* - Service: DataService
|
|
9
|
+
* - Provider: DataCapabilityProvider, useDataService
|
|
10
|
+
* - Factory functions: createDataService
|
|
11
|
+
*
|
|
12
|
+
* @module @pwfabric/runtime/data
|
|
13
|
+
*/
|
|
14
|
+
export type { DataSourceKind, DataSourceDefinition, DataCacheConfig, DataRetryConfig, DataResolveContext, DataFetchOptions, DataFetchResult, DataFetchMetadata, DataFetchError, DataBinding, DataBindingState, LiveDataBinding, DataBindingListener, DataServiceConfig, DataServiceInterface, } from './types.js';
|
|
15
|
+
export { DataService, createDataService } from './manager.js';
|
|
16
|
+
export { DataCapabilityProvider, useDataService, useOptionalDataService, } from './DataCapabilityProvider.js';
|
|
17
|
+
export type { DataCapabilityProviderProps } from './DataCapabilityProvider.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Module
|
|
3
|
+
*
|
|
4
|
+
* Data source resolution, fetch integration, and data binding
|
|
5
|
+
*
|
|
6
|
+
* Exports:
|
|
7
|
+
* - Types: DataSourceDefinition, DataFetchResult, DataBinding, LiveDataBinding, etc.
|
|
8
|
+
* - Service: DataService
|
|
9
|
+
* - Provider: DataCapabilityProvider, useDataService
|
|
10
|
+
* - Factory functions: createDataService
|
|
11
|
+
*
|
|
12
|
+
* @module @pwfabric/runtime/data
|
|
13
|
+
*/
|
|
14
|
+
// Service implementation
|
|
15
|
+
export { DataService, createDataService } from './manager.js';
|
|
16
|
+
// React context provider and hook
|
|
17
|
+
export { DataCapabilityProvider, useDataService, useOptionalDataService, } from './DataCapabilityProvider.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/data/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAqBH,yBAAyB;AACzB,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAE7D,kCAAkC;AAClC,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,sBAAsB,GACvB,MAAM,6BAA6B,CAAA","sourcesContent":["/**\n * Data Module\n *\n * Data source resolution, fetch integration, and data binding\n *\n * Exports:\n * - Types: DataSourceDefinition, DataFetchResult, DataBinding, LiveDataBinding, etc.\n * - Service: DataService\n * - Provider: DataCapabilityProvider, useDataService\n * - Factory functions: createDataService\n *\n * @module @pwfabric/runtime/data\n */\n\n// Types\nexport type {\n DataSourceKind,\n DataSourceDefinition,\n DataCacheConfig,\n DataRetryConfig,\n DataResolveContext,\n DataFetchOptions,\n DataFetchResult,\n DataFetchMetadata,\n DataFetchError,\n DataBinding,\n DataBindingState,\n LiveDataBinding,\n DataBindingListener,\n DataServiceConfig,\n DataServiceInterface,\n} from './types.js'\n\n// Service implementation\nexport { DataService, createDataService } from './manager.js'\n\n// React context provider and hook\nexport {\n DataCapabilityProvider,\n useDataService,\n useOptionalDataService,\n} from './DataCapabilityProvider.js'\nexport type { DataCapabilityProviderProps } from './DataCapabilityProvider.js'\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Service (formerly Data Capability Manager)
|
|
3
|
+
*
|
|
4
|
+
* Default implementation of the data service
|
|
5
|
+
*
|
|
6
|
+
* Provides data source registration, resolution, fetch with caching,
|
|
7
|
+
* and live data bindings for surfaces.
|
|
8
|
+
*
|
|
9
|
+
* @module @pwfabric/runtime/data/manager
|
|
10
|
+
*/
|
|
11
|
+
import type { DataServiceInterface, DataServiceConfig, DataSourceDefinition, DataFetchOptions, DataFetchResult, DataBinding, LiveDataBinding } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Default implementation of the DataServiceInterface.
|
|
14
|
+
*
|
|
15
|
+
* Accepts optional DataServiceConfig for default cache TTL, retry, and timeout.
|
|
16
|
+
*/
|
|
17
|
+
export declare class DataService implements DataServiceInterface {
|
|
18
|
+
private readonly sources;
|
|
19
|
+
private readonly cache;
|
|
20
|
+
private readonly bindings;
|
|
21
|
+
readonly config: DataServiceConfig;
|
|
22
|
+
constructor(config?: DataServiceConfig);
|
|
23
|
+
registerSource<T>(definition: DataSourceDefinition<T>): void;
|
|
24
|
+
resolveDataSource(sourceId: string): DataSourceDefinition | undefined;
|
|
25
|
+
hasSource(sourceId: string): boolean;
|
|
26
|
+
fetchData<T>(sourceId: string, options?: DataFetchOptions): Promise<DataFetchResult<T>>;
|
|
27
|
+
bindData<T>(binding: DataBinding<T>): LiveDataBinding<T>;
|
|
28
|
+
unregisterSource(sourceId: string): boolean;
|
|
29
|
+
getSourceIds(): readonly string[];
|
|
30
|
+
clear(): void;
|
|
31
|
+
private executeFetch;
|
|
32
|
+
private fetchRest;
|
|
33
|
+
private getFromCache;
|
|
34
|
+
private setCache;
|
|
35
|
+
private toFetchError;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a DataService instance with optional config
|
|
39
|
+
*/
|
|
40
|
+
export declare function createDataService(config?: DataServiceConfig): DataServiceInterface;
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Service (formerly Data Capability Manager)
|
|
3
|
+
*
|
|
4
|
+
* Default implementation of the data service
|
|
5
|
+
*
|
|
6
|
+
* Provides data source registration, resolution, fetch with caching,
|
|
7
|
+
* and live data bindings for surfaces.
|
|
8
|
+
*
|
|
9
|
+
* @module @pwfabric/runtime/data/manager
|
|
10
|
+
*/
|
|
11
|
+
import { applyEntityQuery } from '../entity/apply-query.js';
|
|
12
|
+
// ============================================================
|
|
13
|
+
// DATA SERVICE
|
|
14
|
+
// ============================================================
|
|
15
|
+
/**
|
|
16
|
+
* The list, out of whatever the endpoint wrapped it in.
|
|
17
|
+
*
|
|
18
|
+
* `returns` names the property holding the array — `data` for ours,
|
|
19
|
+
* `products` for DummyJSON, absent when the body already IS the array. Three
|
|
20
|
+
* shapes were in circulation and the binding context received whichever one
|
|
21
|
+
* came back, so a block bound to a list could not take all three.
|
|
22
|
+
*
|
|
23
|
+
* A named path that is not there THROWS rather than yielding undefined: a
|
|
24
|
+
* silently empty panel is the failure this field exists to end, and "the
|
|
25
|
+
* endpoint answered, but not with what the plan said" is a sentence the user
|
|
26
|
+
* can act on.
|
|
27
|
+
*/
|
|
28
|
+
function isPlatformOrigin(url, platformApiOrigin) {
|
|
29
|
+
if (!platformApiOrigin)
|
|
30
|
+
return false;
|
|
31
|
+
try {
|
|
32
|
+
// A relative endpoint is same-document, which is our own by definition.
|
|
33
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(url))
|
|
34
|
+
return true;
|
|
35
|
+
return new URL(url).origin === new URL(platformApiOrigin).origin;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* What a non-OK response says about itself: `{ error: { code, message } }`
|
|
43
|
+
* when it is our API (or anyone keeping the same envelope), nothing when the
|
|
44
|
+
* body is absent, not JSON, or not that shape. Never throws — a refusal
|
|
45
|
+
* without a readable body is still a refusal with a status.
|
|
46
|
+
*/
|
|
47
|
+
async function readRefusal(response) {
|
|
48
|
+
try {
|
|
49
|
+
const body = (await response.json());
|
|
50
|
+
const error = body?.error;
|
|
51
|
+
if (!error || typeof error !== 'object')
|
|
52
|
+
return {};
|
|
53
|
+
return {
|
|
54
|
+
...(typeof error.code === 'string' ? { code: error.code } : {}),
|
|
55
|
+
...(typeof error.message === 'string' && error.message ? { message: error.message } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function pluck(body, path) {
|
|
63
|
+
if (!path)
|
|
64
|
+
return body;
|
|
65
|
+
let node = body;
|
|
66
|
+
for (const key of path.split('.')) {
|
|
67
|
+
if (!node || typeof node !== 'object' || Array.isArray(node)) {
|
|
68
|
+
throw new Error(`Response has no '${path}' — nothing at '${key}'`);
|
|
69
|
+
}
|
|
70
|
+
node = node[key];
|
|
71
|
+
}
|
|
72
|
+
if (node === undefined)
|
|
73
|
+
throw new Error(`Response has no '${path}'`);
|
|
74
|
+
return node;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* What `returns` left behind: the body's top-level fields other than the one
|
|
78
|
+
* the rows were plucked from. `{ products, total, skip, limit }` with
|
|
79
|
+
* `returns: "products"` → `{ total, skip, limit }`; with `returns: "data.items"`
|
|
80
|
+
* every top-level field except `data`.
|
|
81
|
+
*/
|
|
82
|
+
function envelopeOf(body, path) {
|
|
83
|
+
if (!path || !body || typeof body !== 'object' || Array.isArray(body))
|
|
84
|
+
return undefined;
|
|
85
|
+
const head = path.split('.')[0] ?? path;
|
|
86
|
+
const rest = {};
|
|
87
|
+
for (const [k, v] of Object.entries(body)) {
|
|
88
|
+
if (k !== head)
|
|
89
|
+
rest[k] = v;
|
|
90
|
+
}
|
|
91
|
+
return rest;
|
|
92
|
+
}
|
|
93
|
+
/** A REST answer on its way out of `executeFetch`: rows plus what came with them. */
|
|
94
|
+
class RestAnswer {
|
|
95
|
+
data;
|
|
96
|
+
status;
|
|
97
|
+
envelope;
|
|
98
|
+
constructor(data, status, envelope) {
|
|
99
|
+
this.data = data;
|
|
100
|
+
this.status = status;
|
|
101
|
+
this.envelope = envelope;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Default implementation of the DataServiceInterface.
|
|
106
|
+
*
|
|
107
|
+
* Accepts optional DataServiceConfig for default cache TTL, retry, and timeout.
|
|
108
|
+
*/
|
|
109
|
+
export class DataService {
|
|
110
|
+
sources = new Map();
|
|
111
|
+
cache = new Map();
|
|
112
|
+
bindings = new Map();
|
|
113
|
+
config;
|
|
114
|
+
constructor(config) {
|
|
115
|
+
this.config = config ?? {};
|
|
116
|
+
}
|
|
117
|
+
registerSource(definition) {
|
|
118
|
+
if (this.sources.has(definition.id)) {
|
|
119
|
+
throw new Error(`Data source '${definition.id}' is already registered`);
|
|
120
|
+
}
|
|
121
|
+
this.sources.set(definition.id, definition);
|
|
122
|
+
}
|
|
123
|
+
resolveDataSource(sourceId) {
|
|
124
|
+
return this.sources.get(sourceId);
|
|
125
|
+
}
|
|
126
|
+
hasSource(sourceId) {
|
|
127
|
+
return this.sources.has(sourceId);
|
|
128
|
+
}
|
|
129
|
+
async fetchData(sourceId, options) {
|
|
130
|
+
const source = this.sources.get(sourceId);
|
|
131
|
+
if (!source) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
error: {
|
|
135
|
+
type: 'not_found',
|
|
136
|
+
message: `Data source '${sourceId}' not found`,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// A write is never answered from the read cache, and never fills it: the
|
|
141
|
+
// record a POST returns is not the list a reader asked for.
|
|
142
|
+
const isWrite = (options?.method ?? source.method ?? 'GET') !== 'GET';
|
|
143
|
+
// Check cache
|
|
144
|
+
if (!isWrite && !options?.skipCache && source.cache?.enabled) {
|
|
145
|
+
const cacheKey = source.cache.key ?? `data:${sourceId}`;
|
|
146
|
+
const cached = this.getFromCache(cacheKey);
|
|
147
|
+
if (cached !== undefined) {
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
data: cached,
|
|
151
|
+
metadata: {
|
|
152
|
+
duration: 0,
|
|
153
|
+
fromCache: true,
|
|
154
|
+
timestamp: Date.now(),
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const startTime = Date.now();
|
|
160
|
+
try {
|
|
161
|
+
const fetched = await this.executeFetch(source, options);
|
|
162
|
+
// A REST answer carries its status and envelope beside the rows.
|
|
163
|
+
const rawData = fetched instanceof RestAnswer ? fetched.data : fetched;
|
|
164
|
+
const data = source.transform ? source.transform(rawData) : rawData;
|
|
165
|
+
const duration = Date.now() - startTime;
|
|
166
|
+
const metadata = {
|
|
167
|
+
duration,
|
|
168
|
+
fromCache: false,
|
|
169
|
+
timestamp: Date.now(),
|
|
170
|
+
...(fetched instanceof RestAnswer
|
|
171
|
+
? { status: fetched.status, ...(fetched.envelope ? { envelope: fetched.envelope } : {}) }
|
|
172
|
+
: {}),
|
|
173
|
+
};
|
|
174
|
+
// Store in cache — use source-level TTL, fall back to service config, then default
|
|
175
|
+
if (!isWrite && source.cache?.enabled) {
|
|
176
|
+
const cacheKey = source.cache.key ?? `data:${sourceId}`;
|
|
177
|
+
const ttl = source.cache.ttl ??
|
|
178
|
+
(this.config.defaultCacheTtl ? this.config.defaultCacheTtl * 1000 : 60_000);
|
|
179
|
+
this.setCache(cacheKey, data, ttl);
|
|
180
|
+
}
|
|
181
|
+
return { success: true, data: data, metadata };
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
const duration = Date.now() - startTime;
|
|
185
|
+
const error = this.toFetchError(err, duration);
|
|
186
|
+
return { success: false, error };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
bindData(binding) {
|
|
190
|
+
const liveBinding = new DefaultLiveDataBinding(binding, this);
|
|
191
|
+
this.bindings.set(binding.id, liveBinding);
|
|
192
|
+
return liveBinding;
|
|
193
|
+
}
|
|
194
|
+
unregisterSource(sourceId) {
|
|
195
|
+
if (!this.sources.has(sourceId)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
this.sources.delete(sourceId);
|
|
199
|
+
// Invalidate cache
|
|
200
|
+
const cacheKey = `data:${sourceId}`;
|
|
201
|
+
this.cache.delete(cacheKey);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
getSourceIds() {
|
|
205
|
+
return Array.from(this.sources.keys());
|
|
206
|
+
}
|
|
207
|
+
clear() {
|
|
208
|
+
this.sources.clear();
|
|
209
|
+
this.cache.clear();
|
|
210
|
+
for (const binding of this.bindings.values()) {
|
|
211
|
+
binding.dispose();
|
|
212
|
+
}
|
|
213
|
+
this.bindings.clear();
|
|
214
|
+
}
|
|
215
|
+
// ============================================================
|
|
216
|
+
// PRIVATE METHODS
|
|
217
|
+
// ============================================================
|
|
218
|
+
async executeFetch(source, options) {
|
|
219
|
+
switch (source.kind) {
|
|
220
|
+
case 'static':
|
|
221
|
+
return source.staticData;
|
|
222
|
+
case 'state': {
|
|
223
|
+
const context = {
|
|
224
|
+
params: options?.params ?? {},
|
|
225
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
226
|
+
};
|
|
227
|
+
if (source.compute) {
|
|
228
|
+
return source.compute(context);
|
|
229
|
+
}
|
|
230
|
+
return source.staticData;
|
|
231
|
+
}
|
|
232
|
+
case 'computed': {
|
|
233
|
+
if (!source.compute) {
|
|
234
|
+
throw new Error(`Computed data source '${source.id}' has no compute function`);
|
|
235
|
+
}
|
|
236
|
+
const context = {
|
|
237
|
+
params: options?.params ?? {},
|
|
238
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
239
|
+
};
|
|
240
|
+
return source.compute(context);
|
|
241
|
+
}
|
|
242
|
+
case 'rest': {
|
|
243
|
+
if (!source.endpoint) {
|
|
244
|
+
throw new Error(`REST data source '${source.id}' has no endpoint`);
|
|
245
|
+
}
|
|
246
|
+
return this.fetchRest(source, options);
|
|
247
|
+
}
|
|
248
|
+
case 'graphql': {
|
|
249
|
+
if (!source.endpoint) {
|
|
250
|
+
throw new Error(`GraphQL data source '${source.id}' has no endpoint`);
|
|
251
|
+
}
|
|
252
|
+
return this.fetchRest(source, options);
|
|
253
|
+
}
|
|
254
|
+
default:
|
|
255
|
+
throw new Error(`Unknown data source kind: ${source.kind}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async fetchRest(source, options) {
|
|
259
|
+
const collection = source.endpoint;
|
|
260
|
+
// One record of the collection, when the call names it. The source's
|
|
261
|
+
// address is the collection; `<collection>/<id>` is how the platform (and
|
|
262
|
+
// every REST API worth the name) addresses a row of it.
|
|
263
|
+
const url = options?.recordId !== undefined
|
|
264
|
+
? `${collection.replace(/\/+$/, '')}/${encodeURIComponent(options.recordId)}`
|
|
265
|
+
: collection;
|
|
266
|
+
// A write uses the same address with a different verb — the source says
|
|
267
|
+
// how it is read, the call says what it is doing.
|
|
268
|
+
const method = options?.method ?? source.method ?? 'GET';
|
|
269
|
+
const headers = {
|
|
270
|
+
'Content-Type': 'application/json',
|
|
271
|
+
...source.headers,
|
|
272
|
+
...options?.headers,
|
|
273
|
+
};
|
|
274
|
+
// call it AS THE SIGNED-IN USER.
|
|
275
|
+
//
|
|
276
|
+
// Resolved per request and never cached: a delegated token expires within
|
|
277
|
+
// the hour, and a value held here would be the beginning of a credential
|
|
278
|
+
// living somewhere it can be serialised. A resolver that answers null
|
|
279
|
+
// sends the request WITHOUT the header — the server's own 401 is a truer
|
|
280
|
+
// error than a client-side guess, and it is what a panel's `errorWhen`
|
|
281
|
+
// already renders.
|
|
282
|
+
if (source.authProviderRef && this.config.getAccessToken) {
|
|
283
|
+
const token = await this.config.getAccessToken(source.authProviderRef);
|
|
284
|
+
if (token)
|
|
285
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
286
|
+
}
|
|
287
|
+
const timeout = options?.timeout ?? this.config.timeout;
|
|
288
|
+
const requestInit = {
|
|
289
|
+
method,
|
|
290
|
+
headers,
|
|
291
|
+
// Explicit on both sides. Our own API authenticates by cookie and is
|
|
292
|
+
// cross-origin from a published app, so it needs `include`; anyone
|
|
293
|
+
// else's endpoint must never receive one, and leaving this to fetch's
|
|
294
|
+
// `'same-origin'` default would be a decision nobody wrote down.
|
|
295
|
+
credentials: isPlatformOrigin(url, this.config.platformApiOrigin) ? 'include' : 'omit',
|
|
296
|
+
...(options?.signal ? { signal: options.signal } : {}),
|
|
297
|
+
};
|
|
298
|
+
if (options?.body && method !== 'GET') {
|
|
299
|
+
requestInit.body = JSON.stringify(options.body);
|
|
300
|
+
}
|
|
301
|
+
// Apply timeout if configured and no external signal provided
|
|
302
|
+
if (timeout && !options?.signal) {
|
|
303
|
+
requestInit.signal = AbortSignal.timeout(timeout);
|
|
304
|
+
}
|
|
305
|
+
const response = await fetch(url, requestInit);
|
|
306
|
+
if (!response.ok) {
|
|
307
|
+
// The server's own words travel with the refusal. Our API answers
|
|
308
|
+
// `{ error: { code, message } }`; anyone else's body is read the same
|
|
309
|
+
// way and ignored when it is not that shape. Before this the body was
|
|
310
|
+
// dropped, and a form could only report "it failed" and guess why.
|
|
311
|
+
const refusal = await readRefusal(response);
|
|
312
|
+
const error = new Error(refusal.message ?? `HTTP ${response.status}: ${response.statusText}`);
|
|
313
|
+
error.status = response.status;
|
|
314
|
+
if (refusal.code)
|
|
315
|
+
error.code = refusal.code;
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
const body = await response.json();
|
|
319
|
+
const rows = pluck(body, source.returns);
|
|
320
|
+
// Filter / sort / pagination are not sent (the endpoint does not speak
|
|
321
|
+
// them), so they are applied here: otherwise a REST source that declared
|
|
322
|
+
// `filter: status eq open` would show every row.
|
|
323
|
+
const data = source.query && Array.isArray(rows) ? applyEntityQuery(rows, source.query).items : rows;
|
|
324
|
+
return new RestAnswer(data, response.status, envelopeOf(body, source.returns));
|
|
325
|
+
}
|
|
326
|
+
getFromCache(key) {
|
|
327
|
+
const entry = this.cache.get(key);
|
|
328
|
+
if (!entry)
|
|
329
|
+
return undefined;
|
|
330
|
+
const elapsed = Date.now() - entry.timestamp;
|
|
331
|
+
if (elapsed > entry.ttl) {
|
|
332
|
+
this.cache.delete(key);
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
return entry.data;
|
|
336
|
+
}
|
|
337
|
+
setCache(key, data, ttl) {
|
|
338
|
+
this.cache.set(key, { data, timestamp: Date.now(), ttl });
|
|
339
|
+
}
|
|
340
|
+
toFetchError(err, _duration) {
|
|
341
|
+
if (err instanceof Error) {
|
|
342
|
+
if (err.name === 'AbortError') {
|
|
343
|
+
return { type: 'timeout', message: 'Request was aborted', cause: err };
|
|
344
|
+
}
|
|
345
|
+
if (err.message.includes('timeout')) {
|
|
346
|
+
return { type: 'timeout', message: err.message, cause: err };
|
|
347
|
+
}
|
|
348
|
+
const status = err.status;
|
|
349
|
+
if (status) {
|
|
350
|
+
const code = err.code;
|
|
351
|
+
return {
|
|
352
|
+
type: 'network',
|
|
353
|
+
message: err.message,
|
|
354
|
+
status,
|
|
355
|
+
...(code ? { code } : {}),
|
|
356
|
+
cause: err,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return { type: 'unknown', message: err.message, cause: err };
|
|
360
|
+
}
|
|
361
|
+
return { type: 'unknown', message: String(err), cause: err };
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// ============================================================
|
|
365
|
+
// DEFAULT LIVE DATA BINDING
|
|
366
|
+
// ============================================================
|
|
367
|
+
/**
|
|
368
|
+
* Default live data binding implementation
|
|
369
|
+
*/
|
|
370
|
+
class DefaultLiveDataBinding {
|
|
371
|
+
binding;
|
|
372
|
+
_state = 'idle';
|
|
373
|
+
_data = undefined;
|
|
374
|
+
_error = undefined;
|
|
375
|
+
listeners = new Set();
|
|
376
|
+
service;
|
|
377
|
+
constructor(binding, service) {
|
|
378
|
+
this.binding = binding;
|
|
379
|
+
this.service = service;
|
|
380
|
+
}
|
|
381
|
+
get state() {
|
|
382
|
+
return this._state;
|
|
383
|
+
}
|
|
384
|
+
get data() {
|
|
385
|
+
return this._data;
|
|
386
|
+
}
|
|
387
|
+
get error() {
|
|
388
|
+
return this._error;
|
|
389
|
+
}
|
|
390
|
+
async refresh() {
|
|
391
|
+
this.updateState('loading', this._data, undefined);
|
|
392
|
+
const result = await this.service.fetchData(this.binding.sourceId);
|
|
393
|
+
if (result.success) {
|
|
394
|
+
const transformed = this.binding.transform
|
|
395
|
+
? this.binding.transform(result.data)
|
|
396
|
+
: result.data;
|
|
397
|
+
this.updateState('loaded', transformed, undefined);
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
this.updateState('error', this._data, result.error);
|
|
401
|
+
}
|
|
402
|
+
return result;
|
|
403
|
+
}
|
|
404
|
+
subscribe(listener) {
|
|
405
|
+
this.listeners.add(listener);
|
|
406
|
+
return () => {
|
|
407
|
+
this.listeners.delete(listener);
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
dispose() {
|
|
411
|
+
this.listeners.clear();
|
|
412
|
+
this._data = undefined;
|
|
413
|
+
this._error = undefined;
|
|
414
|
+
this._state = 'idle';
|
|
415
|
+
}
|
|
416
|
+
updateState(state, data, error) {
|
|
417
|
+
this._state = state;
|
|
418
|
+
this._data = data;
|
|
419
|
+
this._error = error;
|
|
420
|
+
this.notifyListeners();
|
|
421
|
+
}
|
|
422
|
+
notifyListeners() {
|
|
423
|
+
for (const listener of this.listeners) {
|
|
424
|
+
try {
|
|
425
|
+
listener(this._data, this._state);
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
// Swallow listener errors to prevent cascading failures
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// ============================================================
|
|
434
|
+
// FACTORY FUNCTIONS
|
|
435
|
+
// ============================================================
|
|
436
|
+
/**
|
|
437
|
+
* Create a DataService instance with optional config
|
|
438
|
+
*/
|
|
439
|
+
export function createDataService(config) {
|
|
440
|
+
return new DataService(config);
|
|
441
|
+
}
|
|
442
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/data/manager.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Data Service (formerly Data Capability Manager)\n *\n * Default implementation of the data service\n *\n * Provides data source registration, resolution, fetch with caching,\n * and live data bindings for surfaces.\n *\n * @module @pwfabric/runtime/data/manager\n */\n\nimport { applyEntityQuery } from '../entity/apply-query.js'\nimport type {\n DataServiceInterface,\n DataServiceConfig,\n DataSourceDefinition,\n DataFetchOptions,\n DataFetchResult,\n DataFetchError,\n DataFetchMetadata,\n DataBinding,\n LiveDataBinding,\n DataBindingState,\n DataBindingListener,\n DataResolveContext,\n} from './types.js'\n\n// ============================================================\n// CACHE ENTRY\n// ============================================================\n\ninterface CacheEntry<T> {\n data: T\n timestamp: number\n ttl: number\n}\n\n// ============================================================\n// DATA SERVICE\n// ============================================================\n\n/**\n * The list, out of whatever the endpoint wrapped it in.\n *\n * `returns` names the property holding the array — `data` for ours,\n * `products` for DummyJSON, absent when the body already IS the array. Three\n * shapes were in circulation and the binding context received whichever one\n * came back, so a block bound to a list could not take all three.\n *\n * A named path that is not there THROWS rather than yielding undefined: a\n * silently empty panel is the failure this field exists to end, and \"the\n * endpoint answered, but not with what the plan said\" is a sentence the user\n * can act on.\n */\nfunction isPlatformOrigin(url: string, platformApiOrigin: string | undefined): boolean {\n if (!platformApiOrigin) return false\n try {\n // A relative endpoint is same-document, which is our own by definition.\n if (!/^[a-z][a-z0-9+.-]*:\\/\\//i.test(url)) return true\n return new URL(url).origin === new URL(platformApiOrigin).origin\n } catch {\n return false\n }\n}\n\n/**\n * What a non-OK response says about itself: `{ error: { code, message } }`\n * when it is our API (or anyone keeping the same envelope), nothing when the\n * body is absent, not JSON, or not that shape. Never throws — a refusal\n * without a readable body is still a refusal with a status.\n */\nasync function readRefusal(response: Response): Promise<{ code?: string; message?: string }> {\n try {\n const body = (await response.json()) as { error?: { code?: unknown; message?: unknown } } | null\n const error = body?.error\n if (!error || typeof error !== 'object') return {}\n return {\n ...(typeof error.code === 'string' ? { code: error.code } : {}),\n ...(typeof error.message === 'string' && error.message ? { message: error.message } : {}),\n }\n } catch {\n return {}\n }\n}\n\nfunction pluck(body: unknown, path: string | undefined): unknown {\n if (!path) return body\n let node: unknown = body\n for (const key of path.split('.')) {\n if (!node || typeof node !== 'object' || Array.isArray(node)) {\n throw new Error(`Response has no '${path}' — nothing at '${key}'`)\n }\n node = (node as Record<string, unknown>)[key]\n }\n if (node === undefined) throw new Error(`Response has no '${path}'`)\n return node\n}\n\n/**\n * What `returns` left behind: the body's top-level fields other than the one\n * the rows were plucked from. `{ products, total, skip, limit }` with\n * `returns: \"products\"` → `{ total, skip, limit }`; with `returns: \"data.items\"`\n * every top-level field except `data`.\n */\nfunction envelopeOf(body: unknown, path: string | undefined): Record<string, unknown> | undefined {\n if (!path || !body || typeof body !== 'object' || Array.isArray(body)) return undefined\n const head = path.split('.')[0] ?? path\n const rest: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(body as Record<string, unknown>)) {\n if (k !== head) rest[k] = v\n }\n return rest\n}\n\n/** A REST answer on its way out of `executeFetch`: rows plus what came with them. */\nclass RestAnswer {\n constructor(\n readonly data: unknown,\n readonly status: number,\n readonly envelope: Record<string, unknown> | undefined\n ) {}\n}\n\n/**\n * Default implementation of the DataServiceInterface.\n *\n * Accepts optional DataServiceConfig for default cache TTL, retry, and timeout.\n */\nexport class DataService implements DataServiceInterface {\n private readonly sources = new Map<string, DataSourceDefinition>()\n private readonly cache = new Map<string, CacheEntry<unknown>>()\n private readonly bindings = new Map<string, DefaultLiveDataBinding<unknown>>()\n readonly config: DataServiceConfig\n\n constructor(config?: DataServiceConfig) {\n this.config = config ?? {}\n }\n\n registerSource<T>(definition: DataSourceDefinition<T>): void {\n if (this.sources.has(definition.id)) {\n throw new Error(`Data source '${definition.id}' is already registered`)\n }\n this.sources.set(definition.id, definition as DataSourceDefinition)\n }\n\n resolveDataSource(sourceId: string): DataSourceDefinition | undefined {\n return this.sources.get(sourceId)\n }\n\n hasSource(sourceId: string): boolean {\n return this.sources.has(sourceId)\n }\n\n async fetchData<T>(sourceId: string, options?: DataFetchOptions): Promise<DataFetchResult<T>> {\n const source = this.sources.get(sourceId)\n if (!source) {\n return {\n success: false,\n error: {\n type: 'not_found',\n message: `Data source '${sourceId}' not found`,\n },\n }\n }\n\n // A write is never answered from the read cache, and never fills it: the\n // record a POST returns is not the list a reader asked for.\n const isWrite = (options?.method ?? source.method ?? 'GET') !== 'GET'\n\n // Check cache\n if (!isWrite && !options?.skipCache && source.cache?.enabled) {\n const cacheKey = source.cache.key ?? `data:${sourceId}`\n const cached = this.getFromCache<T>(cacheKey)\n if (cached !== undefined) {\n return {\n success: true,\n data: cached,\n metadata: {\n duration: 0,\n fromCache: true,\n timestamp: Date.now(),\n },\n }\n }\n }\n\n const startTime = Date.now()\n\n try {\n const fetched = await this.executeFetch(source, options)\n // A REST answer carries its status and envelope beside the rows.\n const rawData = fetched instanceof RestAnswer ? fetched.data : fetched\n const data = source.transform ? source.transform(rawData) : rawData\n\n const duration = Date.now() - startTime\n const metadata: DataFetchMetadata = {\n duration,\n fromCache: false,\n timestamp: Date.now(),\n ...(fetched instanceof RestAnswer\n ? { status: fetched.status, ...(fetched.envelope ? { envelope: fetched.envelope } : {}) }\n : {}),\n }\n\n // Store in cache — use source-level TTL, fall back to service config, then default\n if (!isWrite && source.cache?.enabled) {\n const cacheKey = source.cache.key ?? `data:${sourceId}`\n const ttl =\n source.cache.ttl ??\n (this.config.defaultCacheTtl ? this.config.defaultCacheTtl * 1000 : 60_000)\n this.setCache(cacheKey, data, ttl)\n }\n\n return { success: true, data: data as T, metadata }\n } catch (err) {\n const duration = Date.now() - startTime\n const error = this.toFetchError(err, duration)\n return { success: false, error }\n }\n }\n\n bindData<T>(binding: DataBinding<T>): LiveDataBinding<T> {\n const liveBinding = new DefaultLiveDataBinding<T>(binding, this)\n this.bindings.set(binding.id, liveBinding as DefaultLiveDataBinding<unknown>)\n return liveBinding\n }\n\n unregisterSource(sourceId: string): boolean {\n if (!this.sources.has(sourceId)) {\n return false\n }\n this.sources.delete(sourceId)\n // Invalidate cache\n const cacheKey = `data:${sourceId}`\n this.cache.delete(cacheKey)\n return true\n }\n\n getSourceIds(): readonly string[] {\n return Array.from(this.sources.keys())\n }\n\n clear(): void {\n this.sources.clear()\n this.cache.clear()\n for (const binding of this.bindings.values()) {\n binding.dispose()\n }\n this.bindings.clear()\n }\n\n // ============================================================\n // PRIVATE METHODS\n // ============================================================\n\n private async executeFetch(\n source: DataSourceDefinition,\n options?: DataFetchOptions\n ): Promise<unknown> {\n switch (source.kind) {\n case 'static':\n return source.staticData\n\n case 'state': {\n const context: DataResolveContext = {\n params: options?.params ?? {},\n ...(options?.signal ? { signal: options.signal } : {}),\n }\n if (source.compute) {\n return source.compute(context)\n }\n return source.staticData\n }\n\n case 'computed': {\n if (!source.compute) {\n throw new Error(`Computed data source '${source.id}' has no compute function`)\n }\n const context: DataResolveContext = {\n params: options?.params ?? {},\n ...(options?.signal ? { signal: options.signal } : {}),\n }\n return source.compute(context)\n }\n\n case 'rest': {\n if (!source.endpoint) {\n throw new Error(`REST data source '${source.id}' has no endpoint`)\n }\n return this.fetchRest(source, options)\n }\n\n case 'graphql': {\n if (!source.endpoint) {\n throw new Error(`GraphQL data source '${source.id}' has no endpoint`)\n }\n return this.fetchRest(source, options)\n }\n\n default:\n throw new Error(`Unknown data source kind: ${source.kind}`)\n }\n }\n\n private async fetchRest(\n source: DataSourceDefinition,\n options?: DataFetchOptions\n ): Promise<unknown> {\n const collection = source.endpoint as string\n // One record of the collection, when the call names it. The source's\n // address is the collection; `<collection>/<id>` is how the platform (and\n // every REST API worth the name) addresses a row of it.\n const url =\n options?.recordId !== undefined\n ? `${collection.replace(/\\/+$/, '')}/${encodeURIComponent(options.recordId)}`\n : collection\n // A write uses the same address with a different verb — the source says\n // how it is read, the call says what it is doing.\n const method = options?.method ?? source.method ?? 'GET'\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...source.headers,\n ...options?.headers,\n }\n\n // call it AS THE SIGNED-IN USER.\n //\n // Resolved per request and never cached: a delegated token expires within\n // the hour, and a value held here would be the beginning of a credential\n // living somewhere it can be serialised. A resolver that answers null\n // sends the request WITHOUT the header — the server's own 401 is a truer\n // error than a client-side guess, and it is what a panel's `errorWhen`\n // already renders.\n if (source.authProviderRef && this.config.getAccessToken) {\n const token = await this.config.getAccessToken(source.authProviderRef)\n if (token) headers['Authorization'] = `Bearer ${token}`\n }\n\n const timeout = options?.timeout ?? this.config.timeout\n\n const requestInit: RequestInit = {\n method,\n headers,\n // Explicit on both sides. Our own API authenticates by cookie and is\n // cross-origin from a published app, so it needs `include`; anyone\n // else's endpoint must never receive one, and leaving this to fetch's\n // `'same-origin'` default would be a decision nobody wrote down.\n credentials: isPlatformOrigin(url, this.config.platformApiOrigin) ? 'include' : 'omit',\n ...(options?.signal ? { signal: options.signal } : {}),\n }\n\n if (options?.body && method !== 'GET') {\n requestInit.body = JSON.stringify(options.body)\n }\n\n // Apply timeout if configured and no external signal provided\n if (timeout && !options?.signal) {\n requestInit.signal = AbortSignal.timeout(timeout)\n }\n\n const response = await fetch(url, requestInit)\n\n if (!response.ok) {\n // The server's own words travel with the refusal. Our API answers\n // `{ error: { code, message } }`; anyone else's body is read the same\n // way and ignored when it is not that shape. Before this the body was\n // dropped, and a form could only report \"it failed\" and guess why.\n const refusal = await readRefusal(response)\n const error = new Error(refusal.message ?? `HTTP ${response.status}: ${response.statusText}`)\n ;(error as Error & { status: number }).status = response.status\n if (refusal.code) (error as Error & { code: string }).code = refusal.code\n throw error\n }\n\n const body: unknown = await response.json()\n const rows = pluck(body, source.returns)\n // Filter / sort / pagination are not sent (the endpoint does not speak\n // them), so they are applied here: otherwise a REST source that declared\n // `filter: status eq open` would show every row.\n const data =\n source.query && Array.isArray(rows) ? applyEntityQuery(rows as Record<string, unknown>[], source.query).items : rows\n return new RestAnswer(data, response.status, envelopeOf(body, source.returns))\n }\n\n private getFromCache<T>(key: string): T | undefined {\n const entry = this.cache.get(key)\n if (!entry) return undefined\n\n const elapsed = Date.now() - entry.timestamp\n if (elapsed > entry.ttl) {\n this.cache.delete(key)\n return undefined\n }\n\n return entry.data as T\n }\n\n private setCache(key: string, data: unknown, ttl: number): void {\n this.cache.set(key, { data, timestamp: Date.now(), ttl })\n }\n\n private toFetchError(err: unknown, _duration?: number): DataFetchError {\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return { type: 'timeout', message: 'Request was aborted', cause: err }\n }\n if (err.message.includes('timeout')) {\n return { type: 'timeout', message: err.message, cause: err }\n }\n const status = (err as Error & { status?: number }).status\n if (status) {\n const code = (err as Error & { code?: string }).code\n return {\n type: 'network',\n message: err.message,\n status,\n ...(code ? { code } : {}),\n cause: err,\n }\n }\n return { type: 'unknown', message: err.message, cause: err }\n }\n return { type: 'unknown', message: String(err), cause: err }\n }\n}\n\n// ============================================================\n// DEFAULT LIVE DATA BINDING\n// ============================================================\n\n/**\n * Default live data binding implementation\n */\nclass DefaultLiveDataBinding<T> implements LiveDataBinding<T> {\n readonly binding: DataBinding<T>\n\n private _state: DataBindingState = 'idle'\n private _data: T | undefined = undefined\n private _error: DataFetchError | undefined = undefined\n private readonly listeners = new Set<DataBindingListener<T>>()\n private readonly service: DataService\n\n constructor(binding: DataBinding<T>, service: DataService) {\n this.binding = binding\n this.service = service\n }\n\n get state(): DataBindingState {\n return this._state\n }\n\n get data(): T | undefined {\n return this._data\n }\n\n get error(): DataFetchError | undefined {\n return this._error\n }\n\n async refresh(): Promise<DataFetchResult<T>> {\n this.updateState('loading', this._data, undefined)\n\n const result = await this.service.fetchData<T>(this.binding.sourceId)\n\n if (result.success) {\n const transformed = this.binding.transform\n ? (this.binding.transform(result.data) as T)\n : result.data\n this.updateState('loaded', transformed, undefined)\n } else {\n this.updateState('error', this._data, result.error)\n }\n\n return result\n }\n\n subscribe(listener: DataBindingListener<T>): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n dispose(): void {\n this.listeners.clear()\n this._data = undefined\n this._error = undefined\n this._state = 'idle'\n }\n\n private updateState(\n state: DataBindingState,\n data: T | undefined,\n error: DataFetchError | undefined\n ): void {\n this._state = state\n this._data = data\n this._error = error\n this.notifyListeners()\n }\n\n private notifyListeners(): void {\n for (const listener of this.listeners) {\n try {\n listener(this._data, this._state)\n } catch {\n // Swallow listener errors to prevent cascading failures\n }\n }\n }\n}\n\n// ============================================================\n// FACTORY FUNCTIONS\n// ============================================================\n\n/**\n * Create a DataService instance with optional config\n */\nexport function createDataService(config?: DataServiceConfig): DataServiceInterface {\n return new DataService(config)\n}\n\n"]}
|