@schematichq/schematic-react 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -280,6 +280,43 @@ const CreditMeter = () => {
280
280
 
281
281
  While `creditId` is `undefined`, the hook reports the client's loading state and a balance of `0`.
282
282
 
283
+ ### Credit spend policies
284
+
285
+ To show the spend limits that bind the current company and user, use the `useSchematicCreditSpendPolicies` hook. It updates over the DataStream, so a policy edited or deleted in the dashboard reaches the component without a refetch:
286
+
287
+ ```tsx
288
+ import { useSchematicCreditSpendPolicies } from "@schematichq/schematic-react";
289
+
290
+ const SpendLimits = () => {
291
+ const { policies, isLoading } = useSchematicCreditSpendPolicies();
292
+
293
+ if (isLoading) {
294
+ return <div>Loading…</div>;
295
+ }
296
+
297
+ return (
298
+ <ul>
299
+ {policies.map((policy) => (
300
+ <li key={policy.id}>
301
+ {policy.kind === "window" && policy.window
302
+ ? `${policy.scope} limit: ${policy.consumed} of ${policy.limit} credits used this ${policy.window.unit}`
303
+ : `${policy.scope} limit: ${policy.limit} credits per request`}
304
+ </li>
305
+ ))}
306
+ </ul>
307
+ );
308
+ };
309
+ ```
310
+
311
+ | Property | Type | Description |
312
+ | --- | --- | --- |
313
+ | `policies` | `CreditSpendPolicies` | Company-scope policies first, then the user's own; empty when none bind |
314
+ | `isLoading` | `boolean` | `true` while the policies are still loading and nothing has arrived yet |
315
+
316
+ A `per_draw` policy caps a single draw. A `window` policy caps what can be spent in one period: `consumed` is what the current period has spent, `resetsAt` is when the period ends, and `window` names the period. Each new spend sends an updated `consumed`. Once `resetsAt` has passed, read `consumed` as `0` until the next update arrives.
317
+
318
+ The policies are advisory for display: the server and the flag check enforce them.
319
+
283
320
  ## Fallback Behavior
284
321
 
285
322
  The SDK includes built-in fallback behavior you can use to ensure your application continues to function even when unable to reach Schematic (e.g., during service disruptions or network issues).
@@ -51,6 +51,7 @@ __export(index_exports, {
51
51
  useSchematic: () => useSchematic,
52
52
  useSchematicContext: () => useSchematicContext,
53
53
  useSchematicCreditBalance: () => useSchematicCreditBalance,
54
+ useSchematicCreditSpendPolicies: () => useSchematicCreditSpendPolicies,
54
55
  useSchematicEntitlement: () => useSchematicEntitlement,
55
56
  useSchematicEvents: () => useSchematicEvents,
56
57
  useSchematicFlag: () => useSchematicFlag,
@@ -65,7 +66,7 @@ __export(index_exports, {
65
66
  });
66
67
  module.exports = __toCommonJS(index_exports);
67
68
 
68
- // node_modules/.pnpm/@schematichq+schematic-js@1.9.0/node_modules/@schematichq/schematic-js/dist/schematic.esm.js
69
+ // node_modules/.pnpm/@schematichq+schematic-js@1.10.0/node_modules/@schematichq/schematic-js/dist/schematic.esm.js
69
70
  var __create2 = Object.create;
70
71
  var __defProp2 = Object.defineProperty;
71
72
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
@@ -1061,6 +1062,43 @@ function DatastreamCompanyPlanFromJSONTyped(json, ignoreDiscriminator) {
1061
1062
  trialStatus: json["trial_status"] == null ? void 0 : TrialStatusFromJSON(json["trial_status"])
1062
1063
  };
1063
1064
  }
1065
+ function CreditSpendPolicyScopeFromJSON(json) {
1066
+ return CreditSpendPolicyScopeFromJSONTyped(json, false);
1067
+ }
1068
+ function CreditSpendPolicyScopeFromJSONTyped(json, ignoreDiscriminator) {
1069
+ return json;
1070
+ }
1071
+ function CreditSpendWindowFromJSON(json) {
1072
+ return CreditSpendWindowFromJSONTyped(json, false);
1073
+ }
1074
+ function CreditSpendWindowFromJSONTyped(json, ignoreDiscriminator) {
1075
+ if (json == null) {
1076
+ return json;
1077
+ }
1078
+ return {
1079
+ count: json["count"],
1080
+ unit: json["unit"]
1081
+ };
1082
+ }
1083
+ function CreditSpendPolicyFromJSON(json) {
1084
+ return CreditSpendPolicyFromJSONTyped(json, false);
1085
+ }
1086
+ function CreditSpendPolicyFromJSONTyped(json, ignoreDiscriminator) {
1087
+ if (json == null) {
1088
+ return json;
1089
+ }
1090
+ return {
1091
+ consumed: json["consumed"] == null ? void 0 : json["consumed"],
1092
+ creditId: json["credit_id"],
1093
+ id: json["id"],
1094
+ kind: json["kind"],
1095
+ label: json["label"] == null ? void 0 : json["label"],
1096
+ limit: json["limit"],
1097
+ resetsAt: json["resets_at"] == null ? void 0 : new Date(json["resets_at"]),
1098
+ scope: CreditSpendPolicyScopeFromJSON(json["scope"]),
1099
+ window: json["window"] == null ? void 0 : CreditSpendWindowFromJSON(json["window"])
1100
+ };
1101
+ }
1064
1102
  function CheckFlagsResponseDataFromJSON(json) {
1065
1103
  return CheckFlagsResponseDataFromJSONTyped(json, false);
1066
1104
  }
@@ -1070,6 +1108,9 @@ function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
1070
1108
  }
1071
1109
  return {
1072
1110
  creditBalances: json["credit_balances"] == null ? void 0 : mapValues(json["credit_balances"], CompanyCreditBalanceFromJSON),
1111
+ creditSpendPolicies: json["credit_spend_policies"].map(
1112
+ CreditSpendPolicyFromJSON
1113
+ ),
1073
1114
  flags: json["flags"].map(CheckFlagResponseDataFromJSON),
1074
1115
  plan: json["plan"] == null ? void 0 : DatastreamCompanyPlanFromJSON(json["plan"])
1075
1116
  };
@@ -1198,7 +1239,55 @@ var CreditBalancesFromJSON = (json) => {
1198
1239
  }
1199
1240
  return balances;
1200
1241
  };
1201
- var cacheVersion = "7b920cf4";
1242
+ var CreditSpendPoliciesFromJSON = (json) => {
1243
+ if (!Array.isArray(json)) {
1244
+ return [];
1245
+ }
1246
+ const policies = [];
1247
+ for (const raw of json) {
1248
+ if (raw == null || typeof raw !== "object") continue;
1249
+ const id = raw.id;
1250
+ const creditId = raw.credit_id ?? raw.creditId;
1251
+ const kind = raw.kind;
1252
+ const scope = raw.scope;
1253
+ const limit = raw.limit;
1254
+ if (typeof id !== "string" || typeof creditId !== "string" || typeof kind !== "string" || typeof scope !== "string" || typeof limit !== "number") {
1255
+ continue;
1256
+ }
1257
+ const policy = {
1258
+ id,
1259
+ creditId,
1260
+ kind,
1261
+ scope,
1262
+ limit,
1263
+ consumed: typeof raw.consumed === "number" ? raw.consumed : 0
1264
+ };
1265
+ if (typeof raw.label === "string") {
1266
+ policy.label = raw.label;
1267
+ }
1268
+ const window2 = raw.window;
1269
+ if (window2 != null && typeof window2 === "object" && typeof window2.unit === "string" && typeof window2.count === "number") {
1270
+ policy.window = { unit: window2.unit, count: window2.count };
1271
+ }
1272
+ const resetsAt = raw.resets_at ?? raw.resetsAt;
1273
+ if (typeof resetsAt === "string" || resetsAt instanceof Date) {
1274
+ const parsed = new Date(resetsAt);
1275
+ if (!Number.isNaN(parsed.getTime())) {
1276
+ policy.resetsAt = parsed;
1277
+ }
1278
+ }
1279
+ policies.push(policy);
1280
+ }
1281
+ return policies;
1282
+ };
1283
+ var creditSpendPoliciesEqual = (a, b) => {
1284
+ if (a.length !== b.length) return false;
1285
+ return a.every((x, i) => {
1286
+ const y = b[i];
1287
+ return x.id === y.id && x.creditId === y.creditId && x.kind === y.kind && x.scope === y.scope && x.label === y.label && x.limit === y.limit && x.consumed === y.consumed && x.resetsAt?.getTime() === y.resetsAt?.getTime() && x.window?.unit === y.window?.unit && x.window?.count === y.window?.count;
1288
+ });
1289
+ };
1290
+ var cacheVersion = "35d92bc6";
1202
1291
  function contextString(context) {
1203
1292
  const sortedContext = Object.keys(context).reduce((acc, key) => {
1204
1293
  const sortedKeys = Object.keys(
@@ -1213,7 +1302,7 @@ function contextString(context) {
1213
1302
  }, {});
1214
1303
  return JSON.stringify(sortedContext);
1215
1304
  }
1216
- var version = "1.9.0";
1305
+ var version = "1.10.0";
1217
1306
  var SINGLETON = {};
1218
1307
  var models_exports = {};
1219
1308
  __export2(models_exports, {
@@ -2380,6 +2469,7 @@ var flagStateCachePrefix = "schematicCache";
2380
2469
  var flagStateCacheMaxContexts = 10;
2381
2470
  var flagStateCacheDefaultMaxAgeMs = 7 * 24 * 60 * 60 * 1e3;
2382
2471
  var emptyCreditBalances = Object.freeze({});
2472
+ var emptyCreditSpendPolicies = [];
2383
2473
  var Schematic = class {
2384
2474
  _additionalHeaders = {};
2385
2475
  apiKey;
@@ -2397,6 +2487,7 @@ var Schematic = class {
2397
2487
  isPendingListeners = /* @__PURE__ */ new Set();
2398
2488
  planListeners = /* @__PURE__ */ new Set();
2399
2489
  creditBalanceListeners = /* @__PURE__ */ new Set();
2490
+ creditSpendPolicyListeners = /* @__PURE__ */ new Set();
2400
2491
  storage;
2401
2492
  persistFlagState = true;
2402
2493
  flagStateCacheKey;
@@ -2407,6 +2498,7 @@ var Schematic = class {
2407
2498
  featureUsageEventMap = {};
2408
2499
  planChecks = {};
2409
2500
  creditBalances = {};
2501
+ creditSpendPolicies = {};
2410
2502
  webSocketUrl = "wss://api.schematichq.com";
2411
2503
  webSocketConnectionTimeout = 1e4;
2412
2504
  webSocketReconnect = true;
@@ -2799,6 +2891,19 @@ var Schematic = class {
2799
2891
  this.notifyCreditBalanceListeners(merged);
2800
2892
  }
2801
2893
  }
2894
+ if (message.credit_spend_policies !== void 0 && message.credit_spend_policies !== null) {
2895
+ const contextStr = contextString(context);
2896
+ const next = CreditSpendPoliciesFromJSON(message.credit_spend_policies);
2897
+ const previous = this.creditSpendPolicies[contextStr] ?? [];
2898
+ if (!creditSpendPoliciesEqual(previous, next)) {
2899
+ this.creditSpendPolicies[contextStr] = next;
2900
+ this.debug(
2901
+ `WebSocket credit spend policy update received. Notifying listeners`,
2902
+ { creditSpendPolicies: next }
2903
+ );
2904
+ this.notifyCreditSpendPolicyListeners(next);
2905
+ }
2906
+ }
2802
2907
  this.persistContextToCache(contextString(context));
2803
2908
  this.flushContextDependentEventQueue();
2804
2909
  this.setIsPending(false);
@@ -3300,10 +3405,19 @@ var Schematic = class {
3300
3405
  this.creditBalances[contextStr] = balances;
3301
3406
  }
3302
3407
  }
3408
+ let revivedCreditSpendPolicies;
3409
+ if (entry.creditSpendPolicies !== void 0 && entry.creditSpendPolicies !== null) {
3410
+ const policies = CreditSpendPoliciesFromJSON(entry.creditSpendPolicies);
3411
+ if (policies.length > 0) {
3412
+ revivedCreditSpendPolicies = policies;
3413
+ this.creditSpendPolicies[contextStr] = policies;
3414
+ }
3415
+ }
3303
3416
  fresh[contextStr] = {
3304
3417
  checks: revivedChecks,
3305
3418
  plan: revivedPlan,
3306
3419
  creditBalances: revivedCreditBalances,
3420
+ creditSpendPolicies: revivedCreditSpendPolicies,
3307
3421
  updatedAt: entry.updatedAt
3308
3422
  };
3309
3423
  contextsLoaded += 1;
@@ -3317,7 +3431,8 @@ var Schematic = class {
3317
3431
  const checksForContext = this.checks[contextStr];
3318
3432
  const planForContext = this.planChecks[contextStr];
3319
3433
  const creditBalancesForContext = this.creditBalances[contextStr];
3320
- if (checksForContext === void 0 && planForContext === void 0 && creditBalancesForContext === void 0) {
3434
+ const creditSpendPoliciesForContext = this.creditSpendPolicies[contextStr];
3435
+ if (checksForContext === void 0 && planForContext === void 0 && creditBalancesForContext === void 0 && creditSpendPoliciesForContext === void 0) {
3321
3436
  return;
3322
3437
  }
3323
3438
  if (this.cachedFlagState === null) {
@@ -3336,6 +3451,7 @@ var Schematic = class {
3336
3451
  checks: cleanChecks,
3337
3452
  plan: planForContext,
3338
3453
  creditBalances: creditBalancesForContext,
3454
+ creditSpendPolicies: creditSpendPoliciesForContext,
3339
3455
  updatedAt: Date.now()
3340
3456
  };
3341
3457
  const entries = Object.entries(this.cachedFlagState.contexts);
@@ -3352,6 +3468,9 @@ var Schematic = class {
3352
3468
  for (const key of Object.keys(this.creditBalances)) {
3353
3469
  if (!survivorKeys.has(key)) delete this.creditBalances[key];
3354
3470
  }
3471
+ for (const key of Object.keys(this.creditSpendPolicies)) {
3472
+ if (!survivorKeys.has(key)) delete this.creditSpendPolicies[key];
3473
+ }
3355
3474
  this.cachedFlagState = {
3356
3475
  version: cacheVersion,
3357
3476
  contexts: Object.fromEntries(survivors)
@@ -3946,6 +4065,18 @@ var Schematic = class {
3946
4065
  const contextStr = contextString(this.context);
3947
4066
  return this.creditBalances[contextStr]?.[creditId];
3948
4067
  };
4068
+ /** Get every credit spend policy binding the current context, company-scope first */
4069
+ getCreditSpendPolicies = () => {
4070
+ const contextStr = contextString(this.context);
4071
+ return this.creditSpendPolicies[contextStr] ?? emptyCreditSpendPolicies;
4072
+ };
4073
+ /** Get the spend policies binding a single credit type in the current context */
4074
+ getCreditSpendPoliciesForCredit = (creditId) => {
4075
+ const policies = this.getCreditSpendPolicies().filter(
4076
+ (policy) => policy.creditId === creditId
4077
+ );
4078
+ return policies.length > 0 ? policies : emptyCreditSpendPolicies;
4079
+ };
3949
4080
  // flag checks state
3950
4081
  getFlagCheck = (flagKey) => {
3951
4082
  const contextStr = contextString(this.context);
@@ -4012,6 +4143,13 @@ var Schematic = class {
4012
4143
  this.creditBalanceListeners.delete(listener);
4013
4144
  };
4014
4145
  };
4146
+ /** Register an event listener that will be notified with the context's credit spend policies whenever the set changes */
4147
+ addCreditSpendPolicyListener = (listener) => {
4148
+ this.creditSpendPolicyListeners.add(listener);
4149
+ return () => {
4150
+ this.creditSpendPolicyListeners.delete(listener);
4151
+ };
4152
+ };
4015
4153
  notifyFlagCheckListeners = (flagKey, check) => {
4016
4154
  const listeners = this.flagCheckListeners?.[flagKey] ?? [];
4017
4155
  if (listeners.size > 0) {
@@ -4075,6 +4213,16 @@ var Schematic = class {
4075
4213
  });
4076
4214
  });
4077
4215
  };
4216
+ notifyCreditSpendPolicyListeners = (value) => {
4217
+ const listeners = this.creditSpendPolicyListeners ?? [];
4218
+ listeners.forEach((listener) => {
4219
+ if (listener.length > 0) {
4220
+ listener(value);
4221
+ return;
4222
+ }
4223
+ listener();
4224
+ });
4225
+ };
4078
4226
  notifyCreditBalanceListeners = (value) => {
4079
4227
  const listeners = this.creditBalanceListeners ?? [];
4080
4228
  if (listeners.size > 0) {
@@ -5091,7 +5239,7 @@ function staticSource(data) {
5091
5239
  }
5092
5240
 
5093
5241
  // src/version.ts
5094
- var version2 = "1.9.0";
5242
+ var version2 = "1.10.0";
5095
5243
 
5096
5244
  // src/context/schematic.tsx
5097
5245
  var import_jsx_runtime4 = require("react/jsx-runtime");
@@ -5184,6 +5332,7 @@ var useSchematic = () => {
5184
5332
 
5185
5333
  // src/hooks/schematic.ts
5186
5334
  var import_react6 = require("react");
5335
+ var emptyPolicies = [];
5187
5336
  var useSchematicClient = (opts) => {
5188
5337
  const schematic = useSchematic();
5189
5338
  const { client } = opts ?? {};
@@ -5299,6 +5448,34 @@ var useSchematicCreditBalance = (creditId, opts) => {
5299
5448
  [balance, isPending]
5300
5449
  );
5301
5450
  };
5451
+ var useSchematicCreditSpendPolicies = (opts) => {
5452
+ const client = useSchematicClient(opts);
5453
+ const subscribe = (0, import_react6.useCallback)(
5454
+ (callback) => client.addCreditSpendPolicyListener(callback),
5455
+ [client]
5456
+ );
5457
+ const policies = (0, import_react6.useSyncExternalStore)(
5458
+ subscribe,
5459
+ () => client.getCreditSpendPolicies(),
5460
+ () => emptyPolicies
5461
+ );
5462
+ const isPendingSubscribe = (0, import_react6.useCallback)(
5463
+ (callback) => client.addIsPendingListener(callback),
5464
+ [client]
5465
+ );
5466
+ const isPending = (0, import_react6.useSyncExternalStore)(
5467
+ isPendingSubscribe,
5468
+ () => client.getIsPending(),
5469
+ () => true
5470
+ );
5471
+ return (0, import_react6.useMemo)(
5472
+ () => ({
5473
+ policies,
5474
+ isLoading: policies.length === 0 && isPending
5475
+ }),
5476
+ [policies, isPending]
5477
+ );
5478
+ };
5302
5479
  var useSchematicIsPending = (opts) => {
5303
5480
  const client = useSchematicClient(opts);
5304
5481
  const subscribe = (0, import_react6.useCallback)(
@@ -593,6 +593,12 @@ declare interface CheckFlagsResponseData {
593
593
  creditBalances?: {
594
594
  [key: string]: CompanyCreditBalance;
595
595
  };
596
+ /**
597
+ * Credit spend policies binding the evaluated company and user; empty when none bind. Each response carries the whole set, so replace any previously received set with it. Advisory: the flag values do not reflect them, since a check names no draw amount
598
+ * @type {Array<CreditSpendPolicy>}
599
+ * @memberof CheckFlagsResponseData
600
+ */
601
+ creditSpendPolicies: Array<CreditSpendPolicy_2>;
596
602
  /**
597
603
  *
598
604
  * @type {Array<CheckFlagResponseData>}
@@ -946,6 +952,153 @@ declare const CreditBalancesFromJSON: (json: any) => CreditBalances;
946
952
 
947
953
  declare type CreditBalancesListenerFn = (value: CreditBalances) => void;
948
954
 
955
+ /** Every spend policy binding the current context, company-scope first */
956
+ export declare type CreditSpendPolicies = CreditSpendPolicy[];
957
+
958
+ /** Compare two policy sets by value, so an unchanged restream skips the notify. */
959
+ declare const creditSpendPoliciesEqual: (a: CreditSpendPolicies, b: CreditSpendPolicies) => boolean;
960
+
961
+ declare const CreditSpendPoliciesFromJSON: (json: any) => CreditSpendPolicies;
962
+
963
+ declare type CreditSpendPoliciesListenerFn = (value: CreditSpendPolicies) => void;
964
+
965
+ /** One limit on spending a credit */
966
+ export declare type CreditSpendPolicy = {
967
+ id: string;
968
+ creditId: string;
969
+ kind: CreditSpendPolicyKind;
970
+ scope: CreditSpendPolicyScope;
971
+ label?: string;
972
+ /** The ceiling, in credits */
973
+ limit: number;
974
+ /** How much of the limit is already spent in the current period; 0 for per_draw */
975
+ consumed: number;
976
+ /** For windowed kinds, when the current period ends and consumed no longer applies */
977
+ resetsAt?: Date;
978
+ /** Set for windowed kinds only */
979
+ window?: CreditSpendWindow;
980
+ };
981
+
982
+ /**
983
+ *
984
+ * @export
985
+ * @interface CreditSpendPolicy
986
+ */
987
+ declare interface CreditSpendPolicy_2 {
988
+ /**
989
+ * How much of the limit is already spent in the current period
990
+ * @type {number}
991
+ * @memberof CreditSpendPolicy
992
+ */
993
+ consumed?: number;
994
+ /**
995
+ * The credit the policy limits
996
+ * @type {string}
997
+ * @memberof CreditSpendPolicy
998
+ */
999
+ creditId: string;
1000
+ /**
1001
+ * The ID of the policy
1002
+ * @type {string}
1003
+ * @memberof CreditSpendPolicy
1004
+ */
1005
+ id: string;
1006
+ /**
1007
+ * How the limit is applied
1008
+ * @type {string}
1009
+ * @memberof CreditSpendPolicy
1010
+ */
1011
+ kind: string;
1012
+ /**
1013
+ * The name the account gave the policy
1014
+ * @type {string}
1015
+ * @memberof CreditSpendPolicy
1016
+ */
1017
+ label?: string | null;
1018
+ /**
1019
+ * The ceiling, in credits
1020
+ * @type {number}
1021
+ * @memberof CreditSpendPolicy
1022
+ */
1023
+ limit: number;
1024
+ /**
1025
+ * For a windowed limit, when the current period ends and consumed no longer applies
1026
+ * @type {Date}
1027
+ * @memberof CreditSpendPolicy
1028
+ */
1029
+ resetsAt?: Date | null;
1030
+ /**
1031
+ * Whether the policy limits the company or one user
1032
+ * @type {CreditSpendPolicyScope}
1033
+ * @memberof CreditSpendPolicy
1034
+ */
1035
+ scope: CreditSpendPolicyScope_2;
1036
+ /**
1037
+ * For a windowed limit, the period it accumulates over
1038
+ * @type {CreditSpendWindow}
1039
+ * @memberof CreditSpendPolicy
1040
+ */
1041
+ window?: CreditSpendWindow_2;
1042
+ }
1043
+
1044
+ /** How a spend policy applies its limit. Unknown kinds are carried, not dropped:
1045
+ * the server may ship a kind this SDK version predates. */
1046
+ export declare type CreditSpendPolicyKind = "per_draw" | "window" | (string & {});
1047
+
1048
+ declare type CreditSpendPolicyListenerFn = CreditSpendPoliciesListenerFn | EmptyListenerFn;
1049
+
1050
+ /** Whether a spend policy limits the company or one user */
1051
+ export declare type CreditSpendPolicyScope = "company" | "user" | (string & {});
1052
+
1053
+ /**
1054
+ *
1055
+ * @export
1056
+ */
1057
+ declare const CreditSpendPolicyScope_2: {
1058
+ readonly Company: "company";
1059
+ readonly User: "user";
1060
+ readonly Group: "group";
1061
+ };
1062
+
1063
+ declare type CreditSpendPolicyScope_2 = (typeof CreditSpendPolicyScope_2)[keyof typeof CreditSpendPolicyScope_2];
1064
+
1065
+ /** The period a windowed spend policy accumulates over */
1066
+ export declare type CreditSpendWindow = {
1067
+ unit: string;
1068
+ count: number;
1069
+ };
1070
+
1071
+ /**
1072
+ * Schematic API
1073
+ * Schematic API
1074
+ *
1075
+ * The version of the OpenAPI document: 0.1
1076
+ *
1077
+ *
1078
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
1079
+ * https://openapi-generator.tech
1080
+ * Do not edit the class manually.
1081
+ */
1082
+ /**
1083
+ *
1084
+ * @export
1085
+ * @interface CreditSpendWindow
1086
+ */
1087
+ declare interface CreditSpendWindow_2 {
1088
+ /**
1089
+ * How many units make up one period
1090
+ * @type {number}
1091
+ * @memberof CreditSpendWindow
1092
+ */
1093
+ count: number;
1094
+ /**
1095
+ * The period the limit accumulates over
1096
+ * @type {string}
1097
+ * @memberof CreditSpendWindow
1098
+ */
1099
+ unit: string;
1100
+ }
1101
+
949
1102
  /**
950
1103
  *
951
1104
  * @export
@@ -1463,6 +1616,7 @@ export declare class Schematic {
1463
1616
  private isPendingListeners;
1464
1617
  private planListeners;
1465
1618
  private creditBalanceListeners;
1619
+ private creditSpendPolicyListeners;
1466
1620
  private storage;
1467
1621
  private persistFlagState;
1468
1622
  private flagStateCacheKey;
@@ -1473,6 +1627,7 @@ export declare class Schematic {
1473
1627
  private featureUsageEventMap;
1474
1628
  private planChecks;
1475
1629
  private creditBalances;
1630
+ private creditSpendPolicies;
1476
1631
  private webSocketUrl;
1477
1632
  private webSocketConnectionTimeout;
1478
1633
  private webSocketReconnect;
@@ -1691,6 +1846,10 @@ export declare class Schematic {
1691
1846
  getCreditBalances: () => CreditBalances;
1692
1847
  /** Get the company's lease-aware balance for a single credit type in the current context */
1693
1848
  getCreditBalance: (creditId: string) => CreditBalance | undefined;
1849
+ /** Get every credit spend policy binding the current context, company-scope first */
1850
+ getCreditSpendPolicies: () => CreditSpendPolicies;
1851
+ /** Get the spend policies binding a single credit type in the current context */
1852
+ getCreditSpendPoliciesForCredit: (creditId: string) => CreditSpendPolicies;
1694
1853
  getFlagCheck: (flagKey: string) => CheckFlagReturn | undefined;
1695
1854
  getFlagValue: (flagKey: string) => boolean | undefined;
1696
1855
  /** Register an event listener that will be notified with the boolean value for a given flag when this value changes */
@@ -1700,11 +1859,14 @@ export declare class Schematic {
1700
1859
  addPlanListener: (listener: PlanListenerFn) => () => void;
1701
1860
  /** Register an event listener that will be notified with the company's credit balances (keyed by credit ID) whenever they change */
1702
1861
  addCreditBalanceListener: (listener: CreditBalanceListenerFn) => () => void;
1862
+ /** Register an event listener that will be notified with the context's credit spend policies whenever the set changes */
1863
+ addCreditSpendPolicyListener: (listener: CreditSpendPolicyListenerFn) => () => void;
1703
1864
  private notifyFlagCheckListeners;
1704
1865
  /** Add or update a CheckFlagReturn in the featureUsageEventMap */
1705
1866
  private updateFeatureUsageEventMap;
1706
1867
  private notifyFlagValueListeners;
1707
1868
  private notifyPlanListeners;
1869
+ private notifyCreditSpendPolicyListeners;
1708
1870
  private notifyCreditBalanceListeners;
1709
1871
  }
1710
1872
 
@@ -1752,6 +1914,14 @@ export declare type SchematicCreditBalance = {
1752
1914
  isLoading: boolean;
1753
1915
  };
1754
1916
 
1917
+ /** The credit spend policies binding the current context, plus a loading flag */
1918
+ export declare type SchematicCreditSpendPolicies = {
1919
+ /** Company-scope policies first, then the actor's own; empty when none bind */
1920
+ policies: SchematicJS.CreditSpendPolicies;
1921
+ /** True while the policies are still loading and nothing has arrived yet */
1922
+ isLoading: boolean;
1923
+ };
1924
+
1755
1925
  export declare interface SchematicHookOpts {
1756
1926
  client?: SchematicJS.Schematic;
1757
1927
  }
@@ -1822,6 +1992,15 @@ declare namespace SchematicJS {
1822
1992
  CreditBalances,
1823
1993
  CreditBalancesFromJSON,
1824
1994
  CreditBalancesListenerFn,
1995
+ CreditSpendPolicies,
1996
+ creditSpendPoliciesEqual,
1997
+ CreditSpendPoliciesFromJSON,
1998
+ CreditSpendPoliciesListenerFn,
1999
+ CreditSpendPolicy,
2000
+ CreditSpendPolicyKind,
2001
+ CreditSpendPolicyListenerFn,
2002
+ CreditSpendPolicyScope,
2003
+ CreditSpendWindow,
1825
2004
  DEFAULT_INVOICE_QUERY,
1826
2005
  Discount,
1827
2006
  EmptyListenerFn,
@@ -2206,6 +2385,11 @@ export declare const useSchematicContext: (opts?: SchematicHookOpts) => {
2206
2385
  */
2207
2386
  export declare const useSchematicCreditBalance: (creditId: string | undefined, opts?: SchematicHookOpts) => SchematicCreditBalance;
2208
2387
 
2388
+ /** Every credit spend policy binding the current context, company-scope first.
2389
+ * Streams over the websocket, so an edit in the dashboard lands here without a
2390
+ * refetch. */
2391
+ export declare const useSchematicCreditSpendPolicies: (opts?: SchematicHookOpts) => SchematicCreditSpendPolicies;
2392
+
2209
2393
  export declare const useSchematicEntitlement: (key: string, opts?: UseSchematicFlagOpts) => SchematicJS.CheckFlagReturn;
2210
2394
 
2211
2395
  export declare const useSchematicEvents: (opts?: SchematicHookOpts) => {
@@ -1,4 +1,4 @@
1
- // node_modules/.pnpm/@schematichq+schematic-js@1.9.0/node_modules/@schematichq/schematic-js/dist/schematic.esm.js
1
+ // node_modules/.pnpm/@schematichq+schematic-js@1.10.0/node_modules/@schematichq/schematic-js/dist/schematic.esm.js
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -994,6 +994,43 @@ function DatastreamCompanyPlanFromJSONTyped(json, ignoreDiscriminator) {
994
994
  trialStatus: json["trial_status"] == null ? void 0 : TrialStatusFromJSON(json["trial_status"])
995
995
  };
996
996
  }
997
+ function CreditSpendPolicyScopeFromJSON(json) {
998
+ return CreditSpendPolicyScopeFromJSONTyped(json, false);
999
+ }
1000
+ function CreditSpendPolicyScopeFromJSONTyped(json, ignoreDiscriminator) {
1001
+ return json;
1002
+ }
1003
+ function CreditSpendWindowFromJSON(json) {
1004
+ return CreditSpendWindowFromJSONTyped(json, false);
1005
+ }
1006
+ function CreditSpendWindowFromJSONTyped(json, ignoreDiscriminator) {
1007
+ if (json == null) {
1008
+ return json;
1009
+ }
1010
+ return {
1011
+ count: json["count"],
1012
+ unit: json["unit"]
1013
+ };
1014
+ }
1015
+ function CreditSpendPolicyFromJSON(json) {
1016
+ return CreditSpendPolicyFromJSONTyped(json, false);
1017
+ }
1018
+ function CreditSpendPolicyFromJSONTyped(json, ignoreDiscriminator) {
1019
+ if (json == null) {
1020
+ return json;
1021
+ }
1022
+ return {
1023
+ consumed: json["consumed"] == null ? void 0 : json["consumed"],
1024
+ creditId: json["credit_id"],
1025
+ id: json["id"],
1026
+ kind: json["kind"],
1027
+ label: json["label"] == null ? void 0 : json["label"],
1028
+ limit: json["limit"],
1029
+ resetsAt: json["resets_at"] == null ? void 0 : new Date(json["resets_at"]),
1030
+ scope: CreditSpendPolicyScopeFromJSON(json["scope"]),
1031
+ window: json["window"] == null ? void 0 : CreditSpendWindowFromJSON(json["window"])
1032
+ };
1033
+ }
997
1034
  function CheckFlagsResponseDataFromJSON(json) {
998
1035
  return CheckFlagsResponseDataFromJSONTyped(json, false);
999
1036
  }
@@ -1003,6 +1040,9 @@ function CheckFlagsResponseDataFromJSONTyped(json, ignoreDiscriminator) {
1003
1040
  }
1004
1041
  return {
1005
1042
  creditBalances: json["credit_balances"] == null ? void 0 : mapValues(json["credit_balances"], CompanyCreditBalanceFromJSON),
1043
+ creditSpendPolicies: json["credit_spend_policies"].map(
1044
+ CreditSpendPolicyFromJSON
1045
+ ),
1006
1046
  flags: json["flags"].map(CheckFlagResponseDataFromJSON),
1007
1047
  plan: json["plan"] == null ? void 0 : DatastreamCompanyPlanFromJSON(json["plan"])
1008
1048
  };
@@ -1131,7 +1171,55 @@ var CreditBalancesFromJSON = (json) => {
1131
1171
  }
1132
1172
  return balances;
1133
1173
  };
1134
- var cacheVersion = "7b920cf4";
1174
+ var CreditSpendPoliciesFromJSON = (json) => {
1175
+ if (!Array.isArray(json)) {
1176
+ return [];
1177
+ }
1178
+ const policies = [];
1179
+ for (const raw of json) {
1180
+ if (raw == null || typeof raw !== "object") continue;
1181
+ const id = raw.id;
1182
+ const creditId = raw.credit_id ?? raw.creditId;
1183
+ const kind = raw.kind;
1184
+ const scope = raw.scope;
1185
+ const limit = raw.limit;
1186
+ if (typeof id !== "string" || typeof creditId !== "string" || typeof kind !== "string" || typeof scope !== "string" || typeof limit !== "number") {
1187
+ continue;
1188
+ }
1189
+ const policy = {
1190
+ id,
1191
+ creditId,
1192
+ kind,
1193
+ scope,
1194
+ limit,
1195
+ consumed: typeof raw.consumed === "number" ? raw.consumed : 0
1196
+ };
1197
+ if (typeof raw.label === "string") {
1198
+ policy.label = raw.label;
1199
+ }
1200
+ const window2 = raw.window;
1201
+ if (window2 != null && typeof window2 === "object" && typeof window2.unit === "string" && typeof window2.count === "number") {
1202
+ policy.window = { unit: window2.unit, count: window2.count };
1203
+ }
1204
+ const resetsAt = raw.resets_at ?? raw.resetsAt;
1205
+ if (typeof resetsAt === "string" || resetsAt instanceof Date) {
1206
+ const parsed = new Date(resetsAt);
1207
+ if (!Number.isNaN(parsed.getTime())) {
1208
+ policy.resetsAt = parsed;
1209
+ }
1210
+ }
1211
+ policies.push(policy);
1212
+ }
1213
+ return policies;
1214
+ };
1215
+ var creditSpendPoliciesEqual = (a, b) => {
1216
+ if (a.length !== b.length) return false;
1217
+ return a.every((x, i) => {
1218
+ const y = b[i];
1219
+ return x.id === y.id && x.creditId === y.creditId && x.kind === y.kind && x.scope === y.scope && x.label === y.label && x.limit === y.limit && x.consumed === y.consumed && x.resetsAt?.getTime() === y.resetsAt?.getTime() && x.window?.unit === y.window?.unit && x.window?.count === y.window?.count;
1220
+ });
1221
+ };
1222
+ var cacheVersion = "35d92bc6";
1135
1223
  function contextString(context) {
1136
1224
  const sortedContext = Object.keys(context).reduce((acc, key) => {
1137
1225
  const sortedKeys = Object.keys(
@@ -1146,7 +1234,7 @@ function contextString(context) {
1146
1234
  }, {});
1147
1235
  return JSON.stringify(sortedContext);
1148
1236
  }
1149
- var version = "1.9.0";
1237
+ var version = "1.10.0";
1150
1238
  var SINGLETON = {};
1151
1239
  var models_exports = {};
1152
1240
  __export(models_exports, {
@@ -2313,6 +2401,7 @@ var flagStateCachePrefix = "schematicCache";
2313
2401
  var flagStateCacheMaxContexts = 10;
2314
2402
  var flagStateCacheDefaultMaxAgeMs = 7 * 24 * 60 * 60 * 1e3;
2315
2403
  var emptyCreditBalances = Object.freeze({});
2404
+ var emptyCreditSpendPolicies = [];
2316
2405
  var Schematic = class {
2317
2406
  _additionalHeaders = {};
2318
2407
  apiKey;
@@ -2330,6 +2419,7 @@ var Schematic = class {
2330
2419
  isPendingListeners = /* @__PURE__ */ new Set();
2331
2420
  planListeners = /* @__PURE__ */ new Set();
2332
2421
  creditBalanceListeners = /* @__PURE__ */ new Set();
2422
+ creditSpendPolicyListeners = /* @__PURE__ */ new Set();
2333
2423
  storage;
2334
2424
  persistFlagState = true;
2335
2425
  flagStateCacheKey;
@@ -2340,6 +2430,7 @@ var Schematic = class {
2340
2430
  featureUsageEventMap = {};
2341
2431
  planChecks = {};
2342
2432
  creditBalances = {};
2433
+ creditSpendPolicies = {};
2343
2434
  webSocketUrl = "wss://api.schematichq.com";
2344
2435
  webSocketConnectionTimeout = 1e4;
2345
2436
  webSocketReconnect = true;
@@ -2732,6 +2823,19 @@ var Schematic = class {
2732
2823
  this.notifyCreditBalanceListeners(merged);
2733
2824
  }
2734
2825
  }
2826
+ if (message.credit_spend_policies !== void 0 && message.credit_spend_policies !== null) {
2827
+ const contextStr = contextString(context);
2828
+ const next = CreditSpendPoliciesFromJSON(message.credit_spend_policies);
2829
+ const previous = this.creditSpendPolicies[contextStr] ?? [];
2830
+ if (!creditSpendPoliciesEqual(previous, next)) {
2831
+ this.creditSpendPolicies[contextStr] = next;
2832
+ this.debug(
2833
+ `WebSocket credit spend policy update received. Notifying listeners`,
2834
+ { creditSpendPolicies: next }
2835
+ );
2836
+ this.notifyCreditSpendPolicyListeners(next);
2837
+ }
2838
+ }
2735
2839
  this.persistContextToCache(contextString(context));
2736
2840
  this.flushContextDependentEventQueue();
2737
2841
  this.setIsPending(false);
@@ -3233,10 +3337,19 @@ var Schematic = class {
3233
3337
  this.creditBalances[contextStr] = balances;
3234
3338
  }
3235
3339
  }
3340
+ let revivedCreditSpendPolicies;
3341
+ if (entry.creditSpendPolicies !== void 0 && entry.creditSpendPolicies !== null) {
3342
+ const policies = CreditSpendPoliciesFromJSON(entry.creditSpendPolicies);
3343
+ if (policies.length > 0) {
3344
+ revivedCreditSpendPolicies = policies;
3345
+ this.creditSpendPolicies[contextStr] = policies;
3346
+ }
3347
+ }
3236
3348
  fresh[contextStr] = {
3237
3349
  checks: revivedChecks,
3238
3350
  plan: revivedPlan,
3239
3351
  creditBalances: revivedCreditBalances,
3352
+ creditSpendPolicies: revivedCreditSpendPolicies,
3240
3353
  updatedAt: entry.updatedAt
3241
3354
  };
3242
3355
  contextsLoaded += 1;
@@ -3250,7 +3363,8 @@ var Schematic = class {
3250
3363
  const checksForContext = this.checks[contextStr];
3251
3364
  const planForContext = this.planChecks[contextStr];
3252
3365
  const creditBalancesForContext = this.creditBalances[contextStr];
3253
- if (checksForContext === void 0 && planForContext === void 0 && creditBalancesForContext === void 0) {
3366
+ const creditSpendPoliciesForContext = this.creditSpendPolicies[contextStr];
3367
+ if (checksForContext === void 0 && planForContext === void 0 && creditBalancesForContext === void 0 && creditSpendPoliciesForContext === void 0) {
3254
3368
  return;
3255
3369
  }
3256
3370
  if (this.cachedFlagState === null) {
@@ -3269,6 +3383,7 @@ var Schematic = class {
3269
3383
  checks: cleanChecks,
3270
3384
  plan: planForContext,
3271
3385
  creditBalances: creditBalancesForContext,
3386
+ creditSpendPolicies: creditSpendPoliciesForContext,
3272
3387
  updatedAt: Date.now()
3273
3388
  };
3274
3389
  const entries = Object.entries(this.cachedFlagState.contexts);
@@ -3285,6 +3400,9 @@ var Schematic = class {
3285
3400
  for (const key of Object.keys(this.creditBalances)) {
3286
3401
  if (!survivorKeys.has(key)) delete this.creditBalances[key];
3287
3402
  }
3403
+ for (const key of Object.keys(this.creditSpendPolicies)) {
3404
+ if (!survivorKeys.has(key)) delete this.creditSpendPolicies[key];
3405
+ }
3288
3406
  this.cachedFlagState = {
3289
3407
  version: cacheVersion,
3290
3408
  contexts: Object.fromEntries(survivors)
@@ -3879,6 +3997,18 @@ var Schematic = class {
3879
3997
  const contextStr = contextString(this.context);
3880
3998
  return this.creditBalances[contextStr]?.[creditId];
3881
3999
  };
4000
+ /** Get every credit spend policy binding the current context, company-scope first */
4001
+ getCreditSpendPolicies = () => {
4002
+ const contextStr = contextString(this.context);
4003
+ return this.creditSpendPolicies[contextStr] ?? emptyCreditSpendPolicies;
4004
+ };
4005
+ /** Get the spend policies binding a single credit type in the current context */
4006
+ getCreditSpendPoliciesForCredit = (creditId) => {
4007
+ const policies = this.getCreditSpendPolicies().filter(
4008
+ (policy) => policy.creditId === creditId
4009
+ );
4010
+ return policies.length > 0 ? policies : emptyCreditSpendPolicies;
4011
+ };
3882
4012
  // flag checks state
3883
4013
  getFlagCheck = (flagKey) => {
3884
4014
  const contextStr = contextString(this.context);
@@ -3945,6 +4075,13 @@ var Schematic = class {
3945
4075
  this.creditBalanceListeners.delete(listener);
3946
4076
  };
3947
4077
  };
4078
+ /** Register an event listener that will be notified with the context's credit spend policies whenever the set changes */
4079
+ addCreditSpendPolicyListener = (listener) => {
4080
+ this.creditSpendPolicyListeners.add(listener);
4081
+ return () => {
4082
+ this.creditSpendPolicyListeners.delete(listener);
4083
+ };
4084
+ };
3948
4085
  notifyFlagCheckListeners = (flagKey, check) => {
3949
4086
  const listeners = this.flagCheckListeners?.[flagKey] ?? [];
3950
4087
  if (listeners.size > 0) {
@@ -4008,6 +4145,16 @@ var Schematic = class {
4008
4145
  });
4009
4146
  });
4010
4147
  };
4148
+ notifyCreditSpendPolicyListeners = (value) => {
4149
+ const listeners = this.creditSpendPolicyListeners ?? [];
4150
+ listeners.forEach((listener) => {
4151
+ if (listener.length > 0) {
4152
+ listener(value);
4153
+ return;
4154
+ }
4155
+ listener();
4156
+ });
4157
+ };
4011
4158
  notifyCreditBalanceListeners = (value) => {
4012
4159
  const listeners = this.creditBalanceListeners ?? [];
4013
4160
  if (listeners.size > 0) {
@@ -5030,7 +5177,7 @@ function staticSource(data) {
5030
5177
  }
5031
5178
 
5032
5179
  // src/version.ts
5033
- var version2 = "1.9.0";
5180
+ var version2 = "1.10.0";
5034
5181
 
5035
5182
  // src/context/schematic.tsx
5036
5183
  import { jsx as jsx4 } from "react/jsx-runtime";
@@ -5123,6 +5270,7 @@ var useSchematic = () => {
5123
5270
 
5124
5271
  // src/hooks/schematic.ts
5125
5272
  import { useMemo as useMemo6, useSyncExternalStore as useSyncExternalStore2, useCallback as useCallback2 } from "react";
5273
+ var emptyPolicies = [];
5126
5274
  var useSchematicClient = (opts) => {
5127
5275
  const schematic = useSchematic();
5128
5276
  const { client } = opts ?? {};
@@ -5238,6 +5386,34 @@ var useSchematicCreditBalance = (creditId, opts) => {
5238
5386
  [balance, isPending]
5239
5387
  );
5240
5388
  };
5389
+ var useSchematicCreditSpendPolicies = (opts) => {
5390
+ const client = useSchematicClient(opts);
5391
+ const subscribe = useCallback2(
5392
+ (callback) => client.addCreditSpendPolicyListener(callback),
5393
+ [client]
5394
+ );
5395
+ const policies = useSyncExternalStore2(
5396
+ subscribe,
5397
+ () => client.getCreditSpendPolicies(),
5398
+ () => emptyPolicies
5399
+ );
5400
+ const isPendingSubscribe = useCallback2(
5401
+ (callback) => client.addIsPendingListener(callback),
5402
+ [client]
5403
+ );
5404
+ const isPending = useSyncExternalStore2(
5405
+ isPendingSubscribe,
5406
+ () => client.getIsPending(),
5407
+ () => true
5408
+ );
5409
+ return useMemo6(
5410
+ () => ({
5411
+ policies,
5412
+ isLoading: policies.length === 0 && isPending
5413
+ }),
5414
+ [policies, isPending]
5415
+ );
5416
+ };
5241
5417
  var useSchematicIsPending = (opts) => {
5242
5418
  const client = useSchematicClient(opts);
5243
5419
  const subscribe = useCallback2(
@@ -5269,6 +5445,7 @@ export {
5269
5445
  useSchematic,
5270
5446
  useSchematicContext,
5271
5447
  useSchematicCreditBalance,
5448
+ useSchematicCreditSpendPolicies,
5272
5449
  useSchematicEntitlement,
5273
5450
  useSchematicEvents,
5274
5451
  useSchematicFlag,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schematichq/schematic-react",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "main": "dist/schematic-react.cjs.js",
5
5
  "module": "dist/schematic-react.esm.js",
6
6
  "types": "dist/schematic-react.d.ts",
@@ -34,7 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@eslint/js": "^10.0.1",
36
36
  "@microsoft/api-extractor": "^7.59.2",
37
- "@schematichq/schematic-js": "^1.9.0",
37
+ "@schematichq/schematic-js": "^1.10.0",
38
38
  "@testing-library/dom": "^10.4.2",
39
39
  "@testing-library/jest-dom": "^7.0.1",
40
40
  "@testing-library/react": "^16.3.3",