kitcn 0.33.5 → 0.33.7
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/CHANGELOG.md +31 -0
- package/dist/aggregate/index.d.ts +1 -1
- package/dist/auth/client/index.d.ts +74 -1
- package/dist/auth/client/index.js +238 -32
- package/dist/auth/generated/index.d.ts +1 -1
- package/dist/auth/index.d.ts +19 -19
- package/dist/auth/start/index.js +134 -2
- package/dist/crpc/index.d.ts +2 -2
- package/dist/crpc/index.js +2 -2
- package/dist/{generated-contract-disabled-PdNGvNYP.d.ts → generated-contract-disabled-QZXEzUnd.d.ts} +26 -26
- package/dist/orm/index.d.ts +1 -1
- package/dist/orm/migrations/index.d.ts +1 -1
- package/dist/{query-options-DGG93--5.js → query-options-YkFvau71.js} +6 -2
- package/dist/react/index.d.ts +12 -3
- package/dist/react/index.js +157 -140
- package/dist/rsc/index.d.ts +12 -2
- package/dist/rsc/index.js +1 -1
- package/dist/solid/index.js +4 -1
- package/dist/{auth-store-x3_wvhTR.js → token-gate-CEzbXArg.js} +178 -1
- package/dist/{types-C7JkpfeY.d.ts → types-Bb0kwteA.d.ts} +5 -1
- package/dist/{where-clause-compiler-qH6-NCM9.d.ts → where-clause-compiler-DAqhUREe.d.ts} +67 -67
- package/package.json +1 -1
- package/skills/kitcn/references/features/auth.md +35 -2
- package/skills/kitcn/references/features/react.md +16 -0
package/dist/react/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { A as
|
|
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:
|
|
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) => {
|
|
@@ -1155,10 +1159,12 @@ function useConvexMutationOptions(mutation, options, transformer) {
|
|
|
1155
1159
|
const name = getFunctionName(mutation);
|
|
1156
1160
|
const [namespace, fnName] = name.split(":");
|
|
1157
1161
|
const authType = getMeta(namespace, fnName)?.auth;
|
|
1158
|
-
const
|
|
1162
|
+
const { optimisticUpdate, ...mutationOptions } = options ?? {};
|
|
1163
|
+
const reactMutation = useMutation(mutation);
|
|
1164
|
+
const convexMutation = optimisticUpdate ? reactMutation.withOptimisticUpdate(optimisticUpdate) : reactMutation;
|
|
1159
1165
|
const resolvedTransformer = getTransformer(transformer);
|
|
1160
1166
|
return {
|
|
1161
|
-
...
|
|
1167
|
+
...mutationOptions,
|
|
1162
1168
|
mutationFn: async (args) => {
|
|
1163
1169
|
if (authType === "required" && guard()) throw new CRPCClientError({
|
|
1164
1170
|
code: "UNAUTHORIZED",
|
|
@@ -1496,21 +1502,16 @@ function createCRPCContext(options) {
|
|
|
1496
1502
|
convexSiteUrl: httpOptions.convexSiteUrl,
|
|
1497
1503
|
routes: meta._http,
|
|
1498
1504
|
headers: async () => {
|
|
1499
|
-
const token = authStore.get("token");
|
|
1500
1505
|
const expiresAt = authStore.get("expiresAt");
|
|
1501
1506
|
const timeRemaining = expiresAt ? expiresAt - Date.now() : 0;
|
|
1502
|
-
|
|
1503
|
-
|
|
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,
|
|
1504
1513
|
Authorization: `Bearer ${token}`
|
|
1505
|
-
};
|
|
1506
|
-
if (fetchAccessToken) {
|
|
1507
|
-
const newToken = await fetchAccessToken({ forceRefreshToken: !!expiresAt });
|
|
1508
|
-
if (newToken) return {
|
|
1509
|
-
...typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers,
|
|
1510
|
-
Authorization: `Bearer ${newToken}`
|
|
1511
|
-
};
|
|
1512
|
-
}
|
|
1513
|
-
return { ...typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers };
|
|
1514
|
+
}, admit) : { ...userHeaders };
|
|
1514
1515
|
},
|
|
1515
1516
|
fetch: httpOptions.fetch,
|
|
1516
1517
|
onError: httpOptions.onError,
|
|
@@ -1643,10 +1644,19 @@ function isAuthMutationError(error) {
|
|
|
1643
1644
|
|
|
1644
1645
|
//#endregion
|
|
1645
1646
|
//#region src/react/auth-mutations.ts
|
|
1646
|
-
|
|
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
|
+
};
|
|
1647
1656
|
const waitForAuth = async (store, timeout = 5e3) => {
|
|
1648
1657
|
const start = Date.now();
|
|
1649
1658
|
while (Date.now() - start < timeout) {
|
|
1659
|
+
assertDocumentNotTripped();
|
|
1650
1660
|
if (store.get("token")) return true;
|
|
1651
1661
|
await new Promise((r) => setTimeout(r, 50));
|
|
1652
1662
|
}
|
|
@@ -1671,9 +1681,10 @@ const readReturnedToken = (value) => {
|
|
|
1671
1681
|
const seedReturnedToken = (store, value) => {
|
|
1672
1682
|
const token = readReturnedToken(value);
|
|
1673
1683
|
if (!token) return;
|
|
1674
|
-
store
|
|
1675
|
-
|
|
1676
|
-
|
|
1684
|
+
if (!publishToken(store, token, {
|
|
1685
|
+
announce: true,
|
|
1686
|
+
sessionSyncGraceUntil: Date.now() + AUTH_SESSION_SYNC_GRACE_MS
|
|
1687
|
+
})) throw tokenIdentityChangedError();
|
|
1677
1688
|
if (decodeJwtExp(token) === null) writeAuthSessionFallbackToken(token);
|
|
1678
1689
|
};
|
|
1679
1690
|
const toAuthMutationError = (error) => new AuthMutationError({
|
|
@@ -1748,12 +1759,13 @@ function createAuthMutations(authClient) {
|
|
|
1748
1759
|
mutationFn: async (args) => {
|
|
1749
1760
|
const signInSocial = authClient.signIn?.social;
|
|
1750
1761
|
if (typeof signInSocial !== "function") throw new Error("Auth client does not expose signIn.social");
|
|
1762
|
+
assertDocumentNotTripped();
|
|
1751
1763
|
const res = await callAuthMethod(signInSocial, withDisabledSessionSignal(args));
|
|
1752
1764
|
if (res?.error) throw toAuthMutationError(res.error);
|
|
1753
1765
|
seedReturnedToken(authStoreApi, res);
|
|
1754
1766
|
await hydrateReturnedSession(authClient, res);
|
|
1755
1767
|
await ensureAuth(authStoreApi);
|
|
1756
|
-
authStoreApi
|
|
1768
|
+
if (!publishAuthenticated(authStoreApi)) throw tokenIdentityChangedError();
|
|
1757
1769
|
return res;
|
|
1758
1770
|
}
|
|
1759
1771
|
};
|
|
@@ -1766,12 +1778,13 @@ function createAuthMutations(authClient) {
|
|
|
1766
1778
|
mutationFn: async (args) => {
|
|
1767
1779
|
const signIn = authClient.signIn?.[signInMethod];
|
|
1768
1780
|
if (typeof signIn !== "function") throw new Error(`Auth client does not expose signIn.${signInMethod}`);
|
|
1781
|
+
assertDocumentNotTripped();
|
|
1769
1782
|
const res = await callAuthMethod(signIn, withDisabledSessionSignal(args));
|
|
1770
1783
|
if (res?.error) throw toAuthMutationError(res.error);
|
|
1771
1784
|
seedReturnedToken(authStoreApi, res);
|
|
1772
1785
|
await hydrateReturnedSession(authClient, res);
|
|
1773
1786
|
await ensureAuth(authStoreApi);
|
|
1774
|
-
authStoreApi
|
|
1787
|
+
if (!publishAuthenticated(authStoreApi)) throw tokenIdentityChangedError();
|
|
1775
1788
|
return res;
|
|
1776
1789
|
}
|
|
1777
1790
|
};
|
|
@@ -1783,12 +1796,13 @@ function createAuthMutations(authClient) {
|
|
|
1783
1796
|
mutationFn: async (args) => {
|
|
1784
1797
|
const signUpEmail = authClient.signUp?.email;
|
|
1785
1798
|
if (typeof signUpEmail !== "function") throw new Error("Auth client does not expose signUp.email");
|
|
1799
|
+
assertDocumentNotTripped();
|
|
1786
1800
|
const res = await callAuthMethod(signUpEmail, withDisabledSessionSignal(args));
|
|
1787
1801
|
if (res?.error) throw toAuthMutationError(res.error);
|
|
1788
1802
|
seedReturnedToken(authStoreApi, res);
|
|
1789
1803
|
await hydrateReturnedSession(authClient, res);
|
|
1790
1804
|
await ensureAuth(authStoreApi);
|
|
1791
|
-
authStoreApi
|
|
1805
|
+
if (!publishAuthenticated(authStoreApi)) throw tokenIdentityChangedError();
|
|
1792
1806
|
return res;
|
|
1793
1807
|
}
|
|
1794
1808
|
};
|
|
@@ -2105,7 +2119,10 @@ var ConvexQueryClient = class {
|
|
|
2105
2119
|
this._queryClient = options.queryClient;
|
|
2106
2120
|
this.unsubscribe = this.subscribeInner(options.queryClient.getQueryCache());
|
|
2107
2121
|
}
|
|
2108
|
-
if (isServer) this.serverHttpClient = new ConvexHttpClient(this.convexClient.url, {
|
|
2122
|
+
if (isServer) this.serverHttpClient = new ConvexHttpClient(this.convexClient.url, {
|
|
2123
|
+
fetch: options.serverFetch,
|
|
2124
|
+
logger: this.convexClient.logger
|
|
2125
|
+
});
|
|
2109
2126
|
}
|
|
2110
2127
|
/**
|
|
2111
2128
|
* Connect to TanStack QueryClient.
|
package/dist/rsc/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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-
|
|
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
|
+
import { Value } from "convex/values";
|
|
5
6
|
import { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server";
|
|
7
|
+
import { OptimisticUpdate } from "convex/browser";
|
|
6
8
|
import { z } from "zod";
|
|
7
9
|
import { DefaultError, QueryFilters, SkipToken, UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
|
|
8
10
|
|
|
@@ -70,11 +72,19 @@ type DecorateInfiniteQuery<T extends FunctionReference<'query'>> = {
|
|
|
70
72
|
infiniteQueryKey: (args?: DeepPartial<InfiniteQueryInput<FunctionArgs<T>>>) => ConvexQueryKey<T>; /** Function metadata from server (auth, limit, rateLimit, role, type) */
|
|
71
73
|
meta: PaginatedFnMeta;
|
|
72
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* Convex-native optimistic update for a cRPC mutation. It edits Convex's local
|
|
77
|
+
* query store, which the query client reads, so Convex rolls it back when the
|
|
78
|
+
* mutation fails. Args and query values are in their wire shape.
|
|
79
|
+
*/
|
|
80
|
+
type ConvexOptimisticUpdateOption<Args extends Record<string, Value>> = {
|
|
81
|
+
optimisticUpdate?: OptimisticUpdate<Args>;
|
|
82
|
+
};
|
|
73
83
|
/**
|
|
74
84
|
* Decorated mutation procedure with mutationOptions and mutationKey methods.
|
|
75
85
|
*/
|
|
76
86
|
type DecorateMutation<T extends FunctionReference<'mutation'>> = {
|
|
77
|
-
mutationOptions: (opts?: DistributiveOmit<UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>, ReservedMutationOptions$1>) => UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>; /** Get mutation key for QueryClient methods */
|
|
87
|
+
mutationOptions: (opts?: DistributiveOmit<UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>, ReservedMutationOptions$1> & ConvexOptimisticUpdateOption<FunctionArgs<T>>) => UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>; /** Get mutation key for QueryClient methods */
|
|
78
88
|
mutationKey: () => ConvexMutationKey;
|
|
79
89
|
};
|
|
80
90
|
/**
|
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 {
|
|
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";
|
package/dist/solid/index.js
CHANGED
|
@@ -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:
|
|
580
|
+
headers: sendHeaders,
|
|
578
581
|
...mergedClientOpts.init
|
|
579
582
|
});
|
|
580
583
|
if (!response.ok) {
|
|
@@ -470,4 +470,181 @@ function Unauthenticated({ children }) {
|
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
//#endregion
|
|
473
|
-
|
|
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 };
|