@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 @@
|
|
|
1
|
+
{"version":3,"file":"data-binding-types.js","sourceRoot":"","sources":["../../src/render/data-binding-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG","sourcesContent":["/**\n * Data Binding Types for Surface Renderer\n *\n * Type definitions for data-bound surface rendering.\n * These types define the shape of surface JSON definitions that include\n * dataSources, state, and expression-based prop bindings.\n *\n * @module @pwfabric/runtime/render/data-binding-types\n */\n\nimport type { SurfaceDataCache } from './data-cache.js'\nimport type { Block } from '@pwfabric/core'\nimport type { RenderableSurface, SurfaceRendererProps } from './types.js'\nimport type { DataServiceConfig } from '../data/types.js'\n\n// ============================================================\n// DATA SOURCE DEFINITION (Surface JSON level)\n// ============================================================\n\n/**\n * Source kinds supported in surface JSON definitions.\n * Note: 'computed' is excluded because functions can't be serialized in JSON.\n */\nexport type SurfaceDataSourceKind = 'rest' | 'static' | 'state' | 'entity'\n\n/**\n * Data source definition within a surface JSON.\n * Unlike DataSourceDefinition from data/types, this is JSON-serializable.\n */\nexport interface SurfaceDataSourceDefinition {\n /** Unique source identifier, used as the key in expression context */\n readonly id: string\n /** Source kind */\n readonly kind: SurfaceDataSourceKind\n /** REST endpoint (may contain {{state.key}} placeholders) */\n readonly endpoint?: string\n /** HTTP method for REST sources */\n readonly method?: 'GET' | 'POST' | 'PUT' | 'DELETE'\n /** Static inline data */\n readonly staticData?: unknown\n /** Auto-refresh interval in ms (0 or undefined = no auto-refresh) */\n readonly refreshInterval?: number\n /** Optional HTTP headers to include in REST requests (e.g. Authorization) */\n readonly headers?: Readonly<Record<string, string>>\n /** Entity type to list (kind: 'entity') via the platform/entities facade */\n readonly entityType?: string\n /** Optional query for entity sources (filter/sort/pagination). String\n * leaves may carry `{{state.x}}` expressions — resolved against surface\n * state before the connector is called (see render/query-state.ts). */\n readonly query?: Readonly<Record<string, unknown>>\n /** Payload bound when the source cannot be served (connector missing /\n * failed). `__errors.<id>` stays populated alongside it. */\n readonly fallback?: unknown\n /**\n * Contract parity with `dataSourceSchema.connector`: which\n * data-plane engine serves this entity source (e.g. 'graph-lists').\n * Routed through the host-provided `ConnectorEngineRegistry` when the\n * connection is registered there; absent/unregistered = the default\n * entity facade path (today's behavior).\n */\n readonly connector?: string\n /**\n * The response property holding the list — `data`, `products`, or absent\n * when the body IS the array. Dotted paths allowed for a nested one.\n */\n readonly returns?: string\n /** world auth binding BY NAME — never credential values. */\n readonly authProviderRef?: string\n /**\n * Contract parity with `dataSourceSchema.aggregate`: ask for ONE\n * NUMBER over the collection instead of its rows. The source binds\n * `{ fn, field?, value, count }` — `{{<id>.value}}` — and is exempt from\n * the list wrap. `filter` leaves may carry `{{state.x}}` like `query`.\n */\n readonly aggregate?: {\n readonly fn: 'count' | 'sum' | 'avg'\n readonly field?: string\n readonly filter?: Readonly<Record<string, unknown>>\n }\n}\n\n// ============================================================\n// SURFACE ACTIONS\n// ============================================================\n\n/**\n * Action types that blocks can dispatch via their action prop.\n */\nexport type SurfaceActionType =\n | 'setState'\n | 'refresh'\n | 'refreshAll'\n | 'showBlock'\n | 'hideBlock'\n | 'navigate'\n\n/**\n * Action dispatched by a block (e.g. button click, tab change).\n */\nexport type SurfaceAction =\n | { readonly type: 'setState'; readonly key: string; readonly value: unknown }\n | { readonly type: 'refresh'; readonly sourceId: string }\n | { readonly type: 'refreshAll' }\n | { readonly type: 'showBlock'; readonly blockId: string }\n | { readonly type: 'hideBlock'; readonly blockId: string }\n | { readonly type: 'navigate'; readonly url: string }\n\n/**\n * What a block's `action` prop may hold: one action, or a list run in order\n * on the block's click (`block:click`) — never on its other events.\n */\nexport type BlockActionProp = SurfaceAction | readonly SurfaceAction[]\n\n/**\n * A behavior stored on a published surface — maps an event trigger to one or more actions.\n * The `event` field accepts canonical colon form (`block:click`) or legacy dot form\n * (`surface.load`) — normalized at match time by `canonicalizeEventType`.\n */\nexport interface RuntimeBehavior {\n readonly id: string\n readonly event: string\n readonly targetBlockId?: string\n readonly actions: readonly SurfaceAction[]\n}\n\n// ============================================================\n// DATA-BOUND SURFACE\n// ============================================================\n\n/**\n * Surface definition extended with data binding capabilities.\n * This is the shape of a surface JSON that uses live data.\n */\nexport interface DataBoundSurface {\n /**\n * Stable surface id — SurfaceRenderer's render scope.\n *\n * Omitted from this type originally, which made the drop invisible: the\n * renderer decomposes the surface into blocks/appearance/capabilities and\n * rebuilds it, and a field the type never had could not survive the rebuild.\n * Downstream, `[data-pw-surface]` rendered with an EMPTY value and every\n * per-surface rule (dark theme, visibility, interaction states) fell back to\n * the value-less selector shared by every surface in the document — the exact\n * collision the scoping was added to prevent.\n */\n readonly id?: string\n /** Block composition */\n readonly blocks: readonly Block[]\n /** ui-appearance atom reference (theme + layout + styleVariables consolidated). */\n readonly appearance?: RenderableSurface['appearance']\n /** capability list — `platform/persistence` carries dataSources + initialState in its config. */\n readonly capabilities?: RenderableSurface['capabilities']\n}\n\n// ============================================================\n// RENDERER PROPS\n// ============================================================\n\n/**\n * Props for DataBoundSurfaceRenderer.\n * Extends SurfaceRendererProps but accepts a DataBoundSurface.\n */\nexport interface DataBoundSurfaceRendererProps extends Omit<SurfaceRendererProps, 'surface'> {\n /** Surface definition with optional dataSources and state */\n readonly surface: DataBoundSurface\n /** Stored behaviors to execute when their trigger event fires on the bus */\n readonly behaviors?: readonly RuntimeBehavior[]\n /**\n * Intercept a `navigate` action before the default `window.location.assign`\n * fallback runs. Return `true` to signal the host handled it (e.g. swapped\n * to a sibling page in place, connected-preview style) — the default hard\n * navigation is skipped. Return `false`/`undefined` to fall through to the\n * default behavior.\n */\n readonly onNavigateAction?: (url: string) => boolean\n /**\n * What to render while the initial `fetchAllSources` pass is in flight.\n *\n * - `'gate'` (default) — the historical behavior: an all-or-nothing\n * \"Loading data...\" placeholder replaces the ENTIRE block tree until the\n * first fetch settles. Kept as the default so existing consumers (e.g.\n * published `/s/` pages) are byte-identical.\n * - `'immediate'` — render the block tree right away; expressions bound to\n * still-loading sources resolve to `undefined` (never a literal `{{…}}`\n * string — path expressions always evaluate successfully) and fill in as\n * each source lands. Static chrome (navbar, headings, footer) is visible\n * from the first frame. Pair with the `__loading.<sourceId>` /\n * `__anyLoading` expression keys to drive per-widget skeletons.\n */\n readonly loadingStrategy?: 'gate' | 'immediate'\n /**\n * Yesterday's rows while today's load. A source whose key (see\n * `dataSourceCacheKey`) is present is seeded from the cache on the first\n * frame and still re-fetched; the fresh result replaces the seed and is\n * written back. Pairs naturally with `loadingStrategy: 'immediate'`.\n */\n readonly dataCache?: SurfaceDataCache\n\n /**\n * Configuration for the DataService this renderer creates.\n *\n * IT WAS NEVER PASSED. `<DataCapabilityProvider>` was mounted bare, so the\n * service was built with no config — which means `getAccessToken` was always\n * undefined and every `authProviderRef` on every rest source resolved to\n * nobody. The field parsed, travelled and was read by a resolver that could\n * not exist. Threading it is the repair; it is also what lets a host say\n * which origin is its own, so our API can be called with a cookie and\n * nobody else's can.\n */\n readonly dataServiceConfig?: DataServiceConfig\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A host-provided cache for fetched source data — stale-while-revalidate.
|
|
3
|
+
*
|
|
4
|
+
* A page the visitor has already seen should not come back empty while its
|
|
5
|
+
* sources are re-fetched. The renderer knows nothing about where the cache
|
|
6
|
+
* lives (the published-app runner keeps it in sessionStorage, a host may keep
|
|
7
|
+
* it in memory); it only asks for a value by key on the first frame and
|
|
8
|
+
* writes the fresh result back after a successful fetch. A failed fetch never
|
|
9
|
+
* writes, so a good entry is not replaced by nothing.
|
|
10
|
+
*
|
|
11
|
+
* @module @pwfabric/runtime/render/data-cache
|
|
12
|
+
*/
|
|
13
|
+
import type { SurfaceDataSourceDefinition } from './data-binding-types.js';
|
|
14
|
+
export interface SurfaceDataCache {
|
|
15
|
+
/** The last successful result for this key, or undefined. */
|
|
16
|
+
get(key: string): unknown;
|
|
17
|
+
/** Remember a successful result. Must not throw on a full store. */
|
|
18
|
+
set(key: string, value: unknown): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The key a source's fetched data is remembered under.
|
|
22
|
+
*
|
|
23
|
+
* Built from everything that decides WHAT is fetched — kind, id, resolved address,
|
|
24
|
+
* entity type, connector, and the query with its `{{state.*}}` placeholders
|
|
25
|
+
* resolved against the current state — so page 2 never answers as page 1.
|
|
26
|
+
* The `fallback` payload is left out: sample rows are not part of what was
|
|
27
|
+
* fetched. `state` and `static` sources are never fetched, so they have no
|
|
28
|
+
* key.
|
|
29
|
+
*/
|
|
30
|
+
export declare function dataSourceCacheKey(source: SurfaceDataSourceDefinition, state: Readonly<Record<string, unknown>>): string | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { resolveQueryState } from './query-state.js';
|
|
2
|
+
import { endpointMissingPathValue, resolveEndpoint } from './endpoint.js';
|
|
3
|
+
/**
|
|
4
|
+
* The key a source's fetched data is remembered under.
|
|
5
|
+
*
|
|
6
|
+
* Built from everything that decides WHAT is fetched — kind, id, resolved address,
|
|
7
|
+
* entity type, connector, and the query with its `{{state.*}}` placeholders
|
|
8
|
+
* resolved against the current state — so page 2 never answers as page 1.
|
|
9
|
+
* The `fallback` payload is left out: sample rows are not part of what was
|
|
10
|
+
* fetched. `state` and `static` sources are never fetched, so they have no
|
|
11
|
+
* key.
|
|
12
|
+
*/
|
|
13
|
+
export function dataSourceCacheKey(source, state) {
|
|
14
|
+
if (source.kind === 'state' || source.kind === 'static')
|
|
15
|
+
return undefined;
|
|
16
|
+
// An idle source (a required path value is empty) is not fetched either.
|
|
17
|
+
if (source.kind === 'rest' && source.endpoint !== undefined && endpointMissingPathValue(source.endpoint, state)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const query = source.query ? resolveQueryState(source.query, state) : null;
|
|
21
|
+
const aggregate = source.aggregate ? resolveQueryState(source.aggregate, state) : null;
|
|
22
|
+
return JSON.stringify([
|
|
23
|
+
source.kind,
|
|
24
|
+
source.id,
|
|
25
|
+
// The RESOLVED address, not the template: with the template every
|
|
26
|
+
// `/products/{{state.id}}` shared one key, and `?id=13` opened on the
|
|
27
|
+
// rows cached for 12 until the fetch landed.
|
|
28
|
+
source.endpoint !== undefined ? resolveEndpoint(source.endpoint, state) : null,
|
|
29
|
+
source.method ?? null,
|
|
30
|
+
source.entityType ?? null,
|
|
31
|
+
source.connector ?? null,
|
|
32
|
+
source.returns ?? null,
|
|
33
|
+
query,
|
|
34
|
+
aggregate,
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=data-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-cache.js","sourceRoot":"","sources":["../../src/render/data-cache.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AASzE;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAmC,EACnC,KAAwC;IAExC,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IACzE,yEAAyE;IACzE,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,wBAAwB,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC;QAChH,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1E,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACtF,OAAO,IAAI,CAAC,SAAS,CAAC;QACpB,MAAM,CAAC,IAAI;QACX,MAAM,CAAC,EAAE;QACT,kEAAkE;QAClE,sEAAsE;QACtE,6CAA6C;QAC7C,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;QAC9E,MAAM,CAAC,MAAM,IAAI,IAAI;QACrB,MAAM,CAAC,UAAU,IAAI,IAAI;QACzB,MAAM,CAAC,SAAS,IAAI,IAAI;QACxB,MAAM,CAAC,OAAO,IAAI,IAAI;QACtB,KAAK;QACL,SAAS;KACV,CAAC,CAAA;AACJ,CAAC","sourcesContent":["/**\n * A host-provided cache for fetched source data — stale-while-revalidate.\n *\n * A page the visitor has already seen should not come back empty while its\n * sources are re-fetched. The renderer knows nothing about where the cache\n * lives (the published-app runner keeps it in sessionStorage, a host may keep\n * it in memory); it only asks for a value by key on the first frame and\n * writes the fresh result back after a successful fetch. A failed fetch never\n * writes, so a good entry is not replaced by nothing.\n *\n * @module @pwfabric/runtime/render/data-cache\n */\nimport type { SurfaceDataSourceDefinition } from './data-binding-types.js'\nimport { resolveQueryState } from './query-state.js'\nimport { endpointMissingPathValue, resolveEndpoint } from './endpoint.js'\n\nexport interface SurfaceDataCache {\n /** The last successful result for this key, or undefined. */\n get(key: string): unknown\n /** Remember a successful result. Must not throw on a full store. */\n set(key: string, value: unknown): void\n}\n\n/**\n * The key a source's fetched data is remembered under.\n *\n * Built from everything that decides WHAT is fetched — kind, id, resolved address,\n * entity type, connector, and the query with its `{{state.*}}` placeholders\n * resolved against the current state — so page 2 never answers as page 1.\n * The `fallback` payload is left out: sample rows are not part of what was\n * fetched. `state` and `static` sources are never fetched, so they have no\n * key.\n */\nexport function dataSourceCacheKey(\n source: SurfaceDataSourceDefinition,\n state: Readonly<Record<string, unknown>>\n): string | undefined {\n if (source.kind === 'state' || source.kind === 'static') return undefined\n // An idle source (a required path value is empty) is not fetched either.\n if (source.kind === 'rest' && source.endpoint !== undefined && endpointMissingPathValue(source.endpoint, state)) {\n return undefined\n }\n const query = source.query ? resolveQueryState(source.query, state) : null\n const aggregate = source.aggregate ? resolveQueryState(source.aggregate, state) : null\n return JSON.stringify([\n source.kind,\n source.id,\n // The RESOLVED address, not the template: with the template every\n // `/products/{{state.id}}` shared one key, and `?id=13` opened on the\n // rows cached for 12 until the fetch landed.\n source.endpoint !== undefined ? resolveEndpoint(source.endpoint, state) : null,\n source.method ?? null,\n source.entityType ?? null,\n source.connector ?? null,\n source.returns ?? null,\n query,\n aggregate,\n ])\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive a dark theme variant from a light SurfaceTheme.
|
|
3
|
+
*
|
|
4
|
+
* A colour palette is a preset the end user edits — not a reason to ship a
|
|
5
|
+
* separate appearance atom. The dark variant is DERIVED from the light palette
|
|
6
|
+
* rather than authored twice.
|
|
7
|
+
*
|
|
8
|
+
* The implementation lives in `@pwfabric/contracts`
|
|
9
|
+
* (`fabric/appearance-cascade`), because the derivation can run at WRITE
|
|
10
|
+
* time: an authoring tool may bake the result into
|
|
11
|
+
* `appearance.config.darkTheme` so the author can see and edit the dark palette
|
|
12
|
+
* instead of it being invisible render-time magic. Render still derives as the
|
|
13
|
+
* fallback for surfaces that carry no baked variant.
|
|
14
|
+
*
|
|
15
|
+
* @module @pwfabric/runtime/render/derive-dark-theme
|
|
16
|
+
*/
|
|
17
|
+
export { deriveDarkTheme } from '@pwfabric/contracts';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive a dark theme variant from a light SurfaceTheme.
|
|
3
|
+
*
|
|
4
|
+
* A colour palette is a preset the end user edits — not a reason to ship a
|
|
5
|
+
* separate appearance atom. The dark variant is DERIVED from the light palette
|
|
6
|
+
* rather than authored twice.
|
|
7
|
+
*
|
|
8
|
+
* The implementation lives in `@pwfabric/contracts`
|
|
9
|
+
* (`fabric/appearance-cascade`), because the derivation can run at WRITE
|
|
10
|
+
* time: an authoring tool may bake the result into
|
|
11
|
+
* `appearance.config.darkTheme` so the author can see and edit the dark palette
|
|
12
|
+
* instead of it being invisible render-time magic. Render still derives as the
|
|
13
|
+
* fallback for surfaces that carry no baked variant.
|
|
14
|
+
*
|
|
15
|
+
* @module @pwfabric/runtime/render/derive-dark-theme
|
|
16
|
+
*/
|
|
17
|
+
export { deriveDarkTheme } from '@pwfabric/contracts';
|
|
18
|
+
//# sourceMappingURL=derive-dark-theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive-dark-theme.js","sourceRoot":"","sources":["../../src/render/derive-dark-theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA","sourcesContent":["/**\n * Derive a dark theme variant from a light SurfaceTheme.\n *\n * A colour palette is a preset the end user edits — not a reason to ship a\n * separate appearance atom. The dark variant is DERIVED from the light palette\n * rather than authored twice.\n *\n * The implementation lives in `@pwfabric/contracts`\n * (`fabric/appearance-cascade`), because the derivation can run at WRITE\n * time: an authoring tool may bake the result into\n * `appearance.config.darkTheme` so the author can see and edit the dark palette\n * instead of it being invisible render-time magic. Render still derives as the\n * fallback for surfaces that carry no baked variant.\n *\n * @module @pwfabric/runtime/render/derive-dark-theme\n */\n\nexport { deriveDarkTheme } from '@pwfabric/contracts'\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic Artifact Context Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides dynamic block and starter descriptor maps
|
|
5
|
+
* to the component tree. Populated from the host's render grants.
|
|
6
|
+
*
|
|
7
|
+
* @module @pwfabric/runtime/render/dynamic-artifact-context
|
|
8
|
+
*/
|
|
9
|
+
import { type ReactNode } from 'react';
|
|
10
|
+
import type { DynamicBlockDescriptor, DynamicStarterDescriptor } from '@pwfabric/contracts';
|
|
11
|
+
export interface DynamicArtifactContextValue {
|
|
12
|
+
readonly dynamicBlocks: ReadonlyMap<string, DynamicBlockDescriptor>;
|
|
13
|
+
readonly dynamicStarters: ReadonlyMap<string, DynamicStarterDescriptor>;
|
|
14
|
+
readonly isLoaded: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface DynamicArtifactProviderProps {
|
|
17
|
+
readonly dynamicBlocks?: readonly DynamicBlockDescriptor[];
|
|
18
|
+
readonly dynamicStarters?: readonly DynamicStarterDescriptor[];
|
|
19
|
+
/**
|
|
20
|
+
* Whether the dynamic descriptors have finished loading. Server-rendered
|
|
21
|
+
* consumers (a published page) pass fully-resolved descriptors
|
|
22
|
+
* synchronously and omit this — it defaults to `true`. Client-side editors
|
|
23
|
+
* load descriptors from the host's aggregate after mount, so they pass the
|
|
24
|
+
* live loading state; consumers can then tell "still loading" apart from
|
|
25
|
+
* "genuinely no descriptor" instead of flashing every block as unknown on
|
|
26
|
+
* first paint.
|
|
27
|
+
*/
|
|
28
|
+
readonly isLoaded?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* WARM THE WHOLE CATALOG ON MOUNT. Off by default, and that default is the
|
|
31
|
+
* point.
|
|
32
|
+
*
|
|
33
|
+
* This provider typically sits in the host app's root layout, so it is
|
|
34
|
+
* mounted on every route. Preloading from there downloads, hash-verifies and
|
|
35
|
+
* blob-imports every descriptor the host owns on pages that render none of
|
|
36
|
+
* them — dozens of bundles and tens of seconds of summed download on a page
|
|
37
|
+
* with no renderer at all.
|
|
38
|
+
*
|
|
39
|
+
* Rendering a block does not need this: `DynamicBlockWrapper` asks the
|
|
40
|
+
* loader for the descriptor it is about to draw, and the loader caches and
|
|
41
|
+
* de-dupes by block type. Turn it on only where a canvas is genuinely about
|
|
42
|
+
* to render arbitrary blocks and the round trip should already be in flight
|
|
43
|
+
* — a studio or an editor, never a layout.
|
|
44
|
+
*/
|
|
45
|
+
readonly preload?: boolean;
|
|
46
|
+
readonly children: ReactNode;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Warm the loader's cache for a whole catalog, fire-and-forget.
|
|
50
|
+
*
|
|
51
|
+
* The imperative half of the same opt-in: a canvas that knows it is about to
|
|
52
|
+
* render arbitrary blocks can start the downloads itself, without the provider
|
|
53
|
+
* having to guess from the fact that it was mounted. Caching and de-duplication
|
|
54
|
+
* stay entirely the loader's business — calling this twice costs one download.
|
|
55
|
+
*/
|
|
56
|
+
export declare function preloadDynamicBlocks(descriptors: readonly DynamicBlockDescriptor[] | undefined | null): void;
|
|
57
|
+
/**
|
|
58
|
+
* Provides dynamic block and starter descriptors to the component tree.
|
|
59
|
+
*
|
|
60
|
+
* Downloads nothing by itself: blocks load lazily as they are rendered. Pass
|
|
61
|
+
* `preload` to warm the whole catalog on mount.
|
|
62
|
+
*/
|
|
63
|
+
export declare function DynamicArtifactProvider({ dynamicBlocks, dynamicStarters, isLoaded, preload, children, }: DynamicArtifactProviderProps): ReactNode;
|
|
64
|
+
/**
|
|
65
|
+
* Access dynamic block descriptors.
|
|
66
|
+
*/
|
|
67
|
+
export declare function useDynamicBlocks(): {
|
|
68
|
+
readonly blocks: ReadonlyMap<string, DynamicBlockDescriptor>;
|
|
69
|
+
readonly isLoaded: boolean;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Access dynamic starter descriptors.
|
|
73
|
+
*/
|
|
74
|
+
export declare function useDynamicStarters(): {
|
|
75
|
+
readonly starters: ReadonlyMap<string, DynamicStarterDescriptor>;
|
|
76
|
+
readonly isLoaded: boolean;
|
|
77
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Dynamic Artifact Context Provider
|
|
4
|
+
*
|
|
5
|
+
* Provides dynamic block and starter descriptor maps
|
|
6
|
+
* to the component tree. Populated from the host's render grants.
|
|
7
|
+
*
|
|
8
|
+
* @module @pwfabric/runtime/render/dynamic-artifact-context
|
|
9
|
+
*/
|
|
10
|
+
import { createContext, useContext, useEffect, useMemo } from 'react';
|
|
11
|
+
import { BlockModuleLoader } from './dynamic-block-loader.js';
|
|
12
|
+
// ============================================================
|
|
13
|
+
// CONTEXT
|
|
14
|
+
// ============================================================
|
|
15
|
+
const DynamicArtifactContext = createContext({
|
|
16
|
+
dynamicBlocks: new Map(),
|
|
17
|
+
dynamicStarters: new Map(),
|
|
18
|
+
isLoaded: false,
|
|
19
|
+
});
|
|
20
|
+
// ============================================================
|
|
21
|
+
// PROVIDER
|
|
22
|
+
// ============================================================
|
|
23
|
+
/**
|
|
24
|
+
* Warm the loader's cache for a whole catalog, fire-and-forget.
|
|
25
|
+
*
|
|
26
|
+
* The imperative half of the same opt-in: a canvas that knows it is about to
|
|
27
|
+
* render arbitrary blocks can start the downloads itself, without the provider
|
|
28
|
+
* having to guess from the fact that it was mounted. Caching and de-duplication
|
|
29
|
+
* stay entirely the loader's business — calling this twice costs one download.
|
|
30
|
+
*/
|
|
31
|
+
export function preloadDynamicBlocks(descriptors) {
|
|
32
|
+
if (!descriptors || descriptors.length === 0)
|
|
33
|
+
return;
|
|
34
|
+
BlockModuleLoader.preload(descriptors);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Provides dynamic block and starter descriptors to the component tree.
|
|
38
|
+
*
|
|
39
|
+
* Downloads nothing by itself: blocks load lazily as they are rendered. Pass
|
|
40
|
+
* `preload` to warm the whole catalog on mount.
|
|
41
|
+
*/
|
|
42
|
+
export function DynamicArtifactProvider({ dynamicBlocks, dynamicStarters, isLoaded = true, preload = false, children, }) {
|
|
43
|
+
const value = useMemo(() => {
|
|
44
|
+
const blockMap = new Map();
|
|
45
|
+
const starterMap = new Map();
|
|
46
|
+
if (dynamicBlocks) {
|
|
47
|
+
for (const block of dynamicBlocks) {
|
|
48
|
+
blockMap.set(block.blockType, block);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (dynamicStarters) {
|
|
52
|
+
for (const starter of dynamicStarters) {
|
|
53
|
+
starterMap.set(starter.starterId, starter);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
dynamicBlocks: blockMap,
|
|
58
|
+
dynamicStarters: starterMap,
|
|
59
|
+
isLoaded,
|
|
60
|
+
};
|
|
61
|
+
}, [dynamicBlocks, dynamicStarters, isLoaded]);
|
|
62
|
+
// In an effect, not in the memo above: a preload is a side effect and a
|
|
63
|
+
// render is not the place for one — inside `useMemo` it also ran during
|
|
64
|
+
// server rendering, where nothing can consume the result.
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!preload)
|
|
67
|
+
return;
|
|
68
|
+
preloadDynamicBlocks(dynamicBlocks);
|
|
69
|
+
}, [preload, dynamicBlocks]);
|
|
70
|
+
return _jsx(DynamicArtifactContext.Provider, { value: value, children: children });
|
|
71
|
+
}
|
|
72
|
+
// ============================================================
|
|
73
|
+
// HOOKS
|
|
74
|
+
// ============================================================
|
|
75
|
+
/**
|
|
76
|
+
* Access dynamic block descriptors.
|
|
77
|
+
*/
|
|
78
|
+
export function useDynamicBlocks() {
|
|
79
|
+
const ctx = useContext(DynamicArtifactContext);
|
|
80
|
+
return { blocks: ctx.dynamicBlocks, isLoaded: ctx.isLoaded };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Access dynamic starter descriptors.
|
|
84
|
+
*/
|
|
85
|
+
export function useDynamicStarters() {
|
|
86
|
+
const ctx = useContext(DynamicArtifactContext);
|
|
87
|
+
return { starters: ctx.dynamicStarters, isLoaded: ctx.isLoaded };
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=dynamic-artifact-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-artifact-context.js","sourceRoot":"","sources":["../../src/render/dynamic-artifact-context.tsx"],"names":[],"mappings":";AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAkB,MAAM,OAAO,CAAA;AAErF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AA6C7D,+DAA+D;AAC/D,UAAU;AACV,+DAA+D;AAE/D,MAAM,sBAAsB,GAAG,aAAa,CAA8B;IACxE,aAAa,EAAE,IAAI,GAAG,EAAE;IACxB,eAAe,EAAE,IAAI,GAAG,EAAE;IAC1B,QAAQ,EAAE,KAAK;CAChB,CAAC,CAAA;AAEF,+DAA+D;AAC/D,WAAW;AACX,+DAA+D;AAE/D;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,WAAiE;IAEjE,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IACpD,iBAAiB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;AACxC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,EACtC,aAAa,EACb,eAAe,EACf,QAAQ,GAAG,IAAI,EACf,OAAO,GAAG,KAAK,EACf,QAAQ,GACqB;IAC7B,MAAM,KAAK,GAAG,OAAO,CAA8B,GAAG,EAAE;QACtD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkC,CAAA;QAC1D,MAAM,UAAU,GAAG,IAAI,GAAG,EAAoC,CAAA;QAE9D,IAAI,aAAa,EAAE,CAAC;YAClB,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;gBAClC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;YACtC,CAAC;QACH,CAAC;QAED,IAAI,eAAe,EAAE,CAAC;YACpB,KAAK,MAAM,OAAO,IAAI,eAAe,EAAE,CAAC;gBACtC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;YAC5C,CAAC;QACH,CAAC;QAED,OAAO;YACL,aAAa,EAAE,QAAQ;YACvB,eAAe,EAAE,UAAU;YAC3B,QAAQ;SACT,CAAA;IACH,CAAC,EAAE,CAAC,aAAa,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE9C,wEAAwE;IACxE,wEAAwE;IACxE,0DAA0D;IAC1D,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,oBAAoB,CAAC,aAAa,CAAC,CAAA;IACrC,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAA;IAE5B,OAAO,KAAC,sBAAsB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAmC,CAAA;AACpG,CAAC;AAED,+DAA+D;AAC/D,QAAQ;AACR,+DAA+D;AAE/D;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAI9B,MAAM,GAAG,GAAG,UAAU,CAAC,sBAAsB,CAAC,CAAA;IAC9C,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,aAAa,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAA;AAC9D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB;IAIhC,MAAM,GAAG,GAAG,UAAU,CAAC,sBAAsB,CAAC,CAAA;IAC9C,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,eAAe,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAA;AAClE,CAAC","sourcesContent":["/**\n * Dynamic Artifact Context Provider\n *\n * Provides dynamic block and starter descriptor maps\n * to the component tree. Populated from the host's render grants.\n *\n * @module @pwfabric/runtime/render/dynamic-artifact-context\n */\n\nimport { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react'\nimport type { DynamicBlockDescriptor, DynamicStarterDescriptor } from '@pwfabric/contracts'\nimport { BlockModuleLoader } from './dynamic-block-loader.js'\n\n// ============================================================\n// TYPES\n// ============================================================\n\nexport interface DynamicArtifactContextValue {\n readonly dynamicBlocks: ReadonlyMap<string, DynamicBlockDescriptor>\n readonly dynamicStarters: ReadonlyMap<string, DynamicStarterDescriptor>\n readonly isLoaded: boolean\n}\n\nexport interface DynamicArtifactProviderProps {\n readonly dynamicBlocks?: readonly DynamicBlockDescriptor[]\n readonly dynamicStarters?: readonly DynamicStarterDescriptor[]\n /**\n * Whether the dynamic descriptors have finished loading. Server-rendered\n * consumers (a published page) pass fully-resolved descriptors\n * synchronously and omit this — it defaults to `true`. Client-side editors\n * load descriptors from the host's aggregate after mount, so they pass the\n * live loading state; consumers can then tell \"still loading\" apart from\n * \"genuinely no descriptor\" instead of flashing every block as unknown on\n * first paint.\n */\n readonly isLoaded?: boolean\n /**\n * WARM THE WHOLE CATALOG ON MOUNT. Off by default, and that default is the\n * point.\n *\n * This provider typically sits in the host app's root layout, so it is\n * mounted on every route. Preloading from there downloads, hash-verifies and\n * blob-imports every descriptor the host owns on pages that render none of\n * them — dozens of bundles and tens of seconds of summed download on a page\n * with no renderer at all.\n *\n * Rendering a block does not need this: `DynamicBlockWrapper` asks the\n * loader for the descriptor it is about to draw, and the loader caches and\n * de-dupes by block type. Turn it on only where a canvas is genuinely about\n * to render arbitrary blocks and the round trip should already be in flight\n * — a studio or an editor, never a layout.\n */\n readonly preload?: boolean\n readonly children: ReactNode\n}\n\n// ============================================================\n// CONTEXT\n// ============================================================\n\nconst DynamicArtifactContext = createContext<DynamicArtifactContextValue>({\n dynamicBlocks: new Map(),\n dynamicStarters: new Map(),\n isLoaded: false,\n})\n\n// ============================================================\n// PROVIDER\n// ============================================================\n\n/**\n * Warm the loader's cache for a whole catalog, fire-and-forget.\n *\n * The imperative half of the same opt-in: a canvas that knows it is about to\n * render arbitrary blocks can start the downloads itself, without the provider\n * having to guess from the fact that it was mounted. Caching and de-duplication\n * stay entirely the loader's business — calling this twice costs one download.\n */\nexport function preloadDynamicBlocks(\n descriptors: readonly DynamicBlockDescriptor[] | undefined | null\n): void {\n if (!descriptors || descriptors.length === 0) return\n BlockModuleLoader.preload(descriptors)\n}\n\n/**\n * Provides dynamic block and starter descriptors to the component tree.\n *\n * Downloads nothing by itself: blocks load lazily as they are rendered. Pass\n * `preload` to warm the whole catalog on mount.\n */\nexport function DynamicArtifactProvider({\n dynamicBlocks,\n dynamicStarters,\n isLoaded = true,\n preload = false,\n children,\n}: DynamicArtifactProviderProps): ReactNode {\n const value = useMemo<DynamicArtifactContextValue>(() => {\n const blockMap = new Map<string, DynamicBlockDescriptor>()\n const starterMap = new Map<string, DynamicStarterDescriptor>()\n\n if (dynamicBlocks) {\n for (const block of dynamicBlocks) {\n blockMap.set(block.blockType, block)\n }\n }\n\n if (dynamicStarters) {\n for (const starter of dynamicStarters) {\n starterMap.set(starter.starterId, starter)\n }\n }\n\n return {\n dynamicBlocks: blockMap,\n dynamicStarters: starterMap,\n isLoaded,\n }\n }, [dynamicBlocks, dynamicStarters, isLoaded])\n\n // In an effect, not in the memo above: a preload is a side effect and a\n // render is not the place for one — inside `useMemo` it also ran during\n // server rendering, where nothing can consume the result.\n useEffect(() => {\n if (!preload) return\n preloadDynamicBlocks(dynamicBlocks)\n }, [preload, dynamicBlocks])\n\n return <DynamicArtifactContext.Provider value={value}>{children}</DynamicArtifactContext.Provider>\n}\n\n// ============================================================\n// HOOKS\n// ============================================================\n\n/**\n * Access dynamic block descriptors.\n */\nexport function useDynamicBlocks(): {\n readonly blocks: ReadonlyMap<string, DynamicBlockDescriptor>\n readonly isLoaded: boolean\n} {\n const ctx = useContext(DynamicArtifactContext)\n return { blocks: ctx.dynamicBlocks, isLoaded: ctx.isLoaded }\n}\n\n/**\n * Access dynamic starter descriptors.\n */\nexport function useDynamicStarters(): {\n readonly starters: ReadonlyMap<string, DynamicStarterDescriptor>\n readonly isLoaded: boolean\n} {\n const ctx = useContext(DynamicArtifactContext)\n return { starters: ctx.dynamicStarters, isLoaded: ctx.isLoaded }\n}\n"]}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic Block Module Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads third-party block ESM modules at runtime via dynamic import().
|
|
5
|
+
* extends to first-party packs — the loader now SHA-256 hash
|
|
6
|
+
* checks the bundle bytes against `descriptor.integrity` before evaluation
|
|
7
|
+
* and discovers both the component and the BlockDefinition under stable
|
|
8
|
+
* export names (`Component` + `definition`) emitted by the bundle wrapper.
|
|
9
|
+
*
|
|
10
|
+
* @module @pwfabric/runtime/render/dynamic-block-loader
|
|
11
|
+
*/
|
|
12
|
+
import type { BlockDefinition } from '@pwfabric/core';
|
|
13
|
+
import type { DynamicBlockDescriptor } from '@pwfabric/contracts';
|
|
14
|
+
import type { BlockComponent } from './types.js';
|
|
15
|
+
export interface LoadedAtom {
|
|
16
|
+
readonly Component: BlockComponent;
|
|
17
|
+
readonly definition: BlockDefinition | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Singleton loader for dynamic block ESM modules.
|
|
21
|
+
*
|
|
22
|
+
* - Caches loaded modules by blockType
|
|
23
|
+
* - Deduplicates concurrent requests for the same module
|
|
24
|
+
* - Provides preload for fire-and-forget pre-fetching
|
|
25
|
+
*/
|
|
26
|
+
declare class BlockModuleLoaderImpl {
|
|
27
|
+
private readonly cache;
|
|
28
|
+
private readonly pending;
|
|
29
|
+
/**
|
|
30
|
+
* Load a dynamic block module and return its component.
|
|
31
|
+
*/
|
|
32
|
+
load(descriptor: DynamicBlockDescriptor): Promise<BlockComponent>;
|
|
33
|
+
/**
|
|
34
|
+
* load both the component and the BlockDefinition. Hash-checks
|
|
35
|
+
* the bundle bytes against `descriptor.integrity` before evaluation.
|
|
36
|
+
*/
|
|
37
|
+
loadAtom(descriptor: DynamicBlockDescriptor): Promise<LoadedAtom>;
|
|
38
|
+
/**
|
|
39
|
+
* Preload multiple block modules (fire-and-forget).
|
|
40
|
+
*/
|
|
41
|
+
preload(descriptors: readonly DynamicBlockDescriptor[]): void;
|
|
42
|
+
/**
|
|
43
|
+
* Check if a block module is already loaded.
|
|
44
|
+
*/
|
|
45
|
+
isLoaded(blockType: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Synchronously get a cached block component, or null if not yet loaded.
|
|
48
|
+
*/
|
|
49
|
+
getSync(blockType: string): BlockComponent | null;
|
|
50
|
+
/**
|
|
51
|
+
* Synchronously get the cached block definition, or null if not yet loaded
|
|
52
|
+
* (or the bundle did not export one).
|
|
53
|
+
*/
|
|
54
|
+
getDefinitionSync(blockType: string): BlockDefinition | null;
|
|
55
|
+
/**
|
|
56
|
+
* Invalidate cache for a specific block type or all blocks.
|
|
57
|
+
*/
|
|
58
|
+
invalidate(blockType?: string): void;
|
|
59
|
+
private doLoad;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* SHA-256 verify the fetched bundle bytes against `descriptor.integrity`.
|
|
63
|
+
*
|
|
64
|
+
* Accepted formats:
|
|
65
|
+
* - bare 64-char hex (the format `bundleHash` is stored in DB / listing
|
|
66
|
+
* metadata)
|
|
67
|
+
* - SRI-style `sha256-<base64>` (allowed for forward-compat with W3C
|
|
68
|
+
* subresource-integrity strings)
|
|
69
|
+
*
|
|
70
|
+
* Empty / missing integrity is treated as a hard failure — this is a
|
|
71
|
+
* security boundary, not a hint.
|
|
72
|
+
*
|
|
73
|
+
* Exported for unit tests; runtime callers should use `BlockModuleLoader.load`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function verifyIntegrity(bytes: ArrayBuffer, integrity: string, blockType: string): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Singleton instance
|
|
78
|
+
*/
|
|
79
|
+
export declare const BlockModuleLoader: BlockModuleLoaderImpl;
|
|
80
|
+
export {};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic Block Module Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads third-party block ESM modules at runtime via dynamic import().
|
|
5
|
+
* extends to first-party packs — the loader now SHA-256 hash
|
|
6
|
+
* checks the bundle bytes against `descriptor.integrity` before evaluation
|
|
7
|
+
* and discovers both the component and the BlockDefinition under stable
|
|
8
|
+
* export names (`Component` + `definition`) emitted by the bundle wrapper.
|
|
9
|
+
*
|
|
10
|
+
* @module @pwfabric/runtime/render/dynamic-block-loader
|
|
11
|
+
*/
|
|
12
|
+
// ============================================================
|
|
13
|
+
// MODULE LOADER
|
|
14
|
+
// ============================================================
|
|
15
|
+
/**
|
|
16
|
+
* Singleton loader for dynamic block ESM modules.
|
|
17
|
+
*
|
|
18
|
+
* - Caches loaded modules by blockType
|
|
19
|
+
* - Deduplicates concurrent requests for the same module
|
|
20
|
+
* - Provides preload for fire-and-forget pre-fetching
|
|
21
|
+
*/
|
|
22
|
+
class BlockModuleLoaderImpl {
|
|
23
|
+
cache = new Map();
|
|
24
|
+
pending = new Map();
|
|
25
|
+
/**
|
|
26
|
+
* Load a dynamic block module and return its component.
|
|
27
|
+
*/
|
|
28
|
+
async load(descriptor) {
|
|
29
|
+
const atom = await this.loadAtom(descriptor);
|
|
30
|
+
return atom.Component;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* load both the component and the BlockDefinition. Hash-checks
|
|
34
|
+
* the bundle bytes against `descriptor.integrity` before evaluation.
|
|
35
|
+
*/
|
|
36
|
+
async loadAtom(descriptor) {
|
|
37
|
+
// Check cache
|
|
38
|
+
const cached = this.cache.get(descriptor.blockType);
|
|
39
|
+
if (cached)
|
|
40
|
+
return { Component: cached.component, definition: cached.definition };
|
|
41
|
+
// Check pending (dedup concurrent requests)
|
|
42
|
+
const inflight = this.pending.get(descriptor.blockType);
|
|
43
|
+
if (inflight)
|
|
44
|
+
return inflight;
|
|
45
|
+
const promise = this.doLoad(descriptor);
|
|
46
|
+
this.pending.set(descriptor.blockType, promise);
|
|
47
|
+
try {
|
|
48
|
+
const atom = await promise;
|
|
49
|
+
this.cache.set(descriptor.blockType, {
|
|
50
|
+
component: atom.Component,
|
|
51
|
+
definition: atom.definition,
|
|
52
|
+
loadedAt: Date.now(),
|
|
53
|
+
});
|
|
54
|
+
return atom;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
this.pending.delete(descriptor.blockType);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Preload multiple block modules (fire-and-forget).
|
|
62
|
+
*/
|
|
63
|
+
preload(descriptors) {
|
|
64
|
+
for (const descriptor of descriptors) {
|
|
65
|
+
if (!this.cache.has(descriptor.blockType) && !this.pending.has(descriptor.blockType)) {
|
|
66
|
+
this.loadAtom(descriptor).catch(() => {
|
|
67
|
+
// Preload failure is non-fatal
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Check if a block module is already loaded.
|
|
74
|
+
*/
|
|
75
|
+
isLoaded(blockType) {
|
|
76
|
+
return this.cache.has(blockType);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Synchronously get a cached block component, or null if not yet loaded.
|
|
80
|
+
*/
|
|
81
|
+
getSync(blockType) {
|
|
82
|
+
return this.cache.get(blockType)?.component ?? null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Synchronously get the cached block definition, or null if not yet loaded
|
|
86
|
+
* (or the bundle did not export one).
|
|
87
|
+
*/
|
|
88
|
+
getDefinitionSync(blockType) {
|
|
89
|
+
return this.cache.get(blockType)?.definition ?? null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Invalidate cache for a specific block type or all blocks.
|
|
93
|
+
*/
|
|
94
|
+
invalidate(blockType) {
|
|
95
|
+
if (blockType) {
|
|
96
|
+
this.cache.delete(blockType);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.cache.clear();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async doLoad(descriptor) {
|
|
103
|
+
const LOAD_TIMEOUT_MS = 10_000;
|
|
104
|
+
const fetchAndImport = async () => {
|
|
105
|
+
// integrity is required before evaluation. We fetch the
|
|
106
|
+
// bundle, hash-verify the bytes against `descriptor.integrity`, and
|
|
107
|
+
// only then create a Blob URL and feed it to `import()`. Skipping
|
|
108
|
+
// this would let a CDN-side compromise execute attacker code under
|
|
109
|
+
// our origin.
|
|
110
|
+
const res = await fetch(descriptor.moduleUrl, { credentials: 'omit' });
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
throw new Error(`Bundle fetch failed for ${descriptor.blockType}: ${res.status}`);
|
|
113
|
+
}
|
|
114
|
+
const bytes = await res.arrayBuffer();
|
|
115
|
+
await verifyIntegrity(bytes, descriptor.integrity, descriptor.blockType);
|
|
116
|
+
const blob = new Blob([bytes], { type: 'text/javascript' });
|
|
117
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
118
|
+
try {
|
|
119
|
+
return (await import(/* webpackIgnore: true */ blobUrl));
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
URL.revokeObjectURL(blobUrl);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const loadPromise = fetchAndImport();
|
|
126
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Module load timed out after ${LOAD_TIMEOUT_MS / 1000}s`)), LOAD_TIMEOUT_MS));
|
|
127
|
+
const mod = await Promise.race([loadPromise, timeoutPromise]);
|
|
128
|
+
// wrapper convention emits `Component` + `definition`.
|
|
129
|
+
// Fall back to legacy export names so older bundles keep working.
|
|
130
|
+
const component = mod.Component ?? mod.default ?? mod.component ?? mod.render;
|
|
131
|
+
if (!component || typeof component !== 'function') {
|
|
132
|
+
throw new Error(`Module at ${descriptor.moduleUrl} does not export a valid block component`);
|
|
133
|
+
}
|
|
134
|
+
const definition = mod.definition && typeof mod.definition === 'object' ? mod.definition : null;
|
|
135
|
+
return { Component: component, definition };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// ============================================================
|
|
139
|
+
// HASH VERIFICATION
|
|
140
|
+
// ============================================================
|
|
141
|
+
/**
|
|
142
|
+
* SHA-256 verify the fetched bundle bytes against `descriptor.integrity`.
|
|
143
|
+
*
|
|
144
|
+
* Accepted formats:
|
|
145
|
+
* - bare 64-char hex (the format `bundleHash` is stored in DB / listing
|
|
146
|
+
* metadata)
|
|
147
|
+
* - SRI-style `sha256-<base64>` (allowed for forward-compat with W3C
|
|
148
|
+
* subresource-integrity strings)
|
|
149
|
+
*
|
|
150
|
+
* Empty / missing integrity is treated as a hard failure — this is a
|
|
151
|
+
* security boundary, not a hint.
|
|
152
|
+
*
|
|
153
|
+
* Exported for unit tests; runtime callers should use `BlockModuleLoader.load`.
|
|
154
|
+
*/
|
|
155
|
+
export async function verifyIntegrity(bytes, integrity, blockType) {
|
|
156
|
+
if (!integrity) {
|
|
157
|
+
throw new Error(`Missing integrity for ${blockType}; refusing to evaluate bundle`);
|
|
158
|
+
}
|
|
159
|
+
const computed = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));
|
|
160
|
+
const computedHex = bytesToHex(computed);
|
|
161
|
+
const expected = integrity.trim();
|
|
162
|
+
if (/^[0-9a-f]{64}$/i.test(expected)) {
|
|
163
|
+
if (computedHex.toLowerCase() !== expected.toLowerCase()) {
|
|
164
|
+
throw new Error(`Integrity check failed for ${blockType}`);
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const sriMatch = /^sha256-(.+)$/i.exec(expected);
|
|
169
|
+
if (sriMatch) {
|
|
170
|
+
const expectedB64 = sriMatch[1] ?? '';
|
|
171
|
+
const computedB64 = bytesToBase64(computed);
|
|
172
|
+
if (expectedB64.replace(/=+$/, '') !== computedB64.replace(/=+$/, '')) {
|
|
173
|
+
throw new Error(`Integrity check failed for ${blockType}`);
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
throw new Error(`Unrecognized integrity format for ${blockType}`);
|
|
178
|
+
}
|
|
179
|
+
function bytesToHex(bytes) {
|
|
180
|
+
let out = '';
|
|
181
|
+
for (const v of bytes) {
|
|
182
|
+
out += v.toString(16).padStart(2, '0');
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
function bytesToBase64(bytes) {
|
|
187
|
+
let bin = '';
|
|
188
|
+
for (const v of bytes) {
|
|
189
|
+
bin += String.fromCharCode(v);
|
|
190
|
+
}
|
|
191
|
+
return btoa(bin);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Singleton instance
|
|
195
|
+
*/
|
|
196
|
+
export const BlockModuleLoader = new BlockModuleLoaderImpl();
|
|
197
|
+
//# sourceMappingURL=dynamic-block-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-block-loader.js","sourceRoot":"","sources":["../../src/render/dynamic-block-loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Dynamic Block Module Loader\n *\n * Loads third-party block ESM modules at runtime via dynamic import().\n * extends to first-party packs — the loader now SHA-256 hash\n * checks the bundle bytes against `descriptor.integrity` before evaluation\n * and discovers both the component and the BlockDefinition under stable\n * export names (`Component` + `definition`) emitted by the bundle wrapper.\n *\n * @module @pwfabric/runtime/render/dynamic-block-loader\n */\n\nimport type { BlockDefinition } from '@pwfabric/core'\nimport type { DynamicBlockDescriptor } from '@pwfabric/contracts'\nimport type { BlockComponent } from './types.js'\n\n// ============================================================\n// TYPES\n// ============================================================\n\nexport interface LoadedAtom {\n readonly Component: BlockComponent\n readonly definition: BlockDefinition | null\n}\n\ninterface CachedModule {\n readonly component: BlockComponent\n readonly definition: BlockDefinition | null\n readonly loadedAt: number\n}\n\ninterface BlockModule {\n readonly default?: BlockComponent\n readonly component?: BlockComponent\n readonly render?: BlockComponent\n /** wrapper convention */\n readonly Component?: BlockComponent\n readonly definition?: BlockDefinition\n}\n\n// ============================================================\n// MODULE LOADER\n// ============================================================\n\n/**\n * Singleton loader for dynamic block ESM modules.\n *\n * - Caches loaded modules by blockType\n * - Deduplicates concurrent requests for the same module\n * - Provides preload for fire-and-forget pre-fetching\n */\nclass BlockModuleLoaderImpl {\n private readonly cache = new Map<string, CachedModule>()\n private readonly pending = new Map<string, Promise<LoadedAtom>>()\n\n /**\n * Load a dynamic block module and return its component.\n */\n async load(descriptor: DynamicBlockDescriptor): Promise<BlockComponent> {\n const atom = await this.loadAtom(descriptor)\n return atom.Component\n }\n\n /**\n * load both the component and the BlockDefinition. Hash-checks\n * the bundle bytes against `descriptor.integrity` before evaluation.\n */\n async loadAtom(descriptor: DynamicBlockDescriptor): Promise<LoadedAtom> {\n // Check cache\n const cached = this.cache.get(descriptor.blockType)\n if (cached) return { Component: cached.component, definition: cached.definition }\n\n // Check pending (dedup concurrent requests)\n const inflight = this.pending.get(descriptor.blockType)\n if (inflight) return inflight\n\n const promise = this.doLoad(descriptor)\n this.pending.set(descriptor.blockType, promise)\n\n try {\n const atom = await promise\n this.cache.set(descriptor.blockType, {\n component: atom.Component,\n definition: atom.definition,\n loadedAt: Date.now(),\n })\n return atom\n } finally {\n this.pending.delete(descriptor.blockType)\n }\n }\n\n /**\n * Preload multiple block modules (fire-and-forget).\n */\n preload(descriptors: readonly DynamicBlockDescriptor[]): void {\n for (const descriptor of descriptors) {\n if (!this.cache.has(descriptor.blockType) && !this.pending.has(descriptor.blockType)) {\n this.loadAtom(descriptor).catch(() => {\n // Preload failure is non-fatal\n })\n }\n }\n }\n\n /**\n * Check if a block module is already loaded.\n */\n isLoaded(blockType: string): boolean {\n return this.cache.has(blockType)\n }\n\n /**\n * Synchronously get a cached block component, or null if not yet loaded.\n */\n getSync(blockType: string): BlockComponent | null {\n return this.cache.get(blockType)?.component ?? null\n }\n\n /**\n * Synchronously get the cached block definition, or null if not yet loaded\n * (or the bundle did not export one).\n */\n getDefinitionSync(blockType: string): BlockDefinition | null {\n return this.cache.get(blockType)?.definition ?? null\n }\n\n /**\n * Invalidate cache for a specific block type or all blocks.\n */\n invalidate(blockType?: string): void {\n if (blockType) {\n this.cache.delete(blockType)\n } else {\n this.cache.clear()\n }\n }\n\n private async doLoad(descriptor: DynamicBlockDescriptor): Promise<LoadedAtom> {\n const LOAD_TIMEOUT_MS = 10_000\n\n const fetchAndImport = async (): Promise<BlockModule> => {\n // integrity is required before evaluation. We fetch the\n // bundle, hash-verify the bytes against `descriptor.integrity`, and\n // only then create a Blob URL and feed it to `import()`. Skipping\n // this would let a CDN-side compromise execute attacker code under\n // our origin.\n const res = await fetch(descriptor.moduleUrl, { credentials: 'omit' })\n if (!res.ok) {\n throw new Error(`Bundle fetch failed for ${descriptor.blockType}: ${res.status}`)\n }\n const bytes = await res.arrayBuffer()\n await verifyIntegrity(bytes, descriptor.integrity, descriptor.blockType)\n\n const blob = new Blob([bytes], { type: 'text/javascript' })\n const blobUrl = URL.createObjectURL(blob)\n try {\n return (await import(/* webpackIgnore: true */ blobUrl)) as BlockModule\n } finally {\n URL.revokeObjectURL(blobUrl)\n }\n }\n\n const loadPromise = fetchAndImport()\n const timeoutPromise = new Promise<never>((_, reject) =>\n setTimeout(\n () => reject(new Error(`Module load timed out after ${LOAD_TIMEOUT_MS / 1000}s`)),\n LOAD_TIMEOUT_MS\n )\n )\n\n const mod = await Promise.race([loadPromise, timeoutPromise])\n\n // wrapper convention emits `Component` + `definition`.\n // Fall back to legacy export names so older bundles keep working.\n const component = mod.Component ?? mod.default ?? mod.component ?? mod.render\n if (!component || typeof component !== 'function') {\n throw new Error(`Module at ${descriptor.moduleUrl} does not export a valid block component`)\n }\n const definition: BlockDefinition | null =\n mod.definition && typeof mod.definition === 'object' ? mod.definition : null\n\n return { Component: component, definition }\n }\n}\n\n// ============================================================\n// HASH VERIFICATION\n// ============================================================\n\n/**\n * SHA-256 verify the fetched bundle bytes against `descriptor.integrity`.\n *\n * Accepted formats:\n * - bare 64-char hex (the format `bundleHash` is stored in DB / listing\n * metadata)\n * - SRI-style `sha256-<base64>` (allowed for forward-compat with W3C\n * subresource-integrity strings)\n *\n * Empty / missing integrity is treated as a hard failure — this is a\n * security boundary, not a hint.\n *\n * Exported for unit tests; runtime callers should use `BlockModuleLoader.load`.\n */\nexport async function verifyIntegrity(\n bytes: ArrayBuffer,\n integrity: string,\n blockType: string\n): Promise<void> {\n if (!integrity) {\n throw new Error(`Missing integrity for ${blockType}; refusing to evaluate bundle`)\n }\n const computed = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes))\n const computedHex = bytesToHex(computed)\n const expected = integrity.trim()\n\n if (/^[0-9a-f]{64}$/i.test(expected)) {\n if (computedHex.toLowerCase() !== expected.toLowerCase()) {\n throw new Error(`Integrity check failed for ${blockType}`)\n }\n return\n }\n\n const sriMatch = /^sha256-(.+)$/i.exec(expected)\n if (sriMatch) {\n const expectedB64 = sriMatch[1] ?? ''\n const computedB64 = bytesToBase64(computed)\n if (expectedB64.replace(/=+$/, '') !== computedB64.replace(/=+$/, '')) {\n throw new Error(`Integrity check failed for ${blockType}`)\n }\n return\n }\n\n throw new Error(`Unrecognized integrity format for ${blockType}`)\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n let out = ''\n for (const v of bytes) {\n out += v.toString(16).padStart(2, '0')\n }\n return out\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let bin = ''\n for (const v of bytes) {\n bin += String.fromCharCode(v)\n }\n return btoa(bin)\n}\n\n/**\n * Singleton instance\n */\nexport const BlockModuleLoader = new BlockModuleLoaderImpl()\n"]}
|