@schematichq/schematic-react 1.2.16 → 1.2.21

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/README.md CHANGED
@@ -136,6 +136,66 @@ const MyComponent = () => {
136
136
 
137
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
138
 
139
+ ## Fallback Behavior
140
+
141
+ The SDK includes built-in fallback behavior you can use to ensure your application continues to function even when unable to reach Schematic (e.g., during service disruptions or network issues).
142
+
143
+ ### Flag Check Fallbacks
144
+
145
+ When flag checks cannot reach Schematic, they use fallback values in the following priority order:
146
+
147
+ 1. Callsite fallback - fallback values can be provided directly in the hook options
148
+ 2. Initialization defaults - fallback values configured via `flagCheckDefaults` or `flagValueDefaults` options when initializing the provider
149
+ 3. Default value - Returns `false` if no fallback is configured
150
+
151
+ ```tsx
152
+ // Provide a fallback value at the callsite
153
+ import { useSchematicFlag } from "@schematichq/schematic-react";
154
+
155
+ const MyComponent = () => {
156
+ const isFeatureEnabled = useSchematicFlag("feature-flag", {
157
+ fallback: true, // Used if API request fails
158
+ });
159
+
160
+ return isFeatureEnabled ? <Feature /> : <Fallback />;
161
+ };
162
+
163
+ // Or configure defaults at initialization
164
+ import { SchematicProvider } from "@schematichq/schematic-react";
165
+
166
+ ReactDOM.render(
167
+ <SchematicProvider
168
+ publishableKey="your-publishable-key"
169
+ flagValueDefaults={{
170
+ "feature-flag": true, // Used if API request fails and no callsite fallback
171
+ }}
172
+ flagCheckDefaults={{
173
+ "another-flag": {
174
+ flag: "another-flag",
175
+ value: true,
176
+ reason: "Default value",
177
+ },
178
+ }}
179
+ >
180
+ <App />
181
+ </SchematicProvider>,
182
+ document.getElementById("root"),
183
+ );
184
+ ```
185
+
186
+ ### Event Queueing and Retry
187
+
188
+ When events (track, identify) cannot be sent due to network issues, they are automatically queued and retried:
189
+
190
+ - Events are queued in memory (up to 100 events by default, configurable via `maxEventQueueSize`)
191
+ - Failed events are retried with exponential backoff (up to 5 attempts by default, configurable via `maxEventRetries`)
192
+ - Events are automatically flushed when the network connection is restored
193
+ - Events queued when the page is hidden are sent when the page becomes visible
194
+
195
+ ### WebSocket Fallback
196
+
197
+ In WebSocket mode, if the WebSocket connection fails, the SDK will provide the last known value or the configured fallback values as [outlined above](/#flag-check-fallbacks). The WebSocket will also automatically attempt to re-establish it's connection with Schematic using an exponential backoff.
198
+
139
199
  ## React Native
140
200
 
141
201
  ### Handling app background/foreground
@@ -217,7 +277,6 @@ ReactDOM.render(
217
277
 
218
278
  Offline mode automatically enables debug mode to help with troubleshooting.
219
279
 
220
-
221
280
  ## License
222
281
 
223
282
  MIT
@@ -651,6 +651,36 @@ function v4(options, buf, offset) {
651
651
  }
652
652
  var v4_default = v4;
653
653
  var import_polyfill = __toESM2(require_browser_polyfill());
654
+ function EntitlementValueTypeFromJSON(json) {
655
+ return EntitlementValueTypeFromJSONTyped(json, false);
656
+ }
657
+ function EntitlementValueTypeFromJSONTyped(json, ignoreDiscriminator) {
658
+ return json;
659
+ }
660
+ function FeatureEntitlementFromJSON(json) {
661
+ return FeatureEntitlementFromJSONTyped(json, false);
662
+ }
663
+ function FeatureEntitlementFromJSONTyped(json, ignoreDiscriminator) {
664
+ if (json == null) {
665
+ return json;
666
+ }
667
+ return {
668
+ allocation: json["allocation"] == null ? void 0 : json["allocation"],
669
+ creditId: json["credit_id"] == null ? void 0 : json["credit_id"],
670
+ creditRemaining: json["credit_remaining"] == null ? void 0 : json["credit_remaining"],
671
+ creditTotal: json["credit_total"] == null ? void 0 : json["credit_total"],
672
+ creditUsed: json["credit_used"] == null ? void 0 : json["credit_used"],
673
+ eventName: json["event_name"] == null ? void 0 : json["event_name"],
674
+ featureId: json["feature_id"],
675
+ featureKey: json["feature_key"],
676
+ metricPeriod: json["metric_period"] == null ? void 0 : json["metric_period"],
677
+ metricResetAt: json["metric_reset_at"] == null ? void 0 : new Date(json["metric_reset_at"]),
678
+ monthReset: json["month_reset"] == null ? void 0 : json["month_reset"],
679
+ softLimit: json["soft_limit"] == null ? void 0 : json["soft_limit"],
680
+ usage: json["usage"] == null ? void 0 : json["usage"],
681
+ valueType: EntitlementValueTypeFromJSON(json["value_type"])
682
+ };
683
+ }
654
684
  function CheckFlagResponseDataFromJSON(json) {
655
685
  return CheckFlagResponseDataFromJSONTyped(json, false);
656
686
  }
@@ -660,6 +690,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
660
690
  }
661
691
  return {
662
692
  companyId: json["company_id"] == null ? void 0 : json["company_id"],
693
+ entitlement: json["entitlement"] == null ? void 0 : FeatureEntitlementFromJSON(json["entitlement"]),
663
694
  error: json["error"] == null ? void 0 : json["error"],
664
695
  featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
665
696
  featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
@@ -799,7 +830,7 @@ function contextString(context) {
799
830
  }, {});
800
831
  return JSON.stringify(sortedContext);
801
832
  }
802
- var version = "1.2.16";
833
+ var version = "1.2.21";
803
834
  var anonymousIdKey = "schematicId";
804
835
  var Schematic = class {
805
836
  additionalHeaders = {};
@@ -845,6 +876,7 @@ var Schematic = class {
845
876
  retryTimer = null;
846
877
  flagValueDefaults = {};
847
878
  flagCheckDefaults = {};
879
+ fallbackCheckCache = {};
848
880
  constructor(apiKey, options) {
849
881
  this.apiKey = apiKey;
850
882
  this.eventQueue = [];
@@ -1013,8 +1045,8 @@ var Schematic = class {
1013
1045
  /**
1014
1046
  * Get value for a single flag.
1015
1047
  * In WebSocket mode, returns cached values if connection is active, otherwise establishes
1016
- * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
1017
- * connection fails.
1048
+ * new connection and then returns the requested value. Falls back to preconfigured fallback
1049
+ * values if WebSocket connection fails.
1018
1050
  * In REST mode, makes an API call for each check.
1019
1051
  */
1020
1052
  async checkFlag(options) {
@@ -1083,10 +1115,17 @@ var Schematic = class {
1083
1115
  await this.setContext(context);
1084
1116
  } catch (error) {
1085
1117
  console.warn(
1086
- "WebSocket connection failed, falling back to REST:",
1118
+ "WebSocket connection failed, using fallback value:",
1087
1119
  error
1088
1120
  );
1089
- return this.fallbackToRest(key, context, fallback);
1121
+ const errorResult = this.resolveFallbackCheckFlagReturn(
1122
+ key,
1123
+ fallback,
1124
+ "WebSocket connection failed",
1125
+ error instanceof Error ? error.message : String(error)
1126
+ );
1127
+ this.submitFlagCheckEvent(key, errorResult, context);
1128
+ return errorResult.value;
1090
1129
  }
1091
1130
  const contextVals = this.checks[contextStr] ?? {};
1092
1131
  const flagCheck = contextVals[key];
@@ -1097,6 +1136,13 @@ var Schematic = class {
1097
1136
  );
1098
1137
  if (typeof flagCheck !== "undefined") {
1099
1138
  this.submitFlagCheckEvent(key, flagCheck, context);
1139
+ } else {
1140
+ const fallbackResult = this.resolveFallbackCheckFlagReturn(
1141
+ key,
1142
+ fallback,
1143
+ "No flag values available"
1144
+ );
1145
+ this.submitFlagCheckEvent(key, fallbackResult, context);
1100
1146
  }
1101
1147
  return result;
1102
1148
  } catch (error) {
@@ -1189,53 +1235,6 @@ var Schematic = class {
1189
1235
  this.debug(`submitting flag check event:`, eventBody);
1190
1236
  return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
1191
1237
  }
1192
- /**
1193
- * Helper method for falling back to REST API when WebSocket connection fails
1194
- */
1195
- async fallbackToRest(key, context, fallback) {
1196
- if (this.isOffline()) {
1197
- const resolvedFallback = this.resolveFallbackValue(key, fallback);
1198
- this.debug(`fallbackToRest offline result: ${key}`, {
1199
- value: resolvedFallback,
1200
- offlineMode: true
1201
- });
1202
- return resolvedFallback;
1203
- }
1204
- try {
1205
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
1206
- const response = await fetch(requestUrl, {
1207
- method: "POST",
1208
- headers: {
1209
- ...this.additionalHeaders ?? {},
1210
- "Content-Type": "application/json;charset=UTF-8",
1211
- "X-Schematic-Api-Key": this.apiKey
1212
- },
1213
- body: JSON.stringify(context)
1214
- });
1215
- if (!response.ok) {
1216
- throw new Error("Network response was not ok");
1217
- }
1218
- const responseJson = await response.json();
1219
- const data = CheckFlagResponseFromJSON(responseJson);
1220
- this.debug(`fallbackToRest result: ${key}`, data);
1221
- const result = CheckFlagReturnFromJSON(data.data);
1222
- if (typeof result.featureUsageEvent === "string") {
1223
- this.updateFeatureUsageEventMap(result);
1224
- }
1225
- this.submitFlagCheckEvent(key, result, context);
1226
- return result.value;
1227
- } catch (error) {
1228
- console.warn("REST API call failed, using fallback value:", error);
1229
- const errorResult = this.resolveFallbackCheckFlagReturn(
1230
- key,
1231
- fallback,
1232
- "API request failed (fallback)",
1233
- error instanceof Error ? error.message : String(error)
1234
- );
1235
- this.submitFlagCheckEvent(key, errorResult, context);
1236
- return errorResult.value;
1237
- }
1238
- }
1239
1238
  /**
1240
1239
  * Make an API call to fetch all flag values for a given context.
1241
1240
  * Recommended for use in REST mode only.
@@ -1309,6 +1308,9 @@ var Schematic = class {
1309
1308
  this.setIsPending(false);
1310
1309
  return Promise.resolve();
1311
1310
  }
1311
+ if (contextString(context) === contextString(this.context) && this.conn !== null && !this.isPending) {
1312
+ return;
1313
+ }
1312
1314
  try {
1313
1315
  this.setIsPending(true);
1314
1316
  if (!this.conn) {
@@ -1348,7 +1350,10 @@ var Schematic = class {
1348
1350
  await this.wsSendMessage(socket, context);
1349
1351
  } catch (error) {
1350
1352
  console.warn("Failed to establish WebSocket connection:", error);
1351
- throw error;
1353
+ this.context = context;
1354
+ this.flushContextDependentEventQueue();
1355
+ this.setIsPending(false);
1356
+ this.attemptReconnect();
1352
1357
  }
1353
1358
  };
1354
1359
  /**
@@ -1989,7 +1994,10 @@ var Schematic = class {
1989
1994
  clearTimeout(timeoutId);
1990
1995
  }
1991
1996
  this.debug(`WebSocket connection ${connectionId} error:`, error);
1992
- reject(error);
1997
+ const wrappedError = new Error(
1998
+ "WebSocket connection failed during handshake"
1999
+ );
2000
+ reject(wrappedError);
1993
2001
  };
1994
2002
  webSocket.onclose = () => {
1995
2003
  if (timeoutId !== null) {
@@ -2036,6 +2044,20 @@ var Schematic = class {
2036
2044
  }
2037
2045
  };
2038
2046
  socket.addEventListener("message", messageHandler);
2047
+ socket.addEventListener("close", (event) => {
2048
+ if (!resolved) {
2049
+ resolved = true;
2050
+ if (event.code === 4001) {
2051
+ reject(
2052
+ new Error(
2053
+ `Authentication failed: ${event.reason !== "" ? event.reason : "Invalid API key"}`
2054
+ )
2055
+ );
2056
+ } else {
2057
+ reject(new Error("WebSocket connection closed unexpectedly"));
2058
+ }
2059
+ }
2060
+ });
2039
2061
  this.currentWebSocket = socket;
2040
2062
  const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
2041
2063
  const messagePayload = {
@@ -2072,6 +2094,7 @@ var Schematic = class {
2072
2094
  };
2073
2095
  };
2074
2096
  setIsPending = (isPending) => {
2097
+ if (this.isPending === isPending) return;
2075
2098
  this.isPending = isPending;
2076
2099
  this.isPendingListeners.forEach(
2077
2100
  (listener) => notifyPendingListener(listener, isPending)
@@ -2086,11 +2109,14 @@ var Schematic = class {
2086
2109
  return check;
2087
2110
  }
2088
2111
  if (flagKey in this.flagCheckDefaults || flagKey in this.flagValueDefaults) {
2089
- return this.resolveFallbackCheckFlagReturn(
2090
- flagKey,
2091
- void 0,
2092
- "Default value used"
2093
- );
2112
+ if (!(flagKey in this.fallbackCheckCache)) {
2113
+ this.fallbackCheckCache[flagKey] = this.resolveFallbackCheckFlagReturn(
2114
+ flagKey,
2115
+ void 0,
2116
+ "Default value used"
2117
+ );
2118
+ }
2119
+ return this.fallbackCheckCache[flagKey];
2094
2120
  }
2095
2121
  return void 0;
2096
2122
  };
@@ -2202,7 +2228,7 @@ var notifyFlagValueListener = (listener, value) => {
2202
2228
  var import_react = __toESM(require("react"));
2203
2229
 
2204
2230
  // src/version.ts
2205
- var version2 = "1.2.16";
2231
+ var version2 = "1.2.21";
2206
2232
 
2207
2233
  // src/context/schematic.tsx
2208
2234
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -606,6 +606,36 @@ function v4(options, buf, offset) {
606
606
  }
607
607
  var v4_default = v4;
608
608
  var import_polyfill = __toESM(require_browser_polyfill());
609
+ function EntitlementValueTypeFromJSON(json) {
610
+ return EntitlementValueTypeFromJSONTyped(json, false);
611
+ }
612
+ function EntitlementValueTypeFromJSONTyped(json, ignoreDiscriminator) {
613
+ return json;
614
+ }
615
+ function FeatureEntitlementFromJSON(json) {
616
+ return FeatureEntitlementFromJSONTyped(json, false);
617
+ }
618
+ function FeatureEntitlementFromJSONTyped(json, ignoreDiscriminator) {
619
+ if (json == null) {
620
+ return json;
621
+ }
622
+ return {
623
+ allocation: json["allocation"] == null ? void 0 : json["allocation"],
624
+ creditId: json["credit_id"] == null ? void 0 : json["credit_id"],
625
+ creditRemaining: json["credit_remaining"] == null ? void 0 : json["credit_remaining"],
626
+ creditTotal: json["credit_total"] == null ? void 0 : json["credit_total"],
627
+ creditUsed: json["credit_used"] == null ? void 0 : json["credit_used"],
628
+ eventName: json["event_name"] == null ? void 0 : json["event_name"],
629
+ featureId: json["feature_id"],
630
+ featureKey: json["feature_key"],
631
+ metricPeriod: json["metric_period"] == null ? void 0 : json["metric_period"],
632
+ metricResetAt: json["metric_reset_at"] == null ? void 0 : new Date(json["metric_reset_at"]),
633
+ monthReset: json["month_reset"] == null ? void 0 : json["month_reset"],
634
+ softLimit: json["soft_limit"] == null ? void 0 : json["soft_limit"],
635
+ usage: json["usage"] == null ? void 0 : json["usage"],
636
+ valueType: EntitlementValueTypeFromJSON(json["value_type"])
637
+ };
638
+ }
609
639
  function CheckFlagResponseDataFromJSON(json) {
610
640
  return CheckFlagResponseDataFromJSONTyped(json, false);
611
641
  }
@@ -615,6 +645,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
615
645
  }
616
646
  return {
617
647
  companyId: json["company_id"] == null ? void 0 : json["company_id"],
648
+ entitlement: json["entitlement"] == null ? void 0 : FeatureEntitlementFromJSON(json["entitlement"]),
618
649
  error: json["error"] == null ? void 0 : json["error"],
619
650
  featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
620
651
  featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
@@ -754,7 +785,7 @@ function contextString(context) {
754
785
  }, {});
755
786
  return JSON.stringify(sortedContext);
756
787
  }
757
- var version = "1.2.16";
788
+ var version = "1.2.21";
758
789
  var anonymousIdKey = "schematicId";
759
790
  var Schematic = class {
760
791
  additionalHeaders = {};
@@ -800,6 +831,7 @@ var Schematic = class {
800
831
  retryTimer = null;
801
832
  flagValueDefaults = {};
802
833
  flagCheckDefaults = {};
834
+ fallbackCheckCache = {};
803
835
  constructor(apiKey, options) {
804
836
  this.apiKey = apiKey;
805
837
  this.eventQueue = [];
@@ -968,8 +1000,8 @@ var Schematic = class {
968
1000
  /**
969
1001
  * Get value for a single flag.
970
1002
  * In WebSocket mode, returns cached values if connection is active, otherwise establishes
971
- * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
972
- * connection fails.
1003
+ * new connection and then returns the requested value. Falls back to preconfigured fallback
1004
+ * values if WebSocket connection fails.
973
1005
  * In REST mode, makes an API call for each check.
974
1006
  */
975
1007
  async checkFlag(options) {
@@ -1038,10 +1070,17 @@ var Schematic = class {
1038
1070
  await this.setContext(context);
1039
1071
  } catch (error) {
1040
1072
  console.warn(
1041
- "WebSocket connection failed, falling back to REST:",
1073
+ "WebSocket connection failed, using fallback value:",
1042
1074
  error
1043
1075
  );
1044
- return this.fallbackToRest(key, context, fallback);
1076
+ const errorResult = this.resolveFallbackCheckFlagReturn(
1077
+ key,
1078
+ fallback,
1079
+ "WebSocket connection failed",
1080
+ error instanceof Error ? error.message : String(error)
1081
+ );
1082
+ this.submitFlagCheckEvent(key, errorResult, context);
1083
+ return errorResult.value;
1045
1084
  }
1046
1085
  const contextVals = this.checks[contextStr] ?? {};
1047
1086
  const flagCheck = contextVals[key];
@@ -1052,6 +1091,13 @@ var Schematic = class {
1052
1091
  );
1053
1092
  if (typeof flagCheck !== "undefined") {
1054
1093
  this.submitFlagCheckEvent(key, flagCheck, context);
1094
+ } else {
1095
+ const fallbackResult = this.resolveFallbackCheckFlagReturn(
1096
+ key,
1097
+ fallback,
1098
+ "No flag values available"
1099
+ );
1100
+ this.submitFlagCheckEvent(key, fallbackResult, context);
1055
1101
  }
1056
1102
  return result;
1057
1103
  } catch (error) {
@@ -1144,53 +1190,6 @@ var Schematic = class {
1144
1190
  this.debug(`submitting flag check event:`, eventBody);
1145
1191
  return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
1146
1192
  }
1147
- /**
1148
- * Helper method for falling back to REST API when WebSocket connection fails
1149
- */
1150
- async fallbackToRest(key, context, fallback) {
1151
- if (this.isOffline()) {
1152
- const resolvedFallback = this.resolveFallbackValue(key, fallback);
1153
- this.debug(`fallbackToRest offline result: ${key}`, {
1154
- value: resolvedFallback,
1155
- offlineMode: true
1156
- });
1157
- return resolvedFallback;
1158
- }
1159
- try {
1160
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
1161
- const response = await fetch(requestUrl, {
1162
- method: "POST",
1163
- headers: {
1164
- ...this.additionalHeaders ?? {},
1165
- "Content-Type": "application/json;charset=UTF-8",
1166
- "X-Schematic-Api-Key": this.apiKey
1167
- },
1168
- body: JSON.stringify(context)
1169
- });
1170
- if (!response.ok) {
1171
- throw new Error("Network response was not ok");
1172
- }
1173
- const responseJson = await response.json();
1174
- const data = CheckFlagResponseFromJSON(responseJson);
1175
- this.debug(`fallbackToRest result: ${key}`, data);
1176
- const result = CheckFlagReturnFromJSON(data.data);
1177
- if (typeof result.featureUsageEvent === "string") {
1178
- this.updateFeatureUsageEventMap(result);
1179
- }
1180
- this.submitFlagCheckEvent(key, result, context);
1181
- return result.value;
1182
- } catch (error) {
1183
- console.warn("REST API call failed, using fallback value:", error);
1184
- const errorResult = this.resolveFallbackCheckFlagReturn(
1185
- key,
1186
- fallback,
1187
- "API request failed (fallback)",
1188
- error instanceof Error ? error.message : String(error)
1189
- );
1190
- this.submitFlagCheckEvent(key, errorResult, context);
1191
- return errorResult.value;
1192
- }
1193
- }
1194
1193
  /**
1195
1194
  * Make an API call to fetch all flag values for a given context.
1196
1195
  * Recommended for use in REST mode only.
@@ -1264,6 +1263,9 @@ var Schematic = class {
1264
1263
  this.setIsPending(false);
1265
1264
  return Promise.resolve();
1266
1265
  }
1266
+ if (contextString(context) === contextString(this.context) && this.conn !== null && !this.isPending) {
1267
+ return;
1268
+ }
1267
1269
  try {
1268
1270
  this.setIsPending(true);
1269
1271
  if (!this.conn) {
@@ -1303,7 +1305,10 @@ var Schematic = class {
1303
1305
  await this.wsSendMessage(socket, context);
1304
1306
  } catch (error) {
1305
1307
  console.warn("Failed to establish WebSocket connection:", error);
1306
- throw error;
1308
+ this.context = context;
1309
+ this.flushContextDependentEventQueue();
1310
+ this.setIsPending(false);
1311
+ this.attemptReconnect();
1307
1312
  }
1308
1313
  };
1309
1314
  /**
@@ -1944,7 +1949,10 @@ var Schematic = class {
1944
1949
  clearTimeout(timeoutId);
1945
1950
  }
1946
1951
  this.debug(`WebSocket connection ${connectionId} error:`, error);
1947
- reject(error);
1952
+ const wrappedError = new Error(
1953
+ "WebSocket connection failed during handshake"
1954
+ );
1955
+ reject(wrappedError);
1948
1956
  };
1949
1957
  webSocket.onclose = () => {
1950
1958
  if (timeoutId !== null) {
@@ -1991,6 +1999,20 @@ var Schematic = class {
1991
1999
  }
1992
2000
  };
1993
2001
  socket.addEventListener("message", messageHandler);
2002
+ socket.addEventListener("close", (event) => {
2003
+ if (!resolved) {
2004
+ resolved = true;
2005
+ if (event.code === 4001) {
2006
+ reject(
2007
+ new Error(
2008
+ `Authentication failed: ${event.reason !== "" ? event.reason : "Invalid API key"}`
2009
+ )
2010
+ );
2011
+ } else {
2012
+ reject(new Error("WebSocket connection closed unexpectedly"));
2013
+ }
2014
+ }
2015
+ });
1994
2016
  this.currentWebSocket = socket;
1995
2017
  const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
1996
2018
  const messagePayload = {
@@ -2027,6 +2049,7 @@ var Schematic = class {
2027
2049
  };
2028
2050
  };
2029
2051
  setIsPending = (isPending) => {
2052
+ if (this.isPending === isPending) return;
2030
2053
  this.isPending = isPending;
2031
2054
  this.isPendingListeners.forEach(
2032
2055
  (listener) => notifyPendingListener(listener, isPending)
@@ -2041,11 +2064,14 @@ var Schematic = class {
2041
2064
  return check;
2042
2065
  }
2043
2066
  if (flagKey in this.flagCheckDefaults || flagKey in this.flagValueDefaults) {
2044
- return this.resolveFallbackCheckFlagReturn(
2045
- flagKey,
2046
- void 0,
2047
- "Default value used"
2048
- );
2067
+ if (!(flagKey in this.fallbackCheckCache)) {
2068
+ this.fallbackCheckCache[flagKey] = this.resolveFallbackCheckFlagReturn(
2069
+ flagKey,
2070
+ void 0,
2071
+ "Default value used"
2072
+ );
2073
+ }
2074
+ return this.fallbackCheckCache[flagKey];
2049
2075
  }
2050
2076
  return void 0;
2051
2077
  };
@@ -2157,7 +2183,7 @@ var notifyFlagValueListener = (listener, value) => {
2157
2183
  import React, { createContext, useEffect, useMemo, useRef } from "react";
2158
2184
 
2159
2185
  // src/version.ts
2160
- var version2 = "1.2.16";
2186
+ var version2 = "1.2.21";
2161
2187
 
2162
2188
  // src/context/schematic.tsx
2163
2189
  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.16",
3
+ "version": "1.2.21",
4
4
  "main": "dist/schematic-react.cjs.js",
5
5
  "module": "dist/schematic-react.esm.js",
6
6
  "types": "dist/schematic-react.d.ts",
@@ -31,31 +31,31 @@
31
31
  "prepare": "husky"
32
32
  },
33
33
  "dependencies": {
34
- "@schematichq/schematic-js": "^1.2.16"
34
+ "@schematichq/schematic-js": "^1.2.21"
35
35
  },
36
36
  "devDependencies": {
37
- "@eslint/js": "^9.39.2",
38
- "@microsoft/api-extractor": "^7.55.0",
37
+ "@eslint/js": "^10.0.1",
38
+ "@microsoft/api-extractor": "^7.57.6",
39
39
  "@testing-library/dom": "^10.4.1",
40
40
  "@testing-library/jest-dom": "^6.9.1",
41
- "@testing-library/react": "^16.3.1",
42
- "@types/react": "^19.2.7",
43
- "@vitest/browser": "^4.0.8",
44
- "esbuild": "^0.27.1",
45
- "eslint": "^9.39.2",
41
+ "@testing-library/react": "^16.3.2",
42
+ "@types/react": "^19.2.14",
43
+ "@vitest/browser": "^4.0.18",
44
+ "esbuild": "^0.27.3",
45
+ "eslint": "^10.0.2",
46
46
  "eslint-plugin-import": "^2.32.0",
47
47
  "eslint-plugin-react": "^7.37.5",
48
48
  "eslint-plugin-react-hooks": "^7.0.1",
49
- "globals": "^16.5.0",
50
- "happy-dom": "^20.0.10",
49
+ "globals": "^17.4.0",
50
+ "happy-dom": "^20.8.3",
51
51
  "husky": "^9.1.7",
52
- "jsdom": "^27.2.0",
53
- "prettier": "^3.7.4",
54
- "react": "^19.2.3",
55
- "react-dom": "^19.2.3",
52
+ "jsdom": "^28.1.0",
53
+ "prettier": "^3.8.1",
54
+ "react": "^19.2.4",
55
+ "react-dom": "^19.2.4",
56
56
  "typescript": "^5.9.3",
57
- "typescript-eslint": "^8.47.0",
58
- "vitest": "^4.0.8"
57
+ "typescript-eslint": "^8.56.1",
58
+ "vitest": "^4.0.18"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "react": ">=18"