@repobit/dex-store-elements 1.7.3 → 2.0.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/CHANGELOG.md +6 -906
- package/README.md +308 -283
- package/dist/src/actions/action.adapter.d.ts +36 -0
- package/dist/src/actions/action.adapter.js +183 -0
- package/dist/src/actions/action.adapter.js.map +1 -0
- package/dist/src/actions/action.attributes.d.ts +15 -0
- package/dist/src/actions/action.attributes.js +137 -0
- package/dist/src/actions/action.attributes.js.map +1 -0
- package/dist/src/actions/action.registry.d.ts +4 -0
- package/dist/src/actions/action.registry.js +119 -0
- package/dist/src/actions/action.registry.js.map +1 -0
- package/dist/src/actions/index.d.ts +3 -2
- package/dist/src/actions/index.js +3 -80
- package/dist/src/actions/index.js.map +1 -1
- package/dist/src/compute/compute.expand.d.ts +70 -0
- package/dist/src/compute/compute.expand.js +319 -0
- package/dist/src/compute/compute.expand.js.map +1 -0
- package/dist/src/compute/compute.scan.d.ts +54 -0
- package/dist/src/compute/compute.scan.js +160 -0
- package/dist/src/compute/compute.scan.js.map +1 -0
- package/dist/src/compute/option-resolver.d.ts +31 -0
- package/dist/src/compute/option-resolver.js +147 -0
- package/dist/src/compute/option-resolver.js.map +1 -0
- package/dist/src/contexts/context.compute.d.ts +12 -0
- package/dist/src/contexts/context.compute.js +4 -0
- package/dist/src/contexts/context.compute.js.map +1 -0
- package/dist/src/contexts/context.scope.d.ts +139 -0
- package/dist/src/contexts/context.scope.js +48 -0
- package/dist/src/contexts/context.scope.js.map +1 -0
- package/dist/src/controllers/controller.action-resolve.d.ts +110 -0
- package/dist/src/controllers/controller.action-resolve.js +203 -0
- package/dist/src/controllers/controller.action-resolve.js.map +1 -0
- package/dist/src/controllers/controller.action-topology.d.ts +59 -0
- package/dist/src/controllers/controller.action-topology.js +130 -0
- package/dist/src/controllers/controller.action-topology.js.map +1 -0
- package/dist/src/controllers/controller.compute.d.ts +72 -0
- package/dist/src/controllers/controller.compute.js +110 -0
- package/dist/src/controllers/controller.compute.js.map +1 -0
- package/dist/src/controllers/controller.resolve.d.ts +78 -0
- package/dist/src/controllers/controller.resolve.js +205 -0
- package/dist/src/controllers/controller.resolve.js.map +1 -0
- package/dist/src/core/action-topology.d.ts +62 -0
- package/dist/src/core/action-topology.js +67 -0
- package/dist/src/core/action-topology.js.map +1 -0
- package/dist/src/core/actions.d.ts +64 -0
- package/dist/src/core/actions.js +47 -0
- package/dist/src/core/actions.js.map +1 -0
- package/dist/src/core/node.element.d.ts +179 -0
- package/dist/src/core/node.element.js +573 -0
- package/dist/src/core/node.element.js.map +1 -0
- package/dist/src/core/node.scope.d.ts +374 -0
- package/dist/src/core/node.scope.js +558 -0
- package/dist/src/core/node.scope.js.map +1 -0
- package/dist/src/core/option-update.d.ts +11 -0
- package/dist/src/core/option-update.js +59 -0
- package/dist/src/core/option-update.js.map +1 -0
- package/dist/src/dsl/compilers/array/compiler.js +13 -14
- package/dist/src/dsl/compilers/array/compiler.js.map +1 -1
- package/dist/src/dsl/compilers/boolean/compiler.d.ts +1 -1
- package/dist/src/dsl/compilers/boolean/compiler.js +14 -27
- package/dist/src/dsl/compilers/boolean/compiler.js.map +1 -1
- package/dist/src/dsl/compilers/enum/compiler.js +7 -10
- package/dist/src/dsl/compilers/enum/compiler.js.map +1 -1
- package/dist/src/dsl/compilers/index.d.ts +1 -1
- package/dist/src/dsl/compilers/index.js +5 -5
- package/dist/src/dsl/compilers/index.js.map +1 -1
- package/dist/src/dsl/utilty.d.ts +0 -4
- package/dist/src/dsl/utilty.js +3 -30
- package/dist/src/dsl/utilty.js.map +1 -1
- package/dist/src/eta/batch.d.ts +123 -0
- package/dist/src/eta/batch.js +162 -0
- package/dist/src/eta/batch.js.map +1 -0
- package/dist/src/eta/batcher.d.ts +184 -0
- package/dist/src/eta/batcher.js +335 -0
- package/dist/src/eta/batcher.js.map +1 -0
- package/dist/src/eta/cache.d.ts +36 -0
- package/dist/src/eta/cache.js +56 -0
- package/dist/src/eta/cache.js.map +1 -0
- package/dist/src/eta/controller.d.ts +608 -0
- package/dist/src/eta/controller.js +1036 -0
- package/dist/src/eta/controller.js.map +1 -0
- package/dist/src/eta/factory.d.ts +74 -0
- package/dist/src/eta/factory.js +121 -0
- package/dist/src/eta/factory.js.map +1 -0
- package/dist/src/eta/idle.d.ts +39 -0
- package/dist/src/eta/idle.js +52 -0
- package/dist/src/eta/idle.js.map +1 -0
- package/dist/src/eta/index.d.ts +52 -0
- package/dist/src/eta/index.js +13 -0
- package/dist/src/eta/index.js.map +1 -0
- package/dist/src/eta/observer.d.ts +263 -0
- package/dist/src/eta/observer.js +263 -0
- package/dist/src/eta/observer.js.map +1 -0
- package/dist/src/eta/operations.d.ts +41 -0
- package/dist/src/eta/operations.js +59 -0
- package/dist/src/eta/operations.js.map +1 -0
- package/dist/src/eta/source-registry.d.ts +198 -0
- package/dist/src/eta/source-registry.js +267 -0
- package/dist/src/eta/source-registry.js.map +1 -0
- package/dist/src/eta/template-index.d.ts +285 -0
- package/dist/src/eta/template-index.js +642 -0
- package/dist/src/eta/template-index.js.map +1 -0
- package/dist/src/eta/types.d.ts +529 -0
- package/dist/src/eta/types.js +2 -0
- package/dist/src/eta/types.js.map +1 -0
- package/dist/src/index.d.ts +21 -9
- package/dist/src/index.js +22 -11
- package/dist/src/index.js.map +1 -1
- package/dist/src/nodes/node.context.d.ts +5 -3
- package/dist/src/nodes/node.context.js +48 -3
- package/dist/src/nodes/node.context.js.map +1 -1
- package/dist/src/nodes/node.option.d.ts +11 -31
- package/dist/src/nodes/node.option.js +158 -222
- package/dist/src/nodes/node.option.js.map +1 -1
- package/dist/src/nodes/node.product.d.ts +7 -11
- package/dist/src/nodes/node.product.js +85 -64
- package/dist/src/nodes/node.product.js.map +1 -1
- package/dist/src/renders/attributes/buyLink.d.ts +2 -2
- package/dist/src/renders/attributes/buyLink.js +14 -11
- package/dist/src/renders/attributes/buyLink.js.map +1 -1
- package/dist/src/renders/attributes/devices.d.ts +2 -2
- package/dist/src/renders/attributes/devices.js +9 -19
- package/dist/src/renders/attributes/devices.js.map +1 -1
- package/dist/src/renders/attributes/discount.d.ts +2 -2
- package/dist/src/renders/attributes/discount.js +56 -44
- package/dist/src/renders/attributes/discount.js.map +1 -1
- package/dist/src/renders/attributes/hide.d.ts +2 -2
- package/dist/src/renders/attributes/hide.js +49 -14
- package/dist/src/renders/attributes/hide.js.map +1 -1
- package/dist/src/renders/attributes/index.d.ts +3 -3
- package/dist/src/renders/attributes/index.js +2 -4
- package/dist/src/renders/attributes/index.js.map +1 -1
- package/dist/src/renders/attributes/price.d.ts +2 -2
- package/dist/src/renders/attributes/price.js +30 -44
- package/dist/src/renders/attributes/price.js.map +1 -1
- package/dist/src/renders/attributes/subscription.d.ts +2 -2
- package/dist/src/renders/attributes/subscription.js +14 -20
- package/dist/src/renders/attributes/subscription.js.map +1 -1
- package/dist/src/renders/attributes/utilty.d.ts +2 -0
- package/dist/src/renders/attributes/utilty.js +17 -4
- package/dist/src/renders/attributes/utilty.js.map +1 -1
- package/dist/src/renders/context.d.ts +122 -10
- package/dist/src/renders/context.js +140 -80
- package/dist/src/renders/context.js.map +1 -1
- package/dist/src/renders/format.js +2 -2
- package/dist/src/renders/format.js.map +1 -1
- package/dist/src/renders/index.d.ts +6 -2
- package/dist/src/renders/index.js +67 -29
- package/dist/src/renders/index.js.map +1 -1
- package/dist/src/renders/observe.d.ts +1 -4
- package/dist/src/renders/observe.js +10 -24
- package/dist/src/renders/observe.js.map +1 -1
- package/dist/src/renders/render.wrapper.d.ts +37 -0
- package/dist/src/renders/render.wrapper.js +147 -0
- package/dist/src/renders/render.wrapper.js.map +1 -0
- package/dist/src/templating/eta.js +1 -4
- package/dist/src/templating/eta.js.map +1 -1
- package/package.json +4 -5
- package/dist/src/actions/action.button.d.ts +0 -2
- package/dist/src/actions/action.button.js +0 -8
- package/dist/src/actions/action.button.js.map +0 -1
- package/dist/src/actions/action.input.d.ts +0 -2
- package/dist/src/actions/action.input.js +0 -43
- package/dist/src/actions/action.input.js.map +0 -1
- package/dist/src/actions/action.select.d.ts +0 -2
- package/dist/src/actions/action.select.js +0 -14
- package/dist/src/actions/action.select.js.map +0 -1
- package/dist/src/actions/utilty.d.ts +0 -16
- package/dist/src/actions/utilty.js +0 -149
- package/dist/src/actions/utilty.js.map +0 -1
- package/dist/src/contexts/context.datalayer.d.ts +0 -9
- package/dist/src/contexts/context.datalayer.js +0 -3
- package/dist/src/contexts/context.datalayer.js.map +0 -1
- package/dist/src/contexts/context.derived.d.ts +0 -12
- package/dist/src/contexts/context.derived.js +0 -3
- package/dist/src/contexts/context.derived.js.map +0 -1
- package/dist/src/contexts/context.option.d.ts +0 -5
- package/dist/src/contexts/context.option.js +0 -3
- package/dist/src/contexts/context.option.js.map +0 -1
- package/dist/src/contexts/context.product.d.ts +0 -5
- package/dist/src/contexts/context.product.js +0 -3
- package/dist/src/contexts/context.product.js.map +0 -1
- package/dist/src/contexts/context.state.d.ts +0 -28
- package/dist/src/contexts/context.state.js +0 -3
- package/dist/src/contexts/context.state.js.map +0 -1
- package/dist/src/contexts/context.store.d.ts +0 -5
- package/dist/src/contexts/context.store.js +0 -3
- package/dist/src/contexts/context.store.js.map +0 -1
- package/dist/src/controllers/collect-controller.d.ts +0 -39
- package/dist/src/controllers/collect-controller.js +0 -161
- package/dist/src/controllers/collect-controller.js.map +0 -1
- package/dist/src/controllers/compute-controller.d.ts +0 -55
- package/dist/src/controllers/compute-controller.js +0 -350
- package/dist/src/controllers/compute-controller.js.map +0 -1
- package/dist/src/controllers/eta-renderer/eta-dom-renderer.d.ts +0 -25
- package/dist/src/controllers/eta-renderer/eta-dom-renderer.js +0 -192
- package/dist/src/controllers/eta-renderer/eta-dom-renderer.js.map +0 -1
- package/dist/src/controllers/eta-renderer/eta-mutation-observer.d.ts +0 -28
- package/dist/src/controllers/eta-renderer/eta-mutation-observer.js +0 -206
- package/dist/src/controllers/eta-renderer/eta-mutation-observer.js.map +0 -1
- package/dist/src/controllers/eta-renderer/eta-renderer.d.ts +0 -21
- package/dist/src/controllers/eta-renderer/eta-renderer.js +0 -77
- package/dist/src/controllers/eta-renderer/eta-renderer.js.map +0 -1
- package/dist/src/controllers/eta-renderer/eta-template-cache.d.ts +0 -24
- package/dist/src/controllers/eta-renderer/eta-template-cache.js +0 -61
- package/dist/src/controllers/eta-renderer/eta-template-cache.js.map +0 -1
- package/dist/src/controllers/eta-renderer/eta-types.d.ts +0 -11
- package/dist/src/controllers/eta-renderer/eta-types.js +0 -1
- package/dist/src/controllers/eta-renderer/eta-types.js.map +0 -1
- package/dist/src/controllers/eta-renderer/index.d.ts +0 -2
- package/dist/src/controllers/eta-renderer/index.js +0 -2
- package/dist/src/controllers/eta-renderer/index.js.map +0 -1
- package/dist/src/controllers/event-pipeline-controller.d.ts +0 -28
- package/dist/src/controllers/event-pipeline-controller.js +0 -72
- package/dist/src/controllers/event-pipeline-controller.js.map +0 -1
- package/dist/src/events/events.d.ts +0 -75
- package/dist/src/events/events.js +0 -78
- package/dist/src/events/events.js.map +0 -1
- package/dist/src/nodes/node.root.d.ts +0 -16
- package/dist/src/nodes/node.root.js +0 -49
- package/dist/src/nodes/node.root.js.map +0 -1
- package/dist/src/nodes/node.state.d.ts +0 -88
- package/dist/src/nodes/node.state.js +0 -330
- package/dist/src/nodes/node.state.js.map +0 -1
- package/dist/src/renders/utility.d.ts +0 -32
- package/dist/src/renders/utility.js +0 -191
- package/dist/src/renders/utility.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/actions/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC","sourcesContent":["export * from './action.adapter.js';\nexport * from './action.attributes.js';\nexport * from './action.registry.js';\n\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { BdActionCommand } from '../core/actions.js';
|
|
2
|
+
import type { Product, Store } from '@repobit/dex-store';
|
|
3
|
+
import { type BdActionDomain, type BdSeedDeclaration } from './compute.scan.js';
|
|
4
|
+
import { type BdOptionScenario } from './option-resolver.js';
|
|
5
|
+
/**
|
|
6
|
+
* Min/max reduction over every option reachable from a compute boundary.
|
|
7
|
+
*/
|
|
8
|
+
export type BdComputeValue = Readonly<{
|
|
9
|
+
value: number;
|
|
10
|
+
fmt: string;
|
|
11
|
+
}>;
|
|
12
|
+
export type BdComputeRange = Readonly<{
|
|
13
|
+
min: BdComputeValue;
|
|
14
|
+
max: BdComputeValue;
|
|
15
|
+
}>;
|
|
16
|
+
export type BdComputeMonthlyRange = Readonly<BdComputeRange & {
|
|
17
|
+
monthly: BdComputeRange;
|
|
18
|
+
}>;
|
|
19
|
+
export type BdComputeDiscountRange = Readonly<BdComputeMonthlyRange & {
|
|
20
|
+
percentage: BdComputeMonthlyRange;
|
|
21
|
+
}>;
|
|
22
|
+
export type BdComputeResult = Readonly<{
|
|
23
|
+
min: number;
|
|
24
|
+
max: number;
|
|
25
|
+
minFormatted: string;
|
|
26
|
+
maxFormatted: string;
|
|
27
|
+
scenarios: number;
|
|
28
|
+
price: BdComputeMonthlyRange;
|
|
29
|
+
discountedPrice: BdComputeMonthlyRange;
|
|
30
|
+
discount: BdComputeDiscountRange;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve declarative seeds into concrete product/option scenarios. Seeds that
|
|
34
|
+
* cannot be resolved (unknown product, missing option) are dropped.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveSeedScenarios(store: Store, seeds: readonly BdSeedDeclaration[], signal?: AbortSignal): Promise<BdOptionScenario[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Fixed-point expansion of the reachable option space.
|
|
39
|
+
*
|
|
40
|
+
* Starting from the seeds, every static command and every domain-expanded
|
|
41
|
+
* command is applied to every newly discovered scenario, until no new scenario
|
|
42
|
+
* appears. Domains expand against the scenario's own product, which is what
|
|
43
|
+
* makes a product-switching action correctly explore the new product's
|
|
44
|
+
* device/subscription dimensions rather than the originally rendered ones.
|
|
45
|
+
*/
|
|
46
|
+
export declare function expandScenarios(params: Readonly<{
|
|
47
|
+
store?: Store;
|
|
48
|
+
seeds: readonly BdOptionScenario[];
|
|
49
|
+
staticCommands: readonly BdActionCommand[];
|
|
50
|
+
domains: readonly BdActionDomain[];
|
|
51
|
+
signal?: AbortSignal;
|
|
52
|
+
maxScenarios?: number;
|
|
53
|
+
}>): Promise<BdOptionScenario[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Expand a domain into per-value set commands against a specific product. A
|
|
56
|
+
* devices domain becomes one useAsValue update per device value the product
|
|
57
|
+
* offers, and likewise for subscriptions.
|
|
58
|
+
*/
|
|
59
|
+
export declare function commandsFromDomains(domains: readonly BdActionDomain[], product: Product): BdActionCommand[];
|
|
60
|
+
/**
|
|
61
|
+
* Reduce a set of scenarios to a min/max price range. Prices are read with the
|
|
62
|
+
* same ProductOption.getPrice() the runtime uses, so formatting and currency
|
|
63
|
+
* match exactly.
|
|
64
|
+
*/
|
|
65
|
+
export declare function reducePriceRange(scenarios: readonly BdOptionScenario[]): BdComputeResult | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Convenience: a stable signature for a compute result so consumers can cheaply
|
|
68
|
+
* detect a meaningful change.
|
|
69
|
+
*/
|
|
70
|
+
export declare function computeResultKey(result: BdComputeResult | undefined): string;
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// src/compute/compute.expand.ts
|
|
2
|
+
import { resolveScenarioCommand, scenarioKey } from './option-resolver.js';
|
|
3
|
+
const DEFAULT_MAX_SCENARIOS = 10000;
|
|
4
|
+
/**
|
|
5
|
+
* Resolve declarative seeds into concrete product/option scenarios. Seeds that
|
|
6
|
+
* cannot be resolved (unknown product, missing option) are dropped.
|
|
7
|
+
*/
|
|
8
|
+
export async function resolveSeedScenarios(store, seeds, signal) {
|
|
9
|
+
const resolved = await Promise.all(seeds.map(async (seed) => {
|
|
10
|
+
const product = await store.getProduct({
|
|
11
|
+
id: seed.productId,
|
|
12
|
+
campaign: seed.campaign
|
|
13
|
+
});
|
|
14
|
+
throwIfAborted(signal);
|
|
15
|
+
if (!product) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const option = await product.getOption({
|
|
19
|
+
devices: seed.devices,
|
|
20
|
+
subscription: seed.subscription
|
|
21
|
+
});
|
|
22
|
+
if (!option) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
product,
|
|
27
|
+
option
|
|
28
|
+
};
|
|
29
|
+
}));
|
|
30
|
+
return resolved.filter((scenario) => scenario !== undefined);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Fixed-point expansion of the reachable option space.
|
|
34
|
+
*
|
|
35
|
+
* Starting from the seeds, every static command and every domain-expanded
|
|
36
|
+
* command is applied to every newly discovered scenario, until no new scenario
|
|
37
|
+
* appears. Domains expand against the scenario's own product, which is what
|
|
38
|
+
* makes a product-switching action correctly explore the new product's
|
|
39
|
+
* device/subscription dimensions rather than the originally rendered ones.
|
|
40
|
+
*/
|
|
41
|
+
export async function expandScenarios(params) {
|
|
42
|
+
const limit = params.maxScenarios ?? DEFAULT_MAX_SCENARIOS;
|
|
43
|
+
const visited = new Map();
|
|
44
|
+
let frontier = [];
|
|
45
|
+
for (const seed of params.seeds) {
|
|
46
|
+
const key = scenarioKey(seed);
|
|
47
|
+
if (!visited.has(key)) {
|
|
48
|
+
visited.set(key, seed);
|
|
49
|
+
frontier.push(seed);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
while (frontier.length > 0 && visited.size < limit) {
|
|
53
|
+
const next = [];
|
|
54
|
+
for (const scenario of frontier) {
|
|
55
|
+
const commands = [
|
|
56
|
+
...params.staticCommands,
|
|
57
|
+
...commandsFromDomains(params.domains, scenario.product)
|
|
58
|
+
];
|
|
59
|
+
for (const command of commands) {
|
|
60
|
+
throwIfAborted(params.signal);
|
|
61
|
+
const result = await resolveScenarioCommand({
|
|
62
|
+
store: params.store,
|
|
63
|
+
scenario,
|
|
64
|
+
command
|
|
65
|
+
});
|
|
66
|
+
if (!result) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const key = scenarioKey(result);
|
|
70
|
+
if (visited.has(key)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
visited.set(key, result);
|
|
74
|
+
next.push(result);
|
|
75
|
+
if (visited.size >= limit) {
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (visited.size >= limit) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
frontier = next;
|
|
84
|
+
}
|
|
85
|
+
return [...visited.values()];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Expand a domain into per-value set commands against a specific product. A
|
|
89
|
+
* devices domain becomes one useAsValue update per device value the product
|
|
90
|
+
* offers, and likewise for subscriptions.
|
|
91
|
+
*/
|
|
92
|
+
export function commandsFromDomains(domains, product) {
|
|
93
|
+
const commands = [];
|
|
94
|
+
for (const domain of domains) {
|
|
95
|
+
const values = domain.type === 'devices'
|
|
96
|
+
? product.getDevices().values
|
|
97
|
+
: product.getSubscriptions().values;
|
|
98
|
+
for (const value of values) {
|
|
99
|
+
if (!isValueInDomain(value, domain)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
commands.push(domainValueCommand(domain.type, value));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return commands;
|
|
106
|
+
}
|
|
107
|
+
function isValueInDomain(value, domain) {
|
|
108
|
+
if (domain.min !== undefined &&
|
|
109
|
+
value < domain.min) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (domain.max !== undefined &&
|
|
113
|
+
value > domain.max) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
function domainValueCommand(type, value) {
|
|
119
|
+
return {
|
|
120
|
+
kind: 'update-by-delta',
|
|
121
|
+
update: {
|
|
122
|
+
type,
|
|
123
|
+
delta: value,
|
|
124
|
+
useAsValue: true
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Reduce a set of scenarios to a min/max price range. Prices are read with the
|
|
130
|
+
* same ProductOption.getPrice() the runtime uses, so formatting and currency
|
|
131
|
+
* match exactly.
|
|
132
|
+
*/
|
|
133
|
+
export function reducePriceRange(scenarios) {
|
|
134
|
+
const price = createMonthlyAccumulator();
|
|
135
|
+
const discountedPrice = createMonthlyAccumulator();
|
|
136
|
+
const discount = createDiscountAccumulator();
|
|
137
|
+
for (const scenario of scenarios) {
|
|
138
|
+
collectOptionCompute(scenario.option, {
|
|
139
|
+
price,
|
|
140
|
+
discountedPrice,
|
|
141
|
+
discount
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (!price.base.hasValue) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const priceRange = finalizeMonthlyAccumulator(price);
|
|
148
|
+
return {
|
|
149
|
+
min: priceRange.min.value,
|
|
150
|
+
max: priceRange.max.value,
|
|
151
|
+
minFormatted: priceRange.min.fmt,
|
|
152
|
+
maxFormatted: priceRange.max.fmt,
|
|
153
|
+
scenarios: scenarios.length,
|
|
154
|
+
price: priceRange,
|
|
155
|
+
discountedPrice: finalizeMonthlyAccumulator(discountedPrice),
|
|
156
|
+
discount: finalizeDiscountAccumulator(discount)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Convenience: a stable signature for a compute result so consumers can cheaply
|
|
161
|
+
* detect a meaningful change.
|
|
162
|
+
*/
|
|
163
|
+
export function computeResultKey(result) {
|
|
164
|
+
if (!result) {
|
|
165
|
+
return '';
|
|
166
|
+
}
|
|
167
|
+
return [
|
|
168
|
+
result.scenarios,
|
|
169
|
+
rangeKey(result.price),
|
|
170
|
+
rangeKey(result.price.monthly),
|
|
171
|
+
rangeKey(result.discountedPrice),
|
|
172
|
+
rangeKey(result.discountedPrice.monthly),
|
|
173
|
+
rangeKey(result.discount),
|
|
174
|
+
rangeKey(result.discount.monthly),
|
|
175
|
+
rangeKey(result.discount.percentage),
|
|
176
|
+
rangeKey(result.discount.percentage.monthly)
|
|
177
|
+
].join('|');
|
|
178
|
+
}
|
|
179
|
+
function collectOptionCompute(option, buckets) {
|
|
180
|
+
collectMetric(buckets.price.base, readPriceMetric(option));
|
|
181
|
+
collectMetric(buckets.price.monthly, readPriceMetric(option, { monthly: true }));
|
|
182
|
+
collectMetric(buckets.discountedPrice.base, readOptionalPriceMetric(option, 'getDiscountedPrice'));
|
|
183
|
+
collectMetric(buckets.discountedPrice.monthly, readOptionalPriceMetric(option, 'getDiscountedPrice', { monthly: true }));
|
|
184
|
+
collectMetric(buckets.discount.base, readOptionalPriceMetric(option, 'getDiscount', {
|
|
185
|
+
percentage: false,
|
|
186
|
+
symbol: false
|
|
187
|
+
}, {
|
|
188
|
+
percentage: false
|
|
189
|
+
}));
|
|
190
|
+
collectMetric(buckets.discount.monthly, readOptionalPriceMetric(option, 'getDiscount', {
|
|
191
|
+
percentage: false,
|
|
192
|
+
monthly: true,
|
|
193
|
+
symbol: false
|
|
194
|
+
}, {
|
|
195
|
+
percentage: false,
|
|
196
|
+
monthly: true
|
|
197
|
+
}));
|
|
198
|
+
collectMetric(buckets.discount.percentage.base, readOptionalPriceMetric(option, 'getDiscount', {
|
|
199
|
+
percentage: true,
|
|
200
|
+
symbol: false
|
|
201
|
+
}, {
|
|
202
|
+
percentage: true
|
|
203
|
+
}));
|
|
204
|
+
collectMetric(buckets.discount.percentage.monthly, readOptionalPriceMetric(option, 'getDiscount', {
|
|
205
|
+
percentage: true,
|
|
206
|
+
monthly: true,
|
|
207
|
+
symbol: false
|
|
208
|
+
}, {
|
|
209
|
+
percentage: true,
|
|
210
|
+
monthly: true
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
function readPriceMetric(option, params = {}) {
|
|
214
|
+
return {
|
|
215
|
+
value: option.getPrice({
|
|
216
|
+
...params,
|
|
217
|
+
currency: false
|
|
218
|
+
}),
|
|
219
|
+
fmt: String(option.getPrice({
|
|
220
|
+
...params,
|
|
221
|
+
currency: true
|
|
222
|
+
}))
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function readOptionalPriceMetric(option, method, numericParams = {}, formattedParams = numericParams) {
|
|
226
|
+
const withOptional = option;
|
|
227
|
+
const fn = withOptional[method];
|
|
228
|
+
if (!fn) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
const value = fn.call(option, {
|
|
232
|
+
...numericParams,
|
|
233
|
+
currency: false
|
|
234
|
+
});
|
|
235
|
+
if (typeof value !== 'number' ||
|
|
236
|
+
!Number.isFinite(value)) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
const fmt = fn.call(option, formattedParams);
|
|
240
|
+
return {
|
|
241
|
+
value,
|
|
242
|
+
fmt: fmt === undefined ? '' : String(fmt)
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function collectMetric(accumulator, metric) {
|
|
246
|
+
if (!metric) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (!accumulator.hasValue ||
|
|
250
|
+
metric.value < accumulator.min.value) {
|
|
251
|
+
accumulator.min = metric;
|
|
252
|
+
}
|
|
253
|
+
if (!accumulator.hasValue ||
|
|
254
|
+
metric.value > accumulator.max.value) {
|
|
255
|
+
accumulator.max = metric;
|
|
256
|
+
}
|
|
257
|
+
accumulator.hasValue = true;
|
|
258
|
+
}
|
|
259
|
+
function createAccumulator() {
|
|
260
|
+
return {
|
|
261
|
+
min: emptyComputeValue(Number.MAX_SAFE_INTEGER),
|
|
262
|
+
max: emptyComputeValue(Number.MIN_SAFE_INTEGER),
|
|
263
|
+
hasValue: false
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function createMonthlyAccumulator() {
|
|
267
|
+
return {
|
|
268
|
+
base: createAccumulator(),
|
|
269
|
+
monthly: createAccumulator()
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function createDiscountAccumulator() {
|
|
273
|
+
return {
|
|
274
|
+
...createMonthlyAccumulator(),
|
|
275
|
+
percentage: createMonthlyAccumulator()
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function finalizeAccumulator(accumulator) {
|
|
279
|
+
return {
|
|
280
|
+
min: accumulator.hasValue
|
|
281
|
+
? accumulator.min
|
|
282
|
+
: emptyComputeValue(Number.MAX_SAFE_INTEGER),
|
|
283
|
+
max: accumulator.hasValue
|
|
284
|
+
? accumulator.max
|
|
285
|
+
: emptyComputeValue(Number.MIN_SAFE_INTEGER)
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function finalizeMonthlyAccumulator(accumulator) {
|
|
289
|
+
return {
|
|
290
|
+
...finalizeAccumulator(accumulator.base),
|
|
291
|
+
monthly: finalizeAccumulator(accumulator.monthly)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function finalizeDiscountAccumulator(accumulator) {
|
|
295
|
+
return {
|
|
296
|
+
...finalizeMonthlyAccumulator(accumulator),
|
|
297
|
+
percentage: finalizeMonthlyAccumulator(accumulator.percentage)
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function emptyComputeValue(value) {
|
|
301
|
+
return {
|
|
302
|
+
value,
|
|
303
|
+
fmt: ''
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function rangeKey(range) {
|
|
307
|
+
return [
|
|
308
|
+
range.min.value,
|
|
309
|
+
range.min.fmt,
|
|
310
|
+
range.max.value,
|
|
311
|
+
range.max.fmt
|
|
312
|
+
].join(':');
|
|
313
|
+
}
|
|
314
|
+
function throwIfAborted(signal) {
|
|
315
|
+
if (signal?.aborted) {
|
|
316
|
+
throw signal.reason ?? new DOMException('Aborted', 'AbortError');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=compute.expand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute.expand.js","sourceRoot":"","sources":["../../../src/compute/compute.expand.ts"],"names":[],"mappings":"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","sourcesContent":["// src/compute/compute.expand.ts\n\nimport type {\n BdActionCommand,\n UpdateByDelta\n} from '@/core/actions.js';\nimport type {\n Product,\n ProductOption,\n Store\n} from '@repobit/dex-store';\nimport {\n type BdActionDomain,\n type BdSeedDeclaration\n} from './compute.scan.js';\nimport {\n resolveScenarioCommand,\n scenarioKey,\n type BdOptionScenario\n} from './option-resolver.js';\n\n/**\n * Min/max reduction over every option reachable from a compute boundary.\n */\nexport type BdComputeValue = Readonly<{\n value: number;\n fmt : string;\n}>;\n\nexport type BdComputeRange = Readonly<{\n min: BdComputeValue;\n max: BdComputeValue;\n}>;\n\nexport type BdComputeMonthlyRange = Readonly<BdComputeRange & {\n monthly: BdComputeRange;\n}>;\n\nexport type BdComputeDiscountRange = Readonly<BdComputeMonthlyRange & {\n percentage: BdComputeMonthlyRange;\n}>;\n\nexport type BdComputeResult = Readonly<{\n min : number;\n max : number;\n minFormatted: string;\n maxFormatted: string;\n scenarios : number;\n\n price : BdComputeMonthlyRange;\n discountedPrice: BdComputeMonthlyRange;\n discount : BdComputeDiscountRange;\n}>;\n\nconst DEFAULT_MAX_SCENARIOS = 10000;\n\n/**\n * Resolve declarative seeds into concrete product/option scenarios. Seeds that\n * cannot be resolved (unknown product, missing option) are dropped.\n */\nexport async function resolveSeedScenarios(\n store: Store,\n seeds: readonly BdSeedDeclaration[],\n signal?: AbortSignal\n): Promise<BdOptionScenario[]> {\n const resolved = await Promise.all(\n seeds.map(async (seed) => {\n const product =\n await store.getProduct({\n id : seed.productId,\n campaign: seed.campaign\n });\n\n throwIfAborted(signal);\n\n if (!product) {\n return undefined;\n }\n\n const option =\n await product.getOption({\n devices : seed.devices,\n subscription: seed.subscription\n });\n\n if (!option) {\n return undefined;\n }\n\n return {\n product,\n option\n } satisfies BdOptionScenario;\n })\n );\n\n return resolved.filter(\n (scenario): scenario is BdOptionScenario => scenario !== undefined\n );\n}\n\n/**\n * Fixed-point expansion of the reachable option space.\n *\n * Starting from the seeds, every static command and every domain-expanded\n * command is applied to every newly discovered scenario, until no new scenario\n * appears. Domains expand against the scenario's own product, which is what\n * makes a product-switching action correctly explore the new product's\n * device/subscription dimensions rather than the originally rendered ones.\n */\nexport async function expandScenarios(\n params: Readonly<{\n store? : Store;\n seeds : readonly BdOptionScenario[];\n staticCommands: readonly BdActionCommand[];\n domains : readonly BdActionDomain[];\n signal? : AbortSignal;\n maxScenarios? : number;\n }>\n): Promise<BdOptionScenario[]> {\n const limit =\n params.maxScenarios ?? DEFAULT_MAX_SCENARIOS;\n\n const visited = new Map<string, BdOptionScenario>();\n let frontier: BdOptionScenario[] = [];\n\n for (const seed of params.seeds) {\n const key = scenarioKey(seed);\n\n if (!visited.has(key)) {\n visited.set(key, seed);\n frontier.push(seed);\n }\n }\n\n while (frontier.length > 0 && visited.size < limit) {\n const next: BdOptionScenario[] = [];\n\n for (const scenario of frontier) {\n const commands = [\n ...params.staticCommands,\n ...commandsFromDomains(params.domains, scenario.product)\n ];\n\n for (const command of commands) {\n throwIfAborted(params.signal);\n\n const result =\n await resolveScenarioCommand({\n store: params.store,\n scenario,\n command\n });\n\n if (!result) {\n continue;\n }\n\n const key = scenarioKey(result);\n\n if (visited.has(key)) {\n continue;\n }\n\n visited.set(key, result);\n next.push(result);\n\n if (visited.size >= limit) {\n break;\n }\n }\n\n if (visited.size >= limit) {\n break;\n }\n }\n\n frontier = next;\n }\n\n return [...visited.values()];\n}\n\n/**\n * Expand a domain into per-value set commands against a specific product. A\n * devices domain becomes one useAsValue update per device value the product\n * offers, and likewise for subscriptions.\n */\nexport function commandsFromDomains(\n domains: readonly BdActionDomain[],\n product: Product\n): BdActionCommand[] {\n const commands: BdActionCommand[] = [];\n\n for (const domain of domains) {\n const values =\n domain.type === 'devices'\n ? product.getDevices().values\n : product.getSubscriptions().values;\n\n for (const value of values) {\n if (!isValueInDomain(value, domain)) {\n continue;\n }\n\n commands.push(\n domainValueCommand(domain.type, value)\n );\n }\n }\n\n return commands;\n}\n\nfunction isValueInDomain(\n value: number,\n domain: BdActionDomain\n): boolean {\n if (\n domain.min !== undefined &&\n value < domain.min\n ) {\n return false;\n }\n\n if (\n domain.max !== undefined &&\n value > domain.max\n ) {\n return false;\n }\n\n return true;\n}\n\nfunction domainValueCommand(\n type: UpdateByDelta['type'],\n value: number\n): BdActionCommand {\n return {\n kind : 'update-by-delta',\n update: {\n type,\n delta : value,\n useAsValue: true\n }\n };\n}\n\n/**\n * Reduce a set of scenarios to a min/max price range. Prices are read with the\n * same ProductOption.getPrice() the runtime uses, so formatting and currency\n * match exactly.\n */\nexport function reducePriceRange(\n scenarios: readonly BdOptionScenario[]\n): BdComputeResult | undefined {\n const price = createMonthlyAccumulator();\n const discountedPrice = createMonthlyAccumulator();\n const discount = createDiscountAccumulator();\n\n for (const scenario of scenarios) {\n collectOptionCompute(\n scenario.option,\n {\n price,\n discountedPrice,\n discount\n }\n );\n }\n\n if (!price.base.hasValue) {\n return undefined;\n }\n\n const priceRange = finalizeMonthlyAccumulator(price);\n\n return {\n min : priceRange.min.value,\n max : priceRange.max.value,\n minFormatted: priceRange.min.fmt,\n maxFormatted: priceRange.max.fmt,\n scenarios : scenarios.length,\n\n price: priceRange,\n discountedPrice:\n finalizeMonthlyAccumulator(discountedPrice),\n discount:\n finalizeDiscountAccumulator(discount)\n };\n}\n\n/**\n * Convenience: a stable signature for a compute result so consumers can cheaply\n * detect a meaningful change.\n */\nexport function computeResultKey(\n result: BdComputeResult | undefined\n): string {\n if (!result) {\n return '';\n }\n\n return [\n result.scenarios,\n rangeKey(result.price),\n rangeKey(result.price.monthly),\n rangeKey(result.discountedPrice),\n rangeKey(result.discountedPrice.monthly),\n rangeKey(result.discount),\n rangeKey(result.discount.monthly),\n rangeKey(result.discount.percentage),\n rangeKey(result.discount.percentage.monthly)\n ].join('|');\n}\n\ntype ComputeBuckets = {\n price : MonthlyAccumulator;\n discountedPrice: MonthlyAccumulator;\n discount : DiscountAccumulator;\n};\n\ntype Accumulator = {\n min : BdComputeValue;\n max : BdComputeValue;\n hasValue: boolean;\n};\n\ntype MonthlyAccumulator = {\n base : Accumulator;\n monthly: Accumulator;\n};\n\ntype DiscountAccumulator = MonthlyAccumulator & {\n percentage: MonthlyAccumulator;\n};\n\ntype OptionalPriceMethod =\n | 'getDiscountedPrice'\n | 'getDiscount';\n\ntype OptionWithOptionalPrices = ProductOption & Partial<Record<\n OptionalPriceMethod,\n (params?: Record<string, unknown>) => string | number | undefined\n>>;\n\nfunction collectOptionCompute(\n option: ProductOption,\n buckets: ComputeBuckets\n): void {\n collectMetric(\n buckets.price.base,\n readPriceMetric(option)\n );\n collectMetric(\n buckets.price.monthly,\n readPriceMetric(\n option,\n { monthly: true }\n )\n );\n\n collectMetric(\n buckets.discountedPrice.base,\n readOptionalPriceMetric(\n option,\n 'getDiscountedPrice'\n )\n );\n collectMetric(\n buckets.discountedPrice.monthly,\n readOptionalPriceMetric(\n option,\n 'getDiscountedPrice',\n { monthly: true }\n )\n );\n\n collectMetric(\n buckets.discount.base,\n readOptionalPriceMetric(\n option,\n 'getDiscount',\n {\n percentage: false,\n symbol : false\n },\n {\n percentage: false\n }\n )\n );\n collectMetric(\n buckets.discount.monthly,\n readOptionalPriceMetric(\n option,\n 'getDiscount',\n {\n percentage: false,\n monthly : true,\n symbol : false\n },\n {\n percentage: false,\n monthly : true\n }\n )\n );\n collectMetric(\n buckets.discount.percentage.base,\n readOptionalPriceMetric(\n option,\n 'getDiscount',\n {\n percentage: true,\n symbol : false\n },\n {\n percentage: true\n }\n )\n );\n collectMetric(\n buckets.discount.percentage.monthly,\n readOptionalPriceMetric(\n option,\n 'getDiscount',\n {\n percentage: true,\n monthly : true,\n symbol : false\n },\n {\n percentage: true,\n monthly : true\n }\n )\n );\n}\n\nfunction readPriceMetric(\n option: ProductOption,\n params: {\n monthly?: boolean;\n } = {}\n): BdComputeValue {\n return {\n value: option.getPrice({\n ...params,\n currency: false\n }),\n fmt: String(\n option.getPrice({\n ...params,\n currency: true\n })\n )\n };\n}\n\nfunction readOptionalPriceMetric(\n option: ProductOption,\n method: OptionalPriceMethod,\n numericParams: Record<string, unknown> = {},\n formattedParams: Record<string, unknown> = numericParams\n): BdComputeValue | undefined {\n const withOptional = option as OptionWithOptionalPrices;\n const fn = withOptional[method];\n\n if (!fn) {\n return undefined;\n }\n\n const value = fn.call(\n option,\n {\n ...numericParams,\n currency: false\n }\n );\n\n if (\n typeof value !== 'number' ||\n !Number.isFinite(value)\n ) {\n return undefined;\n }\n\n const fmt = fn.call(\n option,\n formattedParams\n );\n\n return {\n value,\n fmt: fmt === undefined ? '' : String(fmt)\n };\n}\n\nfunction collectMetric(\n accumulator: Accumulator,\n metric: BdComputeValue | undefined\n): void {\n if (!metric) {\n return;\n }\n\n if (\n !accumulator.hasValue ||\n metric.value < accumulator.min.value\n ) {\n accumulator.min = metric;\n }\n\n if (\n !accumulator.hasValue ||\n metric.value > accumulator.max.value\n ) {\n accumulator.max = metric;\n }\n\n accumulator.hasValue = true;\n}\n\nfunction createAccumulator(): Accumulator {\n return {\n min : emptyComputeValue(Number.MAX_SAFE_INTEGER),\n max : emptyComputeValue(Number.MIN_SAFE_INTEGER),\n hasValue: false\n };\n}\n\nfunction createMonthlyAccumulator(): MonthlyAccumulator {\n return {\n base : createAccumulator(),\n monthly: createAccumulator()\n };\n}\n\nfunction createDiscountAccumulator(): DiscountAccumulator {\n return {\n ...createMonthlyAccumulator(),\n percentage: createMonthlyAccumulator()\n };\n}\n\nfunction finalizeAccumulator(\n accumulator: Accumulator\n): BdComputeRange {\n return {\n min: accumulator.hasValue\n ? accumulator.min\n : emptyComputeValue(Number.MAX_SAFE_INTEGER),\n max: accumulator.hasValue\n ? accumulator.max\n : emptyComputeValue(Number.MIN_SAFE_INTEGER)\n };\n}\n\nfunction finalizeMonthlyAccumulator(\n accumulator: MonthlyAccumulator\n): BdComputeMonthlyRange {\n return {\n ...finalizeAccumulator(accumulator.base),\n monthly: finalizeAccumulator(accumulator.monthly)\n };\n}\n\nfunction finalizeDiscountAccumulator(\n accumulator: DiscountAccumulator\n): BdComputeDiscountRange {\n return {\n ...finalizeMonthlyAccumulator(accumulator),\n percentage:\n finalizeMonthlyAccumulator(accumulator.percentage)\n };\n}\n\nfunction emptyComputeValue(\n value: number\n): BdComputeValue {\n return {\n value,\n fmt: ''\n };\n}\n\nfunction rangeKey(\n range: BdComputeRange\n): string {\n return [\n range.min.value,\n range.min.fmt,\n range.max.value,\n range.max.fmt\n ].join(':');\n}\n\nfunction throwIfAborted(signal?: AbortSignal): void {\n if (signal?.aborted) {\n throw signal.reason ?? new DOMException('Aborted', 'AbortError');\n }\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { BdActionCommand } from '../core/actions.js';
|
|
2
|
+
export declare const DATA_DEVICES_DOMAIN: "data-store-devices";
|
|
3
|
+
export declare const DATA_SUBSCRIPTION_DOMAIN: "data-store-subscription";
|
|
4
|
+
export type BdActionDomainKind = 'devices' | 'subscription';
|
|
5
|
+
export type BdActionDomain = Readonly<{
|
|
6
|
+
type: BdActionDomainKind;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* A declarative seed scenario used by standalone compute helpers. Live node
|
|
12
|
+
* compute uses resolved ProductOption seeds collected from option nodes.
|
|
13
|
+
*/
|
|
14
|
+
export type BdSeedDeclaration = Readonly<{
|
|
15
|
+
productId: string;
|
|
16
|
+
campaign?: string;
|
|
17
|
+
bundle: boolean;
|
|
18
|
+
devices: number;
|
|
19
|
+
subscription: number;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Result of scanning the action elements owned by a compute boundary.
|
|
23
|
+
*
|
|
24
|
+
* - staticCommands: concrete jumps (button says product-b, option says
|
|
25
|
+
* subscription=24, select option children, etc).
|
|
26
|
+
* - domains: controls that represent a devices / subscription space of
|
|
27
|
+
* whatever product a scenario lands on. Inputs may bound that space by min/max.
|
|
28
|
+
*/
|
|
29
|
+
export type BdComputeScan = Readonly<{
|
|
30
|
+
staticCommands: readonly BdActionCommand[];
|
|
31
|
+
domains: readonly BdActionDomain[];
|
|
32
|
+
}>;
|
|
33
|
+
export declare const EMPTY_COMPUTE_SCAN: BdComputeScan;
|
|
34
|
+
/**
|
|
35
|
+
* Stable fingerprint of the declared seeds. Used as a compute task arg so the
|
|
36
|
+
* controller only reruns when the declared seed set actually changes, not on
|
|
37
|
+
* every host update.
|
|
38
|
+
*/
|
|
39
|
+
export declare function seedDeclarationsKey(seeds: readonly BdSeedDeclaration[]): string;
|
|
40
|
+
/**
|
|
41
|
+
* Turn the action elements owned by a compute boundary into static commands and
|
|
42
|
+
* dynamic domains. Reuses the same parser as the runtime action layer; the only
|
|
43
|
+
* difference is breadth: a select contributes all of its option children here,
|
|
44
|
+
* not just the selected one.
|
|
45
|
+
*/
|
|
46
|
+
export declare function scanActionElements(elements: Iterable<HTMLElement>): BdComputeScan;
|
|
47
|
+
export declare function collectActionElementCompute(element: HTMLElement): BdComputeScan;
|
|
48
|
+
export declare function mergeComputeScans(scans: Iterable<BdComputeScan>): BdComputeScan;
|
|
49
|
+
/**
|
|
50
|
+
* A select / control is a dynamic domain only when it is actionable AND tagged
|
|
51
|
+
* as a devices or subscription dimension. A plain `data-store-devices` span just
|
|
52
|
+
* renders the current value and contributes nothing to compute.
|
|
53
|
+
*/
|
|
54
|
+
export declare function readComputeDomain(element: HTMLElement): BdActionDomain | undefined;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/compute/compute.scan.ts
|
|
2
|
+
import { isActionElement, readBdActionCommand, readInputValueCommand } from '../actions/action.attributes.js';
|
|
3
|
+
export const DATA_DEVICES_DOMAIN = 'data-store-devices';
|
|
4
|
+
export const DATA_SUBSCRIPTION_DOMAIN = 'data-store-subscription';
|
|
5
|
+
export const EMPTY_COMPUTE_SCAN = Object.freeze({
|
|
6
|
+
staticCommands: Object.freeze([]),
|
|
7
|
+
domains: Object.freeze([])
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Stable fingerprint of the declared seeds. Used as a compute task arg so the
|
|
11
|
+
* controller only reruns when the declared seed set actually changes, not on
|
|
12
|
+
* every host update.
|
|
13
|
+
*/
|
|
14
|
+
export function seedDeclarationsKey(seeds) {
|
|
15
|
+
return seeds
|
|
16
|
+
.map((seed) => [
|
|
17
|
+
seed.productId,
|
|
18
|
+
seed.campaign ?? '',
|
|
19
|
+
seed.bundle ? '1' : '0',
|
|
20
|
+
seed.devices,
|
|
21
|
+
seed.subscription
|
|
22
|
+
].join('|'))
|
|
23
|
+
.sort()
|
|
24
|
+
.join(';');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Turn the action elements owned by a compute boundary into static commands and
|
|
28
|
+
* dynamic domains. Reuses the same parser as the runtime action layer; the only
|
|
29
|
+
* difference is breadth: a select contributes all of its option children here,
|
|
30
|
+
* not just the selected one.
|
|
31
|
+
*/
|
|
32
|
+
export function scanActionElements(elements) {
|
|
33
|
+
const staticCommands = [];
|
|
34
|
+
const domains = [];
|
|
35
|
+
for (const element of elements) {
|
|
36
|
+
const scan = collectActionElementCompute(element);
|
|
37
|
+
staticCommands.push(...scan.staticCommands);
|
|
38
|
+
domains.push(...scan.domains);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
staticCommands,
|
|
42
|
+
domains
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function collectActionElementCompute(element) {
|
|
46
|
+
const domain = readComputeDomain(element);
|
|
47
|
+
if (domain) {
|
|
48
|
+
return {
|
|
49
|
+
staticCommands: [],
|
|
50
|
+
domains: [domain]
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (element instanceof HTMLSelectElement) {
|
|
54
|
+
const staticCommands = [];
|
|
55
|
+
collectSelectCommands(element, staticCommands);
|
|
56
|
+
return {
|
|
57
|
+
staticCommands,
|
|
58
|
+
domains: []
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const command = element instanceof HTMLInputElement
|
|
62
|
+
? readInputActionCommand(element)
|
|
63
|
+
: readBdActionCommand(element);
|
|
64
|
+
if (!command) {
|
|
65
|
+
return EMPTY_COMPUTE_SCAN;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
staticCommands: [command],
|
|
69
|
+
domains: []
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function mergeComputeScans(scans) {
|
|
73
|
+
const staticCommands = [];
|
|
74
|
+
const domains = [];
|
|
75
|
+
for (const scan of scans) {
|
|
76
|
+
staticCommands.push(...scan.staticCommands);
|
|
77
|
+
domains.push(...scan.domains);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
staticCommands,
|
|
81
|
+
domains
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A select / control is a dynamic domain only when it is actionable AND tagged
|
|
86
|
+
* as a devices or subscription dimension. A plain `data-store-devices` span just
|
|
87
|
+
* renders the current value and contributes nothing to compute.
|
|
88
|
+
*/
|
|
89
|
+
export function readComputeDomain(element) {
|
|
90
|
+
if (!isActionElement(element)) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
if (element instanceof HTMLInputElement &&
|
|
94
|
+
(element.type === 'number' ||
|
|
95
|
+
element.type === 'text')) {
|
|
96
|
+
return readInputComputeDomain(element);
|
|
97
|
+
}
|
|
98
|
+
if (element.hasAttribute(DATA_DEVICES_DOMAIN)) {
|
|
99
|
+
return {
|
|
100
|
+
type: 'devices'
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (element.hasAttribute(DATA_SUBSCRIPTION_DOMAIN)) {
|
|
104
|
+
return {
|
|
105
|
+
type: 'subscription'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
function readInputComputeDomain(input) {
|
|
111
|
+
const command = readBdActionCommand(input);
|
|
112
|
+
if (command?.kind !== 'update-by-delta') {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
type: command.update.type,
|
|
117
|
+
min: toOptionalNumber(input.min) ?? command.update.min,
|
|
118
|
+
max: toOptionalNumber(input.max) ?? command.update.max
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function readInputActionCommand(input) {
|
|
122
|
+
switch (input.type) {
|
|
123
|
+
case 'number':
|
|
124
|
+
case 'text':
|
|
125
|
+
return readInputValueCommand(input);
|
|
126
|
+
default:
|
|
127
|
+
return readBdActionCommand(input);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function collectSelectCommands(select, out) {
|
|
131
|
+
let collected = false;
|
|
132
|
+
for (const option of Array.from(select.options)) {
|
|
133
|
+
const command = readBdActionCommand(option);
|
|
134
|
+
if (command) {
|
|
135
|
+
out.push(command);
|
|
136
|
+
collected = true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (collected) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const command = readBdActionCommand(select);
|
|
143
|
+
if (command?.kind === 'set-action') {
|
|
144
|
+
out.push(command);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function toOptionalNumber(value) {
|
|
148
|
+
if (value === undefined) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const trimmed = value.trim();
|
|
152
|
+
if (!trimmed) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const parsed = Number(trimmed);
|
|
156
|
+
return Number.isNaN(parsed)
|
|
157
|
+
? undefined
|
|
158
|
+
: parsed;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=compute.scan.js.map
|