@launchdarkly/js-server-sdk-common
Version:
LaunchDarkly Server SDK for JavaScript - common code
265 lines • 10.7 kB
JavaScript
"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