use-convex 0.0.2

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 (87) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +321 -0
  3. package/dist/module.d.mts +97 -0
  4. package/dist/module.json +12 -0
  5. package/dist/module.mjs +232 -0
  6. package/dist/runtime/components/AuthLoading.d.vue.ts +13 -0
  7. package/dist/runtime/components/AuthLoading.vue +8 -0
  8. package/dist/runtime/components/AuthLoading.vue.d.ts +13 -0
  9. package/dist/runtime/components/AuthRefreshing.d.vue.ts +13 -0
  10. package/dist/runtime/components/AuthRefreshing.vue +8 -0
  11. package/dist/runtime/components/AuthRefreshing.vue.d.ts +13 -0
  12. package/dist/runtime/components/Authenticated.d.vue.ts +13 -0
  13. package/dist/runtime/components/Authenticated.vue +8 -0
  14. package/dist/runtime/components/Authenticated.vue.d.ts +13 -0
  15. package/dist/runtime/components/Unauthenticated.d.vue.ts +13 -0
  16. package/dist/runtime/components/Unauthenticated.vue +8 -0
  17. package/dist/runtime/components/Unauthenticated.vue.d.ts +13 -0
  18. package/dist/runtime/composables/prewarmQuery.d.ts +9 -0
  19. package/dist/runtime/composables/prewarmQuery.js +14 -0
  20. package/dist/runtime/composables/useAuth.d.ts +60 -0
  21. package/dist/runtime/composables/useAuth.js +426 -0
  22. package/dist/runtime/composables/useAuthToken.d.ts +24 -0
  23. package/dist/runtime/composables/useAuthToken.js +71 -0
  24. package/dist/runtime/composables/useConvex.d.ts +5 -0
  25. package/dist/runtime/composables/useConvex.js +10 -0
  26. package/dist/runtime/composables/useConvexAction.d.ts +13 -0
  27. package/dist/runtime/composables/useConvexAction.js +30 -0
  28. package/dist/runtime/composables/useConvexAuth.d.ts +51 -0
  29. package/dist/runtime/composables/useConvexAuth.js +96 -0
  30. package/dist/runtime/composables/useConvexConnectionState.d.ts +8 -0
  31. package/dist/runtime/composables/useConvexConnectionState.js +21 -0
  32. package/dist/runtime/composables/useConvexGate.d.ts +20 -0
  33. package/dist/runtime/composables/useConvexGate.js +21 -0
  34. package/dist/runtime/composables/useConvexMutation.d.ts +25 -0
  35. package/dist/runtime/composables/useConvexMutation.js +35 -0
  36. package/dist/runtime/composables/useConvexPaginatedQuery.d.ts +43 -0
  37. package/dist/runtime/composables/useConvexPaginatedQuery.js +188 -0
  38. package/dist/runtime/composables/useConvexQueries.d.ts +27 -0
  39. package/dist/runtime/composables/useConvexQueries.js +84 -0
  40. package/dist/runtime/composables/useConvexQuery.d.ts +56 -0
  41. package/dist/runtime/composables/useConvexQuery.js +147 -0
  42. package/dist/runtime/plugin.auth.client.d.ts +8 -0
  43. package/dist/runtime/plugin.auth.client.js +41 -0
  44. package/dist/runtime/plugin.client.d.ts +7 -0
  45. package/dist/runtime/plugin.client.js +40 -0
  46. package/dist/runtime/plugin.devtools.client.d.ts +6 -0
  47. package/dist/runtime/plugin.devtools.client.js +16 -0
  48. package/dist/runtime/plugin.server.d.ts +7 -0
  49. package/dist/runtime/plugin.server.js +44 -0
  50. package/dist/runtime/server/api/convex/auth/session.d.ts +36 -0
  51. package/dist/runtime/server/api/convex/auth/session.js +95 -0
  52. package/dist/runtime/server/auth.d.ts +19 -0
  53. package/dist/runtime/server/auth.js +32 -0
  54. package/dist/runtime/server/authCookies.d.ts +9 -0
  55. package/dist/runtime/server/authCookies.js +86 -0
  56. package/dist/runtime/server/fetch.d.ts +43 -0
  57. package/dist/runtime/server/fetch.js +63 -0
  58. package/dist/runtime/server/routes/__convex_devtools.get.d.ts +6 -0
  59. package/dist/runtime/server/routes/__convex_devtools.get.js +32 -0
  60. package/dist/runtime/server/sameOrigin.d.ts +14 -0
  61. package/dist/runtime/server/sameOrigin.js +33 -0
  62. package/dist/runtime/utils/authCookie.d.ts +21 -0
  63. package/dist/runtime/utils/authCookie.js +47 -0
  64. package/dist/runtime/utils/authGate.d.ts +11 -0
  65. package/dist/runtime/utils/authGate.js +9 -0
  66. package/dist/runtime/utils/authMutex.d.ts +15 -0
  67. package/dist/runtime/utils/authMutex.js +47 -0
  68. package/dist/runtime/utils/authState.d.ts +31 -0
  69. package/dist/runtime/utils/authState.js +35 -0
  70. package/dist/runtime/utils/authStorage.d.ts +55 -0
  71. package/dist/runtime/utils/authStorage.js +85 -0
  72. package/dist/runtime/utils/context.d.ts +53 -0
  73. package/dist/runtime/utils/context.js +41 -0
  74. package/dist/runtime/utils/fetchToken.d.ts +9 -0
  75. package/dist/runtime/utils/fetchToken.js +6 -0
  76. package/dist/runtime/utils/http.d.ts +10 -0
  77. package/dist/runtime/utils/http.js +13 -0
  78. package/dist/runtime/utils/oauthRedirect.d.ts +5 -0
  79. package/dist/runtime/utils/oauthRedirect.js +12 -0
  80. package/dist/runtime/utils/overlay.d.ts +18 -0
  81. package/dist/runtime/utils/overlay.js +48 -0
  82. package/dist/runtime/utils/paginatedOptimistic.d.ts +37 -0
  83. package/dist/runtime/utils/paginatedOptimistic.js +178 -0
  84. package/dist/runtime/utils/queryKey.d.ts +8 -0
  85. package/dist/runtime/utils/queryKey.js +11 -0
  86. package/dist/types.d.mts +29 -0
  87. package/package.json +66 -0
@@ -0,0 +1,84 @@
1
+ import { getFunctionName } from "convex/server";
2
+ import { convexToJson } from "convex/values";
3
+ import {
4
+ computed,
5
+ onScopeDispose,
6
+ shallowRef,
7
+ toValue,
8
+ watch
9
+ } from "vue";
10
+ import { useConvexContext } from "../utils/context.js";
11
+ function subscriptionSignature(query, args) {
12
+ return `${getFunctionName(query)}:${JSON.stringify(convexToJson(args))}`;
13
+ }
14
+ export function useConvexQueries(queries) {
15
+ const ctx = useConvexContext();
16
+ const results = shallowRef({});
17
+ if (import.meta.server || !ctx.client) {
18
+ return computed(
19
+ () => results.value
20
+ );
21
+ }
22
+ const client = ctx.client;
23
+ const unsubscribers = /* @__PURE__ */ new Map();
24
+ const signatures = /* @__PURE__ */ new Map();
25
+ const dropKey = (key) => {
26
+ unsubscribers.get(key)?.();
27
+ unsubscribers.delete(key);
28
+ signatures.delete(key);
29
+ if (key in results.value) {
30
+ const { [key]: _, ...rest } = results.value;
31
+ results.value = rest;
32
+ }
33
+ };
34
+ const sync = (request) => {
35
+ const nextKeys = new Set(Object.keys(request));
36
+ for (const key of unsubscribers.keys()) {
37
+ if (!nextKeys.has(key)) {
38
+ dropKey(key);
39
+ }
40
+ }
41
+ for (const [key, entry] of Object.entries(request)) {
42
+ if (entry === "skip") {
43
+ dropKey(key);
44
+ continue;
45
+ }
46
+ const { query, args } = entry;
47
+ const signature = subscriptionSignature(query, args);
48
+ if (signatures.get(key) === signature && unsubscribers.has(key)) {
49
+ continue;
50
+ }
51
+ unsubscribers.get(key)?.();
52
+ const unsubscribe = client.onUpdate(
53
+ query,
54
+ args,
55
+ (value) => {
56
+ results.value = { ...results.value, [key]: value };
57
+ },
58
+ (err) => {
59
+ results.value = { ...results.value, [key]: err };
60
+ }
61
+ );
62
+ unsubscribers.set(key, unsubscribe);
63
+ signatures.set(key, signature);
64
+ if (!(key in results.value)) {
65
+ results.value = { ...results.value, [key]: void 0 };
66
+ }
67
+ }
68
+ };
69
+ watch(
70
+ () => toValue(queries),
71
+ (request) => {
72
+ sync(request);
73
+ },
74
+ { immediate: true, deep: true }
75
+ );
76
+ onScopeDispose(() => {
77
+ for (const unsubscribe of unsubscribers.values()) {
78
+ unsubscribe();
79
+ }
80
+ unsubscribers.clear();
81
+ signatures.clear();
82
+ });
83
+ return computed(() => results.value);
84
+ }
@@ -0,0 +1,56 @@
1
+ import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
2
+ import { type ComputedRef, type MaybeRefOrGetter, type Ref } from 'vue';
3
+ export type ConvexQueryArgs<Query extends FunctionReference<'query'>> = FunctionArgs<Query> | 'skip';
4
+ export interface UseConvexQueryOptions {
5
+ /**
6
+ * Explicit Nuxt payload/cache key.
7
+ * Defaults to `convex:<functionName>:<argsJSON>`.
8
+ * `'skip'` does not change the key (same slot as empty args).
9
+ */
10
+ key?: string;
11
+ /**
12
+ * Run the HttpClient snapshot during SSR and transfer it through the Nuxt payload.
13
+ * Defaults to `convex.server` from module config (`true`).
14
+ */
15
+ server?: boolean;
16
+ /**
17
+ * Make the initial Nuxt request non-blocking during client navigation.
18
+ * @default false
19
+ */
20
+ lazy?: boolean;
21
+ /**
22
+ * Subscribe with ConvexClient on the browser after hydration.
23
+ * @default true
24
+ */
25
+ live?: boolean;
26
+ /**
27
+ * JWT for this request's HttpClient (SSR / one-shot refresh).
28
+ * Never put this in public runtime config.
29
+ */
30
+ token?: string;
31
+ /**
32
+ * Skip HttpClient fetch and live subscribe until Convex confirms auth
33
+ * (`isAuthenticated`). SSR still runs when the server plugin stamps auth
34
+ * from the JWT cookie; the client keeps the SSR payload via the overlay
35
+ * until `setAuth` confirms. Prefer this over manually wrapping args in
36
+ * `computed(() => isAuthenticated.value ? args : 'skip')`.
37
+ *
38
+ * @default false
39
+ */
40
+ authenticated?: boolean;
41
+ }
42
+ export interface UseConvexQueryReturn<T> {
43
+ data: Ref<T | null | undefined> | ComputedRef<T | null | undefined>;
44
+ error: Ref<Error | null | undefined> | ComputedRef<Error | null | undefined>;
45
+ pending: Ref<boolean> | ComputedRef<boolean>;
46
+ status: Ref<'pending' | 'success' | 'error' | string> | ComputedRef<'pending' | 'success' | 'error' | string>;
47
+ refresh: () => Promise<void>;
48
+ }
49
+ /**
50
+ * Nuxt-native Convex query.
51
+ *
52
+ * Server + client both call `useAsyncData` so the Nuxt payload hydrates without
53
+ * a duplicate HTTP request. On the browser, a live `ConvexClient` subscription
54
+ * overlays the payload (`live ?? payload`), matching Next.js `usePreloadedQuery`.
55
+ */
56
+ export declare function useConvexQuery<Query extends FunctionReference<'query'>>(query: Query, args?: MaybeRefOrGetter<ConvexQueryArgs<Query>>, options?: UseConvexQueryOptions): Promise<UseConvexQueryReturn<FunctionReturnType<Query>>>;
@@ -0,0 +1,147 @@
1
+ import { convexToJson, jsonToConvex } from "convex/values";
2
+ import { useAsyncData, useNuxtApp, useRuntimeConfig } from "nuxt/app";
3
+ import {
4
+ computed,
5
+ onScopeDispose,
6
+ ref,
7
+ toValue,
8
+ watch
9
+ } from "vue";
10
+ import { resolveAuthGatedArgs } from "../utils/authGate.js";
11
+ import { useConvexContext } from "../utils/context.js";
12
+ import { resolveQueryOverlay } from "../utils/overlay.js";
13
+ import { convexQueryKey } from "../utils/queryKey.js";
14
+ export async function useConvexQuery(query, args = {}, options = {}) {
15
+ const runtimeConfig = useRuntimeConfig();
16
+ const defaultServer = runtimeConfig.public.convex?.server;
17
+ const server = options.server ?? defaultServer ?? true;
18
+ const live = options.live ?? true;
19
+ const requireAuth = options.authenticated ?? false;
20
+ const ctx = useConvexContext();
21
+ const resolveRawArgs = () => toValue(args);
22
+ const resolveEffectiveArgs = () => resolveAuthGatedArgs(resolveRawArgs(), {
23
+ authenticated: requireAuth,
24
+ isAuthenticated: ctx.auth.isAuthenticated.value
25
+ });
26
+ const key = computed(
27
+ () => convexQueryKey(query, resolveRawArgs(), options.key)
28
+ );
29
+ const asyncData = await useAsyncData(
30
+ key,
31
+ async () => {
32
+ const argsValue = resolveEffectiveArgs();
33
+ if (argsValue === "skip") {
34
+ return null;
35
+ }
36
+ const token = options.token ?? ctx.ssrToken.value;
37
+ if (requireAuth && import.meta.client && !token) {
38
+ const nuxtApp = useNuxtApp();
39
+ const cached = nuxtApp.payload.data[toValue(key)];
40
+ return cached ?? null;
41
+ }
42
+ const http = ctx.createHttpClient({ token });
43
+ const result = await http.query(
44
+ query,
45
+ argsValue ?? {}
46
+ );
47
+ return jsonToConvex(convexToJson(result));
48
+ },
49
+ {
50
+ server,
51
+ lazy: options.lazy ?? false,
52
+ watch: [
53
+ () => toValue(args),
54
+ // Re-run HttpClient snapshot when the SSR JWT cookie / token changes
55
+ // (e.g. after sign-in with live:false, or Refresh).
56
+ () => options.token ?? ctx.ssrToken.value,
57
+ // Re-run when auth confirms *and* a JWT is available for HttpClient
58
+ // (readable cookie / SSR). Avoids anonymous client refetches with HttpOnly.
59
+ () => requireAuth && ctx.auth.isAuthenticated.value && !!(options.token ?? ctx.ssrToken.value)
60
+ ]
61
+ }
62
+ );
63
+ const refresh = async () => {
64
+ if (requireAuth && import.meta.client && !(options.token ?? ctx.ssrToken.value)) {
65
+ return;
66
+ }
67
+ await asyncData.refresh();
68
+ };
69
+ if (import.meta.server || !live) {
70
+ return {
71
+ data: asyncData.data,
72
+ error: asyncData.error,
73
+ pending: computed(() => {
74
+ if (resolveEffectiveArgs() === "skip") {
75
+ return false;
76
+ }
77
+ return asyncData.pending.value;
78
+ }),
79
+ status: computed(() => {
80
+ if (resolveEffectiveArgs() === "skip") {
81
+ return "success";
82
+ }
83
+ return asyncData.status.value;
84
+ }),
85
+ refresh
86
+ };
87
+ }
88
+ const client = ctx.client;
89
+ if (!client) {
90
+ throw new Error("[convex-nuxt] ConvexClient is not available on the client.");
91
+ }
92
+ const liveReady = ref(false);
93
+ const liveData = ref();
94
+ const liveError = ref(null);
95
+ let cancel;
96
+ const subscribe = (argsValue) => {
97
+ cancel?.();
98
+ cancel = void 0;
99
+ liveReady.value = false;
100
+ liveError.value = null;
101
+ if (argsValue === "skip") {
102
+ liveData.value = void 0;
103
+ return;
104
+ }
105
+ cancel = client.onUpdate(
106
+ query,
107
+ argsValue ?? {},
108
+ (result) => {
109
+ liveData.value = result;
110
+ liveError.value = null;
111
+ liveReady.value = true;
112
+ },
113
+ (err) => {
114
+ liveError.value = err;
115
+ liveReady.value = true;
116
+ }
117
+ );
118
+ };
119
+ watch(
120
+ () => [resolveEffectiveArgs(), ctx.auth.isAuthenticated.value],
121
+ ([argsValue]) => {
122
+ subscribe(argsValue);
123
+ },
124
+ { immediate: true }
125
+ );
126
+ onScopeDispose(() => {
127
+ cancel?.();
128
+ });
129
+ const overlay = computed(
130
+ () => resolveQueryOverlay({
131
+ skipped: resolveEffectiveArgs() === "skip",
132
+ liveReady: liveReady.value,
133
+ liveData: liveData.value,
134
+ liveError: liveError.value,
135
+ payload: asyncData.data.value,
136
+ payloadError: asyncData.error.value ?? null,
137
+ payloadPending: asyncData.pending.value
138
+ })
139
+ );
140
+ return {
141
+ data: computed(() => overlay.value.data),
142
+ error: computed(() => overlay.value.error),
143
+ pending: computed(() => overlay.value.pending),
144
+ status: computed(() => overlay.value.status),
145
+ refresh
146
+ };
147
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * First-party Convex Auth wiring (opt-in via `convex.auth.provider: 'convex-auth'`).
3
+ *
4
+ * Must stay synchronous through `useConvexAuth` / `inject` — awaiting first
5
+ * drops Vue setup context and caused the "plugin did not start" 500.
6
+ */
7
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
8
+ export default _default;
@@ -0,0 +1,41 @@
1
+ import { defineNuxtPlugin, useRuntimeConfig } from "nuxt/app";
2
+ import { useConvexAuth } from "./composables/useConvexAuth.js";
3
+ import {
4
+ consumeOAuthCodeFromUrl,
5
+ getAuthToken,
6
+ hasPendingOAuthCallback,
7
+ hydrateAuthFromStorage,
8
+ useAuthProviderState
9
+ } from "./composables/useAuth.js";
10
+ import { tryUseConvexContext } from "./utils/context.js";
11
+ export default defineNuxtPlugin({
12
+ name: "convex-nuxt-auth",
13
+ dependsOn: ["convex-nuxt-client"],
14
+ setup() {
15
+ const config = useRuntimeConfig();
16
+ const convexConfig = config.public.convex;
17
+ if (!convexConfig?.url) {
18
+ return;
19
+ }
20
+ if (!tryUseConvexContext()) {
21
+ return;
22
+ }
23
+ const pendingOAuth = hasPendingOAuthCallback();
24
+ if (!pendingOAuth) {
25
+ hydrateAuthFromStorage();
26
+ }
27
+ const { isLoading, hasSession } = useAuthProviderState();
28
+ useConvexAuth({
29
+ fetchToken: ({ forceRefreshToken }) => getAuthToken({ forceRefreshToken }),
30
+ isLoading,
31
+ isAuthenticated: hasSession
32
+ });
33
+ if (pendingOAuth) {
34
+ void consumeOAuthCodeFromUrl().then((ok) => {
35
+ if (!ok) {
36
+ hydrateAuthFromStorage();
37
+ }
38
+ });
39
+ }
40
+ }
41
+ });
@@ -0,0 +1,7 @@
1
+ import { type ConvexNuxtContext } from './utils/context.js';
2
+ declare const _default: import("nuxt/app").Plugin<{
3
+ convex: ConvexNuxtContext;
4
+ }> & import("nuxt/app").ObjectPlugin<{
5
+ convex: ConvexNuxtContext;
6
+ }>;
7
+ export default _default;
@@ -0,0 +1,40 @@
1
+ import { ConvexClient } from "convex/browser";
2
+ import { defineNuxtPlugin, useRuntimeConfig } from "nuxt/app";
3
+ import { useSsrTokenRef } from "./utils/authCookie.js";
4
+ import { createHttpClient } from "./utils/http.js";
5
+ import {
6
+ convexNuxtKey,
7
+ createAuthContext
8
+ } from "./utils/context.js";
9
+ export default defineNuxtPlugin({
10
+ name: "convex-nuxt-client",
11
+ setup(nuxtApp) {
12
+ const config = useRuntimeConfig();
13
+ const convexConfig = config.public.convex;
14
+ const url = convexConfig?.url;
15
+ if (!url) {
16
+ if (import.meta.dev) {
17
+ console.warn(
18
+ "[convex-nuxt] No Convex URL configured. Set convex.url or NUXT_PUBLIC_CONVEX_URL."
19
+ );
20
+ }
21
+ return;
22
+ }
23
+ const client = new ConvexClient(url, convexConfig?.client ?? {});
24
+ const ssrToken = useSsrTokenRef();
25
+ const auth = createAuthContext();
26
+ const ctx = {
27
+ url,
28
+ client,
29
+ ssrToken,
30
+ auth,
31
+ createHttpClient: (options) => createHttpClient(url, options)
32
+ };
33
+ nuxtApp.vueApp.provide(convexNuxtKey, ctx);
34
+ return {
35
+ provide: {
36
+ convex: ctx
37
+ }
38
+ };
39
+ }
40
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Expose connection state on `window.__CONVEX_NUXT__` in development for
3
+ * quick inspection alongside the DevTools iframe tab.
4
+ */
5
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
6
+ export default _default;
@@ -0,0 +1,16 @@
1
+ import { defineNuxtPlugin } from "nuxt/app";
2
+ import { useConvexConnectionState } from "./composables/useConvexConnectionState.js";
3
+ import { tryUseConvexContext } from "./utils/context.js";
4
+ export default defineNuxtPlugin({
5
+ name: "convex-nuxt-devtools",
6
+ setup() {
7
+ if (!import.meta.dev || !tryUseConvexContext()) {
8
+ return;
9
+ }
10
+ const connection = useConvexConnectionState();
11
+ if (typeof window !== "undefined") {
12
+ ;
13
+ window.__CONVEX_NUXT__ = { connection };
14
+ }
15
+ }
16
+ });
@@ -0,0 +1,7 @@
1
+ import { type ConvexNuxtContext } from './utils/context.js';
2
+ declare const _default: import("nuxt/app").Plugin<{
3
+ convex: ConvexNuxtContext;
4
+ }> & import("nuxt/app").ObjectPlugin<{
5
+ convex: ConvexNuxtContext;
6
+ }>;
7
+ export default _default;
@@ -0,0 +1,44 @@
1
+ import { defineNuxtPlugin, useRuntimeConfig } from "nuxt/app";
2
+ import { useSsrTokenRef } from "./utils/authCookie.js";
3
+ import { createHttpClient } from "./utils/http.js";
4
+ import {
5
+ convexNuxtKey,
6
+ createAuthContext
7
+ } from "./utils/context.js";
8
+ export default defineNuxtPlugin({
9
+ name: "convex-nuxt-server",
10
+ setup(nuxtApp) {
11
+ const config = useRuntimeConfig();
12
+ const convexConfig = config.public.convex;
13
+ const url = convexConfig?.url;
14
+ if (!url) {
15
+ if (import.meta.dev) {
16
+ console.warn(
17
+ "[convex-nuxt] No Convex URL configured. Set convex.url or NUXT_PUBLIC_CONVEX_URL."
18
+ );
19
+ }
20
+ return;
21
+ }
22
+ const ssrToken = useSsrTokenRef();
23
+ const auth = createAuthContext();
24
+ if (ssrToken.value) {
25
+ auth.providerAuthenticated.value = true;
26
+ auth.isConvexAuthenticated.value = true;
27
+ auth.isLoading.value = false;
28
+ auth.isAuthenticated.value = true;
29
+ }
30
+ const ctx = {
31
+ url,
32
+ client: null,
33
+ ssrToken,
34
+ auth,
35
+ createHttpClient: (options) => createHttpClient(url, options)
36
+ };
37
+ nuxtApp.vueApp.provide(convexNuxtKey, ctx);
38
+ return {
39
+ provide: {
40
+ convex: ctx
41
+ }
42
+ };
43
+ }
44
+ });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Persist / read / refresh HttpOnly auth cookies.
3
+ *
4
+ * - GET → `{ hasSession }` only (no JWT in the response body)
5
+ * - POST `{ getToken: true }` → `{ hasSession, token }` for ConvexClient.setAuth
6
+ * - POST `{ refresh: true }` → refresh cookies, return `{ token }`
7
+ * - POST `{ token, refreshToken }` → set cookies
8
+ * - DELETE → clear cookies
9
+ *
10
+ * All methods require same-origin (fail-closed). Returning the JWT only via
11
+ * POST reduces accidental leakage via prefetch/logs; XSS can still obtain a
12
+ * token once ConvexClient needs it in memory — HttpOnly mitigates cookie theft,
13
+ * not XSS session theft.
14
+ */
15
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
16
+ hasSession: boolean;
17
+ ok?: undefined;
18
+ token?: undefined;
19
+ } | {
20
+ ok: boolean;
21
+ hasSession?: undefined;
22
+ token?: undefined;
23
+ } | {
24
+ hasSession: boolean;
25
+ token: string | null;
26
+ ok?: undefined;
27
+ } | {
28
+ token: null;
29
+ hasSession?: undefined;
30
+ ok?: undefined;
31
+ } | {
32
+ token: string;
33
+ hasSession?: undefined;
34
+ ok?: undefined;
35
+ }>>;
36
+ export default _default;
@@ -0,0 +1,95 @@
1
+ import { ConvexHttpClient } from "convex/browser";
2
+ import { makeFunctionReference } from "convex/server";
3
+ import {
4
+ createError,
5
+ defineEventHandler,
6
+ readBody
7
+ } from "h3";
8
+ import { useRuntimeConfig } from "nitropack/runtime";
9
+ import {
10
+ readHttpOnlyJwt,
11
+ readHttpOnlyRefresh,
12
+ setAuthCookies
13
+ } from "../../../authCookies.js";
14
+ import { isHttpOnlyAuth } from "../../../../utils/authStorage.js";
15
+ import { assertSameOrigin } from "../../../sameOrigin.js";
16
+ const authSignIn = makeFunctionReference("auth:signIn");
17
+ function convexUrl(event) {
18
+ const config = useRuntimeConfig(event);
19
+ const url = config.public?.convex?.url ?? process.env.NUXT_PUBLIC_CONVEX_URL;
20
+ if (!url) {
21
+ throw createError({
22
+ statusCode: 500,
23
+ message: "Convex URL is not configured"
24
+ });
25
+ }
26
+ return url;
27
+ }
28
+ function requireHttpOnly(event) {
29
+ const config = useRuntimeConfig(event);
30
+ const auth = config.public?.convex?.auth;
31
+ if (!isHttpOnlyAuth(auth)) {
32
+ throw createError({
33
+ statusCode: 404,
34
+ message: "HttpOnly auth is not enabled"
35
+ });
36
+ }
37
+ }
38
+ export default defineEventHandler(async (event) => {
39
+ requireHttpOnly(event);
40
+ assertSameOrigin(event);
41
+ if (event.method === "GET") {
42
+ const token = readHttpOnlyJwt(event);
43
+ return {
44
+ hasSession: !!token
45
+ };
46
+ }
47
+ if (event.method === "DELETE") {
48
+ setAuthCookies(event, { token: null });
49
+ return { ok: true };
50
+ }
51
+ if (event.method === "POST") {
52
+ const body = await readBody(event);
53
+ if (body?.getToken) {
54
+ const token = readHttpOnlyJwt(event);
55
+ return {
56
+ hasSession: !!token,
57
+ token: token ?? null
58
+ };
59
+ }
60
+ if (body?.refresh) {
61
+ const refreshToken = readHttpOnlyRefresh(event);
62
+ if (!refreshToken) {
63
+ setAuthCookies(event, { token: null });
64
+ return { token: null };
65
+ }
66
+ const http = new ConvexHttpClient(convexUrl(event));
67
+ try {
68
+ const result = await http.action(authSignIn, { refreshToken });
69
+ const tokens = result.tokens ?? null;
70
+ if (!tokens) {
71
+ setAuthCookies(event, { token: null });
72
+ return { token: null };
73
+ }
74
+ setAuthCookies(event, {
75
+ token: tokens.token,
76
+ refreshToken: tokens.refreshToken
77
+ });
78
+ return { token: tokens.token };
79
+ } catch {
80
+ setAuthCookies(event, { token: null });
81
+ return { token: null };
82
+ }
83
+ }
84
+ if (!body?.token) {
85
+ setAuthCookies(event, { token: null });
86
+ return { ok: true };
87
+ }
88
+ setAuthCookies(event, {
89
+ token: body.token,
90
+ refreshToken: body.refreshToken ?? null
91
+ });
92
+ return { ok: true };
93
+ }
94
+ throw createError({ statusCode: 405, message: "Method not allowed" });
95
+ });
@@ -0,0 +1,19 @@
1
+ import type { H3Event } from 'h3';
2
+ /**
3
+ * Read the Convex JWT from the configured auth cookie on this request.
4
+ * Returns `undefined` when the cookie is missing or auth cookies are disabled.
5
+ */
6
+ export declare function getConvexToken(event: H3Event): string | undefined;
7
+ /**
8
+ * Require a Convex JWT cookie on this Nitro request.
9
+ * Throws an H3 401 when the cookie is missing.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * export default defineEventHandler(async (event) => {
14
+ * requireConvexAuth(event)
15
+ * return await fetchQuery(api.tasks.list, {}, { event })
16
+ * })
17
+ * ```
18
+ */
19
+ export declare function requireConvexAuth(event: H3Event): string;
@@ -0,0 +1,32 @@
1
+ import { createError, getCookie } from "h3";
2
+ import { useRuntimeConfig } from "nitropack/runtime";
3
+ import { resolveAuthCookieName } from "../utils/authStorage.js";
4
+ import { resolveFetchToken } from "../utils/fetchToken.js";
5
+ function readConvexConfig(event) {
6
+ try {
7
+ const config = useRuntimeConfig(event);
8
+ return config.public?.convex;
9
+ } catch {
10
+ return void 0;
11
+ }
12
+ }
13
+ export function getConvexToken(event) {
14
+ const cookieName = resolveAuthCookieName(readConvexConfig(event)?.auth);
15
+ if (!cookieName) {
16
+ return void 0;
17
+ }
18
+ return resolveFetchToken({
19
+ cookieName,
20
+ cookieValue: getCookie(event, cookieName)
21
+ });
22
+ }
23
+ export function requireConvexAuth(event) {
24
+ const token = getConvexToken(event);
25
+ if (!token) {
26
+ throw createError({
27
+ statusCode: 401,
28
+ message: "Not authenticated \u2014 sign in so the Convex auth cookie is set."
29
+ });
30
+ }
31
+ return token;
32
+ }
@@ -0,0 +1,9 @@
1
+ import type { H3Event } from 'h3';
2
+ export interface AuthCookieSet {
3
+ token: string | null;
4
+ refreshToken?: string | null;
5
+ }
6
+ /** Persist or clear HttpOnly auth cookies + readable presence marker. */
7
+ export declare function setAuthCookies(event: H3Event, tokens: AuthCookieSet): void;
8
+ export declare function readHttpOnlyJwt(event: H3Event): string | undefined;
9
+ export declare function readHttpOnlyRefresh(event: H3Event): string | undefined;