Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Commit 5aa56fc

Browse files
LaunchDarklyReleaseBotLaunchDarklyCIeli-darklymaxwellgerberFauxFaux
authored
prepare 7.0.0 release (#265)
* add mention of singleton usage * update diagnostic event info for OS name, data store type, Node version * standardize linting * disallow window and document * fix null/undef checks * misc linting fixes * inlineUsersInEvents is not an unknown option * drop node-sha1 dependency * don't omit streamInits.failed when it's false * bump request dependency to get security patch; loosen some exact dependencies * remove request package; improve polling cache logic + add test * bump typescript version to fix build error in Node 6 * update @types/node to fix TypeScript check step * lint * make sure we keep polling regardless of whether we got new data * use launchdarkly-eventsource, make stream retry behavior consistent * stream retry delay option should be in seconds & should be included in diagnostics * minor test fix * fix: Throw an error on malformed user-supplied logger * don't call unref() on Redis client; ensure that database integration tests close the store * update Redis driver to major version 3 * add test case * allow redisOpts parameter to be omitted * add logger adapter shim + tests * minor cleanup and comments for ch74741 fix (logger wrapper) * fix proxy tunnel configuration and make sure it's used in streaming * change some string concatenation expressions to use interpolation * feat: upgrade winston (#189) * fix merge * remove support for indirect/patch and indirect/put (#182) * reuse same Promise and same event listeners for all waitForInitialization calls * better docs for waitForInitialization + misc doc cleanup (#184) * update js-eventsource to 1.3.1 for stream parsing bugfix (#185) * fix broken logger format (#186) * retroactively update changelog for bugfix in 5.13.2 release * allow get/getAll Redis queries to be queued if Redis client hasn't yet connected * set stream read timeout * adding the alias functionality (#190) * Removed the guides link * remove monkey-patching of setImmediate * Persist contextKind property during feature and custom event transformations (#194) * add inlineUsersInEvents option in TypeScript * Add support for seed to bucketUser * Add note for incorporating seed into evaluation * Send events when the evaluation is from an experiment * Use seed to evaluate. * Clean up test descriptions * Rename variable to be less confusing * Use ternary to eliminate mutation * Make return signature more consistent * Un-prettier the tests * redis lower bounds bump (#199) * update launchdarkly-js-test-helpers to fix TLS tests (#200) * update js-eventsource to remove vulnerability warning (#201) * add CI jobs for all compatible Node versions * CI fixes * more CI fixes * comment * use default value to simplify config * (6.0 - #1) stop saying we're compatible with Node <12 (#203) * add CI jobs for all compatible Node versions (#202) * (6.0 - #2) remove Redis integration (#204) * allow feature store to be specified as a factory (so it can get our logger) * (6.0 - #3) remove Winston (#205) * remove deprecated things for 6.0 (#206) * update node-cache to 5.x (drops old Node compat) * update semver to 7.x (drops old Node compat) * update uuid to 8.x (Node compat, perf improvements, bugfixes) * update dev dependencies * linter * replace lrucache package with lru-cache (#209) * make yaml dependency optional (#210) * update release metadata to include maintenance branch * remove package-lock.json (#211) * rm prerelease changelog * (big segments #1) add interfaces for big segments (#212) * (big segments #2) add all components for big segments except evaluation (#213) * (big segments #3) implement big segments in flag evaluation (#214) * (big segments #4) add standard test suite for big segment store tests + refactor feature store tests (#215) * move new interfaces to a module instead of a namespace (#216) * fix TS export of CachingStoreWrapper * use Releaser v2 config * fix overly specific test expectation that breaks in Node 17 * Initial work on FlagBuilder (#219) * Add TestData factory(with some dummy methods); Initial work on FlagBuilder * fixed indentation and linter errors; fixed an error in update; fixed incorrect test label * fixed typo in TestData store * converted boolean variation constants to be file variables instead of class variables Co-authored-by: charukiewicz <christian@foxhound.systems> Co-authored-by: belevy <ben@foxhound.systems> * implemented FlagRuleBuilder; added .build() methods to FlagBuilder/FlagRuleBuilder and changed tests to avoid using private interface * converted _targets to be Map instead of object literal; changed variationForBoolean to be a module-scoped function instead class-scoped * Implement stream processor(data source) interface for test data * Add TestData to index.js and write out the types for TestData and friends * added testdata documentation to index.d.ts; fix linter errors; changed flag default behavior to create boolean flag * Fix the interface file: reindented to 2 spaces, corrected definition of functions from properties to functions in interfaces; corrected issues in JSDoc comments * modify tests to fix capitalization and actually test the test datasource works as an LDClient updateProcessor. * Fix linter error on defaulted callback * explicitly enable JSDOM types in TypeScript build to avoid errors when jsdom is referenced for some reason * capitalize Big Segments in docs & logs * documentation comment fixes for TestData * pin TypeScript to 4.4.x * move TestData and FIleDataSource to integrations module * lint * rename types used by TestData for clarity (#229) * use varargs semantics for TestFlagBuilder.variations() and add it to the TS interface (#230) * don't ever use for...in (#232) * don't ever use for...in * add null guard * bump launchdarkly-eventsource dependency for sc-136154 fix * use TestData in our own tests (#231) * use TestData in our own tests * update TS interface * lint * typo * fix allFlagsState behavior regarding experimentation * lint * allow "secondary" to be referenced in clauses * don't throw an exception for non-string in semver comparison * correctly handle "client not ready" condition in allFlagsState * lint * Flags with a version of 0 reported as 'unknown' in summary events. (#239) * Initial draft of typescript types. (#236) * Implement attribute reference support. * implement contract test service, not including big segments (#242) Co-authored-by: Eli Bishop <eli@launchdarkly.com> * Implement Application tags for the node SDK. (#241) * update js-eventsource to 1.4.4 for security fix * remove package-lock.json * adjust test expectation about error message to work in recent Node versions * #3 Add context filtering and legacy to single kind conversion. (#238) Co-authored-by: Eli Bishop <eli@launchdarkly.com> * #4 Switch from user to context for events. (#244) Co-authored-by: Eli Bishop <eli@launchdarkly.com> * #5 Rlamb/sc 142950/implement u2c evaluation (#248) Co-authored-by: Eli Bishop <eli@launchdarkly.com> * #6 Rlamb/sc 145767/attribute reference improvements (#250) Co-authored-by: Eli Bishop <eli@launchdarkly.com> * #7 Rlamb/sc 146614/do not support bucketby for experiments (#251) Co-authored-by: Eli Bishop <eli@launchdarkly.com> * #8 Rlamb/sc 147263/treat cyclic segements as errors (#252) Co-authored-by: Eli Bishop <eli@launchdarkly.com> * Do not use the secondary key for experiments. (#256) * Resolve issues with V2 test harness. (#258) * Adds link to Relay Proxy docs * Update index.d.ts Co-authored-by: Eli Bishop <eli@launchdarkly.com> * ensure setTimeout task is cleared when polling is stopped * fix some flaky tests using async blocking logic * rm unused * simplify polling implementation using setInterval * Update the test data source for U2C. (#257) * use newer js-test-helpers for async tests * add request number to timeout message * Enforce 64 character limit for application tag values. (#263) * Changed transient back to anonymous. (#264) * Fixed operator field key name in TestDataRuleBuilder (#246) * Do not set `inExperiment` if there is not a context for the specified kind. (#266) * [sc-160948] Switch to partial URL encoding. (#265) * Update event schema version. (#267) * [sc-171125] Do now allow indexing into an array with an attribute reference. (#268) * [sc-174033] Remove support for secondary. (#269) * Treat 'kind' and '/kind' the same. (#270) * [sc-176598] Update node U2C with latest changes from main. (#272) * [sc-176599] Update documentation for privateAttributes _meta attribute of contexts. (#271) * Remove copy/paste error. (#274) * [sc-177983] Add support for executing old style user tests. (#275) * Update release metadata. * Do not generate events for bad contexts. (#277) Co-authored-by: Yusinto Ngadiman <yusinto@gmail.com> * Handle nested segment dependencies. (#278) Co-authored-by: LaunchDarklyCI <dev@launchdarkly.com> Co-authored-by: Eli Bishop <eli@launchdarkly.com> Co-authored-by: Maxwell Gerber <maxwell.gerber@mulesoft.com> Co-authored-by: Chris West <solo-github@goeswhere.com> Co-authored-by: Ben Woskow <48036130+bwoskow-ld@users.noreply.github.com> Co-authored-by: Mike Zorn <mike@launchdarkly.com> Co-authored-by: Ben Woskow <bwoskow@launchdarkly.com> Co-authored-by: Robert J. Neal <rneal@launchdarkly.com> Co-authored-by: Ben Levy <benjaminlevy007@gmail.com> Co-authored-by: charukiewicz <christian@foxhound.systems> Co-authored-by: belevy <ben@foxhound.systems> Co-authored-by: charukiewicz <charukiewicz@protonmail.com> Co-authored-by: LaunchDarklyReleaseBot <launchdarklyreleasebot@launchdarkly.com> Co-authored-by: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Co-authored-by: Ember Stevens <ember.stevens@launchdarkly.com> Co-authored-by: Ember Stevens <79482775+ember-stevens@users.noreply.github.com> Co-authored-by: Yusinto Ngadiman <yusinto@gmail.com>
1 parent b3dc03b commit 5aa56fc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+4361
-1314
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ junit.xml
66
npm-debug.log
77
test-types.js
88
.vscode
9+
coverage/

.ldrelease/config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ repo:
66

77
branches:
88
- name: main
9-
description: 6.x
9+
description: 7.x
10+
- name: 6.x
1011
- name: 5.x
1112

1213
publications:

attribute_reference.js

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/**
2+
* Take a key string and escape the characters to allow it to be used as a reference.
3+
* @param {string} key
4+
* @returns {string} The processed key.
5+
*/
6+
function processEscapeCharacters(key) {
7+
return key.replace(/~/g, '~0').replace(/\//g, '~1');
8+
}
9+
10+
/**
11+
* @param {string} reference The reference to get the components of.
12+
* @returns {string[]} The components of the reference. Escape characters will be converted to their representative values.
13+
*/
14+
function getComponents(reference) {
15+
const referenceWithoutPrefix = reference.startsWith('/') ? reference.substring(1) : reference;
16+
return referenceWithoutPrefix
17+
.split('/')
18+
.map(component => (component.indexOf('~') >= 0 ? component.replace(/~1/g, '/').replace(/~0/g, '~') : component));
19+
}
20+
21+
/**
22+
* @param {string} reference The reference to check if it is a literal.
23+
* @returns true if the reference is a literal.
24+
*/
25+
function isLiteral(reference) {
26+
return !reference.startsWith('/');
27+
}
28+
29+
/**
30+
* Get an attribute value from a literal.
31+
* @param {Object} target
32+
* @param {string} literal
33+
*/
34+
function getFromLiteral(target, literal) {
35+
if (target !== null && target !== undefined && Object.prototype.hasOwnProperty.call(target, literal)) {
36+
return target[literal];
37+
}
38+
}
39+
40+
/**
41+
* Gets the `target` object's value at the `reference`'s location.
42+
*
43+
* This method method follows the rules for accessing attributes for use
44+
* in evaluating clauses.
45+
*
46+
* Accessing the root of the target will always result in undefined.
47+
*
48+
* @param {Object} target
49+
* @param {string} reference
50+
* @returns The `target` object's value at the `reference`'s location.
51+
* Undefined if the field does not exist or if the reference is not valid.
52+
*/
53+
function get(target, reference) {
54+
if (reference === '' || reference === '/') {
55+
return undefined;
56+
}
57+
58+
if (isLiteral(reference)) {
59+
return getFromLiteral(target, reference);
60+
}
61+
62+
const components = getComponents(reference);
63+
let current = target;
64+
for (const component of components) {
65+
if (
66+
current !== null &&
67+
current !== undefined &&
68+
typeof current === 'object' &&
69+
// We do not want to allow indexing into an array.
70+
!Array.isArray(current) &&
71+
// For arrays and strings, in addition to objects, a hasOwnProperty check
72+
// will be true for indexes (as strings or numbers), which are present
73+
// in the object/string/array.
74+
Object.prototype.hasOwnProperty.call(current, component)
75+
) {
76+
current = current[component];
77+
} else {
78+
return undefined;
79+
}
80+
}
81+
82+
return current;
83+
}
84+
85+
/**
86+
* Compare two references and determine if they are equivalent.
87+
* @param {string} a
88+
* @param {string} b
89+
*/
90+
function compare(a, b) {
91+
const aIsLiteral = isLiteral(a);
92+
const bIsLiteral = isLiteral(b);
93+
if (aIsLiteral && bIsLiteral) {
94+
return a === b;
95+
}
96+
if (aIsLiteral) {
97+
const bComponents = getComponents(b);
98+
if (bComponents.length !== 1) {
99+
return false;
100+
}
101+
return a === bComponents[0];
102+
}
103+
if (bIsLiteral) {
104+
const aComponents = getComponents(a);
105+
if (aComponents.length !== 1) {
106+
return false;
107+
}
108+
return b === aComponents[0];
109+
}
110+
return a === b;
111+
}
112+
113+
/**
114+
* @param {string} a
115+
* @param {string} b
116+
* @returns The two strings joined by '/'.
117+
*/
118+
function join(a, b) {
119+
return `${a}/${b}`;
120+
}
121+
122+
/**
123+
* There are cases where a field could have been named with a preceeding '/'.
124+
* If that attribute was private, then the literal would appear to be a reference.
125+
* This method can be used to convert a literal to a reference in such situations.
126+
* @param {string} literal The literal to convert to a reference.
127+
* @returns A literal which has been converted to a reference.
128+
*/
129+
function literalToReference(literal) {
130+
return `/${processEscapeCharacters(literal)}`;
131+
}
132+
133+
/**
134+
* Clone an object excluding the values referenced by a list of references.
135+
* @param {Object} target The object to clone.
136+
* @param {string[]} references A list of references from the cloned object.
137+
* @returns {{cloned: Object, excluded: string[]}} The cloned object and a list of excluded values.
138+
*/
139+
function cloneExcluding(target, references) {
140+
const stack = [];
141+
const cloned = {};
142+
const excluded = [];
143+
144+
stack.push(
145+
...Object.keys(target).map(key => ({
146+
key,
147+
ptr: literalToReference(key),
148+
source: target,
149+
parent: cloned,
150+
visited: [target],
151+
}))
152+
);
153+
154+
while (stack.length) {
155+
const item = stack.pop();
156+
if (!references.some(ptr => compare(ptr, item.ptr))) {
157+
const value = item.source[item.key];
158+
159+
// Handle null because it overlaps with object, which we will want to handle later.
160+
if (value === null) {
161+
item.parent[item.key] = value;
162+
} else if (Array.isArray(value)) {
163+
item.parent[item.key] = [...value];
164+
} else if (typeof value === 'object') {
165+
//Arrays and null must already be handled.
166+
167+
//Prevent cycles by not visiting the same object
168+
//with in the same branch. Parallel branches
169+
//may contain the same object.
170+
if (item.visited.includes(value)) {
171+
continue;
172+
}
173+
174+
item.parent[item.key] = {};
175+
176+
stack.push(
177+
...Object.keys(value).map(key => ({
178+
key,
179+
ptr: join(item.ptr, processEscapeCharacters(key)),
180+
source: value,
181+
parent: item.parent[item.key],
182+
visited: [...item.visited, value],
183+
}))
184+
);
185+
} else {
186+
item.parent[item.key] = value;
187+
}
188+
} else {
189+
excluded.push(item.ptr);
190+
}
191+
}
192+
return { cloned, excluded: excluded.sort() };
193+
}
194+
195+
function isValidReference(reference) {
196+
return !reference.match(/\/\/|(^\/.*~[^0|^1])|~$/);
197+
}
198+
199+
/**
200+
* Check if the given attribute reference is for the "kind" attribute.
201+
* @param {string} reference String containing an attribute reference.
202+
*/
203+
function isKind(reference) {
204+
// There are only 2 valid ways to specify the kind attribute,
205+
// so this just checks them. Given the current flow of evaluation
206+
// this is much less intense a process than doing full validation and parsing.
207+
return reference === 'kind' || reference === '/kind';
208+
}
209+
210+
module.exports = {
211+
cloneExcluding,
212+
compare,
213+
get,
214+
isValidReference,
215+
literalToReference,
216+
isKind,
217+
};

configuration.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@ module.exports = (function () {
1818
offline: false,
1919
useLdd: false,
2020
allAttributesPrivate: false,
21-
privateAttributeNames: [],
22-
inlineUsersInEvents: false,
23-
userKeysCapacity: 1000,
24-
userKeysFlushInterval: 300,
21+
privateAttributes: [],
22+
contextKeysCapacity: 1000,
23+
contextKeysFlushInterval: 300,
2524
diagnosticOptOut: false,
2625
diagnosticRecordingInterval: 900,
2726
featureStore: InMemoryFeatureStore(),
@@ -89,7 +88,10 @@ module.exports = (function () {
8988
};
9089

9190
/* eslint-disable camelcase */
92-
const deprecatedOptions = {};
91+
const deprecatedOptions = {
92+
userKeysCapacity: 'contextKeysCapacity',
93+
userKeysFlushInterval: 'contextKeysFlushInterval',
94+
};
9395
/* eslint-enable camelcase */
9496

9597
function checkDeprecatedOptions(configIn) {
@@ -137,6 +139,7 @@ module.exports = (function () {
137139
}
138140
return 'object';
139141
};
142+
140143
Object.keys(config).forEach(name => {
141144
const value = config[name];
142145
if (value !== null && value !== undefined) {

context.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Validate a context kind.
3+
* @param {string} kind
4+
* @returns true if the kind is valid.
5+
*/
6+
function validKind(kind) {
7+
return typeof kind === 'string' && kind !== 'kind' && kind.match(/^(\w|\.|-)+$/);
8+
}
9+
10+
/**
11+
* Validate a context key.
12+
* @param {string} key
13+
* @returns true if the key is valid.
14+
*/
15+
function validKey(key) {
16+
return key !== undefined && key !== null && key !== '' && typeof key === 'string';
17+
}
18+
19+
/**
20+
* Perform a check of basic context requirements.
21+
* @param {Object} context
22+
* @param {boolean} allowLegacyKey If true, then a legacy user can have an
23+
* empty or non-string key. A legacy user is a context without a kind.
24+
* @returns true if the context meets basic requirements.
25+
*/
26+
function checkContext(context, allowLegacyKey) {
27+
if (context) {
28+
if (allowLegacyKey && (context.kind === undefined || context.kind === null)) {
29+
return context.key !== undefined && context.key !== null;
30+
}
31+
const key = context.key;
32+
const kind = context.kind === undefined ? 'user' : context.kind;
33+
const kindValid = validKind(kind);
34+
const keyValid = kind === 'multi' || validKey(key);
35+
if (kind === 'multi') {
36+
const kinds = Object.keys(context).filter(key => key !== 'kind');
37+
return keyValid && kinds.every(key => validKind(key)) && kinds.every(key => validKey(context[key].key));
38+
}
39+
return keyValid && kindValid;
40+
}
41+
return false;
42+
}
43+
44+
/**
45+
* The partial URL encoding is needed because : is a valid character in context keys.
46+
*
47+
* Partial encoding is the replacement of all colon (:) characters with the URL
48+
* encoded equivalent (%3A) and all percent (%) characters with the URL encoded
49+
* equivalent (%25).
50+
* @param {string} key The key to encode.
51+
* @returns {string} Partially URL encoded key.
52+
*/
53+
function encodeKey(key) {
54+
if (key.includes('%') || key.includes(':')) {
55+
return key.replace(/%/g, '%25').replace(/:/g, '%3A');
56+
}
57+
return key;
58+
}
59+
60+
/**
61+
* For a given context get a list of context kinds.
62+
* @param {Object} context
63+
* @returns A list of kinds in the context.
64+
*/
65+
function getContextKinds(context) {
66+
if (context) {
67+
if (context.kind === null || context.kind === undefined) {
68+
return ['user'];
69+
}
70+
if (context.kind !== 'multi') {
71+
return [context.kind];
72+
}
73+
return Object.keys(context).filter(kind => kind !== 'kind');
74+
}
75+
return [];
76+
}
77+
78+
function getCanonicalKey(context) {
79+
if (context) {
80+
if ((context.kind === undefined || context.kind === null || context.kind === 'user') && context.key) {
81+
return context.key;
82+
} else if (context.kind !== 'multi' && context.key) {
83+
return `${context.kind}:${encodeKey(context.key)}`;
84+
} else if (context.kind === 'multi') {
85+
return Object.keys(context)
86+
.sort()
87+
.filter(key => key !== 'kind')
88+
.map(key => `${key}:${encodeKey(context[key].key)}`)
89+
.join(':');
90+
}
91+
}
92+
}
93+
94+
module.exports = {
95+
checkContext,
96+
getContextKinds,
97+
getCanonicalKey,
98+
};

0 commit comments

Comments
 (0)