kitcn 0.0.1 → 0.12.1

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 (93) hide show
  1. package/bin/intent.js +3 -0
  2. package/dist/aggregate/index.d.ts +388 -0
  3. package/dist/aggregate/index.js +37 -0
  4. package/dist/api-entry-BckXqaLb.js +66 -0
  5. package/dist/auth/client/index.d.ts +37 -0
  6. package/dist/auth/client/index.js +217 -0
  7. package/dist/auth/config/index.d.ts +45 -0
  8. package/dist/auth/config/index.js +24 -0
  9. package/dist/auth/generated/index.d.ts +2 -0
  10. package/dist/auth/generated/index.js +3 -0
  11. package/dist/auth/http/index.d.ts +64 -0
  12. package/dist/auth/http/index.js +461 -0
  13. package/dist/auth/index.d.ts +221 -0
  14. package/dist/auth/index.js +1398 -0
  15. package/dist/auth/nextjs/index.d.ts +50 -0
  16. package/dist/auth/nextjs/index.js +81 -0
  17. package/dist/auth-store-Cljlmdmi.js +197 -0
  18. package/dist/builder-CBdG5W6A.js +1974 -0
  19. package/dist/caller-factory-cTXNvYdz.js +216 -0
  20. package/dist/cli.mjs +13264 -0
  21. package/dist/codegen-lF80HSWu.mjs +3416 -0
  22. package/dist/context-utils-HPC5nXzx.d.ts +17 -0
  23. package/dist/create-schema-odyF4kCy.js +156 -0
  24. package/dist/create-schema-orm-DOyiNDCx.js +246 -0
  25. package/dist/crpc/index.d.ts +105 -0
  26. package/dist/crpc/index.js +169 -0
  27. package/dist/customFunctions-C0voKmtx.js +144 -0
  28. package/dist/error-BZEnI7Sq.js +41 -0
  29. package/dist/generated-contract-disabled-Cih4eITO.js +50 -0
  30. package/dist/generated-contract-disabled-D-sOFy92.d.ts +354 -0
  31. package/dist/http-types-DqJubRPJ.d.ts +292 -0
  32. package/dist/meta-utils-0Pu0Nrap.js +117 -0
  33. package/dist/middleware-BUybuv9n.d.ts +34 -0
  34. package/dist/middleware-C2qTZ3V7.js +84 -0
  35. package/dist/orm/index.d.ts +17 -0
  36. package/dist/orm/index.js +10713 -0
  37. package/dist/plugins/index.d.ts +2 -0
  38. package/dist/plugins/index.js +3 -0
  39. package/dist/procedure-caller-DtxLmGwA.d.ts +1467 -0
  40. package/dist/procedure-caller-MWcxhQDv.js +349 -0
  41. package/dist/query-context-B8o6-8kC.js +1518 -0
  42. package/dist/query-context-CFZqIvD7.d.ts +42 -0
  43. package/dist/query-options-Dw7cOyXl.js +121 -0
  44. package/dist/ratelimit/index.d.ts +269 -0
  45. package/dist/ratelimit/index.js +856 -0
  46. package/dist/ratelimit/react/index.d.ts +76 -0
  47. package/dist/ratelimit/react/index.js +183 -0
  48. package/dist/react/index.d.ts +1284 -0
  49. package/dist/react/index.js +2526 -0
  50. package/dist/rsc/index.d.ts +276 -0
  51. package/dist/rsc/index.js +233 -0
  52. package/dist/runtime-CtvJPkur.js +2453 -0
  53. package/dist/server/index.d.ts +5 -0
  54. package/dist/server/index.js +6 -0
  55. package/dist/solid/index.d.ts +1221 -0
  56. package/dist/solid/index.js +2940 -0
  57. package/dist/transformer-DtDhR3Lc.js +194 -0
  58. package/dist/types-BTb_4BaU.d.ts +42 -0
  59. package/dist/types-BiJE7qxR.d.ts +4 -0
  60. package/dist/types-DEJpkIhw.d.ts +88 -0
  61. package/dist/types-HhO_R6pd.d.ts +213 -0
  62. package/dist/validators-B7oIJCAp.js +279 -0
  63. package/dist/validators-vzRKjBJC.d.ts +88 -0
  64. package/dist/watcher.mjs +96 -0
  65. package/dist/where-clause-compiler-DdjN63Io.d.ts +4756 -0
  66. package/package.json +107 -34
  67. package/skills/convex/SKILL.md +486 -0
  68. package/skills/convex/references/features/aggregates.md +353 -0
  69. package/skills/convex/references/features/auth-admin.md +446 -0
  70. package/skills/convex/references/features/auth-organizations.md +1141 -0
  71. package/skills/convex/references/features/auth-polar.md +579 -0
  72. package/skills/convex/references/features/auth.md +470 -0
  73. package/skills/convex/references/features/create-plugins.md +153 -0
  74. package/skills/convex/references/features/http.md +676 -0
  75. package/skills/convex/references/features/migrations.md +162 -0
  76. package/skills/convex/references/features/orm.md +1166 -0
  77. package/skills/convex/references/features/react.md +657 -0
  78. package/skills/convex/references/features/scheduling.md +267 -0
  79. package/skills/convex/references/features/testing.md +209 -0
  80. package/skills/convex/references/setup/auth.md +501 -0
  81. package/skills/convex/references/setup/biome.md +190 -0
  82. package/skills/convex/references/setup/doc-guidelines.md +145 -0
  83. package/skills/convex/references/setup/index.md +761 -0
  84. package/skills/convex/references/setup/next.md +116 -0
  85. package/skills/convex/references/setup/react.md +175 -0
  86. package/skills/convex/references/setup/server.md +473 -0
  87. package/skills/convex/references/setup/start.md +67 -0
  88. package/LICENSE +0 -21
  89. package/README.md +0 -0
  90. package/dist/index.d.mts +0 -5
  91. package/dist/index.d.mts.map +0 -1
  92. package/dist/index.mjs +0 -6
  93. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,2940 @@
1
+ import { getFunctionName } from "convex/server";
2
+ import { Show, createContext, createEffect, createMemo, createSignal, on, onCleanup, onMount, useContext } from "solid-js";
3
+ import { createComponent, memo } from "solid-js/web";
4
+ import { createStore } from "solid-js/store";
5
+ import { notifyManager, skipToken, useQueries, useQueryClient } from "@tanstack/solid-query";
6
+ import { ConvexClient, ConvexHttpClient } from "convex/browser";
7
+ import { hashKey } from "@tanstack/query-core";
8
+ import { convexToJson } from "convex/values";
9
+
10
+ //#region src/crpc/error.ts
11
+ /**
12
+ * Client-side CRPC error.
13
+ * Mirrors backend CRPCError pattern with typed error codes.
14
+ */
15
+ var CRPCClientError = class extends Error {
16
+ name = "CRPCClientError";
17
+ code;
18
+ functionName;
19
+ constructor(opts) {
20
+ super(opts.message ?? `${opts.code}: ${opts.functionName}`);
21
+ this.code = opts.code;
22
+ this.functionName = opts.functionName;
23
+ }
24
+ };
25
+ /** Type guard for CRPCClientError */
26
+ const isCRPCClientError = (error) => error instanceof CRPCClientError;
27
+ /** Default unauthorized detection - checks UNAUTHORIZED code */
28
+ const defaultIsUnauthorized = (error) => {
29
+ if (!error || typeof error !== "object") return false;
30
+ if ("data" in error) {
31
+ const data = error.data;
32
+ if (data && typeof data === "object" && "code" in data) return data.code === "UNAUTHORIZED";
33
+ }
34
+ if ("code" in error) return error.code === "UNAUTHORIZED";
35
+ return false;
36
+ };
37
+
38
+ //#endregion
39
+ //#region src/solid/auth-store.tsx
40
+ /** @jsxImportSource solid-js */
41
+ /**
42
+ * Auth Store - Generic auth state management with Solid stores
43
+ *
44
+ * Provides token storage and auth callback configuration.
45
+ * App configures handlers, lib hooks consume state.
46
+ */
47
+ const FetchAccessTokenContext = createContext(null);
48
+ /** Get fetchAccessToken from context (available immediately, no race condition) */
49
+ const useFetchAccessToken = () => useContext(FetchAccessTokenContext);
50
+ /**
51
+ * Context that holds auth result from ConvexAuthBridge.
52
+ * Allows @convex-dev/auth users to use skipUnauth queries without better-auth.
53
+ */
54
+ const ConvexAuthBridgeContext = createContext(null);
55
+ /** Get auth from bridge context (null if no bridge configured) */
56
+ const useConvexAuthBridge = () => useContext(ConvexAuthBridgeContext);
57
+ /** Decode JWT expiration (ms timestamp) from token */
58
+ function decodeJwtExp(token) {
59
+ try {
60
+ const payload = JSON.parse(atob(token.split(".")[1]));
61
+ return payload.exp ? payload.exp * 1e3 : null;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+ const defaultState = {
67
+ onMutationUnauthorized: () => {
68
+ throw new CRPCClientError({
69
+ code: "UNAUTHORIZED",
70
+ functionName: "mutation"
71
+ });
72
+ },
73
+ onQueryUnauthorized: () => {},
74
+ isUnauthorized: defaultIsUnauthorized,
75
+ token: null,
76
+ expiresAt: null,
77
+ isLoading: true,
78
+ isAuthenticated: false
79
+ };
80
+ const AuthStoreContext = createContext(null);
81
+ function useAuthStore() {
82
+ const ctx = useContext(AuthStoreContext);
83
+ if (!ctx) return {
84
+ get: (key) => defaultState[key],
85
+ set: () => {},
86
+ store: null
87
+ };
88
+ return ctx;
89
+ }
90
+ function useAuthValue(key) {
91
+ return useAuthStore().get(key);
92
+ }
93
+ function AuthProvider(props) {
94
+ const [state, setState] = createStore({
95
+ ...defaultState,
96
+ ...props.initialValues,
97
+ ...props.isUnauthorized && { isUnauthorized: props.isUnauthorized },
98
+ ...props.onMutationUnauthorized && { onMutationUnauthorized: props.onMutationUnauthorized },
99
+ ...props.onQueryUnauthorized && { onQueryUnauthorized: props.onQueryUnauthorized }
100
+ });
101
+ const store = {
102
+ get: (key) => state[key],
103
+ set: (key, value) => setState(key, value),
104
+ store: state
105
+ };
106
+ return createComponent(AuthStoreContext.Provider, {
107
+ value: store,
108
+ get children() {
109
+ return props.children;
110
+ }
111
+ });
112
+ }
113
+ /**
114
+ * Safe wrapper that doesn't throw when used outside auth provider.
115
+ * Returns { isAuthenticated: false, isLoading: false } when no auth provider.
116
+ *
117
+ * Supports both:
118
+ * - better-auth users (via AuthProvider)
119
+ * - @convex-dev/auth users (via ConvexAuthBridge)
120
+ */
121
+ function useSafeConvexAuth() {
122
+ const authStore = useAuthStore();
123
+ const bridgeAuth = useConvexAuthBridge();
124
+ return {
125
+ get isAuthenticated() {
126
+ if (authStore.store) return authStore.get("isAuthenticated");
127
+ if (bridgeAuth !== null) return bridgeAuth.isAuthenticated;
128
+ return false;
129
+ },
130
+ get isLoading() {
131
+ if (authStore.store) return authStore.get("isLoading");
132
+ if (bridgeAuth !== null) return bridgeAuth.isLoading;
133
+ return false;
134
+ }
135
+ };
136
+ }
137
+ /**
138
+ * Bridge component that provides auth state via context.
139
+ * @internal
140
+ */
141
+ function ConvexAuthBridge(props) {
142
+ return createComponent(ConvexAuthBridgeContext.Provider, {
143
+ value: {
144
+ get isLoading() {
145
+ return props.isLoading;
146
+ },
147
+ get isAuthenticated() {
148
+ return props.isAuthenticated;
149
+ }
150
+ },
151
+ get children() {
152
+ return props.children;
153
+ }
154
+ });
155
+ }
156
+ const useAuth = () => {
157
+ const authStore = useAuthStore();
158
+ const bridgeAuth = useConvexAuthBridge();
159
+ return {
160
+ get hasSession() {
161
+ if (authStore.store) return !!authStore.get("token");
162
+ return false;
163
+ },
164
+ get isAuthenticated() {
165
+ if (authStore.store) return authStore.get("isAuthenticated");
166
+ if (bridgeAuth !== null) return bridgeAuth.isAuthenticated;
167
+ return false;
168
+ },
169
+ get isLoading() {
170
+ if (authStore.store) return authStore.get("isLoading");
171
+ if (bridgeAuth !== null) return bridgeAuth.isLoading;
172
+ return false;
173
+ }
174
+ };
175
+ };
176
+ /** Check if user maybe has auth (optimistic, has token) */
177
+ const useMaybeAuth = () => {
178
+ const auth = useAuth();
179
+ return () => auth.hasSession;
180
+ };
181
+ /** Check if user is authenticated (server-verified) */
182
+ const useIsAuth = () => {
183
+ const auth = useAuth();
184
+ return () => auth.isAuthenticated;
185
+ };
186
+ const useAuthGuard = () => {
187
+ const authStore = useAuthStore();
188
+ const bridgeAuth = useConvexAuthBridge();
189
+ return (callback) => {
190
+ let isAuthenticated = false;
191
+ if (authStore.store) isAuthenticated = authStore.get("isAuthenticated");
192
+ else if (bridgeAuth !== null) isAuthenticated = bridgeAuth.isAuthenticated;
193
+ if (!isAuthenticated) {
194
+ authStore.get("onMutationUnauthorized")();
195
+ return true;
196
+ }
197
+ return callback ? void callback() : false;
198
+ };
199
+ };
200
+ /** Render children only when maybe has auth (optimistic) */
201
+ function MaybeAuthenticated(props) {
202
+ const isAuth = useMaybeAuth();
203
+ return createComponent(Show, {
204
+ get when() {
205
+ return isAuth();
206
+ },
207
+ get children() {
208
+ return props.children;
209
+ }
210
+ });
211
+ }
212
+ /** Render children only when authenticated (server-verified) */
213
+ function Authenticated(props) {
214
+ const isAuth = useIsAuth();
215
+ return createComponent(Show, {
216
+ get when() {
217
+ return isAuth();
218
+ },
219
+ get children() {
220
+ return props.children;
221
+ }
222
+ });
223
+ }
224
+ /** Render children only when maybe not auth (optimistic) */
225
+ function MaybeUnauthenticated(props) {
226
+ const isAuth = useMaybeAuth();
227
+ return createComponent(Show, {
228
+ get when() {
229
+ return !isAuth();
230
+ },
231
+ get children() {
232
+ return props.children;
233
+ }
234
+ });
235
+ }
236
+ /** Render children only when not authenticated (server-verified) */
237
+ function Unauthenticated(props) {
238
+ const auth = useAuth();
239
+ return createComponent(Show, {
240
+ get when() {
241
+ return memo(() => !!!auth.isAuthenticated)() && !auth.isLoading;
242
+ },
243
+ get children() {
244
+ return props.children;
245
+ }
246
+ });
247
+ }
248
+
249
+ //#endregion
250
+ //#region src/solid/auth.ts
251
+ const MetaContext = createContext(void 0);
252
+ /**
253
+ * Hook to access the meta object from context.
254
+ * Returns undefined if meta was not provided.
255
+ */
256
+ function useMeta() {
257
+ return useContext(MetaContext);
258
+ }
259
+ /**
260
+ * Hook to get function metadata from the meta index.
261
+ */
262
+ function useFnMeta() {
263
+ const meta = useMeta();
264
+ return (namespace, fnName) => meta?.[namespace]?.[fnName];
265
+ }
266
+ /** Get auth type from meta for a function */
267
+ function getAuthType(meta, funcName) {
268
+ const [namespace, fnName] = funcName.split(":");
269
+ return meta?.[namespace]?.[fnName]?.auth;
270
+ }
271
+ /** Hook to compute auth-based skip logic for queries */
272
+ function useAuthSkip(funcRef, opts) {
273
+ const auth = useSafeConvexAuth();
274
+ const authType = getAuthType(useMeta(), getFunctionName(funcRef));
275
+ const authLoadingApplies = authType === "optional" || authType === "required";
276
+ return {
277
+ authType,
278
+ get isAuthLoading() {
279
+ return auth.isLoading;
280
+ },
281
+ get isAuthenticated() {
282
+ return auth.isAuthenticated;
283
+ },
284
+ get shouldSkip() {
285
+ const isAuthLoading = auth.isLoading;
286
+ const isAuthenticated = auth.isAuthenticated;
287
+ return opts?.enabled === false || authLoadingApplies && isAuthLoading || authType === "required" && !isAuthenticated && !isAuthLoading || !isAuthenticated && !isAuthLoading && !!opts?.skipUnauth;
288
+ }
289
+ };
290
+ }
291
+
292
+ //#endregion
293
+ //#region src/shared/meta-utils.ts
294
+ const metaCache = /* @__PURE__ */ new WeakMap();
295
+ const nonMetaLeafKeys = new Set(["functionRef", "ref"]);
296
+ function isRecord(value) {
297
+ return typeof value === "object" && value !== null;
298
+ }
299
+ function isFunctionType(value) {
300
+ return value === "query" || value === "mutation" || value === "action";
301
+ }
302
+ function isMetaScalar(value) {
303
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
304
+ }
305
+ function extractLeafMeta(value) {
306
+ const type = value.type;
307
+ if (!isFunctionType(type)) return;
308
+ const result = { type };
309
+ for (const [key, entry] of Object.entries(value)) {
310
+ if (key === "type" || nonMetaLeafKeys.has(key) || key.startsWith("_")) continue;
311
+ if (entry === void 0) continue;
312
+ if (isMetaScalar(entry)) result[key] = entry;
313
+ }
314
+ return result;
315
+ }
316
+ function getHttpRoutes(api) {
317
+ const routes = api._http;
318
+ if (!isRecord(routes)) return;
319
+ const normalized = {};
320
+ for (const [routeKey, routeValue] of Object.entries(routes)) {
321
+ if (!isRecord(routeValue)) continue;
322
+ const routePath = routeValue.path;
323
+ const routeMethod = routeValue.method;
324
+ if (typeof routePath === "string" && typeof routeMethod === "string") normalized[routeKey] = {
325
+ path: routePath,
326
+ method: routeMethod
327
+ };
328
+ }
329
+ return normalized;
330
+ }
331
+ /**
332
+ * Build a metadata index from merged API leaves.
333
+ * Supports both generated `api` objects and plain metadata fixtures.
334
+ */
335
+ function buildMetaIndex(api) {
336
+ const cached = metaCache.get(api);
337
+ if (cached) return cached;
338
+ const meta = {};
339
+ const httpRoutes = getHttpRoutes(api);
340
+ if (httpRoutes) meta._http = httpRoutes;
341
+ const walk = (node, path) => {
342
+ for (const [key, value] of Object.entries(node)) {
343
+ if (key.startsWith("_")) continue;
344
+ if (!isRecord(value)) continue;
345
+ const leafMeta = extractLeafMeta(value);
346
+ if (leafMeta) {
347
+ if (path.length === 0) continue;
348
+ const namespace = path.join("/");
349
+ meta[namespace] ??= {};
350
+ meta[namespace][key] = leafMeta;
351
+ continue;
352
+ }
353
+ walk(value, [...path, key]);
354
+ }
355
+ };
356
+ walk(api, []);
357
+ metaCache.set(api, meta);
358
+ return meta;
359
+ }
360
+ /**
361
+ * Get a function reference from the API object by traversing the path.
362
+ */
363
+ function getFuncRef(api, path) {
364
+ let current = api;
365
+ for (const key of path) if (current && typeof current === "object") {
366
+ const next = current[key];
367
+ if (next === void 0) throw new Error(`Invalid path: ${path.join(".")}`);
368
+ current = next;
369
+ } else throw new Error(`Invalid path: ${path.join(".")}`);
370
+ if (current && typeof current === "object") {
371
+ const maybeFunctionRef = current.functionRef;
372
+ if (maybeFunctionRef && typeof maybeFunctionRef === "object") return maybeFunctionRef;
373
+ }
374
+ if (!current || typeof current !== "object") throw new Error(`Invalid function reference at path: ${path.join(".")}`);
375
+ return current;
376
+ }
377
+ /**
378
+ * Get function type from meta using path.
379
+ * Supports nested paths like ['items', 'queries', 'list'] → namespace='items/queries', fn='list'
380
+ *
381
+ * @param path - Path segments like ['todos', 'create'] or ['items', 'queries', 'list']
382
+ * @param meta - The meta object from codegen
383
+ * @returns Function type or 'query' as default
384
+ */
385
+ function getFunctionType(path, source) {
386
+ if (path.length < 2) return "query";
387
+ const meta = buildMetaIndex(source);
388
+ const fnName = path.at(-1);
389
+ const fnType = meta[path.slice(0, -1).join("/")]?.[fnName]?.type;
390
+ if (fnType === "query" || fnType === "mutation" || fnType === "action") return fnType;
391
+ return "query";
392
+ }
393
+ /**
394
+ * Get function metadata from meta using path.
395
+ * Supports nested paths like ['items', 'queries', 'list'] → namespace='items/queries', fn='list'
396
+ *
397
+ * @param path - Path segments like ['todos', 'create'] or ['items', 'queries', 'list']
398
+ * @param meta - The meta object from codegen
399
+ * @returns Function metadata or undefined
400
+ */
401
+ function getFunctionMeta(path, source) {
402
+ if (path.length < 2) return;
403
+ const meta = buildMetaIndex(source);
404
+ const fnName = path.at(-1);
405
+ return meta[path.slice(0, -1).join("/")]?.[fnName];
406
+ }
407
+
408
+ //#endregion
409
+ //#region src/crpc/http-types.ts
410
+ /** HTTP client error */
411
+ var HttpClientError = class extends Error {
412
+ name = "HttpClientError";
413
+ code;
414
+ status;
415
+ procedureName;
416
+ constructor(opts) {
417
+ super(opts.message ?? `${opts.code}: ${opts.procedureName}`);
418
+ this.code = opts.code;
419
+ this.status = opts.status;
420
+ this.procedureName = opts.procedureName;
421
+ }
422
+ };
423
+
424
+ //#endregion
425
+ //#region src/crpc/http-client.ts
426
+ /**
427
+ * HTTP Client Helpers
428
+ *
429
+ * Framework-agnostic utilities for executing HTTP requests
430
+ * against Convex HTTP endpoints.
431
+ */
432
+ /** Reserved keys that are not part of JSON body */
433
+ const RESERVED_KEYS = new Set([
434
+ "params",
435
+ "searchParams",
436
+ "form",
437
+ "fetch",
438
+ "init",
439
+ "headers"
440
+ ]);
441
+ /**
442
+ * Replace URL path parameters with actual values.
443
+ * e.g., '/users/:id' with { id: '123' } -> '/users/123'
444
+ */
445
+ function replaceUrlParam(url, params) {
446
+ return url.replace(/:(\w+)/g, (_, key) => {
447
+ const value = params[key];
448
+ return value !== void 0 ? encodeURIComponent(value) : `:${key}`;
449
+ });
450
+ }
451
+ /**
452
+ * Build URLSearchParams from query object.
453
+ * Handles array values as multiple params with same key (like Hono).
454
+ */
455
+ function buildSearchParams(query) {
456
+ const params = new URLSearchParams();
457
+ for (const [key, value] of Object.entries(query)) if (Array.isArray(value)) for (const v of value) params.append(key, v);
458
+ else if (value !== void 0 && value !== null) params.append(key, value);
459
+ return params;
460
+ }
461
+ /**
462
+ * Hono-style HTTP request executor.
463
+ * Processes args in the same way as Hono's ClientRequestImpl.fetch().
464
+ */
465
+ async function executeHttpRequest(opts) {
466
+ const { method, path } = opts.route;
467
+ const args = opts.args ?? {};
468
+ let rBody;
469
+ let cType;
470
+ if (args.form) {
471
+ const form = new FormData();
472
+ for (const [k, v] of Object.entries(args.form)) if (Array.isArray(v)) for (const v2 of v) form.append(k, v2);
473
+ else form.append(k, v);
474
+ rBody = form;
475
+ } else {
476
+ const jsonBody = {};
477
+ for (const [key, value] of Object.entries(args)) if (!RESERVED_KEYS.has(key) && value !== void 0) jsonBody[key] = value;
478
+ if (Object.keys(jsonBody).length > 0) {
479
+ rBody = JSON.stringify(opts.transformer.input.serialize(jsonBody));
480
+ cType = "application/json";
481
+ }
482
+ }
483
+ const argsClientOpts = {};
484
+ if (args.fetch) argsClientOpts.fetch = args.fetch;
485
+ if (args.init) argsClientOpts.init = args.init;
486
+ if (args.headers) argsClientOpts.headers = args.headers;
487
+ const mergedClientOpts = {
488
+ ...opts.clientOpts,
489
+ ...argsClientOpts
490
+ };
491
+ const resolvedBaseHeaders = typeof opts.baseHeaders === "function" ? await opts.baseHeaders() : opts.baseHeaders;
492
+ const headerValues = { ...typeof mergedClientOpts.headers === "function" ? await mergedClientOpts.headers() : mergedClientOpts.headers };
493
+ if (cType) headerValues["Content-Type"] = cType;
494
+ const finalHeaders = {};
495
+ if (resolvedBaseHeaders) {
496
+ for (const [key, value] of Object.entries(resolvedBaseHeaders)) if (value !== void 0) finalHeaders[key] = value;
497
+ }
498
+ Object.assign(finalHeaders, headerValues);
499
+ let url = opts.convexSiteUrl + path;
500
+ if (args.params) url = opts.convexSiteUrl + replaceUrlParam(path, args.params);
501
+ if (args.searchParams) {
502
+ const queryString = buildSearchParams(args.searchParams).toString();
503
+ if (queryString) url = `${url}?${queryString}`;
504
+ }
505
+ const methodUpperCase = method.toUpperCase();
506
+ const setBody = !(methodUpperCase === "GET" || methodUpperCase === "HEAD");
507
+ const response = await (mergedClientOpts.fetch ?? opts.baseFetch ?? globalThis.fetch)(url, {
508
+ body: setBody ? rBody : void 0,
509
+ method: methodUpperCase,
510
+ headers: finalHeaders,
511
+ ...mergedClientOpts.init
512
+ });
513
+ if (!response.ok) {
514
+ const errorData = await response.json().catch(() => ({ error: {
515
+ code: "UNKNOWN",
516
+ message: response.statusText
517
+ } }));
518
+ const errorCode = errorData?.error?.code || "UNKNOWN";
519
+ const errorMessage = errorData?.error?.message || response.statusText;
520
+ throw new HttpClientError({
521
+ code: errorCode,
522
+ status: response.status,
523
+ procedureName: opts.procedureName,
524
+ message: errorMessage
525
+ });
526
+ }
527
+ if (response.headers.get("content-length") === "0" || response.status === 204) return;
528
+ if ((response.headers.get("content-type") || "").includes("application/json")) return opts.transformer.output.deserialize(await response.json());
529
+ return response.text();
530
+ }
531
+
532
+ //#endregion
533
+ //#region src/crpc/transformer.ts
534
+ const isPlainObject = (value) => {
535
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
536
+ const prototype = Object.getPrototypeOf(value);
537
+ return prototype === Object.prototype || prototype === null;
538
+ };
539
+ const CODEC_MARKER_KEY = "__crpc";
540
+ const CODEC_MARKER_VALUE = 1;
541
+ const CODEC_TAG_KEY = "t";
542
+ const CODEC_VALUE_KEY = "v";
543
+ const hasOnlyCodecPayloadKeys = (value) => {
544
+ let keyCount = 0;
545
+ for (const key in value) {
546
+ if (!Object.hasOwn(value, key)) continue;
547
+ keyCount += 1;
548
+ if (key !== CODEC_MARKER_KEY && key !== CODEC_TAG_KEY && key !== CODEC_VALUE_KEY) return false;
549
+ if (keyCount > 3) return false;
550
+ }
551
+ return keyCount === 3;
552
+ };
553
+ /**
554
+ * Date wire tag (Convex-style reserved key).
555
+ */
556
+ const DATE_CODEC_TAG = "$date";
557
+ /**
558
+ * Built-in Date codec.
559
+ */
560
+ const dateWireCodec = {
561
+ tag: DATE_CODEC_TAG,
562
+ isType: (value) => value instanceof Date,
563
+ encode: (value) => value.getTime(),
564
+ decode: (value) => {
565
+ if (typeof value !== "number") return value;
566
+ return new Date(value);
567
+ }
568
+ };
569
+ /**
570
+ * Build a recursive tagged transformer from codecs.
571
+ */
572
+ const createTaggedTransformer = (codecs) => {
573
+ const codecByTag = /* @__PURE__ */ new Map();
574
+ for (const codec of codecs) {
575
+ if (!codec.tag.startsWith("$")) throw new Error(`Invalid wire codec tag '${codec.tag}'. Tags must start with '$'.`);
576
+ if (codecByTag.has(codec.tag)) throw new Error(`Duplicate wire codec tag '${codec.tag}'.`);
577
+ codecByTag.set(codec.tag, codec);
578
+ }
579
+ const serialize = (value) => {
580
+ for (const codec of codecs) if (codec.isType(value)) return {
581
+ [CODEC_MARKER_KEY]: CODEC_MARKER_VALUE,
582
+ [CODEC_TAG_KEY]: codec.tag,
583
+ [CODEC_VALUE_KEY]: serialize(codec.encode(value))
584
+ };
585
+ if (Array.isArray(value)) {
586
+ let result;
587
+ for (let index = 0; index < value.length; index += 1) {
588
+ const item = value[index];
589
+ const serialized = serialize(item);
590
+ if (serialized !== item) {
591
+ if (!result) result = value.slice();
592
+ result[index] = serialized;
593
+ }
594
+ }
595
+ return result ?? value;
596
+ }
597
+ if (isPlainObject(value)) {
598
+ let result;
599
+ for (const key in value) {
600
+ if (!Object.hasOwn(value, key)) continue;
601
+ const nested = value[key];
602
+ const serialized = serialize(nested);
603
+ if (serialized !== nested) {
604
+ if (!result) result = { ...value };
605
+ result[key] = serialized;
606
+ }
607
+ }
608
+ return result ?? value;
609
+ }
610
+ return value;
611
+ };
612
+ const deserialize = (value) => {
613
+ if (Array.isArray(value)) {
614
+ let result;
615
+ for (let index = 0; index < value.length; index += 1) {
616
+ const item = value[index];
617
+ const deserialized = deserialize(item);
618
+ if (deserialized !== item) {
619
+ if (!result) result = value.slice();
620
+ result[index] = deserialized;
621
+ }
622
+ }
623
+ return result ?? value;
624
+ }
625
+ if (isPlainObject(value)) {
626
+ const marker = value[CODEC_MARKER_KEY];
627
+ const tag = value[CODEC_TAG_KEY];
628
+ if (marker === CODEC_MARKER_VALUE && typeof tag === "string" && CODEC_VALUE_KEY in value && hasOnlyCodecPayloadKeys(value)) {
629
+ const codec = codecByTag.get(tag);
630
+ if (codec) return codec.decode(deserialize(value[CODEC_VALUE_KEY]));
631
+ }
632
+ let result;
633
+ for (const key in value) {
634
+ if (!Object.hasOwn(value, key)) continue;
635
+ const nested = value[key];
636
+ const deserialized = deserialize(nested);
637
+ if (deserialized !== nested) {
638
+ if (!result) result = { ...value };
639
+ result[key] = deserialized;
640
+ }
641
+ }
642
+ return result ?? value;
643
+ }
644
+ return value;
645
+ };
646
+ return {
647
+ serialize,
648
+ deserialize
649
+ };
650
+ };
651
+ /**
652
+ * Default cRPC transformer (Date-enabled).
653
+ */
654
+ const defaultCRPCTransformer = createTaggedTransformer([dateWireCodec]);
655
+ const DEFAULT_COMBINED_TRANSFORMER = {
656
+ input: defaultCRPCTransformer,
657
+ output: defaultCRPCTransformer
658
+ };
659
+ /**
660
+ * Normalize transformer config to split input/output shape.
661
+ */
662
+ const normalizeCustomTransformer = (transformer) => {
663
+ if (!transformer) return;
664
+ if ("input" in transformer && "output" in transformer) return transformer;
665
+ return {
666
+ input: transformer,
667
+ output: transformer
668
+ };
669
+ };
670
+ /**
671
+ * Compose user transformer with default Date transformer.
672
+ *
673
+ * Date transformer is always active:
674
+ * - serialize: user -> default(Date)
675
+ * - deserialize: default(Date) -> user
676
+ */
677
+ const composeWithDefault = (transformer) => {
678
+ if (!transformer) return defaultCRPCTransformer;
679
+ return {
680
+ serialize: (value) => defaultCRPCTransformer.serialize(transformer.serialize(value)),
681
+ deserialize: (value) => transformer.deserialize(defaultCRPCTransformer.deserialize(value))
682
+ };
683
+ };
684
+ const transformerCache = /* @__PURE__ */ new WeakMap();
685
+ /**
686
+ * Normalize transformer config to split input/output shape.
687
+ * User transformers are additive and always composed with default Date handling.
688
+ */
689
+ const getTransformer = (transformer) => {
690
+ if (!transformer) return DEFAULT_COMBINED_TRANSFORMER;
691
+ const cacheKey = transformer;
692
+ const cached = transformerCache.get(cacheKey);
693
+ if (cached) return cached;
694
+ const custom = normalizeCustomTransformer(transformer);
695
+ const resolved = {
696
+ input: composeWithDefault(custom?.input),
697
+ output: composeWithDefault(custom?.output)
698
+ };
699
+ transformerCache.set(cacheKey, resolved);
700
+ return resolved;
701
+ };
702
+
703
+ //#endregion
704
+ //#region src/solid/http-proxy.ts
705
+ /**
706
+ * Create a recursive proxy for HTTP routes with TanStack Query integration.
707
+ *
708
+ * Terminal methods:
709
+ * - GET endpoints: `queryOptions`, `queryKey`
710
+ * - POST/PUT/PATCH/DELETE: `mutationOptions`, `mutationKey`
711
+ */
712
+ function createRecursiveHttpProxy(opts, path = []) {
713
+ return new Proxy(() => {}, { get(_, prop) {
714
+ if (typeof prop === "symbol") return;
715
+ if (prop === "then") return;
716
+ const routeKey = path.join(".");
717
+ const route = opts.routes[routeKey];
718
+ if (prop === "query") {
719
+ if (!route) throw new Error(`Unknown HTTP procedure: ${routeKey}`);
720
+ return async (args) => {
721
+ try {
722
+ return await executeHttpRequest({
723
+ convexSiteUrl: opts.convexSiteUrl,
724
+ route,
725
+ procedureName: routeKey,
726
+ args,
727
+ baseHeaders: opts.headers,
728
+ baseFetch: opts.fetch,
729
+ transformer: opts.transformer
730
+ });
731
+ } catch (error) {
732
+ if (opts.onError && error instanceof HttpClientError) opts.onError(error);
733
+ throw error;
734
+ }
735
+ };
736
+ }
737
+ if (prop === "mutate") {
738
+ if (!route) throw new Error(`Unknown HTTP procedure: ${routeKey}`);
739
+ return async (args) => {
740
+ try {
741
+ return await executeHttpRequest({
742
+ convexSiteUrl: opts.convexSiteUrl,
743
+ route,
744
+ procedureName: routeKey,
745
+ args,
746
+ baseHeaders: opts.headers,
747
+ baseFetch: opts.fetch,
748
+ transformer: opts.transformer
749
+ });
750
+ } catch (error) {
751
+ if (opts.onError && error instanceof HttpClientError) opts.onError(error);
752
+ throw error;
753
+ }
754
+ };
755
+ }
756
+ if (prop === "queryOptions") {
757
+ if (!route) throw new Error(`Unknown HTTP procedure: ${routeKey}`);
758
+ if (route.method !== "GET") throw new Error(`queryOptions is only available for GET endpoints, got ${route.method} for ${routeKey}`);
759
+ return (args, queryOpts) => ({
760
+ ...queryOpts,
761
+ queryKey: [
762
+ "httpQuery",
763
+ routeKey,
764
+ args
765
+ ],
766
+ queryFn: async () => {
767
+ try {
768
+ return await executeHttpRequest({
769
+ convexSiteUrl: opts.convexSiteUrl,
770
+ route,
771
+ procedureName: routeKey,
772
+ args,
773
+ baseHeaders: opts.headers,
774
+ baseFetch: opts.fetch,
775
+ transformer: opts.transformer
776
+ });
777
+ } catch (error) {
778
+ if (opts.onError && error instanceof HttpClientError) opts.onError(error);
779
+ throw error;
780
+ }
781
+ }
782
+ });
783
+ }
784
+ if (prop === "queryKey") return (args) => {
785
+ return args !== void 0 && !(typeof args === "object" && args !== null && Object.keys(args).length === 0) ? [
786
+ "httpQuery",
787
+ routeKey,
788
+ args
789
+ ] : ["httpQuery", routeKey];
790
+ };
791
+ if (prop === "queryFilter") return (args, filters) => {
792
+ const hasArgs = args !== void 0 && !(typeof args === "object" && args !== null && Object.keys(args).length === 0);
793
+ return {
794
+ ...filters,
795
+ queryKey: hasArgs ? [
796
+ "httpQuery",
797
+ routeKey,
798
+ args
799
+ ] : ["httpQuery", routeKey]
800
+ };
801
+ };
802
+ if (prop === "mutationOptions") {
803
+ if (!route) throw new Error(`Unknown HTTP procedure: ${routeKey}`);
804
+ return (mutationOpts) => ({
805
+ ...mutationOpts,
806
+ mutationKey: ["httpMutation", routeKey],
807
+ mutationFn: async (args) => {
808
+ try {
809
+ return await executeHttpRequest({
810
+ convexSiteUrl: opts.convexSiteUrl,
811
+ route,
812
+ procedureName: routeKey,
813
+ args,
814
+ baseHeaders: opts.headers,
815
+ baseFetch: opts.fetch,
816
+ transformer: opts.transformer
817
+ });
818
+ } catch (error) {
819
+ if (opts.onError && error instanceof HttpClientError) opts.onError(error);
820
+ throw error;
821
+ }
822
+ }
823
+ });
824
+ }
825
+ if (prop === "mutationKey") return () => ["httpMutation", routeKey];
826
+ return createRecursiveHttpProxy(opts, [...path, prop]);
827
+ } });
828
+ }
829
+ /**
830
+ * Create an HTTP proxy with TanStack Query integration for SolidJS.
831
+ *
832
+ * Returns a proxy that provides:
833
+ * - `queryOptions` for GET endpoints (no subscription)
834
+ * - `mutationOptions` for POST/PUT/PATCH/DELETE endpoints
835
+ *
836
+ * @example
837
+ * ```ts
838
+ * const httpProxy = createHttpProxy<AppRouter>({
839
+ * convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL,
840
+ * routes: httpRoutes,
841
+ * });
842
+ *
843
+ * // GET endpoint
844
+ * const opts = httpProxy.todos.get.queryOptions({ id: '123' });
845
+ * const query = createQuery(() => opts);
846
+ *
847
+ * // POST endpoint
848
+ * const mutation = createMutation(() => httpProxy.todos.create.mutationOptions());
849
+ * await mutation.mutateAsync({ title: 'New todo' });
850
+ * ```
851
+ */
852
+ function createHttpProxy(opts) {
853
+ const transformer = getTransformer(opts.transformer);
854
+ return createRecursiveHttpProxy({
855
+ convexSiteUrl: opts.convexSiteUrl,
856
+ routes: opts.routes,
857
+ headers: opts.headers,
858
+ fetch: opts.fetch,
859
+ onError: opts.onError,
860
+ transformer
861
+ });
862
+ }
863
+
864
+ //#endregion
865
+ //#region src/crpc/query-options.ts
866
+ /**
867
+ * Query options factory for Convex query function subscriptions.
868
+ * Requires `convexQueryClient.queryFn()` set as the default `queryFn` globally.
869
+ */
870
+ function convexQuery(funcRef, args, meta, opts) {
871
+ const finalArgs = args ?? {};
872
+ const isSkip = finalArgs === "skip";
873
+ const funcName = getFunctionName(funcRef);
874
+ const [namespace, fnName] = funcName.split(":");
875
+ const authType = meta?.[namespace]?.[fnName]?.auth;
876
+ const skipUnauth = opts?.skipUnauth;
877
+ return {
878
+ queryKey: [
879
+ "convexQuery",
880
+ funcName,
881
+ isSkip ? "skip" : finalArgs
882
+ ],
883
+ staleTime: Number.POSITIVE_INFINITY,
884
+ refetchInterval: false,
885
+ refetchOnMount: false,
886
+ refetchOnReconnect: false,
887
+ refetchOnWindowFocus: false,
888
+ ...isSkip ? { enabled: false } : {},
889
+ meta: {
890
+ authType,
891
+ skipUnauth,
892
+ subscribe: true
893
+ }
894
+ };
895
+ }
896
+ /**
897
+ * Query options factory for Convex action functions.
898
+ * Actions are NOT reactive - they follow normal TanStack Query semantics.
899
+ *
900
+ * @example
901
+ * ```ts
902
+ * useQuery(convexAction(api.ai.generate, { prompt }))
903
+ * ```
904
+ *
905
+ * @example With additional options (use spread):
906
+ * ```ts
907
+ * useQuery({
908
+ * ...convexAction(api.files.process, { fileId }),
909
+ * staleTime: 60_000
910
+ * });
911
+ * ```
912
+ */
913
+ function convexAction(funcRef, args, meta, opts) {
914
+ const finalArgs = args ?? {};
915
+ const isSkip = finalArgs === "skip";
916
+ const funcName = getFunctionName(funcRef);
917
+ const [namespace, fnName] = funcName.split(":");
918
+ const authType = meta?.[namespace]?.[fnName]?.auth;
919
+ const skipUnauth = opts?.skipUnauth;
920
+ return {
921
+ queryKey: [
922
+ "convexAction",
923
+ funcName,
924
+ isSkip ? {} : finalArgs
925
+ ],
926
+ staleTime: Number.POSITIVE_INFINITY,
927
+ refetchInterval: false,
928
+ refetchOnMount: false,
929
+ refetchOnReconnect: false,
930
+ refetchOnWindowFocus: false,
931
+ ...isSkip ? { enabled: false } : {},
932
+ meta: {
933
+ authType,
934
+ skipUnauth,
935
+ subscribe: false
936
+ }
937
+ };
938
+ }
939
+ /**
940
+ * Infinite query options factory for paginated Convex queries.
941
+ * Server-safe (non-hook) - can be used in RSC.
942
+ *
943
+ * Uses flat { cursor, limit } input like tRPC.
944
+ */
945
+ function convexInfiniteQueryOptions(funcRef, args, opts = {}, meta) {
946
+ const { limit, skipUnauth, enabled, ...queryOptions } = opts;
947
+ const finalArgs = args === "skip" ? {} : args;
948
+ const isSkip = args === "skip";
949
+ const funcName = getFunctionName(funcRef);
950
+ const [namespace, fnName] = funcName.split(":");
951
+ const authType = (meta?.[namespace]?.[fnName])?.auth;
952
+ const firstPageArgs = {
953
+ ...finalArgs,
954
+ cursor: null,
955
+ limit
956
+ };
957
+ const finalEnabled = enabled === false || isSkip ? false : void 0;
958
+ return {
959
+ queryKey: [
960
+ "convexQuery",
961
+ funcName,
962
+ firstPageArgs
963
+ ],
964
+ staleTime: Number.POSITIVE_INFINITY,
965
+ refetchInterval: false,
966
+ refetchOnMount: false,
967
+ refetchOnReconnect: false,
968
+ refetchOnWindowFocus: false,
969
+ ...queryOptions,
970
+ ...finalEnabled === false ? { enabled: false } : {},
971
+ meta: {
972
+ authType,
973
+ skipUnauth,
974
+ subscribe: true,
975
+ queryName: funcName,
976
+ args: finalArgs,
977
+ limit
978
+ }
979
+ };
980
+ }
981
+
982
+ //#endregion
983
+ //#region src/crpc/types.ts
984
+ /** Symbol key for attaching FunctionReference to options (non-serializable) */
985
+ const FUNC_REF_SYMBOL = Symbol.for("convex.funcRef");
986
+
987
+ //#endregion
988
+ //#region src/solid/convex-solid.tsx
989
+ /** @jsxImportSource solid-js */
990
+ /**
991
+ * Convex Provider for SolidJS
992
+ *
993
+ * Provides ConvexClient via context and auth integration.
994
+ */
995
+ const ConvexContext = createContext();
996
+ /** Get the ConvexClient instance from context */
997
+ function useConvex() {
998
+ const client = useContext(ConvexContext);
999
+ if (!client) throw new Error("useConvex must be used within a ConvexProvider");
1000
+ return client;
1001
+ }
1002
+ function ConvexProvider(props) {
1003
+ return createComponent(ConvexContext.Provider, {
1004
+ get value() {
1005
+ return props.client;
1006
+ },
1007
+ get children() {
1008
+ return props.children;
1009
+ }
1010
+ });
1011
+ }
1012
+ function ConvexProviderWithAuth(props) {
1013
+ const client = props.client;
1014
+ const [isConvexLoading, setIsConvexLoading] = createSignal(true);
1015
+ const [isConvexAuthenticated, setIsConvexAuthenticated] = createSignal(false);
1016
+ createEffect(() => {
1017
+ const auth = props.useAuth();
1018
+ const loading = auth.isLoading;
1019
+ const authenticated = auth.isAuthenticated;
1020
+ if (loading) return;
1021
+ if (!authenticated) {
1022
+ client.clearAuth();
1023
+ setIsConvexLoading(false);
1024
+ setIsConvexAuthenticated(false);
1025
+ return;
1026
+ }
1027
+ client.setAuth(auth.fetchAccessToken, (isAuth) => {
1028
+ setIsConvexLoading(false);
1029
+ setIsConvexAuthenticated(isAuth);
1030
+ });
1031
+ });
1032
+ onCleanup(() => {
1033
+ client.clearAuth();
1034
+ });
1035
+ return createComponent(ConvexContext.Provider, {
1036
+ get value() {
1037
+ return props.client;
1038
+ },
1039
+ get children() {
1040
+ return createComponent(ConvexAuthBridge, {
1041
+ get isAuthenticated() {
1042
+ return isConvexAuthenticated();
1043
+ },
1044
+ get isLoading() {
1045
+ return isConvexLoading();
1046
+ },
1047
+ get children() {
1048
+ return props.children;
1049
+ }
1050
+ });
1051
+ }
1052
+ });
1053
+ }
1054
+ /** Hook returning auth state from Convex */
1055
+ function useConvexAuth() {
1056
+ const bridge = useConvexAuthBridge();
1057
+ if (!bridge) return {
1058
+ isLoading: false,
1059
+ isAuthenticated: false
1060
+ };
1061
+ return bridge;
1062
+ }
1063
+
1064
+ //#endregion
1065
+ //#region src/solid/use-query-options.ts
1066
+ /**
1067
+ * Query options factories for Convex functions (SolidJS).
1068
+ * Port of the React version — uses ConvexClient directly instead of convex/react hooks.
1069
+ *
1070
+ * Note: In @tanstack/solid-query, UseMutationOptions and UseQueryOptions are
1071
+ * Accessor wrappers. We use SolidMutationOptions and SolidQueryOptions (plain
1072
+ * object types) for parameters and return values.
1073
+ */
1074
+ /**
1075
+ * Hook that returns query options for use with useQuery.
1076
+ * Handles skipUnauth by setting enabled: false when unauthorized.
1077
+ *
1078
+ * @example
1079
+ * ```tsx
1080
+ * const { data } = useQuery(useConvexQueryOptions(api.user.get, { id }));
1081
+ * ```
1082
+ *
1083
+ * @example With skipToken for conditional queries
1084
+ * ```tsx
1085
+ * const { data } = useQuery(useConvexQueryOptions(api.user.get, userId ? { id: userId } : skipToken));
1086
+ * ```
1087
+ *
1088
+ * @example With skipUnauth
1089
+ * ```tsx
1090
+ * const { data } = useQuery(useConvexQueryOptions(api.user.get, { id }, { skipUnauth: true }));
1091
+ * ```
1092
+ *
1093
+ * @example With TanStack Query options
1094
+ * ```tsx
1095
+ * const { data } = useQuery(useConvexQueryOptions(api.user.get, { id }, { enabled: !!id, placeholderData: [] }));
1096
+ * ```
1097
+ */
1098
+ function useConvexQueryOptions(funcRef, args, options) {
1099
+ const isSkipped = args === skipToken;
1100
+ const enabled = typeof options?.enabled === "function" ? void 0 : options?.enabled;
1101
+ const authSkip = useAuthSkip(funcRef, {
1102
+ enabled: isSkipped ? false : enabled,
1103
+ skipUnauth: options?.skipUnauth
1104
+ });
1105
+ const baseOptions = convexQuery(funcRef, isSkipped ? {} : args);
1106
+ const { skipUnauth: _, subscribe, ...queryOptions } = options ?? {};
1107
+ return {
1108
+ ...baseOptions,
1109
+ ...queryOptions,
1110
+ enabled: isSkipped ? false : !authSkip.shouldSkip,
1111
+ meta: {
1112
+ ...baseOptions.meta,
1113
+ authType: authSkip.authType,
1114
+ subscribe: subscribe !== false
1115
+ }
1116
+ };
1117
+ }
1118
+ /**
1119
+ * Hook that returns infinite query options for use with useInfiniteQuery.
1120
+ * Handles auth type detection from meta and skipUnauth.
1121
+ *
1122
+ * @example
1123
+ * ```tsx
1124
+ * const { data } = useInfiniteQuery(
1125
+ * useConvexInfiniteQueryOptions(api.posts.list, { userId }, { limit: 20 })
1126
+ * );
1127
+ * ```
1128
+ *
1129
+ * @example With skipToken for conditional queries
1130
+ * ```tsx
1131
+ * const { data } = useInfiniteQuery(
1132
+ * useConvexInfiniteQueryOptions(api.posts.list, userId ? { userId } : skipToken, { limit: 20 })
1133
+ * );
1134
+ * ```
1135
+ *
1136
+ * @example With skipUnauth
1137
+ * ```tsx
1138
+ * const { data } = useInfiniteQuery(
1139
+ * useConvexInfiniteQueryOptions(api.posts.list, { userId }, { limit: 20, skipUnauth: true })
1140
+ * );
1141
+ * ```
1142
+ */
1143
+ function useConvexInfiniteQueryOptions(funcRef, args, opts) {
1144
+ const meta = useMeta();
1145
+ const isSkipped = args === skipToken;
1146
+ const enabledOpt = typeof opts.enabled === "function" ? void 0 : opts.enabled;
1147
+ const authSkip = useAuthSkip(funcRef, {
1148
+ enabled: isSkipped ? false : enabledOpt,
1149
+ skipUnauth: opts.skipUnauth
1150
+ });
1151
+ const enabled = isSkipped || authSkip.shouldSkip ? false : enabledOpt;
1152
+ const baseOptions = convexInfiniteQueryOptions(funcRef, isSkipped ? {} : args, {
1153
+ ...opts,
1154
+ enabled
1155
+ }, meta);
1156
+ return {
1157
+ ...baseOptions,
1158
+ meta: {
1159
+ ...baseOptions.meta,
1160
+ authType: authSkip.authType
1161
+ }
1162
+ };
1163
+ }
1164
+ /**
1165
+ * Hook that returns query options for using an action as a one-shot query.
1166
+ * Actions don't support WebSocket subscriptions - they're one-time calls.
1167
+ *
1168
+ * @example
1169
+ * ```tsx
1170
+ * const { data } = useQuery(useConvexActionQueryOptions(api.ai.analyze, { id }));
1171
+ * ```
1172
+ *
1173
+ * @example With skipToken for conditional queries
1174
+ * ```tsx
1175
+ * const { data } = useQuery(useConvexActionQueryOptions(api.ai.analyze, id ? { id } : skipToken));
1176
+ * ```
1177
+ *
1178
+ * @example With skipUnauth
1179
+ * ```tsx
1180
+ * const { data } = useQuery(useConvexActionQueryOptions(api.ai.analyze, { id }, { skipUnauth: true }));
1181
+ * ```
1182
+ */
1183
+ function useConvexActionQueryOptions(action, args, options) {
1184
+ const isSkipped = args === skipToken;
1185
+ const enabled = typeof options?.enabled === "function" ? void 0 : options?.enabled;
1186
+ const authSkip = useAuthSkip(action, {
1187
+ enabled: isSkipped ? false : enabled,
1188
+ skipUnauth: options?.skipUnauth
1189
+ });
1190
+ const baseOptions = convexAction(action, isSkipped ? {} : args);
1191
+ const { skipUnauth: _, ...queryOptions } = options ?? {};
1192
+ return {
1193
+ ...baseOptions,
1194
+ ...queryOptions,
1195
+ enabled: isSkipped ? false : !authSkip.shouldSkip
1196
+ };
1197
+ }
1198
+ /**
1199
+ * Hook that returns mutation options for use with useMutation.
1200
+ * Wraps the Convex mutation with auth guard logic.
1201
+ *
1202
+ * @example
1203
+ * ```tsx
1204
+ * const { mutate } = useMutation(useConvexMutationOptions(api.user.update));
1205
+ * ```
1206
+ *
1207
+ * @example With TanStack Query options
1208
+ * ```tsx
1209
+ * const { mutate } = useMutation(useConvexMutationOptions(api.user.update, {
1210
+ * onSuccess: () => toast.success('Updated!'),
1211
+ * }));
1212
+ * ```
1213
+ */
1214
+ function useConvexMutationOptions(mutation, options, transformer) {
1215
+ const guard = useAuthGuard();
1216
+ const getMeta = useFnMeta();
1217
+ const name = getFunctionName(mutation);
1218
+ const [namespace, fnName] = name.split(":");
1219
+ const authType = getMeta(namespace, fnName)?.auth;
1220
+ const convex = useConvex();
1221
+ const resolvedTransformer = getTransformer(transformer);
1222
+ return {
1223
+ ...options,
1224
+ mutationFn: async (args) => {
1225
+ if (authType === "required" && guard()) throw new CRPCClientError({
1226
+ code: "UNAUTHORIZED",
1227
+ functionName: name
1228
+ });
1229
+ return convex.mutation(mutation, resolvedTransformer.input.serialize(args));
1230
+ }
1231
+ };
1232
+ }
1233
+ /**
1234
+ * Hook that returns action options for use with useMutation.
1235
+ * Wraps the Convex action with auth guard logic.
1236
+ *
1237
+ * @example
1238
+ * ```tsx
1239
+ * const { mutate } = useMutation(useConvexActionOptions(api.ai.generate));
1240
+ * ```
1241
+ *
1242
+ * @example With TanStack Query options
1243
+ * ```tsx
1244
+ * const { mutate } = useMutation(useConvexActionOptions(api.ai.generate, {
1245
+ * onSuccess: (data) => console.info(data),
1246
+ * }));
1247
+ * ```
1248
+ */
1249
+ function useConvexActionOptions(action, options, transformer) {
1250
+ const guard = useAuthGuard();
1251
+ const getMeta = useFnMeta();
1252
+ const name = getFunctionName(action);
1253
+ const [namespace, fnName] = name.split(":");
1254
+ const authType = getMeta(namespace, fnName)?.auth;
1255
+ const convex = useConvex();
1256
+ const resolvedTransformer = getTransformer(transformer);
1257
+ return {
1258
+ ...options,
1259
+ mutationFn: async (args) => {
1260
+ if (authType === "required" && guard()) throw new CRPCClientError({
1261
+ code: "UNAUTHORIZED",
1262
+ functionName: name
1263
+ });
1264
+ return convex.action(action, resolvedTransformer.input.serialize(args));
1265
+ }
1266
+ };
1267
+ }
1268
+ /**
1269
+ * Hook that returns upload mutation options for use with useMutation.
1270
+ * Generates a presigned URL, then uploads the file directly to storage.
1271
+ *
1272
+ * @example
1273
+ * ```tsx
1274
+ * const { mutate } = useMutation(useUploadMutationOptions(api.storage.generateUrl));
1275
+ * mutate({ file, ...otherArgs });
1276
+ * ```
1277
+ *
1278
+ * @example With TanStack Query options
1279
+ * ```tsx
1280
+ * const { mutate } = useMutation(useUploadMutationOptions(api.storage.generateUrl, {
1281
+ * onSuccess: (result) => console.info('Uploaded:', result.key),
1282
+ * }));
1283
+ * ```
1284
+ */
1285
+ function useUploadMutationOptions(generateUrlMutation, options) {
1286
+ const convex = useConvex();
1287
+ return {
1288
+ ...options,
1289
+ mutationFn: async ({ file, ...args }) => {
1290
+ const result = await convex.mutation(generateUrlMutation, args);
1291
+ const { url } = result;
1292
+ const response = await fetch(url, {
1293
+ body: file,
1294
+ headers: { "Content-Type": file.type },
1295
+ method: "PUT"
1296
+ });
1297
+ if (!response.ok) throw new Error(`Upload failed: ${response.statusText}`);
1298
+ return result;
1299
+ }
1300
+ };
1301
+ }
1302
+
1303
+ //#endregion
1304
+ //#region src/solid/proxy.ts
1305
+ /**
1306
+ * CRPC Recursive Proxy
1307
+ *
1308
+ * Creates a tRPC-like proxy that wraps Convex API functions with
1309
+ * TanStack Query options builders.
1310
+ *
1311
+ * @example
1312
+ * ```ts
1313
+ * const crpc = createCRPCOptionsProxy(api);
1314
+ * const opts = crpc.user.get.queryOptions({ id: '123' });
1315
+ * const { data } = useQuery(opts);
1316
+ * ```
1317
+ */
1318
+ /** Get query key prefix based on function type */
1319
+ function getQueryKeyPrefix(path, meta) {
1320
+ if (getFunctionType(path, meta) === "action") return "convexAction";
1321
+ return "convexQuery";
1322
+ }
1323
+ /**
1324
+ * Create a recursive proxy that accumulates path segments.
1325
+ */
1326
+ function createRecursiveProxy(api, path, meta, transformer) {
1327
+ return new Proxy(() => {}, { get(_target, prop) {
1328
+ if (typeof prop === "symbol") return;
1329
+ if (prop === "then") return;
1330
+ if (prop === "queryOptions") return (args = {}, opts) => {
1331
+ const funcRef = getFuncRef(api, path);
1332
+ if (getFunctionType(path, meta) === "action") return useConvexActionQueryOptions(funcRef, args, opts);
1333
+ return useConvexQueryOptions(funcRef, args, opts);
1334
+ };
1335
+ if (prop === "staticQueryOptions") return (args = {}, opts) => {
1336
+ const funcRef = getFuncRef(api, path);
1337
+ const fnType = getFunctionType(path, meta);
1338
+ const finalArgs = args === skipToken ? "skip" : args;
1339
+ if (fnType === "action") return convexAction(funcRef, finalArgs, meta, opts);
1340
+ return convexQuery(funcRef, finalArgs, meta, opts);
1341
+ };
1342
+ if (prop === "queryKey") return (args = {}) => {
1343
+ const funcName = getFunctionName(getFuncRef(api, path));
1344
+ return [
1345
+ getQueryKeyPrefix(path, meta),
1346
+ funcName,
1347
+ args
1348
+ ];
1349
+ };
1350
+ if (prop === "queryFilter") return (args, filters) => {
1351
+ const funcName = getFunctionName(getFuncRef(api, path));
1352
+ const prefix = getQueryKeyPrefix(path, meta);
1353
+ return {
1354
+ ...filters,
1355
+ queryKey: [
1356
+ prefix,
1357
+ funcName,
1358
+ args
1359
+ ]
1360
+ };
1361
+ };
1362
+ if (prop === "infiniteQueryOptions") return (args = {}, opts = {}) => {
1363
+ const funcRef = getFuncRef(api, path);
1364
+ const options = useConvexInfiniteQueryOptions(funcRef, args, opts);
1365
+ Object.defineProperty(options, FUNC_REF_SYMBOL, {
1366
+ value: funcRef,
1367
+ enumerable: false,
1368
+ configurable: false
1369
+ });
1370
+ return options;
1371
+ };
1372
+ if (prop === "infiniteQueryKey") return (args) => {
1373
+ return [
1374
+ "convexQuery",
1375
+ getFunctionName(getFuncRef(api, path)),
1376
+ args ?? {}
1377
+ ];
1378
+ };
1379
+ if (prop === "meta" && path.length >= 2) return getFunctionMeta(path, meta);
1380
+ if (prop === "mutationKey") return () => {
1381
+ return ["convexMutation", getFunctionName(getFuncRef(api, path))];
1382
+ };
1383
+ if (prop === "mutationOptions") return (opts) => {
1384
+ const funcRef = getFuncRef(api, path);
1385
+ if (getFunctionType(path, meta) === "action") return useConvexActionOptions(funcRef, opts, transformer);
1386
+ return useConvexMutationOptions(funcRef, opts, transformer);
1387
+ };
1388
+ return createRecursiveProxy(api, [...path, prop], meta, transformer);
1389
+ } });
1390
+ }
1391
+ /**
1392
+ * Create a CRPC proxy for a Convex API object.
1393
+ *
1394
+ * The proxy provides a tRPC-like interface for accessing Convex functions
1395
+ * with TanStack Query options builders.
1396
+ *
1397
+ * @param api - The Convex API object (from `@convex/api`)
1398
+ * @param meta - Generated function metadata for runtime type detection
1399
+ * @returns A typed proxy with queryOptions/mutationOptions methods
1400
+ *
1401
+ * @example
1402
+ * ```tsx
1403
+ * import { api } from '@convex/api';
1404
+ *
1405
+ * // Usually you should use createCRPCContext({ api }) instead.
1406
+ * // createCRPCOptionsProxy is a low-level helper.
1407
+ * const crpc = createCRPCOptionsProxy(api, {} as any);
1408
+ *
1409
+ * function MyComponent() {
1410
+ * const { data } = useQuery(crpc.user.get.queryOptions({ id }));
1411
+ * const { mutate } = useMutation(crpc.user.update.mutationOptions());
1412
+ * }
1413
+ * ```
1414
+ */
1415
+ function createCRPCOptionsProxy(api, meta, transformer) {
1416
+ return createRecursiveProxy(api, [], meta, transformer);
1417
+ }
1418
+
1419
+ //#endregion
1420
+ //#region src/solid/vanilla-client.ts
1421
+ /**
1422
+ * Create a recursive proxy for vanilla (direct) calls.
1423
+ */
1424
+ function createRecursiveVanillaProxy(api, path, meta, convexClient, transformer) {
1425
+ return new Proxy(() => {}, { get(_target, prop) {
1426
+ if (typeof prop === "symbol") return;
1427
+ if (prop === "then") return;
1428
+ if (prop === "query") return async (args = {}) => {
1429
+ const funcRef = getFuncRef(api, path);
1430
+ const fnType = getFunctionType(path, meta);
1431
+ const wireArgs = transformer.input.serialize(args);
1432
+ if (fnType === "action") return transformer.output.deserialize(await convexClient.action(funcRef, wireArgs));
1433
+ return transformer.output.deserialize(await convexClient.query(funcRef, wireArgs));
1434
+ };
1435
+ if (prop === "onUpdate") return (args = {}, callback, onError) => {
1436
+ const funcRef = getFuncRef(api, path);
1437
+ return convexClient.onUpdate(funcRef, transformer.input.serialize(args), callback ? (result) => callback(transformer.output.deserialize(result)) : () => {}, onError);
1438
+ };
1439
+ if (prop === "mutate") return async (args = {}) => {
1440
+ const funcRef = getFuncRef(api, path);
1441
+ const fnType = getFunctionType(path, meta);
1442
+ const wireArgs = transformer.input.serialize(args);
1443
+ if (fnType === "action") return transformer.output.deserialize(await convexClient.action(funcRef, wireArgs));
1444
+ return transformer.output.deserialize(await convexClient.mutation(funcRef, wireArgs));
1445
+ };
1446
+ return createRecursiveVanillaProxy(api, [...path, prop], meta, convexClient, transformer);
1447
+ } });
1448
+ }
1449
+ /**
1450
+ * Create a vanilla CRPC proxy for direct procedural calls.
1451
+ *
1452
+ * The proxy provides a tRPC-like interface for imperative Convex function calls.
1453
+ *
1454
+ * @param api - The Convex API object (from `@convex/api`)
1455
+ * @param meta - Generated function metadata for runtime type detection
1456
+ * @param convexClient - The ConvexClient instance
1457
+ * @returns A typed proxy with query/mutate methods
1458
+ *
1459
+ * @example
1460
+ * ```tsx
1461
+ * const client = createVanillaCRPCProxy(api, meta, convexClient);
1462
+ *
1463
+ * // Direct calls (no Solid Query)
1464
+ * const user = await client.user.get.query({ id });
1465
+ * await client.user.update.mutate({ id, name: 'test' });
1466
+ * ```
1467
+ */
1468
+ function createVanillaCRPCProxy(api, meta, convexClient, transformer) {
1469
+ return createRecursiveVanillaProxy(api, [], meta, convexClient, getTransformer(transformer));
1470
+ }
1471
+
1472
+ //#endregion
1473
+ //#region src/solid/context.tsx
1474
+ /** @jsxImportSource solid-js */
1475
+ /**
1476
+ * CRPC Context and Provider
1477
+ *
1478
+ * Provides Solid context for the CRPC proxy, similar to tRPC's createTRPCContext.
1479
+ */
1480
+ const ConvexQueryClientContext = createContext(null);
1481
+ /** Access ConvexQueryClient (e.g., for logout cleanup) */
1482
+ const useConvexQueryClient = () => useContext(ConvexQueryClientContext);
1483
+ /** Headers record that allows empty objects and optional properties */
1484
+ /**
1485
+ * Extract HTTP router from TApi['http'] if present (optional).
1486
+ * Uses NonNullable to handle optional http property.
1487
+ */
1488
+ /**
1489
+ * Create CRPC context, provider, and hooks for a Convex API.
1490
+ *
1491
+ * @param options - Configuration object containing api and optional HTTP settings
1492
+ * @returns Object with CRPCProvider, useCRPC, and useCRPCClient
1493
+ *
1494
+ * @example
1495
+ * ```tsx
1496
+ * // lib/crpc.ts
1497
+ * import { api } from '@convex/api';
1498
+ * import { createCRPCContext } from 'kitcn/solid';
1499
+ *
1500
+ * // Works for both regular Convex functions and generated HTTP router types
1501
+ * export const { useCRPC } = createCRPCContext({
1502
+ * api,
1503
+ * convexSiteUrl: import.meta.env.VITE_CONVEX_SITE_URL,
1504
+ * });
1505
+ *
1506
+ * // components/user-profile.tsx
1507
+ * function UserProfile({ id }) {
1508
+ * const crpc = useCRPC();
1509
+ * const { data } = createQuery(() => crpc.user.get.queryOptions({ id }));
1510
+ *
1511
+ * // HTTP endpoints (if configured)
1512
+ * const { data: httpData } = createQuery(() => crpc.http.todos.get.queryOptions({ id }));
1513
+ * }
1514
+ * ```
1515
+ */
1516
+ function createCRPCContext(options) {
1517
+ const { api, ...httpOptions } = options;
1518
+ const meta = buildMetaIndex(api);
1519
+ const CRPCProxyContext = createContext(null);
1520
+ const VanillaClientContext = createContext(null);
1521
+ const HttpProxyContext = createContext(void 0);
1522
+ function CRPCProvider(props) {
1523
+ const authStore = useAuthStore();
1524
+ const fetchAccessToken = useFetchAccessToken();
1525
+ const proxy = createCRPCOptionsProxy(api, meta, options.transformer);
1526
+ const vanillaClient = createVanillaCRPCProxy(api, meta, props.convexClient, options.transformer);
1527
+ const httpProxy = (() => {
1528
+ if (!httpOptions.convexSiteUrl || !meta._http) return;
1529
+ return createHttpProxy({
1530
+ convexSiteUrl: httpOptions.convexSiteUrl,
1531
+ routes: meta._http,
1532
+ headers: async () => {
1533
+ const token = authStore.get("token");
1534
+ const expiresAt = authStore.get("expiresAt");
1535
+ const timeRemaining = expiresAt ? expiresAt - Date.now() : 0;
1536
+ if (token && expiresAt && timeRemaining >= 6e4) return {
1537
+ ...typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers,
1538
+ Authorization: `Bearer ${token}`
1539
+ };
1540
+ if (fetchAccessToken) {
1541
+ const newToken = await fetchAccessToken({ forceRefreshToken: !!expiresAt });
1542
+ if (newToken) return {
1543
+ ...typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers,
1544
+ Authorization: `Bearer ${newToken}`
1545
+ };
1546
+ }
1547
+ return { ...typeof httpOptions.headers === "function" ? await httpOptions.headers() : httpOptions.headers };
1548
+ },
1549
+ fetch: httpOptions.fetch,
1550
+ onError: httpOptions.onError,
1551
+ transformer: options.transformer
1552
+ });
1553
+ })();
1554
+ return createComponent(ConvexQueryClientContext.Provider, {
1555
+ get value() {
1556
+ return props.convexQueryClient;
1557
+ },
1558
+ get children() {
1559
+ return createComponent(MetaContext.Provider, {
1560
+ value: meta,
1561
+ get children() {
1562
+ return createComponent(VanillaClientContext.Provider, {
1563
+ value: vanillaClient,
1564
+ get children() {
1565
+ return createComponent(HttpProxyContext.Provider, {
1566
+ value: httpProxy,
1567
+ get children() {
1568
+ return createComponent(CRPCProxyContext.Provider, {
1569
+ value: proxy,
1570
+ get children() {
1571
+ return props.children;
1572
+ }
1573
+ });
1574
+ }
1575
+ });
1576
+ }
1577
+ });
1578
+ }
1579
+ });
1580
+ }
1581
+ });
1582
+ }
1583
+ function useCRPC() {
1584
+ const ctx = useContext(CRPCProxyContext);
1585
+ const httpProxy = useContext(HttpProxyContext);
1586
+ if (!ctx) throw new Error("useCRPC must be used within CRPCProvider");
1587
+ if (httpProxy) return new Proxy(ctx, { get(target, prop) {
1588
+ if (prop === "http") return httpProxy;
1589
+ return Reflect.get(target, prop);
1590
+ } });
1591
+ return ctx;
1592
+ }
1593
+ function useCRPCClient() {
1594
+ const ctx = useContext(VanillaClientContext);
1595
+ const httpProxy = useContext(HttpProxyContext);
1596
+ if (!ctx) throw new Error("useCRPCClient must be used within CRPCProvider");
1597
+ if (httpProxy) return new Proxy(ctx, { get(target, prop) {
1598
+ if (prop === "http") return httpProxy;
1599
+ return Reflect.get(target, prop);
1600
+ } });
1601
+ return ctx;
1602
+ }
1603
+ return {
1604
+ CRPCProvider,
1605
+ useCRPC,
1606
+ useCRPCClient
1607
+ };
1608
+ }
1609
+
1610
+ //#endregion
1611
+ //#region src/crpc/auth-error.ts
1612
+ /**
1613
+ * Auth Mutation Error
1614
+ *
1615
+ * Framework-agnostic error class for Better Auth mutations.
1616
+ */
1617
+ /**
1618
+ * Error thrown when a Better Auth mutation fails.
1619
+ * Contains the original error details from Better Auth.
1620
+ */
1621
+ var AuthMutationError = class extends Error {
1622
+ /** Error code from Better Auth (e.g., 'INVALID_PASSWORD', 'EMAIL_ALREADY_REGISTERED') */
1623
+ code;
1624
+ /** HTTP status code */
1625
+ status;
1626
+ /** HTTP status text */
1627
+ statusText;
1628
+ constructor(authError) {
1629
+ super(authError.message || authError.statusText);
1630
+ this.name = "AuthMutationError";
1631
+ this.code = authError.code;
1632
+ this.status = authError.status;
1633
+ this.statusText = authError.statusText;
1634
+ }
1635
+ };
1636
+ /**
1637
+ * Type guard to check if an error is an AuthMutationError.
1638
+ */
1639
+ function isAuthMutationError(error) {
1640
+ return error instanceof AuthMutationError;
1641
+ }
1642
+
1643
+ //#endregion
1644
+ //#region src/solid/auth-mutations.ts
1645
+ /** Poll until JWT token exists (auth complete) (max 5s) */
1646
+ const waitForAuth = async (store, timeout = 5e3) => {
1647
+ const start = Date.now();
1648
+ while (Date.now() - start < timeout) {
1649
+ if (store.get("token")) return true;
1650
+ await new Promise((r) => setTimeout(r, 50));
1651
+ }
1652
+ return false;
1653
+ };
1654
+ const authStateTimeoutError = () => new AuthMutationError({
1655
+ code: "AUTH_STATE_TIMEOUT",
1656
+ message: "Authentication did not complete. Try again.",
1657
+ status: 401,
1658
+ statusText: "UNAUTHORIZED"
1659
+ });
1660
+ const ensureAuth = async (store) => {
1661
+ if (await waitForAuth(store)) return;
1662
+ throw authStateTimeoutError();
1663
+ };
1664
+ const readReturnedToken = (value) => {
1665
+ if (!value || typeof value !== "object") return null;
1666
+ const record = value;
1667
+ if (typeof record.token === "string" && record.token.length > 0) return record.token;
1668
+ return readReturnedToken(record.data) ?? readReturnedToken(record.session);
1669
+ };
1670
+ const seedReturnedToken = (store, value) => {
1671
+ const token = readReturnedToken(value);
1672
+ if (!token) return;
1673
+ store.set("token", token);
1674
+ store.set("expiresAt", decodeJwtExp(token));
1675
+ };
1676
+ /**
1677
+ * Create mutation option hooks from a better-auth client.
1678
+ *
1679
+ * @example
1680
+ * ```tsx
1681
+ * // lib/auth-client.ts
1682
+ * import { createAuthMutations } from 'kitcn/solid';
1683
+ *
1684
+ * export const authClient = createAuthClient({...});
1685
+ *
1686
+ * export const {
1687
+ * useSignOutMutationOptions,
1688
+ * useSignInSocialMutationOptions,
1689
+ * useSignInMutationOptions,
1690
+ * useSignUpMutationOptions,
1691
+ * } = createAuthMutations(authClient);
1692
+ *
1693
+ * // components/header.tsx
1694
+ * const signOutMutation = createMutation(() => useSignOutMutationOptions({
1695
+ * onSuccess: () => navigate('/login'),
1696
+ * }));
1697
+ * ```
1698
+ */
1699
+ function createAuthMutations(authClient) {
1700
+ const useSignOutMutationOptions = ((options) => {
1701
+ const convexQueryClient = useConvexQueryClient();
1702
+ const authStoreApi = useAuthStore();
1703
+ return {
1704
+ ...options,
1705
+ mutationFn: async (args) => {
1706
+ authStoreApi.set("isAuthenticated", false);
1707
+ convexQueryClient?.unsubscribeAuthQueries();
1708
+ const res = await authClient.signOut(args);
1709
+ if (res?.error) throw new AuthMutationError(res.error);
1710
+ authStoreApi.set("token", null);
1711
+ authStoreApi.set("expiresAt", null);
1712
+ return res;
1713
+ }
1714
+ };
1715
+ });
1716
+ const useSignInSocialMutationOptions = ((options) => {
1717
+ const authStoreApi = useAuthStore();
1718
+ return {
1719
+ ...options,
1720
+ mutationFn: async (args) => {
1721
+ const res = await authClient.signIn.social(args);
1722
+ if (res?.error) throw new AuthMutationError(res.error);
1723
+ seedReturnedToken(authStoreApi, res);
1724
+ await ensureAuth(authStoreApi);
1725
+ return res;
1726
+ }
1727
+ };
1728
+ });
1729
+ const useSignInMutationOptions = ((options) => {
1730
+ const authStoreApi = useAuthStore();
1731
+ return {
1732
+ ...options,
1733
+ mutationFn: async (args) => {
1734
+ const res = await authClient.signIn.email(args);
1735
+ if (res?.error) throw new AuthMutationError(res.error);
1736
+ seedReturnedToken(authStoreApi, res);
1737
+ await ensureAuth(authStoreApi);
1738
+ return res;
1739
+ }
1740
+ };
1741
+ });
1742
+ const useSignUpMutationOptions = ((options) => {
1743
+ const authStoreApi = useAuthStore();
1744
+ return {
1745
+ ...options,
1746
+ mutationFn: async (args) => {
1747
+ const res = await authClient.signUp.email(args);
1748
+ if (res?.error) throw new AuthMutationError(res.error);
1749
+ seedReturnedToken(authStoreApi, res);
1750
+ await ensureAuth(authStoreApi);
1751
+ return res;
1752
+ }
1753
+ };
1754
+ });
1755
+ return {
1756
+ useSignOutMutationOptions,
1757
+ useSignInSocialMutationOptions,
1758
+ useSignInMutationOptions,
1759
+ useSignUpMutationOptions
1760
+ };
1761
+ }
1762
+
1763
+ //#endregion
1764
+ //#region src/internal/query-key.ts
1765
+ /**
1766
+ * Shared query key utilities for Convex + TanStack Query.
1767
+ * This file has NO React dependencies so it can be imported in both
1768
+ * server (RSC) and client contexts.
1769
+ */
1770
+ /**
1771
+ * Check if query key is for a Convex query function.
1772
+ * Format: ['convexQuery', 'namespace:functionName', { args }]
1773
+ */
1774
+ function isConvexQuery(queryKey) {
1775
+ return queryKey.length >= 2 && queryKey[0] === "convexQuery";
1776
+ }
1777
+ /**
1778
+ * Check if query key is for a Convex action function.
1779
+ * Format: ['convexAction', 'namespace:functionName', { args }]
1780
+ */
1781
+ function isConvexAction$1(queryKey) {
1782
+ return queryKey.length >= 2 && queryKey[0] === "convexAction";
1783
+ }
1784
+ /**
1785
+ * Create stable hash for Convex query keys.
1786
+ * Uses Convex's JSON serialization for consistent argument hashing.
1787
+ */
1788
+ function hashConvexQuery(queryKey) {
1789
+ const [, funcName, args] = queryKey;
1790
+ return `convexQuery|${funcName}|${JSON.stringify(convexToJson(args))}`;
1791
+ }
1792
+ /**
1793
+ * Create stable hash for Convex action keys.
1794
+ * Uses Convex's JSON serialization for consistent argument hashing.
1795
+ */
1796
+ function hashConvexAction(queryKey) {
1797
+ const [, funcName, args] = queryKey;
1798
+ return `convexAction|${funcName}|${JSON.stringify(convexToJson(args))}`;
1799
+ }
1800
+
1801
+ //#endregion
1802
+ //#region src/internal/hash.ts
1803
+ /**
1804
+ * Create a hash function for TanStack Query that handles Convex keys.
1805
+ */
1806
+ function createHashFn(fallback = hashKey) {
1807
+ return (queryKey) => {
1808
+ if (isConvexQuery(queryKey)) return hashConvexQuery(queryKey);
1809
+ if (isConvexAction$1(queryKey)) return hashConvexAction(queryKey);
1810
+ return fallback(queryKey);
1811
+ };
1812
+ }
1813
+
1814
+ //#endregion
1815
+ //#region src/solid/client.ts
1816
+ /**
1817
+ * ConvexQueryClient - Real-time subscription bridge for TanStack Query + Convex (SolidJS)
1818
+ *
1819
+ * ## Why This Exists
1820
+ *
1821
+ * TanStack Query is request-based (fetch once, cache, refetch on stale).
1822
+ * Convex is subscription-based (WebSocket push updates in real-time).
1823
+ *
1824
+ * This client bridges the two by:
1825
+ * 1. Listening to TanStack Query cache events (query added/removed)
1826
+ * 2. Creating Convex WebSocket subscriptions for each active query
1827
+ * 3. Pushing Convex updates into TanStack Query cache
1828
+ *
1829
+ * ## Architecture
1830
+ *
1831
+ * ```
1832
+ * useConvexQuery (hook)
1833
+ * |
1834
+ * v
1835
+ * TanStack Query Cache <---- ConvexQueryClient listens to cache events
1836
+ * | |
1837
+ * | v
1838
+ * | Convex WebSocket subscriptions
1839
+ * | |
1840
+ * | v
1841
+ * +---------------------- Real-time updates pushed to cache
1842
+ * ```
1843
+ *
1844
+ * ## Subscription Lifecycle
1845
+ *
1846
+ * WebSocket subscriptions are decoupled from cache retention:
1847
+ * - Subscribe when query has observers (component mounted)
1848
+ * - Unsubscribe immediately when last observer removed (component unmounted)
1849
+ * - Cache data persists for gcTime (default 5 min) for instant back-navigation
1850
+ * - On remount: show cached data instantly, re-subscribe for fresh updates
1851
+ *
1852
+ * ## SSR Support
1853
+ *
1854
+ * On server: Uses ConvexHttpClient for one-shot queries (no WebSocket).
1855
+ * On client: Uses ConvexClient with WebSocket subscriptions.
1856
+ *
1857
+ * @module
1858
+ */
1859
+ const isServer = typeof window === "undefined";
1860
+ /**
1861
+ * Check if query is marked as skipped (used when auth required but not authenticated).
1862
+ * Skipped queries have 'skip' as the third element instead of args.
1863
+ */
1864
+ function isConvexSkipped(queryKey) {
1865
+ return queryKey.length >= 2 && ["convexQuery", "convexAction"].includes(queryKey[0]) && queryKey[2] === "skip";
1866
+ }
1867
+ /**
1868
+ * Check if query key is for a Convex action function.
1869
+ * Format: ['convexAction', 'namespace:functionName', { args }]
1870
+ */
1871
+ function isConvexAction(queryKey) {
1872
+ return queryKey.length >= 2 && queryKey[0] === "convexAction";
1873
+ }
1874
+ /**
1875
+ * Bridges TanStack Query with Convex real-time subscriptions (SolidJS).
1876
+ *
1877
+ * Uses `ConvexClient.onUpdate()` instead of `ConvexReactClient.watchQuery()`.
1878
+ */
1879
+ var ConvexQueryClient = class {
1880
+ /** Convex client for WebSocket subscriptions (client) and one-shot queries */
1881
+ convexClient;
1882
+ /**
1883
+ * Active WebSocket subscriptions, keyed by TanStack query hash.
1884
+ * Each subscription has:
1885
+ * - getCurrentValue: getter for latest query result
1886
+ * - unsubscribe: cleanup function to remove the subscription
1887
+ * - queryKey: original query key for cache updates
1888
+ * - lastError: most recent error from onError callback
1889
+ */
1890
+ subscriptions;
1891
+ /** Cleanup function for QueryCache subscription */
1892
+ unsubscribe;
1893
+ /**
1894
+ * Pending unsubscribes with timeout IDs.
1895
+ * Used to debounce unsubscribe/subscribe cycles.
1896
+ */
1897
+ pendingUnsubscribes = /* @__PURE__ */ new Map();
1898
+ /** HTTP client for SSR queries (no WebSocket on server) */
1899
+ serverHttpClient;
1900
+ /** TanStack QueryClient reference */
1901
+ _queryClient;
1902
+ /** SSR query mode: 'consistent' guarantees same timestamp, 'inconsistent' is faster */
1903
+ ssrQueryMode;
1904
+ /** Auth store for checking auth state */
1905
+ authStore;
1906
+ /** Delay before unsubscribing when query has no observers */
1907
+ unsubscribeDelay;
1908
+ /** Payload transformer used across request/response boundaries. */
1909
+ transformer;
1910
+ /** Stored URL for creating HTTP client on server */
1911
+ convexUrl;
1912
+ /** Runtime-safe accessor for pending unsubscribe map (defensive for HMR edge cases) */
1913
+ getPendingUnsubscribesMap() {
1914
+ const self = this;
1915
+ if (!self.pendingUnsubscribes) self.pendingUnsubscribes = /* @__PURE__ */ new Map();
1916
+ return self.pendingUnsubscribes;
1917
+ }
1918
+ /** Cancel a pending delayed unsubscribe for a query hash. */
1919
+ cancelPendingUnsubscribe(queryHash) {
1920
+ const pendingUnsubscribes = this.getPendingUnsubscribesMap();
1921
+ const timeoutId = pendingUnsubscribes.get(queryHash);
1922
+ if (!timeoutId) return;
1923
+ clearTimeout(timeoutId);
1924
+ pendingUnsubscribes.delete(queryHash);
1925
+ }
1926
+ /** Unsubscribe a live Convex subscription (if present) and remove it from the subscription map. */
1927
+ unsubscribeQueryByHash(queryHash) {
1928
+ const sub = this.subscriptions[queryHash];
1929
+ if (!sub) return;
1930
+ sub.unsubscribe();
1931
+ delete this.subscriptions[queryHash];
1932
+ }
1933
+ /** Update auth store (for HMR where store may reset) */
1934
+ updateAuthStore(authStore) {
1935
+ this.authStore = authStore;
1936
+ }
1937
+ /** Get current auth state from store */
1938
+ getAuthState() {
1939
+ if (!this.authStore) return;
1940
+ return {
1941
+ isLoading: this.authStore.get("isLoading"),
1942
+ isAuthenticated: this.authStore.get("isAuthenticated"),
1943
+ onUnauthorized: this.authStore.get("onQueryUnauthorized"),
1944
+ isUnauthorized: this.authStore.get("isUnauthorized")
1945
+ };
1946
+ }
1947
+ /**
1948
+ * Check if subscription should be skipped due to auth state.
1949
+ * Needed for useSuspenseQuery which ignores enabled: false.
1950
+ */
1951
+ shouldSkipSubscription(authType) {
1952
+ if (!authType || !this.authStore) return false;
1953
+ const authState = this.getAuthState();
1954
+ if (authState?.isLoading) return true;
1955
+ if (authType === "required" && !authState?.isAuthenticated) return true;
1956
+ return false;
1957
+ }
1958
+ /** Get QueryClient, throwing if not connected */
1959
+ get queryClient() {
1960
+ if (!this._queryClient) throw new Error("ConvexQueryClient not connected to TanStack QueryClient.");
1961
+ return this._queryClient;
1962
+ }
1963
+ /**
1964
+ * Create a ConvexQueryClient.
1965
+ *
1966
+ * @param client - Convex URL string or existing ConvexClient
1967
+ * @param options - Configuration options
1968
+ */
1969
+ constructor(client, options = {}) {
1970
+ if (typeof client === "string") {
1971
+ this.convexClient = new ConvexClient(client);
1972
+ this.convexUrl = client;
1973
+ } else {
1974
+ this.convexClient = client;
1975
+ this.convexUrl = client.client.url;
1976
+ }
1977
+ this.ssrQueryMode = options.dangerouslyUseInconsistentQueriesDuringSSR ? "inconsistent" : "consistent";
1978
+ this.subscriptions = {};
1979
+ this.authStore = options.authStore;
1980
+ this.unsubscribeDelay = options.unsubscribeDelay ?? 3e3;
1981
+ this.transformer = getTransformer(options.transformer);
1982
+ if (options.queryClient) {
1983
+ this._queryClient = options.queryClient;
1984
+ this.unsubscribe = this.subscribeInner(options.queryClient.getQueryCache());
1985
+ }
1986
+ if (isServer) this.serverHttpClient = new ConvexHttpClient(this.convexUrl, { fetch: options.serverFetch });
1987
+ }
1988
+ /**
1989
+ * Connect to TanStack QueryClient.
1990
+ * Starts listening to cache events for subscription management.
1991
+ */
1992
+ connect(queryClient) {
1993
+ if (this._queryClient === queryClient && this.unsubscribe) return;
1994
+ if (this.unsubscribe) this.unsubscribe();
1995
+ this._queryClient = queryClient;
1996
+ this.unsubscribe = this.subscribeInner(queryClient.getQueryCache());
1997
+ }
1998
+ /**
1999
+ * Clean up all subscriptions.
2000
+ * Call this when the client is no longer needed.
2001
+ */
2002
+ destroy() {
2003
+ this.unsubscribe?.();
2004
+ for (const timeoutId of this.getPendingUnsubscribesMap().values()) clearTimeout(timeoutId);
2005
+ this.getPendingUnsubscribesMap().clear();
2006
+ for (const sub of Object.values(this.subscriptions)) sub.unsubscribe();
2007
+ this.subscriptions = {};
2008
+ }
2009
+ /**
2010
+ * Unsubscribe from all auth-required queries.
2011
+ * Call before logout to prevent UNAUTHORIZED errors during session invalidation.
2012
+ */
2013
+ unsubscribeAuthQueries() {
2014
+ for (const queryHash of Object.keys(this.subscriptions)) if ((this.queryClient.getQueryCache().get(queryHash)?.meta)?.authType === "required") {
2015
+ this.cancelPendingUnsubscribe(queryHash);
2016
+ this.unsubscribeQueryByHash(queryHash);
2017
+ }
2018
+ }
2019
+ /**
2020
+ * Batch update all subscriptions.
2021
+ * Called internally when Convex client reconnects.
2022
+ */
2023
+ onUpdate = () => {
2024
+ notifyManager.batch(() => {
2025
+ for (const key of Object.keys(this.subscriptions)) this.onUpdateQueryKeyHash(key);
2026
+ });
2027
+ };
2028
+ /**
2029
+ * Handle Convex subscription update for a specific query.
2030
+ * Reads latest value from getCurrentValue and updates TanStack cache.
2031
+ *
2032
+ * @param queryHash - TanStack query hash identifying the query
2033
+ */
2034
+ onUpdateQueryKeyHash(queryHash) {
2035
+ const subscription = this.subscriptions[queryHash];
2036
+ if (!subscription) throw new Error(`Internal ConvexQueryClient error: onUpdateQueryKeyHash called for ${queryHash}`);
2037
+ const query = this.queryClient.getQueryCache().get(queryHash);
2038
+ if (!query) return;
2039
+ const { queryKey, getCurrentValue, lastError } = subscription;
2040
+ let result;
2041
+ if (lastError !== void 0) result = {
2042
+ ok: false,
2043
+ error: lastError
2044
+ };
2045
+ else result = {
2046
+ ok: true,
2047
+ value: getCurrentValue()
2048
+ };
2049
+ if (result.ok) {
2050
+ const existingData = this.queryClient.getQueryData(queryKey);
2051
+ if (result.value !== null && result.value !== void 0 || !(existingData !== null && existingData !== void 0)) this.queryClient.setQueryData(queryKey, this.transformer.output.deserialize(result.value));
2052
+ } else {
2053
+ const { error } = result;
2054
+ const authState = this.getAuthState();
2055
+ const meta = query.meta;
2056
+ const isUnauthorized = authState?.isUnauthorized(error) ?? false;
2057
+ if (isUnauthorized && meta?.skipUnauth) {
2058
+ this.queryClient.setQueryData(queryKey, this.transformer.output.deserialize(null));
2059
+ return;
2060
+ }
2061
+ query.setState({
2062
+ error,
2063
+ errorUpdateCount: query.state.errorUpdateCount + 1,
2064
+ errorUpdatedAt: Date.now(),
2065
+ fetchFailureCount: query.state.fetchFailureCount + 1,
2066
+ fetchFailureReason: error,
2067
+ fetchStatus: "idle",
2068
+ status: "error"
2069
+ }, { meta: "set by ConvexQueryClient" });
2070
+ if (isUnauthorized && authState?.isAuthenticated) {
2071
+ const [, funcName] = queryKey;
2072
+ authState.onUnauthorized({ queryName: funcName });
2073
+ }
2074
+ }
2075
+ }
2076
+ /**
2077
+ * Subscribe to TanStack QueryCache events.
2078
+ * Creates/removes Convex WebSocket subscriptions as queries are added/removed.
2079
+ *
2080
+ * @param queryCache - TanStack QueryCache to subscribe to
2081
+ * @returns Cleanup function to unsubscribe
2082
+ */
2083
+ subscribeInner(queryCache) {
2084
+ if (isServer) return () => {};
2085
+ return queryCache.subscribe((event) => {
2086
+ if (!isConvexQuery(event.query.queryKey)) return;
2087
+ if (isConvexSkipped(event.query.queryKey)) return;
2088
+ switch (event.type) {
2089
+ case "removed":
2090
+ this.cancelPendingUnsubscribe(event.query.queryHash);
2091
+ this.unsubscribeQueryByHash(event.query.queryHash);
2092
+ break;
2093
+ case "added": {
2094
+ const meta = event.query.meta;
2095
+ if (meta?.subscribe === false) break;
2096
+ const [, funcName, args] = event.query.queryKey;
2097
+ if (event.query.getObserversCount() === 0) break;
2098
+ if (this.shouldSkipSubscription(meta?.authType)) break;
2099
+ this.createSubscription(event.query.queryHash, funcName, args, event.query.queryKey);
2100
+ break;
2101
+ }
2102
+ case "observerAdded": {
2103
+ this.cancelPendingUnsubscribe(event.query.queryHash);
2104
+ if (this.subscriptions[event.query.queryHash]) break;
2105
+ if (event.query.options.enabled === false) break;
2106
+ const meta = event.query.meta;
2107
+ if (meta?.subscribe === false) break;
2108
+ const [, funcName, args] = event.query.queryKey;
2109
+ if (this.shouldSkipSubscription(meta?.authType)) break;
2110
+ this.createSubscription(event.query.queryHash, funcName, args, event.query.queryKey);
2111
+ break;
2112
+ }
2113
+ case "observerRemoved":
2114
+ if (event.query.getObserversCount() === 0 && this.subscriptions[event.query.queryHash]) {
2115
+ const queryHash = event.query.queryHash;
2116
+ const timeoutId = setTimeout(() => {
2117
+ this.getPendingUnsubscribesMap().delete(queryHash);
2118
+ if (event.query.getObserversCount() === 0) this.unsubscribeQueryByHash(queryHash);
2119
+ }, this.unsubscribeDelay);
2120
+ this.getPendingUnsubscribesMap().set(queryHash, timeoutId);
2121
+ }
2122
+ break;
2123
+ case "observerResultsUpdated": break;
2124
+ case "updated":
2125
+ if (event.action.type === "setState" && event.action.setStateOptions?.meta === "set by ConvexQueryClient") break;
2126
+ break;
2127
+ case "observerOptionsUpdated": {
2128
+ const isDisabled = event.query.options.enabled === false;
2129
+ const isSubscribed = !!this.subscriptions[event.query.queryHash];
2130
+ if (isDisabled && isSubscribed) {
2131
+ this.cancelPendingUnsubscribe(event.query.queryHash);
2132
+ this.unsubscribeQueryByHash(event.query.queryHash);
2133
+ break;
2134
+ }
2135
+ if (isSubscribed || isDisabled) break;
2136
+ const meta = event.query.meta;
2137
+ if (meta?.subscribe === false) break;
2138
+ const [, funcName, args] = event.query.queryKey;
2139
+ if (this.shouldSkipSubscription(meta?.authType)) break;
2140
+ this.createSubscription(event.query.queryHash, funcName, args, event.query.queryKey);
2141
+ break;
2142
+ }
2143
+ }
2144
+ });
2145
+ }
2146
+ /**
2147
+ * Create a WebSocket subscription via ConvexClient.onUpdate().
2148
+ * Stores the subscription in the subscriptions map.
2149
+ */
2150
+ createSubscription(queryHash, funcName, args, queryKey) {
2151
+ this.subscriptions[queryHash] = {
2152
+ getCurrentValue: () => void 0,
2153
+ unsubscribe: () => {},
2154
+ queryKey,
2155
+ lastError: void 0
2156
+ };
2157
+ const unsub = this.convexClient.onUpdate(funcName, this.transformer.input.serialize(args), () => {
2158
+ if (this.subscriptions[queryHash]) this.subscriptions[queryHash].lastError = void 0;
2159
+ this.onUpdateQueryKeyHash(queryHash);
2160
+ }, (error) => {
2161
+ if (this.subscriptions[queryHash]) this.subscriptions[queryHash].lastError = error;
2162
+ this.onUpdateQueryKeyHash(queryHash);
2163
+ });
2164
+ this.subscriptions[queryHash].getCurrentValue = unsub.getCurrentValue.bind(unsub);
2165
+ this.subscriptions[queryHash].unsubscribe = () => unsub();
2166
+ }
2167
+ /**
2168
+ * Create default queryFn for TanStack QueryClient.
2169
+ *
2170
+ * Handles:
2171
+ * - Convex queries and actions
2172
+ * - Auth checking (throws CRPCClientError if unauthorized)
2173
+ * - SSR via HTTP client
2174
+ * - Client via WebSocket client
2175
+ *
2176
+ * @param otherFetch - Fallback queryFn for non-Convex queries
2177
+ * @returns QueryFunction compatible with TanStack Query
2178
+ */
2179
+ queryFn(otherFetch = throwBecauseNotConvexQuery) {
2180
+ return async (context) => {
2181
+ const { queryKey, meta: rawMeta } = context;
2182
+ const meta = rawMeta;
2183
+ if (isConvexSkipped(queryKey)) throw new Error("Skipped query should not actually run, should { enabled: false }");
2184
+ if (isConvexQuery(queryKey)) {
2185
+ const [, funcName, args] = queryKey;
2186
+ const wireArgs = this.transformer.input.serialize(args);
2187
+ const skipUnauth = meta?.skipUnauth ?? false;
2188
+ if (meta?.authType === "required" && !isServer && this.authStore) {
2189
+ const authState = this.getAuthState();
2190
+ if (authState && !authState.isLoading && !authState.isAuthenticated) {
2191
+ if (skipUnauth) return null;
2192
+ authState.onUnauthorized({ queryName: funcName });
2193
+ throw new CRPCClientError({
2194
+ code: "UNAUTHORIZED",
2195
+ functionName: funcName
2196
+ });
2197
+ }
2198
+ }
2199
+ try {
2200
+ if (isServer) {
2201
+ if (this.ssrQueryMode === "consistent") return this.transformer.output.deserialize(await this.serverHttpClient.consistentQuery(funcName, wireArgs));
2202
+ return this.transformer.output.deserialize(await this.serverHttpClient.query(funcName, wireArgs));
2203
+ }
2204
+ return this.transformer.output.deserialize(await this.convexClient.query(funcName, wireArgs));
2205
+ } catch (error) {
2206
+ if (skipUnauth && defaultIsUnauthorized(error)) return null;
2207
+ throw error;
2208
+ }
2209
+ }
2210
+ if (isConvexAction(queryKey)) {
2211
+ const [, funcName, args] = queryKey;
2212
+ const wireArgs = this.transformer.input.serialize(args);
2213
+ const skipUnauth = meta?.skipUnauth ?? false;
2214
+ if (meta?.authType === "required" && !isServer && this.authStore) {
2215
+ const authState = this.getAuthState();
2216
+ if (authState && !authState.isLoading && !authState.isAuthenticated) {
2217
+ if (skipUnauth) return null;
2218
+ authState.onUnauthorized({ queryName: funcName });
2219
+ throw new CRPCClientError({
2220
+ code: "UNAUTHORIZED",
2221
+ functionName: funcName
2222
+ });
2223
+ }
2224
+ }
2225
+ try {
2226
+ if (isServer) return this.transformer.output.deserialize(await this.serverHttpClient.action(funcName, wireArgs));
2227
+ return this.transformer.output.deserialize(await this.convexClient.action(funcName, wireArgs));
2228
+ } catch (error) {
2229
+ if (skipUnauth && defaultIsUnauthorized(error)) return null;
2230
+ throw error;
2231
+ }
2232
+ }
2233
+ return otherFetch(context);
2234
+ };
2235
+ }
2236
+ /**
2237
+ * Create hash function for TanStack QueryClient.
2238
+ *
2239
+ * Uses Convex-specific hashing for Convex queries to ensure
2240
+ * consistent cache keys across serialization.
2241
+ *
2242
+ * @param fallback - Fallback hash function for non-Convex queries
2243
+ * @returns Hash function compatible with TanStack Query
2244
+ */
2245
+ hashFn(fallback) {
2246
+ return createHashFn(fallback);
2247
+ }
2248
+ };
2249
+ /** Default fallback queryFn that throws for non-Convex queries */
2250
+ function throwBecauseNotConvexQuery(context) {
2251
+ throw new Error(`Query key is not for a Convex Query: ${context.queryKey}`);
2252
+ }
2253
+
2254
+ //#endregion
2255
+ //#region src/solid/convex-auth-provider.tsx
2256
+ /** @jsxImportSource solid-js */
2257
+ /**
2258
+ * Unified Convex + Better Auth provider for SolidJS
2259
+ *
2260
+ * Port of the React ConvexAuthProvider to SolidJS.
2261
+ * Handles token sync and auth callbacks.
2262
+ */
2263
+ const defaultMutationHandler = () => {
2264
+ throw new CRPCClientError({
2265
+ code: "UNAUTHORIZED",
2266
+ functionName: "mutation"
2267
+ });
2268
+ };
2269
+ const hasActiveSessionData = (session) => {
2270
+ if (!session || typeof session !== "object") return false;
2271
+ return Boolean(session.session);
2272
+ };
2273
+ /**
2274
+ * Unified auth provider for Convex + Better Auth (SolidJS).
2275
+ * Handles token sync and auth callbacks.
2276
+ *
2277
+ * Structure: AuthProvider wraps ConvexAuthProviderInner so that
2278
+ * useAuthStore() is available when creating fetchAccessToken.
2279
+ */
2280
+ function ConvexAuthProvider(props) {
2281
+ useOTTHandler(props.authClient);
2282
+ const tokenValues = () => ({
2283
+ expiresAt: props.initialToken ? decodeJwtExp(props.initialToken) : null,
2284
+ token: props.initialToken ?? null
2285
+ });
2286
+ return createComponent(AuthProvider, {
2287
+ get initialValues() {
2288
+ return tokenValues();
2289
+ },
2290
+ get isUnauthorized() {
2291
+ return props.isUnauthorized ?? defaultIsUnauthorized;
2292
+ },
2293
+ get onMutationUnauthorized() {
2294
+ return props.onMutationUnauthorized ?? defaultMutationHandler;
2295
+ },
2296
+ get onQueryUnauthorized() {
2297
+ return props.onQueryUnauthorized ?? (() => {});
2298
+ },
2299
+ get children() {
2300
+ return createComponent(ConvexAuthProviderInner, {
2301
+ get authClient() {
2302
+ return props.authClient;
2303
+ },
2304
+ get client() {
2305
+ return props.client;
2306
+ },
2307
+ get children() {
2308
+ return props.children;
2309
+ }
2310
+ });
2311
+ }
2312
+ });
2313
+ }
2314
+ /**
2315
+ * Inner provider that has access to AuthStore via useAuthStore().
2316
+ * Creates fetchAccessToken and passes it through context.
2317
+ */
2318
+ function ConvexAuthProviderInner(props) {
2319
+ const authStore = useAuthStore();
2320
+ const sessionAccessor = props.authClient.useSession();
2321
+ let pendingTokenPromise = null;
2322
+ createEffect(() => {
2323
+ const sessionState = sessionAccessor();
2324
+ const session = sessionState.data;
2325
+ const isPending = sessionState.isPending;
2326
+ if (!hasActiveSessionData(session) && !isPending) {
2327
+ authStore.set("token", null);
2328
+ authStore.set("expiresAt", null);
2329
+ authStore.set("isAuthenticated", false);
2330
+ }
2331
+ });
2332
+ const fetchAccessToken = async ({ forceRefreshToken = false } = {}) => {
2333
+ const sessionState = sessionAccessor();
2334
+ const currentSession = sessionState.data;
2335
+ const currentIsPending = sessionState.isPending;
2336
+ if (!hasActiveSessionData(currentSession)) {
2337
+ if (!currentIsPending) {
2338
+ authStore.set("token", null);
2339
+ authStore.set("expiresAt", null);
2340
+ }
2341
+ return authStore.get("token");
2342
+ }
2343
+ const cachedToken = authStore.get("token");
2344
+ const expiresAt = authStore.get("expiresAt");
2345
+ const timeRemaining = expiresAt ? expiresAt - Date.now() : 0;
2346
+ if (!forceRefreshToken && cachedToken && expiresAt && timeRemaining >= 6e4) return cachedToken;
2347
+ if (!forceRefreshToken && pendingTokenPromise) return pendingTokenPromise;
2348
+ const fetchOptions = { throw: false };
2349
+ if (cachedToken && decodeJwtExp(cachedToken) === null) fetchOptions.headers = { Authorization: `Bearer ${cachedToken}` };
2350
+ pendingTokenPromise = props.authClient.convex.token({ fetchOptions }).then((result) => {
2351
+ const jwt = result.data?.token || null;
2352
+ if (jwt) {
2353
+ const exp = decodeJwtExp(jwt);
2354
+ authStore.set("token", jwt);
2355
+ authStore.set("expiresAt", exp);
2356
+ return jwt;
2357
+ }
2358
+ authStore.set("token", null);
2359
+ authStore.set("expiresAt", null);
2360
+ return null;
2361
+ }).catch((error) => {
2362
+ authStore.set("token", null);
2363
+ authStore.set("expiresAt", null);
2364
+ console.error("[fetchAccessToken] error", error);
2365
+ return null;
2366
+ }).finally(() => {
2367
+ pendingTokenPromise = null;
2368
+ });
2369
+ return pendingTokenPromise;
2370
+ };
2371
+ const useAuth = () => {
2372
+ const sessionState = sessionAccessor();
2373
+ const hasSession = hasActiveSessionData(sessionState.data);
2374
+ const sessionMissing = !hasSession && !sessionState.isPending;
2375
+ const token = authStore.get("token");
2376
+ return {
2377
+ isLoading: sessionState.isPending && !token,
2378
+ isAuthenticated: sessionMissing ? false : hasSession || token !== null,
2379
+ fetchAccessToken
2380
+ };
2381
+ };
2382
+ return createComponent(FetchAccessTokenContext.Provider, {
2383
+ value: fetchAccessToken,
2384
+ get children() {
2385
+ return createComponent(ConvexProviderWithAuth, {
2386
+ get client() {
2387
+ return props.client;
2388
+ },
2389
+ useAuth,
2390
+ get children() {
2391
+ return createComponent(AuthStateSync, { get children() {
2392
+ return props.children;
2393
+ } });
2394
+ }
2395
+ });
2396
+ }
2397
+ });
2398
+ }
2399
+ /**
2400
+ * Syncs auth state from useConvexAuth() to the auth store.
2401
+ * MUST be inside ConvexProviderWithAuth to access useConvexAuth().
2402
+ *
2403
+ * Defensive isLoading computation handles SSR hydration race:
2404
+ * 1. SSR sets token from cookie
2405
+ * 2. Client hydrates
2406
+ * 3. Better Auth's useSession() briefly returns null before loading cookie
2407
+ * 4. Convex sets isConvexAuthenticated = false (no auth to wait for)
2408
+ * 5. Without defensive check, we'd sync { isLoading: false, isAuthenticated: false }
2409
+ * 6. Queries would throw UNAUTHORIZED before token is validated
2410
+ */
2411
+ function AuthStateSync(props) {
2412
+ const convexAuth = useConvexAuth();
2413
+ const authStore = useAuthStore();
2414
+ createEffect(() => {
2415
+ const hasTokenButNotAuth = !!authStore.get("token") && !convexAuth.isAuthenticated;
2416
+ const isLoading = convexAuth.isLoading || hasTokenButNotAuth;
2417
+ authStore.set("isLoading", isLoading);
2418
+ authStore.set("isAuthenticated", convexAuth.isAuthenticated);
2419
+ });
2420
+ return props.children;
2421
+ }
2422
+ /**
2423
+ * Handles cross-domain one-time token (OTT) verification.
2424
+ */
2425
+ function useOTTHandler(authClient) {
2426
+ onMount(async () => {
2427
+ if (typeof window === "undefined" || !window.location?.href) return;
2428
+ const url = new URL(window.location.href);
2429
+ const token = url.searchParams.get("ott");
2430
+ if (token) {
2431
+ const authClientWithCrossDomain = authClient;
2432
+ url.searchParams.delete("ott");
2433
+ window.history.replaceState({}, "", url);
2434
+ const session = (await authClientWithCrossDomain.crossDomain.oneTimeToken.verify({ token })).data?.session;
2435
+ if (session) {
2436
+ await authClient.getSession({ fetchOptions: { headers: { Authorization: `Bearer ${session.token}` } } });
2437
+ authClientWithCrossDomain.updateSession();
2438
+ }
2439
+ }
2440
+ });
2441
+ }
2442
+
2443
+ //#endregion
2444
+ //#region src/solid/singleton.ts
2445
+ const globalStore = globalThis;
2446
+ /** Get/create QueryClient singleton (fresh on SSR, singleton on client) */
2447
+ const getQueryClientSingleton = (factory, symbolKey = "convex.queryClient") => {
2448
+ const key = Symbol.for(symbolKey);
2449
+ if (typeof window === "undefined") return factory();
2450
+ if (!globalStore[key]) globalStore[key] = factory();
2451
+ return globalStore[key];
2452
+ };
2453
+ /** Get/create ConvexQueryClient singleton (fresh on SSR, singleton on client) */
2454
+ const getConvexQueryClientSingleton = ({ authStore, convex, queryClient, symbolKey = "convex.convexQueryClient", unsubscribeDelay, transformer }) => {
2455
+ const key = Symbol.for(symbolKey);
2456
+ const isServer = typeof window === "undefined";
2457
+ let client;
2458
+ if (isServer) client = new ConvexQueryClient(convex, {
2459
+ authStore,
2460
+ unsubscribeDelay,
2461
+ transformer
2462
+ });
2463
+ else {
2464
+ if (globalStore[key]) globalStore[key].updateAuthStore(authStore);
2465
+ else globalStore[key] = new ConvexQueryClient(convex, {
2466
+ authStore,
2467
+ unsubscribeDelay,
2468
+ transformer
2469
+ });
2470
+ client = globalStore[key];
2471
+ client.connect(queryClient);
2472
+ }
2473
+ const currentOpts = queryClient.getDefaultOptions();
2474
+ queryClient.setDefaultOptions({
2475
+ ...currentOpts,
2476
+ queries: {
2477
+ ...currentOpts.queries,
2478
+ queryFn: client.queryFn(),
2479
+ queryKeyHashFn: client.hashFn()
2480
+ }
2481
+ });
2482
+ return client;
2483
+ };
2484
+
2485
+ //#endregion
2486
+ //#region src/solid/use-infinite-query.ts
2487
+ const PAGINATION_KEY_PREFIX = "__pagination__";
2488
+ const paginationIdStore = /* @__PURE__ */ new Map();
2489
+ let paginationIdCounter = 0;
2490
+ const getOrCreatePaginationId = (storeKey) => {
2491
+ const existing = paginationIdStore.get(storeKey);
2492
+ if (existing !== void 0) return existing;
2493
+ const newId = ++paginationIdCounter;
2494
+ paginationIdStore.set(storeKey, newId);
2495
+ return newId;
2496
+ };
2497
+ /** Build a unique key for recovery attempt detection */
2498
+ const buildRecoveryKey = (pageKeys, page0Cursor, page0UpdatedAt) => JSON.stringify({
2499
+ pageKeys,
2500
+ page0Cursor,
2501
+ page0UpdatedAt
2502
+ });
2503
+ /**
2504
+ * Hook for auto-recovering from stale cursors after WebSocket reconnection.
2505
+ *
2506
+ * When Convex WebSocket reconnects, page 0 (cursor: null) resubscribes and
2507
+ * gets fresh data. However, pages 1+ may have stale cursors that fail.
2508
+ *
2509
+ * This hook detects this pattern and creates a recovery page that fetches
2510
+ * enough items to cover the lost pages, preserving the user's scroll position.
2511
+ */
2512
+ const useStaleCursorRecovery = ({ argsObject, combined, limit, setState, state }) => {
2513
+ createEffect(on([
2514
+ () => combined.isFetchNextPageError,
2515
+ () => combined._rawResults,
2516
+ () => state().pageKeys,
2517
+ () => state().queries,
2518
+ () => state().autoRecoveryAttempted,
2519
+ argsObject
2520
+ ], () => {
2521
+ if (!combined.isFetchNextPageError) return;
2522
+ const page0Result = combined._rawResults[0];
2523
+ const page0Data = page0Result?.data;
2524
+ const page0UpdatedAt = page0Result?.dataUpdatedAt ?? 0;
2525
+ const hasPage0Data = page0Data !== void 0 && !page0Result?.isError;
2526
+ const hasSubsequentErrors = combined._rawResults.slice(1).some((q) => q?.isError && !q?.isFetching);
2527
+ if (!hasPage0Data || !hasSubsequentErrors || !page0Data?.continueCursor) return;
2528
+ const currentState = state();
2529
+ const recoveryKey = buildRecoveryKey(currentState.pageKeys, page0Data.continueCursor, page0UpdatedAt);
2530
+ if (currentState.autoRecoveryAttempted === recoveryKey) return;
2531
+ const erroredPageKeys = currentState.pageKeys.filter((_, i) => i > 0 && combined._rawResults[i]?.isError);
2532
+ const itemsToRecover = erroredPageKeys.reduce((sum, key) => {
2533
+ return sum + (currentState.queries[key]?.args?.limit ?? limit ?? 20);
2534
+ }, 0);
2535
+ console.warn("[Pagination] Auto-recovering from stale cursors", {
2536
+ erroredPages: erroredPageKeys.length,
2537
+ itemsToRecover
2538
+ });
2539
+ setState((prev) => ({
2540
+ ...prev,
2541
+ id: prev.id,
2542
+ nextPageKey: 2,
2543
+ pageKeys: [prev.pageKeys[0], 1],
2544
+ queries: {
2545
+ [prev.pageKeys[0]]: prev.queries[prev.pageKeys[0]],
2546
+ 1: { args: {
2547
+ ...argsObject(),
2548
+ cursor: page0Data.continueCursor,
2549
+ limit: Math.min(itemsToRecover + (limit ?? 20), 500),
2550
+ __paginationId: prev.id
2551
+ } }
2552
+ },
2553
+ version: prev.version + 1,
2554
+ autoRecoveryAttempted: recoveryKey
2555
+ }));
2556
+ }));
2557
+ createEffect(on([() => combined.status, () => state().autoRecoveryAttempted], () => {
2558
+ if ((combined.status === "CanLoadMore" || combined.status === "Exhausted") && state().autoRecoveryAttempted) setState((prev) => ({
2559
+ ...prev,
2560
+ autoRecoveryAttempted: void 0
2561
+ }));
2562
+ }));
2563
+ };
2564
+ /**
2565
+ * Internal infinite query hook using TanStack Query + convexQuery.
2566
+ * Each page gets:
2567
+ * - Convex WebSocket subscription (real-time reactivity)
2568
+ * - TanStack Query retry on timeout errors
2569
+ *
2570
+ * Use `useInfiniteQuery` for the public API with auth handling.
2571
+ */
2572
+ const useInfiniteQueryInternal = (query, args, options) => {
2573
+ const { limit, enabled, placeholderData, ...queryOptions } = options;
2574
+ const safeAuth = useSafeConvexAuth();
2575
+ const meta = useMeta();
2576
+ const queryClient = useQueryClient();
2577
+ const prefetchedFirstPage = createMemo(() => {
2578
+ const serverQueryKey = [
2579
+ "convexQuery",
2580
+ getFunctionName(query),
2581
+ {
2582
+ ...args,
2583
+ cursor: null,
2584
+ limit
2585
+ }
2586
+ ];
2587
+ return queryClient.getQueryData(serverQueryKey) ?? null;
2588
+ });
2589
+ const skip = createMemo(() => !prefetchedFirstPage() && (safeAuth.isLoading || enabled === false));
2590
+ const getPaginationState = (key) => {
2591
+ const queryKey = [PAGINATION_KEY_PREFIX, key];
2592
+ return queryClient.getQueryData(queryKey);
2593
+ };
2594
+ const setPaginationState = (key, paginationState) => {
2595
+ const queryKey = [PAGINATION_KEY_PREFIX, key];
2596
+ queryClient.setQueryData(queryKey, paginationState);
2597
+ };
2598
+ const argsObject = createMemo(() => skip() ? {} : args);
2599
+ const storeKey = createMemo(() => JSON.stringify({
2600
+ query: getFunctionName(query),
2601
+ args: argsObject()
2602
+ }));
2603
+ const createInitialState = () => {
2604
+ const id = getOrCreatePaginationId(storeKey());
2605
+ return {
2606
+ id,
2607
+ nextPageKey: 1,
2608
+ pageKeys: skip() ? [] : [0],
2609
+ queries: skip() ? {} : { 0: { args: {
2610
+ ...argsObject(),
2611
+ cursor: null,
2612
+ limit,
2613
+ __paginationId: id
2614
+ } } },
2615
+ version: 0
2616
+ };
2617
+ };
2618
+ let prevArgs = null;
2619
+ const computeInitialState = () => {
2620
+ if (skip()) return {
2621
+ id: 0,
2622
+ nextPageKey: 1,
2623
+ pageKeys: [],
2624
+ queries: {},
2625
+ version: 0
2626
+ };
2627
+ const existingState = getPaginationState(storeKey());
2628
+ if (existingState) return existingState;
2629
+ return createInitialState();
2630
+ };
2631
+ const [state, setLocalState] = createSignal(computeInitialState());
2632
+ const setState = (updater) => {
2633
+ setLocalState((prev) => {
2634
+ const newState = typeof updater === "function" ? updater(prev) : updater;
2635
+ setPaginationState(storeKey(), newState);
2636
+ return newState;
2637
+ });
2638
+ };
2639
+ createEffect(on([skip, storeKey], () => {
2640
+ const prev = prevArgs;
2641
+ const isFirstRun = prev === null;
2642
+ const currentStoreKey = storeKey();
2643
+ const currentSkip = skip();
2644
+ const argsChanged = prev !== null && (prev.storeKey !== currentStoreKey || prev.skip !== currentSkip);
2645
+ const skipBecameFalse = prev?.skip && !currentSkip;
2646
+ prevArgs = {
2647
+ storeKey: currentStoreKey,
2648
+ skip: currentSkip
2649
+ };
2650
+ if (currentSkip) return;
2651
+ if (isFirstRun) {
2652
+ setPaginationState(currentStoreKey, state());
2653
+ return;
2654
+ }
2655
+ if (skipBecameFalse) {
2656
+ const existingState = getPaginationState(currentStoreKey);
2657
+ if (existingState) {
2658
+ setLocalState(existingState);
2659
+ return;
2660
+ }
2661
+ const newState = createInitialState();
2662
+ setLocalState(newState);
2663
+ setPaginationState(currentStoreKey, newState);
2664
+ return;
2665
+ }
2666
+ if (argsChanged) {
2667
+ const existingState = getPaginationState(currentStoreKey);
2668
+ if (existingState) {
2669
+ setLocalState(existingState);
2670
+ return;
2671
+ }
2672
+ const newState = createInitialState();
2673
+ setLocalState(newState);
2674
+ setPaginationState(currentStoreKey, newState);
2675
+ }
2676
+ }));
2677
+ const tanstackQueries = createMemo(() => state().pageKeys.map((key, index) => {
2678
+ const pageArgs = state().queries[key]?.args;
2679
+ return {
2680
+ ...convexQuery(query, pageArgs ? (({ __paginationId, ...rest }) => rest)(pageArgs) : "skip", meta),
2681
+ enabled: !skip() && !!state().queries[key],
2682
+ structuralSharing: false,
2683
+ ...queryOptions ?? {},
2684
+ ...index === 0 && prefetchedFirstPage() ? { initialData: prefetchedFirstPage() } : {},
2685
+ ...index === 0 && placeholderData ? { placeholderData: {
2686
+ page: placeholderData,
2687
+ isDone: false,
2688
+ continueCursor: null
2689
+ } } : {}
2690
+ };
2691
+ }));
2692
+ const combined = useQueries(() => ({
2693
+ queries: tanstackQueries(),
2694
+ combine: (results) => {
2695
+ const allItems = [];
2696
+ const pages = [];
2697
+ const seenIds = /* @__PURE__ */ new Set();
2698
+ let lastPage;
2699
+ let paginationStatus = "LoadingFirstPage";
2700
+ for (let i = 0; i < results.length; i++) {
2701
+ const pageQuery = results[i];
2702
+ if (pageQuery.isLoading || pageQuery.data === void 0) {
2703
+ paginationStatus = i === 0 ? "LoadingFirstPage" : "LoadingMore";
2704
+ break;
2705
+ }
2706
+ const page = pageQuery.data;
2707
+ lastPage = page;
2708
+ pages.push(page.page);
2709
+ for (const item of page.page) {
2710
+ const id = item._id || item.id;
2711
+ if (id && seenIds.has(id)) continue;
2712
+ if (id) seenIds.add(id);
2713
+ allItems.push(item);
2714
+ }
2715
+ paginationStatus = page.isDone ? "Exhausted" : "CanLoadMore";
2716
+ }
2717
+ const isPlaceholderData = results[0]?.isPlaceholderData ?? !!placeholderData;
2718
+ const isFetching = results.some((r) => r.isFetching);
2719
+ return {
2720
+ data: allItems,
2721
+ dataUpdatedAt: Math.max(...results.map((r) => r.dataUpdatedAt ?? 0)),
2722
+ lastPage,
2723
+ pages,
2724
+ status: paginationStatus,
2725
+ error: results.find((r) => r.isError)?.error ?? null,
2726
+ isError: results.some((r) => r.isError),
2727
+ isFetching,
2728
+ isFetchNextPageError: results.length > 1 && (results.at(-1)?.isError ?? false),
2729
+ isPlaceholderData,
2730
+ isRefetching: isFetching && allItems.length > 0 && !isPlaceholderData,
2731
+ isLoading: paginationStatus === "LoadingFirstPage",
2732
+ failureReason: results.find((r) => r.isError)?.error ?? null,
2733
+ _rawResults: results
2734
+ };
2735
+ }
2736
+ }));
2737
+ useStaleCursorRecovery({
2738
+ argsObject,
2739
+ combined,
2740
+ limit,
2741
+ setState,
2742
+ state
2743
+ });
2744
+ createEffect(on([
2745
+ () => combined._rawResults,
2746
+ () => state().pageKeys,
2747
+ () => state().queries,
2748
+ argsObject
2749
+ ], () => {
2750
+ for (let i = 0; i < combined._rawResults.length; i++) {
2751
+ const pageQuery = combined._rawResults[i];
2752
+ if (pageQuery.data) {
2753
+ const page = pageQuery.data;
2754
+ const pageKey = state().pageKeys[i];
2755
+ const pageState = state().queries[pageKey];
2756
+ if (page.splitCursor && pageState && !pageState.endCursor) {
2757
+ setState((prev) => {
2758
+ const currentPageState = prev.queries[pageKey];
2759
+ if (!currentPageState || currentPageState.endCursor) return prev;
2760
+ const newKey = prev.nextPageKey;
2761
+ const splitCursor = page.splitCursor;
2762
+ const splitPageArgs = {
2763
+ ...argsObject(),
2764
+ cursor: splitCursor,
2765
+ limit: currentPageState.args.limit,
2766
+ __paginationId: prev.id
2767
+ };
2768
+ const pageKeyIndex = prev.pageKeys.indexOf(pageKey);
2769
+ const newPageKeys = [...prev.pageKeys];
2770
+ newPageKeys.splice(pageKeyIndex + 1, 0, newKey);
2771
+ return {
2772
+ ...prev,
2773
+ nextPageKey: newKey + 1,
2774
+ pageKeys: newPageKeys,
2775
+ queries: {
2776
+ ...prev.queries,
2777
+ [pageKey]: {
2778
+ ...currentPageState,
2779
+ endCursor: splitCursor
2780
+ },
2781
+ [newKey]: { args: splitPageArgs }
2782
+ }
2783
+ };
2784
+ });
2785
+ return;
2786
+ }
2787
+ }
2788
+ }
2789
+ }));
2790
+ const loadMore = (pageLimit) => {
2791
+ if (combined.status !== "CanLoadMore" || !combined.lastPage?.continueCursor) return;
2792
+ setState((prev) => {
2793
+ const newKey = prev.nextPageKey;
2794
+ return {
2795
+ ...prev,
2796
+ nextPageKey: newKey + 1,
2797
+ pageKeys: [...prev.pageKeys, newKey],
2798
+ queries: {
2799
+ ...prev.queries,
2800
+ [newKey]: { args: {
2801
+ ...argsObject(),
2802
+ cursor: combined.lastPage.continueCursor,
2803
+ limit: pageLimit,
2804
+ __paginationId: prev.id
2805
+ } }
2806
+ }
2807
+ };
2808
+ });
2809
+ };
2810
+ const hasNextPage = createMemo(() => combined.status === "CanLoadMore");
2811
+ const isFetchingNextPage = createMemo(() => combined.status === "LoadingMore");
2812
+ return {
2813
+ get data() {
2814
+ return combined.data;
2815
+ },
2816
+ get error() {
2817
+ return combined.error instanceof Error ? combined.error : null;
2818
+ },
2819
+ get failureReason() {
2820
+ return combined.failureReason ?? null;
2821
+ },
2822
+ get isFetchNextPageError() {
2823
+ return combined.isFetchNextPageError;
2824
+ },
2825
+ get isError() {
2826
+ return combined.isError;
2827
+ },
2828
+ get isFetching() {
2829
+ return combined.isFetching;
2830
+ },
2831
+ get isFetchingNextPage() {
2832
+ return isFetchingNextPage();
2833
+ },
2834
+ get isLoading() {
2835
+ return combined.isLoading;
2836
+ },
2837
+ get isPlaceholderData() {
2838
+ return combined.isPlaceholderData;
2839
+ },
2840
+ get isRefetching() {
2841
+ return combined.isRefetching;
2842
+ },
2843
+ fetchNextPage: (n) => loadMore(n ?? limit),
2844
+ get hasNextPage() {
2845
+ return hasNextPage();
2846
+ },
2847
+ get pages() {
2848
+ return combined.pages;
2849
+ },
2850
+ get status() {
2851
+ return combined.status;
2852
+ }
2853
+ };
2854
+ };
2855
+ /**
2856
+ * Infinite query hook using cRPC-style options.
2857
+ * Accepts options from `crpc.posts.list.infiniteQueryOptions()`.
2858
+ *
2859
+ * @example
2860
+ * ```tsx
2861
+ * const crpc = useCRPC();
2862
+ * const { data, fetchNextPage } = useInfiniteQuery(
2863
+ * crpc.posts.list.infiniteQueryOptions({ userId }, { limit: 20 })
2864
+ * );
2865
+ * ```
2866
+ */
2867
+ function useInfiniteQuery(infiniteOptions) {
2868
+ const query = infiniteOptions[FUNC_REF_SYMBOL];
2869
+ const onQueryUnauthorized = useAuthValue("onQueryUnauthorized");
2870
+ const safeAuth = useSafeConvexAuth();
2871
+ const { queryKey: _queryKey, staleTime: _staleTime, refetchInterval: _refetchInterval, refetchOnMount: _refetchOnMount, refetchOnReconnect: _refetchOnReconnect, refetchOnWindowFocus: _refetchOnWindowFocus, enabled: factoryEnabled, meta, ...queryOptions } = infiniteOptions;
2872
+ const { queryName, args, limit, authType, skipUnauth } = meta;
2873
+ const skipUnauthFinal = skipUnauth ?? false;
2874
+ const isUnauthorized = authType === "required" && !safeAuth.isLoading && !safeAuth.isAuthenticated;
2875
+ const shouldSkip = factoryEnabled === false || authType === "required" && safeAuth.isLoading || authType === "required" && !safeAuth.isAuthenticated;
2876
+ const authError = createMemo(() => {
2877
+ if (isUnauthorized && !skipUnauthFinal) return new CRPCClientError({
2878
+ code: "UNAUTHORIZED",
2879
+ functionName: queryName
2880
+ });
2881
+ return null;
2882
+ });
2883
+ createEffect(() => {
2884
+ if (isUnauthorized && !skipUnauthFinal) onQueryUnauthorized({ queryName });
2885
+ });
2886
+ const result = useInfiniteQueryInternal(query, args, {
2887
+ limit,
2888
+ ...queryOptions,
2889
+ enabled: !shouldSkip
2890
+ });
2891
+ const authLoadingApplies = authType === "optional" || authType === "required";
2892
+ const isSkippedUnauth = isUnauthorized && skipUnauthFinal;
2893
+ return {
2894
+ get data() {
2895
+ return isSkippedUnauth ? [] : result.data;
2896
+ },
2897
+ get pages() {
2898
+ return isSkippedUnauth ? [] : result.pages;
2899
+ },
2900
+ get error() {
2901
+ return authError() ?? result.error;
2902
+ },
2903
+ get isError() {
2904
+ return authError() ? true : result.isError;
2905
+ },
2906
+ get isPlaceholderData() {
2907
+ return isSkippedUnauth ? false : result.isPlaceholderData;
2908
+ },
2909
+ get isLoading() {
2910
+ const ae = authError();
2911
+ const isClientError = isCRPCClientError(result.error);
2912
+ return authLoadingApplies && safeAuth.isLoading || !isClientError && !ae && !isSkippedUnauth && result.isLoading;
2913
+ },
2914
+ get isFetching() {
2915
+ return result.isFetching;
2916
+ },
2917
+ get isFetchingNextPage() {
2918
+ return result.isFetchingNextPage;
2919
+ },
2920
+ get isFetchNextPageError() {
2921
+ return result.isFetchNextPageError;
2922
+ },
2923
+ get isRefetching() {
2924
+ return result.isRefetching;
2925
+ },
2926
+ get failureReason() {
2927
+ return result.failureReason;
2928
+ },
2929
+ fetchNextPage: result.fetchNextPage,
2930
+ get hasNextPage() {
2931
+ return result.hasNextPage;
2932
+ },
2933
+ get status() {
2934
+ return result.status;
2935
+ }
2936
+ };
2937
+ }
2938
+
2939
+ //#endregion
2940
+ export { AuthMutationError, AuthProvider, Authenticated, ConvexAuthBridge, ConvexAuthProvider, ConvexProvider, ConvexProviderWithAuth, ConvexQueryClient, FetchAccessTokenContext, MaybeAuthenticated, MaybeUnauthenticated, MetaContext, Unauthenticated, createAuthMutations, createCRPCContext, createCRPCOptionsProxy, createHttpProxy, createVanillaCRPCProxy, decodeJwtExp, getAuthType, getConvexQueryClientSingleton, getQueryClientSingleton, isAuthMutationError, useAuth, useAuthGuard, useAuthSkip, useAuthStore, useAuthValue, useConvex, useConvexActionOptions, useConvexActionQueryOptions, useConvexAuth, useConvexAuthBridge, useConvexInfiniteQueryOptions, useConvexMutationOptions, useConvexQueryClient, useConvexQueryOptions, useFetchAccessToken, useFnMeta, useInfiniteQuery, useIsAuth, useMaybeAuth, useMeta, useSafeConvexAuth, useUploadMutationOptions };