@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.
- package/dist/client/index.cjs +1 -1
- package/dist/client/index.d.cts +21 -8
- package/dist/client/index.d.cts.map +1 -1
- package/dist/client/index.d.mts +21 -8
- package/dist/client/index.d.mts.map +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/installedTestClient-BhHUzJ9D.cjs +6 -0
- package/dist/client/installedTestClient-BwTUOOhP.mjs +7 -0
- package/dist/client/installedTestClient-BwTUOOhP.mjs.map +1 -0
- package/dist/client/testing.cjs +1 -1
- package/dist/client/testing.d.cts +7 -15
- package/dist/client/testing.d.cts.map +1 -1
- package/dist/client/testing.d.mts +7 -15
- package/dist/client/testing.d.mts.map +1 -1
- package/dist/client/testing.mjs +1 -1
- package/dist/client/testing.mjs.map +1 -1
- package/dist/client/{types-fL7X5zwg.d.cts → types-2Z0rA7lF.d.cts} +57 -16
- package/dist/client/types-2Z0rA7lF.d.cts.map +1 -0
- package/dist/client/{types-DA2VZKNX.d.mts → types-6a1uEkD0.d.mts} +57 -16
- package/dist/client/types-6a1uEkD0.d.mts.map +1 -0
- package/dist/server/DefaultConfigDirectorClient-BplPvnEk.mjs +6 -0
- package/dist/server/DefaultConfigDirectorClient-BplPvnEk.mjs.map +1 -0
- package/dist/server/DefaultConfigDirectorClient-oT53aH0F.cjs +5 -0
- package/dist/server/{types-DnzM2ukj.d.cts → errors-EfmnEmll.d.cts} +130 -47
- package/dist/server/errors-EfmnEmll.d.cts.map +1 -0
- package/dist/server/{types-qIeNbtNf.d.mts → errors-r1MHQ8L5.d.mts} +130 -47
- package/dist/server/errors-r1MHQ8L5.d.mts.map +1 -0
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.d.cts +44 -9
- package/dist/server/index.d.cts.map +1 -1
- package/dist/server/index.d.mts +44 -9
- package/dist/server/index.d.mts.map +1 -1
- package/dist/server/index.mjs +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/native-values-CfqV9hAq.mjs +2 -0
- package/dist/server/native-values-CfqV9hAq.mjs.map +1 -0
- package/dist/server/native-values-UCK4hh1Z.cjs +1 -0
- package/dist/server/testing.cjs +1 -1
- package/dist/server/testing.d.cts +8 -26
- package/dist/server/testing.d.cts.map +1 -1
- package/dist/server/testing.d.mts +8 -26
- package/dist/server/testing.d.mts.map +1 -1
- package/dist/server/testing.mjs +1 -1
- package/dist/server/testing.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/client/installedTestClient-CDdDsfVQ.mjs +0 -7
- package/dist/client/installedTestClient-CDdDsfVQ.mjs.map +0 -1
- package/dist/client/installedTestClient-DH7rRR-J.cjs +0 -6
- package/dist/client/types-DA2VZKNX.d.mts.map +0 -1
- package/dist/client/types-fL7X5zwg.d.cts.map +0 -1
- package/dist/server/DefaultConfigDirectorClient-BZwAj0iJ.cjs +0 -5
- package/dist/server/DefaultConfigDirectorClient-DyFVzSHP.mjs +0 -6
- package/dist/server/DefaultConfigDirectorClient-DyFVzSHP.mjs.map +0 -1
- package/dist/server/errors-BGpFuqOO.mjs +0 -2
- package/dist/server/errors-BGpFuqOO.mjs.map +0 -1
- package/dist/server/errors-CCbuIqT8.cjs +0 -1
- package/dist/server/types-DnzM2ukj.d.cts.map +0 -1
- package/dist/server/types-qIeNbtNf.d.mts.map +0 -1
package/dist/server/testing.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./DefaultConfigDirectorClient-
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./DefaultConfigDirectorClient-oT53aH0F.cjs`),t=require(`./native-values-UCK4hh1Z.cjs`);let n=require(`node:events`);n=e.o(n);const r={environmentId:`test-environment`,projectId:`test-project`};var i=class{constructor(e,t){this.logger=e,this.emitter=new n.default,this.configs=new Map,this.pendingDeliveries=[],this.armed=`nothing`,this.connected=!1,this.delivering=!1,this.encodeAll(t).forEach((e,t)=>this.configs.set(t,e))}setValue(e,t){let n=this.encode(e,t);this.configs.set(e,n),this.deliverIfConnected({...r,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=`nothing`,this.deliverIfConnected(this.fullUpdate())}hold(){this.armed=`hold`}complete(){this.pendingHold?this.pendingHold.complete():this.armed===`hold`&&(this.armed=`nothing`)}fail(){this.pendingHold?this.pendingHold.fail():this.armed=`failure`}connect(e){this.endAttempt();let t=this.armed;switch(this.armed=`nothing`,t){case`failure`:return this.failNow();case`hold`:return this.holdNow(e);default:return this.connectNow(),Promise.resolve(this)}}get isConnected(){return this.connected}on(e,t){this.emitter.on(e,t)}off(e,t){t?this.emitter.off(e,t):this.emitter.removeAllListeners(e)}close(){this.endAttempt()}dispose(){this.close(),this.emitter.removeAllListeners()}endAttempt(){this.connected=!1,this.pendingHold?.end()}connectNow(){this.connected=!0,this.deliver(this.fullUpdate())}holdNow(e){return new Promise((t,n)=>{let r=setTimeout(()=>this.pendingHold?.end(),e),i=()=>{clearTimeout(r),this.pendingHold=void 0};this.pendingHold={end:()=>{i(),t(this)},complete:()=>{i(),this.connectNow(),t(this)},fail:()=>{i();let e=this.fatalError();this.emitter.emit(`connectionError`,e),n(e)}}})}failNow(){let e=this.fatalError();return this.emitter.emit(`connectionError`,e),Promise.reject(e)}fatalError(){return new t.r(`Connection failed with status: 401. Error: the test client failed this initialization. This is an unrecoverable error, will not attempt to reconnect.`,401)}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(`configBundleReceived`,e)}catch(e){this.logger.error(`[InMemoryConnection] Error dispatching a config update from the test client: `,e)}}fullUpdate(){return{...r,kind:`full`,configs:Object.fromEntries(this.configs)}}encodeAll(e){let t=new Map;for(let[n,r]of Object.entries(e))t.set(n,this.encode(n,r));return t}encode(t,n){return e.a(`test-config:${t}`,t,n)}},a=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()}completeInitialization(){this.connection.complete()}failInitialization(){this.connection.fail()}};const o=(t,n)=>{let r=n?.logger??e.i(),o=new i(r,n?.values??{});return new a(new e.t(`test-client`,t,{logger:r,connection:{timeout:n?.timeout}},{transport:o,telemetry:e.r}),o)},s=e=>o({sdkName:`nextjs-sdk`,sdkVersion:`1.11.0`},e),c=e=>{let t=globalThis.__configDirectorServerClient;return globalThis.__configDirectorServerClient=e.client,()=>{globalThis.__configDirectorServerClient=t}};exports.ConfigDirectorConnectionError=t.r,exports.ConfigDirectorValidationError=t.a,exports.createTestClient=s,exports.installServerTestClient=c;
|
|
@@ -1,25 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as NativeValues, i as ConfigDirectorClient, l as NativeJsonValue, m as ConfigDirectorLogger, r as ConfigDirectorValidationError, t as ConfigDirectorConnectionError, u as NativeValue } from "./errors-EfmnEmll.cjs";
|
|
2
2
|
|
|
3
|
-
//#region ../shared/src/errors.d.ts
|
|
4
|
-
declare class ConfigDirectorConnectionError extends Error {
|
|
5
|
-
readonly name: string;
|
|
6
|
-
readonly status?: number;
|
|
7
|
-
constructor(message: string, status?: number);
|
|
8
|
-
}
|
|
9
|
-
declare class ConfigDirectorValidationError extends Error {
|
|
10
|
-
readonly name: string;
|
|
11
|
-
constructor(message: string);
|
|
12
|
-
}
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region ../shared/src/testing/values.d.ts
|
|
15
|
-
type TestJsonValue = boolean | number | string | null | TestJsonValue[] | {
|
|
16
|
-
[key: string]: TestJsonValue;
|
|
17
|
-
};
|
|
18
|
-
type TestValue = boolean | number | string | TestJsonValue[] | {
|
|
19
|
-
[key: string]: TestJsonValue;
|
|
20
|
-
};
|
|
21
|
-
type TestValues = Record<string, TestValue>;
|
|
22
|
-
//#endregion
|
|
23
3
|
//#region ../js-server-sdk/src/testing/TestClient.d.ts
|
|
24
4
|
/**
|
|
25
5
|
* Options for {@link createTestClient}. Every option is optional.
|
|
@@ -31,14 +11,16 @@ type TestClientOptions = {
|
|
|
31
11
|
* number (`float`), a string, or a plain object or array (`json`). Every context receives the
|
|
32
12
|
* same value.
|
|
33
13
|
*/
|
|
34
|
-
values?:
|
|
14
|
+
values?: NativeValues;
|
|
35
15
|
/**
|
|
36
16
|
* The connection timeout of the SDK client, in milliseconds. It bounds how long a held
|
|
37
17
|
* `initialize` waits. Defaults to the SDK's production timeout.
|
|
38
18
|
*/
|
|
39
19
|
timeout?: number;
|
|
40
20
|
/**
|
|
41
|
-
* The logger the SDK client uses. Defaults to the SDK's console logger at `warn` level.
|
|
21
|
+
* The logger the SDK client uses. Defaults to the SDK's console logger at `warn` level. Since
|
|
22
|
+
* the test client sends no telemetry, every read that supplies a context with an invalid kind
|
|
23
|
+
* logs an error, rather than each invalid kind once per telemetry interval.
|
|
42
24
|
*/
|
|
43
25
|
logger?: ConfigDirectorLogger;
|
|
44
26
|
};
|
|
@@ -60,7 +42,7 @@ interface TestClient {
|
|
|
60
42
|
* Stores `value` under `key` and, when the client is connected, delivers it as an update, so
|
|
61
43
|
* reads, watchers, and `configsUpdated` handlers see it.
|
|
62
44
|
*/
|
|
63
|
-
setValue(key: string, value:
|
|
45
|
+
setValue(key: string, value: NativeValue): void;
|
|
64
46
|
/**
|
|
65
47
|
* Removes `key` and, when the client is connected, delivers a full update without it, so reads
|
|
66
48
|
* fall back to the in-code default value and watchers of `key` receive that default.
|
|
@@ -70,7 +52,7 @@ interface TestClient {
|
|
|
70
52
|
* Replaces every stored value with `values`, disarms any armed hold or failure, and, when the
|
|
71
53
|
* client is connected, delivers the new values as a full update.
|
|
72
54
|
*/
|
|
73
|
-
replaceValues(values:
|
|
55
|
+
replaceValues(values: NativeValues): void;
|
|
74
56
|
/**
|
|
75
57
|
* Makes the next `initialize` wait until {@link completeInitialization} or
|
|
76
58
|
* {@link failInitialization} is called, or until the client's timeout elapses.
|
|
@@ -115,5 +97,5 @@ declare const createTestClient: (options?: TestClientOptions) => TestClient;
|
|
|
115
97
|
*/
|
|
116
98
|
declare const installServerTestClient: (testClient: TestClient) => (() => void);
|
|
117
99
|
//#endregion
|
|
118
|
-
export { ConfigDirectorConnectionError, ConfigDirectorValidationError, type TestClient, type TestClientOptions, type TestJsonValue, type TestValue, type TestValues, createTestClient, installServerTestClient };
|
|
100
|
+
export { ConfigDirectorConnectionError, ConfigDirectorValidationError, type TestClient, type TestClientOptions, type NativeJsonValue as TestJsonValue, type NativeValue as TestValue, type NativeValues as TestValues, createTestClient, installServerTestClient };
|
|
119
101
|
//# sourceMappingURL=testing.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.cts","names":[],"sources":["../../../
|
|
1
|
+
{"version":3,"file":"testing.d.cts","names":[],"sources":["../../../js-server-sdk/src/testing/TestClient.ts","../../src/server/testing.ts"],"mappings":";;;;AAYA;;KAAY,iBAAA;EAkBmB;;;;;;EAX7B,MAAA,GAAS,YAAA;EAWoB;AAW/B;;;EAjBE,OAAA;EA4B6B;;;;;EAtB7B,MAAA,GAAS,oBAAA;AAAA;;;;;;;;;UAWM,UAAA;EAmCf;;;;EAAA,SA9BS,MAAA,EAAQ,oBAAA;;;ACxBnB;;ED8BE,QAAA,CAAS,GAAA,UAAa,KAAA,EAAO,WAAA;EC5B9B;;;;EDkCC,WAAA,CAAY,GAAA;EClCb;AAUD;;;ED8BE,aAAA,CAAc,MAAA,EAAQ,YAAA;EC9BsC;;;;EDoC5D,kBAAA;;;;;EAMA,sBAAA;;;;;EAMA,kBAAA;AAAA;;;;;;;;;;;;;AAzCF;;;;;cCnBa,gBAAA,GAAoB,OAAA,GAAU,iBAAA,KAAoB,UAAA;;;;;;;;;cAYlD,uBAAA,GAA2B,UAAA,EAAY,UAAA"}
|
|
@@ -1,25 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as NativeValues, i as ConfigDirectorClient, l as NativeJsonValue, m as ConfigDirectorLogger, r as ConfigDirectorValidationError, t as ConfigDirectorConnectionError, u as NativeValue } from "./errors-r1MHQ8L5.mjs";
|
|
2
2
|
|
|
3
|
-
//#region ../shared/src/errors.d.ts
|
|
4
|
-
declare class ConfigDirectorConnectionError extends Error {
|
|
5
|
-
readonly name: string;
|
|
6
|
-
readonly status?: number;
|
|
7
|
-
constructor(message: string, status?: number);
|
|
8
|
-
}
|
|
9
|
-
declare class ConfigDirectorValidationError extends Error {
|
|
10
|
-
readonly name: string;
|
|
11
|
-
constructor(message: string);
|
|
12
|
-
}
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region ../shared/src/testing/values.d.ts
|
|
15
|
-
type TestJsonValue = boolean | number | string | null | TestJsonValue[] | {
|
|
16
|
-
[key: string]: TestJsonValue;
|
|
17
|
-
};
|
|
18
|
-
type TestValue = boolean | number | string | TestJsonValue[] | {
|
|
19
|
-
[key: string]: TestJsonValue;
|
|
20
|
-
};
|
|
21
|
-
type TestValues = Record<string, TestValue>;
|
|
22
|
-
//#endregion
|
|
23
3
|
//#region ../js-server-sdk/src/testing/TestClient.d.ts
|
|
24
4
|
/**
|
|
25
5
|
* Options for {@link createTestClient}. Every option is optional.
|
|
@@ -31,14 +11,16 @@ type TestClientOptions = {
|
|
|
31
11
|
* number (`float`), a string, or a plain object or array (`json`). Every context receives the
|
|
32
12
|
* same value.
|
|
33
13
|
*/
|
|
34
|
-
values?:
|
|
14
|
+
values?: NativeValues;
|
|
35
15
|
/**
|
|
36
16
|
* The connection timeout of the SDK client, in milliseconds. It bounds how long a held
|
|
37
17
|
* `initialize` waits. Defaults to the SDK's production timeout.
|
|
38
18
|
*/
|
|
39
19
|
timeout?: number;
|
|
40
20
|
/**
|
|
41
|
-
* The logger the SDK client uses. Defaults to the SDK's console logger at `warn` level.
|
|
21
|
+
* The logger the SDK client uses. Defaults to the SDK's console logger at `warn` level. Since
|
|
22
|
+
* the test client sends no telemetry, every read that supplies a context with an invalid kind
|
|
23
|
+
* logs an error, rather than each invalid kind once per telemetry interval.
|
|
42
24
|
*/
|
|
43
25
|
logger?: ConfigDirectorLogger;
|
|
44
26
|
};
|
|
@@ -60,7 +42,7 @@ interface TestClient {
|
|
|
60
42
|
* Stores `value` under `key` and, when the client is connected, delivers it as an update, so
|
|
61
43
|
* reads, watchers, and `configsUpdated` handlers see it.
|
|
62
44
|
*/
|
|
63
|
-
setValue(key: string, value:
|
|
45
|
+
setValue(key: string, value: NativeValue): void;
|
|
64
46
|
/**
|
|
65
47
|
* Removes `key` and, when the client is connected, delivers a full update without it, so reads
|
|
66
48
|
* fall back to the in-code default value and watchers of `key` receive that default.
|
|
@@ -70,7 +52,7 @@ interface TestClient {
|
|
|
70
52
|
* Replaces every stored value with `values`, disarms any armed hold or failure, and, when the
|
|
71
53
|
* client is connected, delivers the new values as a full update.
|
|
72
54
|
*/
|
|
73
|
-
replaceValues(values:
|
|
55
|
+
replaceValues(values: NativeValues): void;
|
|
74
56
|
/**
|
|
75
57
|
* Makes the next `initialize` wait until {@link completeInitialization} or
|
|
76
58
|
* {@link failInitialization} is called, or until the client's timeout elapses.
|
|
@@ -115,5 +97,5 @@ declare const createTestClient: (options?: TestClientOptions) => TestClient;
|
|
|
115
97
|
*/
|
|
116
98
|
declare const installServerTestClient: (testClient: TestClient) => (() => void);
|
|
117
99
|
//#endregion
|
|
118
|
-
export { ConfigDirectorConnectionError, ConfigDirectorValidationError, type TestClient, type TestClientOptions, type TestJsonValue, type TestValue, type TestValues, createTestClient, installServerTestClient };
|
|
100
|
+
export { ConfigDirectorConnectionError, ConfigDirectorValidationError, type TestClient, type TestClientOptions, type NativeJsonValue as TestJsonValue, type NativeValue as TestValue, type NativeValues as TestValues, createTestClient, installServerTestClient };
|
|
119
101
|
//# sourceMappingURL=testing.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.d.mts","names":[],"sources":["../../../
|
|
1
|
+
{"version":3,"file":"testing.d.mts","names":[],"sources":["../../../js-server-sdk/src/testing/TestClient.ts","../../src/server/testing.ts"],"mappings":";;;;AAYA;;KAAY,iBAAA;EAkBmB;;;;;;EAX7B,MAAA,GAAS,YAAA;EAWoB;AAW/B;;;EAjBE,OAAA;EA4B6B;;;;;EAtB7B,MAAA,GAAS,oBAAA;AAAA;;;;;;;;;UAWM,UAAA;EAmCf;;;;EAAA,SA9BS,MAAA,EAAQ,oBAAA;;;ACxBnB;;ED8BE,QAAA,CAAS,GAAA,UAAa,KAAA,EAAO,WAAA;EC5B9B;;;;EDkCC,WAAA,CAAY,GAAA;EClCb;AAUD;;;ED8BE,aAAA,CAAc,MAAA,EAAQ,YAAA;EC9BsC;;;;EDoC5D,kBAAA;;;;;EAMA,sBAAA;;;;;EAMA,kBAAA;AAAA;;;;;;;;;;;;;AAzCF;;;;;cCnBa,gBAAA,GAAoB,OAAA,GAAU,iBAAA,KAAoB,UAAA;;;;;;;;;cAYlD,uBAAA,GAA2B,UAAA,EAAY,UAAA"}
|
package/dist/server/testing.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{a as e,i as t,r as n,t as r}from"./DefaultConfigDirectorClient-BplPvnEk.mjs";import{a as i,r as a}from"./native-values-CfqV9hAq.mjs";import o from"node:events";const s={environmentId:`test-environment`,projectId:`test-project`};var c=class{constructor(e,t){this.logger=e,this.emitter=new o,this.configs=new Map,this.pendingDeliveries=[],this.armed=`nothing`,this.connected=!1,this.delivering=!1,this.encodeAll(t).forEach((e,t)=>this.configs.set(t,e))}setValue(e,t){let n=this.encode(e,t);this.configs.set(e,n),this.deliverIfConnected({...s,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=`nothing`,this.deliverIfConnected(this.fullUpdate())}hold(){this.armed=`hold`}complete(){this.pendingHold?this.pendingHold.complete():this.armed===`hold`&&(this.armed=`nothing`)}fail(){this.pendingHold?this.pendingHold.fail():this.armed=`failure`}connect(e){this.endAttempt();let t=this.armed;switch(this.armed=`nothing`,t){case`failure`:return this.failNow();case`hold`:return this.holdNow(e);default:return this.connectNow(),Promise.resolve(this)}}get isConnected(){return this.connected}on(e,t){this.emitter.on(e,t)}off(e,t){t?this.emitter.off(e,t):this.emitter.removeAllListeners(e)}close(){this.endAttempt()}dispose(){this.close(),this.emitter.removeAllListeners()}endAttempt(){this.connected=!1,this.pendingHold?.end()}connectNow(){this.connected=!0,this.deliver(this.fullUpdate())}holdNow(e){return new Promise((t,n)=>{let r=setTimeout(()=>this.pendingHold?.end(),e),i=()=>{clearTimeout(r),this.pendingHold=void 0};this.pendingHold={end:()=>{i(),t(this)},complete:()=>{i(),this.connectNow(),t(this)},fail:()=>{i();let e=this.fatalError();this.emitter.emit(`connectionError`,e),n(e)}}})}failNow(){let e=this.fatalError();return this.emitter.emit(`connectionError`,e),Promise.reject(e)}fatalError(){return new a(`Connection failed with status: 401. Error: the test client failed this initialization. This is an unrecoverable error, will not attempt to reconnect.`,401)}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(`configBundleReceived`,e)}catch(e){this.logger.error(`[InMemoryConnection] Error dispatching a config update from the test client: `,e)}}fullUpdate(){return{...s,kind:`full`,configs:Object.fromEntries(this.configs)}}encodeAll(e){let t=new Map;for(let[n,r]of Object.entries(e))t.set(n,this.encode(n,r));return t}encode(t,n){return e(`test-config:${t}`,t,n)}},l=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()}completeInitialization(){this.connection.complete()}failInitialization(){this.connection.fail()}};const u=(e,i)=>{let a=i?.logger??t(),o=new c(a,i?.values??{});return new l(new r(`test-client`,e,{logger:a,connection:{timeout:i?.timeout}},{transport:o,telemetry:n}),o)},d=e=>u({sdkName:`nextjs-sdk`,sdkVersion:`1.11.0`},e),f=e=>{let t=globalThis.__configDirectorServerClient;return globalThis.__configDirectorServerClient=e.client,()=>{globalThis.__configDirectorServerClient=t}};export{a as ConfigDirectorConnectionError,i as ConfigDirectorValidationError,d as createTestClient,f as installServerTestClient};
|
|
2
2
|
//# sourceMappingURL=testing.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.mjs","names":["encodeTestValue","encodeShared","createTestClient"],"sources":["../../../shared/src/testing/values.ts","../../../js-server-sdk/src/testing/values.ts","../../../js-server-sdk/src/testing/InMemoryConnection.ts","../../../js-server-sdk/src/testing/TestClient.ts","../../src/server/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 { ConfigDefinition } 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): ConfigDefinition => {\n const encoded = encodeShared(key, value);\n return {\n id: `test-config:${key}`,\n key,\n type: encoded.type,\n variations: [{ name: null, value: encoded.value }],\n target: { defaultValue: encoded.value, rules: [] },\n };\n};\n","import EventEmitter from \"node:events\";\nimport type { ConfigBundle, ConfigDefinition, ConfigDirectorLogger, Transport, TransportEvents } from \"../types\";\nimport { ConfigDirectorConnectionError } from \"@shared/errors\";\nimport { encodeTestValue, type TestValue, type TestValues } from \"./values\";\n\ntype Armed = \"nothing\" | \"hold\" | \"failure\";\n\ntype PendingHold = {\n end: () => void;\n complete: () => void;\n fail: () => void;\n};\n\nconst TEST_ENVIRONMENT = { environmentId: \"test-environment\", projectId: \"test-project\" };\n\nexport class InMemoryConnection implements Transport {\n private readonly emitter = new EventEmitter();\n private readonly configs = new Map<string, ConfigDefinition>();\n private readonly pendingDeliveries: ConfigBundle[] = [];\n private armed: Armed = \"nothing\";\n private pendingHold: PendingHold | undefined;\n private connected = false;\n private delivering = false;\n\n constructor(\n private readonly logger: ConfigDirectorLogger,\n values: TestValues,\n ) {\n this.encodeAll(values).forEach((definition, key) => this.configs.set(key, definition));\n }\n\n public setValue(key: string, value: TestValue) {\n const definition = encodeTestValue(key, value);\n this.configs.set(key, definition);\n this.deliverIfConnected({ ...TEST_ENVIRONMENT, kind: \"delta\", configs: { [key]: definition } });\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((definition, key) => this.configs.set(key, definition));\n this.armed = \"nothing\";\n this.deliverIfConnected(this.fullUpdate());\n }\n\n public hold() {\n this.armed = \"hold\";\n }\n\n public complete() {\n if (this.pendingHold) {\n this.pendingHold.complete();\n } else if (this.armed === \"hold\") {\n this.armed = \"nothing\";\n }\n }\n\n public fail() {\n if (this.pendingHold) {\n this.pendingHold.fail();\n } else {\n this.armed = \"failure\";\n }\n }\n\n public connect(timeout: number): Promise<this> {\n this.endAttempt();\n const armed = this.armed;\n this.armed = \"nothing\";\n switch (armed) {\n case \"failure\":\n return this.failNow();\n case \"hold\":\n return this.holdNow(timeout);\n default:\n this.connectNow();\n return Promise.resolve(this);\n }\n }\n\n public get isConnected(): boolean {\n return this.connected;\n }\n\n public on<TName extends keyof TransportEvents>(name: TName, handler: (payload: TransportEvents[TName]) => void) {\n this.emitter.on(name, handler);\n }\n\n public off<TName extends keyof TransportEvents>(name: TName, handler?: (payload: TransportEvents[TName]) => void) {\n if (handler) {\n this.emitter.off(name, handler);\n } else {\n this.emitter.removeAllListeners(name);\n }\n }\n\n public close() {\n this.endAttempt();\n }\n\n public dispose() {\n this.close();\n this.emitter.removeAllListeners();\n }\n\n private endAttempt() {\n this.connected = false;\n this.pendingHold?.end();\n }\n\n private connectNow() {\n this.connected = true;\n this.deliver(this.fullUpdate());\n }\n\n private holdNow(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 end: () => {\n release();\n resolve(this);\n },\n complete: () => {\n release();\n this.connectNow();\n resolve(this);\n },\n fail: () => {\n release();\n const error = this.fatalError();\n this.emitter.emit(\"connectionError\", error);\n reject(error);\n },\n };\n });\n }\n\n private failNow(): Promise<this> {\n const error = this.fatalError();\n this.emitter.emit(\"connectionError\", error);\n return Promise.reject(error);\n }\n\n private fatalError() {\n return new ConfigDirectorConnectionError(\n \"Connection failed with status: 401. Error: the test client failed this initialization. This is an unrecoverable error, will not attempt to reconnect.\",\n 401,\n );\n }\n\n private deliverIfConnected(configBundle: ConfigBundle) {\n if (this.connected) {\n this.deliver(configBundle);\n }\n }\n\n private deliver(configBundle: ConfigBundle) {\n this.pendingDeliveries.push(configBundle);\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(configBundle: ConfigBundle) {\n try {\n this.emitter.emit(\"configBundleReceived\", configBundle);\n } catch (error) {\n this.logger.error(\"[InMemoryConnection] Error dispatching a config update from the test client: \", error);\n }\n }\n\n private fullUpdate(): ConfigBundle {\n return { ...TEST_ENVIRONMENT, kind: \"full\", configs: Object.fromEntries(this.configs) };\n }\n\n private encodeAll(values: TestValues): Map<string, ConfigDefinition> {\n const encoded = new Map<string, ConfigDefinition>();\n for (const [key, value] of Object.entries(values)) {\n encoded.set(key, encodeTestValue(key, value));\n }\n return encoded;\n }\n}\n","import { DefaultConfigDirectorClient } from \"../DefaultConfigDirectorClient\";\nimport { createDefaultLogger } from \"../logger\";\nimport type { ConfigDirectorClient, ConfigDirectorLogger, IdentifyingSdkOptions } from \"../types\";\nimport type { ServerTelemetryClient } 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`). Every context receives the\n * same value.\n */\n values?: TestValues;\n /**\n * The connection timeout of the SDK client, in milliseconds. It bounds how long a held\n * `initialize` 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,\n * targeting evaluation, watchers, events, and readiness behave as they do against ConfigDirector.\n * Only the network connection and telemetry are replaced, so no request is ever made and nothing\n * is left running after the client is 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\nconst noopTelemetryClient: ServerTelemetryClient = {\n start: () => {},\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();\n }\n\n public completeInitialization() {\n this.connection.complete();\n }\n\n public failInitialization() {\n this.connection.fail();\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 TEST_CLIENT_SDK_KEY,\n sdkOptions,\n { logger, connection: { timeout: options?.timeout } },\n { transport: connection, telemetry: noopTelemetryClient },\n );\n return new InMemoryTestClient(client, connection);\n};\n","import { createTestClient as createServerTestClient } from \"@js-server-sdk/testing/TestClient\";\nimport type { TestClient, TestClientOptions } from \"@js-server-sdk/testing/TestClient\";\n\nexport type { TestClient, TestClientOptions } from \"@js-server-sdk/testing/TestClient\";\nexport type { TestValue, TestValues, TestJsonValue } from \"@js-server-sdk/testing/values\";\nexport { ConfigDirectorConnectionError, ConfigDirectorValidationError } from \"@shared/errors\";\n\n/**\n * Creates a {@link TestClient}: a real server `ConfigDirectorClient` connected to an in-memory\n * server that the test controls. No network connection is opened and no telemetry is sent. Put it\n * where `register()` would put the server singleton with {@link installServerTestClient}.\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, installServerTestClient } from \"@configdirector/nextjs-sdk/server/testing\";\n *\n * const testClient = createTestClient({ values: { \"new-checkout\": true } });\n * const uninstall = installServerTestClient(testClient);\n * await testClient.client.initialize();\n */\nexport const createTestClient = (options?: TestClientOptions): TestClient => {\n return createServerTestClient({ sdkName: \"nextjs-sdk\", sdkVersion: \"__VERSION__\" }, options);\n};\n\n/**\n * Puts `testClient.client` where `register()` puts the server singleton, so `getConfigClient()`,\n * `generateSsrConfigSet()`, and the server `ConfigDirectorProvider` serve its values. It does not\n * initialize the client: call `await testClient.client.initialize()`, as `register()` would.\n *\n * @param testClient The server test client whose `client` to install\n * @returns A function that restores the client installed before\n */\nexport const installServerTestClient = (testClient: TestClient): (() => void) => {\n const previous = globalThis.__configDirectorServerClient;\n globalThis.__configDirectorServerClient = testClient.client;\n return () => {\n globalThis.__configDirectorServerClient = previous;\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"testing.mjs","names":["createTestClient"],"sources":["../../../js-server-sdk/src/testing/InMemoryConnection.ts","../../../js-server-sdk/src/testing/TestClient.ts","../../src/server/testing.ts"],"sourcesContent":["import EventEmitter from \"node:events\";\nimport type { ConfigBundle, ConfigDefinition, ConfigDirectorLogger, Transport, TransportEvents } from \"../types\";\nimport { ConfigDirectorConnectionError } from \"@shared/errors\";\nimport type { TestValue, TestValues } from \"./values\";\nimport { unconditionalConfig } from \"../native-values\";\n\ntype Armed = \"nothing\" | \"hold\" | \"failure\";\n\ntype PendingHold = {\n end: () => void;\n complete: () => void;\n fail: () => void;\n};\n\nconst TEST_ENVIRONMENT = { environmentId: \"test-environment\", projectId: \"test-project\" };\n\nexport class InMemoryConnection implements Transport {\n private readonly emitter = new EventEmitter();\n private readonly configs = new Map<string, ConfigDefinition>();\n private readonly pendingDeliveries: ConfigBundle[] = [];\n private armed: Armed = \"nothing\";\n private pendingHold: PendingHold | undefined;\n private connected = false;\n private delivering = false;\n\n constructor(\n private readonly logger: ConfigDirectorLogger,\n values: TestValues,\n ) {\n this.encodeAll(values).forEach((definition, key) => this.configs.set(key, definition));\n }\n\n public setValue(key: string, value: TestValue) {\n const definition = this.encode(key, value);\n this.configs.set(key, definition);\n this.deliverIfConnected({ ...TEST_ENVIRONMENT, kind: \"delta\", configs: { [key]: definition } });\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((definition, key) => this.configs.set(key, definition));\n this.armed = \"nothing\";\n this.deliverIfConnected(this.fullUpdate());\n }\n\n public hold() {\n this.armed = \"hold\";\n }\n\n public complete() {\n if (this.pendingHold) {\n this.pendingHold.complete();\n } else if (this.armed === \"hold\") {\n this.armed = \"nothing\";\n }\n }\n\n public fail() {\n if (this.pendingHold) {\n this.pendingHold.fail();\n } else {\n this.armed = \"failure\";\n }\n }\n\n public connect(timeout: number): Promise<this> {\n this.endAttempt();\n const armed = this.armed;\n this.armed = \"nothing\";\n switch (armed) {\n case \"failure\":\n return this.failNow();\n case \"hold\":\n return this.holdNow(timeout);\n default:\n this.connectNow();\n return Promise.resolve(this);\n }\n }\n\n public get isConnected(): boolean {\n return this.connected;\n }\n\n public on<TName extends keyof TransportEvents>(name: TName, handler: (payload: TransportEvents[TName]) => void) {\n this.emitter.on(name, handler);\n }\n\n public off<TName extends keyof TransportEvents>(name: TName, handler?: (payload: TransportEvents[TName]) => void) {\n if (handler) {\n this.emitter.off(name, handler);\n } else {\n this.emitter.removeAllListeners(name);\n }\n }\n\n public close() {\n this.endAttempt();\n }\n\n public dispose() {\n this.close();\n this.emitter.removeAllListeners();\n }\n\n private endAttempt() {\n this.connected = false;\n this.pendingHold?.end();\n }\n\n private connectNow() {\n this.connected = true;\n this.deliver(this.fullUpdate());\n }\n\n private holdNow(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 end: () => {\n release();\n resolve(this);\n },\n complete: () => {\n release();\n this.connectNow();\n resolve(this);\n },\n fail: () => {\n release();\n const error = this.fatalError();\n this.emitter.emit(\"connectionError\", error);\n reject(error);\n },\n };\n });\n }\n\n private failNow(): Promise<this> {\n const error = this.fatalError();\n this.emitter.emit(\"connectionError\", error);\n return Promise.reject(error);\n }\n\n private fatalError() {\n return new ConfigDirectorConnectionError(\n \"Connection failed with status: 401. Error: the test client failed this initialization. This is an unrecoverable error, will not attempt to reconnect.\",\n 401,\n );\n }\n\n private deliverIfConnected(configBundle: ConfigBundle) {\n if (this.connected) {\n this.deliver(configBundle);\n }\n }\n\n private deliver(configBundle: ConfigBundle) {\n this.pendingDeliveries.push(configBundle);\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(configBundle: ConfigBundle) {\n try {\n this.emitter.emit(\"configBundleReceived\", configBundle);\n } catch (error) {\n this.logger.error(\"[InMemoryConnection] Error dispatching a config update from the test client: \", error);\n }\n }\n\n private fullUpdate(): ConfigBundle {\n return { ...TEST_ENVIRONMENT, kind: \"full\", configs: Object.fromEntries(this.configs) };\n }\n\n private encodeAll(values: TestValues): Map<string, ConfigDefinition> {\n const encoded = new Map<string, ConfigDefinition>();\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): ConfigDefinition {\n return unconditionalConfig(`test-config:${key}`, key, value);\n }\n}\n","import { DefaultConfigDirectorClient } from \"../DefaultConfigDirectorClient\";\nimport { createDefaultLogger } from \"../logger\";\nimport type { ConfigDirectorClient, 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`). Every context receives the\n * same value.\n */\n values?: TestValues;\n /**\n * The connection timeout of the SDK client, in milliseconds. It bounds how long a held\n * `initialize` 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. Since\n * the test client sends no telemetry, every read that supplies a context with an invalid kind\n * logs an error, rather than each invalid kind once per telemetry interval.\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,\n * targeting evaluation, watchers, events, and readiness behave as they do against ConfigDirector.\n * Only the network connection and telemetry are replaced, so no request is ever made and nothing\n * is left running after the client is 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\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();\n }\n\n public completeInitialization() {\n this.connection.complete();\n }\n\n public failInitialization() {\n this.connection.fail();\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 TEST_CLIENT_SDK_KEY,\n sdkOptions,\n { logger, connection: { timeout: options?.timeout } },\n { transport: connection, telemetry: discardingTelemetryClient },\n );\n return new InMemoryTestClient(client, connection);\n};\n","import { createTestClient as createServerTestClient } from \"@js-server-sdk/testing/TestClient\";\nimport type { TestClient, TestClientOptions } from \"@js-server-sdk/testing/TestClient\";\n\nexport type { TestClient, TestClientOptions } from \"@js-server-sdk/testing/TestClient\";\nexport type { TestValue, TestValues, TestJsonValue } from \"@js-server-sdk/testing/values\";\nexport { ConfigDirectorConnectionError, ConfigDirectorValidationError } from \"@shared/errors\";\n\n/**\n * Creates a {@link TestClient}: a real server `ConfigDirectorClient` connected to an in-memory\n * server that the test controls. No network connection is opened and no telemetry is sent. Put it\n * where `register()` would put the server singleton with {@link installServerTestClient}.\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, installServerTestClient } from \"@configdirector/nextjs-sdk/server/testing\";\n *\n * const testClient = createTestClient({ values: { \"new-checkout\": true } });\n * const uninstall = installServerTestClient(testClient);\n * await testClient.client.initialize();\n */\nexport const createTestClient = (options?: TestClientOptions): TestClient => {\n return createServerTestClient({ sdkName: \"nextjs-sdk\", sdkVersion: \"__VERSION__\" }, options);\n};\n\n/**\n * Puts `testClient.client` where `register()` puts the server singleton, so `getConfigClient()`,\n * `generateSsrConfigSet()`, and the server `ConfigDirectorProvider` serve its values. It does not\n * initialize the client: call `await testClient.client.initialize()`, as `register()` would.\n *\n * @param testClient The server test client whose `client` to install\n * @returns A function that restores the client installed before\n */\nexport const installServerTestClient = (testClient: TestClient): (() => void) => {\n const previous = globalThis.__configDirectorServerClient;\n globalThis.__configDirectorServerClient = testClient.client;\n return () => {\n globalThis.__configDirectorServerClient = previous;\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
var e=class{constructor(){this.handlerMap=new Map}on(e,t){let n=this.handlerMap.get(e);n?n.push(t):this.handlerMap.set(e,[t])}once(e,t){let n=this;function r(i){n.off(e,r),t.apply(n,i)}this.on(e,r)}off(e,t){let n=this.handlerMap.get(e);if(n)if(t){let e=n.indexOf(t);e>=0&&n.splice(e,1)}else this.handlerMap.set(e,[])}clear(){this.handlerMap.clear()}emit(e,t){let n=this.handlerMap.get(e);n&&n.slice().map(e=>e(t))}},t=class e extends Error{constructor(t,n){super(t),this.name=`ConfigDirectorConnectionError`,this.status=n,Object.setPrototypeOf(this,e.prototype)}},n=class e extends Error{constructor(t){super(t),this.name=`ConfigDirectorValidationError`,Object.setPrototypeOf(this,e.prototype)}},r=class e extends Error{constructor(t){super(t),this.name=`StreamClosedError`,Object.setPrototypeOf(this,e.prototype)}},i=class e extends Error{constructor(t){super(t),this.name=`MissingResponseBodyError`,Object.setPrototypeOf(this,e.prototype)}},a=class e extends Error{constructor(t){super(t),this.name=`InvalidOptionError`,Object.setPrototypeOf(this,e.prototype)}},o=class e extends Error{constructor(t){super(t),this.name=`ValueOutOfRangeError`,Object.setPrototypeOf(this,e.prototype)}};function s(e){"@babel/helpers - typeof";return s=typeof Symbol==`function`&&typeof Symbol.iterator==`symbol`?function(e){return typeof e}:function(e){return e&&typeof Symbol==`function`&&e.constructor===Symbol&&e!==Symbol.prototype?`symbol`:typeof e},s(e)}function c(e,t){if(s(e)!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||`default`);if(s(r)!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function l(e){var t=c(e,`string`);return s(t)==`symbol`?t:t+``}function u(e,t,n){return(t=l(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function d(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function f(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]==null?{}:arguments[t];t%2?d(Object(n),!0).forEach(function(t){u(e,t,n[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):d(Object(n)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))})}return e}var p=class{constructor(e){var t;this.isFirstChunk=!0,this.bufferedInput=``,this.currentEvent={data:``},this.currentEvent={data:``},this.onEvent=(t=e.onEvent)==null?(()=>{}):t,this.onRetry=e.onRetry,this.onComment=e.onComment}parse(e){let t=e;this.isFirstChunk&&(this.isFirstChunk=!1,t.startsWith(``)?t=t.slice(1):t.startsWith(``)&&(t=t.slice(3)));let n=this.scanLines(this.bufferedInput+t);for(let e of n)this.dispatchLine(e)}finish(){this.bufferedInput=``}scanLines(e){let t=[],n=0,r=0;for(;r<e.length;){let i=e[r];i===`\r`||i===`
|
|
3
|
-
`?(t.push(e.slice(n,r)),i===`\r`&&e[r+1]===`
|
|
4
|
-
`&&r++,r++,n=r):r++}return this.bufferedInput=e.slice(n),t}dispatchLine(e){if(e.startsWith(`:`)){var t;(t=this.onComment)==null||t.call(this,this.extractValue(e,1));return}if(e===``){this.emitEvent();return}this.applyField(this.parseField(e))}emitEvent(){let e=this.currentEvent.data.endsWith(`
|
|
5
|
-
`)?this.currentEvent.data.slice(0,-1):this.currentEvent.data;e!==``&&this.onEvent(f(f({},this.currentEvent),{},{id:this.lastEventId,data:e})),this.currentEvent={data:``}}applyField(e){switch(e.field){case`event`:this.currentEvent.type=e.value;break;case`data`:this.currentEvent.data+=e.value+`
|
|
6
|
-
`;break;case`id`:e.value.includes(`\0`)||(this.lastEventId=e.value);break;case`retry`:if(/^\d+$/.test(e.value)){var t;(t=this.onRetry)==null||t.call(this,Number.parseInt(e.value,10))}break;default:break}}parseField(e){let t=e.indexOf(`:`);return t===-1?{field:e,value:``}:{field:e.slice(0,t),value:this.extractValue(e,t+1)}}extractValue(e,t){return e[t]===` `?e.slice(t+1):e.slice(t)}};let m=function(e){return e.OPEN=`open`,e.CLOSED=`closed`,e.CONNECTING=`connecting`,e}({});var h=class{constructor(e){var t,n;this.DEFAULT_SHOULD_RECONNECT=()=>!0,this.DEFAULT_CALCULATE_RECONNECT_DELAY=()=>this.serverReconnectionTime,this.reconnectAttempt=0,this.abortController=new AbortController,this._readyState=m.CLOSED,this.serverReconnectionTime=2e3,this.url=e.url.toString(),this.lastEventId=e.lastEventId,this.headers=f({Accept:`text/event-stream`},e.headers);let r=e.body;this.body=typeof r==`function`?r:()=>r,this.requestOptions={method:(t=e.method)==null?`GET`:t,mode:e.mode,credentials:e.credentials,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy},this._fetch=(n=e.fetch)==null?((e,t)=>fetch(e,t)):n,this._onError=this.resolveCallback(e.onError,()=>{},`onError`),this._onConnect=this.resolveCallback(e.onConnect,()=>{},`onConnect`),this._onDisconnect=this.resolveCallback(e.onDisconnect,()=>{},`onDisconnect`),this._onMessage=this.resolveCallback(e.onMessage,()=>{},`onMessage`),this._onComment=this.resolveCallback(e.onComment,()=>{},`onComment`),this._shouldReconnect=this.resolveCallback(e.shouldReconnect,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`),this._calculateReconnectDelay=this.resolveCallback(e.calculateReconnectDelay,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`calculateReconnectDelay`)}set onError(e){this._onError=this.resolveCallback(e,()=>{},`onError`)}set onMessage(e){this._onMessage=this.resolveCallback(e,()=>{},`onMessage`)}set onComment(e){this._onComment=this.resolveCallback(e,()=>{},`onComment`)}set onConnect(e){this._onConnect=this.resolveCallback(e,()=>{},`onConnect`)}set onDisconnect(e){this._onDisconnect=this.resolveCallback(e,()=>{},`onDisconnect`)}set shouldReconnect(e){this._shouldReconnect=this.resolveCallback(e,this.DEFAULT_SHOULD_RECONNECT,`shouldReconnect`)}set calculateReconnectDelay(e){this._calculateReconnectDelay=this.resolveCallback(e,this.DEFAULT_CALCULATE_RECONNECT_DELAY,`shouldReconnect`)}get readyState(){return this._readyState}connect(){this._readyState===m.CLOSED&&(this.reconnectAttempt=0,this.initiateConnection())}close(){clearTimeout(this.reconnectTimeout),this._readyState=m.CLOSED,this.abortController.abort()}initiateConnection(){this.abortController=new AbortController,this._readyState=m.CONNECTING,this._fetch(this.url,f(f({},this.requestOptions),{},{body:this.body(),headers:this.buildRequestHeaders(),signal:this.abortController.signal,cache:`no-store`})).then(e=>this.handleFetchResponse(e)).catch(e=>{if(e.name===`AbortError`||this.abortController.signal.aborted){this.close();return}this._onError(e),this.scheduleReconnect({error:e})})}async handleFetchResponse(e){var t=this;let{body:n,status:a}=e;if(a===204){t.disconnected();return}if(a>=400){t.scheduleReconnect({status:a});return}if(!n)throw new i(`The server response did not have a body`);t._readyState=m.OPEN,t._onConnect(),t.reconnectAttempt=0;let o=new p({onEvent:e=>{typeof e.id==`string`&&(t.lastEventId=e.id),t._onMessage(e)},onComment:e=>t._onComment(e),onRetry:e=>t.serverReconnectionTime=e});await t.readResponseStream(n,o),t.scheduleReconnect({status:a,error:new r(`The server response stream was closed`)})}async readResponseStream(e,t){let n=e.getReader(),r=new TextDecoder,i=!1;for(;!i;){let e=await n.read();e.value&&t.parse(r.decode(e.value,{stream:!0})),i=e.done}t.finish()}disconnected(){clearTimeout(this.reconnectTimeout),this._readyState=m.CLOSED,this._onDisconnect()}buildRequestHeaders(){let e=this.lastEventId?{"Last-Event-ID":this.lastEventId}:void 0;return f(f({},this.headers),e)}scheduleReconnect(e){if(this.abortController.signal.aborted)return;clearTimeout(this.reconnectTimeout),this.reconnectAttempt+=1;let t={attempt:this.reconnectAttempt,status:e.status,error:e.error,serverReconnectionTime:this.serverReconnectionTime};if(!this._shouldReconnect(t)){this.disconnected();return}this._readyState=m.CONNECTING;let n=this._calculateReconnectDelay(t);(!Number.isFinite(n)||n<1||n>1e3*60*60)&&(this._onError(new o(`The calculated reconnect delay is out of range: ${n}. Defaulting to ${this.serverReconnectionTime}`)),n=this.serverReconnectionTime),this.reconnectTimeout=setTimeout(()=>this.initiateConnection(),n)}resolveCallback(e,t,n){if(e==null)return t;if(typeof e!=`function`)throw new a(`${n} must be a function`);return e}};const g=e=>new h(e);var _=class{constructor(t){this.options=t,this.eventEmitter=new e,this.options=t,this.logger=t.logger,this.url=t.resolveUrl(`client/sse/v1`,t.baseUrl)}async connect(e,t){var n=this;n.eventSource&&n.close();let r=new Promise((t,r)=>{n.eventSource=g({url:n.url,method:`POST`,headers:{"Content-Type":`application/json`},fetch:n.options.fetch,body:JSON.stringify({givenContext:e,metaContext:n.options.metaContext,clientSdkKey:n.options.clientSdkKey,instanceId:n.options.instanceId}),onMessage:({data:e})=>{n.dispatchMessage(e)},onConnect:()=>{n.logger.debug(`[StreamingTransport] Connected`),t(n)},shouldReconnect:e=>{let t=!n.isStatusFatal(e.status);if(!t){let t=n.prepareFatalError(e.status,e.error);n.eventEmitter.emit(`connectionError`,t),r(t)}return t},calculateReconnectDelay:e=>{let t=n.options.connectionRetryDelay(e.attempt),r=`[StreamingTransport] Scheduling reconnect attempt #${e.attempt} in ${t}ms.`;return e.attempt<=5?n.logger.info(r):n.logger.warn(r),t},onError:e=>{n.logger.debug(`[StreamingTransport] Error: `,e)},onDisconnect:()=>{n.logger.debug(`[StreamingTransport] Disconnected`)}}),n.eventSource.connect()});return clearTimeout(n.timeoutTimer),n.timeoutTimer=void 0,Promise.race([r,new Promise(e=>{n.timeoutTimer=setTimeout(()=>e(n),t)})]).finally(()=>clearTimeout(n.timeoutTimer))}dispatchMessage(e){try{let t=JSON.parse(e);this.eventEmitter.emit(`configSetReceived`,t)}catch(e){this.logger.error(`[StreamingTransport] Error parsing and dispatching config data update: `,e)}}prepareFatalError(e,n){let r=e==null?0:e;return new t(`${`Connection failed with status: ${e==null?`unknown`:e}.`}${n?` Error: ${n}`:``}. This is an unrecoverable error, will not attempt to reconnect.`,r)}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}on(e,t){this.eventEmitter.on(e,t)}off(e,t){this.eventEmitter.off(e,t)}clear(){this.eventEmitter.clear()}close(){var e;(e=this.eventSource)==null||e.close(),clearTimeout(this.timeoutTimer)}dispose(){this.close(),this.clear()}};const v=e=>{let t=typeof e;if(t===`object`)try{var n,r;return(n=(r=e.constructor)==null?void 0:r.name)==null?t:n}catch(e){return t}else if(t===`function`)try{let n=e.name;return n?`function: ${n}`:t}catch(e){return t}return t},y=e=>e===`number`||e===`bigint`,b=new Set([`boolean`,`integer`,`float`]),x=(e,t)=>{let n=e.value,r=v(t);if(n==null||n===``)return{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`value-missing`};if(e.type===`json`)return E(n,t,r,e.valueId);if(typeof t==`string`)return b.has(e.type)?{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`type-mismatch`}:{parsedValue:n,parsedValueId:e.valueId,requestedType:r,usedDefault:!1,reason:`found-match`};if(typeof t==`boolean`&&(e.type===`boolean`||e.type===`string`||e.type===`enum`)){let i=S(n),a=typeof i==`boolean`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-boolean`}}if(y(typeof t)&&e.type===`integer`){let i=C(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}if(y(typeof t)&&(e.type===`float`||e.type===`enum`||e.type===`string`)){let i=w(n),a=typeof i==`number`;return{parsedValue:a?i:t,parsedValueId:a?e.valueId:void 0,requestedType:r,usedDefault:!a,reason:a?`found-match`:`invalid-number`}}return{parsedValue:t,parsedValueId:void 0,requestedType:r,usedDefault:!0,reason:`type-mismatch`}},S=e=>{if(!e)return;let t=e.toLowerCase();if(!(t!=`true`&&t!=`false`))return t===`true`},C=e=>{let t=T(e);return t==null?void 0:Math.trunc(t)},w=e=>T(e),T=e=>{if(!e||!ee(e))return;let t=Number(e);if(!(isNaN(t)||!isFinite(t)))return t},ee=e=>{for(let t of e)if(!(t>=`0`&&t<=`9`)&&!`+-.eE`.includes(t))return!1;return!0},E=(e,t,n,r)=>{if(typeof t==`string`)return{parsedValue:e,parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`};try{let i=JSON.parse(e);return D(i,t)?{parsedValue:i,parsedValueId:r,requestedType:n,usedDefault:!1,reason:`found-match`}:{parsedValue:t,parsedValueId:void 0,requestedType:n,usedDefault:!0,reason:`type-mismatch`}}catch(e){return{parsedValue:t,parsedValueId:void 0,requestedType:n,usedDefault:!0,reason:`invalid-json`}}},D=(e,t)=>{if(e===null)return!1;let n=typeof t;return n===`bigint`?typeof e==`number`:typeof e===n},O={off:-1,error:0,warn:1,info:2,debug:3},k=()=>{let e={year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,timeZoneName:`short`};try{return new Intl.DateTimeFormat(void 0,f(f({},e),{},{fractionalSecondDigits:3}))}catch(t){return new Intl.DateTimeFormat(void 0,e)}};var A=class{constructor(e,t){this.level=e,this.decorator=t,this.level=e,this.decorator=t,this.dateFormatter=k()}debug(e,...t){this.log(console.debug,`debug`,e,...t)}info(e,...t){this.log(console.info,`info`,e,...t)}warn(e,...t){this.log(console.warn,`warn`,e,...t)}error(e,...t){this.log(console.error,`error`,e,...t)}log(e,t,n,...r){if(O[this.level]>=O[t]){var i;e(`[${this.dateFormatter.format(new Date)}] ${(i=this.decorator)==null?void 0:i.decorateMessage(n)}`,...r)}}},j=class{decorateMessage(e){return`[ConfigDirector:js-client-sdk] ${e}`}};const M=(e,t)=>new A(e==null?`warn`:e,t==null?new j:t),N=(e,t)=>new URL(e,t==null?void 0:t.toString()),P=e=>{if(e)try{return new URL(e)}catch(t){throw new n(`Invalid URL '${e}'. Parsing failed: ${t}`)}},F=`https://client-sdk-api.configdirector.com`,I=(e,t)=>e===void 0?{seconds:t.defaultSeconds,warning:void 0}:e>=t.minimumSeconds?{seconds:e,warning:void 0}:{seconds:t.minimumSeconds,warning:`pollingInterval of ${e} seconds is below the minimum of ${t.minimumSeconds} seconds. Using ${t.minimumSeconds} seconds.`},L=()=>typeof crypto<`u`&&typeof crypto.randomUUID==`function`?crypto.randomUUID():`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e===`x`?t:t&3|8).toString(16)}),R=`configdirector-sdk:instance-id`,z=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,B=(e,t)=>{let n=t;for(let t=0;t<e.length;t++)n^=e.charCodeAt(t),n=Math.imul(n,16777619);return n>>>0},V=(e,t)=>{let n=`configdirector.instanceId.v1|${e}|${t}`,r=B(n,2166136261);return B(`${r}|${n}`,16777619).toString(16).padStart(8,`0`)+r.toString(16).padStart(8,`0`)},H=()=>{try{var e;return(e=globalThis.localStorage)==null?void 0:e}catch(e){return}},U=e=>{if(!e||typeof e!=`object`)return!1;let{v:t,id:n,createdAt:r,checksum:i}=e;return t!==1||typeof n!=`string`||typeof i!=`string`||typeof r!=`number`||!Number.isInteger(r)||r<=0||!z.test(n)?!1:i===V(n,r)},W=(e,t)=>{let n;try{n=e.getItem(R)}catch(e){t==null||t.debug(`[ConfigDirectorClient] Could not read the stored instance id, generating a new one`,e);return}if(!n)return;let r;try{r=JSON.parse(n)}catch(e){t==null||t.debug(`[ConfigDirectorClient] The stored instance id is not valid JSON, generating a new one`);return}if(!U(r)){t==null||t.debug(`[ConfigDirectorClient] The stored instance id failed validation, generating a new one`);return}let i=Date.now()-r.createdAt;if(i<0||i>=216e5){t==null||t.debug(`[ConfigDirectorClient] The stored instance id has expired, generating a new one`);return}return r.id},G=(e,t,n)=>{let r=Date.now(),i={v:1,id:t,createdAt:r,checksum:V(t,r)};try{e.setItem(R,JSON.stringify(i))}catch(e){n==null||n.debug(`[ConfigDirectorClient] Could not persist the instance id`,e)}},K=e=>{let t=H();if(!t)return L();let n=W(t,e);if(n)return n;let r=L();return G(t,r,e),r};var q=class{constructor(){this.fatalError=!1}clearPollingInterval(){clearInterval(this.pollingInterval)}schedulePollingInterval(e){clearInterval(this.pollingInterval),!(this.pollingIntervalSeconds<=0)&&(this.pollingInterval=setInterval(e,this.pollingIntervalSeconds*1e3))}async handleNonOkResponse(e){var n=this;if(!e.ok)throw n.isStatusFatal(e.status)?(n.fatalError=!0,n.close(),n.prepareFatalResponseStatusError(e.status,await e.text())):new t(`Connection failed with status: ${e.status}`,e.status)}handleFetchError(e){throw this.isFetchErrorFatal(e)?(this.fatalError=!0,this.close(),new t(`Connection failed with fatal error: ${e}. This is an unrecoverable error, retry attempts will be ignored.`)):e instanceof SyntaxError?new t(`Failed to parse the response from the server: ${e}`):new t(`Connection failed with error: ${e}.`)}prepareFatalResponseStatusError(e,n){var r,i;let a=e==null?0:e;return new t(`${`Connection failed with status: ${e==null?`unknown`:e}`}${((r=n==null||(i=n.trim())==null?void 0:i.length)==null?0:r)>0?` (${n})`:``}. This is an unrecoverable error, retry attempts will be ignored.`,a)}isStatusFatal(e){return!!e&&e>=400&&e<500&&e!=429}isFetchErrorFatal(e){return(e==null?void 0:e.name)===`NotAllowedError`}get isConnected(){return this.pollingInterval!==void 0}close(){clearInterval(this.pollingInterval),this.pollingInterval=void 0}dispose(){this.close(),this.clear()}};const J=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),f(f({},n),{},{signal:i.signal}));return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}};var Y=class extends q{constructor(t){var n;super(),this.options=t,this.eventEmitter=new e,this.options=t,this.logger=t.logger,this.url=t.resolveUrl(`client/polling/v1`,t.baseUrl),this.pollingIntervalSeconds=(n=t.pollingInterval)==null?60:n}async connect(e,t){var n=this;n.clearPollingInterval();try{await n.fetchConfigs(e,t)}finally{n.fatalError||n.schedulePollingInterval(()=>{n.fetchConfigs(e,t).catch(e=>{n.logger.error(`[PollingTransport] Error during polling:`,e)})})}return n}async fetchConfigs(e,t){var n=this;if(n.fatalError){n.logger.warn(`[PollingTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.`);return}try{let r=await J(t,n.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({givenContext:e,metaContext:n.options.metaContext,clientSdkKey:n.options.clientSdkKey,instanceId:n.options.instanceId,lastUpdateTimestamp:n.lastUpdateTimestamp})},n.logger);if(await n.handleNonOkResponse(r),r.status==200){let e=JSON.parse(await r.text());n.lastUpdateTimestamp=e.timestamp,n.eventEmitter.emit(`configSetReceived`,e)}}catch(e){try{n.handleFetchError(e)}catch(e){throw n.fatalError&&n.eventEmitter.emit(`connectionError`,e),e}}}on(e,t){this.eventEmitter.on(e,t)}off(e,t){this.eventEmitter.off(e,t)}clear(){this.eventEmitter.clear()}};const X=e=>{try{return e(globalThis)}catch(e){return}},Z=()=>X(e=>{var t;return(t=e.navigator)==null?void 0:t.userAgent}),Q=()=>X(e=>{var t;return(t=e.location)==null?void 0:t.host});var te=class{constructor(t,n,r,i,a){var o,s,c,l,u,d,p,m,h,g,_,v,y,b;this.handlersMap=new Map,this.eventEmitter=new e,this.ready=!1,this.initializing=!1,this.hasReceivedConfigSet=!1,this.logger=(o=i==null?void 0:i.logger)==null?M():o,this.instanceId=K(this.logger),this.timeout=(s=i==null||(c=i.connection)==null?void 0:c.timeout)==null?3e3:s,this.validateSdkKeyPresence(n);let x=(l=a==null?void 0:a.urlFactory)==null?N:l,S=(u=this.parseUrl(i==null||(d=i.connection)==null?void 0:d.url,x))==null?F:u;this.connectionMode=(p=i==null||(m=i.connection)==null?void 0:m.mode)==null?`streaming`:p,this.telemetryClient=t,this.transport=(h=a==null?void 0:a.transport)==null?this.buildTransport(n,r,S,x,i,a):h,this.transport.on(`configSetReceived`,e=>{var t;this.hasReceivedConfigSet=!0,(t=this.readyResolve)==null||t.call(this);let n=this.findKeysRemovedBy(e);!this.configSet||e.kind==`full`?this.configSet=e:this.configSet.configs=f(f({},this.configSet.configs),e.configs);let r=Object.keys(e.configs);this.emit(`configsUpdated`,{keys:r,removedKeys:n}),this.updateWatchers([...r,...n]),this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server:`,{keys:r,removedKeys:n})}),this.transport.on(`connectionError`,e=>{this.initializing=!1,this.logger.error(`[ConfigDirectorClient] The connection encountered an unrecoverable error and will not be retried. Configs will no longer receive updates.`,e),this.emit(`connectionError`,{error:e})}),this.registerHandler(`clientReady`,i==null||(g=i.hooks)==null?void 0:g.clientReady),this.registerHandler(`configEvaluated`,i==null||(_=i.hooks)==null?void 0:_.configEvaluated),this.registerHandler(`configsUpdated`,i==null||(v=i.hooks)==null?void 0:v.configsUpdated),this.registerHandler(`contextUpdated`,i==null||(y=i.hooks)==null?void 0:y.contextUpdated),this.registerHandler(`connectionError`,i==null||(b=i.hooks)==null?void 0:b.connectionError)}registerHandler(e,t){if(!t)return;let n=Array.isArray(t)?t:[t];for(let t of n)typeof t==`function`&&this.on(e,t)}resolvePollingIntervalForMode(e){if(this.connectionMode!==`polling`)return;let t=I(e,{defaultSeconds:60,minimumSeconds:30});return t.warning&&this.logger.warn(`[ConfigDirectorClient] ${t.warning}`),t.seconds}buildTransport(e,t,n,r,i,a){var o,s;return new(this.getTransportConstructor(this.connectionMode))({instanceId:this.instanceId,clientSdkKey:e,baseUrl:n,resolveUrl:r,metaContext:f(f({},i==null?void 0:i.metadata),{},{sdkName:t.sdkName,sdkVersion:t.sdkVersion,userAgent:Z(),host:Q()}),logger:this.logger,fetch:a==null?void 0:a.fetch,connectionRetryDelay:(o=a==null?void 0:a.connectionRetryDelay)==null?(e=>2**Math.min(e,9)*1e3):o,pollingInterval:this.resolvePollingIntervalForMode(i==null||(s=i.connection)==null?void 0:s.pollingInterval)})}getTransportConstructor(e){switch(e){case`polling`:return Y;default:return _}}async initialize(e){var t=this;t.hasReceivedConfigSet||(t.initializing=!0),await t.connectToTransport(e,`initialization`)}async updateContext(e){await this.connectToTransport(e,`context update`)}get context(){return this.currentContext}async connectToTransport(e,t){var n=this;try{n.ready=!1,n.readyPromise=new Promise(e=>{n.readyResolve=e}).then(e=>{e||(n.ready=!0,n.initializing=!1,n.emit(`clientReady`,{action:t}),n.logger.debug(`[ConfigDirectorClient] Received initial payload from the server, client is ready`))});let r=new Date().getTime();await n.transport.connect(e==null?{}:e,n.timeout,t),n.currentContext=e,n.telemetryClient.updateContext(e).catch(e=>n.logger.warn(`[ConfigDirectorClient] Telemetry could not update the context: `,e)),n.emit(`contextUpdated`,{context:e});let i=new Date().getTime()-r,a=n.timeout-i;clearTimeout(n.timeoutTimer),n.timeoutTimer=void 0,a>0&&await Promise.race([n.readyPromise,new Promise(e=>{n.timeoutTimer=setTimeout(()=>e(),a)})]).finally(()=>clearTimeout(n.timeoutTimer)),n.ready||n.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${t} after ${n.timeout}ms. The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds.`)}catch(e){n.logger.error(`[ConfigDirectorClient] An error occurred during ${t}: `,e)}}findKeysRemovedBy(e){return!this.configSet||e.kind!=`full`?[]:Object.keys(this.configSet.configs).filter(t=>!Object.prototype.hasOwnProperty.call(e.configs,t))}updateWatchers(e){e.forEach(e=>this.updateWatchersForConfig(e))}updateWatchersForConfig(e){var t,n;let r=(t=this.configSet)==null?void 0:t.configs[e];(n=this.handlersMap.get(e))==null||n.forEach(t=>{let n=this.getValueFromConfigState(e,r,t.defaultValue);t.handler(n)})}watch(e,t,n){this.validateDefaultValue(t);let r=this.handlersMap.get(e),i={handler:n,defaultValue:t,requestedType:typeof t};return r?r.push(i):this.handlersMap.set(e,[i]),()=>this.unwatch(e,n)}unwatch(e,t){let n=this.handlersMap.get(e);if(n)if(t){let e=n.findIndex(e=>e.handler==t);e>=0&&(n==null||n.splice(e,1))}else n.splice(0)}getValue(e,t){var n;this.validateDefaultValue(t);let r=(n=this.configSet)==null?void 0:n.configs[e];return this.getValueFromConfigState(e,r,t)}getValueFromConfigState(e,t,n){var r,i;if(!t){var a;this.logger.debug(`[ConfigDirectorClient] No config state found for '${e}', returning default value '${n}'`);let t=this.isReady?`config-state-missing`:`client-not-ready`;return this.telemetryClient.evaluatedConfig({contextId:(a=this.currentContext)==null?void 0:a.id,key:e,defaultValue:n,requestedType:v(n),evaluatedValue:n,usedDefault:!0,evaluationReason:t}),this.dispatchEvaluationEvent({key:e,value:n,isDefaultValue:!0,reason:t,context:this.context}),n}let o=x(t,n);return this.telemetryClient.evaluatedConfig({contextId:(r=this.currentContext)==null?void 0:r.id,type:t.type,key:e,defaultValue:n,requestedType:o.requestedType,evaluatedValue:o.parsedValue,evaluatedValueId:o.parsedValueId,usedDefault:o.usedDefault,evaluationReason:o.reason}),this.dispatchEvaluationEvent({key:e,value:o.parsedValue,valueId:(i=o.parsedValueId)==null?void 0:i,isDefaultValue:o.usedDefault,reason:o.reason,context:this.context}),this.logger.debug(`[ConfigDirectorClient] Evaluated '${e}' to '${o.parsedValue}'`),o.parsedValue}dispatchEvaluationEvent(e){setTimeout(()=>this.emit(`configEvaluated`,{evaluation:e}),0)}emit(e,t){try{this.eventEmitter.emit(e,t)}catch(t){this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${e}'`,t)}}parseUrl(e,t){if(e)try{return t(e)}catch(t){throw new n(`Invalid base URL '${e}'. Parsing failed: ${t}`)}}validateSdkKeyPresence(e){if(!e||e.trim().length==0)throw new n(`No client SDK key was provided, the client cannot be instantiated without a valid client SDK key`)}validateDefaultValue(e){if(e==null)throw new n(`Invalid default value. The default value for a config must be defined and non-null.`);if(typeof e==`function`)throw new n(`Invalid default value. The default value for a config cannot be a function.`)}get isReady(){return this.ready}get isInitializing(){return this.initializing}on(e,t){this.eventEmitter.on(e,t)}off(e,t){this.eventEmitter.off(e,t)}clear(){this.logger.debug(`[ConfigDirectorClient] clear() has been called, removing all observers`),this.eventEmitter.clear(),this.handlersMap.clear()}unwatchAll(){this.handlersMap.clear()}pauseNetwork(){this.logger.debug(`[ConfigDirectorClient] pauseNetwork() called, pausing transport connection`),clearTimeout(this.timeoutTimer),this.timeoutTimer=void 0,this.transport.close(),this.ready=!1}async resumeNetwork(){var e=this;await e.connectToTransport(e.currentContext,`network resume`)}async close(){var e=this,t;e.logger.debug(`[ConfigDirectorClient] close() has been called, closing connection to server`),clearTimeout(e.timeoutTimer),(t=e.readyResolve)==null||t.call(e,!0),e.telemetryClient.close().catch(t=>e.logger.warn(`[ConfigDirectorClient] Telemetry could not close: `,t)),e.transport.close(),e.ready=!1,e.initializing=!1}dispose(){this.clear(),this.close()}};const $=Symbol.for(`@configdirector/installed-test-client`),ne=()=>globalThis[$];export{N as a,x as c,n as d,e as f,F as i,f as l,ne as n,P as o,te as r,M as s,$ as t,t as u};
|
|
7
|
-
//# sourceMappingURL=installedTestClient-CDdDsfVQ.mjs.map
|