@schematichq/schematic-react 1.0.0 → 1.0.3

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.
@@ -624,6 +624,7 @@ function contextString(context) {
624
624
  }
625
625
  var anonymousIdKey = "schematicId";
626
626
  var Schematic = class {
627
+ additionalHeaders = {};
627
628
  apiKey;
628
629
  apiUrl = "https://api.schematichq.com";
629
630
  conn = null;
@@ -643,6 +644,9 @@ var Schematic = class {
643
644
  this.eventQueue = [];
644
645
  this.useWebSocket = options?.useWebSocket ?? false;
645
646
  this.flagListener = options?.flagListener;
647
+ if (options?.additionalHeaders) {
648
+ this.additionalHeaders = options.additionalHeaders;
649
+ }
646
650
  if (options?.storage) {
647
651
  this.storage = options.storage;
648
652
  } else if (typeof localStorage !== "undefined") {
@@ -663,36 +667,89 @@ var Schematic = class {
663
667
  });
664
668
  }
665
669
  }
666
- // Get value for a single flag
667
- // If in websocket mode, return the local value, otherwise make an API call
670
+ /**
671
+ * Get value for a single flag.
672
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
673
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
674
+ * connection fails.
675
+ * In REST mode, makes an API call for each check.
676
+ */
668
677
  async checkFlag(options) {
669
678
  const { fallback = false, key } = options;
670
679
  const context = options.context || this.context;
671
- if (this.useWebSocket) {
672
- const contextVals = this.values[contextString(context)] ?? {};
680
+ const contextStr = contextString(context);
681
+ if (!this.useWebSocket) {
682
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
683
+ return fetch(requestUrl, {
684
+ method: "POST",
685
+ headers: {
686
+ ...this.additionalHeaders ?? {},
687
+ "Content-Type": "application/json;charset=UTF-8",
688
+ "X-Schematic-Api-Key": this.apiKey
689
+ },
690
+ body: JSON.stringify(context)
691
+ }).then((response) => {
692
+ if (!response.ok) {
693
+ throw new Error("Network response was not ok");
694
+ }
695
+ return response.json();
696
+ }).then((data) => {
697
+ return data.data.value;
698
+ }).catch((error) => {
699
+ console.error("There was a problem with the fetch operation:", error);
700
+ return fallback;
701
+ });
702
+ }
703
+ try {
704
+ const existingVals = this.values[contextStr];
705
+ if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
706
+ return existingVals[key];
707
+ }
708
+ try {
709
+ await this.setContext(context);
710
+ } catch (error) {
711
+ console.error(
712
+ "WebSocket connection failed, falling back to REST:",
713
+ error
714
+ );
715
+ return this.fallbackToRest(key, context, fallback);
716
+ }
717
+ const contextVals = this.values[contextStr] ?? {};
673
718
  return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
719
+ } catch (error) {
720
+ console.error("Unexpected error in checkFlag:", error);
721
+ return fallback;
674
722
  }
675
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
676
- return fetch(requestUrl, {
677
- method: "POST",
678
- headers: {
679
- "X-Schematic-Api-Key": this.apiKey,
680
- "Content-Type": "application/json;charset=UTF-8"
681
- },
682
- body: JSON.stringify(context)
683
- }).then((response) => {
723
+ }
724
+ /**
725
+ * Helper method for falling back to REST API when WebSocket connection fails
726
+ */
727
+ async fallbackToRest(key, context, fallback) {
728
+ try {
729
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
730
+ const response = await fetch(requestUrl, {
731
+ method: "POST",
732
+ headers: {
733
+ ...this.additionalHeaders ?? {},
734
+ "Content-Type": "application/json;charset=UTF-8",
735
+ "X-Schematic-Api-Key": this.apiKey
736
+ },
737
+ body: JSON.stringify(context)
738
+ });
684
739
  if (!response.ok) {
685
740
  throw new Error("Network response was not ok");
686
741
  }
687
- return response.json();
688
- }).then((data) => {
742
+ const data = await response.json();
689
743
  return data.data.value;
690
- }).catch((error) => {
691
- console.error("There was a problem with the fetch operation:", error);
744
+ } catch (error) {
745
+ console.error("REST API call failed, using fallback value:", error);
692
746
  return fallback;
693
- });
747
+ }
694
748
  }
695
- // Make an API call to fetch all flag values for a given context (use if not in websocket mode)
749
+ /**
750
+ * Make an API call to fetch all flag values for a given context.
751
+ * Recommended for use in REST mode only.
752
+ */
696
753
  checkFlags = async (context) => {
697
754
  context = context || this.context;
698
755
  const requestUrl = `${this.apiUrl}/flags/check`;
@@ -700,6 +757,7 @@ var Schematic = class {
700
757
  return fetch(requestUrl, {
701
758
  method: "POST",
702
759
  headers: {
760
+ ...this.additionalHeaders ?? {},
703
761
  "Content-Type": "application/json;charset=UTF-8",
704
762
  "X-Schematic-Api-Key": this.apiKey
705
763
  },
@@ -722,18 +780,30 @@ var Schematic = class {
722
780
  return false;
723
781
  });
724
782
  };
725
- // Send an identify event
783
+ /**
784
+ * Send an identify event.
785
+ * This will set the context for subsequent flag evaluation and events, and will also
786
+ * send an identify event to the Schematic API which will upsert a user and company.
787
+ */
726
788
  identify = (body) => {
727
- this.setContext({
728
- company: body.company?.keys,
729
- user: body.keys
730
- });
789
+ try {
790
+ this.setContext({
791
+ company: body.company?.keys,
792
+ user: body.keys
793
+ });
794
+ } catch (error) {
795
+ console.error("Error setting context:", error);
796
+ }
731
797
  return this.handleEvent("identify", body);
732
798
  };
733
- // Set the flag evaluation context; if the context has changed,
734
- // this will open a websocket connection (if not already open)
735
- // and submit this context. The promise will resolve when the
736
- // websocket sends back an initial set of flag values.
799
+ /**
800
+ * Set the flag evaluation context.
801
+ * In WebSocket mode, this will:
802
+ * 1. Open a websocket connection if not already open
803
+ * 2. Send the context to the server
804
+ * 3. Wait for initial flag values to be returned
805
+ * The promise resolves when initial flag values are received.
806
+ */
737
807
  setContext = async (context) => {
738
808
  if (!this.useWebSocket) {
739
809
  this.context = context;
@@ -747,10 +817,14 @@ var Schematic = class {
747
817
  const socket = await this.conn;
748
818
  await this.wsSendMessage(socket, context);
749
819
  } catch (error) {
750
- console.error("Error setting Schematic context:", error);
820
+ console.error("Failed to establish WebSocket connection:", error);
821
+ throw error;
751
822
  }
752
823
  };
753
- // Send track event
824
+ /**
825
+ * Send a track event
826
+ * Track usage for a company and/or user.
827
+ */
754
828
  track = (body) => {
755
829
  const { company, user, event, traits } = body;
756
830
  return this.handleEvent("track", {
@@ -805,6 +879,7 @@ var Schematic = class {
805
879
  await fetch(captureUrl, {
806
880
  method: "POST",
807
881
  headers: {
882
+ ...this.additionalHeaders ?? {},
808
883
  "Content-Type": "application/json;charset=UTF-8"
809
884
  },
810
885
  body: payload
@@ -821,6 +896,9 @@ var Schematic = class {
821
896
  /**
822
897
  * Websocket management
823
898
  */
899
+ /**
900
+ * If using websocket mode, close the connection when done.
901
+ */
824
902
  cleanup = async () => {
825
903
  if (this.conn) {
826
904
  try {
@@ -854,8 +932,7 @@ var Schematic = class {
854
932
  wsSendMessage = (socket, context) => {
855
933
  return new Promise((resolve, reject) => {
856
934
  if (contextString(context) == contextString(this.context)) {
857
- resolve();
858
- return;
935
+ return resolve(this.setIsPending(false));
859
936
  }
860
937
  this.context = context;
861
938
  const sendMessage = () => {
@@ -868,7 +945,7 @@ var Schematic = class {
868
945
  (message.flags ?? []).forEach(
869
946
  (flag) => {
870
947
  this.values[contextString(context)][flag.flag] = flag.value;
871
- this.notifyFlagValueListeners(flag.flag);
948
+ this.notifyFlagValueListeners(flag.flag, flag.value);
872
949
  }
873
950
  );
874
951
  if (this.flagListener) {
@@ -912,7 +989,9 @@ var Schematic = class {
912
989
  };
913
990
  setIsPending = (isPending) => {
914
991
  this.isPending = isPending;
915
- this.isPendingListeners.forEach((listener) => listener());
992
+ this.isPendingListeners.forEach(
993
+ (listener) => notifyListener(listener, isPending)
994
+ );
916
995
  };
917
996
  // flagValues state
918
997
  getFlagValue = (flagKey) => {
@@ -932,11 +1011,18 @@ var Schematic = class {
932
1011
  this.flagValueListeners[flagKey].delete(listener);
933
1012
  };
934
1013
  };
935
- notifyFlagValueListeners = (flagKey) => {
1014
+ notifyFlagValueListeners = (flagKey, value) => {
936
1015
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
937
- listeners.forEach((listener) => listener());
1016
+ listeners.forEach((listener) => notifyListener(listener, value));
938
1017
  };
939
1018
  };
1019
+ var notifyListener = (listener, value) => {
1020
+ if (listener.length > 0) {
1021
+ listener(value);
1022
+ } else {
1023
+ listener();
1024
+ }
1025
+ };
940
1026
 
941
1027
  // src/context/schematic.tsx
942
1028
  var import_react = __toESM(require("react"));
@@ -950,16 +1036,19 @@ var SchematicProvider = ({
950
1036
  publishableKey,
951
1037
  ...clientOpts
952
1038
  }) => {
1039
+ const initialOptsRef = (0, import_react.useRef)({
1040
+ publishableKey,
1041
+ useWebSocket: clientOpts.useWebSocket ?? true,
1042
+ ...clientOpts
1043
+ });
953
1044
  const client = (0, import_react.useMemo)(() => {
954
- const { useWebSocket = true } = clientOpts;
955
1045
  if (providedClient) {
956
1046
  return providedClient;
957
1047
  }
958
- return new Schematic(publishableKey, {
959
- useWebSocket,
960
- ...clientOpts
1048
+ return new Schematic(initialOptsRef.current.publishableKey, {
1049
+ ...initialOptsRef.current
961
1050
  });
962
- }, [providedClient, publishableKey, clientOpts]);
1051
+ }, [providedClient]);
963
1052
  (0, import_react.useEffect)(() => {
964
1053
  return () => {
965
1054
  if (!providedClient) {
@@ -582,6 +582,7 @@ function contextString(context) {
582
582
  }
583
583
  var anonymousIdKey = "schematicId";
584
584
  var Schematic = class {
585
+ additionalHeaders = {};
585
586
  apiKey;
586
587
  apiUrl = "https://api.schematichq.com";
587
588
  conn = null;
@@ -601,6 +602,9 @@ var Schematic = class {
601
602
  this.eventQueue = [];
602
603
  this.useWebSocket = options?.useWebSocket ?? false;
603
604
  this.flagListener = options?.flagListener;
605
+ if (options?.additionalHeaders) {
606
+ this.additionalHeaders = options.additionalHeaders;
607
+ }
604
608
  if (options?.storage) {
605
609
  this.storage = options.storage;
606
610
  } else if (typeof localStorage !== "undefined") {
@@ -621,36 +625,89 @@ var Schematic = class {
621
625
  });
622
626
  }
623
627
  }
624
- // Get value for a single flag
625
- // If in websocket mode, return the local value, otherwise make an API call
628
+ /**
629
+ * Get value for a single flag.
630
+ * In WebSocket mode, returns cached values if connection is active, otherwise establishes
631
+ * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
632
+ * connection fails.
633
+ * In REST mode, makes an API call for each check.
634
+ */
626
635
  async checkFlag(options) {
627
636
  const { fallback = false, key } = options;
628
637
  const context = options.context || this.context;
629
- if (this.useWebSocket) {
630
- const contextVals = this.values[contextString(context)] ?? {};
638
+ const contextStr = contextString(context);
639
+ if (!this.useWebSocket) {
640
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
641
+ return fetch(requestUrl, {
642
+ method: "POST",
643
+ headers: {
644
+ ...this.additionalHeaders ?? {},
645
+ "Content-Type": "application/json;charset=UTF-8",
646
+ "X-Schematic-Api-Key": this.apiKey
647
+ },
648
+ body: JSON.stringify(context)
649
+ }).then((response) => {
650
+ if (!response.ok) {
651
+ throw new Error("Network response was not ok");
652
+ }
653
+ return response.json();
654
+ }).then((data) => {
655
+ return data.data.value;
656
+ }).catch((error) => {
657
+ console.error("There was a problem with the fetch operation:", error);
658
+ return fallback;
659
+ });
660
+ }
661
+ try {
662
+ const existingVals = this.values[contextStr];
663
+ if (this.conn && typeof existingVals !== "undefined" && typeof existingVals[key] !== "undefined") {
664
+ return existingVals[key];
665
+ }
666
+ try {
667
+ await this.setContext(context);
668
+ } catch (error) {
669
+ console.error(
670
+ "WebSocket connection failed, falling back to REST:",
671
+ error
672
+ );
673
+ return this.fallbackToRest(key, context, fallback);
674
+ }
675
+ const contextVals = this.values[contextStr] ?? {};
631
676
  return typeof contextVals[key] === "undefined" ? fallback : contextVals[key];
677
+ } catch (error) {
678
+ console.error("Unexpected error in checkFlag:", error);
679
+ return fallback;
632
680
  }
633
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
634
- return fetch(requestUrl, {
635
- method: "POST",
636
- headers: {
637
- "X-Schematic-Api-Key": this.apiKey,
638
- "Content-Type": "application/json;charset=UTF-8"
639
- },
640
- body: JSON.stringify(context)
641
- }).then((response) => {
681
+ }
682
+ /**
683
+ * Helper method for falling back to REST API when WebSocket connection fails
684
+ */
685
+ async fallbackToRest(key, context, fallback) {
686
+ try {
687
+ const requestUrl = `${this.apiUrl}/flags/${key}/check`;
688
+ const response = await fetch(requestUrl, {
689
+ method: "POST",
690
+ headers: {
691
+ ...this.additionalHeaders ?? {},
692
+ "Content-Type": "application/json;charset=UTF-8",
693
+ "X-Schematic-Api-Key": this.apiKey
694
+ },
695
+ body: JSON.stringify(context)
696
+ });
642
697
  if (!response.ok) {
643
698
  throw new Error("Network response was not ok");
644
699
  }
645
- return response.json();
646
- }).then((data) => {
700
+ const data = await response.json();
647
701
  return data.data.value;
648
- }).catch((error) => {
649
- console.error("There was a problem with the fetch operation:", error);
702
+ } catch (error) {
703
+ console.error("REST API call failed, using fallback value:", error);
650
704
  return fallback;
651
- });
705
+ }
652
706
  }
653
- // Make an API call to fetch all flag values for a given context (use if not in websocket mode)
707
+ /**
708
+ * Make an API call to fetch all flag values for a given context.
709
+ * Recommended for use in REST mode only.
710
+ */
654
711
  checkFlags = async (context) => {
655
712
  context = context || this.context;
656
713
  const requestUrl = `${this.apiUrl}/flags/check`;
@@ -658,6 +715,7 @@ var Schematic = class {
658
715
  return fetch(requestUrl, {
659
716
  method: "POST",
660
717
  headers: {
718
+ ...this.additionalHeaders ?? {},
661
719
  "Content-Type": "application/json;charset=UTF-8",
662
720
  "X-Schematic-Api-Key": this.apiKey
663
721
  },
@@ -680,18 +738,30 @@ var Schematic = class {
680
738
  return false;
681
739
  });
682
740
  };
683
- // Send an identify event
741
+ /**
742
+ * Send an identify event.
743
+ * This will set the context for subsequent flag evaluation and events, and will also
744
+ * send an identify event to the Schematic API which will upsert a user and company.
745
+ */
684
746
  identify = (body) => {
685
- this.setContext({
686
- company: body.company?.keys,
687
- user: body.keys
688
- });
747
+ try {
748
+ this.setContext({
749
+ company: body.company?.keys,
750
+ user: body.keys
751
+ });
752
+ } catch (error) {
753
+ console.error("Error setting context:", error);
754
+ }
689
755
  return this.handleEvent("identify", body);
690
756
  };
691
- // Set the flag evaluation context; if the context has changed,
692
- // this will open a websocket connection (if not already open)
693
- // and submit this context. The promise will resolve when the
694
- // websocket sends back an initial set of flag values.
757
+ /**
758
+ * Set the flag evaluation context.
759
+ * In WebSocket mode, this will:
760
+ * 1. Open a websocket connection if not already open
761
+ * 2. Send the context to the server
762
+ * 3. Wait for initial flag values to be returned
763
+ * The promise resolves when initial flag values are received.
764
+ */
695
765
  setContext = async (context) => {
696
766
  if (!this.useWebSocket) {
697
767
  this.context = context;
@@ -705,10 +775,14 @@ var Schematic = class {
705
775
  const socket = await this.conn;
706
776
  await this.wsSendMessage(socket, context);
707
777
  } catch (error) {
708
- console.error("Error setting Schematic context:", error);
778
+ console.error("Failed to establish WebSocket connection:", error);
779
+ throw error;
709
780
  }
710
781
  };
711
- // Send track event
782
+ /**
783
+ * Send a track event
784
+ * Track usage for a company and/or user.
785
+ */
712
786
  track = (body) => {
713
787
  const { company, user, event, traits } = body;
714
788
  return this.handleEvent("track", {
@@ -763,6 +837,7 @@ var Schematic = class {
763
837
  await fetch(captureUrl, {
764
838
  method: "POST",
765
839
  headers: {
840
+ ...this.additionalHeaders ?? {},
766
841
  "Content-Type": "application/json;charset=UTF-8"
767
842
  },
768
843
  body: payload
@@ -779,6 +854,9 @@ var Schematic = class {
779
854
  /**
780
855
  * Websocket management
781
856
  */
857
+ /**
858
+ * If using websocket mode, close the connection when done.
859
+ */
782
860
  cleanup = async () => {
783
861
  if (this.conn) {
784
862
  try {
@@ -812,8 +890,7 @@ var Schematic = class {
812
890
  wsSendMessage = (socket, context) => {
813
891
  return new Promise((resolve, reject) => {
814
892
  if (contextString(context) == contextString(this.context)) {
815
- resolve();
816
- return;
893
+ return resolve(this.setIsPending(false));
817
894
  }
818
895
  this.context = context;
819
896
  const sendMessage = () => {
@@ -826,7 +903,7 @@ var Schematic = class {
826
903
  (message.flags ?? []).forEach(
827
904
  (flag) => {
828
905
  this.values[contextString(context)][flag.flag] = flag.value;
829
- this.notifyFlagValueListeners(flag.flag);
906
+ this.notifyFlagValueListeners(flag.flag, flag.value);
830
907
  }
831
908
  );
832
909
  if (this.flagListener) {
@@ -870,7 +947,9 @@ var Schematic = class {
870
947
  };
871
948
  setIsPending = (isPending) => {
872
949
  this.isPending = isPending;
873
- this.isPendingListeners.forEach((listener) => listener());
950
+ this.isPendingListeners.forEach(
951
+ (listener) => notifyListener(listener, isPending)
952
+ );
874
953
  };
875
954
  // flagValues state
876
955
  getFlagValue = (flagKey) => {
@@ -890,14 +969,21 @@ var Schematic = class {
890
969
  this.flagValueListeners[flagKey].delete(listener);
891
970
  };
892
971
  };
893
- notifyFlagValueListeners = (flagKey) => {
972
+ notifyFlagValueListeners = (flagKey, value) => {
894
973
  const listeners = this.flagValueListeners?.[flagKey] ?? [];
895
- listeners.forEach((listener) => listener());
974
+ listeners.forEach((listener) => notifyListener(listener, value));
896
975
  };
897
976
  };
977
+ var notifyListener = (listener, value) => {
978
+ if (listener.length > 0) {
979
+ listener(value);
980
+ } else {
981
+ listener();
982
+ }
983
+ };
898
984
 
899
985
  // src/context/schematic.tsx
900
- import React, { createContext, useEffect, useMemo } from "react";
986
+ import React, { createContext, useEffect, useMemo, useRef } from "react";
901
987
  import { jsx } from "react/jsx-runtime";
902
988
  var SchematicContext = createContext(
903
989
  null
@@ -908,16 +994,19 @@ var SchematicProvider = ({
908
994
  publishableKey,
909
995
  ...clientOpts
910
996
  }) => {
997
+ const initialOptsRef = useRef({
998
+ publishableKey,
999
+ useWebSocket: clientOpts.useWebSocket ?? true,
1000
+ ...clientOpts
1001
+ });
911
1002
  const client = useMemo(() => {
912
- const { useWebSocket = true } = clientOpts;
913
1003
  if (providedClient) {
914
1004
  return providedClient;
915
1005
  }
916
- return new Schematic(publishableKey, {
917
- useWebSocket,
918
- ...clientOpts
1006
+ return new Schematic(initialOptsRef.current.publishableKey, {
1007
+ ...initialOptsRef.current
919
1008
  });
920
- }, [providedClient, publishableKey, clientOpts]);
1009
+ }, [providedClient]);
921
1010
  useEffect(() => {
922
1011
  return () => {
923
1012
  if (!providedClient) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-react",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "main": "dist/schematic-react.cjs.js",
5
5
  "module": "dist/schematic-react.esm.js",
6
6
  "types": "dist/schematic-react.d.ts",
@@ -17,23 +17,21 @@
17
17
  },
18
18
  "scripts": {
19
19
  "dev": "yarn tsc --watch",
20
- "build": "yarn tsc && yarn openapi && yarn format && yarn lint && yarn clean && yarn build:cjs && yarn build:esm && yarn build:types",
20
+ "build": "yarn tsc && yarn format && yarn lint && yarn clean && yarn build:cjs && yarn build:esm && yarn build:types",
21
21
  "build:cjs": "npx esbuild src/index.ts --bundle --external:react --format=cjs --outfile=dist/schematic-react.cjs.js",
22
22
  "build:esm": "npx esbuild src/index.ts --bundle --external:react --format=esm --outfile=dist/schematic-react.esm.js",
23
23
  "build:types": "npx tsc && npx api-extractor run",
24
24
  "clean": "rm -rf dist",
25
25
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
26
26
  "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --fix",
27
- "openapi": "rm -rf src/api/ && npx openapi-generator-cli generate -c openapi-config.yaml && prettier --write \"src/api/**/*.{ts,tsx}\"",
28
27
  "test": "jest --config jest.config.js",
29
28
  "tsc": "npx tsc"
30
29
  },
31
30
  "dependencies": {
32
- "@schematichq/schematic-js": "^1.0.0"
31
+ "@schematichq/schematic-js": "^1.0.2"
33
32
  },
34
33
  "devDependencies": {
35
34
  "@microsoft/api-extractor": "^7.47.9",
36
- "@openapitools/openapi-generator-cli": "^2.13.9",
37
35
  "@types/jest": "^29.5.13",
38
36
  "@types/react": "^18.3.9",
39
37
  "@typescript-eslint/eslint-plugin": "^8.7.0",
@@ -42,7 +40,7 @@
42
40
  "esbuild-jest": "^0.5.0",
43
41
  "eslint": "^8.57.1",
44
42
  "eslint-plugin-import": "^2.30.0",
45
- "eslint-plugin-react-hooks": "^4.6.2",
43
+ "eslint-plugin-react-hooks": "^5.0.0",
46
44
  "jest": "^29.7.0",
47
45
  "jest-environment-jsdom": "^29.7.0",
48
46
  "jest-esbuild": "^0.3.0",