@schematichq/schematic-react 1.4.1 → 1.6.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.
@@ -30,22 +30,38 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ BillingDataProvider: () => BillingDataProvider,
34
+ BillingProvider: () => BillingProvider,
35
+ DEFAULT_INVOICE_QUERY: () => DEFAULT_INVOICE_QUERY,
36
+ INVOICE_PAGE_SIZE: () => INVOICE_PAGE_SIZE,
37
+ InvoiceStatus: () => InvoiceStatus,
33
38
  RuleType: () => RuleType,
34
39
  Schematic: () => Schematic,
40
+ SchematicI18nContext: () => SchematicI18nContext,
41
+ SchematicI18nProvider: () => SchematicI18nProvider,
35
42
  SchematicProvider: () => SchematicProvider,
36
43
  TrialStatus: () => TrialStatus,
37
44
  UsagePeriod: () => UsagePeriod,
45
+ normalizeInvoiceQuery: () => normalizeInvoiceQuery2,
46
+ useBillingDataSource: () => useBillingDataSource,
47
+ useInvalidateBillingData: () => useInvalidateBillingData,
48
+ useInvoices: () => useInvoices,
38
49
  useSchematic: () => useSchematic,
39
50
  useSchematicContext: () => useSchematicContext,
51
+ useSchematicCreditBalance: () => useSchematicCreditBalance,
40
52
  useSchematicEntitlement: () => useSchematicEntitlement,
41
53
  useSchematicEvents: () => useSchematicEvents,
42
54
  useSchematicFlag: () => useSchematicFlag,
55
+ useSchematicI18n: () => useSchematicI18n,
43
56
  useSchematicIsPending: () => useSchematicIsPending,
44
- useSchematicPlan: () => useSchematicPlan
57
+ useSchematicLocale: () => useSchematicLocale,
58
+ useSchematicPlan: () => useSchematicPlan,
59
+ useSchematicStrings: () => useSchematicStrings,
60
+ useSchematicTranslate: () => useSchematicTranslate
45
61
  });
46
62
  module.exports = __toCommonJS(index_exports);
47
63
 
48
- // node_modules/@schematichq/schematic-js/dist/schematic.esm.js
64
+ // node_modules/.pnpm/@schematichq+schematic-js@1.7.0/node_modules/@schematichq/schematic-js/dist/schematic.esm.js
49
65
  var __create2 = Object.create;
50
66
  var __defProp2 = Object.defineProperty;
51
67
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
@@ -53,7 +69,15 @@ var __getOwnPropNames2 = Object.getOwnPropertyNames;
53
69
  var __getProtoOf2 = Object.getPrototypeOf;
54
70
  var __hasOwnProp2 = Object.prototype.hasOwnProperty;
55
71
  var __commonJS = (cb, mod) => function __require() {
56
- return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
72
+ try {
73
+ return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
74
+ } catch (e) {
75
+ throw mod = 0, e;
76
+ }
77
+ };
78
+ var __export2 = (target, all) => {
79
+ for (var name in all)
80
+ __defProp2(target, name, { get: all[name], enumerable: true });
57
81
  };
58
82
  var __copyProps2 = (to, from, except, desc) => {
59
83
  if (from && typeof from === "object" || typeof from === "function") {
@@ -72,7 +96,7 @@ var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__
72
96
  mod
73
97
  ));
74
98
  var require_browser_polyfill = __commonJS({
75
- "node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
99
+ "node_modules/.pnpm/cross-fetch@4.1.0/node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
76
100
  (function(self2) {
77
101
  var irrelevant = (function(exports2) {
78
102
  var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
@@ -612,19 +636,16 @@ for (let i = 0; i < 256; ++i) {
612
636
  function unsafeStringify(arr, offset = 0) {
613
637
  return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
614
638
  }
615
- var getRandomValues;
616
639
  var rnds8 = new Uint8Array(16);
617
640
  function rng() {
618
- if (!getRandomValues) {
619
- if (typeof crypto === "undefined" || !crypto.getRandomValues) {
620
- throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
621
- }
622
- getRandomValues = crypto.getRandomValues.bind(crypto);
641
+ return crypto.getRandomValues(rnds8);
642
+ }
643
+ function v4(options, buf, offset) {
644
+ if (!buf && !options && crypto.randomUUID) {
645
+ return crypto.randomUUID();
623
646
  }
624
- return getRandomValues(rnds8);
647
+ return _v4(options, buf, offset);
625
648
  }
626
- var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
627
- var native_default = { randomUUID };
628
649
  function _v4(options, buf, offset) {
629
650
  options = options || {};
630
651
  const rnds = options.random ?? options.rng?.() ?? rng();
@@ -645,12 +666,6 @@ function _v4(options, buf, offset) {
645
666
  }
646
667
  return unsafeStringify(rnds);
647
668
  }
648
- function v4(options, buf, offset) {
649
- if (native_default.randomUUID && !buf && !options) {
650
- return native_default.randomUUID();
651
- }
652
- return _v4(options, buf, offset);
653
- }
654
669
  var v4_default = v4;
655
670
  var import_polyfill = __toESM2(require_browser_polyfill());
656
671
  function EntitlementValueTypeFromJSON(json) {
@@ -659,6 +674,30 @@ function EntitlementValueTypeFromJSON(json) {
659
674
  function EntitlementValueTypeFromJSONTyped(json, ignoreDiscriminator) {
660
675
  return json;
661
676
  }
677
+ function MetricPeriodMonthResetFromJSON(json) {
678
+ return MetricPeriodMonthResetFromJSONTyped(json, false);
679
+ }
680
+ function MetricPeriodMonthResetFromJSONTyped(json, ignoreDiscriminator) {
681
+ return json;
682
+ }
683
+ function WarningTierFromJSON(json) {
684
+ return WarningTierFromJSONTyped(json, false);
685
+ }
686
+ function WarningTierFromJSONTyped(json, ignoreDiscriminator) {
687
+ if (json == null) {
688
+ return json;
689
+ }
690
+ return {
691
+ key: json["key"],
692
+ value: json["value"]
693
+ };
694
+ }
695
+ function MetricPeriodFromJSON(json) {
696
+ return MetricPeriodFromJSONTyped(json, false);
697
+ }
698
+ function MetricPeriodFromJSONTyped(json, ignoreDiscriminator) {
699
+ return json;
700
+ }
662
701
  function FeatureEntitlementFromJSON(json) {
663
702
  return FeatureEntitlementFromJSONTyped(json, false);
664
703
  }
@@ -668,21 +707,32 @@ function FeatureEntitlementFromJSONTyped(json, ignoreDiscriminator) {
668
707
  }
669
708
  return {
670
709
  allocation: json["allocation"] == null ? void 0 : json["allocation"],
710
+ consumptionRate: json["consumption_rate"] == null ? void 0 : json["consumption_rate"],
671
711
  creditId: json["credit_id"] == null ? void 0 : json["credit_id"],
672
712
  creditRemaining: json["credit_remaining"] == null ? void 0 : json["credit_remaining"],
713
+ creditReserved: json["credit_reserved"] == null ? void 0 : json["credit_reserved"],
714
+ creditSettled: json["credit_settled"] == null ? void 0 : json["credit_settled"],
673
715
  creditTotal: json["credit_total"] == null ? void 0 : json["credit_total"],
674
716
  creditUsed: json["credit_used"] == null ? void 0 : json["credit_used"],
675
717
  eventName: json["event_name"] == null ? void 0 : json["event_name"],
718
+ eventSubtype: json["event_subtype"] == null ? void 0 : json["event_subtype"],
676
719
  featureId: json["feature_id"],
677
720
  featureKey: json["feature_key"],
678
- metricPeriod: json["metric_period"] == null ? void 0 : json["metric_period"],
721
+ metricPeriod: json["metric_period"] == null ? void 0 : MetricPeriodFromJSON(json["metric_period"]),
679
722
  metricResetAt: json["metric_reset_at"] == null ? void 0 : new Date(json["metric_reset_at"]),
680
- monthReset: json["month_reset"] == null ? void 0 : json["month_reset"],
723
+ monthReset: json["month_reset"] == null ? void 0 : MetricPeriodMonthResetFromJSON(json["month_reset"]),
681
724
  softLimit: json["soft_limit"] == null ? void 0 : json["soft_limit"],
682
725
  usage: json["usage"] == null ? void 0 : json["usage"],
683
- valueType: EntitlementValueTypeFromJSON(json["value_type"])
726
+ valueType: EntitlementValueTypeFromJSON(json["value_type"]),
727
+ warningTiers: json["warning_tiers"] == null ? void 0 : json["warning_tiers"].map(WarningTierFromJSON)
684
728
  };
685
729
  }
730
+ function RuleTypeFromJSON(json) {
731
+ return RuleTypeFromJSONTyped(json, false);
732
+ }
733
+ function RuleTypeFromJSONTyped(json, ignoreDiscriminator) {
734
+ return json;
735
+ }
686
736
  function CheckFlagResponseDataFromJSON(json) {
687
737
  return CheckFlagResponseDataFromJSONTyped(json, false);
688
738
  }
@@ -697,13 +747,13 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
697
747
  featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
698
748
  featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
699
749
  featureUsageEvent: json["feature_usage_event"] == null ? void 0 : json["feature_usage_event"],
700
- featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
750
+ featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : MetricPeriodFromJSON(json["feature_usage_period"]),
701
751
  featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
702
752
  flag: json["flag"],
703
753
  flagId: json["flag_id"] == null ? void 0 : json["flag_id"],
704
754
  reason: json["reason"],
705
755
  ruleId: json["rule_id"] == null ? void 0 : json["rule_id"],
706
- ruleType: json["rule_type"] == null ? void 0 : json["rule_type"],
756
+ ruleType: json["rule_type"] == null ? void 0 : RuleTypeFromJSON(json["rule_type"]),
707
757
  userId: json["user_id"] == null ? void 0 : json["user_id"],
708
758
  value: json["value"]
709
759
  };
@@ -720,6 +770,268 @@ function CheckFlagResponseFromJSONTyped(json, ignoreDiscriminator) {
720
770
  params: json["params"]
721
771
  };
722
772
  }
773
+ var BASE_PATH = "https://api.schematichq.com".replace(/\/+$/, "");
774
+ var Configuration = class {
775
+ constructor(configuration = {}) {
776
+ this.configuration = configuration;
777
+ }
778
+ configuration;
779
+ set config(configuration) {
780
+ this.configuration = configuration;
781
+ }
782
+ get basePath() {
783
+ return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH;
784
+ }
785
+ get fetchApi() {
786
+ return this.configuration.fetchApi;
787
+ }
788
+ get middleware() {
789
+ return this.configuration.middleware || [];
790
+ }
791
+ get queryParamsStringify() {
792
+ return this.configuration.queryParamsStringify || querystring;
793
+ }
794
+ get username() {
795
+ return this.configuration.username;
796
+ }
797
+ get password() {
798
+ return this.configuration.password;
799
+ }
800
+ get apiKey() {
801
+ const apiKey = this.configuration.apiKey;
802
+ if (apiKey) {
803
+ return typeof apiKey === "function" ? apiKey : () => apiKey;
804
+ }
805
+ return void 0;
806
+ }
807
+ get accessToken() {
808
+ const accessToken = this.configuration.accessToken;
809
+ if (accessToken) {
810
+ return typeof accessToken === "function" ? accessToken : async () => accessToken;
811
+ }
812
+ return void 0;
813
+ }
814
+ get headers() {
815
+ return this.configuration.headers;
816
+ }
817
+ get credentials() {
818
+ return this.configuration.credentials;
819
+ }
820
+ };
821
+ var DefaultConfig = new Configuration();
822
+ var BaseAPI = class _BaseAPI {
823
+ constructor(configuration = DefaultConfig) {
824
+ this.configuration = configuration;
825
+ this.middleware = configuration.middleware;
826
+ }
827
+ configuration;
828
+ static jsonRegex = new RegExp(
829
+ "^(:?application/json|[^;/ ]+/[^;/ ]+[+]json)[ ]*(:?;.*)?$",
830
+ "i"
831
+ );
832
+ middleware;
833
+ withMiddleware(...middlewares) {
834
+ const next = this.clone();
835
+ next.middleware = next.middleware.concat(...middlewares);
836
+ return next;
837
+ }
838
+ withPreMiddleware(...preMiddlewares) {
839
+ const middlewares = preMiddlewares.map((pre) => ({ pre }));
840
+ return this.withMiddleware(...middlewares);
841
+ }
842
+ withPostMiddleware(...postMiddlewares) {
843
+ const middlewares = postMiddlewares.map((post) => ({ post }));
844
+ return this.withMiddleware(...middlewares);
845
+ }
846
+ /**
847
+ * Check if the given MIME is a JSON MIME.
848
+ * JSON MIME examples:
849
+ * application/json
850
+ * application/json; charset=UTF8
851
+ * APPLICATION/JSON
852
+ * application/vnd.company+json
853
+ * @param mime - MIME (Multipurpose Internet Mail Extensions)
854
+ * @return True if the given MIME is JSON, false otherwise.
855
+ */
856
+ isJsonMime(mime) {
857
+ if (!mime) {
858
+ return false;
859
+ }
860
+ return _BaseAPI.jsonRegex.test(mime);
861
+ }
862
+ async request(context, initOverrides) {
863
+ const { url, init } = await this.createFetchParams(context, initOverrides);
864
+ const response = await this.fetchApi(url, init);
865
+ if (response && response.status >= 200 && response.status < 300) {
866
+ return response;
867
+ }
868
+ throw new ResponseError(response, "Response returned an error code");
869
+ }
870
+ async createFetchParams(context, initOverrides) {
871
+ let url = this.configuration.basePath + context.path;
872
+ if (context.query !== void 0 && Object.keys(context.query).length !== 0) {
873
+ url += "?" + this.configuration.queryParamsStringify(context.query);
874
+ }
875
+ const headers = Object.assign(
876
+ {},
877
+ this.configuration.headers,
878
+ context.headers
879
+ );
880
+ Object.keys(headers).forEach(
881
+ (key) => headers[key] === void 0 ? delete headers[key] : {}
882
+ );
883
+ const initOverrideFn = typeof initOverrides === "function" ? initOverrides : async () => initOverrides;
884
+ const initParams = {
885
+ method: context.method,
886
+ headers,
887
+ body: context.body,
888
+ credentials: this.configuration.credentials
889
+ };
890
+ const overriddenInit = {
891
+ ...initParams,
892
+ ...await initOverrideFn({
893
+ init: initParams,
894
+ context
895
+ })
896
+ };
897
+ let body;
898
+ if (isFormData(overriddenInit.body) || overriddenInit.body instanceof URLSearchParams || isBlob(overriddenInit.body)) {
899
+ body = overriddenInit.body;
900
+ } else if (this.isJsonMime(headers["Content-Type"])) {
901
+ body = JSON.stringify(overriddenInit.body);
902
+ } else {
903
+ body = overriddenInit.body;
904
+ }
905
+ const init = {
906
+ ...overriddenInit,
907
+ body
908
+ };
909
+ return { url, init };
910
+ }
911
+ fetchApi = async (url, init) => {
912
+ let fetchParams = { url, init };
913
+ for (const middleware of this.middleware) {
914
+ if (middleware.pre) {
915
+ fetchParams = await middleware.pre({
916
+ fetch: this.fetchApi,
917
+ ...fetchParams
918
+ }) || fetchParams;
919
+ }
920
+ }
921
+ let response = void 0;
922
+ try {
923
+ response = await (this.configuration.fetchApi || fetch)(
924
+ fetchParams.url,
925
+ fetchParams.init
926
+ );
927
+ } catch (e) {
928
+ for (const middleware of this.middleware) {
929
+ if (middleware.onError) {
930
+ response = await middleware.onError({
931
+ fetch: this.fetchApi,
932
+ url: fetchParams.url,
933
+ init: fetchParams.init,
934
+ error: e,
935
+ response: response ? response.clone() : void 0
936
+ }) || response;
937
+ }
938
+ }
939
+ if (response === void 0) {
940
+ if (e instanceof Error) {
941
+ throw new FetchError(
942
+ e,
943
+ "The request failed and the interceptors did not return an alternative response"
944
+ );
945
+ } else {
946
+ throw e;
947
+ }
948
+ }
949
+ }
950
+ for (const middleware of this.middleware) {
951
+ if (middleware.post) {
952
+ response = await middleware.post({
953
+ fetch: this.fetchApi,
954
+ url: fetchParams.url,
955
+ init: fetchParams.init,
956
+ response: response.clone()
957
+ }) || response;
958
+ }
959
+ }
960
+ return response;
961
+ };
962
+ /**
963
+ * Create a shallow clone of `this` by constructing a new instance
964
+ * and then shallow cloning data members.
965
+ */
966
+ clone() {
967
+ const constructor = this.constructor;
968
+ const next = new constructor(this.configuration);
969
+ next.middleware = this.middleware.slice();
970
+ return next;
971
+ }
972
+ };
973
+ function isBlob(value) {
974
+ return typeof Blob !== "undefined" && value instanceof Blob;
975
+ }
976
+ function isFormData(value) {
977
+ return typeof FormData !== "undefined" && value instanceof FormData;
978
+ }
979
+ var ResponseError = class extends Error {
980
+ constructor(response, msg) {
981
+ super(msg);
982
+ this.response = response;
983
+ }
984
+ response;
985
+ name = "ResponseError";
986
+ };
987
+ var FetchError = class extends Error {
988
+ constructor(cause, msg) {
989
+ super(msg);
990
+ this.cause = cause;
991
+ }
992
+ cause;
993
+ name = "FetchError";
994
+ };
995
+ function querystring(params, prefix = "") {
996
+ return Object.keys(params).map((key) => querystringSingleKey(key, params[key], prefix)).filter((part) => part.length > 0).join("&");
997
+ }
998
+ function querystringSingleKey(key, value, keyPrefix = "") {
999
+ const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key);
1000
+ if (value instanceof Array) {
1001
+ const multiValue = value.map((singleValue) => encodeURIComponent(String(singleValue))).join(`&${encodeURIComponent(fullKey)}=`);
1002
+ return `${encodeURIComponent(fullKey)}=${multiValue}`;
1003
+ }
1004
+ if (value instanceof Set) {
1005
+ const valueAsArray = Array.from(value);
1006
+ return querystringSingleKey(key, valueAsArray, keyPrefix);
1007
+ }
1008
+ if (value instanceof Date) {
1009
+ return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`;
1010
+ }
1011
+ if (value instanceof Object) {
1012
+ return querystring(value, fullKey);
1013
+ }
1014
+ return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
1015
+ }
1016
+ function mapValues(data, fn) {
1017
+ return Object.keys(data).reduce(
1018
+ (acc, key) => ({ ...acc, [key]: fn(data[key]) }),
1019
+ {}
1020
+ );
1021
+ }
1022
+ function CompanyCreditBalanceFromJSON(json) {
1023
+ return CompanyCreditBalanceFromJSONTyped(json, false);
1024
+ }
1025
+ function CompanyCreditBalanceFromJSONTyped(json, ignoreDiscriminator) {
1026
+ if (json == null) {
1027
+ return json;
1028
+ }
1029
+ return {
1030
+ remaining: json["remaining"],
1031
+ reserved: json["reserved"],
1032
+ settled: json["settled"]
1033
+ };
1034
+ }
723
1035
  var TrialStatus = {
724
1036
  Active: "active",
725
1037
  Converted: "converted",
@@ -753,6 +1065,7 @@ function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
753
1065
  return json;
754
1066
  }
755
1067
  return {
1068
+ creditBalances: json["credit_balances"] == null ? void 0 : mapValues(json["credit_balances"], CompanyCreditBalanceFromJSON),
756
1069
  flags: json["flags"].map(CheckFlagResponseDataFromJSON),
757
1070
  plan: json["plan"] == null ? void 0 : DatastreamCompanyPlanFromJSON(json["plan"])
758
1071
  };
@@ -781,6 +1094,7 @@ function EventBodyFlagCheckToJSONTyped(value, ignoreDiscriminator = false) {
781
1094
  error: value["error"],
782
1095
  flag_id: value["flagId"],
783
1096
  flag_key: value["flagKey"],
1097
+ preflight: value["preflight"],
784
1098
  reason: value["reason"],
785
1099
  req_company: value["reqCompany"],
786
1100
  req_user: value["reqUser"],
@@ -835,7 +1149,10 @@ var CheckFlagReturnFromJSON = (json) => {
835
1149
  return {
836
1150
  featureUsageExceeded,
837
1151
  companyId: companyId == null ? void 0 : companyId,
1152
+ creditId: entitlement?.creditId == null ? void 0 : entitlement.creditId,
838
1153
  creditRemaining: entitlement?.creditRemaining == null ? void 0 : entitlement.creditRemaining,
1154
+ creditReserved: entitlement?.creditReserved == null ? void 0 : entitlement.creditReserved,
1155
+ creditSettled: entitlement?.creditSettled == null ? void 0 : entitlement.creditSettled,
839
1156
  error: error == null ? void 0 : error,
840
1157
  featureAllocation: resolvedAllocation == null ? void 0 : resolvedAllocation,
841
1158
  featureUsage: resolvedUsage == null ? void 0 : resolvedUsage,
@@ -861,6 +1178,22 @@ var CheckPlanReturnFromJSON = (json) => {
861
1178
  trialStatus: trialStatus == null ? void 0 : trialStatus
862
1179
  };
863
1180
  };
1181
+ var CreditBalancesFromJSON = (json) => {
1182
+ if (json == null || typeof json !== "object") {
1183
+ return {};
1184
+ }
1185
+ const balances = {};
1186
+ for (const [creditId, raw] of Object.entries(json)) {
1187
+ if (raw == null || typeof raw !== "object") continue;
1188
+ const { remaining, reserved, settled } = CompanyCreditBalanceFromJSON(raw);
1189
+ if (typeof remaining !== "number" || typeof reserved !== "number" || typeof settled !== "number") {
1190
+ continue;
1191
+ }
1192
+ balances[creditId] = { remaining, reserved, settled };
1193
+ }
1194
+ return balances;
1195
+ };
1196
+ var cacheVersion = "bb56e75d";
864
1197
  function contextString(context) {
865
1198
  const sortedContext = Object.keys(context).reduce((acc, key) => {
866
1199
  const sortedKeys = Object.keys(
@@ -875,143 +1208,688 @@ function contextString(context) {
875
1208
  }, {});
876
1209
  return JSON.stringify(sortedContext);
877
1210
  }
878
- var version = "1.4.0";
879
- var anonymousIdKey = "schematicId";
880
- var Schematic = class {
881
- additionalHeaders = {};
882
- apiKey;
883
- apiUrl = "https://api.schematichq.com";
884
- conn = null;
885
- context = {};
886
- debugEnabled = false;
887
- offlineEnabled = false;
888
- eventQueue;
889
- contextDependentEventQueue;
890
- eventUrl = "https://c.schematichq.com";
891
- flagCheckListeners = {};
892
- flagValueListeners = {};
893
- isPending = true;
894
- isPendingListeners = /* @__PURE__ */ new Set();
895
- planListeners = /* @__PURE__ */ new Set();
896
- storage;
897
- useWebSocket = false;
898
- checks = {};
899
- featureUsageEventMap = {};
900
- planChecks = {};
901
- webSocketUrl = "wss://api.schematichq.com";
902
- webSocketConnectionTimeout = 1e4;
903
- webSocketReconnect = true;
904
- webSocketMaxReconnectAttempts = 7;
905
- // Max attempts after connection disrupted
906
- webSocketMaxConnectionAttempts = 3;
907
- // Max attempts for initial connection
908
- webSocketInitialRetryDelay = 1e3;
909
- webSocketMaxRetryDelay = 3e4;
910
- wsReconnectAttempts = 0;
911
- wsReconnectTimer = null;
912
- wsIntentionalDisconnect = false;
913
- currentWebSocket = null;
914
- isConnecting = false;
915
- maxEventQueueSize = 100;
916
- // Prevent memory issues with very long network outages
917
- maxEventRetries = 5;
918
- // Maximum retry attempts for failed events
919
- eventRetryInitialDelay = 1e3;
920
- // Initial retry delay in ms
921
- eventRetryMaxDelay = 3e4;
922
- // Maximum retry delay in ms
923
- retryTimer = null;
924
- flagValueDefaults = {};
925
- flagCheckDefaults = {};
926
- fallbackCheckCache = {};
927
- constructor(apiKey, options) {
928
- this.apiKey = apiKey;
929
- this.eventQueue = [];
930
- this.contextDependentEventQueue = [];
931
- this.useWebSocket = options?.useWebSocket ?? false;
932
- this.debugEnabled = options?.debug ?? false;
933
- this.offlineEnabled = options?.offline ?? false;
934
- if (typeof window !== "undefined" && typeof window.location !== "undefined") {
935
- const params = new URLSearchParams(window.location.search);
936
- const debugParam = params.get("schematic_debug");
937
- if (debugParam !== null && (debugParam === "" || debugParam === "true" || debugParam === "1")) {
938
- this.debugEnabled = true;
939
- }
940
- const offlineParam = params.get("schematic_offline");
941
- if (offlineParam !== null && (offlineParam === "" || offlineParam === "true" || offlineParam === "1")) {
942
- this.offlineEnabled = true;
943
- this.debugEnabled = true;
944
- }
945
- }
946
- if (this.offlineEnabled && options?.debug !== false) {
947
- this.debugEnabled = true;
948
- }
949
- if (this.offlineEnabled) {
950
- this.setIsPending(false);
951
- }
952
- this.additionalHeaders = {
953
- "X-Schematic-Client-Version": `schematic-js@${version}`,
954
- ...options?.additionalHeaders ?? {}
955
- };
956
- if (options?.storage) {
957
- this.storage = options.storage;
958
- } else {
959
- try {
960
- if (typeof localStorage !== "undefined") {
961
- this.storage = localStorage;
962
- }
963
- } catch {
1211
+ var version = "1.7.0";
1212
+ var models_exports = {};
1213
+ __export2(models_exports, {
1214
+ ApiErrorFromJSON: () => ApiErrorFromJSON,
1215
+ ApiErrorFromJSONTyped: () => ApiErrorFromJSONTyped,
1216
+ ApiErrorToJSON: () => ApiErrorToJSON,
1217
+ ApiErrorToJSONTyped: () => ApiErrorToJSONTyped,
1218
+ CompanyInvoiceResponseDataFromJSON: () => CompanyInvoiceResponseDataFromJSON,
1219
+ CompanyInvoiceResponseDataFromJSONTyped: () => CompanyInvoiceResponseDataFromJSONTyped,
1220
+ CompanyInvoiceResponseDataToJSON: () => CompanyInvoiceResponseDataToJSON,
1221
+ CompanyInvoiceResponseDataToJSONTyped: () => CompanyInvoiceResponseDataToJSONTyped,
1222
+ CompanyInvoicesResponseDataFromJSON: () => CompanyInvoicesResponseDataFromJSON,
1223
+ CompanyInvoicesResponseDataFromJSONTyped: () => CompanyInvoicesResponseDataFromJSONTyped,
1224
+ CompanyInvoicesResponseDataToJSON: () => CompanyInvoicesResponseDataToJSON,
1225
+ CompanyInvoicesResponseDataToJSONTyped: () => CompanyInvoicesResponseDataToJSONTyped,
1226
+ GetCompanyInvoicesParamsFromJSON: () => GetCompanyInvoicesParamsFromJSON,
1227
+ GetCompanyInvoicesParamsFromJSONTyped: () => GetCompanyInvoicesParamsFromJSONTyped,
1228
+ GetCompanyInvoicesParamsToJSON: () => GetCompanyInvoicesParamsToJSON,
1229
+ GetCompanyInvoicesParamsToJSONTyped: () => GetCompanyInvoicesParamsToJSONTyped,
1230
+ GetCompanyInvoicesResponseFromJSON: () => GetCompanyInvoicesResponseFromJSON,
1231
+ GetCompanyInvoicesResponseFromJSONTyped: () => GetCompanyInvoicesResponseFromJSONTyped,
1232
+ GetCompanyInvoicesResponseToJSON: () => GetCompanyInvoicesResponseToJSON,
1233
+ GetCompanyInvoicesResponseToJSONTyped: () => GetCompanyInvoicesResponseToJSONTyped,
1234
+ InvoiceStatus: () => InvoiceStatus,
1235
+ InvoiceStatusFromJSON: () => InvoiceStatusFromJSON,
1236
+ InvoiceStatusFromJSONTyped: () => InvoiceStatusFromJSONTyped,
1237
+ InvoiceStatusToJSON: () => InvoiceStatusToJSON,
1238
+ InvoiceStatusToJSONTyped: () => InvoiceStatusToJSONTyped,
1239
+ instanceOfApiError: () => instanceOfApiError,
1240
+ instanceOfCompanyInvoiceResponseData: () => instanceOfCompanyInvoiceResponseData,
1241
+ instanceOfCompanyInvoicesResponseData: () => instanceOfCompanyInvoicesResponseData,
1242
+ instanceOfGetCompanyInvoicesParams: () => instanceOfGetCompanyInvoicesParams,
1243
+ instanceOfGetCompanyInvoicesResponse: () => instanceOfGetCompanyInvoicesResponse,
1244
+ instanceOfInvoiceStatus: () => instanceOfInvoiceStatus
1245
+ });
1246
+ function instanceOfApiError(value) {
1247
+ if (!("error" in value) || value["error"] === void 0) return false;
1248
+ return true;
1249
+ }
1250
+ function ApiErrorFromJSON(json) {
1251
+ return ApiErrorFromJSONTyped(json, false);
1252
+ }
1253
+ function ApiErrorFromJSONTyped(json, ignoreDiscriminator) {
1254
+ if (json == null) {
1255
+ return json;
1256
+ }
1257
+ return {
1258
+ error: json["error"]
1259
+ };
1260
+ }
1261
+ function ApiErrorToJSON(json) {
1262
+ return ApiErrorToJSONTyped(json, false);
1263
+ }
1264
+ function ApiErrorToJSONTyped(value, ignoreDiscriminator = false) {
1265
+ if (value == null) {
1266
+ return value;
1267
+ }
1268
+ return {
1269
+ error: value["error"]
1270
+ };
1271
+ }
1272
+ var InvoiceStatus = {
1273
+ Draft: "draft",
1274
+ Open: "open",
1275
+ Paid: "paid",
1276
+ Uncollectible: "uncollectible",
1277
+ Void: "void"
1278
+ };
1279
+ function instanceOfInvoiceStatus(value) {
1280
+ for (const key in InvoiceStatus) {
1281
+ if (Object.prototype.hasOwnProperty.call(InvoiceStatus, key)) {
1282
+ if (InvoiceStatus[key] === value) {
1283
+ return true;
964
1284
  }
965
1285
  }
966
- if (options?.apiUrl !== void 0) {
967
- this.apiUrl = options.apiUrl;
968
- }
969
- if (options?.eventUrl !== void 0) {
970
- this.eventUrl = options.eventUrl;
971
- }
972
- if (options?.webSocketUrl !== void 0) {
973
- this.webSocketUrl = options.webSocketUrl;
974
- }
975
- if (options?.webSocketConnectionTimeout !== void 0) {
976
- this.webSocketConnectionTimeout = options.webSocketConnectionTimeout;
977
- }
978
- if (options?.webSocketReconnect !== void 0) {
979
- this.webSocketReconnect = options.webSocketReconnect;
980
- }
981
- if (options?.webSocketMaxReconnectAttempts !== void 0) {
982
- this.webSocketMaxReconnectAttempts = options.webSocketMaxReconnectAttempts;
983
- }
984
- if (options?.webSocketInitialRetryDelay !== void 0) {
985
- this.webSocketInitialRetryDelay = options.webSocketInitialRetryDelay;
986
- }
987
- if (options?.webSocketMaxRetryDelay !== void 0) {
988
- this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
989
- }
990
- if (options?.maxEventQueueSize !== void 0) {
991
- this.maxEventQueueSize = options.maxEventQueueSize;
992
- }
993
- if (options?.maxEventRetries !== void 0) {
994
- this.maxEventRetries = options.maxEventRetries;
995
- }
996
- if (options?.eventRetryInitialDelay !== void 0) {
997
- this.eventRetryInitialDelay = options.eventRetryInitialDelay;
998
- }
999
- if (options?.eventRetryMaxDelay !== void 0) {
1000
- this.eventRetryMaxDelay = options.eventRetryMaxDelay;
1001
- }
1002
- if (options?.flagValueDefaults !== void 0) {
1003
- this.flagValueDefaults = options.flagValueDefaults;
1004
- }
1005
- if (options?.flagCheckDefaults !== void 0) {
1006
- this.flagCheckDefaults = options.flagCheckDefaults;
1007
- }
1008
- if (typeof window !== "undefined" && window?.addEventListener) {
1009
- window.addEventListener("beforeunload", () => {
1010
- this.flushEventQueue();
1011
- this.flushContextDependentEventQueue();
1012
- });
1013
- if (this.useWebSocket) {
1014
- window.addEventListener("offline", () => {
1286
+ }
1287
+ return false;
1288
+ }
1289
+ function InvoiceStatusFromJSON(json) {
1290
+ return InvoiceStatusFromJSONTyped(json, false);
1291
+ }
1292
+ function InvoiceStatusFromJSONTyped(json, ignoreDiscriminator) {
1293
+ return json;
1294
+ }
1295
+ function InvoiceStatusToJSON(value) {
1296
+ return value;
1297
+ }
1298
+ function InvoiceStatusToJSONTyped(value, ignoreDiscriminator) {
1299
+ return value;
1300
+ }
1301
+ function instanceOfCompanyInvoiceResponseData(value) {
1302
+ if (!("amountDue" in value) || value["amountDue"] === void 0) return false;
1303
+ if (!("createdAt" in value) || value["createdAt"] === void 0) return false;
1304
+ if (!("currency" in value) || value["currency"] === void 0) return false;
1305
+ if (!("id" in value) || value["id"] === void 0) return false;
1306
+ return true;
1307
+ }
1308
+ function CompanyInvoiceResponseDataFromJSON(json) {
1309
+ return CompanyInvoiceResponseDataFromJSONTyped(json, false);
1310
+ }
1311
+ function CompanyInvoiceResponseDataFromJSONTyped(json, ignoreDiscriminator) {
1312
+ if (json == null) {
1313
+ return json;
1314
+ }
1315
+ return {
1316
+ amountDue: json["amount_due"],
1317
+ createdAt: new Date(json["created_at"]),
1318
+ currency: json["currency"],
1319
+ dueDate: json["due_date"] == null ? void 0 : new Date(json["due_date"]),
1320
+ id: json["id"],
1321
+ status: json["status"] == null ? void 0 : InvoiceStatusFromJSON(json["status"]),
1322
+ url: json["url"] == null ? void 0 : json["url"]
1323
+ };
1324
+ }
1325
+ function CompanyInvoiceResponseDataToJSON(json) {
1326
+ return CompanyInvoiceResponseDataToJSONTyped(json, false);
1327
+ }
1328
+ function CompanyInvoiceResponseDataToJSONTyped(value, ignoreDiscriminator = false) {
1329
+ if (value == null) {
1330
+ return value;
1331
+ }
1332
+ return {
1333
+ amount_due: value["amountDue"],
1334
+ created_at: value["createdAt"].toISOString(),
1335
+ currency: value["currency"],
1336
+ due_date: value["dueDate"] == null ? void 0 : value["dueDate"].toISOString(),
1337
+ id: value["id"],
1338
+ status: InvoiceStatusToJSON(value["status"]),
1339
+ url: value["url"]
1340
+ };
1341
+ }
1342
+ function instanceOfCompanyInvoicesResponseData(value) {
1343
+ if (!("count" in value) || value["count"] === void 0) return false;
1344
+ if (!("invoices" in value) || value["invoices"] === void 0) return false;
1345
+ return true;
1346
+ }
1347
+ function CompanyInvoicesResponseDataFromJSON(json) {
1348
+ return CompanyInvoicesResponseDataFromJSONTyped(json, false);
1349
+ }
1350
+ function CompanyInvoicesResponseDataFromJSONTyped(json, ignoreDiscriminator) {
1351
+ if (json == null) {
1352
+ return json;
1353
+ }
1354
+ return {
1355
+ count: json["count"],
1356
+ invoices: json["invoices"].map(
1357
+ CompanyInvoiceResponseDataFromJSON
1358
+ )
1359
+ };
1360
+ }
1361
+ function CompanyInvoicesResponseDataToJSON(json) {
1362
+ return CompanyInvoicesResponseDataToJSONTyped(json, false);
1363
+ }
1364
+ function CompanyInvoicesResponseDataToJSONTyped(value, ignoreDiscriminator = false) {
1365
+ if (value == null) {
1366
+ return value;
1367
+ }
1368
+ return {
1369
+ count: value["count"],
1370
+ invoices: value["invoices"].map(
1371
+ CompanyInvoiceResponseDataToJSON
1372
+ )
1373
+ };
1374
+ }
1375
+ function instanceOfGetCompanyInvoicesParams(value) {
1376
+ return true;
1377
+ }
1378
+ function GetCompanyInvoicesParamsFromJSON(json) {
1379
+ return GetCompanyInvoicesParamsFromJSONTyped(json, false);
1380
+ }
1381
+ function GetCompanyInvoicesParamsFromJSONTyped(json, ignoreDiscriminator) {
1382
+ if (json == null) {
1383
+ return json;
1384
+ }
1385
+ return {
1386
+ includePending: json["include_pending"] == null ? void 0 : json["include_pending"],
1387
+ limit: json["limit"] == null ? void 0 : json["limit"],
1388
+ offset: json["offset"] == null ? void 0 : json["offset"]
1389
+ };
1390
+ }
1391
+ function GetCompanyInvoicesParamsToJSON(json) {
1392
+ return GetCompanyInvoicesParamsToJSONTyped(json, false);
1393
+ }
1394
+ function GetCompanyInvoicesParamsToJSONTyped(value, ignoreDiscriminator = false) {
1395
+ if (value == null) {
1396
+ return value;
1397
+ }
1398
+ return {
1399
+ include_pending: value["includePending"],
1400
+ limit: value["limit"],
1401
+ offset: value["offset"]
1402
+ };
1403
+ }
1404
+ function instanceOfGetCompanyInvoicesResponse(value) {
1405
+ if (!("data" in value) || value["data"] === void 0) return false;
1406
+ if (!("params" in value) || value["params"] === void 0) return false;
1407
+ return true;
1408
+ }
1409
+ function GetCompanyInvoicesResponseFromJSON(json) {
1410
+ return GetCompanyInvoicesResponseFromJSONTyped(json, false);
1411
+ }
1412
+ function GetCompanyInvoicesResponseFromJSONTyped(json, ignoreDiscriminator) {
1413
+ if (json == null) {
1414
+ return json;
1415
+ }
1416
+ return {
1417
+ data: CompanyInvoicesResponseDataFromJSON(json["data"]),
1418
+ params: GetCompanyInvoicesParamsFromJSON(json["params"])
1419
+ };
1420
+ }
1421
+ function GetCompanyInvoicesResponseToJSON(json) {
1422
+ return GetCompanyInvoicesResponseToJSONTyped(json, false);
1423
+ }
1424
+ function GetCompanyInvoicesResponseToJSONTyped(value, ignoreDiscriminator = false) {
1425
+ if (value == null) {
1426
+ return value;
1427
+ }
1428
+ return {
1429
+ data: CompanyInvoicesResponseDataToJSON(value["data"]),
1430
+ params: GetCompanyInvoicesParamsToJSON(value["params"])
1431
+ };
1432
+ }
1433
+ var DEFAULT_INVOICE_QUERY = {};
1434
+ function normalizeInvoiceQuery(query) {
1435
+ const { includePending, ...rest } = query;
1436
+ return includePending === true ? { ...rest, includePending: true } : rest;
1437
+ }
1438
+ var SchematicApiError = class extends Error {
1439
+ constructor(status, path, body) {
1440
+ super(
1441
+ typeof body === "object" && body !== null && "error" in body ? String(body.error) : `Request to ${path} failed with status ${status}`
1442
+ );
1443
+ this.status = status;
1444
+ this.path = path;
1445
+ this.body = body;
1446
+ this.name = "SchematicApiError";
1447
+ }
1448
+ status;
1449
+ path;
1450
+ body;
1451
+ };
1452
+ var DEFAULT_API_URL = "https://api.schematichq.com";
1453
+ var TOKEN_CACHE_SIZE = 10;
1454
+ function sessionKey(session) {
1455
+ return JSON.stringify([session.company, session.user ?? null]);
1456
+ }
1457
+ var SchematicSession = class {
1458
+ _apiUrl;
1459
+ _headers;
1460
+ _fetch;
1461
+ _session;
1462
+ _tokens = /* @__PURE__ */ new Map();
1463
+ _resolving = /* @__PURE__ */ new Map();
1464
+ /**
1465
+ * Bumped when the session's identity changes, and only then — the object
1466
+ * itself is replaced on every render. Anything asking "is this still the
1467
+ * session I started under?" counts generations rather than comparing.
1468
+ */
1469
+ _generation = 0;
1470
+ _listeners = /* @__PURE__ */ new Set();
1471
+ constructor(options = {}) {
1472
+ this._session = options.session;
1473
+ this._apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
1474
+ this._headers = options.additionalHeaders ?? {};
1475
+ this._fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
1476
+ }
1477
+ get key() {
1478
+ return this._session === null || this._session === void 0 ? void 0 : sessionKey(this._session);
1479
+ }
1480
+ get status() {
1481
+ if (this._session === void 0) {
1482
+ return "pending";
1483
+ }
1484
+ return this._session === null ? "ended" : "active";
1485
+ }
1486
+ /** Only a statement that changes which pair is read counts as a change. */
1487
+ set(session) {
1488
+ if (session === void 0) {
1489
+ return;
1490
+ }
1491
+ const previous = this._session;
1492
+ this._session = session;
1493
+ if (session === null) {
1494
+ if (previous === null) {
1495
+ return;
1496
+ }
1497
+ this._tokens.clear();
1498
+ this._resolving.clear();
1499
+ this._generation += 1;
1500
+ this._emit({ type: "ended" });
1501
+ return;
1502
+ }
1503
+ if (previous !== null && previous !== void 0) {
1504
+ if (previous.company === session.company && (previous.user ?? null) === (session.user ?? null)) {
1505
+ return;
1506
+ }
1507
+ this._generation += 1;
1508
+ this._emit({ type: "changed" });
1509
+ return;
1510
+ }
1511
+ this._generation += 1;
1512
+ this._emit({ type: "started" });
1513
+ }
1514
+ onChange(listener) {
1515
+ this._listeners.add(listener);
1516
+ return () => {
1517
+ this._listeners.delete(listener);
1518
+ };
1519
+ }
1520
+ _emit(event) {
1521
+ for (const listener of this._listeners) {
1522
+ listener(event);
1523
+ }
1524
+ }
1525
+ _cached(key) {
1526
+ const cached = this._tokens.get(key);
1527
+ if (cached === void 0) {
1528
+ return void 0;
1529
+ }
1530
+ if (cached.expiresAt !== void 0 && cached.expiresAt.getTime() <= Date.now()) {
1531
+ this._tokens.delete(key);
1532
+ return void 0;
1533
+ }
1534
+ this._tokens.delete(key);
1535
+ this._tokens.set(key, cached);
1536
+ return cached;
1537
+ }
1538
+ _cache(key, resolved) {
1539
+ this._tokens.delete(key);
1540
+ this._tokens.set(key, resolved);
1541
+ while (this._tokens.size > TOKEN_CACHE_SIZE) {
1542
+ const oldest = this._tokens.keys().next();
1543
+ if (oldest.done === true) {
1544
+ break;
1545
+ }
1546
+ this._tokens.delete(oldest.value);
1547
+ }
1548
+ }
1549
+ /**
1550
+ * The credential for a session, minted if this one is not held. `force`
1551
+ * retires what is held first: the caller is a request the API has just
1552
+ * rejected.
1553
+ */
1554
+ resolveToken(session, force) {
1555
+ const { token } = session;
1556
+ if (typeof token === "string") {
1557
+ return Promise.resolve(token);
1558
+ }
1559
+ const key = sessionKey(session);
1560
+ if (!force) {
1561
+ const cached = this._cached(key);
1562
+ if (cached !== void 0) {
1563
+ return Promise.resolve(cached.token);
1564
+ }
1565
+ } else {
1566
+ this._tokens.delete(key);
1567
+ }
1568
+ const inflight = this._resolving.get(key);
1569
+ if (inflight !== void 0 && inflight.sessionGeneration === this._generation && (!force || inflight.forced)) {
1570
+ return inflight.promise;
1571
+ }
1572
+ const provider = token;
1573
+ const sessionGeneration = this._generation;
1574
+ const owns = () => this._resolving.get(key)?.promise === promise;
1575
+ const promise = Promise.resolve().then(() => provider()).then((result) => {
1576
+ const { resolved, stamped } = asResolvedToken(result, session);
1577
+ if (owns() && (this._generation === sessionGeneration || stamped)) {
1578
+ this._cache(key, resolved);
1579
+ }
1580
+ return resolved.token;
1581
+ }).finally(() => {
1582
+ if (owns()) {
1583
+ this._resolving.delete(key);
1584
+ }
1585
+ });
1586
+ this._resolving.set(key, { promise, forced: force, sessionGeneration });
1587
+ return promise;
1588
+ }
1589
+ /**
1590
+ * Sends a request under the active session's credential, refreshing once
1591
+ * and retrying if the API rejects it. Rejects rather than answering when
1592
+ * the session changes while the request is in flight: the rows would
1593
+ * otherwise come back as the answer to a question asked for another pair.
1594
+ */
1595
+ async request(path, options = {}) {
1596
+ const { method = "GET", body } = options;
1597
+ const send = async (credential2) => this._fetch(`${this._apiUrl}${path}`, {
1598
+ method,
1599
+ headers: {
1600
+ "Accept": "application/json",
1601
+ ...body === void 0 ? {} : { "Content-Type": "application/json" },
1602
+ ...this._headers,
1603
+ "X-Schematic-Api-Key": credential2
1604
+ },
1605
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
1606
+ });
1607
+ const session = this._session;
1608
+ if (session === null) {
1609
+ throw new Error("There is no session to read data for.");
1610
+ }
1611
+ if (session === void 0) {
1612
+ throw new Error("A session is required to read data.");
1613
+ }
1614
+ const provider = session.token;
1615
+ const generation = this._generation;
1616
+ const stillOurs = async (response2) => {
1617
+ if (this._generation === generation) {
1618
+ return;
1619
+ }
1620
+ if (response2 !== void 0) {
1621
+ await discardBody(response2);
1622
+ }
1623
+ throw new Error("The session changed while this request was in flight.");
1624
+ };
1625
+ const credential = await this.resolveToken(session, false);
1626
+ if (this._generation !== generation) {
1627
+ throw new Error("The session changed before this request was sent.");
1628
+ }
1629
+ let response = await send(credential);
1630
+ await stillOurs(response);
1631
+ if (response.status === 401 && typeof provider === "function" && this._generation === generation) {
1632
+ const status = response.status;
1633
+ const rejected = await readBody(response);
1634
+ let retried = false;
1635
+ const current = this._session;
1636
+ if (this._generation === generation && current !== null && current !== void 0) {
1637
+ const refreshed = await this.resolveToken(current, true);
1638
+ if (this._generation === generation) {
1639
+ response = await send(refreshed);
1640
+ await stillOurs(response);
1641
+ retried = true;
1642
+ }
1643
+ }
1644
+ if (!retried) {
1645
+ throw new SchematicApiError(status, path, rejected);
1646
+ }
1647
+ }
1648
+ const parsed = await readBody(response);
1649
+ await stillOurs();
1650
+ if (!response.ok) {
1651
+ throw new SchematicApiError(response.status, path, parsed);
1652
+ }
1653
+ return parsed;
1654
+ }
1655
+ };
1656
+ function asResolvedToken(result, session) {
1657
+ if (typeof result === "string") {
1658
+ return { resolved: { token: result }, stamped: false };
1659
+ }
1660
+ const token = result?.token;
1661
+ if (typeof token !== "string" || token === "") {
1662
+ throw new Error("The access token provider did not return a token.");
1663
+ }
1664
+ if (result.company !== void 0 && result.company !== session.company || result.user !== void 0 && session.user !== void 0 && result.user !== session.user) {
1665
+ throw new Error(
1666
+ "The access token provider returned a token for a different session."
1667
+ );
1668
+ }
1669
+ const stamped = result.company !== void 0 && (session.user === void 0 || result.user !== void 0);
1670
+ return { resolved: { token, expiresAt: asDate(result.expiresAt) }, stamped };
1671
+ }
1672
+ function asDate(value) {
1673
+ if (value === void 0 || value === null) {
1674
+ return void 0;
1675
+ }
1676
+ const date = value instanceof Date ? value : new Date(value);
1677
+ return Number.isNaN(date.getTime()) ? void 0 : date;
1678
+ }
1679
+ async function discardBody(response) {
1680
+ try {
1681
+ await response.body?.cancel();
1682
+ } catch {
1683
+ }
1684
+ }
1685
+ async function readBody(response) {
1686
+ const text = await response.text();
1687
+ if (text === "") {
1688
+ return null;
1689
+ }
1690
+ try {
1691
+ return JSON.parse(text);
1692
+ } catch {
1693
+ return text;
1694
+ }
1695
+ }
1696
+ var INVOICE_PAGE_SIZE = 12;
1697
+ var INVOICE_MAX_PAGE_SIZE = 250;
1698
+ var SchematicBillingClient = class {
1699
+ /**
1700
+ * Public so a second client over the same surface can be constructed on
1701
+ * this one rather than minting tokens of its own.
1702
+ */
1703
+ session;
1704
+ constructor(options = {}) {
1705
+ this.session = options instanceof SchematicSession ? options : new SchematicSession(options);
1706
+ }
1707
+ get sessionKey() {
1708
+ return this.session.key;
1709
+ }
1710
+ get sessionStatus() {
1711
+ return this.session.status;
1712
+ }
1713
+ setSession(session) {
1714
+ this.session.set(session);
1715
+ }
1716
+ onSessionChange(listener) {
1717
+ return this.session.onChange(listener);
1718
+ }
1719
+ fetchInvoices(params) {
1720
+ const query = new URLSearchParams({
1721
+ limit: String(Math.min(params.limit, INVOICE_MAX_PAGE_SIZE)),
1722
+ offset: String(params.offset)
1723
+ });
1724
+ if (params.includePending !== void 0) {
1725
+ query.set("include_pending", String(params.includePending));
1726
+ }
1727
+ const path = `/company/invoices?${query}`;
1728
+ return this.session.request(path).then((body) => {
1729
+ if (body === null || typeof body !== "object" || !("data" in body)) {
1730
+ throw new Error(`Malformed response from ${path}`);
1731
+ }
1732
+ const data = body.data;
1733
+ if (data == null || data.invoices == null) {
1734
+ return { invoices: [], count: 0 };
1735
+ }
1736
+ const decoded = GetCompanyInvoicesResponseFromJSON(body).data;
1737
+ return { invoices: decoded.invoices, count: decoded.count };
1738
+ });
1739
+ }
1740
+ };
1741
+ var anonymousIdKey = "schematicId";
1742
+ var flagStateCachePrefix = "schematicCache";
1743
+ var flagStateCacheMaxContexts = 10;
1744
+ var flagStateCacheDefaultMaxAgeMs = 7 * 24 * 60 * 60 * 1e3;
1745
+ var emptyCreditBalances = Object.freeze({});
1746
+ var Schematic = class {
1747
+ _additionalHeaders = {};
1748
+ apiKey;
1749
+ _apiUrl = "https://api.schematichq.com";
1750
+ conn = null;
1751
+ context = {};
1752
+ debugEnabled = false;
1753
+ offlineEnabled = false;
1754
+ eventQueue;
1755
+ contextDependentEventQueue;
1756
+ eventUrl = "https://c.schematichq.com";
1757
+ flagCheckListeners = {};
1758
+ flagValueListeners = {};
1759
+ isPending = true;
1760
+ isPendingListeners = /* @__PURE__ */ new Set();
1761
+ planListeners = /* @__PURE__ */ new Set();
1762
+ creditBalanceListeners = /* @__PURE__ */ new Set();
1763
+ storage;
1764
+ persistFlagState = true;
1765
+ flagStateCacheKey;
1766
+ flagStateCacheMaxAgeMs = flagStateCacheDefaultMaxAgeMs;
1767
+ cachedFlagState = null;
1768
+ useWebSocket = false;
1769
+ checks = {};
1770
+ featureUsageEventMap = {};
1771
+ planChecks = {};
1772
+ creditBalances = {};
1773
+ webSocketUrl = "wss://api.schematichq.com";
1774
+ webSocketConnectionTimeout = 1e4;
1775
+ webSocketReconnect = true;
1776
+ webSocketMaxReconnectAttempts = 7;
1777
+ // Max attempts after connection disrupted
1778
+ webSocketMaxConnectionAttempts = 3;
1779
+ // Max attempts for initial connection
1780
+ webSocketInitialRetryDelay = 1e3;
1781
+ webSocketMaxRetryDelay = 3e4;
1782
+ wsReconnectAttempts = 0;
1783
+ wsReconnectTimer = null;
1784
+ wsIntentionalDisconnect = false;
1785
+ currentWebSocket = null;
1786
+ isConnecting = false;
1787
+ maxEventQueueSize = 100;
1788
+ // Prevent memory issues with very long network outages
1789
+ maxEventRetries = 5;
1790
+ // Maximum retry attempts for failed events
1791
+ eventRetryInitialDelay = 1e3;
1792
+ // Initial retry delay in ms
1793
+ eventRetryMaxDelay = 3e4;
1794
+ // Maximum retry delay in ms
1795
+ retryTimer = null;
1796
+ flagValueDefaults = {};
1797
+ flagCheckDefaults = {};
1798
+ fallbackCheckCache = {};
1799
+ constructor(apiKey, options) {
1800
+ this.apiKey = apiKey;
1801
+ this.flagStateCacheKey = `${flagStateCachePrefix}:${apiKey}`;
1802
+ this.eventQueue = [];
1803
+ this.contextDependentEventQueue = [];
1804
+ this.useWebSocket = options?.useWebSocket ?? false;
1805
+ this.debugEnabled = options?.debug ?? false;
1806
+ this.offlineEnabled = options?.offline ?? false;
1807
+ this.persistFlagState = options?.persistFlagState ?? true;
1808
+ if (typeof options?.flagStateCacheMaxAgeMs === "number" && options.flagStateCacheMaxAgeMs > 0) {
1809
+ this.flagStateCacheMaxAgeMs = options.flagStateCacheMaxAgeMs;
1810
+ }
1811
+ if (typeof window !== "undefined" && typeof window.location !== "undefined") {
1812
+ const params = new URLSearchParams(window.location.search);
1813
+ const debugParam = params.get("schematic_debug");
1814
+ if (debugParam !== null && (debugParam === "" || debugParam === "true" || debugParam === "1")) {
1815
+ this.debugEnabled = true;
1816
+ }
1817
+ const offlineParam = params.get("schematic_offline");
1818
+ if (offlineParam !== null && (offlineParam === "" || offlineParam === "true" || offlineParam === "1")) {
1819
+ this.offlineEnabled = true;
1820
+ this.debugEnabled = true;
1821
+ }
1822
+ }
1823
+ if (this.offlineEnabled && options?.debug !== false) {
1824
+ this.debugEnabled = true;
1825
+ }
1826
+ if (this.offlineEnabled) {
1827
+ this.setIsPending(false);
1828
+ }
1829
+ this._additionalHeaders = {
1830
+ "X-Schematic-Client-Version": `schematic-js@${version}`,
1831
+ ...options?.additionalHeaders ?? {}
1832
+ };
1833
+ if (options?.storage) {
1834
+ this.storage = options.storage;
1835
+ } else {
1836
+ try {
1837
+ if (typeof localStorage !== "undefined") {
1838
+ this.storage = localStorage;
1839
+ }
1840
+ } catch {
1841
+ }
1842
+ }
1843
+ this.hydrateFlagStateFromCache();
1844
+ if (options?.apiUrl !== void 0) {
1845
+ this._apiUrl = options.apiUrl;
1846
+ }
1847
+ if (options?.eventUrl !== void 0) {
1848
+ this.eventUrl = options.eventUrl;
1849
+ }
1850
+ if (options?.webSocketUrl !== void 0) {
1851
+ this.webSocketUrl = options.webSocketUrl;
1852
+ }
1853
+ if (options?.webSocketConnectionTimeout !== void 0) {
1854
+ this.webSocketConnectionTimeout = options.webSocketConnectionTimeout;
1855
+ }
1856
+ if (options?.webSocketReconnect !== void 0) {
1857
+ this.webSocketReconnect = options.webSocketReconnect;
1858
+ }
1859
+ if (options?.webSocketMaxReconnectAttempts !== void 0) {
1860
+ this.webSocketMaxReconnectAttempts = options.webSocketMaxReconnectAttempts;
1861
+ }
1862
+ if (options?.webSocketInitialRetryDelay !== void 0) {
1863
+ this.webSocketInitialRetryDelay = options.webSocketInitialRetryDelay;
1864
+ }
1865
+ if (options?.webSocketMaxRetryDelay !== void 0) {
1866
+ this.webSocketMaxRetryDelay = options.webSocketMaxRetryDelay;
1867
+ }
1868
+ if (options?.maxEventQueueSize !== void 0) {
1869
+ this.maxEventQueueSize = options.maxEventQueueSize;
1870
+ }
1871
+ if (options?.maxEventRetries !== void 0) {
1872
+ this.maxEventRetries = options.maxEventRetries;
1873
+ }
1874
+ if (options?.eventRetryInitialDelay !== void 0) {
1875
+ this.eventRetryInitialDelay = options.eventRetryInitialDelay;
1876
+ }
1877
+ if (options?.eventRetryMaxDelay !== void 0) {
1878
+ this.eventRetryMaxDelay = options.eventRetryMaxDelay;
1879
+ }
1880
+ if (options?.flagValueDefaults !== void 0) {
1881
+ this.flagValueDefaults = options.flagValueDefaults;
1882
+ }
1883
+ if (options?.flagCheckDefaults !== void 0) {
1884
+ this.flagCheckDefaults = options.flagCheckDefaults;
1885
+ }
1886
+ if (typeof window !== "undefined" && window?.addEventListener) {
1887
+ window.addEventListener("beforeunload", () => {
1888
+ this.flushEventQueue();
1889
+ this.flushContextDependentEventQueue();
1890
+ });
1891
+ if (this.useWebSocket) {
1892
+ window.addEventListener("offline", () => {
1015
1893
  this.debug("Browser went offline, closing WebSocket connection");
1016
1894
  this.handleNetworkOffline();
1017
1895
  });
@@ -1029,6 +1907,12 @@ var Schematic = class {
1029
1907
  this.debug("Initialized with debug mode enabled");
1030
1908
  }
1031
1909
  }
1910
+ get apiUrl() {
1911
+ return this._apiUrl;
1912
+ }
1913
+ get additionalHeaders() {
1914
+ return { ...this._additionalHeaders };
1915
+ }
1032
1916
  /**
1033
1917
  * Resolve fallback value according to priority order:
1034
1918
  * 1. Callsite fallback value (if provided)
@@ -1114,11 +1998,11 @@ var Schematic = class {
1114
1998
  return resolvedFallbackResult.value;
1115
1999
  }
1116
2000
  if (!this.useWebSocket) {
1117
- const requestUrl = `${this.apiUrl}/flags/${key}/check`;
2001
+ const requestUrl = `${this._apiUrl}/flags/${key}/check`;
1118
2002
  return fetch(requestUrl, {
1119
2003
  method: "POST",
1120
2004
  headers: {
1121
- ...this.additionalHeaders ?? {},
2005
+ ...this._additionalHeaders ?? {},
1122
2006
  "Content-Type": "application/json;charset=UTF-8",
1123
2007
  "X-Schematic-Api-Key": this.apiKey
1124
2008
  },
@@ -1256,6 +2140,29 @@ var Schematic = class {
1256
2140
  });
1257
2141
  this.notifyPlanListeners(plan);
1258
2142
  }
2143
+ if (message.credit_balances !== void 0 && message.credit_balances !== null) {
2144
+ const contextStr = contextString(context);
2145
+ const updates = CreditBalancesFromJSON(message.credit_balances);
2146
+ const previous = this.creditBalances[contextStr] ?? {};
2147
+ let changed = false;
2148
+ const merged = { ...previous };
2149
+ for (const [creditId, next] of Object.entries(updates)) {
2150
+ const current = previous[creditId];
2151
+ if (current === void 0 || current.remaining !== next.remaining || current.reserved !== next.reserved || current.settled !== next.settled) {
2152
+ merged[creditId] = next;
2153
+ changed = true;
2154
+ }
2155
+ }
2156
+ if (changed) {
2157
+ this.creditBalances[contextStr] = merged;
2158
+ this.debug(
2159
+ `WebSocket credit balance update received. Notifying listeners`,
2160
+ { creditBalances: merged }
2161
+ );
2162
+ this.notifyCreditBalanceListeners(merged);
2163
+ }
2164
+ }
2165
+ this.persistContextToCache(contextString(context));
1259
2166
  this.flushContextDependentEventQueue();
1260
2167
  this.setIsPending(false);
1261
2168
  };
@@ -1298,12 +2205,12 @@ var Schematic = class {
1298
2205
  this.debug(`checkFlags offline result: returning empty object`);
1299
2206
  return {};
1300
2207
  }
1301
- const requestUrl = `${this.apiUrl}/flags/check`;
2208
+ const requestUrl = `${this._apiUrl}/flags/check`;
1302
2209
  const requestBody = JSON.stringify(context);
1303
2210
  return fetch(requestUrl, {
1304
2211
  method: "POST",
1305
2212
  headers: {
1306
- ...this.additionalHeaders ?? {},
2213
+ ...this._additionalHeaders ?? {},
1307
2214
  "Content-Type": "application/json;charset=UTF-8",
1308
2215
  "X-Schematic-Api-Key": this.apiKey
1309
2216
  },
@@ -1363,7 +2270,32 @@ var Schematic = class {
1363
2270
  return;
1364
2271
  }
1365
2272
  try {
1366
- this.setIsPending(true);
2273
+ const newContextStr = contextString(context);
2274
+ const cacheHit = this.hasCachedValuesForContext(newContextStr);
2275
+ if (cacheHit) {
2276
+ this.debug(
2277
+ `setContext: cache hit for context, pre-resolving from cached values`
2278
+ );
2279
+ this.context = context;
2280
+ this.flushContextDependentEventQueue();
2281
+ this.setIsPending(false);
2282
+ const cachedChecks = this.checks[newContextStr] ?? {};
2283
+ for (const [flagKey, check] of Object.entries(cachedChecks)) {
2284
+ if (check === void 0) continue;
2285
+ this.notifyFlagCheckListeners(flagKey, check);
2286
+ this.notifyFlagValueListeners(flagKey, check.value);
2287
+ }
2288
+ const cachedPlan = this.planChecks[newContextStr];
2289
+ if (cachedPlan !== void 0) {
2290
+ this.notifyPlanListeners(cachedPlan);
2291
+ }
2292
+ const cachedCreditBalances = this.creditBalances[newContextStr];
2293
+ if (cachedCreditBalances !== void 0) {
2294
+ this.notifyCreditBalanceListeners(cachedCreditBalances);
2295
+ }
2296
+ } else {
2297
+ this.setIsPending(true);
2298
+ }
1367
2299
  if (!this.conn) {
1368
2300
  if (this.isConnecting) {
1369
2301
  this.debug(
@@ -1374,7 +2306,7 @@ var Schematic = class {
1374
2306
  }
1375
2307
  if (this.conn !== null) {
1376
2308
  const socket2 = await this.conn;
1377
- await this.wsSendMessage(socket2, context);
2309
+ await this.wsSendMessage(socket2, context, cacheHit);
1378
2310
  return;
1379
2311
  }
1380
2312
  }
@@ -1390,7 +2322,7 @@ var Schematic = class {
1390
2322
  this.conn = this.wsConnect();
1391
2323
  const socket2 = await this.conn;
1392
2324
  this.isConnecting = false;
1393
- await this.wsSendMessage(socket2, context);
2325
+ await this.wsSendMessage(socket2, context, cacheHit);
1394
2326
  return;
1395
2327
  } catch (error) {
1396
2328
  this.isConnecting = false;
@@ -1398,7 +2330,7 @@ var Schematic = class {
1398
2330
  }
1399
2331
  }
1400
2332
  const socket = await this.conn;
1401
- await this.wsSendMessage(socket, context);
2333
+ await this.wsSendMessage(socket, context, cacheHit);
1402
2334
  } catch (error) {
1403
2335
  console.warn("Failed to establish WebSocket connection:", error);
1404
2336
  this.context = context;
@@ -1458,10 +2390,12 @@ var Schematic = class {
1458
2390
  `Optimistically updating feature usage for event: ${eventName}`,
1459
2391
  { quantity }
1460
2392
  );
2393
+ let dirty = false;
1461
2394
  Object.entries(flagsForEvent).forEach(([flagKey, check]) => {
1462
2395
  if (check === void 0) return;
1463
2396
  const updatedCheck = { ...check };
1464
2397
  if (typeof updatedCheck.featureUsage === "number") {
2398
+ dirty = true;
1465
2399
  updatedCheck.featureUsage += quantity;
1466
2400
  if (typeof updatedCheck.featureAllocation === "number") {
1467
2401
  const wasExceeded = updatedCheck.featureUsageExceeded === true;
@@ -1491,6 +2425,9 @@ var Schematic = class {
1491
2425
  this.notifyFlagValueListeners(flagKey, updatedCheck.value);
1492
2426
  }
1493
2427
  });
2428
+ if (dirty) {
2429
+ this.persistContextToCache(contextString(this.context));
2430
+ }
1494
2431
  };
1495
2432
  /**
1496
2433
  * Event processing
@@ -1593,6 +2530,206 @@ var Schematic = class {
1593
2530
  this.storage.setItem(anonymousIdKey, generatedAnonymousId);
1594
2531
  return generatedAnonymousId;
1595
2532
  };
2533
+ hasCachedValuesForContext = (contextStr) => {
2534
+ const cachedChecks = this.checks[contextStr];
2535
+ if (cachedChecks !== void 0 && Object.keys(cachedChecks).length > 0) {
2536
+ return true;
2537
+ }
2538
+ if (this.planChecks[contextStr] !== void 0) {
2539
+ return true;
2540
+ }
2541
+ const cachedCreditBalances = this.creditBalances[contextStr];
2542
+ return cachedCreditBalances !== void 0 && Object.keys(cachedCreditBalances).length > 0;
2543
+ };
2544
+ readFlagStateCache = () => {
2545
+ if (!this.persistFlagState || this.storage === void 0) {
2546
+ return null;
2547
+ }
2548
+ let raw;
2549
+ try {
2550
+ raw = this.storage.getItem(this.flagStateCacheKey);
2551
+ } catch (error) {
2552
+ this.debug("Failed to read flag state cache:", error);
2553
+ return null;
2554
+ }
2555
+ if (raw === null || raw === void 0) {
2556
+ return null;
2557
+ }
2558
+ let parsed;
2559
+ try {
2560
+ parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
2561
+ } catch (error) {
2562
+ this.debug("Flag state cache is not valid JSON, ignoring:", error);
2563
+ return null;
2564
+ }
2565
+ if (parsed === null || typeof parsed !== "object" || parsed.version !== cacheVersion || typeof parsed.contexts !== "object" || parsed.contexts === null) {
2566
+ this.debug(
2567
+ `Flag state cache invalid or version mismatch (expected ${cacheVersion}), ignoring`,
2568
+ {
2569
+ parsedType: typeof parsed,
2570
+ version: parsed?.version
2571
+ }
2572
+ );
2573
+ return null;
2574
+ }
2575
+ return parsed;
2576
+ };
2577
+ reviveCachedCheck = (raw) => {
2578
+ if (raw === null || typeof raw !== "object") return null;
2579
+ const obj = raw;
2580
+ if (typeof obj.flag !== "string" || typeof obj.value !== "boolean" || typeof obj.reason !== "string") {
2581
+ return null;
2582
+ }
2583
+ const revived = { ...obj };
2584
+ if (typeof revived.featureUsageResetAt === "string") {
2585
+ const d = new Date(revived.featureUsageResetAt);
2586
+ revived.featureUsageResetAt = isNaN(d.getTime()) ? void 0 : d;
2587
+ }
2588
+ return revived;
2589
+ };
2590
+ reviveCachedPlan = (raw) => {
2591
+ if (raw === null || typeof raw !== "object") return null;
2592
+ const obj = raw;
2593
+ if (typeof obj.id !== "string" || typeof obj.name !== "string") {
2594
+ return null;
2595
+ }
2596
+ const revived = { ...obj };
2597
+ if (typeof revived.trialEndDate === "string") {
2598
+ const d = new Date(revived.trialEndDate);
2599
+ revived.trialEndDate = isNaN(d.getTime()) ? void 0 : d;
2600
+ }
2601
+ return revived;
2602
+ };
2603
+ reviveCachedCreditBalances = (raw) => {
2604
+ if (raw === null || typeof raw !== "object") return null;
2605
+ const revived = {};
2606
+ for (const [creditId, value] of Object.entries(
2607
+ raw
2608
+ )) {
2609
+ if (value === null || typeof value !== "object") continue;
2610
+ const obj = value;
2611
+ if (typeof obj.remaining !== "number" || typeof obj.reserved !== "number" || typeof obj.settled !== "number") {
2612
+ continue;
2613
+ }
2614
+ revived[creditId] = {
2615
+ remaining: obj.remaining,
2616
+ reserved: obj.reserved,
2617
+ settled: obj.settled
2618
+ };
2619
+ }
2620
+ return Object.keys(revived).length > 0 ? revived : null;
2621
+ };
2622
+ hydrateFlagStateFromCache = () => {
2623
+ const parsed = this.readFlagStateCache();
2624
+ if (parsed === null) return;
2625
+ const cutoff = Date.now() - this.flagStateCacheMaxAgeMs;
2626
+ const fresh = {};
2627
+ let contextsLoaded = 0;
2628
+ let contextsExpired = 0;
2629
+ for (const [contextStr, entry] of Object.entries(parsed.contexts)) {
2630
+ if (entry === null || typeof entry !== "object") continue;
2631
+ if (typeof entry.updatedAt !== "number") continue;
2632
+ if (entry.updatedAt < cutoff) {
2633
+ contextsExpired += 1;
2634
+ continue;
2635
+ }
2636
+ const revivedChecks = {};
2637
+ if (entry.checks !== null && typeof entry.checks === "object") {
2638
+ for (const [flagKey, rawCheck] of Object.entries(entry.checks)) {
2639
+ const revived = this.reviveCachedCheck(rawCheck);
2640
+ if (revived === null || revived.flag !== flagKey) continue;
2641
+ revivedChecks[flagKey] = revived;
2642
+ if (typeof revived.featureUsageEvent === "string") {
2643
+ this.updateFeatureUsageEventMap(revived);
2644
+ }
2645
+ }
2646
+ if (Object.keys(revivedChecks).length > 0) {
2647
+ this.checks[contextStr] = revivedChecks;
2648
+ }
2649
+ }
2650
+ let revivedPlan;
2651
+ if (entry.plan !== void 0 && entry.plan !== null) {
2652
+ const p = this.reviveCachedPlan(entry.plan);
2653
+ if (p !== null) {
2654
+ revivedPlan = p;
2655
+ this.planChecks[contextStr] = p;
2656
+ }
2657
+ }
2658
+ let revivedCreditBalances;
2659
+ if (entry.creditBalances !== void 0 && entry.creditBalances !== null) {
2660
+ const balances = this.reviveCachedCreditBalances(entry.creditBalances);
2661
+ if (balances !== null) {
2662
+ revivedCreditBalances = balances;
2663
+ this.creditBalances[contextStr] = balances;
2664
+ }
2665
+ }
2666
+ fresh[contextStr] = {
2667
+ checks: revivedChecks,
2668
+ plan: revivedPlan,
2669
+ creditBalances: revivedCreditBalances,
2670
+ updatedAt: entry.updatedAt
2671
+ };
2672
+ contextsLoaded += 1;
2673
+ }
2674
+ this.cachedFlagState = { version: cacheVersion, contexts: fresh };
2675
+ this.debug(
2676
+ `Hydrated flag state cache: ${contextsLoaded} loaded, ${contextsExpired} expired`
2677
+ );
2678
+ };
2679
+ persistContextToCache = (contextStr) => {
2680
+ const checksForContext = this.checks[contextStr];
2681
+ const planForContext = this.planChecks[contextStr];
2682
+ const creditBalancesForContext = this.creditBalances[contextStr];
2683
+ if (checksForContext === void 0 && planForContext === void 0 && creditBalancesForContext === void 0) {
2684
+ return;
2685
+ }
2686
+ if (this.cachedFlagState === null) {
2687
+ this.cachedFlagState = (this.persistFlagState ? this.readFlagStateCache() : null) ?? {
2688
+ version: cacheVersion,
2689
+ contexts: {}
2690
+ };
2691
+ }
2692
+ const cleanChecks = {};
2693
+ if (checksForContext !== void 0) {
2694
+ for (const [flagKey, check] of Object.entries(checksForContext)) {
2695
+ if (check !== void 0) cleanChecks[flagKey] = check;
2696
+ }
2697
+ }
2698
+ this.cachedFlagState.contexts[contextStr] = {
2699
+ checks: cleanChecks,
2700
+ plan: planForContext,
2701
+ creditBalances: creditBalancesForContext,
2702
+ updatedAt: Date.now()
2703
+ };
2704
+ const entries = Object.entries(this.cachedFlagState.contexts);
2705
+ if (entries.length > flagStateCacheMaxContexts) {
2706
+ entries.sort((a, b) => b[1].updatedAt - a[1].updatedAt);
2707
+ const survivors = entries.slice(0, flagStateCacheMaxContexts);
2708
+ const survivorKeys = new Set(survivors.map(([k]) => k));
2709
+ for (const key of Object.keys(this.checks)) {
2710
+ if (!survivorKeys.has(key)) delete this.checks[key];
2711
+ }
2712
+ for (const key of Object.keys(this.planChecks)) {
2713
+ if (!survivorKeys.has(key)) delete this.planChecks[key];
2714
+ }
2715
+ for (const key of Object.keys(this.creditBalances)) {
2716
+ if (!survivorKeys.has(key)) delete this.creditBalances[key];
2717
+ }
2718
+ this.cachedFlagState = {
2719
+ version: cacheVersion,
2720
+ contexts: Object.fromEntries(survivors)
2721
+ };
2722
+ }
2723
+ if (!this.persistFlagState || this.storage === void 0) return;
2724
+ try {
2725
+ this.storage.setItem(
2726
+ this.flagStateCacheKey,
2727
+ JSON.stringify(this.cachedFlagState)
2728
+ );
2729
+ } catch (error) {
2730
+ this.debug("Failed to write flag state cache:", error);
2731
+ }
2732
+ };
1596
2733
  handleEvent = (eventType, eventBody) => {
1597
2734
  const event = {
1598
2735
  api_key: this.apiKey,
@@ -1620,7 +2757,7 @@ var Schematic = class {
1620
2757
  const response = await fetch(captureUrl, {
1621
2758
  method: "POST",
1622
2759
  headers: {
1623
- ...this.additionalHeaders ?? {},
2760
+ ...this._additionalHeaders ?? {},
1624
2761
  "Content-Type": "application/json;charset=UTF-8"
1625
2762
  },
1626
2763
  body: payload
@@ -1853,6 +2990,9 @@ var Schematic = class {
1853
2990
  if (this.conn !== null) {
1854
2991
  try {
1855
2992
  const socket = await this.conn;
2993
+ if (this.currentWebSocket === socket) {
2994
+ this.currentWebSocket = null;
2995
+ }
1856
2996
  if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
1857
2997
  socket.close();
1858
2998
  }
@@ -2018,6 +3158,7 @@ var Schematic = class {
2018
3158
  this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
2019
3159
  let timeoutId = null;
2020
3160
  let isResolved = false;
3161
+ let didOpen = false;
2021
3162
  timeoutId = setTimeout(() => {
2022
3163
  if (!isResolved) {
2023
3164
  isResolved = true;
@@ -2031,6 +3172,7 @@ var Schematic = class {
2031
3172
  webSocket.onopen = () => {
2032
3173
  if (isResolved) return;
2033
3174
  isResolved = true;
3175
+ didOpen = true;
2034
3176
  if (timeoutId !== null) {
2035
3177
  clearTimeout(timeoutId);
2036
3178
  }
@@ -2056,11 +3198,12 @@ var Schematic = class {
2056
3198
  }
2057
3199
  this.debug(`WebSocket connection ${connectionId} closed`);
2058
3200
  this.conn = null;
3201
+ const wasCurrent = this.currentWebSocket === webSocket;
2059
3202
  if (this.currentWebSocket === webSocket) {
2060
3203
  this.currentWebSocket = null;
2061
3204
  this.isConnecting = false;
2062
3205
  }
2063
- if (!isResolved && !this.wsIntentionalDisconnect && this.webSocketReconnect) {
3206
+ if (wasCurrent && didOpen && !this.wsIntentionalDisconnect && this.webSocketReconnect) {
2064
3207
  this.attemptReconnect();
2065
3208
  }
2066
3209
  };
@@ -2110,7 +3253,7 @@ var Schematic = class {
2110
3253
  }
2111
3254
  });
2112
3255
  this.currentWebSocket = socket;
2113
- const clientVersion = this.additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
3256
+ const clientVersion = this._additionalHeaders["X-Schematic-Client-Version"] ?? `schematic-js@${version}`;
2114
3257
  const messagePayload = {
2115
3258
  apiKey: this.apiKey,
2116
3259
  clientVersion,
@@ -2156,6 +3299,16 @@ var Schematic = class {
2156
3299
  const plan = this.planChecks[contextStr];
2157
3300
  return plan;
2158
3301
  };
3302
+ /** Get the company's lease-aware credit balances for the current context, keyed by credit ID */
3303
+ getCreditBalances = () => {
3304
+ const contextStr = contextString(this.context);
3305
+ return this.creditBalances[contextStr] ?? emptyCreditBalances;
3306
+ };
3307
+ /** Get the company's lease-aware balance for a single credit type in the current context */
3308
+ getCreditBalance = (creditId) => {
3309
+ const contextStr = contextString(this.context);
3310
+ return this.creditBalances[contextStr]?.[creditId];
3311
+ };
2159
3312
  // flag checks state
2160
3313
  getFlagCheck = (flagKey) => {
2161
3314
  const contextStr = contextString(this.context);
@@ -2215,6 +3368,13 @@ var Schematic = class {
2215
3368
  this.planListeners.delete(listener);
2216
3369
  };
2217
3370
  };
3371
+ /** Register an event listener that will be notified with the company's credit balances (keyed by credit ID) whenever they change */
3372
+ addCreditBalanceListener = (listener) => {
3373
+ this.creditBalanceListeners.add(listener);
3374
+ return () => {
3375
+ this.creditBalanceListeners.delete(listener);
3376
+ };
3377
+ };
2218
3378
  notifyFlagCheckListeners = (flagKey, check) => {
2219
3379
  const listeners = this.flagCheckListeners?.[flagKey] ?? [];
2220
3380
  if (listeners.size > 0) {
@@ -2278,6 +3438,17 @@ var Schematic = class {
2278
3438
  });
2279
3439
  });
2280
3440
  };
3441
+ notifyCreditBalanceListeners = (value) => {
3442
+ const listeners = this.creditBalanceListeners ?? [];
3443
+ if (listeners.size > 0) {
3444
+ this.debug(`Notifying ${listeners.size} credit balance listeners`, {
3445
+ value
3446
+ });
3447
+ }
3448
+ listeners.forEach(
3449
+ (listener) => notifyCreditBalanceListener(listener, value)
3450
+ );
3451
+ };
2281
3452
  };
2282
3453
  var notifyPendingListener = (listener, value) => {
2283
3454
  if (listener.length > 0) {
@@ -2307,25 +3478,862 @@ var notifyPlanListener = (listener, value) => {
2307
3478
  listener();
2308
3479
  }
2309
3480
  };
3481
+ var notifyCreditBalanceListener = (listener, value) => {
3482
+ if (listener.length > 0) {
3483
+ listener(value);
3484
+ } else {
3485
+ listener();
3486
+ }
3487
+ };
2310
3488
 
2311
3489
  // src/context/schematic.tsx
2312
- var import_react = __toESM(require("react"));
3490
+ var import_react5 = __toESM(require("react"));
3491
+
3492
+ // src/billing/contract/index.ts
3493
+ function normalizeInvoiceQuery2(query) {
3494
+ return normalizeInvoiceQuery(query).includePending === true ? { includePending: true } : {};
3495
+ }
3496
+
3497
+ // src/billing/store.ts
3498
+ var Resource = class {
3499
+ constructor(_fetcher, initial, _readiness = () => "ready") {
3500
+ this._fetcher = _fetcher;
3501
+ this._readiness = _readiness;
3502
+ this._loaded = initial !== void 0;
3503
+ this._snapshot = {
3504
+ data: initial,
3505
+ error: void 0,
3506
+ isPending: initial === void 0
3507
+ };
3508
+ }
3509
+ _fetcher;
3510
+ _readiness;
3511
+ _snapshot;
3512
+ _listeners = /* @__PURE__ */ new Set();
3513
+ _inflight;
3514
+ _extending;
3515
+ /** A refetch asked for mid-load; runs once that load lands. */
3516
+ _queued;
3517
+ _generation = 0;
3518
+ _loaded = false;
3519
+ get snapshot() {
3520
+ return this._snapshot;
3521
+ }
3522
+ get subscriberCount() {
3523
+ return this._listeners.size;
3524
+ }
3525
+ getSnapshot = () => this._snapshot;
3526
+ subscribe = (listener) => {
3527
+ this._listeners.add(listener);
3528
+ if (!this._loaded && this._inflight === void 0 && this._snapshot.error === void 0) {
3529
+ void this.load();
3530
+ }
3531
+ return () => {
3532
+ this._listeners.delete(listener);
3533
+ };
3534
+ };
3535
+ load() {
3536
+ if (this._inflight !== void 0) {
3537
+ return this._inflight;
3538
+ }
3539
+ const readiness = this._readiness();
3540
+ if (readiness !== "ready") {
3541
+ if (readiness === "never" && this._snapshot.isPending) {
3542
+ this._set({ ...this._snapshot, isPending: false });
3543
+ }
3544
+ return Promise.resolve();
3545
+ }
3546
+ const generation = ++this._generation;
3547
+ this._set({ ...this._snapshot, error: void 0, isPending: true });
3548
+ const run = Promise.resolve().then(() => this._fetcher()).then(
3549
+ (data) => {
3550
+ if (generation !== this._generation) return;
3551
+ this._loaded = true;
3552
+ this._set({ data, error: void 0, isPending: false });
3553
+ },
3554
+ (cause) => {
3555
+ if (generation !== this._generation) return;
3556
+ const error = cause instanceof Error ? cause : new Error(String(cause));
3557
+ this._set({ ...this._snapshot, error, isPending: false });
3558
+ }
3559
+ ).finally(() => {
3560
+ if (generation === this._generation) {
3561
+ this._inflight = void 0;
3562
+ }
3563
+ });
3564
+ this._inflight = run;
3565
+ return run;
3566
+ }
3567
+ /**
3568
+ * Reloads; `data` holds its current value until the response lands. Asked
3569
+ * for mid-load, it runs again after: that request was built before
3570
+ * whatever prompted this one. Refetches queued on one load share the
3571
+ * re-run; a reset, seed, or clear in the meantime supersedes it.
3572
+ */
3573
+ refetch() {
3574
+ if (this._inflight === void 0) {
3575
+ return this.load();
3576
+ }
3577
+ if (this._queued === void 0) {
3578
+ const generation = this._generation;
3579
+ const queued = this._inflight.then(() => {
3580
+ if (this._queued === queued) {
3581
+ this._queued = void 0;
3582
+ }
3583
+ if (generation !== this._generation) {
3584
+ return;
3585
+ }
3586
+ return this.load();
3587
+ });
3588
+ this._queued = queued;
3589
+ }
3590
+ return this._queued;
3591
+ }
3592
+ /**
3593
+ * Pages: `fetcher` receives the current data and returns what replaces it.
3594
+ * Never rejects — a caller that wants the failure reads `snapshot.error`,
3595
+ * and `data` keeps what was already fetched.
3596
+ *
3597
+ * A load already in flight wins: it is about to refresh the window this
3598
+ * would have appended to.
3599
+ */
3600
+ extend(fetcher) {
3601
+ const current = this._snapshot.data;
3602
+ if (current === void 0) {
3603
+ return Promise.resolve();
3604
+ }
3605
+ if (this._readiness() !== "ready") {
3606
+ return Promise.resolve();
3607
+ }
3608
+ if (this._extending !== void 0) {
3609
+ return this._extending;
3610
+ }
3611
+ if (this._inflight !== void 0) {
3612
+ return this._inflight;
3613
+ }
3614
+ const generation = ++this._generation;
3615
+ this._set({ ...this._snapshot, error: void 0, isPending: true });
3616
+ const run = Promise.resolve().then(() => fetcher(current)).then(
3617
+ (data) => {
3618
+ if (generation !== this._generation) return;
3619
+ this._set({ data, error: void 0, isPending: false });
3620
+ },
3621
+ (cause) => {
3622
+ if (generation !== this._generation) return;
3623
+ const error = cause instanceof Error ? cause : new Error(String(cause));
3624
+ this._set({ ...this._snapshot, error, isPending: false });
3625
+ }
3626
+ ).finally(() => {
3627
+ if (this._extending === run) {
3628
+ this._extending = void 0;
3629
+ }
3630
+ });
3631
+ this._extending = run;
3632
+ return run;
3633
+ }
3634
+ seed(data) {
3635
+ this._generation++;
3636
+ this._inflight = void 0;
3637
+ this._extending = void 0;
3638
+ this._queued = void 0;
3639
+ this._loaded = true;
3640
+ this._set({ data, error: void 0, isPending: false });
3641
+ }
3642
+ update(fn) {
3643
+ if (this._snapshot.data === void 0) return;
3644
+ this._generation++;
3645
+ this._inflight = void 0;
3646
+ this._extending = void 0;
3647
+ this._queued = void 0;
3648
+ this._set({ ...this._snapshot, data: fn(this._snapshot.data) });
3649
+ }
3650
+ /**
3651
+ * Forgets everything and stays empty — no data, no error, not pending.
3652
+ * What a resource holds when there is no session to hold it for: a session
3653
+ * ending is not a failure and must not surface as one.
3654
+ */
3655
+ clear() {
3656
+ this._generation++;
3657
+ this._inflight = void 0;
3658
+ this._extending = void 0;
3659
+ this._queued = void 0;
3660
+ this._loaded = false;
3661
+ this._set({ data: void 0, error: void 0, isPending: false });
3662
+ }
3663
+ /** Forgets everything; reloads immediately if anyone is subscribed. */
3664
+ reset() {
3665
+ this._generation++;
3666
+ this._inflight = void 0;
3667
+ this._extending = void 0;
3668
+ this._queued = void 0;
3669
+ this._loaded = false;
3670
+ const readiness = this._readiness();
3671
+ const willLoad = this._listeners.size > 0 && readiness === "ready";
3672
+ this._set({
3673
+ data: void 0,
3674
+ error: void 0,
3675
+ isPending: willLoad || readiness === "waiting"
3676
+ });
3677
+ if (willLoad) {
3678
+ void this.load();
3679
+ }
3680
+ }
3681
+ _set(next) {
3682
+ this._snapshot = next;
3683
+ for (const listener of this._listeners) {
3684
+ listener();
3685
+ }
3686
+ }
3687
+ };
3688
+ function hashKey(params) {
3689
+ return JSON.stringify(canonical(params));
3690
+ }
3691
+ function canonical(value) {
3692
+ if (Array.isArray(value)) {
3693
+ return value.map(canonical);
3694
+ }
3695
+ if (value instanceof Date) {
3696
+ const time = value.getTime();
3697
+ return Number.isNaN(time) ? "Invalid Date" : value.toISOString();
3698
+ }
3699
+ if (value !== null && typeof value === "object") {
3700
+ const out = {};
3701
+ for (const key of Object.keys(value).sort()) {
3702
+ const v = value[key];
3703
+ if (v !== void 0) {
3704
+ out[key] = canonical(v);
3705
+ }
3706
+ }
3707
+ return out;
3708
+ }
3709
+ return value ?? null;
3710
+ }
3711
+ var DEFAULT_EVICTION = { maxIdle: 4 };
3712
+ var KeyedResource = class {
3713
+ constructor(_fetcher, options = {}) {
3714
+ this._fetcher = _fetcher;
3715
+ this._readiness = options.readiness ?? (() => "ready");
3716
+ this._hash = options.hash ?? hashKey;
3717
+ this._eviction = {
3718
+ maxIdle: options.eviction?.maxIdle ?? DEFAULT_EVICTION.maxIdle
3719
+ };
3720
+ }
3721
+ _fetcher;
3722
+ _entries = /* @__PURE__ */ new Map();
3723
+ _hash;
3724
+ _eviction;
3725
+ _readiness;
3726
+ hash(params) {
3727
+ return this._hash(params);
3728
+ }
3729
+ get size() {
3730
+ return this._entries.size;
3731
+ }
3732
+ entries() {
3733
+ return Array.from(this._entries, ([key, e]) => ({ key, ...e }));
3734
+ }
3735
+ get(params) {
3736
+ const key = this._hash(params);
3737
+ const existing = this._entries.get(key);
3738
+ if (existing !== void 0) {
3739
+ this._entries.delete(key);
3740
+ this._entries.set(key, existing);
3741
+ return existing.resource;
3742
+ }
3743
+ const resource = new Resource(
3744
+ () => this._fetcher(params, resource.snapshot.data),
3745
+ void 0,
3746
+ this._readiness
3747
+ );
3748
+ this._entries.set(key, { params, resource });
3749
+ return resource;
3750
+ }
3751
+ has(params) {
3752
+ return this._entries.has(this._hash(params));
3753
+ }
3754
+ subscribe(params, listener) {
3755
+ const resource = this.get(params);
3756
+ const unsubscribe = resource.subscribe(listener);
3757
+ return () => {
3758
+ unsubscribe();
3759
+ this._evict();
3760
+ };
3761
+ }
3762
+ seed(params, data) {
3763
+ this.get(params).seed(data);
3764
+ }
3765
+ evict(params) {
3766
+ return this._entries.delete(this._hash(params));
3767
+ }
3768
+ resetAll() {
3769
+ for (const [key, { resource }] of Array.from(this._entries)) {
3770
+ if (resource.subscriberCount === 0) {
3771
+ this._entries.delete(key);
3772
+ } else {
3773
+ resource.reset();
3774
+ }
3775
+ }
3776
+ }
3777
+ resumeAll() {
3778
+ for (const { resource } of this._entries.values()) {
3779
+ if (resource.subscriberCount === 0) {
3780
+ continue;
3781
+ }
3782
+ const { data, error } = resource.snapshot;
3783
+ if (data === void 0 || error !== void 0) {
3784
+ void resource.load();
3785
+ }
3786
+ }
3787
+ }
3788
+ clearAll() {
3789
+ for (const [key, { resource }] of Array.from(this._entries)) {
3790
+ if (resource.subscriberCount === 0) {
3791
+ this._entries.delete(key);
3792
+ } else {
3793
+ resource.clear();
3794
+ }
3795
+ }
3796
+ }
3797
+ invalidateAll() {
3798
+ for (const { resource } of this._entries.values()) {
3799
+ if (resource.snapshot.data !== void 0) {
3800
+ void resource.refetch();
3801
+ }
3802
+ }
3803
+ }
3804
+ _evict() {
3805
+ const idle = Array.from(this._entries).filter(
3806
+ ([, e]) => e.resource.subscriberCount === 0
3807
+ );
3808
+ let excess = idle.length - this._eviction.maxIdle;
3809
+ for (const [key] of idle) {
3810
+ if (excess <= 0) break;
3811
+ this._entries.delete(key);
3812
+ excess -= 1;
3813
+ }
3814
+ }
3815
+ };
3816
+
3817
+ // src/billing/client.ts
3818
+ function claimOf(client) {
3819
+ return { status: client.sessionStatus, key: client.sessionKey };
3820
+ }
3821
+ function claimFrom(session) {
3822
+ if (session === void 0) {
3823
+ return void 0;
3824
+ }
3825
+ return session === null ? { status: "ended", key: void 0 } : { status: "active", key: sessionKey(session) };
3826
+ }
3827
+ function judgeSeed(seedKey, claim) {
3828
+ if (claim.status === "ended") {
3829
+ return "drop";
3830
+ }
3831
+ if (seedKey === void 0) {
3832
+ return "adopt";
3833
+ }
3834
+ if (claim.status === "pending") {
3835
+ return "hold";
3836
+ }
3837
+ return seedKey === claim.key ? "adopt" : "drop";
3838
+ }
3839
+ var READINESS = {
3840
+ pending: "waiting",
3841
+ active: "ready",
3842
+ ended: "never"
3843
+ };
3844
+ var BillingStore = class {
3845
+ constructor(_client, initialData = {}, options = {}) {
3846
+ this._client = _client;
3847
+ this._pageSize = options.pageSize ?? INVOICE_PAGE_SIZE;
3848
+ this.invoices = new KeyedResource(
3849
+ (query, current) => this._reloadWindow(
3850
+ query,
3851
+ Math.max(this._pageSize, current?.invoices.length ?? 0)
3852
+ ),
3853
+ // Waiting until connected; then a session still pending stays
3854
+ // pending, and one that has ended settles empty rather than recording
3855
+ // the client's refusal as an error.
3856
+ {
3857
+ readiness: () => this._connected ? READINESS[this._client.sessionStatus] : "waiting"
3858
+ }
3859
+ );
3860
+ if (initialData.invoices !== void 0) {
3861
+ this._held = {
3862
+ // Normalized the way the hook normalizes, or the caller asking for
3863
+ // it fetches the same page again.
3864
+ params: normalizeInvoiceQuery2(
3865
+ initialData.params?.invoices ?? DEFAULT_INVOICE_QUERY
3866
+ ),
3867
+ data: initialData.invoices,
3868
+ key: initialData.sessionKey
3869
+ };
3870
+ this._settleSeed(claimFrom(options.session) ?? claimOf(this._client));
3871
+ }
3872
+ }
3873
+ _client;
3874
+ invoices;
3875
+ _pageSize;
3876
+ _unsubscribe;
3877
+ _connected = false;
3878
+ _seeded = false;
3879
+ /** The session that prefetch was for, where it said. */
3880
+ _seedKey;
3881
+ /** A stamped prefetch waiting for a session to check it against. */
3882
+ _held;
3883
+ /**
3884
+ * Not done in the constructor: the provider arms it from an effect, and
3885
+ * React runs an effect, its cleanup, then the effect again under
3886
+ * StrictMode. A subscription made once at construction is torn down by
3887
+ * that cleanup and never comes back, leaving the store deaf to every
3888
+ * session change for the rest of the page.
3889
+ *
3890
+ * Also what opens the store: subscribers who arrived first have been
3891
+ * waiting, and this is the moment the session is known to be installed.
3892
+ */
3893
+ connect() {
3894
+ this._unsubscribe?.();
3895
+ const unsubscribe = this._client.onSessionChange?.((event) => {
3896
+ if (event.type === "ended") {
3897
+ this._held = void 0;
3898
+ this._seeded = false;
3899
+ this.clearAll();
3900
+ } else if (event.type === "changed") {
3901
+ this._held = void 0;
3902
+ this._seeded = false;
3903
+ this.resetAll();
3904
+ } else {
3905
+ this._onStarted();
3906
+ }
3907
+ });
3908
+ this._unsubscribe = unsubscribe;
3909
+ if (!this._connected) {
3910
+ this._connected = true;
3911
+ this._settleSeed(claimOf(this._client));
3912
+ this.resumeAll();
3913
+ }
3914
+ return () => {
3915
+ unsubscribe?.();
3916
+ if (this._unsubscribe === unsubscribe) {
3917
+ this._unsubscribe = void 0;
3918
+ }
3919
+ };
3920
+ }
3921
+ _onStarted() {
3922
+ const claim = claimOf(this._client);
3923
+ this._settleSeed(claim);
3924
+ if (this._seeded && judgeSeed(this._seedKey, claim) !== "adopt") {
3925
+ this._seeded = false;
3926
+ this.resetAll();
3927
+ return;
3928
+ }
3929
+ this._seeded = false;
3930
+ this.resumeAll();
3931
+ }
3932
+ /** Adopts, keeps holding, or drops the prefetch, by what `claim` says. */
3933
+ _settleSeed(claim) {
3934
+ const held = this._held;
3935
+ if (held === void 0) {
3936
+ return;
3937
+ }
3938
+ const verdict = judgeSeed(held.key, claim);
3939
+ if (verdict === "hold") {
3940
+ return;
3941
+ }
3942
+ this._held = void 0;
3943
+ if (verdict === "adopt") {
3944
+ this.invoices.seed(held.params, held.data);
3945
+ this._seedKey = held.key;
3946
+ this._seeded = true;
3947
+ }
3948
+ }
3949
+ resource(name) {
3950
+ return this[name];
3951
+ }
3952
+ /** Forgets every resource; subscribed ones reload, idle ones are dropped. */
3953
+ resetAll() {
3954
+ for (const name of RESOURCE_NAMES) {
3955
+ this[name].resetAll();
3956
+ }
3957
+ }
3958
+ /** Forgets every resource and leaves them empty; nothing reloads. */
3959
+ clearAll() {
3960
+ for (const name of RESOURCE_NAMES) {
3961
+ this[name].clearAll();
3962
+ }
3963
+ }
3964
+ /** Loads every resource that is subscribed and has nothing yet. */
3965
+ resumeAll() {
3966
+ for (const name of RESOURCE_NAMES) {
3967
+ this[name].resumeAll();
3968
+ }
3969
+ }
3970
+ /** Reloads every resource that has been loaded, keeping its current data. */
3971
+ invalidateAll() {
3972
+ for (const name of RESOURCE_NAMES) {
3973
+ this[name].invalidateAll();
3974
+ }
3975
+ }
3976
+ /**
3977
+ * Appends the next page to the invoices list for `query`, when there is
3978
+ * one. A failure records the error on the resource and keeps the rows
3979
+ * already fetched; the returned promise never rejects.
3980
+ */
3981
+ loadMoreInvoices(query = DEFAULT_INVOICE_QUERY) {
3982
+ const resource = this.invoices.get(normalizeInvoiceQuery2(query));
3983
+ if (resource.snapshot.data?.hasMore !== true) {
3984
+ return Promise.resolve();
3985
+ }
3986
+ return resource.extend(async (current) => {
3987
+ const page = await this._fetchInvoices(
3988
+ normalizeInvoiceQuery2(query),
3989
+ current.invoices.length,
3990
+ this._pageSize
3991
+ );
3992
+ const invoices = [...current.invoices, ...page.invoices];
3993
+ return {
3994
+ invoices,
3995
+ count: page.count,
3996
+ // From the count the page came back with, not the one the list was
3997
+ // built on: an invoice finalized between the two requests changes
3998
+ // what is left to load. An empty page ends the list whatever the
3999
+ // count says — the rows and the count are two queries, and a count
4000
+ // that outruns the rows would otherwise leave `hasMore` true for
4001
+ // good, with every further page fetching nothing.
4002
+ hasMore: page.invoices.length > 0 && invoices.length < page.count
4003
+ };
4004
+ });
4005
+ }
4006
+ dispose() {
4007
+ this._unsubscribe?.();
4008
+ }
4009
+ /**
4010
+ * Re-reads the window already loaded, in as many requests as the API's
4011
+ * page cap needs. A window paged past that cap still comes back whole:
4012
+ * asking for it in one request would be a 400, and asking for only the
4013
+ * first page would drop the rest.
4014
+ */
4015
+ async _reloadWindow(query, window2) {
4016
+ const invoices = [];
4017
+ let count = 0;
4018
+ while (invoices.length < window2) {
4019
+ const page = await this._fetchInvoices(
4020
+ query,
4021
+ invoices.length,
4022
+ Math.min(INVOICE_MAX_PAGE_SIZE, window2 - invoices.length)
4023
+ );
4024
+ count = page.count;
4025
+ invoices.push(...page.invoices);
4026
+ if (page.invoices.length === 0 || invoices.length >= count) {
4027
+ break;
4028
+ }
4029
+ }
4030
+ return {
4031
+ invoices,
4032
+ count,
4033
+ hasMore: invoices.length > 0 && invoices.length < count
4034
+ };
4035
+ }
4036
+ /**
4037
+ * Returns what the server said and nothing more: whether there is more to
4038
+ * load depends on the whole window, which only the caller assembling it
4039
+ * knows — `_reloadWindow` across its requests, and `loadMoreInvoices`
4040
+ * across the rows already loaded.
4041
+ */
4042
+ _fetchInvoices(query, offset, limit) {
4043
+ return this._client.fetchInvoices({ ...query, limit, offset });
4044
+ }
4045
+ };
4046
+ var RESOURCE_NAMES = ["invoices"];
4047
+
4048
+ // src/billing/context.tsx
4049
+ var import_react2 = require("react");
4050
+
4051
+ // src/i18n/context.tsx
4052
+ var import_react = require("react");
4053
+ var import_jsx_runtime = require("react/jsx-runtime");
4054
+ var EMPTY = Object.freeze({});
4055
+ var SchematicI18nContext = (0, import_react.createContext)(EMPTY);
4056
+ function SchematicI18nProvider({
4057
+ children,
4058
+ locale,
4059
+ onMissingString,
4060
+ strings,
4061
+ translate
4062
+ }) {
4063
+ const parent = (0, import_react.useContext)(SchematicI18nContext);
4064
+ const value = (0, import_react.useMemo)(
4065
+ () => ({
4066
+ locale: locale ?? parent.locale,
4067
+ translate: translate ?? parent.translate,
4068
+ strings: strings === void 0 ? parent.strings : { ...parent.strings, ...strings },
4069
+ onMissingString: onMissingString ?? parent.onMissingString
4070
+ }),
4071
+ [locale, onMissingString, parent, strings, translate]
4072
+ );
4073
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchematicI18nContext.Provider, { value, children });
4074
+ }
4075
+ function useSchematicI18n() {
4076
+ return (0, import_react.useContext)(SchematicI18nContext);
4077
+ }
4078
+ function useSchematicLocale() {
4079
+ return (0, import_react.useContext)(SchematicI18nContext).locale;
4080
+ }
4081
+ function useSchematicTranslate() {
4082
+ return (0, import_react.useContext)(SchematicI18nContext).translate;
4083
+ }
4084
+ function useSchematicStrings() {
4085
+ return (0, import_react.useContext)(SchematicI18nContext).strings;
4086
+ }
4087
+
4088
+ // src/billing/context.tsx
4089
+ var import_jsx_runtime2 = require("react/jsx-runtime");
4090
+ var BillingDataContext = (0, import_react2.createContext)(
4091
+ void 0
4092
+ );
4093
+ var MISSING_BILLING_SOURCE_MESSAGE = "Schematic billing hooks need a SchematicProvider with a session, billingClient, or initialData, or a BillingDataProvider.";
4094
+ var missingSourceError = new Error(MISSING_BILLING_SOURCE_MESSAGE);
4095
+ var missingHandle = {
4096
+ data: void 0,
4097
+ error: missingSourceError,
4098
+ isPending: false,
4099
+ refetch: () => {
4100
+ }
4101
+ };
4102
+ var missingBillingSource = {
4103
+ subscribe: () => () => {
4104
+ },
4105
+ handle: () => missingHandle,
4106
+ loadMoreInvoices: () => Promise.resolve(),
4107
+ invalidateAll: () => {
4108
+ }
4109
+ };
4110
+ function useBillingDataSource() {
4111
+ return (0, import_react2.useContext)(BillingDataContext) ?? missingBillingSource;
4112
+ }
4113
+ function BillingDataProvider({
4114
+ children,
4115
+ data,
4116
+ locale,
4117
+ onLoadMoreInvoices,
4118
+ onMissingString,
4119
+ onRefetch,
4120
+ status,
4121
+ strings,
4122
+ translate
4123
+ }) {
4124
+ const source = (0, import_react2.useMemo)(() => {
4125
+ const handles = /* @__PURE__ */ new Map();
4126
+ return {
4127
+ subscribe: () => () => {
4128
+ },
4129
+ handle: (name) => {
4130
+ const cached = handles.get(name);
4131
+ if (cached !== void 0) {
4132
+ return cached;
4133
+ }
4134
+ const value = data[name];
4135
+ const override = status?.[name];
4136
+ const handle = {
4137
+ data: value,
4138
+ error: override?.error,
4139
+ isPending: override?.isPending ?? value === void 0,
4140
+ refetch: () => onRefetch?.(name)
4141
+ };
4142
+ handles.set(name, handle);
4143
+ return handle;
4144
+ },
4145
+ loadMoreInvoices: (query) => {
4146
+ onLoadMoreInvoices?.(query);
4147
+ return Promise.resolve();
4148
+ },
4149
+ invalidateAll: () => {
4150
+ }
4151
+ };
4152
+ }, [data, onLoadMoreInvoices, onRefetch, status]);
4153
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BillingDataContext.Provider, { value: source, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
4154
+ SchematicI18nProvider,
4155
+ {
4156
+ locale,
4157
+ strings,
4158
+ translate,
4159
+ onMissingString,
4160
+ children
4161
+ }
4162
+ ) });
4163
+ }
4164
+
4165
+ // src/billing/hooks.ts
4166
+ var import_react3 = require("react");
4167
+ function useStableParams(params) {
4168
+ const key = hashKey(params);
4169
+ const ref = (0, import_react3.useRef)({ key, params });
4170
+ if (ref.current.key !== key) {
4171
+ ref.current = { key, params };
4172
+ }
4173
+ return ref.current.params;
4174
+ }
4175
+ function useBillingResource(name, params) {
4176
+ const source = useBillingDataSource();
4177
+ const subscribe = (0, import_react3.useCallback)(
4178
+ (listener) => source.subscribe(name, params, listener),
4179
+ [name, params, source]
4180
+ );
4181
+ const getSnapshot = (0, import_react3.useCallback)(
4182
+ () => source.handle(name, params),
4183
+ [name, params, source]
4184
+ );
4185
+ return (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
4186
+ }
4187
+ function useInvoices(query = DEFAULT_INVOICE_QUERY) {
4188
+ const source = useBillingDataSource();
4189
+ const params = useStableParams(normalizeInvoiceQuery2(query));
4190
+ const handle = useBillingResource("invoices", params);
4191
+ const loadMore = (0, import_react3.useCallback)(
4192
+ () => source.loadMoreInvoices(params),
4193
+ [params, source]
4194
+ );
4195
+ return (0, import_react3.useMemo)(() => ({ ...handle, loadMore }), [handle, loadMore]);
4196
+ }
4197
+ function useInvalidateBillingData() {
4198
+ return useBillingDataSource().invalidateAll;
4199
+ }
4200
+
4201
+ // src/billing/provider.tsx
4202
+ var import_react4 = require("react");
4203
+ var import_jsx_runtime3 = require("react/jsx-runtime");
4204
+ var SESSION_REPLACED_MESSAGE = "BillingProvider is moving its billingClient from one session to another. A client holds one session: if another provider shares this client, each will keep installing its own and show the other's data. Give each provider its own client.";
4205
+ function BillingProvider({
4206
+ billingClient,
4207
+ children,
4208
+ initialData,
4209
+ locale,
4210
+ onMissingString,
4211
+ session,
4212
+ strings,
4213
+ translate
4214
+ }) {
4215
+ const initialRef = (0, import_react4.useRef)(initialData);
4216
+ const install = () => {
4217
+ billingClient?.setSession?.(session);
4218
+ };
4219
+ (0, import_react4.useEffect)(() => {
4220
+ const claim = billingClient?.sessionKey;
4221
+ if (claim !== void 0 && session !== null && session !== void 0 && claim !== sessionKey(session)) {
4222
+ console.warn(SESSION_REPLACED_MESSAGE);
4223
+ }
4224
+ }, []);
4225
+ (0, import_react4.useEffect)(install);
4226
+ const seedSpent = (0, import_react4.useRef)(false);
4227
+ const store = (0, import_react4.useMemo)(
4228
+ () => billingClient === void 0 ? void 0 : new BillingStore(
4229
+ billingClient,
4230
+ seedSpent.current ? void 0 : initialRef.current,
4231
+ {
4232
+ // What `install` is about to say, so a stamped prefetch is
4233
+ // judged now rather than after the effect.
4234
+ session
4235
+ }
4236
+ ),
4237
+ // `session` is read only when the store is built: the client learns
4238
+ // later sessions through `install`, and the store through the client.
4239
+ [billingClient]
4240
+ );
4241
+ (0, import_react4.useEffect)(() => {
4242
+ if (store !== void 0) {
4243
+ seedSpent.current = true;
4244
+ }
4245
+ }, [store]);
4246
+ (0, import_react4.useEffect)(() => store?.connect(), [store]);
4247
+ const source = (0, import_react4.useMemo)(() => {
4248
+ if (store === void 0) {
4249
+ return initialRef.current === void 0 ? void 0 : staticSource(initialRef.current);
4250
+ }
4251
+ const handles = /* @__PURE__ */ new WeakMap();
4252
+ const handle = (name, params) => {
4253
+ const resource = store.resource(name).get(params);
4254
+ const snapshot = resource.getSnapshot();
4255
+ const cached = handles.get(resource);
4256
+ if (cached !== void 0 && cached.snapshot === snapshot) {
4257
+ return cached.handle;
4258
+ }
4259
+ const next = {
4260
+ ...snapshot,
4261
+ refetch: () => void resource.refetch()
4262
+ };
4263
+ handles.set(resource, { snapshot, handle: next });
4264
+ return next;
4265
+ };
4266
+ return {
4267
+ subscribe: (name, params, listener) => store.resource(name).subscribe(params, listener),
4268
+ handle,
4269
+ loadMoreInvoices: (query) => store.loadMoreInvoices(query),
4270
+ invalidateAll: () => store.invalidateAll()
4271
+ };
4272
+ }, [store]);
4273
+ const localized = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4274
+ SchematicI18nProvider,
4275
+ {
4276
+ locale,
4277
+ strings,
4278
+ translate,
4279
+ onMissingString,
4280
+ children
4281
+ }
4282
+ );
4283
+ if (source === void 0) {
4284
+ return localized;
4285
+ }
4286
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BillingDataContext.Provider, { value: source, children: localized });
4287
+ }
4288
+ function staticSource(data) {
4289
+ const handles = /* @__PURE__ */ new Map();
4290
+ return {
4291
+ subscribe: () => () => {
4292
+ },
4293
+ handle: (name) => {
4294
+ const cached = handles.get(name);
4295
+ if (cached !== void 0) {
4296
+ return cached;
4297
+ }
4298
+ const value = data[name];
4299
+ const next = {
4300
+ data: value,
4301
+ error: void 0,
4302
+ isPending: value === void 0,
4303
+ refetch: () => {
4304
+ }
4305
+ };
4306
+ handles.set(name, next);
4307
+ return next;
4308
+ },
4309
+ loadMoreInvoices: () => Promise.resolve(),
4310
+ invalidateAll: () => {
4311
+ }
4312
+ };
4313
+ }
2313
4314
 
2314
4315
  // src/version.ts
2315
- var version2 = "1.4.1";
4316
+ var version2 = "1.6.0";
2316
4317
 
2317
4318
  // src/context/schematic.tsx
2318
- var import_jsx_runtime = require("react/jsx-runtime");
2319
- var SchematicContext = (0, import_react.createContext)(
4319
+ var import_jsx_runtime4 = require("react/jsx-runtime");
4320
+ var SchematicContext = (0, import_react5.createContext)(
2320
4321
  null
2321
4322
  );
2322
4323
  var SchematicProvider = ({
4324
+ session,
4325
+ billingClient,
2323
4326
  children,
2324
4327
  client: providedClient,
4328
+ initialData,
4329
+ locale,
4330
+ onMissingString,
2325
4331
  publishableKey,
4332
+ strings,
4333
+ translate,
2326
4334
  ...clientOpts
2327
4335
  }) => {
2328
- const initialOptsRef = (0, import_react.useRef)({
4336
+ const initialOptsRef = (0, import_react5.useRef)({
2329
4337
  publishableKey,
2330
4338
  useWebSocket: true,
2331
4339
  additionalHeaders: {
@@ -2333,7 +4341,7 @@ var SchematicProvider = ({
2333
4341
  },
2334
4342
  ...clientOpts
2335
4343
  });
2336
- const client = (0, import_react.useMemo)(() => {
4344
+ const client = (0, import_react5.useMemo)(() => {
2337
4345
  if (providedClient) {
2338
4346
  return providedClient;
2339
4347
  }
@@ -2341,7 +4349,7 @@ var SchematicProvider = ({
2341
4349
  ...initialOptsRef.current
2342
4350
  });
2343
4351
  }, [providedClient]);
2344
- (0, import_react.useEffect)(() => {
4352
+ (0, import_react5.useEffect)(() => {
2345
4353
  return () => {
2346
4354
  if (!providedClient) {
2347
4355
  client.cleanup().catch((error) => {
@@ -2350,16 +4358,46 @@ var SchematicProvider = ({
2350
4358
  }
2351
4359
  };
2352
4360
  }, [client, providedClient]);
2353
- const contextValue = (0, import_react.useMemo)(
4361
+ const contextValue = (0, import_react5.useMemo)(
2354
4362
  () => ({
2355
4363
  client
2356
4364
  }),
2357
4365
  [client]
2358
4366
  );
2359
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SchematicContext.Provider, { value: contextValue, children });
4367
+ const { apiUrl, additionalHeaders } = initialOptsRef.current;
4368
+ const providedApiUrl = providedClient?.apiUrl;
4369
+ const resolvedBillingClient = (0, import_react5.useMemo)(
4370
+ () => billingClient ?? new SchematicBillingClient({
4371
+ session,
4372
+ apiUrl: apiUrl ?? providedApiUrl,
4373
+ // Read as they stand when a client is built: headers do not change
4374
+ // where the token goes, so they are no reason to build one.
4375
+ additionalHeaders: {
4376
+ ...providedClient?.additionalHeaders,
4377
+ ...additionalHeaders
4378
+ }
4379
+ }),
4380
+ // `session` is deliberately absent: `BillingProvider` forwards it through
4381
+ // `setSession`, so a token change resets the billing resources without
4382
+ // rebuilding the client.
4383
+ [additionalHeaders, apiUrl, billingClient, providedApiUrl]
4384
+ );
4385
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SchematicContext.Provider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4386
+ BillingProvider,
4387
+ {
4388
+ billingClient: resolvedBillingClient,
4389
+ initialData,
4390
+ locale,
4391
+ session,
4392
+ strings,
4393
+ translate,
4394
+ onMissingString,
4395
+ children
4396
+ }
4397
+ ) });
2360
4398
  };
2361
4399
  var useSchematic = () => {
2362
- const context = import_react.default.useContext(SchematicContext);
4400
+ const context = import_react5.default.useContext(SchematicContext);
2363
4401
  if (context === null) {
2364
4402
  throw new Error("useSchematic must be used within a SchematicProvider");
2365
4403
  }
@@ -2367,11 +4405,11 @@ var useSchematic = () => {
2367
4405
  };
2368
4406
 
2369
4407
  // src/hooks/schematic.ts
2370
- var import_react2 = require("react");
4408
+ var import_react6 = require("react");
2371
4409
  var useSchematicClient = (opts) => {
2372
4410
  const schematic = useSchematic();
2373
4411
  const { client } = opts ?? {};
2374
- return (0, import_react2.useMemo)(() => {
4412
+ return (0, import_react6.useMemo)(() => {
2375
4413
  if (client) {
2376
4414
  return client;
2377
4415
  }
@@ -2380,7 +4418,7 @@ var useSchematicClient = (opts) => {
2380
4418
  };
2381
4419
  var useSchematicContext = (opts) => {
2382
4420
  const client = useSchematicClient(opts);
2383
- return (0, import_react2.useMemo)(
4421
+ return (0, import_react6.useMemo)(
2384
4422
  () => ({
2385
4423
  setContext: client.setContext.bind(client)
2386
4424
  }),
@@ -2389,33 +4427,33 @@ var useSchematicContext = (opts) => {
2389
4427
  };
2390
4428
  var useSchematicEvents = (opts) => {
2391
4429
  const client = useSchematicClient(opts);
2392
- const track = (0, import_react2.useCallback)(
4430
+ const track = (0, import_react6.useCallback)(
2393
4431
  (...args) => client.track(...args),
2394
4432
  [client]
2395
4433
  );
2396
- const identify = (0, import_react2.useCallback)(
4434
+ const identify = (0, import_react6.useCallback)(
2397
4435
  (...args) => client.identify(...args),
2398
4436
  [client]
2399
4437
  );
2400
- return (0, import_react2.useMemo)(() => ({ track, identify }), [track, identify]);
4438
+ return (0, import_react6.useMemo)(() => ({ track, identify }), [track, identify]);
2401
4439
  };
2402
4440
  var useSchematicFlag = (key, opts) => {
2403
4441
  const client = useSchematicClient(opts);
2404
4442
  const fallback = opts?.fallback ?? false;
2405
- const subscribe = (0, import_react2.useCallback)(
4443
+ const subscribe = (0, import_react6.useCallback)(
2406
4444
  (callback) => client.addFlagValueListener(key, callback),
2407
4445
  [client, key]
2408
4446
  );
2409
- const getSnapshot = (0, import_react2.useCallback)(() => {
4447
+ const getSnapshot = (0, import_react6.useCallback)(() => {
2410
4448
  const value = client.getFlagValue(key);
2411
4449
  return typeof value === "undefined" ? fallback : value;
2412
4450
  }, [client, key, fallback]);
2413
- return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, () => fallback);
4451
+ return (0, import_react6.useSyncExternalStore)(subscribe, getSnapshot, () => fallback);
2414
4452
  };
2415
4453
  var useSchematicEntitlement = (key, opts) => {
2416
4454
  const client = useSchematicClient(opts);
2417
4455
  const fallback = opts?.fallback ?? false;
2418
- const fallbackCheck = (0, import_react2.useMemo)(
4456
+ const fallbackCheck = (0, import_react6.useMemo)(
2419
4457
  () => ({
2420
4458
  flag: key,
2421
4459
  reason: "Fallback",
@@ -2423,20 +4461,20 @@ var useSchematicEntitlement = (key, opts) => {
2423
4461
  }),
2424
4462
  [key, fallback]
2425
4463
  );
2426
- const subscribe = (0, import_react2.useCallback)(
4464
+ const subscribe = (0, import_react6.useCallback)(
2427
4465
  (callback) => client.addFlagCheckListener(key, callback),
2428
4466
  [client, key]
2429
4467
  );
2430
- const getSnapshot = (0, import_react2.useCallback)(() => {
4468
+ const getSnapshot = (0, import_react6.useCallback)(() => {
2431
4469
  const check = client.getFlagCheck(key);
2432
4470
  return check ?? fallbackCheck;
2433
4471
  }, [client, key, fallbackCheck]);
2434
- return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, () => fallbackCheck);
4472
+ return (0, import_react6.useSyncExternalStore)(subscribe, getSnapshot, () => fallbackCheck);
2435
4473
  };
2436
4474
  var useSchematicPlan = (opts) => {
2437
4475
  const client = useSchematicClient(opts);
2438
4476
  const fallback = opts?.fallback;
2439
- const fallbackPlan = (0, import_react2.useMemo)(
4477
+ const fallbackPlan = (0, import_react6.useMemo)(
2440
4478
  () => fallback,
2441
4479
  [
2442
4480
  fallback?.id,
@@ -2445,24 +4483,52 @@ var useSchematicPlan = (opts) => {
2445
4483
  fallback?.trialStatus
2446
4484
  ]
2447
4485
  );
2448
- const subscribe = (0, import_react2.useCallback)(
4486
+ const subscribe = (0, import_react6.useCallback)(
2449
4487
  (callback) => client.addPlanListener(callback),
2450
4488
  [client]
2451
4489
  );
2452
- const getSnapshot = (0, import_react2.useCallback)(() => {
4490
+ const getSnapshot = (0, import_react6.useCallback)(() => {
2453
4491
  const plan = client.getPlan();
2454
4492
  return plan ?? fallbackPlan;
2455
4493
  }, [client, fallbackPlan]);
2456
- return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, () => fallbackPlan);
4494
+ return (0, import_react6.useSyncExternalStore)(subscribe, getSnapshot, () => fallbackPlan);
4495
+ };
4496
+ var useSchematicCreditBalance = (creditId, opts) => {
4497
+ const client = useSchematicClient(opts);
4498
+ const subscribe = (0, import_react6.useCallback)(
4499
+ (callback) => client.addCreditBalanceListener(callback),
4500
+ [client]
4501
+ );
4502
+ const getSnapshot = (0, import_react6.useCallback)(
4503
+ () => creditId === void 0 ? void 0 : client.getCreditBalance(creditId),
4504
+ [client, creditId]
4505
+ );
4506
+ const balance = (0, import_react6.useSyncExternalStore)(subscribe, getSnapshot, () => void 0);
4507
+ const isPendingSubscribe = (0, import_react6.useCallback)(
4508
+ (callback) => client.addIsPendingListener(callback),
4509
+ [client]
4510
+ );
4511
+ const isPending = (0, import_react6.useSyncExternalStore)(
4512
+ isPendingSubscribe,
4513
+ () => client.getIsPending(),
4514
+ () => true
4515
+ );
4516
+ return (0, import_react6.useMemo)(
4517
+ () => ({
4518
+ balance: balance?.settled ?? 0,
4519
+ isLoading: balance === void 0 && isPending
4520
+ }),
4521
+ [balance, isPending]
4522
+ );
2457
4523
  };
2458
4524
  var useSchematicIsPending = (opts) => {
2459
4525
  const client = useSchematicClient(opts);
2460
- const subscribe = (0, import_react2.useCallback)(
4526
+ const subscribe = (0, import_react6.useCallback)(
2461
4527
  (callback) => client.addIsPendingListener(callback),
2462
4528
  [client]
2463
4529
  );
2464
- const getSnapshot = (0, import_react2.useCallback)(() => client.getIsPending(), [client]);
2465
- return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, () => true);
4530
+ const getSnapshot = (0, import_react6.useCallback)(() => client.getIsPending(), [client]);
4531
+ return (0, import_react6.useSyncExternalStore)(subscribe, getSnapshot, () => true);
2466
4532
  };
2467
4533
  /*! Bundled license information:
2468
4534