@schematichq/schematic-vue 1.2.16 → 1.4.1

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.1";
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());