UNPKG

@launchdarkly/js-server-sdk-common

Version:
265 lines 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createMigration = exports.LDMigrationError = exports.LDMigrationSuccess = void 0; const api_1 = require("./api"); const LDMigrationOptions_1 = require("./api/options/LDMigrationOptions"); async function safeCall(method) { try { // Awaiting to allow catching. const res = await method(); return res; } catch (error) { return { success: false, error, }; } } /** * Report a successful migration operation from `readNew`, `readOld`, `writeNew` or `writeOld`. * * ``` * readNew: async () => { * const myResult = doMyOldRead(); * if(myResult.wasGood) { * return LDMigrationSuccess(myResult); * } * return LDMigrationError(myResult.error) * } * ``` * * @param result The result of the operation. * @returns An {@link LDMethodResult} */ function LDMigrationSuccess(result) { return { success: true, result, }; } exports.LDMigrationSuccess = LDMigrationSuccess; /** * Report a failed migration operation from `readNew`, `readOld`, `writeNew` or `writeOld`. * * ``` * readNew: async () => { * const myResult = doMyOldRead(); * if(myResult.wasGood) { * return LDMigrationSuccess(myResult); * } * return LDMigrationError(myResult.error) * } * ``` * * @param result The result of the operations. * @returns An {@link LDMethodResult} */ function LDMigrationError(error) { return { success: false, error, }; } exports.LDMigrationError = LDMigrationError; /** * Class which allows performing technology migrations. */ class Migration { constructor(_client, _config) { var _a, _b; this._client = _client; this._config = _config; this._readTable = { [api_1.LDMigrationStage.Off]: async (context) => this._doSingleOp(context, 'old', this._config.readOld.bind(this._config)), [api_1.LDMigrationStage.DualWrite]: async (context) => this._doSingleOp(context, 'old', this._config.readOld.bind(this._config)), [api_1.LDMigrationStage.Shadow]: async (context) => { const { fromOld, fromNew } = await this._doRead(context); this._trackConsistency(context, fromOld, fromNew); return fromOld; }, [api_1.LDMigrationStage.Live]: async (context) => { const { fromNew, fromOld } = await this._doRead(context); this._trackConsistency(context, fromOld, fromNew); return fromNew; }, [api_1.LDMigrationStage.RampDown]: async (context) => this._doSingleOp(context, 'new', this._config.readNew.bind(this._config)), [api_1.LDMigrationStage.Complete]: async (context) => this._doSingleOp(context, 'new', this._config.readNew.bind(this._config)), }; this._writeTable = { [api_1.LDMigrationStage.Off]: async (context) => ({ authoritative: await this._doSingleOp(context, 'old', this._config.writeOld.bind(this._config)), }), [api_1.LDMigrationStage.DualWrite]: async (context) => { const fromOld = await this._doSingleOp(context, 'old', this._config.writeOld.bind(this._config)); if (!fromOld.success) { return { authoritative: fromOld, }; } const fromNew = await this._doSingleOp(context, 'new', this._config.writeNew.bind(this._config)); return { authoritative: fromOld, nonAuthoritative: fromNew, }; }, [api_1.LDMigrationStage.Shadow]: async (context) => { const fromOld = await this._doSingleOp(context, 'old', this._config.writeOld.bind(this._config)); if (!fromOld.success) { return { authoritative: fromOld, }; } const fromNew = await this._doSingleOp(context, 'new', this._config.writeNew.bind(this._config)); return { authoritative: fromOld, nonAuthoritative: fromNew, }; }, [api_1.LDMigrationStage.Live]: async (context) => { const fromNew = await this._doSingleOp(context, 'new', this._config.writeNew.bind(this._config)); if (!fromNew.success) { return { authoritative: fromNew, }; } const fromOld = await this._doSingleOp(context, 'old', this._config.writeOld.bind(this._config)); return { authoritative: fromNew, nonAuthoritative: fromOld, }; }, [api_1.LDMigrationStage.RampDown]: async (context) => { const fromNew = await this._doSingleOp(context, 'new', this._config.writeNew.bind(this._config)); if (!fromNew.success) { return { authoritative: fromNew, }; } const fromOld = await this._doSingleOp(context, 'old', this._config.writeOld.bind(this._config)); return { authoritative: fromNew, nonAuthoritative: fromOld, }; }, [api_1.LDMigrationStage.Complete]: async (context) => ({ authoritative: await this._doSingleOp(context, 'new', this._config.writeNew.bind(this._config)), }), }; if (this._config.execution) { this._execution = this._config.execution; } else { this._execution = new LDMigrationOptions_1.LDConcurrentExecution(); } this._latencyTracking = (_a = this._config.latencyTracking) !== null && _a !== void 0 ? _a : true; this._errorTracking = (_b = this._config.errorTracking) !== null && _b !== void 0 ? _b : true; } async read(key, context, defaultStage, payload) { const stage = await this._client.migrationVariation(key, context, defaultStage); const res = await this._readTable[stage.value]({ payload, tracker: stage.tracker, }); stage.tracker.op('read'); this._sendEvent(stage.tracker); return res; } async write(key, context, defaultStage, payload) { const stage = await this._client.migrationVariation(key, context, defaultStage); const res = await this._writeTable[stage.value]({ payload, tracker: stage.tracker, }); stage.tracker.op('write'); this._sendEvent(stage.tracker); return res; } _sendEvent(tracker) { const event = tracker.createEvent(); if (event) { this._client.trackMigration(event); } } _trackConsistency(context, oldValue, newValue) { if (!this._config.check) { return; } if (oldValue.success && newValue.success) { // Check is validated before this point, so it is force unwrapped. context.tracker.consistency(() => this._config.check(oldValue.result, newValue.result)); } } async _readSequentialFixed(context) { const fromOld = await this._doSingleOp(context, 'old', this._config.readOld.bind(this._config)); const fromNew = await this._doSingleOp(context, 'new', this._config.readNew.bind(this._config)); return { fromOld, fromNew }; } async _readConcurrent(context) { const fromOldPromise = this._doSingleOp(context, 'old', this._config.readOld.bind(this._config)); const fromNewPromise = this._doSingleOp(context, 'new', this._config.readNew.bind(this._config)); const [fromOld, fromNew] = await Promise.all([fromOldPromise, fromNewPromise]); return { fromOld, fromNew }; } async _readSequentialRandom(context) { // This number is not used for a purpose requiring cryptographic security. const randomIndex = Math.floor(Math.random() * 2); // Effectively flip a coin and do it on one order or the other. if (randomIndex === 0) { const fromOld = await this._doSingleOp(context, 'old', this._config.readOld.bind(this._config)); const fromNew = await this._doSingleOp(context, 'new', this._config.readNew.bind(this._config)); return { fromOld, fromNew }; } const fromNew = await this._doSingleOp(context, 'new', this._config.readNew.bind(this._config)); const fromOld = await this._doSingleOp(context, 'old', this._config.readOld.bind(this._config)); return { fromOld, fromNew }; } async _doRead(context) { var _a; if (((_a = this._execution) === null || _a === void 0 ? void 0 : _a.type) === LDMigrationOptions_1.LDExecution.Serial) { const serial = this._execution; if (serial.ordering === LDMigrationOptions_1.LDExecutionOrdering.Fixed) { return this._readSequentialFixed(context); } return this._readSequentialRandom(context); } return this._readConcurrent(context); } async _doSingleOp(context, origin, method) { context.tracker.invoked(origin); const res = await this._trackLatency(context.tracker, origin, () => safeCall(() => method(context.payload))); if (!res.success && this._errorTracking) { context.tracker.error(origin); } return Object.assign({ origin }, res); } async _trackLatency(tracker, origin, method) { if (!this._latencyTracking) { return method(); } let start; let end; let result; // TODO: Need to validate performance existence check with edge SDKs. if (typeof performance !== 'undefined') { start = performance.now(); result = await method(); end = performance.now(); } else { start = Date.now(); result = await method(); end = Date.now(); } // Performance timer is in ms, but may have a microsecond resolution // fractional component. const latency = end - start; tracker.latency(origin, latency); return result; } } function createMigration(client, config) { return new Migration(client, config); } exports.createMigration = createMigration; //# sourceMappingURL=Migration.js.map