@timeax/digital-service-engine 0.2.4 → 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.
@@ -66,6 +66,10 @@ type SnapshotContext = {
66
66
  requireConstraintFit: boolean;
67
67
  };
68
68
  };
69
+ type OrderKindSource = {
70
+ nodeId: string;
71
+ nodeKind: "tag" | "field" | "option";
72
+ };
69
73
  type OrderSnapshot = {
70
74
  version: "1";
71
75
  mode: "prod" | "dev";
@@ -92,6 +96,8 @@ type OrderSnapshot = {
92
96
  };
93
97
  min: number;
94
98
  max: number;
99
+ orderKind?: string | null;
100
+ orderKindSource?: OrderKindSource | null;
95
101
  services: Array<string | number>;
96
102
  serviceMap: Record<string, Array<string | number>>;
97
103
  fallbacks?: ServiceFallbacks;
@@ -169,7 +175,7 @@ type NodeRef = {
169
175
  };
170
176
  type NodeMap = Map<string, NodeRef>;
171
177
 
172
- 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";
178
+ 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";
173
179
  type ValidationError = {
174
180
  code: ValidationCode;
175
181
  message: string;
@@ -450,6 +456,7 @@ type Tag = {
450
456
  };
451
457
  type ServiceProps = {
452
458
  order_for_tags?: Record<string, string[]>;
459
+ orderKinds?: Record<string, string>;
453
460
  filters: Tag[];
454
461
  fields: Field[];
455
462
  includes_for_buttons?: Record<string, string[]>;
@@ -66,6 +66,10 @@ type SnapshotContext = {
66
66
  requireConstraintFit: boolean;
67
67
  };
68
68
  };
69
+ type OrderKindSource = {
70
+ nodeId: string;
71
+ nodeKind: "tag" | "field" | "option";
72
+ };
69
73
  type OrderSnapshot = {
70
74
  version: "1";
71
75
  mode: "prod" | "dev";
@@ -92,6 +96,8 @@ type OrderSnapshot = {
92
96
  };
93
97
  min: number;
94
98
  max: number;
99
+ orderKind?: string | null;
100
+ orderKindSource?: OrderKindSource | null;
95
101
  services: Array<string | number>;
96
102
  serviceMap: Record<string, Array<string | number>>;
97
103
  fallbacks?: ServiceFallbacks;
@@ -169,7 +175,7 @@ type NodeRef = {
169
175
  };
170
176
  type NodeMap = Map<string, NodeRef>;
171
177
 
172
- 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";
178
+ 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";
173
179
  type ValidationError = {
174
180
  code: ValidationCode;
175
181
  message: string;
@@ -450,6 +456,7 @@ type Tag = {
450
456
  };
451
457
  type ServiceProps = {
452
458
  order_for_tags?: Record<string, string[]>;
459
+ orderKinds?: Record<string, string>;
453
460
  filters: Tag[];
454
461
  fields: Field[];
455
462
  includes_for_buttons?: Record<string, string[]>;
@@ -13,6 +13,7 @@ function normalise(input, opts = {}) {
13
13
  const excludes_for_buttons = toStringArrayMap(
14
14
  obj.excludes_for_buttons
15
15
  );
16
+ const orderKinds = toStringMap(obj.orderKinds);
16
17
  const notices = toNoticeArray(obj.notices);
17
18
  let filters = rawFilters.map((t) => coerceTag(t, constraints));
18
19
  const fields = rawFields.map((f) => coerceField(f, defRole));
@@ -24,6 +25,7 @@ function normalise(input, opts = {}) {
24
25
  filters,
25
26
  fields,
26
27
  order_for_tags: obj.order_for_tags,
28
+ ...isNonEmpty(orderKinds) && { orderKinds },
27
29
  ...isNonEmpty(includes_for_buttons) && { includes_for_buttons },
28
30
  ...isNonEmpty(excludes_for_buttons) && { excludes_for_buttons },
29
31
  ...fallbacks && (isNonEmpty(fallbacks.nodes) || isNonEmpty(fallbacks.global)) && {
@@ -234,6 +236,15 @@ function normaliseBindId(bind) {
234
236
  }
235
237
  return void 0;
236
238
  }
239
+ function toStringMap(src) {
240
+ if (!src || typeof src !== "object") return void 0;
241
+ const out = {};
242
+ for (const [k, v] of Object.entries(src)) {
243
+ if (!k || typeof v !== "string") continue;
244
+ out[k] = v;
245
+ }
246
+ return Object.keys(out).length ? out : void 0;
247
+ }
237
248
  function toStringArrayMap(src) {
238
249
  if (!src || typeof src !== "object") return void 0;
239
250
  const out = {};
@@ -1052,6 +1063,129 @@ function validateOptionMaps(v) {
1052
1063
  }
1053
1064
  }
1054
1065
 
1066
+ // src/utils/order-kind.ts
1067
+ function normalizeSelectedTriggerKey(key, nodeMap) {
1068
+ if (!key) return void 0;
1069
+ const compositeIdx = key.indexOf("::");
1070
+ if (compositeIdx !== -1) {
1071
+ const fieldId = key.slice(0, compositeIdx).trim();
1072
+ const optionId = key.slice(compositeIdx + 2).trim();
1073
+ if (optionId) {
1074
+ const optionRef = nodeMap.get(optionId);
1075
+ if ((optionRef == null ? void 0 : optionRef.kind) === "option") {
1076
+ return { nodeId: optionRef.id, nodeKind: "option" };
1077
+ }
1078
+ }
1079
+ if (fieldId) {
1080
+ const fieldRef = nodeMap.get(fieldId);
1081
+ if ((fieldRef == null ? void 0 : fieldRef.kind) === "field") {
1082
+ return { nodeId: fieldRef.id, nodeKind: "field" };
1083
+ }
1084
+ }
1085
+ return void 0;
1086
+ }
1087
+ const ref = nodeMap.get(key);
1088
+ if (!ref) return void 0;
1089
+ if (ref.kind !== "field" && ref.kind !== "option") return void 0;
1090
+ return { nodeId: ref.id, nodeKind: ref.kind };
1091
+ }
1092
+ function normalizeSelectedOrderKindTriggers(selectedTriggerKeys, nodeMap) {
1093
+ if (!selectedTriggerKeys) return [];
1094
+ const out = [];
1095
+ const seen = /* @__PURE__ */ new Set();
1096
+ for (const rawKey of selectedTriggerKeys) {
1097
+ const key = String(rawKey != null ? rawKey : "");
1098
+ const normalized = normalizeSelectedTriggerKey(key, nodeMap);
1099
+ if (!normalized) continue;
1100
+ const dedupeKey = `${normalized.nodeKind}:${normalized.nodeId}`;
1101
+ if (seen.has(dedupeKey)) continue;
1102
+ seen.add(dedupeKey);
1103
+ out.push(normalized);
1104
+ }
1105
+ return out;
1106
+ }
1107
+ function resolveOrderKind(params) {
1108
+ var _a, _b;
1109
+ const nodeMap = (_a = params.nodeMap) != null ? _a : buildNodeMap(params.props);
1110
+ const orderKinds = (_b = params.props.orderKinds) != null ? _b : {};
1111
+ const normalizedSelected = normalizeSelectedOrderKindTriggers(
1112
+ params.selectedTriggerKeys,
1113
+ nodeMap
1114
+ );
1115
+ const selectedKindToSource = /* @__PURE__ */ new Map();
1116
+ const selectedNodeIdsForKinds = /* @__PURE__ */ new Map();
1117
+ for (const trigger of normalizedSelected) {
1118
+ const mappedKind = orderKinds[trigger.nodeId];
1119
+ if (typeof mappedKind !== "string") continue;
1120
+ if (!selectedKindToSource.has(mappedKind)) {
1121
+ selectedKindToSource.set(mappedKind, {
1122
+ nodeId: trigger.nodeId,
1123
+ nodeKind: trigger.nodeKind
1124
+ });
1125
+ }
1126
+ if (!selectedNodeIdsForKinds.has(mappedKind)) {
1127
+ selectedNodeIdsForKinds.set(mappedKind, /* @__PURE__ */ new Set());
1128
+ }
1129
+ selectedNodeIdsForKinds.get(mappedKind).add(trigger.nodeId);
1130
+ }
1131
+ const selectedKinds = Array.from(selectedKindToSource.keys());
1132
+ if (selectedKinds.length > 1) {
1133
+ const conflictingNodeIds = Array.from(selectedNodeIdsForKinds.values()).flatMap((ids) => Array.from(ids)).filter((id, idx, arr) => arr.indexOf(id) === idx);
1134
+ return {
1135
+ kind: null,
1136
+ source: null,
1137
+ error: "multiple_order_kinds_selected",
1138
+ conflictingKinds: selectedKinds,
1139
+ conflictingNodeIds
1140
+ };
1141
+ }
1142
+ if (selectedKinds.length === 1) {
1143
+ const selectedKind = selectedKinds[0];
1144
+ return {
1145
+ kind: selectedKind,
1146
+ source: selectedKindToSource.get(selectedKind)
1147
+ };
1148
+ }
1149
+ const activeTagId = params.activeTagId;
1150
+ if (activeTagId) {
1151
+ const tagKind = orderKinds[activeTagId];
1152
+ if (typeof tagKind === "string") {
1153
+ return {
1154
+ kind: tagKind,
1155
+ source: { nodeId: activeTagId, nodeKind: "tag" }
1156
+ };
1157
+ }
1158
+ }
1159
+ return { kind: null, source: null };
1160
+ }
1161
+
1162
+ // src/core/validate/steps/order-kinds.ts
1163
+ function validateOrderKinds(v) {
1164
+ var _a, _b, _c;
1165
+ const selectedTriggerKeys = Array.from((_a = v.selectedKeys) != null ? _a : []);
1166
+ if (!selectedTriggerKeys.length) return;
1167
+ const resolved = resolveOrderKind({
1168
+ props: v.props,
1169
+ selectedTriggerKeys,
1170
+ nodeMap: v.nodeMap
1171
+ });
1172
+ if (resolved.error !== "multiple_order_kinds_selected") return;
1173
+ const conflicts = (_b = resolved.conflictingKinds) != null ? _b : [];
1174
+ const affected = (_c = resolved.conflictingNodeIds) != null ? _c : [];
1175
+ v.errors.push({
1176
+ code: "multiple_order_kinds_selected",
1177
+ severity: "error",
1178
+ message: "Multiple selected triggers resolve to different order kinds. Select triggers that resolve to a single order kind.",
1179
+ details: withAffected(
1180
+ {
1181
+ conflictingKinds: conflicts,
1182
+ conflictingNodeIds: affected
1183
+ },
1184
+ affected
1185
+ )
1186
+ });
1187
+ }
1188
+
1055
1189
  // src/core/validate/steps/service-vs-input.ts
1056
1190
  function validateServiceVsUserInput(v) {
1057
1191
  for (const f of v.fields) {
@@ -2380,6 +2514,7 @@ var BuilderImpl = class {
2380
2514
  const out = {
2381
2515
  filters: this.props.filters.slice(),
2382
2516
  fields,
2517
+ ...this.props.orderKinds ? { orderKinds: this.props.orderKinds } : {},
2383
2518
  ...includes_for_buttons && { includes_for_buttons },
2384
2519
  ...excludes_for_buttons && { excludes_for_buttons },
2385
2520
  schema_version: (_e = this.props.schema_version) != null ? _e : "1.0",
@@ -2712,6 +2847,7 @@ function validate(props, ctx = {}) {
2712
2847
  validateStructure(v);
2713
2848
  validateIdentity(v);
2714
2849
  validateOptionMaps(v);
2850
+ validateOrderKinds(v);
2715
2851
  v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
2716
2852
  const visSim = readVisibilitySimOpts(options);
2717
2853
  validateVisibility(v, visSim);
@@ -4112,6 +4248,13 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
4112
4248
  fieldById
4113
4249
  );
4114
4250
  const { min, max } = resolveMinMax(servicesList, services);
4251
+ const maybeNodeMap = typeof builder.getNodeMap === "function" ? builder.getNodeMap() : void 0;
4252
+ const resolvedOrderKind = resolveOrderKind({
4253
+ props,
4254
+ activeTagId: tagId,
4255
+ selectedTriggerKeys: selectedButtonKeys,
4256
+ nodeMap: maybeNodeMap
4257
+ });
4115
4258
  const prunedFallbacks = pruneFallbacksConservative(
4116
4259
  props.fallbacks,
4117
4260
  { tagId, constraints: tagConstraints, serviceMap, servicesList },
@@ -4167,6 +4310,8 @@ function buildOrderSnapshot(props, builder, selection, services, settings = {})
4167
4310
  },
4168
4311
  min,
4169
4312
  max: max != null ? max : min,
4313
+ orderKind: resolvedOrderKind.kind,
4314
+ orderKindSource: resolvedOrderKind.source,
4170
4315
  quantity,
4171
4316
  quantitySource,
4172
4317
  services: servicesList,