kitcn 0.33.6 → 0.33.8

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/aggregate/index.d.ts +1 -1
  3. package/dist/auth/client/index.d.ts +45 -21
  4. package/dist/auth/client/index.js +203 -111
  5. package/dist/auth/generated/index.d.ts +1 -1
  6. package/dist/auth/index.d.ts +16 -16
  7. package/dist/auth/nextjs/index.d.ts +3 -3
  8. package/dist/auth/nextjs/index.js +3 -2
  9. package/dist/auth/start/index.js +134 -2
  10. package/dist/auth/start/server/index.d.ts +1 -1
  11. package/dist/auth/start/server/index.js +1 -1
  12. package/dist/crpc/index.d.ts +2 -2
  13. package/dist/crpc/index.js +2 -2
  14. package/dist/{generated-contract-disabled-PdNGvNYP.d.ts → generated-contract-disabled-CFLQf8Wg.d.ts} +25 -25
  15. package/dist/orm/index.d.ts +1 -1
  16. package/dist/orm/migrations/index.d.ts +1 -1
  17. package/dist/{query-options-DGG93--5.js → query-options-YkFvau71.js} +6 -2
  18. package/dist/react/index.js +149 -140
  19. package/dist/rsc/index.d.ts +1 -1
  20. package/dist/rsc/index.js +1 -1
  21. package/dist/solid/index.js +4 -1
  22. package/dist/{token-tlbNKQS0.d.ts → token-COZD00dG.d.ts} +2 -1
  23. package/dist/{token-CgIvcEZX.js → token-CZbuEcTL.js} +13 -4
  24. package/dist/{auth-store-x3_wvhTR.js → token-gate-CEzbXArg.js} +178 -1
  25. package/dist/{types-C7JkpfeY.d.ts → types-Bb0kwteA.d.ts} +5 -1
  26. package/dist/{where-clause-compiler-BzAJnWt4.d.ts → where-clause-compiler-CTblNxLo.d.ts} +24 -24
  27. package/package.json +1 -1
  28. package/skills/kitcn/references/features/auth.md +26 -8
  29. package/skills/kitcn/references/features/react.md +5 -1
  30. package/skills/kitcn/references/setup/next.md +48 -1
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { A as CRPCClientError, C as decodeJwtExp, M as isCRPCClientError, O as writeAuthSessionFallbackData, S as useSafeConvexAuth, T as clearAuthSessionFallback, _ as useConvexAuthBridge, a as ConvexAuthRecoveryError, b as useIsAuth, c as MaybeAuthenticated, d as isSessionSyncGraceActive, f as useAuth, g as useAuthValue, h as useAuthStore, i as ConvexAuthBridge, j as defaultIsUnauthorized, k as writeAuthSessionFallbackToken, l as MaybeUnauthenticated, m as useAuthState, n as AuthProvider, o as ConvexProviderWithAuth, p as useAuthGuard, r as Authenticated, s as FetchAccessTokenContext, t as AUTH_SESSION_SYNC_GRACE_MS, u as Unauthenticated, v as useConvexAuthRecovery, w as decodeJwtIdentity, x as useMaybeAuth, y as useFetchAccessToken } from "../auth-store-x3_wvhTR.js";
2
+ import { A as useAuthState, B as decodeJwtIdentity, C as FetchAccessTokenContext, D as isSessionSyncGraceActive, E as Unauthenticated, F as useFetchAccessToken, G as writeAuthSessionFallbackToken, I as useIsAuth, J as isCRPCClientError, K as CRPCClientError, L as useMaybeAuth, M as useAuthValue, N as useConvexAuthBridge, O as useAuth, P as useConvexAuthRecovery, R as useSafeConvexAuth, S as ConvexProviderWithAuth, T as MaybeUnauthenticated, V as clearAuthSessionFallback, W as writeAuthSessionFallbackData, _ as AUTH_SESSION_SYNC_GRACE_MS, b as ConvexAuthBridge, i as publishToken, j as useAuthStore, k as useAuthGuard, l as isDocumentTripped, q as defaultIsUnauthorized, r as publishAuthenticated, t as admitStoreToken, v as AuthProvider, w as MaybeAuthenticated, x as ConvexAuthRecoveryError, y as Authenticated, z as decodeJwtExp } from "../token-gate-CEzbXArg.js";
3
3
  import { ConvexProvider, ConvexReactClient, ConvexReactClient as ConvexReactClient$1, useAction, useConvex, useMutation } from "convex/react";
4
4
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
@@ -9,122 +9,6 @@ import { hashKey } from "@tanstack/query-core";
9
9
  import { convexToJson } from "convex/values";
10
10
  import { ConvexHttpClient } from "convex/browser";
11
11
 
12
- //#region src/shared/meta-utils.ts
13
- const metaCache = /* @__PURE__ */ new WeakMap();
14
- const nonMetaLeafKeys = new Set(["functionRef", "ref"]);
15
- function isRecord$1(value) {
16
- return typeof value === "object" && value !== null;
17
- }
18
- function isFunctionType(value) {
19
- return value === "query" || value === "mutation" || value === "action";
20
- }
21
- function isMetaScalar(value) {
22
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
23
- }
24
- function extractLeafMeta(value) {
25
- const type = value.type;
26
- if (!isFunctionType(type)) return;
27
- const result = { type };
28
- for (const [key, entry] of Object.entries(value)) {
29
- if (key === "type" || nonMetaLeafKeys.has(key) || key.startsWith("_")) continue;
30
- if (entry === void 0) continue;
31
- if (isMetaScalar(entry)) result[key] = entry;
32
- }
33
- return result;
34
- }
35
- function getHttpRoutes(api) {
36
- const routes = api._http;
37
- if (!isRecord$1(routes)) return;
38
- const normalized = {};
39
- for (const [routeKey, routeValue] of Object.entries(routes)) {
40
- if (!isRecord$1(routeValue)) continue;
41
- const routePath = routeValue.path;
42
- const routeMethod = routeValue.method;
43
- if (typeof routePath === "string" && typeof routeMethod === "string") normalized[routeKey] = {
44
- path: routePath,
45
- method: routeMethod
46
- };
47
- }
48
- return normalized;
49
- }
50
- /**
51
- * Build a metadata index from merged API leaves.
52
- * Supports both generated `api` objects and plain metadata fixtures.
53
- */
54
- function buildMetaIndex(api) {
55
- const cached = metaCache.get(api);
56
- if (cached) return cached;
57
- const meta = {};
58
- const httpRoutes = getHttpRoutes(api);
59
- if (httpRoutes) meta._http = httpRoutes;
60
- const walk = (node, path) => {
61
- for (const [key, value] of Object.entries(node)) {
62
- if (key.startsWith("_")) continue;
63
- if (!isRecord$1(value)) continue;
64
- const leafMeta = extractLeafMeta(value);
65
- if (leafMeta) {
66
- if (path.length === 0) continue;
67
- const namespace = path.join("/");
68
- meta[namespace] ??= {};
69
- meta[namespace][key] = leafMeta;
70
- continue;
71
- }
72
- walk(value, [...path, key]);
73
- }
74
- };
75
- walk(api, []);
76
- metaCache.set(api, meta);
77
- return meta;
78
- }
79
- /**
80
- * Get a function reference from the API object by traversing the path.
81
- */
82
- function getFuncRef(api, path) {
83
- let current = api;
84
- for (const key of path) if (current && typeof current === "object") {
85
- const next = current[key];
86
- if (next === void 0) throw new Error(`Invalid path: ${path.join(".")}`);
87
- current = next;
88
- } else throw new Error(`Invalid path: ${path.join(".")}`);
89
- if (current && typeof current === "object") {
90
- const maybeFunctionRef = current.functionRef;
91
- if (maybeFunctionRef && typeof maybeFunctionRef === "object") return maybeFunctionRef;
92
- }
93
- if (!current || typeof current !== "object") throw new Error(`Invalid function reference at path: ${path.join(".")}`);
94
- return current;
95
- }
96
- /**
97
- * Get function type from meta using path.
98
- * Supports nested paths like ['items', 'queries', 'list'] → namespace='items/queries', fn='list'
99
- *
100
- * @param path - Path segments like ['todos', 'create'] or ['items', 'queries', 'list']
101
- * @param meta - The meta object from codegen
102
- * @returns Function type or 'query' as default
103
- */
104
- function getFunctionType(path, source) {
105
- if (path.length < 2) return "query";
106
- const meta = buildMetaIndex(source);
107
- const fnName = path.at(-1);
108
- const fnType = meta[path.slice(0, -1).join("/")]?.[fnName]?.type;
109
- if (fnType === "query" || fnType === "mutation" || fnType === "action") return fnType;
110
- return "query";
111
- }
112
- /**
113
- * Get function metadata from meta using path.
114
- * Supports nested paths like ['items', 'queries', 'list'] → namespace='items/queries', fn='list'
115
- *
116
- * @param path - Path segments like ['todos', 'create'] or ['items', 'queries', 'list']
117
- * @param meta - The meta object from codegen
118
- * @returns Function metadata or undefined
119
- */
120
- function getFunctionMeta(path, source) {
121
- if (path.length < 2) return;
122
- const meta = buildMetaIndex(source);
123
- const fnName = path.at(-1);
124
- return meta[path.slice(0, -1).join("/")]?.[fnName];
125
- }
126
-
127
- //#endregion
128
12
  //#region src/crpc/http-types.ts
129
13
  /**
130
14
  * Build the exact cache key for an HTTP route.
@@ -209,6 +93,8 @@ function buildSearchParams(query) {
209
93
  else if (value !== void 0 && value !== null) params.append(key, value);
210
94
  return params;
211
95
  }
96
+ const RECHECK_AUTHORIZATION = Symbol.for("kitcn.http.recheckAuthorization");
97
+ const withAuthorizationRecheck = (headers, recheck) => Object.defineProperty(headers, RECHECK_AUTHORIZATION, { value: recheck });
212
98
  /**
213
99
  * Hono-style HTTP request executor.
214
100
  * Processes args in the same way as Hono's ClientRequestImpl.fetch().
@@ -247,6 +133,8 @@ async function executeHttpRequest(opts) {
247
133
  for (const [key, value] of Object.entries(resolvedBaseHeaders)) if (value !== void 0) finalHeaders[key] = value;
248
134
  }
249
135
  Object.assign(finalHeaders, headerValues);
136
+ const recheck = resolvedBaseHeaders?.[RECHECK_AUTHORIZATION];
137
+ const sendHeaders = recheck && finalHeaders.Authorization === resolvedBaseHeaders?.Authorization && !recheck() ? Object.fromEntries(Object.entries(finalHeaders).filter(([key]) => key !== "Authorization")) : finalHeaders;
250
138
  let url = opts.convexSiteUrl + path;
251
139
  if (args.params) url = opts.convexSiteUrl + replaceUrlParam(path, args.params);
252
140
  if (args.searchParams) {
@@ -258,7 +146,7 @@ async function executeHttpRequest(opts) {
258
146
  const response = await (mergedClientOpts.fetch ?? opts.baseFetch ?? globalThis.fetch)(url, {
259
147
  body: setBody ? rBody : void 0,
260
148
  method: methodUpperCase,
261
- headers: finalHeaders,
149
+ headers: sendHeaders,
262
150
  ...mergedClientOpts.init
263
151
  });
264
152
  if (!response.ok) {
@@ -280,6 +168,122 @@ async function executeHttpRequest(opts) {
280
168
  return response.text();
281
169
  }
282
170
 
171
+ //#endregion
172
+ //#region src/shared/meta-utils.ts
173
+ const metaCache = /* @__PURE__ */ new WeakMap();
174
+ const nonMetaLeafKeys = new Set(["functionRef", "ref"]);
175
+ function isRecord$1(value) {
176
+ return typeof value === "object" && value !== null;
177
+ }
178
+ function isFunctionType(value) {
179
+ return value === "query" || value === "mutation" || value === "action";
180
+ }
181
+ function isMetaScalar(value) {
182
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
183
+ }
184
+ function extractLeafMeta(value) {
185
+ const type = value.type;
186
+ if (!isFunctionType(type)) return;
187
+ const result = { type };
188
+ for (const [key, entry] of Object.entries(value)) {
189
+ if (key === "type" || nonMetaLeafKeys.has(key) || key.startsWith("_")) continue;
190
+ if (entry === void 0) continue;
191
+ if (isMetaScalar(entry)) result[key] = entry;
192
+ }
193
+ return result;
194
+ }
195
+ function getHttpRoutes(api) {
196
+ const routes = api._http;
197
+ if (!isRecord$1(routes)) return;
198
+ const normalized = {};
199
+ for (const [routeKey, routeValue] of Object.entries(routes)) {
200
+ if (!isRecord$1(routeValue)) continue;
201
+ const routePath = routeValue.path;
202
+ const routeMethod = routeValue.method;
203
+ if (typeof routePath === "string" && typeof routeMethod === "string") normalized[routeKey] = {
204
+ path: routePath,
205
+ method: routeMethod
206
+ };
207
+ }
208
+ return normalized;
209
+ }
210
+ /**
211
+ * Build a metadata index from merged API leaves.
212
+ * Supports both generated `api` objects and plain metadata fixtures.
213
+ */
214
+ function buildMetaIndex(api) {
215
+ const cached = metaCache.get(api);
216
+ if (cached) return cached;
217
+ const meta = {};
218
+ const httpRoutes = getHttpRoutes(api);
219
+ if (httpRoutes) meta._http = httpRoutes;
220
+ const walk = (node, path) => {
221
+ for (const [key, value] of Object.entries(node)) {
222
+ if (key.startsWith("_")) continue;
223
+ if (!isRecord$1(value)) continue;
224
+ const leafMeta = extractLeafMeta(value);
225
+ if (leafMeta) {
226
+ if (path.length === 0) continue;
227
+ const namespace = path.join("/");
228
+ meta[namespace] ??= {};
229
+ meta[namespace][key] = leafMeta;
230
+ continue;
231
+ }
232
+ walk(value, [...path, key]);
233
+ }
234
+ };
235
+ walk(api, []);
236
+ metaCache.set(api, meta);
237
+ return meta;
238
+ }
239
+ /**
240
+ * Get a function reference from the API object by traversing the path.
241
+ */
242
+ function getFuncRef(api, path) {
243
+ let current = api;
244
+ for (const key of path) if (current && typeof current === "object") {
245
+ const next = current[key];
246
+ if (next === void 0) throw new Error(`Invalid path: ${path.join(".")}`);
247
+ current = next;
248
+ } else throw new Error(`Invalid path: ${path.join(".")}`);
249
+ if (current && typeof current === "object") {
250
+ const maybeFunctionRef = current.functionRef;
251
+ if (maybeFunctionRef && typeof maybeFunctionRef === "object") return maybeFunctionRef;
252
+ }
253
+ if (!current || typeof current !== "object") throw new Error(`Invalid function reference at path: ${path.join(".")}`);
254
+ return current;
255
+ }
256
+ /**
257
+ * Get function type from meta using path.
258
+ * Supports nested paths like ['items', 'queries', 'list'] → namespace='items/queries', fn='list'
259
+ *
260
+ * @param path - Path segments like ['todos', 'create'] or ['items', 'queries', 'list']
261
+ * @param meta - The meta object from codegen
262
+ * @returns Function type or 'query' as default
263
+ */
264
+ function getFunctionType(path, source) {
265
+ if (path.length < 2) return "query";
266
+ const meta = buildMetaIndex(source);
267
+ const fnName = path.at(-1);
268
+ const fnType = meta[path.slice(0, -1).join("/")]?.[fnName]?.type;
269
+ if (fnType === "query" || fnType === "mutation" || fnType === "action") return fnType;
270
+ return "query";
271
+ }
272
+ /**
273
+ * Get function metadata from meta using path.
274
+ * Supports nested paths like ['items', 'queries', 'list'] → namespace='items/queries', fn='list'
275
+ *
276
+ * @param path - Path segments like ['todos', 'create'] or ['items', 'queries', 'list']
277
+ * @param meta - The meta object from codegen
278
+ * @returns Function metadata or undefined
279
+ */
280
+ function getFunctionMeta(path, source) {
281
+ if (path.length < 2) return;
282
+ const meta = buildMetaIndex(source);
283
+ const fnName = path.at(-1);
284
+ return meta[path.slice(0, -1).join("/")]?.[fnName];
285
+ }
286
+
283
287
  //#endregion
284
288
  //#region src/crpc/transformer.ts
285
289
  const isPlainObject = (value) => {
@@ -1498,24 +1502,16 @@ function createCRPCContext(options) {
1498
1502
  convexSiteUrl: httpOptions.convexSiteUrl,
1499
1503
  routes: meta._http,
1500
1504
  headers: async () => {
1501
- if (fetchAccessToken) {
1502
- const expiresAt = authStore.get("expiresAt");
1503
- const timeRemaining = expiresAt ? expiresAt - Date.now() : 0;
1504
- const guardedToken = await fetchAccessToken({ forceRefreshToken: !!expiresAt && timeRemaining < 6e4 });
1505
- const userHeaders = typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers;
1506
- return guardedToken ? {
1507
- ...userHeaders,
1508
- Authorization: `Bearer ${guardedToken}`
1509
- } : { ...userHeaders };
1510
- }
1511
- const token = authStore.get("token");
1512
1505
  const expiresAt = authStore.get("expiresAt");
1513
1506
  const timeRemaining = expiresAt ? expiresAt - Date.now() : 0;
1514
- if (token && expiresAt && timeRemaining >= 6e4) return {
1515
- ...typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers,
1507
+ const heldToken = authStore.get("token");
1508
+ const token = fetchAccessToken ? await fetchAccessToken({ forceRefreshToken: !!expiresAt && timeRemaining < 6e4 }) : heldToken && expiresAt && timeRemaining >= 6e4 ? heldToken : null;
1509
+ const userHeaders = typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers;
1510
+ const admit = () => !!token && admitStoreToken(authStore, token, { use: "handout" });
1511
+ return admit() ? withAuthorizationRecheck({
1512
+ ...userHeaders,
1516
1513
  Authorization: `Bearer ${token}`
1517
- };
1518
- return { ...typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers };
1514
+ }, admit) : { ...userHeaders };
1519
1515
  },
1520
1516
  fetch: httpOptions.fetch,
1521
1517
  onError: httpOptions.onError,
@@ -1648,10 +1644,19 @@ function isAuthMutationError(error) {
1648
1644
 
1649
1645
  //#endregion
1650
1646
  //#region src/react/auth-mutations.ts
1651
- /** Poll until JWT token exists (auth complete) (max 5s) */
1647
+ const tokenIdentityChangedError = () => new AuthMutationError({
1648
+ code: "TOKEN_IDENTITY_CHANGED",
1649
+ message: "This page switched accounts. Reload it to sign in.",
1650
+ status: 401,
1651
+ statusText: "UNAUTHORIZED"
1652
+ });
1653
+ const assertDocumentNotTripped = () => {
1654
+ if (isDocumentTripped()) throw tokenIdentityChangedError();
1655
+ };
1652
1656
  const waitForAuth = async (store, timeout = 5e3) => {
1653
1657
  const start = Date.now();
1654
1658
  while (Date.now() - start < timeout) {
1659
+ assertDocumentNotTripped();
1655
1660
  if (store.get("token")) return true;
1656
1661
  await new Promise((r) => setTimeout(r, 50));
1657
1662
  }
@@ -1676,9 +1681,10 @@ const readReturnedToken = (value) => {
1676
1681
  const seedReturnedToken = (store, value) => {
1677
1682
  const token = readReturnedToken(value);
1678
1683
  if (!token) return;
1679
- store.set("token", token);
1680
- store.set("expiresAt", decodeJwtExp(token));
1681
- store.set("sessionSyncGraceUntil", Date.now() + AUTH_SESSION_SYNC_GRACE_MS);
1684
+ if (!publishToken(store, token, {
1685
+ announce: true,
1686
+ sessionSyncGraceUntil: Date.now() + AUTH_SESSION_SYNC_GRACE_MS
1687
+ })) throw tokenIdentityChangedError();
1682
1688
  if (decodeJwtExp(token) === null) writeAuthSessionFallbackToken(token);
1683
1689
  };
1684
1690
  const toAuthMutationError = (error) => new AuthMutationError({
@@ -1753,12 +1759,13 @@ function createAuthMutations(authClient) {
1753
1759
  mutationFn: async (args) => {
1754
1760
  const signInSocial = authClient.signIn?.social;
1755
1761
  if (typeof signInSocial !== "function") throw new Error("Auth client does not expose signIn.social");
1762
+ assertDocumentNotTripped();
1756
1763
  const res = await callAuthMethod(signInSocial, withDisabledSessionSignal(args));
1757
1764
  if (res?.error) throw toAuthMutationError(res.error);
1758
1765
  seedReturnedToken(authStoreApi, res);
1759
1766
  await hydrateReturnedSession(authClient, res);
1760
1767
  await ensureAuth(authStoreApi);
1761
- authStoreApi.set("isAuthenticated", true);
1768
+ if (!publishAuthenticated(authStoreApi)) throw tokenIdentityChangedError();
1762
1769
  return res;
1763
1770
  }
1764
1771
  };
@@ -1771,12 +1778,13 @@ function createAuthMutations(authClient) {
1771
1778
  mutationFn: async (args) => {
1772
1779
  const signIn = authClient.signIn?.[signInMethod];
1773
1780
  if (typeof signIn !== "function") throw new Error(`Auth client does not expose signIn.${signInMethod}`);
1781
+ assertDocumentNotTripped();
1774
1782
  const res = await callAuthMethod(signIn, withDisabledSessionSignal(args));
1775
1783
  if (res?.error) throw toAuthMutationError(res.error);
1776
1784
  seedReturnedToken(authStoreApi, res);
1777
1785
  await hydrateReturnedSession(authClient, res);
1778
1786
  await ensureAuth(authStoreApi);
1779
- authStoreApi.set("isAuthenticated", true);
1787
+ if (!publishAuthenticated(authStoreApi)) throw tokenIdentityChangedError();
1780
1788
  return res;
1781
1789
  }
1782
1790
  };
@@ -1788,12 +1796,13 @@ function createAuthMutations(authClient) {
1788
1796
  mutationFn: async (args) => {
1789
1797
  const signUpEmail = authClient.signUp?.email;
1790
1798
  if (typeof signUpEmail !== "function") throw new Error("Auth client does not expose signUp.email");
1799
+ assertDocumentNotTripped();
1791
1800
  const res = await callAuthMethod(signUpEmail, withDisabledSessionSignal(args));
1792
1801
  if (res?.error) throw toAuthMutationError(res.error);
1793
1802
  seedReturnedToken(authStoreApi, res);
1794
1803
  await hydrateReturnedSession(authClient, res);
1795
1804
  await ensureAuth(authStoreApi);
1796
- authStoreApi.set("isAuthenticated", true);
1805
+ if (!publishAuthenticated(authStoreApi)) throw tokenIdentityChangedError();
1797
1806
  return res;
1798
1807
  }
1799
1808
  };
@@ -1,7 +1,7 @@
1
1
  import { n as DeepPartial, o as Simplify, r as DistributiveOmit } from "../types-Bqi_ivSd.js";
2
2
  import { L as DataTransformerOptions, _ as CRPCHttpRouter, c as HttpQueryKey, k as HttpProcedure, o as HttpMutationKey, y as HttpRouterRecord } from "../http-types-BhhPZtwa.js";
3
3
  import { g as UnsetMarker } from "../types-Bkeicpo6.js";
4
- import { A as HttpInputArgs, C as ReservedMutationOptions$1, S as ReservedInfiniteQueryOptions, T as StaticQueryOptsParam, _ as IsPaginated, b as PaginatedFnMeta, c as ConvexMutationKey, d as ConvexQueryMeta, f as EmptyObject, g as InfiniteQueryInput, l as ConvexQueryHookOptions, m as FUNC_REF_SYMBOL, o as ConvexActionKey, p as ExtractPaginatedItem, s as ConvexInfiniteQueryMeta, u as ConvexQueryKey, w as ReservedQueryOptions$1, y as MutationVariables } from "../types-C7JkpfeY.js";
4
+ import { A as HttpInputArgs, C as ReservedMutationOptions$1, S as ReservedInfiniteQueryOptions, T as StaticQueryOptsParam, _ as IsPaginated, b as PaginatedFnMeta, c as ConvexMutationKey, d as ConvexQueryMeta, f as EmptyObject, g as InfiniteQueryInput, l as ConvexQueryHookOptions, m as FUNC_REF_SYMBOL, o as ConvexActionKey, p as ExtractPaginatedItem, s as ConvexInfiniteQueryMeta, u as ConvexQueryKey, w as ReservedQueryOptions$1, y as MutationVariables } from "../types-Bb0kwteA.js";
5
5
  import { Value } from "convex/values";
6
6
  import { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server";
7
7
  import { OptimisticUpdate } from "convex/browser";
package/dist/rsc/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { n as defaultIsUnauthorized } from "../error-CLgc9khF.js";
2
2
  import { n as getFuncRef, r as getFunctionMeta, t as buildMetaIndex } from "../meta-utils-Cc7neQOB.js";
3
3
  import { o as encodeWire, s as getTransformer } from "../transformer-C8lVwHNT.js";
4
- import { c as HTTP_DEFAULT_STALE_TIME, n as convexInfiniteQueryOptions, o as executeHttpRequest, r as convexQuery, u as buildHttpQueryKey } from "../query-options-DGG93--5.js";
4
+ import { f as buildHttpQueryKey, n as convexInfiniteQueryOptions, r as convexQuery, s as executeHttpRequest, u as HTTP_DEFAULT_STALE_TIME } from "../query-options-YkFvau71.js";
5
5
  import { convexToJson } from "convex/values";
6
6
  import { getFunctionName } from "convex/server";
7
7
  import { fetchAction, fetchQuery } from "convex/nextjs";
@@ -525,6 +525,7 @@ function buildSearchParams(query) {
525
525
  else if (value !== void 0 && value !== null) params.append(key, value);
526
526
  return params;
527
527
  }
528
+ const RECHECK_AUTHORIZATION = Symbol.for("kitcn.http.recheckAuthorization");
528
529
  /**
529
530
  * Hono-style HTTP request executor.
530
531
  * Processes args in the same way as Hono's ClientRequestImpl.fetch().
@@ -563,6 +564,8 @@ async function executeHttpRequest(opts) {
563
564
  for (const [key, value] of Object.entries(resolvedBaseHeaders)) if (value !== void 0) finalHeaders[key] = value;
564
565
  }
565
566
  Object.assign(finalHeaders, headerValues);
567
+ const recheck = resolvedBaseHeaders?.[RECHECK_AUTHORIZATION];
568
+ const sendHeaders = recheck && finalHeaders.Authorization === resolvedBaseHeaders?.Authorization && !recheck() ? Object.fromEntries(Object.entries(finalHeaders).filter(([key]) => key !== "Authorization")) : finalHeaders;
566
569
  let url = opts.convexSiteUrl + path;
567
570
  if (args.params) url = opts.convexSiteUrl + replaceUrlParam(path, args.params);
568
571
  if (args.searchParams) {
@@ -574,7 +577,7 @@ async function executeHttpRequest(opts) {
574
577
  const response = await (mergedClientOpts.fetch ?? opts.baseFetch ?? globalThis.fetch)(url, {
575
578
  body: setBody ? rBody : void 0,
576
579
  method: methodUpperCase,
577
- headers: finalHeaders,
580
+ headers: sendHeaders,
578
581
  ...mergedClientOpts.init
579
582
  });
580
583
  if (!response.ok) {
@@ -6,7 +6,8 @@ type GetTokenOptions = {
6
6
  jwtCache?: {
7
7
  enabled: boolean;
8
8
  expirationToleranceSeconds?: number;
9
- isAuthError: (error: unknown) => boolean;
9
+ isAuthError: (error: unknown) => boolean; /** Current Unix time in seconds. Defaults to the system clock. */
10
+ now?: () => number | Promise<number>;
10
11
  };
11
12
  };
12
13
  //#endregion
@@ -32,14 +32,23 @@ const getToken = async (siteUrl, headers, opts) => {
32
32
  cookiePrefix: opts?.cookiePrefix
33
33
  });
34
34
  if (!token) return await fetchToken();
35
+ let claims;
35
36
  try {
36
- if (!isTokenExpired(jose.decodeJwt(token)?.exp, opts?.jwtCache?.expirationToleranceSeconds ?? 60)) return {
37
- isFresh: false,
38
- token
39
- };
37
+ claims = jose.decodeJwt(token);
40
38
  } catch (error) {
41
39
  console.error("Error decoding JWT", error);
40
+ return await fetchToken();
41
+ }
42
+ if (!claims.exp) return await fetchToken();
43
+ let now;
44
+ if (opts.jwtCache.now) {
45
+ now = await opts.jwtCache.now();
46
+ if (!Number.isFinite(now)) throw new RangeError("JWT cache now must return finite Unix time in seconds");
42
47
  }
48
+ if (!isTokenExpired(claims.exp, opts.jwtCache.expirationToleranceSeconds ?? 60, now)) return {
49
+ isFresh: false,
50
+ token
51
+ };
43
52
  return await fetchToken();
44
53
  };
45
54
 
@@ -470,4 +470,181 @@ function Unauthenticated({ children }) {
470
470
  }
471
471
 
472
472
  //#endregion
473
- export { CRPCClientError as A, decodeJwtExp as C, readAuthSessionFallbackToken as D, readAuthSessionFallbackData as E, isCRPCClientError as M, writeAuthSessionFallbackData as O, useSafeConvexAuth as S, clearAuthSessionFallback as T, useConvexAuthBridge as _, ConvexAuthRecoveryError as a, useIsAuth as b, MaybeAuthenticated as c, isSessionSyncGraceActive as d, useAuth as f, useAuthValue as g, useAuthStore as h, ConvexAuthBridge as i, defaultIsUnauthorized as j, writeAuthSessionFallbackToken as k, MaybeUnauthenticated as l, useAuthState as m, AuthProvider as n, ConvexProviderWithAuth$1 as o, useAuthGuard as p, Authenticated as r, FetchAccessTokenContext as s, AUTH_SESSION_SYNC_GRACE_MS as t, Unauthenticated as u, useConvexAuthRecovery as v, decodeJwtIdentity as w, useMaybeAuth as x, useFetchAccessToken as y };
473
+ //#region src/react/identity-guard-registry.ts
474
+ const REGISTRY_KEY = Symbol.for("kitcn.identityGuard.v2");
475
+ const createRegistry = () => ({
476
+ documentIdentity: null,
477
+ guards: /* @__PURE__ */ new Set(),
478
+ settledClients: /* @__PURE__ */ new WeakSet(),
479
+ settlementListeners: /* @__PURE__ */ new WeakMap(),
480
+ storeGuards: /* @__PURE__ */ new WeakMap(),
481
+ tripListeners: /* @__PURE__ */ new Set(),
482
+ tripped: false,
483
+ watchedClients: /* @__PURE__ */ new WeakSet()
484
+ });
485
+ const isRegistry = (value) => {
486
+ const candidate = value;
487
+ return typeof candidate === "object" && candidate !== null && typeof candidate.tripped === "boolean" && (candidate.documentIdentity === null || typeof candidate.documentIdentity === "string") && candidate.tripListeners instanceof Set && candidate.guards instanceof Set && candidate.storeGuards instanceof WeakMap && candidate.settledClients instanceof WeakSet && candidate.settlementListeners instanceof WeakMap && candidate.watchedClients instanceof WeakSet;
488
+ };
489
+ let incompatibleStandIn;
490
+ const pageRegistry = () => {
491
+ if (typeof window === "undefined") return null;
492
+ const scope = globalThis;
493
+ scope[REGISTRY_KEY] ??= createRegistry();
494
+ const found = scope[REGISTRY_KEY];
495
+ if (isRegistry(found)) return found;
496
+ if (!incompatibleStandIn) {
497
+ console.warn("[kitcn] identity guard state of another kitcn revision is on this page; guarded providers hand out no token until the page reloads.");
498
+ incompatibleStandIn = {
499
+ ...createRegistry(),
500
+ incompatible: true
501
+ };
502
+ }
503
+ return incompatibleStandIn;
504
+ };
505
+ const isDocumentTripped = () => pageRegistry()?.tripped ?? false;
506
+ const tripDocument = () => {
507
+ const registry = pageRegistry();
508
+ if (!registry || registry.tripped) return;
509
+ registry.tripped = true;
510
+ for (const listener of [...registry.tripListeners]) try {
511
+ listener();
512
+ } catch (error) {
513
+ console.error("[kitcn] identity guard trip listener threw", error);
514
+ }
515
+ };
516
+ const subscribeDocumentTrip = (listener) => {
517
+ const registry = pageRegistry();
518
+ if (!registry) return () => {};
519
+ registry.tripListeners.add(listener);
520
+ return () => {
521
+ registry.tripListeners.delete(listener);
522
+ };
523
+ };
524
+ const attachStoreGuard = (store, guard) => {
525
+ if (store) pageRegistry()?.storeGuards.set(store, guard);
526
+ };
527
+ const storeGuard = (store) => store ? pageRegistry()?.storeGuards.get(store) : void 0;
528
+ const mountGuard = (guard) => {
529
+ const registry = pageRegistry();
530
+ if (!registry) return () => {};
531
+ registry.guards.add(guard);
532
+ joinPage(guard);
533
+ return () => {
534
+ registry.guards.delete(guard);
535
+ };
536
+ };
537
+ const joinPage = (guard) => {
538
+ const registry = pageRegistry();
539
+ if (!registry) return;
540
+ if (guard.guarded && guard.identity) registry.documentIdentity ??= guard.identity;
541
+ for (const mounted of [...registry.guards]) {
542
+ const held = mounted.heldToken();
543
+ if (held && isJwt(held) && !isTokenAdmissible(held, mounted, "hold")) {
544
+ tripDocument();
545
+ break;
546
+ }
547
+ }
548
+ };
549
+ const guardedProviderMounted = () => [...pageRegistry()?.guards ?? []].some((mounted) => mounted.guarded);
550
+ const boundIdentities = (guard) => {
551
+ const registry = pageRegistry();
552
+ const bound = [registry?.documentIdentity ?? null];
553
+ if (guard?.guarded) bound.push(guard.identity, guard.baseline());
554
+ for (const mounted of registry?.guards ?? []) if (mounted.hasGetter) bound.push(mounted.baseline());
555
+ return bound.filter((identity) => identity !== null);
556
+ };
557
+ const identityGuardInPlay = (guard) => !!guard?.guarded || boundIdentities(guard).length > 0 || guardedProviderMounted();
558
+ const isTokenAdmissible = (token, guard, use) => {
559
+ if (isDocumentTripped() || guard?.tripped) return false;
560
+ if (guard?.guarded && pageRegistry()?.incompatible) return false;
561
+ const bound = boundIdentities(guard);
562
+ if (!isJwt(token)) {
563
+ if (use === "hold") return true;
564
+ if (use === "restore") return bound.length === 0;
565
+ return !identityGuardInPlay(guard);
566
+ }
567
+ const identity = decodeTokenSubjectSessionIdentity(token);
568
+ return identity === null ? bound.length === 0 : bound.every((known) => known === identity);
569
+ };
570
+ const admitToken = (token, { announce = false, guard, use }) => {
571
+ if (!isTokenAdmissible(token, guard, use)) {
572
+ if (isJwt(token)) {
573
+ if (guard) guard.tripped = true;
574
+ tripDocument();
575
+ }
576
+ return false;
577
+ }
578
+ const identity = isJwt(token) ? decodeTokenSubjectSessionIdentity(token) : null;
579
+ if (identity !== null && (guard?.guarded || guardedProviderMounted())) {
580
+ if (guard?.guarded) guard.identity = identity;
581
+ const registry = pageRegistry();
582
+ if (registry) registry.documentIdentity ??= identity;
583
+ }
584
+ if (announce && guard?.guarded && isJwt(token)) {
585
+ guard.onAdmitted(token);
586
+ return admitToken(token, {
587
+ guard,
588
+ use
589
+ });
590
+ }
591
+ return !isDocumentTripped() && !guard?.tripped;
592
+ };
593
+ function decodeJwtClaims(token) {
594
+ const segments = token.split(".");
595
+ if (segments.length !== 3 || !segments[1]) return null;
596
+ try {
597
+ const payload = JSON.parse(atob(segments[1].replaceAll("-", "+").replaceAll("_", "/")));
598
+ return typeof payload === "object" && payload !== null && !Array.isArray(payload) ? payload : null;
599
+ } catch {
600
+ return null;
601
+ }
602
+ }
603
+ const isJwt = (token) => decodeJwtClaims(token) !== null;
604
+ /**
605
+ * The user and session a Better Auth Convex JWT speaks for (`sub` and
606
+ * `sessionId`), or null. Other claims (name, email, updatedAt) may change
607
+ * within one session and do not count.
608
+ */
609
+ function decodeTokenSubjectSessionIdentity(token) {
610
+ const claims = token ? decodeJwtClaims(token) : null;
611
+ if (!claims || typeof claims.sub !== "string") return null;
612
+ const sessionId = typeof claims.sessionId === "string" ? claims.sessionId : "";
613
+ return `${claims.sub}|${sessionId}`;
614
+ }
615
+
616
+ //#endregion
617
+ //#region src/react/token-gate.ts
618
+ const admitStoreToken = (authStore, token, options) => admitToken(token, {
619
+ ...options,
620
+ guard: storeGuard(authStore.store)
621
+ });
622
+ const publishToken = (authStore, token, { announce, sessionSyncGraceUntil, use = "hold" }) => {
623
+ if (!admitStoreToken(authStore, token, {
624
+ announce,
625
+ use
626
+ })) return false;
627
+ authStore.set("token", token);
628
+ authStore.set("expiresAt", decodeJwtExp(token));
629
+ if (sessionSyncGraceUntil !== void 0) authStore.set("sessionSyncGraceUntil", sessionSyncGraceUntil);
630
+ return true;
631
+ };
632
+ const publishAuthenticated = (authStore) => {
633
+ const token = authStore.get("token");
634
+ if (isDocumentTripped() || token && !admitStoreToken(authStore, token, { use: "hold" })) return false;
635
+ authStore.set("isAuthenticated", true);
636
+ return true;
637
+ };
638
+ const publishAuthState = (authStore, state) => {
639
+ const tripped = isDocumentTripped();
640
+ authStore.set("isLoading", tripped ? false : state.isLoading);
641
+ if (!state.isAuthenticated || tripped) {
642
+ authStore.set("isAuthenticated", false);
643
+ return;
644
+ }
645
+ const token = authStore.get("token");
646
+ authStore.set("isAuthenticated", !isDocumentTripped() && !(token && !admitStoreToken(authStore, token, { use: "hold" })));
647
+ };
648
+
649
+ //#endregion
650
+ export { useAuthState as A, decodeJwtIdentity as B, FetchAccessTokenContext as C, isSessionSyncGraceActive as D, Unauthenticated as E, useFetchAccessToken as F, writeAuthSessionFallbackToken as G, readAuthSessionFallbackData as H, useIsAuth as I, isCRPCClientError as J, CRPCClientError as K, useMaybeAuth as L, useAuthValue as M, useConvexAuthBridge as N, useAuth as O, useConvexAuthRecovery as P, useSafeConvexAuth as R, ConvexProviderWithAuth$1 as S, MaybeUnauthenticated as T, readAuthSessionFallbackToken as U, clearAuthSessionFallback as V, writeAuthSessionFallbackData as W, AUTH_SESSION_SYNC_GRACE_MS as _, admitToken as a, ConvexAuthBridge as b, identityGuardInPlay as c, isTokenAdmissible as d, joinPage as f, tripDocument as g, subscribeDocumentTrip as h, publishToken as i, useAuthStore as j, useAuthGuard as k, isDocumentTripped as l, pageRegistry as m, publishAuthState as n, attachStoreGuard as o, mountGuard as p, defaultIsUnauthorized as q, publishAuthenticated as r, decodeTokenSubjectSessionIdentity as s, admitStoreToken as t, isJwt as u, AuthProvider as v, MaybeAuthenticated as w, ConvexAuthRecoveryError as x, Authenticated as y, decodeJwtExp as z };