Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Implement Migrations. Refactor for client SDKs. #293

Merged
merged 72 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 68 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
b95bbaf
chore: initial commit
yusinto Jun 27, 2023
4bf4d3c
chore: added createSafeLogger
yusinto Jul 5, 2023
10fb3c2
chore: add LDInspection types. Band aid ts errors for now. Fix export…
yusinto Jul 11, 2023
d29a840
chore: added TypedEventTarget
yusinto Jul 12, 2023
cb891ab
chore: move TypedEventTarget to sdk-client. It's incomplete.
yusinto Jul 13, 2023
147c5da
chore: complete EventTarget dom implementation
yusinto Jul 13, 2023
f539c7a
chore: improved LDEmitter (#207)
yusinto Jul 18, 2023
121b872
Merge branch 'main' into yus/sc-208024/scaffold-js-common-in-js-core
yusinto Jul 18, 2023
080e5c8
Merge branch 'main' into yus/sc-208024/scaffold-js-common-in-js-core
yusinto Jul 19, 2023
4f416b3
fix: eslint errors
yusinto Jul 19, 2023
5c9226f
chore: add prettier and check commands
yusinto Jul 19, 2023
b78c95f
feat: Add migrationVariation method. (#212)
kinyoklion Jul 24, 2023
b48f847
feat: Add migration configuration and basic migration. (#213)
kinyoklion Jul 26, 2023
72c0f53
feat: Add support for payloads to read and write methods. (#215)
kinyoklion Jul 28, 2023
36fef4f
feat: Add support for exclude from summaries. (#216)
kinyoklion Jul 28, 2023
59b8b6f
Merge branch 'main' into feat/node-migrations
kinyoklion Jul 31, 2023
bee4e74
chore: Prettier.
kinyoklion Jul 31, 2023
d32dcc8
Merge branch 'main' into yus/sc-208024/scaffold-js-common-in-js-core
yusinto Aug 1, 2023
965376d
Merge branch 'main' into yus/sc-208024/scaffold-js-common-in-js-core
yusinto Aug 1, 2023
42f48b3
fix: ran prettier
yusinto Aug 1, 2023
2a1eb6f
feat: Add migration operation input event and tracker. (#214)
kinyoklion Aug 1, 2023
87095c9
feat: Implement migration op event and connect to tracking. (#218)
kinyoklion Aug 1, 2023
8655ed3
feat: configuration and options validation (#221)
yusinto Aug 2, 2023
a2154cc
feat: Adjustments to spec and enhanced event validation. (#222)
kinyoklion Aug 3, 2023
ababe74
feat: Add configuration overrides and metrics data kinds. (#220)
kinyoklion Aug 3, 2023
f750bd5
feat: Add custom event support to tracker. (#227)
kinyoklion Aug 4, 2023
7e8fe03
Merge branch 'main' into yus/sc-208024/scaffold-js-common-in-js-core
Aug 10, 2023
9a6bd82
Merge branch 'main' into feat/node-migrations
kinyoklion Aug 16, 2023
02e92a4
feat: Remove custom events. (#243)
kinyoklion Aug 16, 2023
f9b4e6e
feat: Event sampling. (#245)
kinyoklion Aug 23, 2023
ab61937
chore: Add execution order support for contract tests. (#247)
kinyoklion Aug 23, 2023
bd95697
feat: Change migration variation to support forwarding the sampling r…
kinyoklion Aug 23, 2023
ee62381
feat: New data kinds for edge SDKs. (#260)
kinyoklion Aug 28, 2023
ee4ebbf
feat: Add invoked measurement. (#258)
kinyoklion Aug 30, 2023
760567d
chore: Support migration payload contract tests. (#262)
kinyoklion Aug 30, 2023
e12068a
chore: Add initial documentation. (#263)
kinyoklion Aug 30, 2023
01fd110
Merge branch 'main' into rlamb/merge-perf-updates-migrations
kinyoklion Aug 31, 2023
f7ed7eb
fix sampling
kinyoklion Aug 31, 2023
10c4875
fix merging event sampling
kinyoklion Aug 31, 2023
7c08e82
feat: Refactor variation method and consistency tracking. (#264)
kinyoklion Aug 31, 2023
e56e18f
Merge branch 'feat/node-migrations' into rlamb/merge-perf-updates-mig…
kinyoklion Aug 31, 2023
d7b8170
Merge branch 'main' into feat/node-migrations
kinyoklion Aug 31, 2023
cadc90b
fix: Fix double call when platform support performance API. (#268)
kinyoklion Sep 5, 2023
11179ba
fix: Fix log messages for failed migration creation. (#274)
kinyoklion Sep 11, 2023
5ff85e4
chore: Use new granular categories for event sampling. (#277)
kinyoklion Sep 13, 2023
d1f7197
fix: Handle exceptions thrown in the comparison function. (#278)
kinyoklion Sep 15, 2023
5b1a4d3
feat: reuse EventProcessor for dom (#228)
yusinto Sep 19, 2023
55f8e72
Merge branch 'main' into yus/sc-208024/scaffold-js-common-in-js-core
yusinto Sep 19, 2023
37b33fb
chore: fixed conflicts
yusinto Sep 19, 2023
f97935e
chore: remove duplicate stream processor tests.
yusinto Sep 19, 2023
71ee878
chore: fixed missing mocks. prefix and add bash directive.
yusinto Sep 19, 2023
ca881de
chore: add bash directive to shell scripts
yusinto Sep 21, 2023
703f56d
Update package.json
yusinto Sep 21, 2023
fe852fb
Merge branch 'main' into yus/sc-208024/scaffold-js-common-in-js-core
yusinto Sep 21, 2023
c120948
feat: Do not generate an event if the measurements are inconsistent. …
kinyoklion Sep 22, 2023
9c5f402
feat: Add flag version to migration op event. (#281)
kinyoklion Sep 22, 2023
f3481a4
feat: Use a factory method to create migrations. (#283)
kinyoklion Sep 25, 2023
dda6b37
feat: No migration op event for empty flag key. (#287)
kinyoklion Sep 27, 2023
8e96a52
feat: Add typed variation methods. (#288)
kinyoklion Sep 27, 2023
2ada20d
fix: refactor mocks to its own project (#284)
yusinto Oct 2, 2023
837c4e1
fix: Include flag version for WRONG_TYPE migrations. (#290)
kinyoklion Oct 2, 2023
c3f2d88
feat: Remove index and custom event sampling. (#289)
kinyoklion Oct 2, 2023
b22eef4
fix: yarn topological dev build (#291)
yusinto Oct 2, 2023
4ed3c76
feat: Client refactor and Migrations.
kinyoklion Oct 3, 2023
a3f64af
fix unit tests post merge
kinyoklion Oct 3, 2023
bacb607
Merge branch 'main' into feat/merge-client-and-migrations
kinyoklion Oct 3, 2023
a0dac0d
Remove config and metric kind from contract tests.
kinyoklion Oct 3, 2023
8f34dfa
chore: implement initial flag fetch (#294)
yusinto Oct 10, 2023
fc2c212
chore: implement variation functions (#298)
yusinto Oct 13, 2023
f65707b
chore: remove hardcoded path in streamuri (#299)
yusinto Oct 13, 2023
b27ec74
Merge branch 'main' into feat/merge-client-and-migrations
kinyoklion Oct 16, 2023
daa9409
chore: Remove minor version bumping from akamai-edgeworker-sdk
kinyoklion Oct 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ module.exports = {
plugins: ['@typescript-eslint', 'prettier'],
ignorePatterns: ['**/dist/**', '**/vercel/examples/**'],
rules: {
'@typescript-eslint/lines-between-class-members': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{ ignoreRestSiblings: true, argsIgnorePattern: '^_', varsIgnorePattern: '^__' },
],
'prettier/prettier': ['error'],
'class-methods-use-this': 'off',
'import/no-extraneous-dependencies': [
Expand All @@ -18,5 +23,11 @@ module.exports = {
devDependencies: ['**/jest*.ts', '**/*.test.ts', '**/rollup.config.ts'],
},
],
'import/default': 'error',
'import/export': 'error',
'import/no-self-import': 'error',
'import/no-cycle': 'error',
'import/no-useless-path-segments': 'error',
'import/no-duplicates': 'error',
},
};
25 changes: 25 additions & 0 deletions .github/workflows/mocks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: shared/mocks

on:
push:
branches: [main, 'feat/**']
paths-ignore:
- '**.md' #Do not need to run CI for markdown changes.
pull_request:
branches: [main, 'feat/**']
paths-ignore:
- '**.md'

jobs:
build-test-mocks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- id: shared
name: Shared CI Steps
uses: ./actions/ci
with:
workspace_name: '@launchdarkly/private-js-mocks'
workspace_path: packages/shared/mocks
should_build_docs: false
5 changes: 4 additions & 1 deletion actions/ci/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ inputs:
workspace_path:
description: 'Path to the package to release.'
required: true

should_build_docs:
description: 'Whether docs should be built. It will be by default.'
default: true
runs:
using: composite
steps:
Expand Down Expand Up @@ -40,4 +42,5 @@ runs:

- name: Build Docs
shell: bash
if: ${{inputs.should_build_docs == 'true'}}
run: yarn build:doc -- ${{ inputs.workspace_path }}
3 changes: 3 additions & 0 deletions contract-tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ app.get('/', (req, res) => {
'tags',
'big-segments',
'user-type',
'migrations',
'event-sampling',
'strongly-typed',
],
});
});
Expand Down
192 changes: 187 additions & 5 deletions contract-tests/sdkClientEntity.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import ld from 'node-server-sdk';
import got from 'got';
import ld, {
createMigration,
LDConcurrentExecution,
LDExecutionOrdering,
LDMigrationError,
LDMigrationSuccess,
LDSerialExecution,
} from 'node-server-sdk';

import BigSegmentTestStore from './BigSegmentTestStore.js';
import { Log, sdkLogger } from './log.js';
Expand All @@ -9,7 +17,7 @@ export { badCommandError };
export function makeSdkConfig(options, tag) {
const cf = {
logger: sdkLogger(tag),
diagnosticOptOut: true
diagnosticOptOut: true,
};
const maybeTime = (seconds) =>
seconds === undefined || seconds === null ? undefined : seconds / 1000;
Expand Down Expand Up @@ -55,6 +63,30 @@ export function makeSdkConfig(options, tag) {
return cf;
}

function getExecution(order) {
switch (order) {
case 'serial': {
return new LDSerialExecution(LDExecutionOrdering.Fixed);
}
case 'random': {
return new LDSerialExecution(LDExecutionOrdering.Random);
}
case 'concurrent': {
return new LDConcurrentExecution();
}
default: {
throw new Error('Unsupported execution order.');
}
}
}

function makeMigrationPostOptions(payload) {
if (payload) {
return { body: payload };
}
return {};
}

export async function newSdkClientEntity(options) {
const c = {};
const log = Log(options.tag);
Expand Down Expand Up @@ -93,10 +125,65 @@ export async function newSdkClientEntity(options) {
case 'evaluate': {
const pe = params.evaluate;
if (pe.detail) {
return await client.variationDetail(pe.flagKey, pe.context || pe.user, pe.defaultValue);
switch (pe.valueType) {
case 'bool':
return await client.boolVariationDetail(
pe.flagKey,
pe.context || pe.user,
pe.defaultValue,
);
case 'int': // Intentional fallthrough.
case 'double':
return await client.numberVariationDetail(
pe.flagKey,
pe.context || pe.user,
pe.defaultValue,
);
case 'string':
return await client.stringVariationDetail(
pe.flagKey,
pe.context || pe.user,
pe.defaultValue,
);
default:
return await client.variationDetail(
pe.flagKey,
pe.context || pe.user,
pe.defaultValue,
);
}
} else {
const value = await client.variation(pe.flagKey, pe.context || pe.user, pe.defaultValue);
return { value };
switch (pe.valueType) {
case 'bool':
return {
value: await client.boolVariation(
pe.flagKey,
pe.context || pe.user,
pe.defaultValue,
),
};
case 'int': // Intentional fallthrough.
case 'double':
return {
value: await client.numberVariation(
pe.flagKey,
pe.context || pe.user,
pe.defaultValue,
),
};
case 'string':
return {
value: await client.stringVariation(
pe.flagKey,
pe.context || pe.user,
pe.defaultValue,
),
};
default:
return {
value: await client.variation(pe.flagKey, pe.context || pe.user, pe.defaultValue),
};
}
}
}

Expand Down Expand Up @@ -127,6 +214,101 @@ export async function newSdkClientEntity(options) {
case 'getBigSegmentStoreStatus':
return await client.bigSegmentStoreStatusProvider.requireStatus();

case 'migrationVariation':
const migrationVariation = params.migrationVariation;
const res = await client.migrationVariation(
migrationVariation.key,
migrationVariation.context,
migrationVariation.defaultStage,
);
return { result: res.value };

case 'migrationOperation':
const migrationOperation = params.migrationOperation;
const readExecutionOrder = migrationOperation.readExecutionOrder;

const migration = createMigration(client, {
execution: getExecution(readExecutionOrder),
latencyTracking: migrationOperation.trackLatency,
errorTracking: migrationOperation.trackErrors,
check: migrationOperation.trackConsistency ? (a, b) => a === b : undefined,
readNew: async (payload) => {
try {
const res = await got.post(
migrationOperation.newEndpoint,
makeMigrationPostOptions(payload),
);
return LDMigrationSuccess(res.body);
} catch (err) {
return LDMigrationError(err.message);
}
},
writeNew: async (payload) => {
try {
const res = await got.post(
migrationOperation.newEndpoint,
makeMigrationPostOptions(payload),
);
return LDMigrationSuccess(res.body);
} catch (err) {
return LDMigrationError(err.message);
}
},
readOld: async (payload) => {
try {
const res = await got.post(
migrationOperation.oldEndpoint,
makeMigrationPostOptions(payload),
);
return LDMigrationSuccess(res.body);
} catch (err) {
return LDMigrationError(err.message);
}
},
writeOld: async (payload) => {
try {
const res = await got.post(
migrationOperation.oldEndpoint,
makeMigrationPostOptions(payload),
);
return LDMigrationSuccess(res.body);
} catch (err) {
return LDMigrationError(err.message);
}
},
});

switch (migrationOperation.operation) {
case 'read': {
const res = await migration.read(
migrationOperation.key,
migrationOperation.context,
migrationOperation.defaultStage,
migrationOperation.payload,
);
if (res.success) {
return { result: res.result };
} else {
return { result: res.error };
}
}
case 'write': {
const res = await migration.write(
migrationOperation.key,
migrationOperation.context,
migrationOperation.defaultStage,
migrationOperation.payload,
);

if (res.authoritative.success) {
return { result: res.authoritative.result };
} else {
return { result: res.authoritative.error };
}
}
}
return undefined;

default:
throw badCommandError;
}
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"name": "@launchdarkly/js-core",
"workspaces": [
"packages/shared/common",
"packages/shared/mocks",
"packages/shared/sdk-client",
"packages/shared/sdk-server",
"packages/shared/sdk-server-edge",
"packages/shared/akamai-edgeworker-sdk",
Expand Down
3 changes: 1 addition & 2 deletions packages/sdk/akamai-base/example/ldClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ const flagData = `

class MyCustomStoreProvider implements EdgeProvider {
// root key is formatted as LD-Env-{Launchdarkly environment client ID}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async get(rootKey: string): Promise<string> {
async get(_rootKey: string): Promise<string> {
// you should provide an implementation to retrieve your flags from launchdarkly's https://sdk.launchdarkly.com/sdk/latest-all endpoint.
// see https://docs.launchdarkly.com/sdk/features/flags-from-files for more information.
return flagData;
Expand Down
30 changes: 16 additions & 14 deletions packages/sdk/server-node/__tests__/LDClientNode.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { LDContext } from '@launchdarkly/js-server-sdk-common';
import { logger } from '@launchdarkly/private-js-mocks';

import { init } from '../src';

Expand All @@ -10,23 +11,24 @@ it('fires ready event in offline mode', (done) => {
});
});

it('fires the failed event if initialization fails', (done) => {
it('fires the failed event if initialization fails', async () => {
jest.useFakeTimers();

const failedHandler = jest.fn().mockName('failedHandler');
const client = init('sdk_key', {
updateProcessor: {
start: (fn: (err: any) => void) => {
setTimeout(() => {
fn(new Error('BAD THINGS'));
}, 0);
sendEvents: false,
logger,
updateProcessor: (clientContext, dataSourceUpdates, initSuccessHandler, errorHandler) => ({
start: () => {
setTimeout(() => errorHandler?.(new Error('Something unexpected happened')), 0);
},
stop: () => {},
close: () => {},
sendEvents: false,
},
});
client.on('failed', () => {
client.close();
done();
close: jest.fn(),
}),
});
client.on('failed', failedHandler);
jest.runAllTimers();

expect(failedHandler).toBeCalledWith(new Error('Something unexpected happened'));
});

// These tests are done in the node implementation because common doesn't have a crypto
Expand Down
12 changes: 4 additions & 8 deletions packages/sdk/server-node/__tests__/LDClientNode.tls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,14 @@ import {
TestHttpServer,
} from 'launchdarkly-js-test-helpers';

import { basicLogger, LDClient, LDLogger } from '../src';
import { logger } from '@launchdarkly/private-js-mocks';

import { LDClient } from '../src';
import LDClientNode from '../src/LDClientNode';

describe('When using a TLS connection', () => {
let client: LDClient;
let server: TestHttpServer;
let logger: LDLogger;

beforeEach(() => {
logger = basicLogger({
destination: () => {},
});
});

it('can connect via HTTPS to a server with a self-signed certificate, if CA is specified', async () => {
server = await TestHttpServer.startSecure();
Expand Down Expand Up @@ -87,6 +82,7 @@ describe('When using a TLS connection', () => {
stream: false,
tlsParams: { ca: server.certificate },
diagnosticOptOut: true,
logger,
});

await client.waitForInitialization();
Expand Down
Loading