@schematichq/schematic-react 1.2.6 → 1.2.7

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-2025 Schematic, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -134,6 +134,8 @@ const MyComponent = () => {
134
134
  };
135
135
  ```
136
136
 
137
+ *Note: `useSchematicIsPending` is checking if entitlement data has been loaded, typically via `identify`. It should, therefore, be used to wrap flag and entitlement checks, but never the initial call to `identify`.*
138
+
137
139
  ## Troubleshooting
138
140
 
139
141
  For debugging and development, Schematic supports two special modes:
@@ -799,7 +799,7 @@ function contextString(context) {
799
799
  }, {});
800
800
  return JSON.stringify(sortedContext);
801
801
  }
802
- var version = "1.2.6";
802
+ var version = "1.2.7";
803
803
  var anonymousIdKey = "schematicId";
804
804
  var Schematic = class {
805
805
  additionalHeaders = {};
@@ -821,6 +821,14 @@ var Schematic = class {
821
821
  checks = {};
822
822
  featureUsageEventMap = {};
823
823
  webSocketUrl = "wss://api.schematichq.com";
824
+ webSocketConnectionTimeout = 1e4;
825
+ webSocketReconnect = true;
826
+ webSocketMaxReconnectAttempts = 7;
827
+ webSocketInitialRetryDelay = 1e3;
828
+ webSocketMaxRetryDelay = 3e4;
829
+ wsReconnectAttempts = 0;
830
+ wsReconnectTimer = null;
831
+ wsIntentionalDisconnect = false;
824
832
  constructor(apiKey, options) {
825
833
  this.apiKey = apiKey;
826
834
  this.eventQueue = [];
@@ -864,11 +872,36 @@ var Schematic = class {
864
872
  if (options?.webSocketUrl !== void 0) {
865
873
  this.webSocketUrl = options.webSocketUrl;
866
874
  }
875
+ if (options?.webSocketConnectionTimeout !== void 0) {
876
+ this.webSocketConnectionTimeout = options.webSocketConnectionTimeout;
877
+ }
878
+ if (options?.webSocketReconnect !== void 0) {
879
+ this.webSocketReconnect = options.webSocketReconnect;
880
+ }
881
+ if (options?.webSocketMaxReconnectAttempts !== void 0) {
882
+ this.webSocketMaxReconnectAttempts = options.webSocketMaxReconnectAttempts;
883
+ }
884
+ if (options?.webSocketInitialRetryDelay !== void 0) {
885
+ this.webSocketInitialRetryDelay = options.webSocketInitialRetryDelay;
886
+ }
887
+ if (options?.webSocketMaxRetryDelay !== void 0) {
888
+ this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
889
+ }
867
890
  if (typeof window !== "undefined" && window?.addEventListener) {
868
891
  window.addEventListener("beforeunload", () => {
869
892
  this.flushEventQueue();
870
893
  this.flushContextDependentEventQueue();
871
894
  });
895
+ if (this.useWebSocket) {
896
+ window.addEventListener("offline", () => {
897
+ this.debug("Browser went offline, closing WebSocket connection");
898
+ this.handleNetworkOffline();
899
+ });
900
+ window.addEventListener("online", () => {
901
+ this.debug("Browser came online, attempting to reconnect WebSocket");
902
+ this.handleNetworkOnline();
903
+ });
904
+ }
872
905
  }
873
906
  if (this.offlineEnabled) {
874
907
  this.debug(
@@ -1133,6 +1166,13 @@ var Schematic = class {
1133
1166
  try {
1134
1167
  this.setIsPending(true);
1135
1168
  if (!this.conn) {
1169
+ if (this.wsReconnectTimer !== null) {
1170
+ this.debug(
1171
+ `Cancelling scheduled reconnection, connecting immediately`
1172
+ );
1173
+ clearTimeout(this.wsReconnectTimer);
1174
+ this.wsReconnectTimer = null;
1175
+ }
1136
1176
  this.conn = this.wsConnect();
1137
1177
  }
1138
1178
  const socket = await this.conn;
@@ -1339,6 +1379,11 @@ var Schematic = class {
1339
1379
  this.debug("cleanup: skipped (offline mode)");
1340
1380
  return Promise.resolve();
1341
1381
  }
1382
+ this.wsIntentionalDisconnect = true;
1383
+ if (this.wsReconnectTimer !== null) {
1384
+ clearTimeout(this.wsReconnectTimer);
1385
+ this.wsReconnectTimer = null;
1386
+ }
1342
1387
  if (this.conn) {
1343
1388
  try {
1344
1389
  const socket = await this.conn;
@@ -1350,6 +1395,91 @@ var Schematic = class {
1350
1395
  }
1351
1396
  }
1352
1397
  };
1398
+ /**
1399
+ * Calculate the delay for the next reconnection attempt using exponential backoff with jitter.
1400
+ * This helps prevent dogpiling when the server recovers from an outage.
1401
+ */
1402
+ calculateReconnectDelay = () => {
1403
+ const exponentialDelay = this.webSocketInitialRetryDelay * Math.pow(2, this.wsReconnectAttempts);
1404
+ const cappedDelay = Math.min(exponentialDelay, this.webSocketMaxRetryDelay);
1405
+ const jitter = Math.random() * cappedDelay * 0.5;
1406
+ const totalDelay = cappedDelay + jitter;
1407
+ this.debug(
1408
+ `Reconnect delay calculated: ${totalDelay.toFixed(0)}ms (attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts})`
1409
+ );
1410
+ return totalDelay;
1411
+ };
1412
+ /**
1413
+ * Handle browser going offline
1414
+ */
1415
+ handleNetworkOffline = async () => {
1416
+ if (this.conn !== null) {
1417
+ try {
1418
+ const socket = await this.conn;
1419
+ socket.close();
1420
+ } catch (error) {
1421
+ this.debug("Error closing connection on offline:", error);
1422
+ }
1423
+ this.conn = null;
1424
+ }
1425
+ if (this.wsReconnectTimer !== null) {
1426
+ clearTimeout(this.wsReconnectTimer);
1427
+ this.wsReconnectTimer = null;
1428
+ }
1429
+ };
1430
+ /**
1431
+ * Handle browser coming back online
1432
+ */
1433
+ handleNetworkOnline = () => {
1434
+ if (this.context.company === void 0 && this.context.user === void 0) {
1435
+ this.debug("No context set, skipping reconnection");
1436
+ return;
1437
+ }
1438
+ this.wsReconnectAttempts = 0;
1439
+ if (this.wsReconnectTimer !== null) {
1440
+ clearTimeout(this.wsReconnectTimer);
1441
+ this.wsReconnectTimer = null;
1442
+ }
1443
+ this.debug("Network online, reconnecting immediately");
1444
+ this.attemptReconnect();
1445
+ };
1446
+ /**
1447
+ * Attempt to reconnect the WebSocket connection with exponential backoff.
1448
+ * Called automatically when the connection closes unexpectedly.
1449
+ */
1450
+ attemptReconnect = () => {
1451
+ if (this.wsReconnectAttempts >= this.webSocketMaxReconnectAttempts) {
1452
+ this.debug(
1453
+ `Maximum reconnection attempts (${this.webSocketMaxReconnectAttempts}) reached, giving up`
1454
+ );
1455
+ return;
1456
+ }
1457
+ if (this.wsReconnectTimer !== null) {
1458
+ clearTimeout(this.wsReconnectTimer);
1459
+ }
1460
+ const delay = this.calculateReconnectDelay();
1461
+ this.debug(
1462
+ `Scheduling reconnection attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts} in ${delay.toFixed(0)}ms`
1463
+ );
1464
+ this.wsReconnectTimer = setTimeout(async () => {
1465
+ this.wsReconnectTimer = null;
1466
+ this.wsReconnectAttempts++;
1467
+ this.debug(
1468
+ `Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
1469
+ );
1470
+ try {
1471
+ this.conn = this.wsConnect();
1472
+ const socket = await this.conn;
1473
+ if (this.context.company !== void 0 || this.context.user !== void 0) {
1474
+ this.debug(`Reconnected, re-sending context`);
1475
+ await this.wsSendMessage(socket, this.context);
1476
+ }
1477
+ this.debug(`Reconnection successful`);
1478
+ } catch (error) {
1479
+ this.debug(`Reconnection attempt failed:`, error);
1480
+ }
1481
+ }, delay);
1482
+ };
1353
1483
  // Open a websocket connection
1354
1484
  wsConnect = () => {
1355
1485
  if (this.isOffline()) {
@@ -1362,17 +1492,45 @@ var Schematic = class {
1362
1492
  const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
1363
1493
  this.debug(`connecting to WebSocket:`, wsUrl);
1364
1494
  const webSocket = new WebSocket(wsUrl);
1495
+ let timeoutId = null;
1496
+ let isResolved = false;
1497
+ timeoutId = setTimeout(() => {
1498
+ if (!isResolved) {
1499
+ this.debug(
1500
+ `WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`
1501
+ );
1502
+ webSocket.close();
1503
+ reject(new Error("WebSocket connection timeout"));
1504
+ }
1505
+ }, this.webSocketConnectionTimeout);
1365
1506
  webSocket.onopen = () => {
1507
+ isResolved = true;
1508
+ if (timeoutId !== null) {
1509
+ clearTimeout(timeoutId);
1510
+ }
1511
+ this.wsReconnectAttempts = 0;
1512
+ this.wsIntentionalDisconnect = false;
1366
1513
  this.debug(`WebSocket connection opened`);
1367
1514
  resolve(webSocket);
1368
1515
  };
1369
1516
  webSocket.onerror = (error) => {
1517
+ isResolved = true;
1518
+ if (timeoutId !== null) {
1519
+ clearTimeout(timeoutId);
1520
+ }
1370
1521
  this.debug(`WebSocket connection error:`, error);
1371
1522
  reject(error);
1372
1523
  };
1373
1524
  webSocket.onclose = () => {
1525
+ isResolved = true;
1526
+ if (timeoutId !== null) {
1527
+ clearTimeout(timeoutId);
1528
+ }
1374
1529
  this.debug(`WebSocket connection closed`);
1375
1530
  this.conn = null;
1531
+ if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
1532
+ this.attemptReconnect();
1533
+ }
1376
1534
  };
1377
1535
  });
1378
1536
  };
@@ -1564,7 +1722,7 @@ var notifyFlagValueListener = (listener, value) => {
1564
1722
  var import_react = __toESM(require("react"));
1565
1723
 
1566
1724
  // src/version.ts
1567
- var version2 = "1.2.6";
1725
+ var version2 = "1.2.7";
1568
1726
 
1569
1727
  // src/context/schematic.tsx
1570
1728
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -754,7 +754,7 @@ function contextString(context) {
754
754
  }, {});
755
755
  return JSON.stringify(sortedContext);
756
756
  }
757
- var version = "1.2.6";
757
+ var version = "1.2.7";
758
758
  var anonymousIdKey = "schematicId";
759
759
  var Schematic = class {
760
760
  additionalHeaders = {};
@@ -776,6 +776,14 @@ var Schematic = class {
776
776
  checks = {};
777
777
  featureUsageEventMap = {};
778
778
  webSocketUrl = "wss://api.schematichq.com";
779
+ webSocketConnectionTimeout = 1e4;
780
+ webSocketReconnect = true;
781
+ webSocketMaxReconnectAttempts = 7;
782
+ webSocketInitialRetryDelay = 1e3;
783
+ webSocketMaxRetryDelay = 3e4;
784
+ wsReconnectAttempts = 0;
785
+ wsReconnectTimer = null;
786
+ wsIntentionalDisconnect = false;
779
787
  constructor(apiKey, options) {
780
788
  this.apiKey = apiKey;
781
789
  this.eventQueue = [];
@@ -819,11 +827,36 @@ var Schematic = class {
819
827
  if (options?.webSocketUrl !== void 0) {
820
828
  this.webSocketUrl = options.webSocketUrl;
821
829
  }
830
+ if (options?.webSocketConnectionTimeout !== void 0) {
831
+ this.webSocketConnectionTimeout = options.webSocketConnectionTimeout;
832
+ }
833
+ if (options?.webSocketReconnect !== void 0) {
834
+ this.webSocketReconnect = options.webSocketReconnect;
835
+ }
836
+ if (options?.webSocketMaxReconnectAttempts !== void 0) {
837
+ this.webSocketMaxReconnectAttempts = options.webSocketMaxReconnectAttempts;
838
+ }
839
+ if (options?.webSocketInitialRetryDelay !== void 0) {
840
+ this.webSocketInitialRetryDelay = options.webSocketInitialRetryDelay;
841
+ }
842
+ if (options?.webSocketMaxRetryDelay !== void 0) {
843
+ this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
844
+ }
822
845
  if (typeof window !== "undefined" && window?.addEventListener) {
823
846
  window.addEventListener("beforeunload", () => {
824
847
  this.flushEventQueue();
825
848
  this.flushContextDependentEventQueue();
826
849
  });
850
+ if (this.useWebSocket) {
851
+ window.addEventListener("offline", () => {
852
+ this.debug("Browser went offline, closing WebSocket connection");
853
+ this.handleNetworkOffline();
854
+ });
855
+ window.addEventListener("online", () => {
856
+ this.debug("Browser came online, attempting to reconnect WebSocket");
857
+ this.handleNetworkOnline();
858
+ });
859
+ }
827
860
  }
828
861
  if (this.offlineEnabled) {
829
862
  this.debug(
@@ -1088,6 +1121,13 @@ var Schematic = class {
1088
1121
  try {
1089
1122
  this.setIsPending(true);
1090
1123
  if (!this.conn) {
1124
+ if (this.wsReconnectTimer !== null) {
1125
+ this.debug(
1126
+ `Cancelling scheduled reconnection, connecting immediately`
1127
+ );
1128
+ clearTimeout(this.wsReconnectTimer);
1129
+ this.wsReconnectTimer = null;
1130
+ }
1091
1131
  this.conn = this.wsConnect();
1092
1132
  }
1093
1133
  const socket = await this.conn;
@@ -1294,6 +1334,11 @@ var Schematic = class {
1294
1334
  this.debug("cleanup: skipped (offline mode)");
1295
1335
  return Promise.resolve();
1296
1336
  }
1337
+ this.wsIntentionalDisconnect = true;
1338
+ if (this.wsReconnectTimer !== null) {
1339
+ clearTimeout(this.wsReconnectTimer);
1340
+ this.wsReconnectTimer = null;
1341
+ }
1297
1342
  if (this.conn) {
1298
1343
  try {
1299
1344
  const socket = await this.conn;
@@ -1305,6 +1350,91 @@ var Schematic = class {
1305
1350
  }
1306
1351
  }
1307
1352
  };
1353
+ /**
1354
+ * Calculate the delay for the next reconnection attempt using exponential backoff with jitter.
1355
+ * This helps prevent dogpiling when the server recovers from an outage.
1356
+ */
1357
+ calculateReconnectDelay = () => {
1358
+ const exponentialDelay = this.webSocketInitialRetryDelay * Math.pow(2, this.wsReconnectAttempts);
1359
+ const cappedDelay = Math.min(exponentialDelay, this.webSocketMaxRetryDelay);
1360
+ const jitter = Math.random() * cappedDelay * 0.5;
1361
+ const totalDelay = cappedDelay + jitter;
1362
+ this.debug(
1363
+ `Reconnect delay calculated: ${totalDelay.toFixed(0)}ms (attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts})`
1364
+ );
1365
+ return totalDelay;
1366
+ };
1367
+ /**
1368
+ * Handle browser going offline
1369
+ */
1370
+ handleNetworkOffline = async () => {
1371
+ if (this.conn !== null) {
1372
+ try {
1373
+ const socket = await this.conn;
1374
+ socket.close();
1375
+ } catch (error) {
1376
+ this.debug("Error closing connection on offline:", error);
1377
+ }
1378
+ this.conn = null;
1379
+ }
1380
+ if (this.wsReconnectTimer !== null) {
1381
+ clearTimeout(this.wsReconnectTimer);
1382
+ this.wsReconnectTimer = null;
1383
+ }
1384
+ };
1385
+ /**
1386
+ * Handle browser coming back online
1387
+ */
1388
+ handleNetworkOnline = () => {
1389
+ if (this.context.company === void 0 && this.context.user === void 0) {
1390
+ this.debug("No context set, skipping reconnection");
1391
+ return;
1392
+ }
1393
+ this.wsReconnectAttempts = 0;
1394
+ if (this.wsReconnectTimer !== null) {
1395
+ clearTimeout(this.wsReconnectTimer);
1396
+ this.wsReconnectTimer = null;
1397
+ }
1398
+ this.debug("Network online, reconnecting immediately");
1399
+ this.attemptReconnect();
1400
+ };
1401
+ /**
1402
+ * Attempt to reconnect the WebSocket connection with exponential backoff.
1403
+ * Called automatically when the connection closes unexpectedly.
1404
+ */
1405
+ attemptReconnect = () => {
1406
+ if (this.wsReconnectAttempts >= this.webSocketMaxReconnectAttempts) {
1407
+ this.debug(
1408
+ `Maximum reconnection attempts (${this.webSocketMaxReconnectAttempts}) reached, giving up`
1409
+ );
1410
+ return;
1411
+ }
1412
+ if (this.wsReconnectTimer !== null) {
1413
+ clearTimeout(this.wsReconnectTimer);
1414
+ }
1415
+ const delay = this.calculateReconnectDelay();
1416
+ this.debug(
1417
+ `Scheduling reconnection attempt ${this.wsReconnectAttempts + 1}/${this.webSocketMaxReconnectAttempts} in ${delay.toFixed(0)}ms`
1418
+ );
1419
+ this.wsReconnectTimer = setTimeout(async () => {
1420
+ this.wsReconnectTimer = null;
1421
+ this.wsReconnectAttempts++;
1422
+ this.debug(
1423
+ `Attempting to reconnect (attempt ${this.wsReconnectAttempts}/${this.webSocketMaxReconnectAttempts})`
1424
+ );
1425
+ try {
1426
+ this.conn = this.wsConnect();
1427
+ const socket = await this.conn;
1428
+ if (this.context.company !== void 0 || this.context.user !== void 0) {
1429
+ this.debug(`Reconnected, re-sending context`);
1430
+ await this.wsSendMessage(socket, this.context);
1431
+ }
1432
+ this.debug(`Reconnection successful`);
1433
+ } catch (error) {
1434
+ this.debug(`Reconnection attempt failed:`, error);
1435
+ }
1436
+ }, delay);
1437
+ };
1308
1438
  // Open a websocket connection
1309
1439
  wsConnect = () => {
1310
1440
  if (this.isOffline()) {
@@ -1317,17 +1447,45 @@ var Schematic = class {
1317
1447
  const wsUrl = `${this.webSocketUrl}/flags/bootstrap?apiKey=${this.apiKey}`;
1318
1448
  this.debug(`connecting to WebSocket:`, wsUrl);
1319
1449
  const webSocket = new WebSocket(wsUrl);
1450
+ let timeoutId = null;
1451
+ let isResolved = false;
1452
+ timeoutId = setTimeout(() => {
1453
+ if (!isResolved) {
1454
+ this.debug(
1455
+ `WebSocket connection timeout after ${this.webSocketConnectionTimeout}ms`
1456
+ );
1457
+ webSocket.close();
1458
+ reject(new Error("WebSocket connection timeout"));
1459
+ }
1460
+ }, this.webSocketConnectionTimeout);
1320
1461
  webSocket.onopen = () => {
1462
+ isResolved = true;
1463
+ if (timeoutId !== null) {
1464
+ clearTimeout(timeoutId);
1465
+ }
1466
+ this.wsReconnectAttempts = 0;
1467
+ this.wsIntentionalDisconnect = false;
1321
1468
  this.debug(`WebSocket connection opened`);
1322
1469
  resolve(webSocket);
1323
1470
  };
1324
1471
  webSocket.onerror = (error) => {
1472
+ isResolved = true;
1473
+ if (timeoutId !== null) {
1474
+ clearTimeout(timeoutId);
1475
+ }
1325
1476
  this.debug(`WebSocket connection error:`, error);
1326
1477
  reject(error);
1327
1478
  };
1328
1479
  webSocket.onclose = () => {
1480
+ isResolved = true;
1481
+ if (timeoutId !== null) {
1482
+ clearTimeout(timeoutId);
1483
+ }
1329
1484
  this.debug(`WebSocket connection closed`);
1330
1485
  this.conn = null;
1486
+ if (!this.wsIntentionalDisconnect && this.webSocketReconnect) {
1487
+ this.attemptReconnect();
1488
+ }
1331
1489
  };
1332
1490
  });
1333
1491
  };
@@ -1519,7 +1677,7 @@ var notifyFlagValueListener = (listener, value) => {
1519
1677
  import React, { createContext, useEffect, useMemo, useRef } from "react";
1520
1678
 
1521
1679
  // src/version.ts
1522
- var version2 = "1.2.6";
1680
+ var version2 = "1.2.7";
1523
1681
 
1524
1682
  // src/context/schematic.tsx
1525
1683
  import { jsx } from "react/jsx-runtime";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-react",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "main": "dist/schematic-react.cjs.js",
5
5
  "module": "dist/schematic-react.esm.js",
6
6
  "types": "dist/schematic-react.d.ts",
@@ -24,40 +24,38 @@
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
- "test": "jest --config jest.config.js",
28
- "test:reactnative": "jest --config jest.config.reactnative.js",
27
+ "test": "vitest run",
28
+ "test:reactnative": "vitest run --config vitest.config.reactnative.ts",
29
+ "test:watch": "vitest",
29
30
  "tsc": "npx tsc",
30
31
  "prepare": "husky"
31
32
  },
32
33
  "dependencies": {
33
- "@schematichq/schematic-js": "^1.2.6"
34
+ "@schematichq/schematic-js": "^1.2.7"
34
35
  },
35
36
  "devDependencies": {
36
- "@eslint/js": "^9.24.0",
37
- "@microsoft/api-extractor": "^7.52.2",
37
+ "@eslint/js": "^9.39.1",
38
+ "@microsoft/api-extractor": "^7.55.0",
38
39
  "@testing-library/dom": "^10.4.1",
39
- "@testing-library/jest-dom": "^6.8.0",
40
+ "@testing-library/jest-dom": "^6.9.1",
40
41
  "@testing-library/react": "^16.3.0",
41
- "@types/jest": "^30.0.0",
42
- "@types/react": "^19.1.1",
43
- "esbuild": "^0.25.2",
44
- "esbuild-jest": "^0.5.0",
45
- "eslint": "^9.24.0",
42
+ "@types/react": "^19.2.3",
43
+ "@vitest/browser": "^4.0.8",
44
+ "esbuild": "^0.27.0",
45
+ "eslint": "^9.39.1",
46
46
  "eslint-plugin-import": "^2.32.0",
47
47
  "eslint-plugin-react": "^7.37.5",
48
- "eslint-plugin-react-hooks": "^5.2.0",
49
- "globals": "^16.0.0",
48
+ "eslint-plugin-react-hooks": "^7.0.1",
49
+ "globals": "^16.5.0",
50
+ "happy-dom": "^20.0.10",
50
51
  "husky": "^9.1.7",
51
- "jest": "^30.0.0",
52
- "jest-environment-jsdom": "^30.0.0",
53
- "jest-esbuild": "^0.4.0",
54
- "jest-fetch-mock": "^3.0.3",
52
+ "jsdom": "^27.2.0",
55
53
  "prettier": "^3.6.2",
56
- "react": "^19.1.1",
57
- "react-dom": "^19.1.1",
58
- "ts-jest": "^29.3.0",
59
- "typescript": "^5.9.2",
60
- "typescript-eslint": "^8.29.1"
54
+ "react": "^19.2.0",
55
+ "react-dom": "^19.2.0",
56
+ "typescript": "^5.9.3",
57
+ "typescript-eslint": "^8.46.4",
58
+ "vitest": "^4.0.8"
61
59
  },
62
60
  "peerDependencies": {
63
61
  "react": ">=18"