@timeax/digital-service-engine 0.0.3 → 0.0.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.
@@ -23,8 +23,10 @@ __export(core_exports, {
23
23
  buildOrderSnapshot: () => buildOrderSnapshot,
24
24
  collectFailedFallbacks: () => collectFailedFallbacks,
25
25
  createBuilder: () => createBuilder,
26
+ createFallbackEditor: () => createFallbackEditor,
26
27
  createNodeIndex: () => createNodeIndex,
27
28
  getEligibleFallbacks: () => getEligibleFallbacks,
29
+ getFallbackRegistrationInfo: () => getFallbackRegistrationInfo,
28
30
  normalise: () => normalise,
29
31
  resolveServiceFallback: () => resolveServiceFallback,
30
32
  validate: () => validate,
@@ -2591,6 +2593,10 @@ function getEligibleFallbacks(params) {
2591
2593
  }
2592
2594
  return eligible;
2593
2595
  }
2596
+ function getFallbackRegistrationInfo(props, nodeId) {
2597
+ const { primary, tagContexts } = primaryForNode(props, nodeId);
2598
+ return { primary, tagContexts };
2599
+ }
2594
2600
 
2595
2601
  // src/core/rate-coherence.ts
2596
2602
  function validateRateCoherenceDeep(params) {
@@ -3821,13 +3827,356 @@ function resolveMinMax(servicesList, services) {
3821
3827
  }
3822
3828
  return { min: min != null ? min : 1, ...max !== void 0 ? { max } : {} };
3823
3829
  }
3830
+
3831
+ // src/core/fallback-editor.ts
3832
+ function createFallbackEditor(options = {}) {
3833
+ var _a, _b;
3834
+ const original = cloneFallbacks(options.fallbacks);
3835
+ let current = cloneFallbacks(options.fallbacks);
3836
+ const props = options.props;
3837
+ const services = (_a = options.services) != null ? _a : {};
3838
+ const settings = (_b = options.settings) != null ? _b : {};
3839
+ function state() {
3840
+ return {
3841
+ original: cloneFallbacks(original),
3842
+ current: cloneFallbacks(current),
3843
+ changed: !sameFallbacks(original, current)
3844
+ };
3845
+ }
3846
+ function value() {
3847
+ return cloneFallbacks(current);
3848
+ }
3849
+ function reset() {
3850
+ current = cloneFallbacks(original);
3851
+ return state();
3852
+ }
3853
+ function get(serviceId) {
3854
+ var _a2, _b2;
3855
+ const out = [];
3856
+ for (const [primary, list] of Object.entries((_a2 = current.global) != null ? _a2 : {})) {
3857
+ if (String(primary) !== String(serviceId)) continue;
3858
+ out.push({
3859
+ scope: "global",
3860
+ primary,
3861
+ services: [...list != null ? list : []]
3862
+ });
3863
+ }
3864
+ if (!props) return out;
3865
+ for (const [nodeId, list] of Object.entries((_b2 = current.nodes) != null ? _b2 : {})) {
3866
+ const info = getFallbackRegistrationInfo(props, nodeId);
3867
+ if (String(info.primary) !== String(serviceId)) continue;
3868
+ out.push({
3869
+ scope: "node",
3870
+ scopeId: nodeId,
3871
+ primary: info.primary,
3872
+ services: [...list != null ? list : []]
3873
+ });
3874
+ }
3875
+ return out;
3876
+ }
3877
+ function getScope(context) {
3878
+ var _a2, _b2, _c, _d;
3879
+ if (context.scope === "global") {
3880
+ return [...(_b2 = (_a2 = current.global) == null ? void 0 : _a2[context.primary]) != null ? _b2 : []];
3881
+ }
3882
+ return [...(_d = (_c = current.nodes) == null ? void 0 : _c[context.nodeId]) != null ? _d : []];
3883
+ }
3884
+ function check(context, candidates) {
3885
+ var _a2, _b2;
3886
+ const normalized = normalizeCandidateList(
3887
+ candidates != null ? candidates : getScope(context),
3888
+ true
3889
+ );
3890
+ if (context.scope === "node" && !props) {
3891
+ return {
3892
+ context,
3893
+ allowed: [],
3894
+ rejected: normalized.map((candidate) => ({
3895
+ candidate,
3896
+ ok: false,
3897
+ reasons: ["missing_service_props"]
3898
+ })),
3899
+ warnings: ["missing_service_props"]
3900
+ };
3901
+ }
3902
+ const tempFallbacks = cloneFallbacks(current);
3903
+ if (context.scope === "global") {
3904
+ (_a2 = tempFallbacks.global) != null ? _a2 : tempFallbacks.global = {};
3905
+ if (normalized.length)
3906
+ tempFallbacks.global[context.primary] = normalized;
3907
+ else delete tempFallbacks.global[context.primary];
3908
+ } else {
3909
+ (_b2 = tempFallbacks.nodes) != null ? _b2 : tempFallbacks.nodes = {};
3910
+ if (normalized.length)
3911
+ tempFallbacks.nodes[context.nodeId] = normalized;
3912
+ else delete tempFallbacks.nodes[context.nodeId];
3913
+ }
3914
+ if (!props) {
3915
+ if (context.scope !== "global") {
3916
+ return {
3917
+ context,
3918
+ allowed: [],
3919
+ rejected: normalized.map((candidate) => ({
3920
+ candidate,
3921
+ ok: false,
3922
+ reasons: ["missing_service_props"]
3923
+ })),
3924
+ warnings: ["missing_service_props"]
3925
+ };
3926
+ }
3927
+ const rejected2 = [];
3928
+ const allowed2 = [];
3929
+ for (const candidate of normalized) {
3930
+ const reasons = [];
3931
+ if (String(candidate) === String(context.primary)) {
3932
+ reasons.push("self_reference");
3933
+ }
3934
+ if (reasons.length) {
3935
+ rejected2.push({ candidate, ok: false, reasons });
3936
+ } else {
3937
+ allowed2.push(candidate);
3938
+ }
3939
+ }
3940
+ return {
3941
+ context,
3942
+ primary: context.primary,
3943
+ allowed: allowed2,
3944
+ rejected: rejected2,
3945
+ warnings: []
3946
+ };
3947
+ }
3948
+ const fakeProps = {
3949
+ ...props,
3950
+ fallbacks: tempFallbacks
3951
+ };
3952
+ const diags = collectFailedFallbacks(fakeProps, services, {
3953
+ ...settings,
3954
+ mode: "dev"
3955
+ });
3956
+ const scoped = diags.filter((d) => {
3957
+ if (context.scope === "global") {
3958
+ return d.scope === "global" && String(d.primary) === String(context.primary);
3959
+ }
3960
+ return d.scope === "node" && String(d.nodeId) === String(context.nodeId);
3961
+ });
3962
+ const rejected = normalized.map((candidate) => {
3963
+ const reasons = scoped.filter((d) => String(d.candidate) === String(candidate)).map((d) => mapDiagReason(d.reason));
3964
+ return {
3965
+ candidate,
3966
+ ok: reasons.length === 0,
3967
+ reasons
3968
+ };
3969
+ }).filter((row) => !row.ok);
3970
+ const allowed = normalized.filter(
3971
+ (candidate) => !rejected.some(
3972
+ (r) => String(r.candidate) === String(candidate)
3973
+ )
3974
+ );
3975
+ const info = context.scope === "global" ? { ok: true, primary: context.primary } : getNodeRegistrationInfo(props, context.nodeId);
3976
+ const primary = (info == null ? void 0 : info.ok) ? info.primary : void 0;
3977
+ return {
3978
+ context,
3979
+ primary,
3980
+ allowed,
3981
+ rejected,
3982
+ warnings: []
3983
+ };
3984
+ }
3985
+ function add(context, candidate, options2) {
3986
+ return addMany(context, [candidate], options2);
3987
+ }
3988
+ function addMany(context, candidates, options2) {
3989
+ const existing = getScope(context);
3990
+ const merged = [...existing];
3991
+ const insertAt = typeof (options2 == null ? void 0 : options2.index) === "number" ? clamp(options2.index, 0, merged.length) : void 0;
3992
+ const incoming = normalizeCandidateList(candidates, true).filter(
3993
+ (id) => !merged.some((x) => String(x) === String(id))
3994
+ );
3995
+ if (insertAt === void 0) {
3996
+ merged.push(...incoming);
3997
+ } else {
3998
+ merged.splice(insertAt, 0, ...incoming);
3999
+ }
4000
+ return replace(context, merged, options2);
4001
+ }
4002
+ function remove(context, candidate) {
4003
+ const next = getScope(context).filter(
4004
+ (id) => String(id) !== String(candidate)
4005
+ );
4006
+ return writeScope(context, next);
4007
+ }
4008
+ function replace(context, candidates, options2) {
4009
+ const strict = !!(options2 == null ? void 0 : options2.strict);
4010
+ const normalized = normalizeCandidateList(candidates, true);
4011
+ const checked = check(context, normalized);
4012
+ const next = strict ? checked.allowed : normalized;
4013
+ return writeScope(context, next);
4014
+ }
4015
+ function clear(context) {
4016
+ return writeScope(context, []);
4017
+ }
4018
+ function eligible(context, opt) {
4019
+ if (!props) return [];
4020
+ if (context.scope === "global") {
4021
+ return getEligibleFallbacks({
4022
+ primary: context.primary,
4023
+ services,
4024
+ fallbacks: current,
4025
+ settings,
4026
+ props,
4027
+ exclude: opt == null ? void 0 : opt.exclude,
4028
+ unique: opt == null ? void 0 : opt.unique,
4029
+ limit: opt == null ? void 0 : opt.limit
4030
+ });
4031
+ }
4032
+ const info = getFallbackRegistrationInfo(props, context.nodeId);
4033
+ if (!info.primary) return [];
4034
+ return getEligibleFallbacks({
4035
+ primary: info.primary,
4036
+ nodeId: context.nodeId,
4037
+ tagId: info.tagContexts[0],
4038
+ services,
4039
+ fallbacks: current,
4040
+ settings,
4041
+ props,
4042
+ exclude: opt == null ? void 0 : opt.exclude,
4043
+ unique: opt == null ? void 0 : opt.unique,
4044
+ limit: opt == null ? void 0 : opt.limit
4045
+ });
4046
+ }
4047
+ function writeScope(context, nextList) {
4048
+ var _a2, _b2;
4049
+ const next = cloneFallbacks(current);
4050
+ if (context.scope === "global") {
4051
+ (_a2 = next.global) != null ? _a2 : next.global = {};
4052
+ if (nextList.length) {
4053
+ next.global[context.primary] = [...nextList];
4054
+ } else {
4055
+ delete next.global[context.primary];
4056
+ if (!Object.keys(next.global).length) delete next.global;
4057
+ }
4058
+ } else {
4059
+ (_b2 = next.nodes) != null ? _b2 : next.nodes = {};
4060
+ if (nextList.length) {
4061
+ next.nodes[context.nodeId] = [...nextList];
4062
+ } else {
4063
+ delete next.nodes[context.nodeId];
4064
+ if (!Object.keys(next.nodes).length) delete next.nodes;
4065
+ }
4066
+ }
4067
+ current = next;
4068
+ return state();
4069
+ }
4070
+ return {
4071
+ state,
4072
+ value,
4073
+ reset,
4074
+ get,
4075
+ getScope,
4076
+ check,
4077
+ add,
4078
+ addMany,
4079
+ remove,
4080
+ replace,
4081
+ clear,
4082
+ eligible
4083
+ };
4084
+ }
4085
+ function cloneFallbacks(input) {
4086
+ return {
4087
+ ...(input == null ? void 0 : input.nodes) ? { nodes: cloneRecordArray(input.nodes) } : {},
4088
+ ...(input == null ? void 0 : input.global) ? { global: cloneRecordArray(input.global) } : {}
4089
+ };
4090
+ }
4091
+ function cloneRecordArray(input) {
4092
+ const out = {};
4093
+ for (const [k, v] of Object.entries(input)) out[k] = [...v != null ? v : []];
4094
+ return out;
4095
+ }
4096
+ function sameFallbacks(a, b) {
4097
+ return JSON.stringify(a != null ? a : {}) === JSON.stringify(b != null ? b : {});
4098
+ }
4099
+ function normalizeCandidateList(input, preserveOrder) {
4100
+ const out = [];
4101
+ for (const item of input != null ? input : []) {
4102
+ if (!isValidServiceIdRef(item)) continue;
4103
+ const exists = out.some((x) => String(x) === String(item));
4104
+ if (exists) continue;
4105
+ out.push(item);
4106
+ }
4107
+ return preserveOrder ? out : out;
4108
+ }
4109
+ function isValidServiceIdRef(value) {
4110
+ return typeof value === "number" && Number.isFinite(value) || typeof value === "string" && value.trim().length > 0;
4111
+ }
4112
+ function clamp(n, min, max) {
4113
+ return Math.max(min, Math.min(max, n));
4114
+ }
4115
+ function getNodeRegistrationInfo(props, nodeId) {
4116
+ const tag = props.filters.find((t) => t.id === nodeId);
4117
+ if (tag) {
4118
+ if (!isValidServiceIdRef(tag.service_id)) {
4119
+ return { ok: false, reasons: ["no_primary"] };
4120
+ }
4121
+ return {
4122
+ ok: true,
4123
+ primary: tag.service_id,
4124
+ tagContexts: [tag.id]
4125
+ };
4126
+ }
4127
+ const hit = findOptionOwner(props.fields, nodeId);
4128
+ if (!hit) {
4129
+ return { ok: false, reasons: ["node_not_found"] };
4130
+ }
4131
+ if (!isValidServiceIdRef(hit.option.service_id)) {
4132
+ return { ok: false, reasons: ["no_primary"] };
4133
+ }
4134
+ return {
4135
+ ok: true,
4136
+ primary: hit.option.service_id,
4137
+ tagContexts: bindIdsToArray2(hit.field.bind_id)
4138
+ };
4139
+ }
4140
+ function findOptionOwner(fields, optionId) {
4141
+ var _a;
4142
+ for (const field of fields) {
4143
+ for (const option of (_a = field.options) != null ? _a : []) {
4144
+ if (option.id === optionId) return { field, option };
4145
+ }
4146
+ }
4147
+ return null;
4148
+ }
4149
+ function bindIdsToArray2(v) {
4150
+ if (Array.isArray(v)) return v.filter(Boolean);
4151
+ return v ? [v] : [];
4152
+ }
4153
+ function mapDiagReason(reason) {
4154
+ switch (String(reason)) {
4155
+ case "unknown_service":
4156
+ return "unknown_service";
4157
+ case "no_primary":
4158
+ return "no_primary";
4159
+ case "rate_violation":
4160
+ return "rate_violation";
4161
+ case "constraint_mismatch":
4162
+ return "constraint_mismatch";
4163
+ case "cycle":
4164
+ return "cycle";
4165
+ case "no_tag_context":
4166
+ return "no_tag_context";
4167
+ default:
4168
+ return "node_not_found";
4169
+ }
4170
+ }
3824
4171
  // Annotate the CommonJS export names for ESM import in node:
3825
4172
  0 && (module.exports = {
3826
4173
  buildOrderSnapshot,
3827
4174
  collectFailedFallbacks,
3828
4175
  createBuilder,
4176
+ createFallbackEditor,
3829
4177
  createNodeIndex,
3830
4178
  getEligibleFallbacks,
4179
+ getFallbackRegistrationInfo,
3831
4180
  normalise,
3832
4181
  resolveServiceFallback,
3833
4182
  validate,