@configdirector/nextjs-sdk 1.9.2 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/client/index.cjs +1 -1
  2. package/dist/client/index.d.cts +21 -8
  3. package/dist/client/index.d.cts.map +1 -1
  4. package/dist/client/index.d.mts +21 -8
  5. package/dist/client/index.d.mts.map +1 -1
  6. package/dist/client/index.mjs +1 -1
  7. package/dist/client/index.mjs.map +1 -1
  8. package/dist/client/installedTestClient-BhHUzJ9D.cjs +6 -0
  9. package/dist/client/installedTestClient-BwTUOOhP.mjs +7 -0
  10. package/dist/client/installedTestClient-BwTUOOhP.mjs.map +1 -0
  11. package/dist/client/testing.cjs +1 -1
  12. package/dist/client/testing.d.cts +7 -15
  13. package/dist/client/testing.d.cts.map +1 -1
  14. package/dist/client/testing.d.mts +7 -15
  15. package/dist/client/testing.d.mts.map +1 -1
  16. package/dist/client/testing.mjs +1 -1
  17. package/dist/client/testing.mjs.map +1 -1
  18. package/dist/client/{types-fL7X5zwg.d.cts → types-2Z0rA7lF.d.cts} +57 -16
  19. package/dist/client/types-2Z0rA7lF.d.cts.map +1 -0
  20. package/dist/client/{types-DA2VZKNX.d.mts → types-6a1uEkD0.d.mts} +57 -16
  21. package/dist/client/types-6a1uEkD0.d.mts.map +1 -0
  22. package/dist/server/DefaultConfigDirectorClient-BplPvnEk.mjs +6 -0
  23. package/dist/server/DefaultConfigDirectorClient-BplPvnEk.mjs.map +1 -0
  24. package/dist/server/DefaultConfigDirectorClient-oT53aH0F.cjs +5 -0
  25. package/dist/server/{types-DnzM2ukj.d.cts → errors-EfmnEmll.d.cts} +130 -47
  26. package/dist/server/errors-EfmnEmll.d.cts.map +1 -0
  27. package/dist/server/{types-qIeNbtNf.d.mts → errors-r1MHQ8L5.d.mts} +130 -47
  28. package/dist/server/errors-r1MHQ8L5.d.mts.map +1 -0
  29. package/dist/server/index.cjs +1 -1
  30. package/dist/server/index.d.cts +44 -9
  31. package/dist/server/index.d.cts.map +1 -1
  32. package/dist/server/index.d.mts +44 -9
  33. package/dist/server/index.d.mts.map +1 -1
  34. package/dist/server/index.mjs +1 -1
  35. package/dist/server/index.mjs.map +1 -1
  36. package/dist/server/native-values-CfqV9hAq.mjs +2 -0
  37. package/dist/server/native-values-CfqV9hAq.mjs.map +1 -0
  38. package/dist/server/native-values-UCK4hh1Z.cjs +1 -0
  39. package/dist/server/testing.cjs +1 -1
  40. package/dist/server/testing.d.cts +8 -26
  41. package/dist/server/testing.d.cts.map +1 -1
  42. package/dist/server/testing.d.mts +8 -26
  43. package/dist/server/testing.d.mts.map +1 -1
  44. package/dist/server/testing.mjs +1 -1
  45. package/dist/server/testing.mjs.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/client/installedTestClient-CDdDsfVQ.mjs +0 -7
  48. package/dist/client/installedTestClient-CDdDsfVQ.mjs.map +0 -1
  49. package/dist/client/installedTestClient-DH7rRR-J.cjs +0 -6
  50. package/dist/client/types-DA2VZKNX.d.mts.map +0 -1
  51. package/dist/client/types-fL7X5zwg.d.cts.map +0 -1
  52. package/dist/server/DefaultConfigDirectorClient-BZwAj0iJ.cjs +0 -5
  53. package/dist/server/DefaultConfigDirectorClient-DyFVzSHP.mjs +0 -6
  54. package/dist/server/DefaultConfigDirectorClient-DyFVzSHP.mjs.map +0 -1
  55. package/dist/server/errors-BGpFuqOO.mjs +0 -2
  56. package/dist/server/errors-BGpFuqOO.mjs.map +0 -1
  57. package/dist/server/errors-CCbuIqT8.cjs +0 -1
  58. package/dist/server/types-DnzM2ukj.d.cts.map +0 -1
  59. package/dist/server/types-qIeNbtNf.d.mts.map +0 -1
@@ -1,5 +1,5 @@
1
1
 
2
- import { i as ConfigDirectorLogger, n as ConfigDirectorClient, r as ConfigDirectorContext } from "./types-DA2VZKNX.mjs";
2
+ import { a as NativeJsonValue, c as ConfigDirectorContext, l as ConfigDirectorLogger, n as ConfigDirectorClient, o as NativeValue, s as NativeValues } from "./types-6a1uEkD0.mjs";
3
3
 
4
4
  //#region ../shared/src/errors.d.ts
5
5
  declare class ConfigDirectorConnectionError extends Error {
@@ -12,15 +12,6 @@ declare class ConfigDirectorValidationError extends Error {
12
12
  constructor(message: string);
13
13
  }
14
14
  //#endregion
15
- //#region ../shared/src/testing/values.d.ts
16
- type TestJsonValue = boolean | number | string | null | TestJsonValue[] | {
17
- [key: string]: TestJsonValue;
18
- };
19
- type TestValue = boolean | number | string | TestJsonValue[] | {
20
- [key: string]: TestJsonValue;
21
- };
22
- type TestValues = Record<string, TestValue>;
23
- //#endregion
24
15
  //#region ../js-client-core/src/testing/TestClient.d.ts
25
16
  /**
26
17
  * Options for {@link createTestClient}. Every option is optional.
@@ -31,7 +22,7 @@ type TestClientOptions = {
31
22
  * config type follows from each value: a boolean, an integral number (`integer`), any other
32
23
  * number (`float`), a string, or a plain object or array (`json`).
33
24
  */
34
- values?: TestValues;
25
+ values?: NativeValues;
35
26
  /**
36
27
  * The connection timeout of the SDK client, in milliseconds. It bounds how long a held
37
28
  * `initialize` or `updateContext` waits. Defaults to the SDK's production timeout.
@@ -60,7 +51,7 @@ interface TestClient {
60
51
  * Stores `value` under `key` and, when the client is connected, delivers it as an update, so
61
52
  * reads, watchers, and `configsUpdated` handlers see it.
62
53
  */
63
- setValue(key: string, value: TestValue): void;
54
+ setValue(key: string, value: NativeValue): void;
64
55
  /**
65
56
  * Removes `key` and, when the client is connected, delivers a full update without it, so reads
66
57
  * fall back to the in-code default value and watchers of `key` receive that default.
@@ -70,7 +61,7 @@ interface TestClient {
70
61
  * Replaces every stored value with `values`, disarms any armed hold or failure, and, when the
71
62
  * client is connected, delivers the new values as a full update.
72
63
  */
73
- replaceValues(values: TestValues): void;
64
+ replaceValues(values: NativeValues): void;
74
65
  /**
75
66
  * Makes the next `initialize` wait until {@link completeInitialization} or
76
67
  * {@link failInitialization} is called, or until the client's timeout elapses.
@@ -103,7 +94,8 @@ interface TestClient {
103
94
  failContextUpdate(): void;
104
95
  /**
105
96
  * The context of every `initialize` and `updateContext` call, in call order. `initialize` without
106
- * a context records an empty context. Resumes are not recorded.
97
+ * a context records an empty context, and so does a call whose context has an invalid kind, since
98
+ * the client connects without it. Resumes are not recorded.
107
99
  */
108
100
  readonly contextUpdates: ConfigDirectorContext[];
109
101
  }
@@ -155,5 +147,5 @@ declare const installTestClient: (testClient: TestClient) => (() => void);
155
147
  */
156
148
  declare const createTestClient: (options?: TestClientOptions) => TestClient;
157
149
  //#endregion
158
- export { ConfigDirectorConnectionError, ConfigDirectorValidationError, type TestClient, type TestClientOptions, type TestJsonValue, type TestValue, type TestValues, createTestClient, installTestClient };
150
+ export { ConfigDirectorConnectionError, ConfigDirectorValidationError, type TestClient, type TestClientOptions, type NativeJsonValue as TestJsonValue, type NativeValue as TestValue, type NativeValues as TestValues, createTestClient, installTestClient };
159
151
  //# sourceMappingURL=testing.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.mts","names":[],"sources":["../../../shared/src/errors.ts","../../../shared/src/testing/values.ts","../../../js-client-core/src/testing/TestClient.ts","../../../js-client-core/src/testing/installTestClient.ts","../../src/client/testing.ts"],"mappings":";;;;cAAa,6BAAA,SAAsC,KAAA;EAAA,SACxB,IAAA;EAAA,SACT,MAAA;cAEJ,OAAA,UAAiB,MAAA;AAAA;AAAA,cAQlB,6BAAA,SAAsC,KAAA;EAAA,SACxB,IAAA;cAEb,OAAA;AAAA;;;KCZF,aAAA,sCAAmD,aAAA;EAAA,CAAqB,GAAA,WAAc,aAAA;AAAA;AAAA,KAEtF,SAAA,+BAAwC,aAAA;EAAA,CAAqB,GAAA,WAAc,aAAA;AAAA;AAAA,KAE3E,UAAA,GAAa,MAAA,SAAe,SAAA;;;;ADPxC;;KEYY,iBAAA;EFZ4C;;;;;EEkBtD,MAAA,GAAS,UAAA;EFdoB;;;AAQ/B;EEWE,OAAA;;;;EAIA,MAAA,GAAS,oBAAA;AAAA;;;;;;;ADxBX;;UCmCiB,UAAA;EDnC8F;;;;EAAA,SCwCpG,MAAA,EAAQ,oBAAA;EDxC4F;AAE/G;;;EC4CE,QAAA,CAAS,GAAA,UAAa,KAAA,EAAO,SAAA;ED5CqB;;;;ECkDlD,WAAA,CAAY,GAAA;EDhDF;;;;ECsDV,aAAA,CAAc,MAAA,EAAQ,UAAA;;;;AAjDxB;EAuDE,kBAAA;;;;;EAMA,sBAAA;EA9CA;;;;EAoDA,kBAAA;EAzCyB;;;;EA+CzB,iBAAA;EAkByB;;;;EAZzB,qBAAA;EA1CA;;;;EAgDA,iBAAA;EA1CY;;;;EAAA,SAgDH,cAAA,EAAgB,qBAAA;AAAA;;;;;AFvG3B;;;;;;;;;;;;AAYA;;;;;;;cGYa,iBAAA,GAAqB,UAAA,EAAY,UAAA;;;;;;;;;;;;;;AHZ9C;;;;;;;;;cIiBa,gBAAA,GAAoB,OAAA,GAAU,iBAAA,KAAoB,UAAA"}
1
+ {"version":3,"file":"testing.d.mts","names":[],"sources":["../../../shared/src/errors.ts","../../../js-client-core/src/testing/TestClient.ts","../../../js-client-core/src/testing/installTestClient.ts","../../src/client/testing.ts"],"mappings":";;;;cAAa,6BAAA,SAAsC,KAAA;EAAA,SACxB,IAAA;EAAA,SACT,MAAA;cAEJ,OAAA,UAAiB,MAAA;AAAA;AAAA,cAQlB,6BAAA,SAAsC,KAAA;EAAA,SACxB,IAAA;cAEb,OAAA;AAAA;;;;AAfd;;KCYY,iBAAA;EDZ4C;;;;;ECkBtD,MAAA,GAAS,YAAA;EDdoB;;;AAQ/B;ECWE,OAAA;;;;EAIA,MAAA,GAAS,oBAAA;AAAA;;;;;;;AAfX;;UA0BiB,UAAA;EAXc;;;;EAAA,SAgBpB,MAAA,EAAQ,oBAAA;EAhBR;;;AAWX;EAWE,QAAA,CAAS,GAAA,UAAa,KAAA,EAAO,WAAA;;;;;EAM7B,WAAA,CAAY,GAAA;EAiDkC;;;;EA3C9C,aAAA,CAAc,MAAA,EAAQ,YAAA;EAZb;;;;EAkBT,kBAAA;EANA;;;;EAYA,sBAAA;EAMA;;;;EAAA,kBAAA;EAyByB;;;;EAnBzB,iBAAA;;AC7DF;;;EDmEE,qBAAA;ECnEsD;;;;EDyEtD,iBAAA;EElED;;;;;EAAA,SFyEU,cAAA,EAAgB,qBAAA;AAAA;;;;;ADxG3B;;;;;;;;;;;;AAYA;;;;;;;cEYa,iBAAA,GAAqB,UAAA,EAAY,UAAA;;;;;;;;;;;;;;AFZ9C;;;;;;;;;cGiBa,gBAAA,GAAoB,OAAA,GAAU,iBAAA,KAAoB,UAAA"}
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- "use client";import{d as e,f as t,l as n,r,s as i,t as a,u as o}from"./installedTestClient-CDdDsfVQ.mjs";const s=(t,n)=>{if(t.trim().length==0)throw new e(`Invalid config key. The key of a test value cannot be empty.`);return c(t,n)},c=(t,n)=>{switch(typeof n){case`boolean`:return{type:`boolean`,value:n?`true`:`false`};case`number`:return l(t,n);case`string`:return{type:`string`,value:n};case`object`:if(n!==null)return d(t,n,``),{type:`json`,value:JSON.stringify(n)}}throw new e(`Invalid test value for '${t}': ${m(n)}. Use a boolean, a number, a string, a plain object, or an array.`)},l=(t,n)=>{if(!Number.isFinite(n))throw new e(`Invalid test value for '${t}': ${m(n)} is not a finite number.`);if(Number.isInteger(n)){if(!Number.isSafeInteger(n))throw new e(`Invalid test value for '${t}': ${n} is not a safe integer, so the SDK could not read it back exactly.`);return{type:`integer`,value:String(n)}}return{type:`float`,value:u(n)}},u=e=>{let t=String(e),n=t.indexOf(`e-`);if(n<0)return t;let r=t.startsWith(`-`)?`-`:``,[i=``,a=``]=t.slice(r.length,n).split(`.`),o=Number(t.slice(n+2))-i.length;return`${r}0.${`0`.repeat(o)}${i}${a}`},d=(e,t,n)=>{if(!(t===null||typeof t==`boolean`||typeof t==`string`)){if(typeof t==`number`){if(!Number.isFinite(t))throw f(e,n,`${m(t)} is not a finite number`);return}if(Array.isArray(t)){t.forEach((t,r)=>d(e,t,`${n}[${r}]`));return}if(p(t)){for(let[r,i]of Object.entries(t))d(e,i,n?`${n}.${r}`:r);return}throw f(e,n,`${m(t)} cannot be encoded as JSON`)}},f=(t,n,r)=>new e(`Invalid test value for '${t}'${n?` at '${n}'`:``}: ${r}. JSON contents can hold booleans, finite numbers, strings, null, arrays, and plain objects.`),p=e=>{if(typeof e!=`object`||!e)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null},m=e=>{if(e===null)return`null`;if(typeof e==`function`)return`a function`;if(typeof e==`object`){var t,n;return`a ${(t=(n=e.constructor)==null?void 0:n.name)==null?`object`:t} instance`}return typeof e==`symbol`?`a symbol`:String(e)},h=(e,t,n)=>{let r=s(e,t);return{id:`test-config:${e}`,key:e,type:r.type,value:r.value,valueId:`test-value:${e}:${n}`}},g={environmentId:`test-environment`,projectId:`test-project`},_=e=>{switch(e){case`initialization`:return`initialize`;case`context update`:return`updateContext`;default:return}};var v=class{constructor(e,n){this.logger=e,this.emitter=new t,this.configs=new Map,this.recordedContexts=[],this.pendingDeliveries=[],this.armed={initialize:`nothing`,updateContext:`nothing`},this.connected=!1,this.delivering=!1,this.revision=0,this.attemptGeneration=0,this.on=(e,t)=>this.emitter.on(e,t),this.off=(e,t)=>this.emitter.off(e,t),this.store(n)}get contextUpdates(){return[...this.recordedContexts]}setValue(e,t){let r=h(e,t,++this.revision);this.configs.set(e,r),this.deliverIfConnected(n(n({},g),{},{kind:`delta`,configs:{[e]:r}}))}removeValue(e){this.configs.delete(e),this.deliverIfConnected(this.fullUpdate())}replaceValues(e){let t=this.encodeAll(e);this.configs.clear(),t.forEach((e,t)=>this.configs.set(t,e)),this.armed.initialize=`nothing`,this.armed.updateContext=`nothing`,this.deliverIfConnected(this.fullUpdate())}hold(e){this.armed[e]=`hold`}complete(e){var t;((t=this.pendingHold)==null?void 0:t.attempt)===e?this.pendingHold.complete():this.armed[e]===`hold`&&(this.armed[e]=`nothing`)}fail(e){var t;((t=this.pendingHold)==null?void 0:t.attempt)===e?this.pendingHold.fail():this.armed[e]=`failure`}connect(e,t,n){this.endAttempt();let r=_(n);if(!r)return this.connectNow();this.recordedContexts.push(e);let i=this.armed[r];switch(this.armed[r]=`nothing`,i){case`failure`:return this.failNow(n);case`hold`:return this.holdNow(r,n,t);default:return this.connectNow()}}clear(){this.emitter.clear()}close(){this.endAttempt()}dispose(){this.close(),this.clear()}endAttempt(){var e;this.attemptGeneration++,this.connected=!1,(e=this.pendingHold)==null||e.end()}connectNow(){let e=Promise.resolve(this);return this.scheduleFirstDelivery(),e}holdNow(e,t,n){return new Promise((r,i)=>{let a=setTimeout(()=>{var e;return(e=this.pendingHold)==null?void 0:e.end()},n),o=()=>{clearTimeout(a),this.pendingHold=void 0};this.pendingHold={attempt:e,end:()=>{o(),r(this)},complete:()=>{o(),r(this),this.scheduleFirstDelivery()},fail:()=>{o();let e=this.fatalError(t);this.emitter.emit(`connectionError`,e),i(e)}}})}failNow(e){let t=this.fatalError(e);return this.emitter.emit(`connectionError`,t),Promise.reject(t)}fatalError(e){return new o(`Connection failed with status: 401. Error: the test client failed this ${e}. This is an unrecoverable error, will not attempt to reconnect.`,401)}scheduleFirstDelivery(){let e=this.attemptGeneration;Promise.resolve().then(()=>{}).then(()=>{e===this.attemptGeneration&&(this.connected=!0,this.deliver(this.fullUpdate()))})}deliverIfConnected(e){this.connected&&this.deliver(e)}deliver(e){if(this.pendingDeliveries.push(e),!this.delivering){this.delivering=!0;try{let e=this.pendingDeliveries.shift();for(;e;)this.emitCaught(e),e=this.pendingDeliveries.shift()}finally{this.delivering=!1}}}emitCaught(e){try{this.emitter.emit(`configSetReceived`,e)}catch(e){this.logger.error(`[InMemoryConnection] Error dispatching a config update from the test client: `,e)}}fullUpdate(){return n(n({},g),{},{kind:`full`,configs:Object.fromEntries(this.configs)})}store(e){this.encodeAll(e).forEach((e,t)=>this.configs.set(t,e))}encodeAll(e){let t=new Map;for(let[n,r]of Object.entries(e))t.set(n,h(n,r,++this.revision));return t}};const y={updateContext:async()=>{},evaluatedConfig:()=>{},close:async()=>{}};var b=class{constructor(e,t){this.client=e,this.connection=t}setValue(e,t){this.connection.setValue(e,t)}removeValue(e){this.connection.removeValue(e)}replaceValues(e){this.connection.replaceValues(e)}holdInitialization(){this.connection.hold(`initialize`)}completeInitialization(){this.connection.complete(`initialize`)}failInitialization(){this.connection.fail(`initialize`)}holdContextUpdate(){this.connection.hold(`updateContext`)}completeContextUpdate(){this.connection.complete(`updateContext`)}failContextUpdate(){this.connection.fail(`updateContext`)}get contextUpdates(){return this.connection.contextUpdates}};const x=(e,t)=>{var n,a;let o=(n=t==null?void 0:t.logger)==null?i():n,s=new v(o,(a=t==null?void 0:t.values)==null?{}:a);return new b(new r(y,`test-client`,e,{logger:o,connection:{timeout:t==null?void 0:t.timeout}},{transport:s}),s)},S=e=>{let t=globalThis,n=t[a];return t[a]=e.client,()=>{t[a]=n}},C=e=>x({sdkName:`nextjs-sdk`,sdkVersion:`1.9.2`},e);export{o as ConfigDirectorConnectionError,e as ConfigDirectorValidationError,C as createTestClient,S as installTestClient};
2
+ "use client";import{a as e,c as t,d as n,f as r,l as i,o as a,r as o,t as s,u as c}from"./installedTestClient-BwTUOOhP.mjs";const l={environmentId:`test-environment`,projectId:`test-project`},u=e=>{switch(e){case`initialization`:return`initialize`;case`context update`:return`updateContext`;default:return}};var d=class{constructor(e,t){this.logger=e,this.emitter=new n,this.configs=new Map,this.recordedContexts=[],this.pendingDeliveries=[],this.armed={initialize:`nothing`,updateContext:`nothing`},this.connected=!1,this.delivering=!1,this.revision=0,this.attemptGeneration=0,this.on=(e,t)=>this.emitter.on(e,t),this.off=(e,t)=>this.emitter.off(e,t),this.store(t)}get contextUpdates(){return[...this.recordedContexts]}setValue(e,t){let n=this.encode(e,t);this.configs.set(e,n),this.deliverIfConnected(r(r({},l),{},{kind:`delta`,configs:{[e]:n}}))}removeValue(e){this.configs.delete(e),this.deliverIfConnected(this.fullUpdate())}replaceValues(e){let t=this.encodeAll(e);this.configs.clear(),t.forEach((e,t)=>this.configs.set(t,e)),this.armed.initialize=`nothing`,this.armed.updateContext=`nothing`,this.deliverIfConnected(this.fullUpdate())}hold(e){this.armed[e]=`hold`}complete(e){var t;((t=this.pendingHold)==null?void 0:t.attempt)===e?this.pendingHold.complete():this.armed[e]===`hold`&&(this.armed[e]=`nothing`)}fail(e){var t;((t=this.pendingHold)==null?void 0:t.attempt)===e?this.pendingHold.fail():this.armed[e]=`failure`}connect(e,t,n){this.endAttempt();let r=u(n);if(!r)return this.connectNow();this.recordedContexts.push(e);let i=this.armed[r];switch(this.armed[r]=`nothing`,i){case`failure`:return this.failNow(n);case`hold`:return this.holdNow(r,n,t);default:return this.connectNow()}}clear(){this.emitter.clear()}close(){this.endAttempt()}dispose(){this.close(),this.clear()}endAttempt(){var e;this.attemptGeneration++,this.connected=!1,(e=this.pendingHold)==null||e.end()}connectNow(){let e=Promise.resolve(this);return this.scheduleFirstDelivery(),e}holdNow(e,t,n){return new Promise((r,i)=>{let a=setTimeout(()=>{var e;return(e=this.pendingHold)==null?void 0:e.end()},n),o=()=>{clearTimeout(a),this.pendingHold=void 0};this.pendingHold={attempt:e,end:()=>{o(),r(this)},complete:()=>{o(),r(this),this.scheduleFirstDelivery()},fail:()=>{o();let e=this.fatalError(t);this.emitter.emit(`connectionError`,e),i(e)}}})}failNow(e){let t=this.fatalError(e);return this.emitter.emit(`connectionError`,t),Promise.reject(t)}fatalError(e){return new i(`Connection failed with status: 401. Error: the test client failed this ${e}. This is an unrecoverable error, will not attempt to reconnect.`,401)}scheduleFirstDelivery(){let e=this.attemptGeneration;Promise.resolve().then(()=>{}).then(()=>{e===this.attemptGeneration&&(this.connected=!0,this.deliver(this.fullUpdate()))})}deliverIfConnected(e){this.connected&&this.deliver(e)}deliver(e){if(this.pendingDeliveries.push(e),!this.delivering){this.delivering=!0;try{let e=this.pendingDeliveries.shift();for(;e;)this.emitCaught(e),e=this.pendingDeliveries.shift()}finally{this.delivering=!1}}}emitCaught(e){try{this.emitter.emit(`configSetReceived`,e)}catch(e){this.logger.error(`[InMemoryConnection] Error dispatching a config update from the test client: `,e)}}fullUpdate(){return r(r({},l),{},{kind:`full`,configs:Object.fromEntries(this.configs)})}store(e){this.encodeAll(e).forEach((e,t)=>this.configs.set(t,e))}encodeAll(e){let t=new Map;for(let[n,r]of Object.entries(e))t.set(n,this.encode(n,r));return t}encode(e,n){return t(`test-config:${e}`,e,n,`test-value:${e}:${++this.revision}`)}},f=class{constructor(e,t){this.client=e,this.connection=t}setValue(e,t){this.connection.setValue(e,t)}removeValue(e){this.connection.removeValue(e)}replaceValues(e){this.connection.replaceValues(e)}holdInitialization(){this.connection.hold(`initialize`)}completeInitialization(){this.connection.complete(`initialize`)}failInitialization(){this.connection.fail(`initialize`)}holdContextUpdate(){this.connection.hold(`updateContext`)}completeContextUpdate(){this.connection.complete(`updateContext`)}failContextUpdate(){this.connection.fail(`updateContext`)}get contextUpdates(){return this.connection.contextUpdates}};const p=(t,n)=>{var r,i;let s=(r=n==null?void 0:n.logger)==null?a():r,c=new d(s,(i=n==null?void 0:n.values)==null?{}:i);return new f(new o(()=>e,`test-client`,t,{logger:s,connection:{timeout:n==null?void 0:n.timeout}},{transport:c}),c)},m=e=>{let t=globalThis,n=t[s];return t[s]=e.client,()=>{t[s]=n}},h=e=>p({sdkName:`nextjs-sdk`,sdkVersion:`1.11.0`},e);export{i as ConfigDirectorConnectionError,c as ConfigDirectorValidationError,h as createTestClient,m as installTestClient};
3
3
  //# sourceMappingURL=testing.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.mjs","names":["encodeTestValue","encodeShared","createTestClient"],"sources":["../../../shared/src/testing/values.ts","../../../js-client-core/src/testing/values.ts","../../../js-client-core/src/testing/InMemoryConnection.ts","../../../js-client-core/src/testing/TestClient.ts","../../../js-client-core/src/testing/installTestClient.ts","../../src/client/testing.ts"],"sourcesContent":["import type { ConfigType } from \"../types\";\nimport { ConfigDirectorValidationError } from \"../errors\";\n\nexport type TestJsonValue = boolean | number | string | null | TestJsonValue[] | { [key: string]: TestJsonValue };\n\nexport type TestValue = boolean | number | string | TestJsonValue[] | { [key: string]: TestJsonValue };\n\nexport type TestValues = Record<string, TestValue>;\n\nexport type EncodedTestValue = { type: ConfigType; value: string };\n\nexport const encodeTestValue = (key: string, value: TestValue): EncodedTestValue => {\n if (key.trim().length == 0) {\n throw new ConfigDirectorValidationError(\"Invalid config key. The key of a test value cannot be empty.\");\n }\n return encode(key, value);\n};\n\nconst encode = (key: string, value: TestValue): EncodedTestValue => {\n switch (typeof value) {\n case \"boolean\":\n return { type: \"boolean\", value: value ? \"true\" : \"false\" };\n case \"number\":\n return encodeNumber(key, value);\n case \"string\":\n return { type: \"string\", value };\n case \"object\":\n if (value !== null) {\n assertJsonContents(key, value, \"\");\n return { type: \"json\", value: JSON.stringify(value) };\n }\n }\n throw new ConfigDirectorValidationError(\n `Invalid test value for '${key}': ${describe(value)}. Use a boolean, a number, a string, a plain object, or an array.`,\n );\n};\n\nconst encodeNumber = (key: string, value: number): EncodedTestValue => {\n if (!Number.isFinite(value)) {\n throw new ConfigDirectorValidationError(`Invalid test value for '${key}': ${describe(value)} is not a finite number.`);\n }\n if (Number.isInteger(value)) {\n if (!Number.isSafeInteger(value)) {\n throw new ConfigDirectorValidationError(\n `Invalid test value for '${key}': ${value} is not a safe integer, so the SDK could not read it back exactly.`,\n );\n }\n return { type: \"integer\", value: String(value) };\n }\n return { type: \"float\", value: toPlainDecimal(value) };\n};\n\nconst toPlainDecimal = (value: number): string => {\n const text = String(value);\n const exponentIndex = text.indexOf(\"e-\");\n if (exponentIndex < 0) {\n return text;\n }\n const sign = text.startsWith(\"-\") ? \"-\" : \"\";\n const [integerDigits = \"\", fractionDigits = \"\"] = text.slice(sign.length, exponentIndex).split(\".\");\n const leadingZeros = Number(text.slice(exponentIndex + 2)) - integerDigits.length;\n return `${sign}0.${\"0\".repeat(leadingZeros)}${integerDigits}${fractionDigits}`;\n};\n\nconst assertJsonContents = (key: string, value: unknown, path: string): void => {\n if (value === null || typeof value === \"boolean\" || typeof value === \"string\") {\n return;\n }\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) {\n throw invalidJsonContents(key, path, `${describe(value)} is not a finite number`);\n }\n return;\n }\n if (Array.isArray(value)) {\n value.forEach((item, index) => assertJsonContents(key, item, `${path}[${index}]`));\n return;\n }\n if (isPlainObject(value)) {\n for (const [property, item] of Object.entries(value)) {\n assertJsonContents(key, item, path ? `${path}.${property}` : property);\n }\n return;\n }\n throw invalidJsonContents(key, path, `${describe(value)} cannot be encoded as JSON`);\n};\n\nconst invalidJsonContents = (key: string, path: string, problem: string) =>\n new ConfigDirectorValidationError(\n `Invalid test value for '${key}'${path ? ` at '${path}'` : \"\"}: ${problem}. JSON contents can hold booleans, finite numbers, strings, null, arrays, and plain objects.`,\n );\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> => {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n};\n\nconst describe = (value: unknown): string => {\n if (value === null) {\n return \"null\";\n }\n if (typeof value === \"function\") {\n return \"a function\";\n }\n if (typeof value === \"object\") {\n return `a ${value.constructor?.name ?? \"object\"} instance`;\n }\n if (typeof value === \"symbol\") {\n return \"a symbol\";\n }\n return String(value);\n};\n","import type { ConfigState } from \"../types\";\nimport { encodeTestValue as encodeShared } from \"@shared/testing/values\";\nimport type { TestValue } from \"@shared/testing/values\";\n\nexport type { TestJsonValue, TestValue, TestValues } from \"@shared/testing/values\";\n\nexport const encodeTestValue = (key: string, value: TestValue, revision: number): ConfigState => {\n const encoded = encodeShared(key, value);\n return {\n id: `test-config:${key}`,\n key,\n type: encoded.type,\n value: encoded.value,\n valueId: `test-value:${key}:${revision}`,\n };\n};\n","import type {\n ClientConnectAction,\n ConfigDirectorContext,\n ConfigDirectorLogger,\n ConfigSet,\n ConfigState,\n Transport,\n TransportEvents,\n} from \"../types\";\nimport { Emitter } from \"../Emitter\";\nimport { ConfigDirectorConnectionError } from \"../errors\";\nimport { encodeTestValue, type TestValue, type TestValues } from \"./values\";\n\ntype ControlledAttempt = \"initialize\" | \"updateContext\";\n\ntype Armed = \"nothing\" | \"hold\" | \"failure\";\n\ntype PendingHold = {\n attempt: ControlledAttempt;\n end: () => void;\n complete: () => void;\n fail: () => void;\n};\n\nconst TEST_ENVIRONMENT = { environmentId: \"test-environment\", projectId: \"test-project\" };\n\nconst controlledAttemptFor = (reason: ClientConnectAction): ControlledAttempt | undefined => {\n switch (reason) {\n case \"initialization\":\n return \"initialize\";\n case \"context update\":\n return \"updateContext\";\n default:\n return undefined;\n }\n};\n\nexport class InMemoryConnection implements Transport {\n private readonly emitter = new Emitter<TransportEvents>();\n private readonly configs = new Map<string, ConfigState>();\n private readonly recordedContexts: ConfigDirectorContext[] = [];\n private readonly pendingDeliveries: ConfigSet[] = [];\n private readonly armed: Record<ControlledAttempt, Armed> = { initialize: \"nothing\", updateContext: \"nothing\" };\n private pendingHold: PendingHold | undefined;\n private connected = false;\n private delivering = false;\n private revision = 0;\n private attemptGeneration = 0;\n\n constructor(\n private readonly logger: ConfigDirectorLogger,\n values: TestValues,\n ) {\n this.store(values);\n }\n\n public get contextUpdates(): ConfigDirectorContext[] {\n return [...this.recordedContexts];\n }\n\n public setValue(key: string, value: TestValue) {\n const configState = encodeTestValue(key, value, ++this.revision);\n this.configs.set(key, configState);\n this.deliverIfConnected({ ...TEST_ENVIRONMENT, kind: \"delta\", configs: { [key]: configState } });\n }\n\n public removeValue(key: string) {\n this.configs.delete(key);\n this.deliverIfConnected(this.fullUpdate());\n }\n\n public replaceValues(values: TestValues) {\n const encoded = this.encodeAll(values);\n this.configs.clear();\n encoded.forEach((configState, key) => this.configs.set(key, configState));\n this.armed.initialize = \"nothing\";\n this.armed.updateContext = \"nothing\";\n this.deliverIfConnected(this.fullUpdate());\n }\n\n public hold(attempt: ControlledAttempt) {\n this.armed[attempt] = \"hold\";\n }\n\n public complete(attempt: ControlledAttempt) {\n if (this.pendingHold?.attempt === attempt) {\n this.pendingHold.complete();\n } else if (this.armed[attempt] === \"hold\") {\n this.armed[attempt] = \"nothing\";\n }\n }\n\n public fail(attempt: ControlledAttempt) {\n if (this.pendingHold?.attempt === attempt) {\n this.pendingHold.fail();\n } else {\n this.armed[attempt] = \"failure\";\n }\n }\n\n public connect(context: ConfigDirectorContext, timeout: number, reason: ClientConnectAction): Promise<this> {\n this.endAttempt();\n const attempt = controlledAttemptFor(reason);\n if (!attempt) {\n return this.connectNow();\n }\n this.recordedContexts.push(context);\n const armed = this.armed[attempt];\n this.armed[attempt] = \"nothing\";\n switch (armed) {\n case \"failure\":\n return this.failNow(reason);\n case \"hold\":\n return this.holdNow(attempt, reason, timeout);\n default:\n return this.connectNow();\n }\n }\n\n public on: Transport[\"on\"] = (name, handler) => this.emitter.on(name, handler);\n\n public off: Transport[\"off\"] = (name, handler) => this.emitter.off(name, handler);\n\n public clear() {\n this.emitter.clear();\n }\n\n public close() {\n this.endAttempt();\n }\n\n public dispose() {\n this.close();\n this.clear();\n }\n\n private endAttempt() {\n this.attemptGeneration++;\n this.connected = false;\n this.pendingHold?.end();\n }\n\n private connectNow(): Promise<this> {\n const connected = Promise.resolve(this);\n this.scheduleFirstDelivery();\n return connected;\n }\n\n private holdNow(attempt: ControlledAttempt, reason: ClientConnectAction, timeout: number): Promise<this> {\n return new Promise<this>((resolve, reject) => {\n const timer = setTimeout(() => this.pendingHold?.end(), timeout);\n const release = () => {\n clearTimeout(timer);\n this.pendingHold = undefined;\n };\n this.pendingHold = {\n attempt,\n end: () => {\n release();\n resolve(this);\n },\n complete: () => {\n release();\n resolve(this);\n this.scheduleFirstDelivery();\n },\n fail: () => {\n release();\n const error = this.fatalError(reason);\n this.emitter.emit(\"connectionError\", error);\n reject(error);\n },\n };\n });\n }\n\n private failNow(reason: ClientConnectAction): Promise<this> {\n const error = this.fatalError(reason);\n this.emitter.emit(\"connectionError\", error);\n return Promise.reject(error);\n }\n\n private fatalError(reason: ClientConnectAction) {\n return new ConfigDirectorConnectionError(\n `Connection failed with status: 401. Error: the test client failed this ${reason}. This is an unrecoverable error, will not attempt to reconnect.`,\n 401,\n );\n }\n\n private scheduleFirstDelivery() {\n const generation = this.attemptGeneration;\n void Promise.resolve()\n .then(() => {})\n .then(() => {\n if (generation !== this.attemptGeneration) {\n return;\n }\n this.connected = true;\n this.deliver(this.fullUpdate());\n });\n }\n\n private deliverIfConnected(configSet: ConfigSet) {\n if (this.connected) {\n this.deliver(configSet);\n }\n }\n\n private deliver(configSet: ConfigSet) {\n this.pendingDeliveries.push(configSet);\n if (this.delivering) {\n return;\n }\n this.delivering = true;\n try {\n let next = this.pendingDeliveries.shift();\n while (next) {\n this.emitCaught(next);\n next = this.pendingDeliveries.shift();\n }\n } finally {\n this.delivering = false;\n }\n }\n\n private emitCaught(configSet: ConfigSet) {\n try {\n this.emitter.emit(\"configSetReceived\", configSet);\n } catch (error) {\n this.logger.error(\"[InMemoryConnection] Error dispatching a config update from the test client: \", error);\n }\n }\n\n private fullUpdate(): ConfigSet {\n return { ...TEST_ENVIRONMENT, kind: \"full\", configs: Object.fromEntries(this.configs) };\n }\n\n private store(values: TestValues) {\n this.encodeAll(values).forEach((configState, key) => this.configs.set(key, configState));\n }\n\n private encodeAll(values: TestValues): Map<string, ConfigState> {\n const encoded = new Map<string, ConfigState>();\n for (const [key, value] of Object.entries(values)) {\n encoded.set(key, encodeTestValue(key, value, ++this.revision));\n }\n return encoded;\n }\n}\n","import { DefaultConfigDirectorClient } from \"../DefaultConfigDirectorClient\";\nimport { createDefaultLogger } from \"../logger\";\nimport type { ConfigDirectorClient, ConfigDirectorContext, ConfigDirectorLogger, IdentifyingSdkOptions } from \"../types\";\nimport type { TelemetryClient } from \"../telemetry\";\nimport { InMemoryConnection } from \"./InMemoryConnection\";\nimport type { TestValue, TestValues } from \"./values\";\n\nconst TEST_CLIENT_SDK_KEY = \"test-client\";\n\n/**\n * Options for {@link createTestClient}. Every option is optional.\n */\nexport type TestClientOptions = {\n /**\n * The config values the test client serves once its client connects, keyed by config key. The\n * config type follows from each value: a boolean, an integral number (`integer`), any other\n * number (`float`), a string, or a plain object or array (`json`).\n */\n values?: TestValues;\n /**\n * The connection timeout of the SDK client, in milliseconds. It bounds how long a held\n * `initialize` or `updateContext` waits. Defaults to the SDK's production timeout.\n */\n timeout?: number;\n /**\n * The logger the SDK client uses. Defaults to the SDK's console logger at `warn` level.\n */\n logger?: ConfigDirectorLogger;\n};\n\n/**\n * A real ConfigDirector client connected to an in-memory server that the test controls.\n *\n * The client under {@link TestClient.client} is the SDK's production client: value parsing, watchers,\n * events, and readiness behave as they do against ConfigDirector. Only the network connection and\n * telemetry are replaced, so no request is ever made and nothing is left running after the client is\n * disposed.\n */\nexport interface TestClient {\n /**\n * The SDK client to hand to the code under test. It starts uninitialized, like a production\n * client; `initialize` completes at once with the seeded values unless initialization is held.\n */\n readonly client: ConfigDirectorClient;\n\n /**\n * Stores `value` under `key` and, when the client is connected, delivers it as an update, so\n * reads, watchers, and `configsUpdated` handlers see it.\n */\n setValue(key: string, value: TestValue): void;\n\n /**\n * Removes `key` and, when the client is connected, delivers a full update without it, so reads\n * fall back to the in-code default value and watchers of `key` receive that default.\n */\n removeValue(key: string): void;\n\n /**\n * Replaces every stored value with `values`, disarms any armed hold or failure, and, when the\n * client is connected, delivers the new values as a full update.\n */\n replaceValues(values: TestValues): void;\n\n /**\n * Makes the next `initialize` wait until {@link completeInitialization} or\n * {@link failInitialization} is called, or until the client's timeout elapses.\n */\n holdInitialization(): void;\n\n /**\n * Delivers the stored values to a held `initialize`, so it completes with the client ready. When\n * a hold is armed but no `initialize` has started, it disarms the hold instead.\n */\n completeInitialization(): void;\n\n /**\n * Fails a held `initialize` with an unrecoverable connection error, or arms the next `initialize`\n * to fail. `initialize` completes promptly with the client not ready and `connectionError` fires.\n */\n failInitialization(): void;\n\n /**\n * Makes the next `updateContext` wait until {@link completeContextUpdate} or\n * {@link failContextUpdate} is called, or until the client's timeout elapses.\n */\n holdContextUpdate(): void;\n\n /**\n * Delivers the stored values to a held `updateContext`, or disarms an armed hold that no\n * `updateContext` has picked up.\n */\n completeContextUpdate(): void;\n\n /**\n * Fails a held `updateContext` with an unrecoverable connection error, or arms the next\n * `updateContext` to fail.\n */\n failContextUpdate(): void;\n\n /**\n * The context of every `initialize` and `updateContext` call, in call order. `initialize` without\n * a context records an empty context. Resumes are not recorded.\n */\n readonly contextUpdates: ConfigDirectorContext[];\n}\n\nconst noopTelemetryClient: TelemetryClient = {\n updateContext: async () => {},\n evaluatedConfig: () => {},\n close: async () => {},\n};\n\nclass InMemoryTestClient implements TestClient {\n constructor(\n public readonly client: ConfigDirectorClient,\n private readonly connection: InMemoryConnection,\n ) {}\n\n public setValue(key: string, value: TestValue) {\n this.connection.setValue(key, value);\n }\n\n public removeValue(key: string) {\n this.connection.removeValue(key);\n }\n\n public replaceValues(values: TestValues) {\n this.connection.replaceValues(values);\n }\n\n public holdInitialization() {\n this.connection.hold(\"initialize\");\n }\n\n public completeInitialization() {\n this.connection.complete(\"initialize\");\n }\n\n public failInitialization() {\n this.connection.fail(\"initialize\");\n }\n\n public holdContextUpdate() {\n this.connection.hold(\"updateContext\");\n }\n\n public completeContextUpdate() {\n this.connection.complete(\"updateContext\");\n }\n\n public failContextUpdate() {\n this.connection.fail(\"updateContext\");\n }\n\n public get contextUpdates(): ConfigDirectorContext[] {\n return this.connection.contextUpdates;\n }\n}\n\nexport const createTestClient = (sdkOptions: IdentifyingSdkOptions, options?: TestClientOptions): TestClient => {\n const logger = options?.logger ?? createDefaultLogger();\n const connection = new InMemoryConnection(logger, options?.values ?? {});\n const client = new DefaultConfigDirectorClient(\n noopTelemetryClient,\n TEST_CLIENT_SDK_KEY,\n sdkOptions,\n { logger, connection: { timeout: options?.timeout } },\n { transport: connection },\n );\n return new InMemoryTestClient(client, connection);\n};\n","import { INSTALLED_TEST_CLIENT_SLOT, type InstalledTestClientSlot } from \"../installedTestClient\";\nimport type { TestClient } from \"./TestClient\";\n\n/**\n * Makes every `ConfigDirectorProvider` mounted without a `client` prop, and every client\n * `withProvider` would build, use `testClient.client` instead of building a client, until the\n * returned function uninstalls it. Construction props such as `sdkKey` are ignored while installed;\n * `context` and `hooks` apply to the installed client as they do for a `client` prop. A provider\n * reads the installed client when it is constructed, so install before rendering.\n *\n * Installing again replaces the installed test client. Each uninstall function restores the test\n * client that was installed when it was created, so nested installs are uninstalled in reverse order.\n *\n * @param testClient The test client whose `client` providers should use\n * @returns A function that uninstalls it\n *\n * @example\n * let uninstallTestClient: () => void;\n * beforeEach(() => {\n * testClient = createTestClient({ values: { \"new-checkout\": true } });\n * uninstallTestClient = installTestClient(testClient);\n * });\n * afterEach(() => uninstallTestClient());\n */\nexport const installTestClient = (testClient: TestClient): (() => void) => {\n const slot = globalThis as InstalledTestClientSlot;\n const previous = slot[INSTALLED_TEST_CLIENT_SLOT];\n slot[INSTALLED_TEST_CLIENT_SLOT] = testClient.client;\n return () => {\n slot[INSTALLED_TEST_CLIENT_SLOT] = previous;\n };\n};\n","\"use client\";\n\nimport { createTestClient as createCoreTestClient } from \"@js-client-core/testing/index\";\nimport type { TestClient, TestClientOptions } from \"@js-client-core/testing/index\";\n\nexport { installTestClient } from \"@js-client-core/testing/index\";\nexport type { TestClient, TestClientOptions, TestValue, TestValues, TestJsonValue } from \"@js-client-core/testing/index\";\nexport { ConfigDirectorConnectionError, ConfigDirectorValidationError } from \"@shared/errors\";\n\n/**\n * Creates a {@link TestClient}: a real browser `ConfigDirectorClient` connected to an in-memory\n * server that the test controls. No network connection is opened and no telemetry is sent. Render\n * the client `ConfigDirectorProvider` with `client={testClient.client}`, or install it with\n * `installTestClient` for components that render their own provider.\n *\n * @param options {@link TestClientOptions}: the initial values, the client's timeout, and its logger\n * @returns A {@link TestClient} whose `client` is passed to the code under test\n *\n * @example\n * import { createTestClient } from \"@configdirector/nextjs-sdk/client/testing\";\n *\n * const testClient = createTestClient({ values: { \"new-checkout\": true } });\n * render(\n * <ConfigDirectorProvider client={testClient.client}>\n * <Checkout />\n * </ConfigDirectorProvider>,\n * );\n * act(() => testClient.setValue(\"new-checkout\", false));\n */\nexport const createTestClient = (options?: TestClientOptions): TestClient => {\n return createCoreTestClient({ sdkName: \"nextjs-sdk\", sdkVersion: \"__VERSION__\" }, options);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"testing.mjs","names":["createTestClient"],"sources":["../../../js-client-core/src/testing/InMemoryConnection.ts","../../../js-client-core/src/testing/TestClient.ts","../../../js-client-core/src/testing/installTestClient.ts","../../src/client/testing.ts"],"sourcesContent":["import type {\n ClientConnectAction,\n ConfigDirectorContext,\n ConfigDirectorLogger,\n ConfigSet,\n ConfigState,\n Transport,\n TransportEvents,\n} from \"../types\";\nimport { Emitter } from \"../Emitter\";\nimport { ConfigDirectorConnectionError } from \"../errors\";\nimport type { TestValue, TestValues } from \"./values\";\nimport { nativeValueConfigState } from \"../native-values\";\n\ntype ControlledAttempt = \"initialize\" | \"updateContext\";\n\ntype Armed = \"nothing\" | \"hold\" | \"failure\";\n\ntype PendingHold = {\n attempt: ControlledAttempt;\n end: () => void;\n complete: () => void;\n fail: () => void;\n};\n\nconst TEST_ENVIRONMENT = { environmentId: \"test-environment\", projectId: \"test-project\" };\n\nconst controlledAttemptFor = (reason: ClientConnectAction): ControlledAttempt | undefined => {\n switch (reason) {\n case \"initialization\":\n return \"initialize\";\n case \"context update\":\n return \"updateContext\";\n default:\n return undefined;\n }\n};\n\nexport class InMemoryConnection implements Transport {\n private readonly emitter = new Emitter<TransportEvents>();\n private readonly configs = new Map<string, ConfigState>();\n private readonly recordedContexts: ConfigDirectorContext[] = [];\n private readonly pendingDeliveries: ConfigSet[] = [];\n private readonly armed: Record<ControlledAttempt, Armed> = { initialize: \"nothing\", updateContext: \"nothing\" };\n private pendingHold: PendingHold | undefined;\n private connected = false;\n private delivering = false;\n private revision = 0;\n private attemptGeneration = 0;\n\n constructor(\n private readonly logger: ConfigDirectorLogger,\n values: TestValues,\n ) {\n this.store(values);\n }\n\n public get contextUpdates(): ConfigDirectorContext[] {\n return [...this.recordedContexts];\n }\n\n public setValue(key: string, value: TestValue) {\n const configState = this.encode(key, value);\n this.configs.set(key, configState);\n this.deliverIfConnected({ ...TEST_ENVIRONMENT, kind: \"delta\", configs: { [key]: configState } });\n }\n\n public removeValue(key: string) {\n this.configs.delete(key);\n this.deliverIfConnected(this.fullUpdate());\n }\n\n public replaceValues(values: TestValues) {\n const encoded = this.encodeAll(values);\n this.configs.clear();\n encoded.forEach((configState, key) => this.configs.set(key, configState));\n this.armed.initialize = \"nothing\";\n this.armed.updateContext = \"nothing\";\n this.deliverIfConnected(this.fullUpdate());\n }\n\n public hold(attempt: ControlledAttempt) {\n this.armed[attempt] = \"hold\";\n }\n\n public complete(attempt: ControlledAttempt) {\n if (this.pendingHold?.attempt === attempt) {\n this.pendingHold.complete();\n } else if (this.armed[attempt] === \"hold\") {\n this.armed[attempt] = \"nothing\";\n }\n }\n\n public fail(attempt: ControlledAttempt) {\n if (this.pendingHold?.attempt === attempt) {\n this.pendingHold.fail();\n } else {\n this.armed[attempt] = \"failure\";\n }\n }\n\n public connect(context: ConfigDirectorContext, timeout: number, reason: ClientConnectAction): Promise<this> {\n this.endAttempt();\n const attempt = controlledAttemptFor(reason);\n if (!attempt) {\n return this.connectNow();\n }\n this.recordedContexts.push(context);\n const armed = this.armed[attempt];\n this.armed[attempt] = \"nothing\";\n switch (armed) {\n case \"failure\":\n return this.failNow(reason);\n case \"hold\":\n return this.holdNow(attempt, reason, timeout);\n default:\n return this.connectNow();\n }\n }\n\n public on: Transport[\"on\"] = (name, handler) => this.emitter.on(name, handler);\n\n public off: Transport[\"off\"] = (name, handler) => this.emitter.off(name, handler);\n\n public clear() {\n this.emitter.clear();\n }\n\n public close() {\n this.endAttempt();\n }\n\n public dispose() {\n this.close();\n this.clear();\n }\n\n private endAttempt() {\n this.attemptGeneration++;\n this.connected = false;\n this.pendingHold?.end();\n }\n\n private connectNow(): Promise<this> {\n const connected = Promise.resolve(this);\n this.scheduleFirstDelivery();\n return connected;\n }\n\n private holdNow(attempt: ControlledAttempt, reason: ClientConnectAction, timeout: number): Promise<this> {\n return new Promise<this>((resolve, reject) => {\n const timer = setTimeout(() => this.pendingHold?.end(), timeout);\n const release = () => {\n clearTimeout(timer);\n this.pendingHold = undefined;\n };\n this.pendingHold = {\n attempt,\n end: () => {\n release();\n resolve(this);\n },\n complete: () => {\n release();\n resolve(this);\n this.scheduleFirstDelivery();\n },\n fail: () => {\n release();\n const error = this.fatalError(reason);\n this.emitter.emit(\"connectionError\", error);\n reject(error);\n },\n };\n });\n }\n\n private failNow(reason: ClientConnectAction): Promise<this> {\n const error = this.fatalError(reason);\n this.emitter.emit(\"connectionError\", error);\n return Promise.reject(error);\n }\n\n private fatalError(reason: ClientConnectAction) {\n return new ConfigDirectorConnectionError(\n `Connection failed with status: 401. Error: the test client failed this ${reason}. This is an unrecoverable error, will not attempt to reconnect.`,\n 401,\n );\n }\n\n private scheduleFirstDelivery() {\n const generation = this.attemptGeneration;\n void Promise.resolve()\n .then(() => {})\n .then(() => {\n if (generation !== this.attemptGeneration) {\n return;\n }\n this.connected = true;\n this.deliver(this.fullUpdate());\n });\n }\n\n private deliverIfConnected(configSet: ConfigSet) {\n if (this.connected) {\n this.deliver(configSet);\n }\n }\n\n private deliver(configSet: ConfigSet) {\n this.pendingDeliveries.push(configSet);\n if (this.delivering) {\n return;\n }\n this.delivering = true;\n try {\n let next = this.pendingDeliveries.shift();\n while (next) {\n this.emitCaught(next);\n next = this.pendingDeliveries.shift();\n }\n } finally {\n this.delivering = false;\n }\n }\n\n private emitCaught(configSet: ConfigSet) {\n try {\n this.emitter.emit(\"configSetReceived\", configSet);\n } catch (error) {\n this.logger.error(\"[InMemoryConnection] Error dispatching a config update from the test client: \", error);\n }\n }\n\n private fullUpdate(): ConfigSet {\n return { ...TEST_ENVIRONMENT, kind: \"full\", configs: Object.fromEntries(this.configs) };\n }\n\n private store(values: TestValues) {\n this.encodeAll(values).forEach((configState, key) => this.configs.set(key, configState));\n }\n\n private encodeAll(values: TestValues): Map<string, ConfigState> {\n const encoded = new Map<string, ConfigState>();\n for (const [key, value] of Object.entries(values)) {\n encoded.set(key, this.encode(key, value));\n }\n return encoded;\n }\n\n private encode(key: string, value: TestValue): ConfigState {\n return nativeValueConfigState(`test-config:${key}`, key, value, `test-value:${key}:${++this.revision}`);\n }\n}\n","import { DefaultConfigDirectorClient } from \"../DefaultConfigDirectorClient\";\nimport { createDefaultLogger } from \"../logger\";\nimport type { ConfigDirectorClient, ConfigDirectorContext, ConfigDirectorLogger, IdentifyingSdkOptions } from \"../types\";\nimport { discardingTelemetryClient } from \"../telemetry/discarding\";\nimport { InMemoryConnection } from \"./InMemoryConnection\";\nimport type { TestValue, TestValues } from \"./values\";\n\nconst TEST_CLIENT_SDK_KEY = \"test-client\";\n\n/**\n * Options for {@link createTestClient}. Every option is optional.\n */\nexport type TestClientOptions = {\n /**\n * The config values the test client serves once its client connects, keyed by config key. The\n * config type follows from each value: a boolean, an integral number (`integer`), any other\n * number (`float`), a string, or a plain object or array (`json`).\n */\n values?: TestValues;\n /**\n * The connection timeout of the SDK client, in milliseconds. It bounds how long a held\n * `initialize` or `updateContext` waits. Defaults to the SDK's production timeout.\n */\n timeout?: number;\n /**\n * The logger the SDK client uses. Defaults to the SDK's console logger at `warn` level.\n */\n logger?: ConfigDirectorLogger;\n};\n\n/**\n * A real ConfigDirector client connected to an in-memory server that the test controls.\n *\n * The client under {@link TestClient.client} is the SDK's production client: value parsing, watchers,\n * events, and readiness behave as they do against ConfigDirector. Only the network connection and\n * telemetry are replaced, so no request is ever made and nothing is left running after the client is\n * disposed.\n */\nexport interface TestClient {\n /**\n * The SDK client to hand to the code under test. It starts uninitialized, like a production\n * client; `initialize` completes at once with the seeded values unless initialization is held.\n */\n readonly client: ConfigDirectorClient;\n\n /**\n * Stores `value` under `key` and, when the client is connected, delivers it as an update, so\n * reads, watchers, and `configsUpdated` handlers see it.\n */\n setValue(key: string, value: TestValue): void;\n\n /**\n * Removes `key` and, when the client is connected, delivers a full update without it, so reads\n * fall back to the in-code default value and watchers of `key` receive that default.\n */\n removeValue(key: string): void;\n\n /**\n * Replaces every stored value with `values`, disarms any armed hold or failure, and, when the\n * client is connected, delivers the new values as a full update.\n */\n replaceValues(values: TestValues): void;\n\n /**\n * Makes the next `initialize` wait until {@link completeInitialization} or\n * {@link failInitialization} is called, or until the client's timeout elapses.\n */\n holdInitialization(): void;\n\n /**\n * Delivers the stored values to a held `initialize`, so it completes with the client ready. When\n * a hold is armed but no `initialize` has started, it disarms the hold instead.\n */\n completeInitialization(): void;\n\n /**\n * Fails a held `initialize` with an unrecoverable connection error, or arms the next `initialize`\n * to fail. `initialize` completes promptly with the client not ready and `connectionError` fires.\n */\n failInitialization(): void;\n\n /**\n * Makes the next `updateContext` wait until {@link completeContextUpdate} or\n * {@link failContextUpdate} is called, or until the client's timeout elapses.\n */\n holdContextUpdate(): void;\n\n /**\n * Delivers the stored values to a held `updateContext`, or disarms an armed hold that no\n * `updateContext` has picked up.\n */\n completeContextUpdate(): void;\n\n /**\n * Fails a held `updateContext` with an unrecoverable connection error, or arms the next\n * `updateContext` to fail.\n */\n failContextUpdate(): void;\n\n /**\n * The context of every `initialize` and `updateContext` call, in call order. `initialize` without\n * a context records an empty context, and so does a call whose context has an invalid kind, since\n * the client connects without it. Resumes are not recorded.\n */\n readonly contextUpdates: ConfigDirectorContext[];\n}\n\nclass InMemoryTestClient implements TestClient {\n constructor(\n public readonly client: ConfigDirectorClient,\n private readonly connection: InMemoryConnection,\n ) {}\n\n public setValue(key: string, value: TestValue) {\n this.connection.setValue(key, value);\n }\n\n public removeValue(key: string) {\n this.connection.removeValue(key);\n }\n\n public replaceValues(values: TestValues) {\n this.connection.replaceValues(values);\n }\n\n public holdInitialization() {\n this.connection.hold(\"initialize\");\n }\n\n public completeInitialization() {\n this.connection.complete(\"initialize\");\n }\n\n public failInitialization() {\n this.connection.fail(\"initialize\");\n }\n\n public holdContextUpdate() {\n this.connection.hold(\"updateContext\");\n }\n\n public completeContextUpdate() {\n this.connection.complete(\"updateContext\");\n }\n\n public failContextUpdate() {\n this.connection.fail(\"updateContext\");\n }\n\n public get contextUpdates(): ConfigDirectorContext[] {\n return this.connection.contextUpdates;\n }\n}\n\nexport const createTestClient = (sdkOptions: IdentifyingSdkOptions, options?: TestClientOptions): TestClient => {\n const logger = options?.logger ?? createDefaultLogger();\n const connection = new InMemoryConnection(logger, options?.values ?? {});\n const client = new DefaultConfigDirectorClient(\n () => discardingTelemetryClient,\n TEST_CLIENT_SDK_KEY,\n sdkOptions,\n { logger, connection: { timeout: options?.timeout } },\n { transport: connection },\n );\n return new InMemoryTestClient(client, connection);\n};\n","import { INSTALLED_TEST_CLIENT_SLOT, type InstalledTestClientSlot } from \"../installedTestClient\";\nimport type { TestClient } from \"./TestClient\";\n\n/**\n * Makes every `ConfigDirectorProvider` mounted without a `client` prop, and every client\n * `withProvider` would build, use `testClient.client` instead of building a client, until the\n * returned function uninstalls it. Construction props such as `sdkKey` are ignored while installed;\n * `context` and `hooks` apply to the installed client as they do for a `client` prop. A provider\n * reads the installed client when it is constructed, so install before rendering.\n *\n * Installing again replaces the installed test client. Each uninstall function restores the test\n * client that was installed when it was created, so nested installs are uninstalled in reverse order.\n *\n * @param testClient The test client whose `client` providers should use\n * @returns A function that uninstalls it\n *\n * @example\n * let uninstallTestClient: () => void;\n * beforeEach(() => {\n * testClient = createTestClient({ values: { \"new-checkout\": true } });\n * uninstallTestClient = installTestClient(testClient);\n * });\n * afterEach(() => uninstallTestClient());\n */\nexport const installTestClient = (testClient: TestClient): (() => void) => {\n const slot = globalThis as InstalledTestClientSlot;\n const previous = slot[INSTALLED_TEST_CLIENT_SLOT];\n slot[INSTALLED_TEST_CLIENT_SLOT] = testClient.client;\n return () => {\n slot[INSTALLED_TEST_CLIENT_SLOT] = previous;\n };\n};\n","\"use client\";\n\nimport { createTestClient as createCoreTestClient } from \"@js-client-core/testing/index\";\nimport type { TestClient, TestClientOptions } from \"@js-client-core/testing/index\";\n\nexport { installTestClient } from \"@js-client-core/testing/index\";\nexport type { TestClient, TestClientOptions, TestValue, TestValues, TestJsonValue } from \"@js-client-core/testing/index\";\nexport { ConfigDirectorConnectionError, ConfigDirectorValidationError } from \"@shared/errors\";\n\n/**\n * Creates a {@link TestClient}: a real browser `ConfigDirectorClient` connected to an in-memory\n * server that the test controls. No network connection is opened and no telemetry is sent. Render\n * the client `ConfigDirectorProvider` with `client={testClient.client}`, or install it with\n * `installTestClient` for components that render their own provider.\n *\n * @param options {@link TestClientOptions}: the initial values, the client's timeout, and its logger\n * @returns A {@link TestClient} whose `client` is passed to the code under test\n *\n * @example\n * import { createTestClient } from \"@configdirector/nextjs-sdk/client/testing\";\n *\n * const testClient = createTestClient({ values: { \"new-checkout\": true } });\n * render(\n * <ConfigDirectorProvider client={testClient.client}>\n * <Checkout />\n * </ConfigDirectorProvider>,\n * );\n * act(() => testClient.setValue(\"new-checkout\", false));\n */\nexport const createTestClient = (options?: TestClientOptions): TestClient => {\n return createCoreTestClient({ sdkName: \"nextjs-sdk\", sdkVersion: \"__VERSION__\" }, options);\n};\n"],"mappings":"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"}
@@ -40,27 +40,40 @@ type ConfigEvaluation = {
40
40
  context?: ConfigDirectorContext;
41
41
  };
42
42
  /**
43
- * The user's context to be sent to ConfigDirector. This context will be used for targeting
44
- * rules evaluation.
43
+ * The context to be sent to ConfigDirector: the user or other entity, such as a device or an
44
+ * organization, that configs are evaluated for. This context will be used for targeting rules
45
+ * evaluation.
45
46
  */
46
47
  type ConfigDirectorContext = {
47
48
  /**
48
- * The user's identifier. This should be a value that uniquely identifies an application
49
- * user.
49
+ * The context's identifier. This should be a value that uniquely identifies the user or other
50
+ * entity among contexts of the same {@link kind}.
50
51
  * In the case of anonymous users, you could generate a UUID or alternatively not provide
51
52
  * the {@link id} and the SDK will generate a random UUID. However, keep in mind that this
52
- * value is used for segmenting users in percentage rollouts, and changes to the {@link id}
53
- * could result in the user being assigned to a different percentile.
53
+ * value is used for segmenting contexts in percentage rollouts, and changes to the {@link id}
54
+ * could result in the context being assigned to a different percentile.
54
55
  */
55
56
  id?: string;
56
57
  /**
57
- * The user's display name. This will be shown in the ConfigDirector dashboard and may be
58
+ * The kind of entity this context represents, such as `user`, `device`, `organization`,
59
+ * `tenant` or `listing`. You can use any other kind that fits your application. A kind has 1
60
+ * to 64 characters, using only lowercase letters (`a`-`z`), digits, `-` and `_`. It is used
61
+ * exactly as given, without trimming or changing its case.
62
+ *
63
+ * A context with an invalid kind is ignored as a whole: the SDK logs an error and evaluates
64
+ * as if no context had been provided.
65
+ *
66
+ * @default "user"
67
+ */
68
+ kind?: string;
69
+ /**
70
+ * The context's display name. This will be shown in the ConfigDirector dashboard and may be
58
71
  * used for targeting rules.
59
72
  */
60
73
  name?: string;
61
74
  /**
62
- * Any arbitrary traits for the current user. They will be shown in the ConfigDirector
63
- * dashboard and may be used for targeting rules.
75
+ * Any arbitrary traits for the context. They will be shown in the ConfigDirector dashboard
76
+ * and may be used for targeting rules.
64
77
  */
65
78
  traits?: {
66
79
  [key: string]: unknown;
@@ -74,9 +87,29 @@ type ConfigDirectorContext = {
74
87
  */
75
88
  anonymous?: boolean;
76
89
  };
77
- type ConnectionMode = "streaming" | "polling";
90
+ type ConnectionMode = "streaming" | "polling" | "offline";
91
+ //#endregion
92
+ //#region ../shared/src/native-values.d.ts
93
+ type NativeJsonValue = boolean | number | string | null | NativeJsonValue[] | {
94
+ [key: string]: NativeJsonValue;
95
+ };
96
+ type NativeValue = boolean | number | string | NativeJsonValue[] | {
97
+ [key: string]: NativeJsonValue;
98
+ };
99
+ type NativeValues = Record<string, NativeValue>;
78
100
  //#endregion
79
101
  //#region ../js-client-core/src/types.d.ts
102
+ /**
103
+ * One value an offline client serves for a config key. The config type follows from the value: a
104
+ * boolean, an integral number (`integer`), any other number (`float`), a string, or a plain object
105
+ * or array (`json`).
106
+ */
107
+ type OfflineValue = NativeValue;
108
+ /**
109
+ * The values an offline client serves, keyed by config key. See {@link OfflineValue} for how each
110
+ * value's config type is decided. Every context receives the same value.
111
+ */
112
+ type OfflineValues = NativeValues;
80
113
  type ClientHooks = {
81
114
  clientReady?: HookHandler<"clientReady"> | HookHandler<"clientReady">[];
82
115
  configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
@@ -126,17 +159,25 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
126
159
  * not attempt to re-connect and an error will be logged to the console or the provided
127
160
  * logger.
128
161
  *
129
- * @param context The current user's context to be used for evaluating targeting rules (optional).
162
+ * A context whose {@link ConfigDirectorContext.kind} is invalid is ignored as a whole: an error is
163
+ * logged, and the client connects as if no context had been provided.
164
+ *
165
+ * @param context The current context to be used for evaluating targeting rules (optional).
130
166
  */
131
167
  initialize(context?: ConfigDirectorContext): Promise<void>;
132
168
  /**
133
- * Updates the user's context and re-evaluates all config and flag values based on the new context.
169
+ * Updates the context and re-evaluates all config and flag values based on the new context.
170
+ *
171
+ * A context whose {@link ConfigDirectorContext.kind} is invalid is ignored as a whole: an error is
172
+ * logged, the previous context is no longer used, and the client connects as if no context had been
173
+ * provided.
134
174
  *
135
- * @param context The current user's context to be used for evaluating targeting rules (required).
175
+ * @param context The current context to be used for evaluating targeting rules (required).
136
176
  */
137
177
  updateContext(context: ConfigDirectorContext): Promise<void>;
138
178
  /**
139
- * Returns the current context the client is configured with, or undefined if there is no context.
179
+ * Returns the current context the client is configured with, or undefined if there is no context,
180
+ * including after a context with an invalid kind was ignored.
140
181
  *
141
182
  * Note that the value returned does not updated immediately after calling {@link updateContext}. Only
142
183
  * once the underlying connection was successful or timed out, the current context is updated. This is
@@ -216,5 +257,5 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
216
257
  dispose(): void;
217
258
  }
218
259
  //#endregion
219
- export { ConfigDirectorLoggingLevel as a, ConnectionMode as c, ConfigDirectorLogger as i, ConfigDirectorClient as n, ConfigState as o, ConfigDirectorContext as r, ConfigValueType as s, ClientHooks as t };
220
- //# sourceMappingURL=types-fL7X5zwg.d.cts.map
260
+ export { NativeJsonValue as a, ConfigDirectorContext as c, ConfigState as d, ConfigValueType as f, OfflineValues as i, ConfigDirectorLogger as l, ConfigDirectorClient as n, NativeValue as o, ConnectionMode as p, OfflineValue as r, NativeValues as s, ClientHooks as t, ConfigDirectorLoggingLevel as u };
261
+ //# sourceMappingURL=types-2Z0rA7lF.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-2Z0rA7lF.d.cts","names":[],"sources":["../../../js-client-core/src/Emitter.ts","../../../shared/src/types.ts","../../../shared/src/native-values.ts","../../../js-client-core/src/types.ts"],"mappings":"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"}
@@ -40,27 +40,40 @@ type ConfigEvaluation = {
40
40
  context?: ConfigDirectorContext;
41
41
  };
42
42
  /**
43
- * The user's context to be sent to ConfigDirector. This context will be used for targeting
44
- * rules evaluation.
43
+ * The context to be sent to ConfigDirector: the user or other entity, such as a device or an
44
+ * organization, that configs are evaluated for. This context will be used for targeting rules
45
+ * evaluation.
45
46
  */
46
47
  type ConfigDirectorContext = {
47
48
  /**
48
- * The user's identifier. This should be a value that uniquely identifies an application
49
- * user.
49
+ * The context's identifier. This should be a value that uniquely identifies the user or other
50
+ * entity among contexts of the same {@link kind}.
50
51
  * In the case of anonymous users, you could generate a UUID or alternatively not provide
51
52
  * the {@link id} and the SDK will generate a random UUID. However, keep in mind that this
52
- * value is used for segmenting users in percentage rollouts, and changes to the {@link id}
53
- * could result in the user being assigned to a different percentile.
53
+ * value is used for segmenting contexts in percentage rollouts, and changes to the {@link id}
54
+ * could result in the context being assigned to a different percentile.
54
55
  */
55
56
  id?: string;
56
57
  /**
57
- * The user's display name. This will be shown in the ConfigDirector dashboard and may be
58
+ * The kind of entity this context represents, such as `user`, `device`, `organization`,
59
+ * `tenant` or `listing`. You can use any other kind that fits your application. A kind has 1
60
+ * to 64 characters, using only lowercase letters (`a`-`z`), digits, `-` and `_`. It is used
61
+ * exactly as given, without trimming or changing its case.
62
+ *
63
+ * A context with an invalid kind is ignored as a whole: the SDK logs an error and evaluates
64
+ * as if no context had been provided.
65
+ *
66
+ * @default "user"
67
+ */
68
+ kind?: string;
69
+ /**
70
+ * The context's display name. This will be shown in the ConfigDirector dashboard and may be
58
71
  * used for targeting rules.
59
72
  */
60
73
  name?: string;
61
74
  /**
62
- * Any arbitrary traits for the current user. They will be shown in the ConfigDirector
63
- * dashboard and may be used for targeting rules.
75
+ * Any arbitrary traits for the context. They will be shown in the ConfigDirector dashboard
76
+ * and may be used for targeting rules.
64
77
  */
65
78
  traits?: {
66
79
  [key: string]: unknown;
@@ -74,9 +87,29 @@ type ConfigDirectorContext = {
74
87
  */
75
88
  anonymous?: boolean;
76
89
  };
77
- type ConnectionMode = "streaming" | "polling";
90
+ type ConnectionMode = "streaming" | "polling" | "offline";
91
+ //#endregion
92
+ //#region ../shared/src/native-values.d.ts
93
+ type NativeJsonValue = boolean | number | string | null | NativeJsonValue[] | {
94
+ [key: string]: NativeJsonValue;
95
+ };
96
+ type NativeValue = boolean | number | string | NativeJsonValue[] | {
97
+ [key: string]: NativeJsonValue;
98
+ };
99
+ type NativeValues = Record<string, NativeValue>;
78
100
  //#endregion
79
101
  //#region ../js-client-core/src/types.d.ts
102
+ /**
103
+ * One value an offline client serves for a config key. The config type follows from the value: a
104
+ * boolean, an integral number (`integer`), any other number (`float`), a string, or a plain object
105
+ * or array (`json`).
106
+ */
107
+ type OfflineValue = NativeValue;
108
+ /**
109
+ * The values an offline client serves, keyed by config key. See {@link OfflineValue} for how each
110
+ * value's config type is decided. Every context receives the same value.
111
+ */
112
+ type OfflineValues = NativeValues;
80
113
  type ClientHooks = {
81
114
  clientReady?: HookHandler<"clientReady"> | HookHandler<"clientReady">[];
82
115
  configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
@@ -126,17 +159,25 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
126
159
  * not attempt to re-connect and an error will be logged to the console or the provided
127
160
  * logger.
128
161
  *
129
- * @param context The current user's context to be used for evaluating targeting rules (optional).
162
+ * A context whose {@link ConfigDirectorContext.kind} is invalid is ignored as a whole: an error is
163
+ * logged, and the client connects as if no context had been provided.
164
+ *
165
+ * @param context The current context to be used for evaluating targeting rules (optional).
130
166
  */
131
167
  initialize(context?: ConfigDirectorContext): Promise<void>;
132
168
  /**
133
- * Updates the user's context and re-evaluates all config and flag values based on the new context.
169
+ * Updates the context and re-evaluates all config and flag values based on the new context.
170
+ *
171
+ * A context whose {@link ConfigDirectorContext.kind} is invalid is ignored as a whole: an error is
172
+ * logged, the previous context is no longer used, and the client connects as if no context had been
173
+ * provided.
134
174
  *
135
- * @param context The current user's context to be used for evaluating targeting rules (required).
175
+ * @param context The current context to be used for evaluating targeting rules (required).
136
176
  */
137
177
  updateContext(context: ConfigDirectorContext): Promise<void>;
138
178
  /**
139
- * Returns the current context the client is configured with, or undefined if there is no context.
179
+ * Returns the current context the client is configured with, or undefined if there is no context,
180
+ * including after a context with an invalid kind was ignored.
140
181
  *
141
182
  * Note that the value returned does not updated immediately after calling {@link updateContext}. Only
142
183
  * once the underlying connection was successful or timed out, the current context is updated. This is
@@ -216,5 +257,5 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
216
257
  dispose(): void;
217
258
  }
218
259
  //#endregion
219
- export { ConfigDirectorLoggingLevel as a, ConnectionMode as c, ConfigDirectorLogger as i, ConfigDirectorClient as n, ConfigState as o, ConfigDirectorContext as r, ConfigValueType as s, ClientHooks as t };
220
- //# sourceMappingURL=types-DA2VZKNX.d.mts.map
260
+ export { NativeJsonValue as a, ConfigDirectorContext as c, ConfigState as d, ConfigValueType as f, OfflineValues as i, ConfigDirectorLogger as l, ConfigDirectorClient as n, NativeValue as o, ConnectionMode as p, OfflineValue as r, NativeValues as s, ClientHooks as t, ConfigDirectorLoggingLevel as u };
261
+ //# sourceMappingURL=types-6a1uEkD0.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-6a1uEkD0.d.mts","names":[],"sources":["../../../js-client-core/src/Emitter.ts","../../../shared/src/types.ts","../../../shared/src/native-values.ts","../../../js-client-core/src/types.ts"],"mappings":"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"}