@repobit/dex-store-elements 1.7.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +5 -905
  2. package/README.md +9 -7
  3. package/dist/src/actions/action.adapter.d.ts +36 -0
  4. package/dist/src/actions/action.adapter.js +183 -0
  5. package/dist/src/actions/action.adapter.js.map +1 -0
  6. package/dist/src/actions/action.attributes.d.ts +15 -0
  7. package/dist/src/actions/action.attributes.js +137 -0
  8. package/dist/src/actions/action.attributes.js.map +1 -0
  9. package/dist/src/actions/action.registry.d.ts +4 -0
  10. package/dist/src/actions/action.registry.js +119 -0
  11. package/dist/src/actions/action.registry.js.map +1 -0
  12. package/dist/src/actions/index.d.ts +3 -2
  13. package/dist/src/actions/index.js +3 -80
  14. package/dist/src/actions/index.js.map +1 -1
  15. package/dist/src/compute/compute.expand.d.ts +70 -0
  16. package/dist/src/compute/compute.expand.js +319 -0
  17. package/dist/src/compute/compute.expand.js.map +1 -0
  18. package/dist/src/compute/compute.scan.d.ts +54 -0
  19. package/dist/src/compute/compute.scan.js +160 -0
  20. package/dist/src/compute/compute.scan.js.map +1 -0
  21. package/dist/src/compute/option-resolver.d.ts +31 -0
  22. package/dist/src/compute/option-resolver.js +147 -0
  23. package/dist/src/compute/option-resolver.js.map +1 -0
  24. package/dist/src/contexts/context.compute.d.ts +12 -0
  25. package/dist/src/contexts/context.compute.js +4 -0
  26. package/dist/src/contexts/context.compute.js.map +1 -0
  27. package/dist/src/contexts/context.scope.d.ts +139 -0
  28. package/dist/src/contexts/context.scope.js +48 -0
  29. package/dist/src/contexts/context.scope.js.map +1 -0
  30. package/dist/src/controllers/controller.action-resolve.d.ts +110 -0
  31. package/dist/src/controllers/controller.action-resolve.js +203 -0
  32. package/dist/src/controllers/controller.action-resolve.js.map +1 -0
  33. package/dist/src/controllers/controller.action-topology.d.ts +59 -0
  34. package/dist/src/controllers/controller.action-topology.js +130 -0
  35. package/dist/src/controllers/controller.action-topology.js.map +1 -0
  36. package/dist/src/controllers/controller.compute.d.ts +72 -0
  37. package/dist/src/controllers/controller.compute.js +110 -0
  38. package/dist/src/controllers/controller.compute.js.map +1 -0
  39. package/dist/src/controllers/controller.resolve.d.ts +78 -0
  40. package/dist/src/controllers/controller.resolve.js +205 -0
  41. package/dist/src/controllers/controller.resolve.js.map +1 -0
  42. package/dist/src/core/action-topology.d.ts +62 -0
  43. package/dist/src/core/action-topology.js +67 -0
  44. package/dist/src/core/action-topology.js.map +1 -0
  45. package/dist/src/core/actions.d.ts +64 -0
  46. package/dist/src/core/actions.js +47 -0
  47. package/dist/src/core/actions.js.map +1 -0
  48. package/dist/src/core/node.element.d.ts +179 -0
  49. package/dist/src/core/node.element.js +573 -0
  50. package/dist/src/core/node.element.js.map +1 -0
  51. package/dist/src/core/node.scope.d.ts +374 -0
  52. package/dist/src/core/node.scope.js +558 -0
  53. package/dist/src/core/node.scope.js.map +1 -0
  54. package/dist/src/core/option-update.d.ts +11 -0
  55. package/dist/src/core/option-update.js +59 -0
  56. package/dist/src/core/option-update.js.map +1 -0
  57. package/dist/src/dsl/compilers/array/compiler.js +13 -14
  58. package/dist/src/dsl/compilers/array/compiler.js.map +1 -1
  59. package/dist/src/dsl/compilers/boolean/compiler.d.ts +1 -1
  60. package/dist/src/dsl/compilers/boolean/compiler.js +14 -27
  61. package/dist/src/dsl/compilers/boolean/compiler.js.map +1 -1
  62. package/dist/src/dsl/compilers/enum/compiler.js +7 -10
  63. package/dist/src/dsl/compilers/enum/compiler.js.map +1 -1
  64. package/dist/src/dsl/compilers/index.d.ts +1 -1
  65. package/dist/src/dsl/compilers/index.js +5 -5
  66. package/dist/src/dsl/compilers/index.js.map +1 -1
  67. package/dist/src/dsl/utilty.d.ts +0 -4
  68. package/dist/src/dsl/utilty.js +3 -30
  69. package/dist/src/dsl/utilty.js.map +1 -1
  70. package/dist/src/eta/batch.d.ts +123 -0
  71. package/dist/src/eta/batch.js +162 -0
  72. package/dist/src/eta/batch.js.map +1 -0
  73. package/dist/src/eta/batcher.d.ts +184 -0
  74. package/dist/src/eta/batcher.js +335 -0
  75. package/dist/src/eta/batcher.js.map +1 -0
  76. package/dist/src/eta/cache.d.ts +36 -0
  77. package/dist/src/eta/cache.js +56 -0
  78. package/dist/src/eta/cache.js.map +1 -0
  79. package/dist/src/eta/controller.d.ts +608 -0
  80. package/dist/src/eta/controller.js +1036 -0
  81. package/dist/src/eta/controller.js.map +1 -0
  82. package/dist/src/eta/factory.d.ts +74 -0
  83. package/dist/src/eta/factory.js +121 -0
  84. package/dist/src/eta/factory.js.map +1 -0
  85. package/dist/src/eta/idle.d.ts +39 -0
  86. package/dist/src/eta/idle.js +52 -0
  87. package/dist/src/eta/idle.js.map +1 -0
  88. package/dist/src/eta/index.d.ts +52 -0
  89. package/dist/src/eta/index.js +13 -0
  90. package/dist/src/eta/index.js.map +1 -0
  91. package/dist/src/eta/observer.d.ts +263 -0
  92. package/dist/src/eta/observer.js +263 -0
  93. package/dist/src/eta/observer.js.map +1 -0
  94. package/dist/src/eta/operations.d.ts +41 -0
  95. package/dist/src/eta/operations.js +59 -0
  96. package/dist/src/eta/operations.js.map +1 -0
  97. package/dist/src/eta/source-registry.d.ts +198 -0
  98. package/dist/src/eta/source-registry.js +267 -0
  99. package/dist/src/eta/source-registry.js.map +1 -0
  100. package/dist/src/eta/template-index.d.ts +285 -0
  101. package/dist/src/eta/template-index.js +642 -0
  102. package/dist/src/eta/template-index.js.map +1 -0
  103. package/dist/src/eta/types.d.ts +529 -0
  104. package/dist/src/eta/types.js +2 -0
  105. package/dist/src/eta/types.js.map +1 -0
  106. package/dist/src/index.d.ts +21 -9
  107. package/dist/src/index.js +22 -11
  108. package/dist/src/index.js.map +1 -1
  109. package/dist/src/nodes/node.context.d.ts +5 -3
  110. package/dist/src/nodes/node.context.js +48 -3
  111. package/dist/src/nodes/node.context.js.map +1 -1
  112. package/dist/src/nodes/node.option.d.ts +11 -31
  113. package/dist/src/nodes/node.option.js +158 -222
  114. package/dist/src/nodes/node.option.js.map +1 -1
  115. package/dist/src/nodes/node.product.d.ts +7 -11
  116. package/dist/src/nodes/node.product.js +85 -64
  117. package/dist/src/nodes/node.product.js.map +1 -1
  118. package/dist/src/renders/attributes/buyLink.d.ts +2 -2
  119. package/dist/src/renders/attributes/buyLink.js +14 -11
  120. package/dist/src/renders/attributes/buyLink.js.map +1 -1
  121. package/dist/src/renders/attributes/devices.d.ts +2 -2
  122. package/dist/src/renders/attributes/devices.js +9 -19
  123. package/dist/src/renders/attributes/devices.js.map +1 -1
  124. package/dist/src/renders/attributes/discount.d.ts +2 -2
  125. package/dist/src/renders/attributes/discount.js +56 -44
  126. package/dist/src/renders/attributes/discount.js.map +1 -1
  127. package/dist/src/renders/attributes/hide.d.ts +2 -2
  128. package/dist/src/renders/attributes/hide.js +49 -14
  129. package/dist/src/renders/attributes/hide.js.map +1 -1
  130. package/dist/src/renders/attributes/index.d.ts +3 -3
  131. package/dist/src/renders/attributes/index.js +2 -4
  132. package/dist/src/renders/attributes/index.js.map +1 -1
  133. package/dist/src/renders/attributes/price.d.ts +2 -2
  134. package/dist/src/renders/attributes/price.js +30 -44
  135. package/dist/src/renders/attributes/price.js.map +1 -1
  136. package/dist/src/renders/attributes/subscription.d.ts +2 -2
  137. package/dist/src/renders/attributes/subscription.js +14 -20
  138. package/dist/src/renders/attributes/subscription.js.map +1 -1
  139. package/dist/src/renders/attributes/utilty.d.ts +2 -0
  140. package/dist/src/renders/attributes/utilty.js +17 -4
  141. package/dist/src/renders/attributes/utilty.js.map +1 -1
  142. package/dist/src/renders/context.d.ts +122 -10
  143. package/dist/src/renders/context.js +140 -80
  144. package/dist/src/renders/context.js.map +1 -1
  145. package/dist/src/renders/format.js +2 -2
  146. package/dist/src/renders/format.js.map +1 -1
  147. package/dist/src/renders/index.d.ts +6 -2
  148. package/dist/src/renders/index.js +67 -29
  149. package/dist/src/renders/index.js.map +1 -1
  150. package/dist/src/renders/observe.d.ts +1 -4
  151. package/dist/src/renders/observe.js +10 -24
  152. package/dist/src/renders/observe.js.map +1 -1
  153. package/dist/src/renders/render.wrapper.d.ts +37 -0
  154. package/dist/src/renders/render.wrapper.js +147 -0
  155. package/dist/src/renders/render.wrapper.js.map +1 -0
  156. package/dist/src/templating/eta.js +1 -4
  157. package/dist/src/templating/eta.js.map +1 -1
  158. package/package.json +4 -5
  159. package/dist/src/actions/action.button.d.ts +0 -2
  160. package/dist/src/actions/action.button.js +0 -8
  161. package/dist/src/actions/action.button.js.map +0 -1
  162. package/dist/src/actions/action.input.d.ts +0 -2
  163. package/dist/src/actions/action.input.js +0 -43
  164. package/dist/src/actions/action.input.js.map +0 -1
  165. package/dist/src/actions/action.select.d.ts +0 -2
  166. package/dist/src/actions/action.select.js +0 -14
  167. package/dist/src/actions/action.select.js.map +0 -1
  168. package/dist/src/actions/utilty.d.ts +0 -16
  169. package/dist/src/actions/utilty.js +0 -149
  170. package/dist/src/actions/utilty.js.map +0 -1
  171. package/dist/src/contexts/context.datalayer.d.ts +0 -9
  172. package/dist/src/contexts/context.datalayer.js +0 -3
  173. package/dist/src/contexts/context.datalayer.js.map +0 -1
  174. package/dist/src/contexts/context.derived.d.ts +0 -12
  175. package/dist/src/contexts/context.derived.js +0 -3
  176. package/dist/src/contexts/context.derived.js.map +0 -1
  177. package/dist/src/contexts/context.option.d.ts +0 -5
  178. package/dist/src/contexts/context.option.js +0 -3
  179. package/dist/src/contexts/context.option.js.map +0 -1
  180. package/dist/src/contexts/context.product.d.ts +0 -5
  181. package/dist/src/contexts/context.product.js +0 -3
  182. package/dist/src/contexts/context.product.js.map +0 -1
  183. package/dist/src/contexts/context.state.d.ts +0 -28
  184. package/dist/src/contexts/context.state.js +0 -3
  185. package/dist/src/contexts/context.state.js.map +0 -1
  186. package/dist/src/contexts/context.store.d.ts +0 -5
  187. package/dist/src/contexts/context.store.js +0 -3
  188. package/dist/src/contexts/context.store.js.map +0 -1
  189. package/dist/src/controllers/collect-controller.d.ts +0 -39
  190. package/dist/src/controllers/collect-controller.js +0 -161
  191. package/dist/src/controllers/collect-controller.js.map +0 -1
  192. package/dist/src/controllers/compute-controller.d.ts +0 -55
  193. package/dist/src/controllers/compute-controller.js +0 -350
  194. package/dist/src/controllers/compute-controller.js.map +0 -1
  195. package/dist/src/controllers/eta-renderer/eta-dom-renderer.d.ts +0 -25
  196. package/dist/src/controllers/eta-renderer/eta-dom-renderer.js +0 -192
  197. package/dist/src/controllers/eta-renderer/eta-dom-renderer.js.map +0 -1
  198. package/dist/src/controllers/eta-renderer/eta-mutation-observer.d.ts +0 -28
  199. package/dist/src/controllers/eta-renderer/eta-mutation-observer.js +0 -206
  200. package/dist/src/controllers/eta-renderer/eta-mutation-observer.js.map +0 -1
  201. package/dist/src/controllers/eta-renderer/eta-renderer.d.ts +0 -21
  202. package/dist/src/controllers/eta-renderer/eta-renderer.js +0 -77
  203. package/dist/src/controllers/eta-renderer/eta-renderer.js.map +0 -1
  204. package/dist/src/controllers/eta-renderer/eta-template-cache.d.ts +0 -24
  205. package/dist/src/controllers/eta-renderer/eta-template-cache.js +0 -61
  206. package/dist/src/controllers/eta-renderer/eta-template-cache.js.map +0 -1
  207. package/dist/src/controllers/eta-renderer/eta-types.d.ts +0 -11
  208. package/dist/src/controllers/eta-renderer/eta-types.js +0 -1
  209. package/dist/src/controllers/eta-renderer/eta-types.js.map +0 -1
  210. package/dist/src/controllers/eta-renderer/index.d.ts +0 -2
  211. package/dist/src/controllers/eta-renderer/index.js +0 -2
  212. package/dist/src/controllers/eta-renderer/index.js.map +0 -1
  213. package/dist/src/controllers/event-pipeline-controller.d.ts +0 -28
  214. package/dist/src/controllers/event-pipeline-controller.js +0 -72
  215. package/dist/src/controllers/event-pipeline-controller.js.map +0 -1
  216. package/dist/src/events/events.d.ts +0 -75
  217. package/dist/src/events/events.js +0 -78
  218. package/dist/src/events/events.js.map +0 -1
  219. package/dist/src/nodes/node.root.d.ts +0 -16
  220. package/dist/src/nodes/node.root.js +0 -49
  221. package/dist/src/nodes/node.root.js.map +0 -1
  222. package/dist/src/nodes/node.state.d.ts +0 -88
  223. package/dist/src/nodes/node.state.js +0 -330
  224. package/dist/src/nodes/node.state.js.map +0 -1
  225. package/dist/src/renders/utility.d.ts +0 -32
  226. package/dist/src/renders/utility.js +0 -191
  227. package/dist/src/renders/utility.js.map +0 -1
@@ -0,0 +1,110 @@
1
+ // src/controllers/controller.compute.ts
2
+ import { expandScenarios, reducePriceRange } from '../compute/compute.expand.js';
3
+ import { mergeComputeScans } from '../compute/compute.scan.js';
4
+ import { scenarioKey } from '../compute/option-resolver.js';
5
+ import { BdResolveController } from './controller.resolve.js';
6
+ export const BD_COMPUTE_CHANGED_EVENT = 'bd-compute-changed';
7
+ /**
8
+ * Dry-run min/max price compute over every product/option combination reachable
9
+ * from a bd node boundary.
10
+ *
11
+ * It never dispatches bd-action-request. It reads resolved option seeds and
12
+ * collected action declarations from topology, expands them against the same
13
+ * command shapes the runtime would honor, and reduces price. Reruns are driven
14
+ * by the store scope, the resolved seed set, and the action topology revision.
15
+ */
16
+ export class BdComputeController {
17
+ constructor(host, options) {
18
+ this.host = host;
19
+ this.options = options;
20
+ host.addController(this);
21
+ this._resolver = new BdResolveController(host, {
22
+ name: options.name ?? 'bd-compute',
23
+ args: () => [
24
+ this.options.store(),
25
+ this._enabled(),
26
+ optionSeedsKey(this.host.collectOptionSeeds()),
27
+ this._revision()
28
+ ],
29
+ enabled: ([store, enabled]) => Boolean(store) &&
30
+ enabled,
31
+ resolve: async ([store], context) => await this._compute(store, context),
32
+ commit: (result) => {
33
+ this._publish(result);
34
+ },
35
+ onDisabled: () => {
36
+ this._publish(undefined);
37
+ }
38
+ });
39
+ }
40
+ hostConnected() {
41
+ // BdResolveController owns compute scheduling.
42
+ }
43
+ hostDisconnected() {
44
+ // BdResolveController owns compute cancellation.
45
+ }
46
+ get pending() {
47
+ return this._resolver.pending;
48
+ }
49
+ get complete() {
50
+ return this._resolver.complete;
51
+ }
52
+ get failed() {
53
+ return this._resolver.failed;
54
+ }
55
+ get error() {
56
+ return this._resolver.error;
57
+ }
58
+ async run() {
59
+ return await this._resolver.run();
60
+ }
61
+ async _compute(store, context) {
62
+ if (!store) {
63
+ return undefined;
64
+ }
65
+ const seeds = this.host.collectOptionSeeds()
66
+ .map(optionToScenario);
67
+ if (seeds.length === 0) {
68
+ return undefined;
69
+ }
70
+ const scan = mergeComputeScans(this.host.collectActionScans());
71
+ const scenarios = await expandScenarios({
72
+ store,
73
+ seeds,
74
+ staticCommands: scan.staticCommands,
75
+ domains: scan.domains,
76
+ signal: context.signal,
77
+ maxScenarios: this.options.maxScenarios
78
+ });
79
+ return reducePriceRange(scenarios);
80
+ }
81
+ _publish(result) {
82
+ this.result = result;
83
+ this.options.onResult?.(result);
84
+ this.host.dispatchEvent(new CustomEvent(BD_COMPUTE_CHANGED_EVENT, {
85
+ bubbles: true,
86
+ composed: true,
87
+ detail: { result }
88
+ }));
89
+ }
90
+ _revision() {
91
+ return (this.options.revision?.() ??
92
+ this.host.actionTopologyRevision);
93
+ }
94
+ _enabled() {
95
+ return this.options.enabled?.() ?? true;
96
+ }
97
+ }
98
+ function optionToScenario(option) {
99
+ return {
100
+ product: option.getProduct(),
101
+ option
102
+ };
103
+ }
104
+ function optionSeedsKey(options) {
105
+ return options
106
+ .map((option) => scenarioKey(optionToScenario(option)))
107
+ .sort()
108
+ .join(';');
109
+ }
110
+ //# sourceMappingURL=controller.compute.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.compute.js","sourceRoot":"","sources":["../../../src/controllers/controller.compute.ts"],"names":[],"mappings":"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","sourcesContent":["// src/controllers/controller.compute.ts\n\nimport {\n expandScenarios,\n reducePriceRange,\n type BdComputeResult\n} from '@/compute/compute.expand.js';\nimport {\n mergeComputeScans,\n type BdComputeScan\n} from '@/compute/compute.scan.js';\nimport {\n scenarioKey,\n type BdOptionScenario\n} from '@/compute/option-resolver.js';\nimport type {\n ProductOption,\n Store\n} from '@repobit/dex-store';\nimport type { ReactiveController } from 'lit';\nimport {\n BdResolveController,\n type BdResolveContext,\n type BdResolveHost\n} from './controller.resolve.js';\n\nexport const BD_COMPUTE_CHANGED_EVENT = 'bd-compute-changed' as const;\n\nexport type BdComputeChangedDetail = Readonly<{\n result?: BdComputeResult;\n}>;\n\ndeclare global {\n interface HTMLElementEventMap {\n [BD_COMPUTE_CHANGED_EVENT]: CustomEvent<BdComputeChangedDetail>;\n }\n}\n\n/**\n * Host requirements for the compute controller. Any BdNodeElement satisfies\n * this: it tracks local work, is an EventTarget, can query its own subtree, and\n * exposes the incremental action topology.\n */\nexport interface BdComputeHost extends BdResolveHost, EventTarget, ParentNode {\n readonly actionTopologyRevision: number;\n collectActionScans(): BdComputeScan[];\n collectOptionSeeds(): ProductOption[];\n}\n\nexport interface BdComputeOptions {\n name?: string;\n\n /**\n * The store used to resolve products. Usually `() => this.scope?.store`.\n */\n store: () => Store | undefined;\n\n /**\n * Global/local compute switch. Disabled compute publishes an undefined result.\n */\n enabled?: () => boolean;\n\n /**\n * Extra change signal mixed into the task args. Defaults to the host's action\n * topology revision, which already bumps on action and bd-subtree changes.\n */\n revision?: () => number;\n\n /**\n * Safety cap on the fixed-point search size.\n */\n maxScenarios?: number;\n\n onResult?: (result: BdComputeResult | undefined) => void;\n}\n\ntype BdComputeArgs = readonly [\n store: Store | undefined,\n enabled: boolean,\n seedsKey: string,\n revision: number\n];\n\n/**\n * Dry-run min/max price compute over every product/option combination reachable\n * from a bd node boundary.\n *\n * It never dispatches bd-action-request. It reads resolved option seeds and\n * collected action declarations from topology, expands them against the same\n * command shapes the runtime would honor, and reduces price. Reruns are driven\n * by the store scope, the resolved seed set, and the action topology revision.\n */\nexport class BdComputeController implements ReactiveController {\n result?: BdComputeResult;\n\n private readonly _resolver: BdResolveController<BdComputeArgs, BdComputeResult | undefined>;\n\n constructor(\n private readonly host: BdComputeHost,\n private readonly options: BdComputeOptions\n ) {\n host.addController(this);\n\n this._resolver = new BdResolveController<BdComputeArgs, BdComputeResult | undefined>(\n host,\n {\n name: options.name ?? 'bd-compute',\n\n args: () =>\n [\n this.options.store(),\n this._enabled(),\n optionSeedsKey(this.host.collectOptionSeeds()),\n this._revision()\n ] as const,\n\n enabled: ([store, enabled]) =>\n Boolean(store) &&\n enabled,\n\n resolve: async ([store], context) =>\n await this._compute(store, context),\n\n commit: (result) => {\n this._publish(result);\n },\n\n onDisabled: () => {\n this._publish(undefined);\n }\n }\n );\n }\n\n hostConnected(): void {\n // BdResolveController owns compute scheduling.\n }\n\n hostDisconnected(): void {\n // BdResolveController owns compute cancellation.\n }\n\n get pending(): boolean {\n return this._resolver.pending;\n }\n\n get complete(): boolean {\n return this._resolver.complete;\n }\n\n get failed(): boolean {\n return this._resolver.failed;\n }\n\n get error(): unknown {\n return this._resolver.error;\n }\n\n async run(): Promise<BdComputeResult | undefined> {\n return await this._resolver.run();\n }\n\n private async _compute(\n store: Store | undefined,\n context: BdResolveContext\n ): Promise<BdComputeResult | undefined> {\n if (!store) {\n return undefined;\n }\n\n const seeds =\n this.host.collectOptionSeeds()\n .map(optionToScenario);\n\n if (seeds.length === 0) {\n return undefined;\n }\n\n const scan =\n mergeComputeScans(this.host.collectActionScans());\n\n const scenarios =\n await expandScenarios({\n store,\n seeds,\n staticCommands: scan.staticCommands,\n domains : scan.domains,\n signal : context.signal,\n maxScenarios : this.options.maxScenarios\n });\n\n return reducePriceRange(scenarios);\n }\n\n private _publish(\n result: BdComputeResult | undefined\n ): void {\n this.result = result;\n this.options.onResult?.(result);\n\n this.host.dispatchEvent(\n new CustomEvent<BdComputeChangedDetail>(\n BD_COMPUTE_CHANGED_EVENT,\n {\n bubbles : true,\n composed: true,\n detail : { result }\n }\n )\n );\n }\n\n private _revision(): number {\n return (\n this.options.revision?.() ??\n this.host.actionTopologyRevision\n );\n }\n\n private _enabled(): boolean {\n return this.options.enabled?.() ?? true;\n }\n}\n\nfunction optionToScenario(\n option: ProductOption\n): BdOptionScenario {\n return {\n product: option.getProduct(),\n option\n };\n}\n\nfunction optionSeedsKey(\n options: readonly ProductOption[]\n): string {\n return options\n .map((option) =>\n scenarioKey(optionToScenario(option))\n )\n .sort()\n .join(';');\n}\n"]}
@@ -0,0 +1,78 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from 'lit';
2
+ import { type TaskStatus as LitTaskStatus } from '@lit/task';
3
+ export type MaybePromise<T> = T | Promise<T>;
4
+ export type BdResolveStatus = 'initial' | 'disabled' | 'pending' | 'complete' | 'error';
5
+ export type BdResolveHookPhase = 'pending' | 'disabled' | 'complete' | 'error';
6
+ export type BdResolveContext = Readonly<{
7
+ signal: AbortSignal;
8
+ runId: number;
9
+ }>;
10
+ export interface BdResolveHost extends ReactiveControllerHost {
11
+ trackLocalWork<T>(key: unknown, work: PromiseLike<T>): Promise<T>;
12
+ }
13
+ export type BdWorkKey = object | symbol;
14
+ export interface BdResolveOptions<TArgs extends readonly unknown[], TResult> {
15
+ /**
16
+ * Optional debug name.
17
+ *
18
+ * This is not used as the tracking key.
19
+ */
20
+ name?: string;
21
+ /**
22
+ * Advanced escape hatch.
23
+ *
24
+ * Usually you should not set this.
25
+ * By default, the controller instance itself is used as the work key.
26
+ */
27
+ workKey?: BdWorkKey;
28
+ args: () => TArgs;
29
+ enabled?: (args: TArgs) => boolean;
30
+ argsEqual?: (oldArgs: TArgs, newArgs: TArgs) => boolean;
31
+ autoRun?: boolean | 'afterUpdate';
32
+ resolve: (args: TArgs, context: BdResolveContext) => MaybePromise<TResult>;
33
+ commit?: (result: TResult, args: TArgs, context: BdResolveContext) => MaybePromise<void>;
34
+ onPending?: (args: TArgs, context: BdResolveContext) => MaybePromise<void>;
35
+ onDisabled?: (args: TArgs, context: BdResolveContext) => MaybePromise<void>;
36
+ onComplete?: (result: TResult, args: TArgs, context: BdResolveContext) => MaybePromise<void>;
37
+ onError?: (error: unknown, args: TArgs, context: BdResolveContext) => MaybePromise<void>;
38
+ onHookError?: (error: unknown, phase: BdResolveHookPhase) => void;
39
+ abortOnDisconnect?: boolean;
40
+ }
41
+ export declare class BdResolveController<TArgs extends readonly unknown[], TResult> implements ReactiveController {
42
+ private readonly host;
43
+ private readonly options;
44
+ status: BdResolveStatus;
45
+ value?: TResult;
46
+ error?: unknown;
47
+ readonly name: string;
48
+ private readonly _workKey;
49
+ private readonly _task;
50
+ private _activeRunId;
51
+ constructor(host: BdResolveHost, options: BdResolveOptions<TArgs, TResult>);
52
+ hostDisconnected(): void;
53
+ get litTaskStatus(): LitTaskStatus;
54
+ get pending(): boolean;
55
+ get complete(): boolean;
56
+ get disabled(): boolean;
57
+ get failed(): boolean;
58
+ /**
59
+ * Manual execution.
60
+ *
61
+ * Useful if options.autoRun === false.
62
+ */
63
+ run(args?: TArgs): Promise<TResult | undefined>;
64
+ /**
65
+ * Abort the current task.
66
+ *
67
+ * This also invalidates the current controller runId so an old async result
68
+ * cannot later commit stale data.
69
+ */
70
+ abort(reason?: unknown): void;
71
+ private _startRunId;
72
+ private _isCurrentRun;
73
+ private _run;
74
+ private _runDisabled;
75
+ private _runEnabled;
76
+ private _callHook;
77
+ }
78
+ export declare function throwIfAborted(signal: AbortSignal): void;
@@ -0,0 +1,205 @@
1
+ import { Task, initialState } from '@lit/task';
2
+ export class BdResolveController {
3
+ constructor(host, options) {
4
+ this.host = host;
5
+ this.options = options;
6
+ this.status = 'initial';
7
+ this._activeRunId = 0;
8
+ this.name =
9
+ options.name ??
10
+ 'bd-resolve';
11
+ /**
12
+ * By default, each controller instance is its own unique work key.
13
+ * This allows the controller to manage its own work without risk of conflict.
14
+ */
15
+ this._workKey =
16
+ options.workKey ??
17
+ this;
18
+ host.addController(this);
19
+ this._task = new Task(host, {
20
+ args: options.args,
21
+ autoRun: options.autoRun ?? true,
22
+ argsEqual: options.argsEqual,
23
+ task: (args, { signal }) => {
24
+ const runId = this._startRunId();
25
+ const context = {
26
+ signal,
27
+ runId
28
+ };
29
+ const work = this._run(args, context);
30
+ return this.host.trackLocalWork(this._workKey, work);
31
+ }
32
+ });
33
+ }
34
+ hostDisconnected() {
35
+ if (this.options.abortOnDisconnect ?? true) {
36
+ this.abort(createAbortError('BdResolveController host disconnected.'));
37
+ }
38
+ }
39
+ get litTaskStatus() {
40
+ return this._task.status;
41
+ }
42
+ get pending() {
43
+ return this.status === 'pending';
44
+ }
45
+ get complete() {
46
+ return this.status === 'complete';
47
+ }
48
+ get disabled() {
49
+ return this.status === 'disabled';
50
+ }
51
+ get failed() {
52
+ return this.status === 'error';
53
+ }
54
+ /**
55
+ * Manual execution.
56
+ *
57
+ * Useful if options.autoRun === false.
58
+ */
59
+ async run(args) {
60
+ await this._task.run(args);
61
+ if (this.status === 'error') {
62
+ throw this.error;
63
+ }
64
+ return this.value;
65
+ }
66
+ /**
67
+ * Abort the current task.
68
+ *
69
+ * This also invalidates the current controller runId so an old async result
70
+ * cannot later commit stale data.
71
+ */
72
+ abort(reason) {
73
+ this._activeRunId += 1;
74
+ this._task.abort(reason ??
75
+ createAbortError(`${this.name} aborted.`));
76
+ this.status = 'initial';
77
+ this.value = undefined;
78
+ this.error = undefined;
79
+ /**
80
+ * Replace this work slot with a completed promise.
81
+ *
82
+ * This prevents updateComplete from being blocked forever by an obsolete
83
+ * task that ignores AbortSignal.
84
+ */
85
+ this.host.trackLocalWork(this._workKey, Promise.resolve(undefined));
86
+ this.host.requestUpdate();
87
+ }
88
+ _startRunId() {
89
+ this._activeRunId += 1;
90
+ return this._activeRunId;
91
+ }
92
+ _isCurrentRun(context) {
93
+ return context.runId === this._activeRunId;
94
+ }
95
+ async _run(args, context) {
96
+ try {
97
+ const enabled = this.options.enabled?.(args) ?? true;
98
+ if (!enabled) {
99
+ return await this._runDisabled(args, context);
100
+ }
101
+ return await this._runEnabled(args, context);
102
+ }
103
+ catch (error) {
104
+ if (context.signal.aborted ||
105
+ !this._isCurrentRun(context)) {
106
+ return initialState;
107
+ }
108
+ this.status = 'error';
109
+ this.error = error;
110
+ this.value = undefined;
111
+ this.host.requestUpdate();
112
+ await this._callHook('error', () => this.options.onError?.(error, args, context));
113
+ /**
114
+ * Re-throw so @lit/task also enters ERROR state.
115
+ */
116
+ throw error;
117
+ }
118
+ }
119
+ async _runDisabled(args, context) {
120
+ if (!this._isCurrentRun(context)) {
121
+ return initialState;
122
+ }
123
+ this.status = 'disabled';
124
+ this.value = undefined;
125
+ this.error = undefined;
126
+ this.host.requestUpdate();
127
+ throwIfAborted(context.signal);
128
+ if (!this._isCurrentRun(context)) {
129
+ return initialState;
130
+ }
131
+ await this._callHook('disabled', () => this.options.onDisabled?.(args, context));
132
+ throwIfAborted(context.signal);
133
+ if (!this._isCurrentRun(context)) {
134
+ return initialState;
135
+ }
136
+ this.host.requestUpdate();
137
+ return initialState;
138
+ }
139
+ async _runEnabled(args, context) {
140
+ if (!this._isCurrentRun(context)) {
141
+ return initialState;
142
+ }
143
+ this.status = 'pending';
144
+ this.value = undefined;
145
+ this.error = undefined;
146
+ this.host.requestUpdate();
147
+ throwIfAborted(context.signal);
148
+ if (!this._isCurrentRun(context)) {
149
+ return initialState;
150
+ }
151
+ await this._callHook('pending', () => this.options.onPending?.(args, context));
152
+ throwIfAborted(context.signal);
153
+ if (!this._isCurrentRun(context)) {
154
+ return initialState;
155
+ }
156
+ const result = await this.options.resolve(args, context);
157
+ throwIfAborted(context.signal);
158
+ if (!this._isCurrentRun(context)) {
159
+ return initialState;
160
+ }
161
+ await this.options.commit?.(result, args, context);
162
+ throwIfAborted(context.signal);
163
+ if (!this._isCurrentRun(context)) {
164
+ return initialState;
165
+ }
166
+ this.status = 'complete';
167
+ this.value = result;
168
+ this.error = undefined;
169
+ this.host.requestUpdate();
170
+ await this._callHook('complete', () => this.options.onComplete?.(result, args, context));
171
+ throwIfAborted(context.signal);
172
+ if (!this._isCurrentRun(context)) {
173
+ return initialState;
174
+ }
175
+ return result;
176
+ }
177
+ async _callHook(phase, callback) {
178
+ try {
179
+ await callback();
180
+ }
181
+ catch (error) {
182
+ this.options.onHookError?.(error, phase);
183
+ }
184
+ }
185
+ }
186
+ export function throwIfAborted(signal) {
187
+ if (!signal.aborted) {
188
+ return;
189
+ }
190
+ if (typeof signal.throwIfAborted === 'function') {
191
+ signal.throwIfAborted();
192
+ return;
193
+ }
194
+ throw (signal.reason ??
195
+ createAbortError('The operation was aborted.'));
196
+ }
197
+ function createAbortError(message) {
198
+ if (typeof DOMException === 'function') {
199
+ return new DOMException(message, 'AbortError');
200
+ }
201
+ const error = new Error(message);
202
+ error.name = 'AbortError';
203
+ return error;
204
+ }
205
+ //# sourceMappingURL=controller.resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.resolve.js","sourceRoot":"","sources":["../../../src/controllers/controller.resolve.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ReactiveController,\n ReactiveControllerHost\n} from 'lit';\n\nimport {\n Task,\n initialState,\n type TaskStatus as LitTaskStatus\n} from '@lit/task';\n\nexport type MaybePromise<T> = T | Promise<T>;\n\nexport type BdResolveStatus =\n | 'initial'\n | 'disabled'\n | 'pending'\n | 'complete'\n | 'error';\n\nexport type BdResolveHookPhase =\n | 'pending'\n | 'disabled'\n | 'complete'\n | 'error';\n\nexport type BdResolveContext = Readonly<{\n signal: AbortSignal;\n runId : number;\n}>;\n\nexport interface BdResolveHost extends ReactiveControllerHost {\n trackLocalWork<T>(\n key: unknown,\n work: PromiseLike<T>,\n ): Promise<T>;\n}\n\nexport type BdWorkKey = object | symbol;\n\nexport interface BdResolveOptions<\n TArgs extends readonly unknown[],\n TResult\n> {\n /**\n * Optional debug name.\n *\n * This is not used as the tracking key.\n */\n name?: string;\n\n /**\n * Advanced escape hatch.\n *\n * Usually you should not set this.\n * By default, the controller instance itself is used as the work key.\n */\n workKey?: BdWorkKey;\n\n args: () => TArgs;\n\n enabled?: (args: TArgs) => boolean;\n\n argsEqual?: (\n oldArgs: TArgs,\n newArgs: TArgs\n ) => boolean;\n\n autoRun?: boolean | 'afterUpdate';\n\n resolve: (\n args: TArgs,\n context: BdResolveContext\n ) => MaybePromise<TResult>;\n\n commit?: (\n result: TResult,\n args: TArgs,\n context: BdResolveContext\n ) => MaybePromise<void>;\n\n onPending?: (\n args: TArgs,\n context: BdResolveContext\n ) => MaybePromise<void>;\n\n onDisabled?: (\n args: TArgs,\n context: BdResolveContext\n ) => MaybePromise<void>;\n\n onComplete?: (\n result: TResult,\n args: TArgs,\n context: BdResolveContext\n ) => MaybePromise<void>;\n\n onError?: (\n error: unknown,\n args: TArgs,\n context: BdResolveContext\n ) => MaybePromise<void>;\n\n onHookError?: (\n error: unknown,\n phase: BdResolveHookPhase\n ) => void;\n\n abortOnDisconnect?: boolean;\n}\n\nexport class BdResolveController<\n TArgs extends readonly unknown[],\n TResult\n> implements ReactiveController {\n status: BdResolveStatus = 'initial';\n value?: TResult;\n error?: unknown;\n\n readonly name: string;\n\n private readonly _workKey: BdWorkKey;\n private readonly _task : Task<TArgs, TResult>;\n private _activeRunId = 0;\n\n constructor(\n private readonly host: BdResolveHost,\n private readonly options: BdResolveOptions<TArgs, TResult>\n ) {\n this.name =\n options.name ??\n 'bd-resolve';\n\n /**\n * By default, each controller instance is its own unique work key.\n * This allows the controller to manage its own work without risk of conflict.\n */\n this._workKey =\n options.workKey ??\n this;\n\n host.addController(this);\n\n this._task = new Task<TArgs, TResult>(host, {\n args: options.args,\n\n autoRun:\n options.autoRun ?? true,\n\n argsEqual:\n options.argsEqual,\n\n task: (args, { signal }) => {\n const runId = this._startRunId();\n\n const context: BdResolveContext = {\n signal,\n runId\n };\n\n const work = this._run(\n args,\n context\n );\n\n return this.host.trackLocalWork(\n this._workKey,\n work\n );\n }\n });\n }\n\n hostDisconnected(): void {\n if (this.options.abortOnDisconnect ?? true) {\n this.abort(\n createAbortError(\n 'BdResolveController host disconnected.'\n )\n );\n }\n }\n\n get litTaskStatus(): LitTaskStatus {\n return this._task.status;\n }\n\n get pending(): boolean {\n return this.status === 'pending';\n }\n\n get complete(): boolean {\n return this.status === 'complete';\n }\n\n get disabled(): boolean {\n return this.status === 'disabled';\n }\n\n get failed(): boolean {\n return this.status === 'error';\n }\n\n /**\n * Manual execution.\n *\n * Useful if options.autoRun === false.\n */\n async run(\n args?: TArgs\n ): Promise<TResult | undefined> {\n await this._task.run(args);\n\n if (this.status === 'error') {\n throw this.error;\n }\n\n return this.value;\n }\n\n /**\n * Abort the current task.\n *\n * This also invalidates the current controller runId so an old async result\n * cannot later commit stale data.\n */\n abort(reason?: unknown): void {\n this._activeRunId += 1;\n\n this._task.abort(\n reason ??\n createAbortError(\n `${this.name} aborted.`\n )\n );\n\n this.status = 'initial';\n this.value = undefined;\n this.error = undefined;\n\n /**\n * Replace this work slot with a completed promise.\n *\n * This prevents updateComplete from being blocked forever by an obsolete\n * task that ignores AbortSignal.\n */\n this.host.trackLocalWork(\n this._workKey,\n Promise.resolve(undefined)\n );\n\n this.host.requestUpdate();\n }\n\n private _startRunId(): number {\n this._activeRunId += 1;\n return this._activeRunId;\n }\n\n private _isCurrentRun(\n context: BdResolveContext\n ): boolean {\n return context.runId === this._activeRunId;\n }\n\n private async _run(\n args: TArgs,\n context: BdResolveContext\n ): Promise<TResult | typeof initialState> {\n try {\n const enabled =\n this.options.enabled?.(args) ?? true;\n\n if (!enabled) {\n return await this._runDisabled(\n args,\n context\n );\n }\n\n return await this._runEnabled(\n args,\n context\n );\n } catch (error) {\n if (\n context.signal.aborted ||\n !this._isCurrentRun(context)\n ) {\n return initialState;\n }\n\n this.status = 'error';\n this.error = error;\n this.value = undefined;\n this.host.requestUpdate();\n\n await this._callHook(\n 'error',\n () =>\n this.options.onError?.(\n error,\n args,\n context\n )\n );\n\n /**\n * Re-throw so @lit/task also enters ERROR state.\n */\n throw error;\n }\n }\n\n private async _runDisabled(\n args: TArgs,\n context: BdResolveContext\n ): Promise<typeof initialState> {\n if (!this._isCurrentRun(context)) {\n return initialState;\n }\n\n this.status = 'disabled';\n this.value = undefined;\n this.error = undefined;\n this.host.requestUpdate();\n\n throwIfAborted(context.signal);\n\n if (!this._isCurrentRun(context)) {\n return initialState;\n }\n\n await this._callHook(\n 'disabled',\n () =>\n this.options.onDisabled?.(\n args,\n context\n )\n );\n\n throwIfAborted(context.signal);\n\n if (!this._isCurrentRun(context)) {\n return initialState;\n }\n\n this.host.requestUpdate();\n\n return initialState;\n }\n\n private async _runEnabled(\n args: TArgs,\n context: BdResolveContext\n ): Promise<TResult | typeof initialState> {\n if (!this._isCurrentRun(context)) {\n return initialState;\n }\n\n this.status = 'pending';\n this.value = undefined;\n this.error = undefined;\n this.host.requestUpdate();\n\n throwIfAborted(context.signal);\n\n if (!this._isCurrentRun(context)) {\n return initialState;\n }\n\n await this._callHook(\n 'pending',\n () =>\n this.options.onPending?.(\n args,\n context\n )\n );\n\n throwIfAborted(context.signal);\n\n if (!this._isCurrentRun(context)) {\n return initialState;\n }\n\n const result =\n await this.options.resolve(\n args,\n context\n );\n\n throwIfAborted(context.signal);\n\n if (!this._isCurrentRun(context)) {\n return initialState;\n }\n\n await this.options.commit?.(\n result,\n args,\n context\n );\n\n throwIfAborted(context.signal);\n\n if (!this._isCurrentRun(context)) {\n return initialState;\n }\n\n this.status = 'complete';\n this.value = result;\n this.error = undefined;\n this.host.requestUpdate();\n\n await this._callHook(\n 'complete',\n () =>\n this.options.onComplete?.(\n result,\n args,\n context\n )\n );\n\n throwIfAborted(context.signal);\n\n if (!this._isCurrentRun(context)) {\n return initialState;\n }\n\n return result;\n }\n\n private async _callHook(\n phase: BdResolveHookPhase,\n callback: () => MaybePromise<void>\n ): Promise<void> {\n try {\n await callback();\n } catch (error) {\n this.options.onHookError?.(\n error,\n phase\n );\n }\n }\n}\n\nexport function throwIfAborted(\n signal: AbortSignal\n): void {\n if (!signal.aborted) {\n return;\n }\n\n if (typeof signal.throwIfAborted === 'function') {\n signal.throwIfAborted();\n return;\n }\n\n throw (\n signal.reason ??\n createAbortError(\n 'The operation was aborted.'\n )\n );\n}\n\nfunction createAbortError(\n message: string\n): Error | DOMException {\n if (typeof DOMException === 'function') {\n return new DOMException(\n message,\n 'AbortError'\n );\n }\n\n const error = new Error(message);\n error.name = 'AbortError';\n\n return error;\n}"]}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Action topology events.
3
+ *
4
+ * These describe STRUCTURE, not intent and not resolved state:
5
+ *
6
+ * bd-action-connect / bd-action-disconnect
7
+ * A native action element appeared / disappeared. Announced by
8
+ * BdActionElementAdapter. Caught by the nearest bd node, which owns that
9
+ * element's membership (same boundary that would catch bd-action-request).
10
+ *
11
+ * bd-action-topology-changed
12
+ * The set of action elements under some bd node changed. Bubbles up so
13
+ * ancestors can bump their action revision and recompute.
14
+ *
15
+ * Topology must never be stored in BdScope. It is local node structure, read on
16
+ * demand by compute. Putting it in scope would break scope immutability and
17
+ * atomicity.
18
+ */
19
+ import type { BdComputeScan } from '../compute/compute.scan.js';
20
+ export declare const BD_ACTION_CONNECT_EVENT: "bd-action-connect";
21
+ export declare const BD_ACTION_DISCONNECT_EVENT: "bd-action-disconnect";
22
+ export declare const BD_ACTION_UPDATE_EVENT: "bd-action-update";
23
+ export declare const BD_ACTION_TOPOLOGY_CHANGED_EVENT: "bd-action-topology-changed";
24
+ /**
25
+ * Sent when an action element connects.
26
+ *
27
+ * The nearest bd node calls claim() so the adapter can later announce the
28
+ * disconnect directly to that owner, even after the element is detached from
29
+ * the DOM (a bubbling event on a detached element would never reach it).
30
+ */
31
+ export type BdActionConnectDetail = Readonly<{
32
+ element: HTMLElement;
33
+ scan: BdComputeScan;
34
+ claim: (owner: EventTarget) => void;
35
+ }>;
36
+ export type BdActionDisconnectDetail = Readonly<{
37
+ element: HTMLElement;
38
+ }>;
39
+ export type BdActionUpdateDetail = Readonly<{
40
+ element: HTMLElement;
41
+ scan: BdComputeScan;
42
+ }>;
43
+ declare global {
44
+ interface HTMLElementEventMap {
45
+ [BD_ACTION_CONNECT_EVENT]: CustomEvent<BdActionConnectDetail>;
46
+ [BD_ACTION_DISCONNECT_EVENT]: CustomEvent<BdActionDisconnectDetail>;
47
+ [BD_ACTION_UPDATE_EVENT]: CustomEvent<BdActionUpdateDetail>;
48
+ [BD_ACTION_TOPOLOGY_CHANGED_EVENT]: CustomEvent<undefined>;
49
+ }
50
+ }
51
+ export declare function dispatchBdActionConnect(element: HTMLElement, scan: BdComputeScan, claim: (owner: EventTarget) => void): void;
52
+ /**
53
+ * Announce a disconnect.
54
+ *
55
+ * target should be the claimed owner when available, because the element may
56
+ * already be detached from the DOM by the time the registry observes its
57
+ * removal. Dispatching on the retained owner guarantees the owning node hears
58
+ * it regardless of DOM connectivity.
59
+ */
60
+ export declare function dispatchBdActionDisconnect(target: EventTarget, element: HTMLElement): void;
61
+ export declare function dispatchBdActionUpdate(target: EventTarget, element: HTMLElement, scan: BdComputeScan): void;
62
+ export declare function dispatchBdActionTopologyChanged(target: EventTarget): void;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Action topology events.
3
+ *
4
+ * These describe STRUCTURE, not intent and not resolved state:
5
+ *
6
+ * bd-action-connect / bd-action-disconnect
7
+ * A native action element appeared / disappeared. Announced by
8
+ * BdActionElementAdapter. Caught by the nearest bd node, which owns that
9
+ * element's membership (same boundary that would catch bd-action-request).
10
+ *
11
+ * bd-action-topology-changed
12
+ * The set of action elements under some bd node changed. Bubbles up so
13
+ * ancestors can bump their action revision and recompute.
14
+ *
15
+ * Topology must never be stored in BdScope. It is local node structure, read on
16
+ * demand by compute. Putting it in scope would break scope immutability and
17
+ * atomicity.
18
+ */
19
+ export const BD_ACTION_CONNECT_EVENT = 'bd-action-connect';
20
+ export const BD_ACTION_DISCONNECT_EVENT = 'bd-action-disconnect';
21
+ export const BD_ACTION_UPDATE_EVENT = 'bd-action-update';
22
+ export const BD_ACTION_TOPOLOGY_CHANGED_EVENT = 'bd-action-topology-changed';
23
+ export function dispatchBdActionConnect(element, scan, claim) {
24
+ element.dispatchEvent(new CustomEvent(BD_ACTION_CONNECT_EVENT, {
25
+ bubbles: true,
26
+ composed: true,
27
+ detail: {
28
+ element,
29
+ scan,
30
+ claim
31
+ }
32
+ }));
33
+ }
34
+ /**
35
+ * Announce a disconnect.
36
+ *
37
+ * target should be the claimed owner when available, because the element may
38
+ * already be detached from the DOM by the time the registry observes its
39
+ * removal. Dispatching on the retained owner guarantees the owning node hears
40
+ * it regardless of DOM connectivity.
41
+ */
42
+ export function dispatchBdActionDisconnect(target, element) {
43
+ target.dispatchEvent(new CustomEvent(BD_ACTION_DISCONNECT_EVENT, {
44
+ bubbles: true,
45
+ composed: true,
46
+ detail: {
47
+ element
48
+ }
49
+ }));
50
+ }
51
+ export function dispatchBdActionUpdate(target, element, scan) {
52
+ target.dispatchEvent(new CustomEvent(BD_ACTION_UPDATE_EVENT, {
53
+ bubbles: true,
54
+ composed: true,
55
+ detail: {
56
+ element,
57
+ scan
58
+ }
59
+ }));
60
+ }
61
+ export function dispatchBdActionTopologyChanged(target) {
62
+ target.dispatchEvent(new CustomEvent(BD_ACTION_TOPOLOGY_CHANGED_EVENT, {
63
+ bubbles: true,
64
+ composed: true
65
+ }));
66
+ }
67
+ //# sourceMappingURL=action-topology.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-topology.js","sourceRoot":"","sources":["../../../src/core/action-topology.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,CAAC,MAAM,uBAAuB,GAClC,mBAA4B,CAAC;AAE/B,MAAM,CAAC,MAAM,0BAA0B,GACrC,sBAA+B,CAAC;AAElC,MAAM,CAAC,MAAM,sBAAsB,GACjC,kBAA2B,CAAC;AAE9B,MAAM,CAAC,MAAM,gCAAgC,GAC3C,4BAAqC,CAAC;AAiCxC,MAAM,UAAU,uBAAuB,CACrC,OAAoB,EACpB,IAAmB,EACnB,KAAmC;IAEnC,OAAO,CAAC,aAAa,CACnB,IAAI,WAAW,CACb,uBAAuB,EACvB;QACE,OAAO,EAAG,IAAI;QACd,QAAQ,EAAE,IAAI;QACd,MAAM,EAAI;YACR,OAAO;YACP,IAAI;YACJ,KAAK;SACN;KACF,CACF,CACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAAmB,EACnB,OAAoB;IAEpB,MAAM,CAAC,aAAa,CAClB,IAAI,WAAW,CACb,0BAA0B,EAC1B;QACE,OAAO,EAAG,IAAI;QACd,QAAQ,EAAE,IAAI;QACd,MAAM,EAAI;YACR,OAAO;SACR;KACF,CACF,CACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,MAAmB,EACnB,OAAoB,EACpB,IAAmB;IAEnB,MAAM,CAAC,aAAa,CAClB,IAAI,WAAW,CACb,sBAAsB,EACtB;QACE,OAAO,EAAG,IAAI;QACd,QAAQ,EAAE,IAAI;QACd,MAAM,EAAI;YACR,OAAO;YACP,IAAI;SACL;KACF,CACF,CACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,MAAmB;IAEnB,MAAM,CAAC,aAAa,CAClB,IAAI,WAAW,CACb,gCAAgC,EAChC;QACE,OAAO,EAAG,IAAI;QACd,QAAQ,EAAE,IAAI;KACf,CACF,CACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Action topology events.\n *\n * These describe STRUCTURE, not intent and not resolved state:\n *\n * bd-action-connect / bd-action-disconnect\n * A native action element appeared / disappeared. Announced by\n * BdActionElementAdapter. Caught by the nearest bd node, which owns that\n * element's membership (same boundary that would catch bd-action-request).\n *\n * bd-action-topology-changed\n * The set of action elements under some bd node changed. Bubbles up so\n * ancestors can bump their action revision and recompute.\n *\n * Topology must never be stored in BdScope. It is local node structure, read on\n * demand by compute. Putting it in scope would break scope immutability and\n * atomicity.\n */\n\nimport type { BdComputeScan } from '@/compute/compute.scan.js';\n\nexport const BD_ACTION_CONNECT_EVENT =\n 'bd-action-connect' as const;\n\nexport const BD_ACTION_DISCONNECT_EVENT =\n 'bd-action-disconnect' as const;\n\nexport const BD_ACTION_UPDATE_EVENT =\n 'bd-action-update' as const;\n\nexport const BD_ACTION_TOPOLOGY_CHANGED_EVENT =\n 'bd-action-topology-changed' as const;\n\n/**\n * Sent when an action element connects.\n *\n * The nearest bd node calls claim() so the adapter can later announce the\n * disconnect directly to that owner, even after the element is detached from\n * the DOM (a bubbling event on a detached element would never reach it).\n */\nexport type BdActionConnectDetail = Readonly<{\n element: HTMLElement;\n scan : BdComputeScan;\n claim : (owner: EventTarget) => void;\n}>;\n\nexport type BdActionDisconnectDetail = Readonly<{\n element: HTMLElement;\n}>;\n\nexport type BdActionUpdateDetail = Readonly<{\n element: HTMLElement;\n scan : BdComputeScan;\n}>;\n\ndeclare global {\n interface HTMLElementEventMap {\n [BD_ACTION_CONNECT_EVENT] : CustomEvent<BdActionConnectDetail>;\n [BD_ACTION_DISCONNECT_EVENT] : CustomEvent<BdActionDisconnectDetail>;\n [BD_ACTION_UPDATE_EVENT] : CustomEvent<BdActionUpdateDetail>;\n [BD_ACTION_TOPOLOGY_CHANGED_EVENT]: CustomEvent<undefined>;\n }\n}\n\nexport function dispatchBdActionConnect(\n element: HTMLElement,\n scan: BdComputeScan,\n claim: (owner: EventTarget) => void\n): void {\n element.dispatchEvent(\n new CustomEvent<BdActionConnectDetail>(\n BD_ACTION_CONNECT_EVENT,\n {\n bubbles : true,\n composed: true,\n detail : {\n element,\n scan,\n claim\n }\n }\n )\n );\n}\n\n/**\n * Announce a disconnect.\n *\n * target should be the claimed owner when available, because the element may\n * already be detached from the DOM by the time the registry observes its\n * removal. Dispatching on the retained owner guarantees the owning node hears\n * it regardless of DOM connectivity.\n */\nexport function dispatchBdActionDisconnect(\n target: EventTarget,\n element: HTMLElement\n): void {\n target.dispatchEvent(\n new CustomEvent<BdActionDisconnectDetail>(\n BD_ACTION_DISCONNECT_EVENT,\n {\n bubbles : true,\n composed: true,\n detail : {\n element\n }\n }\n )\n );\n}\n\nexport function dispatchBdActionUpdate(\n target: EventTarget,\n element: HTMLElement,\n scan: BdComputeScan\n): void {\n target.dispatchEvent(\n new CustomEvent<BdActionUpdateDetail>(\n BD_ACTION_UPDATE_EVENT,\n {\n bubbles : true,\n composed: true,\n detail : {\n element,\n scan\n }\n }\n )\n );\n}\n\nexport function dispatchBdActionTopologyChanged(\n target: EventTarget\n): void {\n target.dispatchEvent(\n new CustomEvent<undefined>(\n BD_ACTION_TOPOLOGY_CHANGED_EVENT,\n {\n bubbles : true,\n composed: true\n }\n )\n );\n}\n"]}
@@ -0,0 +1,64 @@
1
+ export type UpdateByDelta = Readonly<{
2
+ type: 'devices' | 'subscription';
3
+ delta: number | 'next' | 'prev';
4
+ min?: number;
5
+ max?: number;
6
+ useAsValue?: boolean;
7
+ /**
8
+ * Optional identifier for the action source element
9
+ * (`data-store-event-id`). Consumers filter against this id via the
10
+ * `ignore-events` attribute on `bd-*` provider elements.
11
+ */
12
+ eventId?: string;
13
+ }>;
14
+ export type Action = Readonly<{
15
+ id?: string;
16
+ campaign?: string;
17
+ bundle?: boolean;
18
+ devices?: number;
19
+ subscription?: number;
20
+ /**
21
+ * Optional identifier for the action source element
22
+ * (`data-store-event-id`). Consumers filter against this id via the
23
+ * `ignore-events` attribute on `bd-*` provider elements.
24
+ */
25
+ eventId?: string;
26
+ }>;
27
+ export type BdActionCommand = Readonly<{
28
+ kind: 'update-by-delta';
29
+ update: UpdateByDelta;
30
+ }> | Readonly<{
31
+ kind: 'set-action';
32
+ action: Action;
33
+ }>;
34
+ export type BdTransition = Readonly<{
35
+ seq: number;
36
+ command: BdActionCommand;
37
+ originNode?: EventTarget | null;
38
+ createdAt: number;
39
+ }>;
40
+ export declare const BD_ACTION_REQUEST_EVENT: "bd-action-request";
41
+ export type BdActionRequestDetail = Readonly<{
42
+ command: BdActionCommand;
43
+ }>;
44
+ declare global {
45
+ interface HTMLElementEventMap {
46
+ [BD_ACTION_REQUEST_EVENT]: CustomEvent<BdActionRequestDetail>;
47
+ }
48
+ }
49
+ export declare function createBdTransition(detail: BdActionRequestDetail, originNode?: EventTarget | null): BdTransition;
50
+ export declare function dispatchBdActionRequest(eventTarget: EventTarget, detail: BdActionRequestDetail): boolean;
51
+ export declare function isBdActionRequestEvent(event: Event): event is CustomEvent<BdActionRequestDetail>;
52
+ /**
53
+ * Pulls the optional `eventId` off the command carried by a transition.
54
+ *
55
+ * `eventId` is set on the action element via `data-store-event-id="…"` and
56
+ * is the matching key for the `ignore-events="…"` attribute on `bd-*`
57
+ * provider elements. Returns `undefined` for commands that did not declare
58
+ * one.
59
+ */
60
+ export declare function getCommandEventId(command: BdActionCommand): string | undefined;
61
+ /**
62
+ * Convenience wrapper around {@link getCommandEventId} for transitions.
63
+ */
64
+ export declare function getTransitionEventId(transition: BdTransition): string | undefined;