@timeax/digital-service-engine 0.3.2 → 0.3.4

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.
@@ -5008,141 +5008,74 @@ function toBindArray(bind) {
5008
5008
  return Array.isArray(bind) ? bind.slice() : [bind];
5009
5009
  }
5010
5010
 
5011
- // src/utils/build-order-snapshot.ts
5012
- function buildOrderSnapshot(props, builder, selection, services, settings = {}) {
5013
- var _a2, _b, _c, _d, _e, _f, _g, _h;
5014
- const mode = (_a2 = settings.mode) != null ? _a2 : "prod";
5015
- const hostDefaultQty = Number.isFinite(
5016
- (_b = settings.hostDefaultQuantity) != null ? _b : 1
5017
- ) ? settings.hostDefaultQuantity : 1;
5018
- const fbSettings = {
5019
- requireConstraintFit: true,
5020
- ratePolicy: { kind: "lte_primary", pct: 5 },
5021
- selectionStrategy: "priority",
5022
- mode: mode === "dev" ? "dev" : "strict",
5023
- ...(_c = settings.fallback) != null ? _c : {}
5024
- };
5025
- const builtAt = (/* @__PURE__ */ new Date()).toISOString();
5026
- const tagId = selection.activeTagId;
5027
- const selectedButtonKeys = (_d = selection.selectedKeys) != null ? _d : toSelectedOptionKeys(selection.optionSelectionsByFieldId);
5028
- const visibleFieldIds = builder.visibleFields(
5029
- tagId,
5030
- selectedButtonKeys
5031
- );
5032
- const tagById = new Map(
5033
- ((_e = props.filters) != null ? _e : []).map((t) => [t.id, t])
5034
- );
5035
- const fieldById = new Map(
5036
- ((_f = props.fields) != null ? _f : []).map((f) => [f.id, f])
5037
- );
5038
- const tagConstraints = (_h = (_g = tagById.get(tagId)) == null ? void 0 : _g.constraints) != null ? _h : void 0;
5039
- const selectionFields = visibleFieldIds.map((fid) => fieldById.get(fid)).filter((f) => !!f).map((f) => {
5040
- var _a3;
5041
- const optIds = isOptionBased(f) ? (_a3 = selection.optionSelectionsByFieldId[f.id]) != null ? _a3 : [] : void 0;
5011
+ // src/utils/build-order-snapshot/fallbacks.ts
5012
+ function pruneFallbacksConservative(fallbacks, env, svcMap, policy) {
5013
+ var _a2, _b;
5014
+ if (!fallbacks) return { pruned: void 0, original: void 0 };
5015
+ try {
5016
+ const { props: prunedProps } = pruneInvalidNodeFallbacks(
5017
+ {
5018
+ filters: [],
5019
+ fields: [],
5020
+ schema_version: "1.0",
5021
+ fallbacks
5022
+ },
5023
+ svcMap,
5024
+ policy
5025
+ );
5042
5026
  return {
5043
- id: f.id,
5044
- type: String(f.type),
5045
- ...optIds && optIds.length ? { selectedOptions: optIds } : {}
5027
+ pruned: prunedProps.fallbacks,
5028
+ original: fallbacks
5046
5029
  };
5047
- });
5048
- const { formValues, selections } = buildInputs(
5049
- visibleFieldIds,
5050
- fieldById,
5051
- selection
5052
- );
5053
- const qtyRes = resolveQuantity(
5054
- visibleFieldIds,
5055
- fieldById,
5056
- tagById,
5057
- selection,
5058
- tagId,
5059
- hostDefaultQty
5060
- );
5061
- const quantity = qtyRes.quantity;
5062
- const quantitySource = qtyRes.source;
5063
- const { serviceMap, servicesList } = resolveServices(
5064
- tagId,
5065
- visibleFieldIds,
5066
- selection,
5067
- tagById,
5068
- fieldById
5069
- );
5070
- const { min, max } = resolveMinMax(servicesList, services);
5071
- const maybeNodeMap = typeof builder.getNodeMap === "function" ? builder.getNodeMap() : void 0;
5072
- const resolvedOrderKind = resolveOrderKind({
5073
- props,
5074
- activeTagId: tagId,
5075
- selectedTriggerKeys: selectedButtonKeys,
5076
- nodeMap: maybeNodeMap
5077
- });
5078
- const prunedFallbacks = pruneFallbacksConservative(
5079
- props.fallbacks,
5080
- { tagId, constraints: tagConstraints, serviceMap, servicesList },
5081
- services,
5082
- fbSettings
5083
- );
5084
- const utilities = collectUtilityLineItems(
5085
- visibleFieldIds,
5086
- fieldById,
5087
- selection,
5088
- quantity
5089
- );
5090
- const warnings = mode === "dev" ? buildDevWarnings(
5091
- props,
5092
- services,
5093
- tagId,
5094
- serviceMap,
5095
- prunedFallbacks.original,
5096
- prunedFallbacks.pruned,
5097
- fieldById,
5098
- visibleFieldIds,
5099
- selection
5100
- ) : void 0;
5101
- const snapshotPolicy = toSnapshotPolicy(fbSettings);
5102
- const meta = {
5103
- schema_version: props.schema_version,
5104
- workspaceId: settings.workspaceId,
5105
- builder: settings.builderCommit ? { commit: settings.builderCommit } : void 0,
5106
- context: {
5107
- tag: tagId,
5108
- constraints: tagConstraints != null ? tagConstraints : {},
5109
- nodeContexts: buildNodeContexts(
5110
- tagId,
5111
- visibleFieldIds,
5112
- fieldById,
5113
- selection
5114
- ),
5115
- policy: snapshotPolicy
5030
+ } catch {
5031
+ const out = {};
5032
+ const requireFit = (_a2 = policy.requireConstraintFit) != null ? _a2 : true;
5033
+ if (fallbacks.nodes) {
5034
+ const keptNodes = {};
5035
+ for (const [nodeId, candidates] of Object.entries(
5036
+ fallbacks.nodes
5037
+ )) {
5038
+ if (!env.serviceMap[nodeId]) continue;
5039
+ const primary = ((_b = env.serviceMap[nodeId]) != null ? _b : [])[0];
5040
+ const kept = [];
5041
+ for (const cand of candidates != null ? candidates : []) {
5042
+ if (!rateOk(svcMap, cand, primary, policy)) continue;
5043
+ if (requireFit && env.constraints && !constraintFitOk(svcMap, cand, env.constraints)) continue;
5044
+ kept.push(cand);
5045
+ }
5046
+ if (kept.length) keptNodes[nodeId] = kept;
5047
+ }
5048
+ if (Object.keys(keptNodes).length) out.nodes = keptNodes;
5116
5049
  }
5117
- };
5118
- const snapshot = {
5119
- version: "1",
5120
- mode,
5121
- builtAt,
5122
- selection: {
5123
- tag: tagId,
5124
- buttons: selectedButtonKeys,
5125
- fields: selectionFields
5126
- },
5127
- inputs: {
5128
- form: formValues,
5129
- selections
5130
- },
5131
- min,
5132
- max: max != null ? max : min,
5133
- orderKind: resolvedOrderKind.kind,
5134
- orderKindSource: resolvedOrderKind.source,
5135
- quantity,
5136
- quantitySource,
5137
- services: servicesList,
5138
- serviceMap,
5139
- ...prunedFallbacks.pruned ? { fallbacks: prunedFallbacks.pruned } : {},
5140
- ...utilities.length ? { utilities } : {},
5141
- ...warnings ? { warnings } : {},
5142
- meta
5143
- };
5144
- return snapshot;
5050
+ if (fallbacks.global) {
5051
+ const keptGlobal = {};
5052
+ const present = new Set(env.servicesList.map((sid) => String(sid)));
5053
+ for (const [primary, cands] of Object.entries(
5054
+ fallbacks.global
5055
+ )) {
5056
+ if (!present.has(String(primary))) continue;
5057
+ const primId = isFiniteNumber2(primary) ? Number(primary) : primary;
5058
+ const kept = [];
5059
+ for (const cand of cands != null ? cands : []) {
5060
+ if (!rateOk(svcMap, cand, primId, policy)) continue;
5061
+ if (requireFit && env.constraints && !constraintFitOk(svcMap, cand, env.constraints)) continue;
5062
+ kept.push(cand);
5063
+ }
5064
+ if (kept.length) keptGlobal[primId] = kept;
5065
+ }
5066
+ if (Object.keys(keptGlobal).length) out.global = keptGlobal;
5067
+ }
5068
+ return {
5069
+ pruned: Object.keys(out).length ? out : void 0,
5070
+ original: fallbacks
5071
+ };
5072
+ }
5145
5073
  }
5074
+ function isFiniteNumber2(v) {
5075
+ return typeof v === "number" && Number.isFinite(v);
5076
+ }
5077
+
5078
+ // src/utils/build-order-snapshot/selection.ts
5146
5079
  function isOptionBased(f) {
5147
5080
  const hasOptions = Array.isArray(f.options) && f.options.length > 0;
5148
5081
  return hasOptions || isMultiField(f);
@@ -5150,50 +5083,227 @@ function isOptionBased(f) {
5150
5083
  function toSelectedOptionKeys(byField) {
5151
5084
  const keys = [];
5152
5085
  for (const [fieldId, optionIds] of Object.entries(byField != null ? byField : {})) {
5153
- for (const optId of optionIds != null ? optionIds : []) {
5154
- keys.push(`${fieldId}::${optId}`);
5086
+ for (const optionId of optionIds != null ? optionIds : []) {
5087
+ keys.push(`${fieldId}::${optionId}`);
5155
5088
  }
5156
5089
  }
5157
5090
  return keys;
5158
5091
  }
5159
- function isServicedBased(field) {
5160
- if (field.service_id) return true;
5161
- return !!(field.options && field.options.some((item) => item.service_id));
5092
+ function getSelectedOptionsByFieldId(selection, fieldById) {
5093
+ const out = {};
5094
+ for (const visit of buildSelectedNodeVisitOrder(selection, fieldById)) {
5095
+ if (visit.kind !== "option") continue;
5096
+ if (!out[visit.fieldId]) out[visit.fieldId] = [];
5097
+ out[visit.fieldId].push(visit.optionId);
5098
+ }
5099
+ return out;
5100
+ }
5101
+ function buildSelectedNodeVisitOrder(selection, fieldById) {
5102
+ var _a2, _b, _c;
5103
+ const out = [];
5104
+ const seen = /* @__PURE__ */ new Set();
5105
+ function pushField(fieldId) {
5106
+ const key = `field:${fieldId}`;
5107
+ if (seen.has(key)) return;
5108
+ seen.add(key);
5109
+ out.push({ kind: "field", fieldId });
5110
+ }
5111
+ function pushOption(fieldId, optionId) {
5112
+ const key = `option:${fieldId}::${optionId}`;
5113
+ if (seen.has(key)) return;
5114
+ seen.add(key);
5115
+ out.push({ kind: "option", fieldId, optionId });
5116
+ }
5117
+ for (const item of (_a2 = selection.optionTraversalOrder) != null ? _a2 : []) {
5118
+ pushOption(item.fieldId, item.optionId);
5119
+ }
5120
+ for (const rawKey of (_b = selection.selectedKeys) != null ? _b : []) {
5121
+ const key = String(rawKey);
5122
+ if (key.includes("::")) {
5123
+ const [fieldId, optionId] = key.split("::", 2);
5124
+ if (fieldId && optionId) pushOption(fieldId, optionId);
5125
+ continue;
5126
+ }
5127
+ const field = fieldById.get(key);
5128
+ if (field) {
5129
+ pushField(field.id);
5130
+ continue;
5131
+ }
5132
+ const ownerField = findOptionOwnerField(key, fieldById);
5133
+ if (ownerField) pushOption(ownerField.id, key);
5134
+ }
5135
+ for (const [fieldId, optionIds] of Object.entries(
5136
+ (_c = selection.optionSelectionsByFieldId) != null ? _c : {}
5137
+ )) {
5138
+ if (!fieldById.has(fieldId)) continue;
5139
+ for (const optionId of optionIds != null ? optionIds : []) {
5140
+ pushOption(fieldId, optionId);
5141
+ }
5142
+ }
5143
+ return out;
5144
+ }
5145
+ function findOptionOwnerField(optionId, fieldById) {
5146
+ var _a2;
5147
+ for (const field of fieldById.values()) {
5148
+ if ((_a2 = field.options) == null ? void 0 : _a2.some((option) => option.id === optionId)) return field;
5149
+ }
5150
+ return void 0;
5162
5151
  }
5163
- function buildInputs(visibleFieldIds, fieldById, selection) {
5152
+
5153
+ // src/utils/build-order-snapshot/services.ts
5154
+ function isServiceBased(field) {
5155
+ var _a2;
5156
+ if (field.service_id !== void 0 && field.service_id !== null) return true;
5157
+ return !!((_a2 = field.options) == null ? void 0 : _a2.some(
5158
+ (item) => item.service_id !== void 0 && item.service_id !== null
5159
+ ));
5160
+ }
5161
+ function resolveServices(tagId, visibleFieldIds, selection, tagById, fieldById, services) {
5162
+ var _a2, _b, _c, _d;
5163
+ const serviceMap = {};
5164
+ const visible = new Set(visibleFieldIds);
5165
+ const selectedBaseServices = [];
5166
+ const visits = buildSelectedNodeVisitOrder(selection, fieldById);
5167
+ let index = 0;
5168
+ function addSelectedBaseService(origin, sid) {
5169
+ pushService(serviceMap, origin, sid);
5170
+ selectedBaseServices.push({
5171
+ origin,
5172
+ sid,
5173
+ rate: readServiceRate(services, sid),
5174
+ index: index++
5175
+ });
5176
+ }
5177
+ for (const visit of visits) {
5178
+ if (!visible.has(visit.fieldId)) continue;
5179
+ const field = fieldById.get(visit.fieldId);
5180
+ if (!field) continue;
5181
+ if (visit.kind === "field") {
5182
+ const role2 = (_a2 = field.pricing_role) != null ? _a2 : "base";
5183
+ if (role2 === "utility") continue;
5184
+ if (field.service_id !== void 0 && field.service_id !== null) {
5185
+ addSelectedBaseService(field.id, field.service_id);
5186
+ }
5187
+ continue;
5188
+ }
5189
+ const option = (_b = field.options) == null ? void 0 : _b.find((item) => item.id === visit.optionId);
5190
+ if (!option) continue;
5191
+ const role = (_d = (_c = option.pricing_role) != null ? _c : field.pricing_role) != null ? _d : "base";
5192
+ if (role === "utility") continue;
5193
+ if (option.service_id !== void 0 && option.service_id !== null) {
5194
+ addSelectedBaseService(option.id, option.service_id);
5195
+ }
5196
+ }
5197
+ if (selectedBaseServices.length > 0) {
5198
+ const primary = pickHighestRatePrimary2(selectedBaseServices);
5199
+ const ordered = [
5200
+ primary.sid,
5201
+ ...selectedBaseServices.filter((item) => item !== primary).sort((a, b) => a.index - b.index).map((item) => item.sid)
5202
+ ];
5203
+ return { serviceMap, servicesList: dedupeByString(ordered) };
5204
+ }
5205
+ const tag = tagById.get(tagId);
5206
+ if ((tag == null ? void 0 : tag.service_id) !== void 0 && tag.service_id !== null) {
5207
+ pushService(serviceMap, tagId, tag.service_id);
5208
+ return { serviceMap, servicesList: [tag.service_id] };
5209
+ }
5210
+ return { serviceMap, servicesList: [] };
5211
+ }
5212
+ function pickHighestRatePrimary2(services) {
5213
+ let best = services[0];
5214
+ for (const item of services.slice(1)) {
5215
+ if (item.rate > best.rate) best = item;
5216
+ }
5217
+ return best;
5218
+ }
5219
+ function getCap2(map, id) {
5220
+ var _a2;
5221
+ return (_a2 = map == null ? void 0 : map[id]) != null ? _a2 : map == null ? void 0 : map[String(id)];
5222
+ }
5223
+ function readServiceRate(services, sid) {
5224
+ var _a2;
5225
+ const rate = Number((_a2 = getCap2(services, sid)) == null ? void 0 : _a2.rate);
5226
+ return Number.isFinite(rate) ? rate : Number.NEGATIVE_INFINITY;
5227
+ }
5228
+ function pushService(map, nodeId, sid) {
5229
+ if (!map[nodeId]) map[nodeId] = [];
5230
+ map[nodeId].push(sid);
5231
+ }
5232
+ function dedupeByString(arr) {
5233
+ const seen = /* @__PURE__ */ new Set();
5234
+ const out = [];
5235
+ for (const value of arr) {
5236
+ const key = String(value);
5237
+ if (seen.has(key)) continue;
5238
+ seen.add(key);
5239
+ out.push(value);
5240
+ }
5241
+ return out;
5242
+ }
5243
+
5244
+ // src/utils/build-order-snapshot/inputs.ts
5245
+ function buildInputs(visibleFieldIds, fieldById, selection, selectedOptionsByFieldId) {
5164
5246
  const formValues = {};
5165
5247
  const selections = {};
5166
5248
  for (const fid of visibleFieldIds) {
5167
- const f = fieldById.get(fid);
5168
- if (!f) continue;
5169
- const selOptIds = selection.optionSelectionsByFieldId[fid];
5170
- if (selOptIds && selOptIds.length) {
5171
- selections[fid] = [...selOptIds];
5172
- }
5173
- if (!isServicedBased(f)) {
5174
- const name = f.name;
5175
- const val = selection.formValuesByFieldId[fid];
5176
- if (!name || val === void 0) continue;
5177
- formValues[name] = val;
5249
+ const field = fieldById.get(fid);
5250
+ if (!field) continue;
5251
+ const selectedOptionIds = selectedOptionsByFieldId[fid];
5252
+ if (selectedOptionIds == null ? void 0 : selectedOptionIds.length) selections[fid] = [...selectedOptionIds];
5253
+ if (!isServiceBased(field)) {
5254
+ const name = field.name;
5255
+ const value = selection.formValuesByFieldId[fid];
5256
+ if (!name || value === void 0) continue;
5257
+ formValues[name] = value;
5178
5258
  }
5179
5259
  }
5180
5260
  return { formValues, selections };
5181
5261
  }
5262
+
5263
+ // src/utils/build-order-snapshot/min-max.ts
5264
+ function getCap3(map, id) {
5265
+ var _a2;
5266
+ return (_a2 = map == null ? void 0 : map[id]) != null ? _a2 : map == null ? void 0 : map[String(id)];
5267
+ }
5268
+ function resolveMinMax(servicesList, services) {
5269
+ let min;
5270
+ let max;
5271
+ for (const sid of servicesList) {
5272
+ const cap = getCap3(services, sid);
5273
+ if (!cap) continue;
5274
+ if (typeof cap.min === "number" && Number.isFinite(cap.min)) {
5275
+ min = min === void 0 ? cap.min : Math.min(min, cap.min);
5276
+ }
5277
+ if (typeof cap.max === "number" && Number.isFinite(cap.max)) {
5278
+ max = max === void 0 ? cap.max : Math.max(max, cap.max);
5279
+ }
5280
+ }
5281
+ return { min: min != null ? min : 1, ...max !== void 0 ? { max } : {} };
5282
+ }
5283
+
5284
+ // src/utils/build-order-snapshot/policy.ts
5285
+ function toSnapshotPolicy(settings) {
5286
+ var _a2;
5287
+ return {
5288
+ ratePolicy: normalizeRatePolicy(settings.ratePolicy),
5289
+ requireConstraintFit: (_a2 = settings.requireConstraintFit) != null ? _a2 : true
5290
+ };
5291
+ }
5292
+
5293
+ // src/utils/build-order-snapshot/quantity.ts
5182
5294
  function resolveQuantity(visibleFieldIds, fieldById, tagById, selection, tagId, hostDefault) {
5183
5295
  var _a2;
5184
5296
  for (const fid of visibleFieldIds) {
5185
- const f = fieldById.get(fid);
5186
- if (!f) continue;
5187
- const rule = readQuantityRule(
5188
- (_a2 = f.meta) == null ? void 0 : _a2.quantity
5189
- );
5297
+ const field = fieldById.get(fid);
5298
+ if (!field) continue;
5299
+ const rule = readQuantityRule((_a2 = field.meta) == null ? void 0 : _a2.quantity);
5190
5300
  if (!rule) continue;
5191
5301
  const raw = selection.formValuesByFieldId[fid];
5192
5302
  const evaluated = evaluateQuantityRule(rule, raw);
5193
5303
  if (Number.isFinite(evaluated) && evaluated > 0) {
5194
5304
  return {
5195
5305
  quantity: evaluated,
5196
- source: { kind: "field", id: f.id, rule }
5306
+ source: { kind: "field", id: field.id, rule }
5197
5307
  };
5198
5308
  }
5199
5309
  break;
@@ -5206,24 +5316,47 @@ function resolveQuantity(visibleFieldIds, fieldById, tagById, selection, tagId,
5206
5316
  tagId
5207
5317
  );
5208
5318
  if (nodeDefault) return nodeDefault;
5209
- return {
5210
- quantity: hostDefault,
5211
- source: { kind: "default", defaultedFromHost: true }
5212
- };
5319
+ return { quantity: hostDefault, source: { kind: "default", defaultedFromHost: true } };
5320
+ }
5321
+ function resolveNodeDefaultQuantity(visibleFieldIds, fieldById, tagById, selection, tagId) {
5322
+ var _a2, _b, _c;
5323
+ const visible = new Set(visibleFieldIds);
5324
+ const visits = buildSelectedNodeVisitOrder(selection, fieldById);
5325
+ for (const visit of visits) {
5326
+ if (visit.kind !== "option") continue;
5327
+ if (!visible.has(visit.fieldId)) continue;
5328
+ const field = fieldById.get(visit.fieldId);
5329
+ if (!((_a2 = field == null ? void 0 : field.options) == null ? void 0 : _a2.length)) continue;
5330
+ const option = field.options.find((item) => item.id === visit.optionId);
5331
+ const quantity = readPositiveFiniteNumber((_b = option == null ? void 0 : option.meta) == null ? void 0 : _b.quantityDefault);
5332
+ if (quantity !== void 0) {
5333
+ return { quantity, source: { kind: "option", id: option.id } };
5334
+ }
5335
+ }
5336
+ for (const visit of visits) {
5337
+ if (!visible.has(visit.fieldId)) continue;
5338
+ const field = fieldById.get(visit.fieldId);
5339
+ if (!field) continue;
5340
+ const quantity = readPositiveFiniteNumber(field.quantityDefault);
5341
+ if (quantity !== void 0) {
5342
+ return { quantity, source: { kind: "field", id: field.id } };
5343
+ }
5344
+ }
5345
+ const tag = tagById.get(tagId);
5346
+ const tagQuantity = readPositiveFiniteNumber((_c = tag == null ? void 0 : tag.meta) == null ? void 0 : _c.quantityDefault);
5347
+ if (tagQuantity !== void 0) {
5348
+ return { quantity: tagQuantity, source: { kind: "tag", id: tagId } };
5349
+ }
5350
+ return void 0;
5213
5351
  }
5214
5352
  function readQuantityRule(v) {
5215
5353
  if (!v || typeof v !== "object") return void 0;
5216
5354
  const src = v;
5217
- if (src.valueBy !== "value" && src.valueBy !== "length" && src.valueBy !== "eval")
5218
- return void 0;
5355
+ if (src.valueBy !== "value" && src.valueBy !== "length" && src.valueBy !== "eval") return void 0;
5219
5356
  const out = { valueBy: src.valueBy };
5220
5357
  if (src.code && typeof src.code === "string") out.code = src.code;
5221
- if (typeof src.multiply === "number" && Number.isFinite(src.multiply)) {
5222
- out.multiply = src.multiply;
5223
- }
5224
- if (typeof src.fallback === "number" && Number.isFinite(src.fallback)) {
5225
- out.fallback = src.fallback;
5226
- }
5358
+ if (typeof src.multiply === "number" && Number.isFinite(src.multiply)) out.multiply = src.multiply;
5359
+ if (typeof src.fallback === "number" && Number.isFinite(src.fallback)) out.fallback = src.fallback;
5227
5360
  if (src.clamp && typeof src.clamp === "object") {
5228
5361
  const min = typeof src.clamp.min === "number" && Number.isFinite(src.clamp.min) ? src.clamp.min : void 0;
5229
5362
  const max = typeof src.clamp.max === "number" && Number.isFinite(src.clamp.max) ? src.clamp.max : void 0;
@@ -5294,231 +5427,43 @@ function applyClamp(value, clamp2) {
5294
5427
  if ((clamp2 == null ? void 0 : clamp2.max) !== void 0) next = Math.min(next, clamp2.max);
5295
5428
  return next;
5296
5429
  }
5297
- function resolveNodeDefaultQuantity(visibleFieldIds, fieldById, tagById, selection, tagId) {
5298
- var _a2, _b, _c, _d;
5299
- const optionVisit = buildOptionVisitOrder(selection, fieldById);
5300
- for (const { fieldId, optionId } of optionVisit) {
5301
- if (!visibleFieldIds.includes(fieldId)) continue;
5302
- const field = fieldById.get(fieldId);
5303
- const option = (_a2 = field == null ? void 0 : field.options) == null ? void 0 : _a2.find((item) => item.id === optionId);
5304
- const quantityDefault = readQuantityDefault(
5305
- (_b = option == null ? void 0 : option.meta) == null ? void 0 : _b.quantityDefault
5306
- );
5307
- if (quantityDefault !== void 0) {
5308
- return {
5309
- quantity: quantityDefault,
5310
- source: { kind: "option", id: optionId }
5311
- };
5312
- }
5313
- }
5314
- for (const fieldId of visibleFieldIds) {
5315
- const field = fieldById.get(fieldId);
5316
- if (!field) continue;
5317
- const isButtonStyle = field.button === true || Array.isArray(field.options) && field.options.length > 0;
5318
- if (!isButtonStyle) continue;
5319
- const quantityDefault = readQuantityDefault(
5320
- field.quantityDefault
5321
- );
5322
- if (quantityDefault !== void 0) {
5323
- return {
5324
- quantity: quantityDefault,
5325
- source: { kind: "field", id: field.id }
5326
- };
5327
- }
5328
- }
5329
- const tagQuantityDefault = readQuantityDefault(
5330
- (_d = (_c = tagById.get(tagId)) == null ? void 0 : _c.meta) == null ? void 0 : _d.quantityDefault
5331
- );
5332
- if (tagQuantityDefault !== void 0) {
5333
- return {
5334
- quantity: tagQuantityDefault,
5335
- source: { kind: "tag", id: tagId }
5336
- };
5337
- }
5338
- return void 0;
5339
- }
5340
- function readQuantityDefault(value) {
5341
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
5342
- }
5343
- function resolveServices(tagId, visibleFieldIds, selection, tagById, fieldById) {
5344
- var _a2;
5345
- const serviceMap = {};
5346
- const ordered = [];
5347
- const tag = tagById.get(tagId);
5348
- let primary;
5349
- let primaryOrigin;
5350
- if ((tag == null ? void 0 : tag.service_id) !== void 0) {
5351
- primary = tag.service_id;
5352
- primaryOrigin = "tag";
5353
- }
5354
- const optionVisit = buildOptionVisitOrder(selection, fieldById);
5355
- for (const { fieldId, optionId } of optionVisit) {
5356
- if (!visibleFieldIds.includes(fieldId)) continue;
5357
- const f = fieldById.get(fieldId);
5358
- if (!f || !Array.isArray(f.options)) continue;
5359
- const opt = f.options.find((o) => o.id === optionId);
5360
- if (!opt) continue;
5361
- const role = (_a2 = opt.pricing_role) != null ? _a2 : "base";
5362
- const sid = opt.service_id;
5363
- if (role === "utility") continue;
5364
- if (sid !== void 0) {
5365
- if (primary === void 0 || primaryOrigin === "tag") {
5366
- primary = sid;
5367
- primaryOrigin = "option";
5368
- ordered.length = 0;
5369
- ordered.push(primary);
5370
- } else {
5371
- ordered.push(sid);
5372
- }
5373
- pushService(serviceMap, optionId, sid);
5374
- }
5375
- }
5376
- if (primaryOrigin !== "option" && primary !== void 0) {
5377
- ordered.unshift(primary);
5378
- pushService(serviceMap, tagId, primary);
5379
- } else {
5380
- }
5381
- const servicesList = dedupeByString(ordered);
5382
- return { serviceMap, servicesList };
5383
- }
5384
- function buildOptionVisitOrder(selection, fieldById) {
5385
- var _a2;
5386
- if (selection.optionTraversalOrder && selection.optionTraversalOrder.length) {
5387
- return selection.optionTraversalOrder.slice();
5388
- }
5389
- const out = [];
5390
- for (const [fid, optIds] of Object.entries(
5391
- (_a2 = selection.optionSelectionsByFieldId) != null ? _a2 : {}
5392
- )) {
5393
- const f = fieldById.get(fid);
5394
- if (!f) continue;
5395
- for (const oid of optIds != null ? optIds : [])
5396
- out.push({ fieldId: fid, optionId: oid });
5397
- }
5398
- return out;
5399
- }
5400
- function pushService(map, nodeId, sid) {
5401
- if (!map[nodeId]) map[nodeId] = [];
5402
- map[nodeId].push(sid);
5403
- }
5404
- function dedupeByString(arr) {
5405
- const s = /* @__PURE__ */ new Set();
5406
- const out = [];
5407
- for (const v of arr) {
5408
- const key = String(v);
5409
- if (s.has(key)) continue;
5410
- s.add(key);
5411
- out.push(v);
5412
- }
5413
- return out;
5414
- }
5415
- function pruneFallbacksConservative(fallbacks, env, svcMap, policy) {
5416
- var _a2, _b;
5417
- if (!fallbacks) return { pruned: void 0, original: void 0 };
5418
- try {
5419
- const { props: prunedProps } = pruneInvalidNodeFallbacks(
5420
- {
5421
- filters: [],
5422
- fields: [],
5423
- schema_version: "1.0",
5424
- fallbacks
5425
- },
5426
- svcMap,
5427
- policy
5428
- );
5429
- return {
5430
- pruned: prunedProps.fallbacks,
5431
- original: fallbacks
5432
- };
5433
- } catch {
5434
- const out = {};
5435
- const requireFit = (_a2 = policy.requireConstraintFit) != null ? _a2 : true;
5436
- if (fallbacks.nodes) {
5437
- const keptNodes = {};
5438
- for (const [nodeId, candidates] of Object.entries(
5439
- fallbacks.nodes
5440
- )) {
5441
- if (!env.serviceMap[nodeId]) continue;
5442
- const primary = ((_b = env.serviceMap[nodeId]) != null ? _b : [])[0];
5443
- const kept = [];
5444
- for (const cand of candidates != null ? candidates : []) {
5445
- if (!rateOk(svcMap, cand, primary, policy)) continue;
5446
- if (requireFit && env.constraints && !constraintFitOk(svcMap, cand, env.constraints))
5447
- continue;
5448
- kept.push(cand);
5449
- }
5450
- if (kept.length) keptNodes[nodeId] = kept;
5451
- }
5452
- if (Object.keys(keptNodes).length) out.nodes = keptNodes;
5453
- }
5454
- if (fallbacks.global) {
5455
- const keptGlobal = {};
5456
- const present = new Set(env.servicesList.map((sid) => String(sid)));
5457
- for (const [primary, cands] of Object.entries(
5458
- fallbacks.global
5459
- )) {
5460
- if (!present.has(String(primary))) continue;
5461
- const primId = isFiniteNumber2(primary) ? Number(primary) : primary;
5462
- const kept = [];
5463
- for (const cand of cands != null ? cands : []) {
5464
- if (!rateOk(svcMap, cand, primId, policy)) continue;
5465
- if (requireFit && env.constraints && !constraintFitOk(svcMap, cand, env.constraints))
5466
- continue;
5467
- kept.push(cand);
5468
- }
5469
- if (kept.length) keptGlobal[primId] = kept;
5470
- }
5471
- if (Object.keys(keptGlobal).length)
5472
- out.global = keptGlobal;
5473
- }
5474
- return {
5475
- pruned: Object.keys(out).length ? out : void 0,
5476
- original: fallbacks
5477
- };
5478
- }
5479
- }
5480
- function isFiniteNumber2(v) {
5481
- return typeof v === "number" && Number.isFinite(v);
5430
+ function readPositiveFiniteNumber(value) {
5431
+ const n = Number(value);
5432
+ return Number.isFinite(n) && n > 0 ? n : void 0;
5482
5433
  }
5483
- function collectUtilityLineItems(visibleFieldIds, fieldById, selection, quantity) {
5434
+
5435
+ // src/utils/build-order-snapshot/utilities.ts
5436
+ function collectUtilityLineItems(visibleFieldIds, fieldById, selection, selectedOptionsByFieldId, quantity) {
5484
5437
  var _a2, _b, _c, _d, _e;
5485
5438
  const items = [];
5486
5439
  for (const fid of visibleFieldIds) {
5487
- const f = fieldById.get(fid);
5488
- if (!f) continue;
5489
- const isUtilityField = ((_a2 = f.pricing_role) != null ? _a2 : "base") === "utility";
5490
- const marker = readUtilityMarker((_b = f.meta) == null ? void 0 : _b.utility);
5440
+ const field = fieldById.get(fid);
5441
+ if (!field) continue;
5442
+ const isUtilityField = ((_a2 = field.pricing_role) != null ? _a2 : "base") === "utility";
5443
+ const marker = readUtilityMarker((_b = field.meta) == null ? void 0 : _b.utility);
5491
5444
  if (isUtilityField && marker) {
5492
- const val = selection.formValuesByFieldId[f.id];
5493
- const item = buildUtilityItemFromMarker(
5494
- f.id,
5495
- marker,
5496
- quantity,
5497
- val
5498
- );
5445
+ const value = selection.formValuesByFieldId[field.id];
5446
+ const item = buildUtilityItemFromMarker(field.id, marker, quantity, value);
5499
5447
  if (item) items.push(item);
5500
5448
  }
5501
- if (Array.isArray(f.options) && f.options.length) {
5502
- const selectedOptIds = (_c = selection.optionSelectionsByFieldId[f.id]) != null ? _c : [];
5503
- if (selectedOptIds.length) {
5504
- const optById = new Map(
5505
- f.options.map((o) => [o.id, o])
5449
+ if (Array.isArray(field.options) && field.options.length) {
5450
+ const selectedOptionIds = (_c = selectedOptionsByFieldId[field.id]) != null ? _c : [];
5451
+ if (!selectedOptionIds.length) continue;
5452
+ const optById = new Map(field.options.map((o) => [o.id, o]));
5453
+ for (const oid of selectedOptionIds) {
5454
+ const option = optById.get(oid);
5455
+ if (!option) continue;
5456
+ if (((_d = option.pricing_role) != null ? _d : "base") !== "utility") continue;
5457
+ const optionMarker = readUtilityMarker((_e = option.meta) == null ? void 0 : _e.utility);
5458
+ if (!optionMarker) continue;
5459
+ const parentValue = selection.formValuesByFieldId[field.id];
5460
+ const item = buildUtilityItemFromMarker(
5461
+ option.id,
5462
+ optionMarker,
5463
+ quantity,
5464
+ parentValue
5506
5465
  );
5507
- for (const oid of selectedOptIds) {
5508
- const opt = optById.get(oid);
5509
- if (!opt) continue;
5510
- if (((_d = opt.pricing_role) != null ? _d : "base") !== "utility") continue;
5511
- const om = readUtilityMarker((_e = opt.meta) == null ? void 0 : _e.utility);
5512
- if (!om) continue;
5513
- const parentVal = selection.formValuesByFieldId[f.id];
5514
- const item = buildUtilityItemFromMarker(
5515
- opt.id,
5516
- om,
5517
- quantity,
5518
- parentVal
5519
- );
5520
- if (item) items.push(item);
5521
- }
5466
+ if (item) items.push(item);
5522
5467
  }
5523
5468
  }
5524
5469
  }
@@ -5527,19 +5472,16 @@ function collectUtilityLineItems(visibleFieldIds, fieldById, selection, quantity
5527
5472
  function readUtilityMarker(v) {
5528
5473
  if (!v || typeof v !== "object") return void 0;
5529
5474
  const src = v;
5530
- if (!src.mode || typeof src.rate !== "number" || !Number.isFinite(src.rate))
5531
- return void 0;
5532
- if (src.mode !== "flat" && src.mode !== "per_quantity" && src.mode !== "per_value" && src.mode !== "percent")
5475
+ if (!src.mode || typeof src.rate !== "number" || !Number.isFinite(src.rate)) return void 0;
5476
+ if (src.mode !== "flat" && src.mode !== "per_quantity" && src.mode !== "per_value" && src.mode !== "percent") {
5533
5477
  return void 0;
5478
+ }
5534
5479
  const out = { mode: src.mode, rate: src.rate };
5535
- if (src.valueBy === "value" || src.valueBy === "length")
5536
- out.valueBy = src.valueBy;
5480
+ if (src.valueBy === "value" || src.valueBy === "length") out.valueBy = src.valueBy;
5537
5481
  if (src.percentBase === "service_total" || src.percentBase === "base_service" || src.percentBase === "all") {
5538
5482
  out.percentBase = src.percentBase;
5539
5483
  }
5540
- if (typeof src.label === "string" && src.label.trim()) {
5541
- out.label = src.label.trim();
5542
- }
5484
+ if (typeof src.label === "string" && src.label.trim()) out.label = src.label.trim();
5543
5485
  return out;
5544
5486
  }
5545
5487
  function buildUtilityItemFromMarker(nodeId, marker, quantity, value) {
@@ -5562,17 +5504,19 @@ function buildUtilityItemFromMarker(nodeId, marker, quantity, value) {
5562
5504
  }
5563
5505
  return base;
5564
5506
  }
5565
- function buildNodeContexts(tagId, visibleFieldIds, fieldById, selection) {
5507
+
5508
+ // src/utils/build-order-snapshot/context.ts
5509
+ function buildNodeContexts(tagId, visibleFieldIds, fieldById, _selection, selectedOptionsByFieldId) {
5566
5510
  var _a2;
5567
5511
  const ctx = {};
5568
5512
  ctx[tagId] = tagId;
5569
5513
  for (const fid of visibleFieldIds) {
5570
- const f = fieldById.get(fid);
5571
- if (!f) continue;
5572
- const binds = normalizeBindIds(f.bind_id);
5514
+ const field = fieldById.get(fid);
5515
+ if (!field) continue;
5516
+ const binds = normalizeBindIds(field.bind_id);
5573
5517
  const applicable = binds.has(tagId);
5574
- const selectedOptIds = (_a2 = selection.optionSelectionsByFieldId[fid]) != null ? _a2 : [];
5575
- for (const oid of selectedOptIds) {
5518
+ const selectedOptionIds = (_a2 = selectedOptionsByFieldId[fid]) != null ? _a2 : [];
5519
+ for (const oid of selectedOptionIds) {
5576
5520
  ctx[oid] = applicable ? tagId : null;
5577
5521
  }
5578
5522
  }
@@ -5588,66 +5532,159 @@ function normalizeBindIds(bind) {
5588
5532
  }
5589
5533
  return out;
5590
5534
  }
5591
- function buildDevWarnings(props, svcMap, _tagId, _snapshotServiceMap, originalFallbacks, _prunedFallbacks, fieldById, visibleFieldIds, selection) {
5535
+
5536
+ // src/utils/build-order-snapshot/warnings.ts
5537
+ function buildDevWarnings(props, svcMap, originalFallbacks, fieldById, visibleFieldIds, selection) {
5592
5538
  const out = {};
5593
5539
  const maybeCollectFailed = globalThis.collectFailedFallbacks;
5594
5540
  try {
5595
5541
  if (maybeCollectFailed && originalFallbacks) {
5596
5542
  const diags = maybeCollectFailed(
5597
- {
5598
- ...props,
5599
- fallbacks: originalFallbacks
5600
- },
5543
+ { ...props, fallbacks: originalFallbacks },
5601
5544
  svcMap,
5602
5545
  { mode: "dev" }
5603
5546
  );
5604
- if (diags && diags.length) {
5605
- out.fallbacks = diags;
5606
- }
5547
+ if (diags == null ? void 0 : diags.length) out.fallbacks = diags;
5607
5548
  }
5608
5549
  } catch {
5609
5550
  }
5610
5551
  const utilityWarnings = [];
5611
5552
  for (const fid of visibleFieldIds) {
5612
- const f = fieldById.get(fid);
5613
- if (!f) continue;
5614
- const hasVal = selection.formValuesByFieldId[fid] !== void 0;
5615
- if (hasVal && !f.name && !isOptionBased(f)) {
5553
+ const field = fieldById.get(fid);
5554
+ if (!field) continue;
5555
+ const hasValue = selection.formValuesByFieldId[fid] !== void 0;
5556
+ if (hasValue && !field.name && !isOptionBased(field)) {
5616
5557
  utilityWarnings.push({
5617
5558
  nodeId: fid,
5618
5559
  reason: "missing_field_name_for_form_value"
5619
5560
  });
5620
5561
  }
5621
5562
  }
5622
- if (utilityWarnings.length) {
5623
- out.utility = utilityWarnings;
5624
- }
5563
+ if (utilityWarnings.length) out.utility = utilityWarnings;
5625
5564
  if (!out.fallbacks && !out.utility) return void 0;
5626
5565
  return out;
5627
5566
  }
5628
- function toSnapshotPolicy(settings) {
5629
- var _a2;
5630
- const requireConstraintFit = (_a2 = settings.requireConstraintFit) != null ? _a2 : true;
5631
- const rp = normalizeRatePolicy(settings.ratePolicy);
5632
- return { ratePolicy: rp, requireConstraintFit };
5633
- }
5634
- function getCap2(map, id) {
5635
- return getServiceCapability(map, id);
5636
- }
5637
- function resolveMinMax(servicesList, services) {
5638
- let min = void 0;
5639
- let max = void 0;
5640
- for (const sid of servicesList) {
5641
- const cap = getCap2(services, sid);
5642
- if (!cap) continue;
5643
- if (typeof cap.min === "number" && Number.isFinite(cap.min)) {
5644
- min = min === void 0 ? cap.min : Math.min(min, cap.min);
5645
- }
5646
- if (typeof cap.max === "number" && Number.isFinite(cap.max)) {
5647
- max = max === void 0 ? cap.max : Math.max(max, cap.max);
5567
+
5568
+ // src/utils/build-order-snapshot/index.ts
5569
+ function buildOrderSnapshot(props, builder, selection, services, settings = {}) {
5570
+ var _a2, _b, _c, _d, _e, _f, _g, _h;
5571
+ const mode = (_a2 = settings.mode) != null ? _a2 : "prod";
5572
+ const hostDefaultQty = Number.isFinite((_b = settings.hostDefaultQuantity) != null ? _b : 1) ? settings.hostDefaultQuantity : 1;
5573
+ const fbSettings = {
5574
+ requireConstraintFit: true,
5575
+ ratePolicy: { kind: "lte_primary", pct: 5 },
5576
+ selectionStrategy: "priority",
5577
+ mode: mode === "dev" ? "dev" : "strict",
5578
+ ...(_c = settings.fallback) != null ? _c : {}
5579
+ };
5580
+ const builtAt = (/* @__PURE__ */ new Date()).toISOString();
5581
+ const tagId = selection.activeTagId;
5582
+ const selectedButtonKeys = (_d = selection.selectedKeys) != null ? _d : toSelectedOptionKeys(selection.optionSelectionsByFieldId);
5583
+ const visibleFieldIds = builder.visibleFields(tagId, selectedButtonKeys);
5584
+ const tagById = new Map(((_e = props.filters) != null ? _e : []).map((t) => [t.id, t]));
5585
+ const fieldById = new Map(((_f = props.fields) != null ? _f : []).map((f) => [f.id, f]));
5586
+ const tagConstraints = (_h = (_g = tagById.get(tagId)) == null ? void 0 : _g.constraints) != null ? _h : void 0;
5587
+ const selectedOptionsByFieldId = getSelectedOptionsByFieldId(selection, fieldById);
5588
+ const selectionFields = visibleFieldIds.map((fid) => fieldById.get(fid)).filter((f) => !!f).map((f) => {
5589
+ var _a3;
5590
+ const optionIds = isOptionBased(f) ? (_a3 = selectedOptionsByFieldId[f.id]) != null ? _a3 : [] : void 0;
5591
+ return {
5592
+ id: f.id,
5593
+ type: String(f.type),
5594
+ ...optionIds && optionIds.length ? { selectedOptions: optionIds } : {}
5595
+ };
5596
+ });
5597
+ const { formValues, selections } = buildInputs(
5598
+ visibleFieldIds,
5599
+ fieldById,
5600
+ selection,
5601
+ selectedOptionsByFieldId
5602
+ );
5603
+ const qtyRes = resolveQuantity(
5604
+ visibleFieldIds,
5605
+ fieldById,
5606
+ tagById,
5607
+ selection,
5608
+ tagId,
5609
+ hostDefaultQty
5610
+ );
5611
+ const { serviceMap, servicesList } = resolveServices(
5612
+ tagId,
5613
+ visibleFieldIds,
5614
+ selection,
5615
+ tagById,
5616
+ fieldById,
5617
+ services
5618
+ );
5619
+ const { min, max } = resolveMinMax(servicesList, services);
5620
+ const maybeNodeMap = typeof builder.getNodeMap === "function" ? builder.getNodeMap() : void 0;
5621
+ const resolvedOrderKind = resolveOrderKind({
5622
+ props,
5623
+ activeTagId: tagId,
5624
+ selectedTriggerKeys: selectedButtonKeys,
5625
+ nodeMap: maybeNodeMap
5626
+ });
5627
+ const prunedFallbacks = pruneFallbacksConservative(
5628
+ props.fallbacks,
5629
+ { tagId, constraints: tagConstraints, serviceMap, servicesList },
5630
+ services,
5631
+ fbSettings
5632
+ );
5633
+ const utilities = collectUtilityLineItems(
5634
+ visibleFieldIds,
5635
+ fieldById,
5636
+ selection,
5637
+ selectedOptionsByFieldId,
5638
+ qtyRes.quantity
5639
+ );
5640
+ const warnings = mode === "dev" ? buildDevWarnings(
5641
+ props,
5642
+ services,
5643
+ prunedFallbacks.original,
5644
+ fieldById,
5645
+ visibleFieldIds,
5646
+ selection
5647
+ ) : void 0;
5648
+ const meta = {
5649
+ schema_version: props.schema_version,
5650
+ workspaceId: settings.workspaceId,
5651
+ builder: settings.builderCommit ? { commit: settings.builderCommit } : void 0,
5652
+ context: {
5653
+ tag: tagId,
5654
+ constraints: tagConstraints != null ? tagConstraints : {},
5655
+ nodeContexts: buildNodeContexts(
5656
+ tagId,
5657
+ visibleFieldIds,
5658
+ fieldById,
5659
+ selection,
5660
+ selectedOptionsByFieldId
5661
+ ),
5662
+ policy: toSnapshotPolicy(fbSettings)
5648
5663
  }
5649
- }
5650
- return { min: min != null ? min : 1, ...max !== void 0 ? { max } : {} };
5664
+ };
5665
+ return {
5666
+ version: "1",
5667
+ mode,
5668
+ builtAt,
5669
+ selection: {
5670
+ tag: tagId,
5671
+ buttons: selectedButtonKeys,
5672
+ fields: selectionFields
5673
+ },
5674
+ inputs: { form: formValues, selections },
5675
+ min,
5676
+ max: max != null ? max : min,
5677
+ orderKind: resolvedOrderKind.kind,
5678
+ orderKindSource: resolvedOrderKind.source,
5679
+ quantity: qtyRes.quantity,
5680
+ quantitySource: qtyRes.source,
5681
+ services: servicesList,
5682
+ serviceMap,
5683
+ ...prunedFallbacks.pruned ? { fallbacks: prunedFallbacks.pruned } : {},
5684
+ ...utilities.length ? { utilities } : {},
5685
+ ...warnings ? { warnings } : {},
5686
+ meta
5687
+ };
5651
5688
  }
5652
5689
 
5653
5690
  // src/core/fallback-editor.ts