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