@timeax/digital-service-engine 0.2.3 → 0.2.5

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.
@@ -78,6 +78,10 @@ type SnapshotContext = {
78
78
  requireConstraintFit: boolean;
79
79
  };
80
80
  };
81
+ type OrderKindSource = {
82
+ nodeId: string;
83
+ nodeKind: "tag" | "field" | "option";
84
+ };
81
85
  type OrderSnapshot = {
82
86
  version: "1";
83
87
  mode: "prod" | "dev";
@@ -104,6 +108,8 @@ type OrderSnapshot = {
104
108
  };
105
109
  min: number;
106
110
  max: number;
111
+ orderKind?: string | null;
112
+ orderKindSource?: OrderKindSource | null;
107
113
  services: Array<string | number>;
108
114
  serviceMap: Record<string, Array<string | number>>;
109
115
  fallbacks?: ServiceFallbacks;
@@ -181,7 +187,7 @@ type NodeRef$1 = {
181
187
  };
182
188
  type NodeMap = Map<string, NodeRef$1>;
183
189
 
184
- type ValidationCode = "root_missing" | "cycle_in_tags" | "bad_bind_reference" | "duplicate_id" | "duplicate_tag_label" | "duplicate_field_name" | "label_missing" | "duplicate_visible_label" | "bad_option_key" | "option_include_exclude_conflict" | "service_field_missing_service_id" | "user_input_field_has_service_option" | "rate_mismatch_across_base" | "rate_coherence_violation" | "utility_without_base" | "unsupported_constraint" | "constraint_contradiction" | "custom_component_missing" | "policy_violation" | "field_unbound" | "constraint_overridden" | "unsupported_constraint_option" | "custom_component_unresolvable" | "quantity_multiple_markers" | "utility_with_service_id" | "utility_missing_rate" | "utility_invalid_mode" | "fallback_bad_node" | "fallback_unknown_service" | "fallback_cycle" | "fallback_no_primary" | "fallback_rate_violation" | "fallback_constraint_mismatch" | "fallback_no_tag_context" | "field_validation_invalid_rule" | "field_validation_invalid_op" | "field_validation_eval_missing_code" | "field_validation_between_missing_bounds" | "field_validation_match_missing_pattern";
190
+ type ValidationCode = "root_missing" | "cycle_in_tags" | "bad_bind_reference" | "duplicate_id" | "duplicate_tag_label" | "duplicate_field_name" | "label_missing" | "duplicate_visible_label" | "bad_option_key" | "option_include_exclude_conflict" | "service_field_missing_service_id" | "user_input_field_has_service_option" | "rate_mismatch_across_base" | "rate_coherence_violation" | "utility_without_base" | "unsupported_constraint" | "constraint_contradiction" | "custom_component_missing" | "policy_violation" | "field_unbound" | "constraint_overridden" | "unsupported_constraint_option" | "custom_component_unresolvable" | "quantity_multiple_markers" | "utility_with_service_id" | "utility_missing_rate" | "utility_invalid_mode" | "fallback_bad_node" | "fallback_unknown_service" | "fallback_cycle" | "fallback_no_primary" | "fallback_rate_violation" | "fallback_constraint_mismatch" | "fallback_no_tag_context" | "field_validation_invalid_rule" | "field_validation_invalid_op" | "field_validation_eval_missing_code" | "field_validation_between_missing_bounds" | "field_validation_match_missing_pattern" | "multiple_order_kinds_selected";
185
191
  type ValidationError = {
186
192
  code: ValidationCode;
187
193
  message: string;
@@ -505,6 +511,7 @@ type Tag = {
505
511
  };
506
512
  type ServiceProps = {
507
513
  order_for_tags?: Record<string, string[]>;
514
+ orderKinds?: Record<string, string>;
508
515
  filters: Tag[];
509
516
  fields: Field[];
510
517
  includes_for_buttons?: Record<string, string[]>;
@@ -1587,6 +1594,9 @@ declare class Editor {
1587
1594
  getFieldValidation(id: string): FieldValidationRule[] | undefined;
1588
1595
  setFieldValidation(id: string, rules: unknown): void;
1589
1596
  clearFieldValidation(id: string): void;
1597
+ setOrderKind(nodeId: string, kind: string): void;
1598
+ deleteOrderKind(nodeId: string): void;
1599
+ pruneKind(kind: string): number;
1590
1600
  getCatalog(): ServiceCatalogState | undefined;
1591
1601
  setCatalog(next?: ServiceCatalogState): void;
1592
1602
  clearCatalog(): void;
@@ -78,6 +78,10 @@ type SnapshotContext = {
78
78
  requireConstraintFit: boolean;
79
79
  };
80
80
  };
81
+ type OrderKindSource = {
82
+ nodeId: string;
83
+ nodeKind: "tag" | "field" | "option";
84
+ };
81
85
  type OrderSnapshot = {
82
86
  version: "1";
83
87
  mode: "prod" | "dev";
@@ -104,6 +108,8 @@ type OrderSnapshot = {
104
108
  };
105
109
  min: number;
106
110
  max: number;
111
+ orderKind?: string | null;
112
+ orderKindSource?: OrderKindSource | null;
107
113
  services: Array<string | number>;
108
114
  serviceMap: Record<string, Array<string | number>>;
109
115
  fallbacks?: ServiceFallbacks;
@@ -181,7 +187,7 @@ type NodeRef$1 = {
181
187
  };
182
188
  type NodeMap = Map<string, NodeRef$1>;
183
189
 
184
- type ValidationCode = "root_missing" | "cycle_in_tags" | "bad_bind_reference" | "duplicate_id" | "duplicate_tag_label" | "duplicate_field_name" | "label_missing" | "duplicate_visible_label" | "bad_option_key" | "option_include_exclude_conflict" | "service_field_missing_service_id" | "user_input_field_has_service_option" | "rate_mismatch_across_base" | "rate_coherence_violation" | "utility_without_base" | "unsupported_constraint" | "constraint_contradiction" | "custom_component_missing" | "policy_violation" | "field_unbound" | "constraint_overridden" | "unsupported_constraint_option" | "custom_component_unresolvable" | "quantity_multiple_markers" | "utility_with_service_id" | "utility_missing_rate" | "utility_invalid_mode" | "fallback_bad_node" | "fallback_unknown_service" | "fallback_cycle" | "fallback_no_primary" | "fallback_rate_violation" | "fallback_constraint_mismatch" | "fallback_no_tag_context" | "field_validation_invalid_rule" | "field_validation_invalid_op" | "field_validation_eval_missing_code" | "field_validation_between_missing_bounds" | "field_validation_match_missing_pattern";
190
+ type ValidationCode = "root_missing" | "cycle_in_tags" | "bad_bind_reference" | "duplicate_id" | "duplicate_tag_label" | "duplicate_field_name" | "label_missing" | "duplicate_visible_label" | "bad_option_key" | "option_include_exclude_conflict" | "service_field_missing_service_id" | "user_input_field_has_service_option" | "rate_mismatch_across_base" | "rate_coherence_violation" | "utility_without_base" | "unsupported_constraint" | "constraint_contradiction" | "custom_component_missing" | "policy_violation" | "field_unbound" | "constraint_overridden" | "unsupported_constraint_option" | "custom_component_unresolvable" | "quantity_multiple_markers" | "utility_with_service_id" | "utility_missing_rate" | "utility_invalid_mode" | "fallback_bad_node" | "fallback_unknown_service" | "fallback_cycle" | "fallback_no_primary" | "fallback_rate_violation" | "fallback_constraint_mismatch" | "fallback_no_tag_context" | "field_validation_invalid_rule" | "field_validation_invalid_op" | "field_validation_eval_missing_code" | "field_validation_between_missing_bounds" | "field_validation_match_missing_pattern" | "multiple_order_kinds_selected";
185
191
  type ValidationError = {
186
192
  code: ValidationCode;
187
193
  message: string;
@@ -505,6 +511,7 @@ type Tag = {
505
511
  };
506
512
  type ServiceProps = {
507
513
  order_for_tags?: Record<string, string[]>;
514
+ orderKinds?: Record<string, string>;
508
515
  filters: Tag[];
509
516
  fields: Field[];
510
517
  includes_for_buttons?: Record<string, string[]>;
@@ -1587,6 +1594,9 @@ declare class Editor {
1587
1594
  getFieldValidation(id: string): FieldValidationRule[] | undefined;
1588
1595
  setFieldValidation(id: string, rules: unknown): void;
1589
1596
  clearFieldValidation(id: string): void;
1597
+ setOrderKind(nodeId: string, kind: string): void;
1598
+ deleteOrderKind(nodeId: string): void;
1599
+ pruneKind(kind: string): number;
1590
1600
  getCatalog(): ServiceCatalogState | undefined;
1591
1601
  setCatalog(next?: ServiceCatalogState): void;
1592
1602
  clearCatalog(): void;
@@ -666,6 +666,7 @@ function normalise(input, opts = {}) {
666
666
  const excludes_for_buttons = toStringArrayMap(
667
667
  obj.excludes_for_buttons
668
668
  );
669
+ const orderKinds = toStringMap(obj.orderKinds);
669
670
  const notices = toNoticeArray(obj.notices);
670
671
  let filters = rawFilters.map((t) => coerceTag(t, constraints));
671
672
  const fields = rawFields.map((f) => coerceField(f, defRole));
@@ -677,6 +678,7 @@ function normalise(input, opts = {}) {
677
678
  filters,
678
679
  fields,
679
680
  order_for_tags: obj.order_for_tags,
681
+ ...isNonEmpty(orderKinds) && { orderKinds },
680
682
  ...isNonEmpty(includes_for_buttons) && { includes_for_buttons },
681
683
  ...isNonEmpty(excludes_for_buttons) && { excludes_for_buttons },
682
684
  ...fallbacks && (isNonEmpty(fallbacks.nodes) || isNonEmpty(fallbacks.global)) && {
@@ -887,6 +889,15 @@ function normaliseBindId(bind) {
887
889
  }
888
890
  return void 0;
889
891
  }
892
+ function toStringMap(src) {
893
+ if (!src || typeof src !== "object") return void 0;
894
+ const out = {};
895
+ for (const [k, v] of Object.entries(src)) {
896
+ if (!k || typeof v !== "string") continue;
897
+ out[k] = v;
898
+ }
899
+ return Object.keys(out).length ? out : void 0;
900
+ }
890
901
  function toStringArrayMap(src) {
891
902
  if (!src || typeof src !== "object") return void 0;
892
903
  const out = {};
@@ -1705,6 +1716,129 @@ function validateOptionMaps(v) {
1705
1716
  }
1706
1717
  }
1707
1718
 
1719
+ // src/utils/order-kind.ts
1720
+ function normalizeSelectedTriggerKey(key, nodeMap) {
1721
+ if (!key) return void 0;
1722
+ const compositeIdx = key.indexOf("::");
1723
+ if (compositeIdx !== -1) {
1724
+ const fieldId = key.slice(0, compositeIdx).trim();
1725
+ const optionId = key.slice(compositeIdx + 2).trim();
1726
+ if (optionId) {
1727
+ const optionRef = nodeMap.get(optionId);
1728
+ if ((optionRef == null ? void 0 : optionRef.kind) === "option") {
1729
+ return { nodeId: optionRef.id, nodeKind: "option" };
1730
+ }
1731
+ }
1732
+ if (fieldId) {
1733
+ const fieldRef = nodeMap.get(fieldId);
1734
+ if ((fieldRef == null ? void 0 : fieldRef.kind) === "field") {
1735
+ return { nodeId: fieldRef.id, nodeKind: "field" };
1736
+ }
1737
+ }
1738
+ return void 0;
1739
+ }
1740
+ const ref = nodeMap.get(key);
1741
+ if (!ref) return void 0;
1742
+ if (ref.kind !== "field" && ref.kind !== "option") return void 0;
1743
+ return { nodeId: ref.id, nodeKind: ref.kind };
1744
+ }
1745
+ function normalizeSelectedOrderKindTriggers(selectedTriggerKeys, nodeMap) {
1746
+ if (!selectedTriggerKeys) return [];
1747
+ const out = [];
1748
+ const seen = /* @__PURE__ */ new Set();
1749
+ for (const rawKey of selectedTriggerKeys) {
1750
+ const key = String(rawKey != null ? rawKey : "");
1751
+ const normalized = normalizeSelectedTriggerKey(key, nodeMap);
1752
+ if (!normalized) continue;
1753
+ const dedupeKey = `${normalized.nodeKind}:${normalized.nodeId}`;
1754
+ if (seen.has(dedupeKey)) continue;
1755
+ seen.add(dedupeKey);
1756
+ out.push(normalized);
1757
+ }
1758
+ return out;
1759
+ }
1760
+ function resolveOrderKind(params) {
1761
+ var _a, _b;
1762
+ const nodeMap = (_a = params.nodeMap) != null ? _a : buildNodeMap(params.props);
1763
+ const orderKinds = (_b = params.props.orderKinds) != null ? _b : {};
1764
+ const normalizedSelected = normalizeSelectedOrderKindTriggers(
1765
+ params.selectedTriggerKeys,
1766
+ nodeMap
1767
+ );
1768
+ const selectedKindToSource = /* @__PURE__ */ new Map();
1769
+ const selectedNodeIdsForKinds = /* @__PURE__ */ new Map();
1770
+ for (const trigger of normalizedSelected) {
1771
+ const mappedKind = orderKinds[trigger.nodeId];
1772
+ if (typeof mappedKind !== "string") continue;
1773
+ if (!selectedKindToSource.has(mappedKind)) {
1774
+ selectedKindToSource.set(mappedKind, {
1775
+ nodeId: trigger.nodeId,
1776
+ nodeKind: trigger.nodeKind
1777
+ });
1778
+ }
1779
+ if (!selectedNodeIdsForKinds.has(mappedKind)) {
1780
+ selectedNodeIdsForKinds.set(mappedKind, /* @__PURE__ */ new Set());
1781
+ }
1782
+ selectedNodeIdsForKinds.get(mappedKind).add(trigger.nodeId);
1783
+ }
1784
+ const selectedKinds = Array.from(selectedKindToSource.keys());
1785
+ if (selectedKinds.length > 1) {
1786
+ const conflictingNodeIds = Array.from(selectedNodeIdsForKinds.values()).flatMap((ids) => Array.from(ids)).filter((id, idx, arr) => arr.indexOf(id) === idx);
1787
+ return {
1788
+ kind: null,
1789
+ source: null,
1790
+ error: "multiple_order_kinds_selected",
1791
+ conflictingKinds: selectedKinds,
1792
+ conflictingNodeIds
1793
+ };
1794
+ }
1795
+ if (selectedKinds.length === 1) {
1796
+ const selectedKind = selectedKinds[0];
1797
+ return {
1798
+ kind: selectedKind,
1799
+ source: selectedKindToSource.get(selectedKind)
1800
+ };
1801
+ }
1802
+ const activeTagId = params.activeTagId;
1803
+ if (activeTagId) {
1804
+ const tagKind = orderKinds[activeTagId];
1805
+ if (typeof tagKind === "string") {
1806
+ return {
1807
+ kind: tagKind,
1808
+ source: { nodeId: activeTagId, nodeKind: "tag" }
1809
+ };
1810
+ }
1811
+ }
1812
+ return { kind: null, source: null };
1813
+ }
1814
+
1815
+ // src/core/validate/steps/order-kinds.ts
1816
+ function validateOrderKinds(v) {
1817
+ var _a, _b, _c;
1818
+ const selectedTriggerKeys = Array.from((_a = v.selectedKeys) != null ? _a : []);
1819
+ if (!selectedTriggerKeys.length) return;
1820
+ const resolved = resolveOrderKind({
1821
+ props: v.props,
1822
+ selectedTriggerKeys,
1823
+ nodeMap: v.nodeMap
1824
+ });
1825
+ if (resolved.error !== "multiple_order_kinds_selected") return;
1826
+ const conflicts = (_b = resolved.conflictingKinds) != null ? _b : [];
1827
+ const affected = (_c = resolved.conflictingNodeIds) != null ? _c : [];
1828
+ v.errors.push({
1829
+ code: "multiple_order_kinds_selected",
1830
+ severity: "error",
1831
+ message: "Multiple selected triggers resolve to different order kinds. Select triggers that resolve to a single order kind.",
1832
+ details: withAffected(
1833
+ {
1834
+ conflictingKinds: conflicts,
1835
+ conflictingNodeIds: affected
1836
+ },
1837
+ affected
1838
+ )
1839
+ });
1840
+ }
1841
+
1708
1842
  // src/core/validate/steps/service-vs-input.ts
1709
1843
  function validateServiceVsUserInput(v) {
1710
1844
  for (const f of v.fields) {
@@ -3033,6 +3167,7 @@ var BuilderImpl = class {
3033
3167
  const out = {
3034
3168
  filters: this.props.filters.slice(),
3035
3169
  fields,
3170
+ ...this.props.orderKinds ? { orderKinds: this.props.orderKinds } : {},
3036
3171
  ...includes_for_buttons && { includes_for_buttons },
3037
3172
  ...excludes_for_buttons && { excludes_for_buttons },
3038
3173
  schema_version: (_e = this.props.schema_version) != null ? _e : "1.0",
@@ -3365,6 +3500,7 @@ function validate(props, ctx = {}) {
3365
3500
  validateStructure(v);
3366
3501
  validateIdentity(v);
3367
3502
  validateOptionMaps(v);
3503
+ validateOrderKinds(v);
3368
3504
  v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
3369
3505
  const visSim = readVisibilitySimOpts(options);
3370
3506
  validateVisibility(v, visSim);
@@ -4713,6 +4849,13 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
4713
4849
  fieldById
4714
4850
  );
4715
4851
  const { min, max } = resolveMinMax(servicesList, services);
4852
+ const maybeNodeMap = typeof builder.getNodeMap === "function" ? builder.getNodeMap() : void 0;
4853
+ const resolvedOrderKind = resolveOrderKind({
4854
+ props,
4855
+ activeTagId: tagId,
4856
+ selectedTriggerKeys: selectedButtonKeys,
4857
+ nodeMap: maybeNodeMap
4858
+ });
4716
4859
  const prunedFallbacks = pruneFallbacksConservative(
4717
4860
  props.fallbacks,
4718
4861
  { tagId, constraints: tagConstraints, serviceMap, servicesList },
@@ -4768,6 +4911,8 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
4768
4911
  },
4769
4912
  min,
4770
4913
  max: max != null ? max : min,
4914
+ orderKind: resolvedOrderKind.kind,
4915
+ orderKindSource: resolvedOrderKind.source,
4771
4916
  quantity,
4772
4917
  quantitySource,
4773
4918
  services: servicesList,
@@ -6789,6 +6934,88 @@ function normalizeQuantityRule(input) {
6789
6934
  return out;
6790
6935
  }
6791
6936
 
6937
+ // src/react/canvas/editor/editor-order-kinds.ts
6938
+ function normalizeKind(kind) {
6939
+ const next = String(kind != null ? kind : "").trim();
6940
+ if (!next) {
6941
+ throw new Error("setOrderKind: kind must be a non-empty string");
6942
+ }
6943
+ return next;
6944
+ }
6945
+ function assertCanonicalNodeId(ctx, nodeId) {
6946
+ const id = String(nodeId != null ? nodeId : "").trim();
6947
+ if (!id) throw new Error("setOrderKind: nodeId is required");
6948
+ if (id.includes("::")) {
6949
+ throw new Error(
6950
+ "setOrderKind: composite/internal trigger keys are not allowed; use canonical tag/field/option ids"
6951
+ );
6952
+ }
6953
+ if (!ctx.isTagId(id) && !ctx.isFieldId(id) && !ctx.isOptionId(id)) {
6954
+ throw new Error(
6955
+ `setOrderKind: node id '${id}' is not a known tag, field, or option`
6956
+ );
6957
+ }
6958
+ if (ctx.isFieldId(id)) {
6959
+ const node = ctx.getNode(id);
6960
+ if (node.kind !== "field" || !isActualButtonField(node.data)) {
6961
+ throw new Error(
6962
+ `setOrderKind: field '${id}' must be a button field without options`
6963
+ );
6964
+ }
6965
+ }
6966
+ }
6967
+ function setOrderKind(ctx, nodeId, kind) {
6968
+ const id = String(nodeId != null ? nodeId : "").trim();
6969
+ const nextKind = normalizeKind(kind);
6970
+ assertCanonicalNodeId(ctx, id);
6971
+ ctx.exec({
6972
+ name: "setOrderKind",
6973
+ do: () => ctx.patchProps((p) => {
6974
+ if (!p.orderKinds) p.orderKinds = {};
6975
+ p.orderKinds[id] = nextKind;
6976
+ }),
6977
+ undo: () => ctx.undo()
6978
+ });
6979
+ }
6980
+ function deleteOrderKind(ctx, nodeId) {
6981
+ const id = String(nodeId != null ? nodeId : "").trim();
6982
+ if (!id) return;
6983
+ ctx.exec({
6984
+ name: "deleteOrderKind",
6985
+ do: () => ctx.patchProps((p) => {
6986
+ if (!p.orderKinds || !Object.prototype.hasOwnProperty.call(p.orderKinds, id)) {
6987
+ return;
6988
+ }
6989
+ delete p.orderKinds[id];
6990
+ if (!Object.keys(p.orderKinds).length) {
6991
+ delete p.orderKinds;
6992
+ }
6993
+ }),
6994
+ undo: () => ctx.undo()
6995
+ });
6996
+ }
6997
+ function pruneOrderKind(ctx, kind) {
6998
+ const target = normalizeKind(kind);
6999
+ let removedCount = 0;
7000
+ ctx.exec({
7001
+ name: "pruneOrderKind",
7002
+ do: () => ctx.patchProps((p) => {
7003
+ if (!p.orderKinds) return;
7004
+ removedCount = 0;
7005
+ for (const [nodeId, mapped] of Object.entries(p.orderKinds)) {
7006
+ if (mapped !== target) continue;
7007
+ delete p.orderKinds[nodeId];
7008
+ removedCount++;
7009
+ }
7010
+ if (!Object.keys(p.orderKinds).length) {
7011
+ delete p.orderKinds;
7012
+ }
7013
+ }),
7014
+ undo: () => ctx.undo()
7015
+ });
7016
+ return removedCount;
7017
+ }
7018
+
6792
7019
  // src/react/canvas/editor/editor-relations.ts
6793
7020
  function wouldCreateTagCycle(_ctx, p, parentId, childId) {
6794
7021
  var _a, _b;
@@ -7769,6 +7996,15 @@ var Editor = class {
7769
7996
  clearFieldValidation(id) {
7770
7997
  return clearFieldValidation(this.moduleCtx(), id);
7771
7998
  }
7999
+ setOrderKind(nodeId, kind) {
8000
+ return setOrderKind(this.moduleCtx(), nodeId, kind);
8001
+ }
8002
+ deleteOrderKind(nodeId) {
8003
+ return deleteOrderKind(this.moduleCtx(), nodeId);
8004
+ }
8005
+ pruneKind(kind) {
8006
+ return pruneOrderKind(this.moduleCtx(), kind);
8007
+ }
7772
8008
  getCatalog() {
7773
8009
  return cloneDeep4(this.catalog);
7774
8010
  }