From 4b5190742487babc270f0abcad3aae67146d4d7c Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 29 Jul 2022 14:25:05 -0700 Subject: [PATCH 01/34] Lint --- docs/.nojekyll | 1 + docs/assets/highlight.css | 85 ++ docs/assets/main.js | 54 + docs/assets/search.js | 1 + docs/assets/style.css | 1224 +++++++++++++++++ docs/assets/widgets.png | Bin 0 -> 480 bytes docs/assets/widgets@2x.png | Bin 0 -> 855 bytes docs/classes/AttributeReference.html | 197 +++ docs/classes/BasicLogger.html | 223 +++ .../BigSegmentStoreStatusProviderImpl.html | 140 ++ docs/classes/Context.html | 363 +++++ docs/classes/ContextFilter.html | 133 ++ docs/classes/DateValidator.html | 111 ++ docs/classes/FactoryOrInstance.html | 110 ++ docs/classes/Function.html | 110 ++ docs/classes/LDClientImpl.html | 547 ++++++++ docs/classes/NumberWithMinimum.html | 136 ++ docs/classes/SafeLogger.html | 212 +++ docs/classes/StringMatchingRegex.html | 136 ++ docs/classes/Type.html | 151 ++ docs/classes/TypeArray.html | 148 ++ docs/classes/TypeValidators.html | 170 +++ docs/index.html | 145 ++ docs/interfaces/BasicLoggerOptions.html | 133 ++ docs/interfaces/LDBigSegmentsOptions.html | 138 ++ docs/interfaces/LDClient.html | 484 +++++++ docs/interfaces/LDContextCommon.html | 107 ++ docs/interfaces/LDContextMeta.html | 95 ++ docs/interfaces/LDEvaluationDetail.html | 98 ++ docs/interfaces/LDEvaluationReason.html | 153 +++ docs/interfaces/LDFlagSet.html | 93 ++ docs/interfaces/LDFlagsState.html | 153 +++ docs/interfaces/LDFlagsStateOptions.html | 98 ++ docs/interfaces/LDLogger.html | 157 +++ docs/interfaces/LDMultiKindContext.html | 79 ++ docs/interfaces/LDOptions.html | 383 ++++++ docs/interfaces/LDProxyOptions.html | 101 ++ docs/interfaces/LDSingleKindContext.html | 121 ++ docs/interfaces/LDTLSOptions.html | 153 +++ docs/interfaces/LDUser.html | 193 +++ docs/interfaces/TypeValidator.html | 99 ++ .../integrations.FileDataSourceOptions.html | 97 ++ docs/interfaces/integrations.TestData.html | 204 +++ .../integrations.TestDataFlagBuilder.html | 402 ++++++ .../integrations.TestDataRuleBuilder.html | 167 +++ .../interfaces.BigSegmentStore.html | 129 ++ .../interfaces.BigSegmentStoreMembership.html | 75 + .../interfaces.BigSegmentStoreMetadata.html | 77 ++ .../interfaces.BigSegmentStoreStatus.html | 99 ++ ...erfaces.BigSegmentStoreStatusProvider.html | 107 ++ .../interfaces/interfaces.DataCollection.html | 91 ++ docs/interfaces/interfaces.DataKind.html | 78 ++ .../interfaces/interfaces.ItemDescriptor.html | 78 ++ .../interfaces.PersistentDataStore.html | 227 +++ .../interfaces.PersistentStoreDataKind.html | 91 ++ .../interfaces.SerializedItemDescriptor.html | 88 ++ docs/interfaces/interfaces.VersionedData.html | 97 ++ docs/interfaces/platform.Crypto.html | 99 ++ docs/interfaces/platform.EventSource.html | 138 ++ .../platform.EventSourceInitDict.html | 122 ++ docs/interfaces/platform.Filesystem.html | 135 ++ docs/interfaces/platform.Hasher.html | 101 ++ docs/interfaces/platform.Headers.html | 148 ++ docs/interfaces/platform.Hmac.html | 103 ++ docs/interfaces/platform.Info.html | 92 ++ docs/interfaces/platform.Options.html | 92 ++ docs/interfaces/platform.Platform.html | 102 ++ docs/interfaces/platform.PlatformData.html | 121 ++ docs/interfaces/platform.Requests.html | 98 ++ docs/interfaces/platform.Response.html | 110 ++ docs/interfaces/platform.SdkData.html | 100 ++ .../interfaces/subsystems.LDFeatureStore.html | 312 +++++ .../subsystems.LDFeatureStoreDataStorage.html | 63 + .../subsystems.LDFeatureStoreItem.html | 86 ++ .../subsystems.LDFeatureStoreKindData.html | 63 + .../subsystems.LDKeyedFeatureStoreItem.html | 92 ++ docs/modules.html | 133 ++ docs/modules/integrations.html | 61 + docs/modules/interfaces.html | 84 ++ docs/modules/platform.html | 81 ++ docs/modules/subsystems.html | 63 + docs/types/LDContext.html | 86 ++ docs/types/LDFlagValue.html | 87 ++ docs/types/LDLogLevel.html | 90 ++ docs/types/interfaces.FullDataSet.html | 71 + docs/types/interfaces.KeyedItems.html | 70 + platform-node/src/platform/NodeFilesystem.ts | 9 +- sdk-common/src/index.ts | 1 - .../__tests__/cache/LruCache.test.ts | 2 +- .../data_sources/PollingProcessor.test.ts | 14 +- .../__tests__/data_sources/Requestor.test.ts | 25 +- .../api/integrations/FileDataSourceOptions.ts | 13 +- .../src/data_sources/FileDataSource.ts | 152 ++ .../src/data_sources/FileLoader.ts | 88 ++ .../src/data_sources/PollingProcessor.ts | 17 +- .../src/data_sources/Requestor.ts | 22 +- server-sdk-common/src/platform/Filesystem.ts | 9 +- server-sdk-common/src/store/serialization.ts | 4 +- 98 files changed, 12652 insertions(+), 49 deletions(-) create mode 100644 docs/.nojekyll create mode 100644 docs/assets/highlight.css create mode 100644 docs/assets/main.js create mode 100644 docs/assets/search.js create mode 100644 docs/assets/style.css create mode 100644 docs/assets/widgets.png create mode 100644 docs/assets/widgets@2x.png create mode 100644 docs/classes/AttributeReference.html create mode 100644 docs/classes/BasicLogger.html create mode 100644 docs/classes/BigSegmentStoreStatusProviderImpl.html create mode 100644 docs/classes/Context.html create mode 100644 docs/classes/ContextFilter.html create mode 100644 docs/classes/DateValidator.html create mode 100644 docs/classes/FactoryOrInstance.html create mode 100644 docs/classes/Function.html create mode 100644 docs/classes/LDClientImpl.html create mode 100644 docs/classes/NumberWithMinimum.html create mode 100644 docs/classes/SafeLogger.html create mode 100644 docs/classes/StringMatchingRegex.html create mode 100644 docs/classes/Type.html create mode 100644 docs/classes/TypeArray.html create mode 100644 docs/classes/TypeValidators.html create mode 100644 docs/index.html create mode 100644 docs/interfaces/BasicLoggerOptions.html create mode 100644 docs/interfaces/LDBigSegmentsOptions.html create mode 100644 docs/interfaces/LDClient.html create mode 100644 docs/interfaces/LDContextCommon.html create mode 100644 docs/interfaces/LDContextMeta.html create mode 100644 docs/interfaces/LDEvaluationDetail.html create mode 100644 docs/interfaces/LDEvaluationReason.html create mode 100644 docs/interfaces/LDFlagSet.html create mode 100644 docs/interfaces/LDFlagsState.html create mode 100644 docs/interfaces/LDFlagsStateOptions.html create mode 100644 docs/interfaces/LDLogger.html create mode 100644 docs/interfaces/LDMultiKindContext.html create mode 100644 docs/interfaces/LDOptions.html create mode 100644 docs/interfaces/LDProxyOptions.html create mode 100644 docs/interfaces/LDSingleKindContext.html create mode 100644 docs/interfaces/LDTLSOptions.html create mode 100644 docs/interfaces/LDUser.html create mode 100644 docs/interfaces/TypeValidator.html create mode 100644 docs/interfaces/integrations.FileDataSourceOptions.html create mode 100644 docs/interfaces/integrations.TestData.html create mode 100644 docs/interfaces/integrations.TestDataFlagBuilder.html create mode 100644 docs/interfaces/integrations.TestDataRuleBuilder.html create mode 100644 docs/interfaces/interfaces.BigSegmentStore.html create mode 100644 docs/interfaces/interfaces.BigSegmentStoreMembership.html create mode 100644 docs/interfaces/interfaces.BigSegmentStoreMetadata.html create mode 100644 docs/interfaces/interfaces.BigSegmentStoreStatus.html create mode 100644 docs/interfaces/interfaces.BigSegmentStoreStatusProvider.html create mode 100644 docs/interfaces/interfaces.DataCollection.html create mode 100644 docs/interfaces/interfaces.DataKind.html create mode 100644 docs/interfaces/interfaces.ItemDescriptor.html create mode 100644 docs/interfaces/interfaces.PersistentDataStore.html create mode 100644 docs/interfaces/interfaces.PersistentStoreDataKind.html create mode 100644 docs/interfaces/interfaces.SerializedItemDescriptor.html create mode 100644 docs/interfaces/interfaces.VersionedData.html create mode 100644 docs/interfaces/platform.Crypto.html create mode 100644 docs/interfaces/platform.EventSource.html create mode 100644 docs/interfaces/platform.EventSourceInitDict.html create mode 100644 docs/interfaces/platform.Filesystem.html create mode 100644 docs/interfaces/platform.Hasher.html create mode 100644 docs/interfaces/platform.Headers.html create mode 100644 docs/interfaces/platform.Hmac.html create mode 100644 docs/interfaces/platform.Info.html create mode 100644 docs/interfaces/platform.Options.html create mode 100644 docs/interfaces/platform.Platform.html create mode 100644 docs/interfaces/platform.PlatformData.html create mode 100644 docs/interfaces/platform.Requests.html create mode 100644 docs/interfaces/platform.Response.html create mode 100644 docs/interfaces/platform.SdkData.html create mode 100644 docs/interfaces/subsystems.LDFeatureStore.html create mode 100644 docs/interfaces/subsystems.LDFeatureStoreDataStorage.html create mode 100644 docs/interfaces/subsystems.LDFeatureStoreItem.html create mode 100644 docs/interfaces/subsystems.LDFeatureStoreKindData.html create mode 100644 docs/interfaces/subsystems.LDKeyedFeatureStoreItem.html create mode 100644 docs/modules.html create mode 100644 docs/modules/integrations.html create mode 100644 docs/modules/interfaces.html create mode 100644 docs/modules/platform.html create mode 100644 docs/modules/subsystems.html create mode 100644 docs/types/LDContext.html create mode 100644 docs/types/LDFlagValue.html create mode 100644 docs/types/LDLogLevel.html create mode 100644 docs/types/interfaces.FullDataSet.html create mode 100644 docs/types/interfaces.KeyedItems.html create mode 100644 server-sdk-common/src/data_sources/FileDataSource.ts create mode 100644 server-sdk-common/src/data_sources/FileLoader.ts diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000000..e2ac6616ad --- /dev/null +++ b/docs/.nojekyll @@ -0,0 +1 @@ +TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/assets/highlight.css b/docs/assets/highlight.css new file mode 100644 index 0000000000..dbcebae847 --- /dev/null +++ b/docs/assets/highlight.css @@ -0,0 +1,85 @@ +:root { + --light-hl-0: #0000FF; + --dark-hl-0: #569CD6; + --light-hl-1: #000000; + --dark-hl-1: #D4D4D4; + --light-hl-2: #0070C1; + --dark-hl-2: #4FC1FF; + --light-hl-3: #795E26; + --dark-hl-3: #DCDCAA; + --light-hl-4: #001080; + --dark-hl-4: #9CDCFE; + --light-hl-5: #A31515; + --dark-hl-5: #CE9178; + --light-hl-6: #008000; + --dark-hl-6: #6A9955; + --light-hl-7: #CD3131; + --dark-hl-7: #F44747; + --light-hl-8: #AF00DB; + --dark-hl-8: #C586C0; + --light-code-background: #FFFFFF; + --dark-code-background: #1E1E1E; +} + +@media (prefers-color-scheme: light) { :root { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --hl-6: var(--light-hl-6); + --hl-7: var(--light-hl-7); + --hl-8: var(--light-hl-8); + --code-background: var(--light-code-background); +} } + +@media (prefers-color-scheme: dark) { :root { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --hl-6: var(--dark-hl-6); + --hl-7: var(--dark-hl-7); + --hl-8: var(--dark-hl-8); + --code-background: var(--dark-code-background); +} } + +:root[data-theme='light'] { + --hl-0: var(--light-hl-0); + --hl-1: var(--light-hl-1); + --hl-2: var(--light-hl-2); + --hl-3: var(--light-hl-3); + --hl-4: var(--light-hl-4); + --hl-5: var(--light-hl-5); + --hl-6: var(--light-hl-6); + --hl-7: var(--light-hl-7); + --hl-8: var(--light-hl-8); + --code-background: var(--light-code-background); +} + +:root[data-theme='dark'] { + --hl-0: var(--dark-hl-0); + --hl-1: var(--dark-hl-1); + --hl-2: var(--dark-hl-2); + --hl-3: var(--dark-hl-3); + --hl-4: var(--dark-hl-4); + --hl-5: var(--dark-hl-5); + --hl-6: var(--dark-hl-6); + --hl-7: var(--dark-hl-7); + --hl-8: var(--dark-hl-8); + --code-background: var(--dark-code-background); +} + +.hl-0 { color: var(--hl-0); } +.hl-1 { color: var(--hl-1); } +.hl-2 { color: var(--hl-2); } +.hl-3 { color: var(--hl-3); } +.hl-4 { color: var(--hl-4); } +.hl-5 { color: var(--hl-5); } +.hl-6 { color: var(--hl-6); } +.hl-7 { color: var(--hl-7); } +.hl-8 { color: var(--hl-8); } +pre, code { background: var(--code-background); } diff --git a/docs/assets/main.js b/docs/assets/main.js new file mode 100644 index 0000000000..c815b33495 --- /dev/null +++ b/docs/assets/main.js @@ -0,0 +1,54 @@ +"use strict"; +(()=>{var Qe=Object.create;var ae=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var Ce=Object.getOwnPropertyNames;var Oe=Object.getPrototypeOf,Re=Object.prototype.hasOwnProperty;var _e=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var Me=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Ce(e))!Re.call(t,i)&&i!==n&&ae(t,i,{get:()=>e[i],enumerable:!(r=Pe(e,i))||r.enumerable});return t};var De=(t,e,n)=>(n=t!=null?Qe(Oe(t)):{},Me(e||!t||!t.__esModule?ae(n,"default",{value:t,enumerable:!0}):n,t));var de=_e((ce,he)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(n){e.console&&console.warn&&console.warn(n)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var h=t.utils.clone(n)||{};h.position=[a,l],h.index=s.length,s.push(new t.Token(r.slice(a,o),h))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ou?h+=2:a==u&&(n+=r[l+1]*i[h+1],l+=2,h+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var h=s.str.charAt(0),m=s.str.charAt(1),v;m in s.node.edges?v=s.node.edges[m]:(v=new t.TokenSet,s.node.edges[m]=v),s.str.length==1&&(v.final=!0),i.push({node:v,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,n){typeof define=="function"&&define.amd?define(n):typeof ce=="object"?he.exports=n():e.lunr=n()}(this,function(){return t})})()});var le=[];function j(t,e){le.push({selector:e,constructor:t})}var Y=class{constructor(){this.createComponents(document.body)}createComponents(e){le.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r}),r.dataset.hasInstance=String(!0))})})}};var k=class{constructor(e){this.el=e.el}};var J=class{constructor(){this.listeners={}}addEventListener(e,n){e in this.listeners||(this.listeners[e]=[]),this.listeners[e].push(n)}removeEventListener(e,n){if(!(e in this.listeners))return;let r=this.listeners[e];for(let i=0,s=r.length;i{let n=Date.now();return(...r)=>{n+e-Date.now()<0&&(t(...r),n=Date.now())}};var re=class extends J{constructor(){super();this.scrollTop=0;this.lastY=0;this.width=0;this.height=0;this.showToolbar=!0;this.toolbar=document.querySelector(".tsd-page-toolbar"),this.navigation=document.querySelector(".col-menu"),window.addEventListener("scroll",ne(()=>this.onScroll(),10)),window.addEventListener("resize",ne(()=>this.onResize(),10)),this.searchInput=document.querySelector("#tsd-search input"),this.searchInput&&this.searchInput.addEventListener("focus",()=>{this.hideShowToolbar()}),this.onResize(),this.onScroll()}triggerResize(){let n=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(n)}onResize(){this.width=window.innerWidth||0,this.height=window.innerHeight||0;let n=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(n)}onScroll(){this.scrollTop=window.scrollY||0;let n=new CustomEvent("scroll",{detail:{scrollTop:this.scrollTop}});this.dispatchEvent(n),this.hideShowToolbar()}hideShowToolbar(){let n=this.showToolbar;this.showToolbar=this.lastY>=this.scrollTop||this.scrollTop<=0||!!this.searchInput&&this.searchInput===document.activeElement,n!==this.showToolbar&&(this.toolbar.classList.toggle("tsd-page-toolbar--hide"),this.navigation?.classList.toggle("col-menu--hide")),this.lastY=this.scrollTop}},R=re;R.instance=new re;var X=class extends k{constructor(n){super(n);this.anchors=[];this.index=-1;R.instance.addEventListener("resize",()=>this.onResize()),R.instance.addEventListener("scroll",r=>this.onScroll(r)),this.createAnchors()}createAnchors(){let n=window.location.href;n.indexOf("#")!=-1&&(n=n.substring(0,n.indexOf("#"))),this.el.querySelectorAll("a").forEach(r=>{let i=r.href;if(i.indexOf("#")==-1||i.substring(0,n.length)!=n)return;let s=i.substring(i.indexOf("#")+1),o=document.querySelector("a.tsd-anchor[name="+s+"]"),a=r.parentNode;!o||!a||this.anchors.push({link:a,anchor:o,position:0})}),this.onResize()}onResize(){let n;for(let i=0,s=this.anchors.length;ii.position-s.position);let r=new CustomEvent("scroll",{detail:{scrollTop:R.instance.scrollTop}});this.onScroll(r)}onScroll(n){let r=n.detail.scrollTop+5,i=this.anchors,s=i.length-1,o=this.index;for(;o>-1&&i[o].position>r;)o-=1;for(;o-1&&this.anchors[this.index].link.classList.remove("focus"),this.index=o,this.index>-1&&this.anchors[this.index].link.classList.add("focus"))}};var ue=(t,e=100)=>{let n;return(...r)=>{clearTimeout(n),n=setTimeout(()=>t(r),e)}};var me=De(de());function ve(){let t=document.getElementById("tsd-search");if(!t)return;let e=document.getElementById("search-script");t.classList.add("loading"),e&&(e.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),e.addEventListener("load",()=>{t.classList.remove("loading"),t.classList.add("ready")}),window.searchData&&t.classList.remove("loading"));let n=document.querySelector("#tsd-search input"),r=document.querySelector("#tsd-search .results");if(!n||!r)throw new Error("The input field or the result list wrapper was not found");let i=!1;r.addEventListener("mousedown",()=>i=!0),r.addEventListener("mouseup",()=>{i=!1,t.classList.remove("has-focus")}),n.addEventListener("focus",()=>t.classList.add("has-focus")),n.addEventListener("blur",()=>{i||(i=!1,t.classList.remove("has-focus"))});let s={base:t.dataset.base+"/"};Fe(t,r,n,s)}function Fe(t,e,n,r){n.addEventListener("input",ue(()=>{Ae(t,e,n,r)},200));let i=!1;n.addEventListener("keydown",s=>{i=!0,s.key=="Enter"?Ve(e,n):s.key=="Escape"?n.blur():s.key=="ArrowUp"?fe(e,-1):s.key==="ArrowDown"?fe(e,1):i=!1}),n.addEventListener("keypress",s=>{i&&s.preventDefault()}),document.body.addEventListener("keydown",s=>{s.altKey||s.ctrlKey||s.metaKey||!n.matches(":focus")&&s.key==="/"&&(n.focus(),s.preventDefault())})}function He(t,e){t.index||window.searchData&&(e.classList.remove("loading"),e.classList.add("ready"),t.data=window.searchData,t.index=me.Index.load(window.searchData.index))}function Ae(t,e,n,r){if(He(r,t),!r.index||!r.data)return;e.textContent="";let i=n.value.trim(),s=i?r.index.search(`*${i}*`):[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o${pe(u.parent,i)}.${l}`);let h=document.createElement("li");h.classList.value=u.classes??"";let m=document.createElement("a");m.href=r.base+u.url,m.innerHTML=l,h.append(m),e.appendChild(h)}}function fe(t,e){let n=t.querySelector(".current");if(!n)n=t.querySelector(e==1?"li:first-child":"li:last-child"),n&&n.classList.add("current");else{let r=n;if(e===1)do r=r.nextElementSibling??void 0;while(r instanceof HTMLElement&&r.offsetParent==null);else do r=r.previousElementSibling??void 0;while(r instanceof HTMLElement&&r.offsetParent==null);r&&(n.classList.remove("current"),r.classList.add("current"))}}function Ve(t,e){let n=t.querySelector(".current");if(n||(n=t.querySelector("li:first-child")),n){let r=n.querySelector("a");r&&(window.location.href=r.href),e.blur()}}function pe(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(ie(t.substring(s,o)),`${ie(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(ie(t.substring(s))),i.join("")}var Ne={"&":"&","<":"<",">":">","'":"'",'"':"""};function ie(t){return t.replace(/[&<>"'"]/g,e=>Ne[e])}var F="mousedown",ye="mousemove",B="mouseup",Z={x:0,y:0},ge=!1,se=!1,je=!1,H=!1,xe=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(xe?"is-mobile":"not-mobile");xe&&"ontouchstart"in document.documentElement&&(je=!0,F="touchstart",ye="touchmove",B="touchend");document.addEventListener(F,t=>{se=!0,H=!1;let e=F=="touchstart"?t.targetTouches[0]:t;Z.y=e.pageY||0,Z.x=e.pageX||0});document.addEventListener(ye,t=>{if(!!se&&!H){let e=F=="touchstart"?t.targetTouches[0]:t,n=Z.x-(e.pageX||0),r=Z.y-(e.pageY||0);H=Math.sqrt(n*n+r*r)>10}});document.addEventListener(B,()=>{se=!1});document.addEventListener("click",t=>{ge&&(t.preventDefault(),t.stopImmediatePropagation(),ge=!1)});var K=class extends k{constructor(n){super(n);this.className=this.el.dataset.toggle||"",this.el.addEventListener(B,r=>this.onPointerUp(r)),this.el.addEventListener("click",r=>r.preventDefault()),document.addEventListener(F,r=>this.onDocumentPointerDown(r)),document.addEventListener(B,r=>this.onDocumentPointerUp(r))}setActive(n){if(this.active==n)return;this.active=n,document.documentElement.classList.toggle("has-"+this.className,n),this.el.classList.toggle("active",n);let r=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(r),setTimeout(()=>document.documentElement.classList.remove(r),500)}onPointerUp(n){H||(this.setActive(!0),n.preventDefault())}onDocumentPointerDown(n){if(this.active){if(n.target.closest(".col-menu, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(n){if(!H&&this.active&&n.target.closest(".col-menu")){let r=n.target.closest("a");if(r){let i=window.location.href;i.indexOf("#")!=-1&&(i=i.substring(0,i.indexOf("#"))),r.href.substring(0,i.length)==i&&setTimeout(()=>this.setActive(!1),250)}}}};var oe;try{oe=localStorage}catch{oe={getItem(){return null},setItem(){}}}var Q=oe;var Le=document.head.appendChild(document.createElement("style"));Le.dataset.for="filters";var ee=class extends k{constructor(n){super(n);this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),Le.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } +`}fromLocalStorage(){let n=Q.getItem(this.key);return n?n==="true":this.el.checked}setLocalStorage(n){Q.setItem(this.key,n.toString()),this.value=n,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),document.querySelectorAll(".tsd-index-section").forEach(n=>{n.style.display="block";let r=Array.from(n.querySelectorAll(".tsd-index-link")).every(i=>i.offsetParent==null);n.style.display=r?"none":"block"})}};var te=class extends k{constructor(n){super(n);this.calculateHeights(),this.summary=this.el.querySelector(".tsd-accordion-summary"),this.icon=this.summary.querySelector("svg"),this.key=`tsd-accordion-${this.summary.textContent.replace(/\s+/g,"-").toLowerCase()}`,this.setLocalStorage(this.fromLocalStorage(),!0),this.summary.addEventListener("click",r=>this.toggleVisibility(r)),this.icon.style.transform=this.getIconRotation()}getIconRotation(n=this.el.open){return`rotate(${n?0:-90}deg)`}calculateHeights(){let n=this.el.open,{position:r,left:i}=this.el.style;this.el.style.position="fixed",this.el.style.left="-9999px",this.el.open=!0,this.expandedHeight=this.el.offsetHeight+"px",this.el.open=!1,this.collapsedHeight=this.el.offsetHeight+"px",this.el.open=n,this.el.style.height=n?this.expandedHeight:this.collapsedHeight,this.el.style.position=r,this.el.style.left=i}toggleVisibility(n){n.preventDefault(),this.el.style.overflow="hidden",this.el.open?this.collapse():this.expand()}expand(n=!0){this.el.open=!0,this.animate(this.collapsedHeight,this.expandedHeight,{opening:!0,duration:n?300:0})}collapse(n=!0){this.animate(this.expandedHeight,this.collapsedHeight,{opening:!1,duration:n?300:0})}animate(n,r,{opening:i,duration:s=300}){if(this.animation)return;let o={duration:s,easing:"ease"};this.animation=this.el.animate({height:[n,r]},o),this.icon.animate({transform:[this.icon.style.transform||this.getIconRotation(!i),this.getIconRotation(i)]},o).addEventListener("finish",()=>{this.icon.style.transform=this.getIconRotation(i)}),this.animation.addEventListener("finish",()=>this.animationEnd(i))}animationEnd(n){this.el.open=n,this.animation=void 0,this.el.style.height="auto",this.el.style.overflow="visible",this.setLocalStorage(n)}fromLocalStorage(){let n=Q.getItem(this.key);return n?n==="true":this.el.open}setLocalStorage(n,r=!1){this.fromLocalStorage()===n&&!r||(Q.setItem(this.key,n.toString()),this.el.open=n,this.handleValueChange(r))}handleValueChange(n=!1){this.fromLocalStorage()===this.el.open&&!n||(this.fromLocalStorage()?this.expand(!1):this.collapse(!1))}};function be(t){let e=Q.getItem("tsd-theme")||"os";t.value=e,Ee(e),t.addEventListener("change",()=>{Q.setItem("tsd-theme",t.value),Ee(t.value)})}function Ee(t){document.documentElement.dataset.theme=t}ve();j(X,".menu-highlight");j(K,"a[data-toggle]");j(te,".tsd-index-accordion");j(ee,".tsd-filter-item input[type=checkbox]");var Se=document.getElementById("theme");Se&&be(Se);var Be=new Y;Object.defineProperty(window,"app",{value:Be});})(); +/*! + * lunr.Builder + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Index + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Pipeline + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Set + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.TokenSet + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.Vector + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.stemmer + * Copyright (C) 2020 Oliver Nightingale + * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt + */ +/*! + * lunr.stopWordFilter + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.tokenizer + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.trimmer + * Copyright (C) 2020 Oliver Nightingale + */ +/*! + * lunr.utils + * Copyright (C) 2020 Oliver Nightingale + */ +/** + * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 + * Copyright (C) 2020 Oliver Nightingale + * @license MIT + */ diff --git a/docs/assets/search.js b/docs/assets/search.js new file mode 100644 index 0000000000..34fa9bc946 --- /dev/null +++ b/docs/assets/search.js @@ -0,0 +1 @@ +window.searchData = JSON.parse("{\"kinds\":{\"4\":\"Namespace\",\"128\":\"Class\",\"256\":\"Interface\",\"512\":\"Constructor\",\"1024\":\"Property\",\"2048\":\"Method\",\"65536\":\"Type literal\",\"262144\":\"Accessor\",\"4194304\":\"Type alias\"},\"rows\":[{\"kind\":4,\"name\":\"platform\",\"url\":\"modules/platform.html\",\"classes\":\"tsd-kind-namespace\"},{\"kind\":256,\"name\":\"Hasher\",\"url\":\"interfaces/platform.Hasher.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"update\",\"url\":\"interfaces/platform.Hasher.html#update\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Hasher\"},{\"kind\":2048,\"name\":\"digest\",\"url\":\"interfaces/platform.Hasher.html#digest\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Hasher\"},{\"kind\":256,\"name\":\"Hmac\",\"url\":\"interfaces/platform.Hmac.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"update\",\"url\":\"interfaces/platform.Hmac.html#update\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Hmac\"},{\"kind\":2048,\"name\":\"digest\",\"url\":\"interfaces/platform.Hmac.html#digest\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Hmac\"},{\"kind\":256,\"name\":\"Crypto\",\"url\":\"interfaces/platform.Crypto.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"createHash\",\"url\":\"interfaces/platform.Crypto.html#createHash\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Crypto\"},{\"kind\":2048,\"name\":\"createHmac\",\"url\":\"interfaces/platform.Crypto.html#createHmac\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Crypto\"},{\"kind\":256,\"name\":\"Filesystem\",\"url\":\"interfaces/platform.Filesystem.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"getFileTimestamp\",\"url\":\"interfaces/platform.Filesystem.html#getFileTimestamp\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Filesystem\"},{\"kind\":2048,\"name\":\"readFile\",\"url\":\"interfaces/platform.Filesystem.html#readFile\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Filesystem\"},{\"kind\":2048,\"name\":\"watch\",\"url\":\"interfaces/platform.Filesystem.html#watch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Filesystem\"},{\"kind\":256,\"name\":\"PlatformData\",\"url\":\"interfaces/platform.PlatformData.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"os\",\"url\":\"interfaces/platform.PlatformData.html#os\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.PlatformData\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/platform.PlatformData.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"platform.PlatformData\"},{\"kind\":1024,\"name\":\"arch\",\"url\":\"interfaces/platform.PlatformData.html#__type.arch\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"platform.PlatformData.__type\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/platform.PlatformData.html#__type.name-1\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"platform.PlatformData.__type\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/platform.PlatformData.html#__type.version\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"platform.PlatformData.__type\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/platform.PlatformData.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.PlatformData\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/platform.PlatformData.html#version-1\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.PlatformData\"},{\"kind\":1024,\"name\":\"additional\",\"url\":\"interfaces/platform.PlatformData.html#additional\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.PlatformData\"},{\"kind\":256,\"name\":\"SdkData\",\"url\":\"interfaces/platform.SdkData.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/platform.SdkData.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.SdkData\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/platform.SdkData.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.SdkData\"},{\"kind\":1024,\"name\":\"wrapperName\",\"url\":\"interfaces/platform.SdkData.html#wrapperName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.SdkData\"},{\"kind\":1024,\"name\":\"wrapperVersion\",\"url\":\"interfaces/platform.SdkData.html#wrapperVersion\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.SdkData\"},{\"kind\":256,\"name\":\"Info\",\"url\":\"interfaces/platform.Info.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"platformData\",\"url\":\"interfaces/platform.Info.html#platformData\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Info\"},{\"kind\":2048,\"name\":\"sdkData\",\"url\":\"interfaces/platform.Info.html#sdkData\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Info\"},{\"kind\":256,\"name\":\"Platform\",\"url\":\"interfaces/platform.Platform.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"info\",\"url\":\"interfaces/platform.Platform.html#info\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Platform\"},{\"kind\":1024,\"name\":\"fileSystem\",\"url\":\"interfaces/platform.Platform.html#fileSystem\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Platform\"},{\"kind\":1024,\"name\":\"crypto\",\"url\":\"interfaces/platform.Platform.html#crypto\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Platform\"},{\"kind\":1024,\"name\":\"requests\",\"url\":\"interfaces/platform.Platform.html#requests\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Platform\"},{\"kind\":256,\"name\":\"Headers\",\"url\":\"interfaces/platform.Headers.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"get\",\"url\":\"interfaces/platform.Headers.html#get\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Headers\"},{\"kind\":2048,\"name\":\"keys\",\"url\":\"interfaces/platform.Headers.html#keys\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Headers\"},{\"kind\":2048,\"name\":\"values\",\"url\":\"interfaces/platform.Headers.html#values\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Headers\"},{\"kind\":2048,\"name\":\"entries\",\"url\":\"interfaces/platform.Headers.html#entries\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Headers\"},{\"kind\":2048,\"name\":\"has\",\"url\":\"interfaces/platform.Headers.html#has\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Headers\"},{\"kind\":256,\"name\":\"Response\",\"url\":\"interfaces/platform.Response.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"headers\",\"url\":\"interfaces/platform.Response.html#headers\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Response\"},{\"kind\":1024,\"name\":\"status\",\"url\":\"interfaces/platform.Response.html#status\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Response\"},{\"kind\":2048,\"name\":\"text\",\"url\":\"interfaces/platform.Response.html#text\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Response\"},{\"kind\":2048,\"name\":\"json\",\"url\":\"interfaces/platform.Response.html#json\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Response\"},{\"kind\":256,\"name\":\"Options\",\"url\":\"interfaces/platform.Options.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"headers\",\"url\":\"interfaces/platform.Options.html#headers\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Options\"},{\"kind\":1024,\"name\":\"method\",\"url\":\"interfaces/platform.Options.html#method\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Options\"},{\"kind\":1024,\"name\":\"body\",\"url\":\"interfaces/platform.Options.html#body\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Options\"},{\"kind\":1024,\"name\":\"timeout\",\"url\":\"interfaces/platform.Options.html#timeout\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Options\"},{\"kind\":256,\"name\":\"Requests\",\"url\":\"interfaces/platform.Requests.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"fetch\",\"url\":\"interfaces/platform.Requests.html#fetch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Requests\"},{\"kind\":2048,\"name\":\"createEventSource\",\"url\":\"interfaces/platform.Requests.html#createEventSource\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Requests\"},{\"kind\":256,\"name\":\"EventSource\",\"url\":\"interfaces/platform.EventSource.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"onclose\",\"url\":\"interfaces/platform.EventSource.html#onclose\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":1024,\"name\":\"onerror\",\"url\":\"interfaces/platform.EventSource.html#onerror\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":1024,\"name\":\"onopen\",\"url\":\"interfaces/platform.EventSource.html#onopen\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":1024,\"name\":\"onretrying\",\"url\":\"interfaces/platform.EventSource.html#onretrying\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":2048,\"name\":\"addEventListener\",\"url\":\"interfaces/platform.EventSource.html#addEventListener\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"interfaces/platform.EventSource.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":256,\"name\":\"EventSourceInitDict\",\"url\":\"interfaces/platform.EventSourceInitDict.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"errorFilter\",\"url\":\"interfaces/platform.EventSourceInitDict.html#errorFilter\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/platform.EventSourceInitDict.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":1024,\"name\":\"headers\",\"url\":\"interfaces/platform.EventSourceInitDict.html#headers\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/platform.EventSourceInitDict.html#__type-2\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":1024,\"name\":\"initialRetryDelayMillis\",\"url\":\"interfaces/platform.EventSourceInitDict.html#initialRetryDelayMillis\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":1024,\"name\":\"readTimeoutMillis\",\"url\":\"interfaces/platform.EventSourceInitDict.html#readTimeoutMillis\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":1024,\"name\":\"retryResetIntervalMillis\",\"url\":\"interfaces/platform.EventSourceInitDict.html#retryResetIntervalMillis\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":128,\"name\":\"LDClientImpl\",\"url\":\"classes/LDClientImpl.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/LDClientImpl.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":1024,\"name\":\"platform\",\"url\":\"classes/LDClientImpl.html#platform\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"LDClientImpl\"},{\"kind\":1024,\"name\":\"initState\",\"url\":\"classes/LDClientImpl.html#initState\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"LDClientImpl\"},{\"kind\":1024,\"name\":\"bigSegmentStoreStatusProvider\",\"url\":\"classes/LDClientImpl.html#bigSegmentStoreStatusProvider\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"initialized\",\"url\":\"classes/LDClientImpl.html#initialized\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"waitForInitialization\",\"url\":\"classes/LDClientImpl.html#waitForInitialization\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"variation\",\"url\":\"classes/LDClientImpl.html#variation\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"variationDetail\",\"url\":\"classes/LDClientImpl.html#variationDetail\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"allFlagsState\",\"url\":\"classes/LDClientImpl.html#allFlagsState\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"secureModeHash\",\"url\":\"classes/LDClientImpl.html#secureModeHash\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"classes/LDClientImpl.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"isOffline\",\"url\":\"classes/LDClientImpl.html#isOffline\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"track\",\"url\":\"classes/LDClientImpl.html#track\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"identify\",\"url\":\"classes/LDClientImpl.html#identify\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"flush\",\"url\":\"classes/LDClientImpl.html#flush\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"on\",\"url\":\"classes/LDClientImpl.html#on\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":128,\"name\":\"BigSegmentStoreStatusProviderImpl\",\"url\":\"classes/BigSegmentStoreStatusProviderImpl.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/BigSegmentStoreStatusProviderImpl.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"BigSegmentStoreStatusProviderImpl\"},{\"kind\":2048,\"name\":\"getStatus\",\"url\":\"classes/BigSegmentStoreStatusProviderImpl.html#getStatus\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BigSegmentStoreStatusProviderImpl\"},{\"kind\":2048,\"name\":\"requireStatus\",\"url\":\"classes/BigSegmentStoreStatusProviderImpl.html#requireStatus\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BigSegmentStoreStatusProviderImpl\"},{\"kind\":2048,\"name\":\"dispatch\",\"url\":\"classes/BigSegmentStoreStatusProviderImpl.html#dispatch\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BigSegmentStoreStatusProviderImpl\"},{\"kind\":4,\"name\":\"integrations\",\"url\":\"modules/integrations.html\",\"classes\":\"tsd-kind-namespace\"},{\"kind\":256,\"name\":\"FileDataSourceOptions\",\"url\":\"interfaces/integrations.FileDataSourceOptions.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"integrations\"},{\"kind\":1024,\"name\":\"paths\",\"url\":\"interfaces/integrations.FileDataSourceOptions.html#paths\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"integrations.FileDataSourceOptions\"},{\"kind\":1024,\"name\":\"autoUpdate\",\"url\":\"interfaces/integrations.FileDataSourceOptions.html#autoUpdate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"integrations.FileDataSourceOptions\"},{\"kind\":1024,\"name\":\"logger\",\"url\":\"interfaces/integrations.FileDataSourceOptions.html#logger\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"integrations.FileDataSourceOptions\"},{\"kind\":256,\"name\":\"TestData\",\"url\":\"interfaces/integrations.TestData.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"integrations\"},{\"kind\":2048,\"name\":\"flag\",\"url\":\"interfaces/integrations.TestData.html#flag\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestData\"},{\"kind\":2048,\"name\":\"update\",\"url\":\"interfaces/integrations.TestData.html#update\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestData\"},{\"kind\":2048,\"name\":\"usePreconfiguredFlag\",\"url\":\"interfaces/integrations.TestData.html#usePreconfiguredFlag\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestData\"},{\"kind\":2048,\"name\":\"usePreconfiguredSegment\",\"url\":\"interfaces/integrations.TestData.html#usePreconfiguredSegment\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestData\"},{\"kind\":256,\"name\":\"TestDataFlagBuilder\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"integrations\"},{\"kind\":2048,\"name\":\"booleanFlag\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#booleanFlag\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"variations\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#variations\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"on\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#on\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"fallthroughVariation\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#fallthroughVariation\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"offVariation\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#offVariation\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"variationForAll\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#variationForAll\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"valueForAll\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#valueForAll\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"variationForUser\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#variationForUser\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"variationForContext\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#variationForContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"clearRules\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#clearRules\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"clearAlltargets\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#clearAlltargets\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"ifMatch\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#ifMatch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"ifNotMatch\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#ifNotMatch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":256,\"name\":\"TestDataRuleBuilder\",\"url\":\"interfaces/integrations.TestDataRuleBuilder.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"integrations\"},{\"kind\":2048,\"name\":\"andMatch\",\"url\":\"interfaces/integrations.TestDataRuleBuilder.html#andMatch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataRuleBuilder\"},{\"kind\":2048,\"name\":\"andNotMatch\",\"url\":\"interfaces/integrations.TestDataRuleBuilder.html#andNotMatch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataRuleBuilder\"},{\"kind\":2048,\"name\":\"thenReturn\",\"url\":\"interfaces/integrations.TestDataRuleBuilder.html#thenReturn\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataRuleBuilder\"},{\"kind\":4,\"name\":\"interfaces\",\"url\":\"modules/interfaces.html\",\"classes\":\"tsd-kind-namespace\"},{\"kind\":256,\"name\":\"BigSegmentStore\",\"url\":\"interfaces/interfaces.BigSegmentStore.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":2048,\"name\":\"getMetadata\",\"url\":\"interfaces/interfaces.BigSegmentStore.html#getMetadata\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStore\"},{\"kind\":2048,\"name\":\"getUserMembership\",\"url\":\"interfaces/interfaces.BigSegmentStore.html#getUserMembership\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStore\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"interfaces/interfaces.BigSegmentStore.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStore\"},{\"kind\":256,\"name\":\"BigSegmentStoreMembership\",\"url\":\"interfaces/interfaces.BigSegmentStoreMembership.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":256,\"name\":\"BigSegmentStoreMetadata\",\"url\":\"interfaces/interfaces.BigSegmentStoreMetadata.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"lastUpToDate\",\"url\":\"interfaces/interfaces.BigSegmentStoreMetadata.html#lastUpToDate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStoreMetadata\"},{\"kind\":256,\"name\":\"BigSegmentStoreStatus\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatus.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"available\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatus.html#available\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStoreStatus\"},{\"kind\":1024,\"name\":\"stale\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatus.html#stale\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStoreStatus\"},{\"kind\":256,\"name\":\"DataCollection\",\"url\":\"interfaces/interfaces.DataCollection.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"kind\",\"url\":\"interfaces/interfaces.DataCollection.html#kind\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.DataCollection\"},{\"kind\":1024,\"name\":\"items\",\"url\":\"interfaces/interfaces.DataCollection.html#items\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.DataCollection\"},{\"kind\":256,\"name\":\"DataKind\",\"url\":\"interfaces/interfaces.DataKind.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"namespace\",\"url\":\"interfaces/interfaces.DataKind.html#namespace\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.DataKind\"},{\"kind\":4194304,\"name\":\"FullDataSet\",\"url\":\"types/interfaces.FullDataSet.html\",\"classes\":\"tsd-kind-type-alias tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":4194304,\"name\":\"KeyedItems\",\"url\":\"types/interfaces.KeyedItems.html\",\"classes\":\"tsd-kind-type-alias tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":256,\"name\":\"VersionedData\",\"url\":\"interfaces/interfaces.VersionedData.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/interfaces.VersionedData.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.VersionedData\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/interfaces.VersionedData.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.VersionedData\"},{\"kind\":1024,\"name\":\"deleted\",\"url\":\"interfaces/interfaces.VersionedData.html#deleted\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.VersionedData\"},{\"kind\":256,\"name\":\"BigSegmentStoreStatusProvider\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatusProvider.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":2048,\"name\":\"getStatus\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatusProvider.html#getStatus\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStoreStatusProvider\"},{\"kind\":2048,\"name\":\"requireStatus\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatusProvider.html#requireStatus\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStoreStatusProvider\"},{\"kind\":256,\"name\":\"ItemDescriptor\",\"url\":\"interfaces/interfaces.ItemDescriptor.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/interfaces.ItemDescriptor.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.ItemDescriptor\"},{\"kind\":1024,\"name\":\"item\",\"url\":\"interfaces/interfaces.ItemDescriptor.html#item\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.ItemDescriptor\"},{\"kind\":256,\"name\":\"PersistentDataStore\",\"url\":\"interfaces/interfaces.PersistentDataStore.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":2048,\"name\":\"get\",\"url\":\"interfaces/interfaces.PersistentDataStore.html#get\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentDataStore\"},{\"kind\":2048,\"name\":\"getAll\",\"url\":\"interfaces/interfaces.PersistentDataStore.html#getAll\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentDataStore\"},{\"kind\":2048,\"name\":\"upsert\",\"url\":\"interfaces/interfaces.PersistentDataStore.html#upsert\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentDataStore\"},{\"kind\":2048,\"name\":\"initialized\",\"url\":\"interfaces/interfaces.PersistentDataStore.html#initialized\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentDataStore\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"interfaces/interfaces.PersistentDataStore.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentDataStore\"},{\"kind\":256,\"name\":\"PersistentStoreDataKind\",\"url\":\"interfaces/interfaces.PersistentStoreDataKind.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"namespace\",\"url\":\"interfaces/interfaces.PersistentStoreDataKind.html#namespace\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentStoreDataKind\"},{\"kind\":2048,\"name\":\"deserialize\",\"url\":\"interfaces/interfaces.PersistentStoreDataKind.html#deserialize\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentStoreDataKind\"},{\"kind\":256,\"name\":\"SerializedItemDescriptor\",\"url\":\"interfaces/interfaces.SerializedItemDescriptor.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/interfaces.SerializedItemDescriptor.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.SerializedItemDescriptor\"},{\"kind\":1024,\"name\":\"deleted\",\"url\":\"interfaces/interfaces.SerializedItemDescriptor.html#deleted\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.SerializedItemDescriptor\"},{\"kind\":1024,\"name\":\"serializedItem\",\"url\":\"interfaces/interfaces.SerializedItemDescriptor.html#serializedItem\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.SerializedItemDescriptor\"},{\"kind\":4,\"name\":\"subsystems\",\"url\":\"modules/subsystems.html\",\"classes\":\"tsd-kind-namespace\"},{\"kind\":256,\"name\":\"LDFeatureStoreItem\",\"url\":\"interfaces/subsystems.LDFeatureStoreItem.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"subsystems\"},{\"kind\":1024,\"name\":\"deleted\",\"url\":\"interfaces/subsystems.LDFeatureStoreItem.html#deleted\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStoreItem\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/subsystems.LDFeatureStoreItem.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStoreItem\"},{\"kind\":256,\"name\":\"LDKeyedFeatureStoreItem\",\"url\":\"interfaces/subsystems.LDKeyedFeatureStoreItem.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"subsystems\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/subsystems.LDKeyedFeatureStoreItem.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"subsystems.LDKeyedFeatureStoreItem\"},{\"kind\":1024,\"name\":\"deleted\",\"url\":\"interfaces/subsystems.LDKeyedFeatureStoreItem.html#deleted\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"subsystems.LDKeyedFeatureStoreItem\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/subsystems.LDKeyedFeatureStoreItem.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"subsystems.LDKeyedFeatureStoreItem\"},{\"kind\":256,\"name\":\"LDFeatureStoreKindData\",\"url\":\"interfaces/subsystems.LDFeatureStoreKindData.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"subsystems\"},{\"kind\":256,\"name\":\"LDFeatureStoreDataStorage\",\"url\":\"interfaces/subsystems.LDFeatureStoreDataStorage.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"subsystems\"},{\"kind\":256,\"name\":\"LDFeatureStore\",\"url\":\"interfaces/subsystems.LDFeatureStore.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"subsystems\"},{\"kind\":2048,\"name\":\"get\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#get\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"all\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#all\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"init\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#init\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"delete\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#delete\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"upsert\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#upsert\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"initialized\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#initialized\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":256,\"name\":\"LDEvaluationDetail\",\"url\":\"interfaces/LDEvaluationDetail.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"value\",\"url\":\"interfaces/LDEvaluationDetail.html#value\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationDetail\"},{\"kind\":1024,\"name\":\"variationIndex\",\"url\":\"interfaces/LDEvaluationDetail.html#variationIndex\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationDetail\"},{\"kind\":1024,\"name\":\"reason\",\"url\":\"interfaces/LDEvaluationDetail.html#reason\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationDetail\"},{\"kind\":256,\"name\":\"LDEvaluationReason\",\"url\":\"interfaces/LDEvaluationReason.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"kind\",\"url\":\"interfaces/LDEvaluationReason.html#kind\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"errorKind\",\"url\":\"interfaces/LDEvaluationReason.html#errorKind\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"ruleIndex\",\"url\":\"interfaces/LDEvaluationReason.html#ruleIndex\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"ruleId\",\"url\":\"interfaces/LDEvaluationReason.html#ruleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"prerequisiteKey\",\"url\":\"interfaces/LDEvaluationReason.html#prerequisiteKey\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"inExperiment\",\"url\":\"interfaces/LDEvaluationReason.html#inExperiment\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"bigSegmentsStatus\",\"url\":\"interfaces/LDEvaluationReason.html#bigSegmentsStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":256,\"name\":\"LDFlagSet\",\"url\":\"interfaces/LDFlagSet.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":256,\"name\":\"LDFlagsState\",\"url\":\"interfaces/LDFlagsState.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"valid\",\"url\":\"interfaces/LDFlagsState.html#valid\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDFlagsState\"},{\"kind\":2048,\"name\":\"getFlagValue\",\"url\":\"interfaces/LDFlagsState.html#getFlagValue\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDFlagsState\"},{\"kind\":2048,\"name\":\"getFlagReason\",\"url\":\"interfaces/LDFlagsState.html#getFlagReason\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDFlagsState\"},{\"kind\":2048,\"name\":\"allValues\",\"url\":\"interfaces/LDFlagsState.html#allValues\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDFlagsState\"},{\"kind\":2048,\"name\":\"toJSON\",\"url\":\"interfaces/LDFlagsState.html#toJSON\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDFlagsState\"},{\"kind\":256,\"name\":\"LDFlagsStateOptions\",\"url\":\"interfaces/LDFlagsStateOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"clientSideOnly\",\"url\":\"interfaces/LDFlagsStateOptions.html#clientSideOnly\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDFlagsStateOptions\"},{\"kind\":1024,\"name\":\"withReasons\",\"url\":\"interfaces/LDFlagsStateOptions.html#withReasons\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDFlagsStateOptions\"},{\"kind\":1024,\"name\":\"detailsOnlyForTrackedFlags\",\"url\":\"interfaces/LDFlagsStateOptions.html#detailsOnlyForTrackedFlags\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDFlagsStateOptions\"},{\"kind\":4194304,\"name\":\"LDFlagValue\",\"url\":\"types/LDFlagValue.html\",\"classes\":\"tsd-kind-type-alias\"},{\"kind\":256,\"name\":\"LDBigSegmentsOptions\",\"url\":\"interfaces/LDBigSegmentsOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"userCacheSize\",\"url\":\"interfaces/LDBigSegmentsOptions.html#userCacheSize\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDBigSegmentsOptions\"},{\"kind\":1024,\"name\":\"userCacheTime\",\"url\":\"interfaces/LDBigSegmentsOptions.html#userCacheTime\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDBigSegmentsOptions\"},{\"kind\":1024,\"name\":\"statusPollInterval\",\"url\":\"interfaces/LDBigSegmentsOptions.html#statusPollInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDBigSegmentsOptions\"},{\"kind\":1024,\"name\":\"staleAfter\",\"url\":\"interfaces/LDBigSegmentsOptions.html#staleAfter\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDBigSegmentsOptions\"},{\"kind\":256,\"name\":\"LDOptions\",\"url\":\"interfaces/LDOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"baseUri\",\"url\":\"interfaces/LDOptions.html#baseUri\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"streamUri\",\"url\":\"interfaces/LDOptions.html#streamUri\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"eventsUri\",\"url\":\"interfaces/LDOptions.html#eventsUri\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"timeout\",\"url\":\"interfaces/LDOptions.html#timeout\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"capacity\",\"url\":\"interfaces/LDOptions.html#capacity\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"logger\",\"url\":\"interfaces/LDOptions.html#logger\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"featureStore\",\"url\":\"interfaces/LDOptions.html#featureStore\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"bigSegments\",\"url\":\"interfaces/LDOptions.html#bigSegments\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"updateProcessor\",\"url\":\"interfaces/LDOptions.html#updateProcessor\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"flushInterval\",\"url\":\"interfaces/LDOptions.html#flushInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"pollInterval\",\"url\":\"interfaces/LDOptions.html#pollInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"proxyOptions\",\"url\":\"interfaces/LDOptions.html#proxyOptions\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"offline\",\"url\":\"interfaces/LDOptions.html#offline\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"stream\",\"url\":\"interfaces/LDOptions.html#stream\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"streamInitialReconnectDelay\",\"url\":\"interfaces/LDOptions.html#streamInitialReconnectDelay\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"useLdd\",\"url\":\"interfaces/LDOptions.html#useLdd\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"sendEvents\",\"url\":\"interfaces/LDOptions.html#sendEvents\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"allAttributesPrivate\",\"url\":\"interfaces/LDOptions.html#allAttributesPrivate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"privateAttributes\",\"url\":\"interfaces/LDOptions.html#privateAttributes\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"contextKeysCapacity\",\"url\":\"interfaces/LDOptions.html#contextKeysCapacity\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"contextKeysFlushInterval\",\"url\":\"interfaces/LDOptions.html#contextKeysFlushInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"tlsParams\",\"url\":\"interfaces/LDOptions.html#tlsParams\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"diagnosticOptOut\",\"url\":\"interfaces/LDOptions.html#diagnosticOptOut\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"diagnosticRecordingInterval\",\"url\":\"interfaces/LDOptions.html#diagnosticRecordingInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"wrapperName\",\"url\":\"interfaces/LDOptions.html#wrapperName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"wrapperVersion\",\"url\":\"interfaces/LDOptions.html#wrapperVersion\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"application\",\"url\":\"interfaces/LDOptions.html#application\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/LDOptions.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"id\",\"url\":\"interfaces/LDOptions.html#__type.id\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"LDOptions.__type\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/LDOptions.html#__type.version\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"LDOptions.__type\"},{\"kind\":256,\"name\":\"LDProxyOptions\",\"url\":\"interfaces/LDProxyOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"host\",\"url\":\"interfaces/LDProxyOptions.html#host\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDProxyOptions\"},{\"kind\":1024,\"name\":\"port\",\"url\":\"interfaces/LDProxyOptions.html#port\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDProxyOptions\"},{\"kind\":1024,\"name\":\"scheme\",\"url\":\"interfaces/LDProxyOptions.html#scheme\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDProxyOptions\"},{\"kind\":1024,\"name\":\"auth\",\"url\":\"interfaces/LDProxyOptions.html#auth\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDProxyOptions\"},{\"kind\":256,\"name\":\"LDTLSOptions\",\"url\":\"interfaces/LDTLSOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"ca\",\"url\":\"interfaces/LDTLSOptions.html#ca\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"cert\",\"url\":\"interfaces/LDTLSOptions.html#cert\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"checkServerIdentity\",\"url\":\"interfaces/LDTLSOptions.html#checkServerIdentity\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/LDTLSOptions.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"ciphers\",\"url\":\"interfaces/LDTLSOptions.html#ciphers\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"pfx\",\"url\":\"interfaces/LDTLSOptions.html#pfx\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/LDTLSOptions.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"passphrase\",\"url\":\"interfaces/LDTLSOptions.html#passphrase\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"rejectUnauthorized\",\"url\":\"interfaces/LDTLSOptions.html#rejectUnauthorized\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"secureProtocol\",\"url\":\"interfaces/LDTLSOptions.html#secureProtocol\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"servername\",\"url\":\"interfaces/LDTLSOptions.html#servername\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":256,\"name\":\"LDClient\",\"url\":\"interfaces/LDClient.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":2048,\"name\":\"initialized\",\"url\":\"interfaces/LDClient.html#initialized\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"waitForInitialization\",\"url\":\"interfaces/LDClient.html#waitForInitialization\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"variation\",\"url\":\"interfaces/LDClient.html#variation\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"variationDetail\",\"url\":\"interfaces/LDClient.html#variationDetail\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"allFlagsState\",\"url\":\"interfaces/LDClient.html#allFlagsState\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"secureModeHash\",\"url\":\"interfaces/LDClient.html#secureModeHash\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"interfaces/LDClient.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"isOffline\",\"url\":\"interfaces/LDClient.html#isOffline\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"track\",\"url\":\"interfaces/LDClient.html#track\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"identify\",\"url\":\"interfaces/LDClient.html#identify\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"flush\",\"url\":\"interfaces/LDClient.html#flush\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"on\",\"url\":\"interfaces/LDClient.html#on\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":128,\"name\":\"AttributeReference\",\"url\":\"classes/AttributeReference.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/AttributeReference.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":1024,\"name\":\"isValid\",\"url\":\"classes/AttributeReference.html#isValid\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":1024,\"name\":\"redactionName\",\"url\":\"classes/AttributeReference.html#redactionName\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":1024,\"name\":\"components\",\"url\":\"classes/AttributeReference.html#components\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"AttributeReference\"},{\"kind\":2048,\"name\":\"get\",\"url\":\"classes/AttributeReference.html#get\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":2048,\"name\":\"getComponent\",\"url\":\"classes/AttributeReference.html#getComponent\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":262144,\"name\":\"depth\",\"url\":\"classes/AttributeReference.html#depth\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":262144,\"name\":\"isKind\",\"url\":\"classes/AttributeReference.html#isKind\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":2048,\"name\":\"compare\",\"url\":\"classes/AttributeReference.html#compare\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":128,\"name\":\"Context\",\"url\":\"classes/Context.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":1024,\"name\":\"userKind\",\"url\":\"classes/Context.html#userKind\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"getValueFromContext\",\"url\":\"classes/Context.html#getValueFromContext\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"fromMultiKindContext\",\"url\":\"classes/Context.html#fromMultiKindContext\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"fromSingleKindContext\",\"url\":\"classes/Context.html#fromSingleKindContext\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"fromLegacyUser\",\"url\":\"classes/Context.html#fromLegacyUser\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"fromLDContext\",\"url\":\"classes/Context.html#fromLDContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/Context.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"context\",\"url\":\"classes/Context.html#context\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"isMulti\",\"url\":\"classes/Context.html#isMulti\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"isUser\",\"url\":\"classes/Context.html#isUser\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"wasLegacy\",\"url\":\"classes/Context.html#wasLegacy\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"contexts\",\"url\":\"classes/Context.html#contexts\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"privateAttributeReferences\",\"url\":\"classes/Context.html#privateAttributeReferences\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"kind\",\"url\":\"classes/Context.html#kind\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"contextForKind\",\"url\":\"classes/Context.html#contextForKind\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"valueForKind\",\"url\":\"classes/Context.html#valueForKind\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"key\",\"url\":\"classes/Context.html#key\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"secondary\",\"url\":\"classes/Context.html#secondary\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":262144,\"name\":\"isMultiKind\",\"url\":\"classes/Context.html#isMultiKind\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":262144,\"name\":\"canonicalKey\",\"url\":\"classes/Context.html#canonicalKey\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":262144,\"name\":\"kinds\",\"url\":\"classes/Context.html#kinds\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":262144,\"name\":\"kindsAndKeys\",\"url\":\"classes/Context.html#kindsAndKeys\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"privateAttributes\",\"url\":\"classes/Context.html#privateAttributes\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"getContexts\",\"url\":\"classes/Context.html#getContexts\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":262144,\"name\":\"legacy\",\"url\":\"classes/Context.html#legacy\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":128,\"name\":\"ContextFilter\",\"url\":\"classes/ContextFilter.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/ContextFilter.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"ContextFilter\"},{\"kind\":1024,\"name\":\"allAttributesPrivate\",\"url\":\"classes/ContextFilter.html#allAttributesPrivate\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"ContextFilter\"},{\"kind\":1024,\"name\":\"privateAttributes\",\"url\":\"classes/ContextFilter.html#privateAttributes\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"ContextFilter\"},{\"kind\":2048,\"name\":\"filter\",\"url\":\"classes/ContextFilter.html#filter\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"ContextFilter\"},{\"kind\":1024,\"name\":\"getAttributesToFilter\",\"url\":\"classes/ContextFilter.html#getAttributesToFilter\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"ContextFilter\"},{\"kind\":1024,\"name\":\"filterSingleKind\",\"url\":\"classes/ContextFilter.html#filterSingleKind\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"ContextFilter\"},{\"kind\":256,\"name\":\"LDContextCommon\",\"url\":\"interfaces/LDContextCommon.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/LDContextCommon.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextCommon\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/LDContextCommon.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextCommon\"},{\"kind\":1024,\"name\":\"_meta\",\"url\":\"interfaces/LDContextCommon.html#_meta\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextCommon\"},{\"kind\":1024,\"name\":\"anonymous\",\"url\":\"interfaces/LDContextCommon.html#anonymous\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextCommon\"},{\"kind\":256,\"name\":\"LDContextMeta\",\"url\":\"interfaces/LDContextMeta.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"secondary\",\"url\":\"interfaces/LDContextMeta.html#secondary\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextMeta\"},{\"kind\":1024,\"name\":\"privateAttributes\",\"url\":\"interfaces/LDContextMeta.html#privateAttributes\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextMeta\"},{\"kind\":256,\"name\":\"LDMultiKindContext\",\"url\":\"interfaces/LDMultiKindContext.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"kind\",\"url\":\"interfaces/LDMultiKindContext.html#kind\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDMultiKindContext\"},{\"kind\":256,\"name\":\"LDSingleKindContext\",\"url\":\"interfaces/LDSingleKindContext.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"kind\",\"url\":\"interfaces/LDSingleKindContext.html#kind\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDSingleKindContext\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/LDSingleKindContext.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"LDSingleKindContext\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/LDSingleKindContext.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"LDSingleKindContext\"},{\"kind\":1024,\"name\":\"_meta\",\"url\":\"interfaces/LDSingleKindContext.html#_meta\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"LDSingleKindContext\"},{\"kind\":1024,\"name\":\"anonymous\",\"url\":\"interfaces/LDSingleKindContext.html#anonymous\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"LDSingleKindContext\"},{\"kind\":256,\"name\":\"LDUser\",\"url\":\"interfaces/LDUser.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/LDUser.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"secondary\",\"url\":\"interfaces/LDUser.html#secondary\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/LDUser.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"firstName\",\"url\":\"interfaces/LDUser.html#firstName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"lastName\",\"url\":\"interfaces/LDUser.html#lastName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"email\",\"url\":\"interfaces/LDUser.html#email\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"avatar\",\"url\":\"interfaces/LDUser.html#avatar\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"ip\",\"url\":\"interfaces/LDUser.html#ip\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"country\",\"url\":\"interfaces/LDUser.html#country\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"anonymous\",\"url\":\"interfaces/LDUser.html#anonymous\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"custom\",\"url\":\"interfaces/LDUser.html#custom\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/LDUser.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"privateAttributeNames\",\"url\":\"interfaces/LDUser.html#privateAttributeNames\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":4194304,\"name\":\"LDContext\",\"url\":\"types/LDContext.html\",\"classes\":\"tsd-kind-type-alias\"},{\"kind\":256,\"name\":\"BasicLoggerOptions\",\"url\":\"interfaces/BasicLoggerOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"level\",\"url\":\"interfaces/BasicLoggerOptions.html#level\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"BasicLoggerOptions\"},{\"kind\":1024,\"name\":\"destination\",\"url\":\"interfaces/BasicLoggerOptions.html#destination\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"BasicLoggerOptions\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/BasicLoggerOptions.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"BasicLoggerOptions\"},{\"kind\":1024,\"name\":\"formatter\",\"url\":\"interfaces/BasicLoggerOptions.html#formatter\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"BasicLoggerOptions\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/BasicLoggerOptions.html#__type-2\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"BasicLoggerOptions\"},{\"kind\":256,\"name\":\"LDLogger\",\"url\":\"interfaces/LDLogger.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":2048,\"name\":\"error\",\"url\":\"interfaces/LDLogger.html#error\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDLogger\"},{\"kind\":2048,\"name\":\"warn\",\"url\":\"interfaces/LDLogger.html#warn\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDLogger\"},{\"kind\":2048,\"name\":\"info\",\"url\":\"interfaces/LDLogger.html#info\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDLogger\"},{\"kind\":2048,\"name\":\"debug\",\"url\":\"interfaces/LDLogger.html#debug\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDLogger\"},{\"kind\":4194304,\"name\":\"LDLogLevel\",\"url\":\"types/LDLogLevel.html\",\"classes\":\"tsd-kind-type-alias\"},{\"kind\":256,\"name\":\"TypeValidator\",\"url\":\"interfaces/TypeValidator.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"interfaces/TypeValidator.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"TypeValidator\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"interfaces/TypeValidator.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"TypeValidator\"},{\"kind\":128,\"name\":\"FactoryOrInstance\",\"url\":\"classes/FactoryOrInstance.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/FactoryOrInstance.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"FactoryOrInstance\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/FactoryOrInstance.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"FactoryOrInstance\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/FactoryOrInstance.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"FactoryOrInstance\"},{\"kind\":128,\"name\":\"Type\",\"url\":\"classes/Type.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/Type.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"Type\"},{\"kind\":1024,\"name\":\"typeName\",\"url\":\"classes/Type.html#typeName\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Type\"},{\"kind\":1024,\"name\":\"typeOf\",\"url\":\"classes/Type.html#typeOf\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-protected\",\"parent\":\"Type\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/Type.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Type\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/Type.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Type\"},{\"kind\":128,\"name\":\"TypeArray\",\"url\":\"classes/TypeArray.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/TypeArray.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"TypeArray\"},{\"kind\":1024,\"name\":\"typeName\",\"url\":\"classes/TypeArray.html#typeName\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"TypeArray\"},{\"kind\":1024,\"name\":\"typeOf\",\"url\":\"classes/TypeArray.html#typeOf\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-protected\",\"parent\":\"TypeArray\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/TypeArray.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"TypeArray\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/TypeArray.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"TypeArray\"},{\"kind\":128,\"name\":\"NumberWithMinimum\",\"url\":\"classes/NumberWithMinimum.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/NumberWithMinimum.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"NumberWithMinimum\"},{\"kind\":1024,\"name\":\"min\",\"url\":\"classes/NumberWithMinimum.html#min\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"NumberWithMinimum\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/NumberWithMinimum.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"NumberWithMinimum\"},{\"kind\":1024,\"name\":\"typeOf\",\"url\":\"classes/NumberWithMinimum.html#typeOf\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-protected tsd-is-inherited\",\"parent\":\"NumberWithMinimum\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/NumberWithMinimum.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-is-inherited\",\"parent\":\"NumberWithMinimum\"},{\"kind\":128,\"name\":\"StringMatchingRegex\",\"url\":\"classes/StringMatchingRegex.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/StringMatchingRegex.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"StringMatchingRegex\"},{\"kind\":1024,\"name\":\"expression\",\"url\":\"classes/StringMatchingRegex.html#expression\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"StringMatchingRegex\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/StringMatchingRegex.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"StringMatchingRegex\"},{\"kind\":1024,\"name\":\"typeOf\",\"url\":\"classes/StringMatchingRegex.html#typeOf\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-protected tsd-is-inherited\",\"parent\":\"StringMatchingRegex\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/StringMatchingRegex.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-is-inherited\",\"parent\":\"StringMatchingRegex\"},{\"kind\":128,\"name\":\"Function\",\"url\":\"classes/Function.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/Function.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"Function\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/Function.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Function\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/Function.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Function\"},{\"kind\":128,\"name\":\"DateValidator\",\"url\":\"classes/DateValidator.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/DateValidator.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"DateValidator\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/DateValidator.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"DateValidator\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/DateValidator.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"DateValidator\"},{\"kind\":128,\"name\":\"TypeValidators\",\"url\":\"classes/TypeValidators.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":1024,\"name\":\"String\",\"url\":\"classes/TypeValidators.html#String\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"Number\",\"url\":\"classes/TypeValidators.html#Number\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"ObjectOrFactory\",\"url\":\"classes/TypeValidators.html#ObjectOrFactory\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"Object\",\"url\":\"classes/TypeValidators.html#Object\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"StringArray\",\"url\":\"classes/TypeValidators.html#StringArray\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"Boolean\",\"url\":\"classes/TypeValidators.html#Boolean\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"Function\",\"url\":\"classes/TypeValidators.html#Function\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":2048,\"name\":\"numberWithMin\",\"url\":\"classes/TypeValidators.html#numberWithMin\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":2048,\"name\":\"stringMatchingRegex\",\"url\":\"classes/TypeValidators.html#stringMatchingRegex\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"Date\",\"url\":\"classes/TypeValidators.html#Date\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/TypeValidators.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":128,\"name\":\"BasicLogger\",\"url\":\"classes/BasicLogger.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/BasicLogger.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"logLevel\",\"url\":\"classes/BasicLogger.html#logLevel\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"destination\",\"url\":\"classes/BasicLogger.html#destination\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"formatter\",\"url\":\"classes/BasicLogger.html#formatter\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"tryFormat\",\"url\":\"classes/BasicLogger.html#tryFormat\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"tryWrite\",\"url\":\"classes/BasicLogger.html#tryWrite\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"log\",\"url\":\"classes/BasicLogger.html#log\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":2048,\"name\":\"error\",\"url\":\"classes/BasicLogger.html#error\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BasicLogger\"},{\"kind\":2048,\"name\":\"warn\",\"url\":\"classes/BasicLogger.html#warn\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BasicLogger\"},{\"kind\":2048,\"name\":\"info\",\"url\":\"classes/BasicLogger.html#info\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BasicLogger\"},{\"kind\":2048,\"name\":\"debug\",\"url\":\"classes/BasicLogger.html#debug\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BasicLogger\"},{\"kind\":128,\"name\":\"SafeLogger\",\"url\":\"classes/SafeLogger.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/SafeLogger.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"SafeLogger\"},{\"kind\":1024,\"name\":\"logger\",\"url\":\"classes/SafeLogger.html#logger\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"SafeLogger\"},{\"kind\":1024,\"name\":\"fallback\",\"url\":\"classes/SafeLogger.html#fallback\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"SafeLogger\"},{\"kind\":1024,\"name\":\"log\",\"url\":\"classes/SafeLogger.html#log\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"SafeLogger\"},{\"kind\":2048,\"name\":\"error\",\"url\":\"classes/SafeLogger.html#error\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SafeLogger\"},{\"kind\":2048,\"name\":\"warn\",\"url\":\"classes/SafeLogger.html#warn\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SafeLogger\"},{\"kind\":2048,\"name\":\"info\",\"url\":\"classes/SafeLogger.html#info\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SafeLogger\"},{\"kind\":2048,\"name\":\"debug\",\"url\":\"classes/SafeLogger.html#debug\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SafeLogger\"}],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"name\",\"comment\"],\"fieldVectors\":[[\"name/0\",[0,48.064]],[\"comment/0\",[]],[\"name/1\",[1,56.537]],[\"comment/1\",[]],[\"name/2\",[2,48.064]],[\"comment/2\",[]],[\"name/3\",[3,51.428]],[\"comment/3\",[]],[\"name/4\",[4,56.537]],[\"comment/4\",[]],[\"name/5\",[2,48.064]],[\"comment/5\",[]],[\"name/6\",[3,51.428]],[\"comment/6\",[]],[\"name/7\",[5,51.428]],[\"comment/7\",[]],[\"name/8\",[6,56.537]],[\"comment/8\",[]],[\"name/9\",[7,56.537]],[\"comment/9\",[]],[\"name/10\",[8,51.428]],[\"comment/10\",[]],[\"name/11\",[9,56.537]],[\"comment/11\",[]],[\"name/12\",[10,56.537]],[\"comment/12\",[]],[\"name/13\",[11,56.537]],[\"comment/13\",[]],[\"name/14\",[12,51.428]],[\"comment/14\",[]],[\"name/15\",[13,56.537]],[\"comment/15\",[]],[\"name/16\",[14,39.191]],[\"comment/16\",[]],[\"name/17\",[15,56.537]],[\"comment/17\",[]],[\"name/18\",[16,41.873]],[\"comment/18\",[]],[\"name/19\",[17,38.078]],[\"comment/19\",[]],[\"name/20\",[16,41.873]],[\"comment/20\",[]],[\"name/21\",[17,38.078]],[\"comment/21\",[]],[\"name/22\",[18,56.537]],[\"comment/22\",[]],[\"name/23\",[19,51.428]],[\"comment/23\",[]],[\"name/24\",[16,41.873]],[\"comment/24\",[]],[\"name/25\",[17,38.078]],[\"comment/25\",[]],[\"name/26\",[20,51.428]],[\"comment/26\",[]],[\"name/27\",[21,51.428]],[\"comment/27\",[]],[\"name/28\",[22,43.544]],[\"comment/28\",[]],[\"name/29\",[12,51.428]],[\"comment/29\",[]],[\"name/30\",[19,51.428]],[\"comment/30\",[]],[\"name/31\",[0,48.064]],[\"comment/31\",[]],[\"name/32\",[22,43.544]],[\"comment/32\",[]],[\"name/33\",[8,51.428]],[\"comment/33\",[]],[\"name/34\",[5,51.428]],[\"comment/34\",[]],[\"name/35\",[23,51.428]],[\"comment/35\",[]],[\"name/36\",[24,45.55]],[\"comment/36\",[]],[\"name/37\",[25,45.55]],[\"comment/37\",[]],[\"name/38\",[26,56.537]],[\"comment/38\",[]],[\"name/39\",[27,56.537]],[\"comment/39\",[]],[\"name/40\",[28,56.537]],[\"comment/40\",[]],[\"name/41\",[29,56.537]],[\"comment/41\",[]],[\"name/42\",[30,56.537]],[\"comment/42\",[]],[\"name/43\",[24,45.55]],[\"comment/43\",[]],[\"name/44\",[31,56.537]],[\"comment/44\",[]],[\"name/45\",[32,56.537]],[\"comment/45\",[]],[\"name/46\",[33,56.537]],[\"comment/46\",[]],[\"name/47\",[34,56.537]],[\"comment/47\",[]],[\"name/48\",[24,45.55]],[\"comment/48\",[]],[\"name/49\",[35,56.537]],[\"comment/49\",[]],[\"name/50\",[36,56.537]],[\"comment/50\",[]],[\"name/51\",[37,51.428]],[\"comment/51\",[]],[\"name/52\",[23,51.428]],[\"comment/52\",[]],[\"name/53\",[38,56.537]],[\"comment/53\",[]],[\"name/54\",[39,56.537]],[\"comment/54\",[]],[\"name/55\",[40,56.537]],[\"comment/55\",[]],[\"name/56\",[41,56.537]],[\"comment/56\",[]],[\"name/57\",[42,56.537]],[\"comment/57\",[]],[\"name/58\",[43,56.537]],[\"comment/58\",[]],[\"name/59\",[44,56.537]],[\"comment/59\",[]],[\"name/60\",[45,56.537]],[\"comment/60\",[]],[\"name/61\",[46,41.873]],[\"comment/61\",[]],[\"name/62\",[47,56.537]],[\"comment/62\",[]],[\"name/63\",[48,56.537]],[\"comment/63\",[]],[\"name/64\",[14,39.191]],[\"comment/64\",[]],[\"name/65\",[24,45.55]],[\"comment/65\",[]],[\"name/66\",[14,39.191]],[\"comment/66\",[]],[\"name/67\",[49,56.537]],[\"comment/67\",[]],[\"name/68\",[50,56.537]],[\"comment/68\",[]],[\"name/69\",[51,56.537]],[\"comment/69\",[]],[\"name/70\",[52,56.537]],[\"comment/70\",[]],[\"name/71\",[53,33.183]],[\"comment/71\",[]],[\"name/72\",[0,48.064]],[\"comment/72\",[]],[\"name/73\",[54,56.537]],[\"comment/73\",[]],[\"name/74\",[55,51.428]],[\"comment/74\",[]],[\"name/75\",[56,45.55]],[\"comment/75\",[]],[\"name/76\",[57,51.428]],[\"comment/76\",[]],[\"name/77\",[58,51.428]],[\"comment/77\",[]],[\"name/78\",[59,51.428]],[\"comment/78\",[]],[\"name/79\",[60,51.428]],[\"comment/79\",[]],[\"name/80\",[61,51.428]],[\"comment/80\",[]],[\"name/81\",[46,41.873]],[\"comment/81\",[]],[\"name/82\",[62,51.428]],[\"comment/82\",[]],[\"name/83\",[63,51.428]],[\"comment/83\",[]],[\"name/84\",[64,51.428]],[\"comment/84\",[]],[\"name/85\",[65,51.428]],[\"comment/85\",[]],[\"name/86\",[66,48.064]],[\"comment/86\",[]],[\"name/87\",[67,56.537]],[\"comment/87\",[]],[\"name/88\",[53,33.183]],[\"comment/88\",[]],[\"name/89\",[68,51.428]],[\"comment/89\",[]],[\"name/90\",[69,51.428]],[\"comment/90\",[]],[\"name/91\",[70,56.537]],[\"comment/91\",[]],[\"name/92\",[71,56.537]],[\"comment/92\",[]],[\"name/93\",[72,56.537]],[\"comment/93\",[]],[\"name/94\",[73,56.537]],[\"comment/94\",[]],[\"name/95\",[74,56.537]],[\"comment/95\",[]],[\"name/96\",[75,48.064]],[\"comment/96\",[]],[\"name/97\",[76,56.537]],[\"comment/97\",[]],[\"name/98\",[77,56.537]],[\"comment/98\",[]],[\"name/99\",[2,48.064]],[\"comment/99\",[]],[\"name/100\",[78,56.537]],[\"comment/100\",[]],[\"name/101\",[79,56.537]],[\"comment/101\",[]],[\"name/102\",[80,56.537]],[\"comment/102\",[]],[\"name/103\",[81,56.537]],[\"comment/103\",[]],[\"name/104\",[82,56.537]],[\"comment/104\",[]],[\"name/105\",[66,48.064]],[\"comment/105\",[]],[\"name/106\",[83,56.537]],[\"comment/106\",[]],[\"name/107\",[84,56.537]],[\"comment/107\",[]],[\"name/108\",[85,56.537]],[\"comment/108\",[]],[\"name/109\",[86,56.537]],[\"comment/109\",[]],[\"name/110\",[87,56.537]],[\"comment/110\",[]],[\"name/111\",[88,56.537]],[\"comment/111\",[]],[\"name/112\",[89,56.537]],[\"comment/112\",[]],[\"name/113\",[90,56.537]],[\"comment/113\",[]],[\"name/114\",[91,56.537]],[\"comment/114\",[]],[\"name/115\",[92,56.537]],[\"comment/115\",[]],[\"name/116\",[93,56.537]],[\"comment/116\",[]],[\"name/117\",[94,56.537]],[\"comment/117\",[]],[\"name/118\",[95,56.537]],[\"comment/118\",[]],[\"name/119\",[96,56.537]],[\"comment/119\",[]],[\"name/120\",[97,56.537]],[\"comment/120\",[]],[\"name/121\",[98,56.537]],[\"comment/121\",[]],[\"name/122\",[99,56.537]],[\"comment/122\",[]],[\"name/123\",[100,56.537]],[\"comment/123\",[]],[\"name/124\",[46,41.873]],[\"comment/124\",[]],[\"name/125\",[101,56.537]],[\"comment/125\",[]],[\"name/126\",[102,56.537]],[\"comment/126\",[]],[\"name/127\",[103,56.537]],[\"comment/127\",[]],[\"name/128\",[104,56.537]],[\"comment/128\",[]],[\"name/129\",[105,56.537]],[\"comment/129\",[]],[\"name/130\",[106,56.537]],[\"comment/130\",[]],[\"name/131\",[107,56.537]],[\"comment/131\",[]],[\"name/132\",[108,43.544]],[\"comment/132\",[]],[\"name/133\",[109,56.537]],[\"comment/133\",[]],[\"name/134\",[110,56.537]],[\"comment/134\",[]],[\"name/135\",[111,51.428]],[\"comment/135\",[]],[\"name/136\",[112,56.537]],[\"comment/136\",[]],[\"name/137\",[113,56.537]],[\"comment/137\",[]],[\"name/138\",[114,56.537]],[\"comment/138\",[]],[\"name/139\",[115,40.442]],[\"comment/139\",[]],[\"name/140\",[17,38.078]],[\"comment/140\",[]],[\"name/141\",[116,45.55]],[\"comment/141\",[]],[\"name/142\",[55,51.428]],[\"comment/142\",[]],[\"name/143\",[68,51.428]],[\"comment/143\",[]],[\"name/144\",[69,51.428]],[\"comment/144\",[]],[\"name/145\",[117,56.537]],[\"comment/145\",[]],[\"name/146\",[17,38.078]],[\"comment/146\",[]],[\"name/147\",[118,56.537]],[\"comment/147\",[]],[\"name/148\",[119,56.537]],[\"comment/148\",[]],[\"name/149\",[25,45.55]],[\"comment/149\",[]],[\"name/150\",[120,56.537]],[\"comment/150\",[]],[\"name/151\",[121,51.428]],[\"comment/151\",[]],[\"name/152\",[56,45.55]],[\"comment/152\",[]],[\"name/153\",[46,41.873]],[\"comment/153\",[]],[\"name/154\",[122,56.537]],[\"comment/154\",[]],[\"name/155\",[111,51.428]],[\"comment/155\",[]],[\"name/156\",[123,56.537]],[\"comment/156\",[]],[\"name/157\",[124,56.537]],[\"comment/157\",[]],[\"name/158\",[17,38.078]],[\"comment/158\",[]],[\"name/159\",[116,45.55]],[\"comment/159\",[]],[\"name/160\",[125,56.537]],[\"comment/160\",[]],[\"name/161\",[126,56.537]],[\"comment/161\",[]],[\"name/162\",[127,56.537]],[\"comment/162\",[]],[\"name/163\",[116,45.55]],[\"comment/163\",[]],[\"name/164\",[17,38.078]],[\"comment/164\",[]],[\"name/165\",[128,56.537]],[\"comment/165\",[]],[\"name/166\",[115,40.442]],[\"comment/166\",[]],[\"name/167\",[116,45.55]],[\"comment/167\",[]],[\"name/168\",[17,38.078]],[\"comment/168\",[]],[\"name/169\",[129,56.537]],[\"comment/169\",[]],[\"name/170\",[130,56.537]],[\"comment/170\",[]],[\"name/171\",[131,56.537]],[\"comment/171\",[]],[\"name/172\",[25,45.55]],[\"comment/172\",[]],[\"name/173\",[132,56.537]],[\"comment/173\",[]],[\"name/174\",[133,56.537]],[\"comment/174\",[]],[\"name/175\",[134,56.537]],[\"comment/175\",[]],[\"name/176\",[121,51.428]],[\"comment/176\",[]],[\"name/177\",[56,45.55]],[\"comment/177\",[]],[\"name/178\",[46,41.873]],[\"comment/178\",[]],[\"name/179\",[135,56.537]],[\"comment/179\",[]],[\"name/180\",[136,56.537]],[\"comment/180\",[]],[\"name/181\",[137,56.537]],[\"comment/181\",[]],[\"name/182\",[138,56.537]],[\"comment/182\",[]],[\"name/183\",[139,56.537]],[\"comment/183\",[]],[\"name/184\",[108,43.544]],[\"comment/184\",[]],[\"name/185\",[140,56.537]],[\"comment/185\",[]],[\"name/186\",[141,56.537]],[\"comment/186\",[]],[\"name/187\",[142,56.537]],[\"comment/187\",[]],[\"name/188\",[143,56.537]],[\"comment/188\",[]],[\"name/189\",[144,56.537]],[\"comment/189\",[]],[\"name/190\",[145,56.537]],[\"comment/190\",[]],[\"name/191\",[146,56.537]],[\"comment/191\",[]],[\"name/192\",[147,56.537]],[\"comment/192\",[]],[\"name/193\",[148,56.537]],[\"comment/193\",[]],[\"name/194\",[149,56.537]],[\"comment/194\",[]],[\"name/195\",[150,56.537]],[\"comment/195\",[]],[\"name/196\",[151,56.537]],[\"comment/196\",[]],[\"name/197\",[152,56.537]],[\"comment/197\",[]],[\"name/198\",[153,56.537]],[\"comment/198\",[]],[\"name/199\",[154,56.537]],[\"comment/199\",[]],[\"name/200\",[155,56.537]],[\"comment/200\",[]],[\"name/201\",[156,56.537]],[\"comment/201\",[]],[\"name/202\",[157,56.537]],[\"comment/202\",[]],[\"name/203\",[158,56.537]],[\"comment/203\",[]],[\"name/204\",[159,56.537]],[\"comment/204\",[]],[\"name/205\",[160,56.537]],[\"comment/205\",[]],[\"name/206\",[161,56.537]],[\"comment/206\",[]],[\"name/207\",[162,56.537]],[\"comment/207\",[]],[\"name/208\",[163,56.537]],[\"comment/208\",[]],[\"name/209\",[164,56.537]],[\"comment/209\",[]],[\"name/210\",[165,56.537]],[\"comment/210\",[]],[\"name/211\",[166,56.537]],[\"comment/211\",[]],[\"name/212\",[37,51.428]],[\"comment/212\",[]],[\"name/213\",[167,56.537]],[\"comment/213\",[]],[\"name/214\",[75,48.064]],[\"comment/214\",[]],[\"name/215\",[168,56.537]],[\"comment/215\",[]],[\"name/216\",[169,56.537]],[\"comment/216\",[]],[\"name/217\",[170,56.537]],[\"comment/217\",[]],[\"name/218\",[171,56.537]],[\"comment/218\",[]],[\"name/219\",[172,56.537]],[\"comment/219\",[]],[\"name/220\",[173,56.537]],[\"comment/220\",[]],[\"name/221\",[174,56.537]],[\"comment/221\",[]],[\"name/222\",[175,56.537]],[\"comment/222\",[]],[\"name/223\",[176,56.537]],[\"comment/223\",[]],[\"name/224\",[177,56.537]],[\"comment/224\",[]],[\"name/225\",[178,56.537]],[\"comment/225\",[]],[\"name/226\",[179,51.428]],[\"comment/226\",[]],[\"name/227\",[180,45.55]],[\"comment/227\",[]],[\"name/228\",[181,56.537]],[\"comment/228\",[]],[\"name/229\",[182,56.537]],[\"comment/229\",[]],[\"name/230\",[183,56.537]],[\"comment/230\",[]],[\"name/231\",[184,56.537]],[\"comment/231\",[]],[\"name/232\",[185,56.537]],[\"comment/232\",[]],[\"name/233\",[20,51.428]],[\"comment/233\",[]],[\"name/234\",[21,51.428]],[\"comment/234\",[]],[\"name/235\",[186,56.537]],[\"comment/235\",[]],[\"name/236\",[14,39.191]],[\"comment/236\",[]],[\"name/237\",[187,56.537]],[\"comment/237\",[]],[\"name/238\",[17,38.078]],[\"comment/238\",[]],[\"name/239\",[188,56.537]],[\"comment/239\",[]],[\"name/240\",[189,56.537]],[\"comment/240\",[]],[\"name/241\",[190,56.537]],[\"comment/241\",[]],[\"name/242\",[191,56.537]],[\"comment/242\",[]],[\"name/243\",[192,56.537]],[\"comment/243\",[]],[\"name/244\",[193,56.537]],[\"comment/244\",[]],[\"name/245\",[194,56.537]],[\"comment/245\",[]],[\"name/246\",[195,56.537]],[\"comment/246\",[]],[\"name/247\",[196,56.537]],[\"comment/247\",[]],[\"name/248\",[14,39.191]],[\"comment/248\",[]],[\"name/249\",[197,56.537]],[\"comment/249\",[]],[\"name/250\",[198,56.537]],[\"comment/250\",[]],[\"name/251\",[115,40.442]],[\"comment/251\",[]],[\"name/252\",[199,56.537]],[\"comment/252\",[]],[\"name/253\",[200,56.537]],[\"comment/253\",[]],[\"name/254\",[201,56.537]],[\"comment/254\",[]],[\"name/255\",[202,56.537]],[\"comment/255\",[]],[\"name/256\",[203,56.537]],[\"comment/256\",[]],[\"name/257\",[56,45.55]],[\"comment/257\",[]],[\"name/258\",[57,51.428]],[\"comment/258\",[]],[\"name/259\",[58,51.428]],[\"comment/259\",[]],[\"name/260\",[59,51.428]],[\"comment/260\",[]],[\"name/261\",[60,51.428]],[\"comment/261\",[]],[\"name/262\",[61,51.428]],[\"comment/262\",[]],[\"name/263\",[46,41.873]],[\"comment/263\",[]],[\"name/264\",[62,51.428]],[\"comment/264\",[]],[\"name/265\",[63,51.428]],[\"comment/265\",[]],[\"name/266\",[64,51.428]],[\"comment/266\",[]],[\"name/267\",[65,51.428]],[\"comment/267\",[]],[\"name/268\",[66,48.064]],[\"comment/268\",[]],[\"name/269\",[204,56.537]],[\"comment/269\",[]],[\"name/270\",[53,33.183]],[\"comment/270\",[]],[\"name/271\",[205,56.537]],[\"comment/271\",[]],[\"name/272\",[206,56.537]],[\"comment/272\",[]],[\"name/273\",[207,56.537]],[\"comment/273\",[]],[\"name/274\",[25,45.55]],[\"comment/274\",[]],[\"name/275\",[208,56.537]],[\"comment/275\",[]],[\"name/276\",[209,56.537]],[\"comment/276\",[]],[\"name/277\",[210,56.537]],[\"comment/277\",[]],[\"name/278\",[211,56.537]],[\"comment/278\",[]],[\"name/279\",[212,51.428]],[\"comment/279\",[]],[\"name/280\",[213,56.537]],[\"comment/280\",[]],[\"name/281\",[214,56.537]],[\"comment/281\",[]],[\"name/282\",[215,56.537]],[\"comment/282\",[]],[\"name/283\",[216,56.537]],[\"comment/283\",[]],[\"name/284\",[217,56.537]],[\"comment/284\",[]],[\"name/285\",[218,56.537]],[\"comment/285\",[]],[\"name/286\",[53,33.183]],[\"comment/286\",[]],[\"name/287\",[212,51.428]],[\"comment/287\",[]],[\"name/288\",[219,56.537]],[\"comment/288\",[]],[\"name/289\",[220,56.537]],[\"comment/289\",[]],[\"name/290\",[221,56.537]],[\"comment/290\",[]],[\"name/291\",[222,56.537]],[\"comment/291\",[]],[\"name/292\",[223,56.537]],[\"comment/292\",[]],[\"name/293\",[108,43.544]],[\"comment/293\",[]],[\"name/294\",[224,56.537]],[\"comment/294\",[]],[\"name/295\",[225,56.537]],[\"comment/295\",[]],[\"name/296\",[115,40.442]],[\"comment/296\",[]],[\"name/297\",[226,48.064]],[\"comment/297\",[]],[\"name/298\",[227,56.537]],[\"comment/298\",[]],[\"name/299\",[228,56.537]],[\"comment/299\",[]],[\"name/300\",[229,56.537]],[\"comment/300\",[]],[\"name/301\",[230,56.537]],[\"comment/301\",[]],[\"name/302\",[180,45.55]],[\"comment/302\",[]],[\"name/303\",[231,56.537]],[\"comment/303\",[]],[\"name/304\",[232,56.537]],[\"comment/304\",[]],[\"name/305\",[233,56.537]],[\"comment/305\",[]],[\"name/306\",[53,33.183]],[\"comment/306\",[]],[\"name/307\",[179,51.428]],[\"comment/307\",[]],[\"name/308\",[180,45.55]],[\"comment/308\",[]],[\"name/309\",[234,56.537]],[\"comment/309\",[]],[\"name/310\",[235,56.537]],[\"comment/310\",[]],[\"name/311\",[236,56.537]],[\"comment/311\",[]],[\"name/312\",[237,56.537]],[\"comment/312\",[]],[\"name/313\",[115,40.442]],[\"comment/313\",[]],[\"name/314\",[16,41.873]],[\"comment/314\",[]],[\"name/315\",[238,51.428]],[\"comment/315\",[]],[\"name/316\",[239,48.064]],[\"comment/316\",[]],[\"name/317\",[240,56.537]],[\"comment/317\",[]],[\"name/318\",[226,48.064]],[\"comment/318\",[]],[\"name/319\",[180,45.55]],[\"comment/319\",[]],[\"name/320\",[241,56.537]],[\"comment/320\",[]],[\"name/321\",[108,43.544]],[\"comment/321\",[]],[\"name/322\",[242,56.537]],[\"comment/322\",[]],[\"name/323\",[108,43.544]],[\"comment/323\",[]],[\"name/324\",[115,40.442]],[\"comment/324\",[]],[\"name/325\",[16,41.873]],[\"comment/325\",[]],[\"name/326\",[238,51.428]],[\"comment/326\",[]],[\"name/327\",[239,48.064]],[\"comment/327\",[]],[\"name/328\",[243,56.537]],[\"comment/328\",[]],[\"name/329\",[115,40.442]],[\"comment/329\",[]],[\"name/330\",[226,48.064]],[\"comment/330\",[]],[\"name/331\",[16,41.873]],[\"comment/331\",[]],[\"name/332\",[244,56.537]],[\"comment/332\",[]],[\"name/333\",[245,56.537]],[\"comment/333\",[]],[\"name/334\",[246,56.537]],[\"comment/334\",[]],[\"name/335\",[247,56.537]],[\"comment/335\",[]],[\"name/336\",[248,56.537]],[\"comment/336\",[]],[\"name/337\",[249,56.537]],[\"comment/337\",[]],[\"name/338\",[239,48.064]],[\"comment/338\",[]],[\"name/339\",[250,56.537]],[\"comment/339\",[]],[\"name/340\",[14,39.191]],[\"comment/340\",[]],[\"name/341\",[251,56.537]],[\"comment/341\",[]],[\"name/342\",[252,56.537]],[\"comment/342\",[]],[\"name/343\",[253,56.537]],[\"comment/343\",[]],[\"name/344\",[254,56.537]],[\"comment/344\",[]],[\"name/345\",[255,51.428]],[\"comment/345\",[]],[\"name/346\",[14,39.191]],[\"comment/346\",[]],[\"name/347\",[256,51.428]],[\"comment/347\",[]],[\"name/348\",[14,39.191]],[\"comment/348\",[]],[\"name/349\",[257,56.537]],[\"comment/349\",[]],[\"name/350\",[258,48.064]],[\"comment/350\",[]],[\"name/351\",[259,48.064]],[\"comment/351\",[]],[\"name/352\",[22,43.544]],[\"comment/352\",[]],[\"name/353\",[260,48.064]],[\"comment/353\",[]],[\"name/354\",[261,56.537]],[\"comment/354\",[]],[\"name/355\",[262,56.537]],[\"comment/355\",[]],[\"name/356\",[263,39.191]],[\"comment/356\",[]],[\"name/357\",[264,39.191]],[\"comment/357\",[]],[\"name/358\",[265,56.537]],[\"comment/358\",[]],[\"name/359\",[53,33.183]],[\"comment/359\",[]],[\"name/360\",[263,39.191]],[\"comment/360\",[]],[\"name/361\",[264,39.191]],[\"comment/361\",[]],[\"name/362\",[266,56.537]],[\"comment/362\",[]],[\"name/363\",[53,33.183]],[\"comment/363\",[]],[\"name/364\",[267,51.428]],[\"comment/364\",[]],[\"name/365\",[268,45.55]],[\"comment/365\",[]],[\"name/366\",[263,39.191]],[\"comment/366\",[]],[\"name/367\",[264,39.191]],[\"comment/367\",[]],[\"name/368\",[269,56.537]],[\"comment/368\",[]],[\"name/369\",[53,33.183]],[\"comment/369\",[]],[\"name/370\",[267,51.428]],[\"comment/370\",[]],[\"name/371\",[268,45.55]],[\"comment/371\",[]],[\"name/372\",[263,39.191]],[\"comment/372\",[]],[\"name/373\",[264,39.191]],[\"comment/373\",[]],[\"name/374\",[270,56.537]],[\"comment/374\",[]],[\"name/375\",[53,33.183]],[\"comment/375\",[]],[\"name/376\",[271,56.537]],[\"comment/376\",[]],[\"name/377\",[263,39.191]],[\"comment/377\",[]],[\"name/378\",[268,45.55]],[\"comment/378\",[]],[\"name/379\",[264,39.191]],[\"comment/379\",[]],[\"name/380\",[272,51.428]],[\"comment/380\",[]],[\"name/381\",[53,33.183]],[\"comment/381\",[]],[\"name/382\",[273,56.537]],[\"comment/382\",[]],[\"name/383\",[263,39.191]],[\"comment/383\",[]],[\"name/384\",[268,45.55]],[\"comment/384\",[]],[\"name/385\",[264,39.191]],[\"comment/385\",[]],[\"name/386\",[274,51.428]],[\"comment/386\",[]],[\"name/387\",[53,33.183]],[\"comment/387\",[]],[\"name/388\",[263,39.191]],[\"comment/388\",[]],[\"name/389\",[264,39.191]],[\"comment/389\",[]],[\"name/390\",[275,56.537]],[\"comment/390\",[]],[\"name/391\",[53,33.183]],[\"comment/391\",[]],[\"name/392\",[263,39.191]],[\"comment/392\",[]],[\"name/393\",[264,39.191]],[\"comment/393\",[]],[\"name/394\",[276,56.537]],[\"comment/394\",[]],[\"name/395\",[277,56.537]],[\"comment/395\",[]],[\"name/396\",[278,56.537]],[\"comment/396\",[]],[\"name/397\",[279,56.537]],[\"comment/397\",[]],[\"name/398\",[280,56.537]],[\"comment/398\",[]],[\"name/399\",[281,56.537]],[\"comment/399\",[]],[\"name/400\",[282,56.537]],[\"comment/400\",[]],[\"name/401\",[274,51.428]],[\"comment/401\",[]],[\"name/402\",[283,56.537]],[\"comment/402\",[]],[\"name/403\",[272,51.428]],[\"comment/403\",[]],[\"name/404\",[284,56.537]],[\"comment/404\",[]],[\"name/405\",[53,33.183]],[\"comment/405\",[]],[\"name/406\",[285,56.537]],[\"comment/406\",[]],[\"name/407\",[53,33.183]],[\"comment/407\",[]],[\"name/408\",[286,56.537]],[\"comment/408\",[]],[\"name/409\",[255,51.428]],[\"comment/409\",[]],[\"name/410\",[256,51.428]],[\"comment/410\",[]],[\"name/411\",[287,56.537]],[\"comment/411\",[]],[\"name/412\",[288,56.537]],[\"comment/412\",[]],[\"name/413\",[289,51.428]],[\"comment/413\",[]],[\"name/414\",[258,48.064]],[\"comment/414\",[]],[\"name/415\",[259,48.064]],[\"comment/415\",[]],[\"name/416\",[22,43.544]],[\"comment/416\",[]],[\"name/417\",[260,48.064]],[\"comment/417\",[]],[\"name/418\",[290,56.537]],[\"comment/418\",[]],[\"name/419\",[53,33.183]],[\"comment/419\",[]],[\"name/420\",[75,48.064]],[\"comment/420\",[]],[\"name/421\",[291,56.537]],[\"comment/421\",[]],[\"name/422\",[289,51.428]],[\"comment/422\",[]],[\"name/423\",[258,48.064]],[\"comment/423\",[]],[\"name/424\",[259,48.064]],[\"comment/424\",[]],[\"name/425\",[22,43.544]],[\"comment/425\",[]],[\"name/426\",[260,48.064]],[\"comment/426\",[]]],\"invertedIndex\":[[\"__type\",{\"_index\":14,\"name\":{\"16\":{},\"64\":{},\"66\":{},\"236\":{},\"248\":{},\"340\":{},\"346\":{},\"348\":{}},\"comment\":{}}],[\"_meta\",{\"_index\":238,\"name\":{\"315\":{},\"326\":{}},\"comment\":{}}],[\"addeventlistener\",{\"_index\":45,\"name\":{\"60\":{}},\"comment\":{}}],[\"additional\",{\"_index\":18,\"name\":{\"22\":{}},\"comment\":{}}],[\"all\",{\"_index\":132,\"name\":{\"173\":{}},\"comment\":{}}],[\"allattributesprivate\",{\"_index\":179,\"name\":{\"226\":{},\"307\":{}},\"comment\":{}}],[\"allflagsstate\",{\"_index\":60,\"name\":{\"79\":{},\"261\":{}},\"comment\":{}}],[\"allvalues\",{\"_index\":151,\"name\":{\"196\":{}},\"comment\":{}}],[\"andmatch\",{\"_index\":94,\"name\":{\"117\":{}},\"comment\":{}}],[\"andnotmatch\",{\"_index\":95,\"name\":{\"118\":{}},\"comment\":{}}],[\"anonymous\",{\"_index\":239,\"name\":{\"316\":{},\"327\":{},\"338\":{}},\"comment\":{}}],[\"application\",{\"_index\":186,\"name\":{\"235\":{}},\"comment\":{}}],[\"arch\",{\"_index\":15,\"name\":{\"17\":{}},\"comment\":{}}],[\"attributereference\",{\"_index\":204,\"name\":{\"269\":{}},\"comment\":{}}],[\"auth\",{\"_index\":192,\"name\":{\"243\":{}},\"comment\":{}}],[\"autoupdate\",{\"_index\":74,\"name\":{\"95\":{}},\"comment\":{}}],[\"available\",{\"_index\":105,\"name\":{\"129\":{}},\"comment\":{}}],[\"avatar\",{\"_index\":247,\"name\":{\"335\":{}},\"comment\":{}}],[\"baseuri\",{\"_index\":164,\"name\":{\"209\":{}},\"comment\":{}}],[\"basiclogger\",{\"_index\":285,\"name\":{\"406\":{}},\"comment\":{}}],[\"basicloggeroptions\",{\"_index\":253,\"name\":{\"343\":{}},\"comment\":{}}],[\"bigsegments\",{\"_index\":169,\"name\":{\"216\":{}},\"comment\":{}}],[\"bigsegmentsstatus\",{\"_index\":145,\"name\":{\"190\":{}},\"comment\":{}}],[\"bigsegmentstore\",{\"_index\":98,\"name\":{\"121\":{}},\"comment\":{}}],[\"bigsegmentstoremembership\",{\"_index\":101,\"name\":{\"125\":{}},\"comment\":{}}],[\"bigsegmentstoremetadata\",{\"_index\":102,\"name\":{\"126\":{}},\"comment\":{}}],[\"bigsegmentstorestatus\",{\"_index\":104,\"name\":{\"128\":{}},\"comment\":{}}],[\"bigsegmentstorestatusprovider\",{\"_index\":55,\"name\":{\"74\":{},\"142\":{}},\"comment\":{}}],[\"bigsegmentstorestatusproviderimpl\",{\"_index\":67,\"name\":{\"87\":{}},\"comment\":{}}],[\"body\",{\"_index\":36,\"name\":{\"50\":{}},\"comment\":{}}],[\"boolean\",{\"_index\":282,\"name\":{\"400\":{}},\"comment\":{}}],[\"booleanflag\",{\"_index\":81,\"name\":{\"103\":{}},\"comment\":{}}],[\"ca\",{\"_index\":194,\"name\":{\"245\":{}},\"comment\":{}}],[\"canonicalkey\",{\"_index\":228,\"name\":{\"299\":{}},\"comment\":{}}],[\"capacity\",{\"_index\":167,\"name\":{\"213\":{}},\"comment\":{}}],[\"cert\",{\"_index\":195,\"name\":{\"246\":{}},\"comment\":{}}],[\"checkserveridentity\",{\"_index\":196,\"name\":{\"247\":{}},\"comment\":{}}],[\"ciphers\",{\"_index\":197,\"name\":{\"249\":{}},\"comment\":{}}],[\"clearalltargets\",{\"_index\":90,\"name\":{\"113\":{}},\"comment\":{}}],[\"clearrules\",{\"_index\":89,\"name\":{\"112\":{}},\"comment\":{}}],[\"clientsideonly\",{\"_index\":154,\"name\":{\"199\":{}},\"comment\":{}}],[\"close\",{\"_index\":46,\"name\":{\"61\":{},\"81\":{},\"124\":{},\"153\":{},\"178\":{},\"263\":{}},\"comment\":{}}],[\"compare\",{\"_index\":211,\"name\":{\"278\":{}},\"comment\":{}}],[\"components\",{\"_index\":207,\"name\":{\"273\":{}},\"comment\":{}}],[\"constructor\",{\"_index\":53,\"name\":{\"71\":{},\"88\":{},\"270\":{},\"286\":{},\"306\":{},\"359\":{},\"363\":{},\"369\":{},\"375\":{},\"381\":{},\"387\":{},\"391\":{},\"405\":{},\"407\":{},\"419\":{}},\"comment\":{}}],[\"context\",{\"_index\":212,\"name\":{\"279\":{},\"287\":{}},\"comment\":{}}],[\"contextfilter\",{\"_index\":233,\"name\":{\"305\":{}},\"comment\":{}}],[\"contextforkind\",{\"_index\":224,\"name\":{\"294\":{}},\"comment\":{}}],[\"contextkeyscapacity\",{\"_index\":181,\"name\":{\"228\":{}},\"comment\":{}}],[\"contextkeysflushinterval\",{\"_index\":182,\"name\":{\"229\":{}},\"comment\":{}}],[\"contexts\",{\"_index\":222,\"name\":{\"291\":{}},\"comment\":{}}],[\"country\",{\"_index\":249,\"name\":{\"337\":{}},\"comment\":{}}],[\"createeventsource\",{\"_index\":39,\"name\":{\"54\":{}},\"comment\":{}}],[\"createhash\",{\"_index\":6,\"name\":{\"8\":{}},\"comment\":{}}],[\"createhmac\",{\"_index\":7,\"name\":{\"9\":{}},\"comment\":{}}],[\"crypto\",{\"_index\":5,\"name\":{\"7\":{},\"34\":{}},\"comment\":{}}],[\"custom\",{\"_index\":250,\"name\":{\"339\":{}},\"comment\":{}}],[\"datacollection\",{\"_index\":107,\"name\":{\"131\":{}},\"comment\":{}}],[\"datakind\",{\"_index\":110,\"name\":{\"134\":{}},\"comment\":{}}],[\"date\",{\"_index\":284,\"name\":{\"404\":{}},\"comment\":{}}],[\"datevalidator\",{\"_index\":275,\"name\":{\"390\":{}},\"comment\":{}}],[\"debug\",{\"_index\":260,\"name\":{\"353\":{},\"417\":{},\"426\":{}},\"comment\":{}}],[\"delete\",{\"_index\":134,\"name\":{\"175\":{}},\"comment\":{}}],[\"deleted\",{\"_index\":116,\"name\":{\"141\":{},\"159\":{},\"163\":{},\"167\":{}},\"comment\":{}}],[\"depth\",{\"_index\":209,\"name\":{\"276\":{}},\"comment\":{}}],[\"deserialize\",{\"_index\":123,\"name\":{\"156\":{}},\"comment\":{}}],[\"destination\",{\"_index\":255,\"name\":{\"345\":{},\"409\":{}},\"comment\":{}}],[\"detailsonlyfortrackedflags\",{\"_index\":156,\"name\":{\"201\":{}},\"comment\":{}}],[\"diagnosticoptout\",{\"_index\":184,\"name\":{\"231\":{}},\"comment\":{}}],[\"diagnosticrecordinginterval\",{\"_index\":185,\"name\":{\"232\":{}},\"comment\":{}}],[\"digest\",{\"_index\":3,\"name\":{\"3\":{},\"6\":{}},\"comment\":{}}],[\"dispatch\",{\"_index\":70,\"name\":{\"91\":{}},\"comment\":{}}],[\"email\",{\"_index\":246,\"name\":{\"334\":{}},\"comment\":{}}],[\"entries\",{\"_index\":28,\"name\":{\"40\":{}},\"comment\":{}}],[\"error\",{\"_index\":258,\"name\":{\"350\":{},\"414\":{},\"423\":{}},\"comment\":{}}],[\"errorfilter\",{\"_index\":48,\"name\":{\"63\":{}},\"comment\":{}}],[\"errorkind\",{\"_index\":140,\"name\":{\"185\":{}},\"comment\":{}}],[\"eventsource\",{\"_index\":40,\"name\":{\"55\":{}},\"comment\":{}}],[\"eventsourceinitdict\",{\"_index\":47,\"name\":{\"62\":{}},\"comment\":{}}],[\"eventsuri\",{\"_index\":166,\"name\":{\"211\":{}},\"comment\":{}}],[\"expression\",{\"_index\":273,\"name\":{\"382\":{}},\"comment\":{}}],[\"factoryorinstance\",{\"_index\":265,\"name\":{\"358\":{}},\"comment\":{}}],[\"fallback\",{\"_index\":291,\"name\":{\"421\":{}},\"comment\":{}}],[\"fallthroughvariation\",{\"_index\":83,\"name\":{\"106\":{}},\"comment\":{}}],[\"featurestore\",{\"_index\":168,\"name\":{\"215\":{}},\"comment\":{}}],[\"fetch\",{\"_index\":38,\"name\":{\"53\":{}},\"comment\":{}}],[\"filedatasourceoptions\",{\"_index\":72,\"name\":{\"93\":{}},\"comment\":{}}],[\"filesystem\",{\"_index\":8,\"name\":{\"10\":{},\"33\":{}},\"comment\":{}}],[\"filter\",{\"_index\":234,\"name\":{\"309\":{}},\"comment\":{}}],[\"filtersinglekind\",{\"_index\":236,\"name\":{\"311\":{}},\"comment\":{}}],[\"firstname\",{\"_index\":244,\"name\":{\"332\":{}},\"comment\":{}}],[\"flag\",{\"_index\":77,\"name\":{\"98\":{}},\"comment\":{}}],[\"flush\",{\"_index\":65,\"name\":{\"85\":{},\"267\":{}},\"comment\":{}}],[\"flushinterval\",{\"_index\":171,\"name\":{\"218\":{}},\"comment\":{}}],[\"formatter\",{\"_index\":256,\"name\":{\"347\":{},\"410\":{}},\"comment\":{}}],[\"fromldcontext\",{\"_index\":218,\"name\":{\"285\":{}},\"comment\":{}}],[\"fromlegacyuser\",{\"_index\":217,\"name\":{\"284\":{}},\"comment\":{}}],[\"frommultikindcontext\",{\"_index\":215,\"name\":{\"282\":{}},\"comment\":{}}],[\"fromsinglekindcontext\",{\"_index\":216,\"name\":{\"283\":{}},\"comment\":{}}],[\"fulldataset\",{\"_index\":112,\"name\":{\"136\":{}},\"comment\":{}}],[\"function\",{\"_index\":274,\"name\":{\"386\":{},\"401\":{}},\"comment\":{}}],[\"get\",{\"_index\":25,\"name\":{\"37\":{},\"149\":{},\"172\":{},\"274\":{}},\"comment\":{}}],[\"getall\",{\"_index\":120,\"name\":{\"150\":{}},\"comment\":{}}],[\"getattributestofilter\",{\"_index\":235,\"name\":{\"310\":{}},\"comment\":{}}],[\"getcomponent\",{\"_index\":208,\"name\":{\"275\":{}},\"comment\":{}}],[\"getcontexts\",{\"_index\":231,\"name\":{\"303\":{}},\"comment\":{}}],[\"getfiletimestamp\",{\"_index\":9,\"name\":{\"11\":{}},\"comment\":{}}],[\"getflagreason\",{\"_index\":150,\"name\":{\"195\":{}},\"comment\":{}}],[\"getflagvalue\",{\"_index\":149,\"name\":{\"194\":{}},\"comment\":{}}],[\"getmetadata\",{\"_index\":99,\"name\":{\"122\":{}},\"comment\":{}}],[\"getstatus\",{\"_index\":68,\"name\":{\"89\":{},\"143\":{}},\"comment\":{}}],[\"gettype\",{\"_index\":264,\"name\":{\"357\":{},\"361\":{},\"367\":{},\"373\":{},\"379\":{},\"385\":{},\"389\":{},\"393\":{}},\"comment\":{}}],[\"getusermembership\",{\"_index\":100,\"name\":{\"123\":{}},\"comment\":{}}],[\"getvaluefromcontext\",{\"_index\":214,\"name\":{\"281\":{}},\"comment\":{}}],[\"has\",{\"_index\":29,\"name\":{\"41\":{}},\"comment\":{}}],[\"hasher\",{\"_index\":1,\"name\":{\"1\":{}},\"comment\":{}}],[\"headers\",{\"_index\":24,\"name\":{\"36\":{},\"43\":{},\"48\":{},\"65\":{}},\"comment\":{}}],[\"hmac\",{\"_index\":4,\"name\":{\"4\":{}},\"comment\":{}}],[\"host\",{\"_index\":189,\"name\":{\"240\":{}},\"comment\":{}}],[\"id\",{\"_index\":187,\"name\":{\"237\":{}},\"comment\":{}}],[\"identify\",{\"_index\":64,\"name\":{\"84\":{},\"266\":{}},\"comment\":{}}],[\"ifmatch\",{\"_index\":91,\"name\":{\"114\":{}},\"comment\":{}}],[\"ifnotmatch\",{\"_index\":92,\"name\":{\"115\":{}},\"comment\":{}}],[\"inexperiment\",{\"_index\":144,\"name\":{\"189\":{}},\"comment\":{}}],[\"info\",{\"_index\":22,\"name\":{\"28\":{},\"32\":{},\"352\":{},\"416\":{},\"425\":{}},\"comment\":{}}],[\"init\",{\"_index\":133,\"name\":{\"174\":{}},\"comment\":{}}],[\"initialized\",{\"_index\":56,\"name\":{\"75\":{},\"152\":{},\"177\":{},\"257\":{}},\"comment\":{}}],[\"initialretrydelaymillis\",{\"_index\":49,\"name\":{\"67\":{}},\"comment\":{}}],[\"initstate\",{\"_index\":54,\"name\":{\"73\":{}},\"comment\":{}}],[\"integrations\",{\"_index\":71,\"name\":{\"92\":{}},\"comment\":{}}],[\"interfaces\",{\"_index\":97,\"name\":{\"120\":{}},\"comment\":{}}],[\"ip\",{\"_index\":248,\"name\":{\"336\":{}},\"comment\":{}}],[\"is\",{\"_index\":263,\"name\":{\"356\":{},\"360\":{},\"366\":{},\"372\":{},\"377\":{},\"383\":{},\"388\":{},\"392\":{}},\"comment\":{}}],[\"iskind\",{\"_index\":210,\"name\":{\"277\":{}},\"comment\":{}}],[\"ismulti\",{\"_index\":219,\"name\":{\"288\":{}},\"comment\":{}}],[\"ismultikind\",{\"_index\":227,\"name\":{\"298\":{}},\"comment\":{}}],[\"isoffline\",{\"_index\":62,\"name\":{\"82\":{},\"264\":{}},\"comment\":{}}],[\"isuser\",{\"_index\":220,\"name\":{\"289\":{}},\"comment\":{}}],[\"isvalid\",{\"_index\":205,\"name\":{\"271\":{}},\"comment\":{}}],[\"item\",{\"_index\":118,\"name\":{\"147\":{}},\"comment\":{}}],[\"itemdescriptor\",{\"_index\":117,\"name\":{\"145\":{}},\"comment\":{}}],[\"items\",{\"_index\":109,\"name\":{\"133\":{}},\"comment\":{}}],[\"json\",{\"_index\":33,\"name\":{\"46\":{}},\"comment\":{}}],[\"key\",{\"_index\":115,\"name\":{\"139\":{},\"166\":{},\"251\":{},\"296\":{},\"313\":{},\"324\":{},\"329\":{}},\"comment\":{}}],[\"keyeditems\",{\"_index\":113,\"name\":{\"137\":{}},\"comment\":{}}],[\"keys\",{\"_index\":26,\"name\":{\"38\":{}},\"comment\":{}}],[\"kind\",{\"_index\":108,\"name\":{\"132\":{},\"184\":{},\"293\":{},\"321\":{},\"323\":{}},\"comment\":{}}],[\"kinds\",{\"_index\":229,\"name\":{\"300\":{}},\"comment\":{}}],[\"kindsandkeys\",{\"_index\":230,\"name\":{\"301\":{}},\"comment\":{}}],[\"lastname\",{\"_index\":245,\"name\":{\"333\":{}},\"comment\":{}}],[\"lastuptodate\",{\"_index\":103,\"name\":{\"127\":{}},\"comment\":{}}],[\"ldbigsegmentsoptions\",{\"_index\":158,\"name\":{\"203\":{}},\"comment\":{}}],[\"ldclient\",{\"_index\":203,\"name\":{\"256\":{}},\"comment\":{}}],[\"ldclientimpl\",{\"_index\":52,\"name\":{\"70\":{}},\"comment\":{}}],[\"ldcontext\",{\"_index\":252,\"name\":{\"342\":{}},\"comment\":{}}],[\"ldcontextcommon\",{\"_index\":237,\"name\":{\"312\":{}},\"comment\":{}}],[\"ldcontextmeta\",{\"_index\":240,\"name\":{\"317\":{}},\"comment\":{}}],[\"ldevaluationdetail\",{\"_index\":135,\"name\":{\"179\":{}},\"comment\":{}}],[\"ldevaluationreason\",{\"_index\":139,\"name\":{\"183\":{}},\"comment\":{}}],[\"ldfeaturestore\",{\"_index\":131,\"name\":{\"171\":{}},\"comment\":{}}],[\"ldfeaturestoredatastorage\",{\"_index\":130,\"name\":{\"170\":{}},\"comment\":{}}],[\"ldfeaturestoreitem\",{\"_index\":127,\"name\":{\"162\":{}},\"comment\":{}}],[\"ldfeaturestorekinddata\",{\"_index\":129,\"name\":{\"169\":{}},\"comment\":{}}],[\"ldflagset\",{\"_index\":146,\"name\":{\"191\":{}},\"comment\":{}}],[\"ldflagsstate\",{\"_index\":147,\"name\":{\"192\":{}},\"comment\":{}}],[\"ldflagsstateoptions\",{\"_index\":153,\"name\":{\"198\":{}},\"comment\":{}}],[\"ldflagvalue\",{\"_index\":157,\"name\":{\"202\":{}},\"comment\":{}}],[\"ldkeyedfeaturestoreitem\",{\"_index\":128,\"name\":{\"165\":{}},\"comment\":{}}],[\"ldlogger\",{\"_index\":257,\"name\":{\"349\":{}},\"comment\":{}}],[\"ldloglevel\",{\"_index\":261,\"name\":{\"354\":{}},\"comment\":{}}],[\"ldmultikindcontext\",{\"_index\":241,\"name\":{\"320\":{}},\"comment\":{}}],[\"ldoptions\",{\"_index\":163,\"name\":{\"208\":{}},\"comment\":{}}],[\"ldproxyoptions\",{\"_index\":188,\"name\":{\"239\":{}},\"comment\":{}}],[\"ldsinglekindcontext\",{\"_index\":242,\"name\":{\"322\":{}},\"comment\":{}}],[\"ldtlsoptions\",{\"_index\":193,\"name\":{\"244\":{}},\"comment\":{}}],[\"lduser\",{\"_index\":243,\"name\":{\"328\":{}},\"comment\":{}}],[\"legacy\",{\"_index\":232,\"name\":{\"304\":{}},\"comment\":{}}],[\"level\",{\"_index\":254,\"name\":{\"344\":{}},\"comment\":{}}],[\"log\",{\"_index\":289,\"name\":{\"413\":{},\"422\":{}},\"comment\":{}}],[\"logger\",{\"_index\":75,\"name\":{\"96\":{},\"214\":{},\"420\":{}},\"comment\":{}}],[\"loglevel\",{\"_index\":286,\"name\":{\"408\":{}},\"comment\":{}}],[\"method\",{\"_index\":35,\"name\":{\"49\":{}},\"comment\":{}}],[\"min\",{\"_index\":271,\"name\":{\"376\":{}},\"comment\":{}}],[\"name\",{\"_index\":16,\"name\":{\"18\":{},\"20\":{},\"24\":{},\"314\":{},\"325\":{},\"331\":{}},\"comment\":{}}],[\"namespace\",{\"_index\":111,\"name\":{\"135\":{},\"155\":{}},\"comment\":{}}],[\"number\",{\"_index\":278,\"name\":{\"396\":{}},\"comment\":{}}],[\"numberwithmin\",{\"_index\":283,\"name\":{\"402\":{}},\"comment\":{}}],[\"numberwithminimum\",{\"_index\":270,\"name\":{\"374\":{}},\"comment\":{}}],[\"object\",{\"_index\":280,\"name\":{\"398\":{}},\"comment\":{}}],[\"objectorfactory\",{\"_index\":279,\"name\":{\"397\":{}},\"comment\":{}}],[\"offline\",{\"_index\":174,\"name\":{\"221\":{}},\"comment\":{}}],[\"offvariation\",{\"_index\":84,\"name\":{\"107\":{}},\"comment\":{}}],[\"on\",{\"_index\":66,\"name\":{\"86\":{},\"105\":{},\"268\":{}},\"comment\":{}}],[\"onclose\",{\"_index\":41,\"name\":{\"56\":{}},\"comment\":{}}],[\"onerror\",{\"_index\":42,\"name\":{\"57\":{}},\"comment\":{}}],[\"onopen\",{\"_index\":43,\"name\":{\"58\":{}},\"comment\":{}}],[\"onretrying\",{\"_index\":44,\"name\":{\"59\":{}},\"comment\":{}}],[\"options\",{\"_index\":34,\"name\":{\"47\":{}},\"comment\":{}}],[\"os\",{\"_index\":13,\"name\":{\"15\":{}},\"comment\":{}}],[\"passphrase\",{\"_index\":199,\"name\":{\"252\":{}},\"comment\":{}}],[\"paths\",{\"_index\":73,\"name\":{\"94\":{}},\"comment\":{}}],[\"persistentdatastore\",{\"_index\":119,\"name\":{\"148\":{}},\"comment\":{}}],[\"persistentstoredatakind\",{\"_index\":122,\"name\":{\"154\":{}},\"comment\":{}}],[\"pfx\",{\"_index\":198,\"name\":{\"250\":{}},\"comment\":{}}],[\"platform\",{\"_index\":0,\"name\":{\"0\":{},\"31\":{},\"72\":{}},\"comment\":{}}],[\"platformdata\",{\"_index\":12,\"name\":{\"14\":{},\"29\":{}},\"comment\":{}}],[\"pollinterval\",{\"_index\":172,\"name\":{\"219\":{}},\"comment\":{}}],[\"port\",{\"_index\":190,\"name\":{\"241\":{}},\"comment\":{}}],[\"prerequisitekey\",{\"_index\":143,\"name\":{\"188\":{}},\"comment\":{}}],[\"privateattributenames\",{\"_index\":251,\"name\":{\"341\":{}},\"comment\":{}}],[\"privateattributereferences\",{\"_index\":223,\"name\":{\"292\":{}},\"comment\":{}}],[\"privateattributes\",{\"_index\":180,\"name\":{\"227\":{},\"302\":{},\"308\":{},\"319\":{}},\"comment\":{}}],[\"proxyoptions\",{\"_index\":173,\"name\":{\"220\":{}},\"comment\":{}}],[\"readfile\",{\"_index\":10,\"name\":{\"12\":{}},\"comment\":{}}],[\"readtimeoutmillis\",{\"_index\":50,\"name\":{\"68\":{}},\"comment\":{}}],[\"reason\",{\"_index\":138,\"name\":{\"182\":{}},\"comment\":{}}],[\"redactionname\",{\"_index\":206,\"name\":{\"272\":{}},\"comment\":{}}],[\"rejectunauthorized\",{\"_index\":200,\"name\":{\"253\":{}},\"comment\":{}}],[\"requests\",{\"_index\":23,\"name\":{\"35\":{},\"52\":{}},\"comment\":{}}],[\"requirestatus\",{\"_index\":69,\"name\":{\"90\":{},\"144\":{}},\"comment\":{}}],[\"response\",{\"_index\":30,\"name\":{\"42\":{}},\"comment\":{}}],[\"retryresetintervalmillis\",{\"_index\":51,\"name\":{\"69\":{}},\"comment\":{}}],[\"ruleid\",{\"_index\":142,\"name\":{\"187\":{}},\"comment\":{}}],[\"ruleindex\",{\"_index\":141,\"name\":{\"186\":{}},\"comment\":{}}],[\"safelogger\",{\"_index\":290,\"name\":{\"418\":{}},\"comment\":{}}],[\"scheme\",{\"_index\":191,\"name\":{\"242\":{}},\"comment\":{}}],[\"sdkdata\",{\"_index\":19,\"name\":{\"23\":{},\"30\":{}},\"comment\":{}}],[\"secondary\",{\"_index\":226,\"name\":{\"297\":{},\"318\":{},\"330\":{}},\"comment\":{}}],[\"securemodehash\",{\"_index\":61,\"name\":{\"80\":{},\"262\":{}},\"comment\":{}}],[\"secureprotocol\",{\"_index\":201,\"name\":{\"254\":{}},\"comment\":{}}],[\"sendevents\",{\"_index\":178,\"name\":{\"225\":{}},\"comment\":{}}],[\"serializeditem\",{\"_index\":125,\"name\":{\"160\":{}},\"comment\":{}}],[\"serializeditemdescriptor\",{\"_index\":124,\"name\":{\"157\":{}},\"comment\":{}}],[\"servername\",{\"_index\":202,\"name\":{\"255\":{}},\"comment\":{}}],[\"stale\",{\"_index\":106,\"name\":{\"130\":{}},\"comment\":{}}],[\"staleafter\",{\"_index\":162,\"name\":{\"207\":{}},\"comment\":{}}],[\"status\",{\"_index\":31,\"name\":{\"44\":{}},\"comment\":{}}],[\"statuspollinterval\",{\"_index\":161,\"name\":{\"206\":{}},\"comment\":{}}],[\"stream\",{\"_index\":175,\"name\":{\"222\":{}},\"comment\":{}}],[\"streaminitialreconnectdelay\",{\"_index\":176,\"name\":{\"223\":{}},\"comment\":{}}],[\"streamuri\",{\"_index\":165,\"name\":{\"210\":{}},\"comment\":{}}],[\"string\",{\"_index\":277,\"name\":{\"395\":{}},\"comment\":{}}],[\"stringarray\",{\"_index\":281,\"name\":{\"399\":{}},\"comment\":{}}],[\"stringmatchingregex\",{\"_index\":272,\"name\":{\"380\":{},\"403\":{}},\"comment\":{}}],[\"subsystems\",{\"_index\":126,\"name\":{\"161\":{}},\"comment\":{}}],[\"testdata\",{\"_index\":76,\"name\":{\"97\":{}},\"comment\":{}}],[\"testdataflagbuilder\",{\"_index\":80,\"name\":{\"102\":{}},\"comment\":{}}],[\"testdatarulebuilder\",{\"_index\":93,\"name\":{\"116\":{}},\"comment\":{}}],[\"text\",{\"_index\":32,\"name\":{\"45\":{}},\"comment\":{}}],[\"thenreturn\",{\"_index\":96,\"name\":{\"119\":{}},\"comment\":{}}],[\"timeout\",{\"_index\":37,\"name\":{\"51\":{},\"212\":{}},\"comment\":{}}],[\"tlsparams\",{\"_index\":183,\"name\":{\"230\":{}},\"comment\":{}}],[\"tojson\",{\"_index\":152,\"name\":{\"197\":{}},\"comment\":{}}],[\"track\",{\"_index\":63,\"name\":{\"83\":{},\"265\":{}},\"comment\":{}}],[\"tryformat\",{\"_index\":287,\"name\":{\"411\":{}},\"comment\":{}}],[\"trywrite\",{\"_index\":288,\"name\":{\"412\":{}},\"comment\":{}}],[\"type\",{\"_index\":266,\"name\":{\"362\":{}},\"comment\":{}}],[\"typearray\",{\"_index\":269,\"name\":{\"368\":{}},\"comment\":{}}],[\"typename\",{\"_index\":267,\"name\":{\"364\":{},\"370\":{}},\"comment\":{}}],[\"typeof\",{\"_index\":268,\"name\":{\"365\":{},\"371\":{},\"378\":{},\"384\":{}},\"comment\":{}}],[\"typevalidator\",{\"_index\":262,\"name\":{\"355\":{}},\"comment\":{}}],[\"typevalidators\",{\"_index\":276,\"name\":{\"394\":{}},\"comment\":{}}],[\"update\",{\"_index\":2,\"name\":{\"2\":{},\"5\":{},\"99\":{}},\"comment\":{}}],[\"updateprocessor\",{\"_index\":170,\"name\":{\"217\":{}},\"comment\":{}}],[\"upsert\",{\"_index\":121,\"name\":{\"151\":{},\"176\":{}},\"comment\":{}}],[\"useldd\",{\"_index\":177,\"name\":{\"224\":{}},\"comment\":{}}],[\"usepreconfiguredflag\",{\"_index\":78,\"name\":{\"100\":{}},\"comment\":{}}],[\"usepreconfiguredsegment\",{\"_index\":79,\"name\":{\"101\":{}},\"comment\":{}}],[\"usercachesize\",{\"_index\":159,\"name\":{\"204\":{}},\"comment\":{}}],[\"usercachetime\",{\"_index\":160,\"name\":{\"205\":{}},\"comment\":{}}],[\"userkind\",{\"_index\":213,\"name\":{\"280\":{}},\"comment\":{}}],[\"valid\",{\"_index\":148,\"name\":{\"193\":{}},\"comment\":{}}],[\"value\",{\"_index\":136,\"name\":{\"180\":{}},\"comment\":{}}],[\"valueforall\",{\"_index\":86,\"name\":{\"109\":{}},\"comment\":{}}],[\"valueforkind\",{\"_index\":225,\"name\":{\"295\":{}},\"comment\":{}}],[\"values\",{\"_index\":27,\"name\":{\"39\":{}},\"comment\":{}}],[\"variation\",{\"_index\":58,\"name\":{\"77\":{},\"259\":{}},\"comment\":{}}],[\"variationdetail\",{\"_index\":59,\"name\":{\"78\":{},\"260\":{}},\"comment\":{}}],[\"variationforall\",{\"_index\":85,\"name\":{\"108\":{}},\"comment\":{}}],[\"variationforcontext\",{\"_index\":88,\"name\":{\"111\":{}},\"comment\":{}}],[\"variationforuser\",{\"_index\":87,\"name\":{\"110\":{}},\"comment\":{}}],[\"variationindex\",{\"_index\":137,\"name\":{\"181\":{}},\"comment\":{}}],[\"variations\",{\"_index\":82,\"name\":{\"104\":{}},\"comment\":{}}],[\"version\",{\"_index\":17,\"name\":{\"19\":{},\"21\":{},\"25\":{},\"140\":{},\"146\":{},\"158\":{},\"164\":{},\"168\":{},\"238\":{}},\"comment\":{}}],[\"versioneddata\",{\"_index\":114,\"name\":{\"138\":{}},\"comment\":{}}],[\"waitforinitialization\",{\"_index\":57,\"name\":{\"76\":{},\"258\":{}},\"comment\":{}}],[\"warn\",{\"_index\":259,\"name\":{\"351\":{},\"415\":{},\"424\":{}},\"comment\":{}}],[\"waslegacy\",{\"_index\":221,\"name\":{\"290\":{}},\"comment\":{}}],[\"watch\",{\"_index\":11,\"name\":{\"13\":{}},\"comment\":{}}],[\"withreasons\",{\"_index\":155,\"name\":{\"200\":{}},\"comment\":{}}],[\"wrappername\",{\"_index\":20,\"name\":{\"26\":{},\"233\":{}},\"comment\":{}}],[\"wrapperversion\",{\"_index\":21,\"name\":{\"27\":{},\"234\":{}},\"comment\":{}}]],\"pipeline\":[]}}"); \ No newline at end of file diff --git a/docs/assets/style.css b/docs/assets/style.css new file mode 100644 index 0000000000..8f6ed2c437 --- /dev/null +++ b/docs/assets/style.css @@ -0,0 +1,1224 @@ +:root { + /* Light */ + --light-color-background: #f2f4f8; + --light-color-background-secondary: #eff0f1; + --light-color-icon-background: var(--light-color-background); + --light-color-accent: #c5c7c9; + --light-color-text: #222; + --light-color-text-aside: #707070; + --light-color-link: #4da6ff; + --light-color-ts: #db1373; + --light-color-ts-interface: #139d2c; + --light-color-ts-enum: #9c891a; + --light-color-ts-class: #2484e5; + --light-color-ts-function: #572be7; + --light-color-ts-namespace: #b111c9; + --light-color-ts-private: #707070; + --light-color-ts-variable: #4d68ff; + --light-external-icon: url("data:image/svg+xml;utf8,"); + --light-color-scheme: light; + + /* Dark */ + --dark-color-background: #2b2e33; + --dark-color-background-secondary: #1e2024; + --dark-color-icon-background: var(--dark-color-background-secondary); + --dark-color-accent: #9096a2; + --dark-color-text: #f5f5f5; + --dark-color-text-aside: #dddddd; + --dark-color-link: #00aff4; + --dark-color-ts: #ff6492; + --dark-color-ts-interface: #6cff87; + --dark-color-ts-enum: #f4d93e; + --dark-color-ts-class: #61b0ff; + --dark-color-ts-function: #9772ff; + --dark-color-ts-namespace: #e14dff; + --dark-color-ts-private: #e2e2e2; + --dark-color-ts-variable: #4d68ff; + --dark-external-icon: url("data:image/svg+xml;utf8,"); + --dark-color-scheme: dark; +} + +@media (prefers-color-scheme: light) { + :root { + --color-background: var(--light-color-background); + --color-background-secondary: var(--light-color-background-secondary); + --color-icon-background: var(--light-color-icon-background); + --color-accent: var(--light-color-accent); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + --color-link: var(--light-color-link); + --color-ts: var(--light-color-ts); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-class: var(--light-color-ts-class); + --color-ts-function: var(--light-color-ts-function); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-private: var(--light-color-ts-private); + --color-ts-variable: var(--light-color-ts-variable); + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); + } +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--dark-color-background); + --color-background-secondary: var(--dark-color-background-secondary); + --color-icon-background: var(--dark-color-icon-background); + --color-accent: var(--dark-color-accent); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + --color-link: var(--dark-color-link); + --color-ts: var(--dark-color-ts); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-private: var(--dark-color-ts-private); + --color-ts-variable: var(--dark-color-ts-variable); + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); + } +} + +html { + color-scheme: var(--color-scheme); +} + +body { + margin: 0; +} + +:root[data-theme="light"] { + --color-background: var(--light-color-background); + --color-background-secondary: var(--light-color-background-secondary); + --color-icon-background: var(--light-color-icon-background); + --color-accent: var(--light-color-accent); + --color-text: var(--light-color-text); + --color-text-aside: var(--light-color-text-aside); + --color-link: var(--light-color-link); + --color-ts: var(--light-color-ts); + --color-ts-interface: var(--light-color-ts-interface); + --color-ts-enum: var(--light-color-ts-enum); + --color-ts-class: var(--light-color-ts-class); + --color-ts-function: var(--light-color-ts-function); + --color-ts-namespace: var(--light-color-ts-namespace); + --color-ts-private: var(--light-color-ts-private); + --color-ts-variable: var(--light-color-ts-variable); + --external-icon: var(--light-external-icon); + --color-scheme: var(--light-color-scheme); +} + +:root[data-theme="dark"] { + --color-background: var(--dark-color-background); + --color-background-secondary: var(--dark-color-background-secondary); + --color-icon-background: var(--dark-color-icon-background); + --color-accent: var(--dark-color-accent); + --color-text: var(--dark-color-text); + --color-text-aside: var(--dark-color-text-aside); + --color-link: var(--dark-color-link); + --color-ts: var(--dark-color-ts); + --color-ts-interface: var(--dark-color-ts-interface); + --color-ts-enum: var(--dark-color-ts-enum); + --color-ts-class: var(--dark-color-ts-class); + --color-ts-function: var(--dark-color-ts-function); + --color-ts-namespace: var(--dark-color-ts-namespace); + --color-ts-private: var(--dark-color-ts-private); + --color-ts-variable: var(--dark-color-ts-variable); + --external-icon: var(--dark-external-icon); + --color-scheme: var(--dark-color-scheme); +} + +h1, +h2, +h3, +h4, +h5, +h6 { + line-height: 1.2; +} + +h1 { + font-size: 1.875rem; + margin: 0.67rem 0; +} + +h2 { + font-size: 1.5rem; + margin: 0.83rem 0; +} + +h3 { + font-size: 1.25rem; + margin: 1rem 0; +} + +h4 { + font-size: 1.05rem; + margin: 1.33rem 0; +} + +h5 { + font-size: 1rem; + margin: 1.5rem 0; +} + +h6 { + font-size: 0.875rem; + margin: 2.33rem 0; +} + +.uppercase { + text-transform: uppercase; +} + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: break-word; +} + +dl, +menu, +ol, +ul { + margin: 1em 0; +} + +dd { + margin: 0 0 0 40px; +} + +.container { + max-width: 1600px; + padding: 0 2rem; +} + +@media (min-width: 640px) { + .container { + padding: 0 4rem; + } +} +@media (min-width: 1200px) { + .container { + padding: 0 8rem; + } +} +@media (min-width: 1600px) { + .container { + padding: 0 12rem; + } +} + +/* Footer */ +.tsd-generator { + border-top: 1px solid var(--color-accent); + padding-top: 1rem; + padding-bottom: 1rem; + max-height: 3.5rem; +} + +.tsd-generator > p { + margin-top: 0; + margin-bottom: 0; + padding: 0 1rem; +} + +.container-main { + display: flex; + justify-content: space-between; + position: relative; + margin: 0 auto; +} + +.col-4, +.col-8 { + box-sizing: border-box; + float: left; + padding: 2rem 1rem; +} + +.col-4 { + flex: 0 0 25%; +} +.col-8 { + flex: 1 0; + flex-wrap: wrap; + padding-left: 0; +} + +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@keyframes fade-out { + from { + opacity: 1; + visibility: visible; + } + to { + opacity: 0; + } +} +@keyframes fade-in-delayed { + 0% { + opacity: 0; + } + 33% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +@keyframes fade-out-delayed { + 0% { + opacity: 1; + visibility: visible; + } + 66% { + opacity: 0; + } + 100% { + opacity: 0; + } +} +@keyframes shift-to-left { + from { + transform: translate(0, 0); + } + to { + transform: translate(-25%, 0); + } +} +@keyframes unshift-to-left { + from { + transform: translate(-25%, 0); + } + to { + transform: translate(0, 0); + } +} +@keyframes pop-in-from-right { + from { + transform: translate(100%, 0); + } + to { + transform: translate(0, 0); + } +} +@keyframes pop-out-to-right { + from { + transform: translate(0, 0); + visibility: visible; + } + to { + transform: translate(100%, 0); + } +} +body { + background: var(--color-background); + font-family: "Segoe UI", sans-serif; + font-size: 16px; + color: var(--color-text); +} + +a { + color: var(--color-link); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +a.external[target="_blank"] { + background-image: var(--external-icon); + background-position: top 3px right; + background-repeat: no-repeat; + padding-right: 13px; +} + +code, +pre { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + padding: 0.2em; + margin: 0; + font-size: 0.875rem; + border-radius: 0.8em; +} + +pre { + padding: 10px; + border: 0.1em solid var(--color-accent); +} +pre code { + padding: 0; + font-size: 100%; +} + +blockquote { + margin: 1em 0; + padding-left: 1em; + border-left: 4px solid gray; +} + +.tsd-typography { + line-height: 1.333em; +} +.tsd-typography ul { + list-style: square; + padding: 0 0 0 20px; + margin: 0; +} +.tsd-typography h4, +.tsd-typography .tsd-index-panel h3, +.tsd-index-panel .tsd-typography h3, +.tsd-typography h5, +.tsd-typography h6 { + font-size: 1em; + margin: 0; +} +.tsd-typography h5, +.tsd-typography h6 { + font-weight: normal; +} +.tsd-typography p, +.tsd-typography ul, +.tsd-typography ol { + margin: 1em 0; +} + +@media (max-width: 1024px) { + html .col-content { + float: none; + max-width: 100%; + width: 100%; + padding-top: 3rem; + } + html .col-menu { + position: fixed !important; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + z-index: 1024; + top: 0 !important; + bottom: 0 !important; + left: auto !important; + right: 0 !important; + padding: 1.5rem 1.5rem 0 0; + max-width: 25rem; + visibility: hidden; + background-color: var(--color-background); + transform: translate(100%, 0); + } + html .col-menu > *:last-child { + padding-bottom: 20px; + } + html .overlay { + content: ""; + display: block; + position: fixed; + z-index: 1023; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.75); + visibility: hidden; + } + + .to-has-menu .overlay { + animation: fade-in 0.4s; + } + + .to-has-menu :is(header, footer, .col-content) { + animation: shift-to-left 0.4s; + } + + .to-has-menu .col-menu { + animation: pop-in-from-right 0.4s; + } + + .from-has-menu .overlay { + animation: fade-out 0.4s; + } + + .from-has-menu :is(header, footer, .col-content) { + animation: unshift-to-left 0.4s; + } + + .from-has-menu .col-menu { + animation: pop-out-to-right 0.4s; + } + + .has-menu body { + overflow: hidden; + } + .has-menu .overlay { + visibility: visible; + } + .has-menu :is(header, footer, .col-content) { + transform: translate(-25%, 0); + } + .has-menu .col-menu { + visibility: visible; + transform: translate(0, 0); + display: grid; + align-items: center; + grid-template-rows: auto 1fr; + grid-gap: 1.5rem; + max-height: 100vh; + padding: 1rem 2rem; + } + .has-menu .tsd-navigation { + max-height: 100%; + } +} + +.tsd-breadcrumb { + margin: 0; + padding: 0; + color: var(--color-text-aside); +} +.tsd-breadcrumb a { + color: var(--color-text-aside); + text-decoration: none; +} +.tsd-breadcrumb a:hover { + text-decoration: underline; +} +.tsd-breadcrumb li { + display: inline; +} +.tsd-breadcrumb li:after { + content: " / "; +} + +.tsd-comment-tags { + display: flex; + flex-direction: column; +} +dl.tsd-comment-tag-group { + display: flex; + align-items: center; + overflow: hidden; + margin: 0.5em 0; +} +dl.tsd-comment-tag-group dt { + display: flex; + margin-right: 0.5em; + font-size: 0.875em; + font-weight: normal; +} +dl.tsd-comment-tag-group dd { + margin: 0; +} +code.tsd-tag { + padding: 0.25em 0.4em; + border: 0.1em solid var(--color-accent); + margin-right: 0.25em; + font-size: 70%; +} +h1 code.tsd-tag:first-of-type { + margin-left: 0.25em; +} + +dl.tsd-comment-tag-group dd:before, +dl.tsd-comment-tag-group dd:after { + content: " "; +} +dl.tsd-comment-tag-group dd pre, +dl.tsd-comment-tag-group dd:after { + clear: both; +} +dl.tsd-comment-tag-group p { + margin: 0; +} + +.tsd-panel.tsd-comment .lead { + font-size: 1.1em; + line-height: 1.333em; + margin-bottom: 2em; +} +.tsd-panel.tsd-comment .lead:last-child { + margin-bottom: 0; +} + +.tsd-filter-visibility h4 { + font-size: 1rem; + padding-top: 0.75rem; + padding-bottom: 0.5rem; + margin: 0; +} +.tsd-filter-item:not(:last-child) { + margin-bottom: 0.5rem; +} +.tsd-filter-input { + display: flex; + width: fit-content; + width: -moz-fit-content; + align-items: center; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + cursor: pointer; +} +.tsd-filter-input input[type="checkbox"] { + cursor: pointer; + position: absolute; + width: 1.5em; + height: 1.5em; + opacity: 0; +} +.tsd-filter-input input[type="checkbox"]:disabled { + pointer-events: none; +} +.tsd-filter-input svg { + cursor: pointer; + width: 1.5em; + height: 1.5em; + margin-right: 0.5em; + border-radius: 0.33em; + /* Leaving this at full opacity breaks event listeners on Firefox. + Don't remove unless you know what you're doing. */ + opacity: 0.99; +} +.tsd-filter-input input[type="checkbox"]:focus + svg { + transform: scale(0.95); +} +.tsd-filter-input input[type="checkbox"]:focus:not(:focus-visible) + svg { + transform: scale(1); +} +.tsd-checkbox-background { + fill: var(--color-accent); +} +input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { + stroke: var(--color-text); +} +.tsd-filter-input input:disabled ~ svg > .tsd-checkbox-background { + fill: var(--color-background); + stroke: var(--color-accent); + stroke-width: 0.25rem; +} +.tsd-filter-input input:disabled ~ svg > .tsd-checkbox-checkmark { + stroke: var(--color-accent); +} + +.tsd-theme-toggle { + padding-top: 0.75rem; +} +.tsd-theme-toggle > h4 { + display: inline; + vertical-align: middle; + margin-right: 0.75rem; +} + +.tsd-hierarchy { + list-style: square; + margin: 0; +} +.tsd-hierarchy .target { + font-weight: bold; +} + +.tsd-panel-group.tsd-index-group { + margin-bottom: 0; +} +.tsd-index-panel .tsd-index-list { + list-style: none; + line-height: 1.333em; + margin: 0; + padding: 0.25rem 0 0 0; + overflow: hidden; + display: grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 1rem; + grid-template-rows: auto; +} +@media (max-width: 1024px) { + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(2, 1fr); + } +} +@media (max-width: 768px) { + .tsd-index-panel .tsd-index-list { + grid-template-columns: repeat(1, 1fr); + } +} +.tsd-index-panel .tsd-index-list li { + -webkit-page-break-inside: avoid; + -moz-page-break-inside: avoid; + -ms-page-break-inside: avoid; + -o-page-break-inside: avoid; + page-break-inside: avoid; +} +.tsd-index-panel a, +.tsd-index-panel a.tsd-parent-kind-module { + color: var(--color-ts); +} +.tsd-index-panel a.tsd-parent-kind-interface { + color: var(--color-ts-interface); +} +.tsd-index-panel a.tsd-parent-kind-enum { + color: var(--color-ts-enum); +} +.tsd-index-panel a.tsd-parent-kind-class { + color: var(--color-ts-class); +} +.tsd-index-panel a.tsd-kind-module { + color: var(--color-ts-namespace); +} +.tsd-index-panel a.tsd-kind-interface { + color: var(--color-ts-interface); +} +.tsd-index-panel a.tsd-kind-enum { + color: var(--color-ts-enum); +} +.tsd-index-panel a.tsd-kind-class { + color: var(--color-ts-class); +} +.tsd-index-panel a.tsd-kind-function { + color: var(--color-ts-function); +} +.tsd-index-panel a.tsd-kind-namespace { + color: var(--color-ts-namespace); +} +.tsd-index-panel a.tsd-kind-variable { + color: var(--color-ts-variable); +} +.tsd-index-panel a.tsd-is-private { + color: var(--color-ts-private); +} + +.tsd-flag { + display: inline-block; + padding: 0.25em 0.4em; + border-radius: 4px; + color: var(--color-comment-tag-text); + background-color: var(--color-comment-tag); + text-indent: 0; + font-size: 75%; + line-height: 1; + font-weight: normal; +} + +.tsd-anchor { + position: absolute; + top: -100px; +} + +.tsd-member { + position: relative; +} +.tsd-member .tsd-anchor + h3 { + display: flex; + align-items: center; + margin-top: 0; + margin-bottom: 0; + border-bottom: none; +} +.tsd-member [data-tsd-kind] { + color: var(--color-ts); +} +.tsd-member [data-tsd-kind="Interface"] { + color: var(--color-ts-interface); +} +.tsd-member [data-tsd-kind="Enum"] { + color: var(--color-ts-enum); +} +.tsd-member [data-tsd-kind="Class"] { + color: var(--color-ts-class); +} +.tsd-member [data-tsd-kind="Private"] { + color: var(--color-ts-private); +} + +.tsd-navigation a { + display: block; + margin: 0.4rem 0; + border-left: 2px solid transparent; + color: var(--color-text); + text-decoration: none; + transition: border-left-color 0.1s; +} +.tsd-navigation a:hover { + text-decoration: underline; +} +.tsd-navigation ul { + margin: 0; + padding: 0; + list-style: none; +} +.tsd-navigation li { + padding: 0; +} + +.tsd-navigation.primary .tsd-accordion-details > ul { + margin-top: 0.75rem; +} +.tsd-navigation.primary a { + padding: 0.75rem 0.5rem; + margin: 0; +} +.tsd-navigation.primary ul li a { + margin-left: 0.5rem; +} +.tsd-navigation.primary ul li li a { + margin-left: 1.5rem; +} +.tsd-navigation.primary ul li li li a { + margin-left: 2.5rem; +} +.tsd-navigation.primary ul li li li li a { + margin-left: 3.5rem; +} +.tsd-navigation.primary ul li li li li li a { + margin-left: 4.5rem; +} +.tsd-navigation.primary ul li li li li li li a { + margin-left: 5.5rem; +} +.tsd-navigation.primary li.current > a { + border-left: 0.15rem var(--color-text) solid; +} +.tsd-navigation.primary li.selected > a { + font-weight: bold; + border-left: 0.2rem var(--color-text) solid; +} +.tsd-navigation.primary ul li a:hover { + border-left: 0.2rem var(--color-text-aside) solid; +} +.tsd-navigation.primary li.globals + li > span, +.tsd-navigation.primary li.globals + li > a { + padding-top: 20px; +} + +.tsd-navigation.secondary.tsd-navigation--toolbar-hide { + max-height: calc(100vh - 1rem); + top: 0.5rem; +} +.tsd-navigation.secondary > ul { + display: inline; + padding-right: 0.5rem; + transition: opacity 0.2s; +} +.tsd-navigation.secondary ul li a { + padding-left: 0; +} +.tsd-navigation.secondary ul li li a { + padding-left: 1.1rem; +} +.tsd-navigation.secondary ul li li li a { + padding-left: 2.2rem; +} +.tsd-navigation.secondary ul li li li li a { + padding-left: 3.3rem; +} +.tsd-navigation.secondary ul li li li li li a { + padding-left: 4.4rem; +} +.tsd-navigation.secondary ul li li li li li li a { + padding-left: 5.5rem; +} + +a.tsd-index-link { + margin: 0.25rem 0; + font-size: 1rem; + line-height: 1.25rem; + display: inline-flex; + align-items: center; +} +.tsd-accordion-summary > h1, +.tsd-accordion-summary > h2, +.tsd-accordion-summary > h3, +.tsd-accordion-summary > h4, +.tsd-accordion-summary > h5 { + display: inline-flex; + align-items: center; + vertical-align: middle; + margin-bottom: 0; + user-select: none; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; +} +.tsd-accordion-summary { + display: block; + cursor: pointer; +} +.tsd-accordion-summary > * { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; +} +.tsd-accordion-summary::-webkit-details-marker { + display: none; +} +.tsd-index-accordion .tsd-accordion-summary svg { + margin-right: 0.25rem; +} +.tsd-index-content > :not(:first-child) { + margin-top: 0.75rem; +} +.tsd-index-heading { + margin-top: 1.5rem; + margin-bottom: 0.75rem; +} + +.tsd-kind-icon { + margin-right: 0.5rem; + width: 1.25rem; + height: 1.25rem; + min-width: 1.25rem; + min-height: 1.25rem; +} +.tsd-kind-icon path { + transform-origin: center; + transform: scale(1.1); +} +.tsd-signature > .tsd-kind-icon { + margin-right: 0.8rem; +} + +@media (min-width: 1024px) { + .col-content { + margin: 2rem auto; + } + + .menu-sticky-wrap { + position: sticky; + height: calc(100vh - 2rem); + top: 4rem; + right: 0; + padding: 0 1.5rem; + padding-top: 1rem; + margin-top: 3rem; + transition: 0.3s ease-in-out; + transition-property: top, padding-top, padding, height; + overflow-y: auto; + } + .col-menu { + border-left: 1px solid var(--color-accent); + } + .col-menu--hide { + top: 1rem; + } + .col-menu .tsd-navigation:not(:last-child) { + padding-bottom: 1.75rem; + } +} + +.tsd-panel { + margin-bottom: 2.5rem; +} +.tsd-panel.tsd-member { + margin-bottom: 4rem; +} +.tsd-panel:empty { + display: none; +} +.tsd-panel > h1, +.tsd-panel > h2, +.tsd-panel > h3 { + margin: 1.5rem -1.5rem 0.75rem -1.5rem; + padding: 0 1.5rem 0.75rem 1.5rem; +} +.tsd-panel > h1.tsd-before-signature, +.tsd-panel > h2.tsd-before-signature, +.tsd-panel > h3.tsd-before-signature { + margin-bottom: 0; + border-bottom: none; +} + +.tsd-panel-group { + margin: 4rem 0; +} +.tsd-panel-group.tsd-index-group { + margin: 2rem 0; +} +.tsd-panel-group.tsd-index-group details { + margin: 2rem 0; +} + +#tsd-search { + transition: background-color 0.2s; +} +#tsd-search .title { + position: relative; + z-index: 2; +} +#tsd-search .field { + position: absolute; + left: 0; + top: 0; + right: 2.5rem; + height: 100%; +} +#tsd-search .field input { + box-sizing: border-box; + position: relative; + top: -50px; + z-index: 1; + width: 100%; + padding: 0 10px; + opacity: 0; + outline: 0; + border: 0; + background: transparent; + color: var(--color-text); +} +#tsd-search .field label { + position: absolute; + overflow: hidden; + right: -40px; +} +#tsd-search .field input, +#tsd-search .title { + transition: opacity 0.2s; +} +#tsd-search .results { + position: absolute; + visibility: hidden; + top: 40px; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); +} +#tsd-search .results li { + padding: 0 10px; + background-color: var(--color-background); +} +#tsd-search .results li:nth-child(even) { + background-color: var(--color-background-secondary); +} +#tsd-search .results li.state { + display: none; +} +#tsd-search .results li.current, +#tsd-search .results li:hover { + background-color: var(--color-accent); +} +#tsd-search .results a { + display: block; +} +#tsd-search .results a:before { + top: 10px; +} +#tsd-search .results span.parent { + color: var(--color-text-aside); + font-weight: normal; +} +#tsd-search.has-focus { + background-color: var(--color-accent); +} +#tsd-search.has-focus .field input { + top: 0; + opacity: 1; +} +#tsd-search.has-focus .title { + z-index: 0; + opacity: 0; +} +#tsd-search.has-focus .results { + visibility: visible; +} +#tsd-search.loading .results li.state.loading { + display: block; +} +#tsd-search.failure .results li.state.failure { + display: block; +} + +.tsd-signature { + margin: 0 0 1rem 0; + padding: 1rem 0.5rem; + border: 1px solid var(--color-accent); + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + font-size: 14px; + overflow-x: auto; +} + +.tsd-signature-symbol { + color: var(--color-text-aside); + font-weight: normal; +} + +.tsd-signature-type { + font-style: italic; + font-weight: normal; +} + +.tsd-signatures { + padding: 0; + margin: 0 0 1em 0; + list-style-type: none; +} +.tsd-signatures .tsd-signature { + margin: 0; + border-color: var(--color-accent); + border-width: 1px 0; + transition: background-color 0.1s; +} +.tsd-description .tsd-signatures .tsd-signature { + border-width: 1px; +} + +ul.tsd-parameter-list, +ul.tsd-type-parameter-list { + list-style: square; + margin: 0; + padding-left: 20px; +} +ul.tsd-parameter-list > li.tsd-parameter-signature, +ul.tsd-type-parameter-list > li.tsd-parameter-signature { + list-style: none; + margin-left: -20px; +} +ul.tsd-parameter-list h5, +ul.tsd-type-parameter-list h5 { + font-size: 16px; + margin: 1em 0 0.5em 0; +} +.tsd-sources { + margin-top: 1rem; + font-size: 0.875em; +} +.tsd-sources a { + color: var(--color-text-aside); + text-decoration: underline; +} +.tsd-sources ul { + list-style: none; + padding: 0; +} + +.tsd-page-toolbar { + position: fixed; + z-index: 1; + top: 0; + left: 0; + width: 100%; + color: var(--color-text); + background: var(--color-background-secondary); + border-bottom: 1px var(--color-accent) solid; + transition: transform 0.3s ease-in-out; +} +.tsd-page-toolbar a { + color: var(--color-text); + text-decoration: none; +} +.tsd-page-toolbar a.title { + font-weight: bold; +} +.tsd-page-toolbar a.title:hover { + text-decoration: underline; +} +.tsd-page-toolbar .tsd-toolbar-contents { + display: flex; + justify-content: space-between; + height: 2.5rem; +} +.tsd-page-toolbar .table-cell { + position: relative; + white-space: nowrap; + line-height: 40px; +} +.tsd-page-toolbar .table-cell:first-child { + width: 100%; +} + +.tsd-page-toolbar--hide { + transform: translateY(-100%); +} + +.tsd-widget { + display: inline-block; + overflow: hidden; + opacity: 0.8; + height: 40px; + transition: opacity 0.1s, background-color 0.2s; + vertical-align: bottom; + cursor: pointer; +} +.tsd-widget:hover { + opacity: 0.9; +} +.tsd-widget.active { + opacity: 1; + background-color: var(--color-accent); +} +.tsd-widget.no-caption { + width: 40px; +} +.tsd-widget.no-caption:before { + margin: 0; +} + +.tsd-widget.options, +.tsd-widget.menu { + display: none; +} +@media (max-width: 1024px) { + .tsd-widget.options, + .tsd-widget.menu { + display: inline-block; + } +} +input[type="checkbox"] + .tsd-widget:before { + background-position: -120px 0; +} +input[type="checkbox"]:checked + .tsd-widget:before { + background-position: -160px 0; +} + +img { + max-width: 100%; +} + +.tsd-anchor-icon { + display: inline-flex; + align-items: center; + margin-left: 0.5rem; + vertical-align: middle; + color: var(--color-text); +} + +.tsd-anchor-icon svg { + width: 1em; + height: 1em; + visibility: hidden; +} + +.tsd-anchor-link:hover > .tsd-anchor-icon svg { + visibility: visible; +} + +.deprecated { + text-decoration: line-through; +} + +* { + scrollbar-width: thin; + scrollbar-color: var(--color-accent) var(--color-icon-background); +} + +*::-webkit-scrollbar { + width: 0.75rem; +} + +*::-webkit-scrollbar-track { + background: var(--color-icon-background); +} + +*::-webkit-scrollbar-thumb { + background-color: var(--color-accent); + border-radius: 999rem; + border: 0.25rem solid var(--color-icon-background); +} diff --git a/docs/assets/widgets.png b/docs/assets/widgets.png new file mode 100644 index 0000000000000000000000000000000000000000..c7380532ac1b45400620011c37c4dcb7aec27a4c GIT binary patch literal 480 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoH8@y+q^jrZML>b&o-U3d6^w6h1+IPUz|;DW zIZ;96kdsD>Qv^q=09&hp0GpEni<1IR%gvP3v%OR9*{MuRTKWHZyIbuBt)Ci`cU_&% z1T+i^Y)o{%281-<3TpPAUTzw5v;RY=>1rvxmPl96#kYc9hX!6V^nB|ad#(S+)}?8C zr_H+lT3B#So$T=?$(w3-{rbQ4R<@nsf$}$hwSO)A$8&`(j+wQf=Jwhb0`CvhR5DCf z^OgI)KQemrUFPH+UynC$Y~QHG%DbTVh-Skz{enNU)cV_hPu~{TD7TPZl>0&K>iuE| z7AYn$7)Jrb9GE&SfQW4q&G*@N|4cHI`VakFa5-C!ov&XD)J(qp$rJJ*9e z-sHv}#g*T7Cv048d1v~BEAzM5FztAse#q78WWC^BUCzQ U&wLp6h6BX&boFyt=akR{0G%$)mH+?% literal 0 HcmV?d00001 diff --git a/docs/assets/widgets@2x.png b/docs/assets/widgets@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4bbbd57272f3b28f47527d4951ad10f950b8ad43 GIT binary patch literal 855 zcmeAS@N?(olHy`uVBq!ia0y~yU}^xe12~w0Jcmn z@(X6T|9^jgLcx21{)7exgY)a>N6m2F0<`Rqr;B4q1>>88jUdw-7W`c)zLE*mq8W2H z-<&Jl_Hco5BuC5n@AbF5GD82~-e8-v=#zCyUX0F-o}8pPfAv`!GN$ff+TL<~@kgt} z62eO?_|&+>xBmM$@p|z`tIKEdpPf8%qI>4r7@jn<=eta*{3~?g(zz{Ke9zc-G^gr? z-7foa?LcS!hmbwzru}ICvbWLlW8;+l-}!^=c32!^nV`+`C*;0-*Y%l94pC;Cb3GXz zzSf%a!{gVr{Y_lVuUj+a)*Ca+!-Hu%xmP&&X-2CuANY8^i{D7Kg6qzP zXz_ps9+lN8ESH{K4`yu&b~I>N9xGlE&;2u*b?+Go!AhN?m-bxlLvtC#MzDF2kFzfHJ1W7ybqdefSqVhbOykd*Yi%EDuhs z4wF{ft^bv2+DDnKb8gj1FuvcV`M}luS>lO<^)8x>y1#R;a=-ZKwWTQQb)ioBbi;zh zD!f5V)8581to1LL7c9!l^PSC$NBPYif!_vAZhmL4)v4U)4UsrLYiH_9rmQDd?)(e5 z^pcH>qvBg*i0dus2r*mp4;zKvu=P#s-ti;2obl`NjjwoYd>e(oo#j_uyRb<7Pv^If zzZ|mGHmV)8^tbO%^>eqMw(@7(&3g{jEp-Najo7V75xI_ZHK*FA`elF{r5}E*d7+j_R literal 0 HcmV?d00001 diff --git a/docs/classes/AttributeReference.html b/docs/classes/AttributeReference.html new file mode 100644 index 0000000000..a582bfffc9 --- /dev/null +++ b/docs/classes/AttributeReference.html @@ -0,0 +1,197 @@ +AttributeReference | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Hierarchy

+
    +
  • AttributeReference
+
+
+
+ +
+
+

Constructors

+
+
+

Properties

+
+
+

Accessors

+
+
+

Methods

+
+
+

Constructors

+
+ +
    + +
  • +

    Take an attribute reference string, or literal string, and produce +an attribute reference.

    +

    Legacy user objects would have been created with names not +references. So, in that case, we need to use them as a component +without escaping them.

    +

    e.g. A user could contain a custom attribute of /a which would +become the literal a if treated as a reference. Which would cause +it to no longer be redacted.

    +
    +
    +

    Parameters

    +
      +
    • +
      refOrLiteral: string
      +

      The attribute reference string or literal string.

      +
    • +
    • +
      Optional literal: boolean
      +

      it true the value should be treated as a literal.

      +
    +

    Returns AttributeReference

+
+

Properties

+
+ +
components: any
+
+ +
isValid: boolean
+
+ +
redactionName: string
+

When redacting attributes this name can be directly added to the list of +redactions.

+
+
+

Accessors

+
+ +
    +
  • get depth(): number
  • +
  • +

    Returns number

+
+ +
    +
  • get isKind(): boolean
  • +
  • +

    Returns boolean

+
+

Methods

+
+ +
+
+ +
+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      depth: number
    +

    Returns string

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/BasicLogger.html b/docs/classes/BasicLogger.html new file mode 100644 index 0000000000..e4b901a802 --- /dev/null +++ b/docs/classes/BasicLogger.html @@ -0,0 +1,223 @@ +BasicLogger | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

A basic logger which handles filtering by level.

+

With the default options it will write to console.error +and it will use the formatting provided by console.error. +If the destination is overwritten, then it will use an included +formatter similar to util.format.

+

If a formatter is available, then that should be overridden +as well for performance.

+
+
+

Hierarchy

+
    +
  • BasicLogger
+
+

Implements

+
+
+
+
+ +
+
+

Constructors

+
+
+

Properties

+
+
+

Methods

+
+
+

Constructors

+
+ +
+
+

Properties

+
+ +
destination?: any
+
+ +
formatter?: any
+
+ +
log: any
+
+ +
logLevel: any
+
+ +
tryFormat: any
+
+ +
tryWrite: any
+
+

Methods

+
+ +
    + +
  • +

    The debug logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+ +
    + +
  • +

    The error logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+ +
    + +
  • +

    The info logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+ +
    + +
  • +

    The warning logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/BigSegmentStoreStatusProviderImpl.html b/docs/classes/BigSegmentStoreStatusProviderImpl.html new file mode 100644 index 0000000000..3b8489f31a --- /dev/null +++ b/docs/classes/BigSegmentStoreStatusProviderImpl.html @@ -0,0 +1,140 @@ +BigSegmentStoreStatusProviderImpl | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Class BigSegmentStoreStatusProviderImpl

+
+

An interface for querying the status of a Big Segment store.

+

The Big Segment store is the component that receives information about Big Segments, normally +from a database populated by the LaunchDarkly Relay Proxy. Big Segments are a specific type of +user segments. For more information, read the LaunchDarkly documentation: +https://docs.launchdarkly.com/home/users/big-segments

+

An implementation of this interface is returned by +LDClient.bigSegmentStoreStatusProvider. Application code never needs to implement this +interface.

+
+
+

Hierarchy

+
    +
  • BigSegmentStoreStatusProviderImpl
+
+

Implements

+
+
+
+
+ +
+
+

Constructors

+
+
+

Methods

+
+
+

Constructors

+
+ +
+
+

Methods

+
+ +
+
+ +
+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/Context.html b/docs/classes/Context.html new file mode 100644 index 0000000000..931d773170 --- /dev/null +++ b/docs/classes/Context.html @@ -0,0 +1,363 @@ +Context | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Container for a context/contexts. Because contexts come from external code +they must be thoroughly validated and then formed to comply with +the type system.

+
+
+

Hierarchy

+
    +
  • Context
+
+
+
+ +
+
+

Constructors

+
+ +
    + +
  • +

    Contexts should be created using the static factory method fromLDContext.

    +
    +

    Returns Context

+
+

Properties

+
+ +
context?: any
+
+ +
contextForKind: any
+
+ +
contexts: any
+
+ +
isMulti: any
+
+ +
isUser: any
+
+ +
kind: string
+
+ +
privateAttributeReferences?: any
+
+ +
wasLegacy: any
+
+ +
fromLegacyUser: any
+
+ +
fromMultiKindContext: any
+
+ +
fromSingleKindContext: any
+
+ +
getValueFromContext: any
+
+ +
userKind: string
+
+

Accessors

+
+ +
    +
  • get canonicalKey(): string
  • +
  • +

    Get the canonical key for this context.

    +
    +

    Returns string

+
+ +
    +
  • get isMultiKind(): boolean
  • +
  • +

    True if this is a multi-kind context.

    +
    +

    Returns boolean

+
+ +
    +
  • get kinds(): string[]
  • +
  • +

    Get the kinds of this context.

    +
    +

    Returns string[]

+
+ +
    +
  • get kindsAndKeys(): Record<string, string>
  • +
  • +

    Get the kinds, and their keys, for this context.

    +
    +

    Returns Record<string, string>

+
+ +
    +
  • get legacy(): boolean
  • +
  • +

    Returns boolean

+
+

Methods

+
+ +
    + +
  • +

    Get the underlying context objects from this context.

    +

    This method is intended to be used in event generation.

    +

    The returned objects should not be modified.

    +
    +

    Returns [string, LDContextCommon][]

+
+ +
    + +
  • +

    Attempt to get a key for the specified kind.

    + +

    Returns

    The key for the specified kind, or undefined.

    +
    +
    +

    Parameters

    +
      +
    • +
      Optional kind: string
      +

      The kind to get a key for.

      +
    +

    Returns undefined | string

+
+ +
    + +
  • +

    Get the attribute references.

    +
    +
    +

    Parameters

    +
      +
    • +
      kind: string
    +

    Returns AttributeReference[]

+
+ +
    + +
  • +

    Attempt to get a secondary key from a context.

    + +

    Returns

    the secondary key, or undefined if not present or not a string.

    +
    +
    +

    Parameters

    +
      +
    • +
      Optional kind: string
      +

      The kind of the context to get the secondary key for.

      +
    +

    Returns undefined | string

+
+ +
    + +
  • +

    Attempt to get a value for the given context kind using the given reference.

    + +

    Returns

    a value or undefined if one is not found.

    +
    +
    +

    Parameters

    +
      +
    • +
      reference: AttributeReference
      +

      The reference to the value to get.

      +
    • +
    • +
      Optional kind: string
      +

      The kind of the context to get the value for.

      +
    +

    Returns any

+
+ +
    + +
  • +

    Attempt to create a Context from an LDContext.

    + +

    Returns

    a Context or undefined if one could not be created.

    +
    +
    +

    Parameters

    +
      +
    • +
      context: LDContext
      +

      The input context to create a Context from.

      +
    +

    Returns undefined | Context

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/ContextFilter.html b/docs/classes/ContextFilter.html new file mode 100644 index 0000000000..c9580527c9 --- /dev/null +++ b/docs/classes/ContextFilter.html @@ -0,0 +1,133 @@ +ContextFilter | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Hierarchy

+
    +
  • ContextFilter
+
+
+
+ +
+
+

Constructors

+
+ +
+
+

Properties

+
+ +
allAttributesPrivate: any
+
+ +
filterSingleKind: any
+
+ +
getAttributesToFilter: any
+
+ +
privateAttributes: any
+
+

Methods

+
+ +
    + +
  • +
    +

    Parameters

    +
    +

    Returns any

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/DateValidator.html b/docs/classes/DateValidator.html new file mode 100644 index 0000000000..976af26d63 --- /dev/null +++ b/docs/classes/DateValidator.html @@ -0,0 +1,111 @@ +DateValidator | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Validate a value is a date. Values which are numbers are treated as dates and any string +which if compliant with time.RFC3339Nano is a date.

+
+
+

Hierarchy

+
    +
  • DateValidator
+
+

Implements

+
+
+
+
+ +
+
+

Constructors

+
+
+

Methods

+
+
+

Constructors

+
+ +
+
+

Methods

+
+ +
    + +
  • +

    Returns string

+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      u: unknown
    +

    Returns boolean

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/FactoryOrInstance.html b/docs/classes/FactoryOrInstance.html new file mode 100644 index 0000000000..7c1627ae32 --- /dev/null +++ b/docs/classes/FactoryOrInstance.html @@ -0,0 +1,110 @@ +FactoryOrInstance | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Validate a factory or instance.

+
+
+

Hierarchy

+
    +
  • FactoryOrInstance
+
+

Implements

+
+
+
+
+ +
+
+

Constructors

+
+
+

Methods

+
+
+

Constructors

+
+ +
+
+

Methods

+
+ +
    + +
  • +

    Returns string

+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      factoryOrInstance: unknown
    +

    Returns boolean

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/Function.html b/docs/classes/Function.html new file mode 100644 index 0000000000..ff7a6b127e --- /dev/null +++ b/docs/classes/Function.html @@ -0,0 +1,110 @@ +Function | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Validate a value is a function.

+
+
+

Hierarchy

+
    +
  • Function
+
+

Implements

+
+
+
+
+ +
+
+

Constructors

+
+
+

Methods

+
+
+

Constructors

+
+ +
+
+

Methods

+
+ +
    + +
  • +

    Returns string

+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      u: unknown
    +

    Returns u is ((...args: any[]) => void)

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/LDClientImpl.html b/docs/classes/LDClientImpl.html new file mode 100644 index 0000000000..07c7b13609 --- /dev/null +++ b/docs/classes/LDClientImpl.html @@ -0,0 +1,547 @@ +LDClientImpl | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

The LaunchDarkly SDK client object.

+

Create this object with init. Applications should configure the client at startup time and +continue to use it throughout the lifetime of the application, rather than creating instances on +the fly.

+
+
+

Hierarchy

+
    +
  • LDClientImpl
+
+

Implements

+
+
+
+
+ +
+
+

Constructors

+
+ +
+
+

Properties

+
+ +
bigSegmentStoreStatusProvider: BigSegmentStoreStatusProviderImpl = ...
+

Intended for use by platform specific client implementations.

+

It is not included in the main interface because it requires the use of +a platform event system. For node this would be an EventEmitter, for other +platforms it would likely be an EventTarget.

+
+
+ +
initState: InitState = InitState.Initializing
+
+ +
platform: Platform
+
+

Methods

+
+ +
    + +
  • +

    Builds an object that encapsulates the state of all feature flags for a given context. +This includes the flag values and also metadata that can be used on the front end. This +method does not send analytics events back to LaunchDarkly.

    +

    The most common use case for this method is to bootstrap a set of client-side +feature flags from a back-end service. Call the toJSON() method of the returned object +to convert it to the data structure used by the client-side SDK.

    + +

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved + with the result as an LDFlagsState.

    +
    +
    +

    Parameters

    +
      +
    • +
      context: LDContext
      +

      The context requesting the feature flags.

      +
    • +
    • +
      Optional options: LDFlagsStateOptions
      +

      Optional LDFlagsStateOptions to determine how the state is computed.

      +
    • +
    • +
      Optional callback: ((err: Error, res: LDFlagsState) => void)
      +

      A Node-style callback to receive the result (as an LDFlagsState). If omitted, you + will receive a Promise instead.

      +
      +
    +

    Returns Promise<LDFlagsState>

+
+ +
    + +
  • +

    Discards all network connections, background tasks, and other resources held by the client.

    +

    Do not attempt to use the client after calling this method.

    +
    +

    Returns void

+
+ +
    + +
  • +

    Flushes all pending analytics events.

    +

    Normally, batches of events are delivered in the background at intervals determined by the +flushInterval property of LDOptions. Calling flush() triggers an immediate delivery. +However, like Node I/O in general, this is still an asynchronous operation so you must still +use Promise chaining, a callback, or async/await to detect when it has finished or failed.

    + +

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which resolves once + flushing is finished. Note that the Promise will be rejected if the HTTP request + fails, so be sure to attach a rejection handler to it.

    +
    +
    +

    Parameters

    +
      +
    • +
      Optional callback: ((err: Error, res: boolean) => void)
      +

      A function which will be called when the flush completes (meaning that all pending events + have been delivered to LaunchDarkly). If omitted, you will receive a Promise instead.

      +
      +
        +
      • +
          +
        • (err: Error, res: boolean): void
        • +
        • +
          +

          Parameters

          +
            +
          • +
            err: Error
          • +
          • +
            res: boolean
          +

          Returns void

    +

    Returns Promise<void>

+
+ +
    + +
  • +

    Identifies a context to LaunchDarkly.

    +

    This simply creates an analytics event that will transmit the given user properties to +LaunchDarkly, so that the context will be visible on your dashboard even if you have not +evaluated any flags for that user. It has no other effect.

    +

    If the context is omitted or has no key, the client will log a warning +and will not send an event.

    +
    +
    +

    Parameters

    +
      +
    • +
      context: LDContext
      +

      The context properties. Must contain at least the key property.

      +
    +

    Returns void

+
+ +
    + +
  • +

    Tests whether the client has completed initialization.

    +

    If this returns false, it means that the client has not yet successfully connected to +LaunchDarkly. It might still be in the process of starting up, or it might be attempting to +reconnect after an unsuccessful attempt, or it might have received an unrecoverable error (such +as an invalid SDK key) and given up.

    + +

    Returns

    True if the client has successfully initialized.

    +
    +

    Returns boolean

+
+ +
+
+ +
    + +
  • +

    Registers an event listener that will be called when the client triggers some type of event.

    +
      +
    • "ready": Sent only once, when the client has successfully connected to LaunchDarkly. +Alternately, you can detect this with waitForInitialization.
    • +
    • "failed": Sent only once, if the client has permanently failed to connect to LaunchDarkly. +Alternately, you can detect this with waitForInitialization.
    • +
    • "error": Contains an error object describing some abnormal condition that the client has +detected (such as a network error).
    • +
    • "update": The client has received a change to a feature flag. The event parameter is an +object containing a single property, key, the flag key. Note that this does not necessarily +mean the flag's value has changed for any particular context, only that some part of the flag +configuration was changed.
    • +
    • "update:KEY": The client has received a change to the feature flag whose key is KEY. This +is the same as "update" but allows you to listen for a specific flag.
    • +
    +
    +
    +

    Parameters

    +
      +
    • +
      event: string | symbol
      +

      the name of the event to listen for

      +
    • +
    • +
      listener: ((...args: any[]) => void)
      +

      the function to call when the event happens

      +
      +
        +
      • +
          +
        • (...args: any[]): void
        • +
        • +
          +

          Parameters

          +
            +
          • +
            Rest ...args: any[]
          +

          Returns void

    +

    Returns LDClientImpl

+
+ +
+
+ +
    + +
  • +

    Tracks that a context performed an event.

    +

    LaunchDarkly automatically tracks pageviews and clicks that are specified in the Goals section +of the dashboard. This can be used to track custom goals or other events that do not currently +have goals.

    +

    Note that event delivery is asynchronous, so the event may not actually be sent until later; +see flush.

    +

    If the context is omitted or has no key, the client will log a warning and will not send an +event.

    +
    +
    +

    Parameters

    +
      +
    • +
      key: string
      +

      The name of the event, which may correspond to a goal in A/B tests.

      +
    • +
    • +
      context: LDContext
      +

      The context to track.

      +
    • +
    • +
      Optional data: any
      +

      Optional additional information to associate with the event.

      +
    • +
    • +
      Optional metricValue: number
      +

      A numeric value used by the LaunchDarkly experimentation feature in numeric + custom metrics. Can be omitted if this event is used by only non-numeric metrics. This field + will also be returned as part of the custom event for Data Export.

      +
    +

    Returns void

+
+ +
    + +
  • +

    Determines the variation of a feature flag for a context.

    + +

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved with + the result value.

    +
    +
    +

    Parameters

    +
      +
    • +
      key: string
      +

      The unique key of the feature flag.

      +
    • +
    • +
      context: LDContext
      +

      The context requesting the flag. The client will generate an analytics event to + register this context with LaunchDarkly if the context does not already exist.

      +
    • +
    • +
      defaultValue: any
      +

      The default value of the flag, to be used if the value is not available + from LaunchDarkly.

      +
    • +
    • +
      Optional callback: ((err: any, res: any) => void)
      +

      A Node-style callback to receive the result value. If omitted, you will receive + a Promise instead.

      +
      +
        +
      • +
          +
        • (err: any, res: any): void
        • +
        • +
          +

          Parameters

          +
            +
          • +
            err: any
          • +
          • +
            res: any
          +

          Returns void

    +

    Returns Promise<any>

+
+ +
    + +
  • +

    Determines the variation of a feature flag for a context, along with information about how it +was calculated.

    +

    The reason property of the result will also be included in analytics events, if you are +capturing detailed event data for this flag.

    +

    For more information, see the SDK reference +guide.

    + +

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved with + the result (as an LDEvaluationDetail).

    +
    +
    +

    Parameters

    +
      +
    • +
      key: string
      +

      The unique key of the feature flag.

      +
    • +
    • +
      context: LDContext
      +

      The context requesting the flag. The client will generate an analytics event to + register this context with LaunchDarkly if the context does not already exist.

      +
    • +
    • +
      defaultValue: any
      +

      The default value of the flag, to be used if the value is not available + from LaunchDarkly.

      +
    • +
    • +
      Optional callback: ((err: any, res: LDEvaluationDetail) => void)
      +

      A Node-style callback to receive the result (as an LDEvaluationDetail). If + omitted, you will receive a Promise instead.

      +
      +
    +

    Returns Promise<LDEvaluationDetail>

+
+ +
    + +
  • +

    Returns a Promise that tracks the client's initialization state.

    +

    The Promise will be resolved if the client successfully initializes, or rejected if client +initialization has failed unrecoverably (for instance, if it detects that the SDK key is +invalid). Keep in mind that unhandled Promise rejections can be fatal in Node, so if you call +this method, be sure to attach a rejection handler to it (or, if using async/await, a catch +block).

    +

    Note that you can also use event listeners (on) for the same purpose: the event "ready" +indicates success, and "failed" indicates failure.

    +

    There is no built-in timeout for this method. If you want your code to stop waiting on the +Promise after some amount of time, you could use +https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race|Promise.race() +or one of the several NPM helper packages that provides a standard mechanism for this. +Regardless of whether you continue to wait, the SDK will still retry all connection failures +indefinitely unless it gets an unrecoverable error as described above.

    + +

    Returns

    A Promise that will be resolved if the client initializes successfully, or rejected if it + fails. If successful, the result is the same client object.

    + +

    Example

    This example shows use of Promise chaining methods for specifying handlers:

    +
      client.waitForInitialization().then(() => {
    // do whatever is appropriate if initialization has succeeded
    }).catch(err => {
    // do whatever is appropriate if initialization has failed
    }) +
    + +

    Example

    This example shows use of async/await syntax for specifying handlers:

    +
      try {
    await client.waitForInitialization();
    // do whatever is appropriate if initialization has succeeded
    } catch (err) {
    // do whatever is appropriate if initialization has failed
    } +
    +
    +

    Returns Promise<LDClient>

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/NumberWithMinimum.html b/docs/classes/NumberWithMinimum.html new file mode 100644 index 0000000000..1d133f4bbf --- /dev/null +++ b/docs/classes/NumberWithMinimum.html @@ -0,0 +1,136 @@ +NumberWithMinimum | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Validate a value is a number and is greater or eval than a minimum.

+
+
+

Hierarchy

+
    +
  • Type<number> +
      +
    • NumberWithMinimum
+
+
+
+ +
+
+

Constructors

+
+
+

Properties

+
+
+

Methods

+
+
+

Constructors

+
+ +
+
+

Properties

+
+ +
min: number
+
+ +
typeOf: string
+
+

Methods

+
+ +
    + +
  • +

    Returns string

+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      u: unknown
    +

    Returns u is number

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/SafeLogger.html b/docs/classes/SafeLogger.html new file mode 100644 index 0000000000..fa2f10d9ea --- /dev/null +++ b/docs/classes/SafeLogger.html @@ -0,0 +1,212 @@ +SafeLogger | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

The safeLogger logic exists because we allow the application to pass in a custom logger, but +there is no guarantee that the logger works correctly and if it ever throws exceptions there +could be serious consequences (e.g. an uncaught exception within an error event handler, due +to the SDK trying to log the error, can terminate the application). An exception could result +from faulty logic in the logger implementation, or it could be that this is not a logger at +all but some other kind of object; the former is handled by a catch block that logs an error +message to the SDK's default logger, and we can at least partly guard against the latter by +checking for the presence of required methods at configuration time.

+
+
+

Hierarchy

+
    +
  • SafeLogger
+
+

Implements

+
+
+
+
+ +
+
+

Constructors

+
+
+

Properties

+
+
+

Methods

+
+
+

Constructors

+
+ +
    + +
  • +

    Construct a safe logger with the specified logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      logger: LDLogger
      +

      The logger to use.

      +
    • +
    • +
      fallback: LDLogger
      +

      A fallback logger to use in case an issue is encountered using +the provided logger.

      +
    +

    Returns SafeLogger

+
+

Properties

+
+ +
fallback: any
+
+ +
log: any
+
+ +
logger: any
+
+

Methods

+
+ +
    + +
  • +

    The debug logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+ +
    + +
  • +

    The error logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+ +
    + +
  • +

    The info logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+ +
    + +
  • +

    The warning logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/StringMatchingRegex.html b/docs/classes/StringMatchingRegex.html new file mode 100644 index 0000000000..9051c3175d --- /dev/null +++ b/docs/classes/StringMatchingRegex.html @@ -0,0 +1,136 @@ +StringMatchingRegex | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Validate a value is a string and it matches the given expression.

+
+
+

Hierarchy

+
    +
  • Type<string> +
      +
    • StringMatchingRegex
+
+
+
+ +
+
+

Constructors

+
+
+

Properties

+
+
+

Methods

+
+
+

Constructors

+
+ +
+
+

Properties

+
+ +
expression: RegExp
+
+ +
typeOf: string
+
+

Methods

+
+ +
    + +
  • +

    Returns string

+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      u: unknown
    +

    Returns u is string

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/Type.html b/docs/classes/Type.html new file mode 100644 index 0000000000..7709fb8f5d --- /dev/null +++ b/docs/classes/Type.html @@ -0,0 +1,151 @@ +Type | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Validate a basic type.

+
+
+

Type Parameters

+
    +
  • +

    T

+
+

Hierarchy

+
+
+

Implements

+
+
+
+
+ +
+
+

Constructors

+
+
+

Properties

+
+
+

Methods

+
+
+

Constructors

+
+ +
    + +
  • +
    +

    Type Parameters

    +
      +
    • +

      T

    +
    +

    Parameters

    +
      +
    • +
      typeName: string
    • +
    • +
      example: T
    +

    Returns Type<T>

+
+

Properties

+
+ +
typeName: any
+
+ +
typeOf: string
+
+

Methods

+
+ +
    + +
  • +

    Returns string

+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      u: unknown
    +

    Returns u is T

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/TypeArray.html b/docs/classes/TypeArray.html new file mode 100644 index 0000000000..aa663588d8 --- /dev/null +++ b/docs/classes/TypeArray.html @@ -0,0 +1,148 @@ +TypeArray | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Validate an array of the specified type.

+
+
+

Type Parameters

+
    +
  • +

    T

+
+

Hierarchy

+
    +
  • TypeArray
+
+

Implements

+
+
+
+
+ +
+
+

Constructors

+
+
+

Properties

+
+
+

Methods

+
+
+

Constructors

+
+ +
    + +
  • +
    +

    Type Parameters

    +
      +
    • +

      T

    +
    +

    Parameters

    +
      +
    • +
      typeName: string
    • +
    • +
      example: T
    +

    Returns TypeArray<T>

+
+

Properties

+
+ +
typeName: any
+
+ +
typeOf: string
+
+

Methods

+
+ +
    + +
  • +

    Returns string

+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      u: unknown
    +

    Returns u is T

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/classes/TypeValidators.html b/docs/classes/TypeValidators.html new file mode 100644 index 0000000000..f370aefab9 --- /dev/null +++ b/docs/classes/TypeValidators.html @@ -0,0 +1,170 @@ +TypeValidators | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

A set of standard type validators.

+
+
+

Hierarchy

+
    +
  • TypeValidators
+
+
+
+ +
+
+

Constructors

+
+ +
+
+

Properties

+
+ +
Boolean: Type<boolean>
+
+ +
+
+ +
Function: Function
+
+ +
Number: Type<number>
+
+ +
Object: Type<object>
+
+ +
ObjectOrFactory: FactoryOrInstance
+
+ +
String: Type<string>
+
+ +
StringArray: TypeArray<string>
+
+

Methods

+
+ +
+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000000..2c3679bd1c --- /dev/null +++ b/docs/index.html @@ -0,0 +1,145 @@ +@launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+

@launchdarkly/js-server-sdk-common

+
+ +

LaunchDarkly Server-Side SDK for JavaScript

+
+ + +

The LaunchDarkly Server-Side SDK for Node.js is designed primarily for use in multi-user systems such as web servers and applications. It follows the server-side LaunchDarkly model for multi-user contexts. It is not intended for use in desktop and embedded systems applications.

+

For using LaunchDarkly in client-side Node.js applications, refer to our Client-side Node.js SDK.

+ + +

LaunchDarkly overview

+
+

LaunchDarkly is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. Get started using LaunchDarkly today!

+

Twitter Follow

+ + +

Supported Node versions

+
+

This version of the LaunchDarkly SDK is compatible with Node.js versions 12 and above.

+ + +

Getting started

+
+

Refer to the SDK reference guide for instructions on getting started with using the SDK.

+ + +

Learn more

+
+

Check out our documentation for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the complete reference guide for this SDK.

+

The authoritative description of all properties and methods is in the TypeScript documentation.

+ + +

Testing

+
+

We run integration tests for all our SDKs using a centralized test harness. This approach gives us the ability to test for consistency across SDKs, as well as test networking behavior in a long-running application. These tests cover each method in the SDK, and verify that event sending, flag evaluation, stream reconnection, and other aspects of the SDK all behave correctly.

+ + +

Contributing

+
+

We encourage pull requests and other contributions from the community. Check out our contributing guidelines for instructions on how to contribute to this SDK.

+ + +

About LaunchDarkly

+
+
    +
  • LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
      +
    • Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
    • +
    • Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
    • +
    • Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
    • +
    • Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline.
    • +
    +
  • +
  • LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Check out our documentation for a complete list.
  • +
  • Explore LaunchDarkly +
  • +
+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/BasicLoggerOptions.html b/docs/interfaces/BasicLoggerOptions.html new file mode 100644 index 0000000000..da1d483a6f --- /dev/null +++ b/docs/interfaces/BasicLoggerOptions.html @@ -0,0 +1,133 @@ +BasicLoggerOptions | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Interface BasicLoggerOptions

+
+

Configuration for basicLogger.

+
+
+

Hierarchy

+
    +
  • BasicLoggerOptions
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
destination?: ((line: string) => void)
+
+

Type declaration

+
    +
  • +
      +
    • (line: string): void
    • +
    • +

      An optional function to use to print each log line.

      +

      If this is specified, basicLogger calls it to write each line of output. The +argument is a fully formatted log line, not including a linefeed. The function +is only called for log levels that are enabled.

      +

      If not specified, the default is console.error.

      +

      Setting this property to anything other than a function will cause SDK +initialization to fail.

      +
      +
      +

      Parameters

      +
        +
      • +
        line: string
      +

      Returns void

+
+ +
formatter?: ((...args: any[]) => string)
+
+

Type declaration

+
    +
  • +
      +
    • (...args: any[]): string
    • +
    • +

      An optional formatter to use. The formatter should be compatible +with node-style format strings like those used with util.format.

      +

      If not specified, then a default implementation will be used. +But using a node-specific implementation, for instance, would +have performance and quality benefits.

      +
      +
      +

      Parameters

      +
        +
      • +
        Rest ...args: any[]
      +

      Returns string

+
+ +
level?: LDLogLevel
+

The lowest level of log message to enable.

+

See LDLogLevel for a list of possible levels. Setting a level here causes +all lower-importance levels to be disabled: for instance, if you specify +'warn', then 'debug' and 'info' are disabled.

+

If not specified, the default is 'info' (meaning that 'debug' is disabled).

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDBigSegmentsOptions.html b/docs/interfaces/LDBigSegmentsOptions.html new file mode 100644 index 0000000000..960498a322 --- /dev/null +++ b/docs/interfaces/LDBigSegmentsOptions.html @@ -0,0 +1,138 @@ +LDBigSegmentsOptions | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Interface LDBigSegmentsOptions

+
+

Additional parameters for configuring the SDK's Big Segments behavior.

+

Big Segments are a specific type of user segments. For more information, read the LaunchDarkly +documentation: https://docs.launchdarkly.com/home/users/big-segments

+ +

See

bigSegments

+
+
+

Hierarchy

+
    +
  • LDBigSegmentsOptions
+
+
+
+ +
+
+

Properties

+
+ +
staleAfter?: number
+

The maximum length of time between updates of the Big Segments data before the data is +considered out of date, in seconds.

+

Normally, the LaunchDarkly Relay Proxy updates a timestamp in the Big Segment store at +intervals to confirm that it is still in sync with the LaunchDarkly data, even if there have +been no changes to the store. +If the timestamp falls behind the current time by the amount specified in staleAfter, the +SDK assumes that something is not working correctly in this process and that the data may not +be accurate.

+

While in a stale state, the SDK will still continue using the last known data, but the status +from +getStatus will have stale: true, and any +LDEvaluationReason generated from a feature flag that references a Big Segment will +have a bigSegmentsStatus of "STALE".

+

If not specified, the default value is 120 (two minutes). Zero or negative values are changed +to the default.

+
+
+ +
statusPollInterval?: number
+

The interval at which the SDK will poll the Big Segment store to make sure it is available +and to determine how long ago it was updated, in seconds.

+

If not specified, the default value is 5. Zero or negative values are changed to the default.

+
+
+ +
userCacheSize?: number
+

The maximum number of users whose Big Segment state will be cached by the SDK at any given +time.

+

To reduce database traffic, the SDK maintains a least-recently-used cache by user key. When a +feature flag that references a Big Segment is evaluated for some user who is not currently in +the cache, the SDK queries the database for all Big Segment memberships of that user, and +stores them together in a single cache entry. If the cache is full, the oldest entry is +dropped.

+

A higher value for userCacheSize means that database queries for Big Segments will be done +less often for recently-referenced users, if the application has many users, at the cost of +increased memory used by the cache.

+

Cache entries can also expire based on the setting of userCacheTime.

+

If not specified, the default value is 1000.

+
+
+ +
userCacheTime?: number
+

The maximum length of time that the Big Segment state for a user will be cached by the SDK, +in seconds.

+

See userCacheSize for more about this cache. A higher value for userCacheTime means +that database queries for the Big Segment state of any given user will be done less often, but +that changes to segment membership may not be detected as soon.

+

If not specified, the default value is 5. Negative values are changed to the default.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDClient.html b/docs/interfaces/LDClient.html new file mode 100644 index 0000000000..8160f060d1 --- /dev/null +++ b/docs/interfaces/LDClient.html @@ -0,0 +1,484 @@ +LDClient | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

The LaunchDarkly SDK client object.

+

Create this object with init. Applications should configure the client at startup time and +continue to use it throughout the lifetime of the application, rather than creating instances on +the fly.

+
+
+

Hierarchy

+
    +
  • LDClient
+
+

Implemented by

+
+
+
+
+ +
+
+

Methods

+
+ +
    + +
  • +

    Builds an object that encapsulates the state of all feature flags for a given context. +This includes the flag values and also metadata that can be used on the front end. This +method does not send analytics events back to LaunchDarkly.

    +

    The most common use case for this method is to bootstrap a set of client-side +feature flags from a back-end service. Call the toJSON() method of the returned object +to convert it to the data structure used by the client-side SDK.

    + +

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved + with the result as an LDFlagsState.

    +
    +
    +

    Parameters

    +
      +
    • +
      context: LDContext
      +

      The context requesting the feature flags.

      +
    • +
    • +
      Optional options: LDFlagsStateOptions
      +

      Optional LDFlagsStateOptions to determine how the state is computed.

      +
    • +
    • +
      Optional callback: ((err: Error, res: LDFlagsState) => void)
      +

      A Node-style callback to receive the result (as an LDFlagsState). If omitted, you + will receive a Promise instead.

      +
      +
    +

    Returns Promise<LDFlagsState>

+
+ +
    + +
  • +

    Discards all network connections, background tasks, and other resources held by the client.

    +

    Do not attempt to use the client after calling this method.

    +
    +

    Returns void

+
+ +
    + +
  • +

    Flushes all pending analytics events.

    +

    Normally, batches of events are delivered in the background at intervals determined by the +flushInterval property of LDOptions. Calling flush() triggers an immediate delivery. +However, like Node I/O in general, this is still an asynchronous operation so you must still +use Promise chaining, a callback, or async/await to detect when it has finished or failed.

    + +

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which resolves once + flushing is finished. Note that the Promise will be rejected if the HTTP request + fails, so be sure to attach a rejection handler to it.

    +
    +
    +

    Parameters

    +
      +
    • +
      Optional callback: ((err: Error, res: boolean) => void)
      +

      A function which will be called when the flush completes (meaning that all pending events + have been delivered to LaunchDarkly). If omitted, you will receive a Promise instead.

      +
      +
        +
      • +
          +
        • (err: Error, res: boolean): void
        • +
        • +
          +

          Parameters

          +
            +
          • +
            err: Error
          • +
          • +
            res: boolean
          +

          Returns void

    +

    Returns Promise<void>

+
+ +
    + +
  • +

    Identifies a context to LaunchDarkly.

    +

    This simply creates an analytics event that will transmit the given user properties to +LaunchDarkly, so that the context will be visible on your dashboard even if you have not +evaluated any flags for that user. It has no other effect.

    +

    If the context is omitted or has no key, the client will log a warning +and will not send an event.

    +
    +
    +

    Parameters

    +
      +
    • +
      context: LDContext
      +

      The context properties. Must contain at least the key property.

      +
    +

    Returns void

+
+ +
    + +
  • +

    Tests whether the client has completed initialization.

    +

    If this returns false, it means that the client has not yet successfully connected to +LaunchDarkly. It might still be in the process of starting up, or it might be attempting to +reconnect after an unsuccessful attempt, or it might have received an unrecoverable error (such +as an invalid SDK key) and given up.

    + +

    Returns

    True if the client has successfully initialized.

    +
    +

    Returns boolean

+
+ +
+
+ +
    + +
  • +

    Registers an event listener that will be called when the client triggers some type of event.

    +
      +
    • "ready": Sent only once, when the client has successfully connected to LaunchDarkly. +Alternately, you can detect this with waitForInitialization.
    • +
    • "failed": Sent only once, if the client has permanently failed to connect to LaunchDarkly. +Alternately, you can detect this with waitForInitialization.
    • +
    • "error": Contains an error object describing some abnormal condition that the client has +detected (such as a network error).
    • +
    • "update": The client has received a change to a feature flag. The event parameter is an +object containing a single property, key, the flag key. Note that this does not necessarily +mean the flag's value has changed for any particular context, only that some part of the flag +configuration was changed.
    • +
    • "update:KEY": The client has received a change to the feature flag whose key is KEY. This +is the same as "update" but allows you to listen for a specific flag.
    • +
    +
    +
    +

    Parameters

    +
      +
    • +
      event: string | symbol
      +

      the name of the event to listen for

      +
    • +
    • +
      listener: ((...args: any[]) => void)
      +

      the function to call when the event happens

      +
      +
        +
      • +
          +
        • (...args: any[]): void
        • +
        • +
          +

          Parameters

          +
            +
          • +
            Rest ...args: any[]
          +

          Returns void

    +

    Returns LDClient

+
+ +
    + +
  • +

    Computes an HMAC signature of a context signed with the client's SDK key.

    +

    For more information, see the JavaScript SDK Reference Guide on +Secure mode.

    + +

    Returns

    The hash string.

    +
    +
    +

    Parameters

    +
      +
    • +
      context: LDContext
      +

      The context properties.

      +
    +

    Returns string

+
+ +
    + +
  • +

    Tracks that a context performed an event.

    +

    LaunchDarkly automatically tracks pageviews and clicks that are specified in the Goals section +of the dashboard. This can be used to track custom goals or other events that do not currently +have goals.

    +

    Note that event delivery is asynchronous, so the event may not actually be sent until later; +see flush.

    +

    If the context is omitted or has no key, the client will log a warning and will not send an +event.

    +
    +
    +

    Parameters

    +
      +
    • +
      key: string
      +

      The name of the event, which may correspond to a goal in A/B tests.

      +
    • +
    • +
      context: LDContext
      +

      The context to track.

      +
    • +
    • +
      Optional data: any
      +

      Optional additional information to associate with the event.

      +
    • +
    • +
      Optional metricValue: number
      +

      A numeric value used by the LaunchDarkly experimentation feature in numeric + custom metrics. Can be omitted if this event is used by only non-numeric metrics. This field + will also be returned as part of the custom event for Data Export.

      +
    +

    Returns void

+
+ +
    + +
  • +

    Determines the variation of a feature flag for a context.

    + +

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved with + the result value.

    +
    +
    +

    Parameters

    +
      +
    • +
      key: string
      +

      The unique key of the feature flag.

      +
    • +
    • +
      context: LDContext
      +

      The context requesting the flag. The client will generate an analytics event to + register this context with LaunchDarkly if the context does not already exist.

      +
    • +
    • +
      defaultValue: any
      +

      The default value of the flag, to be used if the value is not available + from LaunchDarkly.

      +
    • +
    • +
      Optional callback: ((err: any, res: any) => void)
      +

      A Node-style callback to receive the result value. If omitted, you will receive + a Promise instead.

      +
      +
        +
      • +
          +
        • (err: any, res: any): void
        • +
        • +
          +

          Parameters

          +
            +
          • +
            err: any
          • +
          • +
            res: any
          +

          Returns void

    +

    Returns Promise<any>

+
+ +
    + +
  • +

    Determines the variation of a feature flag for a context, along with information about how it +was calculated.

    +

    The reason property of the result will also be included in analytics events, if you are +capturing detailed event data for this flag.

    +

    For more information, see the SDK reference +guide.

    + +

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved with + the result (as an LDEvaluationDetail).

    +
    +
    +

    Parameters

    +
      +
    • +
      key: string
      +

      The unique key of the feature flag.

      +
    • +
    • +
      context: LDContext
      +

      The context requesting the flag. The client will generate an analytics event to + register this context with LaunchDarkly if the context does not already exist.

      +
    • +
    • +
      defaultValue: any
      +

      The default value of the flag, to be used if the value is not available + from LaunchDarkly.

      +
    • +
    • +
      Optional callback: ((err: any, res: LDEvaluationDetail) => void)
      +

      A Node-style callback to receive the result (as an LDEvaluationDetail). If + omitted, you will receive a Promise instead.

      +
      +
    +

    Returns Promise<LDEvaluationDetail>

+
+ +
    + +
  • +

    Returns a Promise that tracks the client's initialization state.

    +

    The Promise will be resolved if the client successfully initializes, or rejected if client +initialization has failed unrecoverably (for instance, if it detects that the SDK key is +invalid). Keep in mind that unhandled Promise rejections can be fatal in Node, so if you call +this method, be sure to attach a rejection handler to it (or, if using async/await, a catch +block).

    +

    Note that you can also use event listeners (on) for the same purpose: the event "ready" +indicates success, and "failed" indicates failure.

    +

    There is no built-in timeout for this method. If you want your code to stop waiting on the +Promise after some amount of time, you could use +https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race|Promise.race() +or one of the several NPM helper packages that provides a standard mechanism for this. +Regardless of whether you continue to wait, the SDK will still retry all connection failures +indefinitely unless it gets an unrecoverable error as described above.

    + +

    Returns

    A Promise that will be resolved if the client initializes successfully, or rejected if it + fails. If successful, the result is the same client object.

    + +

    Example

    This example shows use of Promise chaining methods for specifying handlers:

    +
      client.waitForInitialization().then(() => {
    // do whatever is appropriate if initialization has succeeded
    }).catch(err => {
    // do whatever is appropriate if initialization has failed
    }) +
    + +

    Example

    This example shows use of async/await syntax for specifying handlers:

    +
      try {
    await client.waitForInitialization();
    // do whatever is appropriate if initialization has succeeded
    } catch (err) {
    // do whatever is appropriate if initialization has failed
    } +
    +
    +

    Returns Promise<LDClient>

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDContextCommon.html b/docs/interfaces/LDContextCommon.html new file mode 100644 index 0000000000..5879f3111a --- /dev/null +++ b/docs/interfaces/LDContextCommon.html @@ -0,0 +1,107 @@ +LDContextCommon | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Interface LDContextCommon

+
+

Hierarchy

+
+
+

Indexable

+
[attribute: string]: any
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ + +

TODO: U2C We will need some uniform description for this.

+

Meta attributes are used to control behavioral aspects of the Context. +They cannot be addressed in targetting rules.

+
+
+ +
anonymous?: boolean
+

If true, the context will not appear on the Contexts page in the LaunchDarkly dashboard.

+
+
+ +
key: string
+

A unique string identifying a context.

+
+
+ +
name?: string
+

The context's name.

+

You can search for contexts on the Contexts page by name.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDContextMeta.html b/docs/interfaces/LDContextMeta.html new file mode 100644 index 0000000000..8eb99c65ab --- /dev/null +++ b/docs/interfaces/LDContextMeta.html @@ -0,0 +1,95 @@ +LDContextMeta | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

TODO: U2C We will need some uniform description for this.

+

Meta attributes are used to control behavioral aspects of the Context. They +cannot be addressed in targetting rules.

+
+
+

Hierarchy

+
    +
  • LDContextMeta
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
privateAttributes?: string[]
+

TODO: U2C Link to the pointer-like syntax and maybe a name for it?

+

Specifies a list of attribute names (either built-in or custom) which should be +marked as private, and not sent to LaunchDarkly in analytics events. This is in +addition to any private attributes designated in the global configuration +with LDOptions.privateAttributes or LDOptions.allAttributesPrivate.

+
+
+ +
secondary?: string
+

An optional secondary key for a context.

+

TODO: U2C Update with new URL when available.

+

This affects feature flag targeting +as follows: if you have chosen to bucket context by a specific attribute, the secondary key (if +set) is used to further distinguish between contexts which are otherwise identical according to +that attribute.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDEvaluationDetail.html b/docs/interfaces/LDEvaluationDetail.html new file mode 100644 index 0000000000..4b5bac9375 --- /dev/null +++ b/docs/interfaces/LDEvaluationDetail.html @@ -0,0 +1,98 @@ +LDEvaluationDetail | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Interface LDEvaluationDetail

+
+

An object that combines the result of a feature flag evaluation with information about +how it was calculated.

+

This is the result of calling LDClient.variationDetail.

+

For more information, see the SDK reference guide.

+
+
+

Hierarchy

+
    +
  • LDEvaluationDetail
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ + +

An object describing the main factor that influenced the flag evaluation value.

+
+
+ +
value: any
+

The result of the flag evaluation. This will be either one of the flag's variations or +the default value that was passed to LDClient.variationDetail.

+
+
+ +
variationIndex?: number
+

The index of the returned value within the flag's list of variations, e.g. 0 for the +first variation-- or null if the default value was returned.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDEvaluationReason.html b/docs/interfaces/LDEvaluationReason.html new file mode 100644 index 0000000000..6d78352caf --- /dev/null +++ b/docs/interfaces/LDEvaluationReason.html @@ -0,0 +1,153 @@ +LDEvaluationReason | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Interface LDEvaluationReason

+
+

Describes the reason that a flag evaluation produced a particular value. This is +part of the LDEvaluationDetail object returned by LDClient.variationDetail.

+
+
+

Hierarchy

+
    +
  • LDEvaluationReason
+
+
+
+ +
+
+

Properties

+
+ +
bigSegmentsStatus?: "HEALTHY" | "STALE" | "NOT_CONFIGURED" | "STORE_ERROR"
+

Describes the validity of Big Segment information, if and only if the flag evaluation +required querying at least one Big Segment.

+
    +
  • 'HEALTHY': The Big Segment query involved in the flag evaluation was successful, and +the segment state is considered up to date.
  • +
  • 'STALE': The Big Segment query involved in the flag evaluation was successful, but +the segment state may not be up to date
  • +
  • 'NOT_CONFIGURED': Big Segments could not be queried for the flag evaluation because +the SDK configuration did not include a Big Segment store.
  • +
  • 'STORE_ERROR': The Big Segment query involved in the flag evaluation failed, for +instance due to a database error.
  • +
+
+
+ +
errorKind?: string
+

A further description of the error condition, if the kind was 'ERROR'.

+
+
+ +
inExperiment?: boolean
+

Whether the evaluation was part of an experiment.

+

This is true if the evaluation resulted in an experiment rollout and served one of +the variations in the experiment. Otherwise it is false or undefined.

+
+
+ +
kind: string
+

The general category of the reason:

+
    +
  • 'OFF': The flag was off and therefore returned its configured off value.
  • +
  • 'FALLTHROUGH': The flag was on but the context did not match any targets or rules.
  • +
  • 'TARGET_MATCH': The context key was specifically targeted for this flag.
  • +
  • 'RULE_MATCH': the context matched one of the flag's rules.
  • +
  • 'PREREQUISITE_FAILED': The flag was considered off because it had at least one +prerequisite flag that either was off or did not return the desired variation.
  • +
  • 'ERROR': The flag could not be evaluated, e.g. because it does not exist or due +to an unexpected error.
  • +
+
+
+ +
prerequisiteKey?: string
+

The key of the failed prerequisite flag, if the kind was 'PREREQUISITE_FAILED'.

+
+
+ +
ruleId?: string
+

The unique identifier of the matched rule, if the kind was 'RULE_MATCH'.

+
+
+ +
ruleIndex?: number
+

The index of the matched rule (0 for the first), if the kind was 'RULE_MATCH'.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDFlagSet.html b/docs/interfaces/LDFlagSet.html new file mode 100644 index 0000000000..14101417cd --- /dev/null +++ b/docs/interfaces/LDFlagSet.html @@ -0,0 +1,93 @@ +LDFlagSet | @launchdarkly/js-server-sdk-common
+
+ +
+ +
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDFlagsState.html b/docs/interfaces/LDFlagsState.html new file mode 100644 index 0000000000..26a3d661b0 --- /dev/null +++ b/docs/interfaces/LDFlagsState.html @@ -0,0 +1,153 @@ +LDFlagsState | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

An object that contains the state of all feature flags, generated by LDClient.allFlagsState.

+
+
+

Hierarchy

+
    +
  • LDFlagsState
+
+
+
+ +
+
+

Properties

+
+
+

Methods

+
+
+

Properties

+
+ +
valid: boolean
+

True if this object contains a valid snapshot of feature flag state, or false if the +state could not be computed (for instance, because the client was offline or there +was no user).

+
+
+

Methods

+
+ +
+
+ +
+
+ +
    + +
  • +

    Returns the value of an individual feature flag at the time the state was recorded. +It will be null if the flag returned the default value, or if there was no such flag.

    +
    +
    +

    Parameters

    +
      +
    • +
      key: string
      +

      The flag key.

      +
    +

    Returns any

+
+ +
    + +
  • +

    Returns a Javascript representation of the entire state map, in the format used by +the Javascript SDK. Use this method if you are passing data to the front end in +order to "bootstrap" the JavaScript client.

    +

    Do not rely on the exact shape of this data, as it may change in future to support +the needs of the JavaScript client.

    +
    +

    Returns object

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDFlagsStateOptions.html b/docs/interfaces/LDFlagsStateOptions.html new file mode 100644 index 0000000000..56e6475771 --- /dev/null +++ b/docs/interfaces/LDFlagsStateOptions.html @@ -0,0 +1,98 @@ +LDFlagsStateOptions | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Interface LDFlagsStateOptions

+
+

Optional settings that can be passed to LDClient.allFlagsState.

+
+
+

Hierarchy

+
    +
  • LDFlagsStateOptions
+
+
+
+ +
+
+

Properties

+
+ +
clientSideOnly?: boolean
+

True if the state should include only flags that have been marked for use with the +client-side SDK. By default, all flags are included.

+
+
+ +
detailsOnlyForTrackedFlags?: boolean
+

True if any flag metadata that is normally only used for event generation-- such as flag +versions and evaluation reasons-- should be omitted for any flag that does not have event +tracking or debugging turned on. This reduces the size of the JSON data if you are passing the +flag state to the front end.

+
+
+ +
withReasons?: boolean
+

True if evaluation reason data should be captured in the state object (see +LDClient.variationDetail). By default, it is not.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDLogger.html b/docs/interfaces/LDLogger.html new file mode 100644 index 0000000000..beeebe5f09 --- /dev/null +++ b/docs/interfaces/LDLogger.html @@ -0,0 +1,157 @@ +LDLogger | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

The LaunchDarkly client logger interface.

+

The LDOptions.logger property accepts any object that conforms to this +interface. The SDK only uses four logging levels: error, warn, info, and +debug. It will call the corresponding method of the LDLogger either with a +single string argument, or with a format string and variable arguments in the +format used by Node's util.format().

+

The Winston logging package provides a +logger that conforms to this interface, so if you have created a logger with +Winston, you can simply put it into the LDOptions.logger property.

+

If you do not provide a logger object, the SDK uses the basicLogger +implementation with a minimum level of info.

+
+
+

Hierarchy

+
    +
  • LDLogger
+
+

Implemented by

+
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
    + +
  • +

    The debug logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+ +
    + +
  • +

    The error logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+ +
    + +
  • +

    The info logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+ +
    + +
  • +

    The warning logger.

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...args: any[]
      +

      A sequence of any JavaScript values.

      +
    +

    Returns void

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDMultiKindContext.html b/docs/interfaces/LDMultiKindContext.html new file mode 100644 index 0000000000..52e2e5eb05 --- /dev/null +++ b/docs/interfaces/LDMultiKindContext.html @@ -0,0 +1,79 @@ +LDMultiKindContext | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Interface LDMultiKindContext

+
+

TODO: U2C How do we want to describe this?

+

A multi-kind context.

+
+
+

Hierarchy

+
    +
  • LDMultiKindContext
+
+

Indexable

+
[kind: string]: "multi" | LDContextCommon
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
kind: "multi"
+

The kind of the context.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDOptions.html b/docs/interfaces/LDOptions.html new file mode 100644 index 0000000000..af3ea6cd61 --- /dev/null +++ b/docs/interfaces/LDOptions.html @@ -0,0 +1,383 @@ +LDOptions | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

LaunchDarkly initialization options.

+
+
+

Hierarchy

+
    +
  • LDOptions
+
+
+
+ +
+
+

Properties

+
+ +
allAttributesPrivate?: boolean
+

Whether all context attributes (except the contexy key) should be marked as private, and +not sent to LaunchDarkly.

+

By default, this is false.

+
+
+ +
application?: { id?: string; version?: string }
+

Information about the application where the LaunchDarkly SDK is running.

+
+
+

Type declaration

+
    +
  • +
    Optional id?: string
    +

    A unique identifier representing the application where the LaunchDarkly SDK is running.

    +

    This can be specified as any string value as long as it only uses the following characters: +ASCII letters, ASCII digits, period, hyphen, underscore. A string containing any other +characters will be ignored.

    +

    Example: authentication-service

    +
  • +
  • +
    Optional version?: string
    +

    A unique identifier representing the version of the application where the LaunchDarkly SDK is +running.

    +

    This can be specified as any string value as long as it only uses the following characters: +ASCII letters, ASCII digits, period, hyphen, underscore. A string containing any other +characters will be ignored.

    +

    Example: 1.0.0 (standard version string) or abcdef (sha prefix)

    +
+
+ +
baseUri?: string
+

The base URI for the LaunchDarkly server.

+

Most users should use the default value.

+
+
+ +
bigSegments?: LDBigSegmentsOptions
+

Additional parameters for configuring the SDK's Big Segments behavior.

+

Big Segments are a specific type of user segments. For more information, read the +LaunchDarkly documentation: https://docs.launchdarkly.com/home/users/big-segments

+

By default, there is no configuration and Big Segments cannot be evaluated. In this +case, any flag evaluation that references a Big Segment will behave as if no users +are included in any Big Segments, and the LDEvaluationReason associated with any +such flag evaluation will have a bigSegmentsStatus of "NOT_CONFIGURED".

+
+
+ +
capacity?: number
+

The capacity of the analytics events queue.

+

The client buffers up to this many events in memory before flushing. If the capacity is +exceeded before the buffer is flushed, events will be discarded.

+
+
+ +
contextKeysCapacity?: number
+

The number of context keys that the event processor can remember at any one time, +so that duplicate context details will not be sent in analytics events.

+

Defaults to 1000.

+
+
+ +
contextKeysFlushInterval?: number
+

The interval (in seconds) at which the event processor will reset its set of +known context keys.

+

Defaults to 300.

+
+
+ +
diagnosticOptOut?: boolean
+

Set to true to opt out of sending diagnostics data.

+

Unless the diagnosticOptOut field is set to true, the client will send some diagnostics data +to the LaunchDarkly servers in order to assist in the development of future SDK improvements. +These diagnostics consist of an initial payload containing some details of SDK in use, the +SDK's configuration, and the platform the SDK is being run on, as well as payloads sent +periodically with information on irregular occurrences such as dropped events.

+
+
+ +
diagnosticRecordingInterval?: number
+

The interval at which periodic diagnostic data is sent, in seconds.

+

The default is 900 (every 15 minutes) and the minimum value is 60 (every minute).

+
+
+ +
eventsUri?: string
+

The base URI for the LaunchDarkly events server.

+

Most users should use the default value.

+
+
+ +
featureStore?: LDFeatureStore | ((options: LDOptions) => LDFeatureStore)
+

A component that stores feature flags and related data received from LaunchDarkly.

+

By default, this is an in-memory data structure. Database integrations are also +available, as described in the +SDK features guide.

+

Some implementations provide the store implementation object itself, while others +provide a factory function that creates the store implementation based on the SDK +configuration; this property accepts either.

+
+
+ +
flushInterval?: number
+

The interval in between flushes of the analytics events queue, in seconds.

+
+
+ +
logger?: LDLogger
+

Configures a logger for warnings and errors generated by the SDK.

+

The logger can be any object that conforms to the LDLogger interface. +For a simple implementation that lets you filter by log level, see +basicLogger. You can also use an instance of winston.Logger from +the Winston logging package.

+

If you do not set this property, the SDK uses basicLogger with a +minimum level of info.

+
+
+ +
offline?: boolean
+

Whether the client should be initialized in offline mode.

+
+
+ +
pollInterval?: number
+

The time between polling requests, in seconds. Ignored in streaming mode.

+
+
+ +
privateAttributes?: string[]
+

The names of any context attributes that should be marked as private, and not sent +to LaunchDarkly.

+
+
+ +
proxyOptions?: LDProxyOptions
+

Allows you to specify configuration for an optional HTTP proxy.

+
+
+ +
sendEvents?: boolean
+

Whether to send analytics events back to LaunchDarkly. By default, this is true.

+
+
+ +
stream?: boolean
+

Whether streaming mode should be used to receive flag updates.

+

This is true by default. If you set it to false, the client will use polling. +Streaming should only be disabled on the advice of LaunchDarkly support.

+
+
+ +
streamInitialReconnectDelay?: number
+

Sets the initial reconnect delay for the streaming connection, in seconds.

+

The streaming service uses a backoff algorithm (with jitter) every time the connection needs +to be reestablished. The delay for the first reconnection will start near this value, and then +increase exponentially for any subsequent connection failures.

+

The default value is 1.

+
+
+ +
streamUri?: string
+

The base URI for the LaunchDarkly streaming server.

+

Most users should use the default value.

+
+
+ +
timeout?: number
+

The connection timeout, in seconds.

+
+
+ +
tlsParams?: LDTLSOptions
+

Additional parameters to pass to the Node HTTPS API for secure requests. These can include any +of the TLS-related parameters supported by https.request(), such as ca, cert, and key.

+

For more information, see the Node documentation for https.request() and tls.connect().

+
+
+ +
updateProcessor?: object
+

A component that obtains feature flag data and puts it in the feature store.

+

By default, this is the client's default streaming or polling component. It can be changed +for testing purposes; see FileDataSource.

+
+
+ +
useLdd?: boolean
+

Whether you are using the LaunchDarkly relay proxy in daemon mode.

+

In this configuration, the client will not connect to LaunchDarkly to get feature flags, +but will instead get feature state from a database (Redis or another supported feature +store integration) that is populated by the relay. By default, this is false.

+
+
+ +
wrapperName?: string
+

For use by wrapper libraries to set an identifying name for the wrapper being used.

+

This will be sent in User-Agent headers during requests to the LaunchDarkly servers to allow +recording metrics on the usage of these wrapper libraries.

+
+
+ +
wrapperVersion?: string
+

For use by wrapper libraries to report the version of the library in use.

+

If wrapperName is not set, this field will be ignored. Otherwise the version string will be +included in the User-Agent headers along with the wrapperName during requests to the +LaunchDarkly servers.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDProxyOptions.html b/docs/interfaces/LDProxyOptions.html new file mode 100644 index 0000000000..2a82dee533 --- /dev/null +++ b/docs/interfaces/LDProxyOptions.html @@ -0,0 +1,101 @@ +LDProxyOptions | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Interface LDProxyOptions

+
+

Hierarchy

+
    +
  • LDProxyOptions
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
auth?: string
+

Allows you to specify basic authentication parameters for an optional HTTP proxy. +Usually of the form username:password.

+
+
+ +
host?: string
+

Allows you to specify a host for an optional HTTP proxy.

+
+
+ +
port?: number
+

Allows you to specify a port for an optional HTTP proxy.

+

Both the host and port must be specified to enable proxy support.

+
+
+ +
scheme?: string
+

When using an HTTP proxy, specifies whether it is accessed via http or https.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDSingleKindContext.html b/docs/interfaces/LDSingleKindContext.html new file mode 100644 index 0000000000..f3595a3b6d --- /dev/null +++ b/docs/interfaces/LDSingleKindContext.html @@ -0,0 +1,121 @@ +LDSingleKindContext | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Interface LDSingleKindContext

+
+

TODO: U2C How do we want to describe this?

+

A single-kind context.

+
+
+

Hierarchy

+
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ + +

TODO: U2C We will need some uniform description for this.

+

Meta attributes are used to control behavioral aspects of the Context. +They cannot be addressed in targetting rules.

+
+
+ +
anonymous?: boolean
+

If true, the context will not appear on the Contexts page in the LaunchDarkly dashboard.

+
+
+ +
key: string
+

A unique string identifying a context.

+
+
+ +
kind: string
+

The kind of the context.

+
+
+ +
name?: string
+

The context's name.

+

You can search for contexts on the Contexts page by name.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDTLSOptions.html b/docs/interfaces/LDTLSOptions.html new file mode 100644 index 0000000000..fd136f04e4 --- /dev/null +++ b/docs/interfaces/LDTLSOptions.html @@ -0,0 +1,153 @@ +LDTLSOptions | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Additional parameters to pass to the Node HTTPS API for secure requests. These can include any +of the TLS-related parameters supported by https.request(), such as ca, cert, and key.

+

For more information, see the Node documentation for https.request() and tls.connect().

+
+
+

Hierarchy

+
    +
  • LDTLSOptions
+
+
+
+ +
+
+

Properties

+
+ +
ca?: string | string[] | Buffer | Buffer[]
+
+ +
cert?: string | string[] | Buffer | Buffer[]
+
+ +
checkServerIdentity?: ((servername: string, cert: any) => undefined | Error)
+
+

Type declaration

+
    +
  • +
      +
    • (servername: string, cert: any): undefined | Error
    • +
    • +
      +

      Parameters

      +
        +
      • +
        servername: string
      • +
      • +
        cert: any
      +

      Returns undefined | Error

+
+ +
ciphers?: string
+
+ +
key?: string | string[] | Buffer | Buffer[] | object[]
+
+ +
passphrase?: string
+
+ +
pfx?: string | string[] | Buffer | Buffer[] | object[]
+
+ +
rejectUnauthorized?: boolean
+
+ +
secureProtocol?: string
+
+ +
servername?: string
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/LDUser.html b/docs/interfaces/LDUser.html new file mode 100644 index 0000000000..04e4a0793a --- /dev/null +++ b/docs/interfaces/LDUser.html @@ -0,0 +1,193 @@ +LDUser | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

A LaunchDarkly user object.

+
+
+

Hierarchy

+
    +
  • LDUser
+
+
+
+ +
+
+

Properties

+
+ +
anonymous?: boolean
+

If true, the user will not appear on the Users page in the LaunchDarkly dashboard.

+
+
+ +
avatar?: string
+

An absolute URL to an avatar image for the user.

+
+
+ +
country?: string
+

The country associated with the user.

+
+
+ +
custom?: { [key: string]: string | boolean | number | (string | boolean | number)[] }
+

Any additional attributes associated with the user.

+
+
+

Type declaration

+
    +
  • +
    [key: string]: string | boolean | number | (string | boolean | number)[]
+
+ +
email?: string
+

The user's email address.

+

If an avatar URL is not provided, LaunchDarkly will use Gravatar +to try to display an avatar for the user on the Users page.

+
+
+ +
firstName?: string
+

The user's first name.

+
+
+ +
ip?: string
+

The user's IP address.

+

If you provide an IP, LaunchDarkly will use a geolocation service to +automatically infer a country for the user, unless you've already +specified one.

+
+
+ +
key: string
+

A unique string identifying a user.

+
+
+ +
lastName?: string
+

The user's last name.

+
+
+ +
name?: string
+

The user's name.

+

You can search for users on the User page by name.

+
+
+ +
privateAttributeNames?: string[]
+

Specifies a list of attribute names (either built-in or custom) which should be +marked as private, and not sent to LaunchDarkly in analytics events. This is in +addition to any private attributes designated in the global configuration +with LDOptions.privateAttributeNames or LDOptions.allAttributesPrivate.

+
+
+ +
secondary?: string
+

An optional secondary key for a user.

+

This affects feature flag +targeting +as follows: if you have chosen to bucket users by a specific attribute, the secondary key (if +set) is used to further distinguish between users who are otherwise identical according to that +attribute.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/TypeValidator.html b/docs/interfaces/TypeValidator.html new file mode 100644 index 0000000000..1e4f7c1a76 --- /dev/null +++ b/docs/interfaces/TypeValidator.html @@ -0,0 +1,99 @@ +TypeValidator | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Interface for type validation.

+
+
+

Hierarchy

+
    +
  • TypeValidator
+
+

Implemented by

+
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
    + +
  • +

    Returns string

+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      u: unknown
    +

    Returns boolean

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/integrations.FileDataSourceOptions.html b/docs/interfaces/integrations.FileDataSourceOptions.html new file mode 100644 index 0000000000..e9bde6ea56 --- /dev/null +++ b/docs/interfaces/integrations.FileDataSourceOptions.html @@ -0,0 +1,97 @@ +FileDataSourceOptions | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Configuration for FileDataSource.

+
+
+

Hierarchy

+
    +
  • FileDataSourceOptions
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
autoUpdate?: boolean
+

True if FileDataSource should reload flags whenever one of the data files is modified. +This feature uses Node's fs.watch() API, so it is subject to +the limitations described here.

+
+
+ +
logger?: object | LDLogger
+

Configures a logger for warnings and errors. This can be a custom logger or an instance of +winston.Logger. By default, it uses the same logger as the rest of the SDK.

+
+
+ +
paths: string[]
+

The path(s) of the file(s) that FileDataSource will read.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/integrations.TestData.html b/docs/interfaces/integrations.TestData.html new file mode 100644 index 0000000000..91111d4072 --- /dev/null +++ b/docs/interfaces/integrations.TestData.html @@ -0,0 +1,204 @@ +TestData | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

A mechanism for providing dynamically updatable feature flag state in a simplified form to an SDK +client in test scenarios.

+

Unlike FileData, this mechanism does not use any external resources. It provides only the +data that the application has put into it using the TestData.update method.

+ +

Example

const { TestData } = require('launchdarkly-node-server-sdk/interfaces');

+
const td = TestData();
testData.update(td.flag("flag-key-1").booleanFlag().variationForAll(true));
const client = new LDClient(sdkKey, { updateProcessor: td });

// flags can be updated at any time:
td.update(td.flag("flag-key-2")
.variationForContext("user", "some-user-key", true)
.fallthroughVariation(false)); +
+

The above example uses a simple boolean flag, but more complex configurations are possible using +the methods of the TestDataFlagBuilder that is returned by TestData.flag. +TestDataFlagBuilder supports many of the ways a flag can be configured on the LaunchDarkly +dashboard, but does not currently support

+
    +
  1. rule operators other than "in" and "not in", or
  2. +
  3. percentage rollouts.
  4. +
+

If the same TestData instance is used to configure multiple LDClient instances, +any changes made to the data will propagate to all of the LDClients.

+ +

See

FileDataSource

+
+
+

Hierarchy

+
    +
  • TestData
+
+
+
+ +
+
+

Methods

+
+ +
    + +
  • +

    Creates or copies a TestDataFlagBuilder for building a test flag configuration.

    +

    If the flag key has already been defined in this TestData instance, +then the builder starts with the same configuration that was last +provided for this flag.

    +

    Otherwise, it starts with a new default configuration in which the flag +has true and false variations, is true for all users when targeting +is turned on and false otherwise, and currently has targeting turned on. +You can change any of those properties and provide more complex behavior +using the TestDataFlagBuilder methods.

    +

    Once you have set the desired configuration, pass the builder to +TestData.update.

    + +

    Returns

    a flag configuration builder

    +
    +
    +

    Parameters

    +
      +
    • +
      key: string
      +

      the flag key

      +
    +

    Returns TestDataFlagBuilder

+
+ +
    + +
  • +

    Updates the test data with the specified flag configuration.

    +

    This has the same effect as if a flag were added or modified in the +LaunchDarkly dashboard. It immediately propagates the flag changes to +any LDClient instance(s) that you have already configured to use +this TestData. If no LDClient has been started yet, it simply adds +this flag to the test data which will be provided to any LDClient +that you subsequently configure.

    +

    Any subsequent changes to this TestDataFlagBuilder instance do not affect +the test data unless you call update again.

    + +

    Returns

    a promise that will resolve when the feature stores are updated

    +
    +
    +

    Parameters

    +
    +

    Returns Promise<any>

+
+ +
    + +
  • +

    Copies a full feature flag data model object into the test data.

    +

    It immediately propagates the flag change to any LDClient instance(s) that you have already +configured to use this TestData. If no LDClient has been started yet, it simply adds this +flag to the test data which will be provided to any LDClient that you subsequently configure.

    +

    Use this method if you need to use advanced flag configuration properties that are not +supported by the simplified TestDataFlagBuilder API. Otherwise it is recommended to use the +regular flag/update mechanism to avoid dependencies on details of the data model.

    +

    You cannot make incremental changes with flag/update to a flag that has been added in +this way; you can only replace it with an entirely new flag configuration.

    + +

    Returns

    a promise that will resolve when the feature stores are updated

    +
    +
    +

    Parameters

    +
      +
    • +
      flagConfig: any
      +

      the flag configuration as a JSON object

      +
    +

    Returns Promise<any>

+
+ +
    + +
  • +

    Copies a full segment data model object into the test data.

    +

    It immediately propagates the change to any LDClient instance(s) that you have already +configured to use this TestData. If no LDClient has been started yet, it simply adds +this segment to the test data which will be provided to any LDClient that you subsequently +configure.

    +

    This method is currently the only way to inject segment data, since there is no builder +API for segments. It is mainly intended for the SDK's own tests of segment functionality, +since application tests that need to produce a desired evaluation state could do so more easily +by just setting flag values.

    + +

    Returns

    a promise that will resolve when the feature stores are updated

    +
    +
    +

    Parameters

    +
      +
    • +
      segmentConfig: any
      +

      the segment configuration as a JSON object

      +
    +

    Returns Promise<any>

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/integrations.TestDataFlagBuilder.html b/docs/interfaces/integrations.TestDataFlagBuilder.html new file mode 100644 index 0000000000..cd9e0dd97c --- /dev/null +++ b/docs/interfaces/integrations.TestDataFlagBuilder.html @@ -0,0 +1,402 @@ +TestDataFlagBuilder | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

A builder for feature flag configurations to be used with TestData.

+
+
+

Hierarchy

+
    +
  • TestDataFlagBuilder
+
+
+
+ +
+
+

Methods

+
+ +
+
+ +
+
+ +
+
+ +
    + +
  • +

    Specifies the fallthrough variation for a flag. The fallthrough is +the value that is returned if targeting is on and the user was not +matched by a more specific target or rule.

    +

    If a boolean is supplied, and the flag was previously configured with +other variations, this also changes it to a boolean flag.

    + +

    Returns

    the flag builder

    +
    +
    +

    Parameters

    +
      +
    • +
      variation: number | boolean
      +

      either true or false or the index of the desired fallthrough + variation: 0 for the first, 1 for the second, etc.

      +
    +

    Returns TestDataFlagBuilder

+
+ +
    + +
  • +

    Starts defining a flag rule using the "is one of" operator.

    +

    For example, this creates a rule that returnes true if the name is +"Patsy" or "Edina":

    +
    testData.flag('flag')
    .ifMatch('user', name', 'Patsy', 'Edina')
    .thenReturn(true) +
    + +

    Returns

    a flag rule builder; call thenReturn to finish the rule + or add more tests with another method like andMatch

    +
    +
    +

    Parameters

    +
      +
    • +
      contextKind: string
      +

      the kind of the context

      +
    • +
    • +
      attribute: string
      +

      the context attribute to match against

      +
    • +
    • +
      Rest ...values: any
      +

      values to compare to

      +
    +

    Returns TestDataRuleBuilder

+
+ +
    + +
  • +

    Starts defining a flag rule using the "is not one of" operator.

    +

    For example, this creates a rule that returnes true if the name is +neither "Saffron" nor "Bubble":

    +
    testData.flag('flag')
    .ifNotMatch('user', 'name', 'Saffron', 'Bubble')
    .thenReturn(true) +
    + +

    Returns

    a flag rule builder; call thenReturn to finish the rule + or add more tests with another method like andNotMatch

    +
    +
    +

    Parameters

    +
      +
    • +
      contextKind: string
      +

      the kind of the context

      +
    • +
    • +
      attribute: string
      +

      the user attribute to match against

      +
    • +
    • +
      Rest ...values: any
      +

      values to compare to

      +
    +

    Returns TestDataRuleBuilder

+
+ +
    + +
  • +

    Specifies the off variation for a flag. This is the variation that is +returned whenever targeting is off.

    +

    If a boolean is supplied, and the flag was previously configured with +other variations, this also changes it to a boolean flag.

    + +

    Returns

    the flag builder

    +
    +
    +

    Parameters

    +
      +
    • +
      variation: number | boolean
      +

      either true or false or the index of the desired off + variation: 0 for the first, 1 for the second, etc.

      +
    +

    Returns TestDataFlagBuilder

+
+ +
    + +
  • +

    Sets targeting to be on or off for this flag.

    +

    The effect of this depends on the rest of the flag configuration, just +as it does on the real LaunchDarkly dashboard. In the default configuration +that you get from calling TestData.flag with a new flag key, the flag +will return false whenever targeting is off and true when targeting +is on.

    + +

    Returns

    the flag builder

    +
    +
    +

    Parameters

    +
      +
    • +
      targetingOn: boolean
      +

      true if targeting should be on

      +
    +

    Returns TestDataFlagBuilder

+
+ +
    + +
  • +

    Sets the flag to always return the specified variation value for all contexts.

    +

    The value may be of any valid JSON type. This method changes the flag to have +only a single variation, which is this value, and to return the same variation +regardless of whether targeting is on or off. Any existing targets or rules +are removed.

    + +

    Returns

    the flag builder

    +
    +
    +

    Parameters

    +
      +
    • +
      value: any
      +

      The desired value to be returned for all contexts.

      +
    +

    Returns TestDataFlagBuilder

+
+ +
    + +
  • +

    Sets the flag to always return the specified variation for all contexts.

    +

    Targeting is switched on, any existing targets or rules are removed, +and the fallthrough variation is set to the specified value. The off +variation is left unchanged.

    +

    If a boolean is supplied, and the flag was previously configured with +other variations, this also changes it to a boolean flag.

    + +

    Returns

    the flag builder

    +
    +
    +

    Parameters

    +
      +
    • +
      variation: number | boolean
    +

    Returns TestDataFlagBuilder

+
+ +
    + +
  • +

    Sets the flag to return the specified variation for a specific context key +when targeting is on.

    +

    This has no effect when targeting is turned off for the flag.

    +

    If the variation is a boolean value and the flag was not already a boolean +flag, this also changes it to be a boolean flag.

    +

    If the variation is an integer, it specifies a variation out of whatever +variation values have already been defined.

    + +

    Returns

    the flag builder

    +
    +
    +

    Parameters

    +
      +
    • +
      contextKind: string
      +

      a context kind

      +
    • +
    • +
      contextKey: string
      +

      a context key

      +
    • +
    • +
      variation: number | boolean
      +

      either true or false or the index of the desired variation: + 0 for the first, 1 for the second, etc.

      +
    +

    Returns TestDataFlagBuilder

+
+ +
    + +
  • +

    Sets the flag to return the specified variation for a specific context key +when targeting is on. The context kind for contexts created with this method +will be 'user'.

    +

    This has no effect when targeting is turned off for the flag.

    +

    If the variation is a boolean value and the flag was not already a boolean +flag, this also changes it to be a boolean flag.

    +

    If the variation is an integer, it specifies a variation out of whatever +variation values have already been defined.

    + +

    Returns

    the flag builder

    +
    +
    +

    Parameters

    +
      +
    • +
      contextKey: string
      +

      a context key

      +
    • +
    • +
      variation: number | boolean
      +

      either true or false or the index of the desired variation: + 0 for the first, 1 for the second, etc.

      +
    +

    Returns TestDataFlagBuilder

+
+ +
    + +
  • +

    Sets the allowable variation values for the flag.

    +

    The values may be of any JSON-compatible type: boolean, number, string, array, +or object. For instance, a boolean flag normally has variations(true, false); +a string-valued flag might have variations("red", "green"); etc.

    + +

    Returns

    the flag builder

    +
    +
    +

    Parameters

    +
      +
    • +
      Rest ...values: any[]
      +

      any number of variation values

      +
    +

    Returns TestDataFlagBuilder

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/integrations.TestDataRuleBuilder.html b/docs/interfaces/integrations.TestDataRuleBuilder.html new file mode 100644 index 0000000000..149f915941 --- /dev/null +++ b/docs/interfaces/integrations.TestDataRuleBuilder.html @@ -0,0 +1,167 @@ +TestDataRuleBuilder | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

A builder for feature flag rules to be used with TestDataFlagBuilder.

+

In the LaunchDarkly model, a flag can have any number of rules, and +a rule can have any number of clauses. A clause is an individual test +such as "name is 'X'". A rule matches a user if all of the rule's +clauses match the user.

+

To start defining a rule, use one of the flag builder's matching methods +such as ifMatch. This defines the first clause for the rule. Optionally, +you may add more clauses with the rule builder's methods such as andMatch. +Finally, call thenReturn to finish defining the rule.

+
+
+

Hierarchy

+
    +
  • TestDataRuleBuilder
+
+
+
+ +
+
+

Methods

+
+ +
    + +
  • +

    Adds another clause using the "is one of" operator.

    +

    For example, this creates a rule that returns true if the name is +"Patsy" and the country is "gb":

    +
    testData.flag('flag')
    .ifMatch('name', 'Patsy')
    .andMatch('country', 'gb')
    .thenReturn(true) +
    + +

    Returns

    the flag rule builder

    +
    +
    +

    Parameters

    +
      +
    • +
      contextKind: string
      +

      the kind of the context

      +
    • +
    • +
      attribute: string
      +

      the user attribute to match against

      +
    • +
    • +
      Rest ...values: any
      +

      values to compare to

      +
    +

    Returns TestDataRuleBuilder

+
+ +
    + +
  • +

    Adds another clause using the "is not one of" operator.

    +

    For example, this creates a rule that returns true if the name is +"Patsy" and the country is not "gb":

    +
    testData.flag('flag')
    .ifMatch('name', 'Patsy')
    .andNotMatch('country', 'gb')
    .thenReturn(true) +
    + +

    Returns

    the flag rule builder

    +
    +
    +

    Parameters

    +
      +
    • +
      contextKind: string
      +

      the kind of the context

      +
    • +
    • +
      attribute: string
      +

      the user attribute to match against

      +
    • +
    • +
      Rest ...values: any
      +

      values to compare to

      +
    +

    Returns TestDataRuleBuilder

+
+ +
    + +
  • +

    Finishes defining the rule, specifying the result value as either a boolean or an index

    +

    If the variation is a boolean value and the flag was not already a boolean +flag, this also changes it to be a boolean flag.

    +

    If the variation is an integer, it specifies a variation out of whatever +variation values have already been defined.

    + +

    Returns

    the flag rule builder

    +
    +
    +

    Parameters

    +
      +
    • +
      variation: number | boolean
      +

      either true or false or the index of the desired variation: + 0 for the first, 1 for the second, etc.

      +
    +

    Returns TestDataFlagBuilder

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.BigSegmentStore.html b/docs/interfaces/interfaces.BigSegmentStore.html new file mode 100644 index 0000000000..46d83ea76f --- /dev/null +++ b/docs/interfaces/interfaces.BigSegmentStore.html @@ -0,0 +1,129 @@ +BigSegmentStore | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

A read-only data store that allows querying of user membership in Big Segments.

+

Big Segments are a specific type of user segments. For more information, read the LaunchDarkly +documentation: https://docs.launchdarkly.com/home/users/big-segments

+
+
+

Hierarchy

+
    +
  • BigSegmentStore
+
+
+
+ +
+
+

Methods

+
+ +
+
+ +
    + +
  • +

    Queries information about the overall state of the store.

    +

    The resolved value of the Promise should always be a BigSegmentStoreMetadata object. If the +store is accessible but contains no metadata, the object's lastUpToDate property can be +undefined. If the store is not accessible due to a database error, the method can throw an +exception/reject the promise.

    +

    This method will be called only when the SDK needs the latest state, so it should not be +cached.

    + +

    Returns

    a Promise for the result of the query

    +
    +

    Returns Promise<BigSegmentStoreMetadata>

+
+ +
    + +
  • +

    Queries the store for a snapshot of the current segment state for a specific user.

    +

    The userHash is a base64-encoded string produced by hashing the user key as defined by +the Big Segments specification; the store implementation does not need to know the details +of how this is done, because it deals only with already-hashed keys, but the string can be +assumed to only contain characters that are valid in base64.

    +

    The resolved value of the Promise should be either a BigSegmentStoreMembership, or +undefined if the user is not referenced in any Big Segments (this is equivalent to a +BigSegmentStoreMembership that has no properties).

    + +

    Returns

    a Promise for the result of the query.

    +
    +
    +

    Parameters

    +
      +
    • +
      userHash: string
      +

      identifies the user

      +
    +

    Returns Promise<undefined | BigSegmentStoreMembership>

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.BigSegmentStoreMembership.html b/docs/interfaces/interfaces.BigSegmentStoreMembership.html new file mode 100644 index 0000000000..5571093256 --- /dev/null +++ b/docs/interfaces/interfaces.BigSegmentStoreMembership.html @@ -0,0 +1,75 @@ +BigSegmentStoreMembership | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

The return type of BigSegmentStore.getUserMembership, describing which Big Segments a +specific user is included in or excluded from.

+

This object may be cached by the SDK, so it should not be modified after it is created. It +is a snapshot of the segment membership state at one point in time.

+
+
+

Hierarchy

+
    +
  • BigSegmentStoreMembership
+
+

Indexable

+
[segmentRef: string]: boolean
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.BigSegmentStoreMetadata.html b/docs/interfaces/interfaces.BigSegmentStoreMetadata.html new file mode 100644 index 0000000000..b5d897961e --- /dev/null +++ b/docs/interfaces/interfaces.BigSegmentStoreMetadata.html @@ -0,0 +1,77 @@ +BigSegmentStoreMetadata | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Values returned by BigSegmentStore.getMetadata().

+
+
+

Hierarchy

+
    +
  • BigSegmentStoreMetadata
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
lastUpToDate?: number
+

The Unix epoch millisecond timestamp of the last update to the BigSegmentStore. It is +undefined if the store has never been updated.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.BigSegmentStoreStatus.html b/docs/interfaces/interfaces.BigSegmentStoreStatus.html new file mode 100644 index 0000000000..2f769a464c --- /dev/null +++ b/docs/interfaces/interfaces.BigSegmentStoreStatus.html @@ -0,0 +1,99 @@ +BigSegmentStoreStatus | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Information about the status of a Big Segment store, provided by +BigSegmentStoreStatusProvider.

+

Big Segments are a specific type of user segments. For more information, read the LaunchDarkly +documentation: https://docs.launchdarkly.com/home/users/big-segments

+
+
+

Hierarchy

+
    +
  • BigSegmentStoreStatus
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
available: boolean
+

True if the Big Segment store is able to respond to queries, so that the SDK can +evaluate whether a user is in a segment or not.

+

If this property is false, the store is not able to make queries (for instance, it may not have +a valid database connection). In this case, the SDK will treat any reference to a Big Segment +as if no users are included in that segment. Also, the LDEvaluationReason associated +with any flag evaluation that references a Big Segment when the store is not available will +have a bigSegmentsStatus of "STORE_ERROR".

+
+
+ +
stale: boolean
+

True if the Big Segment store is available, but has not been updated within the amount of time +specified by staleAfter.

+

This may indicate that the LaunchDarkly Relay Proxy, which populates the store, has stopped +running or has become unable to receive fresh data from LaunchDarkly. Any feature flag +evaluations that reference a Big Segment will be using the last known data, which may be out +of date.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.BigSegmentStoreStatusProvider.html b/docs/interfaces/interfaces.BigSegmentStoreStatusProvider.html new file mode 100644 index 0000000000..362451ff71 --- /dev/null +++ b/docs/interfaces/interfaces.BigSegmentStoreStatusProvider.html @@ -0,0 +1,107 @@ +BigSegmentStoreStatusProvider | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+
+ +

Interface BigSegmentStoreStatusProvider

+
+

An interface for querying the status of a Big Segment store.

+

The Big Segment store is the component that receives information about Big Segments, normally +from a database populated by the LaunchDarkly Relay Proxy. Big Segments are a specific type of +user segments. For more information, read the LaunchDarkly documentation: +https://docs.launchdarkly.com/home/users/big-segments

+

An implementation of this interface is returned by +LDClient.bigSegmentStoreStatusProvider. Application code never needs to implement this +interface.

+
+
+

Hierarchy

+
    +
  • BigSegmentStoreStatusProvider
+
+

Implemented by

+
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.DataCollection.html b/docs/interfaces/interfaces.DataCollection.html new file mode 100644 index 0000000000..080da5f9f1 --- /dev/null +++ b/docs/interfaces/interfaces.DataCollection.html @@ -0,0 +1,91 @@ +DataCollection | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Used internally for data store implementations that require items in an ordered list rather +than as object properties.

+
+
+

Type Parameters

+
    +
  • +

    T

+
+

Hierarchy

+
    +
  • DataCollection
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
items: T[]
+

An ordered list of items of this kind.

+
+
+ +
kind: DataKind
+

Describes the kind of items, such as feature flags or user segments.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.DataKind.html b/docs/interfaces/interfaces.DataKind.html new file mode 100644 index 0000000000..909f7081da --- /dev/null +++ b/docs/interfaces/interfaces.DataKind.html @@ -0,0 +1,78 @@ +DataKind | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Used internally to describe the type of data being queried or updated, such as feature flags or +user segments.

+
+
+

Hierarchy

+
    +
  • DataKind
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
namespace: string
+

A string such as "features" or "segments" which can be used in keys to distinguish this +kind of data from other kinds.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.ItemDescriptor.html b/docs/interfaces/interfaces.ItemDescriptor.html new file mode 100644 index 0000000000..3ee0a5f937 --- /dev/null +++ b/docs/interfaces/interfaces.ItemDescriptor.html @@ -0,0 +1,78 @@ +ItemDescriptor | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Hierarchy

+
    +
  • ItemDescriptor
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
item: any
+
+ +
version: number
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.PersistentDataStore.html b/docs/interfaces/interfaces.PersistentDataStore.html new file mode 100644 index 0000000000..99e449a9e9 --- /dev/null +++ b/docs/interfaces/interfaces.PersistentDataStore.html @@ -0,0 +1,227 @@ +PersistentDataStore | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

This interface should be used for database integrations, or any other data store +implementation that stores data in some external service. The SDK will take care of +converting between its own internal data model and a serialized string form; the data +store interacts only with the serialized form. The SDK will also provide its own caching +layer on top of the persistent data store; the data store implementation should not +provide caching, but simply do every query or update that the SDK tells it to do.

+

Conceptually, each item in the store is a SerializedItemDescriptor which always has +a version number, and can represent either a serialized object or a placeholder (tombstone) +for a deleted item. There are two approaches a persistent store implementation can use for +persisting this data:

+
    +
  1. Preferably, it should store the version number and the deleted +state separately so that the object does not need to be fully deserialized to read them. In +this case, deleted item placeholders can ignore the value of SerializedItemDescriptor#item +on writes and can set it to null on reads. The store should never call +DataKind#deserialize.

    +
  2. +
  3. If that isn't possible, then the store should simply persist the exact string from +serializedItem on writes, and return the persisted +string on reads (returning zero for the version and false for + deleted). The string is guaranteed to provide the SDK with +enough information to infer the version and the deleted state. On updates, the store must call +deserialize in order to inspect the version number of the +existing item if any.

    +
  4. +
+
+
+

Hierarchy

+
    +
  • PersistentDataStore
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
    + +
  • +

    Releases any resources being used by the feature store.

    +
    +

    Returns void

+
+ +
+
+ +
+
+ +
    + +
  • +

    Tests whether the store is initialized.

    +

    "Initialized" means that the store has been populated with data, either by the client +having called init() within this process, or by another process (if this is a shared +database).

    +
    +
    +

    Parameters

    +
      +
    • +
      callback: ((isInitialized: boolean) => void)
      +

      Will be called back with the boolean result.

      +
      +
        +
      • +
          +
        • (isInitialized: boolean): void
        • +
        • +
          +

          Parameters

          +
            +
          • +
            isInitialized: boolean
          +

          Returns void

    +

    Returns void

+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.PersistentStoreDataKind.html b/docs/interfaces/interfaces.PersistentStoreDataKind.html new file mode 100644 index 0000000000..8570b2f94b --- /dev/null +++ b/docs/interfaces/interfaces.PersistentStoreDataKind.html @@ -0,0 +1,91 @@ +PersistentStoreDataKind | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Hierarchy

+
    +
  • PersistentStoreDataKind
+
+
+
+ +
+
+

Properties

+
+
+

Methods

+
+
+

Properties

+
+ +
namespace: string
+
+

Methods

+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      data: string
    +

    Returns ItemDescriptor

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.SerializedItemDescriptor.html b/docs/interfaces/interfaces.SerializedItemDescriptor.html new file mode 100644 index 0000000000..ea6b13c3d8 --- /dev/null +++ b/docs/interfaces/interfaces.SerializedItemDescriptor.html @@ -0,0 +1,88 @@ +SerializedItemDescriptor | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

A versioned item (or placeholder) storable in a [PersistentDataStore].

+
+
+

Hierarchy

+
    +
  • SerializedItemDescriptor
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
deleted: boolean
+
+ +
serializedItem: string
+
+ +
version: number
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/interfaces.VersionedData.html b/docs/interfaces/interfaces.VersionedData.html new file mode 100644 index 0000000000..7dc2173806 --- /dev/null +++ b/docs/interfaces/interfaces.VersionedData.html @@ -0,0 +1,97 @@ +VersionedData | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Used internally to describe the basic properties of stored data such as feature flags or user +segments.

+

This is the actual type of parameters and return values in LDFeatureStore methods that refer +to a flag or segment item. Those methods still use the object type for backward compatibility.

+
+
+

Hierarchy

+
    +
  • VersionedData
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
deleted?: boolean
+

True if this is a deleted item placeholder (tombstone).

+
+
+ +
key: string
+

The item's unique key, such as a feature flag key.

+
+
+ +
version: number
+

A version number that LaunchDarkly will increment each time this item is changed.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.Crypto.html b/docs/interfaces/platform.Crypto.html new file mode 100644 index 0000000000..54a6be6cb3 --- /dev/null +++ b/docs/interfaces/platform.Crypto.html @@ -0,0 +1,99 @@ +Crypto | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Interface provided by the platform for doing cryptographic operations.

+
+
+

Hierarchy

+
    +
  • Crypto
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.EventSource.html b/docs/interfaces/platform.EventSource.html new file mode 100644 index 0000000000..aa5d220a1c --- /dev/null +++ b/docs/interfaces/platform.EventSource.html @@ -0,0 +1,138 @@ +EventSource | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Hierarchy

+
    +
  • EventSource
+
+
+
+ +
+
+

Properties

+
+
+

Methods

+
+
+

Properties

+
+ +
onclose: undefined | (() => void)
+
+ +
onerror: undefined | (() => void)
+
+ +
onopen: undefined | (() => void)
+
+ +
onretrying: undefined | ((e: { delayMillis: number }) => void)
+
+

Methods

+
+ +
    + +
  • +
    +

    Parameters

    +
      +
    • +
      type: string
    • +
    • +
      listener: ((event?: { data?: any }) => void)
      +
        +
      • +
          +
        • (event?: { data?: any }): void
        • +
        • +
          +

          Parameters

          +
            +
          • +
            Optional event: { data?: any }
            +
              +
            • +
              Optional data?: any
          +

          Returns void

    +

    Returns void

+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.EventSourceInitDict.html b/docs/interfaces/platform.EventSourceInitDict.html new file mode 100644 index 0000000000..7306f78c14 --- /dev/null +++ b/docs/interfaces/platform.EventSourceInitDict.html @@ -0,0 +1,122 @@ +EventSourceInitDict | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Hierarchy

+
    +
  • EventSourceInitDict
+
+
+
+ +
+
+

Properties

+
+ +
errorFilter: ((err: { message: string; status: number }) => boolean)
+
+

Type declaration

+
    +
  • +
      +
    • (err: { message: string; status: number }): boolean
    • +
    • +
      +

      Parameters

      +
        +
      • +
        err: { message: string; status: number }
        +
          +
        • +
          message: string
        • +
        • +
          status: number
      +

      Returns boolean

+
+ +
headers: { [key: string]: string | string[] }
+
+

Type declaration

+
    +
  • +
    [key: string]: string | string[]
+
+ +
initialRetryDelayMillis: number
+
+ +
readTimeoutMillis: number
+
+ +
retryResetIntervalMillis: number
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.Filesystem.html b/docs/interfaces/platform.Filesystem.html new file mode 100644 index 0000000000..c6d4f5c875 --- /dev/null +++ b/docs/interfaces/platform.Filesystem.html @@ -0,0 +1,135 @@ +Filesystem | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Interface for doing filesystem operations on the platform.

+
+
+

Hierarchy

+
    +
  • Filesystem
+
+
+
+ +
+
+

Methods

+
+ +
    + +
  • +

    The time, in ms since POSIX epoch, that the file was last modified.

    + +

    Returns

    A promise which will resolve to a timestamp if succeful, or be +rejected if the operation fails.

    +
    +
    +

    Parameters

    +
      +
    • +
      path: string
      +

      The path to get a timestamp for.

      +
    +

    Returns Promise<number>

+
+ +
    + +
  • +

    Read a file into a utf8 encoded string.

    + +

    Returns

    A promise which will resolve to utf8 encoded file content, or be +rejected if the operation fails.

    +
    +
    +

    Parameters

    +
      +
    • +
      path: string
      +

      The path of the file to read.

      +
    +

    Returns Promise<string>

+
+ +
    + +
  • +

    Watch for changes to the specified path.

    +

    The implementation of this methods should be non-persistent. Meaning that +it should not keep the containing process running as long as it is +executing. For node this means setting the persistent option to false.

    + +

    Returns

    An async iterator that watches for changes to path.

    +
    +
    +

    Parameters

    +
      +
    • +
      path: string
      +

      The path to watch.

      +
    +

    Returns AsyncIterable<{ eventType: string; filename: string }>

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.Hasher.html b/docs/interfaces/platform.Hasher.html new file mode 100644 index 0000000000..fecf5fc0c6 --- /dev/null +++ b/docs/interfaces/platform.Hasher.html @@ -0,0 +1,101 @@ +Hasher | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Interface implemented by platform provided hasher.

+

The hash implementation must support 'sha256' and 'sha1'.

+

The has implementation must support digesting to 'hex' or 'base64'.

+
+
+

Hierarchy

+
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.Headers.html b/docs/interfaces/platform.Headers.html new file mode 100644 index 0000000000..3297e78ad7 --- /dev/null +++ b/docs/interfaces/platform.Headers.html @@ -0,0 +1,148 @@ +Headers | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Interface for headers that are part of a fetch response.

+
+
+

Hierarchy

+
    +
  • Headers
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.Hmac.html b/docs/interfaces/platform.Hmac.html new file mode 100644 index 0000000000..e30877c1c1 --- /dev/null +++ b/docs/interfaces/platform.Hmac.html @@ -0,0 +1,103 @@ +Hmac | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Interface implemented by platform provided hmac.

+

The hash implementation must support 'sha256'.

+

The has implementation must support digesting to 'hex'.

+
+
+

Hierarchy

+
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.Info.html b/docs/interfaces/platform.Info.html new file mode 100644 index 0000000000..facf89b02a --- /dev/null +++ b/docs/interfaces/platform.Info.html @@ -0,0 +1,92 @@ +Info | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Interface for getting information about the SDK or the environment it is +executing in.

+
+
+

Hierarchy

+
    +
  • Info
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.Options.html b/docs/interfaces/platform.Options.html new file mode 100644 index 0000000000..1be6263860 --- /dev/null +++ b/docs/interfaces/platform.Options.html @@ -0,0 +1,92 @@ +Options | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Hierarchy

+
    +
  • Options
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
body?: string
+
+ +
headers?: Record<string, string>
+
+ +
method?: string
+
+ +
timeout?: number
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.Platform.html b/docs/interfaces/platform.Platform.html new file mode 100644 index 0000000000..48acfb46f0 --- /dev/null +++ b/docs/interfaces/platform.Platform.html @@ -0,0 +1,102 @@ +Platform | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Hierarchy

+
    +
  • Platform
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
crypto: Crypto
+

The interface for performing cryptographic operations.

+
+
+ +
fileSystem?: Filesystem
+

The interface for performing file system operations. If the platform does +not support filesystem access, then this may be undefined.

+
+
+ +
info: Info
+

The interface for getting information about the platform and the execution +environment.

+
+
+ +
requests: Requests
+

The interface for performing http/https requests.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.PlatformData.html b/docs/interfaces/platform.PlatformData.html new file mode 100644 index 0000000000..c1f51ad7d1 --- /dev/null +++ b/docs/interfaces/platform.PlatformData.html @@ -0,0 +1,121 @@ +PlatformData | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Information about the platform of the SDK and the environment it is executing.

+
+
+

Hierarchy

+
    +
  • PlatformData
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
additional?: Record<string, string>
+

Any additional attributes associated with the platform.

+
+
+ +
name?: string
+

The name of the platform the SDK is running on. For instance 'Node'.

+
+
+ +
os?: { arch?: string; name?: string; version?: string }
+

Information about the OS on which the SDK is running. Should be populated +when available. Not all platforms will make this data accessible.

+
+
+

Type declaration

+
    +
  • +
    Optional arch?: string
    +

    The architecture. Ideally at runtime, but may be build time if that is +a constraint.

    +
  • +
  • +
    Optional name?: string
    +

    The name of the OS. "MacOS", "Windows", or "Linux". If not one of those, +then use the value provided by the OS.

    +
  • +
  • +
    Optional version?: string
    +

    The version of the OS.

    +
+
+ +
version?: string
+

The version of the platform the SDK is running on. e.g. "13.1.0".

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.Requests.html b/docs/interfaces/platform.Requests.html new file mode 100644 index 0000000000..c6f3467976 --- /dev/null +++ b/docs/interfaces/platform.Requests.html @@ -0,0 +1,98 @@ +Requests | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Hierarchy

+
    +
  • Requests
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.Response.html b/docs/interfaces/platform.Response.html new file mode 100644 index 0000000000..e3ac9f3f6e --- /dev/null +++ b/docs/interfaces/platform.Response.html @@ -0,0 +1,110 @@ +Response | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Interface for fetch responses.

+
+
+

Hierarchy

+
    +
  • Response
+
+
+
+ +
+
+

Properties

+
+
+

Methods

+
+
+

Properties

+
+ +
headers: Headers
+
+ +
status: number
+
+

Methods

+
+ +
+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/platform.SdkData.html b/docs/interfaces/platform.SdkData.html new file mode 100644 index 0000000000..36aa0479b1 --- /dev/null +++ b/docs/interfaces/platform.SdkData.html @@ -0,0 +1,100 @@ +SdkData | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Hierarchy

+
    +
  • SdkData
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
name?: string
+

The name of the SDK. e.g. "node-server-sdk"

+
+
+ +
version?: string
+

The version of the SDK.

+
+
+ +
wrapperName?: string
+

Name of the wrapper SDK if present.

+
+
+ +
wrapperVersion?: string
+

Version of the wrapper if present.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/subsystems.LDFeatureStore.html b/docs/interfaces/subsystems.LDFeatureStore.html new file mode 100644 index 0000000000..cbe1a1a36d --- /dev/null +++ b/docs/interfaces/subsystems.LDFeatureStore.html @@ -0,0 +1,312 @@ +LDFeatureStore | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Interface for a feature store component.

+

The feature store is what the client uses to store feature flag data that has been received +from LaunchDarkly. By default, it uses an in-memory implementation; database integrations are +also available. Read the SDK features guide. +You will not need to use this interface unless you are writing your own implementation.

+

Feature store methods can and should call their callbacks directly whenever possible, rather +than deferring them with setImmediate() or process.nextTick(). This means that if for any +reason you are updating or querying a feature store directly in your application code (which +is not part of normal use of the SDK) you should be aware that the callback may be executed +immediately.

+
+
+

Hierarchy

+
    +
  • LDFeatureStore
+
+
+
+ +
+
+

Methods

+
+
+

Methods

+
+ +
+
+ +
+
+ +
    + +
  • +

    Delete an entity from the store.

    +

    Deletion should be implemented by storing a placeholder object with the property +deleted: true and a version property equal to the provided version. In other words, +it should be exactly the same as calling upsert with such an object.

    +
    +
    +

    Parameters

    +
      +
    • +
      kind: DataKind
      +

      The type of data to be accessed. The actual type of this parameter is + interfaces.DataKind.

      +
    • +
    • +
      key: string
      +

      The unique key of the entity within the specified collection.

      +
    • +
    • +
      version: number
      +

      A number that must be greater than the version property of the existing entity in + order for it to be deleted. If it is less than or equal to the existing version, the + method should do nothing.

      +
    • +
    • +
      callback: (() => void)
      +

      Will be called when the delete operation is complete.

      +
      +
        +
      • +
          +
        • (): void
        • +
        • +

          Returns void

    +

    Returns void

+
+ +
+
+ +
    + +
  • +

    Initialize the store, overwriting any existing data.

    +
    +
    +

    Parameters

    +
      +
    • +
      allData: LDFeatureStoreDataStorage
      +

      An object in which each key is the "namespace" of a collection (e.g. "features") and + the value is an object that maps keys to entities. The actual type of this parameter is + interfaces.FullDataSet<VersionedData>.

      +
    • +
    • +
      callback: (() => void)
      +

      Will be called when the store has been initialized.

      +
      +
        +
      • +
          +
        • (): void
        • +
        • +

          Returns void

    +

    Returns void

+
+ +
    + +
  • +

    Tests whether the store is initialized.

    +

    "Initialized" means that the store has been populated with data, either by the client +having called init() within this process, or by another process (if this is a shared +database).

    +
    +
    +

    Parameters

    +
      +
    • +
      callback: ((isInitialized: boolean) => void)
      +

      Will be called back with the boolean result.

      +
      +
        +
      • +
          +
        • (isInitialized: boolean): void
        • +
        • +
          +

          Parameters

          +
            +
          • +
            isInitialized: boolean
          +

          Returns void

    +

    Returns void

+
+ +
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/subsystems.LDFeatureStoreDataStorage.html b/docs/interfaces/subsystems.LDFeatureStoreDataStorage.html new file mode 100644 index 0000000000..9fe6ff91fe --- /dev/null +++ b/docs/interfaces/subsystems.LDFeatureStoreDataStorage.html @@ -0,0 +1,63 @@ +LDFeatureStoreDataStorage | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Represents the storage for the full data store.

+
+
+

Hierarchy

+
    +
  • LDFeatureStoreDataStorage
+
+

Indexable

+
[namespace: string]: LDFeatureStoreKindData
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/subsystems.LDFeatureStoreItem.html b/docs/interfaces/subsystems.LDFeatureStoreItem.html new file mode 100644 index 0000000000..3277a0926e --- /dev/null +++ b/docs/interfaces/subsystems.LDFeatureStoreItem.html @@ -0,0 +1,86 @@ +LDFeatureStoreItem | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Represents an item which can be stored in the feature store.

+
+
+

Hierarchy

+
+
+

Indexable

+
[attribute: string]: any
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
deleted?: boolean
+
+ +
version: number
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/subsystems.LDFeatureStoreKindData.html b/docs/interfaces/subsystems.LDFeatureStoreKindData.html new file mode 100644 index 0000000000..f46aee39ee --- /dev/null +++ b/docs/interfaces/subsystems.LDFeatureStoreKindData.html @@ -0,0 +1,63 @@ +LDFeatureStoreKindData | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

Represents the storage for a single kind of data. e.g. 'flag' or 'segment'.

+
+
+

Hierarchy

+
    +
  • LDFeatureStoreKindData
+
+

Indexable

+
[key: string]: LDFeatureStoreItem
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/interfaces/subsystems.LDKeyedFeatureStoreItem.html b/docs/interfaces/subsystems.LDKeyedFeatureStoreItem.html new file mode 100644 index 0000000000..6df2a640b6 --- /dev/null +++ b/docs/interfaces/subsystems.LDKeyedFeatureStoreItem.html @@ -0,0 +1,92 @@ +LDKeyedFeatureStoreItem | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
+

When upserting an item it must contain a key.

+
+
+

Hierarchy

+
+
+
+
+ +
+
+

Properties

+
+
+

Properties

+
+ +
deleted?: boolean
+
+ +
key: string
+
+ +
version: number
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/modules.html b/docs/modules.html new file mode 100644 index 0000000000..16f75f42ad --- /dev/null +++ b/docs/modules.html @@ -0,0 +1,133 @@ +@launchdarkly/js-server-sdk-common
+
+ +
+ +
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/modules/integrations.html b/docs/modules/integrations.html new file mode 100644 index 0000000000..491e498fe6 --- /dev/null +++ b/docs/modules/integrations.html @@ -0,0 +1,61 @@ +integrations | @launchdarkly/js-server-sdk-common
+
+ +
+
+ +
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/modules/interfaces.html b/docs/modules/interfaces.html new file mode 100644 index 0000000000..938f5a7b5f --- /dev/null +++ b/docs/modules/interfaces.html @@ -0,0 +1,84 @@ +interfaces | @launchdarkly/js-server-sdk-common
+
+ +
+ +
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/modules/platform.html b/docs/modules/platform.html new file mode 100644 index 0000000000..6710b335a7 --- /dev/null +++ b/docs/modules/platform.html @@ -0,0 +1,81 @@ +platform | @launchdarkly/js-server-sdk-common
+
+ +
+
+ +
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/modules/subsystems.html b/docs/modules/subsystems.html new file mode 100644 index 0000000000..78148ffbbc --- /dev/null +++ b/docs/modules/subsystems.html @@ -0,0 +1,63 @@ +subsystems | @launchdarkly/js-server-sdk-common
+
+ +
+ +
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/types/LDContext.html b/docs/types/LDContext.html new file mode 100644 index 0000000000..b61a7e69a8 --- /dev/null +++ b/docs/types/LDContext.html @@ -0,0 +1,86 @@ +LDContext | @launchdarkly/js-server-sdk-common
+
+ +
+ +
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/types/LDFlagValue.html b/docs/types/LDFlagValue.html new file mode 100644 index 0000000000..4760d170aa --- /dev/null +++ b/docs/types/LDFlagValue.html @@ -0,0 +1,87 @@ +LDFlagValue | @launchdarkly/js-server-sdk-common
+
+ +
+ +
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/types/LDLogLevel.html b/docs/types/LDLogLevel.html new file mode 100644 index 0000000000..2cc3557ca8 --- /dev/null +++ b/docs/types/LDLogLevel.html @@ -0,0 +1,90 @@ +LDLogLevel | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
LDLogLevel: "debug" | "info" | "warn" | "error" | "none"
+

Logging levels that can be used with basicLogger.

+

Set BasicLoggerOptions.level to one of these values to control what levels +of log messages are enabled. Going from lowest importance (and most verbose) +to most importance, the levels are 'debug', 'info', 'warn', and 'error'. +You can also specify 'none' instead to disable all logging.

+
+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/types/interfaces.FullDataSet.html b/docs/types/interfaces.FullDataSet.html new file mode 100644 index 0000000000..7d538a644f --- /dev/null +++ b/docs/types/interfaces.FullDataSet.html @@ -0,0 +1,71 @@ +FullDataSet | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
FullDataSet<T>: Record<string, KeyedItems<T>>
+

Used internally to describe a full set of environment data, which can include both feature +flags and user segments. The string key for each item is the namespace property of a +DataKind.

+
+

Type Parameters

+
    +
  • +

    T

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/docs/types/interfaces.KeyedItems.html b/docs/types/interfaces.KeyedItems.html new file mode 100644 index 0000000000..6272e877c1 --- /dev/null +++ b/docs/types/interfaces.KeyedItems.html @@ -0,0 +1,70 @@ +KeyedItems | @launchdarkly/js-server-sdk-common
+
+ +
+
+
+ +
KeyedItems<T>: Record<string, T>
+

Used internally to describe a set of stored data items of the same kind, such as feature flags +or user segments. The string key for each item is the same as the item's key property.

+
+

Type Parameters

+
    +
  • +

    T

+
+
+

Generated using TypeDoc

+
\ No newline at end of file diff --git a/platform-node/src/platform/NodeFilesystem.ts b/platform-node/src/platform/NodeFilesystem.ts index 26853d4c97..e01d0de203 100644 --- a/platform-node/src/platform/NodeFilesystem.ts +++ b/platform-node/src/platform/NodeFilesystem.ts @@ -14,7 +14,12 @@ export default class NodeFilesystem implements platform.Filesystem { return fsPromises.readFile(path, 'utf8'); } - watch(path: string): AsyncIterable<{ eventType: string; filename: string; }> { - return fsPromises.watch(path, { persistent: false }); + watch( + path: string, + callback: (eventType: string, filename: string) => void, + ): platform.WatchHandle { + return fs.watch(path, { persistent: false }, (eventType, filename) => { + callback(eventType, filename); + }); } } diff --git a/sdk-common/src/index.ts b/sdk-common/src/index.ts index f511e19e83..ff8dc52d4c 100644 --- a/sdk-common/src/index.ts +++ b/sdk-common/src/index.ts @@ -11,4 +11,3 @@ export { Context, ContextFilter, }; - diff --git a/server-sdk-common/__tests__/cache/LruCache.test.ts b/server-sdk-common/__tests__/cache/LruCache.test.ts index a117b339c7..8b8dc0fbbf 100644 --- a/server-sdk-common/__tests__/cache/LruCache.test.ts +++ b/server-sdk-common/__tests__/cache/LruCache.test.ts @@ -22,7 +22,7 @@ it('when the max size is exceeded', () => { lruCache.set(`${i}`, i); } - for(let i = 0; i < 10; i++) { + for (let i = 0; i < 10; i++) { expect(lruCache.get(i.toString())).toBeUndefined(); } diff --git a/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts b/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts index 6c5457a5c0..d365147459 100644 --- a/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts +++ b/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts @@ -10,7 +10,7 @@ import TestLogger, { LogLevel } from '../Logger'; describe('given an event processor', () => { const requestor = { - requestAllData: jest.fn() + requestAllData: jest.fn(), }; const longInterval = 100000; const allData = { flags: { flag: { version: 1 } }, segments: { segment: { version: 1 } } }; @@ -21,14 +21,13 @@ describe('given an event processor', () => { let config: Configuration; let processor: PollingProcessor; - beforeEach(() => { store = new InMemoryFeatureStore(); storeFacade = new AsyncStoreFacade(store); config = new Configuration({ featureStore: store, pollInterval: longInterval, - logger: new TestLogger() + logger: new TestLogger(), }); processor = new PollingProcessor(config, requestor as unknown as Requestor); }); @@ -68,7 +67,7 @@ describe('given an event processor', () => { describe('given a polling processor with a short poll duration', () => { const requestor = { - requestAllData: jest.fn() + requestAllData: jest.fn(), }; const shortInterval = 0.1; const allData = { flags: { flag: { version: 1 } }, segments: { segment: { version: 1 } } }; @@ -79,14 +78,13 @@ describe('given a polling processor with a short poll duration', () => { let config: Configuration; let processor: PollingProcessor; - beforeEach(() => { store = new InMemoryFeatureStore(); storeFacade = new AsyncStoreFacade(store); config = new Configuration({ featureStore: store, pollInterval: shortInterval, - logger: new TestLogger() + logger: new TestLogger(), }); // Configuration will not let us set this as low as needed for the test. Object.defineProperty(config, 'pollInterval', { value: 0.1 }); @@ -110,7 +108,7 @@ describe('given a polling processor with a short poll duration', () => { it.each([400, 408, 429, 500, 503])('continues polling after recoverable error', (status, done) => { requestor.requestAllData = jest.fn((cb) => cb({ - status + status, }, undefined)); processor.start((e) => { expect(e).toBeUndefined(); @@ -141,7 +139,7 @@ describe('given a polling processor with a short poll duration', () => { it.each([401, 403])('does not continue after non-recoverable error', (status, done) => { requestor.requestAllData = jest.fn((cb) => cb({ - status + status, }, undefined)); processor.start((e) => { expect(e).toBeDefined(); diff --git a/server-sdk-common/__tests__/data_sources/Requestor.test.ts b/server-sdk-common/__tests__/data_sources/Requestor.test.ts index fa9b1e335d..00f750e244 100644 --- a/server-sdk-common/__tests__/data_sources/Requestor.test.ts +++ b/server-sdk-common/__tests__/data_sources/Requestor.test.ts @@ -2,7 +2,9 @@ import promisify from '../../src/async/promisify'; import Requestor from '../../src/data_sources/Requestor'; import Configuration from '../../src/options/Configuration'; import { EventSourceInitDict, EventSource } from '../../src/platform'; -import { Headers, Options, Requests, Response } from '../../src/platform/Requests'; +import { + Headers, Options, Requests, Response, +} from '../../src/platform/Requests'; import basicPlatform from '../evaluation/mocks/platform'; describe('given a requestor', () => { @@ -23,7 +25,6 @@ describe('given a requestor', () => { throwThis = undefined; } - beforeEach(() => { resetRequestState(); @@ -56,7 +57,7 @@ describe('given a requestor', () => { headers, status: testStatus, async text(): Promise { - return testResponse ?? ""; + return testResponse ?? ''; }, json(): Promise { throw new Error('Function not implemented.'); @@ -77,14 +78,14 @@ describe('given a requestor', () => { }); it('gets data', (done) => { - testResponse = "a response"; + testResponse = 'a response'; requestor.requestAllData((err, body) => { expect(err).toBeUndefined(); expect(body).toEqual(testResponse); expect(requestsMade.length).toBe(1); - expect(requestsMade[0].url).toBe("https://sdk.launchdarkly.com/sdk/latest-all"); - expect(requestsMade[0].options.headers?.['authorization']).toBe('sdkKey'); + expect(requestsMade[0].url).toBe('https://sdk.launchdarkly.com/sdk/latest-all'); + expect(requestsMade[0].options.headers?.authorization).toBe('sdkKey'); done(); }); @@ -99,7 +100,7 @@ describe('given a requestor', () => { }); it('returns an error result for a network error', (done) => { - throwThis = "SOMETHING BAD"; + throwThis = 'SOMETHING BAD'; requestor.requestAllData((err, _body) => { expect(err.message).toBe(throwThis); done(); @@ -108,13 +109,13 @@ describe('given a requestor', () => { it('stores and sends etags', async () => { testHeaders.etag = 'abc123'; - testResponse = "a response"; - const res1 = await promisify<{err:any, body: any}>((cb) => { - requestor.requestAllData((err, body) => cb({err, body})); + testResponse = 'a response'; + const res1 = await promisify<{ err:any, body: any }>((cb) => { + requestor.requestAllData((err, body) => cb({ err, body })); }); testStatus = 304; - const res2 = await promisify<{err:any, body: any}>((cb) => { - requestor.requestAllData((err, body) => cb({err, body})); + const res2 = await promisify<{ err:any, body: any }>((cb) => { + requestor.requestAllData((err, body) => cb({ err, body })); }); expect(res1.err).toBeUndefined(); expect(res1.body).toEqual(testResponse); diff --git a/server-sdk-common/src/api/integrations/FileDataSourceOptions.ts b/server-sdk-common/src/api/integrations/FileDataSourceOptions.ts index 36cdf83fd5..23759e3a5d 100644 --- a/server-sdk-common/src/api/integrations/FileDataSourceOptions.ts +++ b/server-sdk-common/src/api/integrations/FileDataSourceOptions.ts @@ -17,8 +17,15 @@ export interface FileDataSourceOptions { autoUpdate?: boolean; /** - * Configures a logger for warnings and errors. This can be a custom logger or an instance of - * `winston.Logger`. By default, it uses the same logger as the rest of the SDK. + * Configures a logger for warnings and errors. This can be a custom logger or an instance of. + * By default, it uses the same logger as the rest of the SDK. */ - logger?: LDLogger | object; + logger?: LDLogger; + + /** + * The SDK can support yaml if provided with a parser. The parser must output + * objects which are equivalent to the standard JSON parser. The parser of the + * `yaml` package can be used. + */ + yamlParser?: (data: string) => any; } diff --git a/server-sdk-common/src/data_sources/FileDataSource.ts b/server-sdk-common/src/data_sources/FileDataSource.ts new file mode 100644 index 0000000000..66e008687f --- /dev/null +++ b/server-sdk-common/src/data_sources/FileDataSource.ts @@ -0,0 +1,152 @@ +import { LDLogger } from '@launchdarkly/js-sdk-common'; +import { LDStreamProcessor } from '../api'; +import { FileDataSourceOptions } from '../api/integrations'; +import { DataKind } from '../api/interfaces'; +import { LDFeatureStore, LDFeatureStoreDataStorage } from '../api/subsystems'; +import { Flag } from '../evaluation/data/Flag'; +import Configuration from '../options/Configuration'; +import { Filesystem } from '../platform'; +import { processFlag, processSegment } from '../store/serialization'; +import VersionedDataKinds from '../store/VersionedDataKinds'; +import FileLoader from './FileLoader'; + +function makeFlagWithValue(key: string, value: any): Flag { + return { + key, + on: true, + fallthrough: { variation: 0 }, + variations: [value], + version: 1, + }; +} + +class FileDataSource implements LDStreamProcessor { + private logger?: LDLogger; + + private yamlParser?: (data: string) => any; + + private fileLoader: FileLoader; + + private allData: LDFeatureStoreDataStorage = {}; + + private initCallback?: ((err?: any) => void); + + /** + * This is internal because we want instances to only be created with the + * factory method. + * @internal + */ + constructor( + options: FileDataSourceOptions, + filesystem: Filesystem, + private readonly featureStore: LDFeatureStore, + ) { + this.fileLoader = new FileLoader( + filesystem, + options.paths, + options.autoUpdate ?? false, + (results: { path: string, data: string }[]) => { + // Whenever changes are detected we re-process all of the data. + // The FileLoader will have handled debouncing for us. + this.processFileData(results); + }, + ); + + this.logger = options.logger; + this.yamlParser = options.yamlParser; + } + + start(fn?: ((err?: any) => void) | undefined): void { + this.initCallback = fn; + this.fileLoader.loadAndWatch(); + } + + stop(): void { + this.fileLoader.close(); + } + + close(): void { + this.stop(); + } + + private tryParse(parser: (data: string) => any, path: string, data: string): any { + try { + return parser(data); + } catch (err) { + this.logger?.error(`Error parsing file ${path}. ${err}`); + return undefined; + } + } + + private addItem(kind: DataKind, item: any) { + if (!this.allData[kind.namespace]) { + this.allData[kind.namespace] = {}; + } + if (this.allData[kind.namespace][item.key]) { + this.logger?.error(`found duplicate key: "${item.key}"`); + } else { + this.allData[kind.namespace][item.key] = item; + } + } + + private processFileData(fileData: { path: string, data: string }[]) { + // Clear any existing data before re-populating it. + this.allData = {}; + + fileData.forEach((fd) => { + let parsed: any; + if (fd.path.endsWith('.yml') || fd.path.endsWith('.yaml')) { + if (this.yamlParser) { + parsed = this.tryParse(this.yamlParser, fd.path, fd.data); + } else { + this.logger?.error('Attempted to parse yaml file without parser.'); + } + } else { + parsed = this.tryParse(JSON.parse, fd.path, fd.data); + } + + if (parsed) { + this.processParsedData(parsed); + } + }); + + this.featureStore.init(this.allData, () => { + // Call the init callback if present. + // Then clear the callback so we cannot call it again. + this.initCallback?.(); + this.initCallback = undefined; + }); + } + + private processParsedData(parsed: any) { + Object.keys(parsed.flags || {}).forEach((key) => { + processFlag(parsed.flags[key]); + this.addItem(VersionedDataKinds.Features, parsed.flags[key]); + }); + Object.keys(parsed.flagValues || {}).forEach((key) => { + const flag = makeFlagWithValue(key, parsed.flagValues[key]); + processFlag(flag); + this.addItem(VersionedDataKinds.Features, flag); + }); + Object.keys(parsed.segments || {}).forEach((key) => { + processSegment(parsed.segments[key]); + this.addItem(VersionedDataKinds.Segments, parsed.segments[key]); + }); + } +} + +/** + * Creates a factory which will create {@link FileDataSource} instances + * based on the SDK configuration. + */ +export default function factory(options: FileDataSourceOptions): +(config: Configuration, filesystem: Filesystem, featureStore: LDFeatureStore) => FileDataSource { + return (config: Configuration, filesystem: Filesystem) => { + const updatedOptions: FileDataSourceOptions = { + paths: options.paths, + autoUpdate: options.autoUpdate, + logger: options.logger || config.logger, + }; + return new FileDataSource(updatedOptions, filesystem, config.featureStore); + }; +} diff --git a/server-sdk-common/src/data_sources/FileLoader.ts b/server-sdk-common/src/data_sources/FileLoader.ts new file mode 100644 index 0000000000..9d5e9406ee --- /dev/null +++ b/server-sdk-common/src/data_sources/FileLoader.ts @@ -0,0 +1,88 @@ +import { Filesystem, WatchHandle } from '../platform'; + +/** + * A debounced file load/watcher for use with the {@link FileDataSource}. + * + * The file loader will load all files specified and keep the string representations in memory. + * Whenever a change is made to any of the files, then that file will be reloaded and the in + * memory version updated. + * + * Updates to many files, which occur within 10ms of each other, will be coalesced into + * a single callback. + * + * @internal + */ +export default class FileLoader { + private watchers: WatchHandle[] = []; + + private fileData: { [path: string]: string; } = {}; + + private fileTimestamps: { [path: string]: number; } = {}; + + private debounceHandle: any; + + constructor( + private readonly filesystem: Filesystem, + private readonly paths: string[], + private readonly watch: boolean, + private readonly callback: (results: { path: string; data: string; }[]) => void, + ) { + } + + /** + * Load all the files and start watching them if watching is enabled. + */ + async loadAndWatch() { + const promises = this.paths.map(async (path) => { + const data = await this.filesystem.readFile(path); + const timeStamp = await this.filesystem.getFileTimestamp(path); + return { data, path, timeStamp }; + }); + const results = await Promise.all(promises); + results.forEach((res) => { + this.fileData[res.path] = res.data; + this.fileTimestamps[res.path] = res.timeStamp; + }); + this.callback(results); + + // If we are watching, then setup watchers and notify of any changes. + if (this.watch) { + this.paths.forEach((path) => { + const watcher = this.filesystem.watch(path, async (_, updatePath) => { + const timeStamp = await this.filesystem.getFileTimestamp(updatePath); + // The modification time is the same, so we are going to ignore this update. + // In some implementations watch might be triggered multiple times for a single update. + if (timeStamp === this.fileTimestamps[updatePath]) { + return; + } + this.fileTimestamps[updatePath] = timeStamp; + const data = await this.filesystem.readFile(updatePath); + this.fileData[updatePath] = data; + this.debounceCallback(); + }); + this.watchers.push(watcher); + }); + } + } + + close() { + this.watchers.forEach((watcher) => watcher.close()); + } + + private debounceCallback() { + // If there is a handle, then we have already started the debounce process. + if (!this.debounceHandle) { + this.debounceHandle = setTimeout(() => { + this.debounceHandle = undefined; + this.callback(Object.entries(this.fileData) + .reduce((acc: { path: string; data: string; }[], [path, data]) => { + acc.push({ path, data }); + return acc; + }, [])); + }, 10); + // The 10ms delay above is arbitrary - we just don't want to have the number be zero, + // because in a case where multiple watch events are fired off one after another, + // we want the reload to happen only after all of the event handlers have executed. + } + } +} diff --git a/server-sdk-common/src/data_sources/PollingProcessor.ts b/server-sdk-common/src/data_sources/PollingProcessor.ts index de2128da7d..c166675319 100644 --- a/server-sdk-common/src/data_sources/PollingProcessor.ts +++ b/server-sdk-common/src/data_sources/PollingProcessor.ts @@ -31,7 +31,7 @@ export default class PollingProcessor implements LDStreamProcessor { } const reportJsonError = (data: string) => { - this.logger?.error(`Polling received invalid data`); + this.logger?.error('Polling received invalid data'); this.logger?.debug(`Invalid JSON follows: ${data}`); fn?.(new LDPollingError('Malformed JSON data in polling response')); }; @@ -51,9 +51,8 @@ export default class PollingProcessor implements LDStreamProcessor { // It is not recoverable, return and do not trigger another // poll. return; - } else { - this.logger?.warn(httpErrorMessage(err, 'polling request', 'will retry')); } + this.logger?.warn(httpErrorMessage(err, 'polling request', 'will retry')); } else if (body) { const parsed = deserializePoll(body); if (!parsed) { @@ -73,7 +72,7 @@ export default class PollingProcessor implements LDStreamProcessor { }, sleepFor); }); // The poll will be triggered by the feature store initialization - // completing. + // completing. return; } } @@ -87,18 +86,18 @@ export default class PollingProcessor implements LDStreamProcessor { } start(fn?: ((err?: any) => void) | undefined) { - this.poll(fn) - }; + this.poll(fn); + } stop() { - if(this.timeoutHandle) { + if (this.timeoutHandle) { clearTimeout(this.timeoutHandle); this.timeoutHandle = undefined; } this.stopped = true; - }; + } close() { this.stop(); - }; + } } diff --git a/server-sdk-common/src/data_sources/Requestor.ts b/server-sdk-common/src/data_sources/Requestor.ts index 22fedfefac..a69b5697c7 100644 --- a/server-sdk-common/src/data_sources/Requestor.ts +++ b/server-sdk-common/src/data_sources/Requestor.ts @@ -1,11 +1,14 @@ import { LDFeatureRequestor } from '../api/subsystems'; import { LDStreamingError } from '../errors'; import Configuration from '../options/Configuration'; -import { Info, Options, Requests, Response } from '../platform'; +import { + Info, Options, Requests, Response, +} from '../platform'; import defaultHeaders from './defaultHeaders'; export default class Requestor implements LDFeatureRequestor { private readonly headers: Record; + private readonly uri: string; private readonly eTagCache: Record void) { const options: Options = { method: 'GET', - headers: this.headers + headers: this.headers, }; try { const { res, body } = await this.requestWithETagCache(this.uri, options); if (res.status !== 200 && res.status !== 304) { const err = new LDStreamingError( - 'Unexpected status code: ' + res.status, - res.status + `Unexpected status code: ${res.status}`, + res.status, ); return cb(err, undefined); } - return cb(undefined, res.status === 304? null : body); + return cb(undefined, res.status === 304 ? null : body); } catch (err) { return cb(err, undefined); } - }; + } } diff --git a/server-sdk-common/src/platform/Filesystem.ts b/server-sdk-common/src/platform/Filesystem.ts index 67d5636c1e..ce9ed2905d 100644 --- a/server-sdk-common/src/platform/Filesystem.ts +++ b/server-sdk-common/src/platform/Filesystem.ts @@ -1,3 +1,10 @@ +export interface WatchHandle { + /** + * Stop watching. + */ + close(): void; +} + /** * Interface for doing filesystem operations on the platform. */ @@ -31,5 +38,5 @@ export interface Filesystem { * * @returns An async iterator that watches for changes to `path`. */ - watch(path: string): AsyncIterable<{ eventType: string, filename: string }>; + watch(path: string, callback: (eventType: string, filename: string) => void): WatchHandle; } diff --git a/server-sdk-common/src/store/serialization.ts b/server-sdk-common/src/store/serialization.ts index 70b0a82b1f..dadd0194da 100644 --- a/server-sdk-common/src/store/serialization.ts +++ b/server-sdk-common/src/store/serialization.ts @@ -70,7 +70,7 @@ function processRollout(rollout?: Rollout) { } } -function processFlag(flag: Flag) { +export function processFlag(flag: Flag) { if (flag.fallthrough && flag.fallthrough.rollout) { const rollout = flag.fallthrough.rollout!; processRollout(rollout); @@ -90,7 +90,7 @@ function processFlag(flag: Flag) { }); } -function processSegment(segment: Segment) { +export function processSegment(segment: Segment) { segment?.rules?.forEach((rule) => { if (rule.bucketBy) { // Rules before U2C would have had literals for attributes. From f208ff4e5bcf4c312b1636f25a40e9a8f326f7cd Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 1 Aug 2022 11:47:04 -0700 Subject: [PATCH 02/34] Write unit tests for file data source --- .../data_sources/FileDataSource.test.ts | 585 ++++++++++++++++++ .../src/data_sources/FileDataSource.ts | 63 +- .../src/data_sources/FileDataSourceFactory.ts | 43 ++ .../src/data_sources/FileLoader.ts | 5 +- 4 files changed, 657 insertions(+), 39 deletions(-) create mode 100644 server-sdk-common/__tests__/data_sources/FileDataSource.test.ts create mode 100644 server-sdk-common/src/data_sources/FileDataSourceFactory.ts diff --git a/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts b/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts new file mode 100644 index 0000000000..19b1b986e8 --- /dev/null +++ b/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts @@ -0,0 +1,585 @@ +import { Context } from '@launchdarkly/js-sdk-common'; +import promisify from '../../src/async/promisify'; +import FileDataSourceFactory from '../../src/data_sources/FileDataSourceFactory'; +import { Flag } from '../../src/evaluation/data/Flag'; +import { Segment } from '../../src/evaluation/data/Segment'; +import Evaluator from '../../src/evaluation/Evaluator'; +import { Filesystem, WatchHandle } from '../../src/platform'; +import AsyncStoreFacade from '../../src/store/AsyncStoreFacade'; +import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; +import VersionedDataKinds from '../../src/store/VersionedDataKinds'; +import basicPlatform from '../evaluation/mocks/platform'; +import TestLogger from '../Logger'; + +const flag1Key = 'flag1'; +const flag2Key = 'flag2'; +const flag2Value = 'value2'; +const segment1Key = 'seg1'; + +const flag1 = { + key: flag1Key, + on: true, + fallthrough: { + variation: 2, + }, + variations: ['fall', 'off', 'on'], +}; + +const segment1 = { + key: segment1Key, + include: ['user1'], +}; + +const flagOnlyJson = ` +{ + "flags": { + "${flag1Key}": ${JSON.stringify(flag1)} + } +}`; + +const segmentOnlyJson = ` +{ + "segments": { + "${segment1Key}": ${JSON.stringify(segment1)} + } +}`; + +const allPropertiesJson = ` +{ + "flags": { + "${flag1Key}": ${JSON.stringify(flag1)} + }, + "flagValues": { + "${flag2Key}": "${flag2Value}" + }, + "segments": { + "${segment1Key}": ${JSON.stringify(segment1)} + } +}`; + +// const allPropertiesYaml = ` +// flags: +// ${flag1Key}: +// key: ${flag1Key} +// on: true +// fallthrough: +// variation: 2 +// variations: +// - fall +// - off +// - on +// flagValues: +// ${flag2Key}: "${flag2Value}" +// segments: +// ${segment1Key}: +// key: ${segment1Key} +// include: +// - user1 +// `; + +function sorted(a: any[]) { + const a1 = Array.from(a); + a1.sort(); + return a1; +} + +class MockFilesystem implements Filesystem { + public fileData: Record = {}; + + public watches: Record void } + )[]> = {}; + + public watchHandleId = 0; + + async getFileTimestamp(path: string): Promise { + return this.fileData[path]?.timestamp; + } + + async readFile(path: string): Promise { + if (!this.fileData[path]) { + throw new Error('FILE NOT FOUND'); + } + return this.fileData[path]?.data; + } + + watch(path: string, callback: (eventType: string, filename: string) => void): WatchHandle { + if (!this.watches[path]) { + this.watches[path] = []; + } + const watchHandles = this.watches; + const id = this.watchHandleId; + const newHandle = { + id, + close: () => { + const index = watchHandles[path].findIndex((handle) => handle.id === id); + if (index >= 0) { + watchHandles[path].splice(index, 1); + } + }, + cb: callback, + }; + this.watches[path].push(newHandle); + + this.watchHandleId += 1; + + return newHandle; + } +} + +describe('given a mock filesystem and memory feature store', () => { + let filesystem: MockFilesystem; + let logger: TestLogger; + let featureStore: InMemoryFeatureStore; + + let asyncFeatureStore: AsyncStoreFacade; + + beforeEach(() => { + filesystem = new MockFilesystem(); + logger = new TestLogger(); + featureStore = new InMemoryFeatureStore(); + asyncFeatureStore = new AsyncStoreFacade(featureStore); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('does not load flags prior to start', async () => { + filesystem.fileData['testfile.json'] = { timestamp: 0, data: '{"flagValues":{"key":"value"}}' }; + jest.spyOn(filesystem, 'readFile'); + + const factory = new FileDataSourceFactory({ + paths: ['testfile.json'], + }); + + factory.create({ + featureStore, + logger, + }, filesystem); + + expect(await asyncFeatureStore.initialized()).toBeFalsy(); + + expect(await asyncFeatureStore.all(VersionedDataKinds.Features)).toEqual({}); + expect(await asyncFeatureStore.all(VersionedDataKinds.Segments)).toEqual({}); + // There was no file access. + expect(filesystem.readFile).toHaveBeenCalledTimes(0); + }); + + it('loads all properties', (done) => { + filesystem.fileData['testfile.json'] = { timestamp: 0, data: allPropertiesJson }; + jest.spyOn(filesystem, 'readFile'); + + const factory = new FileDataSourceFactory({ + paths: ['testfile.json'], + }); + + const fds = factory.create({ + featureStore, + logger, + }, filesystem); + + fds.start(async () => { + expect(await asyncFeatureStore.initialized()).toBeTruthy(); + + const flags = await asyncFeatureStore.all(VersionedDataKinds.Features); + expect(sorted(Object.keys(flags))).toEqual([flag1Key, flag2Key]); + + const segments = await asyncFeatureStore.all(VersionedDataKinds.Segments); + expect(segments).toEqual({ seg1: segment1 }); + expect(filesystem.readFile).toHaveBeenCalledTimes(1); + done(); + }); + }); + + it('does not load if a file it not found', (done) => { + const factory = new FileDataSourceFactory({ + paths: ['missing-file.json'], + }); + + const fds = factory.create({ + featureStore, + logger, + }, filesystem); + + fds.start(async (err) => { + expect(err).toBeDefined(); + expect(await asyncFeatureStore.initialized()).toBeFalsy(); + + expect(await asyncFeatureStore.all(VersionedDataKinds.Features)).toEqual({}); + expect(await asyncFeatureStore.all(VersionedDataKinds.Segments)).toEqual({}); + done(); + }); + }); + + it('does not load if a file was malformed', (done) => { + filesystem.fileData['malformed_file.json'] = { timestamp: 0, data: '{sorry' }; + jest.spyOn(filesystem, 'readFile'); + const factory = new FileDataSourceFactory({ + paths: ['malformed_file.json'], + }); + + const fds = factory.create({ + featureStore, + logger, + }, filesystem); + + fds.start(async (err) => { + expect(err).toBeDefined(); + expect(await asyncFeatureStore.initialized()).toBeFalsy(); + + expect(await asyncFeatureStore.all(VersionedDataKinds.Features)).toEqual({}); + expect(await asyncFeatureStore.all(VersionedDataKinds.Segments)).toEqual({}); + expect(filesystem.readFile).toHaveBeenCalledWith('malformed_file.json'); + done(); + }); + }); + + it('can load multiple files', (done) => { + filesystem.fileData['file1.json'] = { timestamp: 0, data: flagOnlyJson }; + filesystem.fileData['file2.json'] = { timestamp: 0, data: segmentOnlyJson }; + + jest.spyOn(filesystem, 'readFile'); + const factory = new FileDataSourceFactory({ + paths: ['file1.json', 'file2.json'], + }); + + const fds = factory.create({ + featureStore, logger, + }, filesystem); + + fds.start(async () => { + expect(await asyncFeatureStore.initialized()).toBeTruthy(); + + const flags = await asyncFeatureStore.all(VersionedDataKinds.Features); + expect(sorted(Object.keys(flags))).toEqual([flag1Key]); + + const segments = await asyncFeatureStore.all(VersionedDataKinds.Segments); + expect(segments).toEqual({ seg1: segment1 }); + expect(filesystem.readFile).toHaveBeenCalledTimes(2); + done(); + }); + }); + + it('does not allow duplicate keys', (done) => { + filesystem.fileData['file1.json'] = { timestamp: 0, data: flagOnlyJson }; + filesystem.fileData['file2.json'] = { timestamp: 0, data: flagOnlyJson }; + + jest.spyOn(filesystem, 'readFile'); + const factory = new FileDataSourceFactory({ + paths: ['file1.json', 'file2.json'], + }); + + const fds = factory.create({ + featureStore, logger, + }, filesystem); + + fds.start(async (err) => { + expect(err).toBeDefined(); + expect(await asyncFeatureStore.initialized()).toBeFalsy(); + expect(filesystem.readFile).toHaveBeenCalledTimes(2); + done(); + }); + }); + + it('does not create watchers if auto-update if off', (done) => { + filesystem.fileData['file1.json'] = { timestamp: 0, data: flagOnlyJson }; + filesystem.fileData['file2.json'] = { timestamp: 0, data: segmentOnlyJson }; + + jest.spyOn(filesystem, 'watch'); + const factory = new FileDataSourceFactory({ + paths: ['file1.json', 'file2.json'], + }); + + const fds = factory.create({ + featureStore, logger, + }, filesystem); + + fds.start(async () => { + expect(await asyncFeatureStore.initialized()).toBeTruthy(); + expect(filesystem.watch).toHaveBeenCalledTimes(0); + done(); + }); + }); + + // TODO: Use the full client for some evaluations when it is merged together. + + it('can evaluate simple loaded flags', (done) => { + filesystem.fileData['file1.json'] = { timestamp: 0, data: allPropertiesJson }; + + const factory = new FileDataSourceFactory({ + paths: ['file1.json'], + }); + + const fds = factory.create({ + featureStore, logger, + }, filesystem); + + fds.start(async () => { + const evaluator = new Evaluator(basicPlatform, { + getFlag: async (key) => (await asyncFeatureStore.get( + VersionedDataKinds.Features, + key, + ) as Flag ?? undefined), + getSegment: async (key) => (await asyncFeatureStore.get( + VersionedDataKinds.Segments, + key, + ) as Segment) ?? undefined, + getBigSegmentsMembership: () => Promise.resolve(undefined), + }); + + const flag = await asyncFeatureStore.get(VersionedDataKinds.Features, flag2Key); + const res = await evaluator.evaluate( + flag as Flag, + Context.fromLDContext({ key: 'userkey' })!, + ); + expect(res.detail.value).toEqual(flag2Value); + done(); + }); + }); + + it('can evaluate full loaded flags', (done) => { + filesystem.fileData['file1.json'] = { timestamp: 0, data: allPropertiesJson }; + + const factory = new FileDataSourceFactory({ + paths: ['file1.json'], + }); + + const fds = factory.create({ + featureStore, logger, + }, filesystem); + + fds.start(async () => { + const evaluator = new Evaluator(basicPlatform, { + getFlag: async (key) => (await asyncFeatureStore.get( + VersionedDataKinds.Features, + key, + ) as Flag ?? undefined), + getSegment: async (key) => (await asyncFeatureStore.get( + VersionedDataKinds.Segments, + key, + ) as Segment) ?? undefined, + getBigSegmentsMembership: () => Promise.resolve(undefined), + }); + + const flag = await asyncFeatureStore.get(VersionedDataKinds.Features, flag1Key); + const res = await evaluator.evaluate( + flag as Flag, + Context.fromLDContext({ key: 'userkey' })!, + ); + expect(res.detail.value).toEqual('on'); + done(); + }); + }); + + it( + 'register watchers when auto update is enabled and unregisters them when it is closed', + (done) => { + filesystem.fileData['file1.json'] = { timestamp: 0, data: flagOnlyJson }; + filesystem.fileData['file2.json'] = { timestamp: 0, data: segmentOnlyJson }; + + jest.spyOn(filesystem, 'watch'); + const factory = new FileDataSourceFactory({ + paths: ['file1.json', 'file2.json'], + autoUpdate: true, + }); + + const fds = factory.create({ + featureStore, logger, + }, filesystem); + + fds.start(async () => { + expect(await asyncFeatureStore.initialized()).toBeTruthy(); + expect(filesystem.watch).toHaveBeenCalledTimes(2); + expect(filesystem.watches['file1.json'].length).toEqual(1); + expect(filesystem.watches['file2.json'].length).toEqual(1); + fds.close(); + + expect(filesystem.watches['file1.json'].length).toEqual(0); + expect(filesystem.watches['file2.json'].length).toEqual(0); + done(); + }); + }, + ); + + it('reloads modified files when auto update is enabled', (done) => { + filesystem.fileData['file1.json'] = { timestamp: 0, data: flagOnlyJson }; + + jest.spyOn(filesystem, 'watch'); + const factory = new FileDataSourceFactory({ + paths: ['file1.json'], + autoUpdate: true, + }); + + const fds = factory.create({ + featureStore, logger, + }, filesystem); + + fds.start(async () => { + expect(await asyncFeatureStore.initialized()).toBeTruthy(); + + const flags = await asyncFeatureStore.all(VersionedDataKinds.Features); + expect(Object.keys(flags).length).toEqual(1); + + const segments = await asyncFeatureStore.all(VersionedDataKinds.Segments); + expect(Object.keys(segments).length).toEqual(0); + + // Need to update the timestamp, or it will think the file has not changed. + filesystem.fileData['file1.json'] = { timestamp: 100, data: segmentOnlyJson }; + filesystem.watches['file1.json'][0].cb('change', 'file1.json'); + + // The handling of the file loading is async, and additionally we debounce + // the callback. So we have to wait a bit to account for the awaits and the debounce. + setTimeout(async () => { + const flags2 = await asyncFeatureStore.all(VersionedDataKinds.Features); + expect(Object.keys(flags2).length).toEqual(0); + + const segments2 = await asyncFeatureStore.all(VersionedDataKinds.Segments); + expect(Object.keys(segments2).length).toEqual(1); + done(); + }, 100); + }); + }); + + it('debounces the callback for file loading', (done) => { + filesystem.fileData['file1.json'] = { timestamp: 0, data: flagOnlyJson }; + + jest.spyOn(filesystem, 'watch'); + jest.spyOn(featureStore, 'init'); + const factory = new FileDataSourceFactory({ + paths: ['file1.json'], + autoUpdate: true, + }); + + const fds = factory.create({ + featureStore, logger, + }, filesystem); + + fds.start(async () => { + expect(await asyncFeatureStore.initialized()).toBeTruthy(); + + const flags = await asyncFeatureStore.all(VersionedDataKinds.Features); + expect(Object.keys(flags).length).toEqual(1); + + const segments = await asyncFeatureStore.all(VersionedDataKinds.Segments); + expect(Object.keys(segments).length).toEqual(0); + + // Trigger several change callbacks. + filesystem.fileData['file1.json'] = { timestamp: 100, data: segmentOnlyJson }; + filesystem.watches['file1.json'][0].cb('change', 'file1.json'); + filesystem.fileData['file1.json'] = { timestamp: 101, data: segmentOnlyJson }; + filesystem.watches['file1.json'][0].cb('change', 'file1.json'); + filesystem.fileData['file1.json'] = { timestamp: 102, data: segmentOnlyJson }; + filesystem.watches['file1.json'][0].cb('change', 'file1.json'); + filesystem.fileData['file1.json'] = { timestamp: 103, data: segmentOnlyJson }; + filesystem.watches['file1.json'][0].cb('change', 'file1.json'); + + // The handling of the file loading is async, and additionally we debounce + // the callback. So we have to wait a bit to account for the awaits and the debounce. + setTimeout(async () => { + // Once for the start, and then again for the coalesced update. + expect(featureStore.init).toHaveBeenCalledTimes(2); + done(); + }, 100); + }); + }); + + it('does not callback if the timestamp has not changed', (done) => { + filesystem.fileData['file1.json'] = { timestamp: 0, data: flagOnlyJson }; + + jest.spyOn(filesystem, 'watch'); + jest.spyOn(featureStore, 'init'); + const factory = new FileDataSourceFactory({ + paths: ['file1.json'], + autoUpdate: true, + }); + + const fds = factory.create({ + featureStore, logger, + }, filesystem); + + fds.start(async () => { + expect(await asyncFeatureStore.initialized()).toBeTruthy(); + + const flags = await asyncFeatureStore.all(VersionedDataKinds.Features); + expect(Object.keys(flags).length).toEqual(1); + + const segments = await asyncFeatureStore.all(VersionedDataKinds.Segments); + expect(Object.keys(segments).length).toEqual(0); + + filesystem.watches['file1.json'][0].cb('change', 'file1.json'); + filesystem.watches['file1.json'][0].cb('change', 'file1.json'); + + // The handling of the file loading is async, and additionally we debounce + // the callback. So we have to wait a bit to account for the awaits and the debounce. + setTimeout(async () => { + // Once for the start. + expect(featureStore.init).toHaveBeenCalledTimes(1); + done(); + }, 100); + }); + }); + + it.each([['yml'], ['yaml']])( + 'does not initialize when a yaml file is specified, but no parser is provided %s', + async (ext) => { + jest.spyOn(filesystem, 'readFile'); + const fileName = `yamlfile.${ext}`; + filesystem.fileData[fileName] = { timestamp: 0, data: '' }; + const factory = new FileDataSourceFactory({ + paths: [fileName], + }); + + const fds = factory.create({ + featureStore, + logger, + }, filesystem); + + const err = await promisify((cb) => { + fds.start(cb); + }); + + expect((err as any).message) + .toEqual(`Attempted to parse yaml file (yamlfile.${ext}) without parser.`); + expect(await asyncFeatureStore.initialized()).toBeFalsy(); + + expect(await asyncFeatureStore.all(VersionedDataKinds.Features)).toEqual({}); + expect(await asyncFeatureStore.all(VersionedDataKinds.Segments)).toEqual({}); + }, + ); + + it.each([['yml'], ['yaml']])('uses the yaml parser when specified %s', async (ext) => { + const parser = jest.fn(() => (JSON.parse(allPropertiesJson))); + + jest.spyOn(filesystem, 'readFile'); + const fileName = `yamlfile.${ext}`; + filesystem.fileData[fileName] = { timestamp: 0, data: 'the data' }; + const factory = new FileDataSourceFactory({ + paths: [fileName], + yamlParser: parser, + }); + + const fds = factory.create({ + featureStore, + logger, + }, filesystem); + + const err = await promisify((cb) => { + fds.start(cb); + }); + + expect(err).toBeUndefined(); + expect(await asyncFeatureStore.initialized()).toBeTruthy(); + + const flags = await asyncFeatureStore.all(VersionedDataKinds.Features); + expect(sorted(Object.keys(flags))).toEqual([flag1Key, flag2Key]); + + const segments = await asyncFeatureStore.all(VersionedDataKinds.Segments); + expect(segments).toEqual({ seg1: segment1 }); + expect(filesystem.readFile).toHaveBeenCalledTimes(1); + expect(parser).toHaveBeenCalledWith('the data'); + }); +}); diff --git a/server-sdk-common/src/data_sources/FileDataSource.ts b/server-sdk-common/src/data_sources/FileDataSource.ts index 66e008687f..6470c0cf81 100644 --- a/server-sdk-common/src/data_sources/FileDataSource.ts +++ b/server-sdk-common/src/data_sources/FileDataSource.ts @@ -4,7 +4,6 @@ import { FileDataSourceOptions } from '../api/integrations'; import { DataKind } from '../api/interfaces'; import { LDFeatureStore, LDFeatureStoreDataStorage } from '../api/subsystems'; import { Flag } from '../evaluation/data/Flag'; -import Configuration from '../options/Configuration'; import { Filesystem } from '../platform'; import { processFlag, processSegment } from '../store/serialization'; import VersionedDataKinds from '../store/VersionedDataKinds'; @@ -20,7 +19,7 @@ function makeFlagWithValue(key: string, value: any): Flag { }; } -class FileDataSource implements LDStreamProcessor { +export default class FileDataSource implements LDStreamProcessor { private logger?: LDLogger; private yamlParser?: (data: string) => any; @@ -33,7 +32,7 @@ class FileDataSource implements LDStreamProcessor { /** * This is internal because we want instances to only be created with the - * factory method. + * factory. * @internal */ constructor( @@ -48,7 +47,13 @@ class FileDataSource implements LDStreamProcessor { (results: { path: string, data: string }[]) => { // Whenever changes are detected we re-process all of the data. // The FileLoader will have handled debouncing for us. - this.processFileData(results); + try { + this.processFileData(results); + } catch (err) { + // If this was during start, then the initCallback will be present. + this.initCallback?.(err); + this.logger?.error(`Error processing files: ${err}`); + } }, ); @@ -58,7 +63,17 @@ class FileDataSource implements LDStreamProcessor { start(fn?: ((err?: any) => void) | undefined): void { this.initCallback = fn; - this.fileLoader.loadAndWatch(); + // Use an immediately invoked function expression to allow handling of the + // async loading without making start async itself. + (async () => { + try { + await this.fileLoader.loadAndWatch(); + } catch (err) { + // There was an issue loading/watching the files. + // Report back to the caller. + fn?.(err); + } + })(); } stop(): void { @@ -69,21 +84,12 @@ class FileDataSource implements LDStreamProcessor { this.stop(); } - private tryParse(parser: (data: string) => any, path: string, data: string): any { - try { - return parser(data); - } catch (err) { - this.logger?.error(`Error parsing file ${path}. ${err}`); - return undefined; - } - } - private addItem(kind: DataKind, item: any) { if (!this.allData[kind.namespace]) { this.allData[kind.namespace] = {}; } if (this.allData[kind.namespace][item.key]) { - this.logger?.error(`found duplicate key: "${item.key}"`); + throw new Error(`found duplicate key: "${item.key}"`); } else { this.allData[kind.namespace][item.key] = item; } @@ -93,21 +99,20 @@ class FileDataSource implements LDStreamProcessor { // Clear any existing data before re-populating it. this.allData = {}; + // We let the parsers throw, and the caller can handle the rejection. fileData.forEach((fd) => { let parsed: any; if (fd.path.endsWith('.yml') || fd.path.endsWith('.yaml')) { if (this.yamlParser) { - parsed = this.tryParse(this.yamlParser, fd.path, fd.data); + parsed = this.yamlParser(fd.data); } else { - this.logger?.error('Attempted to parse yaml file without parser.'); + throw new Error(`Attempted to parse yaml file (${fd.path}) without parser.`); } } else { - parsed = this.tryParse(JSON.parse, fd.path, fd.data); + parsed = JSON.parse(fd.data); } - if (parsed) { - this.processParsedData(parsed); - } + this.processParsedData(parsed); }); this.featureStore.init(this.allData, () => { @@ -134,19 +139,3 @@ class FileDataSource implements LDStreamProcessor { }); } } - -/** - * Creates a factory which will create {@link FileDataSource} instances - * based on the SDK configuration. - */ -export default function factory(options: FileDataSourceOptions): -(config: Configuration, filesystem: Filesystem, featureStore: LDFeatureStore) => FileDataSource { - return (config: Configuration, filesystem: Filesystem) => { - const updatedOptions: FileDataSourceOptions = { - paths: options.paths, - autoUpdate: options.autoUpdate, - logger: options.logger || config.logger, - }; - return new FileDataSource(updatedOptions, filesystem, config.featureStore); - }; -} diff --git a/server-sdk-common/src/data_sources/FileDataSourceFactory.ts b/server-sdk-common/src/data_sources/FileDataSourceFactory.ts new file mode 100644 index 0000000000..aa2beb25e5 --- /dev/null +++ b/server-sdk-common/src/data_sources/FileDataSourceFactory.ts @@ -0,0 +1,43 @@ +import { LDLogger } from '@launchdarkly/js-sdk-common'; +import { FileDataSourceOptions } from '../api/integrations'; +import { LDFeatureStore } from '../api/subsystems'; +import { Filesystem } from '../platform'; +import FileDataSource from './FileDataSource'; + +/** + * Components of the SDK runtime configuration which are required + * by the FileDataSource. + */ +export interface FileDataSourceFactoryConfig { + featureStore: LDFeatureStore, + logger?: LDLogger +} + +/** + * Class for creating file data sources. + */ + +export default class FileDataSourceFactory { + constructor(private readonly options: FileDataSourceOptions) { } + + /** + * Method for creating instances of the file data source. This method is intended to be used + * by the SDK. + * + * @param config SDK configuration required by the file data source. + * @param filesystem Platform abstraction used for filesystem access. + * @returns a {@link FileDataSource} + */ + create( + config: FileDataSourceFactoryConfig, + filesystem: Filesystem, + ) { + const updatedOptions: FileDataSourceOptions = { + paths: this.options.paths, + autoUpdate: this.options.autoUpdate, + logger: this.options.logger || config.logger, + yamlParser: this.options.yamlParser, + }; + return new FileDataSource(updatedOptions, filesystem, config.featureStore); + } +} diff --git a/server-sdk-common/src/data_sources/FileLoader.ts b/server-sdk-common/src/data_sources/FileLoader.ts index 9d5e9406ee..6637f2745a 100644 --- a/server-sdk-common/src/data_sources/FileLoader.ts +++ b/server-sdk-common/src/data_sources/FileLoader.ts @@ -15,9 +15,9 @@ import { Filesystem, WatchHandle } from '../platform'; export default class FileLoader { private watchers: WatchHandle[] = []; - private fileData: { [path: string]: string; } = {}; + private fileData: Record = {}; - private fileTimestamps: { [path: string]: number; } = {}; + private fileTimestamps: Record = {}; private debounceHandle: any; @@ -38,6 +38,7 @@ export default class FileLoader { const timeStamp = await this.filesystem.getFileTimestamp(path); return { data, path, timeStamp }; }); + // This promise could be rejected, let the caller handle it. const results = await Promise.all(promises); results.forEach((res) => { this.fileData[res.path] = res.data; From ca829cafc5e01db4e6c1c08aa05db0c924e398a7 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 1 Aug 2022 11:47:43 -0700 Subject: [PATCH 03/34] Remove docs folder. --- docs/.nojekyll | 1 - docs/assets/highlight.css | 85 -- docs/assets/main.js | 54 - docs/assets/search.js | 1 - docs/assets/style.css | 1224 ----------------- docs/assets/widgets.png | Bin 480 -> 0 bytes docs/assets/widgets@2x.png | Bin 855 -> 0 bytes docs/classes/AttributeReference.html | 197 --- docs/classes/BasicLogger.html | 223 --- .../BigSegmentStoreStatusProviderImpl.html | 140 -- docs/classes/Context.html | 363 ----- docs/classes/ContextFilter.html | 133 -- docs/classes/DateValidator.html | 111 -- docs/classes/FactoryOrInstance.html | 110 -- docs/classes/Function.html | 110 -- docs/classes/LDClientImpl.html | 547 -------- docs/classes/NumberWithMinimum.html | 136 -- docs/classes/SafeLogger.html | 212 --- docs/classes/StringMatchingRegex.html | 136 -- docs/classes/Type.html | 151 -- docs/classes/TypeArray.html | 148 -- docs/classes/TypeValidators.html | 170 --- docs/index.html | 145 -- docs/interfaces/BasicLoggerOptions.html | 133 -- docs/interfaces/LDBigSegmentsOptions.html | 138 -- docs/interfaces/LDClient.html | 484 ------- docs/interfaces/LDContextCommon.html | 107 -- docs/interfaces/LDContextMeta.html | 95 -- docs/interfaces/LDEvaluationDetail.html | 98 -- docs/interfaces/LDEvaluationReason.html | 153 --- docs/interfaces/LDFlagSet.html | 93 -- docs/interfaces/LDFlagsState.html | 153 --- docs/interfaces/LDFlagsStateOptions.html | 98 -- docs/interfaces/LDLogger.html | 157 --- docs/interfaces/LDMultiKindContext.html | 79 -- docs/interfaces/LDOptions.html | 383 ------ docs/interfaces/LDProxyOptions.html | 101 -- docs/interfaces/LDSingleKindContext.html | 121 -- docs/interfaces/LDTLSOptions.html | 153 --- docs/interfaces/LDUser.html | 193 --- docs/interfaces/TypeValidator.html | 99 -- .../integrations.FileDataSourceOptions.html | 97 -- docs/interfaces/integrations.TestData.html | 204 --- .../integrations.TestDataFlagBuilder.html | 402 ------ .../integrations.TestDataRuleBuilder.html | 167 --- .../interfaces.BigSegmentStore.html | 129 -- .../interfaces.BigSegmentStoreMembership.html | 75 - .../interfaces.BigSegmentStoreMetadata.html | 77 -- .../interfaces.BigSegmentStoreStatus.html | 99 -- ...erfaces.BigSegmentStoreStatusProvider.html | 107 -- .../interfaces/interfaces.DataCollection.html | 91 -- docs/interfaces/interfaces.DataKind.html | 78 -- .../interfaces/interfaces.ItemDescriptor.html | 78 -- .../interfaces.PersistentDataStore.html | 227 --- .../interfaces.PersistentStoreDataKind.html | 91 -- .../interfaces.SerializedItemDescriptor.html | 88 -- docs/interfaces/interfaces.VersionedData.html | 97 -- docs/interfaces/platform.Crypto.html | 99 -- docs/interfaces/platform.EventSource.html | 138 -- .../platform.EventSourceInitDict.html | 122 -- docs/interfaces/platform.Filesystem.html | 135 -- docs/interfaces/platform.Hasher.html | 101 -- docs/interfaces/platform.Headers.html | 148 -- docs/interfaces/platform.Hmac.html | 103 -- docs/interfaces/platform.Info.html | 92 -- docs/interfaces/platform.Options.html | 92 -- docs/interfaces/platform.Platform.html | 102 -- docs/interfaces/platform.PlatformData.html | 121 -- docs/interfaces/platform.Requests.html | 98 -- docs/interfaces/platform.Response.html | 110 -- docs/interfaces/platform.SdkData.html | 100 -- .../interfaces/subsystems.LDFeatureStore.html | 312 ----- .../subsystems.LDFeatureStoreDataStorage.html | 63 - .../subsystems.LDFeatureStoreItem.html | 86 -- .../subsystems.LDFeatureStoreKindData.html | 63 - .../subsystems.LDKeyedFeatureStoreItem.html | 92 -- docs/modules.html | 133 -- docs/modules/integrations.html | 61 - docs/modules/interfaces.html | 84 -- docs/modules/platform.html | 81 -- docs/modules/subsystems.html | 63 - docs/types/LDContext.html | 86 -- docs/types/LDFlagValue.html | 87 -- docs/types/LDLogLevel.html | 90 -- docs/types/interfaces.FullDataSet.html | 71 - docs/types/interfaces.KeyedItems.html | 70 - 86 files changed, 12345 deletions(-) delete mode 100644 docs/.nojekyll delete mode 100644 docs/assets/highlight.css delete mode 100644 docs/assets/main.js delete mode 100644 docs/assets/search.js delete mode 100644 docs/assets/style.css delete mode 100644 docs/assets/widgets.png delete mode 100644 docs/assets/widgets@2x.png delete mode 100644 docs/classes/AttributeReference.html delete mode 100644 docs/classes/BasicLogger.html delete mode 100644 docs/classes/BigSegmentStoreStatusProviderImpl.html delete mode 100644 docs/classes/Context.html delete mode 100644 docs/classes/ContextFilter.html delete mode 100644 docs/classes/DateValidator.html delete mode 100644 docs/classes/FactoryOrInstance.html delete mode 100644 docs/classes/Function.html delete mode 100644 docs/classes/LDClientImpl.html delete mode 100644 docs/classes/NumberWithMinimum.html delete mode 100644 docs/classes/SafeLogger.html delete mode 100644 docs/classes/StringMatchingRegex.html delete mode 100644 docs/classes/Type.html delete mode 100644 docs/classes/TypeArray.html delete mode 100644 docs/classes/TypeValidators.html delete mode 100644 docs/index.html delete mode 100644 docs/interfaces/BasicLoggerOptions.html delete mode 100644 docs/interfaces/LDBigSegmentsOptions.html delete mode 100644 docs/interfaces/LDClient.html delete mode 100644 docs/interfaces/LDContextCommon.html delete mode 100644 docs/interfaces/LDContextMeta.html delete mode 100644 docs/interfaces/LDEvaluationDetail.html delete mode 100644 docs/interfaces/LDEvaluationReason.html delete mode 100644 docs/interfaces/LDFlagSet.html delete mode 100644 docs/interfaces/LDFlagsState.html delete mode 100644 docs/interfaces/LDFlagsStateOptions.html delete mode 100644 docs/interfaces/LDLogger.html delete mode 100644 docs/interfaces/LDMultiKindContext.html delete mode 100644 docs/interfaces/LDOptions.html delete mode 100644 docs/interfaces/LDProxyOptions.html delete mode 100644 docs/interfaces/LDSingleKindContext.html delete mode 100644 docs/interfaces/LDTLSOptions.html delete mode 100644 docs/interfaces/LDUser.html delete mode 100644 docs/interfaces/TypeValidator.html delete mode 100644 docs/interfaces/integrations.FileDataSourceOptions.html delete mode 100644 docs/interfaces/integrations.TestData.html delete mode 100644 docs/interfaces/integrations.TestDataFlagBuilder.html delete mode 100644 docs/interfaces/integrations.TestDataRuleBuilder.html delete mode 100644 docs/interfaces/interfaces.BigSegmentStore.html delete mode 100644 docs/interfaces/interfaces.BigSegmentStoreMembership.html delete mode 100644 docs/interfaces/interfaces.BigSegmentStoreMetadata.html delete mode 100644 docs/interfaces/interfaces.BigSegmentStoreStatus.html delete mode 100644 docs/interfaces/interfaces.BigSegmentStoreStatusProvider.html delete mode 100644 docs/interfaces/interfaces.DataCollection.html delete mode 100644 docs/interfaces/interfaces.DataKind.html delete mode 100644 docs/interfaces/interfaces.ItemDescriptor.html delete mode 100644 docs/interfaces/interfaces.PersistentDataStore.html delete mode 100644 docs/interfaces/interfaces.PersistentStoreDataKind.html delete mode 100644 docs/interfaces/interfaces.SerializedItemDescriptor.html delete mode 100644 docs/interfaces/interfaces.VersionedData.html delete mode 100644 docs/interfaces/platform.Crypto.html delete mode 100644 docs/interfaces/platform.EventSource.html delete mode 100644 docs/interfaces/platform.EventSourceInitDict.html delete mode 100644 docs/interfaces/platform.Filesystem.html delete mode 100644 docs/interfaces/platform.Hasher.html delete mode 100644 docs/interfaces/platform.Headers.html delete mode 100644 docs/interfaces/platform.Hmac.html delete mode 100644 docs/interfaces/platform.Info.html delete mode 100644 docs/interfaces/platform.Options.html delete mode 100644 docs/interfaces/platform.Platform.html delete mode 100644 docs/interfaces/platform.PlatformData.html delete mode 100644 docs/interfaces/platform.Requests.html delete mode 100644 docs/interfaces/platform.Response.html delete mode 100644 docs/interfaces/platform.SdkData.html delete mode 100644 docs/interfaces/subsystems.LDFeatureStore.html delete mode 100644 docs/interfaces/subsystems.LDFeatureStoreDataStorage.html delete mode 100644 docs/interfaces/subsystems.LDFeatureStoreItem.html delete mode 100644 docs/interfaces/subsystems.LDFeatureStoreKindData.html delete mode 100644 docs/interfaces/subsystems.LDKeyedFeatureStoreItem.html delete mode 100644 docs/modules.html delete mode 100644 docs/modules/integrations.html delete mode 100644 docs/modules/interfaces.html delete mode 100644 docs/modules/platform.html delete mode 100644 docs/modules/subsystems.html delete mode 100644 docs/types/LDContext.html delete mode 100644 docs/types/LDFlagValue.html delete mode 100644 docs/types/LDLogLevel.html delete mode 100644 docs/types/interfaces.FullDataSet.html delete mode 100644 docs/types/interfaces.KeyedItems.html diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e2ac6616ad..0000000000 --- a/docs/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/assets/highlight.css b/docs/assets/highlight.css deleted file mode 100644 index dbcebae847..0000000000 --- a/docs/assets/highlight.css +++ /dev/null @@ -1,85 +0,0 @@ -:root { - --light-hl-0: #0000FF; - --dark-hl-0: #569CD6; - --light-hl-1: #000000; - --dark-hl-1: #D4D4D4; - --light-hl-2: #0070C1; - --dark-hl-2: #4FC1FF; - --light-hl-3: #795E26; - --dark-hl-3: #DCDCAA; - --light-hl-4: #001080; - --dark-hl-4: #9CDCFE; - --light-hl-5: #A31515; - --dark-hl-5: #CE9178; - --light-hl-6: #008000; - --dark-hl-6: #6A9955; - --light-hl-7: #CD3131; - --dark-hl-7: #F44747; - --light-hl-8: #AF00DB; - --dark-hl-8: #C586C0; - --light-code-background: #FFFFFF; - --dark-code-background: #1E1E1E; -} - -@media (prefers-color-scheme: light) { :root { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --hl-3: var(--light-hl-3); - --hl-4: var(--light-hl-4); - --hl-5: var(--light-hl-5); - --hl-6: var(--light-hl-6); - --hl-7: var(--light-hl-7); - --hl-8: var(--light-hl-8); - --code-background: var(--light-code-background); -} } - -@media (prefers-color-scheme: dark) { :root { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --hl-3: var(--dark-hl-3); - --hl-4: var(--dark-hl-4); - --hl-5: var(--dark-hl-5); - --hl-6: var(--dark-hl-6); - --hl-7: var(--dark-hl-7); - --hl-8: var(--dark-hl-8); - --code-background: var(--dark-code-background); -} } - -:root[data-theme='light'] { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --hl-3: var(--light-hl-3); - --hl-4: var(--light-hl-4); - --hl-5: var(--light-hl-5); - --hl-6: var(--light-hl-6); - --hl-7: var(--light-hl-7); - --hl-8: var(--light-hl-8); - --code-background: var(--light-code-background); -} - -:root[data-theme='dark'] { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --hl-3: var(--dark-hl-3); - --hl-4: var(--dark-hl-4); - --hl-5: var(--dark-hl-5); - --hl-6: var(--dark-hl-6); - --hl-7: var(--dark-hl-7); - --hl-8: var(--dark-hl-8); - --code-background: var(--dark-code-background); -} - -.hl-0 { color: var(--hl-0); } -.hl-1 { color: var(--hl-1); } -.hl-2 { color: var(--hl-2); } -.hl-3 { color: var(--hl-3); } -.hl-4 { color: var(--hl-4); } -.hl-5 { color: var(--hl-5); } -.hl-6 { color: var(--hl-6); } -.hl-7 { color: var(--hl-7); } -.hl-8 { color: var(--hl-8); } -pre, code { background: var(--code-background); } diff --git a/docs/assets/main.js b/docs/assets/main.js deleted file mode 100644 index c815b33495..0000000000 --- a/docs/assets/main.js +++ /dev/null @@ -1,54 +0,0 @@ -"use strict"; -(()=>{var Qe=Object.create;var ae=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var Ce=Object.getOwnPropertyNames;var Oe=Object.getPrototypeOf,Re=Object.prototype.hasOwnProperty;var _e=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var Me=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Ce(e))!Re.call(t,i)&&i!==n&&ae(t,i,{get:()=>e[i],enumerable:!(r=Pe(e,i))||r.enumerable});return t};var De=(t,e,n)=>(n=t!=null?Qe(Oe(t)):{},Me(e||!t||!t.__esModule?ae(n,"default",{value:t,enumerable:!0}):n,t));var de=_e((ce,he)=>{(function(){var t=function(e){var n=new t.Builder;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),n.searchPipeline.add(t.stemmer),e.call(n,n),n.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(n){e.console&&console.warn&&console.warn(n)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var n=Object.create(null),r=Object.keys(e),i=0;i0){var h=t.utils.clone(n)||{};h.position=[a,l],h.index=s.length,s.push(new t.Token(r.slice(a,o),h))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. -`,e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(r){var i=t.Pipeline.registeredFunctions[r];if(i)n.add(i);else throw new Error("Cannot load unregistered function: "+r)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(n){t.Pipeline.warnIfFunctionNotRegistered(n),this._stack.push(n)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");r=r+1,this._stack.splice(r,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var r=this._stack.indexOf(e);if(r==-1)throw new Error("Cannot find existingFn");this._stack.splice(r,0,n)},t.Pipeline.prototype.remove=function(e){var n=this._stack.indexOf(e);n!=-1&&this._stack.splice(n,1)},t.Pipeline.prototype.run=function(e){for(var n=this._stack.length,r=0;r1&&(oe&&(r=s),o!=e);)i=r-n,s=n+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ou?h+=2:a==u&&(n+=r[l+1]*i[h+1],l+=2,h+=2);return n},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),n=1,r=0;n0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var h=s.str.charAt(0),m=s.str.charAt(1),v;m in s.node.edges?v=s.node.edges[m]:(v=new t.TokenSet,s.node.edges[m]=v),s.str.length==1&&(v.final=!0),i.push({node:v,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return r},t.TokenSet.fromString=function(e){for(var n=new t.TokenSet,r=n,i=0,s=e.length;i=e;n--){var r=this.uncheckedNodes[n],i=r.child.toString();i in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[i]:(r.child._str=i,this.minimizedNodes[i]=r.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(n){var r=new t.QueryParser(e,n);r.parse()})},t.Index.prototype.query=function(e){for(var n=new t.Query(this.fields),r=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,n){var r=e[this._ref],i=Object.keys(this._fields);this._documents[r]=n||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,n;do e=this.next(),n=e.charCodeAt(0);while(n>47&&n<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var n=e.next();if(n==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(n.charCodeAt(0)==92){e.escapeCharacter();continue}if(n==":")return t.QueryLexer.lexField;if(n=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(n=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(n=="+"&&e.width()===1||n=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(n.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,n){this.lexer=new t.QueryLexer(e),this.query=n,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var n=e.peekLexeme();if(n!=null)switch(n.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+n.type;throw n.str.length>=1&&(r+=" with value '"+n.str+"'"),new t.QueryParseError(r,n.start,n.end)}},t.QueryParser.parsePresence=function(e){var n=e.consumeLexeme();if(n!=null){switch(n.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator'"+n.str+"'";throw new t.QueryParseError(r,n.start,n.end)}var i=e.peekLexeme();if(i==null){var r="expecting term or field, found nothing";throw new t.QueryParseError(r,n.start,n.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var r="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(r,i.start,i.end)}}},t.QueryParser.parseField=function(e){var n=e.consumeLexeme();if(n!=null){if(e.query.allFields.indexOf(n.str)==-1){var r=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+n.str+"', possible fields: "+r;throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.fields=[n.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,n.start,n.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var n=e.consumeLexeme();if(n!=null){e.currentClause.term=n.str.toLowerCase(),n.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(r==null){e.nextClause();return}switch(r.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+r.type+"'";throw new t.QueryParseError(i,r.start,r.end)}}},t.QueryParser.parseEditDistance=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="edit distance must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.editDistance=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var n=e.consumeLexeme();if(n!=null){var r=parseInt(n.str,10);if(isNaN(r)){var i="boost must be numeric";throw new t.QueryParseError(i,n.start,n.end)}e.currentClause.boost=r;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,n){typeof define=="function"&&define.amd?define(n):typeof ce=="object"?he.exports=n():e.lunr=n()}(this,function(){return t})})()});var le=[];function j(t,e){le.push({selector:e,constructor:t})}var Y=class{constructor(){this.createComponents(document.body)}createComponents(e){le.forEach(n=>{e.querySelectorAll(n.selector).forEach(r=>{r.dataset.hasInstance||(new n.constructor({el:r}),r.dataset.hasInstance=String(!0))})})}};var k=class{constructor(e){this.el=e.el}};var J=class{constructor(){this.listeners={}}addEventListener(e,n){e in this.listeners||(this.listeners[e]=[]),this.listeners[e].push(n)}removeEventListener(e,n){if(!(e in this.listeners))return;let r=this.listeners[e];for(let i=0,s=r.length;i{let n=Date.now();return(...r)=>{n+e-Date.now()<0&&(t(...r),n=Date.now())}};var re=class extends J{constructor(){super();this.scrollTop=0;this.lastY=0;this.width=0;this.height=0;this.showToolbar=!0;this.toolbar=document.querySelector(".tsd-page-toolbar"),this.navigation=document.querySelector(".col-menu"),window.addEventListener("scroll",ne(()=>this.onScroll(),10)),window.addEventListener("resize",ne(()=>this.onResize(),10)),this.searchInput=document.querySelector("#tsd-search input"),this.searchInput&&this.searchInput.addEventListener("focus",()=>{this.hideShowToolbar()}),this.onResize(),this.onScroll()}triggerResize(){let n=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(n)}onResize(){this.width=window.innerWidth||0,this.height=window.innerHeight||0;let n=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(n)}onScroll(){this.scrollTop=window.scrollY||0;let n=new CustomEvent("scroll",{detail:{scrollTop:this.scrollTop}});this.dispatchEvent(n),this.hideShowToolbar()}hideShowToolbar(){let n=this.showToolbar;this.showToolbar=this.lastY>=this.scrollTop||this.scrollTop<=0||!!this.searchInput&&this.searchInput===document.activeElement,n!==this.showToolbar&&(this.toolbar.classList.toggle("tsd-page-toolbar--hide"),this.navigation?.classList.toggle("col-menu--hide")),this.lastY=this.scrollTop}},R=re;R.instance=new re;var X=class extends k{constructor(n){super(n);this.anchors=[];this.index=-1;R.instance.addEventListener("resize",()=>this.onResize()),R.instance.addEventListener("scroll",r=>this.onScroll(r)),this.createAnchors()}createAnchors(){let n=window.location.href;n.indexOf("#")!=-1&&(n=n.substring(0,n.indexOf("#"))),this.el.querySelectorAll("a").forEach(r=>{let i=r.href;if(i.indexOf("#")==-1||i.substring(0,n.length)!=n)return;let s=i.substring(i.indexOf("#")+1),o=document.querySelector("a.tsd-anchor[name="+s+"]"),a=r.parentNode;!o||!a||this.anchors.push({link:a,anchor:o,position:0})}),this.onResize()}onResize(){let n;for(let i=0,s=this.anchors.length;ii.position-s.position);let r=new CustomEvent("scroll",{detail:{scrollTop:R.instance.scrollTop}});this.onScroll(r)}onScroll(n){let r=n.detail.scrollTop+5,i=this.anchors,s=i.length-1,o=this.index;for(;o>-1&&i[o].position>r;)o-=1;for(;o-1&&this.anchors[this.index].link.classList.remove("focus"),this.index=o,this.index>-1&&this.anchors[this.index].link.classList.add("focus"))}};var ue=(t,e=100)=>{let n;return(...r)=>{clearTimeout(n),n=setTimeout(()=>t(r),e)}};var me=De(de());function ve(){let t=document.getElementById("tsd-search");if(!t)return;let e=document.getElementById("search-script");t.classList.add("loading"),e&&(e.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),e.addEventListener("load",()=>{t.classList.remove("loading"),t.classList.add("ready")}),window.searchData&&t.classList.remove("loading"));let n=document.querySelector("#tsd-search input"),r=document.querySelector("#tsd-search .results");if(!n||!r)throw new Error("The input field or the result list wrapper was not found");let i=!1;r.addEventListener("mousedown",()=>i=!0),r.addEventListener("mouseup",()=>{i=!1,t.classList.remove("has-focus")}),n.addEventListener("focus",()=>t.classList.add("has-focus")),n.addEventListener("blur",()=>{i||(i=!1,t.classList.remove("has-focus"))});let s={base:t.dataset.base+"/"};Fe(t,r,n,s)}function Fe(t,e,n,r){n.addEventListener("input",ue(()=>{Ae(t,e,n,r)},200));let i=!1;n.addEventListener("keydown",s=>{i=!0,s.key=="Enter"?Ve(e,n):s.key=="Escape"?n.blur():s.key=="ArrowUp"?fe(e,-1):s.key==="ArrowDown"?fe(e,1):i=!1}),n.addEventListener("keypress",s=>{i&&s.preventDefault()}),document.body.addEventListener("keydown",s=>{s.altKey||s.ctrlKey||s.metaKey||!n.matches(":focus")&&s.key==="/"&&(n.focus(),s.preventDefault())})}function He(t,e){t.index||window.searchData&&(e.classList.remove("loading"),e.classList.add("ready"),t.data=window.searchData,t.index=me.Index.load(window.searchData.index))}function Ae(t,e,n,r){if(He(r,t),!r.index||!r.data)return;e.textContent="";let i=n.value.trim(),s=i?r.index.search(`*${i}*`):[];for(let o=0;oa.score-o.score);for(let o=0,a=Math.min(10,s.length);o${pe(u.parent,i)}.${l}`);let h=document.createElement("li");h.classList.value=u.classes??"";let m=document.createElement("a");m.href=r.base+u.url,m.innerHTML=l,h.append(m),e.appendChild(h)}}function fe(t,e){let n=t.querySelector(".current");if(!n)n=t.querySelector(e==1?"li:first-child":"li:last-child"),n&&n.classList.add("current");else{let r=n;if(e===1)do r=r.nextElementSibling??void 0;while(r instanceof HTMLElement&&r.offsetParent==null);else do r=r.previousElementSibling??void 0;while(r instanceof HTMLElement&&r.offsetParent==null);r&&(n.classList.remove("current"),r.classList.add("current"))}}function Ve(t,e){let n=t.querySelector(".current");if(n||(n=t.querySelector("li:first-child")),n){let r=n.querySelector("a");r&&(window.location.href=r.href),e.blur()}}function pe(t,e){if(e==="")return t;let n=t.toLocaleLowerCase(),r=e.toLocaleLowerCase(),i=[],s=0,o=n.indexOf(r);for(;o!=-1;)i.push(ie(t.substring(s,o)),`${ie(t.substring(o,o+r.length))}`),s=o+r.length,o=n.indexOf(r,s);return i.push(ie(t.substring(s))),i.join("")}var Ne={"&":"&","<":"<",">":">","'":"'",'"':"""};function ie(t){return t.replace(/[&<>"'"]/g,e=>Ne[e])}var F="mousedown",ye="mousemove",B="mouseup",Z={x:0,y:0},ge=!1,se=!1,je=!1,H=!1,xe=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(xe?"is-mobile":"not-mobile");xe&&"ontouchstart"in document.documentElement&&(je=!0,F="touchstart",ye="touchmove",B="touchend");document.addEventListener(F,t=>{se=!0,H=!1;let e=F=="touchstart"?t.targetTouches[0]:t;Z.y=e.pageY||0,Z.x=e.pageX||0});document.addEventListener(ye,t=>{if(!!se&&!H){let e=F=="touchstart"?t.targetTouches[0]:t,n=Z.x-(e.pageX||0),r=Z.y-(e.pageY||0);H=Math.sqrt(n*n+r*r)>10}});document.addEventListener(B,()=>{se=!1});document.addEventListener("click",t=>{ge&&(t.preventDefault(),t.stopImmediatePropagation(),ge=!1)});var K=class extends k{constructor(n){super(n);this.className=this.el.dataset.toggle||"",this.el.addEventListener(B,r=>this.onPointerUp(r)),this.el.addEventListener("click",r=>r.preventDefault()),document.addEventListener(F,r=>this.onDocumentPointerDown(r)),document.addEventListener(B,r=>this.onDocumentPointerUp(r))}setActive(n){if(this.active==n)return;this.active=n,document.documentElement.classList.toggle("has-"+this.className,n),this.el.classList.toggle("active",n);let r=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(r),setTimeout(()=>document.documentElement.classList.remove(r),500)}onPointerUp(n){H||(this.setActive(!0),n.preventDefault())}onDocumentPointerDown(n){if(this.active){if(n.target.closest(".col-menu, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(n){if(!H&&this.active&&n.target.closest(".col-menu")){let r=n.target.closest("a");if(r){let i=window.location.href;i.indexOf("#")!=-1&&(i=i.substring(0,i.indexOf("#"))),r.href.substring(0,i.length)==i&&setTimeout(()=>this.setActive(!1),250)}}}};var oe;try{oe=localStorage}catch{oe={getItem(){return null},setItem(){}}}var Q=oe;var Le=document.head.appendChild(document.createElement("style"));Le.dataset.for="filters";var ee=class extends k{constructor(n){super(n);this.key=`filter-${this.el.name}`,this.value=this.el.checked,this.el.addEventListener("change",()=>{this.setLocalStorage(this.el.checked)}),this.setLocalStorage(this.fromLocalStorage()),Le.innerHTML+=`html:not(.${this.key}) .tsd-is-${this.el.name} { display: none; } -`}fromLocalStorage(){let n=Q.getItem(this.key);return n?n==="true":this.el.checked}setLocalStorage(n){Q.setItem(this.key,n.toString()),this.value=n,this.handleValueChange()}handleValueChange(){this.el.checked=this.value,document.documentElement.classList.toggle(this.key,this.value),document.querySelectorAll(".tsd-index-section").forEach(n=>{n.style.display="block";let r=Array.from(n.querySelectorAll(".tsd-index-link")).every(i=>i.offsetParent==null);n.style.display=r?"none":"block"})}};var te=class extends k{constructor(n){super(n);this.calculateHeights(),this.summary=this.el.querySelector(".tsd-accordion-summary"),this.icon=this.summary.querySelector("svg"),this.key=`tsd-accordion-${this.summary.textContent.replace(/\s+/g,"-").toLowerCase()}`,this.setLocalStorage(this.fromLocalStorage(),!0),this.summary.addEventListener("click",r=>this.toggleVisibility(r)),this.icon.style.transform=this.getIconRotation()}getIconRotation(n=this.el.open){return`rotate(${n?0:-90}deg)`}calculateHeights(){let n=this.el.open,{position:r,left:i}=this.el.style;this.el.style.position="fixed",this.el.style.left="-9999px",this.el.open=!0,this.expandedHeight=this.el.offsetHeight+"px",this.el.open=!1,this.collapsedHeight=this.el.offsetHeight+"px",this.el.open=n,this.el.style.height=n?this.expandedHeight:this.collapsedHeight,this.el.style.position=r,this.el.style.left=i}toggleVisibility(n){n.preventDefault(),this.el.style.overflow="hidden",this.el.open?this.collapse():this.expand()}expand(n=!0){this.el.open=!0,this.animate(this.collapsedHeight,this.expandedHeight,{opening:!0,duration:n?300:0})}collapse(n=!0){this.animate(this.expandedHeight,this.collapsedHeight,{opening:!1,duration:n?300:0})}animate(n,r,{opening:i,duration:s=300}){if(this.animation)return;let o={duration:s,easing:"ease"};this.animation=this.el.animate({height:[n,r]},o),this.icon.animate({transform:[this.icon.style.transform||this.getIconRotation(!i),this.getIconRotation(i)]},o).addEventListener("finish",()=>{this.icon.style.transform=this.getIconRotation(i)}),this.animation.addEventListener("finish",()=>this.animationEnd(i))}animationEnd(n){this.el.open=n,this.animation=void 0,this.el.style.height="auto",this.el.style.overflow="visible",this.setLocalStorage(n)}fromLocalStorage(){let n=Q.getItem(this.key);return n?n==="true":this.el.open}setLocalStorage(n,r=!1){this.fromLocalStorage()===n&&!r||(Q.setItem(this.key,n.toString()),this.el.open=n,this.handleValueChange(r))}handleValueChange(n=!1){this.fromLocalStorage()===this.el.open&&!n||(this.fromLocalStorage()?this.expand(!1):this.collapse(!1))}};function be(t){let e=Q.getItem("tsd-theme")||"os";t.value=e,Ee(e),t.addEventListener("change",()=>{Q.setItem("tsd-theme",t.value),Ee(t.value)})}function Ee(t){document.documentElement.dataset.theme=t}ve();j(X,".menu-highlight");j(K,"a[data-toggle]");j(te,".tsd-index-accordion");j(ee,".tsd-filter-item input[type=checkbox]");var Se=document.getElementById("theme");Se&&be(Se);var Be=new Y;Object.defineProperty(window,"app",{value:Be});})(); -/*! - * lunr.Builder - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Index - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Pipeline - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Set - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.TokenSet - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Vector - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.stemmer - * Copyright (C) 2020 Oliver Nightingale - * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt - */ -/*! - * lunr.stopWordFilter - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.tokenizer - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.trimmer - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.utils - * Copyright (C) 2020 Oliver Nightingale - */ -/** - * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 - * Copyright (C) 2020 Oliver Nightingale - * @license MIT - */ diff --git a/docs/assets/search.js b/docs/assets/search.js deleted file mode 100644 index 34fa9bc946..0000000000 --- a/docs/assets/search.js +++ /dev/null @@ -1 +0,0 @@ -window.searchData = JSON.parse("{\"kinds\":{\"4\":\"Namespace\",\"128\":\"Class\",\"256\":\"Interface\",\"512\":\"Constructor\",\"1024\":\"Property\",\"2048\":\"Method\",\"65536\":\"Type literal\",\"262144\":\"Accessor\",\"4194304\":\"Type alias\"},\"rows\":[{\"kind\":4,\"name\":\"platform\",\"url\":\"modules/platform.html\",\"classes\":\"tsd-kind-namespace\"},{\"kind\":256,\"name\":\"Hasher\",\"url\":\"interfaces/platform.Hasher.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"update\",\"url\":\"interfaces/platform.Hasher.html#update\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Hasher\"},{\"kind\":2048,\"name\":\"digest\",\"url\":\"interfaces/platform.Hasher.html#digest\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Hasher\"},{\"kind\":256,\"name\":\"Hmac\",\"url\":\"interfaces/platform.Hmac.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"update\",\"url\":\"interfaces/platform.Hmac.html#update\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Hmac\"},{\"kind\":2048,\"name\":\"digest\",\"url\":\"interfaces/platform.Hmac.html#digest\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Hmac\"},{\"kind\":256,\"name\":\"Crypto\",\"url\":\"interfaces/platform.Crypto.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"createHash\",\"url\":\"interfaces/platform.Crypto.html#createHash\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Crypto\"},{\"kind\":2048,\"name\":\"createHmac\",\"url\":\"interfaces/platform.Crypto.html#createHmac\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Crypto\"},{\"kind\":256,\"name\":\"Filesystem\",\"url\":\"interfaces/platform.Filesystem.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"getFileTimestamp\",\"url\":\"interfaces/platform.Filesystem.html#getFileTimestamp\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Filesystem\"},{\"kind\":2048,\"name\":\"readFile\",\"url\":\"interfaces/platform.Filesystem.html#readFile\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Filesystem\"},{\"kind\":2048,\"name\":\"watch\",\"url\":\"interfaces/platform.Filesystem.html#watch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Filesystem\"},{\"kind\":256,\"name\":\"PlatformData\",\"url\":\"interfaces/platform.PlatformData.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"os\",\"url\":\"interfaces/platform.PlatformData.html#os\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.PlatformData\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/platform.PlatformData.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"platform.PlatformData\"},{\"kind\":1024,\"name\":\"arch\",\"url\":\"interfaces/platform.PlatformData.html#__type.arch\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"platform.PlatformData.__type\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/platform.PlatformData.html#__type.name-1\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"platform.PlatformData.__type\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/platform.PlatformData.html#__type.version\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"platform.PlatformData.__type\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/platform.PlatformData.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.PlatformData\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/platform.PlatformData.html#version-1\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.PlatformData\"},{\"kind\":1024,\"name\":\"additional\",\"url\":\"interfaces/platform.PlatformData.html#additional\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.PlatformData\"},{\"kind\":256,\"name\":\"SdkData\",\"url\":\"interfaces/platform.SdkData.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/platform.SdkData.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.SdkData\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/platform.SdkData.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.SdkData\"},{\"kind\":1024,\"name\":\"wrapperName\",\"url\":\"interfaces/platform.SdkData.html#wrapperName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.SdkData\"},{\"kind\":1024,\"name\":\"wrapperVersion\",\"url\":\"interfaces/platform.SdkData.html#wrapperVersion\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.SdkData\"},{\"kind\":256,\"name\":\"Info\",\"url\":\"interfaces/platform.Info.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"platformData\",\"url\":\"interfaces/platform.Info.html#platformData\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Info\"},{\"kind\":2048,\"name\":\"sdkData\",\"url\":\"interfaces/platform.Info.html#sdkData\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Info\"},{\"kind\":256,\"name\":\"Platform\",\"url\":\"interfaces/platform.Platform.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"info\",\"url\":\"interfaces/platform.Platform.html#info\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Platform\"},{\"kind\":1024,\"name\":\"fileSystem\",\"url\":\"interfaces/platform.Platform.html#fileSystem\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Platform\"},{\"kind\":1024,\"name\":\"crypto\",\"url\":\"interfaces/platform.Platform.html#crypto\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Platform\"},{\"kind\":1024,\"name\":\"requests\",\"url\":\"interfaces/platform.Platform.html#requests\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Platform\"},{\"kind\":256,\"name\":\"Headers\",\"url\":\"interfaces/platform.Headers.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"get\",\"url\":\"interfaces/platform.Headers.html#get\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Headers\"},{\"kind\":2048,\"name\":\"keys\",\"url\":\"interfaces/platform.Headers.html#keys\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Headers\"},{\"kind\":2048,\"name\":\"values\",\"url\":\"interfaces/platform.Headers.html#values\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Headers\"},{\"kind\":2048,\"name\":\"entries\",\"url\":\"interfaces/platform.Headers.html#entries\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Headers\"},{\"kind\":2048,\"name\":\"has\",\"url\":\"interfaces/platform.Headers.html#has\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Headers\"},{\"kind\":256,\"name\":\"Response\",\"url\":\"interfaces/platform.Response.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"headers\",\"url\":\"interfaces/platform.Response.html#headers\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Response\"},{\"kind\":1024,\"name\":\"status\",\"url\":\"interfaces/platform.Response.html#status\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Response\"},{\"kind\":2048,\"name\":\"text\",\"url\":\"interfaces/platform.Response.html#text\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Response\"},{\"kind\":2048,\"name\":\"json\",\"url\":\"interfaces/platform.Response.html#json\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Response\"},{\"kind\":256,\"name\":\"Options\",\"url\":\"interfaces/platform.Options.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"headers\",\"url\":\"interfaces/platform.Options.html#headers\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Options\"},{\"kind\":1024,\"name\":\"method\",\"url\":\"interfaces/platform.Options.html#method\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Options\"},{\"kind\":1024,\"name\":\"body\",\"url\":\"interfaces/platform.Options.html#body\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Options\"},{\"kind\":1024,\"name\":\"timeout\",\"url\":\"interfaces/platform.Options.html#timeout\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.Options\"},{\"kind\":256,\"name\":\"Requests\",\"url\":\"interfaces/platform.Requests.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":2048,\"name\":\"fetch\",\"url\":\"interfaces/platform.Requests.html#fetch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Requests\"},{\"kind\":2048,\"name\":\"createEventSource\",\"url\":\"interfaces/platform.Requests.html#createEventSource\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.Requests\"},{\"kind\":256,\"name\":\"EventSource\",\"url\":\"interfaces/platform.EventSource.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"onclose\",\"url\":\"interfaces/platform.EventSource.html#onclose\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":1024,\"name\":\"onerror\",\"url\":\"interfaces/platform.EventSource.html#onerror\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":1024,\"name\":\"onopen\",\"url\":\"interfaces/platform.EventSource.html#onopen\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":1024,\"name\":\"onretrying\",\"url\":\"interfaces/platform.EventSource.html#onretrying\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":2048,\"name\":\"addEventListener\",\"url\":\"interfaces/platform.EventSource.html#addEventListener\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"interfaces/platform.EventSource.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"platform.EventSource\"},{\"kind\":256,\"name\":\"EventSourceInitDict\",\"url\":\"interfaces/platform.EventSourceInitDict.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"platform\"},{\"kind\":1024,\"name\":\"errorFilter\",\"url\":\"interfaces/platform.EventSourceInitDict.html#errorFilter\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/platform.EventSourceInitDict.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":1024,\"name\":\"headers\",\"url\":\"interfaces/platform.EventSourceInitDict.html#headers\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/platform.EventSourceInitDict.html#__type-2\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":1024,\"name\":\"initialRetryDelayMillis\",\"url\":\"interfaces/platform.EventSourceInitDict.html#initialRetryDelayMillis\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":1024,\"name\":\"readTimeoutMillis\",\"url\":\"interfaces/platform.EventSourceInitDict.html#readTimeoutMillis\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":1024,\"name\":\"retryResetIntervalMillis\",\"url\":\"interfaces/platform.EventSourceInitDict.html#retryResetIntervalMillis\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"platform.EventSourceInitDict\"},{\"kind\":128,\"name\":\"LDClientImpl\",\"url\":\"classes/LDClientImpl.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/LDClientImpl.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":1024,\"name\":\"platform\",\"url\":\"classes/LDClientImpl.html#platform\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"LDClientImpl\"},{\"kind\":1024,\"name\":\"initState\",\"url\":\"classes/LDClientImpl.html#initState\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"LDClientImpl\"},{\"kind\":1024,\"name\":\"bigSegmentStoreStatusProvider\",\"url\":\"classes/LDClientImpl.html#bigSegmentStoreStatusProvider\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"initialized\",\"url\":\"classes/LDClientImpl.html#initialized\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"waitForInitialization\",\"url\":\"classes/LDClientImpl.html#waitForInitialization\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"variation\",\"url\":\"classes/LDClientImpl.html#variation\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"variationDetail\",\"url\":\"classes/LDClientImpl.html#variationDetail\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"allFlagsState\",\"url\":\"classes/LDClientImpl.html#allFlagsState\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"secureModeHash\",\"url\":\"classes/LDClientImpl.html#secureModeHash\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"classes/LDClientImpl.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"isOffline\",\"url\":\"classes/LDClientImpl.html#isOffline\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"track\",\"url\":\"classes/LDClientImpl.html#track\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"identify\",\"url\":\"classes/LDClientImpl.html#identify\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"flush\",\"url\":\"classes/LDClientImpl.html#flush\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":2048,\"name\":\"on\",\"url\":\"classes/LDClientImpl.html#on\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"LDClientImpl\"},{\"kind\":128,\"name\":\"BigSegmentStoreStatusProviderImpl\",\"url\":\"classes/BigSegmentStoreStatusProviderImpl.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/BigSegmentStoreStatusProviderImpl.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"BigSegmentStoreStatusProviderImpl\"},{\"kind\":2048,\"name\":\"getStatus\",\"url\":\"classes/BigSegmentStoreStatusProviderImpl.html#getStatus\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BigSegmentStoreStatusProviderImpl\"},{\"kind\":2048,\"name\":\"requireStatus\",\"url\":\"classes/BigSegmentStoreStatusProviderImpl.html#requireStatus\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BigSegmentStoreStatusProviderImpl\"},{\"kind\":2048,\"name\":\"dispatch\",\"url\":\"classes/BigSegmentStoreStatusProviderImpl.html#dispatch\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BigSegmentStoreStatusProviderImpl\"},{\"kind\":4,\"name\":\"integrations\",\"url\":\"modules/integrations.html\",\"classes\":\"tsd-kind-namespace\"},{\"kind\":256,\"name\":\"FileDataSourceOptions\",\"url\":\"interfaces/integrations.FileDataSourceOptions.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"integrations\"},{\"kind\":1024,\"name\":\"paths\",\"url\":\"interfaces/integrations.FileDataSourceOptions.html#paths\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"integrations.FileDataSourceOptions\"},{\"kind\":1024,\"name\":\"autoUpdate\",\"url\":\"interfaces/integrations.FileDataSourceOptions.html#autoUpdate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"integrations.FileDataSourceOptions\"},{\"kind\":1024,\"name\":\"logger\",\"url\":\"interfaces/integrations.FileDataSourceOptions.html#logger\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"integrations.FileDataSourceOptions\"},{\"kind\":256,\"name\":\"TestData\",\"url\":\"interfaces/integrations.TestData.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"integrations\"},{\"kind\":2048,\"name\":\"flag\",\"url\":\"interfaces/integrations.TestData.html#flag\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestData\"},{\"kind\":2048,\"name\":\"update\",\"url\":\"interfaces/integrations.TestData.html#update\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestData\"},{\"kind\":2048,\"name\":\"usePreconfiguredFlag\",\"url\":\"interfaces/integrations.TestData.html#usePreconfiguredFlag\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestData\"},{\"kind\":2048,\"name\":\"usePreconfiguredSegment\",\"url\":\"interfaces/integrations.TestData.html#usePreconfiguredSegment\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestData\"},{\"kind\":256,\"name\":\"TestDataFlagBuilder\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"integrations\"},{\"kind\":2048,\"name\":\"booleanFlag\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#booleanFlag\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"variations\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#variations\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"on\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#on\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"fallthroughVariation\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#fallthroughVariation\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"offVariation\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#offVariation\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"variationForAll\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#variationForAll\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"valueForAll\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#valueForAll\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"variationForUser\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#variationForUser\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"variationForContext\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#variationForContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"clearRules\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#clearRules\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"clearAlltargets\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#clearAlltargets\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"ifMatch\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#ifMatch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":2048,\"name\":\"ifNotMatch\",\"url\":\"interfaces/integrations.TestDataFlagBuilder.html#ifNotMatch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataFlagBuilder\"},{\"kind\":256,\"name\":\"TestDataRuleBuilder\",\"url\":\"interfaces/integrations.TestDataRuleBuilder.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"integrations\"},{\"kind\":2048,\"name\":\"andMatch\",\"url\":\"interfaces/integrations.TestDataRuleBuilder.html#andMatch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataRuleBuilder\"},{\"kind\":2048,\"name\":\"andNotMatch\",\"url\":\"interfaces/integrations.TestDataRuleBuilder.html#andNotMatch\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataRuleBuilder\"},{\"kind\":2048,\"name\":\"thenReturn\",\"url\":\"interfaces/integrations.TestDataRuleBuilder.html#thenReturn\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"integrations.TestDataRuleBuilder\"},{\"kind\":4,\"name\":\"interfaces\",\"url\":\"modules/interfaces.html\",\"classes\":\"tsd-kind-namespace\"},{\"kind\":256,\"name\":\"BigSegmentStore\",\"url\":\"interfaces/interfaces.BigSegmentStore.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":2048,\"name\":\"getMetadata\",\"url\":\"interfaces/interfaces.BigSegmentStore.html#getMetadata\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStore\"},{\"kind\":2048,\"name\":\"getUserMembership\",\"url\":\"interfaces/interfaces.BigSegmentStore.html#getUserMembership\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStore\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"interfaces/interfaces.BigSegmentStore.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStore\"},{\"kind\":256,\"name\":\"BigSegmentStoreMembership\",\"url\":\"interfaces/interfaces.BigSegmentStoreMembership.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":256,\"name\":\"BigSegmentStoreMetadata\",\"url\":\"interfaces/interfaces.BigSegmentStoreMetadata.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"lastUpToDate\",\"url\":\"interfaces/interfaces.BigSegmentStoreMetadata.html#lastUpToDate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStoreMetadata\"},{\"kind\":256,\"name\":\"BigSegmentStoreStatus\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatus.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"available\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatus.html#available\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStoreStatus\"},{\"kind\":1024,\"name\":\"stale\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatus.html#stale\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStoreStatus\"},{\"kind\":256,\"name\":\"DataCollection\",\"url\":\"interfaces/interfaces.DataCollection.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"kind\",\"url\":\"interfaces/interfaces.DataCollection.html#kind\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.DataCollection\"},{\"kind\":1024,\"name\":\"items\",\"url\":\"interfaces/interfaces.DataCollection.html#items\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.DataCollection\"},{\"kind\":256,\"name\":\"DataKind\",\"url\":\"interfaces/interfaces.DataKind.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"namespace\",\"url\":\"interfaces/interfaces.DataKind.html#namespace\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.DataKind\"},{\"kind\":4194304,\"name\":\"FullDataSet\",\"url\":\"types/interfaces.FullDataSet.html\",\"classes\":\"tsd-kind-type-alias tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":4194304,\"name\":\"KeyedItems\",\"url\":\"types/interfaces.KeyedItems.html\",\"classes\":\"tsd-kind-type-alias tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":256,\"name\":\"VersionedData\",\"url\":\"interfaces/interfaces.VersionedData.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/interfaces.VersionedData.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.VersionedData\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/interfaces.VersionedData.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.VersionedData\"},{\"kind\":1024,\"name\":\"deleted\",\"url\":\"interfaces/interfaces.VersionedData.html#deleted\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.VersionedData\"},{\"kind\":256,\"name\":\"BigSegmentStoreStatusProvider\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatusProvider.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":2048,\"name\":\"getStatus\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatusProvider.html#getStatus\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStoreStatusProvider\"},{\"kind\":2048,\"name\":\"requireStatus\",\"url\":\"interfaces/interfaces.BigSegmentStoreStatusProvider.html#requireStatus\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.BigSegmentStoreStatusProvider\"},{\"kind\":256,\"name\":\"ItemDescriptor\",\"url\":\"interfaces/interfaces.ItemDescriptor.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/interfaces.ItemDescriptor.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.ItemDescriptor\"},{\"kind\":1024,\"name\":\"item\",\"url\":\"interfaces/interfaces.ItemDescriptor.html#item\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.ItemDescriptor\"},{\"kind\":256,\"name\":\"PersistentDataStore\",\"url\":\"interfaces/interfaces.PersistentDataStore.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":2048,\"name\":\"get\",\"url\":\"interfaces/interfaces.PersistentDataStore.html#get\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentDataStore\"},{\"kind\":2048,\"name\":\"getAll\",\"url\":\"interfaces/interfaces.PersistentDataStore.html#getAll\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentDataStore\"},{\"kind\":2048,\"name\":\"upsert\",\"url\":\"interfaces/interfaces.PersistentDataStore.html#upsert\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentDataStore\"},{\"kind\":2048,\"name\":\"initialized\",\"url\":\"interfaces/interfaces.PersistentDataStore.html#initialized\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentDataStore\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"interfaces/interfaces.PersistentDataStore.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentDataStore\"},{\"kind\":256,\"name\":\"PersistentStoreDataKind\",\"url\":\"interfaces/interfaces.PersistentStoreDataKind.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"namespace\",\"url\":\"interfaces/interfaces.PersistentStoreDataKind.html#namespace\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentStoreDataKind\"},{\"kind\":2048,\"name\":\"deserialize\",\"url\":\"interfaces/interfaces.PersistentStoreDataKind.html#deserialize\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"interfaces.PersistentStoreDataKind\"},{\"kind\":256,\"name\":\"SerializedItemDescriptor\",\"url\":\"interfaces/interfaces.SerializedItemDescriptor.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"interfaces\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/interfaces.SerializedItemDescriptor.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.SerializedItemDescriptor\"},{\"kind\":1024,\"name\":\"deleted\",\"url\":\"interfaces/interfaces.SerializedItemDescriptor.html#deleted\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.SerializedItemDescriptor\"},{\"kind\":1024,\"name\":\"serializedItem\",\"url\":\"interfaces/interfaces.SerializedItemDescriptor.html#serializedItem\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"interfaces.SerializedItemDescriptor\"},{\"kind\":4,\"name\":\"subsystems\",\"url\":\"modules/subsystems.html\",\"classes\":\"tsd-kind-namespace\"},{\"kind\":256,\"name\":\"LDFeatureStoreItem\",\"url\":\"interfaces/subsystems.LDFeatureStoreItem.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"subsystems\"},{\"kind\":1024,\"name\":\"deleted\",\"url\":\"interfaces/subsystems.LDFeatureStoreItem.html#deleted\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStoreItem\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/subsystems.LDFeatureStoreItem.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStoreItem\"},{\"kind\":256,\"name\":\"LDKeyedFeatureStoreItem\",\"url\":\"interfaces/subsystems.LDKeyedFeatureStoreItem.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"subsystems\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/subsystems.LDKeyedFeatureStoreItem.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"subsystems.LDKeyedFeatureStoreItem\"},{\"kind\":1024,\"name\":\"deleted\",\"url\":\"interfaces/subsystems.LDKeyedFeatureStoreItem.html#deleted\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"subsystems.LDKeyedFeatureStoreItem\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/subsystems.LDKeyedFeatureStoreItem.html#version\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"subsystems.LDKeyedFeatureStoreItem\"},{\"kind\":256,\"name\":\"LDFeatureStoreKindData\",\"url\":\"interfaces/subsystems.LDFeatureStoreKindData.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"subsystems\"},{\"kind\":256,\"name\":\"LDFeatureStoreDataStorage\",\"url\":\"interfaces/subsystems.LDFeatureStoreDataStorage.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"subsystems\"},{\"kind\":256,\"name\":\"LDFeatureStore\",\"url\":\"interfaces/subsystems.LDFeatureStore.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-namespace\",\"parent\":\"subsystems\"},{\"kind\":2048,\"name\":\"get\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#get\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"all\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#all\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"init\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#init\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"delete\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#delete\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"upsert\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#upsert\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"initialized\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#initialized\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"interfaces/subsystems.LDFeatureStore.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"subsystems.LDFeatureStore\"},{\"kind\":256,\"name\":\"LDEvaluationDetail\",\"url\":\"interfaces/LDEvaluationDetail.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"value\",\"url\":\"interfaces/LDEvaluationDetail.html#value\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationDetail\"},{\"kind\":1024,\"name\":\"variationIndex\",\"url\":\"interfaces/LDEvaluationDetail.html#variationIndex\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationDetail\"},{\"kind\":1024,\"name\":\"reason\",\"url\":\"interfaces/LDEvaluationDetail.html#reason\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationDetail\"},{\"kind\":256,\"name\":\"LDEvaluationReason\",\"url\":\"interfaces/LDEvaluationReason.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"kind\",\"url\":\"interfaces/LDEvaluationReason.html#kind\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"errorKind\",\"url\":\"interfaces/LDEvaluationReason.html#errorKind\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"ruleIndex\",\"url\":\"interfaces/LDEvaluationReason.html#ruleIndex\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"ruleId\",\"url\":\"interfaces/LDEvaluationReason.html#ruleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"prerequisiteKey\",\"url\":\"interfaces/LDEvaluationReason.html#prerequisiteKey\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"inExperiment\",\"url\":\"interfaces/LDEvaluationReason.html#inExperiment\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":1024,\"name\":\"bigSegmentsStatus\",\"url\":\"interfaces/LDEvaluationReason.html#bigSegmentsStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDEvaluationReason\"},{\"kind\":256,\"name\":\"LDFlagSet\",\"url\":\"interfaces/LDFlagSet.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":256,\"name\":\"LDFlagsState\",\"url\":\"interfaces/LDFlagsState.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"valid\",\"url\":\"interfaces/LDFlagsState.html#valid\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDFlagsState\"},{\"kind\":2048,\"name\":\"getFlagValue\",\"url\":\"interfaces/LDFlagsState.html#getFlagValue\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDFlagsState\"},{\"kind\":2048,\"name\":\"getFlagReason\",\"url\":\"interfaces/LDFlagsState.html#getFlagReason\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDFlagsState\"},{\"kind\":2048,\"name\":\"allValues\",\"url\":\"interfaces/LDFlagsState.html#allValues\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDFlagsState\"},{\"kind\":2048,\"name\":\"toJSON\",\"url\":\"interfaces/LDFlagsState.html#toJSON\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDFlagsState\"},{\"kind\":256,\"name\":\"LDFlagsStateOptions\",\"url\":\"interfaces/LDFlagsStateOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"clientSideOnly\",\"url\":\"interfaces/LDFlagsStateOptions.html#clientSideOnly\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDFlagsStateOptions\"},{\"kind\":1024,\"name\":\"withReasons\",\"url\":\"interfaces/LDFlagsStateOptions.html#withReasons\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDFlagsStateOptions\"},{\"kind\":1024,\"name\":\"detailsOnlyForTrackedFlags\",\"url\":\"interfaces/LDFlagsStateOptions.html#detailsOnlyForTrackedFlags\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDFlagsStateOptions\"},{\"kind\":4194304,\"name\":\"LDFlagValue\",\"url\":\"types/LDFlagValue.html\",\"classes\":\"tsd-kind-type-alias\"},{\"kind\":256,\"name\":\"LDBigSegmentsOptions\",\"url\":\"interfaces/LDBigSegmentsOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"userCacheSize\",\"url\":\"interfaces/LDBigSegmentsOptions.html#userCacheSize\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDBigSegmentsOptions\"},{\"kind\":1024,\"name\":\"userCacheTime\",\"url\":\"interfaces/LDBigSegmentsOptions.html#userCacheTime\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDBigSegmentsOptions\"},{\"kind\":1024,\"name\":\"statusPollInterval\",\"url\":\"interfaces/LDBigSegmentsOptions.html#statusPollInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDBigSegmentsOptions\"},{\"kind\":1024,\"name\":\"staleAfter\",\"url\":\"interfaces/LDBigSegmentsOptions.html#staleAfter\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDBigSegmentsOptions\"},{\"kind\":256,\"name\":\"LDOptions\",\"url\":\"interfaces/LDOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"baseUri\",\"url\":\"interfaces/LDOptions.html#baseUri\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"streamUri\",\"url\":\"interfaces/LDOptions.html#streamUri\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"eventsUri\",\"url\":\"interfaces/LDOptions.html#eventsUri\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"timeout\",\"url\":\"interfaces/LDOptions.html#timeout\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"capacity\",\"url\":\"interfaces/LDOptions.html#capacity\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"logger\",\"url\":\"interfaces/LDOptions.html#logger\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"featureStore\",\"url\":\"interfaces/LDOptions.html#featureStore\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"bigSegments\",\"url\":\"interfaces/LDOptions.html#bigSegments\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"updateProcessor\",\"url\":\"interfaces/LDOptions.html#updateProcessor\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"flushInterval\",\"url\":\"interfaces/LDOptions.html#flushInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"pollInterval\",\"url\":\"interfaces/LDOptions.html#pollInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"proxyOptions\",\"url\":\"interfaces/LDOptions.html#proxyOptions\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"offline\",\"url\":\"interfaces/LDOptions.html#offline\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"stream\",\"url\":\"interfaces/LDOptions.html#stream\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"streamInitialReconnectDelay\",\"url\":\"interfaces/LDOptions.html#streamInitialReconnectDelay\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"useLdd\",\"url\":\"interfaces/LDOptions.html#useLdd\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"sendEvents\",\"url\":\"interfaces/LDOptions.html#sendEvents\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"allAttributesPrivate\",\"url\":\"interfaces/LDOptions.html#allAttributesPrivate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"privateAttributes\",\"url\":\"interfaces/LDOptions.html#privateAttributes\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"contextKeysCapacity\",\"url\":\"interfaces/LDOptions.html#contextKeysCapacity\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"contextKeysFlushInterval\",\"url\":\"interfaces/LDOptions.html#contextKeysFlushInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"tlsParams\",\"url\":\"interfaces/LDOptions.html#tlsParams\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"diagnosticOptOut\",\"url\":\"interfaces/LDOptions.html#diagnosticOptOut\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"diagnosticRecordingInterval\",\"url\":\"interfaces/LDOptions.html#diagnosticRecordingInterval\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"wrapperName\",\"url\":\"interfaces/LDOptions.html#wrapperName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"wrapperVersion\",\"url\":\"interfaces/LDOptions.html#wrapperVersion\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"application\",\"url\":\"interfaces/LDOptions.html#application\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/LDOptions.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"LDOptions\"},{\"kind\":1024,\"name\":\"id\",\"url\":\"interfaces/LDOptions.html#__type.id\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"LDOptions.__type\"},{\"kind\":1024,\"name\":\"version\",\"url\":\"interfaces/LDOptions.html#__type.version\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"LDOptions.__type\"},{\"kind\":256,\"name\":\"LDProxyOptions\",\"url\":\"interfaces/LDProxyOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"host\",\"url\":\"interfaces/LDProxyOptions.html#host\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDProxyOptions\"},{\"kind\":1024,\"name\":\"port\",\"url\":\"interfaces/LDProxyOptions.html#port\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDProxyOptions\"},{\"kind\":1024,\"name\":\"scheme\",\"url\":\"interfaces/LDProxyOptions.html#scheme\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDProxyOptions\"},{\"kind\":1024,\"name\":\"auth\",\"url\":\"interfaces/LDProxyOptions.html#auth\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDProxyOptions\"},{\"kind\":256,\"name\":\"LDTLSOptions\",\"url\":\"interfaces/LDTLSOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"ca\",\"url\":\"interfaces/LDTLSOptions.html#ca\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"cert\",\"url\":\"interfaces/LDTLSOptions.html#cert\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"checkServerIdentity\",\"url\":\"interfaces/LDTLSOptions.html#checkServerIdentity\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/LDTLSOptions.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"ciphers\",\"url\":\"interfaces/LDTLSOptions.html#ciphers\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"pfx\",\"url\":\"interfaces/LDTLSOptions.html#pfx\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/LDTLSOptions.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"passphrase\",\"url\":\"interfaces/LDTLSOptions.html#passphrase\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"rejectUnauthorized\",\"url\":\"interfaces/LDTLSOptions.html#rejectUnauthorized\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"secureProtocol\",\"url\":\"interfaces/LDTLSOptions.html#secureProtocol\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":1024,\"name\":\"servername\",\"url\":\"interfaces/LDTLSOptions.html#servername\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDTLSOptions\"},{\"kind\":256,\"name\":\"LDClient\",\"url\":\"interfaces/LDClient.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":2048,\"name\":\"initialized\",\"url\":\"interfaces/LDClient.html#initialized\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"waitForInitialization\",\"url\":\"interfaces/LDClient.html#waitForInitialization\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"variation\",\"url\":\"interfaces/LDClient.html#variation\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"variationDetail\",\"url\":\"interfaces/LDClient.html#variationDetail\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"allFlagsState\",\"url\":\"interfaces/LDClient.html#allFlagsState\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"secureModeHash\",\"url\":\"interfaces/LDClient.html#secureModeHash\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"close\",\"url\":\"interfaces/LDClient.html#close\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"isOffline\",\"url\":\"interfaces/LDClient.html#isOffline\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"track\",\"url\":\"interfaces/LDClient.html#track\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"identify\",\"url\":\"interfaces/LDClient.html#identify\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"flush\",\"url\":\"interfaces/LDClient.html#flush\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":2048,\"name\":\"on\",\"url\":\"interfaces/LDClient.html#on\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDClient\"},{\"kind\":128,\"name\":\"AttributeReference\",\"url\":\"classes/AttributeReference.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/AttributeReference.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":1024,\"name\":\"isValid\",\"url\":\"classes/AttributeReference.html#isValid\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":1024,\"name\":\"redactionName\",\"url\":\"classes/AttributeReference.html#redactionName\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":1024,\"name\":\"components\",\"url\":\"classes/AttributeReference.html#components\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"AttributeReference\"},{\"kind\":2048,\"name\":\"get\",\"url\":\"classes/AttributeReference.html#get\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":2048,\"name\":\"getComponent\",\"url\":\"classes/AttributeReference.html#getComponent\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":262144,\"name\":\"depth\",\"url\":\"classes/AttributeReference.html#depth\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":262144,\"name\":\"isKind\",\"url\":\"classes/AttributeReference.html#isKind\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":2048,\"name\":\"compare\",\"url\":\"classes/AttributeReference.html#compare\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"AttributeReference\"},{\"kind\":128,\"name\":\"Context\",\"url\":\"classes/Context.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":1024,\"name\":\"userKind\",\"url\":\"classes/Context.html#userKind\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"getValueFromContext\",\"url\":\"classes/Context.html#getValueFromContext\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"fromMultiKindContext\",\"url\":\"classes/Context.html#fromMultiKindContext\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"fromSingleKindContext\",\"url\":\"classes/Context.html#fromSingleKindContext\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"fromLegacyUser\",\"url\":\"classes/Context.html#fromLegacyUser\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"fromLDContext\",\"url\":\"classes/Context.html#fromLDContext\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/Context.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"context\",\"url\":\"classes/Context.html#context\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"isMulti\",\"url\":\"classes/Context.html#isMulti\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"isUser\",\"url\":\"classes/Context.html#isUser\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"wasLegacy\",\"url\":\"classes/Context.html#wasLegacy\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"contexts\",\"url\":\"classes/Context.html#contexts\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"privateAttributeReferences\",\"url\":\"classes/Context.html#privateAttributeReferences\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"kind\",\"url\":\"classes/Context.html#kind\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":1024,\"name\":\"contextForKind\",\"url\":\"classes/Context.html#contextForKind\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"valueForKind\",\"url\":\"classes/Context.html#valueForKind\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"key\",\"url\":\"classes/Context.html#key\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"secondary\",\"url\":\"classes/Context.html#secondary\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":262144,\"name\":\"isMultiKind\",\"url\":\"classes/Context.html#isMultiKind\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":262144,\"name\":\"canonicalKey\",\"url\":\"classes/Context.html#canonicalKey\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":262144,\"name\":\"kinds\",\"url\":\"classes/Context.html#kinds\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":262144,\"name\":\"kindsAndKeys\",\"url\":\"classes/Context.html#kindsAndKeys\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"privateAttributes\",\"url\":\"classes/Context.html#privateAttributes\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":2048,\"name\":\"getContexts\",\"url\":\"classes/Context.html#getContexts\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":262144,\"name\":\"legacy\",\"url\":\"classes/Context.html#legacy\",\"classes\":\"tsd-kind-accessor tsd-parent-kind-class\",\"parent\":\"Context\"},{\"kind\":128,\"name\":\"ContextFilter\",\"url\":\"classes/ContextFilter.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/ContextFilter.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"ContextFilter\"},{\"kind\":1024,\"name\":\"allAttributesPrivate\",\"url\":\"classes/ContextFilter.html#allAttributesPrivate\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"ContextFilter\"},{\"kind\":1024,\"name\":\"privateAttributes\",\"url\":\"classes/ContextFilter.html#privateAttributes\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"ContextFilter\"},{\"kind\":2048,\"name\":\"filter\",\"url\":\"classes/ContextFilter.html#filter\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"ContextFilter\"},{\"kind\":1024,\"name\":\"getAttributesToFilter\",\"url\":\"classes/ContextFilter.html#getAttributesToFilter\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"ContextFilter\"},{\"kind\":1024,\"name\":\"filterSingleKind\",\"url\":\"classes/ContextFilter.html#filterSingleKind\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"ContextFilter\"},{\"kind\":256,\"name\":\"LDContextCommon\",\"url\":\"interfaces/LDContextCommon.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/LDContextCommon.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextCommon\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/LDContextCommon.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextCommon\"},{\"kind\":1024,\"name\":\"_meta\",\"url\":\"interfaces/LDContextCommon.html#_meta\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextCommon\"},{\"kind\":1024,\"name\":\"anonymous\",\"url\":\"interfaces/LDContextCommon.html#anonymous\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextCommon\"},{\"kind\":256,\"name\":\"LDContextMeta\",\"url\":\"interfaces/LDContextMeta.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"secondary\",\"url\":\"interfaces/LDContextMeta.html#secondary\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextMeta\"},{\"kind\":1024,\"name\":\"privateAttributes\",\"url\":\"interfaces/LDContextMeta.html#privateAttributes\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDContextMeta\"},{\"kind\":256,\"name\":\"LDMultiKindContext\",\"url\":\"interfaces/LDMultiKindContext.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"kind\",\"url\":\"interfaces/LDMultiKindContext.html#kind\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDMultiKindContext\"},{\"kind\":256,\"name\":\"LDSingleKindContext\",\"url\":\"interfaces/LDSingleKindContext.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"kind\",\"url\":\"interfaces/LDSingleKindContext.html#kind\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDSingleKindContext\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/LDSingleKindContext.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"LDSingleKindContext\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/LDSingleKindContext.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"LDSingleKindContext\"},{\"kind\":1024,\"name\":\"_meta\",\"url\":\"interfaces/LDSingleKindContext.html#_meta\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"LDSingleKindContext\"},{\"kind\":1024,\"name\":\"anonymous\",\"url\":\"interfaces/LDSingleKindContext.html#anonymous\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface tsd-is-inherited\",\"parent\":\"LDSingleKindContext\"},{\"kind\":256,\"name\":\"LDUser\",\"url\":\"interfaces/LDUser.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"key\",\"url\":\"interfaces/LDUser.html#key\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"secondary\",\"url\":\"interfaces/LDUser.html#secondary\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/LDUser.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"firstName\",\"url\":\"interfaces/LDUser.html#firstName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"lastName\",\"url\":\"interfaces/LDUser.html#lastName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"email\",\"url\":\"interfaces/LDUser.html#email\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"avatar\",\"url\":\"interfaces/LDUser.html#avatar\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"ip\",\"url\":\"interfaces/LDUser.html#ip\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"country\",\"url\":\"interfaces/LDUser.html#country\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"anonymous\",\"url\":\"interfaces/LDUser.html#anonymous\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"custom\",\"url\":\"interfaces/LDUser.html#custom\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/LDUser.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":1024,\"name\":\"privateAttributeNames\",\"url\":\"interfaces/LDUser.html#privateAttributeNames\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"LDUser\"},{\"kind\":4194304,\"name\":\"LDContext\",\"url\":\"types/LDContext.html\",\"classes\":\"tsd-kind-type-alias\"},{\"kind\":256,\"name\":\"BasicLoggerOptions\",\"url\":\"interfaces/BasicLoggerOptions.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":1024,\"name\":\"level\",\"url\":\"interfaces/BasicLoggerOptions.html#level\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"BasicLoggerOptions\"},{\"kind\":1024,\"name\":\"destination\",\"url\":\"interfaces/BasicLoggerOptions.html#destination\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"BasicLoggerOptions\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/BasicLoggerOptions.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"BasicLoggerOptions\"},{\"kind\":1024,\"name\":\"formatter\",\"url\":\"interfaces/BasicLoggerOptions.html#formatter\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"BasicLoggerOptions\"},{\"kind\":65536,\"name\":\"__type\",\"url\":\"interfaces/BasicLoggerOptions.html#__type-2\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-interface\",\"parent\":\"BasicLoggerOptions\"},{\"kind\":256,\"name\":\"LDLogger\",\"url\":\"interfaces/LDLogger.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":2048,\"name\":\"error\",\"url\":\"interfaces/LDLogger.html#error\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDLogger\"},{\"kind\":2048,\"name\":\"warn\",\"url\":\"interfaces/LDLogger.html#warn\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDLogger\"},{\"kind\":2048,\"name\":\"info\",\"url\":\"interfaces/LDLogger.html#info\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDLogger\"},{\"kind\":2048,\"name\":\"debug\",\"url\":\"interfaces/LDLogger.html#debug\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"LDLogger\"},{\"kind\":4194304,\"name\":\"LDLogLevel\",\"url\":\"types/LDLogLevel.html\",\"classes\":\"tsd-kind-type-alias\"},{\"kind\":256,\"name\":\"TypeValidator\",\"url\":\"interfaces/TypeValidator.html\",\"classes\":\"tsd-kind-interface\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"interfaces/TypeValidator.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"TypeValidator\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"interfaces/TypeValidator.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-interface\",\"parent\":\"TypeValidator\"},{\"kind\":128,\"name\":\"FactoryOrInstance\",\"url\":\"classes/FactoryOrInstance.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/FactoryOrInstance.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"FactoryOrInstance\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/FactoryOrInstance.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"FactoryOrInstance\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/FactoryOrInstance.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"FactoryOrInstance\"},{\"kind\":128,\"name\":\"Type\",\"url\":\"classes/Type.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/Type.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"Type\"},{\"kind\":1024,\"name\":\"typeName\",\"url\":\"classes/Type.html#typeName\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"Type\"},{\"kind\":1024,\"name\":\"typeOf\",\"url\":\"classes/Type.html#typeOf\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-protected\",\"parent\":\"Type\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/Type.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Type\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/Type.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Type\"},{\"kind\":128,\"name\":\"TypeArray\",\"url\":\"classes/TypeArray.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/TypeArray.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"TypeArray\"},{\"kind\":1024,\"name\":\"typeName\",\"url\":\"classes/TypeArray.html#typeName\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"TypeArray\"},{\"kind\":1024,\"name\":\"typeOf\",\"url\":\"classes/TypeArray.html#typeOf\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-protected\",\"parent\":\"TypeArray\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/TypeArray.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"TypeArray\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/TypeArray.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"TypeArray\"},{\"kind\":128,\"name\":\"NumberWithMinimum\",\"url\":\"classes/NumberWithMinimum.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/NumberWithMinimum.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"NumberWithMinimum\"},{\"kind\":1024,\"name\":\"min\",\"url\":\"classes/NumberWithMinimum.html#min\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"NumberWithMinimum\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/NumberWithMinimum.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"NumberWithMinimum\"},{\"kind\":1024,\"name\":\"typeOf\",\"url\":\"classes/NumberWithMinimum.html#typeOf\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-protected tsd-is-inherited\",\"parent\":\"NumberWithMinimum\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/NumberWithMinimum.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-is-inherited\",\"parent\":\"NumberWithMinimum\"},{\"kind\":128,\"name\":\"StringMatchingRegex\",\"url\":\"classes/StringMatchingRegex.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/StringMatchingRegex.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"StringMatchingRegex\"},{\"kind\":1024,\"name\":\"expression\",\"url\":\"classes/StringMatchingRegex.html#expression\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"StringMatchingRegex\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/StringMatchingRegex.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"StringMatchingRegex\"},{\"kind\":1024,\"name\":\"typeOf\",\"url\":\"classes/StringMatchingRegex.html#typeOf\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-protected tsd-is-inherited\",\"parent\":\"StringMatchingRegex\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/StringMatchingRegex.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class tsd-is-inherited\",\"parent\":\"StringMatchingRegex\"},{\"kind\":128,\"name\":\"Function\",\"url\":\"classes/Function.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/Function.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"Function\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/Function.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Function\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/Function.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"Function\"},{\"kind\":128,\"name\":\"DateValidator\",\"url\":\"classes/DateValidator.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/DateValidator.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"DateValidator\"},{\"kind\":2048,\"name\":\"is\",\"url\":\"classes/DateValidator.html#is\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"DateValidator\"},{\"kind\":2048,\"name\":\"getType\",\"url\":\"classes/DateValidator.html#getType\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"DateValidator\"},{\"kind\":128,\"name\":\"TypeValidators\",\"url\":\"classes/TypeValidators.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":1024,\"name\":\"String\",\"url\":\"classes/TypeValidators.html#String\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"Number\",\"url\":\"classes/TypeValidators.html#Number\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"ObjectOrFactory\",\"url\":\"classes/TypeValidators.html#ObjectOrFactory\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"Object\",\"url\":\"classes/TypeValidators.html#Object\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"StringArray\",\"url\":\"classes/TypeValidators.html#StringArray\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"Boolean\",\"url\":\"classes/TypeValidators.html#Boolean\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"Function\",\"url\":\"classes/TypeValidators.html#Function\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":2048,\"name\":\"numberWithMin\",\"url\":\"classes/TypeValidators.html#numberWithMin\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":2048,\"name\":\"stringMatchingRegex\",\"url\":\"classes/TypeValidators.html#stringMatchingRegex\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":1024,\"name\":\"Date\",\"url\":\"classes/TypeValidators.html#Date\",\"classes\":\"tsd-kind-property tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/TypeValidators.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"TypeValidators\"},{\"kind\":128,\"name\":\"BasicLogger\",\"url\":\"classes/BasicLogger.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/BasicLogger.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"logLevel\",\"url\":\"classes/BasicLogger.html#logLevel\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"destination\",\"url\":\"classes/BasicLogger.html#destination\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"formatter\",\"url\":\"classes/BasicLogger.html#formatter\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"tryFormat\",\"url\":\"classes/BasicLogger.html#tryFormat\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"tryWrite\",\"url\":\"classes/BasicLogger.html#tryWrite\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":1024,\"name\":\"log\",\"url\":\"classes/BasicLogger.html#log\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"BasicLogger\"},{\"kind\":2048,\"name\":\"error\",\"url\":\"classes/BasicLogger.html#error\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BasicLogger\"},{\"kind\":2048,\"name\":\"warn\",\"url\":\"classes/BasicLogger.html#warn\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BasicLogger\"},{\"kind\":2048,\"name\":\"info\",\"url\":\"classes/BasicLogger.html#info\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BasicLogger\"},{\"kind\":2048,\"name\":\"debug\",\"url\":\"classes/BasicLogger.html#debug\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"BasicLogger\"},{\"kind\":128,\"name\":\"SafeLogger\",\"url\":\"classes/SafeLogger.html\",\"classes\":\"tsd-kind-class\"},{\"kind\":512,\"name\":\"constructor\",\"url\":\"classes/SafeLogger.html#constructor\",\"classes\":\"tsd-kind-constructor tsd-parent-kind-class\",\"parent\":\"SafeLogger\"},{\"kind\":1024,\"name\":\"logger\",\"url\":\"classes/SafeLogger.html#logger\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"SafeLogger\"},{\"kind\":1024,\"name\":\"fallback\",\"url\":\"classes/SafeLogger.html#fallback\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"SafeLogger\"},{\"kind\":1024,\"name\":\"log\",\"url\":\"classes/SafeLogger.html#log\",\"classes\":\"tsd-kind-property tsd-parent-kind-class tsd-is-private\",\"parent\":\"SafeLogger\"},{\"kind\":2048,\"name\":\"error\",\"url\":\"classes/SafeLogger.html#error\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SafeLogger\"},{\"kind\":2048,\"name\":\"warn\",\"url\":\"classes/SafeLogger.html#warn\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SafeLogger\"},{\"kind\":2048,\"name\":\"info\",\"url\":\"classes/SafeLogger.html#info\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SafeLogger\"},{\"kind\":2048,\"name\":\"debug\",\"url\":\"classes/SafeLogger.html#debug\",\"classes\":\"tsd-kind-method tsd-parent-kind-class\",\"parent\":\"SafeLogger\"}],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"name\",\"comment\"],\"fieldVectors\":[[\"name/0\",[0,48.064]],[\"comment/0\",[]],[\"name/1\",[1,56.537]],[\"comment/1\",[]],[\"name/2\",[2,48.064]],[\"comment/2\",[]],[\"name/3\",[3,51.428]],[\"comment/3\",[]],[\"name/4\",[4,56.537]],[\"comment/4\",[]],[\"name/5\",[2,48.064]],[\"comment/5\",[]],[\"name/6\",[3,51.428]],[\"comment/6\",[]],[\"name/7\",[5,51.428]],[\"comment/7\",[]],[\"name/8\",[6,56.537]],[\"comment/8\",[]],[\"name/9\",[7,56.537]],[\"comment/9\",[]],[\"name/10\",[8,51.428]],[\"comment/10\",[]],[\"name/11\",[9,56.537]],[\"comment/11\",[]],[\"name/12\",[10,56.537]],[\"comment/12\",[]],[\"name/13\",[11,56.537]],[\"comment/13\",[]],[\"name/14\",[12,51.428]],[\"comment/14\",[]],[\"name/15\",[13,56.537]],[\"comment/15\",[]],[\"name/16\",[14,39.191]],[\"comment/16\",[]],[\"name/17\",[15,56.537]],[\"comment/17\",[]],[\"name/18\",[16,41.873]],[\"comment/18\",[]],[\"name/19\",[17,38.078]],[\"comment/19\",[]],[\"name/20\",[16,41.873]],[\"comment/20\",[]],[\"name/21\",[17,38.078]],[\"comment/21\",[]],[\"name/22\",[18,56.537]],[\"comment/22\",[]],[\"name/23\",[19,51.428]],[\"comment/23\",[]],[\"name/24\",[16,41.873]],[\"comment/24\",[]],[\"name/25\",[17,38.078]],[\"comment/25\",[]],[\"name/26\",[20,51.428]],[\"comment/26\",[]],[\"name/27\",[21,51.428]],[\"comment/27\",[]],[\"name/28\",[22,43.544]],[\"comment/28\",[]],[\"name/29\",[12,51.428]],[\"comment/29\",[]],[\"name/30\",[19,51.428]],[\"comment/30\",[]],[\"name/31\",[0,48.064]],[\"comment/31\",[]],[\"name/32\",[22,43.544]],[\"comment/32\",[]],[\"name/33\",[8,51.428]],[\"comment/33\",[]],[\"name/34\",[5,51.428]],[\"comment/34\",[]],[\"name/35\",[23,51.428]],[\"comment/35\",[]],[\"name/36\",[24,45.55]],[\"comment/36\",[]],[\"name/37\",[25,45.55]],[\"comment/37\",[]],[\"name/38\",[26,56.537]],[\"comment/38\",[]],[\"name/39\",[27,56.537]],[\"comment/39\",[]],[\"name/40\",[28,56.537]],[\"comment/40\",[]],[\"name/41\",[29,56.537]],[\"comment/41\",[]],[\"name/42\",[30,56.537]],[\"comment/42\",[]],[\"name/43\",[24,45.55]],[\"comment/43\",[]],[\"name/44\",[31,56.537]],[\"comment/44\",[]],[\"name/45\",[32,56.537]],[\"comment/45\",[]],[\"name/46\",[33,56.537]],[\"comment/46\",[]],[\"name/47\",[34,56.537]],[\"comment/47\",[]],[\"name/48\",[24,45.55]],[\"comment/48\",[]],[\"name/49\",[35,56.537]],[\"comment/49\",[]],[\"name/50\",[36,56.537]],[\"comment/50\",[]],[\"name/51\",[37,51.428]],[\"comment/51\",[]],[\"name/52\",[23,51.428]],[\"comment/52\",[]],[\"name/53\",[38,56.537]],[\"comment/53\",[]],[\"name/54\",[39,56.537]],[\"comment/54\",[]],[\"name/55\",[40,56.537]],[\"comment/55\",[]],[\"name/56\",[41,56.537]],[\"comment/56\",[]],[\"name/57\",[42,56.537]],[\"comment/57\",[]],[\"name/58\",[43,56.537]],[\"comment/58\",[]],[\"name/59\",[44,56.537]],[\"comment/59\",[]],[\"name/60\",[45,56.537]],[\"comment/60\",[]],[\"name/61\",[46,41.873]],[\"comment/61\",[]],[\"name/62\",[47,56.537]],[\"comment/62\",[]],[\"name/63\",[48,56.537]],[\"comment/63\",[]],[\"name/64\",[14,39.191]],[\"comment/64\",[]],[\"name/65\",[24,45.55]],[\"comment/65\",[]],[\"name/66\",[14,39.191]],[\"comment/66\",[]],[\"name/67\",[49,56.537]],[\"comment/67\",[]],[\"name/68\",[50,56.537]],[\"comment/68\",[]],[\"name/69\",[51,56.537]],[\"comment/69\",[]],[\"name/70\",[52,56.537]],[\"comment/70\",[]],[\"name/71\",[53,33.183]],[\"comment/71\",[]],[\"name/72\",[0,48.064]],[\"comment/72\",[]],[\"name/73\",[54,56.537]],[\"comment/73\",[]],[\"name/74\",[55,51.428]],[\"comment/74\",[]],[\"name/75\",[56,45.55]],[\"comment/75\",[]],[\"name/76\",[57,51.428]],[\"comment/76\",[]],[\"name/77\",[58,51.428]],[\"comment/77\",[]],[\"name/78\",[59,51.428]],[\"comment/78\",[]],[\"name/79\",[60,51.428]],[\"comment/79\",[]],[\"name/80\",[61,51.428]],[\"comment/80\",[]],[\"name/81\",[46,41.873]],[\"comment/81\",[]],[\"name/82\",[62,51.428]],[\"comment/82\",[]],[\"name/83\",[63,51.428]],[\"comment/83\",[]],[\"name/84\",[64,51.428]],[\"comment/84\",[]],[\"name/85\",[65,51.428]],[\"comment/85\",[]],[\"name/86\",[66,48.064]],[\"comment/86\",[]],[\"name/87\",[67,56.537]],[\"comment/87\",[]],[\"name/88\",[53,33.183]],[\"comment/88\",[]],[\"name/89\",[68,51.428]],[\"comment/89\",[]],[\"name/90\",[69,51.428]],[\"comment/90\",[]],[\"name/91\",[70,56.537]],[\"comment/91\",[]],[\"name/92\",[71,56.537]],[\"comment/92\",[]],[\"name/93\",[72,56.537]],[\"comment/93\",[]],[\"name/94\",[73,56.537]],[\"comment/94\",[]],[\"name/95\",[74,56.537]],[\"comment/95\",[]],[\"name/96\",[75,48.064]],[\"comment/96\",[]],[\"name/97\",[76,56.537]],[\"comment/97\",[]],[\"name/98\",[77,56.537]],[\"comment/98\",[]],[\"name/99\",[2,48.064]],[\"comment/99\",[]],[\"name/100\",[78,56.537]],[\"comment/100\",[]],[\"name/101\",[79,56.537]],[\"comment/101\",[]],[\"name/102\",[80,56.537]],[\"comment/102\",[]],[\"name/103\",[81,56.537]],[\"comment/103\",[]],[\"name/104\",[82,56.537]],[\"comment/104\",[]],[\"name/105\",[66,48.064]],[\"comment/105\",[]],[\"name/106\",[83,56.537]],[\"comment/106\",[]],[\"name/107\",[84,56.537]],[\"comment/107\",[]],[\"name/108\",[85,56.537]],[\"comment/108\",[]],[\"name/109\",[86,56.537]],[\"comment/109\",[]],[\"name/110\",[87,56.537]],[\"comment/110\",[]],[\"name/111\",[88,56.537]],[\"comment/111\",[]],[\"name/112\",[89,56.537]],[\"comment/112\",[]],[\"name/113\",[90,56.537]],[\"comment/113\",[]],[\"name/114\",[91,56.537]],[\"comment/114\",[]],[\"name/115\",[92,56.537]],[\"comment/115\",[]],[\"name/116\",[93,56.537]],[\"comment/116\",[]],[\"name/117\",[94,56.537]],[\"comment/117\",[]],[\"name/118\",[95,56.537]],[\"comment/118\",[]],[\"name/119\",[96,56.537]],[\"comment/119\",[]],[\"name/120\",[97,56.537]],[\"comment/120\",[]],[\"name/121\",[98,56.537]],[\"comment/121\",[]],[\"name/122\",[99,56.537]],[\"comment/122\",[]],[\"name/123\",[100,56.537]],[\"comment/123\",[]],[\"name/124\",[46,41.873]],[\"comment/124\",[]],[\"name/125\",[101,56.537]],[\"comment/125\",[]],[\"name/126\",[102,56.537]],[\"comment/126\",[]],[\"name/127\",[103,56.537]],[\"comment/127\",[]],[\"name/128\",[104,56.537]],[\"comment/128\",[]],[\"name/129\",[105,56.537]],[\"comment/129\",[]],[\"name/130\",[106,56.537]],[\"comment/130\",[]],[\"name/131\",[107,56.537]],[\"comment/131\",[]],[\"name/132\",[108,43.544]],[\"comment/132\",[]],[\"name/133\",[109,56.537]],[\"comment/133\",[]],[\"name/134\",[110,56.537]],[\"comment/134\",[]],[\"name/135\",[111,51.428]],[\"comment/135\",[]],[\"name/136\",[112,56.537]],[\"comment/136\",[]],[\"name/137\",[113,56.537]],[\"comment/137\",[]],[\"name/138\",[114,56.537]],[\"comment/138\",[]],[\"name/139\",[115,40.442]],[\"comment/139\",[]],[\"name/140\",[17,38.078]],[\"comment/140\",[]],[\"name/141\",[116,45.55]],[\"comment/141\",[]],[\"name/142\",[55,51.428]],[\"comment/142\",[]],[\"name/143\",[68,51.428]],[\"comment/143\",[]],[\"name/144\",[69,51.428]],[\"comment/144\",[]],[\"name/145\",[117,56.537]],[\"comment/145\",[]],[\"name/146\",[17,38.078]],[\"comment/146\",[]],[\"name/147\",[118,56.537]],[\"comment/147\",[]],[\"name/148\",[119,56.537]],[\"comment/148\",[]],[\"name/149\",[25,45.55]],[\"comment/149\",[]],[\"name/150\",[120,56.537]],[\"comment/150\",[]],[\"name/151\",[121,51.428]],[\"comment/151\",[]],[\"name/152\",[56,45.55]],[\"comment/152\",[]],[\"name/153\",[46,41.873]],[\"comment/153\",[]],[\"name/154\",[122,56.537]],[\"comment/154\",[]],[\"name/155\",[111,51.428]],[\"comment/155\",[]],[\"name/156\",[123,56.537]],[\"comment/156\",[]],[\"name/157\",[124,56.537]],[\"comment/157\",[]],[\"name/158\",[17,38.078]],[\"comment/158\",[]],[\"name/159\",[116,45.55]],[\"comment/159\",[]],[\"name/160\",[125,56.537]],[\"comment/160\",[]],[\"name/161\",[126,56.537]],[\"comment/161\",[]],[\"name/162\",[127,56.537]],[\"comment/162\",[]],[\"name/163\",[116,45.55]],[\"comment/163\",[]],[\"name/164\",[17,38.078]],[\"comment/164\",[]],[\"name/165\",[128,56.537]],[\"comment/165\",[]],[\"name/166\",[115,40.442]],[\"comment/166\",[]],[\"name/167\",[116,45.55]],[\"comment/167\",[]],[\"name/168\",[17,38.078]],[\"comment/168\",[]],[\"name/169\",[129,56.537]],[\"comment/169\",[]],[\"name/170\",[130,56.537]],[\"comment/170\",[]],[\"name/171\",[131,56.537]],[\"comment/171\",[]],[\"name/172\",[25,45.55]],[\"comment/172\",[]],[\"name/173\",[132,56.537]],[\"comment/173\",[]],[\"name/174\",[133,56.537]],[\"comment/174\",[]],[\"name/175\",[134,56.537]],[\"comment/175\",[]],[\"name/176\",[121,51.428]],[\"comment/176\",[]],[\"name/177\",[56,45.55]],[\"comment/177\",[]],[\"name/178\",[46,41.873]],[\"comment/178\",[]],[\"name/179\",[135,56.537]],[\"comment/179\",[]],[\"name/180\",[136,56.537]],[\"comment/180\",[]],[\"name/181\",[137,56.537]],[\"comment/181\",[]],[\"name/182\",[138,56.537]],[\"comment/182\",[]],[\"name/183\",[139,56.537]],[\"comment/183\",[]],[\"name/184\",[108,43.544]],[\"comment/184\",[]],[\"name/185\",[140,56.537]],[\"comment/185\",[]],[\"name/186\",[141,56.537]],[\"comment/186\",[]],[\"name/187\",[142,56.537]],[\"comment/187\",[]],[\"name/188\",[143,56.537]],[\"comment/188\",[]],[\"name/189\",[144,56.537]],[\"comment/189\",[]],[\"name/190\",[145,56.537]],[\"comment/190\",[]],[\"name/191\",[146,56.537]],[\"comment/191\",[]],[\"name/192\",[147,56.537]],[\"comment/192\",[]],[\"name/193\",[148,56.537]],[\"comment/193\",[]],[\"name/194\",[149,56.537]],[\"comment/194\",[]],[\"name/195\",[150,56.537]],[\"comment/195\",[]],[\"name/196\",[151,56.537]],[\"comment/196\",[]],[\"name/197\",[152,56.537]],[\"comment/197\",[]],[\"name/198\",[153,56.537]],[\"comment/198\",[]],[\"name/199\",[154,56.537]],[\"comment/199\",[]],[\"name/200\",[155,56.537]],[\"comment/200\",[]],[\"name/201\",[156,56.537]],[\"comment/201\",[]],[\"name/202\",[157,56.537]],[\"comment/202\",[]],[\"name/203\",[158,56.537]],[\"comment/203\",[]],[\"name/204\",[159,56.537]],[\"comment/204\",[]],[\"name/205\",[160,56.537]],[\"comment/205\",[]],[\"name/206\",[161,56.537]],[\"comment/206\",[]],[\"name/207\",[162,56.537]],[\"comment/207\",[]],[\"name/208\",[163,56.537]],[\"comment/208\",[]],[\"name/209\",[164,56.537]],[\"comment/209\",[]],[\"name/210\",[165,56.537]],[\"comment/210\",[]],[\"name/211\",[166,56.537]],[\"comment/211\",[]],[\"name/212\",[37,51.428]],[\"comment/212\",[]],[\"name/213\",[167,56.537]],[\"comment/213\",[]],[\"name/214\",[75,48.064]],[\"comment/214\",[]],[\"name/215\",[168,56.537]],[\"comment/215\",[]],[\"name/216\",[169,56.537]],[\"comment/216\",[]],[\"name/217\",[170,56.537]],[\"comment/217\",[]],[\"name/218\",[171,56.537]],[\"comment/218\",[]],[\"name/219\",[172,56.537]],[\"comment/219\",[]],[\"name/220\",[173,56.537]],[\"comment/220\",[]],[\"name/221\",[174,56.537]],[\"comment/221\",[]],[\"name/222\",[175,56.537]],[\"comment/222\",[]],[\"name/223\",[176,56.537]],[\"comment/223\",[]],[\"name/224\",[177,56.537]],[\"comment/224\",[]],[\"name/225\",[178,56.537]],[\"comment/225\",[]],[\"name/226\",[179,51.428]],[\"comment/226\",[]],[\"name/227\",[180,45.55]],[\"comment/227\",[]],[\"name/228\",[181,56.537]],[\"comment/228\",[]],[\"name/229\",[182,56.537]],[\"comment/229\",[]],[\"name/230\",[183,56.537]],[\"comment/230\",[]],[\"name/231\",[184,56.537]],[\"comment/231\",[]],[\"name/232\",[185,56.537]],[\"comment/232\",[]],[\"name/233\",[20,51.428]],[\"comment/233\",[]],[\"name/234\",[21,51.428]],[\"comment/234\",[]],[\"name/235\",[186,56.537]],[\"comment/235\",[]],[\"name/236\",[14,39.191]],[\"comment/236\",[]],[\"name/237\",[187,56.537]],[\"comment/237\",[]],[\"name/238\",[17,38.078]],[\"comment/238\",[]],[\"name/239\",[188,56.537]],[\"comment/239\",[]],[\"name/240\",[189,56.537]],[\"comment/240\",[]],[\"name/241\",[190,56.537]],[\"comment/241\",[]],[\"name/242\",[191,56.537]],[\"comment/242\",[]],[\"name/243\",[192,56.537]],[\"comment/243\",[]],[\"name/244\",[193,56.537]],[\"comment/244\",[]],[\"name/245\",[194,56.537]],[\"comment/245\",[]],[\"name/246\",[195,56.537]],[\"comment/246\",[]],[\"name/247\",[196,56.537]],[\"comment/247\",[]],[\"name/248\",[14,39.191]],[\"comment/248\",[]],[\"name/249\",[197,56.537]],[\"comment/249\",[]],[\"name/250\",[198,56.537]],[\"comment/250\",[]],[\"name/251\",[115,40.442]],[\"comment/251\",[]],[\"name/252\",[199,56.537]],[\"comment/252\",[]],[\"name/253\",[200,56.537]],[\"comment/253\",[]],[\"name/254\",[201,56.537]],[\"comment/254\",[]],[\"name/255\",[202,56.537]],[\"comment/255\",[]],[\"name/256\",[203,56.537]],[\"comment/256\",[]],[\"name/257\",[56,45.55]],[\"comment/257\",[]],[\"name/258\",[57,51.428]],[\"comment/258\",[]],[\"name/259\",[58,51.428]],[\"comment/259\",[]],[\"name/260\",[59,51.428]],[\"comment/260\",[]],[\"name/261\",[60,51.428]],[\"comment/261\",[]],[\"name/262\",[61,51.428]],[\"comment/262\",[]],[\"name/263\",[46,41.873]],[\"comment/263\",[]],[\"name/264\",[62,51.428]],[\"comment/264\",[]],[\"name/265\",[63,51.428]],[\"comment/265\",[]],[\"name/266\",[64,51.428]],[\"comment/266\",[]],[\"name/267\",[65,51.428]],[\"comment/267\",[]],[\"name/268\",[66,48.064]],[\"comment/268\",[]],[\"name/269\",[204,56.537]],[\"comment/269\",[]],[\"name/270\",[53,33.183]],[\"comment/270\",[]],[\"name/271\",[205,56.537]],[\"comment/271\",[]],[\"name/272\",[206,56.537]],[\"comment/272\",[]],[\"name/273\",[207,56.537]],[\"comment/273\",[]],[\"name/274\",[25,45.55]],[\"comment/274\",[]],[\"name/275\",[208,56.537]],[\"comment/275\",[]],[\"name/276\",[209,56.537]],[\"comment/276\",[]],[\"name/277\",[210,56.537]],[\"comment/277\",[]],[\"name/278\",[211,56.537]],[\"comment/278\",[]],[\"name/279\",[212,51.428]],[\"comment/279\",[]],[\"name/280\",[213,56.537]],[\"comment/280\",[]],[\"name/281\",[214,56.537]],[\"comment/281\",[]],[\"name/282\",[215,56.537]],[\"comment/282\",[]],[\"name/283\",[216,56.537]],[\"comment/283\",[]],[\"name/284\",[217,56.537]],[\"comment/284\",[]],[\"name/285\",[218,56.537]],[\"comment/285\",[]],[\"name/286\",[53,33.183]],[\"comment/286\",[]],[\"name/287\",[212,51.428]],[\"comment/287\",[]],[\"name/288\",[219,56.537]],[\"comment/288\",[]],[\"name/289\",[220,56.537]],[\"comment/289\",[]],[\"name/290\",[221,56.537]],[\"comment/290\",[]],[\"name/291\",[222,56.537]],[\"comment/291\",[]],[\"name/292\",[223,56.537]],[\"comment/292\",[]],[\"name/293\",[108,43.544]],[\"comment/293\",[]],[\"name/294\",[224,56.537]],[\"comment/294\",[]],[\"name/295\",[225,56.537]],[\"comment/295\",[]],[\"name/296\",[115,40.442]],[\"comment/296\",[]],[\"name/297\",[226,48.064]],[\"comment/297\",[]],[\"name/298\",[227,56.537]],[\"comment/298\",[]],[\"name/299\",[228,56.537]],[\"comment/299\",[]],[\"name/300\",[229,56.537]],[\"comment/300\",[]],[\"name/301\",[230,56.537]],[\"comment/301\",[]],[\"name/302\",[180,45.55]],[\"comment/302\",[]],[\"name/303\",[231,56.537]],[\"comment/303\",[]],[\"name/304\",[232,56.537]],[\"comment/304\",[]],[\"name/305\",[233,56.537]],[\"comment/305\",[]],[\"name/306\",[53,33.183]],[\"comment/306\",[]],[\"name/307\",[179,51.428]],[\"comment/307\",[]],[\"name/308\",[180,45.55]],[\"comment/308\",[]],[\"name/309\",[234,56.537]],[\"comment/309\",[]],[\"name/310\",[235,56.537]],[\"comment/310\",[]],[\"name/311\",[236,56.537]],[\"comment/311\",[]],[\"name/312\",[237,56.537]],[\"comment/312\",[]],[\"name/313\",[115,40.442]],[\"comment/313\",[]],[\"name/314\",[16,41.873]],[\"comment/314\",[]],[\"name/315\",[238,51.428]],[\"comment/315\",[]],[\"name/316\",[239,48.064]],[\"comment/316\",[]],[\"name/317\",[240,56.537]],[\"comment/317\",[]],[\"name/318\",[226,48.064]],[\"comment/318\",[]],[\"name/319\",[180,45.55]],[\"comment/319\",[]],[\"name/320\",[241,56.537]],[\"comment/320\",[]],[\"name/321\",[108,43.544]],[\"comment/321\",[]],[\"name/322\",[242,56.537]],[\"comment/322\",[]],[\"name/323\",[108,43.544]],[\"comment/323\",[]],[\"name/324\",[115,40.442]],[\"comment/324\",[]],[\"name/325\",[16,41.873]],[\"comment/325\",[]],[\"name/326\",[238,51.428]],[\"comment/326\",[]],[\"name/327\",[239,48.064]],[\"comment/327\",[]],[\"name/328\",[243,56.537]],[\"comment/328\",[]],[\"name/329\",[115,40.442]],[\"comment/329\",[]],[\"name/330\",[226,48.064]],[\"comment/330\",[]],[\"name/331\",[16,41.873]],[\"comment/331\",[]],[\"name/332\",[244,56.537]],[\"comment/332\",[]],[\"name/333\",[245,56.537]],[\"comment/333\",[]],[\"name/334\",[246,56.537]],[\"comment/334\",[]],[\"name/335\",[247,56.537]],[\"comment/335\",[]],[\"name/336\",[248,56.537]],[\"comment/336\",[]],[\"name/337\",[249,56.537]],[\"comment/337\",[]],[\"name/338\",[239,48.064]],[\"comment/338\",[]],[\"name/339\",[250,56.537]],[\"comment/339\",[]],[\"name/340\",[14,39.191]],[\"comment/340\",[]],[\"name/341\",[251,56.537]],[\"comment/341\",[]],[\"name/342\",[252,56.537]],[\"comment/342\",[]],[\"name/343\",[253,56.537]],[\"comment/343\",[]],[\"name/344\",[254,56.537]],[\"comment/344\",[]],[\"name/345\",[255,51.428]],[\"comment/345\",[]],[\"name/346\",[14,39.191]],[\"comment/346\",[]],[\"name/347\",[256,51.428]],[\"comment/347\",[]],[\"name/348\",[14,39.191]],[\"comment/348\",[]],[\"name/349\",[257,56.537]],[\"comment/349\",[]],[\"name/350\",[258,48.064]],[\"comment/350\",[]],[\"name/351\",[259,48.064]],[\"comment/351\",[]],[\"name/352\",[22,43.544]],[\"comment/352\",[]],[\"name/353\",[260,48.064]],[\"comment/353\",[]],[\"name/354\",[261,56.537]],[\"comment/354\",[]],[\"name/355\",[262,56.537]],[\"comment/355\",[]],[\"name/356\",[263,39.191]],[\"comment/356\",[]],[\"name/357\",[264,39.191]],[\"comment/357\",[]],[\"name/358\",[265,56.537]],[\"comment/358\",[]],[\"name/359\",[53,33.183]],[\"comment/359\",[]],[\"name/360\",[263,39.191]],[\"comment/360\",[]],[\"name/361\",[264,39.191]],[\"comment/361\",[]],[\"name/362\",[266,56.537]],[\"comment/362\",[]],[\"name/363\",[53,33.183]],[\"comment/363\",[]],[\"name/364\",[267,51.428]],[\"comment/364\",[]],[\"name/365\",[268,45.55]],[\"comment/365\",[]],[\"name/366\",[263,39.191]],[\"comment/366\",[]],[\"name/367\",[264,39.191]],[\"comment/367\",[]],[\"name/368\",[269,56.537]],[\"comment/368\",[]],[\"name/369\",[53,33.183]],[\"comment/369\",[]],[\"name/370\",[267,51.428]],[\"comment/370\",[]],[\"name/371\",[268,45.55]],[\"comment/371\",[]],[\"name/372\",[263,39.191]],[\"comment/372\",[]],[\"name/373\",[264,39.191]],[\"comment/373\",[]],[\"name/374\",[270,56.537]],[\"comment/374\",[]],[\"name/375\",[53,33.183]],[\"comment/375\",[]],[\"name/376\",[271,56.537]],[\"comment/376\",[]],[\"name/377\",[263,39.191]],[\"comment/377\",[]],[\"name/378\",[268,45.55]],[\"comment/378\",[]],[\"name/379\",[264,39.191]],[\"comment/379\",[]],[\"name/380\",[272,51.428]],[\"comment/380\",[]],[\"name/381\",[53,33.183]],[\"comment/381\",[]],[\"name/382\",[273,56.537]],[\"comment/382\",[]],[\"name/383\",[263,39.191]],[\"comment/383\",[]],[\"name/384\",[268,45.55]],[\"comment/384\",[]],[\"name/385\",[264,39.191]],[\"comment/385\",[]],[\"name/386\",[274,51.428]],[\"comment/386\",[]],[\"name/387\",[53,33.183]],[\"comment/387\",[]],[\"name/388\",[263,39.191]],[\"comment/388\",[]],[\"name/389\",[264,39.191]],[\"comment/389\",[]],[\"name/390\",[275,56.537]],[\"comment/390\",[]],[\"name/391\",[53,33.183]],[\"comment/391\",[]],[\"name/392\",[263,39.191]],[\"comment/392\",[]],[\"name/393\",[264,39.191]],[\"comment/393\",[]],[\"name/394\",[276,56.537]],[\"comment/394\",[]],[\"name/395\",[277,56.537]],[\"comment/395\",[]],[\"name/396\",[278,56.537]],[\"comment/396\",[]],[\"name/397\",[279,56.537]],[\"comment/397\",[]],[\"name/398\",[280,56.537]],[\"comment/398\",[]],[\"name/399\",[281,56.537]],[\"comment/399\",[]],[\"name/400\",[282,56.537]],[\"comment/400\",[]],[\"name/401\",[274,51.428]],[\"comment/401\",[]],[\"name/402\",[283,56.537]],[\"comment/402\",[]],[\"name/403\",[272,51.428]],[\"comment/403\",[]],[\"name/404\",[284,56.537]],[\"comment/404\",[]],[\"name/405\",[53,33.183]],[\"comment/405\",[]],[\"name/406\",[285,56.537]],[\"comment/406\",[]],[\"name/407\",[53,33.183]],[\"comment/407\",[]],[\"name/408\",[286,56.537]],[\"comment/408\",[]],[\"name/409\",[255,51.428]],[\"comment/409\",[]],[\"name/410\",[256,51.428]],[\"comment/410\",[]],[\"name/411\",[287,56.537]],[\"comment/411\",[]],[\"name/412\",[288,56.537]],[\"comment/412\",[]],[\"name/413\",[289,51.428]],[\"comment/413\",[]],[\"name/414\",[258,48.064]],[\"comment/414\",[]],[\"name/415\",[259,48.064]],[\"comment/415\",[]],[\"name/416\",[22,43.544]],[\"comment/416\",[]],[\"name/417\",[260,48.064]],[\"comment/417\",[]],[\"name/418\",[290,56.537]],[\"comment/418\",[]],[\"name/419\",[53,33.183]],[\"comment/419\",[]],[\"name/420\",[75,48.064]],[\"comment/420\",[]],[\"name/421\",[291,56.537]],[\"comment/421\",[]],[\"name/422\",[289,51.428]],[\"comment/422\",[]],[\"name/423\",[258,48.064]],[\"comment/423\",[]],[\"name/424\",[259,48.064]],[\"comment/424\",[]],[\"name/425\",[22,43.544]],[\"comment/425\",[]],[\"name/426\",[260,48.064]],[\"comment/426\",[]]],\"invertedIndex\":[[\"__type\",{\"_index\":14,\"name\":{\"16\":{},\"64\":{},\"66\":{},\"236\":{},\"248\":{},\"340\":{},\"346\":{},\"348\":{}},\"comment\":{}}],[\"_meta\",{\"_index\":238,\"name\":{\"315\":{},\"326\":{}},\"comment\":{}}],[\"addeventlistener\",{\"_index\":45,\"name\":{\"60\":{}},\"comment\":{}}],[\"additional\",{\"_index\":18,\"name\":{\"22\":{}},\"comment\":{}}],[\"all\",{\"_index\":132,\"name\":{\"173\":{}},\"comment\":{}}],[\"allattributesprivate\",{\"_index\":179,\"name\":{\"226\":{},\"307\":{}},\"comment\":{}}],[\"allflagsstate\",{\"_index\":60,\"name\":{\"79\":{},\"261\":{}},\"comment\":{}}],[\"allvalues\",{\"_index\":151,\"name\":{\"196\":{}},\"comment\":{}}],[\"andmatch\",{\"_index\":94,\"name\":{\"117\":{}},\"comment\":{}}],[\"andnotmatch\",{\"_index\":95,\"name\":{\"118\":{}},\"comment\":{}}],[\"anonymous\",{\"_index\":239,\"name\":{\"316\":{},\"327\":{},\"338\":{}},\"comment\":{}}],[\"application\",{\"_index\":186,\"name\":{\"235\":{}},\"comment\":{}}],[\"arch\",{\"_index\":15,\"name\":{\"17\":{}},\"comment\":{}}],[\"attributereference\",{\"_index\":204,\"name\":{\"269\":{}},\"comment\":{}}],[\"auth\",{\"_index\":192,\"name\":{\"243\":{}},\"comment\":{}}],[\"autoupdate\",{\"_index\":74,\"name\":{\"95\":{}},\"comment\":{}}],[\"available\",{\"_index\":105,\"name\":{\"129\":{}},\"comment\":{}}],[\"avatar\",{\"_index\":247,\"name\":{\"335\":{}},\"comment\":{}}],[\"baseuri\",{\"_index\":164,\"name\":{\"209\":{}},\"comment\":{}}],[\"basiclogger\",{\"_index\":285,\"name\":{\"406\":{}},\"comment\":{}}],[\"basicloggeroptions\",{\"_index\":253,\"name\":{\"343\":{}},\"comment\":{}}],[\"bigsegments\",{\"_index\":169,\"name\":{\"216\":{}},\"comment\":{}}],[\"bigsegmentsstatus\",{\"_index\":145,\"name\":{\"190\":{}},\"comment\":{}}],[\"bigsegmentstore\",{\"_index\":98,\"name\":{\"121\":{}},\"comment\":{}}],[\"bigsegmentstoremembership\",{\"_index\":101,\"name\":{\"125\":{}},\"comment\":{}}],[\"bigsegmentstoremetadata\",{\"_index\":102,\"name\":{\"126\":{}},\"comment\":{}}],[\"bigsegmentstorestatus\",{\"_index\":104,\"name\":{\"128\":{}},\"comment\":{}}],[\"bigsegmentstorestatusprovider\",{\"_index\":55,\"name\":{\"74\":{},\"142\":{}},\"comment\":{}}],[\"bigsegmentstorestatusproviderimpl\",{\"_index\":67,\"name\":{\"87\":{}},\"comment\":{}}],[\"body\",{\"_index\":36,\"name\":{\"50\":{}},\"comment\":{}}],[\"boolean\",{\"_index\":282,\"name\":{\"400\":{}},\"comment\":{}}],[\"booleanflag\",{\"_index\":81,\"name\":{\"103\":{}},\"comment\":{}}],[\"ca\",{\"_index\":194,\"name\":{\"245\":{}},\"comment\":{}}],[\"canonicalkey\",{\"_index\":228,\"name\":{\"299\":{}},\"comment\":{}}],[\"capacity\",{\"_index\":167,\"name\":{\"213\":{}},\"comment\":{}}],[\"cert\",{\"_index\":195,\"name\":{\"246\":{}},\"comment\":{}}],[\"checkserveridentity\",{\"_index\":196,\"name\":{\"247\":{}},\"comment\":{}}],[\"ciphers\",{\"_index\":197,\"name\":{\"249\":{}},\"comment\":{}}],[\"clearalltargets\",{\"_index\":90,\"name\":{\"113\":{}},\"comment\":{}}],[\"clearrules\",{\"_index\":89,\"name\":{\"112\":{}},\"comment\":{}}],[\"clientsideonly\",{\"_index\":154,\"name\":{\"199\":{}},\"comment\":{}}],[\"close\",{\"_index\":46,\"name\":{\"61\":{},\"81\":{},\"124\":{},\"153\":{},\"178\":{},\"263\":{}},\"comment\":{}}],[\"compare\",{\"_index\":211,\"name\":{\"278\":{}},\"comment\":{}}],[\"components\",{\"_index\":207,\"name\":{\"273\":{}},\"comment\":{}}],[\"constructor\",{\"_index\":53,\"name\":{\"71\":{},\"88\":{},\"270\":{},\"286\":{},\"306\":{},\"359\":{},\"363\":{},\"369\":{},\"375\":{},\"381\":{},\"387\":{},\"391\":{},\"405\":{},\"407\":{},\"419\":{}},\"comment\":{}}],[\"context\",{\"_index\":212,\"name\":{\"279\":{},\"287\":{}},\"comment\":{}}],[\"contextfilter\",{\"_index\":233,\"name\":{\"305\":{}},\"comment\":{}}],[\"contextforkind\",{\"_index\":224,\"name\":{\"294\":{}},\"comment\":{}}],[\"contextkeyscapacity\",{\"_index\":181,\"name\":{\"228\":{}},\"comment\":{}}],[\"contextkeysflushinterval\",{\"_index\":182,\"name\":{\"229\":{}},\"comment\":{}}],[\"contexts\",{\"_index\":222,\"name\":{\"291\":{}},\"comment\":{}}],[\"country\",{\"_index\":249,\"name\":{\"337\":{}},\"comment\":{}}],[\"createeventsource\",{\"_index\":39,\"name\":{\"54\":{}},\"comment\":{}}],[\"createhash\",{\"_index\":6,\"name\":{\"8\":{}},\"comment\":{}}],[\"createhmac\",{\"_index\":7,\"name\":{\"9\":{}},\"comment\":{}}],[\"crypto\",{\"_index\":5,\"name\":{\"7\":{},\"34\":{}},\"comment\":{}}],[\"custom\",{\"_index\":250,\"name\":{\"339\":{}},\"comment\":{}}],[\"datacollection\",{\"_index\":107,\"name\":{\"131\":{}},\"comment\":{}}],[\"datakind\",{\"_index\":110,\"name\":{\"134\":{}},\"comment\":{}}],[\"date\",{\"_index\":284,\"name\":{\"404\":{}},\"comment\":{}}],[\"datevalidator\",{\"_index\":275,\"name\":{\"390\":{}},\"comment\":{}}],[\"debug\",{\"_index\":260,\"name\":{\"353\":{},\"417\":{},\"426\":{}},\"comment\":{}}],[\"delete\",{\"_index\":134,\"name\":{\"175\":{}},\"comment\":{}}],[\"deleted\",{\"_index\":116,\"name\":{\"141\":{},\"159\":{},\"163\":{},\"167\":{}},\"comment\":{}}],[\"depth\",{\"_index\":209,\"name\":{\"276\":{}},\"comment\":{}}],[\"deserialize\",{\"_index\":123,\"name\":{\"156\":{}},\"comment\":{}}],[\"destination\",{\"_index\":255,\"name\":{\"345\":{},\"409\":{}},\"comment\":{}}],[\"detailsonlyfortrackedflags\",{\"_index\":156,\"name\":{\"201\":{}},\"comment\":{}}],[\"diagnosticoptout\",{\"_index\":184,\"name\":{\"231\":{}},\"comment\":{}}],[\"diagnosticrecordinginterval\",{\"_index\":185,\"name\":{\"232\":{}},\"comment\":{}}],[\"digest\",{\"_index\":3,\"name\":{\"3\":{},\"6\":{}},\"comment\":{}}],[\"dispatch\",{\"_index\":70,\"name\":{\"91\":{}},\"comment\":{}}],[\"email\",{\"_index\":246,\"name\":{\"334\":{}},\"comment\":{}}],[\"entries\",{\"_index\":28,\"name\":{\"40\":{}},\"comment\":{}}],[\"error\",{\"_index\":258,\"name\":{\"350\":{},\"414\":{},\"423\":{}},\"comment\":{}}],[\"errorfilter\",{\"_index\":48,\"name\":{\"63\":{}},\"comment\":{}}],[\"errorkind\",{\"_index\":140,\"name\":{\"185\":{}},\"comment\":{}}],[\"eventsource\",{\"_index\":40,\"name\":{\"55\":{}},\"comment\":{}}],[\"eventsourceinitdict\",{\"_index\":47,\"name\":{\"62\":{}},\"comment\":{}}],[\"eventsuri\",{\"_index\":166,\"name\":{\"211\":{}},\"comment\":{}}],[\"expression\",{\"_index\":273,\"name\":{\"382\":{}},\"comment\":{}}],[\"factoryorinstance\",{\"_index\":265,\"name\":{\"358\":{}},\"comment\":{}}],[\"fallback\",{\"_index\":291,\"name\":{\"421\":{}},\"comment\":{}}],[\"fallthroughvariation\",{\"_index\":83,\"name\":{\"106\":{}},\"comment\":{}}],[\"featurestore\",{\"_index\":168,\"name\":{\"215\":{}},\"comment\":{}}],[\"fetch\",{\"_index\":38,\"name\":{\"53\":{}},\"comment\":{}}],[\"filedatasourceoptions\",{\"_index\":72,\"name\":{\"93\":{}},\"comment\":{}}],[\"filesystem\",{\"_index\":8,\"name\":{\"10\":{},\"33\":{}},\"comment\":{}}],[\"filter\",{\"_index\":234,\"name\":{\"309\":{}},\"comment\":{}}],[\"filtersinglekind\",{\"_index\":236,\"name\":{\"311\":{}},\"comment\":{}}],[\"firstname\",{\"_index\":244,\"name\":{\"332\":{}},\"comment\":{}}],[\"flag\",{\"_index\":77,\"name\":{\"98\":{}},\"comment\":{}}],[\"flush\",{\"_index\":65,\"name\":{\"85\":{},\"267\":{}},\"comment\":{}}],[\"flushinterval\",{\"_index\":171,\"name\":{\"218\":{}},\"comment\":{}}],[\"formatter\",{\"_index\":256,\"name\":{\"347\":{},\"410\":{}},\"comment\":{}}],[\"fromldcontext\",{\"_index\":218,\"name\":{\"285\":{}},\"comment\":{}}],[\"fromlegacyuser\",{\"_index\":217,\"name\":{\"284\":{}},\"comment\":{}}],[\"frommultikindcontext\",{\"_index\":215,\"name\":{\"282\":{}},\"comment\":{}}],[\"fromsinglekindcontext\",{\"_index\":216,\"name\":{\"283\":{}},\"comment\":{}}],[\"fulldataset\",{\"_index\":112,\"name\":{\"136\":{}},\"comment\":{}}],[\"function\",{\"_index\":274,\"name\":{\"386\":{},\"401\":{}},\"comment\":{}}],[\"get\",{\"_index\":25,\"name\":{\"37\":{},\"149\":{},\"172\":{},\"274\":{}},\"comment\":{}}],[\"getall\",{\"_index\":120,\"name\":{\"150\":{}},\"comment\":{}}],[\"getattributestofilter\",{\"_index\":235,\"name\":{\"310\":{}},\"comment\":{}}],[\"getcomponent\",{\"_index\":208,\"name\":{\"275\":{}},\"comment\":{}}],[\"getcontexts\",{\"_index\":231,\"name\":{\"303\":{}},\"comment\":{}}],[\"getfiletimestamp\",{\"_index\":9,\"name\":{\"11\":{}},\"comment\":{}}],[\"getflagreason\",{\"_index\":150,\"name\":{\"195\":{}},\"comment\":{}}],[\"getflagvalue\",{\"_index\":149,\"name\":{\"194\":{}},\"comment\":{}}],[\"getmetadata\",{\"_index\":99,\"name\":{\"122\":{}},\"comment\":{}}],[\"getstatus\",{\"_index\":68,\"name\":{\"89\":{},\"143\":{}},\"comment\":{}}],[\"gettype\",{\"_index\":264,\"name\":{\"357\":{},\"361\":{},\"367\":{},\"373\":{},\"379\":{},\"385\":{},\"389\":{},\"393\":{}},\"comment\":{}}],[\"getusermembership\",{\"_index\":100,\"name\":{\"123\":{}},\"comment\":{}}],[\"getvaluefromcontext\",{\"_index\":214,\"name\":{\"281\":{}},\"comment\":{}}],[\"has\",{\"_index\":29,\"name\":{\"41\":{}},\"comment\":{}}],[\"hasher\",{\"_index\":1,\"name\":{\"1\":{}},\"comment\":{}}],[\"headers\",{\"_index\":24,\"name\":{\"36\":{},\"43\":{},\"48\":{},\"65\":{}},\"comment\":{}}],[\"hmac\",{\"_index\":4,\"name\":{\"4\":{}},\"comment\":{}}],[\"host\",{\"_index\":189,\"name\":{\"240\":{}},\"comment\":{}}],[\"id\",{\"_index\":187,\"name\":{\"237\":{}},\"comment\":{}}],[\"identify\",{\"_index\":64,\"name\":{\"84\":{},\"266\":{}},\"comment\":{}}],[\"ifmatch\",{\"_index\":91,\"name\":{\"114\":{}},\"comment\":{}}],[\"ifnotmatch\",{\"_index\":92,\"name\":{\"115\":{}},\"comment\":{}}],[\"inexperiment\",{\"_index\":144,\"name\":{\"189\":{}},\"comment\":{}}],[\"info\",{\"_index\":22,\"name\":{\"28\":{},\"32\":{},\"352\":{},\"416\":{},\"425\":{}},\"comment\":{}}],[\"init\",{\"_index\":133,\"name\":{\"174\":{}},\"comment\":{}}],[\"initialized\",{\"_index\":56,\"name\":{\"75\":{},\"152\":{},\"177\":{},\"257\":{}},\"comment\":{}}],[\"initialretrydelaymillis\",{\"_index\":49,\"name\":{\"67\":{}},\"comment\":{}}],[\"initstate\",{\"_index\":54,\"name\":{\"73\":{}},\"comment\":{}}],[\"integrations\",{\"_index\":71,\"name\":{\"92\":{}},\"comment\":{}}],[\"interfaces\",{\"_index\":97,\"name\":{\"120\":{}},\"comment\":{}}],[\"ip\",{\"_index\":248,\"name\":{\"336\":{}},\"comment\":{}}],[\"is\",{\"_index\":263,\"name\":{\"356\":{},\"360\":{},\"366\":{},\"372\":{},\"377\":{},\"383\":{},\"388\":{},\"392\":{}},\"comment\":{}}],[\"iskind\",{\"_index\":210,\"name\":{\"277\":{}},\"comment\":{}}],[\"ismulti\",{\"_index\":219,\"name\":{\"288\":{}},\"comment\":{}}],[\"ismultikind\",{\"_index\":227,\"name\":{\"298\":{}},\"comment\":{}}],[\"isoffline\",{\"_index\":62,\"name\":{\"82\":{},\"264\":{}},\"comment\":{}}],[\"isuser\",{\"_index\":220,\"name\":{\"289\":{}},\"comment\":{}}],[\"isvalid\",{\"_index\":205,\"name\":{\"271\":{}},\"comment\":{}}],[\"item\",{\"_index\":118,\"name\":{\"147\":{}},\"comment\":{}}],[\"itemdescriptor\",{\"_index\":117,\"name\":{\"145\":{}},\"comment\":{}}],[\"items\",{\"_index\":109,\"name\":{\"133\":{}},\"comment\":{}}],[\"json\",{\"_index\":33,\"name\":{\"46\":{}},\"comment\":{}}],[\"key\",{\"_index\":115,\"name\":{\"139\":{},\"166\":{},\"251\":{},\"296\":{},\"313\":{},\"324\":{},\"329\":{}},\"comment\":{}}],[\"keyeditems\",{\"_index\":113,\"name\":{\"137\":{}},\"comment\":{}}],[\"keys\",{\"_index\":26,\"name\":{\"38\":{}},\"comment\":{}}],[\"kind\",{\"_index\":108,\"name\":{\"132\":{},\"184\":{},\"293\":{},\"321\":{},\"323\":{}},\"comment\":{}}],[\"kinds\",{\"_index\":229,\"name\":{\"300\":{}},\"comment\":{}}],[\"kindsandkeys\",{\"_index\":230,\"name\":{\"301\":{}},\"comment\":{}}],[\"lastname\",{\"_index\":245,\"name\":{\"333\":{}},\"comment\":{}}],[\"lastuptodate\",{\"_index\":103,\"name\":{\"127\":{}},\"comment\":{}}],[\"ldbigsegmentsoptions\",{\"_index\":158,\"name\":{\"203\":{}},\"comment\":{}}],[\"ldclient\",{\"_index\":203,\"name\":{\"256\":{}},\"comment\":{}}],[\"ldclientimpl\",{\"_index\":52,\"name\":{\"70\":{}},\"comment\":{}}],[\"ldcontext\",{\"_index\":252,\"name\":{\"342\":{}},\"comment\":{}}],[\"ldcontextcommon\",{\"_index\":237,\"name\":{\"312\":{}},\"comment\":{}}],[\"ldcontextmeta\",{\"_index\":240,\"name\":{\"317\":{}},\"comment\":{}}],[\"ldevaluationdetail\",{\"_index\":135,\"name\":{\"179\":{}},\"comment\":{}}],[\"ldevaluationreason\",{\"_index\":139,\"name\":{\"183\":{}},\"comment\":{}}],[\"ldfeaturestore\",{\"_index\":131,\"name\":{\"171\":{}},\"comment\":{}}],[\"ldfeaturestoredatastorage\",{\"_index\":130,\"name\":{\"170\":{}},\"comment\":{}}],[\"ldfeaturestoreitem\",{\"_index\":127,\"name\":{\"162\":{}},\"comment\":{}}],[\"ldfeaturestorekinddata\",{\"_index\":129,\"name\":{\"169\":{}},\"comment\":{}}],[\"ldflagset\",{\"_index\":146,\"name\":{\"191\":{}},\"comment\":{}}],[\"ldflagsstate\",{\"_index\":147,\"name\":{\"192\":{}},\"comment\":{}}],[\"ldflagsstateoptions\",{\"_index\":153,\"name\":{\"198\":{}},\"comment\":{}}],[\"ldflagvalue\",{\"_index\":157,\"name\":{\"202\":{}},\"comment\":{}}],[\"ldkeyedfeaturestoreitem\",{\"_index\":128,\"name\":{\"165\":{}},\"comment\":{}}],[\"ldlogger\",{\"_index\":257,\"name\":{\"349\":{}},\"comment\":{}}],[\"ldloglevel\",{\"_index\":261,\"name\":{\"354\":{}},\"comment\":{}}],[\"ldmultikindcontext\",{\"_index\":241,\"name\":{\"320\":{}},\"comment\":{}}],[\"ldoptions\",{\"_index\":163,\"name\":{\"208\":{}},\"comment\":{}}],[\"ldproxyoptions\",{\"_index\":188,\"name\":{\"239\":{}},\"comment\":{}}],[\"ldsinglekindcontext\",{\"_index\":242,\"name\":{\"322\":{}},\"comment\":{}}],[\"ldtlsoptions\",{\"_index\":193,\"name\":{\"244\":{}},\"comment\":{}}],[\"lduser\",{\"_index\":243,\"name\":{\"328\":{}},\"comment\":{}}],[\"legacy\",{\"_index\":232,\"name\":{\"304\":{}},\"comment\":{}}],[\"level\",{\"_index\":254,\"name\":{\"344\":{}},\"comment\":{}}],[\"log\",{\"_index\":289,\"name\":{\"413\":{},\"422\":{}},\"comment\":{}}],[\"logger\",{\"_index\":75,\"name\":{\"96\":{},\"214\":{},\"420\":{}},\"comment\":{}}],[\"loglevel\",{\"_index\":286,\"name\":{\"408\":{}},\"comment\":{}}],[\"method\",{\"_index\":35,\"name\":{\"49\":{}},\"comment\":{}}],[\"min\",{\"_index\":271,\"name\":{\"376\":{}},\"comment\":{}}],[\"name\",{\"_index\":16,\"name\":{\"18\":{},\"20\":{},\"24\":{},\"314\":{},\"325\":{},\"331\":{}},\"comment\":{}}],[\"namespace\",{\"_index\":111,\"name\":{\"135\":{},\"155\":{}},\"comment\":{}}],[\"number\",{\"_index\":278,\"name\":{\"396\":{}},\"comment\":{}}],[\"numberwithmin\",{\"_index\":283,\"name\":{\"402\":{}},\"comment\":{}}],[\"numberwithminimum\",{\"_index\":270,\"name\":{\"374\":{}},\"comment\":{}}],[\"object\",{\"_index\":280,\"name\":{\"398\":{}},\"comment\":{}}],[\"objectorfactory\",{\"_index\":279,\"name\":{\"397\":{}},\"comment\":{}}],[\"offline\",{\"_index\":174,\"name\":{\"221\":{}},\"comment\":{}}],[\"offvariation\",{\"_index\":84,\"name\":{\"107\":{}},\"comment\":{}}],[\"on\",{\"_index\":66,\"name\":{\"86\":{},\"105\":{},\"268\":{}},\"comment\":{}}],[\"onclose\",{\"_index\":41,\"name\":{\"56\":{}},\"comment\":{}}],[\"onerror\",{\"_index\":42,\"name\":{\"57\":{}},\"comment\":{}}],[\"onopen\",{\"_index\":43,\"name\":{\"58\":{}},\"comment\":{}}],[\"onretrying\",{\"_index\":44,\"name\":{\"59\":{}},\"comment\":{}}],[\"options\",{\"_index\":34,\"name\":{\"47\":{}},\"comment\":{}}],[\"os\",{\"_index\":13,\"name\":{\"15\":{}},\"comment\":{}}],[\"passphrase\",{\"_index\":199,\"name\":{\"252\":{}},\"comment\":{}}],[\"paths\",{\"_index\":73,\"name\":{\"94\":{}},\"comment\":{}}],[\"persistentdatastore\",{\"_index\":119,\"name\":{\"148\":{}},\"comment\":{}}],[\"persistentstoredatakind\",{\"_index\":122,\"name\":{\"154\":{}},\"comment\":{}}],[\"pfx\",{\"_index\":198,\"name\":{\"250\":{}},\"comment\":{}}],[\"platform\",{\"_index\":0,\"name\":{\"0\":{},\"31\":{},\"72\":{}},\"comment\":{}}],[\"platformdata\",{\"_index\":12,\"name\":{\"14\":{},\"29\":{}},\"comment\":{}}],[\"pollinterval\",{\"_index\":172,\"name\":{\"219\":{}},\"comment\":{}}],[\"port\",{\"_index\":190,\"name\":{\"241\":{}},\"comment\":{}}],[\"prerequisitekey\",{\"_index\":143,\"name\":{\"188\":{}},\"comment\":{}}],[\"privateattributenames\",{\"_index\":251,\"name\":{\"341\":{}},\"comment\":{}}],[\"privateattributereferences\",{\"_index\":223,\"name\":{\"292\":{}},\"comment\":{}}],[\"privateattributes\",{\"_index\":180,\"name\":{\"227\":{},\"302\":{},\"308\":{},\"319\":{}},\"comment\":{}}],[\"proxyoptions\",{\"_index\":173,\"name\":{\"220\":{}},\"comment\":{}}],[\"readfile\",{\"_index\":10,\"name\":{\"12\":{}},\"comment\":{}}],[\"readtimeoutmillis\",{\"_index\":50,\"name\":{\"68\":{}},\"comment\":{}}],[\"reason\",{\"_index\":138,\"name\":{\"182\":{}},\"comment\":{}}],[\"redactionname\",{\"_index\":206,\"name\":{\"272\":{}},\"comment\":{}}],[\"rejectunauthorized\",{\"_index\":200,\"name\":{\"253\":{}},\"comment\":{}}],[\"requests\",{\"_index\":23,\"name\":{\"35\":{},\"52\":{}},\"comment\":{}}],[\"requirestatus\",{\"_index\":69,\"name\":{\"90\":{},\"144\":{}},\"comment\":{}}],[\"response\",{\"_index\":30,\"name\":{\"42\":{}},\"comment\":{}}],[\"retryresetintervalmillis\",{\"_index\":51,\"name\":{\"69\":{}},\"comment\":{}}],[\"ruleid\",{\"_index\":142,\"name\":{\"187\":{}},\"comment\":{}}],[\"ruleindex\",{\"_index\":141,\"name\":{\"186\":{}},\"comment\":{}}],[\"safelogger\",{\"_index\":290,\"name\":{\"418\":{}},\"comment\":{}}],[\"scheme\",{\"_index\":191,\"name\":{\"242\":{}},\"comment\":{}}],[\"sdkdata\",{\"_index\":19,\"name\":{\"23\":{},\"30\":{}},\"comment\":{}}],[\"secondary\",{\"_index\":226,\"name\":{\"297\":{},\"318\":{},\"330\":{}},\"comment\":{}}],[\"securemodehash\",{\"_index\":61,\"name\":{\"80\":{},\"262\":{}},\"comment\":{}}],[\"secureprotocol\",{\"_index\":201,\"name\":{\"254\":{}},\"comment\":{}}],[\"sendevents\",{\"_index\":178,\"name\":{\"225\":{}},\"comment\":{}}],[\"serializeditem\",{\"_index\":125,\"name\":{\"160\":{}},\"comment\":{}}],[\"serializeditemdescriptor\",{\"_index\":124,\"name\":{\"157\":{}},\"comment\":{}}],[\"servername\",{\"_index\":202,\"name\":{\"255\":{}},\"comment\":{}}],[\"stale\",{\"_index\":106,\"name\":{\"130\":{}},\"comment\":{}}],[\"staleafter\",{\"_index\":162,\"name\":{\"207\":{}},\"comment\":{}}],[\"status\",{\"_index\":31,\"name\":{\"44\":{}},\"comment\":{}}],[\"statuspollinterval\",{\"_index\":161,\"name\":{\"206\":{}},\"comment\":{}}],[\"stream\",{\"_index\":175,\"name\":{\"222\":{}},\"comment\":{}}],[\"streaminitialreconnectdelay\",{\"_index\":176,\"name\":{\"223\":{}},\"comment\":{}}],[\"streamuri\",{\"_index\":165,\"name\":{\"210\":{}},\"comment\":{}}],[\"string\",{\"_index\":277,\"name\":{\"395\":{}},\"comment\":{}}],[\"stringarray\",{\"_index\":281,\"name\":{\"399\":{}},\"comment\":{}}],[\"stringmatchingregex\",{\"_index\":272,\"name\":{\"380\":{},\"403\":{}},\"comment\":{}}],[\"subsystems\",{\"_index\":126,\"name\":{\"161\":{}},\"comment\":{}}],[\"testdata\",{\"_index\":76,\"name\":{\"97\":{}},\"comment\":{}}],[\"testdataflagbuilder\",{\"_index\":80,\"name\":{\"102\":{}},\"comment\":{}}],[\"testdatarulebuilder\",{\"_index\":93,\"name\":{\"116\":{}},\"comment\":{}}],[\"text\",{\"_index\":32,\"name\":{\"45\":{}},\"comment\":{}}],[\"thenreturn\",{\"_index\":96,\"name\":{\"119\":{}},\"comment\":{}}],[\"timeout\",{\"_index\":37,\"name\":{\"51\":{},\"212\":{}},\"comment\":{}}],[\"tlsparams\",{\"_index\":183,\"name\":{\"230\":{}},\"comment\":{}}],[\"tojson\",{\"_index\":152,\"name\":{\"197\":{}},\"comment\":{}}],[\"track\",{\"_index\":63,\"name\":{\"83\":{},\"265\":{}},\"comment\":{}}],[\"tryformat\",{\"_index\":287,\"name\":{\"411\":{}},\"comment\":{}}],[\"trywrite\",{\"_index\":288,\"name\":{\"412\":{}},\"comment\":{}}],[\"type\",{\"_index\":266,\"name\":{\"362\":{}},\"comment\":{}}],[\"typearray\",{\"_index\":269,\"name\":{\"368\":{}},\"comment\":{}}],[\"typename\",{\"_index\":267,\"name\":{\"364\":{},\"370\":{}},\"comment\":{}}],[\"typeof\",{\"_index\":268,\"name\":{\"365\":{},\"371\":{},\"378\":{},\"384\":{}},\"comment\":{}}],[\"typevalidator\",{\"_index\":262,\"name\":{\"355\":{}},\"comment\":{}}],[\"typevalidators\",{\"_index\":276,\"name\":{\"394\":{}},\"comment\":{}}],[\"update\",{\"_index\":2,\"name\":{\"2\":{},\"5\":{},\"99\":{}},\"comment\":{}}],[\"updateprocessor\",{\"_index\":170,\"name\":{\"217\":{}},\"comment\":{}}],[\"upsert\",{\"_index\":121,\"name\":{\"151\":{},\"176\":{}},\"comment\":{}}],[\"useldd\",{\"_index\":177,\"name\":{\"224\":{}},\"comment\":{}}],[\"usepreconfiguredflag\",{\"_index\":78,\"name\":{\"100\":{}},\"comment\":{}}],[\"usepreconfiguredsegment\",{\"_index\":79,\"name\":{\"101\":{}},\"comment\":{}}],[\"usercachesize\",{\"_index\":159,\"name\":{\"204\":{}},\"comment\":{}}],[\"usercachetime\",{\"_index\":160,\"name\":{\"205\":{}},\"comment\":{}}],[\"userkind\",{\"_index\":213,\"name\":{\"280\":{}},\"comment\":{}}],[\"valid\",{\"_index\":148,\"name\":{\"193\":{}},\"comment\":{}}],[\"value\",{\"_index\":136,\"name\":{\"180\":{}},\"comment\":{}}],[\"valueforall\",{\"_index\":86,\"name\":{\"109\":{}},\"comment\":{}}],[\"valueforkind\",{\"_index\":225,\"name\":{\"295\":{}},\"comment\":{}}],[\"values\",{\"_index\":27,\"name\":{\"39\":{}},\"comment\":{}}],[\"variation\",{\"_index\":58,\"name\":{\"77\":{},\"259\":{}},\"comment\":{}}],[\"variationdetail\",{\"_index\":59,\"name\":{\"78\":{},\"260\":{}},\"comment\":{}}],[\"variationforall\",{\"_index\":85,\"name\":{\"108\":{}},\"comment\":{}}],[\"variationforcontext\",{\"_index\":88,\"name\":{\"111\":{}},\"comment\":{}}],[\"variationforuser\",{\"_index\":87,\"name\":{\"110\":{}},\"comment\":{}}],[\"variationindex\",{\"_index\":137,\"name\":{\"181\":{}},\"comment\":{}}],[\"variations\",{\"_index\":82,\"name\":{\"104\":{}},\"comment\":{}}],[\"version\",{\"_index\":17,\"name\":{\"19\":{},\"21\":{},\"25\":{},\"140\":{},\"146\":{},\"158\":{},\"164\":{},\"168\":{},\"238\":{}},\"comment\":{}}],[\"versioneddata\",{\"_index\":114,\"name\":{\"138\":{}},\"comment\":{}}],[\"waitforinitialization\",{\"_index\":57,\"name\":{\"76\":{},\"258\":{}},\"comment\":{}}],[\"warn\",{\"_index\":259,\"name\":{\"351\":{},\"415\":{},\"424\":{}},\"comment\":{}}],[\"waslegacy\",{\"_index\":221,\"name\":{\"290\":{}},\"comment\":{}}],[\"watch\",{\"_index\":11,\"name\":{\"13\":{}},\"comment\":{}}],[\"withreasons\",{\"_index\":155,\"name\":{\"200\":{}},\"comment\":{}}],[\"wrappername\",{\"_index\":20,\"name\":{\"26\":{},\"233\":{}},\"comment\":{}}],[\"wrapperversion\",{\"_index\":21,\"name\":{\"27\":{},\"234\":{}},\"comment\":{}}]],\"pipeline\":[]}}"); \ No newline at end of file diff --git a/docs/assets/style.css b/docs/assets/style.css deleted file mode 100644 index 8f6ed2c437..0000000000 --- a/docs/assets/style.css +++ /dev/null @@ -1,1224 +0,0 @@ -:root { - /* Light */ - --light-color-background: #f2f4f8; - --light-color-background-secondary: #eff0f1; - --light-color-icon-background: var(--light-color-background); - --light-color-accent: #c5c7c9; - --light-color-text: #222; - --light-color-text-aside: #707070; - --light-color-link: #4da6ff; - --light-color-ts: #db1373; - --light-color-ts-interface: #139d2c; - --light-color-ts-enum: #9c891a; - --light-color-ts-class: #2484e5; - --light-color-ts-function: #572be7; - --light-color-ts-namespace: #b111c9; - --light-color-ts-private: #707070; - --light-color-ts-variable: #4d68ff; - --light-external-icon: url("data:image/svg+xml;utf8,"); - --light-color-scheme: light; - - /* Dark */ - --dark-color-background: #2b2e33; - --dark-color-background-secondary: #1e2024; - --dark-color-icon-background: var(--dark-color-background-secondary); - --dark-color-accent: #9096a2; - --dark-color-text: #f5f5f5; - --dark-color-text-aside: #dddddd; - --dark-color-link: #00aff4; - --dark-color-ts: #ff6492; - --dark-color-ts-interface: #6cff87; - --dark-color-ts-enum: #f4d93e; - --dark-color-ts-class: #61b0ff; - --dark-color-ts-function: #9772ff; - --dark-color-ts-namespace: #e14dff; - --dark-color-ts-private: #e2e2e2; - --dark-color-ts-variable: #4d68ff; - --dark-external-icon: url("data:image/svg+xml;utf8,"); - --dark-color-scheme: dark; -} - -@media (prefers-color-scheme: light) { - :root { - --color-background: var(--light-color-background); - --color-background-secondary: var(--light-color-background-secondary); - --color-icon-background: var(--light-color-icon-background); - --color-accent: var(--light-color-accent); - --color-text: var(--light-color-text); - --color-text-aside: var(--light-color-text-aside); - --color-link: var(--light-color-link); - --color-ts: var(--light-color-ts); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-class: var(--light-color-ts-class); - --color-ts-function: var(--light-color-ts-function); - --color-ts-namespace: var(--light-color-ts-namespace); - --color-ts-private: var(--light-color-ts-private); - --color-ts-variable: var(--light-color-ts-variable); - --external-icon: var(--light-external-icon); - --color-scheme: var(--light-color-scheme); - } -} - -@media (prefers-color-scheme: dark) { - :root { - --color-background: var(--dark-color-background); - --color-background-secondary: var(--dark-color-background-secondary); - --color-icon-background: var(--dark-color-icon-background); - --color-accent: var(--dark-color-accent); - --color-text: var(--dark-color-text); - --color-text-aside: var(--dark-color-text-aside); - --color-link: var(--dark-color-link); - --color-ts: var(--dark-color-ts); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-function: var(--dark-color-ts-function); - --color-ts-namespace: var(--dark-color-ts-namespace); - --color-ts-private: var(--dark-color-ts-private); - --color-ts-variable: var(--dark-color-ts-variable); - --external-icon: var(--dark-external-icon); - --color-scheme: var(--dark-color-scheme); - } -} - -html { - color-scheme: var(--color-scheme); -} - -body { - margin: 0; -} - -:root[data-theme="light"] { - --color-background: var(--light-color-background); - --color-background-secondary: var(--light-color-background-secondary); - --color-icon-background: var(--light-color-icon-background); - --color-accent: var(--light-color-accent); - --color-text: var(--light-color-text); - --color-text-aside: var(--light-color-text-aside); - --color-link: var(--light-color-link); - --color-ts: var(--light-color-ts); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-class: var(--light-color-ts-class); - --color-ts-function: var(--light-color-ts-function); - --color-ts-namespace: var(--light-color-ts-namespace); - --color-ts-private: var(--light-color-ts-private); - --color-ts-variable: var(--light-color-ts-variable); - --external-icon: var(--light-external-icon); - --color-scheme: var(--light-color-scheme); -} - -:root[data-theme="dark"] { - --color-background: var(--dark-color-background); - --color-background-secondary: var(--dark-color-background-secondary); - --color-icon-background: var(--dark-color-icon-background); - --color-accent: var(--dark-color-accent); - --color-text: var(--dark-color-text); - --color-text-aside: var(--dark-color-text-aside); - --color-link: var(--dark-color-link); - --color-ts: var(--dark-color-ts); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-function: var(--dark-color-ts-function); - --color-ts-namespace: var(--dark-color-ts-namespace); - --color-ts-private: var(--dark-color-ts-private); - --color-ts-variable: var(--dark-color-ts-variable); - --external-icon: var(--dark-external-icon); - --color-scheme: var(--dark-color-scheme); -} - -h1, -h2, -h3, -h4, -h5, -h6 { - line-height: 1.2; -} - -h1 { - font-size: 1.875rem; - margin: 0.67rem 0; -} - -h2 { - font-size: 1.5rem; - margin: 0.83rem 0; -} - -h3 { - font-size: 1.25rem; - margin: 1rem 0; -} - -h4 { - font-size: 1.05rem; - margin: 1.33rem 0; -} - -h5 { - font-size: 1rem; - margin: 1.5rem 0; -} - -h6 { - font-size: 0.875rem; - margin: 2.33rem 0; -} - -.uppercase { - text-transform: uppercase; -} - -pre { - white-space: pre; - white-space: pre-wrap; - word-wrap: break-word; -} - -dl, -menu, -ol, -ul { - margin: 1em 0; -} - -dd { - margin: 0 0 0 40px; -} - -.container { - max-width: 1600px; - padding: 0 2rem; -} - -@media (min-width: 640px) { - .container { - padding: 0 4rem; - } -} -@media (min-width: 1200px) { - .container { - padding: 0 8rem; - } -} -@media (min-width: 1600px) { - .container { - padding: 0 12rem; - } -} - -/* Footer */ -.tsd-generator { - border-top: 1px solid var(--color-accent); - padding-top: 1rem; - padding-bottom: 1rem; - max-height: 3.5rem; -} - -.tsd-generator > p { - margin-top: 0; - margin-bottom: 0; - padding: 0 1rem; -} - -.container-main { - display: flex; - justify-content: space-between; - position: relative; - margin: 0 auto; -} - -.col-4, -.col-8 { - box-sizing: border-box; - float: left; - padding: 2rem 1rem; -} - -.col-4 { - flex: 0 0 25%; -} -.col-8 { - flex: 1 0; - flex-wrap: wrap; - padding-left: 0; -} - -@keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@keyframes fade-out { - from { - opacity: 1; - visibility: visible; - } - to { - opacity: 0; - } -} -@keyframes fade-in-delayed { - 0% { - opacity: 0; - } - 33% { - opacity: 0; - } - 100% { - opacity: 1; - } -} -@keyframes fade-out-delayed { - 0% { - opacity: 1; - visibility: visible; - } - 66% { - opacity: 0; - } - 100% { - opacity: 0; - } -} -@keyframes shift-to-left { - from { - transform: translate(0, 0); - } - to { - transform: translate(-25%, 0); - } -} -@keyframes unshift-to-left { - from { - transform: translate(-25%, 0); - } - to { - transform: translate(0, 0); - } -} -@keyframes pop-in-from-right { - from { - transform: translate(100%, 0); - } - to { - transform: translate(0, 0); - } -} -@keyframes pop-out-to-right { - from { - transform: translate(0, 0); - visibility: visible; - } - to { - transform: translate(100%, 0); - } -} -body { - background: var(--color-background); - font-family: "Segoe UI", sans-serif; - font-size: 16px; - color: var(--color-text); -} - -a { - color: var(--color-link); - text-decoration: none; -} -a:hover { - text-decoration: underline; -} -a.external[target="_blank"] { - background-image: var(--external-icon); - background-position: top 3px right; - background-repeat: no-repeat; - padding-right: 13px; -} - -code, -pre { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - padding: 0.2em; - margin: 0; - font-size: 0.875rem; - border-radius: 0.8em; -} - -pre { - padding: 10px; - border: 0.1em solid var(--color-accent); -} -pre code { - padding: 0; - font-size: 100%; -} - -blockquote { - margin: 1em 0; - padding-left: 1em; - border-left: 4px solid gray; -} - -.tsd-typography { - line-height: 1.333em; -} -.tsd-typography ul { - list-style: square; - padding: 0 0 0 20px; - margin: 0; -} -.tsd-typography h4, -.tsd-typography .tsd-index-panel h3, -.tsd-index-panel .tsd-typography h3, -.tsd-typography h5, -.tsd-typography h6 { - font-size: 1em; - margin: 0; -} -.tsd-typography h5, -.tsd-typography h6 { - font-weight: normal; -} -.tsd-typography p, -.tsd-typography ul, -.tsd-typography ol { - margin: 1em 0; -} - -@media (max-width: 1024px) { - html .col-content { - float: none; - max-width: 100%; - width: 100%; - padding-top: 3rem; - } - html .col-menu { - position: fixed !important; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - z-index: 1024; - top: 0 !important; - bottom: 0 !important; - left: auto !important; - right: 0 !important; - padding: 1.5rem 1.5rem 0 0; - max-width: 25rem; - visibility: hidden; - background-color: var(--color-background); - transform: translate(100%, 0); - } - html .col-menu > *:last-child { - padding-bottom: 20px; - } - html .overlay { - content: ""; - display: block; - position: fixed; - z-index: 1023; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.75); - visibility: hidden; - } - - .to-has-menu .overlay { - animation: fade-in 0.4s; - } - - .to-has-menu :is(header, footer, .col-content) { - animation: shift-to-left 0.4s; - } - - .to-has-menu .col-menu { - animation: pop-in-from-right 0.4s; - } - - .from-has-menu .overlay { - animation: fade-out 0.4s; - } - - .from-has-menu :is(header, footer, .col-content) { - animation: unshift-to-left 0.4s; - } - - .from-has-menu .col-menu { - animation: pop-out-to-right 0.4s; - } - - .has-menu body { - overflow: hidden; - } - .has-menu .overlay { - visibility: visible; - } - .has-menu :is(header, footer, .col-content) { - transform: translate(-25%, 0); - } - .has-menu .col-menu { - visibility: visible; - transform: translate(0, 0); - display: grid; - align-items: center; - grid-template-rows: auto 1fr; - grid-gap: 1.5rem; - max-height: 100vh; - padding: 1rem 2rem; - } - .has-menu .tsd-navigation { - max-height: 100%; - } -} - -.tsd-breadcrumb { - margin: 0; - padding: 0; - color: var(--color-text-aside); -} -.tsd-breadcrumb a { - color: var(--color-text-aside); - text-decoration: none; -} -.tsd-breadcrumb a:hover { - text-decoration: underline; -} -.tsd-breadcrumb li { - display: inline; -} -.tsd-breadcrumb li:after { - content: " / "; -} - -.tsd-comment-tags { - display: flex; - flex-direction: column; -} -dl.tsd-comment-tag-group { - display: flex; - align-items: center; - overflow: hidden; - margin: 0.5em 0; -} -dl.tsd-comment-tag-group dt { - display: flex; - margin-right: 0.5em; - font-size: 0.875em; - font-weight: normal; -} -dl.tsd-comment-tag-group dd { - margin: 0; -} -code.tsd-tag { - padding: 0.25em 0.4em; - border: 0.1em solid var(--color-accent); - margin-right: 0.25em; - font-size: 70%; -} -h1 code.tsd-tag:first-of-type { - margin-left: 0.25em; -} - -dl.tsd-comment-tag-group dd:before, -dl.tsd-comment-tag-group dd:after { - content: " "; -} -dl.tsd-comment-tag-group dd pre, -dl.tsd-comment-tag-group dd:after { - clear: both; -} -dl.tsd-comment-tag-group p { - margin: 0; -} - -.tsd-panel.tsd-comment .lead { - font-size: 1.1em; - line-height: 1.333em; - margin-bottom: 2em; -} -.tsd-panel.tsd-comment .lead:last-child { - margin-bottom: 0; -} - -.tsd-filter-visibility h4 { - font-size: 1rem; - padding-top: 0.75rem; - padding-bottom: 0.5rem; - margin: 0; -} -.tsd-filter-item:not(:last-child) { - margin-bottom: 0.5rem; -} -.tsd-filter-input { - display: flex; - width: fit-content; - width: -moz-fit-content; - align-items: center; - user-select: none; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - cursor: pointer; -} -.tsd-filter-input input[type="checkbox"] { - cursor: pointer; - position: absolute; - width: 1.5em; - height: 1.5em; - opacity: 0; -} -.tsd-filter-input input[type="checkbox"]:disabled { - pointer-events: none; -} -.tsd-filter-input svg { - cursor: pointer; - width: 1.5em; - height: 1.5em; - margin-right: 0.5em; - border-radius: 0.33em; - /* Leaving this at full opacity breaks event listeners on Firefox. - Don't remove unless you know what you're doing. */ - opacity: 0.99; -} -.tsd-filter-input input[type="checkbox"]:focus + svg { - transform: scale(0.95); -} -.tsd-filter-input input[type="checkbox"]:focus:not(:focus-visible) + svg { - transform: scale(1); -} -.tsd-checkbox-background { - fill: var(--color-accent); -} -input[type="checkbox"]:checked ~ svg .tsd-checkbox-checkmark { - stroke: var(--color-text); -} -.tsd-filter-input input:disabled ~ svg > .tsd-checkbox-background { - fill: var(--color-background); - stroke: var(--color-accent); - stroke-width: 0.25rem; -} -.tsd-filter-input input:disabled ~ svg > .tsd-checkbox-checkmark { - stroke: var(--color-accent); -} - -.tsd-theme-toggle { - padding-top: 0.75rem; -} -.tsd-theme-toggle > h4 { - display: inline; - vertical-align: middle; - margin-right: 0.75rem; -} - -.tsd-hierarchy { - list-style: square; - margin: 0; -} -.tsd-hierarchy .target { - font-weight: bold; -} - -.tsd-panel-group.tsd-index-group { - margin-bottom: 0; -} -.tsd-index-panel .tsd-index-list { - list-style: none; - line-height: 1.333em; - margin: 0; - padding: 0.25rem 0 0 0; - overflow: hidden; - display: grid; - grid-template-columns: repeat(3, 1fr); - column-gap: 1rem; - grid-template-rows: auto; -} -@media (max-width: 1024px) { - .tsd-index-panel .tsd-index-list { - grid-template-columns: repeat(2, 1fr); - } -} -@media (max-width: 768px) { - .tsd-index-panel .tsd-index-list { - grid-template-columns: repeat(1, 1fr); - } -} -.tsd-index-panel .tsd-index-list li { - -webkit-page-break-inside: avoid; - -moz-page-break-inside: avoid; - -ms-page-break-inside: avoid; - -o-page-break-inside: avoid; - page-break-inside: avoid; -} -.tsd-index-panel a, -.tsd-index-panel a.tsd-parent-kind-module { - color: var(--color-ts); -} -.tsd-index-panel a.tsd-parent-kind-interface { - color: var(--color-ts-interface); -} -.tsd-index-panel a.tsd-parent-kind-enum { - color: var(--color-ts-enum); -} -.tsd-index-panel a.tsd-parent-kind-class { - color: var(--color-ts-class); -} -.tsd-index-panel a.tsd-kind-module { - color: var(--color-ts-namespace); -} -.tsd-index-panel a.tsd-kind-interface { - color: var(--color-ts-interface); -} -.tsd-index-panel a.tsd-kind-enum { - color: var(--color-ts-enum); -} -.tsd-index-panel a.tsd-kind-class { - color: var(--color-ts-class); -} -.tsd-index-panel a.tsd-kind-function { - color: var(--color-ts-function); -} -.tsd-index-panel a.tsd-kind-namespace { - color: var(--color-ts-namespace); -} -.tsd-index-panel a.tsd-kind-variable { - color: var(--color-ts-variable); -} -.tsd-index-panel a.tsd-is-private { - color: var(--color-ts-private); -} - -.tsd-flag { - display: inline-block; - padding: 0.25em 0.4em; - border-radius: 4px; - color: var(--color-comment-tag-text); - background-color: var(--color-comment-tag); - text-indent: 0; - font-size: 75%; - line-height: 1; - font-weight: normal; -} - -.tsd-anchor { - position: absolute; - top: -100px; -} - -.tsd-member { - position: relative; -} -.tsd-member .tsd-anchor + h3 { - display: flex; - align-items: center; - margin-top: 0; - margin-bottom: 0; - border-bottom: none; -} -.tsd-member [data-tsd-kind] { - color: var(--color-ts); -} -.tsd-member [data-tsd-kind="Interface"] { - color: var(--color-ts-interface); -} -.tsd-member [data-tsd-kind="Enum"] { - color: var(--color-ts-enum); -} -.tsd-member [data-tsd-kind="Class"] { - color: var(--color-ts-class); -} -.tsd-member [data-tsd-kind="Private"] { - color: var(--color-ts-private); -} - -.tsd-navigation a { - display: block; - margin: 0.4rem 0; - border-left: 2px solid transparent; - color: var(--color-text); - text-decoration: none; - transition: border-left-color 0.1s; -} -.tsd-navigation a:hover { - text-decoration: underline; -} -.tsd-navigation ul { - margin: 0; - padding: 0; - list-style: none; -} -.tsd-navigation li { - padding: 0; -} - -.tsd-navigation.primary .tsd-accordion-details > ul { - margin-top: 0.75rem; -} -.tsd-navigation.primary a { - padding: 0.75rem 0.5rem; - margin: 0; -} -.tsd-navigation.primary ul li a { - margin-left: 0.5rem; -} -.tsd-navigation.primary ul li li a { - margin-left: 1.5rem; -} -.tsd-navigation.primary ul li li li a { - margin-left: 2.5rem; -} -.tsd-navigation.primary ul li li li li a { - margin-left: 3.5rem; -} -.tsd-navigation.primary ul li li li li li a { - margin-left: 4.5rem; -} -.tsd-navigation.primary ul li li li li li li a { - margin-left: 5.5rem; -} -.tsd-navigation.primary li.current > a { - border-left: 0.15rem var(--color-text) solid; -} -.tsd-navigation.primary li.selected > a { - font-weight: bold; - border-left: 0.2rem var(--color-text) solid; -} -.tsd-navigation.primary ul li a:hover { - border-left: 0.2rem var(--color-text-aside) solid; -} -.tsd-navigation.primary li.globals + li > span, -.tsd-navigation.primary li.globals + li > a { - padding-top: 20px; -} - -.tsd-navigation.secondary.tsd-navigation--toolbar-hide { - max-height: calc(100vh - 1rem); - top: 0.5rem; -} -.tsd-navigation.secondary > ul { - display: inline; - padding-right: 0.5rem; - transition: opacity 0.2s; -} -.tsd-navigation.secondary ul li a { - padding-left: 0; -} -.tsd-navigation.secondary ul li li a { - padding-left: 1.1rem; -} -.tsd-navigation.secondary ul li li li a { - padding-left: 2.2rem; -} -.tsd-navigation.secondary ul li li li li a { - padding-left: 3.3rem; -} -.tsd-navigation.secondary ul li li li li li a { - padding-left: 4.4rem; -} -.tsd-navigation.secondary ul li li li li li li a { - padding-left: 5.5rem; -} - -a.tsd-index-link { - margin: 0.25rem 0; - font-size: 1rem; - line-height: 1.25rem; - display: inline-flex; - align-items: center; -} -.tsd-accordion-summary > h1, -.tsd-accordion-summary > h2, -.tsd-accordion-summary > h3, -.tsd-accordion-summary > h4, -.tsd-accordion-summary > h5 { - display: inline-flex; - align-items: center; - vertical-align: middle; - margin-bottom: 0; - user-select: none; - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; -} -.tsd-accordion-summary { - display: block; - cursor: pointer; -} -.tsd-accordion-summary > * { - margin-top: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; -} -.tsd-accordion-summary::-webkit-details-marker { - display: none; -} -.tsd-index-accordion .tsd-accordion-summary svg { - margin-right: 0.25rem; -} -.tsd-index-content > :not(:first-child) { - margin-top: 0.75rem; -} -.tsd-index-heading { - margin-top: 1.5rem; - margin-bottom: 0.75rem; -} - -.tsd-kind-icon { - margin-right: 0.5rem; - width: 1.25rem; - height: 1.25rem; - min-width: 1.25rem; - min-height: 1.25rem; -} -.tsd-kind-icon path { - transform-origin: center; - transform: scale(1.1); -} -.tsd-signature > .tsd-kind-icon { - margin-right: 0.8rem; -} - -@media (min-width: 1024px) { - .col-content { - margin: 2rem auto; - } - - .menu-sticky-wrap { - position: sticky; - height: calc(100vh - 2rem); - top: 4rem; - right: 0; - padding: 0 1.5rem; - padding-top: 1rem; - margin-top: 3rem; - transition: 0.3s ease-in-out; - transition-property: top, padding-top, padding, height; - overflow-y: auto; - } - .col-menu { - border-left: 1px solid var(--color-accent); - } - .col-menu--hide { - top: 1rem; - } - .col-menu .tsd-navigation:not(:last-child) { - padding-bottom: 1.75rem; - } -} - -.tsd-panel { - margin-bottom: 2.5rem; -} -.tsd-panel.tsd-member { - margin-bottom: 4rem; -} -.tsd-panel:empty { - display: none; -} -.tsd-panel > h1, -.tsd-panel > h2, -.tsd-panel > h3 { - margin: 1.5rem -1.5rem 0.75rem -1.5rem; - padding: 0 1.5rem 0.75rem 1.5rem; -} -.tsd-panel > h1.tsd-before-signature, -.tsd-panel > h2.tsd-before-signature, -.tsd-panel > h3.tsd-before-signature { - margin-bottom: 0; - border-bottom: none; -} - -.tsd-panel-group { - margin: 4rem 0; -} -.tsd-panel-group.tsd-index-group { - margin: 2rem 0; -} -.tsd-panel-group.tsd-index-group details { - margin: 2rem 0; -} - -#tsd-search { - transition: background-color 0.2s; -} -#tsd-search .title { - position: relative; - z-index: 2; -} -#tsd-search .field { - position: absolute; - left: 0; - top: 0; - right: 2.5rem; - height: 100%; -} -#tsd-search .field input { - box-sizing: border-box; - position: relative; - top: -50px; - z-index: 1; - width: 100%; - padding: 0 10px; - opacity: 0; - outline: 0; - border: 0; - background: transparent; - color: var(--color-text); -} -#tsd-search .field label { - position: absolute; - overflow: hidden; - right: -40px; -} -#tsd-search .field input, -#tsd-search .title { - transition: opacity 0.2s; -} -#tsd-search .results { - position: absolute; - visibility: hidden; - top: 40px; - width: 100%; - margin: 0; - padding: 0; - list-style: none; - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); -} -#tsd-search .results li { - padding: 0 10px; - background-color: var(--color-background); -} -#tsd-search .results li:nth-child(even) { - background-color: var(--color-background-secondary); -} -#tsd-search .results li.state { - display: none; -} -#tsd-search .results li.current, -#tsd-search .results li:hover { - background-color: var(--color-accent); -} -#tsd-search .results a { - display: block; -} -#tsd-search .results a:before { - top: 10px; -} -#tsd-search .results span.parent { - color: var(--color-text-aside); - font-weight: normal; -} -#tsd-search.has-focus { - background-color: var(--color-accent); -} -#tsd-search.has-focus .field input { - top: 0; - opacity: 1; -} -#tsd-search.has-focus .title { - z-index: 0; - opacity: 0; -} -#tsd-search.has-focus .results { - visibility: visible; -} -#tsd-search.loading .results li.state.loading { - display: block; -} -#tsd-search.failure .results li.state.failure { - display: block; -} - -.tsd-signature { - margin: 0 0 1rem 0; - padding: 1rem 0.5rem; - border: 1px solid var(--color-accent); - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - font-size: 14px; - overflow-x: auto; -} - -.tsd-signature-symbol { - color: var(--color-text-aside); - font-weight: normal; -} - -.tsd-signature-type { - font-style: italic; - font-weight: normal; -} - -.tsd-signatures { - padding: 0; - margin: 0 0 1em 0; - list-style-type: none; -} -.tsd-signatures .tsd-signature { - margin: 0; - border-color: var(--color-accent); - border-width: 1px 0; - transition: background-color 0.1s; -} -.tsd-description .tsd-signatures .tsd-signature { - border-width: 1px; -} - -ul.tsd-parameter-list, -ul.tsd-type-parameter-list { - list-style: square; - margin: 0; - padding-left: 20px; -} -ul.tsd-parameter-list > li.tsd-parameter-signature, -ul.tsd-type-parameter-list > li.tsd-parameter-signature { - list-style: none; - margin-left: -20px; -} -ul.tsd-parameter-list h5, -ul.tsd-type-parameter-list h5 { - font-size: 16px; - margin: 1em 0 0.5em 0; -} -.tsd-sources { - margin-top: 1rem; - font-size: 0.875em; -} -.tsd-sources a { - color: var(--color-text-aside); - text-decoration: underline; -} -.tsd-sources ul { - list-style: none; - padding: 0; -} - -.tsd-page-toolbar { - position: fixed; - z-index: 1; - top: 0; - left: 0; - width: 100%; - color: var(--color-text); - background: var(--color-background-secondary); - border-bottom: 1px var(--color-accent) solid; - transition: transform 0.3s ease-in-out; -} -.tsd-page-toolbar a { - color: var(--color-text); - text-decoration: none; -} -.tsd-page-toolbar a.title { - font-weight: bold; -} -.tsd-page-toolbar a.title:hover { - text-decoration: underline; -} -.tsd-page-toolbar .tsd-toolbar-contents { - display: flex; - justify-content: space-between; - height: 2.5rem; -} -.tsd-page-toolbar .table-cell { - position: relative; - white-space: nowrap; - line-height: 40px; -} -.tsd-page-toolbar .table-cell:first-child { - width: 100%; -} - -.tsd-page-toolbar--hide { - transform: translateY(-100%); -} - -.tsd-widget { - display: inline-block; - overflow: hidden; - opacity: 0.8; - height: 40px; - transition: opacity 0.1s, background-color 0.2s; - vertical-align: bottom; - cursor: pointer; -} -.tsd-widget:hover { - opacity: 0.9; -} -.tsd-widget.active { - opacity: 1; - background-color: var(--color-accent); -} -.tsd-widget.no-caption { - width: 40px; -} -.tsd-widget.no-caption:before { - margin: 0; -} - -.tsd-widget.options, -.tsd-widget.menu { - display: none; -} -@media (max-width: 1024px) { - .tsd-widget.options, - .tsd-widget.menu { - display: inline-block; - } -} -input[type="checkbox"] + .tsd-widget:before { - background-position: -120px 0; -} -input[type="checkbox"]:checked + .tsd-widget:before { - background-position: -160px 0; -} - -img { - max-width: 100%; -} - -.tsd-anchor-icon { - display: inline-flex; - align-items: center; - margin-left: 0.5rem; - vertical-align: middle; - color: var(--color-text); -} - -.tsd-anchor-icon svg { - width: 1em; - height: 1em; - visibility: hidden; -} - -.tsd-anchor-link:hover > .tsd-anchor-icon svg { - visibility: visible; -} - -.deprecated { - text-decoration: line-through; -} - -* { - scrollbar-width: thin; - scrollbar-color: var(--color-accent) var(--color-icon-background); -} - -*::-webkit-scrollbar { - width: 0.75rem; -} - -*::-webkit-scrollbar-track { - background: var(--color-icon-background); -} - -*::-webkit-scrollbar-thumb { - background-color: var(--color-accent); - border-radius: 999rem; - border: 0.25rem solid var(--color-icon-background); -} diff --git a/docs/assets/widgets.png b/docs/assets/widgets.png deleted file mode 100644 index c7380532ac1b45400620011c37c4dcb7aec27a4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 480 zcmeAS@N?(olHy`uVBq!ia0y~yU~~YoH8@y+q^jrZML>b&o-U3d6^w6h1+IPUz|;DW zIZ;96kdsD>Qv^q=09&hp0GpEni<1IR%gvP3v%OR9*{MuRTKWHZyIbuBt)Ci`cU_&% z1T+i^Y)o{%281-<3TpPAUTzw5v;RY=>1rvxmPl96#kYc9hX!6V^nB|ad#(S+)}?8C zr_H+lT3B#So$T=?$(w3-{rbQ4R<@nsf$}$hwSO)A$8&`(j+wQf=Jwhb0`CvhR5DCf z^OgI)KQemrUFPH+UynC$Y~QHG%DbTVh-Skz{enNU)cV_hPu~{TD7TPZl>0&K>iuE| z7AYn$7)Jrb9GE&SfQW4q&G*@N|4cHI`VakFa5-C!ov&XD)J(qp$rJJ*9e z-sHv}#g*T7Cv048d1v~BEAzM5FztAse#q78WWC^BUCzQ U&wLp6h6BX&boFyt=akR{0G%$)mH+?% diff --git a/docs/assets/widgets@2x.png b/docs/assets/widgets@2x.png deleted file mode 100644 index 4bbbd57272f3b28f47527d4951ad10f950b8ad43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 855 zcmeAS@N?(olHy`uVBq!ia0y~yU}^xe12~w0Jcmn z@(X6T|9^jgLcx21{)7exgY)a>N6m2F0<`Rqr;B4q1>>88jUdw-7W`c)zLE*mq8W2H z-<&Jl_Hco5BuC5n@AbF5GD82~-e8-v=#zCyUX0F-o}8pPfAv`!GN$ff+TL<~@kgt} z62eO?_|&+>xBmM$@p|z`tIKEdpPf8%qI>4r7@jn<=eta*{3~?g(zz{Ke9zc-G^gr? z-7foa?LcS!hmbwzru}ICvbWLlW8;+l-}!^=c32!^nV`+`C*;0-*Y%l94pC;Cb3GXz zzSf%a!{gVr{Y_lVuUj+a)*Ca+!-Hu%xmP&&X-2CuANY8^i{D7Kg6qzP zXz_ps9+lN8ESH{K4`yu&b~I>N9xGlE&;2u*b?+Go!AhN?m-bxlLvtC#MzDF2kFzfHJ1W7ybqdefSqVhbOykd*Yi%EDuhs z4wF{ft^bv2+DDnKb8gj1FuvcV`M}luS>lO<^)8x>y1#R;a=-ZKwWTQQb)ioBbi;zh zD!f5V)8581to1LL7c9!l^PSC$NBPYif!_vAZhmL4)v4U)4UsrLYiH_9rmQDd?)(e5 z^pcH>qvBg*i0dus2r*mp4;zKvu=P#s-ti;2obl`NjjwoYd>e(oo#j_uyRb<7Pv^If zzZ|mGHmV)8^tbO%^>eqMw(@7(&3g{jEp-Najo7V75xI_ZHK*FA`elF{r5}E*d7+j_R diff --git a/docs/classes/AttributeReference.html b/docs/classes/AttributeReference.html deleted file mode 100644 index a582bfffc9..0000000000 --- a/docs/classes/AttributeReference.html +++ /dev/null @@ -1,197 +0,0 @@ -AttributeReference | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Hierarchy

-
    -
  • AttributeReference
-
-
-
- -
-
-

Constructors

-
-
-

Properties

-
-
-

Accessors

-
-
-

Methods

-
-
-

Constructors

-
- -
    - -
  • -

    Take an attribute reference string, or literal string, and produce -an attribute reference.

    -

    Legacy user objects would have been created with names not -references. So, in that case, we need to use them as a component -without escaping them.

    -

    e.g. A user could contain a custom attribute of /a which would -become the literal a if treated as a reference. Which would cause -it to no longer be redacted.

    -
    -
    -

    Parameters

    -
      -
    • -
      refOrLiteral: string
      -

      The attribute reference string or literal string.

      -
    • -
    • -
      Optional literal: boolean
      -

      it true the value should be treated as a literal.

      -
    -

    Returns AttributeReference

-
-

Properties

-
- -
components: any
-
- -
isValid: boolean
-
- -
redactionName: string
-

When redacting attributes this name can be directly added to the list of -redactions.

-
-
-

Accessors

-
- -
    -
  • get depth(): number
  • -
  • -

    Returns number

-
- -
    -
  • get isKind(): boolean
  • -
  • -

    Returns boolean

-
-

Methods

-
- -
-
- -
-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      depth: number
    -

    Returns string

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/BasicLogger.html b/docs/classes/BasicLogger.html deleted file mode 100644 index e4b901a802..0000000000 --- a/docs/classes/BasicLogger.html +++ /dev/null @@ -1,223 +0,0 @@ -BasicLogger | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

A basic logger which handles filtering by level.

-

With the default options it will write to console.error -and it will use the formatting provided by console.error. -If the destination is overwritten, then it will use an included -formatter similar to util.format.

-

If a formatter is available, then that should be overridden -as well for performance.

-
-
-

Hierarchy

-
    -
  • BasicLogger
-
-

Implements

-
-
-
-
- -
-
-

Constructors

-
-
-

Properties

-
-
-

Methods

-
-
-

Constructors

-
- -
-
-

Properties

-
- -
destination?: any
-
- -
formatter?: any
-
- -
log: any
-
- -
logLevel: any
-
- -
tryFormat: any
-
- -
tryWrite: any
-
-

Methods

-
- -
    - -
  • -

    The debug logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
- -
    - -
  • -

    The error logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
- -
    - -
  • -

    The info logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
- -
    - -
  • -

    The warning logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/BigSegmentStoreStatusProviderImpl.html b/docs/classes/BigSegmentStoreStatusProviderImpl.html deleted file mode 100644 index 3b8489f31a..0000000000 --- a/docs/classes/BigSegmentStoreStatusProviderImpl.html +++ /dev/null @@ -1,140 +0,0 @@ -BigSegmentStoreStatusProviderImpl | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Class BigSegmentStoreStatusProviderImpl

-
-

An interface for querying the status of a Big Segment store.

-

The Big Segment store is the component that receives information about Big Segments, normally -from a database populated by the LaunchDarkly Relay Proxy. Big Segments are a specific type of -user segments. For more information, read the LaunchDarkly documentation: -https://docs.launchdarkly.com/home/users/big-segments

-

An implementation of this interface is returned by -LDClient.bigSegmentStoreStatusProvider. Application code never needs to implement this -interface.

-
-
-

Hierarchy

-
    -
  • BigSegmentStoreStatusProviderImpl
-
-

Implements

-
-
-
-
- -
-
-

Constructors

-
-
-

Methods

-
-
-

Constructors

-
- -
-
-

Methods

-
- -
-
- -
-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/Context.html b/docs/classes/Context.html deleted file mode 100644 index 931d773170..0000000000 --- a/docs/classes/Context.html +++ /dev/null @@ -1,363 +0,0 @@ -Context | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Container for a context/contexts. Because contexts come from external code -they must be thoroughly validated and then formed to comply with -the type system.

-
-
-

Hierarchy

-
    -
  • Context
-
-
-
- -
-
-

Constructors

-
- -
    - -
  • -

    Contexts should be created using the static factory method fromLDContext.

    -
    -

    Returns Context

-
-

Properties

-
- -
context?: any
-
- -
contextForKind: any
-
- -
contexts: any
-
- -
isMulti: any
-
- -
isUser: any
-
- -
kind: string
-
- -
privateAttributeReferences?: any
-
- -
wasLegacy: any
-
- -
fromLegacyUser: any
-
- -
fromMultiKindContext: any
-
- -
fromSingleKindContext: any
-
- -
getValueFromContext: any
-
- -
userKind: string
-
-

Accessors

-
- -
    -
  • get canonicalKey(): string
  • -
  • -

    Get the canonical key for this context.

    -
    -

    Returns string

-
- -
    -
  • get isMultiKind(): boolean
  • -
  • -

    True if this is a multi-kind context.

    -
    -

    Returns boolean

-
- -
    -
  • get kinds(): string[]
  • -
  • -

    Get the kinds of this context.

    -
    -

    Returns string[]

-
- -
    -
  • get kindsAndKeys(): Record<string, string>
  • -
  • -

    Get the kinds, and their keys, for this context.

    -
    -

    Returns Record<string, string>

-
- -
    -
  • get legacy(): boolean
  • -
  • -

    Returns boolean

-
-

Methods

-
- -
    - -
  • -

    Get the underlying context objects from this context.

    -

    This method is intended to be used in event generation.

    -

    The returned objects should not be modified.

    -
    -

    Returns [string, LDContextCommon][]

-
- -
    - -
  • -

    Attempt to get a key for the specified kind.

    - -

    Returns

    The key for the specified kind, or undefined.

    -
    -
    -

    Parameters

    -
      -
    • -
      Optional kind: string
      -

      The kind to get a key for.

      -
    -

    Returns undefined | string

-
- -
    - -
  • -

    Get the attribute references.

    -
    -
    -

    Parameters

    -
      -
    • -
      kind: string
    -

    Returns AttributeReference[]

-
- -
    - -
  • -

    Attempt to get a secondary key from a context.

    - -

    Returns

    the secondary key, or undefined if not present or not a string.

    -
    -
    -

    Parameters

    -
      -
    • -
      Optional kind: string
      -

      The kind of the context to get the secondary key for.

      -
    -

    Returns undefined | string

-
- -
    - -
  • -

    Attempt to get a value for the given context kind using the given reference.

    - -

    Returns

    a value or undefined if one is not found.

    -
    -
    -

    Parameters

    -
      -
    • -
      reference: AttributeReference
      -

      The reference to the value to get.

      -
    • -
    • -
      Optional kind: string
      -

      The kind of the context to get the value for.

      -
    -

    Returns any

-
- -
    - -
  • -

    Attempt to create a Context from an LDContext.

    - -

    Returns

    a Context or undefined if one could not be created.

    -
    -
    -

    Parameters

    -
      -
    • -
      context: LDContext
      -

      The input context to create a Context from.

      -
    -

    Returns undefined | Context

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/ContextFilter.html b/docs/classes/ContextFilter.html deleted file mode 100644 index c9580527c9..0000000000 --- a/docs/classes/ContextFilter.html +++ /dev/null @@ -1,133 +0,0 @@ -ContextFilter | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Hierarchy

-
    -
  • ContextFilter
-
-
-
- -
-
-

Constructors

-
- -
-
-

Properties

-
- -
allAttributesPrivate: any
-
- -
filterSingleKind: any
-
- -
getAttributesToFilter: any
-
- -
privateAttributes: any
-
-

Methods

-
- -
    - -
  • -
    -

    Parameters

    -
    -

    Returns any

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/DateValidator.html b/docs/classes/DateValidator.html deleted file mode 100644 index 976af26d63..0000000000 --- a/docs/classes/DateValidator.html +++ /dev/null @@ -1,111 +0,0 @@ -DateValidator | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Validate a value is a date. Values which are numbers are treated as dates and any string -which if compliant with time.RFC3339Nano is a date.

-
-
-

Hierarchy

-
    -
  • DateValidator
-
-

Implements

-
-
-
-
- -
-
-

Constructors

-
-
-

Methods

-
-
-

Constructors

-
- -
-
-

Methods

-
- -
    - -
  • -

    Returns string

-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      u: unknown
    -

    Returns boolean

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/FactoryOrInstance.html b/docs/classes/FactoryOrInstance.html deleted file mode 100644 index 7c1627ae32..0000000000 --- a/docs/classes/FactoryOrInstance.html +++ /dev/null @@ -1,110 +0,0 @@ -FactoryOrInstance | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Validate a factory or instance.

-
-
-

Hierarchy

-
    -
  • FactoryOrInstance
-
-

Implements

-
-
-
-
- -
-
-

Constructors

-
-
-

Methods

-
-
-

Constructors

-
- -
-
-

Methods

-
- -
    - -
  • -

    Returns string

-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      factoryOrInstance: unknown
    -

    Returns boolean

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/Function.html b/docs/classes/Function.html deleted file mode 100644 index ff7a6b127e..0000000000 --- a/docs/classes/Function.html +++ /dev/null @@ -1,110 +0,0 @@ -Function | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Validate a value is a function.

-
-
-

Hierarchy

-
    -
  • Function
-
-

Implements

-
-
-
-
- -
-
-

Constructors

-
-
-

Methods

-
-
-

Constructors

-
- -
-
-

Methods

-
- -
    - -
  • -

    Returns string

-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      u: unknown
    -

    Returns u is ((...args: any[]) => void)

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/LDClientImpl.html b/docs/classes/LDClientImpl.html deleted file mode 100644 index 07c7b13609..0000000000 --- a/docs/classes/LDClientImpl.html +++ /dev/null @@ -1,547 +0,0 @@ -LDClientImpl | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

The LaunchDarkly SDK client object.

-

Create this object with init. Applications should configure the client at startup time and -continue to use it throughout the lifetime of the application, rather than creating instances on -the fly.

-
-
-

Hierarchy

-
    -
  • LDClientImpl
-
-

Implements

-
-
-
-
- -
-
-

Constructors

-
- -
-
-

Properties

-
- -
bigSegmentStoreStatusProvider: BigSegmentStoreStatusProviderImpl = ...
-

Intended for use by platform specific client implementations.

-

It is not included in the main interface because it requires the use of -a platform event system. For node this would be an EventEmitter, for other -platforms it would likely be an EventTarget.

-
-
- -
initState: InitState = InitState.Initializing
-
- -
platform: Platform
-
-

Methods

-
- -
    - -
  • -

    Builds an object that encapsulates the state of all feature flags for a given context. -This includes the flag values and also metadata that can be used on the front end. This -method does not send analytics events back to LaunchDarkly.

    -

    The most common use case for this method is to bootstrap a set of client-side -feature flags from a back-end service. Call the toJSON() method of the returned object -to convert it to the data structure used by the client-side SDK.

    - -

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved - with the result as an LDFlagsState.

    -
    -
    -

    Parameters

    -
      -
    • -
      context: LDContext
      -

      The context requesting the feature flags.

      -
    • -
    • -
      Optional options: LDFlagsStateOptions
      -

      Optional LDFlagsStateOptions to determine how the state is computed.

      -
    • -
    • -
      Optional callback: ((err: Error, res: LDFlagsState) => void)
      -

      A Node-style callback to receive the result (as an LDFlagsState). If omitted, you - will receive a Promise instead.

      -
      -
    -

    Returns Promise<LDFlagsState>

-
- -
    - -
  • -

    Discards all network connections, background tasks, and other resources held by the client.

    -

    Do not attempt to use the client after calling this method.

    -
    -

    Returns void

-
- -
    - -
  • -

    Flushes all pending analytics events.

    -

    Normally, batches of events are delivered in the background at intervals determined by the -flushInterval property of LDOptions. Calling flush() triggers an immediate delivery. -However, like Node I/O in general, this is still an asynchronous operation so you must still -use Promise chaining, a callback, or async/await to detect when it has finished or failed.

    - -

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which resolves once - flushing is finished. Note that the Promise will be rejected if the HTTP request - fails, so be sure to attach a rejection handler to it.

    -
    -
    -

    Parameters

    -
      -
    • -
      Optional callback: ((err: Error, res: boolean) => void)
      -

      A function which will be called when the flush completes (meaning that all pending events - have been delivered to LaunchDarkly). If omitted, you will receive a Promise instead.

      -
      -
        -
      • -
          -
        • (err: Error, res: boolean): void
        • -
        • -
          -

          Parameters

          -
            -
          • -
            err: Error
          • -
          • -
            res: boolean
          -

          Returns void

    -

    Returns Promise<void>

-
- -
    - -
  • -

    Identifies a context to LaunchDarkly.

    -

    This simply creates an analytics event that will transmit the given user properties to -LaunchDarkly, so that the context will be visible on your dashboard even if you have not -evaluated any flags for that user. It has no other effect.

    -

    If the context is omitted or has no key, the client will log a warning -and will not send an event.

    -
    -
    -

    Parameters

    -
      -
    • -
      context: LDContext
      -

      The context properties. Must contain at least the key property.

      -
    -

    Returns void

-
- -
    - -
  • -

    Tests whether the client has completed initialization.

    -

    If this returns false, it means that the client has not yet successfully connected to -LaunchDarkly. It might still be in the process of starting up, or it might be attempting to -reconnect after an unsuccessful attempt, or it might have received an unrecoverable error (such -as an invalid SDK key) and given up.

    - -

    Returns

    True if the client has successfully initialized.

    -
    -

    Returns boolean

-
- -
-
- -
    - -
  • -

    Registers an event listener that will be called when the client triggers some type of event.

    -
      -
    • "ready": Sent only once, when the client has successfully connected to LaunchDarkly. -Alternately, you can detect this with waitForInitialization.
    • -
    • "failed": Sent only once, if the client has permanently failed to connect to LaunchDarkly. -Alternately, you can detect this with waitForInitialization.
    • -
    • "error": Contains an error object describing some abnormal condition that the client has -detected (such as a network error).
    • -
    • "update": The client has received a change to a feature flag. The event parameter is an -object containing a single property, key, the flag key. Note that this does not necessarily -mean the flag's value has changed for any particular context, only that some part of the flag -configuration was changed.
    • -
    • "update:KEY": The client has received a change to the feature flag whose key is KEY. This -is the same as "update" but allows you to listen for a specific flag.
    • -
    -
    -
    -

    Parameters

    -
      -
    • -
      event: string | symbol
      -

      the name of the event to listen for

      -
    • -
    • -
      listener: ((...args: any[]) => void)
      -

      the function to call when the event happens

      -
      -
        -
      • -
          -
        • (...args: any[]): void
        • -
        • -
          -

          Parameters

          -
            -
          • -
            Rest ...args: any[]
          -

          Returns void

    -

    Returns LDClientImpl

-
- -
-
- -
    - -
  • -

    Tracks that a context performed an event.

    -

    LaunchDarkly automatically tracks pageviews and clicks that are specified in the Goals section -of the dashboard. This can be used to track custom goals or other events that do not currently -have goals.

    -

    Note that event delivery is asynchronous, so the event may not actually be sent until later; -see flush.

    -

    If the context is omitted or has no key, the client will log a warning and will not send an -event.

    -
    -
    -

    Parameters

    -
      -
    • -
      key: string
      -

      The name of the event, which may correspond to a goal in A/B tests.

      -
    • -
    • -
      context: LDContext
      -

      The context to track.

      -
    • -
    • -
      Optional data: any
      -

      Optional additional information to associate with the event.

      -
    • -
    • -
      Optional metricValue: number
      -

      A numeric value used by the LaunchDarkly experimentation feature in numeric - custom metrics. Can be omitted if this event is used by only non-numeric metrics. This field - will also be returned as part of the custom event for Data Export.

      -
    -

    Returns void

-
- -
    - -
  • -

    Determines the variation of a feature flag for a context.

    - -

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved with - the result value.

    -
    -
    -

    Parameters

    -
      -
    • -
      key: string
      -

      The unique key of the feature flag.

      -
    • -
    • -
      context: LDContext
      -

      The context requesting the flag. The client will generate an analytics event to - register this context with LaunchDarkly if the context does not already exist.

      -
    • -
    • -
      defaultValue: any
      -

      The default value of the flag, to be used if the value is not available - from LaunchDarkly.

      -
    • -
    • -
      Optional callback: ((err: any, res: any) => void)
      -

      A Node-style callback to receive the result value. If omitted, you will receive - a Promise instead.

      -
      -
        -
      • -
          -
        • (err: any, res: any): void
        • -
        • -
          -

          Parameters

          -
            -
          • -
            err: any
          • -
          • -
            res: any
          -

          Returns void

    -

    Returns Promise<any>

-
- -
    - -
  • -

    Determines the variation of a feature flag for a context, along with information about how it -was calculated.

    -

    The reason property of the result will also be included in analytics events, if you are -capturing detailed event data for this flag.

    -

    For more information, see the SDK reference -guide.

    - -

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved with - the result (as an LDEvaluationDetail).

    -
    -
    -

    Parameters

    -
      -
    • -
      key: string
      -

      The unique key of the feature flag.

      -
    • -
    • -
      context: LDContext
      -

      The context requesting the flag. The client will generate an analytics event to - register this context with LaunchDarkly if the context does not already exist.

      -
    • -
    • -
      defaultValue: any
      -

      The default value of the flag, to be used if the value is not available - from LaunchDarkly.

      -
    • -
    • -
      Optional callback: ((err: any, res: LDEvaluationDetail) => void)
      -

      A Node-style callback to receive the result (as an LDEvaluationDetail). If - omitted, you will receive a Promise instead.

      -
      -
    -

    Returns Promise<LDEvaluationDetail>

-
- -
    - -
  • -

    Returns a Promise that tracks the client's initialization state.

    -

    The Promise will be resolved if the client successfully initializes, or rejected if client -initialization has failed unrecoverably (for instance, if it detects that the SDK key is -invalid). Keep in mind that unhandled Promise rejections can be fatal in Node, so if you call -this method, be sure to attach a rejection handler to it (or, if using async/await, a catch -block).

    -

    Note that you can also use event listeners (on) for the same purpose: the event "ready" -indicates success, and "failed" indicates failure.

    -

    There is no built-in timeout for this method. If you want your code to stop waiting on the -Promise after some amount of time, you could use -https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race|Promise.race() -or one of the several NPM helper packages that provides a standard mechanism for this. -Regardless of whether you continue to wait, the SDK will still retry all connection failures -indefinitely unless it gets an unrecoverable error as described above.

    - -

    Returns

    A Promise that will be resolved if the client initializes successfully, or rejected if it - fails. If successful, the result is the same client object.

    - -

    Example

    This example shows use of Promise chaining methods for specifying handlers:

    -
      client.waitForInitialization().then(() => {
    // do whatever is appropriate if initialization has succeeded
    }).catch(err => {
    // do whatever is appropriate if initialization has failed
    }) -
    - -

    Example

    This example shows use of async/await syntax for specifying handlers:

    -
      try {
    await client.waitForInitialization();
    // do whatever is appropriate if initialization has succeeded
    } catch (err) {
    // do whatever is appropriate if initialization has failed
    } -
    -
    -

    Returns Promise<LDClient>

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/NumberWithMinimum.html b/docs/classes/NumberWithMinimum.html deleted file mode 100644 index 1d133f4bbf..0000000000 --- a/docs/classes/NumberWithMinimum.html +++ /dev/null @@ -1,136 +0,0 @@ -NumberWithMinimum | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Validate a value is a number and is greater or eval than a minimum.

-
-
-

Hierarchy

-
    -
  • Type<number> -
      -
    • NumberWithMinimum
-
-
-
- -
-
-

Constructors

-
-
-

Properties

-
-
-

Methods

-
-
-

Constructors

-
- -
-
-

Properties

-
- -
min: number
-
- -
typeOf: string
-
-

Methods

-
- -
    - -
  • -

    Returns string

-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      u: unknown
    -

    Returns u is number

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/SafeLogger.html b/docs/classes/SafeLogger.html deleted file mode 100644 index fa2f10d9ea..0000000000 --- a/docs/classes/SafeLogger.html +++ /dev/null @@ -1,212 +0,0 @@ -SafeLogger | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

The safeLogger logic exists because we allow the application to pass in a custom logger, but -there is no guarantee that the logger works correctly and if it ever throws exceptions there -could be serious consequences (e.g. an uncaught exception within an error event handler, due -to the SDK trying to log the error, can terminate the application). An exception could result -from faulty logic in the logger implementation, or it could be that this is not a logger at -all but some other kind of object; the former is handled by a catch block that logs an error -message to the SDK's default logger, and we can at least partly guard against the latter by -checking for the presence of required methods at configuration time.

-
-
-

Hierarchy

-
    -
  • SafeLogger
-
-

Implements

-
-
-
-
- -
-
-

Constructors

-
-
-

Properties

-
-
-

Methods

-
-
-

Constructors

-
- -
    - -
  • -

    Construct a safe logger with the specified logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      logger: LDLogger
      -

      The logger to use.

      -
    • -
    • -
      fallback: LDLogger
      -

      A fallback logger to use in case an issue is encountered using -the provided logger.

      -
    -

    Returns SafeLogger

-
-

Properties

-
- -
fallback: any
-
- -
log: any
-
- -
logger: any
-
-

Methods

-
- -
    - -
  • -

    The debug logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
- -
    - -
  • -

    The error logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
- -
    - -
  • -

    The info logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
- -
    - -
  • -

    The warning logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/StringMatchingRegex.html b/docs/classes/StringMatchingRegex.html deleted file mode 100644 index 9051c3175d..0000000000 --- a/docs/classes/StringMatchingRegex.html +++ /dev/null @@ -1,136 +0,0 @@ -StringMatchingRegex | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Validate a value is a string and it matches the given expression.

-
-
-

Hierarchy

-
    -
  • Type<string> -
      -
    • StringMatchingRegex
-
-
-
- -
-
-

Constructors

-
-
-

Properties

-
-
-

Methods

-
-
-

Constructors

-
- -
-
-

Properties

-
- -
expression: RegExp
-
- -
typeOf: string
-
-

Methods

-
- -
    - -
  • -

    Returns string

-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      u: unknown
    -

    Returns u is string

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/Type.html b/docs/classes/Type.html deleted file mode 100644 index 7709fb8f5d..0000000000 --- a/docs/classes/Type.html +++ /dev/null @@ -1,151 +0,0 @@ -Type | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Validate a basic type.

-
-
-

Type Parameters

-
    -
  • -

    T

-
-

Hierarchy

-
-
-

Implements

-
-
-
-
- -
-
-

Constructors

-
-
-

Properties

-
-
-

Methods

-
-
-

Constructors

-
- -
    - -
  • -
    -

    Type Parameters

    -
      -
    • -

      T

    -
    -

    Parameters

    -
      -
    • -
      typeName: string
    • -
    • -
      example: T
    -

    Returns Type<T>

-
-

Properties

-
- -
typeName: any
-
- -
typeOf: string
-
-

Methods

-
- -
    - -
  • -

    Returns string

-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      u: unknown
    -

    Returns u is T

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/TypeArray.html b/docs/classes/TypeArray.html deleted file mode 100644 index aa663588d8..0000000000 --- a/docs/classes/TypeArray.html +++ /dev/null @@ -1,148 +0,0 @@ -TypeArray | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Validate an array of the specified type.

-
-
-

Type Parameters

-
    -
  • -

    T

-
-

Hierarchy

-
    -
  • TypeArray
-
-

Implements

-
-
-
-
- -
-
-

Constructors

-
-
-

Properties

-
-
-

Methods

-
-
-

Constructors

-
- -
    - -
  • -
    -

    Type Parameters

    -
      -
    • -

      T

    -
    -

    Parameters

    -
      -
    • -
      typeName: string
    • -
    • -
      example: T
    -

    Returns TypeArray<T>

-
-

Properties

-
- -
typeName: any
-
- -
typeOf: string
-
-

Methods

-
- -
    - -
  • -

    Returns string

-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      u: unknown
    -

    Returns u is T

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/classes/TypeValidators.html b/docs/classes/TypeValidators.html deleted file mode 100644 index f370aefab9..0000000000 --- a/docs/classes/TypeValidators.html +++ /dev/null @@ -1,170 +0,0 @@ -TypeValidators | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

A set of standard type validators.

-
-
-

Hierarchy

-
    -
  • TypeValidators
-
-
-
- -
-
-

Constructors

-
- -
-
-

Properties

-
- -
Boolean: Type<boolean>
-
- -
-
- -
Function: Function
-
- -
Number: Type<number>
-
- -
Object: Type<object>
-
- -
ObjectOrFactory: FactoryOrInstance
-
- -
String: Type<string>
-
- -
StringArray: TypeArray<string>
-
-

Methods

-
- -
-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 2c3679bd1c..0000000000 --- a/docs/index.html +++ /dev/null @@ -1,145 +0,0 @@ -@launchdarkly/js-server-sdk-common
-
- -
-
-
-
-

@launchdarkly/js-server-sdk-common

-
- -

LaunchDarkly Server-Side SDK for JavaScript

-
- - -

The LaunchDarkly Server-Side SDK for Node.js is designed primarily for use in multi-user systems such as web servers and applications. It follows the server-side LaunchDarkly model for multi-user contexts. It is not intended for use in desktop and embedded systems applications.

-

For using LaunchDarkly in client-side Node.js applications, refer to our Client-side Node.js SDK.

- - -

LaunchDarkly overview

-
-

LaunchDarkly is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. Get started using LaunchDarkly today!

-

Twitter Follow

- - -

Supported Node versions

-
-

This version of the LaunchDarkly SDK is compatible with Node.js versions 12 and above.

- - -

Getting started

-
-

Refer to the SDK reference guide for instructions on getting started with using the SDK.

- - -

Learn more

-
-

Check out our documentation for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the complete reference guide for this SDK.

-

The authoritative description of all properties and methods is in the TypeScript documentation.

- - -

Testing

-
-

We run integration tests for all our SDKs using a centralized test harness. This approach gives us the ability to test for consistency across SDKs, as well as test networking behavior in a long-running application. These tests cover each method in the SDK, and verify that event sending, flag evaluation, stream reconnection, and other aspects of the SDK all behave correctly.

- - -

Contributing

-
-

We encourage pull requests and other contributions from the community. Check out our contributing guidelines for instructions on how to contribute to this SDK.

- - -

About LaunchDarkly

-
-
    -
  • LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
      -
    • Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
    • -
    • Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
    • -
    • Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
    • -
    • Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline.
    • -
    -
  • -
  • LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Check out our documentation for a complete list.
  • -
  • Explore LaunchDarkly -
  • -
-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/BasicLoggerOptions.html b/docs/interfaces/BasicLoggerOptions.html deleted file mode 100644 index da1d483a6f..0000000000 --- a/docs/interfaces/BasicLoggerOptions.html +++ /dev/null @@ -1,133 +0,0 @@ -BasicLoggerOptions | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Interface BasicLoggerOptions

-
-

Configuration for basicLogger.

-
-
-

Hierarchy

-
    -
  • BasicLoggerOptions
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
destination?: ((line: string) => void)
-
-

Type declaration

-
    -
  • -
      -
    • (line: string): void
    • -
    • -

      An optional function to use to print each log line.

      -

      If this is specified, basicLogger calls it to write each line of output. The -argument is a fully formatted log line, not including a linefeed. The function -is only called for log levels that are enabled.

      -

      If not specified, the default is console.error.

      -

      Setting this property to anything other than a function will cause SDK -initialization to fail.

      -
      -
      -

      Parameters

      -
        -
      • -
        line: string
      -

      Returns void

-
- -
formatter?: ((...args: any[]) => string)
-
-

Type declaration

-
    -
  • -
      -
    • (...args: any[]): string
    • -
    • -

      An optional formatter to use. The formatter should be compatible -with node-style format strings like those used with util.format.

      -

      If not specified, then a default implementation will be used. -But using a node-specific implementation, for instance, would -have performance and quality benefits.

      -
      -
      -

      Parameters

      -
        -
      • -
        Rest ...args: any[]
      -

      Returns string

-
- -
level?: LDLogLevel
-

The lowest level of log message to enable.

-

See LDLogLevel for a list of possible levels. Setting a level here causes -all lower-importance levels to be disabled: for instance, if you specify -'warn', then 'debug' and 'info' are disabled.

-

If not specified, the default is 'info' (meaning that 'debug' is disabled).

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDBigSegmentsOptions.html b/docs/interfaces/LDBigSegmentsOptions.html deleted file mode 100644 index 960498a322..0000000000 --- a/docs/interfaces/LDBigSegmentsOptions.html +++ /dev/null @@ -1,138 +0,0 @@ -LDBigSegmentsOptions | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Interface LDBigSegmentsOptions

-
-

Additional parameters for configuring the SDK's Big Segments behavior.

-

Big Segments are a specific type of user segments. For more information, read the LaunchDarkly -documentation: https://docs.launchdarkly.com/home/users/big-segments

- -

See

bigSegments

-
-
-

Hierarchy

-
    -
  • LDBigSegmentsOptions
-
-
-
- -
-
-

Properties

-
- -
staleAfter?: number
-

The maximum length of time between updates of the Big Segments data before the data is -considered out of date, in seconds.

-

Normally, the LaunchDarkly Relay Proxy updates a timestamp in the Big Segment store at -intervals to confirm that it is still in sync with the LaunchDarkly data, even if there have -been no changes to the store. -If the timestamp falls behind the current time by the amount specified in staleAfter, the -SDK assumes that something is not working correctly in this process and that the data may not -be accurate.

-

While in a stale state, the SDK will still continue using the last known data, but the status -from -getStatus will have stale: true, and any -LDEvaluationReason generated from a feature flag that references a Big Segment will -have a bigSegmentsStatus of "STALE".

-

If not specified, the default value is 120 (two minutes). Zero or negative values are changed -to the default.

-
-
- -
statusPollInterval?: number
-

The interval at which the SDK will poll the Big Segment store to make sure it is available -and to determine how long ago it was updated, in seconds.

-

If not specified, the default value is 5. Zero or negative values are changed to the default.

-
-
- -
userCacheSize?: number
-

The maximum number of users whose Big Segment state will be cached by the SDK at any given -time.

-

To reduce database traffic, the SDK maintains a least-recently-used cache by user key. When a -feature flag that references a Big Segment is evaluated for some user who is not currently in -the cache, the SDK queries the database for all Big Segment memberships of that user, and -stores them together in a single cache entry. If the cache is full, the oldest entry is -dropped.

-

A higher value for userCacheSize means that database queries for Big Segments will be done -less often for recently-referenced users, if the application has many users, at the cost of -increased memory used by the cache.

-

Cache entries can also expire based on the setting of userCacheTime.

-

If not specified, the default value is 1000.

-
-
- -
userCacheTime?: number
-

The maximum length of time that the Big Segment state for a user will be cached by the SDK, -in seconds.

-

See userCacheSize for more about this cache. A higher value for userCacheTime means -that database queries for the Big Segment state of any given user will be done less often, but -that changes to segment membership may not be detected as soon.

-

If not specified, the default value is 5. Negative values are changed to the default.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDClient.html b/docs/interfaces/LDClient.html deleted file mode 100644 index 8160f060d1..0000000000 --- a/docs/interfaces/LDClient.html +++ /dev/null @@ -1,484 +0,0 @@ -LDClient | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

The LaunchDarkly SDK client object.

-

Create this object with init. Applications should configure the client at startup time and -continue to use it throughout the lifetime of the application, rather than creating instances on -the fly.

-
-
-

Hierarchy

-
    -
  • LDClient
-
-

Implemented by

-
-
-
-
- -
-
-

Methods

-
- -
    - -
  • -

    Builds an object that encapsulates the state of all feature flags for a given context. -This includes the flag values and also metadata that can be used on the front end. This -method does not send analytics events back to LaunchDarkly.

    -

    The most common use case for this method is to bootstrap a set of client-side -feature flags from a back-end service. Call the toJSON() method of the returned object -to convert it to the data structure used by the client-side SDK.

    - -

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved - with the result as an LDFlagsState.

    -
    -
    -

    Parameters

    -
      -
    • -
      context: LDContext
      -

      The context requesting the feature flags.

      -
    • -
    • -
      Optional options: LDFlagsStateOptions
      -

      Optional LDFlagsStateOptions to determine how the state is computed.

      -
    • -
    • -
      Optional callback: ((err: Error, res: LDFlagsState) => void)
      -

      A Node-style callback to receive the result (as an LDFlagsState). If omitted, you - will receive a Promise instead.

      -
      -
    -

    Returns Promise<LDFlagsState>

-
- -
    - -
  • -

    Discards all network connections, background tasks, and other resources held by the client.

    -

    Do not attempt to use the client after calling this method.

    -
    -

    Returns void

-
- -
    - -
  • -

    Flushes all pending analytics events.

    -

    Normally, batches of events are delivered in the background at intervals determined by the -flushInterval property of LDOptions. Calling flush() triggers an immediate delivery. -However, like Node I/O in general, this is still an asynchronous operation so you must still -use Promise chaining, a callback, or async/await to detect when it has finished or failed.

    - -

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which resolves once - flushing is finished. Note that the Promise will be rejected if the HTTP request - fails, so be sure to attach a rejection handler to it.

    -
    -
    -

    Parameters

    -
      -
    • -
      Optional callback: ((err: Error, res: boolean) => void)
      -

      A function which will be called when the flush completes (meaning that all pending events - have been delivered to LaunchDarkly). If omitted, you will receive a Promise instead.

      -
      -
        -
      • -
          -
        • (err: Error, res: boolean): void
        • -
        • -
          -

          Parameters

          -
            -
          • -
            err: Error
          • -
          • -
            res: boolean
          -

          Returns void

    -

    Returns Promise<void>

-
- -
    - -
  • -

    Identifies a context to LaunchDarkly.

    -

    This simply creates an analytics event that will transmit the given user properties to -LaunchDarkly, so that the context will be visible on your dashboard even if you have not -evaluated any flags for that user. It has no other effect.

    -

    If the context is omitted or has no key, the client will log a warning -and will not send an event.

    -
    -
    -

    Parameters

    -
      -
    • -
      context: LDContext
      -

      The context properties. Must contain at least the key property.

      -
    -

    Returns void

-
- -
    - -
  • -

    Tests whether the client has completed initialization.

    -

    If this returns false, it means that the client has not yet successfully connected to -LaunchDarkly. It might still be in the process of starting up, or it might be attempting to -reconnect after an unsuccessful attempt, or it might have received an unrecoverable error (such -as an invalid SDK key) and given up.

    - -

    Returns

    True if the client has successfully initialized.

    -
    -

    Returns boolean

-
- -
-
- -
    - -
  • -

    Registers an event listener that will be called when the client triggers some type of event.

    -
      -
    • "ready": Sent only once, when the client has successfully connected to LaunchDarkly. -Alternately, you can detect this with waitForInitialization.
    • -
    • "failed": Sent only once, if the client has permanently failed to connect to LaunchDarkly. -Alternately, you can detect this with waitForInitialization.
    • -
    • "error": Contains an error object describing some abnormal condition that the client has -detected (such as a network error).
    • -
    • "update": The client has received a change to a feature flag. The event parameter is an -object containing a single property, key, the flag key. Note that this does not necessarily -mean the flag's value has changed for any particular context, only that some part of the flag -configuration was changed.
    • -
    • "update:KEY": The client has received a change to the feature flag whose key is KEY. This -is the same as "update" but allows you to listen for a specific flag.
    • -
    -
    -
    -

    Parameters

    -
      -
    • -
      event: string | symbol
      -

      the name of the event to listen for

      -
    • -
    • -
      listener: ((...args: any[]) => void)
      -

      the function to call when the event happens

      -
      -
        -
      • -
          -
        • (...args: any[]): void
        • -
        • -
          -

          Parameters

          -
            -
          • -
            Rest ...args: any[]
          -

          Returns void

    -

    Returns LDClient

-
- -
    - -
  • -

    Computes an HMAC signature of a context signed with the client's SDK key.

    -

    For more information, see the JavaScript SDK Reference Guide on -Secure mode.

    - -

    Returns

    The hash string.

    -
    -
    -

    Parameters

    -
      -
    • -
      context: LDContext
      -

      The context properties.

      -
    -

    Returns string

-
- -
    - -
  • -

    Tracks that a context performed an event.

    -

    LaunchDarkly automatically tracks pageviews and clicks that are specified in the Goals section -of the dashboard. This can be used to track custom goals or other events that do not currently -have goals.

    -

    Note that event delivery is asynchronous, so the event may not actually be sent until later; -see flush.

    -

    If the context is omitted or has no key, the client will log a warning and will not send an -event.

    -
    -
    -

    Parameters

    -
      -
    • -
      key: string
      -

      The name of the event, which may correspond to a goal in A/B tests.

      -
    • -
    • -
      context: LDContext
      -

      The context to track.

      -
    • -
    • -
      Optional data: any
      -

      Optional additional information to associate with the event.

      -
    • -
    • -
      Optional metricValue: number
      -

      A numeric value used by the LaunchDarkly experimentation feature in numeric - custom metrics. Can be omitted if this event is used by only non-numeric metrics. This field - will also be returned as part of the custom event for Data Export.

      -
    -

    Returns void

-
- -
    - -
  • -

    Determines the variation of a feature flag for a context.

    - -

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved with - the result value.

    -
    -
    -

    Parameters

    -
      -
    • -
      key: string
      -

      The unique key of the feature flag.

      -
    • -
    • -
      context: LDContext
      -

      The context requesting the flag. The client will generate an analytics event to - register this context with LaunchDarkly if the context does not already exist.

      -
    • -
    • -
      defaultValue: any
      -

      The default value of the flag, to be used if the value is not available - from LaunchDarkly.

      -
    • -
    • -
      Optional callback: ((err: any, res: any) => void)
      -

      A Node-style callback to receive the result value. If omitted, you will receive - a Promise instead.

      -
      -
        -
      • -
          -
        • (err: any, res: any): void
        • -
        • -
          -

          Parameters

          -
            -
          • -
            err: any
          • -
          • -
            res: any
          -

          Returns void

    -

    Returns Promise<any>

-
- -
    - -
  • -

    Determines the variation of a feature flag for a context, along with information about how it -was calculated.

    -

    The reason property of the result will also be included in analytics events, if you are -capturing detailed event data for this flag.

    -

    For more information, see the SDK reference -guide.

    - -

    Returns

    If you provided a callback, then nothing. Otherwise, a Promise which will be resolved with - the result (as an LDEvaluationDetail).

    -
    -
    -

    Parameters

    -
      -
    • -
      key: string
      -

      The unique key of the feature flag.

      -
    • -
    • -
      context: LDContext
      -

      The context requesting the flag. The client will generate an analytics event to - register this context with LaunchDarkly if the context does not already exist.

      -
    • -
    • -
      defaultValue: any
      -

      The default value of the flag, to be used if the value is not available - from LaunchDarkly.

      -
    • -
    • -
      Optional callback: ((err: any, res: LDEvaluationDetail) => void)
      -

      A Node-style callback to receive the result (as an LDEvaluationDetail). If - omitted, you will receive a Promise instead.

      -
      -
    -

    Returns Promise<LDEvaluationDetail>

-
- -
    - -
  • -

    Returns a Promise that tracks the client's initialization state.

    -

    The Promise will be resolved if the client successfully initializes, or rejected if client -initialization has failed unrecoverably (for instance, if it detects that the SDK key is -invalid). Keep in mind that unhandled Promise rejections can be fatal in Node, so if you call -this method, be sure to attach a rejection handler to it (or, if using async/await, a catch -block).

    -

    Note that you can also use event listeners (on) for the same purpose: the event "ready" -indicates success, and "failed" indicates failure.

    -

    There is no built-in timeout for this method. If you want your code to stop waiting on the -Promise after some amount of time, you could use -https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race|Promise.race() -or one of the several NPM helper packages that provides a standard mechanism for this. -Regardless of whether you continue to wait, the SDK will still retry all connection failures -indefinitely unless it gets an unrecoverable error as described above.

    - -

    Returns

    A Promise that will be resolved if the client initializes successfully, or rejected if it - fails. If successful, the result is the same client object.

    - -

    Example

    This example shows use of Promise chaining methods for specifying handlers:

    -
      client.waitForInitialization().then(() => {
    // do whatever is appropriate if initialization has succeeded
    }).catch(err => {
    // do whatever is appropriate if initialization has failed
    }) -
    - -

    Example

    This example shows use of async/await syntax for specifying handlers:

    -
      try {
    await client.waitForInitialization();
    // do whatever is appropriate if initialization has succeeded
    } catch (err) {
    // do whatever is appropriate if initialization has failed
    } -
    -
    -

    Returns Promise<LDClient>

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDContextCommon.html b/docs/interfaces/LDContextCommon.html deleted file mode 100644 index 5879f3111a..0000000000 --- a/docs/interfaces/LDContextCommon.html +++ /dev/null @@ -1,107 +0,0 @@ -LDContextCommon | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Interface LDContextCommon

-
-

Hierarchy

-
-
-

Indexable

-
[attribute: string]: any
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- - -

TODO: U2C We will need some uniform description for this.

-

Meta attributes are used to control behavioral aspects of the Context. -They cannot be addressed in targetting rules.

-
-
- -
anonymous?: boolean
-

If true, the context will not appear on the Contexts page in the LaunchDarkly dashboard.

-
-
- -
key: string
-

A unique string identifying a context.

-
-
- -
name?: string
-

The context's name.

-

You can search for contexts on the Contexts page by name.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDContextMeta.html b/docs/interfaces/LDContextMeta.html deleted file mode 100644 index 8eb99c65ab..0000000000 --- a/docs/interfaces/LDContextMeta.html +++ /dev/null @@ -1,95 +0,0 @@ -LDContextMeta | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

TODO: U2C We will need some uniform description for this.

-

Meta attributes are used to control behavioral aspects of the Context. They -cannot be addressed in targetting rules.

-
-
-

Hierarchy

-
    -
  • LDContextMeta
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
privateAttributes?: string[]
-

TODO: U2C Link to the pointer-like syntax and maybe a name for it?

-

Specifies a list of attribute names (either built-in or custom) which should be -marked as private, and not sent to LaunchDarkly in analytics events. This is in -addition to any private attributes designated in the global configuration -with LDOptions.privateAttributes or LDOptions.allAttributesPrivate.

-
-
- -
secondary?: string
-

An optional secondary key for a context.

-

TODO: U2C Update with new URL when available.

-

This affects feature flag targeting -as follows: if you have chosen to bucket context by a specific attribute, the secondary key (if -set) is used to further distinguish between contexts which are otherwise identical according to -that attribute.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDEvaluationDetail.html b/docs/interfaces/LDEvaluationDetail.html deleted file mode 100644 index 4b5bac9375..0000000000 --- a/docs/interfaces/LDEvaluationDetail.html +++ /dev/null @@ -1,98 +0,0 @@ -LDEvaluationDetail | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Interface LDEvaluationDetail

-
-

An object that combines the result of a feature flag evaluation with information about -how it was calculated.

-

This is the result of calling LDClient.variationDetail.

-

For more information, see the SDK reference guide.

-
-
-

Hierarchy

-
    -
  • LDEvaluationDetail
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- - -

An object describing the main factor that influenced the flag evaluation value.

-
-
- -
value: any
-

The result of the flag evaluation. This will be either one of the flag's variations or -the default value that was passed to LDClient.variationDetail.

-
-
- -
variationIndex?: number
-

The index of the returned value within the flag's list of variations, e.g. 0 for the -first variation-- or null if the default value was returned.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDEvaluationReason.html b/docs/interfaces/LDEvaluationReason.html deleted file mode 100644 index 6d78352caf..0000000000 --- a/docs/interfaces/LDEvaluationReason.html +++ /dev/null @@ -1,153 +0,0 @@ -LDEvaluationReason | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Interface LDEvaluationReason

-
-

Describes the reason that a flag evaluation produced a particular value. This is -part of the LDEvaluationDetail object returned by LDClient.variationDetail.

-
-
-

Hierarchy

-
    -
  • LDEvaluationReason
-
-
-
- -
-
-

Properties

-
- -
bigSegmentsStatus?: "HEALTHY" | "STALE" | "NOT_CONFIGURED" | "STORE_ERROR"
-

Describes the validity of Big Segment information, if and only if the flag evaluation -required querying at least one Big Segment.

-
    -
  • 'HEALTHY': The Big Segment query involved in the flag evaluation was successful, and -the segment state is considered up to date.
  • -
  • 'STALE': The Big Segment query involved in the flag evaluation was successful, but -the segment state may not be up to date
  • -
  • 'NOT_CONFIGURED': Big Segments could not be queried for the flag evaluation because -the SDK configuration did not include a Big Segment store.
  • -
  • 'STORE_ERROR': The Big Segment query involved in the flag evaluation failed, for -instance due to a database error.
  • -
-
-
- -
errorKind?: string
-

A further description of the error condition, if the kind was 'ERROR'.

-
-
- -
inExperiment?: boolean
-

Whether the evaluation was part of an experiment.

-

This is true if the evaluation resulted in an experiment rollout and served one of -the variations in the experiment. Otherwise it is false or undefined.

-
-
- -
kind: string
-

The general category of the reason:

-
    -
  • 'OFF': The flag was off and therefore returned its configured off value.
  • -
  • 'FALLTHROUGH': The flag was on but the context did not match any targets or rules.
  • -
  • 'TARGET_MATCH': The context key was specifically targeted for this flag.
  • -
  • 'RULE_MATCH': the context matched one of the flag's rules.
  • -
  • 'PREREQUISITE_FAILED': The flag was considered off because it had at least one -prerequisite flag that either was off or did not return the desired variation.
  • -
  • 'ERROR': The flag could not be evaluated, e.g. because it does not exist or due -to an unexpected error.
  • -
-
-
- -
prerequisiteKey?: string
-

The key of the failed prerequisite flag, if the kind was 'PREREQUISITE_FAILED'.

-
-
- -
ruleId?: string
-

The unique identifier of the matched rule, if the kind was 'RULE_MATCH'.

-
-
- -
ruleIndex?: number
-

The index of the matched rule (0 for the first), if the kind was 'RULE_MATCH'.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDFlagSet.html b/docs/interfaces/LDFlagSet.html deleted file mode 100644 index 14101417cd..0000000000 --- a/docs/interfaces/LDFlagSet.html +++ /dev/null @@ -1,93 +0,0 @@ -LDFlagSet | @launchdarkly/js-server-sdk-common
-
- -
- -
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDFlagsState.html b/docs/interfaces/LDFlagsState.html deleted file mode 100644 index 26a3d661b0..0000000000 --- a/docs/interfaces/LDFlagsState.html +++ /dev/null @@ -1,153 +0,0 @@ -LDFlagsState | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

An object that contains the state of all feature flags, generated by LDClient.allFlagsState.

-
-
-

Hierarchy

-
    -
  • LDFlagsState
-
-
-
- -
-
-

Properties

-
-
-

Methods

-
-
-

Properties

-
- -
valid: boolean
-

True if this object contains a valid snapshot of feature flag state, or false if the -state could not be computed (for instance, because the client was offline or there -was no user).

-
-
-

Methods

-
- -
-
- -
-
- -
    - -
  • -

    Returns the value of an individual feature flag at the time the state was recorded. -It will be null if the flag returned the default value, or if there was no such flag.

    -
    -
    -

    Parameters

    -
      -
    • -
      key: string
      -

      The flag key.

      -
    -

    Returns any

-
- -
    - -
  • -

    Returns a Javascript representation of the entire state map, in the format used by -the Javascript SDK. Use this method if you are passing data to the front end in -order to "bootstrap" the JavaScript client.

    -

    Do not rely on the exact shape of this data, as it may change in future to support -the needs of the JavaScript client.

    -
    -

    Returns object

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDFlagsStateOptions.html b/docs/interfaces/LDFlagsStateOptions.html deleted file mode 100644 index 56e6475771..0000000000 --- a/docs/interfaces/LDFlagsStateOptions.html +++ /dev/null @@ -1,98 +0,0 @@ -LDFlagsStateOptions | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Interface LDFlagsStateOptions

-
-

Optional settings that can be passed to LDClient.allFlagsState.

-
-
-

Hierarchy

-
    -
  • LDFlagsStateOptions
-
-
-
- -
-
-

Properties

-
- -
clientSideOnly?: boolean
-

True if the state should include only flags that have been marked for use with the -client-side SDK. By default, all flags are included.

-
-
- -
detailsOnlyForTrackedFlags?: boolean
-

True if any flag metadata that is normally only used for event generation-- such as flag -versions and evaluation reasons-- should be omitted for any flag that does not have event -tracking or debugging turned on. This reduces the size of the JSON data if you are passing the -flag state to the front end.

-
-
- -
withReasons?: boolean
-

True if evaluation reason data should be captured in the state object (see -LDClient.variationDetail). By default, it is not.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDLogger.html b/docs/interfaces/LDLogger.html deleted file mode 100644 index beeebe5f09..0000000000 --- a/docs/interfaces/LDLogger.html +++ /dev/null @@ -1,157 +0,0 @@ -LDLogger | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

The LaunchDarkly client logger interface.

-

The LDOptions.logger property accepts any object that conforms to this -interface. The SDK only uses four logging levels: error, warn, info, and -debug. It will call the corresponding method of the LDLogger either with a -single string argument, or with a format string and variable arguments in the -format used by Node's util.format().

-

The Winston logging package provides a -logger that conforms to this interface, so if you have created a logger with -Winston, you can simply put it into the LDOptions.logger property.

-

If you do not provide a logger object, the SDK uses the basicLogger -implementation with a minimum level of info.

-
-
-

Hierarchy

-
    -
  • LDLogger
-
-

Implemented by

-
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
    - -
  • -

    The debug logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
- -
    - -
  • -

    The error logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
- -
    - -
  • -

    The info logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
- -
    - -
  • -

    The warning logger.

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...args: any[]
      -

      A sequence of any JavaScript values.

      -
    -

    Returns void

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDMultiKindContext.html b/docs/interfaces/LDMultiKindContext.html deleted file mode 100644 index 52e2e5eb05..0000000000 --- a/docs/interfaces/LDMultiKindContext.html +++ /dev/null @@ -1,79 +0,0 @@ -LDMultiKindContext | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Interface LDMultiKindContext

-
-

TODO: U2C How do we want to describe this?

-

A multi-kind context.

-
-
-

Hierarchy

-
    -
  • LDMultiKindContext
-
-

Indexable

-
[kind: string]: "multi" | LDContextCommon
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
kind: "multi"
-

The kind of the context.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDOptions.html b/docs/interfaces/LDOptions.html deleted file mode 100644 index af3ea6cd61..0000000000 --- a/docs/interfaces/LDOptions.html +++ /dev/null @@ -1,383 +0,0 @@ -LDOptions | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

LaunchDarkly initialization options.

-
-
-

Hierarchy

-
    -
  • LDOptions
-
-
-
- -
-
-

Properties

-
- -
allAttributesPrivate?: boolean
-

Whether all context attributes (except the contexy key) should be marked as private, and -not sent to LaunchDarkly.

-

By default, this is false.

-
-
- -
application?: { id?: string; version?: string }
-

Information about the application where the LaunchDarkly SDK is running.

-
-
-

Type declaration

-
    -
  • -
    Optional id?: string
    -

    A unique identifier representing the application where the LaunchDarkly SDK is running.

    -

    This can be specified as any string value as long as it only uses the following characters: -ASCII letters, ASCII digits, period, hyphen, underscore. A string containing any other -characters will be ignored.

    -

    Example: authentication-service

    -
  • -
  • -
    Optional version?: string
    -

    A unique identifier representing the version of the application where the LaunchDarkly SDK is -running.

    -

    This can be specified as any string value as long as it only uses the following characters: -ASCII letters, ASCII digits, period, hyphen, underscore. A string containing any other -characters will be ignored.

    -

    Example: 1.0.0 (standard version string) or abcdef (sha prefix)

    -
-
- -
baseUri?: string
-

The base URI for the LaunchDarkly server.

-

Most users should use the default value.

-
-
- -
bigSegments?: LDBigSegmentsOptions
-

Additional parameters for configuring the SDK's Big Segments behavior.

-

Big Segments are a specific type of user segments. For more information, read the -LaunchDarkly documentation: https://docs.launchdarkly.com/home/users/big-segments

-

By default, there is no configuration and Big Segments cannot be evaluated. In this -case, any flag evaluation that references a Big Segment will behave as if no users -are included in any Big Segments, and the LDEvaluationReason associated with any -such flag evaluation will have a bigSegmentsStatus of "NOT_CONFIGURED".

-
-
- -
capacity?: number
-

The capacity of the analytics events queue.

-

The client buffers up to this many events in memory before flushing. If the capacity is -exceeded before the buffer is flushed, events will be discarded.

-
-
- -
contextKeysCapacity?: number
-

The number of context keys that the event processor can remember at any one time, -so that duplicate context details will not be sent in analytics events.

-

Defaults to 1000.

-
-
- -
contextKeysFlushInterval?: number
-

The interval (in seconds) at which the event processor will reset its set of -known context keys.

-

Defaults to 300.

-
-
- -
diagnosticOptOut?: boolean
-

Set to true to opt out of sending diagnostics data.

-

Unless the diagnosticOptOut field is set to true, the client will send some diagnostics data -to the LaunchDarkly servers in order to assist in the development of future SDK improvements. -These diagnostics consist of an initial payload containing some details of SDK in use, the -SDK's configuration, and the platform the SDK is being run on, as well as payloads sent -periodically with information on irregular occurrences such as dropped events.

-
-
- -
diagnosticRecordingInterval?: number
-

The interval at which periodic diagnostic data is sent, in seconds.

-

The default is 900 (every 15 minutes) and the minimum value is 60 (every minute).

-
-
- -
eventsUri?: string
-

The base URI for the LaunchDarkly events server.

-

Most users should use the default value.

-
-
- -
featureStore?: LDFeatureStore | ((options: LDOptions) => LDFeatureStore)
-

A component that stores feature flags and related data received from LaunchDarkly.

-

By default, this is an in-memory data structure. Database integrations are also -available, as described in the -SDK features guide.

-

Some implementations provide the store implementation object itself, while others -provide a factory function that creates the store implementation based on the SDK -configuration; this property accepts either.

-
-
- -
flushInterval?: number
-

The interval in between flushes of the analytics events queue, in seconds.

-
-
- -
logger?: LDLogger
-

Configures a logger for warnings and errors generated by the SDK.

-

The logger can be any object that conforms to the LDLogger interface. -For a simple implementation that lets you filter by log level, see -basicLogger. You can also use an instance of winston.Logger from -the Winston logging package.

-

If you do not set this property, the SDK uses basicLogger with a -minimum level of info.

-
-
- -
offline?: boolean
-

Whether the client should be initialized in offline mode.

-
-
- -
pollInterval?: number
-

The time between polling requests, in seconds. Ignored in streaming mode.

-
-
- -
privateAttributes?: string[]
-

The names of any context attributes that should be marked as private, and not sent -to LaunchDarkly.

-
-
- -
proxyOptions?: LDProxyOptions
-

Allows you to specify configuration for an optional HTTP proxy.

-
-
- -
sendEvents?: boolean
-

Whether to send analytics events back to LaunchDarkly. By default, this is true.

-
-
- -
stream?: boolean
-

Whether streaming mode should be used to receive flag updates.

-

This is true by default. If you set it to false, the client will use polling. -Streaming should only be disabled on the advice of LaunchDarkly support.

-
-
- -
streamInitialReconnectDelay?: number
-

Sets the initial reconnect delay for the streaming connection, in seconds.

-

The streaming service uses a backoff algorithm (with jitter) every time the connection needs -to be reestablished. The delay for the first reconnection will start near this value, and then -increase exponentially for any subsequent connection failures.

-

The default value is 1.

-
-
- -
streamUri?: string
-

The base URI for the LaunchDarkly streaming server.

-

Most users should use the default value.

-
-
- -
timeout?: number
-

The connection timeout, in seconds.

-
-
- -
tlsParams?: LDTLSOptions
-

Additional parameters to pass to the Node HTTPS API for secure requests. These can include any -of the TLS-related parameters supported by https.request(), such as ca, cert, and key.

-

For more information, see the Node documentation for https.request() and tls.connect().

-
-
- -
updateProcessor?: object
-

A component that obtains feature flag data and puts it in the feature store.

-

By default, this is the client's default streaming or polling component. It can be changed -for testing purposes; see FileDataSource.

-
-
- -
useLdd?: boolean
-

Whether you are using the LaunchDarkly relay proxy in daemon mode.

-

In this configuration, the client will not connect to LaunchDarkly to get feature flags, -but will instead get feature state from a database (Redis or another supported feature -store integration) that is populated by the relay. By default, this is false.

-
-
- -
wrapperName?: string
-

For use by wrapper libraries to set an identifying name for the wrapper being used.

-

This will be sent in User-Agent headers during requests to the LaunchDarkly servers to allow -recording metrics on the usage of these wrapper libraries.

-
-
- -
wrapperVersion?: string
-

For use by wrapper libraries to report the version of the library in use.

-

If wrapperName is not set, this field will be ignored. Otherwise the version string will be -included in the User-Agent headers along with the wrapperName during requests to the -LaunchDarkly servers.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDProxyOptions.html b/docs/interfaces/LDProxyOptions.html deleted file mode 100644 index 2a82dee533..0000000000 --- a/docs/interfaces/LDProxyOptions.html +++ /dev/null @@ -1,101 +0,0 @@ -LDProxyOptions | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Interface LDProxyOptions

-
-

Hierarchy

-
    -
  • LDProxyOptions
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
auth?: string
-

Allows you to specify basic authentication parameters for an optional HTTP proxy. -Usually of the form username:password.

-
-
- -
host?: string
-

Allows you to specify a host for an optional HTTP proxy.

-
-
- -
port?: number
-

Allows you to specify a port for an optional HTTP proxy.

-

Both the host and port must be specified to enable proxy support.

-
-
- -
scheme?: string
-

When using an HTTP proxy, specifies whether it is accessed via http or https.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDSingleKindContext.html b/docs/interfaces/LDSingleKindContext.html deleted file mode 100644 index f3595a3b6d..0000000000 --- a/docs/interfaces/LDSingleKindContext.html +++ /dev/null @@ -1,121 +0,0 @@ -LDSingleKindContext | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Interface LDSingleKindContext

-
-

TODO: U2C How do we want to describe this?

-

A single-kind context.

-
-
-

Hierarchy

-
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- - -

TODO: U2C We will need some uniform description for this.

-

Meta attributes are used to control behavioral aspects of the Context. -They cannot be addressed in targetting rules.

-
-
- -
anonymous?: boolean
-

If true, the context will not appear on the Contexts page in the LaunchDarkly dashboard.

-
-
- -
key: string
-

A unique string identifying a context.

-
-
- -
kind: string
-

The kind of the context.

-
-
- -
name?: string
-

The context's name.

-

You can search for contexts on the Contexts page by name.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDTLSOptions.html b/docs/interfaces/LDTLSOptions.html deleted file mode 100644 index fd136f04e4..0000000000 --- a/docs/interfaces/LDTLSOptions.html +++ /dev/null @@ -1,153 +0,0 @@ -LDTLSOptions | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Additional parameters to pass to the Node HTTPS API for secure requests. These can include any -of the TLS-related parameters supported by https.request(), such as ca, cert, and key.

-

For more information, see the Node documentation for https.request() and tls.connect().

-
-
-

Hierarchy

-
    -
  • LDTLSOptions
-
-
-
- -
-
-

Properties

-
- -
ca?: string | string[] | Buffer | Buffer[]
-
- -
cert?: string | string[] | Buffer | Buffer[]
-
- -
checkServerIdentity?: ((servername: string, cert: any) => undefined | Error)
-
-

Type declaration

-
    -
  • -
      -
    • (servername: string, cert: any): undefined | Error
    • -
    • -
      -

      Parameters

      -
        -
      • -
        servername: string
      • -
      • -
        cert: any
      -

      Returns undefined | Error

-
- -
ciphers?: string
-
- -
key?: string | string[] | Buffer | Buffer[] | object[]
-
- -
passphrase?: string
-
- -
pfx?: string | string[] | Buffer | Buffer[] | object[]
-
- -
rejectUnauthorized?: boolean
-
- -
secureProtocol?: string
-
- -
servername?: string
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/LDUser.html b/docs/interfaces/LDUser.html deleted file mode 100644 index 04e4a0793a..0000000000 --- a/docs/interfaces/LDUser.html +++ /dev/null @@ -1,193 +0,0 @@ -LDUser | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

A LaunchDarkly user object.

-
-
-

Hierarchy

-
    -
  • LDUser
-
-
-
- -
-
-

Properties

-
- -
anonymous?: boolean
-

If true, the user will not appear on the Users page in the LaunchDarkly dashboard.

-
-
- -
avatar?: string
-

An absolute URL to an avatar image for the user.

-
-
- -
country?: string
-

The country associated with the user.

-
-
- -
custom?: { [key: string]: string | boolean | number | (string | boolean | number)[] }
-

Any additional attributes associated with the user.

-
-
-

Type declaration

-
    -
  • -
    [key: string]: string | boolean | number | (string | boolean | number)[]
-
- -
email?: string
-

The user's email address.

-

If an avatar URL is not provided, LaunchDarkly will use Gravatar -to try to display an avatar for the user on the Users page.

-
-
- -
firstName?: string
-

The user's first name.

-
-
- -
ip?: string
-

The user's IP address.

-

If you provide an IP, LaunchDarkly will use a geolocation service to -automatically infer a country for the user, unless you've already -specified one.

-
-
- -
key: string
-

A unique string identifying a user.

-
-
- -
lastName?: string
-

The user's last name.

-
-
- -
name?: string
-

The user's name.

-

You can search for users on the User page by name.

-
-
- -
privateAttributeNames?: string[]
-

Specifies a list of attribute names (either built-in or custom) which should be -marked as private, and not sent to LaunchDarkly in analytics events. This is in -addition to any private attributes designated in the global configuration -with LDOptions.privateAttributeNames or LDOptions.allAttributesPrivate.

-
-
- -
secondary?: string
-

An optional secondary key for a user.

-

This affects feature flag -targeting -as follows: if you have chosen to bucket users by a specific attribute, the secondary key (if -set) is used to further distinguish between users who are otherwise identical according to that -attribute.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/TypeValidator.html b/docs/interfaces/TypeValidator.html deleted file mode 100644 index 1e4f7c1a76..0000000000 --- a/docs/interfaces/TypeValidator.html +++ /dev/null @@ -1,99 +0,0 @@ -TypeValidator | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Interface for type validation.

-
-
-

Hierarchy

-
    -
  • TypeValidator
-
-

Implemented by

-
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
    - -
  • -

    Returns string

-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      u: unknown
    -

    Returns boolean

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/integrations.FileDataSourceOptions.html b/docs/interfaces/integrations.FileDataSourceOptions.html deleted file mode 100644 index e9bde6ea56..0000000000 --- a/docs/interfaces/integrations.FileDataSourceOptions.html +++ /dev/null @@ -1,97 +0,0 @@ -FileDataSourceOptions | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Configuration for FileDataSource.

-
-
-

Hierarchy

-
    -
  • FileDataSourceOptions
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
autoUpdate?: boolean
-

True if FileDataSource should reload flags whenever one of the data files is modified. -This feature uses Node's fs.watch() API, so it is subject to -the limitations described here.

-
-
- -
logger?: object | LDLogger
-

Configures a logger for warnings and errors. This can be a custom logger or an instance of -winston.Logger. By default, it uses the same logger as the rest of the SDK.

-
-
- -
paths: string[]
-

The path(s) of the file(s) that FileDataSource will read.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/integrations.TestData.html b/docs/interfaces/integrations.TestData.html deleted file mode 100644 index 91111d4072..0000000000 --- a/docs/interfaces/integrations.TestData.html +++ /dev/null @@ -1,204 +0,0 @@ -TestData | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

A mechanism for providing dynamically updatable feature flag state in a simplified form to an SDK -client in test scenarios.

-

Unlike FileData, this mechanism does not use any external resources. It provides only the -data that the application has put into it using the TestData.update method.

- -

Example

const { TestData } = require('launchdarkly-node-server-sdk/interfaces');

-
const td = TestData();
testData.update(td.flag("flag-key-1").booleanFlag().variationForAll(true));
const client = new LDClient(sdkKey, { updateProcessor: td });

// flags can be updated at any time:
td.update(td.flag("flag-key-2")
.variationForContext("user", "some-user-key", true)
.fallthroughVariation(false)); -
-

The above example uses a simple boolean flag, but more complex configurations are possible using -the methods of the TestDataFlagBuilder that is returned by TestData.flag. -TestDataFlagBuilder supports many of the ways a flag can be configured on the LaunchDarkly -dashboard, but does not currently support

-
    -
  1. rule operators other than "in" and "not in", or
  2. -
  3. percentage rollouts.
  4. -
-

If the same TestData instance is used to configure multiple LDClient instances, -any changes made to the data will propagate to all of the LDClients.

- -

See

FileDataSource

-
-
-

Hierarchy

-
    -
  • TestData
-
-
-
- -
-
-

Methods

-
- -
    - -
  • -

    Creates or copies a TestDataFlagBuilder for building a test flag configuration.

    -

    If the flag key has already been defined in this TestData instance, -then the builder starts with the same configuration that was last -provided for this flag.

    -

    Otherwise, it starts with a new default configuration in which the flag -has true and false variations, is true for all users when targeting -is turned on and false otherwise, and currently has targeting turned on. -You can change any of those properties and provide more complex behavior -using the TestDataFlagBuilder methods.

    -

    Once you have set the desired configuration, pass the builder to -TestData.update.

    - -

    Returns

    a flag configuration builder

    -
    -
    -

    Parameters

    -
      -
    • -
      key: string
      -

      the flag key

      -
    -

    Returns TestDataFlagBuilder

-
- -
    - -
  • -

    Updates the test data with the specified flag configuration.

    -

    This has the same effect as if a flag were added or modified in the -LaunchDarkly dashboard. It immediately propagates the flag changes to -any LDClient instance(s) that you have already configured to use -this TestData. If no LDClient has been started yet, it simply adds -this flag to the test data which will be provided to any LDClient -that you subsequently configure.

    -

    Any subsequent changes to this TestDataFlagBuilder instance do not affect -the test data unless you call update again.

    - -

    Returns

    a promise that will resolve when the feature stores are updated

    -
    -
    -

    Parameters

    -
    -

    Returns Promise<any>

-
- -
    - -
  • -

    Copies a full feature flag data model object into the test data.

    -

    It immediately propagates the flag change to any LDClient instance(s) that you have already -configured to use this TestData. If no LDClient has been started yet, it simply adds this -flag to the test data which will be provided to any LDClient that you subsequently configure.

    -

    Use this method if you need to use advanced flag configuration properties that are not -supported by the simplified TestDataFlagBuilder API. Otherwise it is recommended to use the -regular flag/update mechanism to avoid dependencies on details of the data model.

    -

    You cannot make incremental changes with flag/update to a flag that has been added in -this way; you can only replace it with an entirely new flag configuration.

    - -

    Returns

    a promise that will resolve when the feature stores are updated

    -
    -
    -

    Parameters

    -
      -
    • -
      flagConfig: any
      -

      the flag configuration as a JSON object

      -
    -

    Returns Promise<any>

-
- -
    - -
  • -

    Copies a full segment data model object into the test data.

    -

    It immediately propagates the change to any LDClient instance(s) that you have already -configured to use this TestData. If no LDClient has been started yet, it simply adds -this segment to the test data which will be provided to any LDClient that you subsequently -configure.

    -

    This method is currently the only way to inject segment data, since there is no builder -API for segments. It is mainly intended for the SDK's own tests of segment functionality, -since application tests that need to produce a desired evaluation state could do so more easily -by just setting flag values.

    - -

    Returns

    a promise that will resolve when the feature stores are updated

    -
    -
    -

    Parameters

    -
      -
    • -
      segmentConfig: any
      -

      the segment configuration as a JSON object

      -
    -

    Returns Promise<any>

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/integrations.TestDataFlagBuilder.html b/docs/interfaces/integrations.TestDataFlagBuilder.html deleted file mode 100644 index cd9e0dd97c..0000000000 --- a/docs/interfaces/integrations.TestDataFlagBuilder.html +++ /dev/null @@ -1,402 +0,0 @@ -TestDataFlagBuilder | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

A builder for feature flag configurations to be used with TestData.

-
-
-

Hierarchy

-
    -
  • TestDataFlagBuilder
-
-
-
- -
-
-

Methods

-
- -
-
- -
-
- -
-
- -
    - -
  • -

    Specifies the fallthrough variation for a flag. The fallthrough is -the value that is returned if targeting is on and the user was not -matched by a more specific target or rule.

    -

    If a boolean is supplied, and the flag was previously configured with -other variations, this also changes it to a boolean flag.

    - -

    Returns

    the flag builder

    -
    -
    -

    Parameters

    -
      -
    • -
      variation: number | boolean
      -

      either true or false or the index of the desired fallthrough - variation: 0 for the first, 1 for the second, etc.

      -
    -

    Returns TestDataFlagBuilder

-
- -
    - -
  • -

    Starts defining a flag rule using the "is one of" operator.

    -

    For example, this creates a rule that returnes true if the name is -"Patsy" or "Edina":

    -
    testData.flag('flag')
    .ifMatch('user', name', 'Patsy', 'Edina')
    .thenReturn(true) -
    - -

    Returns

    a flag rule builder; call thenReturn to finish the rule - or add more tests with another method like andMatch

    -
    -
    -

    Parameters

    -
      -
    • -
      contextKind: string
      -

      the kind of the context

      -
    • -
    • -
      attribute: string
      -

      the context attribute to match against

      -
    • -
    • -
      Rest ...values: any
      -

      values to compare to

      -
    -

    Returns TestDataRuleBuilder

-
- -
    - -
  • -

    Starts defining a flag rule using the "is not one of" operator.

    -

    For example, this creates a rule that returnes true if the name is -neither "Saffron" nor "Bubble":

    -
    testData.flag('flag')
    .ifNotMatch('user', 'name', 'Saffron', 'Bubble')
    .thenReturn(true) -
    - -

    Returns

    a flag rule builder; call thenReturn to finish the rule - or add more tests with another method like andNotMatch

    -
    -
    -

    Parameters

    -
      -
    • -
      contextKind: string
      -

      the kind of the context

      -
    • -
    • -
      attribute: string
      -

      the user attribute to match against

      -
    • -
    • -
      Rest ...values: any
      -

      values to compare to

      -
    -

    Returns TestDataRuleBuilder

-
- -
    - -
  • -

    Specifies the off variation for a flag. This is the variation that is -returned whenever targeting is off.

    -

    If a boolean is supplied, and the flag was previously configured with -other variations, this also changes it to a boolean flag.

    - -

    Returns

    the flag builder

    -
    -
    -

    Parameters

    -
      -
    • -
      variation: number | boolean
      -

      either true or false or the index of the desired off - variation: 0 for the first, 1 for the second, etc.

      -
    -

    Returns TestDataFlagBuilder

-
- -
    - -
  • -

    Sets targeting to be on or off for this flag.

    -

    The effect of this depends on the rest of the flag configuration, just -as it does on the real LaunchDarkly dashboard. In the default configuration -that you get from calling TestData.flag with a new flag key, the flag -will return false whenever targeting is off and true when targeting -is on.

    - -

    Returns

    the flag builder

    -
    -
    -

    Parameters

    -
      -
    • -
      targetingOn: boolean
      -

      true if targeting should be on

      -
    -

    Returns TestDataFlagBuilder

-
- -
    - -
  • -

    Sets the flag to always return the specified variation value for all contexts.

    -

    The value may be of any valid JSON type. This method changes the flag to have -only a single variation, which is this value, and to return the same variation -regardless of whether targeting is on or off. Any existing targets or rules -are removed.

    - -

    Returns

    the flag builder

    -
    -
    -

    Parameters

    -
      -
    • -
      value: any
      -

      The desired value to be returned for all contexts.

      -
    -

    Returns TestDataFlagBuilder

-
- -
    - -
  • -

    Sets the flag to always return the specified variation for all contexts.

    -

    Targeting is switched on, any existing targets or rules are removed, -and the fallthrough variation is set to the specified value. The off -variation is left unchanged.

    -

    If a boolean is supplied, and the flag was previously configured with -other variations, this also changes it to a boolean flag.

    - -

    Returns

    the flag builder

    -
    -
    -

    Parameters

    -
      -
    • -
      variation: number | boolean
    -

    Returns TestDataFlagBuilder

-
- -
    - -
  • -

    Sets the flag to return the specified variation for a specific context key -when targeting is on.

    -

    This has no effect when targeting is turned off for the flag.

    -

    If the variation is a boolean value and the flag was not already a boolean -flag, this also changes it to be a boolean flag.

    -

    If the variation is an integer, it specifies a variation out of whatever -variation values have already been defined.

    - -

    Returns

    the flag builder

    -
    -
    -

    Parameters

    -
      -
    • -
      contextKind: string
      -

      a context kind

      -
    • -
    • -
      contextKey: string
      -

      a context key

      -
    • -
    • -
      variation: number | boolean
      -

      either true or false or the index of the desired variation: - 0 for the first, 1 for the second, etc.

      -
    -

    Returns TestDataFlagBuilder

-
- -
    - -
  • -

    Sets the flag to return the specified variation for a specific context key -when targeting is on. The context kind for contexts created with this method -will be 'user'.

    -

    This has no effect when targeting is turned off for the flag.

    -

    If the variation is a boolean value and the flag was not already a boolean -flag, this also changes it to be a boolean flag.

    -

    If the variation is an integer, it specifies a variation out of whatever -variation values have already been defined.

    - -

    Returns

    the flag builder

    -
    -
    -

    Parameters

    -
      -
    • -
      contextKey: string
      -

      a context key

      -
    • -
    • -
      variation: number | boolean
      -

      either true or false or the index of the desired variation: - 0 for the first, 1 for the second, etc.

      -
    -

    Returns TestDataFlagBuilder

-
- -
    - -
  • -

    Sets the allowable variation values for the flag.

    -

    The values may be of any JSON-compatible type: boolean, number, string, array, -or object. For instance, a boolean flag normally has variations(true, false); -a string-valued flag might have variations("red", "green"); etc.

    - -

    Returns

    the flag builder

    -
    -
    -

    Parameters

    -
      -
    • -
      Rest ...values: any[]
      -

      any number of variation values

      -
    -

    Returns TestDataFlagBuilder

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/integrations.TestDataRuleBuilder.html b/docs/interfaces/integrations.TestDataRuleBuilder.html deleted file mode 100644 index 149f915941..0000000000 --- a/docs/interfaces/integrations.TestDataRuleBuilder.html +++ /dev/null @@ -1,167 +0,0 @@ -TestDataRuleBuilder | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

A builder for feature flag rules to be used with TestDataFlagBuilder.

-

In the LaunchDarkly model, a flag can have any number of rules, and -a rule can have any number of clauses. A clause is an individual test -such as "name is 'X'". A rule matches a user if all of the rule's -clauses match the user.

-

To start defining a rule, use one of the flag builder's matching methods -such as ifMatch. This defines the first clause for the rule. Optionally, -you may add more clauses with the rule builder's methods such as andMatch. -Finally, call thenReturn to finish defining the rule.

-
-
-

Hierarchy

-
    -
  • TestDataRuleBuilder
-
-
-
- -
-
-

Methods

-
- -
    - -
  • -

    Adds another clause using the "is one of" operator.

    -

    For example, this creates a rule that returns true if the name is -"Patsy" and the country is "gb":

    -
    testData.flag('flag')
    .ifMatch('name', 'Patsy')
    .andMatch('country', 'gb')
    .thenReturn(true) -
    - -

    Returns

    the flag rule builder

    -
    -
    -

    Parameters

    -
      -
    • -
      contextKind: string
      -

      the kind of the context

      -
    • -
    • -
      attribute: string
      -

      the user attribute to match against

      -
    • -
    • -
      Rest ...values: any
      -

      values to compare to

      -
    -

    Returns TestDataRuleBuilder

-
- -
    - -
  • -

    Adds another clause using the "is not one of" operator.

    -

    For example, this creates a rule that returns true if the name is -"Patsy" and the country is not "gb":

    -
    testData.flag('flag')
    .ifMatch('name', 'Patsy')
    .andNotMatch('country', 'gb')
    .thenReturn(true) -
    - -

    Returns

    the flag rule builder

    -
    -
    -

    Parameters

    -
      -
    • -
      contextKind: string
      -

      the kind of the context

      -
    • -
    • -
      attribute: string
      -

      the user attribute to match against

      -
    • -
    • -
      Rest ...values: any
      -

      values to compare to

      -
    -

    Returns TestDataRuleBuilder

-
- -
    - -
  • -

    Finishes defining the rule, specifying the result value as either a boolean or an index

    -

    If the variation is a boolean value and the flag was not already a boolean -flag, this also changes it to be a boolean flag.

    -

    If the variation is an integer, it specifies a variation out of whatever -variation values have already been defined.

    - -

    Returns

    the flag rule builder

    -
    -
    -

    Parameters

    -
      -
    • -
      variation: number | boolean
      -

      either true or false or the index of the desired variation: - 0 for the first, 1 for the second, etc.

      -
    -

    Returns TestDataFlagBuilder

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.BigSegmentStore.html b/docs/interfaces/interfaces.BigSegmentStore.html deleted file mode 100644 index 46d83ea76f..0000000000 --- a/docs/interfaces/interfaces.BigSegmentStore.html +++ /dev/null @@ -1,129 +0,0 @@ -BigSegmentStore | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

A read-only data store that allows querying of user membership in Big Segments.

-

Big Segments are a specific type of user segments. For more information, read the LaunchDarkly -documentation: https://docs.launchdarkly.com/home/users/big-segments

-
-
-

Hierarchy

-
    -
  • BigSegmentStore
-
-
-
- -
-
-

Methods

-
- -
-
- -
    - -
  • -

    Queries information about the overall state of the store.

    -

    The resolved value of the Promise should always be a BigSegmentStoreMetadata object. If the -store is accessible but contains no metadata, the object's lastUpToDate property can be -undefined. If the store is not accessible due to a database error, the method can throw an -exception/reject the promise.

    -

    This method will be called only when the SDK needs the latest state, so it should not be -cached.

    - -

    Returns

    a Promise for the result of the query

    -
    -

    Returns Promise<BigSegmentStoreMetadata>

-
- -
    - -
  • -

    Queries the store for a snapshot of the current segment state for a specific user.

    -

    The userHash is a base64-encoded string produced by hashing the user key as defined by -the Big Segments specification; the store implementation does not need to know the details -of how this is done, because it deals only with already-hashed keys, but the string can be -assumed to only contain characters that are valid in base64.

    -

    The resolved value of the Promise should be either a BigSegmentStoreMembership, or -undefined if the user is not referenced in any Big Segments (this is equivalent to a -BigSegmentStoreMembership that has no properties).

    - -

    Returns

    a Promise for the result of the query.

    -
    -
    -

    Parameters

    -
      -
    • -
      userHash: string
      -

      identifies the user

      -
    -

    Returns Promise<undefined | BigSegmentStoreMembership>

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.BigSegmentStoreMembership.html b/docs/interfaces/interfaces.BigSegmentStoreMembership.html deleted file mode 100644 index 5571093256..0000000000 --- a/docs/interfaces/interfaces.BigSegmentStoreMembership.html +++ /dev/null @@ -1,75 +0,0 @@ -BigSegmentStoreMembership | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

The return type of BigSegmentStore.getUserMembership, describing which Big Segments a -specific user is included in or excluded from.

-

This object may be cached by the SDK, so it should not be modified after it is created. It -is a snapshot of the segment membership state at one point in time.

-
-
-

Hierarchy

-
    -
  • BigSegmentStoreMembership
-
-

Indexable

-
[segmentRef: string]: boolean
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.BigSegmentStoreMetadata.html b/docs/interfaces/interfaces.BigSegmentStoreMetadata.html deleted file mode 100644 index b5d897961e..0000000000 --- a/docs/interfaces/interfaces.BigSegmentStoreMetadata.html +++ /dev/null @@ -1,77 +0,0 @@ -BigSegmentStoreMetadata | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Values returned by BigSegmentStore.getMetadata().

-
-
-

Hierarchy

-
    -
  • BigSegmentStoreMetadata
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
lastUpToDate?: number
-

The Unix epoch millisecond timestamp of the last update to the BigSegmentStore. It is -undefined if the store has never been updated.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.BigSegmentStoreStatus.html b/docs/interfaces/interfaces.BigSegmentStoreStatus.html deleted file mode 100644 index 2f769a464c..0000000000 --- a/docs/interfaces/interfaces.BigSegmentStoreStatus.html +++ /dev/null @@ -1,99 +0,0 @@ -BigSegmentStoreStatus | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Information about the status of a Big Segment store, provided by -BigSegmentStoreStatusProvider.

-

Big Segments are a specific type of user segments. For more information, read the LaunchDarkly -documentation: https://docs.launchdarkly.com/home/users/big-segments

-
-
-

Hierarchy

-
    -
  • BigSegmentStoreStatus
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
available: boolean
-

True if the Big Segment store is able to respond to queries, so that the SDK can -evaluate whether a user is in a segment or not.

-

If this property is false, the store is not able to make queries (for instance, it may not have -a valid database connection). In this case, the SDK will treat any reference to a Big Segment -as if no users are included in that segment. Also, the LDEvaluationReason associated -with any flag evaluation that references a Big Segment when the store is not available will -have a bigSegmentsStatus of "STORE_ERROR".

-
-
- -
stale: boolean
-

True if the Big Segment store is available, but has not been updated within the amount of time -specified by staleAfter.

-

This may indicate that the LaunchDarkly Relay Proxy, which populates the store, has stopped -running or has become unable to receive fresh data from LaunchDarkly. Any feature flag -evaluations that reference a Big Segment will be using the last known data, which may be out -of date.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.BigSegmentStoreStatusProvider.html b/docs/interfaces/interfaces.BigSegmentStoreStatusProvider.html deleted file mode 100644 index 362451ff71..0000000000 --- a/docs/interfaces/interfaces.BigSegmentStoreStatusProvider.html +++ /dev/null @@ -1,107 +0,0 @@ -BigSegmentStoreStatusProvider | @launchdarkly/js-server-sdk-common
-
- -
-
-
-
- -

Interface BigSegmentStoreStatusProvider

-
-

An interface for querying the status of a Big Segment store.

-

The Big Segment store is the component that receives information about Big Segments, normally -from a database populated by the LaunchDarkly Relay Proxy. Big Segments are a specific type of -user segments. For more information, read the LaunchDarkly documentation: -https://docs.launchdarkly.com/home/users/big-segments

-

An implementation of this interface is returned by -LDClient.bigSegmentStoreStatusProvider. Application code never needs to implement this -interface.

-
-
-

Hierarchy

-
    -
  • BigSegmentStoreStatusProvider
-
-

Implemented by

-
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.DataCollection.html b/docs/interfaces/interfaces.DataCollection.html deleted file mode 100644 index 080da5f9f1..0000000000 --- a/docs/interfaces/interfaces.DataCollection.html +++ /dev/null @@ -1,91 +0,0 @@ -DataCollection | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Used internally for data store implementations that require items in an ordered list rather -than as object properties.

-
-
-

Type Parameters

-
    -
  • -

    T

-
-

Hierarchy

-
    -
  • DataCollection
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
items: T[]
-

An ordered list of items of this kind.

-
-
- -
kind: DataKind
-

Describes the kind of items, such as feature flags or user segments.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.DataKind.html b/docs/interfaces/interfaces.DataKind.html deleted file mode 100644 index 909f7081da..0000000000 --- a/docs/interfaces/interfaces.DataKind.html +++ /dev/null @@ -1,78 +0,0 @@ -DataKind | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Used internally to describe the type of data being queried or updated, such as feature flags or -user segments.

-
-
-

Hierarchy

-
    -
  • DataKind
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
namespace: string
-

A string such as "features" or "segments" which can be used in keys to distinguish this -kind of data from other kinds.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.ItemDescriptor.html b/docs/interfaces/interfaces.ItemDescriptor.html deleted file mode 100644 index 3ee0a5f937..0000000000 --- a/docs/interfaces/interfaces.ItemDescriptor.html +++ /dev/null @@ -1,78 +0,0 @@ -ItemDescriptor | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Hierarchy

-
    -
  • ItemDescriptor
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
item: any
-
- -
version: number
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.PersistentDataStore.html b/docs/interfaces/interfaces.PersistentDataStore.html deleted file mode 100644 index 99e449a9e9..0000000000 --- a/docs/interfaces/interfaces.PersistentDataStore.html +++ /dev/null @@ -1,227 +0,0 @@ -PersistentDataStore | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

This interface should be used for database integrations, or any other data store -implementation that stores data in some external service. The SDK will take care of -converting between its own internal data model and a serialized string form; the data -store interacts only with the serialized form. The SDK will also provide its own caching -layer on top of the persistent data store; the data store implementation should not -provide caching, but simply do every query or update that the SDK tells it to do.

-

Conceptually, each item in the store is a SerializedItemDescriptor which always has -a version number, and can represent either a serialized object or a placeholder (tombstone) -for a deleted item. There are two approaches a persistent store implementation can use for -persisting this data:

-
    -
  1. Preferably, it should store the version number and the deleted -state separately so that the object does not need to be fully deserialized to read them. In -this case, deleted item placeholders can ignore the value of SerializedItemDescriptor#item -on writes and can set it to null on reads. The store should never call -DataKind#deserialize.

    -
  2. -
  3. If that isn't possible, then the store should simply persist the exact string from -serializedItem on writes, and return the persisted -string on reads (returning zero for the version and false for - deleted). The string is guaranteed to provide the SDK with -enough information to infer the version and the deleted state. On updates, the store must call -deserialize in order to inspect the version number of the -existing item if any.

    -
  4. -
-
-
-

Hierarchy

-
    -
  • PersistentDataStore
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
    - -
  • -

    Releases any resources being used by the feature store.

    -
    -

    Returns void

-
- -
-
- -
-
- -
    - -
  • -

    Tests whether the store is initialized.

    -

    "Initialized" means that the store has been populated with data, either by the client -having called init() within this process, or by another process (if this is a shared -database).

    -
    -
    -

    Parameters

    -
      -
    • -
      callback: ((isInitialized: boolean) => void)
      -

      Will be called back with the boolean result.

      -
      -
        -
      • -
          -
        • (isInitialized: boolean): void
        • -
        • -
          -

          Parameters

          -
            -
          • -
            isInitialized: boolean
          -

          Returns void

    -

    Returns void

-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.PersistentStoreDataKind.html b/docs/interfaces/interfaces.PersistentStoreDataKind.html deleted file mode 100644 index 8570b2f94b..0000000000 --- a/docs/interfaces/interfaces.PersistentStoreDataKind.html +++ /dev/null @@ -1,91 +0,0 @@ -PersistentStoreDataKind | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Hierarchy

-
    -
  • PersistentStoreDataKind
-
-
-
- -
-
-

Properties

-
-
-

Methods

-
-
-

Properties

-
- -
namespace: string
-
-

Methods

-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      data: string
    -

    Returns ItemDescriptor

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.SerializedItemDescriptor.html b/docs/interfaces/interfaces.SerializedItemDescriptor.html deleted file mode 100644 index ea6b13c3d8..0000000000 --- a/docs/interfaces/interfaces.SerializedItemDescriptor.html +++ /dev/null @@ -1,88 +0,0 @@ -SerializedItemDescriptor | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

A versioned item (or placeholder) storable in a [PersistentDataStore].

-
-
-

Hierarchy

-
    -
  • SerializedItemDescriptor
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
deleted: boolean
-
- -
serializedItem: string
-
- -
version: number
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/interfaces.VersionedData.html b/docs/interfaces/interfaces.VersionedData.html deleted file mode 100644 index 7dc2173806..0000000000 --- a/docs/interfaces/interfaces.VersionedData.html +++ /dev/null @@ -1,97 +0,0 @@ -VersionedData | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Used internally to describe the basic properties of stored data such as feature flags or user -segments.

-

This is the actual type of parameters and return values in LDFeatureStore methods that refer -to a flag or segment item. Those methods still use the object type for backward compatibility.

-
-
-

Hierarchy

-
    -
  • VersionedData
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
deleted?: boolean
-

True if this is a deleted item placeholder (tombstone).

-
-
- -
key: string
-

The item's unique key, such as a feature flag key.

-
-
- -
version: number
-

A version number that LaunchDarkly will increment each time this item is changed.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.Crypto.html b/docs/interfaces/platform.Crypto.html deleted file mode 100644 index 54a6be6cb3..0000000000 --- a/docs/interfaces/platform.Crypto.html +++ /dev/null @@ -1,99 +0,0 @@ -Crypto | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Interface provided by the platform for doing cryptographic operations.

-
-
-

Hierarchy

-
    -
  • Crypto
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.EventSource.html b/docs/interfaces/platform.EventSource.html deleted file mode 100644 index aa5d220a1c..0000000000 --- a/docs/interfaces/platform.EventSource.html +++ /dev/null @@ -1,138 +0,0 @@ -EventSource | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Hierarchy

-
    -
  • EventSource
-
-
-
- -
-
-

Properties

-
-
-

Methods

-
-
-

Properties

-
- -
onclose: undefined | (() => void)
-
- -
onerror: undefined | (() => void)
-
- -
onopen: undefined | (() => void)
-
- -
onretrying: undefined | ((e: { delayMillis: number }) => void)
-
-

Methods

-
- -
    - -
  • -
    -

    Parameters

    -
      -
    • -
      type: string
    • -
    • -
      listener: ((event?: { data?: any }) => void)
      -
        -
      • -
          -
        • (event?: { data?: any }): void
        • -
        • -
          -

          Parameters

          -
            -
          • -
            Optional event: { data?: any }
            -
              -
            • -
              Optional data?: any
          -

          Returns void

    -

    Returns void

-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.EventSourceInitDict.html b/docs/interfaces/platform.EventSourceInitDict.html deleted file mode 100644 index 7306f78c14..0000000000 --- a/docs/interfaces/platform.EventSourceInitDict.html +++ /dev/null @@ -1,122 +0,0 @@ -EventSourceInitDict | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Hierarchy

-
    -
  • EventSourceInitDict
-
-
-
- -
-
-

Properties

-
- -
errorFilter: ((err: { message: string; status: number }) => boolean)
-
-

Type declaration

-
    -
  • -
      -
    • (err: { message: string; status: number }): boolean
    • -
    • -
      -

      Parameters

      -
        -
      • -
        err: { message: string; status: number }
        -
          -
        • -
          message: string
        • -
        • -
          status: number
      -

      Returns boolean

-
- -
headers: { [key: string]: string | string[] }
-
-

Type declaration

-
    -
  • -
    [key: string]: string | string[]
-
- -
initialRetryDelayMillis: number
-
- -
readTimeoutMillis: number
-
- -
retryResetIntervalMillis: number
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.Filesystem.html b/docs/interfaces/platform.Filesystem.html deleted file mode 100644 index c6d4f5c875..0000000000 --- a/docs/interfaces/platform.Filesystem.html +++ /dev/null @@ -1,135 +0,0 @@ -Filesystem | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Interface for doing filesystem operations on the platform.

-
-
-

Hierarchy

-
    -
  • Filesystem
-
-
-
- -
-
-

Methods

-
- -
    - -
  • -

    The time, in ms since POSIX epoch, that the file was last modified.

    - -

    Returns

    A promise which will resolve to a timestamp if succeful, or be -rejected if the operation fails.

    -
    -
    -

    Parameters

    -
      -
    • -
      path: string
      -

      The path to get a timestamp for.

      -
    -

    Returns Promise<number>

-
- -
    - -
  • -

    Read a file into a utf8 encoded string.

    - -

    Returns

    A promise which will resolve to utf8 encoded file content, or be -rejected if the operation fails.

    -
    -
    -

    Parameters

    -
      -
    • -
      path: string
      -

      The path of the file to read.

      -
    -

    Returns Promise<string>

-
- -
    - -
  • -

    Watch for changes to the specified path.

    -

    The implementation of this methods should be non-persistent. Meaning that -it should not keep the containing process running as long as it is -executing. For node this means setting the persistent option to false.

    - -

    Returns

    An async iterator that watches for changes to path.

    -
    -
    -

    Parameters

    -
      -
    • -
      path: string
      -

      The path to watch.

      -
    -

    Returns AsyncIterable<{ eventType: string; filename: string }>

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.Hasher.html b/docs/interfaces/platform.Hasher.html deleted file mode 100644 index fecf5fc0c6..0000000000 --- a/docs/interfaces/platform.Hasher.html +++ /dev/null @@ -1,101 +0,0 @@ -Hasher | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Interface implemented by platform provided hasher.

-

The hash implementation must support 'sha256' and 'sha1'.

-

The has implementation must support digesting to 'hex' or 'base64'.

-
-
-

Hierarchy

-
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.Headers.html b/docs/interfaces/platform.Headers.html deleted file mode 100644 index 3297e78ad7..0000000000 --- a/docs/interfaces/platform.Headers.html +++ /dev/null @@ -1,148 +0,0 @@ -Headers | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Interface for headers that are part of a fetch response.

-
-
-

Hierarchy

-
    -
  • Headers
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.Hmac.html b/docs/interfaces/platform.Hmac.html deleted file mode 100644 index e30877c1c1..0000000000 --- a/docs/interfaces/platform.Hmac.html +++ /dev/null @@ -1,103 +0,0 @@ -Hmac | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Interface implemented by platform provided hmac.

-

The hash implementation must support 'sha256'.

-

The has implementation must support digesting to 'hex'.

-
-
-

Hierarchy

-
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.Info.html b/docs/interfaces/platform.Info.html deleted file mode 100644 index facf89b02a..0000000000 --- a/docs/interfaces/platform.Info.html +++ /dev/null @@ -1,92 +0,0 @@ -Info | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Interface for getting information about the SDK or the environment it is -executing in.

-
-
-

Hierarchy

-
    -
  • Info
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.Options.html b/docs/interfaces/platform.Options.html deleted file mode 100644 index 1be6263860..0000000000 --- a/docs/interfaces/platform.Options.html +++ /dev/null @@ -1,92 +0,0 @@ -Options | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Hierarchy

-
    -
  • Options
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
body?: string
-
- -
headers?: Record<string, string>
-
- -
method?: string
-
- -
timeout?: number
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.Platform.html b/docs/interfaces/platform.Platform.html deleted file mode 100644 index 48acfb46f0..0000000000 --- a/docs/interfaces/platform.Platform.html +++ /dev/null @@ -1,102 +0,0 @@ -Platform | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Hierarchy

-
    -
  • Platform
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
crypto: Crypto
-

The interface for performing cryptographic operations.

-
-
- -
fileSystem?: Filesystem
-

The interface for performing file system operations. If the platform does -not support filesystem access, then this may be undefined.

-
-
- -
info: Info
-

The interface for getting information about the platform and the execution -environment.

-
-
- -
requests: Requests
-

The interface for performing http/https requests.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.PlatformData.html b/docs/interfaces/platform.PlatformData.html deleted file mode 100644 index c1f51ad7d1..0000000000 --- a/docs/interfaces/platform.PlatformData.html +++ /dev/null @@ -1,121 +0,0 @@ -PlatformData | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Information about the platform of the SDK and the environment it is executing.

-
-
-

Hierarchy

-
    -
  • PlatformData
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
additional?: Record<string, string>
-

Any additional attributes associated with the platform.

-
-
- -
name?: string
-

The name of the platform the SDK is running on. For instance 'Node'.

-
-
- -
os?: { arch?: string; name?: string; version?: string }
-

Information about the OS on which the SDK is running. Should be populated -when available. Not all platforms will make this data accessible.

-
-
-

Type declaration

-
    -
  • -
    Optional arch?: string
    -

    The architecture. Ideally at runtime, but may be build time if that is -a constraint.

    -
  • -
  • -
    Optional name?: string
    -

    The name of the OS. "MacOS", "Windows", or "Linux". If not one of those, -then use the value provided by the OS.

    -
  • -
  • -
    Optional version?: string
    -

    The version of the OS.

    -
-
- -
version?: string
-

The version of the platform the SDK is running on. e.g. "13.1.0".

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.Requests.html b/docs/interfaces/platform.Requests.html deleted file mode 100644 index c6f3467976..0000000000 --- a/docs/interfaces/platform.Requests.html +++ /dev/null @@ -1,98 +0,0 @@ -Requests | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Hierarchy

-
    -
  • Requests
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.Response.html b/docs/interfaces/platform.Response.html deleted file mode 100644 index e3ac9f3f6e..0000000000 --- a/docs/interfaces/platform.Response.html +++ /dev/null @@ -1,110 +0,0 @@ -Response | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Interface for fetch responses.

-
-
-

Hierarchy

-
    -
  • Response
-
-
-
- -
-
-

Properties

-
-
-

Methods

-
-
-

Properties

-
- -
headers: Headers
-
- -
status: number
-
-

Methods

-
- -
-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/platform.SdkData.html b/docs/interfaces/platform.SdkData.html deleted file mode 100644 index 36aa0479b1..0000000000 --- a/docs/interfaces/platform.SdkData.html +++ /dev/null @@ -1,100 +0,0 @@ -SdkData | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Hierarchy

-
    -
  • SdkData
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
name?: string
-

The name of the SDK. e.g. "node-server-sdk"

-
-
- -
version?: string
-

The version of the SDK.

-
-
- -
wrapperName?: string
-

Name of the wrapper SDK if present.

-
-
- -
wrapperVersion?: string
-

Version of the wrapper if present.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/subsystems.LDFeatureStore.html b/docs/interfaces/subsystems.LDFeatureStore.html deleted file mode 100644 index cbe1a1a36d..0000000000 --- a/docs/interfaces/subsystems.LDFeatureStore.html +++ /dev/null @@ -1,312 +0,0 @@ -LDFeatureStore | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Interface for a feature store component.

-

The feature store is what the client uses to store feature flag data that has been received -from LaunchDarkly. By default, it uses an in-memory implementation; database integrations are -also available. Read the SDK features guide. -You will not need to use this interface unless you are writing your own implementation.

-

Feature store methods can and should call their callbacks directly whenever possible, rather -than deferring them with setImmediate() or process.nextTick(). This means that if for any -reason you are updating or querying a feature store directly in your application code (which -is not part of normal use of the SDK) you should be aware that the callback may be executed -immediately.

-
-
-

Hierarchy

-
    -
  • LDFeatureStore
-
-
-
- -
-
-

Methods

-
-
-

Methods

-
- -
-
- -
-
- -
    - -
  • -

    Delete an entity from the store.

    -

    Deletion should be implemented by storing a placeholder object with the property -deleted: true and a version property equal to the provided version. In other words, -it should be exactly the same as calling upsert with such an object.

    -
    -
    -

    Parameters

    -
      -
    • -
      kind: DataKind
      -

      The type of data to be accessed. The actual type of this parameter is - interfaces.DataKind.

      -
    • -
    • -
      key: string
      -

      The unique key of the entity within the specified collection.

      -
    • -
    • -
      version: number
      -

      A number that must be greater than the version property of the existing entity in - order for it to be deleted. If it is less than or equal to the existing version, the - method should do nothing.

      -
    • -
    • -
      callback: (() => void)
      -

      Will be called when the delete operation is complete.

      -
      -
        -
      • -
          -
        • (): void
        • -
        • -

          Returns void

    -

    Returns void

-
- -
-
- -
    - -
  • -

    Initialize the store, overwriting any existing data.

    -
    -
    -

    Parameters

    -
      -
    • -
      allData: LDFeatureStoreDataStorage
      -

      An object in which each key is the "namespace" of a collection (e.g. "features") and - the value is an object that maps keys to entities. The actual type of this parameter is - interfaces.FullDataSet<VersionedData>.

      -
    • -
    • -
      callback: (() => void)
      -

      Will be called when the store has been initialized.

      -
      -
        -
      • -
          -
        • (): void
        • -
        • -

          Returns void

    -

    Returns void

-
- -
    - -
  • -

    Tests whether the store is initialized.

    -

    "Initialized" means that the store has been populated with data, either by the client -having called init() within this process, or by another process (if this is a shared -database).

    -
    -
    -

    Parameters

    -
      -
    • -
      callback: ((isInitialized: boolean) => void)
      -

      Will be called back with the boolean result.

      -
      -
        -
      • -
          -
        • (isInitialized: boolean): void
        • -
        • -
          -

          Parameters

          -
            -
          • -
            isInitialized: boolean
          -

          Returns void

    -

    Returns void

-
- -
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/subsystems.LDFeatureStoreDataStorage.html b/docs/interfaces/subsystems.LDFeatureStoreDataStorage.html deleted file mode 100644 index 9fe6ff91fe..0000000000 --- a/docs/interfaces/subsystems.LDFeatureStoreDataStorage.html +++ /dev/null @@ -1,63 +0,0 @@ -LDFeatureStoreDataStorage | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Represents the storage for the full data store.

-
-
-

Hierarchy

-
    -
  • LDFeatureStoreDataStorage
-
-

Indexable

-
[namespace: string]: LDFeatureStoreKindData
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/subsystems.LDFeatureStoreItem.html b/docs/interfaces/subsystems.LDFeatureStoreItem.html deleted file mode 100644 index 3277a0926e..0000000000 --- a/docs/interfaces/subsystems.LDFeatureStoreItem.html +++ /dev/null @@ -1,86 +0,0 @@ -LDFeatureStoreItem | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Represents an item which can be stored in the feature store.

-
-
-

Hierarchy

-
-
-

Indexable

-
[attribute: string]: any
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
deleted?: boolean
-
- -
version: number
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/subsystems.LDFeatureStoreKindData.html b/docs/interfaces/subsystems.LDFeatureStoreKindData.html deleted file mode 100644 index f46aee39ee..0000000000 --- a/docs/interfaces/subsystems.LDFeatureStoreKindData.html +++ /dev/null @@ -1,63 +0,0 @@ -LDFeatureStoreKindData | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

Represents the storage for a single kind of data. e.g. 'flag' or 'segment'.

-
-
-

Hierarchy

-
    -
  • LDFeatureStoreKindData
-
-

Indexable

-
[key: string]: LDFeatureStoreItem
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/interfaces/subsystems.LDKeyedFeatureStoreItem.html b/docs/interfaces/subsystems.LDKeyedFeatureStoreItem.html deleted file mode 100644 index 6df2a640b6..0000000000 --- a/docs/interfaces/subsystems.LDKeyedFeatureStoreItem.html +++ /dev/null @@ -1,92 +0,0 @@ -LDKeyedFeatureStoreItem | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
-

When upserting an item it must contain a key.

-
-
-

Hierarchy

-
-
-
-
- -
-
-

Properties

-
-
-

Properties

-
- -
deleted?: boolean
-
- -
key: string
-
- -
version: number
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/modules.html b/docs/modules.html deleted file mode 100644 index 16f75f42ad..0000000000 --- a/docs/modules.html +++ /dev/null @@ -1,133 +0,0 @@ -@launchdarkly/js-server-sdk-common
-
- -
- -
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/modules/integrations.html b/docs/modules/integrations.html deleted file mode 100644 index 491e498fe6..0000000000 --- a/docs/modules/integrations.html +++ /dev/null @@ -1,61 +0,0 @@ -integrations | @launchdarkly/js-server-sdk-common
-
- -
-
- -
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/modules/interfaces.html b/docs/modules/interfaces.html deleted file mode 100644 index 938f5a7b5f..0000000000 --- a/docs/modules/interfaces.html +++ /dev/null @@ -1,84 +0,0 @@ -interfaces | @launchdarkly/js-server-sdk-common
-
- -
- -
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/modules/platform.html b/docs/modules/platform.html deleted file mode 100644 index 6710b335a7..0000000000 --- a/docs/modules/platform.html +++ /dev/null @@ -1,81 +0,0 @@ -platform | @launchdarkly/js-server-sdk-common
-
- -
-
- -
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/modules/subsystems.html b/docs/modules/subsystems.html deleted file mode 100644 index 78148ffbbc..0000000000 --- a/docs/modules/subsystems.html +++ /dev/null @@ -1,63 +0,0 @@ -subsystems | @launchdarkly/js-server-sdk-common
-
- -
- -
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/types/LDContext.html b/docs/types/LDContext.html deleted file mode 100644 index b61a7e69a8..0000000000 --- a/docs/types/LDContext.html +++ /dev/null @@ -1,86 +0,0 @@ -LDContext | @launchdarkly/js-server-sdk-common
-
- -
- -
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/types/LDFlagValue.html b/docs/types/LDFlagValue.html deleted file mode 100644 index 4760d170aa..0000000000 --- a/docs/types/LDFlagValue.html +++ /dev/null @@ -1,87 +0,0 @@ -LDFlagValue | @launchdarkly/js-server-sdk-common
-
- -
- -
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/types/LDLogLevel.html b/docs/types/LDLogLevel.html deleted file mode 100644 index 2cc3557ca8..0000000000 --- a/docs/types/LDLogLevel.html +++ /dev/null @@ -1,90 +0,0 @@ -LDLogLevel | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
LDLogLevel: "debug" | "info" | "warn" | "error" | "none"
-

Logging levels that can be used with basicLogger.

-

Set BasicLoggerOptions.level to one of these values to control what levels -of log messages are enabled. Going from lowest importance (and most verbose) -to most importance, the levels are 'debug', 'info', 'warn', and 'error'. -You can also specify 'none' instead to disable all logging.

-
-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/types/interfaces.FullDataSet.html b/docs/types/interfaces.FullDataSet.html deleted file mode 100644 index 7d538a644f..0000000000 --- a/docs/types/interfaces.FullDataSet.html +++ /dev/null @@ -1,71 +0,0 @@ -FullDataSet | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
FullDataSet<T>: Record<string, KeyedItems<T>>
-

Used internally to describe a full set of environment data, which can include both feature -flags and user segments. The string key for each item is the namespace property of a -DataKind.

-
-

Type Parameters

-
    -
  • -

    T

-
-
-

Generated using TypeDoc

-
\ No newline at end of file diff --git a/docs/types/interfaces.KeyedItems.html b/docs/types/interfaces.KeyedItems.html deleted file mode 100644 index 6272e877c1..0000000000 --- a/docs/types/interfaces.KeyedItems.html +++ /dev/null @@ -1,70 +0,0 @@ -KeyedItems | @launchdarkly/js-server-sdk-common
-
- -
-
-
- -
KeyedItems<T>: Record<string, T>
-

Used internally to describe a set of stored data items of the same kind, such as feature flags -or user segments. The string key for each item is the same as the item's key property.

-
-

Type Parameters

-
    -
  • -

    T

-
-
-

Generated using TypeDoc

-
\ No newline at end of file From 8daa0bb01b198027c95ce6cd7a6e00d34921f023 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 3 Aug 2022 16:25:21 -0700 Subject: [PATCH 04/34] Start adding top level client tests. --- platform-node/__tests__/LDClientNode.test.ts | 48 +++++++++++++++++ sdk-common/src/Context.ts | 2 +- .../__tests__/LDClientImpl.test.ts | 52 +++++++++++++++++++ server-sdk-common/src/LDClientImpl.ts | 24 +++++---- 4 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 platform-node/__tests__/LDClientNode.test.ts create mode 100644 server-sdk-common/__tests__/LDClientImpl.test.ts diff --git a/platform-node/__tests__/LDClientNode.test.ts b/platform-node/__tests__/LDClientNode.test.ts new file mode 100644 index 0000000000..da9ccf18d1 --- /dev/null +++ b/platform-node/__tests__/LDClientNode.test.ts @@ -0,0 +1,48 @@ +import { LDContext } from '@launchdarkly/js-server-sdk-common'; +import { init } from '../src'; + +it('fires ready event in offline mode', (done) => { + const client = init('sdk_key', { offline: true }); + client.on('ready', () => { + done(); + }); +}); + +it('fires the failed event if initialization fails', (done) => { + const client = init('sdk_key', { + updateProcessor: { + start: (fn: (err: any) => void) => { + setTimeout(() => { + fn(new Error("BAD THINGS")); + }, 0); + }, + stop: () => { }, + close: () => { } + }, + }); + client.on('failed', () => { + done(); + }); +}); + +// These tests are done in the node implementation because common doesn't have a crypto +// implementation. +describe('when using secure mode hash', () => { + it('correctly computes hash for a known message and secret', () => { + var client = init('secret', {offline: true}); + var hash = client.secureModeHash({"key": "Message"}); + expect(hash).toEqual("aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597"); + }); + + it.each<[LDContext, string]>([ + [{key: 'Message'}, 'aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597'], + [{kind: 'user', key: 'Message'}, 'aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597'], + [{kind: 'org', key: 'orgtest'}, '40bc9b2e66a842e269ab98dad813e4e15203bbbfd91e8c96b92f3ae6f3f5e223'], + [{kind: 'multi', user: {key: 'user:test'}, org: {key: 'org:test'}}, '607cc91526c615823e320dabca7967ce544fbe83bcb2b7287163f2d1c7aa210f'] +])('it uses the canonical key %p', (context, expectedHash) => { + const client = init('secret', {offline: true}); + const hash = client.secureModeHash(context); + + expect(hash).toEqual(expectedHash); + }); +}); \ No newline at end of file diff --git a/sdk-common/src/Context.ts b/sdk-common/src/Context.ts index 4e543bd1a5..60b6c16379 100644 --- a/sdk-common/src/Context.ts +++ b/sdk-common/src/Context.ts @@ -383,7 +383,7 @@ export default class Context { return this.context!.key; } if (this.isMulti) { - return Object.keys(this.contexts).map((key) => `${key}:${encodeURIComponent(this.contexts[key].key)}`) + return Object.keys(this.contexts).sort().map((key) => `${key}:${encodeURIComponent(this.contexts[key].key)}`) .join(':'); } return `${this.kind}:${encodeURIComponent(this.context!.key)}`; diff --git a/server-sdk-common/__tests__/LDClientImpl.test.ts b/server-sdk-common/__tests__/LDClientImpl.test.ts new file mode 100644 index 0000000000..0c19e9360e --- /dev/null +++ b/server-sdk-common/__tests__/LDClientImpl.test.ts @@ -0,0 +1,52 @@ +import { LDClientImpl } from '../src'; +import basicPlatform from './evaluation/mocks/platform'; + +it('fires ready event in offline mode', (done) => { + const _client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { offline: true }, + (_err) => { }, + (_err) => { }, + () => { + done(); + }, + (_key) => { }); +}); + +it('fires the failed event if initialization fails', (done) => { + const _client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + updateProcessor: { + start: (fn: (err: any) => void) => { + setTimeout(() => { + fn(new Error("BAD THINGS")); + }, 0); + }, + stop: () => {}, + close: () => {} + }, + }, + (_err) => { }, + (_err) => { + done() + }, + () => { }, + (_key) => { }); +}); + +it('isOffline returns true in offline mode', (done) => { + const client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { offline: true }, + (_err) => { }, + (_err) => { }, + () => { + expect(client.isOffline()).toEqual(true); + done(); + }, + (_key) => { }); +}); \ No newline at end of file diff --git a/server-sdk-common/src/LDClientImpl.ts b/server-sdk-common/src/LDClientImpl.ts index 9d739ba006..8c423df91f 100644 --- a/server-sdk-common/src/LDClientImpl.ts +++ b/server-sdk-common/src/LDClientImpl.ts @@ -52,11 +52,11 @@ export default class LDClientImpl implements LDClient { private evaluator: Evaluator; - private initResolve!: (value: LDClient | PromiseLike) => void; + private initResolve?: (value: LDClient | PromiseLike) => void; - private initReject!: (err: Error) => void; + private initReject?: (err: Error) => void; - private initializedPromise: Promise; + private initializedPromise?: Promise; private logger?: LDLogger; @@ -87,11 +87,6 @@ export default class LDClientImpl implements LDClient { this.config = config; this.logger = config.logger; - this.initializedPromise = new Promise((resolve, reject) => { - this.initResolve = resolve; - this.initReject = reject; - }); - const makeDefaultProcessor = () => (config.stream ? new StreamingProcessor( sdkKey, config, @@ -154,11 +149,11 @@ export default class LDClientImpl implements LDClient { this.onError(error); this.onFailed(error); - this.initReject(error); + this.initReject?.(error); this.initState = InitState.Failed; } else if (!this.initialized()) { this.initState = InitState.Initialized; - this.initResolve(this); + this.initResolve?.(this); this.onReady(); } }); @@ -169,6 +164,15 @@ export default class LDClientImpl implements LDClient { } waitForInitialization(): Promise { + if (this.initState === InitState.Initialized) { + return Promise.resolve(this); + } + if (!this.initializedPromise) { + this.initializedPromise = new Promise((resolve, reject) => { + this.initResolve = resolve; + this.initReject = reject; + }); + } return this.initializedPromise; } From 7835b32c93c19895a3d7bcde163024450553651a Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Thu, 4 Aug 2022 09:32:40 -0700 Subject: [PATCH 05/34] Progress on tests. --- platform-node/__tests__/LDClientNode.test.ts | 4 +- .../__tests__/LDClientImpl.listeners.test.ts | 23 +++++ .../__tests__/LDClientImpl.test.ts | 84 ++++++++++++++++++- 3 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 server-sdk-common/__tests__/LDClientImpl.listeners.test.ts diff --git a/platform-node/__tests__/LDClientNode.test.ts b/platform-node/__tests__/LDClientNode.test.ts index da9ccf18d1..31f7ebc7a1 100644 --- a/platform-node/__tests__/LDClientNode.test.ts +++ b/platform-node/__tests__/LDClientNode.test.ts @@ -29,8 +29,8 @@ it('fires the failed event if initialization fails', (done) => { // implementation. describe('when using secure mode hash', () => { it('correctly computes hash for a known message and secret', () => { - var client = init('secret', {offline: true}); - var hash = client.secureModeHash({"key": "Message"}); + const client = init('secret', {offline: true}); + const hash = client.secureModeHash({"key": "Message"}); expect(hash).toEqual("aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597"); }); diff --git a/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts new file mode 100644 index 0000000000..86a48fe773 --- /dev/null +++ b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts @@ -0,0 +1,23 @@ +import { LDClientImpl } from '../src'; +import TestData from '../src/integrations/test_data/TestData'; +import basicPlatform from './evaluation/mocks/platform'; + +describe('given a a client using test data', () => { + let td: TestData; + let client: LDClientImpl; + + const updates = []; + + beforeEach(() => { + client = new LDClientImpl('sdk-key', basicPlatform, { + sendEvents: false, + updateProcessor: td.getFactory(), + }, () => { }, () => { }, () => { }, (key) => { + updates.push(key); + }); + }); + + it('fires an event when a flag is updated', () => { + + }); +}); \ No newline at end of file diff --git a/server-sdk-common/__tests__/LDClientImpl.test.ts b/server-sdk-common/__tests__/LDClientImpl.test.ts index 0c19e9360e..b36868274e 100644 --- a/server-sdk-common/__tests__/LDClientImpl.test.ts +++ b/server-sdk-common/__tests__/LDClientImpl.test.ts @@ -1,5 +1,6 @@ import { LDClientImpl } from '../src'; import basicPlatform from './evaluation/mocks/platform'; +import TestLogger from './Logger'; it('fires ready event in offline mode', (done) => { const _client = new LDClientImpl( @@ -25,8 +26,8 @@ it('fires the failed event if initialization fails', (done) => { fn(new Error("BAD THINGS")); }, 0); }, - stop: () => {}, - close: () => {} + stop: () => { }, + close: () => { } }, }, (_err) => { }, @@ -49,4 +50,83 @@ it('isOffline returns true in offline mode', (done) => { done(); }, (_key) => { }); +}); + +describe('when waiting for initialization', () => { + let client: LDClientImpl; + + beforeEach(() => { + client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + updateProcessor: { + start: (fn: (err?: any) => void) => { + setTimeout(() => { + fn(); + }, 0); + }, + stop: () => { }, + close: () => { } + }, + sendEvents: false, + logger: new TestLogger() + }, + (_err) => { }, + (_err) => { }, + () => { }, + (_key) => { }); + }); + + it('resolves when ready', async () => { + await client.waitForInitialization(); + }); + + it('resolves immediately if the client is already ready', async () => { + await client.waitForInitialization(); + await client.waitForInitialization(); + }); + + it('creates only one Promise', async () => { + const p1 = client.waitForInitialization(); + const p2 = client.waitForInitialization(); + expect(p2).toBe(p1); + }) +}); + +it('does not crash when closing an offline client', () => { + const client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { offline: true }, + (_err) => { }, + (_err) => { }, + () => { + }, + (_key) => { }); + + expect(() => client.close()).not.toThrow(); +}); + +it('the wait for initialization promise is rejected if initialization fails', (done) => { + const client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + updateProcessor: { + start: (fn: (err: any) => void) => { + setTimeout(() => { + fn(new Error("BAD THINGS")); + }, 0); + }, + stop: () => { }, + close: () => { } + }, + }, + (_err) => { }, + (_err) => { }, + () => { }, + (_key) => { }); + + client.waitForInitialization().catch(() => done()); }); \ No newline at end of file From 1b96fada379fd2f4edc8eecfac1bd2ef23d1c986 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 8 Aug 2022 10:54:12 -0700 Subject: [PATCH 06/34] Start implementation of LDClientContext. --- .../__tests__/LDClientImpl.listeners.test.ts | 7 +-- server-sdk-common/src/ClientContext.ts | 23 +++++++++ .../src/api/options/LDClientContext.ts | 50 +++++++++++++++++++ .../src/api/options/LDOptions.ts | 7 ++- .../src/api/subsystems/LDDataSourceUpdates.ts | 43 ++++++++++++++++ server-sdk-common/src/api/subsystems/index.ts | 2 + .../src/integrations/test_data/TestData.ts | 15 ++++-- .../src/options/Configuration.ts | 2 +- 8 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 server-sdk-common/src/ClientContext.ts create mode 100644 server-sdk-common/src/api/options/LDClientContext.ts create mode 100644 server-sdk-common/src/api/subsystems/LDDataSourceUpdates.ts diff --git a/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts index 86a48fe773..06b5ce0810 100644 --- a/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts +++ b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts @@ -9,6 +9,7 @@ describe('given a a client using test data', () => { const updates = []; beforeEach(() => { + td = new TestData(); client = new LDClientImpl('sdk-key', basicPlatform, { sendEvents: false, updateProcessor: td.getFactory(), @@ -17,7 +18,7 @@ describe('given a a client using test data', () => { }); }); - it('fires an event when a flag is updated', () => { - + it('fires an event when a flag is added', () => { + td.update(td.flag('new-flag')); }); -}); \ No newline at end of file +}); diff --git a/server-sdk-common/src/ClientContext.ts b/server-sdk-common/src/ClientContext.ts new file mode 100644 index 0000000000..1b9b6058d8 --- /dev/null +++ b/server-sdk-common/src/ClientContext.ts @@ -0,0 +1,23 @@ +import { LDBasicConfiguration, LDClientContext } from './api/options/LDClientContext'; +import Configuration from './options/Configuration'; +import { Platform } from './platform'; + +/** + * @internal + */ +export default class ClientContext implements LDClientContext { + basicConfiguration: LDBasicConfiguration; + + constructor( + sdkKey: string, + configuration: Configuration, + public readonly platform: Platform + ) { + this.basicConfiguration = { + logger: configuration.logger, + offline: configuration.offline, + sdkKey, + serviceEndpoints: configuration.serviceEndpoints + }; + } +} diff --git a/server-sdk-common/src/api/options/LDClientContext.ts b/server-sdk-common/src/api/options/LDClientContext.ts new file mode 100644 index 0000000000..6f2e08385f --- /dev/null +++ b/server-sdk-common/src/api/options/LDClientContext.ts @@ -0,0 +1,50 @@ +import { LDLogger } from '@launchdarkly/js-sdk-common'; +import { Platform } from '../../platform'; + +/** + * Specifies the base service URIs used by SDK components. + */ +export interface LDServiceEndpoints { + // Properties are for internal SDK components. +} + +/** + * The most basic properties of the SDK client that are available to all SDK component factories. + */ +export interface LDBasicConfiguration { + logger?: LDLogger; + + /** + * True if the SDK was configured to be completely offline. + */ + offline: boolean; + + /** + * The configured SDK key. + */ + sdkKey: string; + + /** + * Defines the base service URIs used by SDK components. + */ + serviceEndpoints: LDServiceEndpoints; +} + +/** + * Factory methods receive this class as a parameter. + * + * Its public properties provide information about the SDK configuration and environment. The SDK + * may also include non-public properties that are relevant only when creating one of the built-in + * component types and are not accessible to custom components. + */ +export interface LDClientContext { + /** + * The SDK's basic global properties. + */ + basicConfiguration: LDBasicConfiguration; + + /** + * Interfaces providing platform specific information and functionality. + */ + platform: Platform +} \ No newline at end of file diff --git a/server-sdk-common/src/api/options/LDOptions.ts b/server-sdk-common/src/api/options/LDOptions.ts index 24a0db5aff..c204f0eeaf 100644 --- a/server-sdk-common/src/api/options/LDOptions.ts +++ b/server-sdk-common/src/api/options/LDOptions.ts @@ -1,6 +1,8 @@ import { LDLogger } from '@launchdarkly/js-sdk-common'; +import { LDDataSourceUpdates, LDStreamProcessor } from '../subsystems'; import { LDFeatureStore } from '../subsystems/LDFeatureStore'; import { LDBigSegmentsOptions } from './LDBigSegmentsOptions'; +import { LDClientContext } from './LDClientContext'; import { LDProxyOptions } from './LDProxyOptions'; import { LDTLSOptions } from './LDTLSOptions'; @@ -66,7 +68,7 @@ export interface LDOptions { * provide a factory function that creates the store implementation based on the SDK * configuration; this property accepts either. */ - featureStore?: LDFeatureStore | ((options: LDOptions) => LDFeatureStore); + featureStore?: LDFeatureStore | ((clientContext: LDClientContext) => LDFeatureStore); /** * Additional parameters for configuring the SDK's Big Segments behavior. @@ -87,7 +89,8 @@ export interface LDOptions { * By default, this is the client's default streaming or polling component. It can be changed * for testing purposes; see [[FileDataSource]]. */ - updateProcessor?: object; + updateProcessor?: object | + ((clientContext: LDClientContext, dataSourceUpdates: LDDataSourceUpdates) => LDStreamProcessor); /** * The interval in between flushes of the analytics events queue, in seconds. diff --git a/server-sdk-common/src/api/subsystems/LDDataSourceUpdates.ts b/server-sdk-common/src/api/subsystems/LDDataSourceUpdates.ts new file mode 100644 index 0000000000..a62669fce1 --- /dev/null +++ b/server-sdk-common/src/api/subsystems/LDDataSourceUpdates.ts @@ -0,0 +1,43 @@ +import { DataKind } from '../interfaces'; +import { LDFeatureStoreDataStorage, LDKeyedFeatureStoreItem } from './LDFeatureStore'; + +/** + * Interface that a data source implementation will use to push data into the SDK. + * + * The data source interacts with this object, rather than manipulating the data store directly, so + * that the SDK can perform any other necessary operations that must happen when data is updated. + */ +export interface LDDataSourceUpdates { + /** + * Completely overwrites the current contents of the data store with a set of items for each + * collection. + * + * @param allData + * An object in which each key is the "namespace" of a collection (e.g. `"features"`) and + * the value is an object that maps keys to entities. The actual type of this parameter is + * `interfaces.FullDataSet`. + * + * @param callback + * Will be called when the store has been initialized. + */ + init(allData: LDFeatureStoreDataStorage, callback: () => void): void; + + /** + * Updates or inserts an item in the specified collection. For updates, the object will only be + * updated if the existing version is less than the new version. + * + * @param kind + * The type of data to be accessed. The actual type of this parameter is + * [[interfaces.DataKind]]. + * + * @param data + * The contents of the entity, as an object that can be converted to JSON. The store + * should check the `version` property of this object, and should *not* overwrite any + * existing data if the existing `version` is greater than or equal to that value. + * The actual type of this parameter is [[interfaces.VersionedData]]. + * + * @param callback + * Will be called after the upsert operation is complete. + */ + upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void; +} diff --git a/server-sdk-common/src/api/subsystems/index.ts b/server-sdk-common/src/api/subsystems/index.ts index b6b2ca3aed..23bb2a2d39 100644 --- a/server-sdk-common/src/api/subsystems/index.ts +++ b/server-sdk-common/src/api/subsystems/index.ts @@ -1,2 +1,4 @@ export * from './LDFeatureRequestor'; export * from './LDFeatureStore'; +export * from './LDStreamProcessor'; +export * from './LDDataSourceUpdates'; diff --git a/server-sdk-common/src/integrations/test_data/TestData.ts b/server-sdk-common/src/integrations/test_data/TestData.ts index c1a44e3489..f0cc3551ab 100644 --- a/server-sdk-common/src/integrations/test_data/TestData.ts +++ b/server-sdk-common/src/integrations/test_data/TestData.ts @@ -1,4 +1,6 @@ import { LDStreamProcessor } from '../../api'; +import { LDClientContext } from '../../api/options/LDClientContext'; +import { LDFeatureStore } from '../../api/subsystems'; import { Flag } from '../../evaluation/data/Flag'; import { Segment } from '../../evaluation/data/Segment'; import Configuration from '../../options/Configuration'; @@ -54,12 +56,19 @@ export default class TestData { * Get a factory for update processors that will be attached to this TestData instance. * @returns An update processor factory. */ - getFactory(): (config: Configuration) => LDStreamProcessor { + getFactory(): ( + clientContext: LDClientContext, + featureStore: LDFeatureStore + ) => LDStreamProcessor { // Provides an arrow function to prevent needed to bind the method to // maintain `this`. - return (config: Configuration) => { + return ( + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + clientContext: LDClientContext, + featureStore: LDFeatureStore, + ) => { const newSource = new TestDataSource( - new AsyncStoreFacade(config.featureStore), + new AsyncStoreFacade(featureStore), this.currentFlags, this.currentSegments, diff --git a/server-sdk-common/src/options/Configuration.ts b/server-sdk-common/src/options/Configuration.ts index 30602bcf7c..bb2688a4b2 100644 --- a/server-sdk-common/src/options/Configuration.ts +++ b/server-sdk-common/src/options/Configuration.ts @@ -30,7 +30,7 @@ const validations: Record = { logger: TypeValidators.Object, featureStore: TypeValidators.ObjectOrFactory, bigSegments: TypeValidators.Object, - updateProcessor: TypeValidators.Object, + updateProcessor: TypeValidators.ObjectOrFactory, flushInterval: TypeValidators.Number, pollInterval: TypeValidators.numberWithMin(30), proxyOptions: TypeValidators.Object, From 18590c44fcce213582cd0551b23a8bad4db86443 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 8 Aug 2022 11:03:59 -0700 Subject: [PATCH 07/34] Change file data source factory to be LDClientContext based. --- .../data_sources/FileDataSource.test.ts | 223 +++++++++++++----- .../integrations/test_data/TestData.test.ts | 37 +-- .../src/data_sources/FileDataSourceFactory.ts | 9 +- 3 files changed, 196 insertions(+), 73 deletions(-) diff --git a/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts b/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts index 19b1b986e8..7de89225f2 100644 --- a/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts +++ b/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts @@ -1,9 +1,11 @@ import { Context } from '@launchdarkly/js-sdk-common'; import promisify from '../../src/async/promisify'; +import ClientContext from '../../src/ClientContext'; import FileDataSourceFactory from '../../src/data_sources/FileDataSourceFactory'; import { Flag } from '../../src/evaluation/data/Flag'; import { Segment } from '../../src/evaluation/data/Segment'; import Evaluator from '../../src/evaluation/Evaluator'; +import Configuration from '../../src/options/Configuration'; import { Filesystem, WatchHandle } from '../../src/platform'; import AsyncStoreFacade from '../../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; @@ -90,8 +92,8 @@ class MockFilesystem implements Filesystem { }> = {}; public watches: Record void } - )[]> = {}; + (WatchHandle & { id: number, cb: (eventType: string, filename: string) => void } + )[]> = {}; public watchHandleId = 0; @@ -156,11 +158,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['testfile.json'], }); - factory.create({ - featureStore, - logger, - }, filesystem); - + factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); expect(await asyncFeatureStore.initialized()).toBeFalsy(); expect(await asyncFeatureStore.all(VersionedDataKinds.Features)).toEqual({}); @@ -177,10 +185,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['testfile.json'], }); - const fds = factory.create({ - featureStore, - logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -200,10 +215,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['missing-file.json'], }); - const fds = factory.create({ - featureStore, - logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async (err) => { expect(err).toBeDefined(); @@ -222,10 +244,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['malformed_file.json'], }); - const fds = factory.create({ - featureStore, - logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async (err) => { expect(err).toBeDefined(); @@ -247,9 +276,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['file1.json', 'file2.json'], }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -273,9 +310,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['file1.json', 'file2.json'], }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async (err) => { expect(err).toBeDefined(); @@ -294,9 +339,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['file1.json', 'file2.json'], }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -314,9 +367,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['file1.json'], }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async () => { const evaluator = new Evaluator(basicPlatform, { @@ -348,9 +409,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['file1.json'], }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async () => { const evaluator = new Evaluator(basicPlatform, { @@ -387,9 +456,17 @@ describe('given a mock filesystem and memory feature store', () => { autoUpdate: true, }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -414,9 +491,17 @@ describe('given a mock filesystem and memory feature store', () => { autoUpdate: true, }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -454,9 +539,17 @@ describe('given a mock filesystem and memory feature store', () => { autoUpdate: true, }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -497,9 +590,17 @@ describe('given a mock filesystem and memory feature store', () => { autoUpdate: true, }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -533,10 +634,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: [fileName], }); - const fds = factory.create({ - featureStore, - logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); const err = await promisify((cb) => { fds.start(cb); @@ -562,10 +670,17 @@ describe('given a mock filesystem and memory feature store', () => { yamlParser: parser, }); - const fds = factory.create({ - featureStore, - logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + ), + featureStore + ); const err = await promisify((cb) => { fds.start(cb); diff --git a/server-sdk-common/__tests__/integrations/test_data/TestData.test.ts b/server-sdk-common/__tests__/integrations/test_data/TestData.test.ts index ce41ce75b8..b98e97ffe0 100644 --- a/server-sdk-common/__tests__/integrations/test_data/TestData.test.ts +++ b/server-sdk-common/__tests__/integrations/test_data/TestData.test.ts @@ -1,4 +1,5 @@ import { AttributeReference } from '../../../src'; +import ClientContext from '../../../src/ClientContext'; import { Flag } from '../../../src/evaluation/data/Flag'; import { FlagRule } from '../../../src/evaluation/data/FlagRule'; import TestData from '../../../src/integrations/test_data/TestData'; @@ -6,6 +7,7 @@ import Configuration from '../../../src/options/Configuration'; import AsyncStoreFacade from '../../../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../../../src/store/InMemoryFeatureStore'; import VersionedDataKinds from '../../../src/store/VersionedDataKinds'; +import basicPlatform from '../../evaluation/mocks/platform'; const basicBooleanFlag: Flag = { fallthrough: { @@ -23,9 +25,10 @@ it('initializes the data store with flags configured the data store is created', td.update(td.flag('new-flag').variationForAll(true)); const store = new InMemoryFeatureStore(); - const processor = td.getFactory()(new Configuration({ - featureStore: store, - })); + const processor = td.getFactory()( + new ClientContext('', new Configuration({}), basicPlatform), + store, + ); processor.start(); const facade = new AsyncStoreFacade(store); @@ -38,9 +41,10 @@ it('initializes the data store with flags configured the data store is created', it('updates the data store when update is called', async () => { const td = new TestData(); const store = new InMemoryFeatureStore(); - const processor = td.getFactory()(new Configuration({ - featureStore: store, - })); + const processor = td.getFactory()( + new ClientContext('', new Configuration({}), basicPlatform), + store, + ); processor.start(); const facade = new AsyncStoreFacade(store); @@ -57,9 +61,10 @@ it('can include pre-configured items', async () => { td.usePreconfiguredSegment({ key: 'my-segment', version: 2000 }); const store = new InMemoryFeatureStore(); - const processor = td.getFactory()(new Configuration({ - featureStore: store, - })); + const processor = td.getFactory()( + new ClientContext('', new Configuration({}), basicPlatform), + store, + ); processor.start(); @@ -104,9 +109,10 @@ it.each([true, false])('does not update the store after stop/close is called', a const td = new TestData(); const store = new InMemoryFeatureStore(); - const processor = td.getFactory()(new Configuration({ - featureStore: store, - })); + const processor = td.getFactory()( + new ClientContext('', new Configuration({}), basicPlatform), + store, + ); processor.start(); td.update(td.flag('new-flag').variationForAll(true)); @@ -131,9 +137,10 @@ it('can update a flag that already exists in the store', async () => { const store = new InMemoryFeatureStore(); - const processor = td.getFactory()(new Configuration({ - featureStore: store, - })); + const processor = td.getFactory()( + new ClientContext('', new Configuration({}), basicPlatform), + store, + ); processor.start(); td.update(td.flag('new-flag').variationForAll(true)); diff --git a/server-sdk-common/src/data_sources/FileDataSourceFactory.ts b/server-sdk-common/src/data_sources/FileDataSourceFactory.ts index aa2beb25e5..e0e35e9983 100644 --- a/server-sdk-common/src/data_sources/FileDataSourceFactory.ts +++ b/server-sdk-common/src/data_sources/FileDataSourceFactory.ts @@ -1,5 +1,6 @@ import { LDLogger } from '@launchdarkly/js-sdk-common'; import { FileDataSourceOptions } from '../api/integrations'; +import { LDClientContext } from '../api/options/LDClientContext'; import { LDFeatureStore } from '../api/subsystems'; import { Filesystem } from '../platform'; import FileDataSource from './FileDataSource'; @@ -29,15 +30,15 @@ export default class FileDataSourceFactory { * @returns a {@link FileDataSource} */ create( - config: FileDataSourceFactoryConfig, - filesystem: Filesystem, + ldClientContext: LDClientContext, + featureStore: LDFeatureStore, ) { const updatedOptions: FileDataSourceOptions = { paths: this.options.paths, autoUpdate: this.options.autoUpdate, - logger: this.options.logger || config.logger, + logger: this.options.logger || ldClientContext.basicConfiguration.logger, yamlParser: this.options.yamlParser, }; - return new FileDataSource(updatedOptions, filesystem, config.featureStore); + return new FileDataSource(updatedOptions, ldClientContext.platform.fileSystem!, featureStore); } } From bbc4b9f185b2dcbd9a0f504dbbe9067b3a9e5da5 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 8 Aug 2022 11:14:34 -0700 Subject: [PATCH 08/34] Fix open handles in tests. --- platform-node/__tests__/LDClientNode.test.ts | 29 +++++----- .../__tests__/LDClientImpl.listeners.test.ts | 24 --------- .../__tests__/LDClientImpl.test.ts | 54 ++++++++++++------- 3 files changed, 52 insertions(+), 55 deletions(-) delete mode 100644 server-sdk-common/__tests__/LDClientImpl.listeners.test.ts diff --git a/platform-node/__tests__/LDClientNode.test.ts b/platform-node/__tests__/LDClientNode.test.ts index 31f7ebc7a1..49d339e255 100644 --- a/platform-node/__tests__/LDClientNode.test.ts +++ b/platform-node/__tests__/LDClientNode.test.ts @@ -4,6 +4,7 @@ import { init } from '../src'; it('fires ready event in offline mode', (done) => { const client = init('sdk_key', { offline: true }); client.on('ready', () => { + client.close(); done(); }); }); @@ -13,36 +14,38 @@ it('fires the failed event if initialization fails', (done) => { updateProcessor: { start: (fn: (err: any) => void) => { setTimeout(() => { - fn(new Error("BAD THINGS")); + fn(new Error('BAD THINGS')); }, 0); }, stop: () => { }, - close: () => { } + close: () => { }, }, }); client.on('failed', () => { + client.close(); done(); }); }); -// These tests are done in the node implementation because common doesn't have a crypto +// These tests are done in the node implementation because common doesn't have a crypto // implementation. describe('when using secure mode hash', () => { it('correctly computes hash for a known message and secret', () => { - const client = init('secret', {offline: true}); - const hash = client.secureModeHash({"key": "Message"}); - expect(hash).toEqual("aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597"); + const client = init('secret', { offline: true }); + const hash = client.secureModeHash({ key: 'Message' }); + expect(hash).toEqual('aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597'); }); it.each<[LDContext, string]>([ - [{key: 'Message'}, 'aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597'], - [{kind: 'user', key: 'Message'}, 'aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597'], - [{kind: 'org', key: 'orgtest'}, '40bc9b2e66a842e269ab98dad813e4e15203bbbfd91e8c96b92f3ae6f3f5e223'], - [{kind: 'multi', user: {key: 'user:test'}, org: {key: 'org:test'}}, '607cc91526c615823e320dabca7967ce544fbe83bcb2b7287163f2d1c7aa210f'] -])('it uses the canonical key %p', (context, expectedHash) => { - const client = init('secret', {offline: true}); + [{ key: 'Message' }, 'aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597'], + [{ kind: 'user', key: 'Message' }, 'aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597'], + [{ kind: 'org', key: 'orgtest' }, '40bc9b2e66a842e269ab98dad813e4e15203bbbfd91e8c96b92f3ae6f3f5e223'], + [{ kind: 'multi', user: { key: 'user:test' }, org: { key: 'org:test' } }, '607cc91526c615823e320dabca7967ce544fbe83bcb2b7287163f2d1c7aa210f'], + ])('it uses the canonical key %p', (context, expectedHash) => { + const client = init('secret', { offline: true }); const hash = client.secureModeHash(context); expect(hash).toEqual(expectedHash); + client.close(); }); -}); \ No newline at end of file +}); diff --git a/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts deleted file mode 100644 index 06b5ce0810..0000000000 --- a/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { LDClientImpl } from '../src'; -import TestData from '../src/integrations/test_data/TestData'; -import basicPlatform from './evaluation/mocks/platform'; - -describe('given a a client using test data', () => { - let td: TestData; - let client: LDClientImpl; - - const updates = []; - - beforeEach(() => { - td = new TestData(); - client = new LDClientImpl('sdk-key', basicPlatform, { - sendEvents: false, - updateProcessor: td.getFactory(), - }, () => { }, () => { }, () => { }, (key) => { - updates.push(key); - }); - }); - - it('fires an event when a flag is added', () => { - td.update(td.flag('new-flag')); - }); -}); diff --git a/server-sdk-common/__tests__/LDClientImpl.test.ts b/server-sdk-common/__tests__/LDClientImpl.test.ts index b36868274e..eae4438ca8 100644 --- a/server-sdk-common/__tests__/LDClientImpl.test.ts +++ b/server-sdk-common/__tests__/LDClientImpl.test.ts @@ -1,9 +1,10 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { LDClientImpl } from '../src'; import basicPlatform from './evaluation/mocks/platform'; import TestLogger from './Logger'; it('fires ready event in offline mode', (done) => { - const _client = new LDClientImpl( + const client = new LDClientImpl( 'sdk-key', basicPlatform, { offline: true }, @@ -12,30 +13,35 @@ it('fires ready event in offline mode', (done) => { () => { done(); }, - (_key) => { }); + (_key) => { }, + ); + client.close(); }); it('fires the failed event if initialization fails', (done) => { - const _client = new LDClientImpl( + const client = new LDClientImpl( 'sdk-key', basicPlatform, { updateProcessor: { start: (fn: (err: any) => void) => { setTimeout(() => { - fn(new Error("BAD THINGS")); + fn(new Error('BAD THINGS')); }, 0); }, stop: () => { }, - close: () => { } + close: () => { }, }, }, (_err) => { }, (_err) => { - done() + done(); }, () => { }, - (_key) => { }); + (_key) => { }, + ); + + client.close(); }); it('isOffline returns true in offline mode', (done) => { @@ -49,7 +55,10 @@ it('isOffline returns true in offline mode', (done) => { expect(client.isOffline()).toEqual(true); done(); }, - (_key) => { }); + (_key) => { }, + ); + + client.close(); }); describe('when waiting for initialization', () => { @@ -67,15 +76,20 @@ describe('when waiting for initialization', () => { }, 0); }, stop: () => { }, - close: () => { } + close: () => { }, }, sendEvents: false, - logger: new TestLogger() + logger: new TestLogger(), }, (_err) => { }, (_err) => { }, () => { }, - (_key) => { }); + (_key) => { }, + ); + }); + + afterEach(() => { + client.close(); }); it('resolves when ready', async () => { @@ -91,7 +105,7 @@ describe('when waiting for initialization', () => { const p1 = client.waitForInitialization(); const p2 = client.waitForInitialization(); expect(p2).toBe(p1); - }) + }); }); it('does not crash when closing an offline client', () => { @@ -103,9 +117,11 @@ it('does not crash when closing an offline client', () => { (_err) => { }, () => { }, - (_key) => { }); + (_key) => { }, + ); expect(() => client.close()).not.toThrow(); + client.close(); }); it('the wait for initialization promise is rejected if initialization fails', (done) => { @@ -116,17 +132,19 @@ it('the wait for initialization promise is rejected if initialization fails', (d updateProcessor: { start: (fn: (err: any) => void) => { setTimeout(() => { - fn(new Error("BAD THINGS")); + fn(new Error('BAD THINGS')); }, 0); }, stop: () => { }, - close: () => { } + close: () => { }, }, }, (_err) => { }, (_err) => { }, () => { }, - (_key) => { }); + (_key) => { }, + ); - client.waitForInitialization().catch(() => done()); -}); \ No newline at end of file + client.waitForInitialization().catch(() => done()); + client.close(); +}); From 5986d0b7015b6c25418f869cc339c14accfb0a0d Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 15 Aug 2022 12:37:06 -0700 Subject: [PATCH 09/34] Update organization to allow for LDDataSourceUpdates to be used with update processors. --- .../data_sources/PollingProcessor.test.ts | 14 ++++++++-- .../data_sources/StreamingProcessor.test.ts | 1 + server-sdk-common/src/LDClientImpl.ts | 15 ++++++++--- .../src/api/options/LDBigSegmentsOptions.ts | 5 ++-- .../src/api/options/LDClientContext.ts | 4 +-- .../src/data_sources/DataSourceUpdates.ts | 16 +++++++++++ .../src/data_sources/PollingProcessor.ts | 12 +++++---- .../src/data_sources/StreamingProcessor.ts | 19 ++++++++----- .../src/options/Configuration.ts | 27 ++++++++++++++----- 9 files changed, 86 insertions(+), 27 deletions(-) create mode 100644 server-sdk-common/src/data_sources/DataSourceUpdates.ts diff --git a/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts b/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts index cc7ec59f55..03a251f644 100644 --- a/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts +++ b/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts @@ -1,11 +1,13 @@ import { LDFeatureStore } from '../../src/api/subsystems'; import promisify from '../../src/async/promisify'; +import ClientContext from '../../src/ClientContext'; import PollingProcessor from '../../src/data_sources/PollingProcessor'; import Requestor from '../../src/data_sources/Requestor'; import Configuration from '../../src/options/Configuration'; import AsyncStoreFacade from '../../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; import VersionedDataKinds from '../../src/store/VersionedDataKinds'; +import basicPlatform from '../evaluation/mocks/platform'; import TestLogger, { LogLevel } from '../Logger'; describe('given an event processor', () => { @@ -29,7 +31,11 @@ describe('given an event processor', () => { pollInterval: longInterval, logger: new TestLogger(), }); - processor = new PollingProcessor(config, requestor as unknown as Requestor); + processor = new PollingProcessor( + config, + requestor as unknown as Requestor, + config.featureStoreFactory(new ClientContext('', config, basicPlatform)), + ); }); afterEach(() => { @@ -86,7 +92,11 @@ describe('given a polling processor with a short poll duration', () => { }); // Configuration will not let us set this as low as needed for the test. Object.defineProperty(config, 'pollInterval', { value: 0.1 }); - processor = new PollingProcessor(config, requestor as unknown as Requestor); + processor = new PollingProcessor( + config, + requestor as unknown as Requestor, + config.featureStoreFactory(new ClientContext('', config, basicPlatform)), + ); }); afterEach(() => { diff --git a/server-sdk-common/__tests__/data_sources/StreamingProcessor.test.ts b/server-sdk-common/__tests__/data_sources/StreamingProcessor.test.ts index 3dcaca1c71..e0b70f8938 100644 --- a/server-sdk-common/__tests__/data_sources/StreamingProcessor.test.ts +++ b/server-sdk-common/__tests__/data_sources/StreamingProcessor.test.ts @@ -65,6 +65,7 @@ describe('given a stream processor with mock event source', () => { config, requests, info, + featureStore, ); }); diff --git a/server-sdk-common/src/LDClientImpl.ts b/server-sdk-common/src/LDClientImpl.ts index 8c423df91f..14f35a6e21 100644 --- a/server-sdk-common/src/LDClientImpl.ts +++ b/server-sdk-common/src/LDClientImpl.ts @@ -7,7 +7,9 @@ import { import { BigSegmentStoreMembership } from './api/interfaces'; import BigSegmentsManager from './BigSegmentsManager'; import BigSegmentStoreStatusProvider from './BigSegmentStatusProviderImpl'; +import ClientContext from './ClientContext'; import ClientMessages from './ClientMessages'; +import DataSourceUpdates from './data_sources/DataSourceUpdates'; import NullUpdateProcessor from './data_sources/NullUpdateProcessor'; import PollingProcessor from './data_sources/PollingProcessor'; import Requestor from './data_sources/Requestor'; @@ -87,20 +89,27 @@ export default class LDClientImpl implements LDClient { this.config = config; this.logger = config.logger; + const clientContext = new ClientContext(sdkKey, config, platform); + const featureStore = config.featureStoreFactory(clientContext); + const dataSourceUpdates = new DataSourceUpdates(featureStore); + const makeDefaultProcessor = () => (config.stream ? new StreamingProcessor( sdkKey, config, this.platform.requests, this.platform.info, + dataSourceUpdates, ) : new PollingProcessor( config, new Requestor(sdkKey, config, this.platform.info, this.platform.requests), + dataSourceUpdates, )); if (config.offline || config.useLdd) { this.updateProcessor = new NullUpdateProcessor(); } else { - this.updateProcessor = config.updateProcessor ?? makeDefaultProcessor(); + this.updateProcessor = config.updateProcessorFactory?.(clientContext, dataSourceUpdates) + ?? makeDefaultProcessor(); } if (!config.sendEvents || config.offline) { @@ -114,11 +123,11 @@ export default class LDClientImpl implements LDClient { ); } - const asyncFacade = new AsyncStoreFacade(config.featureStore); + const asyncFacade = new AsyncStoreFacade(featureStore); this.featureStore = asyncFacade; const manager = new BigSegmentsManager( - config.bigSegments?.store?.(config), + config.bigSegments?.store?.(clientContext), config.bigSegments ?? {}, config.logger, this.platform.crypto, diff --git a/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts b/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts index b904c19959..ca0245ac50 100644 --- a/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts +++ b/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts @@ -1,5 +1,6 @@ import { LDLogger } from '@launchdarkly/js-sdk-common'; import { BigSegmentStore } from '../interfaces'; +import { LDClientContext } from './LDClientContext'; /** * Additional parameters for configuring the SDK's Big Segments behavior. @@ -18,9 +19,7 @@ export interface LDBigSegmentsOptions { * database implementation that matches how the LaunchDarkly Relay Proxy is configured, since the * Relay Proxy manages the Big Segment data. */ - store: (options: { - logger?: LDLogger - }) => BigSegmentStore; + store: (clientContext: LDClientContext) => BigSegmentStore; /** * The maximum number of users whose Big Segment state will be cached by the SDK at any given diff --git a/server-sdk-common/src/api/options/LDClientContext.ts b/server-sdk-common/src/api/options/LDClientContext.ts index 6f2e08385f..3adc443f78 100644 --- a/server-sdk-common/src/api/options/LDClientContext.ts +++ b/server-sdk-common/src/api/options/LDClientContext.ts @@ -46,5 +46,5 @@ export interface LDClientContext { /** * Interfaces providing platform specific information and functionality. */ - platform: Platform -} \ No newline at end of file + platform: Platform +} diff --git a/server-sdk-common/src/data_sources/DataSourceUpdates.ts b/server-sdk-common/src/data_sources/DataSourceUpdates.ts new file mode 100644 index 0000000000..5be55d20e7 --- /dev/null +++ b/server-sdk-common/src/data_sources/DataSourceUpdates.ts @@ -0,0 +1,16 @@ +import { DataKind } from '../api/interfaces'; +import { LDDataSourceUpdates, LDFeatureStore, LDFeatureStoreDataStorage, LDKeyedFeatureStoreItem } from '../api/subsystems'; + +export default class DataSourceUpdates implements LDDataSourceUpdates { + constructor(private readonly featureStore: LDFeatureStore) { + + } + + init(allData: LDFeatureStoreDataStorage, callback: () => void): void { + this.featureStore.init(allData, callback); + } + + upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { + this.featureStore.upsert(kind, data, callback); + } +} \ No newline at end of file diff --git a/server-sdk-common/src/data_sources/PollingProcessor.ts b/server-sdk-common/src/data_sources/PollingProcessor.ts index c166675319..283ef15b39 100644 --- a/server-sdk-common/src/data_sources/PollingProcessor.ts +++ b/server-sdk-common/src/data_sources/PollingProcessor.ts @@ -1,6 +1,6 @@ import { LDLogger } from '@launchdarkly/js-sdk-common'; import { LDStreamProcessor } from '../api'; -import { LDFeatureStore } from '../api/subsystems'; +import { LDDataSourceUpdates } from '../api/subsystems'; import { isHttpRecoverable, LDPollingError } from '../errors'; import Configuration from '../options/Configuration'; import { deserializePoll } from '../store/serialization'; @@ -15,14 +15,16 @@ export default class PollingProcessor implements LDStreamProcessor { private pollInterval: number; - private featureStore: LDFeatureStore; - private timeoutHandle: any; - constructor(config: Configuration, private readonly requestor: Requestor) { + constructor( + config: Configuration, + private readonly requestor: Requestor, + private readonly featureStore: LDDataSourceUpdates, + ) { this.logger = config.logger; this.pollInterval = config.pollInterval; - this.featureStore = config.featureStore; + this.featureStore = featureStore; } private poll(fn?: ((err?: any) => void) | undefined) { diff --git a/server-sdk-common/src/data_sources/StreamingProcessor.ts b/server-sdk-common/src/data_sources/StreamingProcessor.ts index 1bfde4038f..67868591a1 100644 --- a/server-sdk-common/src/data_sources/StreamingProcessor.ts +++ b/server-sdk-common/src/data_sources/StreamingProcessor.ts @@ -1,6 +1,6 @@ import { LDLogger } from '@launchdarkly/js-sdk-common'; import { LDStreamProcessor } from '../api'; -import { LDFeatureStore } from '../api/subsystems'; +import { LDDataSourceUpdates } from '../api/subsystems'; import { isHttpRecoverable, LDStreamingError } from '../errors'; import Configuration from '../options/Configuration'; import { EventSource, Info, Requests } from '../platform'; @@ -31,17 +31,20 @@ export default class StreamingProcessor implements LDStreamProcessor { private requests: Requests; - private featureStore: LDFeatureStore; - private connectionAttemptStartTime?: number; - constructor(sdkKey: string, config: Configuration, requests: Requests, info: Info) { + constructor( + sdkKey: string, + config: Configuration, + requests: Requests, + info: Info, + private readonly featureStore: LDDataSourceUpdates, + ) { // TODO: Will need diagnostics manager. this.headers = defaultHeaders(sdkKey, config, info); this.logger = config.logger; this.streamInitialReconnectDelay = config.streamInitialReconnectDelay; this.requests = requests; - this.featureStore = config.featureStore; this.streamUri = `${config.serviceEndpoints.streaming}/all`; } @@ -167,7 +170,11 @@ export default class StreamingProcessor implements LDStreamProcessor { this.logger?.debug(`Deleting ${key} in ${parsed.kind.namespace}`); // TODO: The interface didn't specify the callback was optional, // but previously it was not included here. Need to resolve. - this.featureStore.delete(parsed.kind, key, parsed.version, () => { }); + this.featureStore.upsert(parsed.kind, { + key, + version: parsed.version, + deleted: true, + }, () => {}); } } } else { diff --git a/server-sdk-common/src/options/Configuration.ts b/server-sdk-common/src/options/Configuration.ts index bb2688a4b2..f6d66cac75 100644 --- a/server-sdk-common/src/options/Configuration.ts +++ b/server-sdk-common/src/options/Configuration.ts @@ -5,7 +5,8 @@ import { LDBigSegmentsOptions, LDOptions, LDProxyOptions, LDStreamProcessor, LDTLSOptions, } from '../api'; -import { LDFeatureStore } from '../api/subsystems'; +import { LDClientContext } from '../api/options/LDClientContext'; +import { LDDataSourceUpdates, LDFeatureStore } from '../api/subsystems'; import InMemoryFeatureStore from '../store/InMemoryFeatureStore'; import ApplicationTags from './ApplicationTags'; import OptionMessages from './OptionMessages'; @@ -189,9 +190,14 @@ export default class Configuration { public readonly diagnosticRecordingInterval: number; - public readonly featureStore: LDFeatureStore; + // public readonly featureStore: LDFeatureStore; - public readonly updateProcessor?: LDStreamProcessor; + public readonly featureStoreFactory: ((clientContext: LDClientContext) => LDFeatureStore); + + // public readonly updateProcessor?: LDStreamProcessor; + + public readonly updateProcessorFactory?: + ((clientContext: LDClientContext, dataSourceUpdates: LDDataSourceUpdates) => LDStreamProcessor); public readonly bigSegments?: LDBigSegmentsOptions; @@ -219,7 +225,6 @@ export default class Configuration { this.timeout = validatedOptions.timeout; this.bigSegments = validatedOptions.bigSegments; - this.updateProcessor = validatedOptions.updateProcessor; this.flushInterval = validatedOptions.flushInterval; this.pollInterval = validatedOptions.pollInterval; this.proxyOptions = validatedOptions.proxyOptions; @@ -240,12 +245,22 @@ export default class Configuration { this.tags = new ApplicationTags(validatedOptions); this.diagnosticRecordingInterval = validatedOptions.diagnosticRecordingInterval; + if (TypeValidators.Function.is(validatedOptions.updateProcessor)) { + // @ts-ignore + this.updateProcessorFactory = validatedOptions.updateProcessor; + } else { + // The processor is already created, just have the method return it. + // @ts-ignore + this.updateProcessorFactory = () => validatedOptions.updateProcessor; + } + if (TypeValidators.Function.is(validatedOptions.featureStore)) { // @ts-ignore - this.featureStore = validatedOptions.featureStore(this); + this.featureStoreFactory = validatedOptions.featureStore; } else { + // The store is already created, just have the method return it. // @ts-ignore - this.featureStore = validatedOptions.featureStore; + this.featureStoreFactory = () => validatedOptions.featureStore; } } } From d8b9565b40002685af372351707ed04aaccb3cfd Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 15 Aug 2022 14:22:07 -0700 Subject: [PATCH 10/34] First pass DataSourceUpdates. --- platform-node/src/LDClientNode.ts | 1 + .../__tests__/LDClientImpl.test.ts | 6 + server-sdk-common/src/LDClientImpl.ts | 8 +- .../src/data_sources/DataSourceUpdates.ts | 140 +++++++++++++++++- .../src/data_sources/DependencyTracker.ts | 51 +++++++ .../src/data_sources/NamespacedDataSet.ts | 40 +++++ 6 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 server-sdk-common/src/data_sources/DependencyTracker.ts create mode 100644 server-sdk-common/src/data_sources/NamespacedDataSet.ts diff --git a/platform-node/src/LDClientNode.ts b/platform-node/src/LDClientNode.ts index 982f8b086e..b1996415e4 100644 --- a/platform-node/src/LDClientNode.ts +++ b/platform-node/src/LDClientNode.ts @@ -48,6 +48,7 @@ class LDClientNode extends LDClientImpl { emitter.emit('update', { key }); emitter.emit(`update:${key}`); }, + () => emitter.eventNames().some((name) => name === 'update' || (typeof name === 'string' && name.startsWith('update:'))), ); this.emitter = emitter; diff --git a/server-sdk-common/__tests__/LDClientImpl.test.ts b/server-sdk-common/__tests__/LDClientImpl.test.ts index eae4438ca8..981bcd2683 100644 --- a/server-sdk-common/__tests__/LDClientImpl.test.ts +++ b/server-sdk-common/__tests__/LDClientImpl.test.ts @@ -14,6 +14,7 @@ it('fires ready event in offline mode', (done) => { done(); }, (_key) => { }, + () => false, ); client.close(); }); @@ -39,6 +40,7 @@ it('fires the failed event if initialization fails', (done) => { }, () => { }, (_key) => { }, + () => false, ); client.close(); @@ -56,6 +58,7 @@ it('isOffline returns true in offline mode', (done) => { done(); }, (_key) => { }, + () => false, ); client.close(); @@ -85,6 +88,7 @@ describe('when waiting for initialization', () => { (_err) => { }, () => { }, (_key) => { }, + () => false, ); }); @@ -118,6 +122,7 @@ it('does not crash when closing an offline client', () => { () => { }, (_key) => { }, + () => false, ); expect(() => client.close()).not.toThrow(); @@ -143,6 +148,7 @@ it('the wait for initialization promise is rejected if initialization fails', (d (_err) => { }, () => { }, (_key) => { }, + () => false, ); client.waitForInitialization().catch(() => done()); diff --git a/server-sdk-common/src/LDClientImpl.ts b/server-sdk-common/src/LDClientImpl.ts index 14f35a6e21..02288058d3 100644 --- a/server-sdk-common/src/LDClientImpl.ts +++ b/server-sdk-common/src/LDClientImpl.ts @@ -80,7 +80,11 @@ export default class LDClientImpl implements LDClient { private onError: (err: Error) => void, private onFailed: (err: Error) => void, private onReady: () => void, - private onUpdate: (key: string) => void, + // Called whenever flags change, if there are listeners. + onUpdate: (key: string) => void, + // Method to check if event listeners have been registered. + // If none are registered, then onUpdate will never be called. + hasEventListeners: () => boolean, ) { const config = new Configuration(options); if (!sdkKey && !config.offline) { @@ -91,7 +95,7 @@ export default class LDClientImpl implements LDClient { const clientContext = new ClientContext(sdkKey, config, platform); const featureStore = config.featureStoreFactory(clientContext); - const dataSourceUpdates = new DataSourceUpdates(featureStore); + const dataSourceUpdates = new DataSourceUpdates(featureStore, hasEventListeners, onUpdate); const makeDefaultProcessor = () => (config.stream ? new StreamingProcessor( sdkKey, diff --git a/server-sdk-common/src/data_sources/DataSourceUpdates.ts b/server-sdk-common/src/data_sources/DataSourceUpdates.ts index 5be55d20e7..61fb2e1cc8 100644 --- a/server-sdk-common/src/data_sources/DataSourceUpdates.ts +++ b/server-sdk-common/src/data_sources/DataSourceUpdates.ts @@ -1,16 +1,144 @@ import { DataKind } from '../api/interfaces'; -import { LDDataSourceUpdates, LDFeatureStore, LDFeatureStoreDataStorage, LDKeyedFeatureStoreItem } from '../api/subsystems'; +import { + LDDataSourceUpdates, LDFeatureStore, LDFeatureStoreDataStorage, + LDFeatureStoreItem, LDKeyedFeatureStoreItem, +} from '../api/subsystems'; +import { Flag } from '../evaluation/data/Flag'; +import VersionedDataKinds from '../store/VersionedDataKinds'; +import DependencyTracker from './DependencyTracker'; +import NamespacedDataSet from './NamespacedDataSet'; +function computeDependencies(namespace: string, item: LDFeatureStoreItem) { + const ret = new NamespacedDataSet(); + if (namespace === VersionedDataKinds.Features.namespace) { + const flag = item as Flag; + flag?.prerequisites?.forEach((prereq) => { + ret.set(namespace, prereq.key, true); + }); + flag?.rules?.forEach((rule) => { + rule.clauses?.forEach((clause) => { + if (clause.op === 'segmentMatch') { + clause.values.forEach((value) => { + ret.set(VersionedDataKinds.Segments.namespace, value, true); + }); + } + }); + }); + } + return ret; +} + +/** + * @internal + */ export default class DataSourceUpdates implements LDDataSourceUpdates { - constructor(private readonly featureStore: LDFeatureStore) { - + private readonly dependencyTracker = new DependencyTracker(); + + constructor( + private readonly featureStore: LDFeatureStore, + private readonly hasEventListeners: () => boolean, + private readonly onChange: (key: string) => void, + ) { } init(allData: LDFeatureStoreDataStorage, callback: () => void): void { - this.featureStore.init(allData, callback); + const checkForChanges = this.hasEventListeners(); + const doInit = (oldData?: LDFeatureStoreDataStorage) => { + this.featureStore.init(allData, () => { + this.dependencyTracker.reset(); + + Object.entries(allData).forEach(([namespace, items]) => { + Object.keys(items || {}).forEach((key) => { + const item = items[key]; + this.dependencyTracker.updateDependenciesFrom( + namespace, + key, + computeDependencies(namespace, item), + ); + }); + }); + + if (checkForChanges && oldData) { + const updatedItems = new NamespacedDataSet(); + Object.keys(allData).forEach((namespace) => { + const oldDataForKind = oldData[namespace]; + const newDataForKind = allData[namespace]; + const mergedData = { ...oldDataForKind, ...newDataForKind }; + Object.keys(mergedData).forEach((key) => { + this.addIfModified( + namespace, + key, + oldDataForKind && oldDataForKind[key], + newDataForKind && newDataForKind[key], + updatedItems, + ); + }); + }); + this.sendChangeEvents(updatedItems); + } + + callback?.(); + }); + }; + + if (checkForChanges) { + this.featureStore.all(VersionedDataKinds.Features, (oldFlags) => { + this.featureStore.all(VersionedDataKinds.Segments, (oldSegments) => { + const oldData = { + [VersionedDataKinds.Features.namespace]: oldFlags, + [VersionedDataKinds.Segments.namespace]: oldSegments, + }; + doInit(oldData); + }); + }); + } else { + doInit(); + } } upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { - this.featureStore.upsert(kind, data, callback); + const { key } = data; + const checkForChanges = this.hasEventListeners(); + const doUpsert = (oldItem?: LDFeatureStoreItem | null) => { + this.featureStore.upsert(kind, data, () => { + this.dependencyTracker.updateDependenciesFrom( + kind.namespace, + key, + computeDependencies(kind.namespace, data), + ); + if (checkForChanges && oldItem) { + const updatedItems = new NamespacedDataSet(); + this.addIfModified(kind.namespace, key, oldItem, data, updatedItems); + this.sendChangeEvents(updatedItems); + } + callback?.(); + }); + }; + if (checkForChanges) { + this.featureStore.get(kind, key, doUpsert); + } else { + doUpsert(); + } + } + + addIfModified( + namespace: string, + key: string, + oldValue: LDFeatureStoreItem, + newValue: LDFeatureStoreItem, + toDataSet: NamespacedDataSet, + ) { + if (newValue && oldValue && newValue.version <= oldValue.version) { + return; + } + this.dependencyTracker.updateModifiedItems(toDataSet, namespace, key); + } + + sendChangeEvents(dataSet: NamespacedDataSet) { + dataSet.enumerate((namespace, key) => { + if (namespace === VersionedDataKinds.Features.namespace) { + this.onChange(key); + } + }); } -} \ No newline at end of file +} diff --git a/server-sdk-common/src/data_sources/DependencyTracker.ts b/server-sdk-common/src/data_sources/DependencyTracker.ts new file mode 100644 index 0000000000..0904150dcb --- /dev/null +++ b/server-sdk-common/src/data_sources/DependencyTracker.ts @@ -0,0 +1,51 @@ +import NamespacedDataSet from './NamespacedDataSet'; + +/** + * @internal + */ +export default class DependencyTracker { + private readonly dependenciesFrom = new NamespacedDataSet>(); + + private readonly dependenciesTo = new NamespacedDataSet>(); + + updateDependenciesFrom( + namespace: string, + key: string, + newDependencySet: NamespacedDataSet, + ) { + const oldDependencySet = this.dependenciesFrom.get(namespace, key); + oldDependencySet?.enumerate((depNs, depKey) => { + const depsToThisDep = this.dependenciesTo.get(depNs, depKey); + depsToThisDep?.remove(namespace, key); + }); + + this.dependenciesFrom.set(namespace, key, newDependencySet); + newDependencySet?.enumerate((depNs, depKey) => { + let depsToThisDep = this.dependenciesTo.get(depNs, depKey); + if (!depsToThisDep) { + depsToThisDep = new NamespacedDataSet(); + this.dependenciesTo.set(depNs, depKey, depsToThisDep); + } + depsToThisDep.set(namespace, key, true); + }); + } + + updateModifiedItems( + inDependencySet: NamespacedDataSet, + modifiedNamespace: string, + modifiedKey: string, + ) { + if (!inDependencySet.get(modifiedNamespace, modifiedKey)) { + inDependencySet.set(modifiedNamespace, modifiedKey, true); + const affectedItems = this.dependenciesTo.get(modifiedNamespace, modifiedKey); + affectedItems?.enumerate((namespace, key) => { + this.updateModifiedItems(inDependencySet, namespace, key); + }); + } + } + + reset() { + this.dependenciesFrom.removeAll(); + this.dependenciesTo.removeAll(); + } +} diff --git a/server-sdk-common/src/data_sources/NamespacedDataSet.ts b/server-sdk-common/src/data_sources/NamespacedDataSet.ts new file mode 100644 index 0000000000..1b6ae6fee2 --- /dev/null +++ b/server-sdk-common/src/data_sources/NamespacedDataSet.ts @@ -0,0 +1,40 @@ +/** + * @internal + */ +export default class NamespacedDataSet { + private itemsByNamespace: Record> = {}; + + get(namespace: string, key: string): T | undefined { + return this.itemsByNamespace[namespace]?.[key]; + } + + set(namespace: string, key: string, value: T) { + if (!(namespace in this.itemsByNamespace)) { + this.itemsByNamespace[namespace] = {}; + } + this.itemsByNamespace[namespace][key] = value; + } + + remove(namespace: string, key: string) { + const items = this.itemsByNamespace[namespace]; + if (items) { + delete items[key]; + } + } + + removeAll() { + this.itemsByNamespace = {}; + } + + enumerate(callback: (namespace: string, key: string, value: T) => void) { + Object.entries(this.itemsByNamespace).forEach(([namespace, values]) => { + Object.entries(values).forEach(([key, value]) => { + callback(namespace, key, value); + }); + }); + } + + mergeFrom(other: NamespacedDataSet) { + other.enumerate(this.set.bind(this)); + } +} From 5bfc3da29c8e2e6ca93733b7e5d0fbf49625261a Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 15 Aug 2022 16:02:33 -0700 Subject: [PATCH 11/34] Add DataSourceUpdates tests. --- server-sdk-common/__tests__/AsyncQueue.ts | 29 +++ .../__tests__/LDClientImpl.listeners.test.ts | 52 +++++ .../data_sources/DataSourceUpdates.test.ts | 179 ++++++++++++++++++ .../src/data_sources/DataSourceUpdates.ts | 8 +- 4 files changed, 264 insertions(+), 4 deletions(-) create mode 100644 server-sdk-common/__tests__/AsyncQueue.ts create mode 100644 server-sdk-common/__tests__/LDClientImpl.listeners.test.ts create mode 100644 server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts diff --git a/server-sdk-common/__tests__/AsyncQueue.ts b/server-sdk-common/__tests__/AsyncQueue.ts new file mode 100644 index 0000000000..e1c626b45b --- /dev/null +++ b/server-sdk-common/__tests__/AsyncQueue.ts @@ -0,0 +1,29 @@ +export class AsyncQueue { + private content: any[] = []; + private takers: { + resolve: (res: any) => void; + reject: (err: Error) => void; + }[] = []; + + push(item: any) { + if (this.takers.length) { + const taker = this.takers.shift(); + taker?.resolve(item); + } + this.content.push(item); + } + + take(): Promise { + if (this.content.length) { + return Promise.resolve(this.content.shift()!); + } + + return new Promise((resolve, reject) => { + this.takers.push({ resolve, reject }); + }); + } + + empty() { + return this.content.length === 0; + } +} diff --git a/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts new file mode 100644 index 0000000000..f43b0c704e --- /dev/null +++ b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts @@ -0,0 +1,52 @@ +import { LDClientImpl } from '../src'; +import TestData from '../src/integrations/test_data/TestData'; +import { AsyncQueue } from './AsyncQueue'; +import basicPlatform from './evaluation/mocks/platform'; +import TestLogger from './Logger'; + +describe('given an LDClient with test data', () => { + let client: LDClientImpl; + let td: TestData; + let queue: AsyncQueue; + + beforeEach(() => { + queue = new AsyncQueue(); + td = new TestData(); + client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + updateProcessor: td.getFactory(), + sendEvents: false, + logger: new TestLogger(), + }, + (_err) => { }, + (_err) => { }, + () => { }, + (key) => { + queue.push(key) + }, + // Always listen to events. + () => true, + ); + }); + + it('sends an event when a flag is added', async () => { + td.update(td.flag('new-flag')); + expect(await queue.take()).toEqual('new-flag'); + }); + + it('sends an event when a flag is updated', async () => { + td.update(td.flag('flag1').on(true)); + td.update(td.flag('flag2').on(true)); + + expect(await queue.take()).toEqual('flag1'); + expect(await queue.take()).toEqual('flag2'); + + td.update(td.flag('flag1').on(false)); + td.update(td.flag('flag2').on(false)); + + expect(await queue.take()).toEqual('flag1'); + expect(await queue.take()).toEqual('flag2'); + }); +}) \ No newline at end of file diff --git a/server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts b/server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts new file mode 100644 index 0000000000..751c3e1e76 --- /dev/null +++ b/server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts @@ -0,0 +1,179 @@ +import { LDFeatureStore } from '../../src/api/subsystems'; +import promisify from '../../src/async/promisify'; +import DataSourceUpdates from '../../src/data_sources/DataSourceUpdates'; +import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; +import VersionedDataKinds from '../../src/store/VersionedDataKinds'; +import { AsyncQueue } from '../AsyncQueue'; + +describe.each([true, false])('given a DataSourceUpdates with in memory store and change listeners: %s', (listen) => { + let store: LDFeatureStore; + let updates: DataSourceUpdates; + + let queue = new AsyncQueue(); + + beforeEach(() => { + store = new InMemoryFeatureStore(); + + updates = new DataSourceUpdates( + store, + () => listen, + (key) => queue.push(key) + ); + }); + + it('sends events for an init of an empty store', async () => { + await promisify((cb) => { + updates.init({ + features: { + a: { key: 'a', version: 1 }, + b: { key: 'b', version: 1 } + }, + segments: {} + }, () => { + cb(undefined); + }); + }); + + if (listen) { + expect(await queue.take()).toEqual('a'); + expect(await queue.take()).toEqual('b'); + } + expect(queue.empty()).toBeTruthy(); + }); + + it('sends events for re-init of non-empty store', async () => { + const allData0 = { + features: { + a: { key: 'a', version: 1 }, + b: { key: 'b', version: 1 }, + c: { key: 'c', version: 1 } + }, + segments: {} + }; + const allData1 = { + features: { + a: { key: 'a', version: 1 }, + b: { key: 'b', version: 2 } + }, + segments: {} + }; + + await promisify((cb) => { + updates.init(allData0, () => { + cb(undefined); + }); + }); + + if (listen) { + expect(await queue.take()).toEqual('a'); + expect(await queue.take()).toEqual('b'); + expect(await queue.take()).toEqual('c'); + } + expect(queue.empty()).toBeTruthy(); + + await promisify((cb) => { + updates.init(allData1, () => { + cb(undefined); + }); + }); + + if (listen) { + // A remained the same. + expect(await queue.take()).toEqual('b'); // Different version + expect(await queue.take()).toEqual('c'); // Deleted + } + expect(queue.empty()).toBeTruthy(); + }); + + it('sends events for upserts', async () => { + await promisify((cb) => { + updates.init({ + features: { + a: { key: 'a', version: 1 }, + }, + segments: {} + }, () => { + cb(undefined); + }); + }); + + if (listen) { + expect(await queue.take()).toEqual('a'); + } + expect(queue.empty()).toBeTruthy(); + + // Upsert the same thing twice. Should only be 1 event. + promisify((cb) => { + updates.upsert(VersionedDataKinds.Features, { key: 'a', version: 2 }, () => cb(undefined)); + }); + promisify((cb) => { + updates.upsert(VersionedDataKinds.Features, { key: 'a', version: 2 }, () => cb(undefined)); + }); + + if (listen) { + expect(await queue.take()).toEqual('a'); + } + expect(queue.empty()).toBeTruthy(); + }); + + it('sends events for transitive dependencies', async () => { + await promisify((cb) => { + updates.init({ + features: { + a: { key: 'a', version: 1 }, + b: { key: 'b', version: 1, prerequisites: [{ key: 'c' }, { key: 'e' }] }, + c: { + key: 'c', version: 1, prerequisites: [{ key: 'd' }], + rules: [ + { clauses: [{ op: 'segmentMatch', values: ['s0'] }] } + ] + }, + d: { key: 'd', version: 1, prerequisites: [{ key: 'e' }] }, + e: { key: 'e', version: 1 } + }, + segments: { + s0: { key: 's0', version: 1 } + } + }, () => { + cb(undefined); + }); + }); + + if (listen) { + expect(await queue.take()).toEqual('a'); + expect(await queue.take()).toEqual('b'); + expect(await queue.take()).toEqual('c'); + expect(await queue.take()).toEqual('d'); + expect(await queue.take()).toEqual('e'); + } + expect(queue.empty()).toBeTruthy(); + + promisify((cb) => { + updates.upsert( + VersionedDataKinds.Features, + { key: 'd', version: 2, prerequisites: [{ key: 'e' }] }, + () => cb(undefined) + ); + }); + + if (listen) { + expect( + [await queue.take(), await queue.take(), await queue.take()].sort() + ).toEqual(['b', 'c', 'd']); + } + expect(queue.empty()).toBeTruthy(); + + promisify((cb) => { + updates.upsert( + VersionedDataKinds.Segments, + { key: 's0', version: 2 }, + () => cb(undefined) + ); + }); + + if (listen) { + expect([await queue.take(), await queue.take()].sort()).toEqual(['b', 'c']); + } + expect(queue.empty()).toBeTruthy(); + }); +}); diff --git a/server-sdk-common/src/data_sources/DataSourceUpdates.ts b/server-sdk-common/src/data_sources/DataSourceUpdates.ts index 61fb2e1cc8..3005f33774 100644 --- a/server-sdk-common/src/data_sources/DataSourceUpdates.ts +++ b/server-sdk-common/src/data_sources/DataSourceUpdates.ts @@ -58,10 +58,10 @@ export default class DataSourceUpdates implements LDDataSourceUpdates { }); }); - if (checkForChanges && oldData) { + if (checkForChanges) { const updatedItems = new NamespacedDataSet(); Object.keys(allData).forEach((namespace) => { - const oldDataForKind = oldData[namespace]; + const oldDataForKind = oldData?.[namespace] || {}; const newDataForKind = allData[namespace]; const mergedData = { ...oldDataForKind, ...newDataForKind }; Object.keys(mergedData).forEach((key) => { @@ -106,7 +106,7 @@ export default class DataSourceUpdates implements LDDataSourceUpdates { key, computeDependencies(kind.namespace, data), ); - if (checkForChanges && oldItem) { + if (checkForChanges) { const updatedItems = new NamespacedDataSet(); this.addIfModified(kind.namespace, key, oldItem, data, updatedItems); this.sendChangeEvents(updatedItems); @@ -124,7 +124,7 @@ export default class DataSourceUpdates implements LDDataSourceUpdates { addIfModified( namespace: string, key: string, - oldValue: LDFeatureStoreItem, + oldValue: LDFeatureStoreItem | null | undefined, newValue: LDFeatureStoreItem, toDataSet: NamespacedDataSet, ) { From b4b69cedb4a4ce6d3499b41604ba5646b379630d Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Mon, 15 Aug 2022 16:45:03 -0700 Subject: [PATCH 12/34] Add client level individual eval tests. --- .../__tests__/LDClient.evaluation.test.ts | 251 ++++++++++++++++++ .../evaluation/Evaluator.rules.test.ts | 8 +- server-sdk-common/src/LDClientImpl.ts | 4 +- .../src/api/data/LDEvaluationDetail.ts | 2 +- .../src/evaluation/EvalResult.ts | 4 +- .../src/events/InputEvalEvent.ts | 2 +- 6 files changed, 261 insertions(+), 10 deletions(-) create mode 100644 server-sdk-common/__tests__/LDClient.evaluation.test.ts diff --git a/server-sdk-common/__tests__/LDClient.evaluation.test.ts b/server-sdk-common/__tests__/LDClient.evaluation.test.ts new file mode 100644 index 0000000000..e31fc33b7b --- /dev/null +++ b/server-sdk-common/__tests__/LDClient.evaluation.test.ts @@ -0,0 +1,251 @@ +import { LDClientImpl } from '../src'; +import { LDFeatureStore } from '../src/api/subsystems'; +import NullUpdateProcessor from '../src/data_sources/NullUpdateProcessor'; +import TestData from '../src/integrations/test_data/TestData'; +import AsyncStoreFacade from '../src/store/AsyncStoreFacade'; +import InMemoryFeatureStore from '../src/store/InMemoryFeatureStore'; +import VersionedDataKinds from '../src/store/VersionedDataKinds'; +import { AsyncQueue } from './AsyncQueue'; +import basicPlatform from './evaluation/mocks/platform'; +import TestLogger, { LogLevel } from './Logger'; + +const defaultUser = { key: 'user' }; + +describe('given an LDClient with test data', () => { + let client: LDClientImpl; + let td: TestData; + + beforeEach(async () => { + td = new TestData(); + client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + updateProcessor: td.getFactory(), + sendEvents: false, + }, + (_err) => { }, + (_err) => { }, + () => { }, + (key) => { }, + // Always listen to events. + () => true, + ); + + await client.waitForInitialization(); + }); + + it('evaluates an existing flag', async () => { + td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1)); + expect(await client.variation('flagkey', defaultUser, 'c')).toBe('b'); + }); + + it('returns default for an unknown flag', async () => { + expect(await client.variation('flagkey', defaultUser, 'c')).toBe('c'); + }); + + it('returns default if a flag key is not specified', async () => { + // @ts-ignore + expect(await client.variation(null, defaultUser, 'c')).toBe('c'); + }); + + it('returns the default for a flag which evaluates to null', async () => { + td.usePreconfiguredFlag({ // TestData normally won't construct a flag with offVariation: null + key: 'flagIsNull', + on: false, + offVariation: null + }); + + expect(await client.variation('flagIsNull', defaultUser, 'default')).toEqual('default'); + }); + + it('returns the default for a flag which evaluates to null using variationDetail', async () => { + td.usePreconfiguredFlag({ // TestData normally won't construct a flag with offVariation: null + key: 'flagIsNull', + on: false, + offVariation: null + }); + + expect(await client.variationDetail('flagIsNull', defaultUser, 'default')).toMatchObject( + { value: 'default', variationIndex: null, reason: { kind: 'OFF' } } + ); + }); + + it('can use a callback instead of a promise', (done) => { + client.variation('nonsense', defaultUser, 'default', (err, result) => { + expect(err).toBeNull(); + expect(result).toEqual('default'); + done(); + }); + }); + + it('can use a callback instead of a promise for variationDetail', (done) => { + client.variationDetail('nonsense', defaultUser, 'default', (err, result) => { + expect(err).toBeNull(); + expect(result).toMatchObject( + { + value: 'default', variationIndex: null, + reason: { kind: 'ERROR', errorKind: 'FLAG_NOT_FOUND' } + } + ); + done(); + }); + }); + + it('can evaluate an existing flag with detail', async () => { + td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1)); + expect(await client.variationDetail('flagkey', defaultUser, 'c')).toMatchObject( + { value: 'b', variationIndex: 1, reason: { kind: 'FALLTHROUGH' } } + ); + }); + + it('returns default for an unknown flag with detail', async () => { + expect(await client.variationDetail('flagkey', defaultUser, 'default')).toMatchObject( + { + value: 'default', variationIndex: null, + reason: { kind: 'ERROR', errorKind: 'FLAG_NOT_FOUND' } + }) + }); +}); + +describe('given an offline client', () => { + let logger: TestLogger; + let client: LDClientImpl; + let td: TestData; + + beforeEach(() => { + logger = new TestLogger(); + td = new TestData(); + client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + offline: true, + updateProcessor: td.getFactory(), + sendEvents: false, + logger + }, + (_err) => { }, + (_err) => { }, + () => { }, + (key) => { }, + // Always listen to events. + () => true, + ); + }); + + it('returns the default value for variation', async () => { + await client.waitForInitialization(); + td.update(td.flag('flagkey').variations('value').variationForAll(0)); + const result = await client.variation('flagkey', defaultUser, 'default'); + expect(result).toEqual('default'); + expect(logger.getCount(LogLevel.Info)).toEqual(1); + }); + + it('returns the default value for variationDetail', async () => { + await client.waitForInitialization(); + td.update(td.flag('flagkey').variations('value').variationForAll(0)); + const result = await client.variationDetail('flagkey', defaultUser, 'default'); + expect(result).toMatchObject({ + value: 'default', variationIndex: null, + reason: { kind: 'ERROR', errorKind: 'CLIENT_NOT_READY' } + }); + expect(logger.getCount(LogLevel.Info)).toEqual(1); + }); +}); + +describe('given a client and store that are uninitialized', () => { + let store: LDFeatureStore; + let client: LDClientImpl; + + beforeEach(async () => { + store = new InMemoryFeatureStore(); + const asyncStore = new AsyncStoreFacade(store); + // Put something in the store, but don't initialize it. + await asyncStore.upsert(VersionedDataKinds.Features, { + key: 'flagkey', + version: 1, + on: false, + offVariation: 0, + variations: ['value'] + }); + + client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + updateProcessor: new NullUpdateProcessor(), + sendEvents: false, + featureStore: store, + }, + (_err) => { }, + (_err) => { }, + () => { }, + (key) => { }, + // Always listen to events. + () => true, + ); + }); + + it('returns the default value for variation', async () => { + expect(await client.variation('flagkey', defaultUser, 'default')).toEqual('default'); + }); + + it('returns the default value for variationDetail', async () => { + expect(await client.variationDetail('flagkey', defaultUser, 'default')).toMatchObject( + { + value: 'default', variationIndex: null, + reason: { kind: 'ERROR', errorKind: 'CLIENT_NOT_READY' } + } + ); + }); +}); + +describe('given a client that is un-initialized and store that is initialized', () => { + let store: LDFeatureStore; + let client: LDClientImpl; + + beforeEach(async () => { + store = new InMemoryFeatureStore(); + const asyncStore = new AsyncStoreFacade(store); + // Put something in the store, but don't initialize it. + await asyncStore.init({ + features: { + flagkey: { + version: 1, + on: false, + offVariation: 0, + variations: ['value'] + } + }, + segments: {} + }); + + client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + updateProcessor: new NullUpdateProcessor(), + sendEvents: false, + featureStore: store, + }, + (_err) => { }, + (_err) => { }, + () => { }, + (key) => { }, + // Always listen to events. + () => true, + ); + }); + + it('returns the value for variation', async () => { + expect(await client.variation('flagkey', defaultUser, 'default')).toEqual('value'); + }); + + it('returns the value for variationDetail', async () => { + expect(await client.variationDetail('flagkey', defaultUser, 'default')).toMatchObject( + { value: 'value', variationIndex: 0, reason: { kind: 'OFF' } } + ); + }); +}); + diff --git a/server-sdk-common/__tests__/evaluation/Evaluator.rules.test.ts b/server-sdk-common/__tests__/evaluation/Evaluator.rules.test.ts index adc2158b2e..299404ab5a 100644 --- a/server-sdk-common/__tests__/evaluation/Evaluator.rules.test.ts +++ b/server-sdk-common/__tests__/evaluation/Evaluator.rules.test.ts @@ -42,7 +42,7 @@ describe('when evaluating user equivalent contexts', () => { const res = await evaluator.evaluate(flag, Context.fromLDContext(userToTest)!); expect(res.isError).toBeTruthy(); expect(res.message).toEqual('Invalid variation index in flag'); - expect(res.detail).toMatchObject({ value: null, variationIndex: undefined, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); + expect(res.detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); }); it.each([basicUser, basicSingleKindUser, basicMultiKindUser])('returns error if rule variation is negative', async (userToTest) => { @@ -51,7 +51,7 @@ describe('when evaluating user equivalent contexts', () => { const res = await evaluator.evaluate(flag, Context.fromLDContext(userToTest)!); expect(res.isError).toBeTruthy(); expect(res.message).toEqual('Invalid variation index in flag'); - expect(res.detail).toMatchObject({ value: null, variationIndex: undefined, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); + expect(res.detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); }); it.each([basicUser, basicSingleKindUser, basicMultiKindUser])('returns error if rule has no variation or rollout', async (userToTest) => { @@ -60,7 +60,7 @@ describe('when evaluating user equivalent contexts', () => { const res = await evaluator.evaluate(flag, Context.fromLDContext(userToTest)!); expect(res.isError).toBeTruthy(); expect(res.message).toEqual('Variation/rollout object with no variation or rollout'); - expect(res.detail).toMatchObject({ value: null, variationIndex: undefined, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); + expect(res.detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); }); it.each([basicUser, basicSingleKindUser, basicMultiKindUser])('returns error if rule has rollout with no variations', async (userToTest) => { @@ -69,7 +69,7 @@ describe('when evaluating user equivalent contexts', () => { const res = await evaluator.evaluate(flag, Context.fromLDContext(userToTest)!); expect(res.isError).toBeTruthy(); expect(res.message).toEqual('Variation/rollout object with no variation or rollout'); - expect(res.detail).toMatchObject({ value: null, variationIndex: undefined, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); + expect(res.detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); }); it.each([basicUser, basicSingleKindUser, basicMultiKindUser])('does not overflow the call stack when evaluating a huge number of rules', async (userToTest) => { diff --git a/server-sdk-common/src/LDClientImpl.ts b/server-sdk-common/src/LDClientImpl.ts index 02288058d3..a8aff83ab0 100644 --- a/server-sdk-common/src/LDClientImpl.ts +++ b/server-sdk-common/src/LDClientImpl.ts @@ -278,7 +278,7 @@ export default class LDClientImpl implements LDClient { builder.addFlag( flag, res.detail.value, - res.detail.variationIndex, + res.detail.variationIndex ?? undefined, res.detail.reason, flag.trackEvents || requireExperimentData, requireExperimentData, @@ -370,7 +370,7 @@ export default class LDClientImpl implements LDClient { return result; } const evalRes = await this.evaluator.evaluate(flag, evalContext, eventFactory); - if (evalRes.detail.variationIndex === undefined) { + if (evalRes.detail.variationIndex === undefined || evalRes.detail.variationIndex === null) { this.logger?.debug('Result value is null in variation'); evalRes.setDefault(defaultValue); } diff --git a/server-sdk-common/src/api/data/LDEvaluationDetail.ts b/server-sdk-common/src/api/data/LDEvaluationDetail.ts index c59ad3fb8b..ebf8589dae 100644 --- a/server-sdk-common/src/api/data/LDEvaluationDetail.ts +++ b/server-sdk-common/src/api/data/LDEvaluationDetail.ts @@ -20,7 +20,7 @@ export interface LDEvaluationDetail { * The index of the returned value within the flag's list of variations, e.g. 0 for the * first variation-- or `null` if the default value was returned. */ - variationIndex?: number; + variationIndex?: number | null; /** * An object describing the main factor that influenced the flag evaluation value. diff --git a/server-sdk-common/src/evaluation/EvalResult.ts b/server-sdk-common/src/evaluation/EvalResult.ts index 83fb802350..2440c9b6a2 100644 --- a/server-sdk-common/src/evaluation/EvalResult.ts +++ b/server-sdk-common/src/evaluation/EvalResult.ts @@ -33,7 +33,7 @@ export default class EvalResult { static forError(errorKind: ErrorKinds, message?: string, def?: any): EvalResult { return new EvalResult(true, { value: def ?? null, - variationIndex: undefined, + variationIndex: null, reason: { kind: 'ERROR', errorKind }, }, message); } @@ -45,7 +45,7 @@ export default class EvalResult { ) { return new EvalResult(false, { value, - variationIndex, + variationIndex: variationIndex === undefined ? null : variationIndex, reason, }); } diff --git a/server-sdk-common/src/events/InputEvalEvent.ts b/server-sdk-common/src/events/InputEvalEvent.ts index abded916c8..ccb9564132 100644 --- a/server-sdk-common/src/events/InputEvalEvent.ts +++ b/server-sdk-common/src/events/InputEvalEvent.ts @@ -41,7 +41,7 @@ export default class InputEvalEvent { this.creationDate = Date.now(); this.context = context; this.default = defValue; - this.variation = detail.variationIndex; + this.variation = detail.variationIndex ?? undefined; this.value = detail.value; if (flag) { From 01ca6746f0cf9f0b550759628f5112ca44407bec Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 16 Aug 2022 11:54:44 -0700 Subject: [PATCH 13/34] Progress on events. --- .../__tests__/LDClient.allFlags.test.ts | 263 ++++++++++++++++++ .../__tests__/LDClient.events.test.ts | 133 +++++++++ server-sdk-common/src/LDClientImpl.ts | 8 +- .../src/integrations/test_data/TestData.ts | 1 - .../src/store/InMemoryFeatureStore.ts | 4 +- 5 files changed, 404 insertions(+), 5 deletions(-) create mode 100644 server-sdk-common/__tests__/LDClient.allFlags.test.ts create mode 100644 server-sdk-common/__tests__/LDClient.events.test.ts diff --git a/server-sdk-common/__tests__/LDClient.allFlags.test.ts b/server-sdk-common/__tests__/LDClient.allFlags.test.ts new file mode 100644 index 0000000000..309b59fc74 --- /dev/null +++ b/server-sdk-common/__tests__/LDClient.allFlags.test.ts @@ -0,0 +1,263 @@ +import { LDClientImpl } from '../src'; +import TestData from '../src/integrations/test_data/TestData'; +import basicPlatform from './evaluation/mocks/platform'; +import TestLogger, { LogLevel } from './Logger'; + +const defaultUser = { key: 'user' }; + +describe('given an LDClient with test data', () => { + let client: LDClientImpl; + let td: TestData; + let logger: TestLogger; + + beforeEach(async () => { + logger = new TestLogger(); + td = new TestData(); + client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + updateProcessor: td.getFactory(), + sendEvents: false, + logger + }, + (_err) => { }, + (_err) => { }, + () => { }, + (_key) => { }, + // Always listen to events. + () => true, + ); + + await client.waitForInitialization(); + }); + + it('captures flag state', async () => { + const value1 = 'value1', value2 = 'value2', value3 = 'value3'; + const flag1 = { + key: 'key1', + version: 100, + on: false, + offVariation: 0, + variations: [value1] + }; + const flag2 = { + key: 'key2', + version: 200, + on: false, + offVariation: 1, + variations: ['x', value2], + trackEvents: true, + debugEventsUntilDate: 1000 + }; + // flag3 has an experiment (evaluation is a fallthrough and TrackEventsFallthrough is on) + const flag3 = { + key: 'key3', + version: 300, + on: true, + fallthrough: { variation: 1 }, + variations: ['x', value3], + trackEvents: false, + trackEventsFallthrough: true + }; + td.usePreconfiguredFlag(flag1); + td.usePreconfiguredFlag(flag2); + td.usePreconfiguredFlag(flag3); + + const state = await client.allFlagsState(defaultUser); + expect(state.valid).toEqual(true); + expect(state.allValues()).toEqual({ [flag1.key]: value1, [flag2.key]: value2, [flag3.key]: value3 }); + expect(state.getFlagValue(flag1.key)).toEqual(value1); + expect(state.getFlagReason('feature')).toEqual(null); + expect(state.toJSON()).toEqual({ + [flag1.key]: value1, + [flag2.key]: value2, + [flag3.key]: value3, + $flagsState: { + [flag1.key]: { + version: flag1.version, + variation: 0, + }, + [flag2.key]: { + version: flag2.version, + variation: 1, + trackEvents: true, + debugEventsUntilDate: 1000 + }, + [flag3.key]: { + version: flag3.version, + variation: 1, + reason: { kind: 'FALLTHROUGH' }, + trackEvents: true, + trackReason: true + } + }, + $valid: true + }); + }); + + it('can filter for only client-side flags', async () => { + td.usePreconfiguredFlag({ key: 'server-side-1', on: false, offVariation: 0, variations: ['a'], clientSide: false }); + td.usePreconfiguredFlag({ key: 'server-side-2', on: false, offVariation: 0, variations: ['b'], clientSide: false }); + td.usePreconfiguredFlag({ key: 'client-side-1', on: false, offVariation: 0, variations: ['value1'], clientSide: true }); + td.usePreconfiguredFlag({ key: 'client-side-2', on: false, offVariation: 0, variations: ['value2'], clientSide: true }); + const state = await client.allFlagsState(defaultUser, { clientSideOnly: true }); + expect(state.valid).toEqual(true); + expect(state.allValues()).toEqual({ 'client-side-1': 'value1', 'client-side-2': 'value2' }); + }); + + it('can include reasons', async () => { + td.usePreconfiguredFlag({ + key: 'feature', + version: 100, + offVariation: 1, + variations: ['a', 'b'], + trackEvents: true, + debugEventsUntilDate: 1000 + }); + const state = await client.allFlagsState(defaultUser, { withReasons: true }); + expect(state.valid).toEqual(true); + expect(state.allValues()).toEqual({ feature: 'b' }); + expect(state.getFlagValue('feature')).toEqual('b'); + expect(state.getFlagReason('feature')).toEqual({"kind": "OFF"}); + expect(state.toJSON()).toEqual({ + feature: 'b', + $flagsState: { + feature: { + version: 100, + variation: 1, + reason: { kind: 'OFF' }, + trackEvents: true, + debugEventsUntilDate: 1000 + } + }, + $valid: true + }); + }); + + it('can omit details for untracked flags', async () => { + const flag1 = { + key: 'flag1', + version: 100, + offVariation: 0, + variations: ['value1'] + }; + const flag2 = { + key: 'flag2', + version: 200, + offVariation: 0, + variations: ['value2'], + trackEvents: true + }; + const flag3 = { + key: 'flag3', + version: 300, + offVariation: 0, + variations: ['value3'], + debugEventsUntilDate: 1000 + }; + td.usePreconfiguredFlag(flag1); + td.usePreconfiguredFlag(flag2); + td.usePreconfiguredFlag(flag3); + + const state = await client.allFlagsState(defaultUser, { withReasons: true, detailsOnlyForTrackedFlags: true }); + expect(state.valid).toEqual(true); + expect(state.allValues()).toEqual({ flag1: 'value1', flag2: 'value2', flag3: 'value3' }); + expect(state.getFlagValue('flag1')).toEqual('value1'); + expect(state.toJSON()).toEqual({ + flag1: 'value1', + flag2: 'value2', + flag3: 'value3', + $flagsState: { + flag1: { + variation: 0 + }, + flag2: { + version: 200, + variation: 0, + reason: { kind: 'OFF' }, + trackEvents: true + }, + flag3: { + version: 300, + variation: 0, + reason: { kind: 'OFF' }, + debugEventsUntilDate: 1000 + } + }, + $valid: true + }); + }); + + it('does not overflow the call stack when evaluating a huge number of flags', async () => { + const flagCount = 5000; + for (let i = 0; i < flagCount; i++) { + td.usePreconfiguredFlag({ + key: 'feature' + i, + version: 1, + on: false + }); + } + const state = await client.allFlagsState(defaultUser); + expect(Object.keys(state.allValues()).length).toEqual(flagCount); + }); + + it('can use callback instead of promise', (done) => { + td.usePreconfiguredFlag({ key: 'server-side-1', on: false, offVariation: 0, variations: ['a'], clientSide: false }); + td.usePreconfiguredFlag({ key: 'server-side-2', on: false, offVariation: 0, variations: ['b'], clientSide: false }); + td.usePreconfiguredFlag({ key: 'client-side-1', on: false, offVariation: 0, variations: ['value1'], clientSide: true }); + td.usePreconfiguredFlag({ key: 'client-side-2', on: false, offVariation: 0, variations: ['value2'], clientSide: true }); + client.allFlagsState(defaultUser, { clientSideOnly: true }, (err, state) => { + expect(state.valid).toEqual(true); + expect(state.allValues()).toEqual({ 'client-side-1': 'value1', 'client-side-2': 'value2' }); + done(); + }); + }); +}); + +describe('given an offline client', () => { + let logger: TestLogger; + let client: LDClientImpl; + let td: TestData; + + beforeEach(() => { + logger = new TestLogger(); + td = new TestData(); + client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + offline: true, + updateProcessor: td.getFactory(), + sendEvents: false, + logger + }, + (_err) => { }, + (_err) => { }, + () => { }, + (key) => { }, + // Always listen to events. + () => true, + ); + }); + + it('returns empty state in offline mode and logs a message', async () => { + const flag = { + key: 'flagkey', + on: false, + offVariation: null + }; + td.usePreconfiguredFlag(flag); + const state = await client.allFlagsState(defaultUser); + expect(state.valid).toEqual(false); + expect(state.allValues()).toEqual({}); + expect(logger.getCount(LogLevel.Info)).toEqual(1); + }); + + it('can use a callback instead of a Promise', (done) => { + client.allFlagsState(defaultUser, {}, (err, state) => { + expect(state.valid).toEqual(false); + done(); + }); + }); +}); \ No newline at end of file diff --git a/server-sdk-common/__tests__/LDClient.events.test.ts b/server-sdk-common/__tests__/LDClient.events.test.ts new file mode 100644 index 0000000000..c2ea875927 --- /dev/null +++ b/server-sdk-common/__tests__/LDClient.events.test.ts @@ -0,0 +1,133 @@ +import { Context, LDClientImpl } from '../src'; +import NullUpdateProcessor from '../src/data_sources/NullUpdateProcessor'; +import EventProcessor from '../src/events/EventProcessor'; +import InputEvent from '../src/events/InputEvent'; +import LDEventProcessor from '../src/events/LDEventProcessor'; +import TestData from '../src/integrations/test_data/TestData'; +import InMemoryFeatureStore from '../src/store/InMemoryFeatureStore'; +import basicPlatform from './evaluation/mocks/platform'; +import TestLogger from './Logger'; + +const defaultUser = { key: 'user' }; +const anonymousUser = { key: 'anon-user', anonymous: true }; +const userWithNoKey = { name: 'Keyless Joe' }; +const userWithEmptyKey = { key: '' }; + +describe('given a client with mock event processor', () => { + let logger: TestLogger; + let client: LDClientImpl; + let store: InMemoryFeatureStore; + let events: InputEvent[]; + let td: TestData; + + beforeEach(async () => { + events = []; + jest.spyOn(EventProcessor.prototype, 'sendEvent').mockImplementation((evt) => events.push(evt)); + jest.spyOn(EventProcessor.prototype, 'flush').mockImplementation(() => Promise.resolve()); + jest.spyOn(EventProcessor.prototype, 'close').mockImplementation(() => {}); + td = new TestData(); + logger = new TestLogger(); + client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + updateProcessor: td.getFactory(), + // featureStore: store, + }, + () => { }, + () => { }, + () => { }, + () => { }, + () => false, + ) + await client.waitForInitialization(); + }); + + it('generates event for existing feature', async () => { + td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1)); + + await client.variation('flagkey', defaultUser, 'c'); + + expect(events).toHaveLength(1); + const e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + key: 'flagkey', + version: 1, + context: Context.fromLDContext(defaultUser), + variation: 1, + value: 'b', + default: 'c' + }); + }); + + it('generates event for existing feature when user is anonymous', async () => { + td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1)); + await client.variation('flagkey', anonymousUser, 'c'); + + expect(events).toHaveLength(1); + var e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + key: 'flagkey', + version: 1, + context: Context.fromLDContext(anonymousUser), + variation: 1, + value: 'b', + default: 'c' + }); + }); + + it('generates event for existing feature with reason', async () => { + td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1)); + await client.variationDetail('flagkey', defaultUser, 'c'); + + expect(events).toHaveLength(1); + var e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + key: 'flagkey', + version: 1, + context: Context.fromLDContext(defaultUser), + variation: 1, + value: 'b', + default: 'c', + reason: { kind: 'FALLTHROUGH' } + }); + }); + + it('forces tracking when a matched rule has trackEvents set', async () => { + td.usePreconfiguredFlag({ // TestData doesn't normally set trackEvents + key: 'flagkey', + version: 1, + on: true, + targets: [], + rules: [ + { + clauses: [ { attribute: 'key', op: 'in', values: [ defaultUser.key ] } ], + variation: 0, + id: 'rule-id', + trackEvents: true + } + ], + fallthrough: { variation: 1 }, + variations: ['a', 'b'] + }); + await client.variation('flagkey', defaultUser, 'c'); + + expect(events).toHaveLength(1); + var e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + creationDate: e.creationDate, + key: 'flagkey', + version: 1, + context: Context.fromLDContext(defaultUser), + variation: 0, + value: 'a', + default: 'c', + trackEvents: true, + reason: { kind: 'RULE_MATCH', ruleIndex: 0, ruleId: 'rule-id' } + }); + }); +}); \ No newline at end of file diff --git a/server-sdk-common/src/LDClientImpl.ts b/server-sdk-common/src/LDClientImpl.ts index a8aff83ab0..6dff538e9e 100644 --- a/server-sdk-common/src/LDClientImpl.ts +++ b/server-sdk-common/src/LDClientImpl.ts @@ -233,14 +233,18 @@ export default class LDClientImpl implements LDClient { ): Promise { if (this.config.offline) { this.logger?.info('allFlagsState() called in offline mode. Returning empty state.'); - return new FlagsStateBuilder(false, false).build(); + const allFlagState = new FlagsStateBuilder(false, false).build(); + callback?.(null, allFlagState); + return allFlagState; } const evalContext = Context.fromLDContext(context); // TODO: Error reporting. if (!evalContext) { this.logger?.info('allFlagsState() called without context. Returning empty state.'); - return new FlagsStateBuilder(false, false).build(); + const allFlagState = new FlagsStateBuilder(false, false).build(); + callback?.(null, allFlagState); + return allFlagState; } let valid = true; diff --git a/server-sdk-common/src/integrations/test_data/TestData.ts b/server-sdk-common/src/integrations/test_data/TestData.ts index f0cc3551ab..b3f8903c79 100644 --- a/server-sdk-common/src/integrations/test_data/TestData.ts +++ b/server-sdk-common/src/integrations/test_data/TestData.ts @@ -3,7 +3,6 @@ import { LDClientContext } from '../../api/options/LDClientContext'; import { LDFeatureStore } from '../../api/subsystems'; import { Flag } from '../../evaluation/data/Flag'; import { Segment } from '../../evaluation/data/Segment'; -import Configuration from '../../options/Configuration'; import AsyncStoreFacade from '../../store/AsyncStoreFacade'; import { processFlag, processSegment } from '../../store/serialization'; import VersionedDataKinds from '../../store/VersionedDataKinds'; diff --git a/server-sdk-common/src/store/InMemoryFeatureStore.ts b/server-sdk-common/src/store/InMemoryFeatureStore.ts index 8cd65118b4..5326f6de1d 100644 --- a/server-sdk-common/src/store/InMemoryFeatureStore.ts +++ b/server-sdk-common/src/store/InMemoryFeatureStore.ts @@ -75,8 +75,8 @@ export default class InMemoryFeatureStore implements LDFeatureStore { } upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { - const item = clone(data); - this.addItem(kind, item.key, item); + // const item = clone(data); + this.addItem(kind, data.key, data); callback?.(); } From 32e6eba8e8aba7c5f3fb93ee341ab5073c036faa Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 16 Aug 2022 11:55:46 -0700 Subject: [PATCH 14/34] Remove upsert clone because we need to retain prototypes. --- server-sdk-common/src/store/InMemoryFeatureStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/server-sdk-common/src/store/InMemoryFeatureStore.ts b/server-sdk-common/src/store/InMemoryFeatureStore.ts index 5326f6de1d..61fc75dea2 100644 --- a/server-sdk-common/src/store/InMemoryFeatureStore.ts +++ b/server-sdk-common/src/store/InMemoryFeatureStore.ts @@ -75,7 +75,6 @@ export default class InMemoryFeatureStore implements LDFeatureStore { } upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { - // const item = clone(data); this.addItem(kind, data.key, data); callback?.(); } From 8c5ee0b02480a017f7b0059b641903605c3fea79 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 16 Aug 2022 12:02:40 -0700 Subject: [PATCH 15/34] Add more events tests. --- sdk-common/src/Context.ts | 3 + .../__tests__/LDClient.events.test.ts | 192 +++++++++++++++++- 2 files changed, 191 insertions(+), 4 deletions(-) diff --git a/sdk-common/src/Context.ts b/sdk-common/src/Context.ts index 60b6c16379..bf9c26d843 100644 --- a/sdk-common/src/Context.ts +++ b/sdk-common/src/Context.ts @@ -319,6 +319,9 @@ export default class Context { * @returns a {@link Context} or `undefined` if one could not be created. */ public static fromLDContext(context: LDContext): Context | undefined { + if (!context) { + return undefined; + } if (isSingleKind(context)) { return Context.fromSingleKindContext(context); } if (isMultiKind(context)) { diff --git a/server-sdk-common/__tests__/LDClient.events.test.ts b/server-sdk-common/__tests__/LDClient.events.test.ts index c2ea875927..02da587897 100644 --- a/server-sdk-common/__tests__/LDClient.events.test.ts +++ b/server-sdk-common/__tests__/LDClient.events.test.ts @@ -1,4 +1,4 @@ -import { Context, LDClientImpl } from '../src'; +import { Context, LDClientImpl, LDContext } from '../src'; import NullUpdateProcessor from '../src/data_sources/NullUpdateProcessor'; import EventProcessor from '../src/events/EventProcessor'; import InputEvent from '../src/events/InputEvent'; @@ -66,7 +66,7 @@ describe('given a client with mock event processor', () => { await client.variation('flagkey', anonymousUser, 'c'); expect(events).toHaveLength(1); - var e = events[0]; + const e = events[0]; expect(e).toMatchObject({ kind: 'feature', key: 'flagkey', @@ -83,7 +83,7 @@ describe('given a client with mock event processor', () => { await client.variationDetail('flagkey', defaultUser, 'c'); expect(events).toHaveLength(1); - var e = events[0]; + const e = events[0]; expect(e).toMatchObject({ kind: 'feature', key: 'flagkey', @@ -116,7 +116,7 @@ describe('given a client with mock event processor', () => { await client.variation('flagkey', defaultUser, 'c'); expect(events).toHaveLength(1); - var e = events[0]; + const e = events[0]; expect(e).toMatchObject({ kind: 'feature', creationDate: e.creationDate, @@ -130,4 +130,188 @@ describe('given a client with mock event processor', () => { reason: { kind: 'RULE_MATCH', ruleIndex: 0, ruleId: 'rule-id' } }); }); + + it('does not force tracking when a matched rule does not have trackEvents set', async () => { + td.usePreconfiguredFlag({ + key: 'flagkey', + version: 1, + on: true, + targets: [], + rules: [ + { + clauses: [ { attribute: 'key', op: 'in', values: [ defaultUser.key ] } ], + variation: 0, + id: 'rule-id' + } + ], + fallthrough: { variation: 1 }, + variations: ['a', 'b'] + }); + await client.variation('flagkey', defaultUser, 'c'); + + expect(events).toHaveLength(1); + const e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + creationDate: e.creationDate, + key: 'flagkey', + version: 1, + context: Context.fromLDContext(defaultUser), + variation: 0, + value: 'a', + default: 'c' + }); + }); + + it('forces tracking for fallthrough result when trackEventsFallthrough is set', async () => { + td.usePreconfiguredFlag({ + key: 'flagkey', + version: 1, + on: true, + targets: [], + rules: [], + fallthrough: { variation: 1 }, + variations: ['a', 'b'], + trackEventsFallthrough: true + }); + await client.variation('flagkey', defaultUser, 'c'); + + expect(events).toHaveLength(1); + const e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + creationDate: e.creationDate, + key: 'flagkey', + version: 1, + context: Context.fromLDContext(defaultUser), + variation: 1, + value: 'b', + default: 'c', + trackEvents: true, + reason: { kind: 'FALLTHROUGH' }, + }); + }); + + it('forces tracking when an evaluation is in the tracked portion of an experiment rollout', async () => { + td.usePreconfiguredFlag({ + key: 'flagkey', + version: 1, + on: true, + targets: [], + rules: [], + fallthrough: { + rollout: { + kind: 'experiment', + variations: [ + { + weight: 100000, + variation: 1, + }, + ], + }, + }, + variations: ['a', 'b'], + }); + await client.variation('flagkey', defaultUser, 'c'); + + expect(events).toHaveLength(1); + const e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + creationDate: e.creationDate, + key: 'flagkey', + version: 1, + context: Context.fromLDContext(defaultUser), + variation: 1, + value: 'b', + default: 'c', + trackEvents: true, + reason: { kind: 'FALLTHROUGH', inExperiment: true }, + }); + }); + + it('does not force tracking when an evaluation is in the untracked portion of an experiment rollout', async () => { + td.usePreconfiguredFlag({ + key: 'flagkey', + version: 1, + on: true, + targets: [], + rules: [], + fallthrough: { + rollout: { + kind: 'experiment', + variations: [ + { + weight: 100000, + variation: 1, + untracked: true, + }, + ], + }, + }, + variations: ['a', 'b'], + }); + + await client.variation('flagkey', defaultUser, 'c'); + + expect(events).toHaveLength(1); + const e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + creationDate: e.creationDate, + key: 'flagkey', + version: 1, + context: Context.fromLDContext(defaultUser), + variation: 1, + value: 'b', + default: 'c', + }); + }); + + it('does not force tracking for fallthrough result when trackEventsFallthrough is not set', async () => { + td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1)); + await client.variation('flagkey', defaultUser, 'c'); + + expect(events).toHaveLength(1); + const e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + creationDate: e.creationDate, + key: 'flagkey', + version: 1, + context: Context.fromLDContext(defaultUser), + variation: 1, + value: 'b', + default: 'c' + }); + }); + + + it('generates event for unknown feature', async () => { + await client.variation('flagkey', defaultUser, 'c'); + + expect(events).toHaveLength(1); + const e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + key: 'flagkey', + context: Context.fromLDContext(defaultUser), + value: 'c', + default: 'c' + }); + }); + + it('generates event for unknown feature when user is anonymous', async () => { + await client.variation('flagkey', anonymousUser, 'c'); + + expect(events).toHaveLength(1); + const e = events[0]; + expect(e).toMatchObject({ + kind: 'feature', + key: 'flagkey', + context: Context.fromLDContext(anonymousUser), + value: 'c', + default: 'c' + }); + }); }); \ No newline at end of file From 516df569bd67c49a4c391ba4a59763b3aec21114 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 16 Aug 2022 13:16:18 -0700 Subject: [PATCH 16/34] Start big segments tests. --- .../LDClientImpl.bigSegments.test.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts diff --git a/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts b/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts new file mode 100644 index 0000000000..7b6882cae5 --- /dev/null +++ b/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts @@ -0,0 +1,73 @@ +import { LDBigSegmentsOptions, LDClientImpl } from '../src'; +import { BigSegmentStore } from '../src/api/interfaces'; +import { LDClientContext } from '../src/api/options/LDClientContext'; +import TestData from '../src/integrations/test_data/TestData'; +import { makeSegmentMatchClause } from './evaluation/flags'; +import basicPlatform from './evaluation/mocks/platform'; + + +const user = { key: 'userkey' }; +const bigSegment = { + key: 'segmentkey', + version: 1, + unbounded: true, + generation: 2, +}; +const flag = { + key: 'flagkey', + on: true, + variations: [false, true], + fallthrough: { variation: 0 }, + rules: [ + { variation: 1, clauses: [makeSegmentMatchClause(bigSegment)] }, + ], +} + +describe('given test data with big segments', () => { + let client: LDClientImpl; + let td: TestData; + + beforeEach(async () => { + td = new TestData(); + td.usePreconfiguredFlag(flag); + td.usePreconfiguredSegment(bigSegment); + }); + + describe('given a big segment store without the user', () => { + beforeEach(async () => { + const bigSegmentsConfig: LDBigSegmentsOptions = { + store: function (clientContext: LDClientContext): BigSegmentStore { + return { + getMetadata: async () => { return { lastUpToDate: new Date().getTime() } }, + getUserMembership: async () => ({}), + close: () => {}, + }; + } + }; + + client = new LDClientImpl( + 'sdk-key', + basicPlatform, + { + updateProcessor: td.getFactory(), + sendEvents: false, + bigSegments: bigSegmentsConfig + }, + (_err) => { }, + (_err) => { }, + () => { }, + (key) => { }, + // Always listen to events. + () => true, + ); + + await client.waitForInitialization(); + }); + + it('user not found in big segment store', async () => { + const result = await client.variationDetail(flag.key, user, false); + expect(result.value).toBe(false); + expect(result.reason.bigSegmentsStatus).toEqual('HEALTHY'); + }); + }); +}); \ No newline at end of file From d427d1ca8a74f2498dd7a5adcd92160a74021c6a Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 16 Aug 2022 14:54:05 -0700 Subject: [PATCH 17/34] Finish big segments test. Linting. --- server-sdk-common/__tests__/AsyncQueue.ts | 3 +- .../__tests__/LDClient.allFlags.test.ts | 109 ++++++++----- .../__tests__/LDClient.evaluation.test.ts | 50 +++--- .../__tests__/LDClient.events.test.ts | 44 +++--- .../LDClientImpl.bigSegments.test.ts | 148 ++++++++++++++++-- .../__tests__/LDClientImpl.listeners.test.ts | 9 +- .../data_sources/DataSourceUpdates.test.ts | 40 ++--- .../data_sources/FileDataSource.test.ts | 64 ++++---- .../evaluation/Evaluator.segments.test.ts | 3 +- .../__tests__/evaluation/mocks/noQueries.ts | 2 +- server-sdk-common/src/ClientContext.ts | 4 +- server-sdk-common/src/LDClientImpl.ts | 5 +- .../src/api/options/LDBigSegmentsOptions.ts | 1 - .../src/data_sources/FileDataSourceFactory.ts | 1 - server-sdk-common/src/evaluation/Evaluator.ts | 120 +++++++++++++- server-sdk-common/src/evaluation/Queries.ts | 3 +- .../src/evaluation/makeBigSegmentRef.ts | 11 ++ .../src/store/InMemoryFeatureStore.ts | 10 -- 18 files changed, 450 insertions(+), 177 deletions(-) create mode 100644 server-sdk-common/src/evaluation/makeBigSegmentRef.ts diff --git a/server-sdk-common/__tests__/AsyncQueue.ts b/server-sdk-common/__tests__/AsyncQueue.ts index e1c626b45b..6dccdcdab0 100644 --- a/server-sdk-common/__tests__/AsyncQueue.ts +++ b/server-sdk-common/__tests__/AsyncQueue.ts @@ -1,5 +1,6 @@ -export class AsyncQueue { +export default class AsyncQueue { private content: any[] = []; + private takers: { resolve: (res: any) => void; reject: (err: Error) => void; diff --git a/server-sdk-common/__tests__/LDClient.allFlags.test.ts b/server-sdk-common/__tests__/LDClient.allFlags.test.ts index 309b59fc74..63ccb0314f 100644 --- a/server-sdk-common/__tests__/LDClient.allFlags.test.ts +++ b/server-sdk-common/__tests__/LDClient.allFlags.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { LDClientImpl } from '../src'; import TestData from '../src/integrations/test_data/TestData'; import basicPlatform from './evaluation/mocks/platform'; @@ -19,7 +20,7 @@ describe('given an LDClient with test data', () => { { updateProcessor: td.getFactory(), sendEvents: false, - logger + logger, }, (_err) => { }, (_err) => { }, @@ -33,13 +34,14 @@ describe('given an LDClient with test data', () => { }); it('captures flag state', async () => { - const value1 = 'value1', value2 = 'value2', value3 = 'value3'; + const value1 = 'value1'; const value2 = 'value2'; const + value3 = 'value3'; const flag1 = { key: 'key1', version: 100, on: false, offVariation: 0, - variations: [value1] + variations: [value1], }; const flag2 = { key: 'key2', @@ -48,7 +50,7 @@ describe('given an LDClient with test data', () => { offVariation: 1, variations: ['x', value2], trackEvents: true, - debugEventsUntilDate: 1000 + debugEventsUntilDate: 1000, }; // flag3 has an experiment (evaluation is a fallthrough and TrackEventsFallthrough is on) const flag3 = { @@ -58,7 +60,7 @@ describe('given an LDClient with test data', () => { fallthrough: { variation: 1 }, variations: ['x', value3], trackEvents: false, - trackEventsFallthrough: true + trackEventsFallthrough: true, }; td.usePreconfiguredFlag(flag1); td.usePreconfiguredFlag(flag2); @@ -66,7 +68,9 @@ describe('given an LDClient with test data', () => { const state = await client.allFlagsState(defaultUser); expect(state.valid).toEqual(true); - expect(state.allValues()).toEqual({ [flag1.key]: value1, [flag2.key]: value2, [flag3.key]: value3 }); + expect(state.allValues()).toEqual( + { [flag1.key]: value1, [flag2.key]: value2, [flag3.key]: value3 }, + ); expect(state.getFlagValue(flag1.key)).toEqual(value1); expect(state.getFlagReason('feature')).toEqual(null); expect(state.toJSON()).toEqual({ @@ -82,25 +86,33 @@ describe('given an LDClient with test data', () => { version: flag2.version, variation: 1, trackEvents: true, - debugEventsUntilDate: 1000 + debugEventsUntilDate: 1000, }, [flag3.key]: { version: flag3.version, variation: 1, reason: { kind: 'FALLTHROUGH' }, trackEvents: true, - trackReason: true - } + trackReason: true, + }, }, - $valid: true + $valid: true, }); }); it('can filter for only client-side flags', async () => { - td.usePreconfiguredFlag({ key: 'server-side-1', on: false, offVariation: 0, variations: ['a'], clientSide: false }); - td.usePreconfiguredFlag({ key: 'server-side-2', on: false, offVariation: 0, variations: ['b'], clientSide: false }); - td.usePreconfiguredFlag({ key: 'client-side-1', on: false, offVariation: 0, variations: ['value1'], clientSide: true }); - td.usePreconfiguredFlag({ key: 'client-side-2', on: false, offVariation: 0, variations: ['value2'], clientSide: true }); + td.usePreconfiguredFlag({ + key: 'server-side-1', on: false, offVariation: 0, variations: ['a'], clientSide: false, + }); + td.usePreconfiguredFlag({ + key: 'server-side-2', on: false, offVariation: 0, variations: ['b'], clientSide: false, + }); + td.usePreconfiguredFlag({ + key: 'client-side-1', on: false, offVariation: 0, variations: ['value1'], clientSide: true, + }); + td.usePreconfiguredFlag({ + key: 'client-side-2', on: false, offVariation: 0, variations: ['value2'], clientSide: true, + }); const state = await client.allFlagsState(defaultUser, { clientSideOnly: true }); expect(state.valid).toEqual(true); expect(state.allValues()).toEqual({ 'client-side-1': 'value1', 'client-side-2': 'value2' }); @@ -113,13 +125,13 @@ describe('given an LDClient with test data', () => { offVariation: 1, variations: ['a', 'b'], trackEvents: true, - debugEventsUntilDate: 1000 + debugEventsUntilDate: 1000, }); const state = await client.allFlagsState(defaultUser, { withReasons: true }); expect(state.valid).toEqual(true); expect(state.allValues()).toEqual({ feature: 'b' }); expect(state.getFlagValue('feature')).toEqual('b'); - expect(state.getFlagReason('feature')).toEqual({"kind": "OFF"}); + expect(state.getFlagReason('feature')).toEqual({ kind: 'OFF' }); expect(state.toJSON()).toEqual({ feature: 'b', $flagsState: { @@ -128,10 +140,10 @@ describe('given an LDClient with test data', () => { variation: 1, reason: { kind: 'OFF' }, trackEvents: true, - debugEventsUntilDate: 1000 - } + debugEventsUntilDate: 1000, + }, }, - $valid: true + $valid: true, }); }); @@ -140,27 +152,30 @@ describe('given an LDClient with test data', () => { key: 'flag1', version: 100, offVariation: 0, - variations: ['value1'] + variations: ['value1'], }; const flag2 = { key: 'flag2', version: 200, offVariation: 0, variations: ['value2'], - trackEvents: true + trackEvents: true, }; const flag3 = { key: 'flag3', version: 300, offVariation: 0, variations: ['value3'], - debugEventsUntilDate: 1000 + debugEventsUntilDate: 1000, }; td.usePreconfiguredFlag(flag1); td.usePreconfiguredFlag(flag2); td.usePreconfiguredFlag(flag3); - const state = await client.allFlagsState(defaultUser, { withReasons: true, detailsOnlyForTrackedFlags: true }); + const state = await client.allFlagsState( + defaultUser, + { withReasons: true, detailsOnlyForTrackedFlags: true }, + ); expect(state.valid).toEqual(true); expect(state.allValues()).toEqual({ flag1: 'value1', flag2: 'value2', flag3: 'value3' }); expect(state.getFlagValue('flag1')).toEqual('value1'); @@ -170,32 +185,32 @@ describe('given an LDClient with test data', () => { flag3: 'value3', $flagsState: { flag1: { - variation: 0 + variation: 0, }, flag2: { version: 200, variation: 0, reason: { kind: 'OFF' }, - trackEvents: true + trackEvents: true, }, flag3: { version: 300, variation: 0, reason: { kind: 'OFF' }, - debugEventsUntilDate: 1000 - } + debugEventsUntilDate: 1000, + }, }, - $valid: true + $valid: true, }); }); it('does not overflow the call stack when evaluating a huge number of flags', async () => { const flagCount = 5000; - for (let i = 0; i < flagCount; i++) { + for (let i = 0; i < flagCount; i += 1) { td.usePreconfiguredFlag({ - key: 'feature' + i, + key: `feature${i}`, version: 1, - on: false + on: false, }); } const state = await client.allFlagsState(defaultUser); @@ -203,15 +218,23 @@ describe('given an LDClient with test data', () => { }); it('can use callback instead of promise', (done) => { - td.usePreconfiguredFlag({ key: 'server-side-1', on: false, offVariation: 0, variations: ['a'], clientSide: false }); - td.usePreconfiguredFlag({ key: 'server-side-2', on: false, offVariation: 0, variations: ['b'], clientSide: false }); - td.usePreconfiguredFlag({ key: 'client-side-1', on: false, offVariation: 0, variations: ['value1'], clientSide: true }); - td.usePreconfiguredFlag({ key: 'client-side-2', on: false, offVariation: 0, variations: ['value2'], clientSide: true }); - client.allFlagsState(defaultUser, { clientSideOnly: true }, (err, state) => { - expect(state.valid).toEqual(true); - expect(state.allValues()).toEqual({ 'client-side-1': 'value1', 'client-side-2': 'value2' }); - done(); - }); + td.usePreconfiguredFlag({ + key: 'server-side-1', on: false, offVariation: 0, variations: ['a'], clientSide: false, + }); + td.usePreconfiguredFlag({ + key: 'server-side-2', on: false, offVariation: 0, variations: ['b'], clientSide: false, + }); + td.usePreconfiguredFlag({ + key: 'client-side-1', on: false, offVariation: 0, variations: ['value1'], clientSide: true, + }); + td.usePreconfiguredFlag({ + key: 'client-side-2', on: false, offVariation: 0, variations: ['value2'], clientSide: true, + }); + client.allFlagsState(defaultUser, { clientSideOnly: true }, (err, state) => { + expect(state.valid).toEqual(true); + expect(state.allValues()).toEqual({ 'client-side-1': 'value1', 'client-side-2': 'value2' }); + done(); + }); }); }); @@ -230,7 +253,7 @@ describe('given an offline client', () => { offline: true, updateProcessor: td.getFactory(), sendEvents: false, - logger + logger, }, (_err) => { }, (_err) => { }, @@ -245,7 +268,7 @@ describe('given an offline client', () => { const flag = { key: 'flagkey', on: false, - offVariation: null + offVariation: null, }; td.usePreconfiguredFlag(flag); const state = await client.allFlagsState(defaultUser); @@ -260,4 +283,4 @@ describe('given an offline client', () => { done(); }); }); -}); \ No newline at end of file +}); diff --git a/server-sdk-common/__tests__/LDClient.evaluation.test.ts b/server-sdk-common/__tests__/LDClient.evaluation.test.ts index e31fc33b7b..b81a5cb51b 100644 --- a/server-sdk-common/__tests__/LDClient.evaluation.test.ts +++ b/server-sdk-common/__tests__/LDClient.evaluation.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { LDClientImpl } from '../src'; import { LDFeatureStore } from '../src/api/subsystems'; import NullUpdateProcessor from '../src/data_sources/NullUpdateProcessor'; @@ -5,7 +6,6 @@ import TestData from '../src/integrations/test_data/TestData'; import AsyncStoreFacade from '../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../src/store/InMemoryFeatureStore'; import VersionedDataKinds from '../src/store/VersionedDataKinds'; -import { AsyncQueue } from './AsyncQueue'; import basicPlatform from './evaluation/mocks/platform'; import TestLogger, { LogLevel } from './Logger'; @@ -53,7 +53,7 @@ describe('given an LDClient with test data', () => { td.usePreconfiguredFlag({ // TestData normally won't construct a flag with offVariation: null key: 'flagIsNull', on: false, - offVariation: null + offVariation: null, }); expect(await client.variation('flagIsNull', defaultUser, 'default')).toEqual('default'); @@ -63,11 +63,11 @@ describe('given an LDClient with test data', () => { td.usePreconfiguredFlag({ // TestData normally won't construct a flag with offVariation: null key: 'flagIsNull', on: false, - offVariation: null + offVariation: null, }); expect(await client.variationDetail('flagIsNull', defaultUser, 'default')).toMatchObject( - { value: 'default', variationIndex: null, reason: { kind: 'OFF' } } + { value: 'default', variationIndex: null, reason: { kind: 'OFF' } }, ); }); @@ -84,9 +84,10 @@ describe('given an LDClient with test data', () => { expect(err).toBeNull(); expect(result).toMatchObject( { - value: 'default', variationIndex: null, - reason: { kind: 'ERROR', errorKind: 'FLAG_NOT_FOUND' } - } + value: 'default', + variationIndex: null, + reason: { kind: 'ERROR', errorKind: 'FLAG_NOT_FOUND' }, + }, ); done(); }); @@ -95,16 +96,18 @@ describe('given an LDClient with test data', () => { it('can evaluate an existing flag with detail', async () => { td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1)); expect(await client.variationDetail('flagkey', defaultUser, 'c')).toMatchObject( - { value: 'b', variationIndex: 1, reason: { kind: 'FALLTHROUGH' } } + { value: 'b', variationIndex: 1, reason: { kind: 'FALLTHROUGH' } }, ); }); it('returns default for an unknown flag with detail', async () => { expect(await client.variationDetail('flagkey', defaultUser, 'default')).toMatchObject( { - value: 'default', variationIndex: null, - reason: { kind: 'ERROR', errorKind: 'FLAG_NOT_FOUND' } - }) + value: 'default', + variationIndex: null, + reason: { kind: 'ERROR', errorKind: 'FLAG_NOT_FOUND' }, + }, + ); }); }); @@ -123,7 +126,7 @@ describe('given an offline client', () => { offline: true, updateProcessor: td.getFactory(), sendEvents: false, - logger + logger, }, (_err) => { }, (_err) => { }, @@ -147,8 +150,9 @@ describe('given an offline client', () => { td.update(td.flag('flagkey').variations('value').variationForAll(0)); const result = await client.variationDetail('flagkey', defaultUser, 'default'); expect(result).toMatchObject({ - value: 'default', variationIndex: null, - reason: { kind: 'ERROR', errorKind: 'CLIENT_NOT_READY' } + value: 'default', + variationIndex: null, + reason: { kind: 'ERROR', errorKind: 'CLIENT_NOT_READY' }, }); expect(logger.getCount(LogLevel.Info)).toEqual(1); }); @@ -167,7 +171,7 @@ describe('given a client and store that are uninitialized', () => { version: 1, on: false, offVariation: 0, - variations: ['value'] + variations: ['value'], }); client = new LDClientImpl( @@ -194,9 +198,10 @@ describe('given a client and store that are uninitialized', () => { it('returns the default value for variationDetail', async () => { expect(await client.variationDetail('flagkey', defaultUser, 'default')).toMatchObject( { - value: 'default', variationIndex: null, - reason: { kind: 'ERROR', errorKind: 'CLIENT_NOT_READY' } - } + value: 'default', + variationIndex: null, + reason: { kind: 'ERROR', errorKind: 'CLIENT_NOT_READY' }, + }, ); }); }); @@ -215,10 +220,10 @@ describe('given a client that is un-initialized and store that is initialized', version: 1, on: false, offVariation: 0, - variations: ['value'] - } + variations: ['value'], + }, }, - segments: {} + segments: {}, }); client = new LDClientImpl( @@ -244,8 +249,7 @@ describe('given a client that is un-initialized and store that is initialized', it('returns the value for variationDetail', async () => { expect(await client.variationDetail('flagkey', defaultUser, 'default')).toMatchObject( - { value: 'value', variationIndex: 0, reason: { kind: 'OFF' } } + { value: 'value', variationIndex: 0, reason: { kind: 'OFF' } }, ); }); }); - diff --git a/server-sdk-common/__tests__/LDClient.events.test.ts b/server-sdk-common/__tests__/LDClient.events.test.ts index 02da587897..2326c731b0 100644 --- a/server-sdk-common/__tests__/LDClient.events.test.ts +++ b/server-sdk-common/__tests__/LDClient.events.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { Context, LDClientImpl, LDContext } from '../src'; import NullUpdateProcessor from '../src/data_sources/NullUpdateProcessor'; import EventProcessor from '../src/events/EventProcessor'; @@ -8,8 +9,8 @@ import InMemoryFeatureStore from '../src/store/InMemoryFeatureStore'; import basicPlatform from './evaluation/mocks/platform'; import TestLogger from './Logger'; -const defaultUser = { key: 'user' }; -const anonymousUser = { key: 'anon-user', anonymous: true }; +const defaultUser = { key: 'user' }; +const anonymousUser = { key: 'anon-user', anonymous: true }; const userWithNoKey = { name: 'Keyless Joe' }; const userWithEmptyKey = { key: '' }; @@ -39,7 +40,7 @@ describe('given a client with mock event processor', () => { () => { }, () => { }, () => false, - ) + ); await client.waitForInitialization(); }); @@ -57,7 +58,7 @@ describe('given a client with mock event processor', () => { context: Context.fromLDContext(defaultUser), variation: 1, value: 'b', - default: 'c' + default: 'c', }); }); @@ -74,7 +75,7 @@ describe('given a client with mock event processor', () => { context: Context.fromLDContext(anonymousUser), variation: 1, value: 'b', - default: 'c' + default: 'c', }); }); @@ -92,7 +93,7 @@ describe('given a client with mock event processor', () => { variation: 1, value: 'b', default: 'c', - reason: { kind: 'FALLTHROUGH' } + reason: { kind: 'FALLTHROUGH' }, }); }); @@ -104,14 +105,14 @@ describe('given a client with mock event processor', () => { targets: [], rules: [ { - clauses: [ { attribute: 'key', op: 'in', values: [ defaultUser.key ] } ], + clauses: [{ attribute: 'key', op: 'in', values: [defaultUser.key] }], variation: 0, id: 'rule-id', - trackEvents: true - } + trackEvents: true, + }, ], fallthrough: { variation: 1 }, - variations: ['a', 'b'] + variations: ['a', 'b'], }); await client.variation('flagkey', defaultUser, 'c'); @@ -127,7 +128,7 @@ describe('given a client with mock event processor', () => { value: 'a', default: 'c', trackEvents: true, - reason: { kind: 'RULE_MATCH', ruleIndex: 0, ruleId: 'rule-id' } + reason: { kind: 'RULE_MATCH', ruleIndex: 0, ruleId: 'rule-id' }, }); }); @@ -139,13 +140,13 @@ describe('given a client with mock event processor', () => { targets: [], rules: [ { - clauses: [ { attribute: 'key', op: 'in', values: [ defaultUser.key ] } ], + clauses: [{ attribute: 'key', op: 'in', values: [defaultUser.key] }], variation: 0, - id: 'rule-id' - } + id: 'rule-id', + }, ], fallthrough: { variation: 1 }, - variations: ['a', 'b'] + variations: ['a', 'b'], }); await client.variation('flagkey', defaultUser, 'c'); @@ -159,7 +160,7 @@ describe('given a client with mock event processor', () => { context: Context.fromLDContext(defaultUser), variation: 0, value: 'a', - default: 'c' + default: 'c', }); }); @@ -172,7 +173,7 @@ describe('given a client with mock event processor', () => { rules: [], fallthrough: { variation: 1 }, variations: ['a', 'b'], - trackEventsFallthrough: true + trackEventsFallthrough: true, }); await client.variation('flagkey', defaultUser, 'c'); @@ -282,11 +283,10 @@ describe('given a client with mock event processor', () => { context: Context.fromLDContext(defaultUser), variation: 1, value: 'b', - default: 'c' + default: 'c', }); }); - it('generates event for unknown feature', async () => { await client.variation('flagkey', defaultUser, 'c'); @@ -297,7 +297,7 @@ describe('given a client with mock event processor', () => { key: 'flagkey', context: Context.fromLDContext(defaultUser), value: 'c', - default: 'c' + default: 'c', }); }); @@ -311,7 +311,7 @@ describe('given a client with mock event processor', () => { key: 'flagkey', context: Context.fromLDContext(anonymousUser), value: 'c', - default: 'c' + default: 'c', }); }); -}); \ No newline at end of file +}); diff --git a/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts b/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts index 7b6882cae5..0474fe3086 100644 --- a/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts +++ b/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts @@ -1,11 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { LDBigSegmentsOptions, LDClientImpl } from '../src'; import { BigSegmentStore } from '../src/api/interfaces'; import { LDClientContext } from '../src/api/options/LDClientContext'; +import makeBigSegmentRef from '../src/evaluation/makeBigSegmentRef'; import TestData from '../src/integrations/test_data/TestData'; +import { Hasher, Crypto, Hmac } from '../src/platform'; import { makeSegmentMatchClause } from './evaluation/flags'; import basicPlatform from './evaluation/mocks/platform'; - const user = { key: 'userkey' }; const bigSegment = { key: 'segmentkey', @@ -21,8 +23,32 @@ const flag = { rules: [ { variation: 1, clauses: [makeSegmentMatchClause(bigSegment)] }, ], +}; + +class TestHasher implements Hasher { + private value: string = 'is_hashed:'; + + update(toAdd: string): Hasher { + this.value += toAdd; + return this; + } + + digest() { + return this.value; + } } +const crypto: Crypto = { + createHash(algorithm: string): Hasher { + expect(algorithm).toEqual('sha256'); + return new TestHasher(); + }, + createHmac(algorithm: string, key: string): Hmac { + // Not used for this test. + throw new Error(`Function not implemented.${algorithm}${key}`); + }, +}; + describe('given test data with big segments', () => { let client: LDClientImpl; let td: TestData; @@ -36,22 +62,22 @@ describe('given test data with big segments', () => { describe('given a big segment store without the user', () => { beforeEach(async () => { const bigSegmentsConfig: LDBigSegmentsOptions = { - store: function (clientContext: LDClientContext): BigSegmentStore { + store(clientContext: LDClientContext): BigSegmentStore { return { - getMetadata: async () => { return { lastUpToDate: new Date().getTime() } }, - getUserMembership: async () => ({}), - close: () => {}, + getMetadata: async () => ({ lastUpToDate: new Date().getTime() }), + getUserMembership: async () => undefined, + close: () => { }, }; - } + }, }; client = new LDClientImpl( 'sdk-key', - basicPlatform, + { ...basicPlatform, crypto }, { updateProcessor: td.getFactory(), sendEvents: false, - bigSegments: bigSegmentsConfig + bigSegments: bigSegmentsConfig, }, (_err) => { }, (_err) => { }, @@ -70,4 +96,108 @@ describe('given test data with big segments', () => { expect(result.reason.bigSegmentsStatus).toEqual('HEALTHY'); }); }); -}); \ No newline at end of file + + describe('given a big segment store with the user', () => { + beforeEach(async () => { + const membership = { [makeBigSegmentRef(bigSegment)]: true }; + const bigSegmentsConfig: LDBigSegmentsOptions = { + store(clientContext: LDClientContext): BigSegmentStore { + return { + getMetadata: async () => ({ lastUpToDate: new Date().getTime() }), + getUserMembership: async (hash) => (hash === `is_hashed:${user.key}` ? membership : undefined), + close: () => { }, + }; + }, + }; + + client = new LDClientImpl( + 'sdk-key', + { ...basicPlatform, crypto }, + { + updateProcessor: td.getFactory(), + sendEvents: false, + bigSegments: bigSegmentsConfig, + }, + (_err) => { }, + (_err) => { }, + () => { }, + (key) => { }, + // Always listen to events. + () => true, + ); + + await client.waitForInitialization(); + }); + + it('user found in big segment store', async () => { + const result = await client.variationDetail(flag.key, user, false); + expect(result.value).toBe(true); + expect(result.reason.bigSegmentsStatus).toEqual('HEALTHY'); + }); + }); + + describe('given a big segment store which experiences an error', () => { + beforeEach(async () => { + const bigSegmentsConfig: LDBigSegmentsOptions = { + store(clientContext: LDClientContext): BigSegmentStore { + return { + getMetadata: async () => ({ lastUpToDate: new Date().getTime() }), + getUserMembership: async (hash) => { throw new Error('sorry'); }, + close: () => { }, + }; + }, + }; + + client = new LDClientImpl( + 'sdk-key', + { ...basicPlatform, crypto }, + { + updateProcessor: td.getFactory(), + sendEvents: false, + bigSegments: bigSegmentsConfig, + }, + (_err) => { }, + (_err) => { }, + () => { }, + (key) => { }, + // Always listen to events. + () => true, + ); + + await client.waitForInitialization(); + }); + + it('produces a store error', async () => { + const result = await client.variationDetail(flag.key, user, false); + expect(result.value).toBe(false); + expect(result.reason.bigSegmentsStatus).toEqual('STORE_ERROR'); + }); + }); + + describe('given a client without big segment support.', () => { + beforeEach(async () => { + client = new LDClientImpl( + 'sdk-key', + { ...basicPlatform, crypto }, + { + updateProcessor: td.getFactory(), + sendEvents: false, + }, + (_err) => { }, + (_err) => { }, + () => { }, + (key) => { }, + // Always listen to events. + () => true, + ); + + await client.waitForInitialization(); + }); + + it('produces a not configured error', async () => { + const result = await client.variationDetail(flag.key, user, false); + expect(result.value).toBe(false); + expect(result.reason.bigSegmentsStatus).toEqual('NOT_CONFIGURED'); + }); + }); +}); diff --git a/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts index f43b0c704e..f0a9f1b593 100644 --- a/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts +++ b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts @@ -1,6 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { LDClientImpl } from '../src'; import TestData from '../src/integrations/test_data/TestData'; -import { AsyncQueue } from './AsyncQueue'; +import AsyncQueue from './AsyncQueue'; import basicPlatform from './evaluation/mocks/platform'; import TestLogger from './Logger'; @@ -24,7 +25,7 @@ describe('given an LDClient with test data', () => { (_err) => { }, () => { }, (key) => { - queue.push(key) + queue.push(key); }, // Always listen to events. () => true, @@ -42,11 +43,11 @@ describe('given an LDClient with test data', () => { expect(await queue.take()).toEqual('flag1'); expect(await queue.take()).toEqual('flag2'); - + td.update(td.flag('flag1').on(false)); td.update(td.flag('flag2').on(false)); expect(await queue.take()).toEqual('flag1'); expect(await queue.take()).toEqual('flag2'); }); -}) \ No newline at end of file +}); diff --git a/server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts b/server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts index 751c3e1e76..72679e06ba 100644 --- a/server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts +++ b/server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts @@ -3,13 +3,13 @@ import promisify from '../../src/async/promisify'; import DataSourceUpdates from '../../src/data_sources/DataSourceUpdates'; import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; import VersionedDataKinds from '../../src/store/VersionedDataKinds'; -import { AsyncQueue } from '../AsyncQueue'; +import AsyncQueue from '../AsyncQueue'; describe.each([true, false])('given a DataSourceUpdates with in memory store and change listeners: %s', (listen) => { let store: LDFeatureStore; let updates: DataSourceUpdates; - let queue = new AsyncQueue(); + const queue = new AsyncQueue(); beforeEach(() => { store = new InMemoryFeatureStore(); @@ -17,7 +17,7 @@ describe.each([true, false])('given a DataSourceUpdates with in memory store and updates = new DataSourceUpdates( store, () => listen, - (key) => queue.push(key) + (key) => queue.push(key), ); }); @@ -26,9 +26,9 @@ describe.each([true, false])('given a DataSourceUpdates with in memory store and updates.init({ features: { a: { key: 'a', version: 1 }, - b: { key: 'b', version: 1 } + b: { key: 'b', version: 1 }, }, - segments: {} + segments: {}, }, () => { cb(undefined); }); @@ -46,16 +46,16 @@ describe.each([true, false])('given a DataSourceUpdates with in memory store and features: { a: { key: 'a', version: 1 }, b: { key: 'b', version: 1 }, - c: { key: 'c', version: 1 } + c: { key: 'c', version: 1 }, }, - segments: {} + segments: {}, }; const allData1 = { features: { a: { key: 'a', version: 1 }, - b: { key: 'b', version: 2 } + b: { key: 'b', version: 2 }, }, - segments: {} + segments: {}, }; await promisify((cb) => { @@ -91,7 +91,7 @@ describe.each([true, false])('given a DataSourceUpdates with in memory store and features: { a: { key: 'a', version: 1 }, }, - segments: {} + segments: {}, }, () => { cb(undefined); }); @@ -123,17 +123,19 @@ describe.each([true, false])('given a DataSourceUpdates with in memory store and a: { key: 'a', version: 1 }, b: { key: 'b', version: 1, prerequisites: [{ key: 'c' }, { key: 'e' }] }, c: { - key: 'c', version: 1, prerequisites: [{ key: 'd' }], + key: 'c', + version: 1, + prerequisites: [{ key: 'd' }], rules: [ - { clauses: [{ op: 'segmentMatch', values: ['s0'] }] } - ] + { clauses: [{ op: 'segmentMatch', values: ['s0'] }] }, + ], }, d: { key: 'd', version: 1, prerequisites: [{ key: 'e' }] }, - e: { key: 'e', version: 1 } + e: { key: 'e', version: 1 }, }, segments: { - s0: { key: 's0', version: 1 } - } + s0: { key: 's0', version: 1 }, + }, }, () => { cb(undefined); }); @@ -152,13 +154,13 @@ describe.each([true, false])('given a DataSourceUpdates with in memory store and updates.upsert( VersionedDataKinds.Features, { key: 'd', version: 2, prerequisites: [{ key: 'e' }] }, - () => cb(undefined) + () => cb(undefined), ); }); if (listen) { expect( - [await queue.take(), await queue.take(), await queue.take()].sort() + [await queue.take(), await queue.take(), await queue.take()].sort(), ).toEqual(['b', 'c', 'd']); } expect(queue.empty()).toBeTruthy(); @@ -167,7 +169,7 @@ describe.each([true, false])('given a DataSourceUpdates with in memory store and updates.upsert( VersionedDataKinds.Segments, { key: 's0', version: 2 }, - () => cb(undefined) + () => cb(undefined), ); }); diff --git a/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts b/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts index 7de89225f2..2fecaed1f1 100644 --- a/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts +++ b/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts @@ -92,8 +92,8 @@ class MockFilesystem implements Filesystem { }> = {}; public watches: Record void } - )[]> = {}; + (WatchHandle & { id: number, cb: (eventType: string, filename: string) => void } + )[]> = {}; public watchHandleId = 0; @@ -165,9 +165,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); expect(await asyncFeatureStore.initialized()).toBeFalsy(); @@ -192,9 +192,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async () => { @@ -222,9 +222,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async (err) => { @@ -251,9 +251,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async (err) => { @@ -283,9 +283,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async () => { @@ -317,9 +317,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async (err) => { @@ -346,9 +346,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async () => { @@ -374,9 +374,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async () => { @@ -416,9 +416,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async () => { @@ -463,9 +463,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async () => { @@ -498,9 +498,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async () => { @@ -546,9 +546,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async () => { @@ -597,9 +597,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); fds.start(async () => { @@ -641,9 +641,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); const err = await promisify((cb) => { @@ -677,9 +677,9 @@ describe('given a mock filesystem and memory feature store', () => { featureStore, logger, }), - { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem } + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, ), - featureStore + featureStore, ); const err = await promisify((cb) => { diff --git a/server-sdk-common/__tests__/evaluation/Evaluator.segments.test.ts b/server-sdk-common/__tests__/evaluation/Evaluator.segments.test.ts index 2a0a45687c..76bc999cc5 100644 --- a/server-sdk-common/__tests__/evaluation/Evaluator.segments.test.ts +++ b/server-sdk-common/__tests__/evaluation/Evaluator.segments.test.ts @@ -27,7 +27,8 @@ class TestQueries implements Queries { return this.data.segments?.find((segment) => segment.key === key); } - getBigSegmentsMembership(userKey: string): Promise { + getBigSegmentsMembership(userKey: string): + Promise<[BigSegmentStoreMembership | null, string] | undefined> { throw new Error('Method not implemented.'); } } diff --git a/server-sdk-common/__tests__/evaluation/mocks/noQueries.ts b/server-sdk-common/__tests__/evaluation/mocks/noQueries.ts index 08a4fb21b7..4a6cee4f34 100644 --- a/server-sdk-common/__tests__/evaluation/mocks/noQueries.ts +++ b/server-sdk-common/__tests__/evaluation/mocks/noQueries.ts @@ -10,7 +10,7 @@ const noQueries: Queries = { getSegment(): Promise { throw new Error('Function not implemented.'); }, - getBigSegmentsMembership(): Promise { + getBigSegmentsMembership(): Promise<[BigSegmentStoreMembership | null, string] | undefined> { throw new Error('Function not implemented.'); }, }; diff --git a/server-sdk-common/src/ClientContext.ts b/server-sdk-common/src/ClientContext.ts index 1b9b6058d8..94af9b0b56 100644 --- a/server-sdk-common/src/ClientContext.ts +++ b/server-sdk-common/src/ClientContext.ts @@ -11,13 +11,13 @@ export default class ClientContext implements LDClientContext { constructor( sdkKey: string, configuration: Configuration, - public readonly platform: Platform + public readonly platform: Platform, ) { this.basicConfiguration = { logger: configuration.logger, offline: configuration.offline, sdkKey, - serviceEndpoints: configuration.serviceEndpoints + serviceEndpoints: configuration.serviceEndpoints, }; } } diff --git a/server-sdk-common/src/LDClientImpl.ts b/server-sdk-common/src/LDClientImpl.ts index 6dff538e9e..2d571a4c35 100644 --- a/server-sdk-common/src/LDClientImpl.ts +++ b/server-sdk-common/src/LDClientImpl.ts @@ -145,8 +145,9 @@ export default class LDClientImpl implements LDClient { async getSegment(key: string): Promise { return (await asyncFacade.get(VersionedDataKinds.Segments, key) as Segment) ?? undefined; }, - getBigSegmentsMembership(userKey: string): Promise { - throw new Error('Function not implemented.'); + getBigSegmentsMembership(userKey: string): + Promise<[BigSegmentStoreMembership | null, string] | undefined> { + return manager.getUserMembership(userKey); }, }; this.evaluator = new Evaluator(this.platform, queries); diff --git a/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts b/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts index ca0245ac50..bef86a39f4 100644 --- a/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts +++ b/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts @@ -1,4 +1,3 @@ -import { LDLogger } from '@launchdarkly/js-sdk-common'; import { BigSegmentStore } from '../interfaces'; import { LDClientContext } from './LDClientContext'; diff --git a/server-sdk-common/src/data_sources/FileDataSourceFactory.ts b/server-sdk-common/src/data_sources/FileDataSourceFactory.ts index e0e35e9983..0cf754e640 100644 --- a/server-sdk-common/src/data_sources/FileDataSourceFactory.ts +++ b/server-sdk-common/src/data_sources/FileDataSourceFactory.ts @@ -2,7 +2,6 @@ import { LDLogger } from '@launchdarkly/js-sdk-common'; import { FileDataSourceOptions } from '../api/integrations'; import { LDClientContext } from '../api/options/LDClientContext'; import { LDFeatureStore } from '../api/subsystems'; -import { Filesystem } from '../platform'; import FileDataSource from './FileDataSource'; /** diff --git a/server-sdk-common/src/evaluation/Evaluator.ts b/server-sdk-common/src/evaluation/Evaluator.ts index 68f984aed2..88249b2d19 100644 --- a/server-sdk-common/src/evaluation/Evaluator.ts +++ b/server-sdk-common/src/evaluation/Evaluator.ts @@ -21,10 +21,46 @@ import { SegmentRule } from './data/SegmentRule'; import { Clause } from './data/Clause'; import EventFactory from '../events/EventFactory'; import InputEvalEvent from '../events/InputEvalEvent'; +import { BigSegmentStoreMembership } from '../api/interfaces'; +import makeBigSegmentRef from './makeBigSegmentRef'; + +type BigSegmentStoreStatusString = 'HEALTHY' | 'STALE' | 'STORE_ERROR' | 'NOT_CONFIGURED'; + +const bigSegmentsStatusPriority: Record = { + HEALTHY: 1, + STALE: 2, + STORE_ERROR: 3, + NOT_CONFIGURED: 4, +}; + +function getBigSegmentsStatusPriority(status?: BigSegmentStoreStatusString) { + if (status !== undefined) { + return bigSegmentsStatusPriority[status] || 0; + } + return 0; +} + +/** + * Given two big segment statuses return the one with the higher priority. + * @returns The status with the higher priority. + */ +function computeUpdatedBigSegmentsStatus( + old?: BigSegmentStoreStatusString, + latest?: BigSegmentStoreStatusString, +): BigSegmentStoreStatusString | undefined { + if (old !== undefined + && getBigSegmentsStatusPriority(old) > getBigSegmentsStatusPriority(latest)) { + return old; + } + return latest; +} class EvalState { events?: InputEvalEvent[]; - // bigSegmentsStatus + + bigSegmentsStatus?: BigSegmentStoreStatusString; + + bigSegmentsMembership?: Record; } class Match { @@ -66,6 +102,9 @@ export default class Evaluator { async evaluate(flag: Flag, context: Context, eventFactory?: EventFactory): Promise { const state = new EvalState(); const res = await this.evaluateInternal(flag, context, state, [], eventFactory); + if (state.bigSegmentsStatus) { + res.detail.reason.bigSegmentsStatus = state.bigSegmentsStatus; + } res.events = state.events; return res; } @@ -287,8 +326,6 @@ export default class Evaluator { rule: FlagRule, ruleIndex: number, context: Context, - // TODO: Will be used once big segments are implemented. - // eslint-disable-next-line @typescript-eslint/no-unused-vars state: EvalState, segmentsVisited: string[], ): Promise { @@ -460,7 +497,80 @@ export default class Evaluator { return this.simpleSegmentMatchContext(segment, context, state, segmentsVisited); } - // TODO: Big segments. - return new Match(false); + if (!segment.generation) { + // Big Segment queries can only be done if the generation is known. If it's unset, + // that probably means the data store was populated by an older SDK that doesn't know + // about the generation property and therefore dropped it from the JSON data. We'll treat + // that as a "not configured" condition. + // eslint-disable-next-line no-param-reassign + state.bigSegmentsStatus = computeUpdatedBigSegmentsStatus( + state.bigSegmentsStatus, + 'NOT_CONFIGURED', + ); + return new Match(false); + } + + const bigSegmentKind = segment.unboundedContextKind || 'user'; + const keyForBigSegment = context.key(bigSegmentKind); + + if (keyForBigSegment === undefined) { + return new Match(false); + } + + if (state.bigSegmentsMembership && state.bigSegmentsMembership[keyForBigSegment]) { + // We've already done the query at some point during the flag evaluation and stored + // the result (if any) in stateOut.bigSegmentsMembership, so we don't need to do it + // again. Even if multiple Big Segments are being referenced, the membership includes + // *all* of the user's segment memberships. + + return this.bigSegmentMatchContext( + state.bigSegmentsMembership[keyForBigSegment], + segment, + context, + state, + ); + } + + const result = await this.queries.getBigSegmentsMembership(keyForBigSegment); + + // eslint-disable-next-line no-param-reassign + state.bigSegmentsMembership = state.bigSegmentsMembership || {}; + if (result) { + const [membership, status] = result; + // eslint-disable-next-line no-param-reassign + state.bigSegmentsMembership[keyForBigSegment] = membership; + // eslint-disable-next-line no-param-reassign + state.bigSegmentsStatus = computeUpdatedBigSegmentsStatus( + state.bigSegmentsStatus, + status as BigSegmentStoreStatusString, + ); + } else { + // eslint-disable-next-line no-param-reassign + state.bigSegmentsStatus = computeUpdatedBigSegmentsStatus( + state.bigSegmentsStatus, + 'NOT_CONFIGURED', + ); + } + /* eslint-enable no-param-reassign */ + return this.bigSegmentMatchContext( + state.bigSegmentsMembership[keyForBigSegment], + segment, + context, + state, + ); + } + + async bigSegmentMatchContext( + membership: BigSegmentStoreMembership | null, + segment: Segment, + context: Context, + state: EvalState, + ): Promise { + const segmentRef = makeBigSegmentRef(segment); + const included = membership?.[segmentRef]; + if (included) { + return new Match(true); + } + return this.simpleSegmentMatchContext(segment, context, state, []); } } diff --git a/server-sdk-common/src/evaluation/Queries.ts b/server-sdk-common/src/evaluation/Queries.ts index 454eab1689..2e2a09e574 100644 --- a/server-sdk-common/src/evaluation/Queries.ts +++ b/server-sdk-common/src/evaluation/Queries.ts @@ -11,5 +11,6 @@ import { Segment } from './data/Segment'; export interface Queries { getFlag(key: string): Promise getSegment(key: string): Promise - getBigSegmentsMembership(userKey: string): Promise + getBigSegmentsMembership(userKey: string): + Promise<[BigSegmentStoreMembership | null, string] | undefined> } diff --git a/server-sdk-common/src/evaluation/makeBigSegmentRef.ts b/server-sdk-common/src/evaluation/makeBigSegmentRef.ts new file mode 100644 index 0000000000..ac4d7624fc --- /dev/null +++ b/server-sdk-common/src/evaluation/makeBigSegmentRef.ts @@ -0,0 +1,11 @@ +import { Segment } from './data/Segment'; + +/** + * @internal + */ +export default function makeBigSegmentRef(segment: Segment): string { + // The format of Big Segment references is independent of what store implementation is being + // used; the store implementation receives only this string and does not know the details of + // the data model. The Relay Proxy will use the same format when writing to the store. + return `${segment.key}.g${segment.generation}`; +} diff --git a/server-sdk-common/src/store/InMemoryFeatureStore.ts b/server-sdk-common/src/store/InMemoryFeatureStore.ts index 61fc75dea2..6c32f921cb 100644 --- a/server-sdk-common/src/store/InMemoryFeatureStore.ts +++ b/server-sdk-common/src/store/InMemoryFeatureStore.ts @@ -7,16 +7,6 @@ import { LDFeatureStoreItem, } from '../api/subsystems'; -/** - * Clone an object using JSON. This will not preserve - * non-JSON types (like functions). - * @param obj - * @returns A clone of the object. - */ -function clone(obj: any): any { - return JSON.parse(JSON.stringify(obj)); -} - export default class InMemoryFeatureStore implements LDFeatureStore { private allData: LDFeatureStoreDataStorage = {}; From 2164dc33a2b6f9150a108421b17a8dd62c120de5 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 16 Aug 2022 15:06:49 -0700 Subject: [PATCH 18/34] Ensure we close the big segments manager. Updated tests to prevent open handles. --- .../__tests__/LDClient.allFlags.test.ts | 8 ++++++++ .../__tests__/LDClient.evaluation.test.ts | 16 ++++++++++++++++ .../__tests__/LDClient.events.test.ts | 7 ++++++- .../__tests__/LDClientImpl.bigSegments.test.ts | 17 +++++++++++++++++ server-sdk-common/src/LDClientImpl.ts | 4 ++++ 5 files changed, 51 insertions(+), 1 deletion(-) diff --git a/server-sdk-common/__tests__/LDClient.allFlags.test.ts b/server-sdk-common/__tests__/LDClient.allFlags.test.ts index 63ccb0314f..4b7e10c5c5 100644 --- a/server-sdk-common/__tests__/LDClient.allFlags.test.ts +++ b/server-sdk-common/__tests__/LDClient.allFlags.test.ts @@ -33,6 +33,10 @@ describe('given an LDClient with test data', () => { await client.waitForInitialization(); }); + afterEach(() => { + client.close(); + }); + it('captures flag state', async () => { const value1 = 'value1'; const value2 = 'value2'; const value3 = 'value3'; @@ -264,6 +268,10 @@ describe('given an offline client', () => { ); }); + afterEach(() => { + client.close(); + }); + it('returns empty state in offline mode and logs a message', async () => { const flag = { key: 'flagkey', diff --git a/server-sdk-common/__tests__/LDClient.evaluation.test.ts b/server-sdk-common/__tests__/LDClient.evaluation.test.ts index b81a5cb51b..911657b3f6 100644 --- a/server-sdk-common/__tests__/LDClient.evaluation.test.ts +++ b/server-sdk-common/__tests__/LDClient.evaluation.test.ts @@ -35,6 +35,10 @@ describe('given an LDClient with test data', () => { await client.waitForInitialization(); }); + afterEach(() => { + client.close(); + }); + it('evaluates an existing flag', async () => { td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1)); expect(await client.variation('flagkey', defaultUser, 'c')).toBe('b'); @@ -137,6 +141,10 @@ describe('given an offline client', () => { ); }); + afterEach(() => { + client.close(); + }); + it('returns the default value for variation', async () => { await client.waitForInitialization(); td.update(td.flag('flagkey').variations('value').variationForAll(0)); @@ -191,6 +199,10 @@ describe('given a client and store that are uninitialized', () => { ); }); + afterEach(() => { + client.close(); + }); + it('returns the default value for variation', async () => { expect(await client.variation('flagkey', defaultUser, 'default')).toEqual('default'); }); @@ -243,6 +255,10 @@ describe('given a client that is un-initialized and store that is initialized', ); }); + afterEach(() => { + client.close(); + }); + it('returns the value for variation', async () => { expect(await client.variation('flagkey', defaultUser, 'default')).toEqual('value'); }); diff --git a/server-sdk-common/__tests__/LDClient.events.test.ts b/server-sdk-common/__tests__/LDClient.events.test.ts index 2326c731b0..372d2f3465 100644 --- a/server-sdk-common/__tests__/LDClient.events.test.ts +++ b/server-sdk-common/__tests__/LDClient.events.test.ts @@ -25,7 +25,7 @@ describe('given a client with mock event processor', () => { events = []; jest.spyOn(EventProcessor.prototype, 'sendEvent').mockImplementation((evt) => events.push(evt)); jest.spyOn(EventProcessor.prototype, 'flush').mockImplementation(() => Promise.resolve()); - jest.spyOn(EventProcessor.prototype, 'close').mockImplementation(() => {}); + // jest.spyOn(EventProcessor.prototype, 'close').mockImplementation(() => {}); td = new TestData(); logger = new TestLogger(); client = new LDClientImpl( @@ -44,6 +44,10 @@ describe('given a client with mock event processor', () => { await client.waitForInitialization(); }); + afterEach(() => { + client.close(); + }); + it('generates event for existing feature', async () => { td.update(td.flag('flagkey').on(true).variations('a', 'b').fallthroughVariation(1)); @@ -315,3 +319,4 @@ describe('given a client with mock event processor', () => { }); }); }); + diff --git a/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts b/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts index 0474fe3086..45b9040277 100644 --- a/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts +++ b/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts @@ -90,6 +90,10 @@ describe('given test data with big segments', () => { await client.waitForInitialization(); }); + afterEach(() => { + client.close(); + }); + it('user not found in big segment store', async () => { const result = await client.variationDetail(flag.key, user, false); expect(result.value).toBe(false); @@ -129,6 +133,11 @@ describe('given test data with big segments', () => { await client.waitForInitialization(); }); + afterEach(() => { + client.close(); + }); + + it('user found in big segment store', async () => { const result = await client.variationDetail(flag.key, user, false); expect(result.value).toBe(true); @@ -167,6 +176,10 @@ describe('given test data with big segments', () => { await client.waitForInitialization(); }); + afterEach(() => { + client.close(); + }); + it('produces a store error', async () => { const result = await client.variationDetail(flag.key, user, false); expect(result.value).toBe(false); @@ -194,6 +207,10 @@ describe('given test data with big segments', () => { await client.waitForInitialization(); }); + afterEach(() => { + client.close(); + }); + it('produces a not configured error', async () => { const result = await client.variationDetail(flag.key, user, false); expect(result.value).toBe(false); diff --git a/server-sdk-common/src/LDClientImpl.ts b/server-sdk-common/src/LDClientImpl.ts index 2d571a4c35..d854b7b80e 100644 --- a/server-sdk-common/src/LDClientImpl.ts +++ b/server-sdk-common/src/LDClientImpl.ts @@ -64,6 +64,8 @@ export default class LDClientImpl implements LDClient { private config: Configuration; + private bigSegmentsManager: BigSegmentsManager; + /** * Intended for use by platform specific client implementations. * @@ -136,6 +138,7 @@ export default class LDClientImpl implements LDClient { config.logger, this.platform.crypto, ); + this.bigSegmentsManager = manager; this.bigSegmentStatusProviderInternal = manager.statusProvider as BigSegmentStoreStatusProvider; const queries: Queries = { @@ -314,6 +317,7 @@ export default class LDClientImpl implements LDClient { this.eventProcessor.close(); this.updateProcessor.close(); this.featureStore.close(); + this.bigSegmentsManager.close(); } isOffline(): boolean { From 57c6a9fd80adcabb50f033c86b83a732ce7d79ed Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 16 Aug 2022 15:07:10 -0700 Subject: [PATCH 19/34] Remove commented jest spy. --- server-sdk-common/__tests__/LDClient.events.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server-sdk-common/__tests__/LDClient.events.test.ts b/server-sdk-common/__tests__/LDClient.events.test.ts index 372d2f3465..92413984f8 100644 --- a/server-sdk-common/__tests__/LDClient.events.test.ts +++ b/server-sdk-common/__tests__/LDClient.events.test.ts @@ -25,7 +25,7 @@ describe('given a client with mock event processor', () => { events = []; jest.spyOn(EventProcessor.prototype, 'sendEvent').mockImplementation((evt) => events.push(evt)); jest.spyOn(EventProcessor.prototype, 'flush').mockImplementation(() => Promise.resolve()); - // jest.spyOn(EventProcessor.prototype, 'close').mockImplementation(() => {}); + td = new TestData(); logger = new TestLogger(); client = new LDClientImpl( From c08491b4a02606118537c9a268aaf7e31e813d35 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 16 Aug 2022 15:13:29 -0700 Subject: [PATCH 20/34] Linting cleanup. --- .../__tests__/LDClient.allFlags.test.ts | 13 ++++--- .../__tests__/LDClient.evaluation.test.ts | 25 +++++++------ .../__tests__/LDClient.events.test.ts | 15 ++------ .../LDClientImpl.bigSegments.test.ts | 35 +++++++++---------- .../__tests__/LDClientImpl.listeners.test.ts | 9 +++-- 5 files changed, 42 insertions(+), 55 deletions(-) diff --git a/server-sdk-common/__tests__/LDClient.allFlags.test.ts b/server-sdk-common/__tests__/LDClient.allFlags.test.ts index 4b7e10c5c5..d47ca60dfd 100644 --- a/server-sdk-common/__tests__/LDClient.allFlags.test.ts +++ b/server-sdk-common/__tests__/LDClient.allFlags.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { LDClientImpl } from '../src'; import TestData from '../src/integrations/test_data/TestData'; import basicPlatform from './evaluation/mocks/platform'; @@ -22,10 +21,10 @@ describe('given an LDClient with test data', () => { sendEvents: false, logger, }, - (_err) => { }, - (_err) => { }, () => { }, - (_key) => { }, + () => { }, + () => { }, + () => { }, // Always listen to events. () => true, ); @@ -259,10 +258,10 @@ describe('given an offline client', () => { sendEvents: false, logger, }, - (_err) => { }, - (_err) => { }, () => { }, - (key) => { }, + () => { }, + () => { }, + () => { }, // Always listen to events. () => true, ); diff --git a/server-sdk-common/__tests__/LDClient.evaluation.test.ts b/server-sdk-common/__tests__/LDClient.evaluation.test.ts index 911657b3f6..743004ee0f 100644 --- a/server-sdk-common/__tests__/LDClient.evaluation.test.ts +++ b/server-sdk-common/__tests__/LDClient.evaluation.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { LDClientImpl } from '../src'; import { LDFeatureStore } from '../src/api/subsystems'; import NullUpdateProcessor from '../src/data_sources/NullUpdateProcessor'; @@ -24,10 +23,10 @@ describe('given an LDClient with test data', () => { updateProcessor: td.getFactory(), sendEvents: false, }, - (_err) => { }, - (_err) => { }, () => { }, - (key) => { }, + () => { }, + () => { }, + () => { }, // Always listen to events. () => true, ); @@ -132,10 +131,10 @@ describe('given an offline client', () => { sendEvents: false, logger, }, - (_err) => { }, - (_err) => { }, () => { }, - (key) => { }, + () => { }, + () => { }, + () => { }, // Always listen to events. () => true, ); @@ -190,10 +189,10 @@ describe('given a client and store that are uninitialized', () => { sendEvents: false, featureStore: store, }, - (_err) => { }, - (_err) => { }, () => { }, - (key) => { }, + () => { }, + () => { }, + () => { }, // Always listen to events. () => true, ); @@ -246,10 +245,10 @@ describe('given a client that is un-initialized and store that is initialized', sendEvents: false, featureStore: store, }, - (_err) => { }, - (_err) => { }, () => { }, - (key) => { }, + () => { }, + () => { }, + () => { }, // Always listen to events. () => true, ); diff --git a/server-sdk-common/__tests__/LDClient.events.test.ts b/server-sdk-common/__tests__/LDClient.events.test.ts index 92413984f8..b3c15dd04a 100644 --- a/server-sdk-common/__tests__/LDClient.events.test.ts +++ b/server-sdk-common/__tests__/LDClient.events.test.ts @@ -1,23 +1,15 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { Context, LDClientImpl, LDContext } from '../src'; -import NullUpdateProcessor from '../src/data_sources/NullUpdateProcessor'; +import { Context } from '@launchdarkly/js-sdk-common'; +import { LDClientImpl } from '../src'; import EventProcessor from '../src/events/EventProcessor'; import InputEvent from '../src/events/InputEvent'; -import LDEventProcessor from '../src/events/LDEventProcessor'; import TestData from '../src/integrations/test_data/TestData'; -import InMemoryFeatureStore from '../src/store/InMemoryFeatureStore'; import basicPlatform from './evaluation/mocks/platform'; -import TestLogger from './Logger'; const defaultUser = { key: 'user' }; const anonymousUser = { key: 'anon-user', anonymous: true }; -const userWithNoKey = { name: 'Keyless Joe' }; -const userWithEmptyKey = { key: '' }; describe('given a client with mock event processor', () => { - let logger: TestLogger; let client: LDClientImpl; - let store: InMemoryFeatureStore; let events: InputEvent[]; let td: TestData; @@ -27,13 +19,11 @@ describe('given a client with mock event processor', () => { jest.spyOn(EventProcessor.prototype, 'flush').mockImplementation(() => Promise.resolve()); td = new TestData(); - logger = new TestLogger(); client = new LDClientImpl( 'sdk-key', basicPlatform, { updateProcessor: td.getFactory(), - // featureStore: store, }, () => { }, () => { }, @@ -319,4 +309,3 @@ describe('given a client with mock event processor', () => { }); }); }); - diff --git a/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts b/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts index 45b9040277..cbe6f75b73 100644 --- a/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts +++ b/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts @@ -1,7 +1,5 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { LDBigSegmentsOptions, LDClientImpl } from '../src'; import { BigSegmentStore } from '../src/api/interfaces'; -import { LDClientContext } from '../src/api/options/LDClientContext'; import makeBigSegmentRef from '../src/evaluation/makeBigSegmentRef'; import TestData from '../src/integrations/test_data/TestData'; import { Hasher, Crypto, Hmac } from '../src/platform'; @@ -62,7 +60,7 @@ describe('given test data with big segments', () => { describe('given a big segment store without the user', () => { beforeEach(async () => { const bigSegmentsConfig: LDBigSegmentsOptions = { - store(clientContext: LDClientContext): BigSegmentStore { + store(): BigSegmentStore { return { getMetadata: async () => ({ lastUpToDate: new Date().getTime() }), getUserMembership: async () => undefined, @@ -79,10 +77,10 @@ describe('given test data with big segments', () => { sendEvents: false, bigSegments: bigSegmentsConfig, }, - (_err) => { }, - (_err) => { }, () => { }, - (key) => { }, + () => { }, + () => { }, + () => { }, // Always listen to events. () => true, ); @@ -105,7 +103,7 @@ describe('given test data with big segments', () => { beforeEach(async () => { const membership = { [makeBigSegmentRef(bigSegment)]: true }; const bigSegmentsConfig: LDBigSegmentsOptions = { - store(clientContext: LDClientContext): BigSegmentStore { + store(): BigSegmentStore { return { getMetadata: async () => ({ lastUpToDate: new Date().getTime() }), getUserMembership: async (hash) => (hash === `is_hashed:${user.key}` ? membership : undefined), @@ -122,10 +120,10 @@ describe('given test data with big segments', () => { sendEvents: false, bigSegments: bigSegmentsConfig, }, - (_err) => { }, - (_err) => { }, () => { }, - (key) => { }, + () => { }, + () => { }, + () => { }, // Always listen to events. () => true, ); @@ -137,7 +135,6 @@ describe('given test data with big segments', () => { client.close(); }); - it('user found in big segment store', async () => { const result = await client.variationDetail(flag.key, user, false); expect(result.value).toBe(true); @@ -148,10 +145,10 @@ describe('given test data with big segments', () => { describe('given a big segment store which experiences an error', () => { beforeEach(async () => { const bigSegmentsConfig: LDBigSegmentsOptions = { - store(clientContext: LDClientContext): BigSegmentStore { + store(): BigSegmentStore { return { getMetadata: async () => ({ lastUpToDate: new Date().getTime() }), - getUserMembership: async (hash) => { throw new Error('sorry'); }, + getUserMembership: async () => { throw new Error('sorry'); }, close: () => { }, }; }, @@ -165,10 +162,10 @@ describe('given test data with big segments', () => { sendEvents: false, bigSegments: bigSegmentsConfig, }, - (_err) => { }, - (_err) => { }, () => { }, - (key) => { }, + () => { }, + () => { }, + () => { }, // Always listen to events. () => true, ); @@ -196,10 +193,10 @@ describe('given test data with big segments', () => { updateProcessor: td.getFactory(), sendEvents: false, }, - (_err) => { }, - (_err) => { }, () => { }, - (key) => { }, + () => { }, + () => { }, + () => { }, // Always listen to events. () => true, ); diff --git a/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts index f0a9f1b593..d2d3eb3991 100644 --- a/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts +++ b/server-sdk-common/__tests__/LDClientImpl.listeners.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { LDClientImpl } from '../src'; import TestData from '../src/integrations/test_data/TestData'; import AsyncQueue from './AsyncQueue'; @@ -21,8 +20,8 @@ describe('given an LDClient with test data', () => { sendEvents: false, logger: new TestLogger(), }, - (_err) => { }, - (_err) => { }, + () => { }, + () => { }, () => { }, (key) => { queue.push(key); @@ -32,6 +31,10 @@ describe('given an LDClient with test data', () => { ); }); + afterEach(() => { + client.close(); + }); + it('sends an event when a flag is added', async () => { td.update(td.flag('new-flag')); expect(await queue.take()).toEqual('new-flag'); From 76ec9a3609a4c3f35d180c539fb4a7881ab940c8 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 16 Aug 2022 16:44:50 -0700 Subject: [PATCH 21/34] File data source tests that use the node filesystem implementation. --- .gitignore | 1 + .../LDClientNode.fileDataSource.test.ts | 164 ++++++++++++++++++ .../__tests__/LDClientNode.listeners.test.ts | 93 ++++++++++ platform-node/src/LDClientNode.ts | 2 +- platform-node/src/platform/NodeFilesystem.ts | 4 +- .../data_sources/FileDataSource.test.ts | 22 +-- server-sdk-common/src/index.ts | 1 + .../FileDataSourceFactory.ts | 13 +- server-sdk-common/src/integrations/index.ts | 6 + .../src/integrations/test_data/index.ts | 9 + 10 files changed, 289 insertions(+), 26 deletions(-) create mode 100644 platform-node/__tests__/LDClientNode.fileDataSource.test.ts create mode 100644 platform-node/__tests__/LDClientNode.listeners.test.ts rename server-sdk-common/src/{data_sources => integrations}/FileDataSourceFactory.ts (78%) create mode 100644 server-sdk-common/src/integrations/index.ts create mode 100644 server-sdk-common/src/integrations/test_data/index.ts diff --git a/.gitignore b/.gitignore index c74d626fdd..bc7f844a24 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist/ node_modules/ **/*.tsbuildinfo coverage/ +tmp/ diff --git a/platform-node/__tests__/LDClientNode.fileDataSource.test.ts b/platform-node/__tests__/LDClientNode.fileDataSource.test.ts new file mode 100644 index 0000000000..7418ddfb32 --- /dev/null +++ b/platform-node/__tests__/LDClientNode.fileDataSource.test.ts @@ -0,0 +1,164 @@ +import { integrations } from '@launchdarkly/js-server-sdk-common'; +import * as fs from 'node:fs'; +import LDClientNode from '../src/LDClientNode'; + +const flag1Key = 'flag1'; +const flag2Key = 'flag2'; +const flag2Value = 'value2'; +const segment1Key = 'seg1'; + +const flag1 = { + key: flag1Key, + on: true, + rules: [ + { clauses: [{ op: 'segmentMatch', values: [segment1Key] }], variation: 1 }, + ], + fallthrough: { + variation: 2, + }, + variations: ['fall', 'off', 'on'], +}; + +const segment1 = { + key: segment1Key, + included: ['user1'], +}; + +const flagOnlyJson = ` +{ + "flags": { + "${flag1Key}": ${JSON.stringify(flag1)} + } +}`; + +const segmentOnlyJson = ` +{ + "segments": { + "${segment1Key}": ${JSON.stringify(segment1)} + } +}`; + +const allPropertiesJson = ` +{ + "flags": { + "${flag1Key}": ${JSON.stringify(flag1)} + }, + "flagValues": { + "${flag2Key}": "${flag2Value}" + }, + "segments": { + "${segment1Key}": ${JSON.stringify(segment1)} + } +}`; + +const tmpFiles: string[] = []; + +function makeTempFile(content: string): string { + const fileName = (Math.random() + 1).toString(36).substring(7); + if (!fs.existsSync('./tmp')) { + fs.mkdirSync('./tmp'); + } + const fullPath = `./tmp/${fileName}`; + fs.writeFileSync(fullPath, content); + tmpFiles.push(fullPath); + return fullPath; +} + +function replaceFileContent(filePath: string, content: string) { + fs.writeFileSync(filePath, content); +} + +describe('When using a file data source', () => { + afterAll(() => { + tmpFiles.forEach((filePath) => { + fs.unlinkSync(filePath); + }); + }); + + it('loads flags on start from JSON', async () => { + const path = makeTempFile(allPropertiesJson); + const fds = new integrations.FileDataSourceFactory({ + paths: [path], + }); + + const client = new LDClientNode('sdk-key', { + updateProcessor: fds.getFactory(), + sendEvents: false, + }); + + await client.waitForInitialization(); + + const f1Var = await client.variation(flag1Key, { key: 'user1' }, 'default'); + expect(f1Var).toEqual('off'); + const f1VarNoSeg = await client.variation(flag1Key, { key: 'user2' }, 'default'); + expect(f1VarNoSeg).toEqual('on'); + const f2Var = await client.variation(flag2Key, { key: 'user1' }, 'default'); + expect(f2Var).toEqual('value2'); + + client.close(); + }); + + it('it can load multiple files', async () => { + const path1 = makeTempFile(flagOnlyJson); + const path2 = makeTempFile(segmentOnlyJson); + const fds = new integrations.FileDataSourceFactory({ + paths: [path1, path2], + }); + + const client = new LDClientNode('sdk-key', { + updateProcessor: fds.getFactory(), + sendEvents: false, + }); + + await client.waitForInitialization(); + + const f1Var = await client.variation(flag1Key, { key: 'user1' }, 'default'); + expect(f1Var).toEqual('off'); + const f1VarNoSeg = await client.variation(flag1Key, { key: 'user2' }, 'default'); + expect(f1VarNoSeg).toEqual('on'); + + client.close(); + }); + + it('reloads the file if the content changes', async () => { + const path = makeTempFile(allPropertiesJson); + const fds = new integrations.FileDataSourceFactory({ + paths: [path], + autoUpdate: true, + }); + + const client = new LDClientNode('sdk-key', { + updateProcessor: fds.getFactory(), + sendEvents: false, + }); + + await client.waitForInitialization(); + + const f1Var = await client.variation(flag1Key, { key: 'user1' }, 'default'); + expect(f1Var).toEqual('off'); + const f1VarNoSeg = await client.variation(flag1Key, { key: 'user2' }, 'default'); + expect(f1VarNoSeg).toEqual('on'); + const f2Var = await client.variation(flag2Key, { key: 'user1' }, 'default'); + expect(f2Var).toEqual('value2'); + + replaceFileContent(path, flagOnlyJson); + + await new Promise((resolve) => { + client.once('update', () => { + // After the file reloads we get changes, so we know we can move onto + // evaluation. + resolve(); + }); + replaceFileContent(path, flagOnlyJson); + }); + + const f1VarB = await client.variation(flag1Key, { key: 'user1' }, 'default'); + expect(f1VarB).toEqual('on'); // Segment doesn't exist anymore. + const f1VarNoSegB = await client.variation(flag1Key, { key: 'user2' }, 'default'); + expect(f1VarNoSegB).toEqual('on'); + const f2VarB = await client.variation(flag2Key, { key: 'user1' }, 'default'); + expect(f2VarB).toEqual('default'); + + client.close(); + }); +}); diff --git a/platform-node/__tests__/LDClientNode.listeners.test.ts b/platform-node/__tests__/LDClientNode.listeners.test.ts new file mode 100644 index 0000000000..4707ff6155 --- /dev/null +++ b/platform-node/__tests__/LDClientNode.listeners.test.ts @@ -0,0 +1,93 @@ +import { integrations } from '@launchdarkly/js-server-sdk-common'; +import { LDClient } from '../src'; +import LDClientNode from '../src/LDClientNode'; + +describe('given an LDClient with test data', () => { + let client: LDClient; + let td: integrations.TestData; + + beforeEach(() => { + td = new integrations.TestData(); + client = new LDClientNode( + 'sdk-key', + { + updateProcessor: td.getFactory(), + sendEvents: false, + }, + ); + }); + + afterEach(() => { + client.close(); + }); + + it('sends an "update" event when a flag is added', (done) => { + client.on('update', (params) => { + expect(params.key).toEqual('new-flag'); + done(); + }); + td.update(td.flag('new-flag')); + }); + + it('sends an "update:new-flag" event when a flag is added', (done) => { + client.on('update:new-flag', (params) => { + expect(params.key).toEqual('new-flag'); + done(); + }); + td.update(td.flag('new-flag')); + }); + + it('sends an "update" when a flag is updated', (done) => { + const expectedUpdates = [ + 'flag1', + 'flag2', + 'flag1', + 'flag2', + ]; + + client.on('update', (params) => { + expect(expectedUpdates.includes(params.key)).toBeTruthy(); + expectedUpdates.splice(expectedUpdates.indexOf(params.key), 1); + if (expectedUpdates.length === 0) { + done(); + } + }); + + td.update(td.flag('flag1').on(true)); + td.update(td.flag('flag2').on(true)); + + td.update(td.flag('flag1').on(false)); + td.update(td.flag('flag2').on(false)); + }); + + it('sends an "update:" when a flag is updated', (done) => { + const expectedUpdates = [ + 'flag1', + 'flag2', + 'flag1', + 'flag2', + ]; + + client.on('update:flag1', (params) => { + expect(expectedUpdates.includes(params.key)).toBeTruthy(); + expectedUpdates.splice(expectedUpdates.indexOf(params.key), 1); + if (expectedUpdates.length === 0) { + done(); + } + }); + + client.on('update:flag2', (params) => { + expect(expectedUpdates.includes(params.key)).toBeTruthy(); + expectedUpdates.splice(expectedUpdates.indexOf(params.key), 1); + if (expectedUpdates.length === 0) { + done(); + } + }); + + td.update(td.flag('flag1').on(true)); + td.update(td.flag('flag2').on(true)); + + td.update(td.flag('flag1').on(false)); + td.update(td.flag('flag2').on(false)); + }); +}); diff --git a/platform-node/src/LDClientNode.ts b/platform-node/src/LDClientNode.ts index b1996415e4..48f14b8e16 100644 --- a/platform-node/src/LDClientNode.ts +++ b/platform-node/src/LDClientNode.ts @@ -46,7 +46,7 @@ class LDClientNode extends LDClientImpl { }, (key: string) => { emitter.emit('update', { key }); - emitter.emit(`update:${key}`); + emitter.emit(`update:${key}`, { key }); }, () => emitter.eventNames().some((name) => name === 'update' || (typeof name === 'string' && name.startsWith('update:'))), ); diff --git a/platform-node/src/platform/NodeFilesystem.ts b/platform-node/src/platform/NodeFilesystem.ts index e01d0de203..f61293a7da 100644 --- a/platform-node/src/platform/NodeFilesystem.ts +++ b/platform-node/src/platform/NodeFilesystem.ts @@ -18,8 +18,8 @@ export default class NodeFilesystem implements platform.Filesystem { path: string, callback: (eventType: string, filename: string) => void, ): platform.WatchHandle { - return fs.watch(path, { persistent: false }, (eventType, filename) => { - callback(eventType, filename); + return fs.watch(path, { persistent: false }, (eventType) => { + callback(eventType, path); }); } } diff --git a/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts b/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts index 2fecaed1f1..d1fc735907 100644 --- a/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts +++ b/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts @@ -1,10 +1,10 @@ import { Context } from '@launchdarkly/js-sdk-common'; import promisify from '../../src/async/promisify'; import ClientContext from '../../src/ClientContext'; -import FileDataSourceFactory from '../../src/data_sources/FileDataSourceFactory'; import { Flag } from '../../src/evaluation/data/Flag'; import { Segment } from '../../src/evaluation/data/Segment'; import Evaluator from '../../src/evaluation/Evaluator'; +import { FileDataSourceFactory } from '../../src/integrations'; import Configuration from '../../src/options/Configuration'; import { Filesystem, WatchHandle } from '../../src/platform'; import AsyncStoreFacade from '../../src/store/AsyncStoreFacade'; @@ -59,26 +59,6 @@ const allPropertiesJson = ` } }`; -// const allPropertiesYaml = ` -// flags: -// ${flag1Key}: -// key: ${flag1Key} -// on: true -// fallthrough: -// variation: 2 -// variations: -// - fall -// - off -// - on -// flagValues: -// ${flag2Key}: "${flag2Value}" -// segments: -// ${segment1Key}: -// key: ${segment1Key} -// include: -// - user1 -// `; - function sorted(a: any[]) { const a1 = Array.from(a); a1.sort(); diff --git a/server-sdk-common/src/index.ts b/server-sdk-common/src/index.ts index fa40083067..e90a9e4bf0 100644 --- a/server-sdk-common/src/index.ts +++ b/server-sdk-common/src/index.ts @@ -1,6 +1,7 @@ import LDClientImpl from './LDClientImpl'; import BigSegmentStoreStatusProviderImpl from './BigSegmentStatusProviderImpl'; +export * as integrations from './integrations'; export * as platform from './platform'; export * from './api'; export * from '@launchdarkly/js-sdk-common'; diff --git a/server-sdk-common/src/data_sources/FileDataSourceFactory.ts b/server-sdk-common/src/integrations/FileDataSourceFactory.ts similarity index 78% rename from server-sdk-common/src/data_sources/FileDataSourceFactory.ts rename to server-sdk-common/src/integrations/FileDataSourceFactory.ts index 0cf754e640..56c043810c 100644 --- a/server-sdk-common/src/data_sources/FileDataSourceFactory.ts +++ b/server-sdk-common/src/integrations/FileDataSourceFactory.ts @@ -1,8 +1,8 @@ import { LDLogger } from '@launchdarkly/js-sdk-common'; import { FileDataSourceOptions } from '../api/integrations'; import { LDClientContext } from '../api/options/LDClientContext'; -import { LDFeatureStore } from '../api/subsystems'; -import FileDataSource from './FileDataSource'; +import { LDFeatureStore, LDStreamProcessor } from '../api/subsystems'; +import FileDataSource from '../data_sources/FileDataSource'; /** * Components of the SDK runtime configuration which are required @@ -27,6 +27,8 @@ export default class FileDataSourceFactory { * @param config SDK configuration required by the file data source. * @param filesystem Platform abstraction used for filesystem access. * @returns a {@link FileDataSource} + * + * @internal */ create( ldClientContext: LDClientContext, @@ -40,4 +42,11 @@ export default class FileDataSourceFactory { }; return new FileDataSource(updatedOptions, ldClientContext.platform.fileSystem!, featureStore); } + + getFactory(): ( + ldClientContext: LDClientContext, + featureStore: LDFeatureStore, + ) => LDStreamProcessor { + return (ldClientContext, featureStore) => this.create(ldClientContext, featureStore); + } } diff --git a/server-sdk-common/src/integrations/index.ts b/server-sdk-common/src/integrations/index.ts new file mode 100644 index 0000000000..57ee25ce9f --- /dev/null +++ b/server-sdk-common/src/integrations/index.ts @@ -0,0 +1,6 @@ +import FileDataSourceFactory from './FileDataSourceFactory'; + +export * from './test_data'; +export { + FileDataSourceFactory, +}; diff --git a/server-sdk-common/src/integrations/test_data/index.ts b/server-sdk-common/src/integrations/test_data/index.ts new file mode 100644 index 0000000000..b5d2ee26cd --- /dev/null +++ b/server-sdk-common/src/integrations/test_data/index.ts @@ -0,0 +1,9 @@ +import TestData from './TestData'; +import TestDataFlagBuilder from './TestDataFlagBuilder'; +import TestDataRuleBuilder from './TestDataRuleBuilder'; + +export { + TestData, + TestDataFlagBuilder, + TestDataRuleBuilder +}; \ No newline at end of file From de3313ba559492f1850fcd951c84d7e50b440fee Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 11:27:31 -0700 Subject: [PATCH 22/34] Add test for node level big segments. Most things are covered by lower levels, but we need to check the event emitter. --- .../LDClientNode.bigSegments.test.ts | 149 ++++++++++++++++++ .../LDClientImpl.bigSegments.test.ts | 6 +- .../src/data_sources/StreamingProcessor.ts | 2 +- 3 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 platform-node/__tests__/LDClientNode.bigSegments.test.ts diff --git a/platform-node/__tests__/LDClientNode.bigSegments.test.ts b/platform-node/__tests__/LDClientNode.bigSegments.test.ts new file mode 100644 index 0000000000..4cd3fdfefe --- /dev/null +++ b/platform-node/__tests__/LDClientNode.bigSegments.test.ts @@ -0,0 +1,149 @@ +import { integrations, interfaces, LDBigSegmentsOptions } from '@launchdarkly/js-server-sdk-common'; +import { LDClientImpl } from '../src'; +import { LDClient } from '../src/api/LDClient'; + +describe('given test data with big segments', () => { + + // To use the public interfaces to create a client which doesn't use the + // network. (Versus being offline, or a null update processor.) + let td: integrations.TestData; + + beforeEach(() => { + td = new integrations.TestData(); + }); + + describe('given a healthy big segment store', () => { + let client: LDClient; + const bigSegmentsConfig: LDBigSegmentsOptions = { + statusPollInterval: 0.1, + store(): interfaces.BigSegmentStore { + return { + getMetadata: async () => ({ lastUpToDate: Date.now() }), + getUserMembership: async () => undefined, + close: () => { }, + }; + }, + }; + + beforeEach(() => { + client = new LDClientImpl('sdk-key', { + updateProcessor: td.getFactory(), + sendEvents: false, + bigSegments: bigSegmentsConfig, + }); + }); + + it('can get status', () => { + const status = client.bigSegmentStoreStatusProvider.getStatus(); + expect(status).toBeUndefined(); + }); + + it('can require status', async () => { + const status = await client.bigSegmentStoreStatusProvider.requireStatus(); + expect(status.available).toEqual(true); + expect(status.stale).toEqual(false); + }); + + it('Can listen to the event emitter for the status', (done) => { + client.bigSegmentStoreStatusProvider.on('change', (status: interfaces.BigSegmentStoreStatus) => { + expect(status.stale).toEqual(false); + expect(status.available).toEqual(true); + + const status2 = client.bigSegmentStoreStatusProvider.getStatus(); + expect(status2!.stale).toEqual(false); + expect(status2!.available).toEqual(true); + done(); + }); + }); + + afterEach(() => { + client.close(); + }); + }); + + describe('given a stale store', () => { + let client: LDClient; + const bigSegmentsConfig: LDBigSegmentsOptions = { + store(): interfaces.BigSegmentStore { + return { + getMetadata: async () => ({ lastUpToDate: 1000 }), + getUserMembership: async () => undefined, + close: () => { }, + }; + }, + }; + + beforeEach(async () => { + + client = new LDClientImpl('sdk-key', { + updateProcessor: td.getFactory(), + sendEvents: false, + bigSegments: bigSegmentsConfig, + }); + + await client.waitForInitialization(); + }); + + it('can require status', async () => { + const status = await client.bigSegmentStoreStatusProvider.requireStatus(); + expect(status.available).toEqual(true); + expect(status.stale).toEqual(true); + }); + + afterEach(() => { + client.close(); + }); + }); + + describe('given a store that can produce an error', () => { + let client: LDClient; + let error: boolean; + const bigSegmentsConfig: LDBigSegmentsOptions = { + statusPollInterval: 0.1, + store(): interfaces.BigSegmentStore { + return { + getMetadata: async () => { + if (error) { + throw new Error('sorry'); + } + return { lastUpToDate: Date.now() }; + }, + getUserMembership: async () => undefined, + close: () => { }, + }; + }, + }; + + beforeEach(async () => { + error = false; + client = new LDClientImpl('sdk-key', { + updateProcessor: td.getFactory(), + sendEvents: false, + bigSegments: bigSegmentsConfig, + }); + + await client.waitForInitialization(); + }); + + it('Can observe the status change', (done) => { + let message = 0; + client.bigSegmentStoreStatusProvider.on('change', (status: interfaces.BigSegmentStoreStatus) => { + if(message === 0) { + expect(status.stale).toEqual(false); + expect(status.available).toEqual(true); + error = true; + message += 1; + } else { + expect(status.stale).toEqual(false); + expect(status.available).toEqual(false); + done(); + } + + }); + }); + + afterEach(() => { + client.close(); + }); + }); +}); \ No newline at end of file diff --git a/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts b/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts index cbe6f75b73..a5bbf1bd72 100644 --- a/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts +++ b/server-sdk-common/__tests__/LDClientImpl.bigSegments.test.ts @@ -62,7 +62,7 @@ describe('given test data with big segments', () => { const bigSegmentsConfig: LDBigSegmentsOptions = { store(): BigSegmentStore { return { - getMetadata: async () => ({ lastUpToDate: new Date().getTime() }), + getMetadata: async () => ({ lastUpToDate: Date.now() }), getUserMembership: async () => undefined, close: () => { }, }; @@ -105,7 +105,7 @@ describe('given test data with big segments', () => { const bigSegmentsConfig: LDBigSegmentsOptions = { store(): BigSegmentStore { return { - getMetadata: async () => ({ lastUpToDate: new Date().getTime() }), + getMetadata: async () => ({ lastUpToDate: Date.now() }), getUserMembership: async (hash) => (hash === `is_hashed:${user.key}` ? membership : undefined), close: () => { }, }; @@ -147,7 +147,7 @@ describe('given test data with big segments', () => { const bigSegmentsConfig: LDBigSegmentsOptions = { store(): BigSegmentStore { return { - getMetadata: async () => ({ lastUpToDate: new Date().getTime() }), + getMetadata: async () => ({ lastUpToDate: Date.now() }), getUserMembership: async () => { throw new Error('sorry'); }, close: () => { }, }; diff --git a/server-sdk-common/src/data_sources/StreamingProcessor.ts b/server-sdk-common/src/data_sources/StreamingProcessor.ts index 67868591a1..38ccab14cf 100644 --- a/server-sdk-common/src/data_sources/StreamingProcessor.ts +++ b/server-sdk-common/src/data_sources/StreamingProcessor.ts @@ -50,7 +50,7 @@ export default class StreamingProcessor implements LDStreamProcessor { } private logConnectionStarted() { - this.connectionAttemptStartTime = new Date().getTime(); + this.connectionAttemptStartTime = Date.now(); } // TODO: Remove once the success is used for something. From 4d23a6d8a189455db06cbc121efe0fe20ca2279b Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 12:26:56 -0700 Subject: [PATCH 23/34] Fix TLS parsing. --- .../__tests__/LDClientNode.tls.test.ts | 36 +++++++++++++++++++ platform-node/package.json | 3 +- platform-node/src/platform/NodeRequests.ts | 21 +++++++++-- 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 platform-node/__tests__/LDClientNode.tls.test.ts diff --git a/platform-node/__tests__/LDClientNode.tls.test.ts b/platform-node/__tests__/LDClientNode.tls.test.ts new file mode 100644 index 0000000000..16395cae7c --- /dev/null +++ b/platform-node/__tests__/LDClientNode.tls.test.ts @@ -0,0 +1,36 @@ +import { LDClient } from '../src'; + +import { + AsyncQueue, + sleepAsync, + TestHttpHandlers, + TestHttpServer, + withCloseable +} from 'launchdarkly-js-test-helpers'; +import LDClientNode from '../src/LDClientNode'; + +describe('', () => { + let client: LDClient; + let server: TestHttpServer; + + it( + 'can connect via HTTPS to a server with a self-signed certificate, if CA is specified', + async () => { + server = await TestHttpServer.startSecure(); + server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson({})); + + client = new LDClientNode('sdk-key', { + baseUri: server.url, + sendEvents: false, + stream: false, + tlsParams: { ca: server.certificate }, + diagnosticOptOut: true, + }); + await client.waitForInitialization(); + }); + + afterEach(() => { + client.close(); + server.close(); + }); +}); \ No newline at end of file diff --git a/platform-node/package.json b/platform-node/package.json index ac32ded7c1..7a97d0f4ba 100644 --- a/platform-node/package.json +++ b/platform-node/package.json @@ -24,6 +24,7 @@ "@types/jest": "^27.4.1", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typescript": "^4.6.3" + "typescript": "^4.6.3", + "launchdarkly-js-test-helpers": "^2.2.0" } } diff --git a/platform-node/src/platform/NodeRequests.ts b/platform-node/src/platform/NodeRequests.ts index 236ac75578..286ea98c91 100644 --- a/platform-node/src/platform/NodeRequests.ts +++ b/platform-node/src/platform/NodeRequests.ts @@ -11,7 +11,7 @@ import { EventSource as LDEventSource } from 'launchdarkly-eventsource'; import NodeResponse from './NodeResponse'; function processTlsOptions(tlsOptions: LDTLSOptions): https.AgentOptions { - return { + const options: https.AgentOptions & { [index: string]: any } = { ca: tlsOptions.ca, cert: tlsOptions.cert, checkServerIdentity: tlsOptions.checkServerIdentity, @@ -27,6 +27,15 @@ function processTlsOptions(tlsOptions: LDTLSOptions): https.AgentOptions { secureProtocol: tlsOptions.secureProtocol, servername: tlsOptions.servername, }; + + // Node does not take kindly to undefined keys. + Object.keys(options).forEach((key) => { + if (options[key] === undefined) { + delete options[key]; + } + }); + + return options; } function processProxyOptions( @@ -34,7 +43,7 @@ function processProxyOptions( additional: https.AgentOptions = {}, ): https.Agent | http.Agent { const protocol = proxyOptions.scheme?.startsWith('https') ? 'https:' : 'http'; - const parsedOptions: HttpsProxyAgentOptions = { + const parsedOptions: HttpsProxyAgentOptions & { [index: string]: any } = { port: proxyOptions.port, host: proxyOptions.host, protocol, @@ -45,6 +54,14 @@ function processProxyOptions( 'Proxy-Authorization': `Basic ${Buffer.from(proxyOptions.auth).toString('base64')}}`, }; } + + // Node does not take kindly to undefined keys. + Object.keys(parsedOptions).forEach((key) => { + if (parsedOptions[key] === undefined) { + delete parsedOptions[key]; + } + }); + return createHttpsProxyAgent(parsedOptions); } From 7c387525354042d8053493335ac64517e57e1915 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 12:42:37 -0700 Subject: [PATCH 24/34] Add TLS tests. --- .../__tests__/LDClientNode.tls.test.ts | 77 ++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/platform-node/__tests__/LDClientNode.tls.test.ts b/platform-node/__tests__/LDClientNode.tls.test.ts index 16395cae7c..d87bbc43fd 100644 --- a/platform-node/__tests__/LDClientNode.tls.test.ts +++ b/platform-node/__tests__/LDClientNode.tls.test.ts @@ -1,17 +1,22 @@ -import { LDClient } from '../src'; +import { basicLogger, LDClient, LDLogger } from '../src'; import { AsyncQueue, sleepAsync, + SSEItem, TestHttpHandlers, TestHttpServer, - withCloseable } from 'launchdarkly-js-test-helpers'; import LDClientNode from '../src/LDClientNode'; describe('', () => { let client: LDClient; let server: TestHttpServer; + let logger: LDLogger; + + beforeEach(() => { + logger = basicLogger({}); + }); it( 'can connect via HTTPS to a server with a self-signed certificate, if CA is specified', @@ -23,12 +28,80 @@ describe('', () => { baseUri: server.url, sendEvents: false, stream: false, + logger, tlsParams: { ca: server.certificate }, diagnosticOptOut: true, }); await client.waitForInitialization(); }); + it('cannot connect via HTTPS to a server with a self-signed certificate, using default config', async () => { + server = await TestHttpServer.startSecure(); + server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson({})); + + client = new LDClientNode('sdk-key', { + baseUri: server.url, + sendEvents: false, + stream: false, + logger, + diagnosticOptOut: true, + }); + + const spy = jest.spyOn(logger, 'warn'); + + await sleepAsync(300); // the client won't signal an unrecoverable error, but it should log a message + + expect(spy).toHaveBeenCalledWith(expect.stringMatching(/self.signed/)); + }); + + it('can use custom TLS options for streaming as well as polling', async () => { + const eventData = { data: { flags: { flag: { version: 1 } }, segments: {} } }; + let events = new AsyncQueue(); + events.add({ type: 'put', data: JSON.stringify(eventData) }); + server = await TestHttpServer.startSecure(); + server.forMethodAndPath('get', '/stream/all', TestHttpHandlers.sseStream(events)); + + client = new LDClientNode('sdk-key', { + baseUri: server.url, + streamUri: server.url + '/stream', + sendEvents: false, + logger: logger, + tlsParams: { ca: server.certificate }, + diagnosticOptOut: true, + }); + + await client.waitForInitialization(); // this won't return until the stream receives the "put" event + events.close(); + }); + + + it('can use custom TLS options for posting events', async () => { + server = await TestHttpServer.startSecure(); + server.forMethodAndPath('post', '/events/bulk', TestHttpHandlers.respond(200)); + server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson({})); + + client = new LDClientNode('sdk-key', { + baseUri: server.url, + eventsUri: server.url + '/events', + stream: false, + tlsParams: { ca: server.certificate }, + diagnosticOptOut: true, + }); + + await client.waitForInitialization(); + client.identify({ key: 'user' }); + await client.flush(); + + const flagsRequest = await server.nextRequest(); + expect(flagsRequest.path).toEqual('/sdk/latest-all'); + + const eventsRequest = await server.nextRequest(); + expect(eventsRequest.path).toEqual('/events/bulk'); + const eventData = JSON.parse(eventsRequest.body!); + expect(eventData.length).toEqual(1); + expect(eventData[0].kind).toEqual('identify'); + }); + afterEach(() => { client.close(); server.close(); From f929aeee4d7cfacdf86de635776e84891e194f64 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 12:44:01 -0700 Subject: [PATCH 25/34] Lint --- .../LDClientNode.bigSegments.test.ts | 7 +-- .../__tests__/LDClientNode.tls.test.ts | 53 ++++++++++--------- .../src/integrations/test_data/index.ts | 4 +- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/platform-node/__tests__/LDClientNode.bigSegments.test.ts b/platform-node/__tests__/LDClientNode.bigSegments.test.ts index 4cd3fdfefe..e69d1fe9cf 100644 --- a/platform-node/__tests__/LDClientNode.bigSegments.test.ts +++ b/platform-node/__tests__/LDClientNode.bigSegments.test.ts @@ -3,7 +3,6 @@ import { LDClientImpl } from '../src'; import { LDClient } from '../src/api/LDClient'; describe('given test data with big segments', () => { - // To use the public interfaces to create a client which doesn't use the // network. (Versus being offline, or a null update processor.) let td: integrations.TestData; @@ -74,7 +73,6 @@ describe('given test data with big segments', () => { }; beforeEach(async () => { - client = new LDClientImpl('sdk-key', { updateProcessor: td.getFactory(), sendEvents: false, @@ -128,7 +126,7 @@ describe('given test data with big segments', () => { it('Can observe the status change', (done) => { let message = 0; client.bigSegmentStoreStatusProvider.on('change', (status: interfaces.BigSegmentStoreStatus) => { - if(message === 0) { + if (message === 0) { expect(status.stale).toEqual(false); expect(status.available).toEqual(true); error = true; @@ -138,7 +136,6 @@ describe('given test data with big segments', () => { expect(status.available).toEqual(false); done(); } - }); }); @@ -146,4 +143,4 @@ describe('given test data with big segments', () => { client.close(); }); }); -}); \ No newline at end of file +}); diff --git a/platform-node/__tests__/LDClientNode.tls.test.ts b/platform-node/__tests__/LDClientNode.tls.test.ts index d87bbc43fd..92585f7b51 100644 --- a/platform-node/__tests__/LDClientNode.tls.test.ts +++ b/platform-node/__tests__/LDClientNode.tls.test.ts @@ -1,5 +1,3 @@ -import { basicLogger, LDClient, LDLogger } from '../src'; - import { AsyncQueue, sleepAsync, @@ -7,6 +5,8 @@ import { TestHttpHandlers, TestHttpServer, } from 'launchdarkly-js-test-helpers'; +import { basicLogger, LDClient, LDLogger } from '../src'; + import LDClientNode from '../src/LDClientNode'; describe('', () => { @@ -33,48 +33,53 @@ describe('', () => { diagnosticOptOut: true, }); await client.waitForInitialization(); - }); + }, + ); - it('cannot connect via HTTPS to a server with a self-signed certificate, using default config', async () => { - server = await TestHttpServer.startSecure(); - server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson({})); + it( + 'cannot connect via HTTPS to a server with a self-signed certificate, using default config', + async () => { + server = await TestHttpServer.startSecure(); + server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson({})); - client = new LDClientNode('sdk-key', { - baseUri: server.url, - sendEvents: false, - stream: false, - logger, - diagnosticOptOut: true, - }); + client = new LDClientNode('sdk-key', { + baseUri: server.url, + sendEvents: false, + stream: false, + logger, + diagnosticOptOut: true, + }); - const spy = jest.spyOn(logger, 'warn'); + const spy = jest.spyOn(logger, 'warn'); - await sleepAsync(300); // the client won't signal an unrecoverable error, but it should log a message + // the client won't signal an unrecoverable error, but it should log a message + await sleepAsync(300); - expect(spy).toHaveBeenCalledWith(expect.stringMatching(/self.signed/)); - }); + expect(spy).toHaveBeenCalledWith(expect.stringMatching(/self.signed/)); + }, + ); it('can use custom TLS options for streaming as well as polling', async () => { const eventData = { data: { flags: { flag: { version: 1 } }, segments: {} } }; - let events = new AsyncQueue(); + const events = new AsyncQueue(); events.add({ type: 'put', data: JSON.stringify(eventData) }); server = await TestHttpServer.startSecure(); server.forMethodAndPath('get', '/stream/all', TestHttpHandlers.sseStream(events)); client = new LDClientNode('sdk-key', { baseUri: server.url, - streamUri: server.url + '/stream', + streamUri: `${server.url}/stream`, sendEvents: false, - logger: logger, + logger, tlsParams: { ca: server.certificate }, diagnosticOptOut: true, }); - await client.waitForInitialization(); // this won't return until the stream receives the "put" event + // this won't return until the stream receives the "put" event + await client.waitForInitialization(); events.close(); }); - it('can use custom TLS options for posting events', async () => { server = await TestHttpServer.startSecure(); server.forMethodAndPath('post', '/events/bulk', TestHttpHandlers.respond(200)); @@ -82,7 +87,7 @@ describe('', () => { client = new LDClientNode('sdk-key', { baseUri: server.url, - eventsUri: server.url + '/events', + eventsUri: `${server.url}/events`, stream: false, tlsParams: { ca: server.certificate }, diagnosticOptOut: true, @@ -106,4 +111,4 @@ describe('', () => { client.close(); server.close(); }); -}); \ No newline at end of file +}); diff --git a/server-sdk-common/src/integrations/test_data/index.ts b/server-sdk-common/src/integrations/test_data/index.ts index b5d2ee26cd..8f8e9663a0 100644 --- a/server-sdk-common/src/integrations/test_data/index.ts +++ b/server-sdk-common/src/integrations/test_data/index.ts @@ -5,5 +5,5 @@ import TestDataRuleBuilder from './TestDataRuleBuilder'; export { TestData, TestDataFlagBuilder, - TestDataRuleBuilder -}; \ No newline at end of file + TestDataRuleBuilder, +}; From 8618392ac808625edd1a4b47456e605b248babca Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 12:58:55 -0700 Subject: [PATCH 26/34] Move tests to match directory structure. Add tests for the header wrapper. --- .../__tests__/platform/HeaderWrapper.test.ts | 60 +++++++++++++++++++ .../__tests__/{ => platform}/NodeInfo.test.ts | 2 +- .../{ => platform}/NodeRequests.test.ts | 2 +- 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 platform-node/__tests__/platform/HeaderWrapper.test.ts rename platform-node/__tests__/{ => platform}/NodeInfo.test.ts (97%) rename platform-node/__tests__/{ => platform}/NodeRequests.test.ts (98%) diff --git a/platform-node/__tests__/platform/HeaderWrapper.test.ts b/platform-node/__tests__/platform/HeaderWrapper.test.ts new file mode 100644 index 0000000000..295310de6e --- /dev/null +++ b/platform-node/__tests__/platform/HeaderWrapper.test.ts @@ -0,0 +1,60 @@ +import * as http from 'http'; +import HeaderWrapper from '../../src/platform/HeaderWrapper'; + +describe('given header values', () => { + const headers: http.IncomingHttpHeaders = { + 'accept': 'anything', + 'some-header': 'some-value', + 'some-array': ['a', 'b'] + }; + const wrapper = new HeaderWrapper(headers); + + it('can get a single value header', () => { + expect(wrapper.get('accept')).toEqual('anything'); + }); + + it('can get an array value header', () => { + expect(wrapper.get('some-array')).toEqual('a, b'); + }); + + it('can get the entries', () => { + const flat = []; + for(const entry of wrapper.entries()) { + flat.push(entry); + } + expect(flat).toEqual([ + ['accept', 'anything'], + ['some-header', 'some-value'], + ['some-array', 'a, b'] + ]); + }); + + it('can check if a value is present', () => { + expect(wrapper.has('accept')).toBeTruthy(); + expect(wrapper.has('potato')).toBeFalsy(); + }); + + it('can key the keys', () => { + const keys = []; + for(const key of wrapper.keys()) { + keys.push(key); + } + expect(keys).toEqual([ + 'accept', + 'some-header', + 'some-array' + ]); + }); + + it('can key the values', () => { + const values = []; + for(const value of wrapper.values()) { + values.push(value); + } + expect(values).toEqual([ + 'anything', + 'some-value', + 'a, b' + ]); + }); +}); diff --git a/platform-node/__tests__/NodeInfo.test.ts b/platform-node/__tests__/platform/NodeInfo.test.ts similarity index 97% rename from platform-node/__tests__/NodeInfo.test.ts rename to platform-node/__tests__/platform/NodeInfo.test.ts index 0567413c76..954c42b00d 100644 --- a/platform-node/__tests__/NodeInfo.test.ts +++ b/platform-node/__tests__/platform/NodeInfo.test.ts @@ -1,5 +1,5 @@ import * as os from 'os'; -import NodeInfo from '../src/platform/NodeInfo'; +import NodeInfo from '../../src/platform/NodeInfo'; describe('given an information instance', () => { const info = new NodeInfo(); diff --git a/platform-node/__tests__/NodeRequests.test.ts b/platform-node/__tests__/platform/NodeRequests.test.ts similarity index 98% rename from platform-node/__tests__/NodeRequests.test.ts rename to platform-node/__tests__/platform/NodeRequests.test.ts index 0bdd79308e..2976451fcc 100644 --- a/platform-node/__tests__/NodeRequests.test.ts +++ b/platform-node/__tests__/platform/NodeRequests.test.ts @@ -1,6 +1,6 @@ import * as http from 'http'; -import NodeRequests from '../src/platform/NodeRequests'; +import NodeRequests from '../../src/platform/NodeRequests'; const PORT = '3333'; const TEXT_RESPONSE = 'Test Text'; From ea0d8a2244d8c3fed7f57f11b7cd220e6c04ca5f Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 13:31:03 -0700 Subject: [PATCH 27/34] Fixes issues from end to end testing. --- platform-node/src/LDClientNode.ts | 3 +- platform-node/src/platform/NodeFilesystem.ts | 4 +- platform-node/src/platform/NodeRequests.ts | 21 +- sdk-common/src/Context.ts | 5 +- server-sdk-common/__tests__/AsyncQueue.ts | 30 +++ .../data_sources/DataSourceUpdates.test.ts | 181 ++++++++++++++ .../data_sources/FileDataSource.test.ts | 229 +++++++++++++----- .../data_sources/PollingProcessor.test.ts | 14 +- .../data_sources/StreamingProcessor.test.ts | 1 + .../evaluation/Evaluator.rules.test.ts | 8 +- .../evaluation/Evaluator.segments.test.ts | 3 +- .../__tests__/evaluation/mocks/noQueries.ts | 2 +- .../integrations/test_data/TestData.test.ts | 37 +-- server-sdk-common/src/ClientContext.ts | 23 ++ server-sdk-common/src/LDClientImpl.ts | 64 +++-- .../src/api/data/LDEvaluationDetail.ts | 2 +- .../src/api/options/LDBigSegmentsOptions.ts | 6 +- .../src/api/options/LDClientContext.ts | 50 ++++ .../src/api/options/LDOptions.ts | 7 +- .../src/api/subsystems/LDDataSourceUpdates.ts | 43 ++++ server-sdk-common/src/api/subsystems/index.ts | 2 + .../src/data_sources/DataSourceUpdates.ts | 144 +++++++++++ .../src/data_sources/DependencyTracker.ts | 51 ++++ .../src/data_sources/NamespacedDataSet.ts | 40 +++ .../src/data_sources/PollingProcessor.ts | 12 +- .../src/data_sources/StreamingProcessor.ts | 21 +- .../src/evaluation/EvalResult.ts | 4 +- server-sdk-common/src/evaluation/Evaluator.ts | 120 ++++++++- server-sdk-common/src/evaluation/Queries.ts | 3 +- .../src/evaluation/makeBigSegmentRef.ts | 11 + .../src/events/InputEvalEvent.ts | 2 +- server-sdk-common/src/index.ts | 1 + .../src/integrations/FileDataSourceFactory.ts | 52 ++++ server-sdk-common/src/integrations/index.ts | 6 + .../src/integrations/test_data/TestData.ts | 16 +- .../src/integrations/test_data/index.ts | 9 + .../src/options/Configuration.ts | 29 ++- .../src/store/InMemoryFeatureStore.ts | 13 +- 38 files changed, 1103 insertions(+), 166 deletions(-) create mode 100644 server-sdk-common/__tests__/AsyncQueue.ts create mode 100644 server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts create mode 100644 server-sdk-common/src/ClientContext.ts create mode 100644 server-sdk-common/src/api/options/LDClientContext.ts create mode 100644 server-sdk-common/src/api/subsystems/LDDataSourceUpdates.ts create mode 100644 server-sdk-common/src/data_sources/DataSourceUpdates.ts create mode 100644 server-sdk-common/src/data_sources/DependencyTracker.ts create mode 100644 server-sdk-common/src/data_sources/NamespacedDataSet.ts create mode 100644 server-sdk-common/src/evaluation/makeBigSegmentRef.ts create mode 100644 server-sdk-common/src/integrations/FileDataSourceFactory.ts create mode 100644 server-sdk-common/src/integrations/index.ts create mode 100644 server-sdk-common/src/integrations/test_data/index.ts diff --git a/platform-node/src/LDClientNode.ts b/platform-node/src/LDClientNode.ts index 982f8b086e..48f14b8e16 100644 --- a/platform-node/src/LDClientNode.ts +++ b/platform-node/src/LDClientNode.ts @@ -46,8 +46,9 @@ class LDClientNode extends LDClientImpl { }, (key: string) => { emitter.emit('update', { key }); - emitter.emit(`update:${key}`); + emitter.emit(`update:${key}`, { key }); }, + () => emitter.eventNames().some((name) => name === 'update' || (typeof name === 'string' && name.startsWith('update:'))), ); this.emitter = emitter; diff --git a/platform-node/src/platform/NodeFilesystem.ts b/platform-node/src/platform/NodeFilesystem.ts index e01d0de203..f61293a7da 100644 --- a/platform-node/src/platform/NodeFilesystem.ts +++ b/platform-node/src/platform/NodeFilesystem.ts @@ -18,8 +18,8 @@ export default class NodeFilesystem implements platform.Filesystem { path: string, callback: (eventType: string, filename: string) => void, ): platform.WatchHandle { - return fs.watch(path, { persistent: false }, (eventType, filename) => { - callback(eventType, filename); + return fs.watch(path, { persistent: false }, (eventType) => { + callback(eventType, path); }); } } diff --git a/platform-node/src/platform/NodeRequests.ts b/platform-node/src/platform/NodeRequests.ts index 236ac75578..286ea98c91 100644 --- a/platform-node/src/platform/NodeRequests.ts +++ b/platform-node/src/platform/NodeRequests.ts @@ -11,7 +11,7 @@ import { EventSource as LDEventSource } from 'launchdarkly-eventsource'; import NodeResponse from './NodeResponse'; function processTlsOptions(tlsOptions: LDTLSOptions): https.AgentOptions { - return { + const options: https.AgentOptions & { [index: string]: any } = { ca: tlsOptions.ca, cert: tlsOptions.cert, checkServerIdentity: tlsOptions.checkServerIdentity, @@ -27,6 +27,15 @@ function processTlsOptions(tlsOptions: LDTLSOptions): https.AgentOptions { secureProtocol: tlsOptions.secureProtocol, servername: tlsOptions.servername, }; + + // Node does not take kindly to undefined keys. + Object.keys(options).forEach((key) => { + if (options[key] === undefined) { + delete options[key]; + } + }); + + return options; } function processProxyOptions( @@ -34,7 +43,7 @@ function processProxyOptions( additional: https.AgentOptions = {}, ): https.Agent | http.Agent { const protocol = proxyOptions.scheme?.startsWith('https') ? 'https:' : 'http'; - const parsedOptions: HttpsProxyAgentOptions = { + const parsedOptions: HttpsProxyAgentOptions & { [index: string]: any } = { port: proxyOptions.port, host: proxyOptions.host, protocol, @@ -45,6 +54,14 @@ function processProxyOptions( 'Proxy-Authorization': `Basic ${Buffer.from(proxyOptions.auth).toString('base64')}}`, }; } + + // Node does not take kindly to undefined keys. + Object.keys(parsedOptions).forEach((key) => { + if (parsedOptions[key] === undefined) { + delete parsedOptions[key]; + } + }); + return createHttpsProxyAgent(parsedOptions); } diff --git a/sdk-common/src/Context.ts b/sdk-common/src/Context.ts index 4e543bd1a5..bf9c26d843 100644 --- a/sdk-common/src/Context.ts +++ b/sdk-common/src/Context.ts @@ -319,6 +319,9 @@ export default class Context { * @returns a {@link Context} or `undefined` if one could not be created. */ public static fromLDContext(context: LDContext): Context | undefined { + if (!context) { + return undefined; + } if (isSingleKind(context)) { return Context.fromSingleKindContext(context); } if (isMultiKind(context)) { @@ -383,7 +386,7 @@ export default class Context { return this.context!.key; } if (this.isMulti) { - return Object.keys(this.contexts).map((key) => `${key}:${encodeURIComponent(this.contexts[key].key)}`) + return Object.keys(this.contexts).sort().map((key) => `${key}:${encodeURIComponent(this.contexts[key].key)}`) .join(':'); } return `${this.kind}:${encodeURIComponent(this.context!.key)}`; diff --git a/server-sdk-common/__tests__/AsyncQueue.ts b/server-sdk-common/__tests__/AsyncQueue.ts new file mode 100644 index 0000000000..6dccdcdab0 --- /dev/null +++ b/server-sdk-common/__tests__/AsyncQueue.ts @@ -0,0 +1,30 @@ +export default class AsyncQueue { + private content: any[] = []; + + private takers: { + resolve: (res: any) => void; + reject: (err: Error) => void; + }[] = []; + + push(item: any) { + if (this.takers.length) { + const taker = this.takers.shift(); + taker?.resolve(item); + } + this.content.push(item); + } + + take(): Promise { + if (this.content.length) { + return Promise.resolve(this.content.shift()!); + } + + return new Promise((resolve, reject) => { + this.takers.push({ resolve, reject }); + }); + } + + empty() { + return this.content.length === 0; + } +} diff --git a/server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts b/server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts new file mode 100644 index 0000000000..72679e06ba --- /dev/null +++ b/server-sdk-common/__tests__/data_sources/DataSourceUpdates.test.ts @@ -0,0 +1,181 @@ +import { LDFeatureStore } from '../../src/api/subsystems'; +import promisify from '../../src/async/promisify'; +import DataSourceUpdates from '../../src/data_sources/DataSourceUpdates'; +import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; +import VersionedDataKinds from '../../src/store/VersionedDataKinds'; +import AsyncQueue from '../AsyncQueue'; + +describe.each([true, false])('given a DataSourceUpdates with in memory store and change listeners: %s', (listen) => { + let store: LDFeatureStore; + let updates: DataSourceUpdates; + + const queue = new AsyncQueue(); + + beforeEach(() => { + store = new InMemoryFeatureStore(); + + updates = new DataSourceUpdates( + store, + () => listen, + (key) => queue.push(key), + ); + }); + + it('sends events for an init of an empty store', async () => { + await promisify((cb) => { + updates.init({ + features: { + a: { key: 'a', version: 1 }, + b: { key: 'b', version: 1 }, + }, + segments: {}, + }, () => { + cb(undefined); + }); + }); + + if (listen) { + expect(await queue.take()).toEqual('a'); + expect(await queue.take()).toEqual('b'); + } + expect(queue.empty()).toBeTruthy(); + }); + + it('sends events for re-init of non-empty store', async () => { + const allData0 = { + features: { + a: { key: 'a', version: 1 }, + b: { key: 'b', version: 1 }, + c: { key: 'c', version: 1 }, + }, + segments: {}, + }; + const allData1 = { + features: { + a: { key: 'a', version: 1 }, + b: { key: 'b', version: 2 }, + }, + segments: {}, + }; + + await promisify((cb) => { + updates.init(allData0, () => { + cb(undefined); + }); + }); + + if (listen) { + expect(await queue.take()).toEqual('a'); + expect(await queue.take()).toEqual('b'); + expect(await queue.take()).toEqual('c'); + } + expect(queue.empty()).toBeTruthy(); + + await promisify((cb) => { + updates.init(allData1, () => { + cb(undefined); + }); + }); + + if (listen) { + // A remained the same. + expect(await queue.take()).toEqual('b'); // Different version + expect(await queue.take()).toEqual('c'); // Deleted + } + expect(queue.empty()).toBeTruthy(); + }); + + it('sends events for upserts', async () => { + await promisify((cb) => { + updates.init({ + features: { + a: { key: 'a', version: 1 }, + }, + segments: {}, + }, () => { + cb(undefined); + }); + }); + + if (listen) { + expect(await queue.take()).toEqual('a'); + } + expect(queue.empty()).toBeTruthy(); + + // Upsert the same thing twice. Should only be 1 event. + promisify((cb) => { + updates.upsert(VersionedDataKinds.Features, { key: 'a', version: 2 }, () => cb(undefined)); + }); + promisify((cb) => { + updates.upsert(VersionedDataKinds.Features, { key: 'a', version: 2 }, () => cb(undefined)); + }); + + if (listen) { + expect(await queue.take()).toEqual('a'); + } + expect(queue.empty()).toBeTruthy(); + }); + + it('sends events for transitive dependencies', async () => { + await promisify((cb) => { + updates.init({ + features: { + a: { key: 'a', version: 1 }, + b: { key: 'b', version: 1, prerequisites: [{ key: 'c' }, { key: 'e' }] }, + c: { + key: 'c', + version: 1, + prerequisites: [{ key: 'd' }], + rules: [ + { clauses: [{ op: 'segmentMatch', values: ['s0'] }] }, + ], + }, + d: { key: 'd', version: 1, prerequisites: [{ key: 'e' }] }, + e: { key: 'e', version: 1 }, + }, + segments: { + s0: { key: 's0', version: 1 }, + }, + }, () => { + cb(undefined); + }); + }); + + if (listen) { + expect(await queue.take()).toEqual('a'); + expect(await queue.take()).toEqual('b'); + expect(await queue.take()).toEqual('c'); + expect(await queue.take()).toEqual('d'); + expect(await queue.take()).toEqual('e'); + } + expect(queue.empty()).toBeTruthy(); + + promisify((cb) => { + updates.upsert( + VersionedDataKinds.Features, + { key: 'd', version: 2, prerequisites: [{ key: 'e' }] }, + () => cb(undefined), + ); + }); + + if (listen) { + expect( + [await queue.take(), await queue.take(), await queue.take()].sort(), + ).toEqual(['b', 'c', 'd']); + } + expect(queue.empty()).toBeTruthy(); + + promisify((cb) => { + updates.upsert( + VersionedDataKinds.Segments, + { key: 's0', version: 2 }, + () => cb(undefined), + ); + }); + + if (listen) { + expect([await queue.take(), await queue.take()].sort()).toEqual(['b', 'c']); + } + expect(queue.empty()).toBeTruthy(); + }); +}); diff --git a/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts b/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts index 19b1b986e8..d1fc735907 100644 --- a/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts +++ b/server-sdk-common/__tests__/data_sources/FileDataSource.test.ts @@ -1,9 +1,11 @@ import { Context } from '@launchdarkly/js-sdk-common'; import promisify from '../../src/async/promisify'; -import FileDataSourceFactory from '../../src/data_sources/FileDataSourceFactory'; +import ClientContext from '../../src/ClientContext'; import { Flag } from '../../src/evaluation/data/Flag'; import { Segment } from '../../src/evaluation/data/Segment'; import Evaluator from '../../src/evaluation/Evaluator'; +import { FileDataSourceFactory } from '../../src/integrations'; +import Configuration from '../../src/options/Configuration'; import { Filesystem, WatchHandle } from '../../src/platform'; import AsyncStoreFacade from '../../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; @@ -57,26 +59,6 @@ const allPropertiesJson = ` } }`; -// const allPropertiesYaml = ` -// flags: -// ${flag1Key}: -// key: ${flag1Key} -// on: true -// fallthrough: -// variation: 2 -// variations: -// - fall -// - off -// - on -// flagValues: -// ${flag2Key}: "${flag2Value}" -// segments: -// ${segment1Key}: -// key: ${segment1Key} -// include: -// - user1 -// `; - function sorted(a: any[]) { const a1 = Array.from(a); a1.sort(); @@ -156,11 +138,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['testfile.json'], }); - factory.create({ + factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), featureStore, - logger, - }, filesystem); - + ); expect(await asyncFeatureStore.initialized()).toBeFalsy(); expect(await asyncFeatureStore.all(VersionedDataKinds.Features)).toEqual({}); @@ -177,10 +165,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['testfile.json'], }); - const fds = factory.create({ + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), featureStore, - logger, - }, filesystem); + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -200,10 +195,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['missing-file.json'], }); - const fds = factory.create({ + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), featureStore, - logger, - }, filesystem); + ); fds.start(async (err) => { expect(err).toBeDefined(); @@ -222,10 +224,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['malformed_file.json'], }); - const fds = factory.create({ + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), featureStore, - logger, - }, filesystem); + ); fds.start(async (err) => { expect(err).toBeDefined(); @@ -247,9 +256,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['file1.json', 'file2.json'], }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), + featureStore, + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -273,9 +290,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['file1.json', 'file2.json'], }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), + featureStore, + ); fds.start(async (err) => { expect(err).toBeDefined(); @@ -294,9 +319,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['file1.json', 'file2.json'], }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), + featureStore, + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -314,9 +347,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['file1.json'], }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), + featureStore, + ); fds.start(async () => { const evaluator = new Evaluator(basicPlatform, { @@ -348,9 +389,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: ['file1.json'], }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), + featureStore, + ); fds.start(async () => { const evaluator = new Evaluator(basicPlatform, { @@ -387,9 +436,17 @@ describe('given a mock filesystem and memory feature store', () => { autoUpdate: true, }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), + featureStore, + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -414,9 +471,17 @@ describe('given a mock filesystem and memory feature store', () => { autoUpdate: true, }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), + featureStore, + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -454,9 +519,17 @@ describe('given a mock filesystem and memory feature store', () => { autoUpdate: true, }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), + featureStore, + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -497,9 +570,17 @@ describe('given a mock filesystem and memory feature store', () => { autoUpdate: true, }); - const fds = factory.create({ - featureStore, logger, - }, filesystem); + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), + featureStore, + ); fds.start(async () => { expect(await asyncFeatureStore.initialized()).toBeTruthy(); @@ -533,10 +614,17 @@ describe('given a mock filesystem and memory feature store', () => { paths: [fileName], }); - const fds = factory.create({ + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), featureStore, - logger, - }, filesystem); + ); const err = await promisify((cb) => { fds.start(cb); @@ -562,10 +650,17 @@ describe('given a mock filesystem and memory feature store', () => { yamlParser: parser, }); - const fds = factory.create({ + const fds = factory.create( + new ClientContext( + '', + new Configuration({ + featureStore, + logger, + }), + { ...basicPlatform, fileSystem: filesystem as unknown as Filesystem }, + ), featureStore, - logger, - }, filesystem); + ); const err = await promisify((cb) => { fds.start(cb); diff --git a/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts b/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts index cc7ec59f55..03a251f644 100644 --- a/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts +++ b/server-sdk-common/__tests__/data_sources/PollingProcessor.test.ts @@ -1,11 +1,13 @@ import { LDFeatureStore } from '../../src/api/subsystems'; import promisify from '../../src/async/promisify'; +import ClientContext from '../../src/ClientContext'; import PollingProcessor from '../../src/data_sources/PollingProcessor'; import Requestor from '../../src/data_sources/Requestor'; import Configuration from '../../src/options/Configuration'; import AsyncStoreFacade from '../../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore'; import VersionedDataKinds from '../../src/store/VersionedDataKinds'; +import basicPlatform from '../evaluation/mocks/platform'; import TestLogger, { LogLevel } from '../Logger'; describe('given an event processor', () => { @@ -29,7 +31,11 @@ describe('given an event processor', () => { pollInterval: longInterval, logger: new TestLogger(), }); - processor = new PollingProcessor(config, requestor as unknown as Requestor); + processor = new PollingProcessor( + config, + requestor as unknown as Requestor, + config.featureStoreFactory(new ClientContext('', config, basicPlatform)), + ); }); afterEach(() => { @@ -86,7 +92,11 @@ describe('given a polling processor with a short poll duration', () => { }); // Configuration will not let us set this as low as needed for the test. Object.defineProperty(config, 'pollInterval', { value: 0.1 }); - processor = new PollingProcessor(config, requestor as unknown as Requestor); + processor = new PollingProcessor( + config, + requestor as unknown as Requestor, + config.featureStoreFactory(new ClientContext('', config, basicPlatform)), + ); }); afterEach(() => { diff --git a/server-sdk-common/__tests__/data_sources/StreamingProcessor.test.ts b/server-sdk-common/__tests__/data_sources/StreamingProcessor.test.ts index 3dcaca1c71..e0b70f8938 100644 --- a/server-sdk-common/__tests__/data_sources/StreamingProcessor.test.ts +++ b/server-sdk-common/__tests__/data_sources/StreamingProcessor.test.ts @@ -65,6 +65,7 @@ describe('given a stream processor with mock event source', () => { config, requests, info, + featureStore, ); }); diff --git a/server-sdk-common/__tests__/evaluation/Evaluator.rules.test.ts b/server-sdk-common/__tests__/evaluation/Evaluator.rules.test.ts index adc2158b2e..299404ab5a 100644 --- a/server-sdk-common/__tests__/evaluation/Evaluator.rules.test.ts +++ b/server-sdk-common/__tests__/evaluation/Evaluator.rules.test.ts @@ -42,7 +42,7 @@ describe('when evaluating user equivalent contexts', () => { const res = await evaluator.evaluate(flag, Context.fromLDContext(userToTest)!); expect(res.isError).toBeTruthy(); expect(res.message).toEqual('Invalid variation index in flag'); - expect(res.detail).toMatchObject({ value: null, variationIndex: undefined, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); + expect(res.detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); }); it.each([basicUser, basicSingleKindUser, basicMultiKindUser])('returns error if rule variation is negative', async (userToTest) => { @@ -51,7 +51,7 @@ describe('when evaluating user equivalent contexts', () => { const res = await evaluator.evaluate(flag, Context.fromLDContext(userToTest)!); expect(res.isError).toBeTruthy(); expect(res.message).toEqual('Invalid variation index in flag'); - expect(res.detail).toMatchObject({ value: null, variationIndex: undefined, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); + expect(res.detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); }); it.each([basicUser, basicSingleKindUser, basicMultiKindUser])('returns error if rule has no variation or rollout', async (userToTest) => { @@ -60,7 +60,7 @@ describe('when evaluating user equivalent contexts', () => { const res = await evaluator.evaluate(flag, Context.fromLDContext(userToTest)!); expect(res.isError).toBeTruthy(); expect(res.message).toEqual('Variation/rollout object with no variation or rollout'); - expect(res.detail).toMatchObject({ value: null, variationIndex: undefined, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); + expect(res.detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); }); it.each([basicUser, basicSingleKindUser, basicMultiKindUser])('returns error if rule has rollout with no variations', async (userToTest) => { @@ -69,7 +69,7 @@ describe('when evaluating user equivalent contexts', () => { const res = await evaluator.evaluate(flag, Context.fromLDContext(userToTest)!); expect(res.isError).toBeTruthy(); expect(res.message).toEqual('Variation/rollout object with no variation or rollout'); - expect(res.detail).toMatchObject({ value: null, variationIndex: undefined, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); + expect(res.detail).toMatchObject({ value: null, variationIndex: null, reason: { kind: 'ERROR', errorKind: 'MALFORMED_FLAG' } }); }); it.each([basicUser, basicSingleKindUser, basicMultiKindUser])('does not overflow the call stack when evaluating a huge number of rules', async (userToTest) => { diff --git a/server-sdk-common/__tests__/evaluation/Evaluator.segments.test.ts b/server-sdk-common/__tests__/evaluation/Evaluator.segments.test.ts index 2a0a45687c..76bc999cc5 100644 --- a/server-sdk-common/__tests__/evaluation/Evaluator.segments.test.ts +++ b/server-sdk-common/__tests__/evaluation/Evaluator.segments.test.ts @@ -27,7 +27,8 @@ class TestQueries implements Queries { return this.data.segments?.find((segment) => segment.key === key); } - getBigSegmentsMembership(userKey: string): Promise { + getBigSegmentsMembership(userKey: string): + Promise<[BigSegmentStoreMembership | null, string] | undefined> { throw new Error('Method not implemented.'); } } diff --git a/server-sdk-common/__tests__/evaluation/mocks/noQueries.ts b/server-sdk-common/__tests__/evaluation/mocks/noQueries.ts index 08a4fb21b7..4a6cee4f34 100644 --- a/server-sdk-common/__tests__/evaluation/mocks/noQueries.ts +++ b/server-sdk-common/__tests__/evaluation/mocks/noQueries.ts @@ -10,7 +10,7 @@ const noQueries: Queries = { getSegment(): Promise { throw new Error('Function not implemented.'); }, - getBigSegmentsMembership(): Promise { + getBigSegmentsMembership(): Promise<[BigSegmentStoreMembership | null, string] | undefined> { throw new Error('Function not implemented.'); }, }; diff --git a/server-sdk-common/__tests__/integrations/test_data/TestData.test.ts b/server-sdk-common/__tests__/integrations/test_data/TestData.test.ts index ce41ce75b8..b98e97ffe0 100644 --- a/server-sdk-common/__tests__/integrations/test_data/TestData.test.ts +++ b/server-sdk-common/__tests__/integrations/test_data/TestData.test.ts @@ -1,4 +1,5 @@ import { AttributeReference } from '../../../src'; +import ClientContext from '../../../src/ClientContext'; import { Flag } from '../../../src/evaluation/data/Flag'; import { FlagRule } from '../../../src/evaluation/data/FlagRule'; import TestData from '../../../src/integrations/test_data/TestData'; @@ -6,6 +7,7 @@ import Configuration from '../../../src/options/Configuration'; import AsyncStoreFacade from '../../../src/store/AsyncStoreFacade'; import InMemoryFeatureStore from '../../../src/store/InMemoryFeatureStore'; import VersionedDataKinds from '../../../src/store/VersionedDataKinds'; +import basicPlatform from '../../evaluation/mocks/platform'; const basicBooleanFlag: Flag = { fallthrough: { @@ -23,9 +25,10 @@ it('initializes the data store with flags configured the data store is created', td.update(td.flag('new-flag').variationForAll(true)); const store = new InMemoryFeatureStore(); - const processor = td.getFactory()(new Configuration({ - featureStore: store, - })); + const processor = td.getFactory()( + new ClientContext('', new Configuration({}), basicPlatform), + store, + ); processor.start(); const facade = new AsyncStoreFacade(store); @@ -38,9 +41,10 @@ it('initializes the data store with flags configured the data store is created', it('updates the data store when update is called', async () => { const td = new TestData(); const store = new InMemoryFeatureStore(); - const processor = td.getFactory()(new Configuration({ - featureStore: store, - })); + const processor = td.getFactory()( + new ClientContext('', new Configuration({}), basicPlatform), + store, + ); processor.start(); const facade = new AsyncStoreFacade(store); @@ -57,9 +61,10 @@ it('can include pre-configured items', async () => { td.usePreconfiguredSegment({ key: 'my-segment', version: 2000 }); const store = new InMemoryFeatureStore(); - const processor = td.getFactory()(new Configuration({ - featureStore: store, - })); + const processor = td.getFactory()( + new ClientContext('', new Configuration({}), basicPlatform), + store, + ); processor.start(); @@ -104,9 +109,10 @@ it.each([true, false])('does not update the store after stop/close is called', a const td = new TestData(); const store = new InMemoryFeatureStore(); - const processor = td.getFactory()(new Configuration({ - featureStore: store, - })); + const processor = td.getFactory()( + new ClientContext('', new Configuration({}), basicPlatform), + store, + ); processor.start(); td.update(td.flag('new-flag').variationForAll(true)); @@ -131,9 +137,10 @@ it('can update a flag that already exists in the store', async () => { const store = new InMemoryFeatureStore(); - const processor = td.getFactory()(new Configuration({ - featureStore: store, - })); + const processor = td.getFactory()( + new ClientContext('', new Configuration({}), basicPlatform), + store, + ); processor.start(); td.update(td.flag('new-flag').variationForAll(true)); diff --git a/server-sdk-common/src/ClientContext.ts b/server-sdk-common/src/ClientContext.ts new file mode 100644 index 0000000000..94af9b0b56 --- /dev/null +++ b/server-sdk-common/src/ClientContext.ts @@ -0,0 +1,23 @@ +import { LDBasicConfiguration, LDClientContext } from './api/options/LDClientContext'; +import Configuration from './options/Configuration'; +import { Platform } from './platform'; + +/** + * @internal + */ +export default class ClientContext implements LDClientContext { + basicConfiguration: LDBasicConfiguration; + + constructor( + sdkKey: string, + configuration: Configuration, + public readonly platform: Platform, + ) { + this.basicConfiguration = { + logger: configuration.logger, + offline: configuration.offline, + sdkKey, + serviceEndpoints: configuration.serviceEndpoints, + }; + } +} diff --git a/server-sdk-common/src/LDClientImpl.ts b/server-sdk-common/src/LDClientImpl.ts index 9d739ba006..d854b7b80e 100644 --- a/server-sdk-common/src/LDClientImpl.ts +++ b/server-sdk-common/src/LDClientImpl.ts @@ -7,7 +7,9 @@ import { import { BigSegmentStoreMembership } from './api/interfaces'; import BigSegmentsManager from './BigSegmentsManager'; import BigSegmentStoreStatusProvider from './BigSegmentStatusProviderImpl'; +import ClientContext from './ClientContext'; import ClientMessages from './ClientMessages'; +import DataSourceUpdates from './data_sources/DataSourceUpdates'; import NullUpdateProcessor from './data_sources/NullUpdateProcessor'; import PollingProcessor from './data_sources/PollingProcessor'; import Requestor from './data_sources/Requestor'; @@ -52,16 +54,18 @@ export default class LDClientImpl implements LDClient { private evaluator: Evaluator; - private initResolve!: (value: LDClient | PromiseLike) => void; + private initResolve?: (value: LDClient | PromiseLike) => void; - private initReject!: (err: Error) => void; + private initReject?: (err: Error) => void; - private initializedPromise: Promise; + private initializedPromise?: Promise; private logger?: LDLogger; private config: Configuration; + private bigSegmentsManager: BigSegmentsManager; + /** * Intended for use by platform specific client implementations. * @@ -78,7 +82,11 @@ export default class LDClientImpl implements LDClient { private onError: (err: Error) => void, private onFailed: (err: Error) => void, private onReady: () => void, - private onUpdate: (key: string) => void, + // Called whenever flags change, if there are listeners. + onUpdate: (key: string) => void, + // Method to check if event listeners have been registered. + // If none are registered, then onUpdate will never be called. + hasEventListeners: () => boolean, ) { const config = new Configuration(options); if (!sdkKey && !config.offline) { @@ -87,25 +95,27 @@ export default class LDClientImpl implements LDClient { this.config = config; this.logger = config.logger; - this.initializedPromise = new Promise((resolve, reject) => { - this.initResolve = resolve; - this.initReject = reject; - }); + const clientContext = new ClientContext(sdkKey, config, platform); + const featureStore = config.featureStoreFactory(clientContext); + const dataSourceUpdates = new DataSourceUpdates(featureStore, hasEventListeners, onUpdate); const makeDefaultProcessor = () => (config.stream ? new StreamingProcessor( sdkKey, config, this.platform.requests, this.platform.info, + dataSourceUpdates, ) : new PollingProcessor( config, new Requestor(sdkKey, config, this.platform.info, this.platform.requests), + dataSourceUpdates, )); if (config.offline || config.useLdd) { this.updateProcessor = new NullUpdateProcessor(); } else { - this.updateProcessor = config.updateProcessor ?? makeDefaultProcessor(); + this.updateProcessor = config.updateProcessorFactory?.(clientContext, dataSourceUpdates) + ?? makeDefaultProcessor(); } if (!config.sendEvents || config.offline) { @@ -119,15 +129,16 @@ export default class LDClientImpl implements LDClient { ); } - const asyncFacade = new AsyncStoreFacade(config.featureStore); + const asyncFacade = new AsyncStoreFacade(featureStore); this.featureStore = asyncFacade; const manager = new BigSegmentsManager( - config.bigSegments?.store?.(config), + config.bigSegments?.store?.(clientContext), config.bigSegments ?? {}, config.logger, this.platform.crypto, ); + this.bigSegmentsManager = manager; this.bigSegmentStatusProviderInternal = manager.statusProvider as BigSegmentStoreStatusProvider; const queries: Queries = { @@ -137,8 +148,9 @@ export default class LDClientImpl implements LDClient { async getSegment(key: string): Promise { return (await asyncFacade.get(VersionedDataKinds.Segments, key) as Segment) ?? undefined; }, - getBigSegmentsMembership(userKey: string): Promise { - throw new Error('Function not implemented.'); + getBigSegmentsMembership(userKey: string): + Promise<[BigSegmentStoreMembership | null, string] | undefined> { + return manager.getUserMembership(userKey); }, }; this.evaluator = new Evaluator(this.platform, queries); @@ -154,11 +166,11 @@ export default class LDClientImpl implements LDClient { this.onError(error); this.onFailed(error); - this.initReject(error); + this.initReject?.(error); this.initState = InitState.Failed; } else if (!this.initialized()) { this.initState = InitState.Initialized; - this.initResolve(this); + this.initResolve?.(this); this.onReady(); } }); @@ -169,6 +181,15 @@ export default class LDClientImpl implements LDClient { } waitForInitialization(): Promise { + if (this.initState === InitState.Initialized) { + return Promise.resolve(this); + } + if (!this.initializedPromise) { + this.initializedPromise = new Promise((resolve, reject) => { + this.initResolve = resolve; + this.initReject = reject; + }); + } return this.initializedPromise; } @@ -216,14 +237,18 @@ export default class LDClientImpl implements LDClient { ): Promise { if (this.config.offline) { this.logger?.info('allFlagsState() called in offline mode. Returning empty state.'); - return new FlagsStateBuilder(false, false).build(); + const allFlagState = new FlagsStateBuilder(false, false).build(); + callback?.(null, allFlagState); + return allFlagState; } const evalContext = Context.fromLDContext(context); // TODO: Error reporting. if (!evalContext) { this.logger?.info('allFlagsState() called without context. Returning empty state.'); - return new FlagsStateBuilder(false, false).build(); + const allFlagState = new FlagsStateBuilder(false, false).build(); + callback?.(null, allFlagState); + return allFlagState; } let valid = true; @@ -261,7 +286,7 @@ export default class LDClientImpl implements LDClient { builder.addFlag( flag, res.detail.value, - res.detail.variationIndex, + res.detail.variationIndex ?? undefined, res.detail.reason, flag.trackEvents || requireExperimentData, requireExperimentData, @@ -292,6 +317,7 @@ export default class LDClientImpl implements LDClient { this.eventProcessor.close(); this.updateProcessor.close(); this.featureStore.close(); + this.bigSegmentsManager.close(); } isOffline(): boolean { @@ -353,7 +379,7 @@ export default class LDClientImpl implements LDClient { return result; } const evalRes = await this.evaluator.evaluate(flag, evalContext, eventFactory); - if (evalRes.detail.variationIndex === undefined) { + if (evalRes.detail.variationIndex === undefined || evalRes.detail.variationIndex === null) { this.logger?.debug('Result value is null in variation'); evalRes.setDefault(defaultValue); } diff --git a/server-sdk-common/src/api/data/LDEvaluationDetail.ts b/server-sdk-common/src/api/data/LDEvaluationDetail.ts index c59ad3fb8b..ebf8589dae 100644 --- a/server-sdk-common/src/api/data/LDEvaluationDetail.ts +++ b/server-sdk-common/src/api/data/LDEvaluationDetail.ts @@ -20,7 +20,7 @@ export interface LDEvaluationDetail { * The index of the returned value within the flag's list of variations, e.g. 0 for the * first variation-- or `null` if the default value was returned. */ - variationIndex?: number; + variationIndex?: number | null; /** * An object describing the main factor that influenced the flag evaluation value. diff --git a/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts b/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts index b904c19959..bef86a39f4 100644 --- a/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts +++ b/server-sdk-common/src/api/options/LDBigSegmentsOptions.ts @@ -1,5 +1,5 @@ -import { LDLogger } from '@launchdarkly/js-sdk-common'; import { BigSegmentStore } from '../interfaces'; +import { LDClientContext } from './LDClientContext'; /** * Additional parameters for configuring the SDK's Big Segments behavior. @@ -18,9 +18,7 @@ export interface LDBigSegmentsOptions { * database implementation that matches how the LaunchDarkly Relay Proxy is configured, since the * Relay Proxy manages the Big Segment data. */ - store: (options: { - logger?: LDLogger - }) => BigSegmentStore; + store: (clientContext: LDClientContext) => BigSegmentStore; /** * The maximum number of users whose Big Segment state will be cached by the SDK at any given diff --git a/server-sdk-common/src/api/options/LDClientContext.ts b/server-sdk-common/src/api/options/LDClientContext.ts new file mode 100644 index 0000000000..3adc443f78 --- /dev/null +++ b/server-sdk-common/src/api/options/LDClientContext.ts @@ -0,0 +1,50 @@ +import { LDLogger } from '@launchdarkly/js-sdk-common'; +import { Platform } from '../../platform'; + +/** + * Specifies the base service URIs used by SDK components. + */ +export interface LDServiceEndpoints { + // Properties are for internal SDK components. +} + +/** + * The most basic properties of the SDK client that are available to all SDK component factories. + */ +export interface LDBasicConfiguration { + logger?: LDLogger; + + /** + * True if the SDK was configured to be completely offline. + */ + offline: boolean; + + /** + * The configured SDK key. + */ + sdkKey: string; + + /** + * Defines the base service URIs used by SDK components. + */ + serviceEndpoints: LDServiceEndpoints; +} + +/** + * Factory methods receive this class as a parameter. + * + * Its public properties provide information about the SDK configuration and environment. The SDK + * may also include non-public properties that are relevant only when creating one of the built-in + * component types and are not accessible to custom components. + */ +export interface LDClientContext { + /** + * The SDK's basic global properties. + */ + basicConfiguration: LDBasicConfiguration; + + /** + * Interfaces providing platform specific information and functionality. + */ + platform: Platform +} diff --git a/server-sdk-common/src/api/options/LDOptions.ts b/server-sdk-common/src/api/options/LDOptions.ts index 24a0db5aff..c204f0eeaf 100644 --- a/server-sdk-common/src/api/options/LDOptions.ts +++ b/server-sdk-common/src/api/options/LDOptions.ts @@ -1,6 +1,8 @@ import { LDLogger } from '@launchdarkly/js-sdk-common'; +import { LDDataSourceUpdates, LDStreamProcessor } from '../subsystems'; import { LDFeatureStore } from '../subsystems/LDFeatureStore'; import { LDBigSegmentsOptions } from './LDBigSegmentsOptions'; +import { LDClientContext } from './LDClientContext'; import { LDProxyOptions } from './LDProxyOptions'; import { LDTLSOptions } from './LDTLSOptions'; @@ -66,7 +68,7 @@ export interface LDOptions { * provide a factory function that creates the store implementation based on the SDK * configuration; this property accepts either. */ - featureStore?: LDFeatureStore | ((options: LDOptions) => LDFeatureStore); + featureStore?: LDFeatureStore | ((clientContext: LDClientContext) => LDFeatureStore); /** * Additional parameters for configuring the SDK's Big Segments behavior. @@ -87,7 +89,8 @@ export interface LDOptions { * By default, this is the client's default streaming or polling component. It can be changed * for testing purposes; see [[FileDataSource]]. */ - updateProcessor?: object; + updateProcessor?: object | + ((clientContext: LDClientContext, dataSourceUpdates: LDDataSourceUpdates) => LDStreamProcessor); /** * The interval in between flushes of the analytics events queue, in seconds. diff --git a/server-sdk-common/src/api/subsystems/LDDataSourceUpdates.ts b/server-sdk-common/src/api/subsystems/LDDataSourceUpdates.ts new file mode 100644 index 0000000000..a62669fce1 --- /dev/null +++ b/server-sdk-common/src/api/subsystems/LDDataSourceUpdates.ts @@ -0,0 +1,43 @@ +import { DataKind } from '../interfaces'; +import { LDFeatureStoreDataStorage, LDKeyedFeatureStoreItem } from './LDFeatureStore'; + +/** + * Interface that a data source implementation will use to push data into the SDK. + * + * The data source interacts with this object, rather than manipulating the data store directly, so + * that the SDK can perform any other necessary operations that must happen when data is updated. + */ +export interface LDDataSourceUpdates { + /** + * Completely overwrites the current contents of the data store with a set of items for each + * collection. + * + * @param allData + * An object in which each key is the "namespace" of a collection (e.g. `"features"`) and + * the value is an object that maps keys to entities. The actual type of this parameter is + * `interfaces.FullDataSet`. + * + * @param callback + * Will be called when the store has been initialized. + */ + init(allData: LDFeatureStoreDataStorage, callback: () => void): void; + + /** + * Updates or inserts an item in the specified collection. For updates, the object will only be + * updated if the existing version is less than the new version. + * + * @param kind + * The type of data to be accessed. The actual type of this parameter is + * [[interfaces.DataKind]]. + * + * @param data + * The contents of the entity, as an object that can be converted to JSON. The store + * should check the `version` property of this object, and should *not* overwrite any + * existing data if the existing `version` is greater than or equal to that value. + * The actual type of this parameter is [[interfaces.VersionedData]]. + * + * @param callback + * Will be called after the upsert operation is complete. + */ + upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void; +} diff --git a/server-sdk-common/src/api/subsystems/index.ts b/server-sdk-common/src/api/subsystems/index.ts index b6b2ca3aed..23bb2a2d39 100644 --- a/server-sdk-common/src/api/subsystems/index.ts +++ b/server-sdk-common/src/api/subsystems/index.ts @@ -1,2 +1,4 @@ export * from './LDFeatureRequestor'; export * from './LDFeatureStore'; +export * from './LDStreamProcessor'; +export * from './LDDataSourceUpdates'; diff --git a/server-sdk-common/src/data_sources/DataSourceUpdates.ts b/server-sdk-common/src/data_sources/DataSourceUpdates.ts new file mode 100644 index 0000000000..3005f33774 --- /dev/null +++ b/server-sdk-common/src/data_sources/DataSourceUpdates.ts @@ -0,0 +1,144 @@ +import { DataKind } from '../api/interfaces'; +import { + LDDataSourceUpdates, LDFeatureStore, LDFeatureStoreDataStorage, + LDFeatureStoreItem, LDKeyedFeatureStoreItem, +} from '../api/subsystems'; +import { Flag } from '../evaluation/data/Flag'; +import VersionedDataKinds from '../store/VersionedDataKinds'; +import DependencyTracker from './DependencyTracker'; +import NamespacedDataSet from './NamespacedDataSet'; + +function computeDependencies(namespace: string, item: LDFeatureStoreItem) { + const ret = new NamespacedDataSet(); + if (namespace === VersionedDataKinds.Features.namespace) { + const flag = item as Flag; + flag?.prerequisites?.forEach((prereq) => { + ret.set(namespace, prereq.key, true); + }); + flag?.rules?.forEach((rule) => { + rule.clauses?.forEach((clause) => { + if (clause.op === 'segmentMatch') { + clause.values.forEach((value) => { + ret.set(VersionedDataKinds.Segments.namespace, value, true); + }); + } + }); + }); + } + return ret; +} + +/** + * @internal + */ +export default class DataSourceUpdates implements LDDataSourceUpdates { + private readonly dependencyTracker = new DependencyTracker(); + + constructor( + private readonly featureStore: LDFeatureStore, + private readonly hasEventListeners: () => boolean, + private readonly onChange: (key: string) => void, + ) { + } + + init(allData: LDFeatureStoreDataStorage, callback: () => void): void { + const checkForChanges = this.hasEventListeners(); + const doInit = (oldData?: LDFeatureStoreDataStorage) => { + this.featureStore.init(allData, () => { + this.dependencyTracker.reset(); + + Object.entries(allData).forEach(([namespace, items]) => { + Object.keys(items || {}).forEach((key) => { + const item = items[key]; + this.dependencyTracker.updateDependenciesFrom( + namespace, + key, + computeDependencies(namespace, item), + ); + }); + }); + + if (checkForChanges) { + const updatedItems = new NamespacedDataSet(); + Object.keys(allData).forEach((namespace) => { + const oldDataForKind = oldData?.[namespace] || {}; + const newDataForKind = allData[namespace]; + const mergedData = { ...oldDataForKind, ...newDataForKind }; + Object.keys(mergedData).forEach((key) => { + this.addIfModified( + namespace, + key, + oldDataForKind && oldDataForKind[key], + newDataForKind && newDataForKind[key], + updatedItems, + ); + }); + }); + this.sendChangeEvents(updatedItems); + } + + callback?.(); + }); + }; + + if (checkForChanges) { + this.featureStore.all(VersionedDataKinds.Features, (oldFlags) => { + this.featureStore.all(VersionedDataKinds.Segments, (oldSegments) => { + const oldData = { + [VersionedDataKinds.Features.namespace]: oldFlags, + [VersionedDataKinds.Segments.namespace]: oldSegments, + }; + doInit(oldData); + }); + }); + } else { + doInit(); + } + } + + upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { + const { key } = data; + const checkForChanges = this.hasEventListeners(); + const doUpsert = (oldItem?: LDFeatureStoreItem | null) => { + this.featureStore.upsert(kind, data, () => { + this.dependencyTracker.updateDependenciesFrom( + kind.namespace, + key, + computeDependencies(kind.namespace, data), + ); + if (checkForChanges) { + const updatedItems = new NamespacedDataSet(); + this.addIfModified(kind.namespace, key, oldItem, data, updatedItems); + this.sendChangeEvents(updatedItems); + } + callback?.(); + }); + }; + if (checkForChanges) { + this.featureStore.get(kind, key, doUpsert); + } else { + doUpsert(); + } + } + + addIfModified( + namespace: string, + key: string, + oldValue: LDFeatureStoreItem | null | undefined, + newValue: LDFeatureStoreItem, + toDataSet: NamespacedDataSet, + ) { + if (newValue && oldValue && newValue.version <= oldValue.version) { + return; + } + this.dependencyTracker.updateModifiedItems(toDataSet, namespace, key); + } + + sendChangeEvents(dataSet: NamespacedDataSet) { + dataSet.enumerate((namespace, key) => { + if (namespace === VersionedDataKinds.Features.namespace) { + this.onChange(key); + } + }); + } +} diff --git a/server-sdk-common/src/data_sources/DependencyTracker.ts b/server-sdk-common/src/data_sources/DependencyTracker.ts new file mode 100644 index 0000000000..0904150dcb --- /dev/null +++ b/server-sdk-common/src/data_sources/DependencyTracker.ts @@ -0,0 +1,51 @@ +import NamespacedDataSet from './NamespacedDataSet'; + +/** + * @internal + */ +export default class DependencyTracker { + private readonly dependenciesFrom = new NamespacedDataSet>(); + + private readonly dependenciesTo = new NamespacedDataSet>(); + + updateDependenciesFrom( + namespace: string, + key: string, + newDependencySet: NamespacedDataSet, + ) { + const oldDependencySet = this.dependenciesFrom.get(namespace, key); + oldDependencySet?.enumerate((depNs, depKey) => { + const depsToThisDep = this.dependenciesTo.get(depNs, depKey); + depsToThisDep?.remove(namespace, key); + }); + + this.dependenciesFrom.set(namespace, key, newDependencySet); + newDependencySet?.enumerate((depNs, depKey) => { + let depsToThisDep = this.dependenciesTo.get(depNs, depKey); + if (!depsToThisDep) { + depsToThisDep = new NamespacedDataSet(); + this.dependenciesTo.set(depNs, depKey, depsToThisDep); + } + depsToThisDep.set(namespace, key, true); + }); + } + + updateModifiedItems( + inDependencySet: NamespacedDataSet, + modifiedNamespace: string, + modifiedKey: string, + ) { + if (!inDependencySet.get(modifiedNamespace, modifiedKey)) { + inDependencySet.set(modifiedNamespace, modifiedKey, true); + const affectedItems = this.dependenciesTo.get(modifiedNamespace, modifiedKey); + affectedItems?.enumerate((namespace, key) => { + this.updateModifiedItems(inDependencySet, namespace, key); + }); + } + } + + reset() { + this.dependenciesFrom.removeAll(); + this.dependenciesTo.removeAll(); + } +} diff --git a/server-sdk-common/src/data_sources/NamespacedDataSet.ts b/server-sdk-common/src/data_sources/NamespacedDataSet.ts new file mode 100644 index 0000000000..1b6ae6fee2 --- /dev/null +++ b/server-sdk-common/src/data_sources/NamespacedDataSet.ts @@ -0,0 +1,40 @@ +/** + * @internal + */ +export default class NamespacedDataSet { + private itemsByNamespace: Record> = {}; + + get(namespace: string, key: string): T | undefined { + return this.itemsByNamespace[namespace]?.[key]; + } + + set(namespace: string, key: string, value: T) { + if (!(namespace in this.itemsByNamespace)) { + this.itemsByNamespace[namespace] = {}; + } + this.itemsByNamespace[namespace][key] = value; + } + + remove(namespace: string, key: string) { + const items = this.itemsByNamespace[namespace]; + if (items) { + delete items[key]; + } + } + + removeAll() { + this.itemsByNamespace = {}; + } + + enumerate(callback: (namespace: string, key: string, value: T) => void) { + Object.entries(this.itemsByNamespace).forEach(([namespace, values]) => { + Object.entries(values).forEach(([key, value]) => { + callback(namespace, key, value); + }); + }); + } + + mergeFrom(other: NamespacedDataSet) { + other.enumerate(this.set.bind(this)); + } +} diff --git a/server-sdk-common/src/data_sources/PollingProcessor.ts b/server-sdk-common/src/data_sources/PollingProcessor.ts index c166675319..283ef15b39 100644 --- a/server-sdk-common/src/data_sources/PollingProcessor.ts +++ b/server-sdk-common/src/data_sources/PollingProcessor.ts @@ -1,6 +1,6 @@ import { LDLogger } from '@launchdarkly/js-sdk-common'; import { LDStreamProcessor } from '../api'; -import { LDFeatureStore } from '../api/subsystems'; +import { LDDataSourceUpdates } from '../api/subsystems'; import { isHttpRecoverable, LDPollingError } from '../errors'; import Configuration from '../options/Configuration'; import { deserializePoll } from '../store/serialization'; @@ -15,14 +15,16 @@ export default class PollingProcessor implements LDStreamProcessor { private pollInterval: number; - private featureStore: LDFeatureStore; - private timeoutHandle: any; - constructor(config: Configuration, private readonly requestor: Requestor) { + constructor( + config: Configuration, + private readonly requestor: Requestor, + private readonly featureStore: LDDataSourceUpdates, + ) { this.logger = config.logger; this.pollInterval = config.pollInterval; - this.featureStore = config.featureStore; + this.featureStore = featureStore; } private poll(fn?: ((err?: any) => void) | undefined) { diff --git a/server-sdk-common/src/data_sources/StreamingProcessor.ts b/server-sdk-common/src/data_sources/StreamingProcessor.ts index 1bfde4038f..38ccab14cf 100644 --- a/server-sdk-common/src/data_sources/StreamingProcessor.ts +++ b/server-sdk-common/src/data_sources/StreamingProcessor.ts @@ -1,6 +1,6 @@ import { LDLogger } from '@launchdarkly/js-sdk-common'; import { LDStreamProcessor } from '../api'; -import { LDFeatureStore } from '../api/subsystems'; +import { LDDataSourceUpdates } from '../api/subsystems'; import { isHttpRecoverable, LDStreamingError } from '../errors'; import Configuration from '../options/Configuration'; import { EventSource, Info, Requests } from '../platform'; @@ -31,23 +31,26 @@ export default class StreamingProcessor implements LDStreamProcessor { private requests: Requests; - private featureStore: LDFeatureStore; - private connectionAttemptStartTime?: number; - constructor(sdkKey: string, config: Configuration, requests: Requests, info: Info) { + constructor( + sdkKey: string, + config: Configuration, + requests: Requests, + info: Info, + private readonly featureStore: LDDataSourceUpdates, + ) { // TODO: Will need diagnostics manager. this.headers = defaultHeaders(sdkKey, config, info); this.logger = config.logger; this.streamInitialReconnectDelay = config.streamInitialReconnectDelay; this.requests = requests; - this.featureStore = config.featureStore; this.streamUri = `${config.serviceEndpoints.streaming}/all`; } private logConnectionStarted() { - this.connectionAttemptStartTime = new Date().getTime(); + this.connectionAttemptStartTime = Date.now(); } // TODO: Remove once the success is used for something. @@ -167,7 +170,11 @@ export default class StreamingProcessor implements LDStreamProcessor { this.logger?.debug(`Deleting ${key} in ${parsed.kind.namespace}`); // TODO: The interface didn't specify the callback was optional, // but previously it was not included here. Need to resolve. - this.featureStore.delete(parsed.kind, key, parsed.version, () => { }); + this.featureStore.upsert(parsed.kind, { + key, + version: parsed.version, + deleted: true, + }, () => {}); } } } else { diff --git a/server-sdk-common/src/evaluation/EvalResult.ts b/server-sdk-common/src/evaluation/EvalResult.ts index 83fb802350..2440c9b6a2 100644 --- a/server-sdk-common/src/evaluation/EvalResult.ts +++ b/server-sdk-common/src/evaluation/EvalResult.ts @@ -33,7 +33,7 @@ export default class EvalResult { static forError(errorKind: ErrorKinds, message?: string, def?: any): EvalResult { return new EvalResult(true, { value: def ?? null, - variationIndex: undefined, + variationIndex: null, reason: { kind: 'ERROR', errorKind }, }, message); } @@ -45,7 +45,7 @@ export default class EvalResult { ) { return new EvalResult(false, { value, - variationIndex, + variationIndex: variationIndex === undefined ? null : variationIndex, reason, }); } diff --git a/server-sdk-common/src/evaluation/Evaluator.ts b/server-sdk-common/src/evaluation/Evaluator.ts index 68f984aed2..88249b2d19 100644 --- a/server-sdk-common/src/evaluation/Evaluator.ts +++ b/server-sdk-common/src/evaluation/Evaluator.ts @@ -21,10 +21,46 @@ import { SegmentRule } from './data/SegmentRule'; import { Clause } from './data/Clause'; import EventFactory from '../events/EventFactory'; import InputEvalEvent from '../events/InputEvalEvent'; +import { BigSegmentStoreMembership } from '../api/interfaces'; +import makeBigSegmentRef from './makeBigSegmentRef'; + +type BigSegmentStoreStatusString = 'HEALTHY' | 'STALE' | 'STORE_ERROR' | 'NOT_CONFIGURED'; + +const bigSegmentsStatusPriority: Record = { + HEALTHY: 1, + STALE: 2, + STORE_ERROR: 3, + NOT_CONFIGURED: 4, +}; + +function getBigSegmentsStatusPriority(status?: BigSegmentStoreStatusString) { + if (status !== undefined) { + return bigSegmentsStatusPriority[status] || 0; + } + return 0; +} + +/** + * Given two big segment statuses return the one with the higher priority. + * @returns The status with the higher priority. + */ +function computeUpdatedBigSegmentsStatus( + old?: BigSegmentStoreStatusString, + latest?: BigSegmentStoreStatusString, +): BigSegmentStoreStatusString | undefined { + if (old !== undefined + && getBigSegmentsStatusPriority(old) > getBigSegmentsStatusPriority(latest)) { + return old; + } + return latest; +} class EvalState { events?: InputEvalEvent[]; - // bigSegmentsStatus + + bigSegmentsStatus?: BigSegmentStoreStatusString; + + bigSegmentsMembership?: Record; } class Match { @@ -66,6 +102,9 @@ export default class Evaluator { async evaluate(flag: Flag, context: Context, eventFactory?: EventFactory): Promise { const state = new EvalState(); const res = await this.evaluateInternal(flag, context, state, [], eventFactory); + if (state.bigSegmentsStatus) { + res.detail.reason.bigSegmentsStatus = state.bigSegmentsStatus; + } res.events = state.events; return res; } @@ -287,8 +326,6 @@ export default class Evaluator { rule: FlagRule, ruleIndex: number, context: Context, - // TODO: Will be used once big segments are implemented. - // eslint-disable-next-line @typescript-eslint/no-unused-vars state: EvalState, segmentsVisited: string[], ): Promise { @@ -460,7 +497,80 @@ export default class Evaluator { return this.simpleSegmentMatchContext(segment, context, state, segmentsVisited); } - // TODO: Big segments. - return new Match(false); + if (!segment.generation) { + // Big Segment queries can only be done if the generation is known. If it's unset, + // that probably means the data store was populated by an older SDK that doesn't know + // about the generation property and therefore dropped it from the JSON data. We'll treat + // that as a "not configured" condition. + // eslint-disable-next-line no-param-reassign + state.bigSegmentsStatus = computeUpdatedBigSegmentsStatus( + state.bigSegmentsStatus, + 'NOT_CONFIGURED', + ); + return new Match(false); + } + + const bigSegmentKind = segment.unboundedContextKind || 'user'; + const keyForBigSegment = context.key(bigSegmentKind); + + if (keyForBigSegment === undefined) { + return new Match(false); + } + + if (state.bigSegmentsMembership && state.bigSegmentsMembership[keyForBigSegment]) { + // We've already done the query at some point during the flag evaluation and stored + // the result (if any) in stateOut.bigSegmentsMembership, so we don't need to do it + // again. Even if multiple Big Segments are being referenced, the membership includes + // *all* of the user's segment memberships. + + return this.bigSegmentMatchContext( + state.bigSegmentsMembership[keyForBigSegment], + segment, + context, + state, + ); + } + + const result = await this.queries.getBigSegmentsMembership(keyForBigSegment); + + // eslint-disable-next-line no-param-reassign + state.bigSegmentsMembership = state.bigSegmentsMembership || {}; + if (result) { + const [membership, status] = result; + // eslint-disable-next-line no-param-reassign + state.bigSegmentsMembership[keyForBigSegment] = membership; + // eslint-disable-next-line no-param-reassign + state.bigSegmentsStatus = computeUpdatedBigSegmentsStatus( + state.bigSegmentsStatus, + status as BigSegmentStoreStatusString, + ); + } else { + // eslint-disable-next-line no-param-reassign + state.bigSegmentsStatus = computeUpdatedBigSegmentsStatus( + state.bigSegmentsStatus, + 'NOT_CONFIGURED', + ); + } + /* eslint-enable no-param-reassign */ + return this.bigSegmentMatchContext( + state.bigSegmentsMembership[keyForBigSegment], + segment, + context, + state, + ); + } + + async bigSegmentMatchContext( + membership: BigSegmentStoreMembership | null, + segment: Segment, + context: Context, + state: EvalState, + ): Promise { + const segmentRef = makeBigSegmentRef(segment); + const included = membership?.[segmentRef]; + if (included) { + return new Match(true); + } + return this.simpleSegmentMatchContext(segment, context, state, []); } } diff --git a/server-sdk-common/src/evaluation/Queries.ts b/server-sdk-common/src/evaluation/Queries.ts index 454eab1689..2e2a09e574 100644 --- a/server-sdk-common/src/evaluation/Queries.ts +++ b/server-sdk-common/src/evaluation/Queries.ts @@ -11,5 +11,6 @@ import { Segment } from './data/Segment'; export interface Queries { getFlag(key: string): Promise getSegment(key: string): Promise - getBigSegmentsMembership(userKey: string): Promise + getBigSegmentsMembership(userKey: string): + Promise<[BigSegmentStoreMembership | null, string] | undefined> } diff --git a/server-sdk-common/src/evaluation/makeBigSegmentRef.ts b/server-sdk-common/src/evaluation/makeBigSegmentRef.ts new file mode 100644 index 0000000000..ac4d7624fc --- /dev/null +++ b/server-sdk-common/src/evaluation/makeBigSegmentRef.ts @@ -0,0 +1,11 @@ +import { Segment } from './data/Segment'; + +/** + * @internal + */ +export default function makeBigSegmentRef(segment: Segment): string { + // The format of Big Segment references is independent of what store implementation is being + // used; the store implementation receives only this string and does not know the details of + // the data model. The Relay Proxy will use the same format when writing to the store. + return `${segment.key}.g${segment.generation}`; +} diff --git a/server-sdk-common/src/events/InputEvalEvent.ts b/server-sdk-common/src/events/InputEvalEvent.ts index abded916c8..ccb9564132 100644 --- a/server-sdk-common/src/events/InputEvalEvent.ts +++ b/server-sdk-common/src/events/InputEvalEvent.ts @@ -41,7 +41,7 @@ export default class InputEvalEvent { this.creationDate = Date.now(); this.context = context; this.default = defValue; - this.variation = detail.variationIndex; + this.variation = detail.variationIndex ?? undefined; this.value = detail.value; if (flag) { diff --git a/server-sdk-common/src/index.ts b/server-sdk-common/src/index.ts index fa40083067..e90a9e4bf0 100644 --- a/server-sdk-common/src/index.ts +++ b/server-sdk-common/src/index.ts @@ -1,6 +1,7 @@ import LDClientImpl from './LDClientImpl'; import BigSegmentStoreStatusProviderImpl from './BigSegmentStatusProviderImpl'; +export * as integrations from './integrations'; export * as platform from './platform'; export * from './api'; export * from '@launchdarkly/js-sdk-common'; diff --git a/server-sdk-common/src/integrations/FileDataSourceFactory.ts b/server-sdk-common/src/integrations/FileDataSourceFactory.ts new file mode 100644 index 0000000000..56c043810c --- /dev/null +++ b/server-sdk-common/src/integrations/FileDataSourceFactory.ts @@ -0,0 +1,52 @@ +import { LDLogger } from '@launchdarkly/js-sdk-common'; +import { FileDataSourceOptions } from '../api/integrations'; +import { LDClientContext } from '../api/options/LDClientContext'; +import { LDFeatureStore, LDStreamProcessor } from '../api/subsystems'; +import FileDataSource from '../data_sources/FileDataSource'; + +/** + * Components of the SDK runtime configuration which are required + * by the FileDataSource. + */ +export interface FileDataSourceFactoryConfig { + featureStore: LDFeatureStore, + logger?: LDLogger +} + +/** + * Class for creating file data sources. + */ + +export default class FileDataSourceFactory { + constructor(private readonly options: FileDataSourceOptions) { } + + /** + * Method for creating instances of the file data source. This method is intended to be used + * by the SDK. + * + * @param config SDK configuration required by the file data source. + * @param filesystem Platform abstraction used for filesystem access. + * @returns a {@link FileDataSource} + * + * @internal + */ + create( + ldClientContext: LDClientContext, + featureStore: LDFeatureStore, + ) { + const updatedOptions: FileDataSourceOptions = { + paths: this.options.paths, + autoUpdate: this.options.autoUpdate, + logger: this.options.logger || ldClientContext.basicConfiguration.logger, + yamlParser: this.options.yamlParser, + }; + return new FileDataSource(updatedOptions, ldClientContext.platform.fileSystem!, featureStore); + } + + getFactory(): ( + ldClientContext: LDClientContext, + featureStore: LDFeatureStore, + ) => LDStreamProcessor { + return (ldClientContext, featureStore) => this.create(ldClientContext, featureStore); + } +} diff --git a/server-sdk-common/src/integrations/index.ts b/server-sdk-common/src/integrations/index.ts new file mode 100644 index 0000000000..57ee25ce9f --- /dev/null +++ b/server-sdk-common/src/integrations/index.ts @@ -0,0 +1,6 @@ +import FileDataSourceFactory from './FileDataSourceFactory'; + +export * from './test_data'; +export { + FileDataSourceFactory, +}; diff --git a/server-sdk-common/src/integrations/test_data/TestData.ts b/server-sdk-common/src/integrations/test_data/TestData.ts index c1a44e3489..b3f8903c79 100644 --- a/server-sdk-common/src/integrations/test_data/TestData.ts +++ b/server-sdk-common/src/integrations/test_data/TestData.ts @@ -1,7 +1,8 @@ import { LDStreamProcessor } from '../../api'; +import { LDClientContext } from '../../api/options/LDClientContext'; +import { LDFeatureStore } from '../../api/subsystems'; import { Flag } from '../../evaluation/data/Flag'; import { Segment } from '../../evaluation/data/Segment'; -import Configuration from '../../options/Configuration'; import AsyncStoreFacade from '../../store/AsyncStoreFacade'; import { processFlag, processSegment } from '../../store/serialization'; import VersionedDataKinds from '../../store/VersionedDataKinds'; @@ -54,12 +55,19 @@ export default class TestData { * Get a factory for update processors that will be attached to this TestData instance. * @returns An update processor factory. */ - getFactory(): (config: Configuration) => LDStreamProcessor { + getFactory(): ( + clientContext: LDClientContext, + featureStore: LDFeatureStore + ) => LDStreamProcessor { // Provides an arrow function to prevent needed to bind the method to // maintain `this`. - return (config: Configuration) => { + return ( + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + clientContext: LDClientContext, + featureStore: LDFeatureStore, + ) => { const newSource = new TestDataSource( - new AsyncStoreFacade(config.featureStore), + new AsyncStoreFacade(featureStore), this.currentFlags, this.currentSegments, diff --git a/server-sdk-common/src/integrations/test_data/index.ts b/server-sdk-common/src/integrations/test_data/index.ts new file mode 100644 index 0000000000..8f8e9663a0 --- /dev/null +++ b/server-sdk-common/src/integrations/test_data/index.ts @@ -0,0 +1,9 @@ +import TestData from './TestData'; +import TestDataFlagBuilder from './TestDataFlagBuilder'; +import TestDataRuleBuilder from './TestDataRuleBuilder'; + +export { + TestData, + TestDataFlagBuilder, + TestDataRuleBuilder, +}; diff --git a/server-sdk-common/src/options/Configuration.ts b/server-sdk-common/src/options/Configuration.ts index 30602bcf7c..f6d66cac75 100644 --- a/server-sdk-common/src/options/Configuration.ts +++ b/server-sdk-common/src/options/Configuration.ts @@ -5,7 +5,8 @@ import { LDBigSegmentsOptions, LDOptions, LDProxyOptions, LDStreamProcessor, LDTLSOptions, } from '../api'; -import { LDFeatureStore } from '../api/subsystems'; +import { LDClientContext } from '../api/options/LDClientContext'; +import { LDDataSourceUpdates, LDFeatureStore } from '../api/subsystems'; import InMemoryFeatureStore from '../store/InMemoryFeatureStore'; import ApplicationTags from './ApplicationTags'; import OptionMessages from './OptionMessages'; @@ -30,7 +31,7 @@ const validations: Record = { logger: TypeValidators.Object, featureStore: TypeValidators.ObjectOrFactory, bigSegments: TypeValidators.Object, - updateProcessor: TypeValidators.Object, + updateProcessor: TypeValidators.ObjectOrFactory, flushInterval: TypeValidators.Number, pollInterval: TypeValidators.numberWithMin(30), proxyOptions: TypeValidators.Object, @@ -189,9 +190,14 @@ export default class Configuration { public readonly diagnosticRecordingInterval: number; - public readonly featureStore: LDFeatureStore; + // public readonly featureStore: LDFeatureStore; - public readonly updateProcessor?: LDStreamProcessor; + public readonly featureStoreFactory: ((clientContext: LDClientContext) => LDFeatureStore); + + // public readonly updateProcessor?: LDStreamProcessor; + + public readonly updateProcessorFactory?: + ((clientContext: LDClientContext, dataSourceUpdates: LDDataSourceUpdates) => LDStreamProcessor); public readonly bigSegments?: LDBigSegmentsOptions; @@ -219,7 +225,6 @@ export default class Configuration { this.timeout = validatedOptions.timeout; this.bigSegments = validatedOptions.bigSegments; - this.updateProcessor = validatedOptions.updateProcessor; this.flushInterval = validatedOptions.flushInterval; this.pollInterval = validatedOptions.pollInterval; this.proxyOptions = validatedOptions.proxyOptions; @@ -240,12 +245,22 @@ export default class Configuration { this.tags = new ApplicationTags(validatedOptions); this.diagnosticRecordingInterval = validatedOptions.diagnosticRecordingInterval; + if (TypeValidators.Function.is(validatedOptions.updateProcessor)) { + // @ts-ignore + this.updateProcessorFactory = validatedOptions.updateProcessor; + } else { + // The processor is already created, just have the method return it. + // @ts-ignore + this.updateProcessorFactory = () => validatedOptions.updateProcessor; + } + if (TypeValidators.Function.is(validatedOptions.featureStore)) { // @ts-ignore - this.featureStore = validatedOptions.featureStore(this); + this.featureStoreFactory = validatedOptions.featureStore; } else { + // The store is already created, just have the method return it. // @ts-ignore - this.featureStore = validatedOptions.featureStore; + this.featureStoreFactory = () => validatedOptions.featureStore; } } } diff --git a/server-sdk-common/src/store/InMemoryFeatureStore.ts b/server-sdk-common/src/store/InMemoryFeatureStore.ts index 3aeabf3d1a..c00ff5af13 100644 --- a/server-sdk-common/src/store/InMemoryFeatureStore.ts +++ b/server-sdk-common/src/store/InMemoryFeatureStore.ts @@ -7,16 +7,6 @@ import { LDFeatureStoreItem, } from '../api/subsystems'; -/** - * Clone an object using JSON. This will not preserve - * non-JSON types (like functions). - * @param obj - * @returns A clone of the object. - */ -function clone(obj: any): any { - return JSON.parse(JSON.stringify(obj)); -} - export default class InMemoryFeatureStore implements LDFeatureStore { private allData: LDFeatureStoreDataStorage = {}; @@ -75,8 +65,7 @@ export default class InMemoryFeatureStore implements LDFeatureStore { } upsert(kind: DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void { - const item = clone(data); - this.addItem(kind, item.key, item); + this.addItem(kind, data.key, data); callback?.(); } From eeb064e6736e94315e957616a809fff250e3b3c5 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 14:02:43 -0700 Subject: [PATCH 28/34] Remove the logging destination from node level tests. --- platform-node/__tests__/LDClientNode.bigSegments.test.ts | 9 +++++++-- platform-node/__tests__/LDClientNode.tls.test.ts | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/platform-node/__tests__/LDClientNode.bigSegments.test.ts b/platform-node/__tests__/LDClientNode.bigSegments.test.ts index e69d1fe9cf..a2923ed2f6 100644 --- a/platform-node/__tests__/LDClientNode.bigSegments.test.ts +++ b/platform-node/__tests__/LDClientNode.bigSegments.test.ts @@ -1,14 +1,18 @@ -import { integrations, interfaces, LDBigSegmentsOptions } from '@launchdarkly/js-server-sdk-common'; -import { LDClientImpl } from '../src'; +import { integrations, interfaces, LDBigSegmentsOptions, LDLogger } from '@launchdarkly/js-server-sdk-common'; +import { basicLogger, LDClientImpl } from '../src'; import { LDClient } from '../src/api/LDClient'; describe('given test data with big segments', () => { // To use the public interfaces to create a client which doesn't use the // network. (Versus being offline, or a null update processor.) let td: integrations.TestData; + let logger: LDLogger; beforeEach(() => { td = new integrations.TestData(); + logger = basicLogger({ + destination: () => {} + }); }); describe('given a healthy big segment store', () => { @@ -118,6 +122,7 @@ describe('given test data with big segments', () => { updateProcessor: td.getFactory(), sendEvents: false, bigSegments: bigSegmentsConfig, + logger, }); await client.waitForInitialization(); diff --git a/platform-node/__tests__/LDClientNode.tls.test.ts b/platform-node/__tests__/LDClientNode.tls.test.ts index 92585f7b51..56d14ae584 100644 --- a/platform-node/__tests__/LDClientNode.tls.test.ts +++ b/platform-node/__tests__/LDClientNode.tls.test.ts @@ -15,7 +15,9 @@ describe('', () => { let logger: LDLogger; beforeEach(() => { - logger = basicLogger({}); + logger = basicLogger({ + destination: () => {} + }); }); it( From 22661fe9a30b0d0f06e4dc3f676ef497839884f6 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 14:35:33 -0700 Subject: [PATCH 29/34] Diagnostic events error handling. --- server-sdk-common/src/events/EventProcessor.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server-sdk-common/src/events/EventProcessor.ts b/server-sdk-common/src/events/EventProcessor.ts index 87770b7102..6d086ad529 100644 --- a/server-sdk-common/src/events/EventProcessor.ts +++ b/server-sdk-common/src/events/EventProcessor.ts @@ -137,7 +137,7 @@ export default class EventProcessor implements LDEventProcessor { if (this.diagnosticsManager) { const initEvent = diagnosticsManager!.createInitEvent(); - this.tryPostingEvents(initEvent, this.diagnosticEventsUri, undefined, true); + this.postDiagnosticEvent(initEvent); this.diagnosticsTimer = setInterval(() => { const statsEvent = this.diagnosticsManager!.createStatsEventAndReset( @@ -149,11 +149,15 @@ export default class EventProcessor implements LDEventProcessor { this.droppedEvents = 0; this.deduplicatedUsers = 0; - this.tryPostingEvents(statsEvent, this.diagnosticEventsUri, undefined, true); + this.postDiagnosticEvent(statsEvent); }, config.diagnosticRecordingInterval * 1000); } } + private postDiagnosticEvent(event: DiagnosticInitEvent | DiagnosticStatsEvent) { + this.tryPostingEvents(event, this.diagnosticEventsUri, undefined, true).catch(() => {}); + } + close() { clearInterval(this.flushTimer); clearInterval(this.flushUsersTimer); From d7983254b6b45dbadff7274d33b2927739249723 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 15:35:38 -0700 Subject: [PATCH 30/34] Make file for proxy tests. --- .../__tests__/LDClientNode.proxy.test.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 platform-node/__tests__/LDClientNode.proxy.test.ts diff --git a/platform-node/__tests__/LDClientNode.proxy.test.ts b/platform-node/__tests__/LDClientNode.proxy.test.ts new file mode 100644 index 0000000000..2aebbf34ed --- /dev/null +++ b/platform-node/__tests__/LDClientNode.proxy.test.ts @@ -0,0 +1,27 @@ +import { + AsyncQueue, + sleepAsync, + SSEItem, + TestHttpHandlers, + TestHttpServer, +} from 'launchdarkly-js-test-helpers'; +import { basicLogger, LDClient, LDLogger } from '../src'; + +import LDClientNode from '../src/LDClientNode'; + +describe('', () => { + let client: LDClient; + let server: TestHttpServer; + let logger: LDLogger; + + beforeEach(() => { + logger = basicLogger({ + destination: () => {} + }); + }); + + afterEach(() => { + client.close(); + server.close(); + }); +}); From 53707d4b4cffd1f88b34a1ea4393bfbc5379a982 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 15:36:11 -0700 Subject: [PATCH 31/34] Add missing describe name. --- platform-node/__tests__/LDClientNode.tls.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-node/__tests__/LDClientNode.tls.test.ts b/platform-node/__tests__/LDClientNode.tls.test.ts index 56d14ae584..e0d952504f 100644 --- a/platform-node/__tests__/LDClientNode.tls.test.ts +++ b/platform-node/__tests__/LDClientNode.tls.test.ts @@ -9,7 +9,7 @@ import { basicLogger, LDClient, LDLogger } from '../src'; import LDClientNode from '../src/LDClientNode'; -describe('', () => { +describe('When using a TLS connection', () => { let client: LDClient; let server: TestHttpServer; let logger: LDLogger; From 1cddc75a071b700a16d767d0b1f5047754df3f9c Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 16:08:03 -0700 Subject: [PATCH 32/34] Add proxy test, fix import issue. --- .../LDClientNode.bigSegments.test.ts | 6 +- .../__tests__/LDClientNode.proxy.test.ts | 114 ++++++++++++++++-- .../__tests__/LDClientNode.tls.test.ts | 2 +- .../__tests__/platform/HeaderWrapper.test.ts | 16 +-- platform-node/src/platform/NodeRequests.ts | 3 +- 5 files changed, 122 insertions(+), 19 deletions(-) diff --git a/platform-node/__tests__/LDClientNode.bigSegments.test.ts b/platform-node/__tests__/LDClientNode.bigSegments.test.ts index a2923ed2f6..109820b96e 100644 --- a/platform-node/__tests__/LDClientNode.bigSegments.test.ts +++ b/platform-node/__tests__/LDClientNode.bigSegments.test.ts @@ -1,4 +1,6 @@ -import { integrations, interfaces, LDBigSegmentsOptions, LDLogger } from '@launchdarkly/js-server-sdk-common'; +import { + integrations, interfaces, LDBigSegmentsOptions, LDLogger, +} from '@launchdarkly/js-server-sdk-common'; import { basicLogger, LDClientImpl } from '../src'; import { LDClient } from '../src/api/LDClient'; @@ -11,7 +13,7 @@ describe('given test data with big segments', () => { beforeEach(() => { td = new integrations.TestData(); logger = basicLogger({ - destination: () => {} + destination: () => {}, }); }); diff --git a/platform-node/__tests__/LDClientNode.proxy.test.ts b/platform-node/__tests__/LDClientNode.proxy.test.ts index 2aebbf34ed..22e553baab 100644 --- a/platform-node/__tests__/LDClientNode.proxy.test.ts +++ b/platform-node/__tests__/LDClientNode.proxy.test.ts @@ -1,27 +1,127 @@ import { AsyncQueue, - sleepAsync, SSEItem, TestHttpHandlers, TestHttpServer, } from 'launchdarkly-js-test-helpers'; -import { basicLogger, LDClient, LDLogger } from '../src'; +import { basicLogger, LDLogger } from '../src'; import LDClientNode from '../src/LDClientNode'; +const sdkKey = 'sdkKey'; +const flagKey = 'flagKey'; +const expectedFlagValue = 'yes'; +const flag = { + key: flagKey, + version: 1, + on: false, + offVariation: 0, + variations: [expectedFlagValue, 'no'], +}; +const allData = { flags: { flagKey: flag }, segments: {} }; + describe('', () => { - let client: LDClient; - let server: TestHttpServer; let logger: LDLogger; + let closeable: { close: () => void }[]; beforeEach(() => { + closeable = []; logger = basicLogger({ - destination: () => {} + destination: () => { }, }); }); afterEach(() => { - client.close(); - server.close(); + closeable.forEach((item) => item.close()); + }); + + it('can use proxy in polling mode', async () => { + const proxy = await TestHttpServer.startProxy(); + const server = await TestHttpServer.start(); + server.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson(allData)); + + const client = new LDClientNode(sdkKey, { + baseUri: server.url, + proxyOptions: { + host: proxy.hostname, + port: proxy.port, + }, + stream: false, + sendEvents: false, + logger, + }); + + closeable.push(proxy, server, client); + + await client.waitForInitialization(); + expect(client.initialized()).toBe(true); + + // If the proxy server did not log a request then the SDK did not actually use the proxy + expect(proxy.requestCount()).toEqual(1); + const req = await proxy.nextRequest(); + expect(req.path).toEqual(server.url); + }); + + it('can use proxy in streaming mode', async () => { + const proxy = await TestHttpServer.startProxy(); + const server = await TestHttpServer.start(); + const events = new AsyncQueue(); + events.add({ type: 'put', data: JSON.stringify({ data: allData }) }); + server.forMethodAndPath('get', '/all', TestHttpHandlers.sseStream(events)); + + const client = new LDClientNode(sdkKey, { + streamUri: server.url, + proxyOptions: { + host: proxy.hostname, + port: proxy.port, + }, + sendEvents: false, + logger, + }); + + closeable.push(proxy, server, events, client); + + await client.waitForInitialization(); + expect(client.initialized()).toBe(true); + + // If the proxy server did not log a request then the SDK did not actually use the proxy + expect(proxy.requestCount()).toEqual(1); + const req = await proxy.nextRequest(); + expect(req.path).toEqual(server.url); + }); + + it('can use proxy for events', async () => { + const proxy = await TestHttpServer.startProxy(); + const pollingServer = await TestHttpServer.start(); + const eventsServer = await TestHttpServer.start(); + pollingServer.forMethodAndPath('get', '/sdk/latest-all', TestHttpHandlers.respondJson(allData)); + eventsServer.forMethodAndPath('post', '/diagnostic', TestHttpHandlers.respond(200)); + + const client = new LDClientNode(sdkKey, { + baseUri: pollingServer.url, + eventsUri: eventsServer.url, + proxyOptions: { + host: proxy.hostname, + port: proxy.port, + }, + stream: false, + logger, + }); + + closeable.push(proxy, pollingServer, eventsServer, client); + + await client.waitForInitialization(); + expect(client.initialized()).toBe(true); + + // If the proxy server did not log a request then the SDK did not actually use the proxy + expect(proxy.requestCount()).toEqual(2); + const req0 = await proxy.nextRequest(); + const req1 = await proxy.nextRequest(); + if (req0.path === pollingServer.url) { + expect(req1.path).toEqual(eventsServer.url); + } else { + expect(req0.path).toEqual(eventsServer.url); + expect(req1.path).toEqual(pollingServer.url); + } }); }); diff --git a/platform-node/__tests__/LDClientNode.tls.test.ts b/platform-node/__tests__/LDClientNode.tls.test.ts index 56d14ae584..f31e4866b1 100644 --- a/platform-node/__tests__/LDClientNode.tls.test.ts +++ b/platform-node/__tests__/LDClientNode.tls.test.ts @@ -16,7 +16,7 @@ describe('', () => { beforeEach(() => { logger = basicLogger({ - destination: () => {} + destination: () => {}, }); }); diff --git a/platform-node/__tests__/platform/HeaderWrapper.test.ts b/platform-node/__tests__/platform/HeaderWrapper.test.ts index 295310de6e..3b44045a06 100644 --- a/platform-node/__tests__/platform/HeaderWrapper.test.ts +++ b/platform-node/__tests__/platform/HeaderWrapper.test.ts @@ -3,9 +3,9 @@ import HeaderWrapper from '../../src/platform/HeaderWrapper'; describe('given header values', () => { const headers: http.IncomingHttpHeaders = { - 'accept': 'anything', + accept: 'anything', 'some-header': 'some-value', - 'some-array': ['a', 'b'] + 'some-array': ['a', 'b'], }; const wrapper = new HeaderWrapper(headers); @@ -19,13 +19,13 @@ describe('given header values', () => { it('can get the entries', () => { const flat = []; - for(const entry of wrapper.entries()) { + for (const entry of wrapper.entries()) { flat.push(entry); } expect(flat).toEqual([ ['accept', 'anything'], ['some-header', 'some-value'], - ['some-array', 'a, b'] + ['some-array', 'a, b'], ]); }); @@ -36,25 +36,25 @@ describe('given header values', () => { it('can key the keys', () => { const keys = []; - for(const key of wrapper.keys()) { + for (const key of wrapper.keys()) { keys.push(key); } expect(keys).toEqual([ 'accept', 'some-header', - 'some-array' + 'some-array', ]); }); it('can key the values', () => { const values = []; - for(const value of wrapper.values()) { + for (const value of wrapper.values()) { values.push(value); } expect(values).toEqual([ 'anything', 'some-value', - 'a, b' + 'a, b', ]); }); }); diff --git a/platform-node/src/platform/NodeRequests.ts b/platform-node/src/platform/NodeRequests.ts index 33d15bfc05..6ee0e2dbd8 100644 --- a/platform-node/src/platform/NodeRequests.ts +++ b/platform-node/src/platform/NodeRequests.ts @@ -1,4 +1,5 @@ -import createHttpsProxyAgent, { HttpsProxyAgentOptions } from 'https-proxy-agent'; +import * as createHttpsProxyAgent from 'https-proxy-agent'; +import { HttpsProxyAgentOptions } from 'https-proxy-agent'; import { platform, LDTLSOptions, LDProxyOptions } from '@launchdarkly/js-server-sdk-common'; From 745ea4b31803d242b01ada701234cfeddb74f87c Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Wed, 17 Aug 2022 16:09:31 -0700 Subject: [PATCH 33/34] Linting --- .../__tests__/LDClientNode.bigSegments.test.ts | 6 ++++-- .../__tests__/LDClientNode.tls.test.ts | 2 +- .../__tests__/platform/HeaderWrapper.test.ts | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/platform-node/__tests__/LDClientNode.bigSegments.test.ts b/platform-node/__tests__/LDClientNode.bigSegments.test.ts index a2923ed2f6..109820b96e 100644 --- a/platform-node/__tests__/LDClientNode.bigSegments.test.ts +++ b/platform-node/__tests__/LDClientNode.bigSegments.test.ts @@ -1,4 +1,6 @@ -import { integrations, interfaces, LDBigSegmentsOptions, LDLogger } from '@launchdarkly/js-server-sdk-common'; +import { + integrations, interfaces, LDBigSegmentsOptions, LDLogger, +} from '@launchdarkly/js-server-sdk-common'; import { basicLogger, LDClientImpl } from '../src'; import { LDClient } from '../src/api/LDClient'; @@ -11,7 +13,7 @@ describe('given test data with big segments', () => { beforeEach(() => { td = new integrations.TestData(); logger = basicLogger({ - destination: () => {} + destination: () => {}, }); }); diff --git a/platform-node/__tests__/LDClientNode.tls.test.ts b/platform-node/__tests__/LDClientNode.tls.test.ts index e0d952504f..635a62dee9 100644 --- a/platform-node/__tests__/LDClientNode.tls.test.ts +++ b/platform-node/__tests__/LDClientNode.tls.test.ts @@ -16,7 +16,7 @@ describe('When using a TLS connection', () => { beforeEach(() => { logger = basicLogger({ - destination: () => {} + destination: () => {}, }); }); diff --git a/platform-node/__tests__/platform/HeaderWrapper.test.ts b/platform-node/__tests__/platform/HeaderWrapper.test.ts index 295310de6e..c0d6a71ec9 100644 --- a/platform-node/__tests__/platform/HeaderWrapper.test.ts +++ b/platform-node/__tests__/platform/HeaderWrapper.test.ts @@ -1,11 +1,13 @@ +/* eslint-disable no-restricted-syntax */ +// The header interface uses generators, so we are using restricted-syntax. import * as http from 'http'; import HeaderWrapper from '../../src/platform/HeaderWrapper'; describe('given header values', () => { const headers: http.IncomingHttpHeaders = { - 'accept': 'anything', + accept: 'anything', 'some-header': 'some-value', - 'some-array': ['a', 'b'] + 'some-array': ['a', 'b'], }; const wrapper = new HeaderWrapper(headers); @@ -19,13 +21,13 @@ describe('given header values', () => { it('can get the entries', () => { const flat = []; - for(const entry of wrapper.entries()) { + for (const entry of wrapper.entries()) { flat.push(entry); } expect(flat).toEqual([ ['accept', 'anything'], ['some-header', 'some-value'], - ['some-array', 'a, b'] + ['some-array', 'a, b'], ]); }); @@ -36,25 +38,25 @@ describe('given header values', () => { it('can key the keys', () => { const keys = []; - for(const key of wrapper.keys()) { + for (const key of wrapper.keys()) { keys.push(key); } expect(keys).toEqual([ 'accept', 'some-header', - 'some-array' + 'some-array', ]); }); it('can key the values', () => { const values = []; - for(const value of wrapper.values()) { + for (const value of wrapper.values()) { values.push(value); } expect(values).toEqual([ 'anything', 'some-value', - 'a, b' + 'a, b', ]); }); }); From 9676c024f3a301e2046b93f76bffa423b7145d16 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 23 Aug 2022 10:50:21 -0700 Subject: [PATCH 34/34] Name describe for proxy tests. --- platform-node/__tests__/LDClientNode.proxy.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-node/__tests__/LDClientNode.proxy.test.ts b/platform-node/__tests__/LDClientNode.proxy.test.ts index 22e553baab..bd764d5901 100644 --- a/platform-node/__tests__/LDClientNode.proxy.test.ts +++ b/platform-node/__tests__/LDClientNode.proxy.test.ts @@ -20,7 +20,7 @@ const flag = { }; const allData = { flags: { flagKey: flag }, segments: {} }; -describe('', () => { +describe('When using a proxy', () => { let logger: LDLogger; let closeable: { close: () => void }[];