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