UNPKG

@devcycle/nodejs-server-sdk

Version:

The DevCycle NodeJS Server SDK used for feature management.

286 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DevCycleProvider = void 0; const server_sdk_1 = require("@openfeature/server-sdk"); const index_1 = require("../index"); const DVCKnownPropertyKeyTypes = { email: 'string', name: 'string', language: 'string', country: 'string', appVersion: 'string', appBuild: 'number', customData: 'object', privateCustomData: 'object', }; class DevCycleProvider { constructor(clientOrKey, options = {}) { var _a; this.metadata = { name: 'devcycle-nodejs-provider', }; this.runsOn = 'server'; if (typeof clientOrKey === 'string') { this.devcycleClient = (0, index_1.initializeDevCycle)(clientOrKey, { ...options, sdkPlatform: 'nodejs-of', }); } else { this.devcycleClient = clientOrKey; } this.logger = (_a = options.logger) !== null && _a !== void 0 ? _a : (0, index_1.dvcDefaultLogger)({ level: options .logLevel, }); } get status() { return this.devcycleClient.isInitialized ? server_sdk_1.ProviderStatus.READY : server_sdk_1.ProviderStatus.NOT_READY; } async initialize(context) { if (this.devcycleClient instanceof index_1.DevCycleCloudClient) return; await this.devcycleClient.onClientInitialized(); } async onClose() { if (this.devcycleClient instanceof index_1.DevCycleCloudClient) return; await this.devcycleClient.close(); } track(trackingEventName, context, trackingEventDetails) { if (!context) { throw new server_sdk_1.TargetingKeyMissingError('Missing context to track event to DevCycle'); } this.devcycleClient.track(this.devcycleUserFromContext(context), { type: trackingEventName, value: trackingEventDetails === null || trackingEventDetails === void 0 ? void 0 : trackingEventDetails.value, metaData: trackingEventDetails && { ...trackingEventDetails, value: undefined, }, }); } /** * Generic function to retrieve a DVC variable and convert it to a ResolutionDetails. * @param flagKey * @param defaultValue * @param context * @private */ async getDVCVariable(flagKey, defaultValue, context) { const dvcVariable = this.devcycleClient.variable(this.devcycleUserFromContext(context), flagKey, defaultValue); return this.resultFromDVCVariable(dvcVariable instanceof Promise ? await dvcVariable : dvcVariable); } /** * Resolve a boolean OpenFeature flag and its evaluation details. * @param flagKey * @param defaultValue * @param context */ async resolveBooleanEvaluation(flagKey, defaultValue, context) { return this.getDVCVariable(flagKey, defaultValue, context); } /** * Resolve a string OpenFeature flag and its evaluation details. * @param flagKey * @param defaultValue * @param context */ async resolveStringEvaluation(flagKey, defaultValue, context) { return this.getDVCVariable(flagKey, defaultValue, context); } /** * Resolve a number OpenFeature flag and its evaluation details. * @param flagKey * @param defaultValue * @param context */ async resolveNumberEvaluation(flagKey, defaultValue, context) { return this.getDVCVariable(flagKey, defaultValue, context); } /** * Resolve a object OpenFeature flag and its evaluation details. * @param flagKey * @param defaultValue * @param context */ async resolveObjectEvaluation(flagKey, defaultValue, context) { return this.getDVCVariable(flagKey, this.defaultValueFromJsonValue(defaultValue), context); } /** * Convert a OpenFeature JsonValue default value into DVCJSON default value for evaluation. * @param jsonValue * @private */ defaultValueFromJsonValue(jsonValue) { if (typeof jsonValue !== 'object' || Array.isArray(jsonValue)) { throw new server_sdk_1.ParseError('DevCycle only supports object values for JSON flags'); } if (!jsonValue) { throw new server_sdk_1.ParseError('DevCycle does not support null default values for JSON flags'); } // Hard casting here because our DVCJSON typing enforces a flat object when we actually support // a JSON Object of any depth. Will be fixed soon. return jsonValue; } /** * Convert a DVCVariable result into a OpenFeature ResolutionDetails. * TODO: add support for variant / reason / and more error codes from DVC. * @param variable * @private */ resultFromDVCVariable(variable) { var _a, _b; const resolutionDetails = { value: variable.value, //TODO: once eval enabled from cloud bucketing, eval won't be null unless defaulted reason: (_b = (_a = variable.eval) === null || _a === void 0 ? void 0 : _a.reason) !== null && _b !== void 0 ? _b : (variable.isDefaulted ? server_sdk_1.StandardResolutionReasons.DEFAULT : server_sdk_1.StandardResolutionReasons.TARGETING_MATCH), }; if (variable.eval) { const { details, target_id } = variable.eval; const metadata = {}; if (details) metadata.evalReasonDetails = details; if (target_id) metadata.evalReasonTargetId = target_id; resolutionDetails.flagMetadata = metadata; } return resolutionDetails; } /** * Convert an OpenFeature EvaluationContext into a DevCycleUser. * @param context * @private */ devcycleUserFromContext(context) { let user_id, user_id_source; if (context.targetingKey) { user_id = context.targetingKey; user_id_source = 'targetingKey'; } else if (context.user_id) { user_id = context.user_id; user_id_source = 'user_id'; } else if (context.userId) { user_id = context.userId; user_id_source = 'userId'; } if (!user_id || user_id === '') { throw new server_sdk_1.TargetingKeyMissingError('DevCycle: Evaluation context does not contain a valid ' + 'targetingKey, user_id, or userId string attribute'); } if (typeof user_id !== 'string') { throw new server_sdk_1.TargetingKeyMissingError(`DevCycle: ${user_id_source} must be a string, got ${typeof user_id}`); } const dvcUserData = {}; let customData = {}; let privateCustomData = {}; for (const [key, value] of Object.entries(context)) { // Skip user ID fields as they're handled above if (key === 'targetingKey' || key === 'user_id' || key === 'userId') continue; const knownValueType = DVCKnownPropertyKeyTypes[key]; if (knownValueType) { if (typeof value !== knownValueType) { this.logger.warn(`Expected DevCycleUser property "${key}" to be "${knownValueType}" ` + `but got "${typeof value}" in EvaluationContext. Ignoring value.`); continue; } switch (knownValueType) { case 'string': dvcUserData[key] = value; break; case 'number': dvcUserData[key] = value; break; case 'object': if (key === 'privateCustomData') { privateCustomData = this.convertToDVCCustomDataJSON(value); } else if (key === 'customData') { customData = { ...customData, ...this.convertToDVCCustomDataJSON(value), }; } break; default: break; } } else { switch (typeof value) { case 'string': customData[key] = value; break; case 'number': customData[key] = value; break; case 'boolean': customData[key] = value; break; case 'object': if (value === null) { customData[key] = null; break; } this.logger.warn(`EvaluationContext property "${key}" is an ${Array.isArray(value) ? 'Array' : 'Object'}. ` + 'DevCycleUser only supports flat customData properties of type ' + 'string / number / boolean / null'); break; default: this.logger.warn(`Unknown EvaluationContext property "${key}" type. ` + 'DevCycleUser only supports flat customData properties of type ' + 'string / number / boolean / null'); break; } } } return new index_1.DevCycleUser({ user_id, customData: Object.keys(customData).length ? customData : undefined, privateCustomData: Object.keys(privateCustomData).length ? privateCustomData : undefined, ...dvcUserData, }); } /** * Convert customData from an OpenFeature EvaluationContextObject into a DevCycleUser customData. * @param evaluationData * @private */ convertToDVCCustomDataJSON(evaluationData) { const customData = {}; for (const [key, value] of Object.entries(evaluationData)) { switch (typeof value) { case 'string': customData[key] = value; break; case 'number': customData[key] = value; break; case 'boolean': customData[key] = value; break; case 'undefined': customData[key] = null; break; default: this.logger.warn(`EvaluationContext property "customData" contains "${key}" property of type ${typeof value}. ` + 'DevCycleUser only supports flat customData properties of type ' + 'string / number / boolean / null'); break; } } return customData; } } exports.DevCycleProvider = DevCycleProvider; //# sourceMappingURL=DevCycleProvider.js.map