@configdirector/nuxt-sdk 0.8.0 → 0.9.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 (24) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +9 -0
  3. package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +1 -1
  4. package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +1 -1
  5. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.mts +6 -0
  6. package/dist/runtime/app/composables/useConfigDirectorClientHooks.d.ts +6 -0
  7. package/dist/runtime/app/composables/useConfigDirectorClientHooks.mjs +22 -0
  8. package/dist/runtime/app/composables/useConfigDirectorContext.d.mts +1 -1
  9. package/dist/runtime/app/composables/useConfigDirectorContext.d.ts +1 -1
  10. package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +1 -1
  11. package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +1 -1
  12. package/dist/runtime/nitro/plugin.d.mts +1 -1
  13. package/dist/runtime/nitro/plugin.d.ts +1 -1
  14. package/dist/runtime/nitro/plugin.mjs +35 -13
  15. package/dist/runtime/plugin.client.mjs +69 -10
  16. package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +1 -1
  17. package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +1 -1
  18. package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.mts +6 -0
  19. package/dist/runtime/server/composables/useConfigDirectorServerHooks.d.ts +6 -0
  20. package/dist/runtime/server/composables/useConfigDirectorServerHooks.mjs +14 -0
  21. package/dist/runtime/{types-DE44nrVT.d.mts → types-B4EK1B6g.d.mts} +12 -2
  22. package/dist/runtime/{types-BqdrorBY.d.mts → types-CHmsCChN.d.mts} +10 -1
  23. package/dist/runtime/{types-Ce8HdKnT.d.mts → types-CzmBIfqo.d.mts} +11 -2
  24. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "0.8.0"
7
+ "version": "0.9.0"
8
8
  }
package/dist/module.mjs CHANGED
@@ -37,6 +37,10 @@ var module_default = defineNuxtModule({
37
37
  name: "useConfigDirectorClient",
38
38
  as: "useConfigDirectorClient",
39
39
  from: resolver.resolve("./runtime/server/composables/useConfigDirectorClient")
40
+ }, {
41
+ name: "useConfigDirectorServerHooks",
42
+ as: "useConfigDirectorServerHooks",
43
+ from: resolver.resolve("./runtime/server/composables/useConfigDirectorServerHooks")
40
44
  }]);
41
45
  addPlugin(resolver.resolve("./runtime/plugin.server"));
42
46
  addPlugin(resolver.resolve("./runtime/plugin.client"));
@@ -60,6 +64,11 @@ var module_default = defineNuxtModule({
60
64
  name: "useConfigDirectorValue",
61
65
  as: "useConfigDirectorValue",
62
66
  from: resolver.resolve("./runtime/app/composables/useConfigDirectorValue")
67
+ },
68
+ {
69
+ name: "useConfigDirectorClientHooks",
70
+ as: "useConfigDirectorClientHooks",
71
+ from: resolver.resolve("./runtime/app/composables/useConfigDirectorClientHooks")
63
72
  }
64
73
  ]);
65
74
  }
@@ -1,4 +1,4 @@
1
- import { t as ConfigDirectorClient } from "../../types-DE44nrVT.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-B4EK1B6g.mjs";
2
2
 
3
3
  //#region src/runtime/app/composables/useConfigDirectorClient.d.ts
4
4
  declare const useConfigDirectorClient: () => {
@@ -1,4 +1,4 @@
1
- import { t as ConfigDirectorClient } from "../../types-DE44nrVT.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-B4EK1B6g.mjs";
2
2
 
3
3
  //#region src/runtime/app/composables/useConfigDirectorClient.d.ts
4
4
  declare const useConfigDirectorClient: () => {
@@ -0,0 +1,6 @@
1
+ import { t as ClientHooks } from "../../types-B4EK1B6g.mjs";
2
+
3
+ //#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
4
+ declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
5
+ //#endregion
6
+ export { useConfigDirectorClientHooks };
@@ -0,0 +1,6 @@
1
+ import { t as ClientHooks } from "../../types-B4EK1B6g.mjs";
2
+
3
+ //#region src/runtime/app/composables/useConfigDirectorClientHooks.d.ts
4
+ declare const useConfigDirectorClientHooks: (hooks: ClientHooks) => void;
5
+ //#endregion
6
+ export { useConfigDirectorClientHooks };
@@ -0,0 +1,22 @@
1
+ import { useNuxtApp } from "#app";
2
+ import { getCurrentInstance, onUnmounted } from "vue";
3
+ //#region src/runtime/app/composables/useConfigDirectorClientHooks.ts
4
+ const useConfigDirectorClientHooks = (hooks) => {
5
+ const { $configDirectorClient } = useNuxtApp();
6
+ const client = $configDirectorClient;
7
+ const hookEntries = Object.entries(hooks);
8
+ for (const [event, handler] of hookEntries) {
9
+ if (!handler) continue;
10
+ const handlers = Array.isArray(handler) ? handler : [handler];
11
+ for (const h of handlers) client.on(event, h);
12
+ }
13
+ if (getCurrentInstance()) onUnmounted(() => {
14
+ for (const [event, handler] of hookEntries) {
15
+ if (!handler) continue;
16
+ const handlers = Array.isArray(handler) ? handler : [handler];
17
+ for (const h of handlers) client.off(event, h);
18
+ }
19
+ });
20
+ };
21
+ //#endregion
22
+ export { useConfigDirectorClientHooks };
@@ -1,4 +1,4 @@
1
- import { t as ConfigDirectorContext } from "../../types-BqdrorBY.mjs";
1
+ import { t as ConfigDirectorContext } from "../../types-CHmsCChN.mjs";
2
2
  import { Ref } from "vue";
3
3
 
4
4
  //#region src/runtime/app/composables/useConfigDirectorContext.d.ts
@@ -1,4 +1,4 @@
1
- import { t as ConfigDirectorContext } from "../../types-BqdrorBY.mjs";
1
+ import { t as ConfigDirectorContext } from "../../types-CHmsCChN.mjs";
2
2
  import { Ref } from "vue";
3
3
 
4
4
  //#region src/runtime/app/composables/useConfigDirectorContext.d.ts
@@ -1,4 +1,4 @@
1
- import { r as ConfigValueType } from "../../types-BqdrorBY.mjs";
1
+ import { i as ConfigValueType } from "../../types-CHmsCChN.mjs";
2
2
  import { t as ClientStatus } from "../../types-CkzBXJwA.mjs";
3
3
  import { DeepReadonly, Ref, ShallowRef } from "vue";
4
4
 
@@ -1,4 +1,4 @@
1
- import { r as ConfigValueType } from "../../types-BqdrorBY.mjs";
1
+ import { i as ConfigValueType } from "../../types-CHmsCChN.mjs";
2
2
  import { t as ClientStatus } from "../../types-CkzBXJwA.mjs";
3
3
  import { DeepReadonly, Ref, ShallowRef } from "vue";
4
4
 
@@ -1,4 +1,4 @@
1
- import { t as ConfigDirectorClient } from "../types-Ce8HdKnT.mjs";
1
+ import { n as ConfigDirectorClient } from "../types-CzmBIfqo.mjs";
2
2
  import * as _$nitropack from "nitropack";
3
3
 
4
4
  //#region src/runtime/nitro/plugin.d.ts
@@ -1,4 +1,4 @@
1
- import { t as ConfigDirectorClient } from "../types-Ce8HdKnT.mjs";
1
+ import { n as ConfigDirectorClient } from "../types-CzmBIfqo.mjs";
2
2
  import * as _$nitropack from "nitropack";
3
3
 
4
4
  //#region src/runtime/nitro/plugin.d.ts
@@ -2,6 +2,7 @@ import EventEmitter, { EventEmitter as EventEmitter$1 } from "node:events";
2
2
  import { findByPointer } from "@jsonjoy.com/json-pointer";
3
3
  import * as semver from "semver";
4
4
  import { rapidhash } from "rapidhash-js";
5
+ import { createHash } from "node:crypto";
5
6
  import { defineNitroPlugin, useRuntimeConfig } from "nitropack/runtime";
6
7
  import { consola } from "consola";
7
8
  //#region ../shared/src/errors.ts
@@ -862,7 +863,7 @@ const parseConfigValue = (configState, defaultValue) => {
862
863
  usedDefault: false,
863
864
  reason: "found-match"
864
865
  };
865
- if (typeof defaultValue === "boolean" && configState.type === "boolean") {
866
+ if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string")) {
866
867
  const boolValue = parseConfigBoolean(value);
867
868
  const hasBoolean = typeof boolValue === "boolean";
868
869
  return {
@@ -884,7 +885,7 @@ const parseConfigValue = (configState, defaultValue) => {
884
885
  reason: hasNumber ? "found-match" : "invalid-number"
885
886
  };
886
887
  }
887
- if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum")) {
888
+ if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum" || configState.type === "string")) {
888
889
  const numValue = parseConfigFloat(value);
889
890
  const hasNumber = typeof numValue === "number";
890
891
  return {
@@ -1360,17 +1361,11 @@ const toBase62 = (buf, length) => {
1360
1361
  }
1361
1362
  return result.padStart(length, "0");
1362
1363
  };
1364
+ const BASE62_LENGTH = Math.ceil(128 / Math.log2(62));
1363
1365
  //#endregion
1364
- //#region ../shared/src/value-id-generator.ts
1365
- const DIGEST_BYTES = 16;
1366
- const BASE62_LENGTH = Math.ceil(DIGEST_BYTES * 8 / Math.log2(62));
1367
- const sha256 = async (value) => {
1368
- const encoded = new TextEncoder().encode(value);
1369
- const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", encoded);
1370
- return new Uint8Array(hashBuffer).subarray(0, DIGEST_BYTES);
1371
- };
1366
+ //#region ../js-server-sdk/src/telemetry/value-id-generator.ts
1372
1367
  const generateValueId = async (v) => {
1373
- return toBase62(await sha256(v?.toString() ?? ""), BASE62_LENGTH);
1368
+ return toBase62(createHash("sha256").update(v?.toString() ?? "").digest().subarray(0, 16), BASE62_LENGTH);
1374
1369
  };
1375
1370
  //#endregion
1376
1371
  //#region ../js-server-sdk/src/DefaultConfigDirectorClient.ts
@@ -1438,6 +1433,14 @@ var DefaultConfigDirectorClient = class {
1438
1433
  }
1439
1434
  this.logger.debug(`[ConfigDirectorClient] ConfigSet updated from server with ${configKeys.length} key(s)`, { keys: configKeys });
1440
1435
  });
1436
+ this.registerHandler("clientReady", clientOptions?.hooks?.clientReady);
1437
+ this.registerHandler("configEvaluated", clientOptions?.hooks?.configEvaluated);
1438
+ this.registerHandler("configsUpdated", clientOptions?.hooks?.configsUpdated);
1439
+ }
1440
+ registerHandler(event, handler) {
1441
+ if (!handler) return;
1442
+ const handlers = Array.isArray(handler) ? handler : [handler];
1443
+ for (const h of handlers) if (typeof h === "function") this.on(event, h);
1441
1444
  }
1442
1445
  async initialize() {
1443
1446
  try {
@@ -1526,10 +1529,19 @@ var DefaultConfigDirectorClient = class {
1526
1529
  evaluationReason: parseResult.reason
1527
1530
  }
1528
1531
  });
1532
+ this.dispatchEvaluationEvent({
1533
+ key: configKey,
1534
+ value: parseResult.parsedValue,
1535
+ valueId: parseResult.parsedValueId ?? void 0,
1536
+ isDefaultValue: parseResult.usedDefault,
1537
+ reason: parseResult.reason,
1538
+ context
1539
+ });
1529
1540
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
1530
1541
  return parseResult.parsedValue;
1531
1542
  } else {
1532
1543
  this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
1544
+ const reason = this.isReady ? "config-state-missing" : "client-not-ready";
1533
1545
  this.usageEventCollector.evaluatedConfig({
1534
1546
  context,
1535
1547
  evaluation: {
@@ -1538,12 +1550,22 @@ var DefaultConfigDirectorClient = class {
1538
1550
  requestedType: getRequestedType(defaultValue),
1539
1551
  evaluatedValue: defaultValue,
1540
1552
  usedDefault: true,
1541
- evaluationReason: "config-state-missing"
1553
+ evaluationReason: reason
1542
1554
  }
1543
1555
  });
1556
+ this.dispatchEvaluationEvent({
1557
+ key: configKey,
1558
+ value: defaultValue,
1559
+ isDefaultValue: true,
1560
+ reason,
1561
+ context
1562
+ });
1544
1563
  return defaultValue;
1545
1564
  }
1546
1565
  }
1566
+ dispatchEvaluationEvent(event) {
1567
+ setTimeout(() => this.eventEmitter.emit("configEvaluated", { evaluation: event }), 0);
1568
+ }
1547
1569
  parseUrl(url) {
1548
1570
  if (!url) return;
1549
1571
  try {
@@ -1614,7 +1636,7 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
1614
1636
  const { serverSdkKey, baseUrl } = runtimeConfig.configdirector;
1615
1637
  const client = new DefaultConfigDirectorClient(serverSdkKey, {
1616
1638
  sdkName: "nuxt-sdk",
1617
- sdkVersion: "0.8.0"
1639
+ sdkVersion: "0.9.0"
1618
1640
  }, {
1619
1641
  logger,
1620
1642
  ...baseUrl ? { connection: { url: baseUrl } } : {}
@@ -515,7 +515,7 @@ const parseConfigValue = (configState, defaultValue) => {
515
515
  usedDefault: false,
516
516
  reason: "found-match"
517
517
  };
518
- if (typeof defaultValue === "boolean" && configState.type === "boolean") {
518
+ if (typeof defaultValue === "boolean" && (configState.type === "boolean" || configState.type === "string")) {
519
519
  const boolValue = parseConfigBoolean(value);
520
520
  const hasBoolean = typeof boolValue === "boolean";
521
521
  return {
@@ -537,7 +537,7 @@ const parseConfigValue = (configState, defaultValue) => {
537
537
  reason: hasNumber ? "found-match" : "invalid-number"
538
538
  };
539
539
  }
540
- if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum")) {
540
+ if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum" || configState.type === "string")) {
541
541
  const numValue = parseConfigFloat(value);
542
542
  const hasNumber = typeof numValue === "number";
543
543
  return {
@@ -868,18 +868,27 @@ var DefaultConfigDirectorClient = class {
868
868
  const configKeys = Object.keys(configSet.configs);
869
869
  if (!this.configSet || configSet.kind == "full") {
870
870
  this.configSet = configSet;
871
- this.eventEmitter.emit("configsUpdated", { keys: configKeys });
871
+ this.emit("configsUpdated", { keys: configKeys });
872
872
  this.updateWatchers(configSet.configs);
873
873
  } else {
874
874
  this.configSet.configs = {
875
875
  ...this.configSet.configs,
876
876
  ...configSet.configs
877
877
  };
878
- this.eventEmitter.emit("configsUpdated", { keys: configKeys });
878
+ this.emit("configsUpdated", { keys: configKeys });
879
879
  this.updateWatchers(configSet.configs);
880
880
  }
881
881
  this.logger.debug("[ConfigDirectorClient] ConfigSet updated from server:", { keys: configKeys });
882
882
  });
883
+ this.registerHandler("clientReady", clientOptions?.hooks?.clientReady);
884
+ this.registerHandler("configEvaluated", clientOptions?.hooks?.configEvaluated);
885
+ this.registerHandler("configsUpdated", clientOptions?.hooks?.configsUpdated);
886
+ this.registerHandler("contextUpdated", clientOptions?.hooks?.contextUpdated);
887
+ }
888
+ registerHandler(event, handler) {
889
+ if (!handler) return;
890
+ const handlers = Array.isArray(handler) ? handler : [handler];
891
+ for (const h of handlers) if (typeof h === "function") this.on(event, h);
883
892
  }
884
893
  getTransportConstructor(connectionMode) {
885
894
  switch (connectionMode) {
@@ -906,14 +915,14 @@ var DefaultConfigDirectorClient = class {
906
915
  }).then(() => {
907
916
  this.ready = true;
908
917
  this.initializing = false;
909
- this.eventEmitter.emit("clientReady", { action: caller });
918
+ this.emit("clientReady", { action: caller });
910
919
  this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
911
920
  });
912
921
  const startTime = (/* @__PURE__ */ new Date()).getTime();
913
922
  await this.transport.connect(context ?? {}, this.timeout);
914
923
  this.currentContext = context;
915
924
  this.telemetryClient.updateContext(context);
916
- this.eventEmitter.emit("contextUpdated", { context });
925
+ this.emit("contextUpdated", { context });
917
926
  const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
918
927
  const remainingTimeout = this.timeout - elapsedTime;
919
928
  clearTimeout(this.timeoutTimer);
@@ -963,6 +972,7 @@ var DefaultConfigDirectorClient = class {
963
972
  getValueFromConfigState(configKey, configState, defaultValue) {
964
973
  if (!configState) {
965
974
  this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
975
+ const reason = this.isReady ? "config-state-missing" : "client-not-ready";
966
976
  this.telemetryClient.evaluatedConfig({
967
977
  contextId: this.currentContext?.id,
968
978
  key: configKey,
@@ -970,7 +980,14 @@ var DefaultConfigDirectorClient = class {
970
980
  requestedType: getRequestedType(defaultValue),
971
981
  evaluatedValue: defaultValue,
972
982
  usedDefault: true,
973
- evaluationReason: "config-state-missing"
983
+ evaluationReason: reason
984
+ });
985
+ this.dispatchEvaluationEvent({
986
+ key: configKey,
987
+ value: defaultValue,
988
+ isDefaultValue: true,
989
+ reason,
990
+ context: this.context
974
991
  });
975
992
  return defaultValue;
976
993
  }
@@ -986,9 +1003,27 @@ var DefaultConfigDirectorClient = class {
986
1003
  usedDefault: parseResult.usedDefault,
987
1004
  evaluationReason: parseResult.reason
988
1005
  });
1006
+ this.dispatchEvaluationEvent({
1007
+ key: configKey,
1008
+ value: parseResult.parsedValue,
1009
+ valueId: parseResult.parsedValueId ?? void 0,
1010
+ isDefaultValue: parseResult.usedDefault,
1011
+ reason: parseResult.reason,
1012
+ context: this.context
1013
+ });
989
1014
  this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
990
1015
  return parseResult.parsedValue;
991
1016
  }
1017
+ dispatchEvaluationEvent(event) {
1018
+ setTimeout(() => this.emit("configEvaluated", { evaluation: event }), 0);
1019
+ }
1020
+ emit(name, payload) {
1021
+ try {
1022
+ this.eventEmitter.emit(name, payload);
1023
+ } catch (error) {
1024
+ this.logger.error(`[ConfigDirectorClient] Error executing event handlers for '${name}'`, error);
1025
+ }
1026
+ }
992
1027
  parseUrl(url, urlFactory) {
993
1028
  if (!url) return;
994
1029
  try {
@@ -1089,7 +1124,7 @@ var WebWorkerTelemetryClient = class {
1089
1124
  flushIntervalDelay: options.flushIntervalDelay ?? 3e4
1090
1125
  }
1091
1126
  };
1092
- this.worker = new Worker(URL.createObjectURL(new Blob(["(function(){let e=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,t=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},n=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},r=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(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let i={clientSdkKey:this.sdkKey,context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500}},o=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},s=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}async flushAndScheduleNext(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},l=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(t){let n=await Promise.all(t.events.map(async t=>({...t,defaultValue:await e(t.defaultValue,this.valueIdGenerator),evaluatedValue:await e(t.evaluatedValue,this.valueIdGenerator)})));return{...t,events:n}}},u=class extends c{constructor(e){super(e),this.reporter=new a(e),this.evaluationEventSnapshotPreprocessor=new l(e.valueIdGenerator)}async updateContext(e){this.cancelScheduledFlush(),await this.flush(),this._context=e,await this.flushAndScheduleNext()}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(e)}async forceFlush(){this.cancelScheduledFlush(),await this.flushAndScheduleNext()}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot());return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let d=(e,t)=>new URL(e,t?.toString()),f=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},p=Math.ceil(128/Math.log2(62)),m=async e=>{let t=new TextEncoder().encode(e),n=await globalThis.crypto.subtle.digest(`SHA-256`,t);return new Uint8Array(n).subarray(0,16)},h=async e=>f(await m(e?.toString()??``),p),g,_=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),v=e=>{try{self.postMessage(e)}catch{self.postMessage(_(e.payload.level,e.payload.message))}},y={debug:function(e,...t){v(_(`debug`,e,t))},info:function(e,...t){v(_(`info`,e,t))},warn:function(e,...t){v(_(`warn`,e,t))},error:function(e,...t){v(_(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!g){y.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(y.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:g=new u({...t.payload,baseUrl:d(t.payload.baseUrl),urlFactory:d,valueIdGenerator:h,logger:y});break;case`EvaluatedConfigEvent`:g?.evaluatedConfig(t.payload);break;case`UpdateContext`:g?.updateContext(t.payload.context);break;case`Flush`:g?.forceFlush();break;case`Close`:(g?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"], { type: "application/javascript" })));
1127
+ this.worker = new Worker(URL.createObjectURL(new Blob(["(function(){let e=async(e,t)=>e.valueId?{valueId:e.valueId}:e.value?e.value.length<=500&&e.type!=`json`?{value:e.value}:{valueId:await t(e.value)}:e,t=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},n=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},r=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(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},i=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var a=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let i={clientSdkKey:this.sdkKey,context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return t(e.discreteEvents)&&t(e.aggregatedEvents)&&n(e.droppedEvents)}async sendReport(e){try{let t=await r(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=i(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500}},o=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},s=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},c=class{constructor(e){this.aggregator=new o,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new s(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}async flushAndScheduleNext(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},l=class{constructor(e){this.valueIdGenerator=e,this.valueIdGenerator=e}async process(t){let n=await Promise.all(t.events.map(async t=>({...t,defaultValue:await e(t.defaultValue,this.valueIdGenerator),evaluatedValue:await e(t.evaluatedValue,this.valueIdGenerator)})));return{...t,events:n}}},u=class extends c{constructor(e){super(e),this.reporter=new a(e),this.evaluationEventSnapshotPreprocessor=new l(e.valueIdGenerator)}async updateContext(e){this.cancelScheduledFlush(),await this.flush(),this._context=e,await this.flushAndScheduleNext()}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(e)}async forceFlush(){this.cancelScheduledFlush(),await this.flushAndScheduleNext()}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=await this.evaluationEventSnapshotPreprocessor.process(this.evaluationEventQueue.takeSnapshot());return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}};let d=(e,t)=>new URL(e,t?.toString()),f=(e,t)=>{let n=BigInt(0),r=BigInt(62),i=Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),a=BigInt(`0x`+i),o=``;for(;a>n;)o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`[Number(a%r)]+o,a/=r;return o.padStart(t,`0`)},p=Math.ceil(128/Math.log2(62)),m=new TextEncoder,h=async e=>{let t=await crypto.subtle.digest(`SHA-256`,m.encode(e?.toString()??``));return f(new Uint8Array(t,0,16),p)},g,_=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),v=e=>{try{self.postMessage(e)}catch{self.postMessage(_(e.payload.level,e.payload.message))}},y={debug:function(e,...t){v(_(`debug`,e,t))},info:function(e,...t){v(_(`info`,e,t))},warn:function(e,...t){v(_(`warn`,e,t))},error:function(e,...t){v(_(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!g){y.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(y.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:g=new u({...t.payload,baseUrl:d(t.payload.baseUrl),urlFactory:d,valueIdGenerator:h,logger:y});break;case`EvaluatedConfigEvent`:g?.evaluatedConfig(t.payload);break;case`UpdateContext`:g?.updateContext(t.payload.context);break;case`Flush`:g?.forceFlush();break;case`Close`:(g?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"], { type: "application/javascript" })));
1093
1128
  this.worker.onmessage = (message) => {
1094
1129
  this.handleWorkerEvent(message.data);
1095
1130
  };
@@ -1170,6 +1205,29 @@ var WebWorkerTelemetryClient = class {
1170
1205
  }
1171
1206
  };
1172
1207
  //#endregion
1208
+ //#region ../shared/src/base62-encoder.ts
1209
+ const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
1210
+ const toBase62 = (buf, length) => {
1211
+ const ZERO = BigInt(0);
1212
+ const BASE = BigInt(62);
1213
+ const hex = Array.from(buf).map((b) => b.toString(16).padStart(2, "0")).join("");
1214
+ let n = BigInt("0x" + hex);
1215
+ let result = "";
1216
+ while (n > ZERO) {
1217
+ result = BASE62[Number(n % BASE)] + result;
1218
+ n /= BASE;
1219
+ }
1220
+ return result.padStart(length, "0");
1221
+ };
1222
+ const BASE62_LENGTH = Math.ceil(128 / Math.log2(62));
1223
+ //#endregion
1224
+ //#region ../js-browser-client/src/telemetry/value-id-generator.ts
1225
+ const encoder = new TextEncoder();
1226
+ const generateValueId = async (v) => {
1227
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encoder.encode(v?.toString() ?? ""));
1228
+ return toBase62(new Uint8Array(hashBuffer, 0, 16), BASE62_LENGTH);
1229
+ };
1230
+ //#endregion
1173
1231
  //#region ../js-browser-client/src/BrowserConfigDirectorClient.ts
1174
1232
  const createBrowserClient = (clientSdkKey, sdkOptions, clientOptions) => {
1175
1233
  const logger = clientOptions?.logger ?? createDefaultLogger$1();
@@ -1177,7 +1235,8 @@ const createBrowserClient = (clientSdkKey, sdkOptions, clientOptions) => {
1177
1235
  sdkKey: clientSdkKey,
1178
1236
  logger,
1179
1237
  baseUrl: parseUrl(clientOptions?.connection?.url) ?? "https://client-sdk-api.configdirector.com",
1180
- urlFactory: defaultUrlFactory
1238
+ urlFactory: defaultUrlFactory,
1239
+ valueIdGenerator: generateValueId
1181
1240
  }), clientSdkKey, sdkOptions, {
1182
1241
  ...clientOptions,
1183
1242
  logger
@@ -1236,7 +1295,7 @@ var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
1236
1295
  const { clientSdkKey, appName, appVersion, baseUrl } = runtimeConfig.public.configdirector;
1237
1296
  const client = createBrowserClient(clientSdkKey, {
1238
1297
  sdkName: "nuxt-sdk",
1239
- sdkVersion: "0.8.0"
1298
+ sdkVersion: "0.9.0"
1240
1299
  }, {
1241
1300
  metadata: {
1242
1301
  appName,
@@ -1,4 +1,4 @@
1
- import { t as ConfigDirectorClient } from "../../types-Ce8HdKnT.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-CzmBIfqo.mjs";
2
2
 
3
3
  //#region src/runtime/server/composables/useConfigDirectorClient.d.ts
4
4
  declare const useConfigDirectorClient: () => ConfigDirectorClient;
@@ -1,4 +1,4 @@
1
- import { t as ConfigDirectorClient } from "../../types-Ce8HdKnT.mjs";
1
+ import { n as ConfigDirectorClient } from "../../types-CzmBIfqo.mjs";
2
2
 
3
3
  //#region src/runtime/server/composables/useConfigDirectorClient.d.ts
4
4
  declare const useConfigDirectorClient: () => ConfigDirectorClient;
@@ -0,0 +1,6 @@
1
+ import { t as ClientHooks } from "../../types-CzmBIfqo.mjs";
2
+
3
+ //#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
4
+ declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
5
+ //#endregion
6
+ export { useConfigDirectorServerHooks };
@@ -0,0 +1,6 @@
1
+ import { t as ClientHooks } from "../../types-CzmBIfqo.mjs";
2
+
3
+ //#region src/runtime/server/composables/useConfigDirectorServerHooks.d.ts
4
+ declare const useConfigDirectorServerHooks: (hooks: ClientHooks) => void;
5
+ //#endregion
6
+ export { useConfigDirectorServerHooks };
@@ -0,0 +1,14 @@
1
+ import { useNitroApp } from "nitropack/runtime";
2
+ //#region src/runtime/server/composables/useConfigDirectorServerHooks.ts
3
+ const useConfigDirectorServerHooks = (hooks) => {
4
+ const client = useNitroApp().configDirectorClient;
5
+ if (!client) throw new Error("The ConfigDirector Nitro plugin was not initialized");
6
+ const entries = Object.entries(hooks);
7
+ for (const [event, handler] of entries) {
8
+ if (!handler) continue;
9
+ const handlers = Array.isArray(handler) ? handler : [handler];
10
+ for (const h of handlers) client.on(event, h);
11
+ }
12
+ };
13
+ //#endregion
14
+ export { useConfigDirectorServerHooks };
@@ -1,4 +1,4 @@
1
- import { r as ConfigValueType, t as ConfigDirectorContext } from "./types-BqdrorBY.mjs";
1
+ import { i as ConfigValueType, n as ConfigEvaluation, t as ConfigDirectorContext } from "./types-CHmsCChN.mjs";
2
2
 
3
3
  //#region ../js-client-core/src/Emitter.d.ts
4
4
  type EventType = string | symbol;
@@ -11,7 +11,14 @@ interface EventProvider<TEventsRecord extends EventsRecord> {
11
11
  }
12
12
  //#endregion
13
13
  //#region ../js-client-core/src/types.d.ts
14
+ type ClientHooks = {
15
+ clientReady?: HookHandler<"clientReady"> | HookHandler<"clientReady">[];
16
+ configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
17
+ contextUpdated?: HookHandler<"contextUpdated"> | HookHandler<"contextUpdated">[];
18
+ configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
19
+ };
14
20
  type ClientConnectAction = "initialization" | "context update" | "network resume";
21
+ type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
15
22
  type ClientEvents = {
16
23
  configsUpdated: {
17
24
  keys: string[];
@@ -22,6 +29,9 @@ type ClientEvents = {
22
29
  contextUpdated: {
23
30
  context: ConfigDirectorContext | undefined;
24
31
  };
32
+ configEvaluated: {
33
+ evaluation: ConfigEvaluation;
34
+ };
25
35
  };
26
36
  type WatchHandler<T extends ConfigValueType> = (message: T) => void;
27
37
  /**
@@ -132,4 +142,4 @@ interface ConfigDirectorClient extends EventProvider<ClientEvents> {
132
142
  dispose(): void;
133
143
  }
134
144
  //#endregion
135
- export { ConfigDirectorClient as t };
145
+ export { ConfigDirectorClient as n, ClientHooks as t };
@@ -12,6 +12,15 @@ type ConfigState = {
12
12
  value: string | undefined | null;
13
13
  valueId?: string | undefined | null;
14
14
  };
15
+ type EvaluationReason = "found-match" | "config-state-missing" | "client-not-ready" | "type-mismatch" | "value-missing" | "invalid-number" | "invalid-json" | "invalid-boolean";
16
+ type ConfigEvaluation = {
17
+ key: string;
18
+ value: ConfigValueType;
19
+ valueId?: string;
20
+ isDefaultValue: boolean;
21
+ reason: EvaluationReason;
22
+ context?: ConfigDirectorContext;
23
+ };
15
24
  /**
16
25
  * The user's context to be sent to ConfigDirector. This context will be used for targeting
17
26
  * rules evaluation.
@@ -48,4 +57,4 @@ type ConfigDirectorContext = {
48
57
  anonymous?: boolean;
49
58
  };
50
59
  //#endregion
51
- export { ConfigState as n, ConfigValueType as r, ConfigDirectorContext as t };
60
+ export { ConfigValueType as i, ConfigEvaluation as n, ConfigState as r, ConfigDirectorContext as t };
@@ -1,4 +1,4 @@
1
- import { n as ConfigState, r as ConfigValueType, t as ConfigDirectorContext } from "./types-BqdrorBY.mjs";
1
+ import { i as ConfigValueType, n as ConfigEvaluation, r as ConfigState, t as ConfigDirectorContext } from "./types-CHmsCChN.mjs";
2
2
 
3
3
  //#region ../js-server-sdk/src/types.d.ts
4
4
  type ClientEvents = {
@@ -6,7 +6,16 @@ type ClientEvents = {
6
6
  keys: string[];
7
7
  };
8
8
  clientReady: undefined;
9
+ configEvaluated: {
10
+ evaluation: ConfigEvaluation;
11
+ };
12
+ };
13
+ type ClientHooks = {
14
+ clientReady?: HookHandler<"clientReady"> | HookHandler<"clientReady">[];
15
+ configsUpdated?: HookHandler<"configsUpdated"> | HookHandler<"configsUpdated">[];
16
+ configEvaluated?: HookHandler<"configEvaluated"> | HookHandler<"configEvaluated">[];
9
17
  };
18
+ type HookHandler<TEvent extends keyof ClientEvents> = (payload: ClientEvents[TEvent]) => void;
10
19
  type WatchHandler<T extends ConfigValueType> = (message: T) => void;
11
20
  /**
12
21
  * The ConfigDirector SDK client object.
@@ -93,4 +102,4 @@ interface ConfigDirectorClient {
93
102
  dispose(): void;
94
103
  }
95
104
  //#endregion
96
- export { ConfigDirectorClient as t };
105
+ export { ConfigDirectorClient as n, ClientHooks as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@configdirector/nuxt-sdk",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Nuxt SDK for ConfigDirector. ConfigDirector is a remote configuration and feature flag service.",
5
5
  "license": "MIT",
6
6
  "author": "ConfigDirector",