UNPKG

@launchdarkly/js-server-sdk-common

Version:
323 lines 12.5 kB
"use strict"; // The deserialization will be updating parameter values, so we don't need this // warning in this file. Object.defineProperty(exports, "__esModule", { value: true }); exports.deserializeSegment = exports.serializeSegment = exports.deserializeFlag = exports.serializeFlag = exports.deserializeDelete = exports.deserializePatch = exports.deserializePoll = exports.deserializeAll = exports.processSegment = exports.processFlag = exports.replacer = exports.nullReplacer = void 0; /* eslint-disable no-param-reassign */ const js_sdk_common_1 = require("@launchdarkly/js-sdk-common"); const VersionedDataKinds_1 = require("./VersionedDataKinds"); // The max size where we use an array instead of a set. const TARGET_LIST_ARRAY_CUTOFF = 100; /** * Performs deep removal of null values. * * Does not remove null values from arrays. * * Note: This is a non-recursive implementation for performance and to avoid * potential stack overflows. * * @param target The target to remove null values from. * @param excludeKeys A list of top-level keys to exclude from null removal. */ function nullReplacer(target, excludeKeys) { const stack = []; if (target === null || target === undefined) { return; } const filteredEntries = Object.entries(target).filter(([key, _value]) => !(excludeKeys === null || excludeKeys === void 0 ? void 0 : excludeKeys.includes(key))); stack.push(...filteredEntries.map(([key, value]) => ({ key, value, parent: target, }))); while (stack.length) { const item = stack.pop(); // Do not remove items from arrays. if (item.value === null && !Array.isArray(item.parent)) { delete item.parent[item.key]; } else if (typeof item.value === 'object' && item.value !== null) { // Add all the children to the stack. This includes array children. // The items in the array could themselves be objects which need nulls // removed from them. stack.push(...Object.entries(item.value).map(([key, value]) => ({ key, value, parent: item.value, }))); } } } exports.nullReplacer = nullReplacer; /** * For use when serializing flags/segments. This will ensure local types * are converted to the appropriate JSON representation. * @param this The scope containing the key/value. * @param key The key of the item being visited. * @param value The value of the item being visited. * @returns A transformed value for serialization. * * @internal */ function replacer(key, value) { if (value instanceof js_sdk_common_1.AttributeReference) { return undefined; } if (Array.isArray(value)) { if (value[0] && value[0] instanceof js_sdk_common_1.AttributeReference) { return undefined; } } // Allow null/undefined values to pass through without modification. if (value === null || value === undefined) { return value; } if (value.generated_includedSet) { value.included = [...value.generated_includedSet]; delete value.generated_includedSet; } if (value.generated_excludedSet) { value.excluded = [...value.generated_excludedSet]; delete value.generated_excludedSet; } if (value.includedContexts) { value.includedContexts.forEach((target) => { if (target.generated_valuesSet) { target.values = [...target.generated_valuesSet]; } delete target.generated_valuesSet; }); } if (value.excludedContexts) { value.excludedContexts.forEach((target) => { if (target.generated_valuesSet) { target.values = [...target.generated_valuesSet]; } delete target.generated_valuesSet; }); } return value; } exports.replacer = replacer; function processRollout(rollout) { if (rollout && rollout.bucketBy) { rollout.bucketByAttributeReference = new js_sdk_common_1.AttributeReference(rollout.bucketBy, !rollout.contextKind); } } /** * @internal */ function processFlag(flag) { var _a; nullReplacer(flag, ['variations']); if (flag.fallthrough && flag.fallthrough.rollout) { const rollout = flag.fallthrough.rollout; processRollout(rollout); } (_a = flag === null || flag === void 0 ? void 0 : flag.rules) === null || _a === void 0 ? void 0 : _a.forEach((rule) => { var _a; processRollout(rule.rollout); (_a = rule === null || rule === void 0 ? void 0 : rule.clauses) === null || _a === void 0 ? void 0 : _a.forEach((clause) => { if (clause && clause.attribute) { // Clauses before U2C would have had literals for attributes. // So use the contextKind to indicate if this is new or old data. clause.attributeReference = new js_sdk_common_1.AttributeReference(clause.attribute, !clause.contextKind); } else if (clause) { clause.attributeReference = js_sdk_common_1.AttributeReference.InvalidReference; } }); }); } exports.processFlag = processFlag; /** * @internal */ function processSegment(segment) { var _a, _b, _c, _d, _e; nullReplacer(segment); if (((_a = segment === null || segment === void 0 ? void 0 : segment.included) === null || _a === void 0 ? void 0 : _a.length) && segment.included.length > TARGET_LIST_ARRAY_CUTOFF) { segment.generated_includedSet = new Set(segment.included); delete segment.included; } if (((_b = segment === null || segment === void 0 ? void 0 : segment.excluded) === null || _b === void 0 ? void 0 : _b.length) && segment.excluded.length > TARGET_LIST_ARRAY_CUTOFF) { segment.generated_excludedSet = new Set(segment.excluded); delete segment.excluded; } if ((_c = segment === null || segment === void 0 ? void 0 : segment.includedContexts) === null || _c === void 0 ? void 0 : _c.length) { segment.includedContexts.forEach((target) => { var _a; if (((_a = target === null || target === void 0 ? void 0 : target.values) === null || _a === void 0 ? void 0 : _a.length) && target.values.length > TARGET_LIST_ARRAY_CUTOFF) { target.generated_valuesSet = new Set(target.values); // Currently typing is non-optional, so we don't delete it. target.values = []; } }); } if ((_d = segment === null || segment === void 0 ? void 0 : segment.excludedContexts) === null || _d === void 0 ? void 0 : _d.length) { segment.excludedContexts.forEach((target) => { var _a; if (((_a = target === null || target === void 0 ? void 0 : target.values) === null || _a === void 0 ? void 0 : _a.length) && target.values.length > TARGET_LIST_ARRAY_CUTOFF) { target.generated_valuesSet = new Set(target.values); // Currently typing is non-optional, so we don't delete it. target.values = []; } }); } (_e = segment === null || segment === void 0 ? void 0 : segment.rules) === null || _e === void 0 ? void 0 : _e.forEach((rule) => { var _a; if (rule.bucketBy) { // Rules before U2C would have had literals for attributes. // So use the rolloutContextKind to indicate if this is new or old data. rule.bucketByAttributeReference = new js_sdk_common_1.AttributeReference(rule.bucketBy, !rule.rolloutContextKind); } (_a = rule === null || rule === void 0 ? void 0 : rule.clauses) === null || _a === void 0 ? void 0 : _a.forEach((clause) => { if (clause && clause.attribute) { // Clauses before U2C would have had literals for attributes. // So use the contextKind to indicate if this is new or old data. clause.attributeReference = new js_sdk_common_1.AttributeReference(clause.attribute, !clause.contextKind); } else if (clause) { clause.attributeReference = js_sdk_common_1.AttributeReference.InvalidReference; } }); }); } exports.processSegment = processSegment; function tryParse(data) { try { return JSON.parse(data); } catch (_a) { return undefined; } } /** * @internal */ function deserializeAll(data) { var _a, _b; // The reviver lacks the context of where a different key exists, being as it // starts at the deepest level and works outward. As a result attributes are // translated into references after the initial parsing. That way we can be sure // they are the correct ones. For instance if we added 'attribute' as a new field to // the schema for something that was NOT an attribute reference, then we wouldn't // want to construct an attribute reference out of it. const parsed = tryParse(data); if (!parsed) { return undefined; } Object.values(((_a = parsed === null || parsed === void 0 ? void 0 : parsed.data) === null || _a === void 0 ? void 0 : _a.flags) || []).forEach((flag) => { processFlag(flag); }); Object.values(((_b = parsed === null || parsed === void 0 ? void 0 : parsed.data) === null || _b === void 0 ? void 0 : _b.segments) || []).forEach((segment) => { processSegment(segment); }); return parsed; } exports.deserializeAll = deserializeAll; /** * This function is intended for usage inside LaunchDarkly SDKs. * This function should NOT be used by customer applications. * This function may be changed or removed without a major version. * * @param data String data from launchdarkly. * @returns The parsed and processed data. */ function deserializePoll(data) { const parsed = tryParse(data); if (!parsed) { return undefined; } Object.values((parsed === null || parsed === void 0 ? void 0 : parsed.flags) || []).forEach((flag) => { processFlag(flag); }); Object.values((parsed === null || parsed === void 0 ? void 0 : parsed.segments) || []).forEach((segment) => { processSegment(segment); }); return parsed; } exports.deserializePoll = deserializePoll; /** * @internal */ function deserializePatch(data) { const parsed = tryParse(data); if (!parsed) { return undefined; } if (parsed.path.startsWith(VersionedDataKinds_1.default.Features.streamApiPath)) { processFlag(parsed.data); parsed.kind = VersionedDataKinds_1.default.Features; } else if (parsed.path.startsWith(VersionedDataKinds_1.default.Segments.streamApiPath)) { processSegment(parsed.data); parsed.kind = VersionedDataKinds_1.default.Segments; } return parsed; } exports.deserializePatch = deserializePatch; /** * @internal */ function deserializeDelete(data) { const parsed = tryParse(data); if (!parsed) { return undefined; } if (parsed.path.startsWith(VersionedDataKinds_1.default.Features.streamApiPath)) { parsed.kind = VersionedDataKinds_1.default.Features; } else if (parsed.path.startsWith(VersionedDataKinds_1.default.Segments.streamApiPath)) { parsed.kind = VersionedDataKinds_1.default.Segments; } return parsed; } exports.deserializeDelete = deserializeDelete; /** * Serialize a single flag. Used for persistent data stores. * * @internal */ function serializeFlag(flag) { return JSON.stringify(flag, replacer); } exports.serializeFlag = serializeFlag; /** * Deserialize a single flag. Used for persistent data stores. * * @internal */ function deserializeFlag(data) { const parsed = tryParse(data); if (!parsed) { return undefined; } processFlag(parsed); return parsed; } exports.deserializeFlag = deserializeFlag; /** * Serialize a single segment. Used for persistent data stores. * * @internal */ function serializeSegment(segment) { return JSON.stringify(segment, replacer); } exports.serializeSegment = serializeSegment; /** * Deserialize a single segment. Used for persistent data stores. * * @internal */ function deserializeSegment(data) { const parsed = tryParse(data); if (!parsed) { return undefined; } processSegment(parsed); return parsed; } exports.deserializeSegment = deserializeSegment; //# sourceMappingURL=serialization.js.map