@schematichq/schematic-vue 1.2.16 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Schematic
3
+ Copyright (c) 2023-2026 Schematic, Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
-
package/README.md CHANGED
@@ -137,6 +137,104 @@ const {
137
137
 
138
138
  _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`._
139
139
 
140
+ ### Company plan information
141
+
142
+ To access the current company's plan and trial status, you can use the `useSchematicPlan` composable:
143
+
144
+ ```vue
145
+ <script setup lang="ts">
146
+ import { useSchematicPlan } from "@schematichq/schematic-vue";
147
+
148
+ const plan = useSchematicPlan();
149
+ </script>
150
+
151
+ <template>
152
+ <div v-if="!plan">No plan assigned</div>
153
+ <div v-else>
154
+ <p>Current plan: {{ plan.name }}</p>
155
+
156
+ <p v-if="plan.trialStatus === 'active'">
157
+ Trial ends: {{ plan.trialEndDate?.toLocaleDateString() }}
158
+ </p>
159
+
160
+ <p v-if="plan.trialStatus === 'expired'">
161
+ Your trial has ended. <a href="/upgrade">Upgrade now</a>
162
+ </p>
163
+ </div>
164
+ </template>
165
+ ```
166
+
167
+ The composable returns an object with the following properties:
168
+
169
+ | Property | Type | Description |
170
+ | --- | --- | --- |
171
+ | `id` | `string` | The plan ID |
172
+ | `name` | `string` | The plan name |
173
+ | `trialEndDate` | `Date \| undefined` | The trial end date, if the company has or had a trial |
174
+ | `trialStatus` | `"active" \| "expired" \| "converted" \| undefined` | The company's trial status: `active` if the trial is ongoing, `expired` if the trial ended without conversion, `converted` if the company converted to a paid plan, or `undefined` if the company has never trialed |
175
+
176
+ ## Fallback Behavior
177
+
178
+ 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).
179
+
180
+ ### Flag Check Fallbacks
181
+
182
+ When flag checks cannot reach Schematic, they use fallback values in the following priority order:
183
+
184
+ 1. Callsite fallback - fallback values can be provided directly in the composable options
185
+ 2. Initialization defaults - fallback values configured via `flagCheckDefaults` or `flagValueDefaults` options when initializing the plugin
186
+ 3. Default value - Returns `false` if no fallback is configured
187
+
188
+ ```vue
189
+ <script setup lang="ts">
190
+ // Provide a fallback value at the callsite
191
+ import { useSchematicFlag } from "@schematichq/schematic-vue";
192
+
193
+ const isFeatureEnabled = useSchematicFlag("feature-flag", {
194
+ fallback: true, // Used if API request fails
195
+ });
196
+ </script>
197
+
198
+ <template>
199
+ <Feature v-if="isFeatureEnabled" />
200
+ <Fallback v-else />
201
+ </template>
202
+ ```
203
+
204
+ ```typescript
205
+ // Or configure defaults at initialization
206
+ import { createApp } from "vue";
207
+ import { SchematicPlugin } from "@schematichq/schematic-vue";
208
+
209
+ const app = createApp(App);
210
+ app.use(SchematicPlugin, {
211
+ publishableKey: "your-publishable-key",
212
+ flagValueDefaults: {
213
+ "feature-flag": true, // Used if API request fails and no callsite fallback
214
+ },
215
+ flagCheckDefaults: {
216
+ "another-flag": {
217
+ flag: "another-flag",
218
+ value: true,
219
+ reason: "Default value",
220
+ },
221
+ },
222
+ });
223
+ ```
224
+
225
+ ### Event Queueing and Retry
226
+
227
+ When events (track, identify) cannot be sent due to network issues, they are automatically queued and retried:
228
+
229
+ - Events are queued in memory (up to 100 events by default, configurable via `maxEventQueueSize`)
230
+ - Failed events are retried with exponential backoff (up to 5 attempts by default, configurable via `maxEventRetries`)
231
+ - Events are automatically flushed when the network connection is restored
232
+ - Events queued when the page is hidden are sent when the page becomes visible
233
+
234
+ ### WebSocket Fallback
235
+
236
+ 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.
237
+
140
238
  ## Options API Support
141
239
 
142
240
  While the primary API uses the Composition API, you can still use these composables in the Options API:
@@ -30,7 +30,8 @@ __export(index_exports, {
30
30
  useSchematicEntitlement: () => useSchematicEntitlement,
31
31
  useSchematicEvents: () => useSchematicEvents,
32
32
  useSchematicFlag: () => useSchematicFlag,
33
- useSchematicIsPending: () => useSchematicIsPending
33
+ useSchematicIsPending: () => useSchematicIsPending,
34
+ useSchematicPlan: () => useSchematicPlan
34
35
  });
35
36
  module.exports = __toCommonJS(index_exports);
36
37
 
@@ -642,6 +643,36 @@ function v4(options, buf, offset) {
642
643
  }
643
644
  var v4_default = v4;
644
645
  var import_polyfill = __toESM(require_browser_polyfill());
646
+ function EntitlementValueTypeFromJSON(json) {
647
+ return EntitlementValueTypeFromJSONTyped(json, false);
648
+ }
649
+ function EntitlementValueTypeFromJSONTyped(json, ignoreDiscriminator) {
650
+ return json;
651
+ }
652
+ function FeatureEntitlementFromJSON(json) {
653
+ return FeatureEntitlementFromJSONTyped(json, false);
654
+ }
655
+ function FeatureEntitlementFromJSONTyped(json, ignoreDiscriminator) {
656
+ if (json == null) {
657
+ return json;
658
+ }
659
+ return {
660
+ allocation: json["allocation"] == null ? void 0 : json["allocation"],
661
+ creditId: json["credit_id"] == null ? void 0 : json["credit_id"],
662
+ creditRemaining: json["credit_remaining"] == null ? void 0 : json["credit_remaining"],
663
+ creditTotal: json["credit_total"] == null ? void 0 : json["credit_total"],
664
+ creditUsed: json["credit_used"] == null ? void 0 : json["credit_used"],
665
+ eventName: json["event_name"] == null ? void 0 : json["event_name"],
666
+ featureId: json["feature_id"],
667
+ featureKey: json["feature_key"],
668
+ metricPeriod: json["metric_period"] == null ? void 0 : json["metric_period"],
669
+ metricResetAt: json["metric_reset_at"] == null ? void 0 : new Date(json["metric_reset_at"]),
670
+ monthReset: json["month_reset"] == null ? void 0 : json["month_reset"],
671
+ softLimit: json["soft_limit"] == null ? void 0 : json["soft_limit"],
672
+ usage: json["usage"] == null ? void 0 : json["usage"],
673
+ valueType: EntitlementValueTypeFromJSON(json["value_type"])
674
+ };
675
+ }
645
676
  function CheckFlagResponseDataFromJSON(json) {
646
677
  return CheckFlagResponseDataFromJSONTyped(json, false);
647
678
  }
@@ -651,6 +682,7 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
651
682
  }
652
683
  return {
653
684
  companyId: json["company_id"] == null ? void 0 : json["company_id"],
685
+ entitlement: json["entitlement"] == null ? void 0 : FeatureEntitlementFromJSON(json["entitlement"]),
654
686
  error: json["error"] == null ? void 0 : json["error"],
655
687
  featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
656
688
  featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
@@ -666,26 +698,6 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
666
698
  value: json["value"]
667
699
  };
668
700
  }
669
- function EventBodyFlagCheckToJSON(json) {
670
- return EventBodyFlagCheckToJSONTyped(json, false);
671
- }
672
- function EventBodyFlagCheckToJSONTyped(value, ignoreDiscriminator = false) {
673
- if (value == null) {
674
- return value;
675
- }
676
- return {
677
- company_id: value["companyId"],
678
- error: value["error"],
679
- flag_id: value["flagId"],
680
- flag_key: value["flagKey"],
681
- reason: value["reason"],
682
- req_company: value["reqCompany"],
683
- req_user: value["reqUser"],
684
- rule_id: value["ruleId"],
685
- user_id: value["userId"],
686
- value: value["value"]
687
- };
688
- }
689
701
  function CheckFlagResponseFromJSON(json) {
690
702
  return CheckFlagResponseFromJSONTyped(json, false);
691
703
  }
@@ -698,6 +710,26 @@ function CheckFlagResponseFromJSONTyped(json, ignoreDiscriminator) {
698
710
  params: json["params"]
699
711
  };
700
712
  }
713
+ function TrialStatusFromJSON(json) {
714
+ return TrialStatusFromJSONTyped(json, false);
715
+ }
716
+ function TrialStatusFromJSONTyped(json, ignoreDiscriminator) {
717
+ return json;
718
+ }
719
+ function DatastreamCompanyPlanFromJSON(json) {
720
+ return DatastreamCompanyPlanFromJSONTyped(json, false);
721
+ }
722
+ function DatastreamCompanyPlanFromJSONTyped(json, ignoreDiscriminator) {
723
+ if (json == null) {
724
+ return json;
725
+ }
726
+ return {
727
+ id: json["id"],
728
+ name: json["name"],
729
+ trialEndDate: json["trial_end_date"] == null ? void 0 : new Date(json["trial_end_date"]),
730
+ trialStatus: json["trial_status"] == null ? void 0 : TrialStatusFromJSON(json["trial_status"])
731
+ };
732
+ }
701
733
  function CheckFlagsResponseDataFromJSON(json) {
702
734
  return CheckFlagsResponseDataFromJSONTyped(json, false);
703
735
  }
@@ -706,7 +738,8 @@ function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
706
738
  return json;
707
739
  }
708
740
  return {
709
- flags: json["flags"].map(CheckFlagResponseDataFromJSON)
741
+ flags: json["flags"].map(CheckFlagResponseDataFromJSON),
742
+ plan: json["plan"] == null ? void 0 : DatastreamCompanyPlanFromJSON(json["plan"])
710
743
  };
711
744
  }
712
745
  function CheckFlagsResponseFromJSON(json) {
@@ -721,6 +754,26 @@ function CheckFlagsResponseFromJSONTyped(json, ignoreDiscriminator) {
721
754
  params: json["params"]
722
755
  };
723
756
  }
757
+ function EventBodyFlagCheckToJSON(json) {
758
+ return EventBodyFlagCheckToJSONTyped(json, false);
759
+ }
760
+ function EventBodyFlagCheckToJSONTyped(value, ignoreDiscriminator = false) {
761
+ if (value == null) {
762
+ return value;
763
+ }
764
+ return {
765
+ company_id: value["companyId"],
766
+ error: value["error"],
767
+ flag_id: value["flagId"],
768
+ flag_key: value["flagKey"],
769
+ reason: value["reason"],
770
+ req_company: value["reqCompany"],
771
+ req_user: value["reqUser"],
772
+ rule_id: value["ruleId"],
773
+ user_id: value["userId"],
774
+ value: value["value"]
775
+ };
776
+ }
724
777
  var RuleType = /* @__PURE__ */ ((RuleType2) => {
725
778
  RuleType2["GLOBAL_OVERRIDE"] = "global_override";
726
779
  RuleType2["COMPANY_OVERRIDE"] = "company_override";
@@ -741,6 +794,7 @@ var UsagePeriod = /* @__PURE__ */ ((UsagePeriod2) => {
741
794
  var CheckFlagReturnFromJSON = (json) => {
742
795
  const {
743
796
  companyId,
797
+ entitlement,
744
798
  error,
745
799
  featureAllocation,
746
800
  featureUsage,
@@ -758,24 +812,40 @@ var CheckFlagReturnFromJSON = (json) => {
758
812
  const featureUsageExceeded = !value && // if flag is not false, then we haven't exceeded usage
759
813
  (ruleType == "company_override_usage_exceeded" || // if the rule type is one of these, then we have exceeded usage
760
814
  ruleType == "plan_entitlement_usage_exceeded");
815
+ const resolvedAllocation = entitlement?.allocation ?? featureAllocation;
816
+ const resolvedUsage = entitlement?.usage ?? featureUsage;
817
+ const resolvedEvent = entitlement?.eventName ?? featureUsageEvent;
818
+ const resolvedPeriod = entitlement?.metricPeriod ?? featureUsagePeriod;
819
+ const resolvedResetAt = entitlement?.metricResetAt ?? featureUsageResetAt;
761
820
  return {
762
821
  featureUsageExceeded,
763
822
  companyId: companyId == null ? void 0 : companyId,
823
+ creditRemaining: entitlement?.creditRemaining == null ? void 0 : entitlement.creditRemaining,
764
824
  error: error == null ? void 0 : error,
765
- featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
766
- featureUsage: featureUsage == null ? void 0 : featureUsage,
767
- featureUsageEvent: featureUsageEvent === null ? void 0 : featureUsageEvent,
768
- featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
769
- featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
825
+ featureAllocation: resolvedAllocation == null ? void 0 : resolvedAllocation,
826
+ featureUsage: resolvedUsage == null ? void 0 : resolvedUsage,
827
+ featureUsageEvent: resolvedEvent == null ? void 0 : resolvedEvent,
828
+ featureUsagePeriod: resolvedPeriod == null ? void 0 : resolvedPeriod,
829
+ featureUsageResetAt: resolvedResetAt == null ? void 0 : resolvedResetAt,
770
830
  flag,
771
831
  flagId: flagId == null ? void 0 : flagId,
772
832
  reason,
773
833
  ruleId: ruleId == null ? void 0 : ruleId,
774
834
  ruleType: ruleType == null ? void 0 : ruleType,
835
+ softLimit: entitlement?.softLimit == null ? void 0 : entitlement.softLimit,
775
836
  userId: userId == null ? void 0 : userId,
776
837
  value
777
838
  };
778
839
  };
840
+ var CheckPlanReturnFromJSON = (json) => {
841
+ const { id, name, trialEndDate, trialStatus } = DatastreamCompanyPlanFromJSON(json);
842
+ return {
843
+ id,
844
+ name,
845
+ trialEndDate: trialEndDate == null ? void 0 : trialEndDate,
846
+ trialStatus: trialStatus == null ? void 0 : trialStatus
847
+ };
848
+ };
779
849
  function contextString(context) {
780
850
  const sortedContext = Object.keys(context).reduce((acc, key) => {
781
851
  const sortedKeys = Object.keys(
@@ -790,7 +860,7 @@ function contextString(context) {
790
860
  }, {});
791
861
  return JSON.stringify(sortedContext);
792
862
  }
793
- var version = "1.2.16";
863
+ var version = "1.4.0";
794
864
  var anonymousIdKey = "schematicId";
795
865
  var Schematic = class {
796
866
  additionalHeaders = {};
@@ -807,10 +877,12 @@ var Schematic = class {
807
877
  flagValueListeners = {};
808
878
  isPending = true;
809
879
  isPendingListeners = /* @__PURE__ */ new Set();
880
+ planListeners = /* @__PURE__ */ new Set();
810
881
  storage;
811
882
  useWebSocket = false;
812
883
  checks = {};
813
884
  featureUsageEventMap = {};
885
+ planChecks = {};
814
886
  webSocketUrl = "wss://api.schematichq.com";
815
887
  webSocketConnectionTimeout = 1e4;
816
888
  webSocketReconnect = true;
@@ -836,6 +908,7 @@ var Schematic = class {
836
908
  retryTimer = null;
837
909
  flagValueDefaults = {};
838
910
  flagCheckDefaults = {};
911
+ fallbackCheckCache = {};
839
912
  constructor(apiKey, options) {
840
913
  this.apiKey = apiKey;
841
914
  this.eventQueue = [];
@@ -1004,8 +1077,8 @@ var Schematic = class {
1004
1077
  /**
1005
1078
  * Get value for a single flag.
1006
1079
  * In WebSocket mode, returns cached values if connection is active, otherwise establishes
1007
- * new connection and then returns the requestedvalue. Falls back to REST API if WebSocket
1008
- * connection fails.
1080
+ * new connection and then returns the requested value. Falls back to preconfigured fallback
1081
+ * values if WebSocket connection fails.
1009
1082
  * In REST mode, makes an API call for each check.
1010
1083
  */
1011
1084
  async checkFlag(options) {
@@ -1074,10 +1147,17 @@ var Schematic = class {
1074
1147
  await this.setContext(context);
1075
1148
  } catch (error) {
1076
1149
  console.warn(
1077
- "WebSocket connection failed, falling back to REST:",
1150
+ "WebSocket connection failed, using fallback value:",
1078
1151
  error
1079
1152
  );
1080
- return this.fallbackToRest(key, context, fallback);
1153
+ const errorResult = this.resolveFallbackCheckFlagReturn(
1154
+ key,
1155
+ fallback,
1156
+ "WebSocket connection failed",
1157
+ error instanceof Error ? error.message : String(error)
1158
+ );
1159
+ this.submitFlagCheckEvent(key, errorResult, context);
1160
+ return errorResult.value;
1081
1161
  }
1082
1162
  const contextVals = this.checks[contextStr] ?? {};
1083
1163
  const flagCheck = contextVals[key];
@@ -1088,6 +1168,13 @@ var Schematic = class {
1088
1168
  );
1089
1169
  if (typeof flagCheck !== "undefined") {
1090
1170
  this.submitFlagCheckEvent(key, flagCheck, context);
1171
+ } else {
1172
+ const fallbackResult = this.resolveFallbackCheckFlagReturn(
1173
+ key,
1174
+ fallback,
1175
+ "No flag values available"
1176
+ );
1177
+ this.submitFlagCheckEvent(key, fallbackResult, context);
1091
1178
  }
1092
1179
  return result;
1093
1180
  } catch (error) {
@@ -1128,28 +1215,32 @@ var Schematic = class {
1128
1215
  this.checks[contextStr] = {};
1129
1216
  }
1130
1217
  this.checks[contextStr][flagCheck.flag] = flagCheck;
1131
- this.debug(`WebSocket flag update:`, {
1132
- flag: flagCheck.flag,
1133
- value: flagCheck.value,
1134
- flagCheck
1135
- });
1136
1218
  if (typeof flagCheck.featureUsageEvent === "string") {
1137
1219
  this.updateFeatureUsageEventMap(flagCheck);
1138
1220
  }
1139
1221
  if ((this.flagCheckListeners[flag.flag]?.size ?? 0) > 0 || (this.flagValueListeners[flag.flag]?.size ?? 0) > 0) {
1140
1222
  this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1141
1223
  }
1142
- this.debug(`About to notify listeners for flag ${flag.flag}`, {
1143
- flag: flag.flag,
1144
- value: flagCheck.value
1145
- });
1224
+ this.debug(
1225
+ `WebSocket flag update received. Notifying listeners for ${flag.flag}`,
1226
+ {
1227
+ flag: flag.flag,
1228
+ value: flagCheck.value,
1229
+ flagCheck
1230
+ }
1231
+ );
1146
1232
  this.notifyFlagCheckListeners(flag.flag, flagCheck);
1147
1233
  this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1148
- this.debug(`Finished notifying listeners for flag ${flag.flag}`, {
1149
- flag: flag.flag,
1150
- value: flagCheck.value
1151
- });
1152
1234
  });
1235
+ if (message.plan !== void 0 && message.plan !== null) {
1236
+ const plan = CheckPlanReturnFromJSON(message.plan);
1237
+ const contextStr = contextString(context);
1238
+ this.planChecks[contextStr] = plan;
1239
+ this.debug(`WebSocket plan update received. Notifying listeners`, {
1240
+ plan
1241
+ });
1242
+ this.notifyPlanListeners(plan);
1243
+ }
1153
1244
  this.flushContextDependentEventQueue();
1154
1245
  this.setIsPending(false);
1155
1246
  };
@@ -1180,53 +1271,6 @@ var Schematic = class {
1180
1271
  this.debug(`submitting flag check event:`, eventBody);
1181
1272
  return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
1182
1273
  }
1183
- /**
1184
- * Helper method for falling back to REST API when WebSocket connection fails
1185
- */
1186
- async fallbackToRest(key, context, fallback) {
1187
- if (this.isOffline()) {
1188
- const resolvedFallback = this.resolveFallbackValue(key, fallback);
1189
- this.debug(`fallbackToRest offline result: ${key}`, {
1190
- value: resolvedFallback,
1191
- offlineMode: true
1192
- });
1193
- return resolvedFallback;
1194
- }
1195
- try {
1196
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
1197
- const response = await fetch(requestUrl, {
1198
- method: "POST",
1199
- headers: {
1200
- ...this.additionalHeaders ?? {},
1201
- "Content-Type": "application/json;charset=UTF-8",
1202
- "X-Schematic-Api-Key": this.apiKey
1203
- },
1204
- body: JSON.stringify(context)
1205
- });
1206
- if (!response.ok) {
1207
- throw new Error("Network response was not ok");
1208
- }
1209
- const responseJson = await response.json();
1210
- const data = CheckFlagResponseFromJSON(responseJson);
1211
- this.debug(`fallbackToRest result: ${key}`, data);
1212
- const result = CheckFlagReturnFromJSON(data.data);
1213
- if (typeof result.featureUsageEvent === "string") {
1214
- this.updateFeatureUsageEventMap(result);
1215
- }
1216
- this.submitFlagCheckEvent(key, result, context);
1217
- return result.value;
1218
- } catch (error) {
1219
- console.warn("REST API call failed, using fallback value:", error);
1220
- const errorResult = this.resolveFallbackCheckFlagReturn(
1221
- key,
1222
- fallback,
1223
- "API request failed (fallback)",
1224
- error instanceof Error ? error.message : String(error)
1225
- );
1226
- this.submitFlagCheckEvent(key, errorResult, context);
1227
- return errorResult.value;
1228
- }
1229
- }
1230
1274
  /**
1231
1275
  * Make an API call to fetch all flag values for a given context.
1232
1276
  * Recommended for use in REST mode only.
@@ -1300,6 +1344,9 @@ var Schematic = class {
1300
1344
  this.setIsPending(false);
1301
1345
  return Promise.resolve();
1302
1346
  }
1347
+ if (contextString(context) === contextString(this.context) && this.conn !== null && !this.isPending) {
1348
+ return;
1349
+ }
1303
1350
  try {
1304
1351
  this.setIsPending(true);
1305
1352
  if (!this.conn) {
@@ -1339,7 +1386,10 @@ var Schematic = class {
1339
1386
  await this.wsSendMessage(socket, context);
1340
1387
  } catch (error) {
1341
1388
  console.warn("Failed to establish WebSocket connection:", error);
1342
- throw error;
1389
+ this.context = context;
1390
+ this.flushContextDependentEventQueue();
1391
+ this.setIsPending(false);
1392
+ this.attemptReconnect();
1343
1393
  }
1344
1394
  };
1345
1395
  /**
@@ -1980,7 +2030,10 @@ var Schematic = class {
1980
2030
  clearTimeout(timeoutId);
1981
2031
  }
1982
2032
  this.debug(`WebSocket connection ${connectionId} error:`, error);
1983
- reject(error);
2033
+ const wrappedError = new Error(
2034
+ "WebSocket connection failed during handshake"
2035
+ );
2036
+ reject(wrappedError);
1984
2037
  };
1985
2038
  webSocket.onclose = () => {
1986
2039
  if (timeoutId !== null) {
@@ -2027,6 +2080,20 @@ var Schematic = class {
2027
2080
  }
2028
2081
  };
2029
2082
  socket.addEventListener("message", messageHandler);
2083
+ socket.addEventListener("close", (event) => {
2084
+ if (!resolved) {
2085
+ resolved = true;
2086
+ if (event.code === 4001) {
2087
+ reject(
2088
+ new Error(
2089
+ `Authentication failed: ${event.reason !== "" ? event.reason : "Invalid API key"}`
2090
+ )
2091
+ );
2092
+ } else {
2093
+ reject(new Error("WebSocket connection closed unexpectedly"));
2094
+ }
2095
+ }
2096
+ });
2030
2097
  this.currentWebSocket = socket;
2031
2098
  const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
2032
2099
  const messagePayload = {
@@ -2063,11 +2130,17 @@ var Schematic = class {
2063
2130
  };
2064
2131
  };
2065
2132
  setIsPending = (isPending) => {
2133
+ if (this.isPending === isPending) return;
2066
2134
  this.isPending = isPending;
2067
2135
  this.isPendingListeners.forEach(
2068
2136
  (listener) => notifyPendingListener(listener, isPending)
2069
2137
  );
2070
2138
  };
2139
+ getPlan = () => {
2140
+ const contextStr = contextString(this.context);
2141
+ const plan = this.planChecks[contextStr];
2142
+ return plan;
2143
+ };
2071
2144
  // flag checks state
2072
2145
  getFlagCheck = (flagKey) => {
2073
2146
  const contextStr = contextString(this.context);
@@ -2077,11 +2150,14 @@ var Schematic = class {
2077
2150
  return check;
2078
2151
  }
2079
2152
  if (flagKey in this.flagCheckDefaults || flagKey in this.flagValueDefaults) {
2080
- return this.resolveFallbackCheckFlagReturn(
2081
- flagKey,
2082
- void 0,
2083
- "Default value used"
2084
- );
2153
+ if (!(flagKey in this.fallbackCheckCache)) {
2154
+ this.fallbackCheckCache[flagKey] = this.resolveFallbackCheckFlagReturn(
2155
+ flagKey,
2156
+ void 0,
2157
+ "Default value used"
2158
+ );
2159
+ }
2160
+ return this.fallbackCheckCache[flagKey];
2085
2161
  }
2086
2162
  return void 0;
2087
2163
  };
@@ -2118,6 +2194,12 @@ var Schematic = class {
2118
2194
  this.flagCheckListeners[flagKey].delete(listener);
2119
2195
  };
2120
2196
  };
2197
+ addPlanListener = (listener) => {
2198
+ this.planListeners.add(listener);
2199
+ return () => {
2200
+ this.planListeners.delete(listener);
2201
+ };
2202
+ };
2121
2203
  notifyFlagCheckListeners = (flagKey, check) => {
2122
2204
  const listeners = this.flagCheckListeners?.[flagKey] ?? [];
2123
2205
  if (listeners.size > 0) {
@@ -2166,6 +2248,21 @@ var Schematic = class {
2166
2248
  });
2167
2249
  });
2168
2250
  };
2251
+ notifyPlanListeners = (value) => {
2252
+ const listeners = this.planListeners ?? [];
2253
+ if (listeners.size > 0) {
2254
+ this.debug(`Notifying ${listeners.size} plan listeners`, { value });
2255
+ }
2256
+ listeners.forEach((listener, index) => {
2257
+ this.debug(`Calling listener ${index} for plan`, {
2258
+ value
2259
+ });
2260
+ notifyPlanListener(listener, value);
2261
+ this.debug(`Listener ${index} for plan completed`, {
2262
+ value
2263
+ });
2264
+ });
2265
+ };
2169
2266
  };
2170
2267
  var notifyPendingListener = (listener, value) => {
2171
2268
  if (listener.length > 0) {
@@ -2188,12 +2285,19 @@ var notifyFlagValueListener = (listener, value) => {
2188
2285
  listener();
2189
2286
  }
2190
2287
  };
2288
+ var notifyPlanListener = (listener, value) => {
2289
+ if (listener.length > 0) {
2290
+ listener(value);
2291
+ } else {
2292
+ listener();
2293
+ }
2294
+ };
2191
2295
 
2192
2296
  // src/context/schematic.ts
2193
2297
  var import_vue = require("vue");
2194
2298
 
2195
2299
  // src/version.ts
2196
- var version2 = "1.2.16";
2300
+ var version2 = "1.4.0";
2197
2301
 
2198
2302
  // src/context/schematic.ts
2199
2303
  var SchematicSymbol = Symbol("schematic");
@@ -2309,6 +2413,23 @@ var useSchematicEntitlement = (key, opts) => {
2309
2413
  userId: (0, import_vue2.computed)(() => flagCheck.value.userId)
2310
2414
  };
2311
2415
  };
2416
+ var useSchematicPlan = (opts) => {
2417
+ const client = useSchematicClient(opts);
2418
+ const fallback = opts?.fallback;
2419
+ const plan = (0, import_vue2.ref)(
2420
+ client.getPlan() ?? fallback
2421
+ );
2422
+ let unsubscribe = null;
2423
+ (0, import_vue2.onMounted)(() => {
2424
+ unsubscribe = client.addPlanListener((value) => {
2425
+ plan.value = value;
2426
+ });
2427
+ });
2428
+ (0, import_vue2.onScopeDispose)(() => {
2429
+ unsubscribe?.();
2430
+ });
2431
+ return plan;
2432
+ };
2312
2433
  var useSchematicIsPending = (opts) => {
2313
2434
  const client = useSchematicClient(opts);
2314
2435
  const isPending = (0, import_vue2.ref)(client.getIsPending());
@@ -1,5 +1,6 @@
1
1
  import { App } from 'vue';
2
2
  import { CheckFlagReturn } from '@schematichq/schematic-js';
3
+ import { CheckPlanReturn } from '@schematichq/schematic-js';
3
4
  import { ComputedRef } from 'vue';
4
5
  import { Event as Event_2 } from '@schematichq/schematic-js';
5
6
  import { EventBody } from '@schematichq/schematic-js';
@@ -21,6 +22,8 @@ declare type BaseSchematicPluginOptions = Omit<SchematicJS.SchematicOptions, "cl
21
22
 
22
23
  export { CheckFlagReturn }
23
24
 
25
+ export { CheckPlanReturn }
26
+
24
27
  export { Event_2 as Event }
25
28
 
26
29
  export { EventBody }
@@ -142,6 +145,7 @@ export declare const useSchematicEntitlement: (key: string, opts?: UseSchematicF
142
145
  check: ComputedRef< {
143
146
  featureUsageExceeded?: boolean | undefined;
144
147
  companyId?: string | undefined;
148
+ creditRemaining?: number | undefined;
145
149
  error?: string | undefined;
146
150
  featureAllocation?: number | undefined;
147
151
  featureUsage?: number | undefined;
@@ -153,6 +157,7 @@ export declare const useSchematicEntitlement: (key: string, opts?: UseSchematicF
153
157
  reason: string;
154
158
  ruleId?: string | undefined;
155
159
  ruleType?: SchematicJS.RuleType | undefined;
160
+ softLimit?: number | undefined;
156
161
  userId?: string | undefined;
157
162
  value: boolean;
158
163
  }>;
@@ -236,4 +241,25 @@ export declare type UseSchematicFlagOpts = SchematicComposableOpts & {
236
241
  */
237
242
  export declare const useSchematicIsPending: (opts?: SchematicComposableOpts) => Ref<boolean>;
238
243
 
244
+ /**
245
+ * Get the current plan for the identified company
246
+ * Returns a reactive ref that updates when the plan changes
247
+ *
248
+ * @param opts - Optional configuration including fallback value
249
+ * @returns Ref<SchematicJS.CheckPlanReturn | undefined> - Reactive plan data
250
+ *
251
+ * @example
252
+ * ```typescript
253
+ * const plan = useSchematicPlan()
254
+ *
255
+ * // In template
256
+ * <div v-if="plan">Current plan: {{ plan.name }}</div>
257
+ * ```
258
+ */
259
+ export declare const useSchematicPlan: (opts?: UseSchematicPlanOpts) => Ref<SchematicJS.CheckPlanReturn | undefined>;
260
+
261
+ export declare type UseSchematicPlanOpts = SchematicComposableOpts & {
262
+ fallback?: SchematicJS.CheckPlanReturn;
263
+ };
264
+
239
265
  export { }
@@ -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"],
@@ -630,26 +661,6 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
630
661
  value: json["value"]
631
662
  };
632
663
  }
633
- function EventBodyFlagCheckToJSON(json) {
634
- return EventBodyFlagCheckToJSONTyped(json, false);
635
- }
636
- function EventBodyFlagCheckToJSONTyped(value, ignoreDiscriminator = false) {
637
- if (value == null) {
638
- return value;
639
- }
640
- return {
641
- company_id: value["companyId"],
642
- error: value["error"],
643
- flag_id: value["flagId"],
644
- flag_key: value["flagKey"],
645
- reason: value["reason"],
646
- req_company: value["reqCompany"],
647
- req_user: value["reqUser"],
648
- rule_id: value["ruleId"],
649
- user_id: value["userId"],
650
- value: value["value"]
651
- };
652
- }
653
664
  function CheckFlagResponseFromJSON(json) {
654
665
  return CheckFlagResponseFromJSONTyped(json, false);
655
666
  }
@@ -662,6 +673,26 @@ function CheckFlagResponseFromJSONTyped(json, ignoreDiscriminator) {
662
673
  params: json["params"]
663
674
  };
664
675
  }
676
+ function TrialStatusFromJSON(json) {
677
+ return TrialStatusFromJSONTyped(json, false);
678
+ }
679
+ function TrialStatusFromJSONTyped(json, ignoreDiscriminator) {
680
+ return json;
681
+ }
682
+ function DatastreamCompanyPlanFromJSON(json) {
683
+ return DatastreamCompanyPlanFromJSONTyped(json, false);
684
+ }
685
+ function DatastreamCompanyPlanFromJSONTyped(json, ignoreDiscriminator) {
686
+ if (json == null) {
687
+ return json;
688
+ }
689
+ return {
690
+ id: json["id"],
691
+ name: json["name"],
692
+ trialEndDate: json["trial_end_date"] == null ? void 0 : new Date(json["trial_end_date"]),
693
+ trialStatus: json["trial_status"] == null ? void 0 : TrialStatusFromJSON(json["trial_status"])
694
+ };
695
+ }
665
696
  function CheckFlagsResponseDataFromJSON(json) {
666
697
  return CheckFlagsResponseDataFromJSONTyped(json, false);
667
698
  }
@@ -670,7 +701,8 @@ function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
670
701
  return json;
671
702
  }
672
703
  return {
673
- flags: json["flags"].map(CheckFlagResponseDataFromJSON)
704
+ flags: json["flags"].map(CheckFlagResponseDataFromJSON),
705
+ plan: json["plan"] == null ? void 0 : DatastreamCompanyPlanFromJSON(json["plan"])
674
706
  };
675
707
  }
676
708
  function CheckFlagsResponseFromJSON(json) {
@@ -685,6 +717,26 @@ function CheckFlagsResponseFromJSONTyped(json, ignoreDiscriminator) {
685
717
  params: json["params"]
686
718
  };
687
719
  }
720
+ function EventBodyFlagCheckToJSON(json) {
721
+ return EventBodyFlagCheckToJSONTyped(json, false);
722
+ }
723
+ function EventBodyFlagCheckToJSONTyped(value, ignoreDiscriminator = false) {
724
+ if (value == null) {
725
+ return value;
726
+ }
727
+ return {
728
+ company_id: value["companyId"],
729
+ error: value["error"],
730
+ flag_id: value["flagId"],
731
+ flag_key: value["flagKey"],
732
+ reason: value["reason"],
733
+ req_company: value["reqCompany"],
734
+ req_user: value["reqUser"],
735
+ rule_id: value["ruleId"],
736
+ user_id: value["userId"],
737
+ value: value["value"]
738
+ };
739
+ }
688
740
  var RuleType = /* @__PURE__ */ ((RuleType2) => {
689
741
  RuleType2["GLOBAL_OVERRIDE"] = "global_override";
690
742
  RuleType2["COMPANY_OVERRIDE"] = "company_override";
@@ -705,6 +757,7 @@ var UsagePeriod = /* @__PURE__ */ ((UsagePeriod2) => {
705
757
  var CheckFlagReturnFromJSON = (json) => {
706
758
  const {
707
759
  companyId,
760
+ entitlement,
708
761
  error,
709
762
  featureAllocation,
710
763
  featureUsage,
@@ -722,24 +775,40 @@ var CheckFlagReturnFromJSON = (json) => {
722
775
  const featureUsageExceeded = !value && // if flag is not false, then we haven't exceeded usage
723
776
  (ruleType == "company_override_usage_exceeded" || // if the rule type is one of these, then we have exceeded usage
724
777
  ruleType == "plan_entitlement_usage_exceeded");
778
+ const resolvedAllocation = entitlement?.allocation ?? featureAllocation;
779
+ const resolvedUsage = entitlement?.usage ?? featureUsage;
780
+ const resolvedEvent = entitlement?.eventName ?? featureUsageEvent;
781
+ const resolvedPeriod = entitlement?.metricPeriod ?? featureUsagePeriod;
782
+ const resolvedResetAt = entitlement?.metricResetAt ?? featureUsageResetAt;
725
783
  return {
726
784
  featureUsageExceeded,
727
785
  companyId: companyId == null ? void 0 : companyId,
786
+ creditRemaining: entitlement?.creditRemaining == null ? void 0 : entitlement.creditRemaining,
728
787
  error: error == null ? void 0 : error,
729
- featureAllocation: featureAllocation == null ? void 0 : featureAllocation,
730
- featureUsage: featureUsage == null ? void 0 : featureUsage,
731
- featureUsageEvent: featureUsageEvent === null ? void 0 : featureUsageEvent,
732
- featureUsagePeriod: featureUsagePeriod == null ? void 0 : featureUsagePeriod,
733
- featureUsageResetAt: featureUsageResetAt == null ? void 0 : featureUsageResetAt,
788
+ featureAllocation: resolvedAllocation == null ? void 0 : resolvedAllocation,
789
+ featureUsage: resolvedUsage == null ? void 0 : resolvedUsage,
790
+ featureUsageEvent: resolvedEvent == null ? void 0 : resolvedEvent,
791
+ featureUsagePeriod: resolvedPeriod == null ? void 0 : resolvedPeriod,
792
+ featureUsageResetAt: resolvedResetAt == null ? void 0 : resolvedResetAt,
734
793
  flag,
735
794
  flagId: flagId == null ? void 0 : flagId,
736
795
  reason,
737
796
  ruleId: ruleId == null ? void 0 : ruleId,
738
797
  ruleType: ruleType == null ? void 0 : ruleType,
798
+ softLimit: entitlement?.softLimit == null ? void 0 : entitlement.softLimit,
739
799
  userId: userId == null ? void 0 : userId,
740
800
  value
741
801
  };
742
802
  };
803
+ var CheckPlanReturnFromJSON = (json) => {
804
+ const { id, name, trialEndDate, trialStatus } = DatastreamCompanyPlanFromJSON(json);
805
+ return {
806
+ id,
807
+ name,
808
+ trialEndDate: trialEndDate == null ? void 0 : trialEndDate,
809
+ trialStatus: trialStatus == null ? void 0 : trialStatus
810
+ };
811
+ };
743
812
  function contextString(context) {
744
813
  const sortedContext = Object.keys(context).reduce((acc, key) => {
745
814
  const sortedKeys = Object.keys(
@@ -754,7 +823,7 @@ function contextString(context) {
754
823
  }, {});
755
824
  return JSON.stringify(sortedContext);
756
825
  }
757
- var version = "1.2.16";
826
+ var version = "1.4.0";
758
827
  var anonymousIdKey = "schematicId";
759
828
  var Schematic = class {
760
829
  additionalHeaders = {};
@@ -771,10 +840,12 @@ var Schematic = class {
771
840
  flagValueListeners = {};
772
841
  isPending = true;
773
842
  isPendingListeners = /* @__PURE__ */ new Set();
843
+ planListeners = /* @__PURE__ */ new Set();
774
844
  storage;
775
845
  useWebSocket = false;
776
846
  checks = {};
777
847
  featureUsageEventMap = {};
848
+ planChecks = {};
778
849
  webSocketUrl = "wss://api.schematichq.com";
779
850
  webSocketConnectionTimeout = 1e4;
780
851
  webSocketReconnect = true;
@@ -800,6 +871,7 @@ var Schematic = class {
800
871
  retryTimer = null;
801
872
  flagValueDefaults = {};
802
873
  flagCheckDefaults = {};
874
+ fallbackCheckCache = {};
803
875
  constructor(apiKey, options) {
804
876
  this.apiKey = apiKey;
805
877
  this.eventQueue = [];
@@ -968,8 +1040,8 @@ var Schematic = class {
968
1040
  /**
969
1041
  * Get value for a single flag.
970
1042
  * 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.
1043
+ * new connection and then returns the requested value. Falls back to preconfigured fallback
1044
+ * values if WebSocket connection fails.
973
1045
  * In REST mode, makes an API call for each check.
974
1046
  */
975
1047
  async checkFlag(options) {
@@ -1038,10 +1110,17 @@ var Schematic = class {
1038
1110
  await this.setContext(context);
1039
1111
  } catch (error) {
1040
1112
  console.warn(
1041
- "WebSocket connection failed, falling back to REST:",
1113
+ "WebSocket connection failed, using fallback value:",
1042
1114
  error
1043
1115
  );
1044
- return this.fallbackToRest(key, context, fallback);
1116
+ const errorResult = this.resolveFallbackCheckFlagReturn(
1117
+ key,
1118
+ fallback,
1119
+ "WebSocket connection failed",
1120
+ error instanceof Error ? error.message : String(error)
1121
+ );
1122
+ this.submitFlagCheckEvent(key, errorResult, context);
1123
+ return errorResult.value;
1045
1124
  }
1046
1125
  const contextVals = this.checks[contextStr] ?? {};
1047
1126
  const flagCheck = contextVals[key];
@@ -1052,6 +1131,13 @@ var Schematic = class {
1052
1131
  );
1053
1132
  if (typeof flagCheck !== "undefined") {
1054
1133
  this.submitFlagCheckEvent(key, flagCheck, context);
1134
+ } else {
1135
+ const fallbackResult = this.resolveFallbackCheckFlagReturn(
1136
+ key,
1137
+ fallback,
1138
+ "No flag values available"
1139
+ );
1140
+ this.submitFlagCheckEvent(key, fallbackResult, context);
1055
1141
  }
1056
1142
  return result;
1057
1143
  } catch (error) {
@@ -1092,28 +1178,32 @@ var Schematic = class {
1092
1178
  this.checks[contextStr] = {};
1093
1179
  }
1094
1180
  this.checks[contextStr][flagCheck.flag] = flagCheck;
1095
- this.debug(`WebSocket flag update:`, {
1096
- flag: flagCheck.flag,
1097
- value: flagCheck.value,
1098
- flagCheck
1099
- });
1100
1181
  if (typeof flagCheck.featureUsageEvent === "string") {
1101
1182
  this.updateFeatureUsageEventMap(flagCheck);
1102
1183
  }
1103
1184
  if ((this.flagCheckListeners[flag.flag]?.size ?? 0) > 0 || (this.flagValueListeners[flag.flag]?.size ?? 0) > 0) {
1104
1185
  this.submitFlagCheckEvent(flagCheck.flag, flagCheck, context);
1105
1186
  }
1106
- this.debug(`About to notify listeners for flag ${flag.flag}`, {
1107
- flag: flag.flag,
1108
- value: flagCheck.value
1109
- });
1187
+ this.debug(
1188
+ `WebSocket flag update received. Notifying listeners for ${flag.flag}`,
1189
+ {
1190
+ flag: flag.flag,
1191
+ value: flagCheck.value,
1192
+ flagCheck
1193
+ }
1194
+ );
1110
1195
  this.notifyFlagCheckListeners(flag.flag, flagCheck);
1111
1196
  this.notifyFlagValueListeners(flag.flag, flagCheck.value);
1112
- this.debug(`Finished notifying listeners for flag ${flag.flag}`, {
1113
- flag: flag.flag,
1114
- value: flagCheck.value
1115
- });
1116
1197
  });
1198
+ if (message.plan !== void 0 && message.plan !== null) {
1199
+ const plan = CheckPlanReturnFromJSON(message.plan);
1200
+ const contextStr = contextString(context);
1201
+ this.planChecks[contextStr] = plan;
1202
+ this.debug(`WebSocket plan update received. Notifying listeners`, {
1203
+ plan
1204
+ });
1205
+ this.notifyPlanListeners(plan);
1206
+ }
1117
1207
  this.flushContextDependentEventQueue();
1118
1208
  this.setIsPending(false);
1119
1209
  };
@@ -1144,53 +1234,6 @@ var Schematic = class {
1144
1234
  this.debug(`submitting flag check event:`, eventBody);
1145
1235
  return this.handleEvent("flag_check", EventBodyFlagCheckToJSON(eventBody));
1146
1236
  }
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
1237
  /**
1195
1238
  * Make an API call to fetch all flag values for a given context.
1196
1239
  * Recommended for use in REST mode only.
@@ -1264,6 +1307,9 @@ var Schematic = class {
1264
1307
  this.setIsPending(false);
1265
1308
  return Promise.resolve();
1266
1309
  }
1310
+ if (contextString(context) === contextString(this.context) && this.conn !== null && !this.isPending) {
1311
+ return;
1312
+ }
1267
1313
  try {
1268
1314
  this.setIsPending(true);
1269
1315
  if (!this.conn) {
@@ -1303,7 +1349,10 @@ var Schematic = class {
1303
1349
  await this.wsSendMessage(socket, context);
1304
1350
  } catch (error) {
1305
1351
  console.warn("Failed to establish WebSocket connection:", error);
1306
- throw error;
1352
+ this.context = context;
1353
+ this.flushContextDependentEventQueue();
1354
+ this.setIsPending(false);
1355
+ this.attemptReconnect();
1307
1356
  }
1308
1357
  };
1309
1358
  /**
@@ -1944,7 +1993,10 @@ var Schematic = class {
1944
1993
  clearTimeout(timeoutId);
1945
1994
  }
1946
1995
  this.debug(`WebSocket connection ${connectionId} error:`, error);
1947
- reject(error);
1996
+ const wrappedError = new Error(
1997
+ "WebSocket connection failed during handshake"
1998
+ );
1999
+ reject(wrappedError);
1948
2000
  };
1949
2001
  webSocket.onclose = () => {
1950
2002
  if (timeoutId !== null) {
@@ -1991,6 +2043,20 @@ var Schematic = class {
1991
2043
  }
1992
2044
  };
1993
2045
  socket.addEventListener("message", messageHandler);
2046
+ socket.addEventListener("close", (event) => {
2047
+ if (!resolved) {
2048
+ resolved = true;
2049
+ if (event.code === 4001) {
2050
+ reject(
2051
+ new Error(
2052
+ `Authentication failed: ${event.reason !== "" ? event.reason : "Invalid API key"}`
2053
+ )
2054
+ );
2055
+ } else {
2056
+ reject(new Error("WebSocket connection closed unexpectedly"));
2057
+ }
2058
+ }
2059
+ });
1994
2060
  this.currentWebSocket = socket;
1995
2061
  const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
1996
2062
  const messagePayload = {
@@ -2027,11 +2093,17 @@ var Schematic = class {
2027
2093
  };
2028
2094
  };
2029
2095
  setIsPending = (isPending) => {
2096
+ if (this.isPending === isPending) return;
2030
2097
  this.isPending = isPending;
2031
2098
  this.isPendingListeners.forEach(
2032
2099
  (listener) => notifyPendingListener(listener, isPending)
2033
2100
  );
2034
2101
  };
2102
+ getPlan = () => {
2103
+ const contextStr = contextString(this.context);
2104
+ const plan = this.planChecks[contextStr];
2105
+ return plan;
2106
+ };
2035
2107
  // flag checks state
2036
2108
  getFlagCheck = (flagKey) => {
2037
2109
  const contextStr = contextString(this.context);
@@ -2041,11 +2113,14 @@ var Schematic = class {
2041
2113
  return check;
2042
2114
  }
2043
2115
  if (flagKey in this.flagCheckDefaults || flagKey in this.flagValueDefaults) {
2044
- return this.resolveFallbackCheckFlagReturn(
2045
- flagKey,
2046
- void 0,
2047
- "Default value used"
2048
- );
2116
+ if (!(flagKey in this.fallbackCheckCache)) {
2117
+ this.fallbackCheckCache[flagKey] = this.resolveFallbackCheckFlagReturn(
2118
+ flagKey,
2119
+ void 0,
2120
+ "Default value used"
2121
+ );
2122
+ }
2123
+ return this.fallbackCheckCache[flagKey];
2049
2124
  }
2050
2125
  return void 0;
2051
2126
  };
@@ -2082,6 +2157,12 @@ var Schematic = class {
2082
2157
  this.flagCheckListeners[flagKey].delete(listener);
2083
2158
  };
2084
2159
  };
2160
+ addPlanListener = (listener) => {
2161
+ this.planListeners.add(listener);
2162
+ return () => {
2163
+ this.planListeners.delete(listener);
2164
+ };
2165
+ };
2085
2166
  notifyFlagCheckListeners = (flagKey, check) => {
2086
2167
  const listeners = this.flagCheckListeners?.[flagKey] ?? [];
2087
2168
  if (listeners.size > 0) {
@@ -2130,6 +2211,21 @@ var Schematic = class {
2130
2211
  });
2131
2212
  });
2132
2213
  };
2214
+ notifyPlanListeners = (value) => {
2215
+ const listeners = this.planListeners ?? [];
2216
+ if (listeners.size > 0) {
2217
+ this.debug(`Notifying ${listeners.size} plan listeners`, { value });
2218
+ }
2219
+ listeners.forEach((listener, index) => {
2220
+ this.debug(`Calling listener ${index} for plan`, {
2221
+ value
2222
+ });
2223
+ notifyPlanListener(listener, value);
2224
+ this.debug(`Listener ${index} for plan completed`, {
2225
+ value
2226
+ });
2227
+ });
2228
+ };
2133
2229
  };
2134
2230
  var notifyPendingListener = (listener, value) => {
2135
2231
  if (listener.length > 0) {
@@ -2152,12 +2248,19 @@ var notifyFlagValueListener = (listener, value) => {
2152
2248
  listener();
2153
2249
  }
2154
2250
  };
2251
+ var notifyPlanListener = (listener, value) => {
2252
+ if (listener.length > 0) {
2253
+ listener(value);
2254
+ } else {
2255
+ listener();
2256
+ }
2257
+ };
2155
2258
 
2156
2259
  // src/context/schematic.ts
2157
2260
  import { inject, onScopeDispose } from "vue";
2158
2261
 
2159
2262
  // src/version.ts
2160
- var version2 = "1.2.16";
2263
+ var version2 = "1.4.0";
2161
2264
 
2162
2265
  // src/context/schematic.ts
2163
2266
  var SchematicSymbol = Symbol("schematic");
@@ -2273,6 +2376,23 @@ var useSchematicEntitlement = (key, opts) => {
2273
2376
  userId: computed(() => flagCheck.value.userId)
2274
2377
  };
2275
2378
  };
2379
+ var useSchematicPlan = (opts) => {
2380
+ const client = useSchematicClient(opts);
2381
+ const fallback = opts?.fallback;
2382
+ const plan = ref(
2383
+ client.getPlan() ?? fallback
2384
+ );
2385
+ let unsubscribe = null;
2386
+ onMounted(() => {
2387
+ unsubscribe = client.addPlanListener((value) => {
2388
+ plan.value = value;
2389
+ });
2390
+ });
2391
+ onScopeDispose2(() => {
2392
+ unsubscribe?.();
2393
+ });
2394
+ return plan;
2395
+ };
2276
2396
  var useSchematicIsPending = (opts) => {
2277
2397
  const client = useSchematicClient(opts);
2278
2398
  const isPending = ref(client.getIsPending());
@@ -2298,7 +2418,8 @@ export {
2298
2418
  useSchematicEntitlement,
2299
2419
  useSchematicEvents,
2300
2420
  useSchematicFlag,
2301
- useSchematicIsPending
2421
+ useSchematicIsPending,
2422
+ useSchematicPlan
2302
2423
  };
2303
2424
  /*! Bundled license information:
2304
2425
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-vue",
3
- "version": "1.2.16",
3
+ "version": "1.4.0",
4
4
  "main": "dist/schematic-vue.cjs.js",
5
5
  "module": "dist/schematic-vue.esm.js",
6
6
  "types": "dist/schematic-vue.d.ts",
@@ -28,17 +28,14 @@
28
28
  "tsc": "npx tsc",
29
29
  "prepare": "husky"
30
30
  },
31
- "dependencies": {
32
- "@schematichq/schematic-js": "^1.2.16"
33
- },
34
31
  "devDependencies": {
35
32
  "@eslint/js": "^9.24.0",
36
33
  "@microsoft/api-extractor": "^7.52.2",
34
+ "@schematichq/schematic-js": "^1.4.0",
37
35
  "@testing-library/dom": "^10.4.1",
38
36
  "@types/jest": "^30.0.0",
39
37
  "@vue/test-utils": "^2.4.6",
40
38
  "esbuild": "^0.25.2",
41
- "esbuild-jest": "^0.5.0",
42
39
  "eslint": "^9.24.0",
43
40
  "eslint-plugin-import": "^2.32.0",
44
41
  "eslint-plugin-vue": "^9.30.0",
@@ -56,5 +53,9 @@
56
53
  "peerDependencies": {
57
54
  "vue": ">=3.3"
58
55
  },
56
+ "resolutions": {
57
+ "handlebars": ">=4.7.9",
58
+ "minimatch": ">=3.1.3"
59
+ },
59
60
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
60
61
  }