export const FEATURE_FLAG_APPLY_DECISION = Object.freeze({ CONTINUE_POST: "CONTINUE_POST", STOP_NOT_APPLIED: "STOP_NOT_APPLIED", STOP_UNKNOWN: "STOP_UNKNOWN", STOP_FOR_MANUAL_DECISION: "STOP_FOR_MANUAL_DECISION", }); function normalizeTargets(targets) { if (!Array.isArray(targets) || targets.length === 0) { throw new TypeError("targets must be a non-empty array"); } const keys = targets.map((target) => String(target?.flagKey ?? "").trim()); if (keys.some((key) => !key) || new Set(keys).size !== keys.length) { throw new TypeError("targets must contain unique non-empty flagKey values"); } return targets.map((target, index) => { const preVersion = Number(target.preVersion); if (!Number.isInteger(preVersion) || preVersion < 1) { throw new TypeError(`invalid preVersion for ${keys[index]}`); } const preOverrideMode = String(target.preOverrideMode ?? "").trim(); const postOverrideMode = String(target.postOverrideMode ?? "").trim(); if (!preOverrideMode || !postOverrideMode) { throw new TypeError(`missing override mode for ${keys[index]}`); } return Object.freeze({ flagKey: keys[index], preVersion, preOverrideMode, postVersion: preVersion + 1, postOverrideMode, }); }); } function normalizeReadback(readback) { if (!Array.isArray(readback)) { throw new TypeError("readback must be an array"); } return new Map( readback.map((item) => [ String(item?.flagKey ?? "").trim(), { version: Number(item?.version), overrideMode: String(item?.overrideMode ?? "").trim(), }, ]), ); } function matchesState(actual, version, overrideMode) { return actual?.version === version && actual?.overrideMode === overrideMode; } export function classifyFeatureFlagApplyReadback({ targets, readback }) { const normalizedTargets = normalizeTargets(targets); const actualByKey = normalizeReadback(readback); const observations = normalizedTargets.map((target) => { const actual = actualByKey.get(target.flagKey); if (matchesState(actual, target.postVersion, target.postOverrideMode)) { return { flagKey: target.flagKey, state: "POST" }; } if (matchesState(actual, target.preVersion, target.preOverrideMode)) { return { flagKey: target.flagKey, state: "PRE" }; } return { flagKey: target.flagKey, state: "OTHER" }; }); if (observations.every((item) => item.state === "POST")) { return Object.freeze({ decision: FEATURE_FLAG_APPLY_DECISION.CONTINUE_POST, classification: "APPLIED", observations: Object.freeze(observations), }); } if (observations.every((item) => item.state === "PRE")) { return Object.freeze({ decision: FEATURE_FLAG_APPLY_DECISION.STOP_NOT_APPLIED, classification: "NOT_APPLIED", observations: Object.freeze(observations), }); } return Object.freeze({ decision: FEATURE_FLAG_APPLY_DECISION.STOP_FOR_MANUAL_DECISION, classification: "PARTIAL_OR_UNEXPECTED", observations: Object.freeze(observations), }); } export async function resolveFeatureFlagApplyUncertainty({ targets, readCurrent }) { if (typeof readCurrent !== "function") { throw new TypeError("readCurrent must be a function"); } try { const readback = await readCurrent(); return classifyFeatureFlagApplyReadback({ targets, readback }); } catch (error) { return Object.freeze({ decision: FEATURE_FLAG_APPLY_DECISION.STOP_UNKNOWN, classification: "READBACK_FAILED", errorCode: error instanceof Error && error.message ? error.message : "readback_failed", observations: Object.freeze([]), }); } } export function createSingleAttemptMutation(sendMutation) { if (typeof sendMutation !== "function") { throw new TypeError("sendMutation must be a function"); } let attemptCount = 0; return Object.freeze({ get attemptCount() { return attemptCount; }, async execute(payload) { if (attemptCount !== 0) { throw new Error("mutation_attempt_already_consumed"); } attemptCount += 1; return sendMutation(payload); }, }); } export async function executeFeatureFlagApplyAttempt({ targets, payload, sendMutation, readCurrent, observedRequestCount = null, }) { const mutation = createSingleAttemptMutation(sendMutation); let response = null; let responseError = null; try { response = await mutation.execute(payload); } catch (error) { responseError = error instanceof Error && error.message ? error.message : "mutation_result_unknown"; } const resolution = await resolveFeatureFlagApplyUncertainty({ targets, readCurrent }); const normalizedObservedCount = Number.isInteger(observedRequestCount) && observedRequestCount >= 0 ? observedRequestCount : null; return Object.freeze({ ...resolution, sendAttemptCount: mutation.attemptCount, responseKnown: responseError === null, response, responseErrorCode: responseError, observedRequestCount: normalizedObservedCount, observerMismatch: normalizedObservedCount !== null && normalizedObservedCount !== mutation.attemptCount, }); }