@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 +1 -2
- package/README.md +98 -0
- package/dist/schematic-vue.cjs.js +221 -100
- package/dist/schematic-vue.d.ts +905 -28
- package/dist/schematic-vue.esm.js +221 -100
- package/package.json +6 -5
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
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:
|
|
766
|
-
featureUsage:
|
|
767
|
-
featureUsageEvent:
|
|
768
|
-
featureUsagePeriod:
|
|
769
|
-
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.
|
|
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
|
|
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,
|
|
1150
|
+
"WebSocket connection failed, using fallback value:",
|
|
1078
1151
|
error
|
|
1079
1152
|
);
|
|
1080
|
-
|
|
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(
|
|
1143
|
-
flag
|
|
1144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2081
|
-
flagKey
|
|
2082
|
-
|
|
2083
|
-
|
|
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.
|
|
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());
|