@elqora/dgp-core 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,20 +1,24 @@
1
1
  # DGP Core
2
2
 
3
- DGP Core is the framework-neutral interpreter for canonical Digital Goods Protocol product definitions. It resolves product context, visibility, relationships, selected-trigger effects, service bindings, capabilities, and fallback registrations into deterministic query results.
3
+ DGP Core is the complete framework-neutral product engine for canonical Digital Goods Protocol product definitions. It provides pure interpretation primitives and a stateful `ProductBuilder` facade for canonical authoring normalization, indexing, graph projection, visibility, relationships, services, fallbacks, and deterministic save preparation.
4
4
 
5
5
  DGP Core does not render interfaces, implement editorial workflows, or execute fulfillment. DGP Ordering handles customer orchestration, DGP Workspace supplies reusable editorial infrastructure, and the DGP SDK defines backend execution.
6
6
 
7
7
  ## Responsibilities
8
8
 
9
9
  - Deterministic product indexing
10
+ - Canonical authoring normalization and save cleanup
11
+ - Stateful Builder replacement with index/interpreter refresh
12
+ - Framework-neutral graph projection
10
13
  - Cycle-safe graph, lineage, and relationship traversal
11
14
  - Context and visibility resolution
12
15
  - Recursive option traversal and selected-trigger effect resolution
13
16
  - Service binding and capability resolution
14
17
  - Node-first, then global fallback-candidate resolution
18
+ - Catalog service filtering and fallback eligibility evidence
15
19
  - A composed, reusable product interpreter
16
20
 
17
- Core accepts canonical v1 definitions as-is. It does not repair or normalize legacy documents, validate publication coherence, mutate customer values, execute expressions, calculate utilities, or select an operational fallback service. Those concerns belong to their owning packages or the backend handler.
21
+ Core accepts canonical v1 definitions only. `normalizeProductDefinition` is an authoring-boundary operation that fills canonical collection defaults, normalizes bindings and authored ordering, and never recognizes legacy aliases. Published inputs must still be validated rather than silently repaired. Core does not own publication diagnostics, editorial history, customer validation, expression execution, utility calculation, final pricing, charges, or operational fallback selection.
18
22
 
19
23
  ## Install
20
24
 
@@ -27,13 +31,17 @@ npm install @elqora/dgp-core @elqora/dgp-spec
27
31
  ## Usage
28
32
 
29
33
  ```ts
30
- import { createProductInterpreter } from "@elqora/dgp-core";
34
+ import { ProductBuilder, createProductInterpreter } from "@elqora/dgp-core";
31
35
 
32
36
  const product = createProductInterpreter(definition);
33
37
  const context = product.resolveContext("checkout", selectedTriggerIds);
34
38
 
35
39
  context?.visibility.fieldIds;
36
40
  context?.capabilities.values;
41
+
42
+ const builder = new ProductBuilder(definition);
43
+ builder.tree();
44
+ builder.cleanedDefinition();
37
45
  ```
38
46
 
39
47
  Public functions are pure with respect to the supplied definition. Returned collections are newly allocated unless explicitly documented as canonical source nodes.
@@ -47,6 +55,7 @@ Public functions are pure with respect to the supplied definition. Returned coll
47
55
  - `order_for_tags` pins visible fields first without introducing hidden fields.
48
56
  - Ancestor capability requirements take precedence over conflicting descendant declarations, and their origin filter is reported without modifying the document.
49
57
  - Option effects may reveal a field and narrow its recursively indexed options. Value effects are returned as declarations for Ordering to apply.
58
+ - The active filter is the first implicit value-effect trigger; ancestor filters are context only, followed by selected button and option triggers in selection order.
50
59
  - Fallback registrations are listed node-first and then by the primary service's global list, with duplicates and self-references removed. Backend policy still decides operational eligibility.
51
60
 
52
61
  ## Development
@@ -76,7 +85,7 @@ npm run check
76
85
 
77
86
  ## Status
78
87
 
79
- The initial DGP v1 interpretation slice is implemented against `@elqora/dgp-spec` 1.x.
88
+ The complete canonical v1 Core migration is implemented against the ratified, unreleased `@elqora/dgp-spec` 1.4 contract. Package 1.1 is unreleased.
80
89
 
81
90
  ## License
82
91
 
@@ -0,0 +1,40 @@
1
+ import type { HandlerService, ProductDefinition } from "@elqora/dgp-spec";
2
+ import { type ProductGraphSnapshot } from "./graph.js";
3
+ import { type ProductIndex } from "./indexer.js";
4
+ import { type ProductInterpreter } from "./interpreter.js";
5
+ import { type NormalizeProductDefinitionOptions, type ProductDefinitionDraft } from "./normalization.js";
6
+ import type { ResolvedVisibility } from "./visibility.js";
7
+ export interface ProductBuilderOptions {
8
+ selected_trigger_ids?: Iterable<string>;
9
+ services?: readonly HandlerService[];
10
+ normalization?: NormalizeProductDefinitionOptions;
11
+ }
12
+ /**
13
+ * Canonical stateful authoring facade. It owns no history and performs no publication validation;
14
+ * each load simply replaces the current normalized document and refreshes all derived views.
15
+ */
16
+ export declare class ProductBuilder {
17
+ #private;
18
+ constructor(input?: ProductDefinitionDraft, options?: ProductBuilderOptions);
19
+ get definition(): ProductDefinition;
20
+ get index(): ProductIndex;
21
+ get interpreter(): ProductInterpreter;
22
+ load(input: ProductDefinitionDraft): this;
23
+ replace(input: ProductDefinitionDraft): this;
24
+ setOptions(patch: ProductBuilderOptions): this;
25
+ selectedTriggerIds(): string[];
26
+ services(): readonly HandlerService[];
27
+ tree(): ProductGraphSnapshot;
28
+ resolveVisibility(filterId: string, selectedTriggerIds?: string[]): ResolvedVisibility;
29
+ visibleFields(filterId: string, selectedTriggerIds?: string[]): string[];
30
+ cleanedDefinition(): ProductDefinition;
31
+ getProps(): ProductDefinition;
32
+ cleanedProps(): ProductDefinition;
33
+ isFilterId(id: string): boolean;
34
+ isFieldId(id: string): boolean;
35
+ isOptionId(id: string): boolean;
36
+ }
37
+ export declare function createProductBuilder(input?: ProductDefinitionDraft, options?: ProductBuilderOptions): ProductBuilder;
38
+ /** Canonical convenience name retained for the established authoring facade experience. */
39
+ export declare const createBuilder: typeof createProductBuilder;
40
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1E,OAAO,EAAgC,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAEL,KAAK,iCAAiC,EACtC,KAAK,sBAAsB,EAC5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IACrC,aAAa,CAAC,EAAE,iCAAiC,CAAC;CACnD;AAED;;;GAGG;AACH,qBAAa,cAAc;;gBASvB,KAAK,GAAE,sBAAkE,EACzE,OAAO,GAAE,qBAA0B;IAUrC,IAAI,UAAU,IAAI,iBAAiB,CAElC;IAED,IAAI,KAAK,IAAI,YAAY,CAExB;IAED,IAAI,WAAW,IAAI,kBAAkB,CAEpC;IAED,IAAI,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI;IAOzC,OAAO,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI;IAI5C,UAAU,CAAC,KAAK,EAAE,qBAAqB,GAAG,IAAI;IAS9C,kBAAkB,IAAI,MAAM,EAAE;IAI9B,QAAQ,IAAI,SAAS,cAAc,EAAE;IAIrC,IAAI,IAAI,oBAAoB;IAI5B,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,kBAAkB,WAA2B,GAAG,kBAAkB;IAItG,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,kBAAkB,WAA2B,GAAG,MAAM,EAAE;IAIxF,iBAAiB,IAAI,iBAAiB;IAItC,QAAQ,IAAI,iBAAiB;IAI7B,YAAY,IAAI,iBAAiB;IAIjC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI/B,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI9B,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;CAGhC;AAED,wBAAgB,oBAAoB,CAClC,KAAK,CAAC,EAAE,sBAAsB,EAC9B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,cAAc,CAEhB;AAED,2FAA2F;AAC3F,eAAO,MAAM,aAAa,6BAAuB,CAAC"}
@@ -0,0 +1,93 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ import { prepareProductDefinitionForSave } from "./cleanup.js";
3
+ import { projectProductGraphWithIndex } from "./graph.js";
4
+ import { createProductIndex } from "./indexer.js";
5
+ import { createProductInterpreter } from "./interpreter.js";
6
+ import { normalizeProductDefinition, } from "./normalization.js";
7
+ /**
8
+ * Canonical stateful authoring facade. It owns no history and performs no publication validation;
9
+ * each load simply replaces the current normalized document and refreshes all derived views.
10
+ */
11
+ export class ProductBuilder {
12
+ #definition;
13
+ #index;
14
+ #interpreter;
15
+ #selectedTriggerIds;
16
+ #services;
17
+ #normalization;
18
+ constructor(input = { id: "draft", name: "Untitled product" }, options = {}) {
19
+ this.#normalization = { ...options.normalization };
20
+ this.#selectedTriggerIds = [...(options.selected_trigger_ids ?? [])];
21
+ this.#services = options.services ?? [];
22
+ this.#definition = normalizeProductDefinition(input, this.#normalization);
23
+ this.#index = createProductIndex(this.#definition);
24
+ this.#interpreter = createProductInterpreter(this.#definition);
25
+ }
26
+ get definition() {
27
+ return this.#definition;
28
+ }
29
+ get index() {
30
+ return this.#index;
31
+ }
32
+ get interpreter() {
33
+ return this.#interpreter;
34
+ }
35
+ load(input) {
36
+ this.#definition = normalizeProductDefinition(input, this.#normalization);
37
+ this.#index = createProductIndex(this.#definition);
38
+ this.#interpreter = createProductInterpreter(this.#definition);
39
+ return this;
40
+ }
41
+ replace(input) {
42
+ return this.load(input);
43
+ }
44
+ setOptions(patch) {
45
+ if (patch.selected_trigger_ids !== undefined) {
46
+ this.#selectedTriggerIds = [...patch.selected_trigger_ids];
47
+ }
48
+ if (patch.services !== undefined)
49
+ this.#services = patch.services;
50
+ if (patch.normalization !== undefined)
51
+ this.#normalization = { ...patch.normalization };
52
+ return this;
53
+ }
54
+ selectedTriggerIds() {
55
+ return [...this.#selectedTriggerIds];
56
+ }
57
+ services() {
58
+ return this.#services;
59
+ }
60
+ tree() {
61
+ return projectProductGraphWithIndex(this.#definition, this.#index);
62
+ }
63
+ resolveVisibility(filterId, selectedTriggerIds = this.#selectedTriggerIds) {
64
+ return this.#interpreter.resolveVisibility(filterId, selectedTriggerIds);
65
+ }
66
+ visibleFields(filterId, selectedTriggerIds = this.#selectedTriggerIds) {
67
+ return this.resolveVisibility(filterId, selectedTriggerIds).fieldIds;
68
+ }
69
+ cleanedDefinition() {
70
+ return prepareProductDefinitionForSave(this.#definition);
71
+ }
72
+ getProps() {
73
+ return this.#definition;
74
+ }
75
+ cleanedProps() {
76
+ return this.cleanedDefinition();
77
+ }
78
+ isFilterId(id) {
79
+ return this.#index.getFilter(id) !== undefined;
80
+ }
81
+ isFieldId(id) {
82
+ return this.#index.getField(id) !== undefined;
83
+ }
84
+ isOptionId(id) {
85
+ return this.#index.getOption(id) !== undefined;
86
+ }
87
+ }
88
+ export function createProductBuilder(input, options) {
89
+ return new ProductBuilder(input, options);
90
+ }
91
+ /** Canonical convenience name retained for the established authoring facade experience. */
92
+ export const createBuilder = createProductBuilder;
93
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.js","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { ProductDefinition } from "@elqora/dgp-spec";
2
+ import { type ProductDefinitionDraft } from "./normalization.js";
3
+ /** Return deterministic canonical save output with dead authoring references removed. */
4
+ export declare function prepareProductDefinitionForSave(input: ProductDefinitionDraft): ProductDefinition;
5
+ //# sourceMappingURL=cleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../src/cleanup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,iBAAiB,EAIlB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAA8B,KAAK,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAwE7F,yFAAyF;AACzF,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,sBAAsB,GAAG,iBAAiB,CAuEhG"}
@@ -0,0 +1,149 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ import { createProductIndex } from "./indexer.js";
3
+ import { normalizeProductDefinition } from "./normalization.js";
4
+ import { fieldOptionIds } from "./options.js";
5
+ import { serviceBindingForNode } from "./services.js";
6
+ function uniqueKnown(values, known) {
7
+ const result = [];
8
+ const seen = new Set();
9
+ for (const value of values) {
10
+ if (!known.has(value) || seen.has(value))
11
+ continue;
12
+ seen.add(value);
13
+ result.push(value);
14
+ }
15
+ return result;
16
+ }
17
+ function isTrigger(index, id) {
18
+ const node = index.getNode(id);
19
+ return node.kind === "option" || (node.kind === "field" && node.field.button === true);
20
+ }
21
+ function fieldIsReachabilityReferenced(definition, fieldId) {
22
+ if (definition.filters.some((filter) => filter.includes?.includes(fieldId) === true))
23
+ return true;
24
+ if (Object.values(definition.includes_for_buttons).some((ids) => ids.includes(fieldId)))
25
+ return true;
26
+ return Object.values(definition.option_effects_for_buttons).some((targets) => targets[fieldId]?.force_visible === true);
27
+ }
28
+ function pruneFields(definition) {
29
+ return definition.fields.filter((field) => {
30
+ if (field.pricing_role !== "utility" || field.bind_id !== undefined)
31
+ return true;
32
+ return fieldIsReachabilityReferenced(definition, field.id);
33
+ });
34
+ }
35
+ function pruneFallbacks(definition, index) {
36
+ if (definition.fallbacks === null)
37
+ return null;
38
+ const nodes = {};
39
+ const global = {};
40
+ for (const [nodeId, candidates] of Object.entries(definition.fallbacks.nodes ?? {})) {
41
+ const primary = serviceBindingForNode(index, nodeId);
42
+ if (primary === undefined)
43
+ continue;
44
+ const seen = new Set([String(primary)]);
45
+ const next = candidates.filter((candidate) => {
46
+ const key = String(candidate);
47
+ if (seen.has(key))
48
+ return false;
49
+ seen.add(key);
50
+ return true;
51
+ });
52
+ if (next.length > 0)
53
+ nodes[nodeId] = next;
54
+ }
55
+ for (const [primary, candidates] of Object.entries(definition.fallbacks.global ?? {})) {
56
+ const seen = new Set([primary]);
57
+ const next = candidates.filter((candidate) => {
58
+ const key = String(candidate);
59
+ if (seen.has(key))
60
+ return false;
61
+ seen.add(key);
62
+ return true;
63
+ });
64
+ if (next.length > 0)
65
+ global[primary] = next;
66
+ }
67
+ return Object.keys(nodes).length === 0 && Object.keys(global).length === 0
68
+ ? null
69
+ : {
70
+ ...(Object.keys(nodes).length === 0 ? {} : { nodes }),
71
+ ...(Object.keys(global).length === 0 ? {} : { global }),
72
+ };
73
+ }
74
+ /** Return deterministic canonical save output with dead authoring references removed. */
75
+ export function prepareProductDefinitionForSave(input) {
76
+ const normalized = normalizeProductDefinition(input);
77
+ const fields = pruneFields(normalized);
78
+ const base = { ...normalized, fields };
79
+ const index = createProductIndex(base);
80
+ const fieldIds = new Set(fields.map((field) => field.id));
81
+ const filterIds = new Set(base.filters.map((filter) => filter.id));
82
+ const triggerIds = new Set(index.triggerIds());
83
+ const orderForTags = {};
84
+ for (const [filterId, values] of Object.entries(base.order_for_tags)) {
85
+ const next = uniqueKnown(values, fieldIds);
86
+ if (filterIds.has(filterId) && next.length > 0)
87
+ orderForTags[filterId] = next;
88
+ }
89
+ const pruneRelationshipMap = (value) => {
90
+ const result = {};
91
+ for (const [triggerId, values] of Object.entries(value)) {
92
+ const next = uniqueKnown(values, fieldIds);
93
+ if (triggerIds.has(triggerId) && next.length > 0)
94
+ result[triggerId] = next;
95
+ }
96
+ return result;
97
+ };
98
+ const optionEffects = {};
99
+ for (const [triggerId, targets] of Object.entries(base.option_effects_for_buttons)) {
100
+ if (!isTrigger(index, triggerId))
101
+ continue;
102
+ const nextTargets = {};
103
+ for (const [fieldId, effect] of Object.entries(targets)) {
104
+ const field = index.getField(fieldId);
105
+ if (field === undefined)
106
+ continue;
107
+ const knownOptions = new Set(fieldOptionIds(field));
108
+ const include = uniqueKnown(effect.include ?? [], knownOptions);
109
+ const exclude = uniqueKnown(effect.exclude ?? [], knownOptions);
110
+ if (effect.force_visible !== true && include.length === 0 && exclude.length === 0)
111
+ continue;
112
+ nextTargets[fieldId] = {
113
+ ...(effect.force_visible === true ? { force_visible: true } : {}),
114
+ ...(include.length === 0 ? {} : { include }),
115
+ ...(exclude.length === 0 ? {} : { exclude }),
116
+ };
117
+ }
118
+ if (Object.keys(nextTargets).length > 0)
119
+ optionEffects[triggerId] = nextTargets;
120
+ }
121
+ const valueEffects = {};
122
+ for (const [triggerId, targets] of Object.entries(base.value_effects_for_triggers)) {
123
+ if (!triggerIds.has(triggerId) && !filterIds.has(triggerId))
124
+ continue;
125
+ const nextTargets = Object.fromEntries(Object.entries(targets).filter(([fieldId]) => fieldIds.has(fieldId)));
126
+ if (Object.keys(nextTargets).length > 0)
127
+ valueEffects[triggerId] = nextTargets;
128
+ }
129
+ const withMaps = {
130
+ ...base,
131
+ filters: base.filters.map((filter) => {
132
+ const { includes: rawIncludes, excludes: rawExcludes, ...rest } = filter;
133
+ const includes = uniqueKnown(rawIncludes ?? [], fieldIds);
134
+ const excludes = uniqueKnown(rawExcludes ?? [], fieldIds);
135
+ return {
136
+ ...rest,
137
+ ...(includes.length === 0 ? {} : { includes }),
138
+ ...(excludes.length === 0 ? {} : { excludes }),
139
+ };
140
+ }),
141
+ order_for_tags: orderForTags,
142
+ includes_for_buttons: pruneRelationshipMap(base.includes_for_buttons),
143
+ excludes_for_buttons: pruneRelationshipMap(base.excludes_for_buttons),
144
+ option_effects_for_buttons: optionEffects,
145
+ value_effects_for_triggers: valueEffects,
146
+ };
147
+ return { ...withMaps, fallbacks: pruneFallbacks(withMaps, createProductIndex(withMaps)) };
148
+ }
149
+ //# sourceMappingURL=cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../src/cleanup.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"effects.d.ts","sourceRoot":"","sources":["../src/effects.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG5E,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,iBAAiB,EAC7B,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM,EAChB,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,GACnC,mBAAmB,EAAE,CAUvB;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,iBAAiB,EAC7B,QAAQ,EAAE,MAAM,EAChB,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,GACnC,mBAAmB,EAAE,CAOvB"}
1
+ {"version":3,"file":"effects.d.ts","sourceRoot":"","sources":["../src/effects.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG5E,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,iBAAiB,EAC7B,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM,EAChB,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,GACnC,mBAAmB,EAAE,CAevB;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,iBAAiB,EAC7B,QAAQ,EAAE,MAAM,EAChB,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,GACnC,mBAAmB,EAAE,CAOvB"}
package/dist/effects.js CHANGED
@@ -3,7 +3,12 @@ import { relevantSelectedTriggers } from "./context.js";
3
3
  import { createProductIndex } from "./indexer.js";
4
4
  export function resolveValueEffectsWithIndex(definition, index, filterId, selectedTriggerIds) {
5
5
  const effects = [];
6
- for (const triggerId of relevantSelectedTriggers(index, filterId, selectedTriggerIds)) {
6
+ // The active filter is an implicit value-effect trigger. Ancestors are context only.
7
+ const triggerIds = [
8
+ ...(index.getFilter(filterId) === undefined ? [] : [filterId]),
9
+ ...relevantSelectedTriggers(index, filterId, selectedTriggerIds),
10
+ ];
11
+ for (const triggerId of triggerIds) {
7
12
  for (const [targetFieldId, effect] of Object.entries(definition.value_effects_for_triggers[triggerId] ?? {})) {
8
13
  effects.push({ triggerId, targetFieldId, effect });
9
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"effects.js","sourceRoot":"","sources":["../src/effects.ts"],"names":[],"mappings":"AAAA,wCAAwC;AAIxC,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAqB,MAAM,cAAc,CAAC;AAQrE,MAAM,UAAU,4BAA4B,CAC1C,UAA6B,EAC7B,KAAmB,EACnB,QAAgB,EAChB,kBAAoC;IAEpC,MAAM,OAAO,GAA0B,EAAE,CAAC;IAC1C,KAAK,MAAM,SAAS,IAAI,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,kBAAkB,CAAC,EAAE,CAAC;QACtF,KAAK,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAClD,UAAU,CAAC,0BAA0B,CAAC,SAAS,CAAC,IAAI,EAAE,CACvD,EAAE,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,UAA6B,EAC7B,QAAgB,EAChB,kBAAoC;IAEpC,OAAO,4BAA4B,CACjC,UAAU,EACV,kBAAkB,CAAC,UAAU,CAAC,EAC9B,QAAQ,EACR,kBAAkB,CACnB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"effects.js","sourceRoot":"","sources":["../src/effects.ts"],"names":[],"mappings":"AAAA,wCAAwC;AAIxC,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAqB,MAAM,cAAc,CAAC;AAQrE,MAAM,UAAU,4BAA4B,CAC1C,UAA6B,EAC7B,KAAmB,EACnB,QAAgB,EAChB,kBAAoC;IAEpC,MAAM,OAAO,GAA0B,EAAE,CAAC;IAC1C,qFAAqF;IACrF,MAAM,UAAU,GAAG;QACjB,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC9D,GAAG,wBAAwB,CAAC,KAAK,EAAE,QAAQ,EAAE,kBAAkB,CAAC;KACjE,CAAC;IACF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,KAAK,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAClD,UAAU,CAAC,0BAA0B,CAAC,SAAS,CAAC,IAAI,EAAE,CACvD,EAAE,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,UAA6B,EAC7B,QAAgB,EAChB,kBAAoC;IAEpC,OAAO,4BAA4B,CACjC,UAAU,EACV,kBAAkB,CAAC,UAAU,CAAC,EAC9B,QAAQ,EACR,kBAAkB,CACnB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { HandlerService, ServiceId, ServiceRatePolicy } from "@elqora/dgp-spec";
2
+ import type { ProductIndex } from "./indexer.js";
3
+ export type FallbackIneligibilityReason = "primary_missing" | "self_reference" | "unknown_service" | "unavailable" | "capability_mismatch" | "rate_mismatch" | "context_missing";
4
+ export interface FallbackEligibilityRow {
5
+ candidate: ServiceId;
6
+ eligible: boolean;
7
+ reasons: FallbackIneligibilityReason[];
8
+ matching_filter_ids: string[];
9
+ }
10
+ export interface ResolveFallbackEligibilityOptions {
11
+ rate_policy?: ServiceRatePolicy;
12
+ require_capability_fit?: boolean;
13
+ exclude_ids?: Iterable<ServiceId>;
14
+ /** Reachable publication contexts supplied by a caller that resolves relationship visibility. */
15
+ filter_ids?: readonly string[];
16
+ }
17
+ /**
18
+ * Evaluate node-scoped fallback candidates against handler catalog evidence. A candidate bound to a
19
+ * multi-context option remains eligible when it satisfies at least one reachable filter context.
20
+ */
21
+ export declare function resolveFallbackEligibilityForNode(index: ProductIndex, nodeId: string, candidates: readonly ServiceId[], services: readonly HandlerService[], options?: ResolveFallbackEligibilityOptions): FallbackEligibilityRow[];
22
+ //# sourceMappingURL=fallbacks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallbacks.d.ts","sourceRoot":"","sources":["../src/fallbacks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,MAAM,MAAM,2BAA2B,GACnC,iBAAiB,GACjB,gBAAgB,GAChB,iBAAiB,GACjB,aAAa,GACb,qBAAqB,GACrB,eAAe,GACf,iBAAiB,CAAC;AAEtB,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,2BAA2B,EAAE,CAAC;IACvC,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,iCAAiC;IAChD,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,WAAW,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IAClC,iGAAiG;IACjG,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,QAAQ,EAAE,SAAS,cAAc,EAAE,EACnC,OAAO,GAAE,iCAAsC,GAC9C,sBAAsB,EAAE,CA8C1B"}
@@ -0,0 +1,62 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ import { findService, resolveCapabilities, serviceSatisfiesCapabilities } from "./capabilities.js";
3
+ import { serviceRateMatchesPolicy } from "./service-filter.js";
4
+ import { serviceBindingForNode } from "./services.js";
5
+ /**
6
+ * Evaluate node-scoped fallback candidates against handler catalog evidence. A candidate bound to a
7
+ * multi-context option remains eligible when it satisfies at least one reachable filter context.
8
+ */
9
+ export function resolveFallbackEligibilityForNode(index, nodeId, candidates, services, options = {}) {
10
+ const primaryId = serviceBindingForNode(index, nodeId);
11
+ const primary = primaryId === undefined ? undefined : findService(services, primaryId);
12
+ const filterIds = options.filter_ids === undefined
13
+ ? index.nodeFilterIds(nodeId)
14
+ : [...new Set(options.filter_ids)];
15
+ const excluded = new Set([...options.exclude_ids ?? []].map(String));
16
+ const seen = new Set();
17
+ const rows = [];
18
+ for (const candidateId of candidates) {
19
+ const key = String(candidateId);
20
+ if (seen.has(key) || excluded.has(key))
21
+ continue;
22
+ seen.add(key);
23
+ const reasons = [];
24
+ const matchingFilterIds = [];
25
+ if (primaryId === undefined || primary === undefined)
26
+ reasons.push("primary_missing");
27
+ if (primaryId !== undefined && key === String(primaryId))
28
+ reasons.push("self_reference");
29
+ const candidate = findService(services, candidateId);
30
+ if (candidate === undefined)
31
+ reasons.push("unknown_service");
32
+ else {
33
+ if (candidate.state !== "enabled")
34
+ reasons.push("unavailable");
35
+ if (options.require_capability_fit === true) {
36
+ for (const filterId of filterIds) {
37
+ if (serviceSatisfiesCapabilities(candidate, resolveCapabilities(index, filterId).values)) {
38
+ matchingFilterIds.push(filterId);
39
+ }
40
+ }
41
+ if (filterIds.length === 0)
42
+ reasons.push("context_missing");
43
+ else if (matchingFilterIds.length === 0)
44
+ reasons.push("capability_mismatch");
45
+ }
46
+ else {
47
+ matchingFilterIds.push(...filterIds);
48
+ }
49
+ if (primary !== undefined
50
+ && !serviceRateMatchesPolicy(candidate.rate, primary.rate, options.rate_policy))
51
+ reasons.push("rate_mismatch");
52
+ }
53
+ rows.push({
54
+ candidate: candidateId,
55
+ eligible: reasons.length === 0,
56
+ reasons,
57
+ matching_filter_ids: matchingFilterIds,
58
+ });
59
+ }
60
+ return rows;
61
+ }
62
+ //# sourceMappingURL=fallbacks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallbacks.js","sourceRoot":"","sources":["../src/fallbacks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { ProductDefinition, ServiceId } from "@elqora/dgp-spec";
2
+ import { type ProductIndex } from "./indexer.js";
3
+ export type ProductGraphNodeKind = "filter" | "field" | "option" | "service";
4
+ export type ProductGraphEdgeKind = "child" | "bind" | "include" | "exclude" | "option" | "option_effect" | "value_effect" | "service";
5
+ export interface ProductGraphNode {
6
+ id: string;
7
+ kind: ProductGraphNodeKind;
8
+ label: string;
9
+ source_id: string | ServiceId;
10
+ field_id?: string;
11
+ parent_option_id?: string;
12
+ }
13
+ export interface ProductGraphEdge {
14
+ id: string;
15
+ kind: ProductGraphEdgeKind;
16
+ from: string;
17
+ to: string;
18
+ }
19
+ export interface ProductGraphSnapshot {
20
+ nodes: ProductGraphNode[];
21
+ edges: ProductGraphEdge[];
22
+ }
23
+ /** Deterministic graph projection over actual canonical node identifiers. */
24
+ export declare function projectProductGraphWithIndex(definition: ProductDefinition, index: ProductIndex): ProductGraphSnapshot;
25
+ export declare function projectProductGraph(definition: ProductDefinition): ProductGraphSnapshot;
26
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAErE,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAIrE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC7E,MAAM,MAAM,oBAAoB,GAC5B,OAAO,GACP,MAAM,GACN,SAAS,GACT,SAAS,GACT,QAAQ,GACR,eAAe,GACf,cAAc,GACd,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,oBAAoB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,oBAAoB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAUD,6EAA6E;AAC7E,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,iBAAiB,EAC7B,KAAK,EAAE,YAAY,GAClB,oBAAoB,CAiFtB;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,oBAAoB,CAEvF"}
package/dist/graph.js ADDED
@@ -0,0 +1,99 @@
1
+ // SPDX-License-Identifier: GPL-3.0-only
2
+ import { createProductIndex } from "./indexer.js";
3
+ import { walkOptions } from "./options.js";
4
+ import { serviceBindingForNode } from "./services.js";
5
+ function serviceNodeId(serviceId) {
6
+ return `service:${String(serviceId)}`;
7
+ }
8
+ function edgeId(kind, from, to, ordinal) {
9
+ return `${kind}:${from}:${to}:${ordinal}`;
10
+ }
11
+ /** Deterministic graph projection over actual canonical node identifiers. */
12
+ export function projectProductGraphWithIndex(definition, index) {
13
+ const nodes = [];
14
+ const edges = [];
15
+ const edgeOrdinals = new Map();
16
+ const pushEdge = (kind, from, to) => {
17
+ const signature = `${kind}:${from}:${to}`;
18
+ const ordinal = edgeOrdinals.get(signature) ?? 0;
19
+ edgeOrdinals.set(signature, ordinal + 1);
20
+ edges.push({ id: edgeId(kind, from, to, ordinal), kind, from, to });
21
+ };
22
+ for (const filter of definition.filters) {
23
+ nodes.push({ id: filter.id, kind: "filter", label: filter.label, source_id: filter.id });
24
+ if (filter.bind_id !== undefined && index.getFilter(filter.bind_id) !== undefined) {
25
+ pushEdge("child", filter.bind_id, filter.id);
26
+ }
27
+ for (const fieldId of filter.includes ?? []) {
28
+ if (index.getField(fieldId) !== undefined)
29
+ pushEdge("include", filter.id, fieldId);
30
+ }
31
+ for (const fieldId of filter.excludes ?? []) {
32
+ if (index.getField(fieldId) !== undefined)
33
+ pushEdge("exclude", filter.id, fieldId);
34
+ }
35
+ }
36
+ for (const field of definition.fields) {
37
+ nodes.push({ id: field.id, kind: "field", label: field.label, source_id: field.id });
38
+ for (const filterId of index.fieldBindIds(field)) {
39
+ if (index.getFilter(filterId) !== undefined)
40
+ pushEdge("bind", filterId, field.id);
41
+ }
42
+ }
43
+ for (const visit of walkOptions(definition)) {
44
+ nodes.push({
45
+ id: visit.optionId,
46
+ kind: "option",
47
+ label: visit.option.label,
48
+ source_id: visit.optionId,
49
+ field_id: visit.fieldId,
50
+ ...(visit.parentId === undefined ? {} : { parent_option_id: visit.parentId }),
51
+ });
52
+ pushEdge("option", visit.parentId ?? visit.fieldId, visit.optionId);
53
+ }
54
+ const seenServices = new Set();
55
+ for (const nodeId of index.nodeIds()) {
56
+ const serviceId = serviceBindingForNode(index, nodeId);
57
+ if (serviceId === undefined)
58
+ continue;
59
+ const id = serviceNodeId(serviceId);
60
+ if (!seenServices.has(id)) {
61
+ seenServices.add(id);
62
+ nodes.push({ id, kind: "service", label: String(serviceId), source_id: serviceId });
63
+ }
64
+ pushEdge("service", nodeId, id);
65
+ }
66
+ for (const [triggerId, fieldIds] of Object.entries(definition.includes_for_buttons)) {
67
+ for (const fieldId of fieldIds) {
68
+ if (index.getNode(triggerId).kind !== "unknown" && index.getField(fieldId) !== undefined) {
69
+ pushEdge("include", triggerId, fieldId);
70
+ }
71
+ }
72
+ }
73
+ for (const [triggerId, fieldIds] of Object.entries(definition.excludes_for_buttons)) {
74
+ for (const fieldId of fieldIds) {
75
+ if (index.getNode(triggerId).kind !== "unknown" && index.getField(fieldId) !== undefined) {
76
+ pushEdge("exclude", triggerId, fieldId);
77
+ }
78
+ }
79
+ }
80
+ for (const [triggerId, targets] of Object.entries(definition.option_effects_for_buttons)) {
81
+ for (const fieldId of Object.keys(targets)) {
82
+ if (index.getNode(triggerId).kind !== "unknown" && index.getField(fieldId) !== undefined) {
83
+ pushEdge("option_effect", triggerId, fieldId);
84
+ }
85
+ }
86
+ }
87
+ for (const [triggerId, targets] of Object.entries(definition.value_effects_for_triggers)) {
88
+ for (const fieldId of Object.keys(targets)) {
89
+ if (index.getNode(triggerId).kind !== "unknown" && index.getField(fieldId) !== undefined) {
90
+ pushEdge("value_effect", triggerId, fieldId);
91
+ }
92
+ }
93
+ }
94
+ return { nodes, edges };
95
+ }
96
+ export function projectProductGraph(definition) {
97
+ return projectProductGraphWithIndex(definition, createProductIndex(definition));
98
+ }
99
+ //# sourceMappingURL=graph.js.map