@schematichq/schematic-js 1.4.0 → 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.
@@ -6,7 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __commonJS = (cb, mod) => function __require() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ try {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ } catch (e) {
12
+ throw mod = 0, e;
13
+ }
10
14
  };
11
15
  var __export = (target, all) => {
12
16
  for (var name in all)
@@ -30,9 +34,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
34
  ));
31
35
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
36
 
33
- // node_modules/cross-fetch/dist/browser-polyfill.js
37
+ // node_modules/.pnpm/cross-fetch@4.1.0/node_modules/cross-fetch/dist/browser-polyfill.js
34
38
  var require_browser_polyfill = __commonJS({
35
- "node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
39
+ "node_modules/.pnpm/cross-fetch@4.1.0/node_modules/cross-fetch/dist/browser-polyfill.js"(exports) {
36
40
  (function(self2) {
37
41
  var irrelevant = (function(exports2) {
38
42
  var g = typeof globalThis !== "undefined" && globalThis || typeof self2 !== "undefined" && self2 || // eslint-disable-next-line no-undef
@@ -573,16 +577,29 @@ __export(index_exports, {
573
577
  CheckFlagReturnFromJSON: () => CheckFlagReturnFromJSON,
574
578
  CheckFlagsResponseFromJSON: () => CheckFlagsResponseFromJSON,
575
579
  CheckPlanReturnFromJSON: () => CheckPlanReturnFromJSON,
580
+ CreditBalancesFromJSON: () => CreditBalancesFromJSON,
581
+ DEFAULT_INVOICE_QUERY: () => DEFAULT_INVOICE_QUERY,
576
582
  DatastreamCompanyPlanFromJSON: () => DatastreamCompanyPlanFromJSON,
577
583
  EventBodyFlagCheckToJSON: () => EventBodyFlagCheckToJSON,
584
+ INVOICE_MAX_PAGE_SIZE: () => INVOICE_MAX_PAGE_SIZE,
585
+ INVOICE_PAGE_SIZE: () => INVOICE_PAGE_SIZE,
586
+ InvoiceStatus: () => InvoiceStatus,
578
587
  RuleType: () => RuleType,
579
588
  Schematic: () => Schematic,
589
+ SchematicApiError: () => SchematicApiError,
590
+ SchematicBillingClient: () => SchematicBillingClient,
591
+ SchematicSession: () => SchematicSession,
592
+ TOKEN_CACHE_SIZE: () => TOKEN_CACHE_SIZE,
580
593
  TrialStatus: () => TrialStatus,
581
- UsagePeriod: () => UsagePeriod
594
+ UsagePeriod: () => UsagePeriod,
595
+ billingApi: () => models_exports,
596
+ fetchBillingData: () => fetchBillingData,
597
+ normalizeInvoiceQuery: () => normalizeInvoiceQuery,
598
+ sessionKey: () => sessionKey
582
599
  });
583
600
  module.exports = __toCommonJS(index_exports);
584
601
 
585
- // node_modules/uuid/dist/stringify.js
602
+ // node_modules/.pnpm/uuid@14.0.2/node_modules/uuid/dist/stringify.js
586
603
  var byteToHex = [];
587
604
  for (let i = 0; i < 256; ++i) {
588
605
  byteToHex.push((i + 256).toString(16).slice(1));
@@ -591,24 +608,19 @@ function unsafeStringify(arr, offset = 0) {
591
608
  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();
592
609
  }
593
610
 
594
- // node_modules/uuid/dist/rng.js
595
- var getRandomValues;
611
+ // node_modules/.pnpm/uuid@14.0.2/node_modules/uuid/dist/rng.js
596
612
  var rnds8 = new Uint8Array(16);
597
613
  function rng() {
598
- if (!getRandomValues) {
599
- if (typeof crypto === "undefined" || !crypto.getRandomValues) {
600
- throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");
601
- }
602
- getRandomValues = crypto.getRandomValues.bind(crypto);
603
- }
604
- return getRandomValues(rnds8);
614
+ return crypto.getRandomValues(rnds8);
605
615
  }
606
616
 
607
- // node_modules/uuid/dist/native.js
608
- var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto);
609
- var native_default = { randomUUID };
610
-
611
- // node_modules/uuid/dist/v4.js
617
+ // node_modules/.pnpm/uuid@14.0.2/node_modules/uuid/dist/v4.js
618
+ function v4(options, buf, offset) {
619
+ if (!buf && !options && crypto.randomUUID) {
620
+ return crypto.randomUUID();
621
+ }
622
+ return _v4(options, buf, offset);
623
+ }
612
624
  function _v4(options, buf, offset) {
613
625
  options = options || {};
614
626
  const rnds = options.random ?? options.rng?.() ?? rng();
@@ -629,12 +641,6 @@ function _v4(options, buf, offset) {
629
641
  }
630
642
  return unsafeStringify(rnds);
631
643
  }
632
- function v4(options, buf, offset) {
633
- if (native_default.randomUUID && !buf && !options) {
634
- return native_default.randomUUID();
635
- }
636
- return _v4(options, buf, offset);
637
- }
638
644
  var v4_default = v4;
639
645
 
640
646
  // src/index.ts
@@ -648,6 +654,36 @@ function EntitlementValueTypeFromJSONTyped(json, ignoreDiscriminator) {
648
654
  return json;
649
655
  }
650
656
 
657
+ // src/types/api/models/MetricPeriodMonthReset.ts
658
+ function MetricPeriodMonthResetFromJSON(json) {
659
+ return MetricPeriodMonthResetFromJSONTyped(json, false);
660
+ }
661
+ function MetricPeriodMonthResetFromJSONTyped(json, ignoreDiscriminator) {
662
+ return json;
663
+ }
664
+
665
+ // src/types/api/models/WarningTier.ts
666
+ function WarningTierFromJSON(json) {
667
+ return WarningTierFromJSONTyped(json, false);
668
+ }
669
+ function WarningTierFromJSONTyped(json, ignoreDiscriminator) {
670
+ if (json == null) {
671
+ return json;
672
+ }
673
+ return {
674
+ key: json["key"],
675
+ value: json["value"]
676
+ };
677
+ }
678
+
679
+ // src/types/api/models/MetricPeriod.ts
680
+ function MetricPeriodFromJSON(json) {
681
+ return MetricPeriodFromJSONTyped(json, false);
682
+ }
683
+ function MetricPeriodFromJSONTyped(json, ignoreDiscriminator) {
684
+ return json;
685
+ }
686
+
651
687
  // src/types/api/models/FeatureEntitlement.ts
652
688
  function FeatureEntitlementFromJSON(json) {
653
689
  return FeatureEntitlementFromJSONTyped(json, false);
@@ -658,22 +694,35 @@ function FeatureEntitlementFromJSONTyped(json, ignoreDiscriminator) {
658
694
  }
659
695
  return {
660
696
  allocation: json["allocation"] == null ? void 0 : json["allocation"],
697
+ consumptionRate: json["consumption_rate"] == null ? void 0 : json["consumption_rate"],
661
698
  creditId: json["credit_id"] == null ? void 0 : json["credit_id"],
662
699
  creditRemaining: json["credit_remaining"] == null ? void 0 : json["credit_remaining"],
700
+ creditReserved: json["credit_reserved"] == null ? void 0 : json["credit_reserved"],
701
+ creditSettled: json["credit_settled"] == null ? void 0 : json["credit_settled"],
663
702
  creditTotal: json["credit_total"] == null ? void 0 : json["credit_total"],
664
703
  creditUsed: json["credit_used"] == null ? void 0 : json["credit_used"],
665
704
  eventName: json["event_name"] == null ? void 0 : json["event_name"],
705
+ eventSubtype: json["event_subtype"] == null ? void 0 : json["event_subtype"],
666
706
  featureId: json["feature_id"],
667
707
  featureKey: json["feature_key"],
668
- metricPeriod: json["metric_period"] == null ? void 0 : json["metric_period"],
708
+ metricPeriod: json["metric_period"] == null ? void 0 : MetricPeriodFromJSON(json["metric_period"]),
669
709
  metricResetAt: json["metric_reset_at"] == null ? void 0 : new Date(json["metric_reset_at"]),
670
- monthReset: json["month_reset"] == null ? void 0 : json["month_reset"],
710
+ monthReset: json["month_reset"] == null ? void 0 : MetricPeriodMonthResetFromJSON(json["month_reset"]),
671
711
  softLimit: json["soft_limit"] == null ? void 0 : json["soft_limit"],
672
712
  usage: json["usage"] == null ? void 0 : json["usage"],
673
- valueType: EntitlementValueTypeFromJSON(json["value_type"])
713
+ valueType: EntitlementValueTypeFromJSON(json["value_type"]),
714
+ warningTiers: json["warning_tiers"] == null ? void 0 : json["warning_tiers"].map(WarningTierFromJSON)
674
715
  };
675
716
  }
676
717
 
718
+ // src/types/api/models/RuleType.ts
719
+ function RuleTypeFromJSON(json) {
720
+ return RuleTypeFromJSONTyped(json, false);
721
+ }
722
+ function RuleTypeFromJSONTyped(json, ignoreDiscriminator) {
723
+ return json;
724
+ }
725
+
677
726
  // src/types/api/models/CheckFlagResponseData.ts
678
727
  function CheckFlagResponseDataFromJSON(json) {
679
728
  return CheckFlagResponseDataFromJSONTyped(json, false);
@@ -689,13 +738,13 @@ function CheckFlagResponseDataFromJSONTyped(json, ignoreDiscriminator) {
689
738
  featureAllocation: json["feature_allocation"] == null ? void 0 : json["feature_allocation"],
690
739
  featureUsage: json["feature_usage"] == null ? void 0 : json["feature_usage"],
691
740
  featureUsageEvent: json["feature_usage_event"] == null ? void 0 : json["feature_usage_event"],
692
- featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : json["feature_usage_period"],
741
+ featureUsagePeriod: json["feature_usage_period"] == null ? void 0 : MetricPeriodFromJSON(json["feature_usage_period"]),
693
742
  featureUsageResetAt: json["feature_usage_reset_at"] == null ? void 0 : new Date(json["feature_usage_reset_at"]),
694
743
  flag: json["flag"],
695
744
  flagId: json["flag_id"] == null ? void 0 : json["flag_id"],
696
745
  reason: json["reason"],
697
746
  ruleId: json["rule_id"] == null ? void 0 : json["rule_id"],
698
- ruleType: json["rule_type"] == null ? void 0 : json["rule_type"],
747
+ ruleType: json["rule_type"] == null ? void 0 : RuleTypeFromJSON(json["rule_type"]),
699
748
  userId: json["user_id"] == null ? void 0 : json["user_id"],
700
749
  value: json["value"]
701
750
  };
@@ -715,6 +764,272 @@ function CheckFlagResponseFromJSONTyped(json, ignoreDiscriminator) {
715
764
  };
716
765
  }
717
766
 
767
+ // src/types/api/runtime.ts
768
+ var BASE_PATH = "https://api.schematichq.com".replace(/\/+$/, "");
769
+ var Configuration = class {
770
+ constructor(configuration = {}) {
771
+ this.configuration = configuration;
772
+ }
773
+ configuration;
774
+ set config(configuration) {
775
+ this.configuration = configuration;
776
+ }
777
+ get basePath() {
778
+ return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH;
779
+ }
780
+ get fetchApi() {
781
+ return this.configuration.fetchApi;
782
+ }
783
+ get middleware() {
784
+ return this.configuration.middleware || [];
785
+ }
786
+ get queryParamsStringify() {
787
+ return this.configuration.queryParamsStringify || querystring;
788
+ }
789
+ get username() {
790
+ return this.configuration.username;
791
+ }
792
+ get password() {
793
+ return this.configuration.password;
794
+ }
795
+ get apiKey() {
796
+ const apiKey = this.configuration.apiKey;
797
+ if (apiKey) {
798
+ return typeof apiKey === "function" ? apiKey : () => apiKey;
799
+ }
800
+ return void 0;
801
+ }
802
+ get accessToken() {
803
+ const accessToken = this.configuration.accessToken;
804
+ if (accessToken) {
805
+ return typeof accessToken === "function" ? accessToken : async () => accessToken;
806
+ }
807
+ return void 0;
808
+ }
809
+ get headers() {
810
+ return this.configuration.headers;
811
+ }
812
+ get credentials() {
813
+ return this.configuration.credentials;
814
+ }
815
+ };
816
+ var DefaultConfig = new Configuration();
817
+ var BaseAPI = class _BaseAPI {
818
+ constructor(configuration = DefaultConfig) {
819
+ this.configuration = configuration;
820
+ this.middleware = configuration.middleware;
821
+ }
822
+ configuration;
823
+ static jsonRegex = new RegExp(
824
+ "^(:?application/json|[^;/ ]+/[^;/ ]+[+]json)[ ]*(:?;.*)?$",
825
+ "i"
826
+ );
827
+ middleware;
828
+ withMiddleware(...middlewares) {
829
+ const next = this.clone();
830
+ next.middleware = next.middleware.concat(...middlewares);
831
+ return next;
832
+ }
833
+ withPreMiddleware(...preMiddlewares) {
834
+ const middlewares = preMiddlewares.map((pre) => ({ pre }));
835
+ return this.withMiddleware(...middlewares);
836
+ }
837
+ withPostMiddleware(...postMiddlewares) {
838
+ const middlewares = postMiddlewares.map((post) => ({ post }));
839
+ return this.withMiddleware(...middlewares);
840
+ }
841
+ /**
842
+ * Check if the given MIME is a JSON MIME.
843
+ * JSON MIME examples:
844
+ * application/json
845
+ * application/json; charset=UTF8
846
+ * APPLICATION/JSON
847
+ * application/vnd.company+json
848
+ * @param mime - MIME (Multipurpose Internet Mail Extensions)
849
+ * @return True if the given MIME is JSON, false otherwise.
850
+ */
851
+ isJsonMime(mime) {
852
+ if (!mime) {
853
+ return false;
854
+ }
855
+ return _BaseAPI.jsonRegex.test(mime);
856
+ }
857
+ async request(context, initOverrides) {
858
+ const { url, init } = await this.createFetchParams(context, initOverrides);
859
+ const response = await this.fetchApi(url, init);
860
+ if (response && response.status >= 200 && response.status < 300) {
861
+ return response;
862
+ }
863
+ throw new ResponseError(response, "Response returned an error code");
864
+ }
865
+ async createFetchParams(context, initOverrides) {
866
+ let url = this.configuration.basePath + context.path;
867
+ if (context.query !== void 0 && Object.keys(context.query).length !== 0) {
868
+ url += "?" + this.configuration.queryParamsStringify(context.query);
869
+ }
870
+ const headers = Object.assign(
871
+ {},
872
+ this.configuration.headers,
873
+ context.headers
874
+ );
875
+ Object.keys(headers).forEach(
876
+ (key) => headers[key] === void 0 ? delete headers[key] : {}
877
+ );
878
+ const initOverrideFn = typeof initOverrides === "function" ? initOverrides : async () => initOverrides;
879
+ const initParams = {
880
+ method: context.method,
881
+ headers,
882
+ body: context.body,
883
+ credentials: this.configuration.credentials
884
+ };
885
+ const overriddenInit = {
886
+ ...initParams,
887
+ ...await initOverrideFn({
888
+ init: initParams,
889
+ context
890
+ })
891
+ };
892
+ let body;
893
+ if (isFormData(overriddenInit.body) || overriddenInit.body instanceof URLSearchParams || isBlob(overriddenInit.body)) {
894
+ body = overriddenInit.body;
895
+ } else if (this.isJsonMime(headers["Content-Type"])) {
896
+ body = JSON.stringify(overriddenInit.body);
897
+ } else {
898
+ body = overriddenInit.body;
899
+ }
900
+ const init = {
901
+ ...overriddenInit,
902
+ body
903
+ };
904
+ return { url, init };
905
+ }
906
+ fetchApi = async (url, init) => {
907
+ let fetchParams = { url, init };
908
+ for (const middleware of this.middleware) {
909
+ if (middleware.pre) {
910
+ fetchParams = await middleware.pre({
911
+ fetch: this.fetchApi,
912
+ ...fetchParams
913
+ }) || fetchParams;
914
+ }
915
+ }
916
+ let response = void 0;
917
+ try {
918
+ response = await (this.configuration.fetchApi || fetch)(
919
+ fetchParams.url,
920
+ fetchParams.init
921
+ );
922
+ } catch (e) {
923
+ for (const middleware of this.middleware) {
924
+ if (middleware.onError) {
925
+ response = await middleware.onError({
926
+ fetch: this.fetchApi,
927
+ url: fetchParams.url,
928
+ init: fetchParams.init,
929
+ error: e,
930
+ response: response ? response.clone() : void 0
931
+ }) || response;
932
+ }
933
+ }
934
+ if (response === void 0) {
935
+ if (e instanceof Error) {
936
+ throw new FetchError(
937
+ e,
938
+ "The request failed and the interceptors did not return an alternative response"
939
+ );
940
+ } else {
941
+ throw e;
942
+ }
943
+ }
944
+ }
945
+ for (const middleware of this.middleware) {
946
+ if (middleware.post) {
947
+ response = await middleware.post({
948
+ fetch: this.fetchApi,
949
+ url: fetchParams.url,
950
+ init: fetchParams.init,
951
+ response: response.clone()
952
+ }) || response;
953
+ }
954
+ }
955
+ return response;
956
+ };
957
+ /**
958
+ * Create a shallow clone of `this` by constructing a new instance
959
+ * and then shallow cloning data members.
960
+ */
961
+ clone() {
962
+ const constructor = this.constructor;
963
+ const next = new constructor(this.configuration);
964
+ next.middleware = this.middleware.slice();
965
+ return next;
966
+ }
967
+ };
968
+ function isBlob(value) {
969
+ return typeof Blob !== "undefined" && value instanceof Blob;
970
+ }
971
+ function isFormData(value) {
972
+ return typeof FormData !== "undefined" && value instanceof FormData;
973
+ }
974
+ var ResponseError = class extends Error {
975
+ constructor(response, msg) {
976
+ super(msg);
977
+ this.response = response;
978
+ }
979
+ response;
980
+ name = "ResponseError";
981
+ };
982
+ var FetchError = class extends Error {
983
+ constructor(cause, msg) {
984
+ super(msg);
985
+ this.cause = cause;
986
+ }
987
+ cause;
988
+ name = "FetchError";
989
+ };
990
+ function querystring(params, prefix = "") {
991
+ return Object.keys(params).map((key) => querystringSingleKey(key, params[key], prefix)).filter((part) => part.length > 0).join("&");
992
+ }
993
+ function querystringSingleKey(key, value, keyPrefix = "") {
994
+ const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key);
995
+ if (value instanceof Array) {
996
+ const multiValue = value.map((singleValue) => encodeURIComponent(String(singleValue))).join(`&${encodeURIComponent(fullKey)}=`);
997
+ return `${encodeURIComponent(fullKey)}=${multiValue}`;
998
+ }
999
+ if (value instanceof Set) {
1000
+ const valueAsArray = Array.from(value);
1001
+ return querystringSingleKey(key, valueAsArray, keyPrefix);
1002
+ }
1003
+ if (value instanceof Date) {
1004
+ return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`;
1005
+ }
1006
+ if (value instanceof Object) {
1007
+ return querystring(value, fullKey);
1008
+ }
1009
+ return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
1010
+ }
1011
+ function mapValues(data, fn) {
1012
+ return Object.keys(data).reduce(
1013
+ (acc, key) => ({ ...acc, [key]: fn(data[key]) }),
1014
+ {}
1015
+ );
1016
+ }
1017
+
1018
+ // src/types/api/models/CompanyCreditBalance.ts
1019
+ function CompanyCreditBalanceFromJSON(json) {
1020
+ return CompanyCreditBalanceFromJSONTyped(json, false);
1021
+ }
1022
+ function CompanyCreditBalanceFromJSONTyped(json, ignoreDiscriminator) {
1023
+ if (json == null) {
1024
+ return json;
1025
+ }
1026
+ return {
1027
+ remaining: json["remaining"],
1028
+ reserved: json["reserved"],
1029
+ settled: json["settled"]
1030
+ };
1031
+ }
1032
+
718
1033
  // src/types/api/models/TrialStatus.ts
719
1034
  var TrialStatus = {
720
1035
  Active: "active",
@@ -753,6 +1068,7 @@ function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
753
1068
  return json;
754
1069
  }
755
1070
  return {
1071
+ creditBalances: json["credit_balances"] == null ? void 0 : mapValues(json["credit_balances"], CompanyCreditBalanceFromJSON),
756
1072
  flags: json["flags"].map(CheckFlagResponseDataFromJSON),
757
1073
  plan: json["plan"] == null ? void 0 : DatastreamCompanyPlanFromJSON(json["plan"])
758
1074
  };
@@ -785,6 +1101,7 @@ function EventBodyFlagCheckToJSONTyped(value, ignoreDiscriminator = false) {
785
1101
  error: value["error"],
786
1102
  flag_id: value["flagId"],
787
1103
  flag_key: value["flagKey"],
1104
+ preflight: value["preflight"],
788
1105
  reason: value["reason"],
789
1106
  req_company: value["reqCompany"],
790
1107
  req_user: value["reqUser"],
@@ -841,7 +1158,10 @@ var CheckFlagReturnFromJSON = (json) => {
841
1158
  return {
842
1159
  featureUsageExceeded,
843
1160
  companyId: companyId == null ? void 0 : companyId,
1161
+ creditId: entitlement?.creditId == null ? void 0 : entitlement.creditId,
844
1162
  creditRemaining: entitlement?.creditRemaining == null ? void 0 : entitlement.creditRemaining,
1163
+ creditReserved: entitlement?.creditReserved == null ? void 0 : entitlement.creditReserved,
1164
+ creditSettled: entitlement?.creditSettled == null ? void 0 : entitlement.creditSettled,
845
1165
  error: error == null ? void 0 : error,
846
1166
  featureAllocation: resolvedAllocation == null ? void 0 : resolvedAllocation,
847
1167
  featureUsage: resolvedUsage == null ? void 0 : resolvedUsage,
@@ -867,6 +1187,24 @@ var CheckPlanReturnFromJSON = (json) => {
867
1187
  trialStatus: trialStatus == null ? void 0 : trialStatus
868
1188
  };
869
1189
  };
1190
+ var CreditBalancesFromJSON = (json) => {
1191
+ if (json == null || typeof json !== "object") {
1192
+ return {};
1193
+ }
1194
+ const balances = {};
1195
+ for (const [creditId, raw] of Object.entries(json)) {
1196
+ if (raw == null || typeof raw !== "object") continue;
1197
+ const { remaining, reserved, settled } = CompanyCreditBalanceFromJSON(raw);
1198
+ if (typeof remaining !== "number" || typeof reserved !== "number" || typeof settled !== "number") {
1199
+ continue;
1200
+ }
1201
+ balances[creditId] = { remaining, reserved, settled };
1202
+ }
1203
+ return balances;
1204
+ };
1205
+
1206
+ // src/cacheVersion.ts
1207
+ var cacheVersion = "bb56e75d";
870
1208
 
871
1209
  // src/utils.ts
872
1210
  function contextString(context) {
@@ -885,10 +1223,597 @@ function contextString(context) {
885
1223
  }
886
1224
 
887
1225
  // src/version.ts
888
- var version = "1.4.0";
1226
+ var version = "1.6.0";
1227
+
1228
+ // src/billing/api/generated/models/index.ts
1229
+ var models_exports = {};
1230
+ __export(models_exports, {
1231
+ ApiErrorFromJSON: () => ApiErrorFromJSON,
1232
+ ApiErrorFromJSONTyped: () => ApiErrorFromJSONTyped,
1233
+ ApiErrorToJSON: () => ApiErrorToJSON,
1234
+ ApiErrorToJSONTyped: () => ApiErrorToJSONTyped,
1235
+ CompanyInvoiceResponseDataFromJSON: () => CompanyInvoiceResponseDataFromJSON,
1236
+ CompanyInvoiceResponseDataFromJSONTyped: () => CompanyInvoiceResponseDataFromJSONTyped,
1237
+ CompanyInvoiceResponseDataToJSON: () => CompanyInvoiceResponseDataToJSON,
1238
+ CompanyInvoiceResponseDataToJSONTyped: () => CompanyInvoiceResponseDataToJSONTyped,
1239
+ CompanyInvoicesResponseDataFromJSON: () => CompanyInvoicesResponseDataFromJSON,
1240
+ CompanyInvoicesResponseDataFromJSONTyped: () => CompanyInvoicesResponseDataFromJSONTyped,
1241
+ CompanyInvoicesResponseDataToJSON: () => CompanyInvoicesResponseDataToJSON,
1242
+ CompanyInvoicesResponseDataToJSONTyped: () => CompanyInvoicesResponseDataToJSONTyped,
1243
+ GetCompanyInvoicesParamsFromJSON: () => GetCompanyInvoicesParamsFromJSON,
1244
+ GetCompanyInvoicesParamsFromJSONTyped: () => GetCompanyInvoicesParamsFromJSONTyped,
1245
+ GetCompanyInvoicesParamsToJSON: () => GetCompanyInvoicesParamsToJSON,
1246
+ GetCompanyInvoicesParamsToJSONTyped: () => GetCompanyInvoicesParamsToJSONTyped,
1247
+ GetCompanyInvoicesResponseFromJSON: () => GetCompanyInvoicesResponseFromJSON,
1248
+ GetCompanyInvoicesResponseFromJSONTyped: () => GetCompanyInvoicesResponseFromJSONTyped,
1249
+ GetCompanyInvoicesResponseToJSON: () => GetCompanyInvoicesResponseToJSON,
1250
+ GetCompanyInvoicesResponseToJSONTyped: () => GetCompanyInvoicesResponseToJSONTyped,
1251
+ InvoiceStatus: () => InvoiceStatus,
1252
+ InvoiceStatusFromJSON: () => InvoiceStatusFromJSON,
1253
+ InvoiceStatusFromJSONTyped: () => InvoiceStatusFromJSONTyped,
1254
+ InvoiceStatusToJSON: () => InvoiceStatusToJSON,
1255
+ InvoiceStatusToJSONTyped: () => InvoiceStatusToJSONTyped,
1256
+ instanceOfApiError: () => instanceOfApiError,
1257
+ instanceOfCompanyInvoiceResponseData: () => instanceOfCompanyInvoiceResponseData,
1258
+ instanceOfCompanyInvoicesResponseData: () => instanceOfCompanyInvoicesResponseData,
1259
+ instanceOfGetCompanyInvoicesParams: () => instanceOfGetCompanyInvoicesParams,
1260
+ instanceOfGetCompanyInvoicesResponse: () => instanceOfGetCompanyInvoicesResponse,
1261
+ instanceOfInvoiceStatus: () => instanceOfInvoiceStatus
1262
+ });
1263
+
1264
+ // src/billing/api/generated/models/ApiError.ts
1265
+ function instanceOfApiError(value) {
1266
+ if (!("error" in value) || value["error"] === void 0) return false;
1267
+ return true;
1268
+ }
1269
+ function ApiErrorFromJSON(json) {
1270
+ return ApiErrorFromJSONTyped(json, false);
1271
+ }
1272
+ function ApiErrorFromJSONTyped(json, ignoreDiscriminator) {
1273
+ if (json == null) {
1274
+ return json;
1275
+ }
1276
+ return {
1277
+ error: json["error"]
1278
+ };
1279
+ }
1280
+ function ApiErrorToJSON(json) {
1281
+ return ApiErrorToJSONTyped(json, false);
1282
+ }
1283
+ function ApiErrorToJSONTyped(value, ignoreDiscriminator = false) {
1284
+ if (value == null) {
1285
+ return value;
1286
+ }
1287
+ return {
1288
+ error: value["error"]
1289
+ };
1290
+ }
1291
+
1292
+ // src/billing/api/generated/models/InvoiceStatus.ts
1293
+ var InvoiceStatus = {
1294
+ Draft: "draft",
1295
+ Open: "open",
1296
+ Paid: "paid",
1297
+ Uncollectible: "uncollectible",
1298
+ Void: "void"
1299
+ };
1300
+ function instanceOfInvoiceStatus(value) {
1301
+ for (const key in InvoiceStatus) {
1302
+ if (Object.prototype.hasOwnProperty.call(InvoiceStatus, key)) {
1303
+ if (InvoiceStatus[key] === value) {
1304
+ return true;
1305
+ }
1306
+ }
1307
+ }
1308
+ return false;
1309
+ }
1310
+ function InvoiceStatusFromJSON(json) {
1311
+ return InvoiceStatusFromJSONTyped(json, false);
1312
+ }
1313
+ function InvoiceStatusFromJSONTyped(json, ignoreDiscriminator) {
1314
+ return json;
1315
+ }
1316
+ function InvoiceStatusToJSON(value) {
1317
+ return value;
1318
+ }
1319
+ function InvoiceStatusToJSONTyped(value, ignoreDiscriminator) {
1320
+ return value;
1321
+ }
1322
+
1323
+ // src/billing/api/generated/models/CompanyInvoiceResponseData.ts
1324
+ function instanceOfCompanyInvoiceResponseData(value) {
1325
+ if (!("amountDue" in value) || value["amountDue"] === void 0) return false;
1326
+ if (!("createdAt" in value) || value["createdAt"] === void 0) return false;
1327
+ if (!("currency" in value) || value["currency"] === void 0) return false;
1328
+ if (!("id" in value) || value["id"] === void 0) return false;
1329
+ return true;
1330
+ }
1331
+ function CompanyInvoiceResponseDataFromJSON(json) {
1332
+ return CompanyInvoiceResponseDataFromJSONTyped(json, false);
1333
+ }
1334
+ function CompanyInvoiceResponseDataFromJSONTyped(json, ignoreDiscriminator) {
1335
+ if (json == null) {
1336
+ return json;
1337
+ }
1338
+ return {
1339
+ amountDue: json["amount_due"],
1340
+ createdAt: new Date(json["created_at"]),
1341
+ currency: json["currency"],
1342
+ dueDate: json["due_date"] == null ? void 0 : new Date(json["due_date"]),
1343
+ id: json["id"],
1344
+ status: json["status"] == null ? void 0 : InvoiceStatusFromJSON(json["status"]),
1345
+ url: json["url"] == null ? void 0 : json["url"]
1346
+ };
1347
+ }
1348
+ function CompanyInvoiceResponseDataToJSON(json) {
1349
+ return CompanyInvoiceResponseDataToJSONTyped(json, false);
1350
+ }
1351
+ function CompanyInvoiceResponseDataToJSONTyped(value, ignoreDiscriminator = false) {
1352
+ if (value == null) {
1353
+ return value;
1354
+ }
1355
+ return {
1356
+ amount_due: value["amountDue"],
1357
+ created_at: value["createdAt"].toISOString(),
1358
+ currency: value["currency"],
1359
+ due_date: value["dueDate"] == null ? void 0 : value["dueDate"].toISOString(),
1360
+ id: value["id"],
1361
+ status: InvoiceStatusToJSON(value["status"]),
1362
+ url: value["url"]
1363
+ };
1364
+ }
1365
+
1366
+ // src/billing/api/generated/models/CompanyInvoicesResponseData.ts
1367
+ function instanceOfCompanyInvoicesResponseData(value) {
1368
+ if (!("count" in value) || value["count"] === void 0) return false;
1369
+ if (!("invoices" in value) || value["invoices"] === void 0) return false;
1370
+ return true;
1371
+ }
1372
+ function CompanyInvoicesResponseDataFromJSON(json) {
1373
+ return CompanyInvoicesResponseDataFromJSONTyped(json, false);
1374
+ }
1375
+ function CompanyInvoicesResponseDataFromJSONTyped(json, ignoreDiscriminator) {
1376
+ if (json == null) {
1377
+ return json;
1378
+ }
1379
+ return {
1380
+ count: json["count"],
1381
+ invoices: json["invoices"].map(
1382
+ CompanyInvoiceResponseDataFromJSON
1383
+ )
1384
+ };
1385
+ }
1386
+ function CompanyInvoicesResponseDataToJSON(json) {
1387
+ return CompanyInvoicesResponseDataToJSONTyped(json, false);
1388
+ }
1389
+ function CompanyInvoicesResponseDataToJSONTyped(value, ignoreDiscriminator = false) {
1390
+ if (value == null) {
1391
+ return value;
1392
+ }
1393
+ return {
1394
+ count: value["count"],
1395
+ invoices: value["invoices"].map(
1396
+ CompanyInvoiceResponseDataToJSON
1397
+ )
1398
+ };
1399
+ }
1400
+
1401
+ // src/billing/api/generated/models/GetCompanyInvoicesParams.ts
1402
+ function instanceOfGetCompanyInvoicesParams(value) {
1403
+ return true;
1404
+ }
1405
+ function GetCompanyInvoicesParamsFromJSON(json) {
1406
+ return GetCompanyInvoicesParamsFromJSONTyped(json, false);
1407
+ }
1408
+ function GetCompanyInvoicesParamsFromJSONTyped(json, ignoreDiscriminator) {
1409
+ if (json == null) {
1410
+ return json;
1411
+ }
1412
+ return {
1413
+ includePending: json["include_pending"] == null ? void 0 : json["include_pending"],
1414
+ limit: json["limit"] == null ? void 0 : json["limit"],
1415
+ offset: json["offset"] == null ? void 0 : json["offset"]
1416
+ };
1417
+ }
1418
+ function GetCompanyInvoicesParamsToJSON(json) {
1419
+ return GetCompanyInvoicesParamsToJSONTyped(json, false);
1420
+ }
1421
+ function GetCompanyInvoicesParamsToJSONTyped(value, ignoreDiscriminator = false) {
1422
+ if (value == null) {
1423
+ return value;
1424
+ }
1425
+ return {
1426
+ include_pending: value["includePending"],
1427
+ limit: value["limit"],
1428
+ offset: value["offset"]
1429
+ };
1430
+ }
1431
+
1432
+ // src/billing/api/generated/models/GetCompanyInvoicesResponse.ts
1433
+ function instanceOfGetCompanyInvoicesResponse(value) {
1434
+ if (!("data" in value) || value["data"] === void 0) return false;
1435
+ if (!("params" in value) || value["params"] === void 0) return false;
1436
+ return true;
1437
+ }
1438
+ function GetCompanyInvoicesResponseFromJSON(json) {
1439
+ return GetCompanyInvoicesResponseFromJSONTyped(json, false);
1440
+ }
1441
+ function GetCompanyInvoicesResponseFromJSONTyped(json, ignoreDiscriminator) {
1442
+ if (json == null) {
1443
+ return json;
1444
+ }
1445
+ return {
1446
+ data: CompanyInvoicesResponseDataFromJSON(json["data"]),
1447
+ params: GetCompanyInvoicesParamsFromJSON(json["params"])
1448
+ };
1449
+ }
1450
+ function GetCompanyInvoicesResponseToJSON(json) {
1451
+ return GetCompanyInvoicesResponseToJSONTyped(json, false);
1452
+ }
1453
+ function GetCompanyInvoicesResponseToJSONTyped(value, ignoreDiscriminator = false) {
1454
+ if (value == null) {
1455
+ return value;
1456
+ }
1457
+ return {
1458
+ data: CompanyInvoicesResponseDataToJSON(value["data"]),
1459
+ params: GetCompanyInvoicesParamsToJSON(value["params"])
1460
+ };
1461
+ }
1462
+
1463
+ // src/billing/contract/invoices.ts
1464
+ var DEFAULT_INVOICE_QUERY = {};
1465
+ function normalizeInvoiceQuery(query) {
1466
+ const { includePending, ...rest } = query;
1467
+ return includePending === true ? { ...rest, includePending: true } : rest;
1468
+ }
1469
+
1470
+ // src/billing/session.ts
1471
+ var SchematicApiError = class extends Error {
1472
+ constructor(status, path, body) {
1473
+ super(
1474
+ typeof body === "object" && body !== null && "error" in body ? String(body.error) : `Request to ${path} failed with status ${status}`
1475
+ );
1476
+ this.status = status;
1477
+ this.path = path;
1478
+ this.body = body;
1479
+ this.name = "SchematicApiError";
1480
+ }
1481
+ status;
1482
+ path;
1483
+ body;
1484
+ };
1485
+ var DEFAULT_API_URL = "https://api.schematichq.com";
1486
+ var TOKEN_CACHE_SIZE = 10;
1487
+ function sessionKey(session) {
1488
+ return JSON.stringify([session.company, session.user ?? null]);
1489
+ }
1490
+ var SchematicSession = class {
1491
+ _apiUrl;
1492
+ _headers;
1493
+ _fetch;
1494
+ _session;
1495
+ _tokens = /* @__PURE__ */ new Map();
1496
+ _resolving = /* @__PURE__ */ new Map();
1497
+ /**
1498
+ * Bumped when the session's identity changes, and only then — the object
1499
+ * itself is replaced on every render. Anything asking "is this still the
1500
+ * session I started under?" counts generations rather than comparing.
1501
+ */
1502
+ _generation = 0;
1503
+ _listeners = /* @__PURE__ */ new Set();
1504
+ constructor(options = {}) {
1505
+ this._session = options.session;
1506
+ this._apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "");
1507
+ this._headers = options.additionalHeaders ?? {};
1508
+ this._fetch = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
1509
+ }
1510
+ get key() {
1511
+ return this._session === null || this._session === void 0 ? void 0 : sessionKey(this._session);
1512
+ }
1513
+ get status() {
1514
+ if (this._session === void 0) {
1515
+ return "pending";
1516
+ }
1517
+ return this._session === null ? "ended" : "active";
1518
+ }
1519
+ /** Only a statement that changes which pair is read counts as a change. */
1520
+ set(session) {
1521
+ if (session === void 0) {
1522
+ return;
1523
+ }
1524
+ const previous = this._session;
1525
+ this._session = session;
1526
+ if (session === null) {
1527
+ if (previous === null) {
1528
+ return;
1529
+ }
1530
+ this._tokens.clear();
1531
+ this._resolving.clear();
1532
+ this._generation += 1;
1533
+ this._emit({ type: "ended" });
1534
+ return;
1535
+ }
1536
+ if (previous !== null && previous !== void 0) {
1537
+ if (previous.company === session.company && (previous.user ?? null) === (session.user ?? null)) {
1538
+ return;
1539
+ }
1540
+ this._generation += 1;
1541
+ this._emit({ type: "changed" });
1542
+ return;
1543
+ }
1544
+ this._generation += 1;
1545
+ this._emit({ type: "started" });
1546
+ }
1547
+ onChange(listener) {
1548
+ this._listeners.add(listener);
1549
+ return () => {
1550
+ this._listeners.delete(listener);
1551
+ };
1552
+ }
1553
+ _emit(event) {
1554
+ for (const listener of this._listeners) {
1555
+ listener(event);
1556
+ }
1557
+ }
1558
+ _cached(key) {
1559
+ const cached = this._tokens.get(key);
1560
+ if (cached === void 0) {
1561
+ return void 0;
1562
+ }
1563
+ if (cached.expiresAt !== void 0 && cached.expiresAt.getTime() <= Date.now()) {
1564
+ this._tokens.delete(key);
1565
+ return void 0;
1566
+ }
1567
+ this._tokens.delete(key);
1568
+ this._tokens.set(key, cached);
1569
+ return cached;
1570
+ }
1571
+ _cache(key, resolved) {
1572
+ this._tokens.delete(key);
1573
+ this._tokens.set(key, resolved);
1574
+ while (this._tokens.size > TOKEN_CACHE_SIZE) {
1575
+ const oldest = this._tokens.keys().next();
1576
+ if (oldest.done === true) {
1577
+ break;
1578
+ }
1579
+ this._tokens.delete(oldest.value);
1580
+ }
1581
+ }
1582
+ /**
1583
+ * The credential for a session, minted if this one is not held. `force`
1584
+ * retires what is held first: the caller is a request the API has just
1585
+ * rejected.
1586
+ */
1587
+ resolveToken(session, force) {
1588
+ const { token } = session;
1589
+ if (typeof token === "string") {
1590
+ return Promise.resolve(token);
1591
+ }
1592
+ const key = sessionKey(session);
1593
+ if (!force) {
1594
+ const cached = this._cached(key);
1595
+ if (cached !== void 0) {
1596
+ return Promise.resolve(cached.token);
1597
+ }
1598
+ } else {
1599
+ this._tokens.delete(key);
1600
+ }
1601
+ const inflight = this._resolving.get(key);
1602
+ if (inflight !== void 0 && inflight.sessionGeneration === this._generation && (!force || inflight.forced)) {
1603
+ return inflight.promise;
1604
+ }
1605
+ const provider = token;
1606
+ const sessionGeneration = this._generation;
1607
+ const owns = () => this._resolving.get(key)?.promise === promise;
1608
+ const promise = Promise.resolve().then(() => provider()).then((result) => {
1609
+ const { resolved, stamped } = asResolvedToken(result, session);
1610
+ if (owns() && (this._generation === sessionGeneration || stamped)) {
1611
+ this._cache(key, resolved);
1612
+ }
1613
+ return resolved.token;
1614
+ }).finally(() => {
1615
+ if (owns()) {
1616
+ this._resolving.delete(key);
1617
+ }
1618
+ });
1619
+ this._resolving.set(key, { promise, forced: force, sessionGeneration });
1620
+ return promise;
1621
+ }
1622
+ /**
1623
+ * Sends a request under the active session's credential, refreshing once
1624
+ * and retrying if the API rejects it. Rejects rather than answering when
1625
+ * the session changes while the request is in flight: the rows would
1626
+ * otherwise come back as the answer to a question asked for another pair.
1627
+ */
1628
+ async request(path, options = {}) {
1629
+ const { method = "GET", body } = options;
1630
+ const send = async (credential2) => this._fetch(`${this._apiUrl}${path}`, {
1631
+ method,
1632
+ headers: {
1633
+ "Accept": "application/json",
1634
+ ...body === void 0 ? {} : { "Content-Type": "application/json" },
1635
+ ...this._headers,
1636
+ "X-Schematic-Api-Key": credential2
1637
+ },
1638
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
1639
+ });
1640
+ const session = this._session;
1641
+ if (session === null) {
1642
+ throw new Error("There is no session to read data for.");
1643
+ }
1644
+ if (session === void 0) {
1645
+ throw new Error("A session is required to read data.");
1646
+ }
1647
+ const provider = session.token;
1648
+ const generation = this._generation;
1649
+ const stillOurs = async (response2) => {
1650
+ if (this._generation === generation) {
1651
+ return;
1652
+ }
1653
+ if (response2 !== void 0) {
1654
+ await discardBody(response2);
1655
+ }
1656
+ throw new Error("The session changed while this request was in flight.");
1657
+ };
1658
+ const credential = await this.resolveToken(session, false);
1659
+ if (this._generation !== generation) {
1660
+ throw new Error("The session changed before this request was sent.");
1661
+ }
1662
+ let response = await send(credential);
1663
+ await stillOurs(response);
1664
+ if (response.status === 401 && typeof provider === "function" && this._generation === generation) {
1665
+ const status = response.status;
1666
+ const rejected = await readBody(response);
1667
+ let retried = false;
1668
+ const current = this._session;
1669
+ if (this._generation === generation && current !== null && current !== void 0) {
1670
+ const refreshed = await this.resolveToken(current, true);
1671
+ if (this._generation === generation) {
1672
+ response = await send(refreshed);
1673
+ await stillOurs(response);
1674
+ retried = true;
1675
+ }
1676
+ }
1677
+ if (!retried) {
1678
+ throw new SchematicApiError(status, path, rejected);
1679
+ }
1680
+ }
1681
+ const parsed = await readBody(response);
1682
+ await stillOurs();
1683
+ if (!response.ok) {
1684
+ throw new SchematicApiError(response.status, path, parsed);
1685
+ }
1686
+ return parsed;
1687
+ }
1688
+ };
1689
+ function asResolvedToken(result, session) {
1690
+ if (typeof result === "string") {
1691
+ return { resolved: { token: result }, stamped: false };
1692
+ }
1693
+ const token = result?.token;
1694
+ if (typeof token !== "string" || token === "") {
1695
+ throw new Error("The access token provider did not return a token.");
1696
+ }
1697
+ if (result.company !== void 0 && result.company !== session.company || result.user !== void 0 && session.user !== void 0 && result.user !== session.user) {
1698
+ throw new Error(
1699
+ "The access token provider returned a token for a different session."
1700
+ );
1701
+ }
1702
+ const stamped = result.company !== void 0 && (session.user === void 0 || result.user !== void 0);
1703
+ return { resolved: { token, expiresAt: asDate(result.expiresAt) }, stamped };
1704
+ }
1705
+ function asDate(value) {
1706
+ if (value === void 0 || value === null) {
1707
+ return void 0;
1708
+ }
1709
+ const date = value instanceof Date ? value : new Date(value);
1710
+ return Number.isNaN(date.getTime()) ? void 0 : date;
1711
+ }
1712
+ async function discardBody(response) {
1713
+ try {
1714
+ await response.body?.cancel();
1715
+ } catch {
1716
+ }
1717
+ }
1718
+ async function readBody(response) {
1719
+ const text = await response.text();
1720
+ if (text === "") {
1721
+ return null;
1722
+ }
1723
+ try {
1724
+ return JSON.parse(text);
1725
+ } catch {
1726
+ return text;
1727
+ }
1728
+ }
1729
+
1730
+ // src/billing/client.ts
1731
+ var INVOICE_PAGE_SIZE = 12;
1732
+ var INVOICE_MAX_PAGE_SIZE = 250;
1733
+ var SchematicBillingClient = class {
1734
+ /**
1735
+ * Public so a second client over the same surface can be constructed on
1736
+ * this one rather than minting tokens of its own.
1737
+ */
1738
+ session;
1739
+ constructor(options = {}) {
1740
+ this.session = options instanceof SchematicSession ? options : new SchematicSession(options);
1741
+ }
1742
+ get sessionKey() {
1743
+ return this.session.key;
1744
+ }
1745
+ get sessionStatus() {
1746
+ return this.session.status;
1747
+ }
1748
+ setSession(session) {
1749
+ this.session.set(session);
1750
+ }
1751
+ onSessionChange(listener) {
1752
+ return this.session.onChange(listener);
1753
+ }
1754
+ fetchInvoices(params) {
1755
+ const query = new URLSearchParams({
1756
+ limit: String(Math.min(params.limit, INVOICE_MAX_PAGE_SIZE)),
1757
+ offset: String(params.offset)
1758
+ });
1759
+ if (params.includePending !== void 0) {
1760
+ query.set("include_pending", String(params.includePending));
1761
+ }
1762
+ const path = `/company/invoices?${query}`;
1763
+ return this.session.request(path).then((body) => {
1764
+ if (body === null || typeof body !== "object" || !("data" in body)) {
1765
+ throw new Error(`Malformed response from ${path}`);
1766
+ }
1767
+ const data = body.data;
1768
+ if (data == null || data.invoices == null) {
1769
+ return { invoices: [], count: 0 };
1770
+ }
1771
+ const decoded = GetCompanyInvoicesResponseFromJSON(body).data;
1772
+ return { invoices: decoded.invoices, count: decoded.count };
1773
+ });
1774
+ }
1775
+ };
1776
+ async function fetchBillingData(client, options = {}) {
1777
+ const wanted = options.names ?? ["invoices"];
1778
+ const data = {};
1779
+ const invoices = options.invoices === void 0 ? void 0 : normalizeInvoiceQuery(options.invoices);
1780
+ if (invoices !== void 0) {
1781
+ data.params = { invoices };
1782
+ }
1783
+ data.sessionKey = client.sessionKey;
1784
+ await Promise.all(
1785
+ wanted.map(async (name) => {
1786
+ try {
1787
+ switch (name) {
1788
+ case "invoices": {
1789
+ const page = await client.fetchInvoices({
1790
+ ...invoices ?? {},
1791
+ limit: INVOICE_PAGE_SIZE,
1792
+ offset: 0
1793
+ });
1794
+ data.invoices = {
1795
+ invoices: page.invoices,
1796
+ count: page.count,
1797
+ // Two queries on the server: a count that outran the rows
1798
+ // would seed a page that pages nothing.
1799
+ hasMore: page.invoices.length > 0 && page.invoices.length < page.count
1800
+ };
1801
+ break;
1802
+ }
1803
+ }
1804
+ } catch {
1805
+ }
1806
+ })
1807
+ );
1808
+ return data;
1809
+ }
889
1810
 
890
1811
  // src/index.ts
891
1812
  var anonymousIdKey = "schematicId";
1813
+ var flagStateCachePrefix = "schematicCache";
1814
+ var flagStateCacheMaxContexts = 10;
1815
+ var flagStateCacheDefaultMaxAgeMs = 7 * 24 * 60 * 60 * 1e3;
1816
+ var emptyCreditBalances = Object.freeze({});
892
1817
  var Schematic = class {
893
1818
  additionalHeaders = {};
894
1819
  apiKey;
@@ -905,11 +1830,17 @@ var Schematic = class {
905
1830
  isPending = true;
906
1831
  isPendingListeners = /* @__PURE__ */ new Set();
907
1832
  planListeners = /* @__PURE__ */ new Set();
1833
+ creditBalanceListeners = /* @__PURE__ */ new Set();
908
1834
  storage;
1835
+ persistFlagState = true;
1836
+ flagStateCacheKey;
1837
+ flagStateCacheMaxAgeMs = flagStateCacheDefaultMaxAgeMs;
1838
+ cachedFlagState = null;
909
1839
  useWebSocket = false;
910
1840
  checks = {};
911
1841
  featureUsageEventMap = {};
912
1842
  planChecks = {};
1843
+ creditBalances = {};
913
1844
  webSocketUrl = "wss://api.schematichq.com";
914
1845
  webSocketConnectionTimeout = 1e4;
915
1846
  webSocketReconnect = true;
@@ -938,11 +1869,16 @@ var Schematic = class {
938
1869
  fallbackCheckCache = {};
939
1870
  constructor(apiKey, options) {
940
1871
  this.apiKey = apiKey;
1872
+ this.flagStateCacheKey = `${flagStateCachePrefix}:${apiKey}`;
941
1873
  this.eventQueue = [];
942
1874
  this.contextDependentEventQueue = [];
943
1875
  this.useWebSocket = options?.useWebSocket ?? false;
944
1876
  this.debugEnabled = options?.debug ?? false;
945
1877
  this.offlineEnabled = options?.offline ?? false;
1878
+ this.persistFlagState = options?.persistFlagState ?? true;
1879
+ if (typeof options?.flagStateCacheMaxAgeMs === "number" && options.flagStateCacheMaxAgeMs > 0) {
1880
+ this.flagStateCacheMaxAgeMs = options.flagStateCacheMaxAgeMs;
1881
+ }
946
1882
  if (typeof window !== "undefined" && typeof window.location !== "undefined") {
947
1883
  const params = new URLSearchParams(window.location.search);
948
1884
  const debugParam = params.get("schematic_debug");
@@ -975,6 +1911,7 @@ var Schematic = class {
975
1911
  } catch {
976
1912
  }
977
1913
  }
1914
+ this.hydrateFlagStateFromCache();
978
1915
  if (options?.apiUrl !== void 0) {
979
1916
  this.apiUrl = options.apiUrl;
980
1917
  }
@@ -1268,6 +2205,29 @@ var Schematic = class {
1268
2205
  });
1269
2206
  this.notifyPlanListeners(plan);
1270
2207
  }
2208
+ if (message.credit_balances !== void 0 && message.credit_balances !== null) {
2209
+ const contextStr = contextString(context);
2210
+ const updates = CreditBalancesFromJSON(message.credit_balances);
2211
+ const previous = this.creditBalances[contextStr] ?? {};
2212
+ let changed = false;
2213
+ const merged = { ...previous };
2214
+ for (const [creditId, next] of Object.entries(updates)) {
2215
+ const current = previous[creditId];
2216
+ if (current === void 0 || current.remaining !== next.remaining || current.reserved !== next.reserved || current.settled !== next.settled) {
2217
+ merged[creditId] = next;
2218
+ changed = true;
2219
+ }
2220
+ }
2221
+ if (changed) {
2222
+ this.creditBalances[contextStr] = merged;
2223
+ this.debug(
2224
+ `WebSocket credit balance update received. Notifying listeners`,
2225
+ { creditBalances: merged }
2226
+ );
2227
+ this.notifyCreditBalanceListeners(merged);
2228
+ }
2229
+ }
2230
+ this.persistContextToCache(contextString(context));
1271
2231
  this.flushContextDependentEventQueue();
1272
2232
  this.setIsPending(false);
1273
2233
  };
@@ -1375,7 +2335,32 @@ var Schematic = class {
1375
2335
  return;
1376
2336
  }
1377
2337
  try {
1378
- this.setIsPending(true);
2338
+ const newContextStr = contextString(context);
2339
+ const cacheHit = this.hasCachedValuesForContext(newContextStr);
2340
+ if (cacheHit) {
2341
+ this.debug(
2342
+ `setContext: cache hit for context, pre-resolving from cached values`
2343
+ );
2344
+ this.context = context;
2345
+ this.flushContextDependentEventQueue();
2346
+ this.setIsPending(false);
2347
+ const cachedChecks = this.checks[newContextStr] ?? {};
2348
+ for (const [flagKey, check] of Object.entries(cachedChecks)) {
2349
+ if (check === void 0) continue;
2350
+ this.notifyFlagCheckListeners(flagKey, check);
2351
+ this.notifyFlagValueListeners(flagKey, check.value);
2352
+ }
2353
+ const cachedPlan = this.planChecks[newContextStr];
2354
+ if (cachedPlan !== void 0) {
2355
+ this.notifyPlanListeners(cachedPlan);
2356
+ }
2357
+ const cachedCreditBalances = this.creditBalances[newContextStr];
2358
+ if (cachedCreditBalances !== void 0) {
2359
+ this.notifyCreditBalanceListeners(cachedCreditBalances);
2360
+ }
2361
+ } else {
2362
+ this.setIsPending(true);
2363
+ }
1379
2364
  if (!this.conn) {
1380
2365
  if (this.isConnecting) {
1381
2366
  this.debug(
@@ -1386,7 +2371,7 @@ var Schematic = class {
1386
2371
  }
1387
2372
  if (this.conn !== null) {
1388
2373
  const socket2 = await this.conn;
1389
- await this.wsSendMessage(socket2, context);
2374
+ await this.wsSendMessage(socket2, context, cacheHit);
1390
2375
  return;
1391
2376
  }
1392
2377
  }
@@ -1402,7 +2387,7 @@ var Schematic = class {
1402
2387
  this.conn = this.wsConnect();
1403
2388
  const socket2 = await this.conn;
1404
2389
  this.isConnecting = false;
1405
- await this.wsSendMessage(socket2, context);
2390
+ await this.wsSendMessage(socket2, context, cacheHit);
1406
2391
  return;
1407
2392
  } catch (error) {
1408
2393
  this.isConnecting = false;
@@ -1410,7 +2395,7 @@ var Schematic = class {
1410
2395
  }
1411
2396
  }
1412
2397
  const socket = await this.conn;
1413
- await this.wsSendMessage(socket, context);
2398
+ await this.wsSendMessage(socket, context, cacheHit);
1414
2399
  } catch (error) {
1415
2400
  console.warn("Failed to establish WebSocket connection:", error);
1416
2401
  this.context = context;
@@ -1470,10 +2455,12 @@ var Schematic = class {
1470
2455
  `Optimistically updating feature usage for event: ${eventName}`,
1471
2456
  { quantity }
1472
2457
  );
2458
+ let dirty = false;
1473
2459
  Object.entries(flagsForEvent).forEach(([flagKey, check]) => {
1474
2460
  if (check === void 0) return;
1475
2461
  const updatedCheck = { ...check };
1476
2462
  if (typeof updatedCheck.featureUsage === "number") {
2463
+ dirty = true;
1477
2464
  updatedCheck.featureUsage += quantity;
1478
2465
  if (typeof updatedCheck.featureAllocation === "number") {
1479
2466
  const wasExceeded = updatedCheck.featureUsageExceeded === true;
@@ -1503,6 +2490,9 @@ var Schematic = class {
1503
2490
  this.notifyFlagValueListeners(flagKey, updatedCheck.value);
1504
2491
  }
1505
2492
  });
2493
+ if (dirty) {
2494
+ this.persistContextToCache(contextString(this.context));
2495
+ }
1506
2496
  };
1507
2497
  /**
1508
2498
  * Event processing
@@ -1605,6 +2595,206 @@ var Schematic = class {
1605
2595
  this.storage.setItem(anonymousIdKey, generatedAnonymousId);
1606
2596
  return generatedAnonymousId;
1607
2597
  };
2598
+ hasCachedValuesForContext = (contextStr) => {
2599
+ const cachedChecks = this.checks[contextStr];
2600
+ if (cachedChecks !== void 0 && Object.keys(cachedChecks).length > 0) {
2601
+ return true;
2602
+ }
2603
+ if (this.planChecks[contextStr] !== void 0) {
2604
+ return true;
2605
+ }
2606
+ const cachedCreditBalances = this.creditBalances[contextStr];
2607
+ return cachedCreditBalances !== void 0 && Object.keys(cachedCreditBalances).length > 0;
2608
+ };
2609
+ readFlagStateCache = () => {
2610
+ if (!this.persistFlagState || this.storage === void 0) {
2611
+ return null;
2612
+ }
2613
+ let raw;
2614
+ try {
2615
+ raw = this.storage.getItem(this.flagStateCacheKey);
2616
+ } catch (error) {
2617
+ this.debug("Failed to read flag state cache:", error);
2618
+ return null;
2619
+ }
2620
+ if (raw === null || raw === void 0) {
2621
+ return null;
2622
+ }
2623
+ let parsed;
2624
+ try {
2625
+ parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
2626
+ } catch (error) {
2627
+ this.debug("Flag state cache is not valid JSON, ignoring:", error);
2628
+ return null;
2629
+ }
2630
+ if (parsed === null || typeof parsed !== "object" || parsed.version !== cacheVersion || typeof parsed.contexts !== "object" || parsed.contexts === null) {
2631
+ this.debug(
2632
+ `Flag state cache invalid or version mismatch (expected ${cacheVersion}), ignoring`,
2633
+ {
2634
+ parsedType: typeof parsed,
2635
+ version: parsed?.version
2636
+ }
2637
+ );
2638
+ return null;
2639
+ }
2640
+ return parsed;
2641
+ };
2642
+ reviveCachedCheck = (raw) => {
2643
+ if (raw === null || typeof raw !== "object") return null;
2644
+ const obj = raw;
2645
+ if (typeof obj.flag !== "string" || typeof obj.value !== "boolean" || typeof obj.reason !== "string") {
2646
+ return null;
2647
+ }
2648
+ const revived = { ...obj };
2649
+ if (typeof revived.featureUsageResetAt === "string") {
2650
+ const d = new Date(revived.featureUsageResetAt);
2651
+ revived.featureUsageResetAt = isNaN(d.getTime()) ? void 0 : d;
2652
+ }
2653
+ return revived;
2654
+ };
2655
+ reviveCachedPlan = (raw) => {
2656
+ if (raw === null || typeof raw !== "object") return null;
2657
+ const obj = raw;
2658
+ if (typeof obj.id !== "string" || typeof obj.name !== "string") {
2659
+ return null;
2660
+ }
2661
+ const revived = { ...obj };
2662
+ if (typeof revived.trialEndDate === "string") {
2663
+ const d = new Date(revived.trialEndDate);
2664
+ revived.trialEndDate = isNaN(d.getTime()) ? void 0 : d;
2665
+ }
2666
+ return revived;
2667
+ };
2668
+ reviveCachedCreditBalances = (raw) => {
2669
+ if (raw === null || typeof raw !== "object") return null;
2670
+ const revived = {};
2671
+ for (const [creditId, value] of Object.entries(
2672
+ raw
2673
+ )) {
2674
+ if (value === null || typeof value !== "object") continue;
2675
+ const obj = value;
2676
+ if (typeof obj.remaining !== "number" || typeof obj.reserved !== "number" || typeof obj.settled !== "number") {
2677
+ continue;
2678
+ }
2679
+ revived[creditId] = {
2680
+ remaining: obj.remaining,
2681
+ reserved: obj.reserved,
2682
+ settled: obj.settled
2683
+ };
2684
+ }
2685
+ return Object.keys(revived).length > 0 ? revived : null;
2686
+ };
2687
+ hydrateFlagStateFromCache = () => {
2688
+ const parsed = this.readFlagStateCache();
2689
+ if (parsed === null) return;
2690
+ const cutoff = Date.now() - this.flagStateCacheMaxAgeMs;
2691
+ const fresh = {};
2692
+ let contextsLoaded = 0;
2693
+ let contextsExpired = 0;
2694
+ for (const [contextStr, entry] of Object.entries(parsed.contexts)) {
2695
+ if (entry === null || typeof entry !== "object") continue;
2696
+ if (typeof entry.updatedAt !== "number") continue;
2697
+ if (entry.updatedAt < cutoff) {
2698
+ contextsExpired += 1;
2699
+ continue;
2700
+ }
2701
+ const revivedChecks = {};
2702
+ if (entry.checks !== null && typeof entry.checks === "object") {
2703
+ for (const [flagKey, rawCheck] of Object.entries(entry.checks)) {
2704
+ const revived = this.reviveCachedCheck(rawCheck);
2705
+ if (revived === null || revived.flag !== flagKey) continue;
2706
+ revivedChecks[flagKey] = revived;
2707
+ if (typeof revived.featureUsageEvent === "string") {
2708
+ this.updateFeatureUsageEventMap(revived);
2709
+ }
2710
+ }
2711
+ if (Object.keys(revivedChecks).length > 0) {
2712
+ this.checks[contextStr] = revivedChecks;
2713
+ }
2714
+ }
2715
+ let revivedPlan;
2716
+ if (entry.plan !== void 0 && entry.plan !== null) {
2717
+ const p = this.reviveCachedPlan(entry.plan);
2718
+ if (p !== null) {
2719
+ revivedPlan = p;
2720
+ this.planChecks[contextStr] = p;
2721
+ }
2722
+ }
2723
+ let revivedCreditBalances;
2724
+ if (entry.creditBalances !== void 0 && entry.creditBalances !== null) {
2725
+ const balances = this.reviveCachedCreditBalances(entry.creditBalances);
2726
+ if (balances !== null) {
2727
+ revivedCreditBalances = balances;
2728
+ this.creditBalances[contextStr] = balances;
2729
+ }
2730
+ }
2731
+ fresh[contextStr] = {
2732
+ checks: revivedChecks,
2733
+ plan: revivedPlan,
2734
+ creditBalances: revivedCreditBalances,
2735
+ updatedAt: entry.updatedAt
2736
+ };
2737
+ contextsLoaded += 1;
2738
+ }
2739
+ this.cachedFlagState = { version: cacheVersion, contexts: fresh };
2740
+ this.debug(
2741
+ `Hydrated flag state cache: ${contextsLoaded} loaded, ${contextsExpired} expired`
2742
+ );
2743
+ };
2744
+ persistContextToCache = (contextStr) => {
2745
+ const checksForContext = this.checks[contextStr];
2746
+ const planForContext = this.planChecks[contextStr];
2747
+ const creditBalancesForContext = this.creditBalances[contextStr];
2748
+ if (checksForContext === void 0 && planForContext === void 0 && creditBalancesForContext === void 0) {
2749
+ return;
2750
+ }
2751
+ if (this.cachedFlagState === null) {
2752
+ this.cachedFlagState = (this.persistFlagState ? this.readFlagStateCache() : null) ?? {
2753
+ version: cacheVersion,
2754
+ contexts: {}
2755
+ };
2756
+ }
2757
+ const cleanChecks = {};
2758
+ if (checksForContext !== void 0) {
2759
+ for (const [flagKey, check] of Object.entries(checksForContext)) {
2760
+ if (check !== void 0) cleanChecks[flagKey] = check;
2761
+ }
2762
+ }
2763
+ this.cachedFlagState.contexts[contextStr] = {
2764
+ checks: cleanChecks,
2765
+ plan: planForContext,
2766
+ creditBalances: creditBalancesForContext,
2767
+ updatedAt: Date.now()
2768
+ };
2769
+ const entries = Object.entries(this.cachedFlagState.contexts);
2770
+ if (entries.length > flagStateCacheMaxContexts) {
2771
+ entries.sort((a, b) => b[1].updatedAt - a[1].updatedAt);
2772
+ const survivors = entries.slice(0, flagStateCacheMaxContexts);
2773
+ const survivorKeys = new Set(survivors.map(([k]) => k));
2774
+ for (const key of Object.keys(this.checks)) {
2775
+ if (!survivorKeys.has(key)) delete this.checks[key];
2776
+ }
2777
+ for (const key of Object.keys(this.planChecks)) {
2778
+ if (!survivorKeys.has(key)) delete this.planChecks[key];
2779
+ }
2780
+ for (const key of Object.keys(this.creditBalances)) {
2781
+ if (!survivorKeys.has(key)) delete this.creditBalances[key];
2782
+ }
2783
+ this.cachedFlagState = {
2784
+ version: cacheVersion,
2785
+ contexts: Object.fromEntries(survivors)
2786
+ };
2787
+ }
2788
+ if (!this.persistFlagState || this.storage === void 0) return;
2789
+ try {
2790
+ this.storage.setItem(
2791
+ this.flagStateCacheKey,
2792
+ JSON.stringify(this.cachedFlagState)
2793
+ );
2794
+ } catch (error) {
2795
+ this.debug("Failed to write flag state cache:", error);
2796
+ }
2797
+ };
1608
2798
  handleEvent = (eventType, eventBody) => {
1609
2799
  const event = {
1610
2800
  api_key: this.apiKey,
@@ -1865,6 +3055,9 @@ var Schematic = class {
1865
3055
  if (this.conn !== null) {
1866
3056
  try {
1867
3057
  const socket = await this.conn;
3058
+ if (this.currentWebSocket === socket) {
3059
+ this.currentWebSocket = null;
3060
+ }
1868
3061
  if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
1869
3062
  socket.close();
1870
3063
  }
@@ -2030,6 +3223,7 @@ var Schematic = class {
2030
3223
  this.debug(`Creating WebSocket connection ${connectionId} to ${wsUrl}`);
2031
3224
  let timeoutId = null;
2032
3225
  let isResolved = false;
3226
+ let didOpen = false;
2033
3227
  timeoutId = setTimeout(() => {
2034
3228
  if (!isResolved) {
2035
3229
  isResolved = true;
@@ -2043,6 +3237,7 @@ var Schematic = class {
2043
3237
  webSocket.onopen = () => {
2044
3238
  if (isResolved) return;
2045
3239
  isResolved = true;
3240
+ didOpen = true;
2046
3241
  if (timeoutId !== null) {
2047
3242
  clearTimeout(timeoutId);
2048
3243
  }
@@ -2068,11 +3263,12 @@ var Schematic = class {
2068
3263
  }
2069
3264
  this.debug(`WebSocket connection ${connectionId} closed`);
2070
3265
  this.conn = null;
3266
+ const wasCurrent = this.currentWebSocket === webSocket;
2071
3267
  if (this.currentWebSocket === webSocket) {
2072
3268
  this.currentWebSocket = null;
2073
3269
  this.isConnecting = false;
2074
3270
  }
2075
- if (!isResolved && !this.wsIntentionalDisconnect && this.webSocketReconnect) {
3271
+ if (wasCurrent && didOpen && !this.wsIntentionalDisconnect && this.webSocketReconnect) {
2076
3272
  this.attemptReconnect();
2077
3273
  }
2078
3274
  };
@@ -2168,6 +3364,16 @@ var Schematic = class {
2168
3364
  const plan = this.planChecks[contextStr];
2169
3365
  return plan;
2170
3366
  };
3367
+ /** Get the company's lease-aware credit balances for the current context, keyed by credit ID */
3368
+ getCreditBalances = () => {
3369
+ const contextStr = contextString(this.context);
3370
+ return this.creditBalances[contextStr] ?? emptyCreditBalances;
3371
+ };
3372
+ /** Get the company's lease-aware balance for a single credit type in the current context */
3373
+ getCreditBalance = (creditId) => {
3374
+ const contextStr = contextString(this.context);
3375
+ return this.creditBalances[contextStr]?.[creditId];
3376
+ };
2171
3377
  // flag checks state
2172
3378
  getFlagCheck = (flagKey) => {
2173
3379
  const contextStr = contextString(this.context);
@@ -2227,6 +3433,13 @@ var Schematic = class {
2227
3433
  this.planListeners.delete(listener);
2228
3434
  };
2229
3435
  };
3436
+ /** Register an event listener that will be notified with the company's credit balances (keyed by credit ID) whenever they change */
3437
+ addCreditBalanceListener = (listener) => {
3438
+ this.creditBalanceListeners.add(listener);
3439
+ return () => {
3440
+ this.creditBalanceListeners.delete(listener);
3441
+ };
3442
+ };
2230
3443
  notifyFlagCheckListeners = (flagKey, check) => {
2231
3444
  const listeners = this.flagCheckListeners?.[flagKey] ?? [];
2232
3445
  if (listeners.size > 0) {
@@ -2290,6 +3503,17 @@ var Schematic = class {
2290
3503
  });
2291
3504
  });
2292
3505
  };
3506
+ notifyCreditBalanceListeners = (value) => {
3507
+ const listeners = this.creditBalanceListeners ?? [];
3508
+ if (listeners.size > 0) {
3509
+ this.debug(`Notifying ${listeners.size} credit balance listeners`, {
3510
+ value
3511
+ });
3512
+ }
3513
+ listeners.forEach(
3514
+ (listener) => notifyCreditBalanceListener(listener, value)
3515
+ );
3516
+ };
2293
3517
  };
2294
3518
  var notifyPendingListener = (listener, value) => {
2295
3519
  if (listener.length > 0) {
@@ -2319,4 +3543,11 @@ var notifyPlanListener = (listener, value) => {
2319
3543
  listener();
2320
3544
  }
2321
3545
  };
3546
+ var notifyCreditBalanceListener = (listener, value) => {
3547
+ if (listener.length > 0) {
3548
+ listener(value);
3549
+ } else {
3550
+ listener();
3551
+ }
3552
+ };
2322
3553
  /* @preserve */