@kerne/react 1.0.0 → 1.2.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.
@@ -194,6 +194,11 @@ var defaultLocalization = {
194
194
  changePlan: "Change plan",
195
195
  current: "Current",
196
196
  switchTo: "Switch",
197
+ switching: "Switching...",
198
+ switchConfirmTitle: "Switch to {plan}?",
199
+ switchConfirmBody: "This applies right away, and your next invoice reflects the price difference for the rest of this period.",
200
+ switchConfirmButton: "Yes, switch",
201
+ switchConfirmDismiss: "Never mind",
197
202
  noPlans: "No other plans are available right now.",
198
203
  notOnInterval: "Not available on this interval",
199
204
  loadFailed: "Could not load your plan right now.",
@@ -373,6 +378,11 @@ var KerneClient = class {
373
378
  baseUrl: this.baseUrl,
374
379
  appId: this.appId,
375
380
  timeout: config.timeout,
381
+ // Forwarded so <KerneProvider defaultRedirects={...} headers={...}> is
382
+ // actually honored - the type inherited them from KerneConfig, but the
383
+ // client was dropping both silently.
384
+ headers: config.headers,
385
+ defaultRedirects: config.defaultRedirects,
376
386
  onUnauthorized: () => {
377
387
  if (process.env.NODE_ENV === "development") {
378
388
  console.warn("[Kerne] 401 received. Auto-logout disabled in dev.");
@@ -690,8 +700,9 @@ var KerneClient = class {
690
700
  return response;
691
701
  }
692
702
  // Cross-Origin Session Handoff
693
- // For apps/portal (auth hosted on its own origin): call this at the end of
694
- // a successful auth flow (login/magic-link/register/activation) instead of
703
+ // For a hosted auth origin (auth on its own domain, separate from the
704
+ // tenant's app): call this at the end of a successful auth flow
705
+ // (login/magic-link/register/activation) instead of
695
706
  // a plain `window.location.href = url` whenever `url` isn't same-origin -
696
707
  // the exchange on the other end is picked up automatically by
697
708
  // `exchangeHandoffOnLoad()`, no code required on the tenant app's side.
@@ -743,6 +754,24 @@ var KerneClient = class {
743
754
  async check(featureKey, requested) {
744
755
  return this.kerne.check(featureKey, { requested });
745
756
  }
757
+ /** Atomically check a QUOTA limit and commit the consumption. Reports for the signed-in subject. */
758
+ async consume(featureKey, params) {
759
+ return this.kerne.consume(featureKey, params);
760
+ }
761
+ /** Report usage without a limit check, for the signed-in subject. See `reportUsageBatch()` for many at once. */
762
+ async reportUsage(featureKey, params) {
763
+ return this.kerne.reportUsage(featureKey, { subject: this.requireUserId(), ...params });
764
+ }
765
+ /** Several usage events in one request instead of one call each - kills the manual request staggering. */
766
+ async reportUsageBatch(events) {
767
+ const subject = this.requireUserId();
768
+ return this.kerne.reportUsageBatch(events.map((e) => ({ subject, ...e })));
769
+ }
770
+ requireUserId() {
771
+ const id = this.user?.id;
772
+ if (!id) throw new Error("reportUsage requires a signed-in user; none is loaded yet.");
773
+ return id;
774
+ }
746
775
  async createCheckout(planPriceId, options) {
747
776
  const { url } = await this.kerne.billing.checkout(planPriceId, options);
748
777
  return url;
@@ -925,7 +954,7 @@ function useAuth() {
925
954
  (token2, password) => client.completeActivation(token2, password),
926
955
  [client]
927
956
  ),
928
- // Cross-origin handoff (apps/portal -> tenant app_url) - see redirectWithSession on KerneClient.
957
+ // Cross-origin handoff (hosted auth origin -> tenant app_url) - see redirectWithSession on KerneClient.
929
958
  redirectWithSession: useCallback2((url) => client.redirectWithSession(url), [client])
930
959
  };
931
960
  }
@@ -981,6 +1010,63 @@ function useChangePlan() {
981
1010
  );
982
1011
  return { changePlan, isChanging };
983
1012
  }
1013
+ function usePlanSwitch(subscription, refetchSubscription, options) {
1014
+ const { openCheckout } = useCheckout();
1015
+ const { changePlan } = useChangePlan();
1016
+ const [pending, setPending] = useState(null);
1017
+ const [switching, setSwitching] = useState(false);
1018
+ const [switchError, setSwitchError] = useState(null);
1019
+ const [checkoutPriceId, setCheckoutPriceId] = useState(null);
1020
+ const [checkoutError, setCheckoutError] = useState(null);
1021
+ const successUrl = options?.successUrl;
1022
+ const cancelUrl = options?.cancelUrl;
1023
+ const selectPlan = useCallback2(
1024
+ async (plan, price) => {
1025
+ if (subscription) {
1026
+ setSwitchError(null);
1027
+ setPending({ plan, price });
1028
+ return;
1029
+ }
1030
+ setCheckoutError(null);
1031
+ setCheckoutPriceId(price.id);
1032
+ try {
1033
+ await openCheckout(price.id, { successUrl, cancelUrl });
1034
+ } catch (e) {
1035
+ setCheckoutError(e);
1036
+ setCheckoutPriceId(null);
1037
+ }
1038
+ },
1039
+ [subscription, openCheckout, successUrl, cancelUrl]
1040
+ );
1041
+ const confirmSwitch = useCallback2(async () => {
1042
+ if (!pending || !subscription) return;
1043
+ setSwitching(true);
1044
+ setSwitchError(null);
1045
+ try {
1046
+ await changePlan(subscription.id, pending.price.id);
1047
+ setPending(null);
1048
+ refetchSubscription();
1049
+ } catch (e) {
1050
+ setSwitchError(e);
1051
+ } finally {
1052
+ setSwitching(false);
1053
+ }
1054
+ }, [pending, subscription, changePlan, refetchSubscription]);
1055
+ const dismissSwitch = useCallback2(() => {
1056
+ setPending(null);
1057
+ setSwitchError(null);
1058
+ }, []);
1059
+ return {
1060
+ pending,
1061
+ switching,
1062
+ switchError,
1063
+ checkoutPriceId,
1064
+ checkoutError,
1065
+ selectPlan,
1066
+ confirmSwitch,
1067
+ dismissSwitch
1068
+ };
1069
+ }
984
1070
  function useAccess(featureKey, requested) {
985
1071
  const client = useClient();
986
1072
  const [state, setState] = useState({
@@ -1145,6 +1231,23 @@ function useUsage(featureKey) {
1145
1231
  }, [client, featureKey]);
1146
1232
  return state;
1147
1233
  }
1234
+ function useReportUsage() {
1235
+ const client = useClient();
1236
+ return {
1237
+ consume: useCallback2(
1238
+ (featureKey, params) => client.consume(featureKey, params),
1239
+ [client]
1240
+ ),
1241
+ report: useCallback2(
1242
+ (featureKey, params) => client.reportUsage(featureKey, params),
1243
+ [client]
1244
+ ),
1245
+ reportBatch: useCallback2(
1246
+ (events) => client.reportUsageBatch(events),
1247
+ [client]
1248
+ )
1249
+ };
1250
+ }
1148
1251
  function useWaitlist() {
1149
1252
  const client = useClient();
1150
1253
  return {
@@ -1210,12 +1313,14 @@ export {
1210
1313
  usePortal,
1211
1314
  useCancelSubscription,
1212
1315
  useChangePlan,
1316
+ usePlanSwitch,
1213
1317
  useAccess,
1214
1318
  useSubscription,
1215
1319
  usePlans,
1216
1320
  useAuthConfig,
1217
1321
  useEntitlements,
1218
1322
  useUsage,
1323
+ useReportUsage,
1219
1324
  useWaitlist,
1220
1325
  useInvitation,
1221
1326
  useUser
@@ -194,6 +194,11 @@ var defaultLocalization = {
194
194
  changePlan: "Change plan",
195
195
  current: "Current",
196
196
  switchTo: "Switch",
197
+ switching: "Switching...",
198
+ switchConfirmTitle: "Switch to {plan}?",
199
+ switchConfirmBody: "This applies right away, and your next invoice reflects the price difference for the rest of this period.",
200
+ switchConfirmButton: "Yes, switch",
201
+ switchConfirmDismiss: "Never mind",
197
202
  noPlans: "No other plans are available right now.",
198
203
  notOnInterval: "Not available on this interval",
199
204
  loadFailed: "Could not load your plan right now.",
@@ -373,6 +378,11 @@ var KerneClient = (_class = class {
373
378
  baseUrl: this.baseUrl,
374
379
  appId: this.appId,
375
380
  timeout: config.timeout,
381
+ // Forwarded so <KerneProvider defaultRedirects={...} headers={...}> is
382
+ // actually honored - the type inherited them from KerneConfig, but the
383
+ // client was dropping both silently.
384
+ headers: config.headers,
385
+ defaultRedirects: config.defaultRedirects,
376
386
  onUnauthorized: () => {
377
387
  if (process.env.NODE_ENV === "development") {
378
388
  console.warn("[Kerne] 401 received. Auto-logout disabled in dev.");
@@ -690,8 +700,9 @@ var KerneClient = (_class = class {
690
700
  return response;
691
701
  }
692
702
  // Cross-Origin Session Handoff
693
- // For apps/portal (auth hosted on its own origin): call this at the end of
694
- // a successful auth flow (login/magic-link/register/activation) instead of
703
+ // For a hosted auth origin (auth on its own domain, separate from the
704
+ // tenant's app): call this at the end of a successful auth flow
705
+ // (login/magic-link/register/activation) instead of
695
706
  // a plain `window.location.href = url` whenever `url` isn't same-origin -
696
707
  // the exchange on the other end is picked up automatically by
697
708
  // `exchangeHandoffOnLoad()`, no code required on the tenant app's side.
@@ -743,6 +754,24 @@ var KerneClient = (_class = class {
743
754
  async check(featureKey, requested) {
744
755
  return this.kerne.check(featureKey, { requested });
745
756
  }
757
+ /** Atomically check a QUOTA limit and commit the consumption. Reports for the signed-in subject. */
758
+ async consume(featureKey, params) {
759
+ return this.kerne.consume(featureKey, params);
760
+ }
761
+ /** Report usage without a limit check, for the signed-in subject. See `reportUsageBatch()` for many at once. */
762
+ async reportUsage(featureKey, params) {
763
+ return this.kerne.reportUsage(featureKey, { subject: this.requireUserId(), ...params });
764
+ }
765
+ /** Several usage events in one request instead of one call each - kills the manual request staggering. */
766
+ async reportUsageBatch(events) {
767
+ const subject = this.requireUserId();
768
+ return this.kerne.reportUsageBatch(events.map((e) => ({ subject, ...e })));
769
+ }
770
+ requireUserId() {
771
+ const id = _optionalChain([this, 'access', _21 => _21.user, 'optionalAccess', _22 => _22.id]);
772
+ if (!id) throw new Error("reportUsage requires a signed-in user; none is loaded yet.");
773
+ return id;
774
+ }
746
775
  async createCheckout(planPriceId, options) {
747
776
  const { url } = await this.kerne.billing.checkout(planPriceId, options);
748
777
  return url;
@@ -824,7 +853,7 @@ function KerneProvider({ children, localization, ...config }) {
824
853
  }, [config.appId, config.baseUrl]);
825
854
  _react.useEffect.call(void 0, () => {
826
855
  return () => {
827
- _optionalChain([client, 'access', _21 => _21.cancelRefresh, 'optionalCall', _22 => _22()]);
856
+ _optionalChain([client, 'access', _23 => _23.cancelRefresh, 'optionalCall', _24 => _24()]);
828
857
  };
829
858
  }, [client]);
830
859
  const l10n = _react.useMemo.call(void 0, () => mergeLocalization(localization), [localization]);
@@ -925,7 +954,7 @@ function useAuth() {
925
954
  (token2, password) => client.completeActivation(token2, password),
926
955
  [client]
927
956
  ),
928
- // Cross-origin handoff (apps/portal -> tenant app_url) - see redirectWithSession on KerneClient.
957
+ // Cross-origin handoff (hosted auth origin -> tenant app_url) - see redirectWithSession on KerneClient.
929
958
  redirectWithSession: _react.useCallback.call(void 0, (url) => client.redirectWithSession(url), [client])
930
959
  };
931
960
  }
@@ -981,6 +1010,63 @@ function useChangePlan() {
981
1010
  );
982
1011
  return { changePlan, isChanging };
983
1012
  }
1013
+ function usePlanSwitch(subscription, refetchSubscription, options) {
1014
+ const { openCheckout } = useCheckout();
1015
+ const { changePlan } = useChangePlan();
1016
+ const [pending, setPending] = _react.useState.call(void 0, null);
1017
+ const [switching, setSwitching] = _react.useState.call(void 0, false);
1018
+ const [switchError, setSwitchError] = _react.useState.call(void 0, null);
1019
+ const [checkoutPriceId, setCheckoutPriceId] = _react.useState.call(void 0, null);
1020
+ const [checkoutError, setCheckoutError] = _react.useState.call(void 0, null);
1021
+ const successUrl = _optionalChain([options, 'optionalAccess', _25 => _25.successUrl]);
1022
+ const cancelUrl = _optionalChain([options, 'optionalAccess', _26 => _26.cancelUrl]);
1023
+ const selectPlan = _react.useCallback.call(void 0,
1024
+ async (plan, price) => {
1025
+ if (subscription) {
1026
+ setSwitchError(null);
1027
+ setPending({ plan, price });
1028
+ return;
1029
+ }
1030
+ setCheckoutError(null);
1031
+ setCheckoutPriceId(price.id);
1032
+ try {
1033
+ await openCheckout(price.id, { successUrl, cancelUrl });
1034
+ } catch (e) {
1035
+ setCheckoutError(e);
1036
+ setCheckoutPriceId(null);
1037
+ }
1038
+ },
1039
+ [subscription, openCheckout, successUrl, cancelUrl]
1040
+ );
1041
+ const confirmSwitch = _react.useCallback.call(void 0, async () => {
1042
+ if (!pending || !subscription) return;
1043
+ setSwitching(true);
1044
+ setSwitchError(null);
1045
+ try {
1046
+ await changePlan(subscription.id, pending.price.id);
1047
+ setPending(null);
1048
+ refetchSubscription();
1049
+ } catch (e) {
1050
+ setSwitchError(e);
1051
+ } finally {
1052
+ setSwitching(false);
1053
+ }
1054
+ }, [pending, subscription, changePlan, refetchSubscription]);
1055
+ const dismissSwitch = _react.useCallback.call(void 0, () => {
1056
+ setPending(null);
1057
+ setSwitchError(null);
1058
+ }, []);
1059
+ return {
1060
+ pending,
1061
+ switching,
1062
+ switchError,
1063
+ checkoutPriceId,
1064
+ checkoutError,
1065
+ selectPlan,
1066
+ confirmSwitch,
1067
+ dismissSwitch
1068
+ };
1069
+ }
984
1070
  function useAccess(featureKey, requested) {
985
1071
  const client = useClient();
986
1072
  const [state, setState] = _react.useState.call(void 0, {
@@ -1040,8 +1126,8 @@ function useSubscription(productSlug) {
1040
1126
  setState((prev) => ({ ...prev, isLoading: true }));
1041
1127
  fetchOnce({ current: true });
1042
1128
  }, [fetchOnce]);
1043
- const isActive = _optionalChain([state, 'access', _23 => _23.subscription, 'optionalAccess', _24 => _24.status]) === "ACTIVE" || _optionalChain([state, 'access', _25 => _25.subscription, 'optionalAccess', _26 => _26.status]) === "TRIALING";
1044
- const planSlug = _optionalChain([state, 'access', _27 => _27.subscription, 'optionalAccess', _28 => _28.plan, 'optionalAccess', _29 => _29.slug]);
1129
+ const isActive = _optionalChain([state, 'access', _27 => _27.subscription, 'optionalAccess', _28 => _28.status]) === "ACTIVE" || _optionalChain([state, 'access', _29 => _29.subscription, 'optionalAccess', _30 => _30.status]) === "TRIALING";
1130
+ const planSlug = _optionalChain([state, 'access', _31 => _31.subscription, 'optionalAccess', _32 => _32.plan, 'optionalAccess', _33 => _33.slug]);
1045
1131
  return {
1046
1132
  ...state,
1047
1133
  isActive,
@@ -1145,6 +1231,23 @@ function useUsage(featureKey) {
1145
1231
  }, [client, featureKey]);
1146
1232
  return state;
1147
1233
  }
1234
+ function useReportUsage() {
1235
+ const client = useClient();
1236
+ return {
1237
+ consume: _react.useCallback.call(void 0,
1238
+ (featureKey, params) => client.consume(featureKey, params),
1239
+ [client]
1240
+ ),
1241
+ report: _react.useCallback.call(void 0,
1242
+ (featureKey, params) => client.reportUsage(featureKey, params),
1243
+ [client]
1244
+ ),
1245
+ reportBatch: _react.useCallback.call(void 0,
1246
+ (events) => client.reportUsageBatch(events),
1247
+ [client]
1248
+ )
1249
+ };
1250
+ }
1148
1251
  function useWaitlist() {
1149
1252
  const client = useClient();
1150
1253
  return {
@@ -1189,9 +1292,9 @@ function useUser() {
1189
1292
  update: updateProfile,
1190
1293
  // Computed properties
1191
1294
  fullName: user ? `${user.first_name || ""} ${user.last_name || ""}`.trim() : null,
1192
- initials: user ? `${_optionalChain([user, 'access', _30 => _30.first_name, 'optionalAccess', _31 => _31[0]]) || ""}${_optionalChain([user, 'access', _32 => _32.last_name, 'optionalAccess', _33 => _33[0]]) || ""}`.toUpperCase() : null,
1193
- email: _optionalChain([user, 'optionalAccess', _34 => _34.email]) || null,
1194
- emailVerified: _optionalChain([user, 'optionalAccess', _35 => _35.email_verified]) || false
1295
+ initials: user ? `${_optionalChain([user, 'access', _34 => _34.first_name, 'optionalAccess', _35 => _35[0]]) || ""}${_optionalChain([user, 'access', _36 => _36.last_name, 'optionalAccess', _37 => _37[0]]) || ""}`.toUpperCase() : null,
1296
+ email: _optionalChain([user, 'optionalAccess', _38 => _38.email]) || null,
1297
+ emailVerified: _optionalChain([user, 'optionalAccess', _39 => _39.email_verified]) || false
1195
1298
  };
1196
1299
  }
1197
1300
 
@@ -1219,4 +1322,6 @@ function useUser() {
1219
1322
 
1220
1323
 
1221
1324
 
1222
- exports.defaultLocalization = defaultLocalization; exports.useLocalization = useLocalization; exports.fill = fill; exports.fillNode = fillNode; exports.useErrorResolver = useErrorResolver; exports.KerneClient = KerneClient; exports.KerneContext = KerneContext; exports.KerneProvider = KerneProvider; exports.useClient = useClient; exports.useAuth = useAuth; exports.useCheckout = useCheckout; exports.usePortal = usePortal; exports.useCancelSubscription = useCancelSubscription; exports.useChangePlan = useChangePlan; exports.useAccess = useAccess; exports.useSubscription = useSubscription; exports.usePlans = usePlans; exports.useAuthConfig = useAuthConfig; exports.useEntitlements = useEntitlements; exports.useUsage = useUsage; exports.useWaitlist = useWaitlist; exports.useInvitation = useInvitation; exports.useUser = useUser;
1325
+
1326
+
1327
+ exports.defaultLocalization = defaultLocalization; exports.useLocalization = useLocalization; exports.fill = fill; exports.fillNode = fillNode; exports.useErrorResolver = useErrorResolver; exports.KerneClient = KerneClient; exports.KerneContext = KerneContext; exports.KerneProvider = KerneProvider; exports.useClient = useClient; exports.useAuth = useAuth; exports.useCheckout = useCheckout; exports.usePortal = usePortal; exports.useCancelSubscription = useCancelSubscription; exports.useChangePlan = useChangePlan; exports.usePlanSwitch = usePlanSwitch; exports.useAccess = useAccess; exports.useSubscription = useSubscription; exports.usePlans = usePlans; exports.useAuthConfig = useAuthConfig; exports.useEntitlements = useEntitlements; exports.useUsage = useUsage; exports.useReportUsage = useReportUsage; exports.useWaitlist = useWaitlist; exports.useInvitation = useInvitation; exports.useUser = useUser;
@@ -195,6 +195,11 @@ interface KerneLocalization {
195
195
  changePlan: string;
196
196
  current: string;
197
197
  switchTo: string;
198
+ switching: string;
199
+ switchConfirmTitle: string;
200
+ switchConfirmBody: string;
201
+ switchConfirmButton: string;
202
+ switchConfirmDismiss: string;
198
203
  noPlans: string;
199
204
  notOnInterval: string;
200
205
  loadFailed: string;
@@ -268,7 +273,7 @@ interface KerneLocalization {
268
273
  signOut: string;
269
274
  };
270
275
  /**
271
- * Keyed by the API's stable ErrorCode (see packages/core/src/errors).
276
+ * Keyed by the API's stable ErrorCode.
272
277
  * Anything absent falls back to `generic` - never to the raw server message,
273
278
  * which is written for an integrator and is always English.
274
279
  */
@@ -195,6 +195,11 @@ interface KerneLocalization {
195
195
  changePlan: string;
196
196
  current: string;
197
197
  switchTo: string;
198
+ switching: string;
199
+ switchConfirmTitle: string;
200
+ switchConfirmBody: string;
201
+ switchConfirmButton: string;
202
+ switchConfirmDismiss: string;
198
203
  noPlans: string;
199
204
  notOnInterval: string;
200
205
  loadFailed: string;
@@ -268,7 +273,7 @@ interface KerneLocalization {
268
273
  signOut: string;
269
274
  };
270
275
  /**
271
- * Keyed by the API's stable ErrorCode (see packages/core/src/errors).
276
+ * Keyed by the API's stable ErrorCode.
272
277
  * Anything absent falls back to `generic` - never to the raw server message,
273
278
  * which is written for an integrator and is always English.
274
279
  */
package/dist/index.cjs CHANGED
@@ -19,25 +19,27 @@
19
19
 
20
20
 
21
21
 
22
- var _chunkIMZFZ3P5cjs = require('./chunk-IMZFZ3P5.cjs');
22
+
23
+
24
+ var _chunkN6VPGJ36cjs = require('./chunk-N6VPGJ36.cjs');
23
25
 
24
26
  // src/components.tsx
25
27
  var _react = require('react'); var _react2 = _interopRequireDefault(_react);
26
28
  var _jsxruntime = require('react/jsx-runtime');
27
29
  function Authenticated({ children, fallback = null }) {
28
- const { isAuthenticated } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
30
+ const { isAuthenticated } = _chunkN6VPGJ36cjs.useAuth.call(void 0, );
29
31
  return isAuthenticated ? children : fallback;
30
32
  }
31
33
  function Unauthenticated({ children, fallback = null }) {
32
- const { isAuthenticated } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
34
+ const { isAuthenticated } = _chunkN6VPGJ36cjs.useAuth.call(void 0, );
33
35
  return !isAuthenticated ? children : fallback;
34
36
  }
35
37
  function AuthLoading({ children }) {
36
- const client = _chunkIMZFZ3P5cjs.useClient.call(void 0, );
38
+ const client = _chunkN6VPGJ36cjs.useClient.call(void 0, );
37
39
  return client.isLoading ? children : null;
38
40
  }
39
41
  function HasSubscription({ children, fallback = null }) {
40
- const client = _chunkIMZFZ3P5cjs.useClient.call(void 0, );
42
+ const client = _chunkN6VPGJ36cjs.useClient.call(void 0, );
41
43
  const [hasSubscription, setHasSubscription] = _react2.default.useState(null);
42
44
  _react2.default.useEffect(() => {
43
45
  client.getSubscription().then((sub) => {
@@ -55,7 +57,7 @@ function Access({
55
57
  requested,
56
58
  fallback = null
57
59
  }) {
58
- const { allowed, isLoading } = _chunkIMZFZ3P5cjs.useAccess.call(void 0, featureKey, requested);
60
+ const { allowed, isLoading } = _chunkN6VPGJ36cjs.useAccess.call(void 0, featureKey, requested);
59
61
  if (isLoading) return null;
60
62
  return allowed ? children : fallback;
61
63
  }
@@ -66,7 +68,7 @@ function Protected({
66
68
  authFallback = null,
67
69
  billingFallback = null
68
70
  }) {
69
- const { isAuthenticated } = _chunkIMZFZ3P5cjs.useAuth.call(void 0, );
71
+ const { isAuthenticated } = _chunkN6VPGJ36cjs.useAuth.call(void 0, );
70
72
  if (auth && !isAuthenticated) {
71
73
  return authFallback;
72
74
  }
@@ -179,4 +181,6 @@ function useKerneError() {
179
181
 
180
182
 
181
183
 
182
- exports.Access = Access; exports.AuthLoading = AuthLoading; exports.Authenticated = Authenticated; exports.HasSubscription = HasSubscription; exports.KerneClient = _chunkIMZFZ3P5cjs.KerneClient; exports.KerneContext = _chunkIMZFZ3P5cjs.KerneContext; exports.KerneErrorBoundary = KerneErrorBoundary; exports.KerneProvider = _chunkIMZFZ3P5cjs.KerneProvider; exports.Protected = Protected; exports.Unauthenticated = Unauthenticated; exports.defaultLocalization = _chunkIMZFZ3P5cjs.defaultLocalization; exports.useAccess = _chunkIMZFZ3P5cjs.useAccess; exports.useAuth = _chunkIMZFZ3P5cjs.useAuth; exports.useAuthConfig = _chunkIMZFZ3P5cjs.useAuthConfig; exports.useCancelSubscription = _chunkIMZFZ3P5cjs.useCancelSubscription; exports.useChangePlan = _chunkIMZFZ3P5cjs.useChangePlan; exports.useCheckout = _chunkIMZFZ3P5cjs.useCheckout; exports.useClient = _chunkIMZFZ3P5cjs.useClient; exports.useEntitlements = _chunkIMZFZ3P5cjs.useEntitlements; exports.useInvitation = _chunkIMZFZ3P5cjs.useInvitation; exports.useKerneError = useKerneError; exports.usePlans = _chunkIMZFZ3P5cjs.usePlans; exports.usePortal = _chunkIMZFZ3P5cjs.usePortal; exports.useSubscription = _chunkIMZFZ3P5cjs.useSubscription; exports.useUsage = _chunkIMZFZ3P5cjs.useUsage; exports.useUser = _chunkIMZFZ3P5cjs.useUser; exports.useWaitlist = _chunkIMZFZ3P5cjs.useWaitlist;
184
+
185
+
186
+ exports.Access = Access; exports.AuthLoading = AuthLoading; exports.Authenticated = Authenticated; exports.HasSubscription = HasSubscription; exports.KerneClient = _chunkN6VPGJ36cjs.KerneClient; exports.KerneContext = _chunkN6VPGJ36cjs.KerneContext; exports.KerneErrorBoundary = KerneErrorBoundary; exports.KerneProvider = _chunkN6VPGJ36cjs.KerneProvider; exports.Protected = Protected; exports.Unauthenticated = Unauthenticated; exports.defaultLocalization = _chunkN6VPGJ36cjs.defaultLocalization; exports.useAccess = _chunkN6VPGJ36cjs.useAccess; exports.useAuth = _chunkN6VPGJ36cjs.useAuth; exports.useAuthConfig = _chunkN6VPGJ36cjs.useAuthConfig; exports.useCancelSubscription = _chunkN6VPGJ36cjs.useCancelSubscription; exports.useChangePlan = _chunkN6VPGJ36cjs.useChangePlan; exports.useCheckout = _chunkN6VPGJ36cjs.useCheckout; exports.useClient = _chunkN6VPGJ36cjs.useClient; exports.useEntitlements = _chunkN6VPGJ36cjs.useEntitlements; exports.useInvitation = _chunkN6VPGJ36cjs.useInvitation; exports.useKerneError = useKerneError; exports.usePlanSwitch = _chunkN6VPGJ36cjs.usePlanSwitch; exports.usePlans = _chunkN6VPGJ36cjs.usePlans; exports.usePortal = _chunkN6VPGJ36cjs.usePortal; exports.useReportUsage = _chunkN6VPGJ36cjs.useReportUsage; exports.useSubscription = _chunkN6VPGJ36cjs.useSubscription; exports.useUsage = _chunkN6VPGJ36cjs.useUsage; exports.useUser = _chunkN6VPGJ36cjs.useUser; exports.useWaitlist = _chunkN6VPGJ36cjs.useWaitlist;
package/dist/index.d.cts CHANGED
@@ -1,12 +1,12 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React, { ReactNode, Component, ErrorInfo } from 'react';
3
- import { KerneConfig, JoinWaitlistParams, ValidateTokenResponse, ValidateCodeResponse } from '@kerne/server';
3
+ import { KerneConfig, ConsumeParams, ReportUsageParams, ReportUsageBatchEvent, BatchUsageResult, JoinWaitlistParams, ValidateTokenResponse, ValidateCodeResponse } from '@kerne/server';
4
4
  export { JoinWaitlistParams, ValidateCodeResponse, ValidateTokenResponse, WaitlistEntry } from '@kerne/server';
5
5
  import * as _kerne_types from '@kerne/types';
6
- import { User, AuthResponse, ActivationContext, AuthConfig, EntitlementCheck, SubscriptionWithPlan, Subscription, PublicPlan, Entitlements, UsageRecord } from '@kerne/types';
7
- export { ActivationContext, AuthConfig, AuthResponse, EntitlementCheck, Entitlements, PublicPlan, Subscription, SubscriptionWithPlan, UsageRecord, User } from '@kerne/types';
8
- import { D as DeepPartial, K as KerneLocalization } from './i18n-Di5qlnL1.cjs';
9
- export { d as defaultLocalization } from './i18n-Di5qlnL1.cjs';
6
+ import { User, AuthResponse, ActivationContext, AuthConfig, EntitlementCheck, UsageRecord, SubscriptionWithPlan, Subscription, PublicPlan, Entitlements, PublicPlanPrice } from '@kerne/types';
7
+ export { ActivationContext, AuthConfig, AuthResponse, BatchUsageResult, EntitlementCheck, Entitlements, PublicPlan, Subscription, SubscriptionWithPlan, UsageRecord, User, ValidationFieldError } from '@kerne/types';
8
+ import { D as DeepPartial, K as KerneLocalization } from './i18n-CXEhPK58.cjs';
9
+ export { d as defaultLocalization } from './i18n-CXEhPK58.cjs';
10
10
 
11
11
  interface KerneReactConfig extends Omit<KerneConfig, 'secretKey'> {
12
12
  storage?: Storage;
@@ -166,6 +166,13 @@ declare class KerneClient {
166
166
  allows(featureKey: string, requested?: number): Promise<boolean>;
167
167
  /** Full entitlement detail (limit/used/remaining/overage) - use `allows()` for a plain boolean. */
168
168
  check(featureKey: string, requested?: number): Promise<EntitlementCheck>;
169
+ /** Atomically check a QUOTA limit and commit the consumption. Reports for the signed-in subject. */
170
+ consume(featureKey: string, params?: Omit<ConsumeParams, 'subject'>): Promise<EntitlementCheck>;
171
+ /** Report usage without a limit check, for the signed-in subject. See `reportUsageBatch()` for many at once. */
172
+ reportUsage(featureKey: string, params?: Omit<ReportUsageParams, 'subject'>): Promise<UsageRecord>;
173
+ /** Several usage events in one request instead of one call each - kills the manual request staggering. */
174
+ reportUsageBatch(events: Array<Omit<ReportUsageBatchEvent, 'subject'>>): Promise<BatchUsageResult[]>;
175
+ private requireUserId;
169
176
  createCheckout(planPriceId: string, options?: {
170
177
  successUrl?: string;
171
178
  cancelUrl?: string;
@@ -344,6 +351,51 @@ declare function useChangePlan(): {
344
351
  }) => Promise<SubscriptionWithPlan>;
345
352
  isChanging: boolean;
346
353
  };
354
+ interface PlanSwitchTarget {
355
+ plan: PublicPlan;
356
+ price: PublicPlanPrice;
357
+ }
358
+ /**
359
+ * The decision every "pick a plan" surface needs, shared so it is only
360
+ * decided once: an existing subscriber switching plans has to swap in place
361
+ * (`changePlanPrice`), never start a fresh checkout - Stripe/Polar don't know
362
+ * about the subscription already on file, so a new checkout session creates
363
+ * a SECOND, independent one instead of replacing it. The old one stays live
364
+ * and billed at the provider while Kerne only ever tracks the new one. A
365
+ * first-time subscriber has nothing to swap, so that case goes straight to
366
+ * checkout, same as before.
367
+ *
368
+ * The switch is staged rather than applied on `selectPlan` alone - it bills
369
+ * immediately on confirmation (unlike checkout, which only starts billing
370
+ * once the provider's own page is completed), so the caller's UI gets a
371
+ * chance to say so before `confirmSwitch()` commits it.
372
+ *
373
+ * @param subscription the subject's current subscription, or null - pass
374
+ * what `useSubscription()` already returned rather than fetching it again
375
+ * here, so a page showing the current plan and this hook always agree.
376
+ * @param refetchSubscription called after a successful switch, typically
377
+ * that same `useSubscription()` call's own `refetch()`.
378
+ *
379
+ * @example
380
+ * ```tsx
381
+ * const { subscription, refetch } = useSubscription();
382
+ * const { pending, switching, selectPlan, confirmSwitch, dismissSwitch } =
383
+ * usePlanSwitch(subscription, refetch);
384
+ * ```
385
+ */
386
+ declare function usePlanSwitch(subscription: SubscriptionWithPlan | null, refetchSubscription: () => void, options?: {
387
+ successUrl?: string;
388
+ cancelUrl?: string;
389
+ }): {
390
+ pending: PlanSwitchTarget | null;
391
+ switching: boolean;
392
+ switchError: unknown;
393
+ checkoutPriceId: string | null;
394
+ checkoutError: unknown;
395
+ selectPlan: (plan: PublicPlan, price: PublicPlanPrice) => Promise<void>;
396
+ confirmSwitch: () => Promise<void>;
397
+ dismissSwitch: () => void;
398
+ };
347
399
  /**
348
400
  * Whether the current scope has access to one feature - the single entry
349
401
  * point for a live, per-feature decision. Deliberately not named after what
@@ -450,6 +502,24 @@ declare function useUsage(featureKey?: string): {
450
502
  isLoading: boolean;
451
503
  error: Error | null;
452
504
  };
505
+ /**
506
+ * Usage write path for the signed-in user: `consume()` gates a QUOTA and commits
507
+ * in one step, `report()`/`reportBatch()` record usage without a check.
508
+ * `reportBatch()` sends many events in one request - the fix for hand-rolled
509
+ * request staggering in a component reporting several rows at once.
510
+ *
511
+ * @example
512
+ * ```tsx
513
+ * const { consume, reportBatch } = useReportUsage();
514
+ * await consume('exports', { delta: 1 });
515
+ * await reportBatch(machines.map((m) => ({ featureKey: 'machine_minutes', delta: m.minutes })));
516
+ * ```
517
+ */
518
+ declare function useReportUsage(): {
519
+ consume: (featureKey: string, params?: Omit<ConsumeParams, "subject">) => Promise<EntitlementCheck>;
520
+ report: (featureKey: string, params?: Omit<ReportUsageParams, "subject">) => Promise<UsageRecord>;
521
+ reportBatch: (events: Array<Omit<ReportUsageBatchEvent, "subject">>) => Promise<_kerne_types.BatchUsageResult[]>;
522
+ };
453
523
  /**
454
524
  * Waitlist signup form - a mutation, not a data fetch, same shape as
455
525
  * `useCheckout`/`usePortal`.
@@ -691,4 +761,4 @@ declare function useKerneError(): {
691
761
  clearError: () => void;
692
762
  };
693
763
 
694
- export { Access, AuthLoading, Authenticated, HasSubscription, KerneClient, KerneContext, type KerneError, KerneErrorBoundary, KerneLocalization, KerneProvider, type KerneProviderProps, type KerneReactConfig, Protected, Unauthenticated, useAccess, useAuth, useAuthConfig, useCancelSubscription, useChangePlan, useCheckout, useClient, useEntitlements, useInvitation, useKerneError, usePlans, usePortal, useSubscription, useUsage, useUser, useWaitlist };
764
+ export { Access, AuthLoading, Authenticated, HasSubscription, KerneClient, KerneContext, type KerneError, KerneErrorBoundary, KerneLocalization, KerneProvider, type KerneProviderProps, type KerneReactConfig, type PlanSwitchTarget, Protected, Unauthenticated, useAccess, useAuth, useAuthConfig, useCancelSubscription, useChangePlan, useCheckout, useClient, useEntitlements, useInvitation, useKerneError, usePlanSwitch, usePlans, usePortal, useReportUsage, useSubscription, useUsage, useUser, useWaitlist };