@schematichq/schematic-react 1.0.0 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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",