@timeax/digital-service-engine 0.3.1 → 0.3.3

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.
@@ -4664,141 +4664,74 @@ function toBindArray(bind) {
4664
4664
  return Array.isArray(bind) ? bind.slice() : [bind];
4665
4665
  }
4666
4666
 
4667
- // src/utils/build-order-snapshot.ts
4668
- function buildOrderSnapshot(props, builder, selection, services, settings = {}) {
4669
- var _a, _b, _c, _d, _e, _f, _g, _h;
4670
- const mode = (_a = settings.mode) != null ? _a : "prod";
4671
- const hostDefaultQty = Number.isFinite(
4672
- (_b = settings.hostDefaultQuantity) != null ? _b : 1
4673
- ) ? settings.hostDefaultQuantity : 1;
4674
- const fbSettings = {
4675
- requireConstraintFit: true,
4676
- ratePolicy: { kind: "lte_primary", pct: 5 },
4677
- selectionStrategy: "priority",
4678
- mode: mode === "dev" ? "dev" : "strict",
4679
- ...(_c = settings.fallback) != null ? _c : {}
4680
- };
4681
- const builtAt = (/* @__PURE__ */ new Date()).toISOString();
4682
- const tagId = selection.activeTagId;
4683
- const selectedButtonKeys = (_d = selection.selectedKeys) != null ? _d : toSelectedOptionKeys(selection.optionSelectionsByFieldId);
4684
- const visibleFieldIds = builder.visibleFields(
4685
- tagId,
4686
- selectedButtonKeys
4687
- );
4688
- const tagById = new Map(
4689
- ((_e = props.filters) != null ? _e : []).map((t) => [t.id, t])
4690
- );
4691
- const fieldById = new Map(
4692
- ((_f = props.fields) != null ? _f : []).map((f) => [f.id, f])
4693
- );
4694
- const tagConstraints = (_h = (_g = tagById.get(tagId)) == null ? void 0 : _g.constraints) != null ? _h : void 0;
4695
- const selectionFields = visibleFieldIds.map((fid) => fieldById.get(fid)).filter((f) => !!f).map((f) => {
4696
- var _a2;
4697
- const optIds = isOptionBased(f) ? (_a2 = selection.optionSelectionsByFieldId[f.id]) != null ? _a2 : [] : void 0;
4667
+ // src/utils/build-order-snapshot/fallbacks.ts
4668
+ function pruneFallbacksConservative(fallbacks, env, svcMap, policy) {
4669
+ var _a, _b;
4670
+ if (!fallbacks) return { pruned: void 0, original: void 0 };
4671
+ try {
4672
+ const { props: prunedProps } = pruneInvalidNodeFallbacks(
4673
+ {
4674
+ filters: [],
4675
+ fields: [],
4676
+ schema_version: "1.0",
4677
+ fallbacks
4678
+ },
4679
+ svcMap,
4680
+ policy
4681
+ );
4698
4682
  return {
4699
- id: f.id,
4700
- type: String(f.type),
4701
- ...optIds && optIds.length ? { selectedOptions: optIds } : {}
4683
+ pruned: prunedProps.fallbacks,
4684
+ original: fallbacks
4702
4685
  };
4703
- });
4704
- const { formValues, selections } = buildInputs(
4705
- visibleFieldIds,
4706
- fieldById,
4707
- selection
4708
- );
4709
- const qtyRes = resolveQuantity(
4710
- visibleFieldIds,
4711
- fieldById,
4712
- tagById,
4713
- selection,
4714
- tagId,
4715
- hostDefaultQty
4716
- );
4717
- const quantity = qtyRes.quantity;
4718
- const quantitySource = qtyRes.source;
4719
- const { serviceMap, servicesList } = resolveServices(
4720
- tagId,
4721
- visibleFieldIds,
4722
- selection,
4723
- tagById,
4724
- fieldById
4725
- );
4726
- const { min, max } = resolveMinMax(servicesList, services);
4727
- const maybeNodeMap = typeof builder.getNodeMap === "function" ? builder.getNodeMap() : void 0;
4728
- const resolvedOrderKind = resolveOrderKind({
4729
- props,
4730
- activeTagId: tagId,
4731
- selectedTriggerKeys: selectedButtonKeys,
4732
- nodeMap: maybeNodeMap
4733
- });
4734
- const prunedFallbacks = pruneFallbacksConservative(
4735
- props.fallbacks,
4736
- { tagId, constraints: tagConstraints, serviceMap, servicesList },
4737
- services,
4738
- fbSettings
4739
- );
4740
- const utilities = collectUtilityLineItems(
4741
- visibleFieldIds,
4742
- fieldById,
4743
- selection,
4744
- quantity
4745
- );
4746
- const warnings = mode === "dev" ? buildDevWarnings(
4747
- props,
4748
- services,
4749
- tagId,
4750
- serviceMap,
4751
- prunedFallbacks.original,
4752
- prunedFallbacks.pruned,
4753
- fieldById,
4754
- visibleFieldIds,
4755
- selection
4756
- ) : void 0;
4757
- const snapshotPolicy = toSnapshotPolicy(fbSettings);
4758
- const meta = {
4759
- schema_version: props.schema_version,
4760
- workspaceId: settings.workspaceId,
4761
- builder: settings.builderCommit ? { commit: settings.builderCommit } : void 0,
4762
- context: {
4763
- tag: tagId,
4764
- constraints: tagConstraints != null ? tagConstraints : {},
4765
- nodeContexts: buildNodeContexts(
4766
- tagId,
4767
- visibleFieldIds,
4768
- fieldById,
4769
- selection
4770
- ),
4771
- policy: snapshotPolicy
4686
+ } catch {
4687
+ const out = {};
4688
+ const requireFit = (_a = policy.requireConstraintFit) != null ? _a : true;
4689
+ if (fallbacks.nodes) {
4690
+ const keptNodes = {};
4691
+ for (const [nodeId, candidates] of Object.entries(
4692
+ fallbacks.nodes
4693
+ )) {
4694
+ if (!env.serviceMap[nodeId]) continue;
4695
+ const primary = ((_b = env.serviceMap[nodeId]) != null ? _b : [])[0];
4696
+ const kept = [];
4697
+ for (const cand of candidates != null ? candidates : []) {
4698
+ if (!rateOk(svcMap, cand, primary, policy)) continue;
4699
+ if (requireFit && env.constraints && !constraintFitOk(svcMap, cand, env.constraints)) continue;
4700
+ kept.push(cand);
4701
+ }
4702
+ if (kept.length) keptNodes[nodeId] = kept;
4703
+ }
4704
+ if (Object.keys(keptNodes).length) out.nodes = keptNodes;
4772
4705
  }
4773
- };
4774
- const snapshot = {
4775
- version: "1",
4776
- mode,
4777
- builtAt,
4778
- selection: {
4779
- tag: tagId,
4780
- buttons: selectedButtonKeys,
4781
- fields: selectionFields
4782
- },
4783
- inputs: {
4784
- form: formValues,
4785
- selections
4786
- },
4787
- min,
4788
- max: max != null ? max : min,
4789
- orderKind: resolvedOrderKind.kind,
4790
- orderKindSource: resolvedOrderKind.source,
4791
- quantity,
4792
- quantitySource,
4793
- services: servicesList,
4794
- serviceMap,
4795
- ...prunedFallbacks.pruned ? { fallbacks: prunedFallbacks.pruned } : {},
4796
- ...utilities.length ? { utilities } : {},
4797
- ...warnings ? { warnings } : {},
4798
- meta
4799
- };
4800
- return snapshot;
4706
+ if (fallbacks.global) {
4707
+ const keptGlobal = {};
4708
+ const present = new Set(env.servicesList.map((sid) => String(sid)));
4709
+ for (const [primary, cands] of Object.entries(
4710
+ fallbacks.global
4711
+ )) {
4712
+ if (!present.has(String(primary))) continue;
4713
+ const primId = isFiniteNumber2(primary) ? Number(primary) : primary;
4714
+ const kept = [];
4715
+ for (const cand of cands != null ? cands : []) {
4716
+ if (!rateOk(svcMap, cand, primId, policy)) continue;
4717
+ if (requireFit && env.constraints && !constraintFitOk(svcMap, cand, env.constraints)) continue;
4718
+ kept.push(cand);
4719
+ }
4720
+ if (kept.length) keptGlobal[primId] = kept;
4721
+ }
4722
+ if (Object.keys(keptGlobal).length) out.global = keptGlobal;
4723
+ }
4724
+ return {
4725
+ pruned: Object.keys(out).length ? out : void 0,
4726
+ original: fallbacks
4727
+ };
4728
+ }
4729
+ }
4730
+ function isFiniteNumber2(v) {
4731
+ return typeof v === "number" && Number.isFinite(v);
4801
4732
  }
4733
+
4734
+ // src/utils/build-order-snapshot/selection.ts
4802
4735
  function isOptionBased(f) {
4803
4736
  const hasOptions = Array.isArray(f.options) && f.options.length > 0;
4804
4737
  return hasOptions || isMultiField(f);
@@ -4806,50 +4739,227 @@ function isOptionBased(f) {
4806
4739
  function toSelectedOptionKeys(byField) {
4807
4740
  const keys = [];
4808
4741
  for (const [fieldId, optionIds] of Object.entries(byField != null ? byField : {})) {
4809
- for (const optId of optionIds != null ? optionIds : []) {
4810
- keys.push(`${fieldId}::${optId}`);
4742
+ for (const optionId of optionIds != null ? optionIds : []) {
4743
+ keys.push(`${fieldId}::${optionId}`);
4811
4744
  }
4812
4745
  }
4813
4746
  return keys;
4814
4747
  }
4815
- function isServicedBased(field) {
4816
- if (field.service_id) return true;
4817
- return !!(field.options && field.options.some((item) => item.service_id));
4748
+ function getSelectedOptionsByFieldId(selection, fieldById) {
4749
+ const out = {};
4750
+ for (const visit of buildSelectedNodeVisitOrder(selection, fieldById)) {
4751
+ if (visit.kind !== "option") continue;
4752
+ if (!out[visit.fieldId]) out[visit.fieldId] = [];
4753
+ out[visit.fieldId].push(visit.optionId);
4754
+ }
4755
+ return out;
4756
+ }
4757
+ function buildSelectedNodeVisitOrder(selection, fieldById) {
4758
+ var _a, _b, _c;
4759
+ const out = [];
4760
+ const seen = /* @__PURE__ */ new Set();
4761
+ function pushField(fieldId) {
4762
+ const key = `field:${fieldId}`;
4763
+ if (seen.has(key)) return;
4764
+ seen.add(key);
4765
+ out.push({ kind: "field", fieldId });
4766
+ }
4767
+ function pushOption(fieldId, optionId) {
4768
+ const key = `option:${fieldId}::${optionId}`;
4769
+ if (seen.has(key)) return;
4770
+ seen.add(key);
4771
+ out.push({ kind: "option", fieldId, optionId });
4772
+ }
4773
+ for (const item of (_a = selection.optionTraversalOrder) != null ? _a : []) {
4774
+ pushOption(item.fieldId, item.optionId);
4775
+ }
4776
+ for (const rawKey of (_b = selection.selectedKeys) != null ? _b : []) {
4777
+ const key = String(rawKey);
4778
+ if (key.includes("::")) {
4779
+ const [fieldId, optionId] = key.split("::", 2);
4780
+ if (fieldId && optionId) pushOption(fieldId, optionId);
4781
+ continue;
4782
+ }
4783
+ const field = fieldById.get(key);
4784
+ if (field) {
4785
+ pushField(field.id);
4786
+ continue;
4787
+ }
4788
+ const ownerField = findOptionOwnerField(key, fieldById);
4789
+ if (ownerField) pushOption(ownerField.id, key);
4790
+ }
4791
+ for (const [fieldId, optionIds] of Object.entries(
4792
+ (_c = selection.optionSelectionsByFieldId) != null ? _c : {}
4793
+ )) {
4794
+ if (!fieldById.has(fieldId)) continue;
4795
+ for (const optionId of optionIds != null ? optionIds : []) {
4796
+ pushOption(fieldId, optionId);
4797
+ }
4798
+ }
4799
+ return out;
4800
+ }
4801
+ function findOptionOwnerField(optionId, fieldById) {
4802
+ var _a;
4803
+ for (const field of fieldById.values()) {
4804
+ if ((_a = field.options) == null ? void 0 : _a.some((option) => option.id === optionId)) return field;
4805
+ }
4806
+ return void 0;
4807
+ }
4808
+
4809
+ // src/utils/build-order-snapshot/services.ts
4810
+ function isServiceBased(field) {
4811
+ var _a;
4812
+ if (field.service_id !== void 0 && field.service_id !== null) return true;
4813
+ return !!((_a = field.options) == null ? void 0 : _a.some(
4814
+ (item) => item.service_id !== void 0 && item.service_id !== null
4815
+ ));
4818
4816
  }
4819
- function buildInputs(visibleFieldIds, fieldById, selection) {
4817
+ function resolveServices(tagId, visibleFieldIds, selection, tagById, fieldById, services) {
4818
+ var _a, _b, _c, _d;
4819
+ const serviceMap = {};
4820
+ const visible = new Set(visibleFieldIds);
4821
+ const selectedBaseServices = [];
4822
+ const visits = buildSelectedNodeVisitOrder(selection, fieldById);
4823
+ let index = 0;
4824
+ function addSelectedBaseService(origin, sid) {
4825
+ pushService(serviceMap, origin, sid);
4826
+ selectedBaseServices.push({
4827
+ origin,
4828
+ sid,
4829
+ rate: readServiceRate(services, sid),
4830
+ index: index++
4831
+ });
4832
+ }
4833
+ for (const visit of visits) {
4834
+ if (!visible.has(visit.fieldId)) continue;
4835
+ const field = fieldById.get(visit.fieldId);
4836
+ if (!field) continue;
4837
+ if (visit.kind === "field") {
4838
+ const role2 = (_a = field.pricing_role) != null ? _a : "base";
4839
+ if (role2 === "utility") continue;
4840
+ if (field.service_id !== void 0 && field.service_id !== null) {
4841
+ addSelectedBaseService(field.id, field.service_id);
4842
+ }
4843
+ continue;
4844
+ }
4845
+ const option = (_b = field.options) == null ? void 0 : _b.find((item) => item.id === visit.optionId);
4846
+ if (!option) continue;
4847
+ const role = (_d = (_c = option.pricing_role) != null ? _c : field.pricing_role) != null ? _d : "base";
4848
+ if (role === "utility") continue;
4849
+ if (option.service_id !== void 0 && option.service_id !== null) {
4850
+ addSelectedBaseService(option.id, option.service_id);
4851
+ }
4852
+ }
4853
+ if (selectedBaseServices.length > 0) {
4854
+ const primary = pickHighestRatePrimary2(selectedBaseServices);
4855
+ const ordered = [
4856
+ primary.sid,
4857
+ ...selectedBaseServices.filter((item) => item !== primary).sort((a, b) => a.index - b.index).map((item) => item.sid)
4858
+ ];
4859
+ return { serviceMap, servicesList: dedupeByString(ordered) };
4860
+ }
4861
+ const tag = tagById.get(tagId);
4862
+ if ((tag == null ? void 0 : tag.service_id) !== void 0 && tag.service_id !== null) {
4863
+ pushService(serviceMap, tagId, tag.service_id);
4864
+ return { serviceMap, servicesList: [tag.service_id] };
4865
+ }
4866
+ return { serviceMap, servicesList: [] };
4867
+ }
4868
+ function pickHighestRatePrimary2(services) {
4869
+ let best = services[0];
4870
+ for (const item of services.slice(1)) {
4871
+ if (item.rate > best.rate) best = item;
4872
+ }
4873
+ return best;
4874
+ }
4875
+ function getCap2(map, id) {
4876
+ var _a;
4877
+ return (_a = map == null ? void 0 : map[id]) != null ? _a : map == null ? void 0 : map[String(id)];
4878
+ }
4879
+ function readServiceRate(services, sid) {
4880
+ var _a;
4881
+ const rate = Number((_a = getCap2(services, sid)) == null ? void 0 : _a.rate);
4882
+ return Number.isFinite(rate) ? rate : Number.NEGATIVE_INFINITY;
4883
+ }
4884
+ function pushService(map, nodeId, sid) {
4885
+ if (!map[nodeId]) map[nodeId] = [];
4886
+ map[nodeId].push(sid);
4887
+ }
4888
+ function dedupeByString(arr) {
4889
+ const seen = /* @__PURE__ */ new Set();
4890
+ const out = [];
4891
+ for (const value of arr) {
4892
+ const key = String(value);
4893
+ if (seen.has(key)) continue;
4894
+ seen.add(key);
4895
+ out.push(value);
4896
+ }
4897
+ return out;
4898
+ }
4899
+
4900
+ // src/utils/build-order-snapshot/inputs.ts
4901
+ function buildInputs(visibleFieldIds, fieldById, selection, selectedOptionsByFieldId) {
4820
4902
  const formValues = {};
4821
4903
  const selections = {};
4822
4904
  for (const fid of visibleFieldIds) {
4823
- const f = fieldById.get(fid);
4824
- if (!f) continue;
4825
- const selOptIds = selection.optionSelectionsByFieldId[fid];
4826
- if (selOptIds && selOptIds.length) {
4827
- selections[fid] = [...selOptIds];
4828
- }
4829
- if (!isServicedBased(f)) {
4830
- const name = f.name;
4831
- const val = selection.formValuesByFieldId[fid];
4832
- if (!name || val === void 0) continue;
4833
- formValues[name] = val;
4905
+ const field = fieldById.get(fid);
4906
+ if (!field) continue;
4907
+ const selectedOptionIds = selectedOptionsByFieldId[fid];
4908
+ if (selectedOptionIds == null ? void 0 : selectedOptionIds.length) selections[fid] = [...selectedOptionIds];
4909
+ if (!isServiceBased(field)) {
4910
+ const name = field.name;
4911
+ const value = selection.formValuesByFieldId[fid];
4912
+ if (!name || value === void 0) continue;
4913
+ formValues[name] = value;
4834
4914
  }
4835
4915
  }
4836
4916
  return { formValues, selections };
4837
4917
  }
4918
+
4919
+ // src/utils/build-order-snapshot/min-max.ts
4920
+ function getCap3(map, id) {
4921
+ var _a;
4922
+ return (_a = map == null ? void 0 : map[id]) != null ? _a : map == null ? void 0 : map[String(id)];
4923
+ }
4924
+ function resolveMinMax(servicesList, services) {
4925
+ let min;
4926
+ let max;
4927
+ for (const sid of servicesList) {
4928
+ const cap = getCap3(services, sid);
4929
+ if (!cap) continue;
4930
+ if (typeof cap.min === "number" && Number.isFinite(cap.min)) {
4931
+ min = min === void 0 ? cap.min : Math.min(min, cap.min);
4932
+ }
4933
+ if (typeof cap.max === "number" && Number.isFinite(cap.max)) {
4934
+ max = max === void 0 ? cap.max : Math.max(max, cap.max);
4935
+ }
4936
+ }
4937
+ return { min: min != null ? min : 1, ...max !== void 0 ? { max } : {} };
4938
+ }
4939
+
4940
+ // src/utils/build-order-snapshot/policy.ts
4941
+ function toSnapshotPolicy(settings) {
4942
+ var _a;
4943
+ return {
4944
+ ratePolicy: normalizeRatePolicy(settings.ratePolicy),
4945
+ requireConstraintFit: (_a = settings.requireConstraintFit) != null ? _a : true
4946
+ };
4947
+ }
4948
+
4949
+ // src/utils/build-order-snapshot/quantity.ts
4838
4950
  function resolveQuantity(visibleFieldIds, fieldById, tagById, selection, tagId, hostDefault) {
4839
4951
  var _a;
4840
4952
  for (const fid of visibleFieldIds) {
4841
- const f = fieldById.get(fid);
4842
- if (!f) continue;
4843
- const rule = readQuantityRule(
4844
- (_a = f.meta) == null ? void 0 : _a.quantity
4845
- );
4953
+ const field = fieldById.get(fid);
4954
+ if (!field) continue;
4955
+ const rule = readQuantityRule((_a = field.meta) == null ? void 0 : _a.quantity);
4846
4956
  if (!rule) continue;
4847
4957
  const raw = selection.formValuesByFieldId[fid];
4848
4958
  const evaluated = evaluateQuantityRule(rule, raw);
4849
4959
  if (Number.isFinite(evaluated) && evaluated > 0) {
4850
4960
  return {
4851
4961
  quantity: evaluated,
4852
- source: { kind: "field", id: f.id, rule }
4962
+ source: { kind: "field", id: field.id, rule }
4853
4963
  };
4854
4964
  }
4855
4965
  break;
@@ -4862,24 +4972,47 @@ function resolveQuantity(visibleFieldIds, fieldById, tagById, selection, tagId,
4862
4972
  tagId
4863
4973
  );
4864
4974
  if (nodeDefault) return nodeDefault;
4865
- return {
4866
- quantity: hostDefault,
4867
- source: { kind: "default", defaultedFromHost: true }
4868
- };
4975
+ return { quantity: hostDefault, source: { kind: "default", defaultedFromHost: true } };
4976
+ }
4977
+ function resolveNodeDefaultQuantity(visibleFieldIds, fieldById, tagById, selection, tagId) {
4978
+ var _a, _b, _c;
4979
+ const visible = new Set(visibleFieldIds);
4980
+ const visits = buildSelectedNodeVisitOrder(selection, fieldById);
4981
+ for (const visit of visits) {
4982
+ if (visit.kind !== "option") continue;
4983
+ if (!visible.has(visit.fieldId)) continue;
4984
+ const field = fieldById.get(visit.fieldId);
4985
+ if (!((_a = field == null ? void 0 : field.options) == null ? void 0 : _a.length)) continue;
4986
+ const option = field.options.find((item) => item.id === visit.optionId);
4987
+ const quantity = readPositiveFiniteNumber((_b = option == null ? void 0 : option.meta) == null ? void 0 : _b.quantityDefault);
4988
+ if (quantity !== void 0) {
4989
+ return { quantity, source: { kind: "option", id: option.id } };
4990
+ }
4991
+ }
4992
+ for (const visit of visits) {
4993
+ if (!visible.has(visit.fieldId)) continue;
4994
+ const field = fieldById.get(visit.fieldId);
4995
+ if (!field) continue;
4996
+ const quantity = readPositiveFiniteNumber(field.quantityDefault);
4997
+ if (quantity !== void 0) {
4998
+ return { quantity, source: { kind: "field", id: field.id } };
4999
+ }
5000
+ }
5001
+ const tag = tagById.get(tagId);
5002
+ const tagQuantity = readPositiveFiniteNumber((_c = tag == null ? void 0 : tag.meta) == null ? void 0 : _c.quantityDefault);
5003
+ if (tagQuantity !== void 0) {
5004
+ return { quantity: tagQuantity, source: { kind: "tag", id: tagId } };
5005
+ }
5006
+ return void 0;
4869
5007
  }
4870
5008
  function readQuantityRule(v) {
4871
5009
  if (!v || typeof v !== "object") return void 0;
4872
5010
  const src = v;
4873
- if (src.valueBy !== "value" && src.valueBy !== "length" && src.valueBy !== "eval")
4874
- return void 0;
5011
+ if (src.valueBy !== "value" && src.valueBy !== "length" && src.valueBy !== "eval") return void 0;
4875
5012
  const out = { valueBy: src.valueBy };
4876
5013
  if (src.code && typeof src.code === "string") out.code = src.code;
4877
- if (typeof src.multiply === "number" && Number.isFinite(src.multiply)) {
4878
- out.multiply = src.multiply;
4879
- }
4880
- if (typeof src.fallback === "number" && Number.isFinite(src.fallback)) {
4881
- out.fallback = src.fallback;
4882
- }
5014
+ if (typeof src.multiply === "number" && Number.isFinite(src.multiply)) out.multiply = src.multiply;
5015
+ if (typeof src.fallback === "number" && Number.isFinite(src.fallback)) out.fallback = src.fallback;
4883
5016
  if (src.clamp && typeof src.clamp === "object") {
4884
5017
  const min = typeof src.clamp.min === "number" && Number.isFinite(src.clamp.min) ? src.clamp.min : void 0;
4885
5018
  const max = typeof src.clamp.max === "number" && Number.isFinite(src.clamp.max) ? src.clamp.max : void 0;
@@ -4950,231 +5083,43 @@ function applyClamp(value, clamp2) {
4950
5083
  if ((clamp2 == null ? void 0 : clamp2.max) !== void 0) next = Math.min(next, clamp2.max);
4951
5084
  return next;
4952
5085
  }
4953
- function resolveNodeDefaultQuantity(visibleFieldIds, fieldById, tagById, selection, tagId) {
4954
- var _a, _b, _c, _d;
4955
- const optionVisit = buildOptionVisitOrder(selection, fieldById);
4956
- for (const { fieldId, optionId } of optionVisit) {
4957
- if (!visibleFieldIds.includes(fieldId)) continue;
4958
- const field = fieldById.get(fieldId);
4959
- const option = (_a = field == null ? void 0 : field.options) == null ? void 0 : _a.find((item) => item.id === optionId);
4960
- const quantityDefault = readQuantityDefault(
4961
- (_b = option == null ? void 0 : option.meta) == null ? void 0 : _b.quantityDefault
4962
- );
4963
- if (quantityDefault !== void 0) {
4964
- return {
4965
- quantity: quantityDefault,
4966
- source: { kind: "option", id: optionId }
4967
- };
4968
- }
4969
- }
4970
- for (const fieldId of visibleFieldIds) {
4971
- const field = fieldById.get(fieldId);
4972
- if (!field) continue;
4973
- const isButtonStyle = field.button === true || Array.isArray(field.options) && field.options.length > 0;
4974
- if (!isButtonStyle) continue;
4975
- const quantityDefault = readQuantityDefault(
4976
- field.quantityDefault
4977
- );
4978
- if (quantityDefault !== void 0) {
4979
- return {
4980
- quantity: quantityDefault,
4981
- source: { kind: "field", id: field.id }
4982
- };
4983
- }
4984
- }
4985
- const tagQuantityDefault = readQuantityDefault(
4986
- (_d = (_c = tagById.get(tagId)) == null ? void 0 : _c.meta) == null ? void 0 : _d.quantityDefault
4987
- );
4988
- if (tagQuantityDefault !== void 0) {
4989
- return {
4990
- quantity: tagQuantityDefault,
4991
- source: { kind: "tag", id: tagId }
4992
- };
4993
- }
4994
- return void 0;
4995
- }
4996
- function readQuantityDefault(value) {
4997
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
4998
- }
4999
- function resolveServices(tagId, visibleFieldIds, selection, tagById, fieldById) {
5000
- var _a;
5001
- const serviceMap = {};
5002
- const ordered = [];
5003
- const tag = tagById.get(tagId);
5004
- let primary;
5005
- let primaryOrigin;
5006
- if ((tag == null ? void 0 : tag.service_id) !== void 0) {
5007
- primary = tag.service_id;
5008
- primaryOrigin = "tag";
5009
- }
5010
- const optionVisit = buildOptionVisitOrder(selection, fieldById);
5011
- for (const { fieldId, optionId } of optionVisit) {
5012
- if (!visibleFieldIds.includes(fieldId)) continue;
5013
- const f = fieldById.get(fieldId);
5014
- if (!f || !Array.isArray(f.options)) continue;
5015
- const opt = f.options.find((o) => o.id === optionId);
5016
- if (!opt) continue;
5017
- const role = (_a = opt.pricing_role) != null ? _a : "base";
5018
- const sid = opt.service_id;
5019
- if (role === "utility") continue;
5020
- if (sid !== void 0) {
5021
- if (primary === void 0 || primaryOrigin === "tag") {
5022
- primary = sid;
5023
- primaryOrigin = "option";
5024
- ordered.length = 0;
5025
- ordered.push(primary);
5026
- } else {
5027
- ordered.push(sid);
5028
- }
5029
- pushService(serviceMap, optionId, sid);
5030
- }
5031
- }
5032
- if (primaryOrigin !== "option" && primary !== void 0) {
5033
- ordered.unshift(primary);
5034
- pushService(serviceMap, tagId, primary);
5035
- } else {
5036
- }
5037
- const servicesList = dedupeByString(ordered);
5038
- return { serviceMap, servicesList };
5039
- }
5040
- function buildOptionVisitOrder(selection, fieldById) {
5041
- var _a;
5042
- if (selection.optionTraversalOrder && selection.optionTraversalOrder.length) {
5043
- return selection.optionTraversalOrder.slice();
5044
- }
5045
- const out = [];
5046
- for (const [fid, optIds] of Object.entries(
5047
- (_a = selection.optionSelectionsByFieldId) != null ? _a : {}
5048
- )) {
5049
- const f = fieldById.get(fid);
5050
- if (!f) continue;
5051
- for (const oid of optIds != null ? optIds : [])
5052
- out.push({ fieldId: fid, optionId: oid });
5053
- }
5054
- return out;
5055
- }
5056
- function pushService(map, nodeId, sid) {
5057
- if (!map[nodeId]) map[nodeId] = [];
5058
- map[nodeId].push(sid);
5059
- }
5060
- function dedupeByString(arr) {
5061
- const s = /* @__PURE__ */ new Set();
5062
- const out = [];
5063
- for (const v of arr) {
5064
- const key = String(v);
5065
- if (s.has(key)) continue;
5066
- s.add(key);
5067
- out.push(v);
5068
- }
5069
- return out;
5070
- }
5071
- function pruneFallbacksConservative(fallbacks, env, svcMap, policy) {
5072
- var _a, _b;
5073
- if (!fallbacks) return { pruned: void 0, original: void 0 };
5074
- try {
5075
- const { props: prunedProps } = pruneInvalidNodeFallbacks(
5076
- {
5077
- filters: [],
5078
- fields: [],
5079
- schema_version: "1.0",
5080
- fallbacks
5081
- },
5082
- svcMap,
5083
- policy
5084
- );
5085
- return {
5086
- pruned: prunedProps.fallbacks,
5087
- original: fallbacks
5088
- };
5089
- } catch {
5090
- const out = {};
5091
- const requireFit = (_a = policy.requireConstraintFit) != null ? _a : true;
5092
- if (fallbacks.nodes) {
5093
- const keptNodes = {};
5094
- for (const [nodeId, candidates] of Object.entries(
5095
- fallbacks.nodes
5096
- )) {
5097
- if (!env.serviceMap[nodeId]) continue;
5098
- const primary = ((_b = env.serviceMap[nodeId]) != null ? _b : [])[0];
5099
- const kept = [];
5100
- for (const cand of candidates != null ? candidates : []) {
5101
- if (!rateOk(svcMap, cand, primary, policy)) continue;
5102
- if (requireFit && env.constraints && !constraintFitOk(svcMap, cand, env.constraints))
5103
- continue;
5104
- kept.push(cand);
5105
- }
5106
- if (kept.length) keptNodes[nodeId] = kept;
5107
- }
5108
- if (Object.keys(keptNodes).length) out.nodes = keptNodes;
5109
- }
5110
- if (fallbacks.global) {
5111
- const keptGlobal = {};
5112
- const present = new Set(env.servicesList.map((sid) => String(sid)));
5113
- for (const [primary, cands] of Object.entries(
5114
- fallbacks.global
5115
- )) {
5116
- if (!present.has(String(primary))) continue;
5117
- const primId = isFiniteNumber2(primary) ? Number(primary) : primary;
5118
- const kept = [];
5119
- for (const cand of cands != null ? cands : []) {
5120
- if (!rateOk(svcMap, cand, primId, policy)) continue;
5121
- if (requireFit && env.constraints && !constraintFitOk(svcMap, cand, env.constraints))
5122
- continue;
5123
- kept.push(cand);
5124
- }
5125
- if (kept.length) keptGlobal[primId] = kept;
5126
- }
5127
- if (Object.keys(keptGlobal).length)
5128
- out.global = keptGlobal;
5129
- }
5130
- return {
5131
- pruned: Object.keys(out).length ? out : void 0,
5132
- original: fallbacks
5133
- };
5134
- }
5135
- }
5136
- function isFiniteNumber2(v) {
5137
- return typeof v === "number" && Number.isFinite(v);
5086
+ function readPositiveFiniteNumber(value) {
5087
+ const n = Number(value);
5088
+ return Number.isFinite(n) && n > 0 ? n : void 0;
5138
5089
  }
5139
- function collectUtilityLineItems(visibleFieldIds, fieldById, selection, quantity) {
5090
+
5091
+ // src/utils/build-order-snapshot/utilities.ts
5092
+ function collectUtilityLineItems(visibleFieldIds, fieldById, selection, selectedOptionsByFieldId, quantity) {
5140
5093
  var _a, _b, _c, _d, _e;
5141
5094
  const items = [];
5142
5095
  for (const fid of visibleFieldIds) {
5143
- const f = fieldById.get(fid);
5144
- if (!f) continue;
5145
- const isUtilityField = ((_a = f.pricing_role) != null ? _a : "base") === "utility";
5146
- const marker = readUtilityMarker((_b = f.meta) == null ? void 0 : _b.utility);
5096
+ const field = fieldById.get(fid);
5097
+ if (!field) continue;
5098
+ const isUtilityField = ((_a = field.pricing_role) != null ? _a : "base") === "utility";
5099
+ const marker = readUtilityMarker((_b = field.meta) == null ? void 0 : _b.utility);
5147
5100
  if (isUtilityField && marker) {
5148
- const val = selection.formValuesByFieldId[f.id];
5149
- const item = buildUtilityItemFromMarker(
5150
- f.id,
5151
- marker,
5152
- quantity,
5153
- val
5154
- );
5101
+ const value = selection.formValuesByFieldId[field.id];
5102
+ const item = buildUtilityItemFromMarker(field.id, marker, quantity, value);
5155
5103
  if (item) items.push(item);
5156
5104
  }
5157
- if (Array.isArray(f.options) && f.options.length) {
5158
- const selectedOptIds = (_c = selection.optionSelectionsByFieldId[f.id]) != null ? _c : [];
5159
- if (selectedOptIds.length) {
5160
- const optById = new Map(
5161
- f.options.map((o) => [o.id, o])
5105
+ if (Array.isArray(field.options) && field.options.length) {
5106
+ const selectedOptionIds = (_c = selectedOptionsByFieldId[field.id]) != null ? _c : [];
5107
+ if (!selectedOptionIds.length) continue;
5108
+ const optById = new Map(field.options.map((o) => [o.id, o]));
5109
+ for (const oid of selectedOptionIds) {
5110
+ const option = optById.get(oid);
5111
+ if (!option) continue;
5112
+ if (((_d = option.pricing_role) != null ? _d : "base") !== "utility") continue;
5113
+ const optionMarker = readUtilityMarker((_e = option.meta) == null ? void 0 : _e.utility);
5114
+ if (!optionMarker) continue;
5115
+ const parentValue = selection.formValuesByFieldId[field.id];
5116
+ const item = buildUtilityItemFromMarker(
5117
+ option.id,
5118
+ optionMarker,
5119
+ quantity,
5120
+ parentValue
5162
5121
  );
5163
- for (const oid of selectedOptIds) {
5164
- const opt = optById.get(oid);
5165
- if (!opt) continue;
5166
- if (((_d = opt.pricing_role) != null ? _d : "base") !== "utility") continue;
5167
- const om = readUtilityMarker((_e = opt.meta) == null ? void 0 : _e.utility);
5168
- if (!om) continue;
5169
- const parentVal = selection.formValuesByFieldId[f.id];
5170
- const item = buildUtilityItemFromMarker(
5171
- opt.id,
5172
- om,
5173
- quantity,
5174
- parentVal
5175
- );
5176
- if (item) items.push(item);
5177
- }
5122
+ if (item) items.push(item);
5178
5123
  }
5179
5124
  }
5180
5125
  }
@@ -5183,19 +5128,16 @@ function collectUtilityLineItems(visibleFieldIds, fieldById, selection, quantity
5183
5128
  function readUtilityMarker(v) {
5184
5129
  if (!v || typeof v !== "object") return void 0;
5185
5130
  const src = v;
5186
- if (!src.mode || typeof src.rate !== "number" || !Number.isFinite(src.rate))
5187
- return void 0;
5188
- if (src.mode !== "flat" && src.mode !== "per_quantity" && src.mode !== "per_value" && src.mode !== "percent")
5131
+ if (!src.mode || typeof src.rate !== "number" || !Number.isFinite(src.rate)) return void 0;
5132
+ if (src.mode !== "flat" && src.mode !== "per_quantity" && src.mode !== "per_value" && src.mode !== "percent") {
5189
5133
  return void 0;
5134
+ }
5190
5135
  const out = { mode: src.mode, rate: src.rate };
5191
- if (src.valueBy === "value" || src.valueBy === "length")
5192
- out.valueBy = src.valueBy;
5136
+ if (src.valueBy === "value" || src.valueBy === "length") out.valueBy = src.valueBy;
5193
5137
  if (src.percentBase === "service_total" || src.percentBase === "base_service" || src.percentBase === "all") {
5194
5138
  out.percentBase = src.percentBase;
5195
5139
  }
5196
- if (typeof src.label === "string" && src.label.trim()) {
5197
- out.label = src.label.trim();
5198
- }
5140
+ if (typeof src.label === "string" && src.label.trim()) out.label = src.label.trim();
5199
5141
  return out;
5200
5142
  }
5201
5143
  function buildUtilityItemFromMarker(nodeId, marker, quantity, value) {
@@ -5218,17 +5160,19 @@ function buildUtilityItemFromMarker(nodeId, marker, quantity, value) {
5218
5160
  }
5219
5161
  return base;
5220
5162
  }
5221
- function buildNodeContexts(tagId, visibleFieldIds, fieldById, selection) {
5163
+
5164
+ // src/utils/build-order-snapshot/context.ts
5165
+ function buildNodeContexts(tagId, visibleFieldIds, fieldById, _selection, selectedOptionsByFieldId) {
5222
5166
  var _a;
5223
5167
  const ctx = {};
5224
5168
  ctx[tagId] = tagId;
5225
5169
  for (const fid of visibleFieldIds) {
5226
- const f = fieldById.get(fid);
5227
- if (!f) continue;
5228
- const binds = normalizeBindIds(f.bind_id);
5170
+ const field = fieldById.get(fid);
5171
+ if (!field) continue;
5172
+ const binds = normalizeBindIds(field.bind_id);
5229
5173
  const applicable = binds.has(tagId);
5230
- const selectedOptIds = (_a = selection.optionSelectionsByFieldId[fid]) != null ? _a : [];
5231
- for (const oid of selectedOptIds) {
5174
+ const selectedOptionIds = (_a = selectedOptionsByFieldId[fid]) != null ? _a : [];
5175
+ for (const oid of selectedOptionIds) {
5232
5176
  ctx[oid] = applicable ? tagId : null;
5233
5177
  }
5234
5178
  }
@@ -5244,66 +5188,159 @@ function normalizeBindIds(bind) {
5244
5188
  }
5245
5189
  return out;
5246
5190
  }
5247
- function buildDevWarnings(props, svcMap, _tagId, _snapshotServiceMap, originalFallbacks, _prunedFallbacks, fieldById, visibleFieldIds, selection) {
5191
+
5192
+ // src/utils/build-order-snapshot/warnings.ts
5193
+ function buildDevWarnings(props, svcMap, originalFallbacks, fieldById, visibleFieldIds, selection) {
5248
5194
  const out = {};
5249
5195
  const maybeCollectFailed = globalThis.collectFailedFallbacks;
5250
5196
  try {
5251
5197
  if (maybeCollectFailed && originalFallbacks) {
5252
5198
  const diags = maybeCollectFailed(
5253
- {
5254
- ...props,
5255
- fallbacks: originalFallbacks
5256
- },
5199
+ { ...props, fallbacks: originalFallbacks },
5257
5200
  svcMap,
5258
5201
  { mode: "dev" }
5259
5202
  );
5260
- if (diags && diags.length) {
5261
- out.fallbacks = diags;
5262
- }
5203
+ if (diags == null ? void 0 : diags.length) out.fallbacks = diags;
5263
5204
  }
5264
5205
  } catch {
5265
5206
  }
5266
5207
  const utilityWarnings = [];
5267
5208
  for (const fid of visibleFieldIds) {
5268
- const f = fieldById.get(fid);
5269
- if (!f) continue;
5270
- const hasVal = selection.formValuesByFieldId[fid] !== void 0;
5271
- if (hasVal && !f.name && !isOptionBased(f)) {
5209
+ const field = fieldById.get(fid);
5210
+ if (!field) continue;
5211
+ const hasValue = selection.formValuesByFieldId[fid] !== void 0;
5212
+ if (hasValue && !field.name && !isOptionBased(field)) {
5272
5213
  utilityWarnings.push({
5273
5214
  nodeId: fid,
5274
5215
  reason: "missing_field_name_for_form_value"
5275
5216
  });
5276
5217
  }
5277
5218
  }
5278
- if (utilityWarnings.length) {
5279
- out.utility = utilityWarnings;
5280
- }
5219
+ if (utilityWarnings.length) out.utility = utilityWarnings;
5281
5220
  if (!out.fallbacks && !out.utility) return void 0;
5282
5221
  return out;
5283
5222
  }
5284
- function toSnapshotPolicy(settings) {
5285
- var _a;
5286
- const requireConstraintFit = (_a = settings.requireConstraintFit) != null ? _a : true;
5287
- const rp = normalizeRatePolicy(settings.ratePolicy);
5288
- return { ratePolicy: rp, requireConstraintFit };
5289
- }
5290
- function getCap2(map, id) {
5291
- return getServiceCapability(map, id);
5292
- }
5293
- function resolveMinMax(servicesList, services) {
5294
- let min = void 0;
5295
- let max = void 0;
5296
- for (const sid of servicesList) {
5297
- const cap = getCap2(services, sid);
5298
- if (!cap) continue;
5299
- if (typeof cap.min === "number" && Number.isFinite(cap.min)) {
5300
- min = min === void 0 ? cap.min : Math.min(min, cap.min);
5301
- }
5302
- if (typeof cap.max === "number" && Number.isFinite(cap.max)) {
5303
- max = max === void 0 ? cap.max : Math.max(max, cap.max);
5223
+
5224
+ // src/utils/build-order-snapshot/index.ts
5225
+ function buildOrderSnapshot(props, builder, selection, services, settings = {}) {
5226
+ var _a, _b, _c, _d, _e, _f, _g, _h;
5227
+ const mode = (_a = settings.mode) != null ? _a : "prod";
5228
+ const hostDefaultQty = Number.isFinite((_b = settings.hostDefaultQuantity) != null ? _b : 1) ? settings.hostDefaultQuantity : 1;
5229
+ const fbSettings = {
5230
+ requireConstraintFit: true,
5231
+ ratePolicy: { kind: "lte_primary", pct: 5 },
5232
+ selectionStrategy: "priority",
5233
+ mode: mode === "dev" ? "dev" : "strict",
5234
+ ...(_c = settings.fallback) != null ? _c : {}
5235
+ };
5236
+ const builtAt = (/* @__PURE__ */ new Date()).toISOString();
5237
+ const tagId = selection.activeTagId;
5238
+ const selectedButtonKeys = (_d = selection.selectedKeys) != null ? _d : toSelectedOptionKeys(selection.optionSelectionsByFieldId);
5239
+ const visibleFieldIds = builder.visibleFields(tagId, selectedButtonKeys);
5240
+ const tagById = new Map(((_e = props.filters) != null ? _e : []).map((t) => [t.id, t]));
5241
+ const fieldById = new Map(((_f = props.fields) != null ? _f : []).map((f) => [f.id, f]));
5242
+ const tagConstraints = (_h = (_g = tagById.get(tagId)) == null ? void 0 : _g.constraints) != null ? _h : void 0;
5243
+ const selectedOptionsByFieldId = getSelectedOptionsByFieldId(selection, fieldById);
5244
+ const selectionFields = visibleFieldIds.map((fid) => fieldById.get(fid)).filter((f) => !!f).map((f) => {
5245
+ var _a2;
5246
+ const optionIds = isOptionBased(f) ? (_a2 = selectedOptionsByFieldId[f.id]) != null ? _a2 : [] : void 0;
5247
+ return {
5248
+ id: f.id,
5249
+ type: String(f.type),
5250
+ ...optionIds && optionIds.length ? { selectedOptions: optionIds } : {}
5251
+ };
5252
+ });
5253
+ const { formValues, selections } = buildInputs(
5254
+ visibleFieldIds,
5255
+ fieldById,
5256
+ selection,
5257
+ selectedOptionsByFieldId
5258
+ );
5259
+ const qtyRes = resolveQuantity(
5260
+ visibleFieldIds,
5261
+ fieldById,
5262
+ tagById,
5263
+ selection,
5264
+ tagId,
5265
+ hostDefaultQty
5266
+ );
5267
+ const { serviceMap, servicesList } = resolveServices(
5268
+ tagId,
5269
+ visibleFieldIds,
5270
+ selection,
5271
+ tagById,
5272
+ fieldById,
5273
+ services
5274
+ );
5275
+ const { min, max } = resolveMinMax(servicesList, services);
5276
+ const maybeNodeMap = typeof builder.getNodeMap === "function" ? builder.getNodeMap() : void 0;
5277
+ const resolvedOrderKind = resolveOrderKind({
5278
+ props,
5279
+ activeTagId: tagId,
5280
+ selectedTriggerKeys: selectedButtonKeys,
5281
+ nodeMap: maybeNodeMap
5282
+ });
5283
+ const prunedFallbacks = pruneFallbacksConservative(
5284
+ props.fallbacks,
5285
+ { tagId, constraints: tagConstraints, serviceMap, servicesList },
5286
+ services,
5287
+ fbSettings
5288
+ );
5289
+ const utilities = collectUtilityLineItems(
5290
+ visibleFieldIds,
5291
+ fieldById,
5292
+ selection,
5293
+ selectedOptionsByFieldId,
5294
+ qtyRes.quantity
5295
+ );
5296
+ const warnings = mode === "dev" ? buildDevWarnings(
5297
+ props,
5298
+ services,
5299
+ prunedFallbacks.original,
5300
+ fieldById,
5301
+ visibleFieldIds,
5302
+ selection
5303
+ ) : void 0;
5304
+ const meta = {
5305
+ schema_version: props.schema_version,
5306
+ workspaceId: settings.workspaceId,
5307
+ builder: settings.builderCommit ? { commit: settings.builderCommit } : void 0,
5308
+ context: {
5309
+ tag: tagId,
5310
+ constraints: tagConstraints != null ? tagConstraints : {},
5311
+ nodeContexts: buildNodeContexts(
5312
+ tagId,
5313
+ visibleFieldIds,
5314
+ fieldById,
5315
+ selection,
5316
+ selectedOptionsByFieldId
5317
+ ),
5318
+ policy: toSnapshotPolicy(fbSettings)
5304
5319
  }
5305
- }
5306
- return { min: min != null ? min : 1, ...max !== void 0 ? { max } : {} };
5320
+ };
5321
+ return {
5322
+ version: "1",
5323
+ mode,
5324
+ builtAt,
5325
+ selection: {
5326
+ tag: tagId,
5327
+ buttons: selectedButtonKeys,
5328
+ fields: selectionFields
5329
+ },
5330
+ inputs: { form: formValues, selections },
5331
+ min,
5332
+ max: max != null ? max : min,
5333
+ orderKind: resolvedOrderKind.kind,
5334
+ orderKindSource: resolvedOrderKind.source,
5335
+ quantity: qtyRes.quantity,
5336
+ quantitySource: qtyRes.source,
5337
+ services: servicesList,
5338
+ serviceMap,
5339
+ ...prunedFallbacks.pruned ? { fallbacks: prunedFallbacks.pruned } : {},
5340
+ ...utilities.length ? { utilities } : {},
5341
+ ...warnings ? { warnings } : {},
5342
+ meta
5343
+ };
5307
5344
  }
5308
5345
 
5309
5346
  // src/core/fallback-editor.ts