convex-helpers 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -6
- package/dist/index.d.ts +31 -1
- package/dist/index.js +18 -1
- package/dist/react/sessions.d.ts +47 -0
- package/dist/react/sessions.js +111 -0
- package/dist/server/customFunctions.d.ts +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +9 -0
- package/dist/server/relationships.d.ts +2 -2
- package/dist/server/sessions.d.ts +105 -0
- package/dist/server/sessions.js +20 -0
- package/dist/server/zod.d.ts +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/index.ts +45 -2
- package/package.json +17 -2
- package/react/sessions.ts +236 -0
- package/server/customFunctions.test.ts +1 -17
- package/server/customFunctions.ts +1 -2
- package/server/index.ts +15 -0
- package/server/relationships.ts +2 -2
- package/server/sessions.ts +157 -0
- package/server/zod.test.ts +1 -17
- package/server/zod.ts +1 -3
package/README.md
CHANGED
|
@@ -15,17 +15,18 @@ define custom behavior, allowing you to:
|
|
|
15
15
|
as taking in an authentication parameter like an API key or session ID.
|
|
16
16
|
These arguments must be sent up by the client along with each request.
|
|
17
17
|
|
|
18
|
+
See the associated [Stack Post](https://stack.convex.dev/custom-functions)
|
|
19
|
+
|
|
18
20
|
For example:
|
|
19
21
|
```js
|
|
20
22
|
import { customQuery } from "convex-helpers/server/customFunctions.js
|
|
21
23
|
|
|
22
24
|
const myQueryBuilder = customQuery(query, {
|
|
23
|
-
args: {
|
|
25
|
+
args: { apiToken: v.id("api_tokens") },
|
|
24
26
|
input: async (ctx, args) => {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
return { ctx: { db, user, session }, args: {} };
|
|
27
|
+
const apiUser = await getApiUser(args.apiToken);
|
|
28
|
+
const db = wrapDatabaseReader({ apiUser }, ctx.db, rlsRules);
|
|
29
|
+
return { ctx: { db, apiUser }, args: {} };
|
|
29
30
|
},
|
|
30
31
|
});
|
|
31
32
|
|
|
@@ -33,7 +34,7 @@ const myQueryBuilder = customQuery(query, {
|
|
|
33
34
|
export const getSomeData = myQueryBuilder({
|
|
34
35
|
args: { someArg: v.string() },
|
|
35
36
|
handler: async (ctx, args) => {
|
|
36
|
-
const { db,
|
|
37
|
+
const { db, apiUser, scheduler } = ctx;
|
|
37
38
|
const { someArg } = args;
|
|
38
39
|
// ...
|
|
39
40
|
}
|
|
@@ -72,6 +73,16 @@ const posts = await asyncMap(
|
|
|
72
73
|
);
|
|
73
74
|
```
|
|
74
75
|
|
|
76
|
+
## Session tracking via client-side sessionID storage
|
|
77
|
+
|
|
78
|
+
Store a session ID on the client and pass it up with requests to keep track of
|
|
79
|
+
a user, even if they aren't logged in.
|
|
80
|
+
|
|
81
|
+
Use the client-side helpers in [react/sessions](./react/sessions.ts) and
|
|
82
|
+
server-side helpers in [server/sessions](./server/sessions.ts).
|
|
83
|
+
|
|
84
|
+
See the associated [Stack post](https://stack.convex.dev/track-sessions-without-cookies) for more information.
|
|
85
|
+
|
|
75
86
|
## Row-level security
|
|
76
87
|
|
|
77
88
|
See the [Stack post on row-level security](https://stack.convex.dev/row-level-security)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* asyncMap returns the results of applying an async function over an list.
|
|
3
3
|
*
|
|
4
|
+
* The list can even be a promise, or an iterable like a Set.
|
|
4
5
|
* @param list - Iterable object of items, e.g. an Array, Set, Object.keys
|
|
5
6
|
* @param asyncTransform
|
|
6
7
|
* @returns
|
|
7
8
|
*/
|
|
8
|
-
export declare function asyncMap<FromType, ToType>(list: Iterable<FromType
|
|
9
|
+
export declare function asyncMap<FromType, ToType>(list: Iterable<FromType> | Promise<Iterable<FromType>>, asyncTransform: (item: FromType, index: number) => Promise<ToType>): Promise<ToType[]>;
|
|
9
10
|
/**
|
|
10
11
|
* Filters out null elements from an array.
|
|
11
12
|
* @param list List of elements that might be null.
|
|
@@ -20,3 +21,32 @@ export declare class NullDocumentError extends Error {
|
|
|
20
21
|
* @returns Same list of elements with a refined type.
|
|
21
22
|
*/
|
|
22
23
|
export declare function nullThrows<T>(doc: T | null, message?: string): T;
|
|
24
|
+
export type EmptyObject = Record<string, never>;
|
|
25
|
+
/**
|
|
26
|
+
* An `Omit<>` type that:
|
|
27
|
+
* 1. Applies to each element of a union.
|
|
28
|
+
* 2. Preserves the index signature of the underlying type.
|
|
29
|
+
*/
|
|
30
|
+
export type BetterOmit<T, K extends keyof T> = {
|
|
31
|
+
[Property in keyof T as Property extends K ? never : Property]: T[Property];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* TESTS
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* Tests if two types are exactly the same.
|
|
38
|
+
* Taken from https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650
|
|
39
|
+
* (Apache Version 2.0, January 2004)
|
|
40
|
+
*/
|
|
41
|
+
export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
|
|
42
|
+
/**
|
|
43
|
+
* A utility for both compile-time type assertions and runtime assertions.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* // Compile-time assertion
|
|
48
|
+
* assert<Equals<1, 1>>();
|
|
49
|
+
* ```
|
|
50
|
+
* @param arg A value to assert the truthiness of.
|
|
51
|
+
*/
|
|
52
|
+
export declare function assert<T extends true>(arg?: T): void;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* asyncMap returns the results of applying an async function over an list.
|
|
3
3
|
*
|
|
4
|
+
* The list can even be a promise, or an iterable like a Set.
|
|
4
5
|
* @param list - Iterable object of items, e.g. an Array, Set, Object.keys
|
|
5
6
|
* @param asyncTransform
|
|
6
7
|
* @returns
|
|
@@ -8,7 +9,7 @@
|
|
|
8
9
|
export async function asyncMap(list, asyncTransform) {
|
|
9
10
|
const promises = [];
|
|
10
11
|
let index = 0;
|
|
11
|
-
for (const item of list) {
|
|
12
|
+
for (const item of await list) {
|
|
12
13
|
promises.push(asyncTransform(item, index));
|
|
13
14
|
index += 1;
|
|
14
15
|
}
|
|
@@ -35,3 +36,19 @@ export function nullThrows(doc, message) {
|
|
|
35
36
|
}
|
|
36
37
|
return doc;
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A utility for both compile-time type assertions and runtime assertions.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* // Compile-time assertion
|
|
45
|
+
* assert<Equals<1, 1>>();
|
|
46
|
+
* ```
|
|
47
|
+
* @param arg A value to assert the truthiness of.
|
|
48
|
+
*/
|
|
49
|
+
export function assert(arg) {
|
|
50
|
+
// no need to do anything! we're just asserting at compile time that the type
|
|
51
|
+
// parameter is true.
|
|
52
|
+
if (arg !== undefined && !arg)
|
|
53
|
+
throw new Error(`Assertion failed: ${arg}`);
|
|
54
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React helpers for adding session data to Convex functions.
|
|
3
|
+
*
|
|
4
|
+
* !Important!: To use these functions, you must wrap your code with
|
|
5
|
+
* ```tsx
|
|
6
|
+
* <ConvexProvider client={convex}>
|
|
7
|
+
* <SessionProvider>
|
|
8
|
+
* <App />
|
|
9
|
+
* </SessionProvider>
|
|
10
|
+
* </ConvexProvider>
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* With the `SessionProvider` inside the `ConvexProvider` but outside your app.
|
|
14
|
+
*
|
|
15
|
+
* See the associated [Stack post](https://stack.convex.dev/track-sessions-without-cookies)
|
|
16
|
+
* for more information.
|
|
17
|
+
*/
|
|
18
|
+
import React, { Dispatch, SetStateAction } from "react";
|
|
19
|
+
import type { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server";
|
|
20
|
+
import type { SessionId } from "../server/sessions";
|
|
21
|
+
import { EmptyObject, BetterOmit } from "..";
|
|
22
|
+
export type UseStorage<T> = (key: string, initialValue: T) => readonly [T, Dispatch<SetStateAction<T>>];
|
|
23
|
+
export type RefreshSessionFn = (beforeUpdate?: (newSessionId: SessionId) => any | Promise<any>) => Promise<SessionId>;
|
|
24
|
+
type SessionFunction<T extends "query" | "mutation" | "action", Args extends any = any> = FunctionReference<T, "public", {
|
|
25
|
+
sessionId: SessionId;
|
|
26
|
+
} & Args, any>;
|
|
27
|
+
type SessionQueryArgsArray<Fn extends SessionFunction<"query">> = keyof FunctionArgs<Fn> extends "sessionId" ? [args?: EmptyObject | "skip"] : [args: BetterOmit<FunctionArgs<Fn>, "sessionId"> | "skip"];
|
|
28
|
+
type SessionArgsArray<Fn extends SessionFunction<"mutation" | "action">> = keyof FunctionArgs<Fn> extends "sessionId" ? [args?: EmptyObject] : [args: BetterOmit<FunctionArgs<Fn>, "sessionId">];
|
|
29
|
+
/**
|
|
30
|
+
* Context for a Convex session, creating a server session and providing the id.
|
|
31
|
+
*
|
|
32
|
+
* @param props - Where you want your session ID to be persisted. Roughly:
|
|
33
|
+
* - sessionStorage is saved per-tab
|
|
34
|
+
* - localStorage is shared between tabs, but not browser profiles.
|
|
35
|
+
* @returns A provider to wrap your React nodes which provides the session ID.
|
|
36
|
+
* To be used with useSessionQuery and useSessionMutation.
|
|
37
|
+
*/
|
|
38
|
+
export declare const SessionProvider: React.FC<{
|
|
39
|
+
useStorage?: UseStorage<SessionId>;
|
|
40
|
+
storageKey?: string;
|
|
41
|
+
children?: React.ReactNode;
|
|
42
|
+
}>;
|
|
43
|
+
export declare function useSessionQuery<Query extends SessionFunction<"query">>(query: Query, ...args: SessionQueryArgsArray<Query>): FunctionReturnType<Query> | undefined;
|
|
44
|
+
export declare function useSessionMutation<Mutation extends SessionFunction<"mutation">>(name: Mutation): (...args: SessionArgsArray<Mutation>) => Promise<FunctionReturnType<Mutation>>;
|
|
45
|
+
export declare function useSessionAction<Action extends SessionFunction<"action">>(name: Action): (...args: SessionArgsArray<Action>) => Promise<FunctionReturnType<Action>>;
|
|
46
|
+
export declare function useSessionId(): readonly [SessionId, RefreshSessionFn];
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React helpers for adding session data to Convex functions.
|
|
3
|
+
*
|
|
4
|
+
* !Important!: To use these functions, you must wrap your code with
|
|
5
|
+
* ```tsx
|
|
6
|
+
* <ConvexProvider client={convex}>
|
|
7
|
+
* <SessionProvider>
|
|
8
|
+
* <App />
|
|
9
|
+
* </SessionProvider>
|
|
10
|
+
* </ConvexProvider>
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* With the `SessionProvider` inside the `ConvexProvider` but outside your app.
|
|
14
|
+
*
|
|
15
|
+
* See the associated [Stack post](https://stack.convex.dev/track-sessions-without-cookies)
|
|
16
|
+
* for more information.
|
|
17
|
+
*/
|
|
18
|
+
import React, { useCallback, useContext, useMemo, useState, } from "react";
|
|
19
|
+
import { useQuery, useMutation, useAction } from "convex/react";
|
|
20
|
+
import { assert } from "..";
|
|
21
|
+
const SessionContext = React.createContext(null);
|
|
22
|
+
/**
|
|
23
|
+
* Context for a Convex session, creating a server session and providing the id.
|
|
24
|
+
*
|
|
25
|
+
* @param props - Where you want your session ID to be persisted. Roughly:
|
|
26
|
+
* - sessionStorage is saved per-tab
|
|
27
|
+
* - localStorage is shared between tabs, but not browser profiles.
|
|
28
|
+
* @returns A provider to wrap your React nodes which provides the session ID.
|
|
29
|
+
* To be used with useSessionQuery and useSessionMutation.
|
|
30
|
+
*/
|
|
31
|
+
export const SessionProvider = ({ useStorage, storageKey, children }) => {
|
|
32
|
+
const storeKey = storageKey ?? "convex-session-id";
|
|
33
|
+
const initialId = useMemo(() => crypto.randomUUID(), []);
|
|
34
|
+
// Get or set the ID from our desired storage location.
|
|
35
|
+
const useStorageOrDefault = useStorage ?? useSessionStorage;
|
|
36
|
+
const [sessionId, setSessionId] = useStorageOrDefault(storeKey, initialId);
|
|
37
|
+
const refreshSessionId = useCallback(async (beforeUpdate) => {
|
|
38
|
+
const newSessionId = crypto.randomUUID();
|
|
39
|
+
if (beforeUpdate) {
|
|
40
|
+
await beforeUpdate(newSessionId);
|
|
41
|
+
}
|
|
42
|
+
setSessionId(newSessionId);
|
|
43
|
+
return newSessionId;
|
|
44
|
+
}, [setSessionId]);
|
|
45
|
+
return React.createElement(SessionContext.Provider, { value: { sessionId, refreshSessionId } }, children);
|
|
46
|
+
};
|
|
47
|
+
// Like useQuery, but for a Query that takes a session ID.
|
|
48
|
+
export function useSessionQuery(query, ...args) {
|
|
49
|
+
const skip = args[0] === "skip";
|
|
50
|
+
const [sessionId] = useSessionId();
|
|
51
|
+
const originalArgs = args[0] === "skip" ? {} : args[0] ?? {};
|
|
52
|
+
const newArgs = skip ? "skip" : { ...originalArgs, sessionId };
|
|
53
|
+
return useQuery(query, ...[newArgs]);
|
|
54
|
+
}
|
|
55
|
+
// Like useMutation, but for a Mutation that takes a session ID.
|
|
56
|
+
export function useSessionMutation(name) {
|
|
57
|
+
const [sessionId] = useSessionId();
|
|
58
|
+
const originalMutation = useMutation(name);
|
|
59
|
+
return useCallback((...args) => {
|
|
60
|
+
const newArgs = {
|
|
61
|
+
...(args[0] ?? {}),
|
|
62
|
+
sessionId,
|
|
63
|
+
};
|
|
64
|
+
return originalMutation(...[newArgs]);
|
|
65
|
+
}, [sessionId, originalMutation]);
|
|
66
|
+
}
|
|
67
|
+
// Like useAction, but for a Action that takes a session ID.
|
|
68
|
+
export function useSessionAction(name) {
|
|
69
|
+
const [sessionId] = useSessionId();
|
|
70
|
+
const originalAction = useAction(name);
|
|
71
|
+
return useCallback((...args) => {
|
|
72
|
+
const newArgs = {
|
|
73
|
+
...(args[0] ?? {}),
|
|
74
|
+
sessionId,
|
|
75
|
+
};
|
|
76
|
+
return originalAction(...[newArgs]);
|
|
77
|
+
}, [sessionId, originalAction]);
|
|
78
|
+
}
|
|
79
|
+
export function useSessionId() {
|
|
80
|
+
const ctx = useContext(SessionContext);
|
|
81
|
+
if (ctx === null) {
|
|
82
|
+
throw new Error("Missing a <SessionProvider> wrapping this code.");
|
|
83
|
+
}
|
|
84
|
+
return [ctx.sessionId, ctx.refreshSessionId];
|
|
85
|
+
}
|
|
86
|
+
function useSessionStorage(key, initialValue) {
|
|
87
|
+
const [value, setValueInternal] = useState(() => {
|
|
88
|
+
if (typeof sessionStorage !== "undefined") {
|
|
89
|
+
const existing = sessionStorage.getItem(key);
|
|
90
|
+
if (existing) {
|
|
91
|
+
try {
|
|
92
|
+
return existing;
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
console.error(e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
sessionStorage.setItem(key, initialValue);
|
|
99
|
+
}
|
|
100
|
+
return initialValue;
|
|
101
|
+
});
|
|
102
|
+
const setValue = useCallback((value) => {
|
|
103
|
+
sessionStorage.setItem(key, value);
|
|
104
|
+
setValueInternal(value);
|
|
105
|
+
}, [key]);
|
|
106
|
+
return [value, setValue];
|
|
107
|
+
}
|
|
108
|
+
assert();
|
|
109
|
+
assert();
|
|
110
|
+
assert();
|
|
111
|
+
assert();
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { ObjectType, PropertyValidators } from "convex/values";
|
|
14
14
|
import { ActionBuilder, FunctionVisibility, GenericActionCtx, GenericDataModel, GenericMutationCtx, GenericQueryCtx, MutationBuilder, QueryBuilder, RegisteredAction, RegisteredMutation, RegisteredQuery, UnvalidatedFunction } from "convex/server";
|
|
15
|
+
import { EmptyObject } from "..";
|
|
15
16
|
/**
|
|
16
17
|
* A modifier for a query, mutation, or action.
|
|
17
18
|
*
|
|
@@ -255,6 +256,5 @@ export type UnvalidatedBuilder<FuncType extends "query" | "mutation" | "action",
|
|
|
255
256
|
type CustomBuilder<FuncType extends "query" | "mutation" | "action", ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, InputCtx, Visibility extends FunctionVisibility> = ModArgsValidator extends EmptyObject ? ValidatedBuilder<FuncType, ModArgsValidator, ModCtx, ModMadeArgs, InputCtx, Visibility> & UnvalidatedBuilder<FuncType, ModCtx, ModMadeArgs, InputCtx, Visibility> : ValidatedBuilder<FuncType, ModArgsValidator, ModCtx, ModMadeArgs, InputCtx, Visibility>;
|
|
256
257
|
export type CustomCtx<Builder> = Builder extends ValidatedBuilder<any, any, infer ModCtx, any, infer InputCtx, any> ? Overwrite<InputCtx, ModCtx> : never;
|
|
257
258
|
type Overwrite<T, U> = Omit<T, keyof U> & U;
|
|
258
|
-
type EmptyObject = Record<string, never>;
|
|
259
259
|
type DefaultFunctionArgs = Record<string, unknown>;
|
|
260
260
|
export {};
|
package/dist/server/index.d.ts
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -26,3 +26,12 @@ export function Table(name, fields) {
|
|
|
26
26
|
withSystemFields,
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
export function missingEnvVariableUrl(envVarName, whereToGet) {
|
|
30
|
+
const deploymentName = process.env.CONVEX_CLOUD_URL?.slice(8).replace(".convex.cloud", "");
|
|
31
|
+
return (`\n Missing ${envVarName} in environment variables.\n\n` +
|
|
32
|
+
` Get it from ${whereToGet} .\n` +
|
|
33
|
+
" Paste it on the Convex dashboard:\n" +
|
|
34
|
+
" https://dashboard.convex.dev/d/" +
|
|
35
|
+
deploymentName +
|
|
36
|
+
`/settings?var=${envVarName}`);
|
|
37
|
+
}
|
|
@@ -8,7 +8,7 @@ import { GenericId } from "convex/values";
|
|
|
8
8
|
* @param ids An list (or other iterable) of Ids pointing to a table.
|
|
9
9
|
* @returns The Documents referenced by the Ids, in order. `null` if not found.
|
|
10
10
|
*/
|
|
11
|
-
export declare function getAll<DataModel extends GenericDataModel, TableName extends TableNamesInDataModel<DataModel>>(db: GenericDatabaseReader<DataModel>, ids: Iterable<GenericId<TableName>>): Promise<(DocumentByName<DataModel, TableName> | null)[]>;
|
|
11
|
+
export declare function getAll<DataModel extends GenericDataModel, TableName extends TableNamesInDataModel<DataModel>>(db: GenericDatabaseReader<DataModel>, ids: Iterable<GenericId<TableName>> | Promise<Iterable<GenericId<TableName>>>): Promise<(DocumentByName<DataModel, TableName> | null)[]>;
|
|
12
12
|
/**
|
|
13
13
|
* getAllOrThrow returns a list of Documents for the `Id`s passed in.
|
|
14
14
|
*
|
|
@@ -17,7 +17,7 @@ export declare function getAll<DataModel extends GenericDataModel, TableName ext
|
|
|
17
17
|
* @param ids An list (or other iterable) of Ids pointing to a table.
|
|
18
18
|
* @returns The Documents referenced by the Ids, in order. `null` if not found.
|
|
19
19
|
*/
|
|
20
|
-
export declare function getAllOrThrow<DataModel extends GenericDataModel, TableName extends TableNamesInDataModel<DataModel>>(db: GenericDatabaseReader<DataModel>, ids: Iterable<GenericId<TableName>>): Promise<DocumentByName<DataModel, TableName>[]>;
|
|
20
|
+
export declare function getAllOrThrow<DataModel extends GenericDataModel, TableName extends TableNamesInDataModel<DataModel>>(db: GenericDatabaseReader<DataModel>, ids: Iterable<GenericId<TableName>> | Promise<Iterable<GenericId<TableName>>>): Promise<DocumentByName<DataModel, TableName>[]>;
|
|
21
21
|
type UserIndexes<DataModel extends GenericDataModel, TableName extends TableNamesInDataModel<DataModel>> = Exclude<IndexNames<NamedTableInfo<DataModel, TableName>>, "by_creation_time"> & string;
|
|
22
22
|
type TablesWithLookups<DataModel extends GenericDataModel> = {
|
|
23
23
|
[T in TableNamesInDataModel<DataModel>]: UserIndexes<DataModel, T> extends never ? never : T;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allows you to persist state server-side, associated with a sessionId stored
|
|
3
|
+
* on the client (in localStorage, e.g.).
|
|
4
|
+
*
|
|
5
|
+
* You can define your function to take in a sessionId parameter of type
|
|
6
|
+
* SessionId, which is just a branded string to help avoid errors.
|
|
7
|
+
* The validator is vSessionId, or you can spread the argument in for your
|
|
8
|
+
* function like this:
|
|
9
|
+
* ```ts
|
|
10
|
+
* const myMutation = mutation({
|
|
11
|
+
* args: {
|
|
12
|
+
* arg1: v.number(), // whatever other args you want
|
|
13
|
+
* ...SessionIdArg,
|
|
14
|
+
* },
|
|
15
|
+
* handler: async (ctx, args) => {
|
|
16
|
+
* // args.sessionId is a SessionId
|
|
17
|
+
* })
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Then, on the client side, you can use {@link useSessionMutation} to call
|
|
22
|
+
* your function with the sessionId automatically passed in, like:
|
|
23
|
+
* ```ts
|
|
24
|
+
* const myMutation = useSessionMutation(api.myModule.myMutation);
|
|
25
|
+
* ...
|
|
26
|
+
* await myMutation({ arg1: 123 });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* To codify the sessionId parameter, you can use the customFunction module to
|
|
30
|
+
* create a custom mutation or query, like:
|
|
31
|
+
* ```ts
|
|
32
|
+
* export const sessionMutation = customMutation(mutation, {
|
|
33
|
+
* args: { ...SessionIdArg },
|
|
34
|
+
* input: (ctx, { sessionId }) => {
|
|
35
|
+
* const anonUser = await getAnonymousUser(ctx, sessionId);
|
|
36
|
+
* return { ctx: { anonUser }, args: {} };
|
|
37
|
+
* },
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Then you can define functions like:
|
|
42
|
+
* ```ts
|
|
43
|
+
* export const myMutation = sessionMutation({
|
|
44
|
+
* args: { arg1: v.number() }, // whatever other args you want
|
|
45
|
+
* handler: async (ctx, args) => {
|
|
46
|
+
* // ctx.anonUser exists and has a type from getAnonymousUser.
|
|
47
|
+
* // args is { arg1: number }
|
|
48
|
+
* })
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* See the associated [Stack post](https://stack.convex.dev/track-sessions-without-cookies)
|
|
53
|
+
* for more information.
|
|
54
|
+
*/
|
|
55
|
+
import { FunctionArgs, FunctionReference, FunctionReturnType, GenericActionCtx, GenericDataModel } from "convex/server";
|
|
56
|
+
import { Validator } from "convex/values";
|
|
57
|
+
import { BetterOmit, EmptyObject } from "..";
|
|
58
|
+
export type SessionId = string & {
|
|
59
|
+
__SessionId: true;
|
|
60
|
+
};
|
|
61
|
+
export declare const vSessionId: Validator<SessionId, false, never>;
|
|
62
|
+
export declare const SessionIdArg: {
|
|
63
|
+
sessionId: Validator<SessionId, false, never>;
|
|
64
|
+
};
|
|
65
|
+
type SessionFunction<T extends "query" | "mutation" | "action", Args extends any = any> = FunctionReference<T, "public" | "internal", {
|
|
66
|
+
sessionId: SessionId;
|
|
67
|
+
} & Args, any>;
|
|
68
|
+
type SessionArgsArray<Fn extends SessionFunction<"query" | "mutation" | "action", any>> = keyof FunctionArgs<Fn> extends "sessionId" ? [args?: EmptyObject] : [args: BetterOmit<FunctionArgs<Fn>, "sessionId">];
|
|
69
|
+
export interface RunSessionFunctions {
|
|
70
|
+
/**
|
|
71
|
+
* Run the Convex query with the given name and arguments.
|
|
72
|
+
*
|
|
73
|
+
* Consider using an {@link internalQuery} to prevent users from calling the
|
|
74
|
+
* query directly.
|
|
75
|
+
*
|
|
76
|
+
* @param query - A {@link FunctionReference} for the query to run.
|
|
77
|
+
* @param args - The arguments to the query function.
|
|
78
|
+
* @returns A promise of the query's result.
|
|
79
|
+
*/
|
|
80
|
+
runSessionQuery<Query extends SessionFunction<"query">>(query: Query, ...args: SessionArgsArray<Query>): Promise<FunctionReturnType<Query>>;
|
|
81
|
+
/**
|
|
82
|
+
* Run the Convex mutation with the given name and arguments.
|
|
83
|
+
*
|
|
84
|
+
* Consider using an {@link internalMutation} to prevent users from calling
|
|
85
|
+
* the mutation directly.
|
|
86
|
+
*
|
|
87
|
+
* @param mutation - A {@link FunctionReference} for the mutation to run.
|
|
88
|
+
* @param args - The arguments to the mutation function.
|
|
89
|
+
* @returns A promise of the mutation's result.
|
|
90
|
+
*/
|
|
91
|
+
runSessionMutation<Mutation extends SessionFunction<"mutation">>(mutation: Mutation, ...args: SessionArgsArray<Mutation>): Promise<FunctionReturnType<Mutation>>;
|
|
92
|
+
/**
|
|
93
|
+
* Run the Convex action with the given name and arguments.
|
|
94
|
+
*
|
|
95
|
+
* Consider using an {@link internalAction} to prevent users from calling the
|
|
96
|
+
* action directly.
|
|
97
|
+
*
|
|
98
|
+
* @param action - A {@link FunctionReference} for the action to run.
|
|
99
|
+
* @param args - The arguments to the action function.
|
|
100
|
+
* @returns A promise of the action's result.
|
|
101
|
+
*/
|
|
102
|
+
runSessionAction<Action extends SessionFunction<"action">>(action: Action, ...args: SessionArgsArray<Action>): Promise<FunctionReturnType<Action>>;
|
|
103
|
+
}
|
|
104
|
+
export declare function runSessionFunctions<DataModel extends GenericDataModel>(ctx: GenericActionCtx<DataModel>, sessionId: SessionId): RunSessionFunctions;
|
|
105
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
// Validator for session IDs.
|
|
3
|
+
export const vSessionId = v.string();
|
|
4
|
+
export const SessionIdArg = { sessionId: vSessionId };
|
|
5
|
+
export function runSessionFunctions(ctx, sessionId) {
|
|
6
|
+
return {
|
|
7
|
+
runSessionQuery(fn, ...args) {
|
|
8
|
+
const argsWithSession = { ...(args[0] ?? {}), sessionId };
|
|
9
|
+
return ctx.runQuery(fn, argsWithSession);
|
|
10
|
+
},
|
|
11
|
+
runSessionMutation(fn, ...args) {
|
|
12
|
+
const argsWithSession = { ...(args[0] ?? {}), sessionId };
|
|
13
|
+
return ctx.runMutation(fn, argsWithSession);
|
|
14
|
+
},
|
|
15
|
+
runSessionAction(fn, ...args) {
|
|
16
|
+
const argsWithSession = { ...(args[0] ?? {}), sessionId };
|
|
17
|
+
return ctx.runAction(fn, argsWithSession);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
package/dist/server/zod.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ZodTypeDef, z } from "zod";
|
|
|
2
2
|
import { v, GenericId, ObjectType, PropertyValidators, Validator } from "convex/values";
|
|
3
3
|
import { FunctionVisibility, GenericDataModel, GenericActionCtx, GenericQueryCtx, MutationBuilder, QueryBuilder, GenericMutationCtx, ActionBuilder } from "convex/server";
|
|
4
4
|
import { Mod, Registration, UnvalidatedBuilder } from "./customFunctions";
|
|
5
|
+
import { EmptyObject } from "..";
|
|
5
6
|
export type ZodValidator = Record<string, z.ZodTypeAny>;
|
|
6
7
|
/**
|
|
7
8
|
* Create a validator for a Convex `Id`.
|
|
@@ -193,7 +194,6 @@ type ValidatedBuilder<FuncType extends "query" | "mutation" | "action", ModArgsV
|
|
|
193
194
|
* builder will require argument validation too.
|
|
194
195
|
*/
|
|
195
196
|
type CustomBuilder<FuncType extends "query" | "mutation" | "action", ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, InputCtx, Visibility extends FunctionVisibility> = ModArgsValidator extends EmptyObject ? ValidatedBuilder<FuncType, ModArgsValidator, ModCtx, ModMadeArgs, InputCtx, Visibility> & UnvalidatedBuilder<FuncType, ModCtx, ModMadeArgs, InputCtx, Visibility> : ValidatedBuilder<FuncType, ModArgsValidator, ModCtx, ModMadeArgs, InputCtx, Visibility>;
|
|
196
|
-
type EmptyObject = Record<string, never>;
|
|
197
197
|
type ConvexValidatorFromZod<Z extends z.ZodTypeAny> = Z extends Zid<infer TableName> ? Validator<GenericId<TableName>> : Z extends z.ZodString ? Validator<string> : Z extends z.ZodNumber ? Validator<number> : Z extends z.ZodNaN ? Validator<number> : Z extends z.ZodBigInt ? Validator<bigint> : Z extends z.ZodBoolean ? Validator<boolean> : Z extends z.ZodNull ? Validator<null> : Z extends z.ZodUnknown ? Validator<any, false, string> : Z extends z.ZodAny ? Validator<any, false, string> : Z extends z.ZodArray<infer Inner> ? Validator<ConvexValidatorFromZod<Inner>["type"][]> : Z extends z.ZodObject<infer ZodShape> ? ReturnType<typeof v.object<{
|
|
198
198
|
[key in keyof ZodShape]: ConvexValidatorFromZod<ZodShape[key]>;
|
|
199
199
|
}>> : Z extends z.ZodUnion<infer T> ? Validator<ConvexValidatorFromZod<T[number]>["type"], false, ConvexValidatorFromZod<T[number]>["fieldPaths"]> : Z extends z.ZodDiscriminatedUnion<any, infer T> ? Validator<ConvexValidatorFromZod<T[number]>["type"], false, ConvexValidatorFromZod<T[number]>["fieldPaths"]> : Z extends z.ZodTuple<infer Inner> ? Validator<ConvexValidatorFromZod<Inner[number]>["type"][]> : Z extends z.ZodLazy<infer Inner> ? ConvexValidatorFromZod<Inner> : Z extends z.ZodLiteral<infer Literal> ? Validator<Literal> : Z extends z.ZodEnum<infer T> ? Validator<T[number]> : Z extends z.ZodEffects<infer Inner> ? ConvexValidatorFromZod<Inner> : Z extends z.ZodOptional<infer Inner> ? ConvexValidatorFromZod<Inner> extends Validator<infer InnerConvex, false, infer InnerFieldPaths> ? Validator<InnerConvex | undefined, true, InnerFieldPaths> : never : Z extends z.ZodNullable<infer Inner> ? ConvexValidatorFromZod<Inner> extends Validator<infer InnerConvex, infer InnerOptional, infer InnerFieldPaths> ? Validator<null | InnerConvex, InnerOptional, InnerFieldPaths> : never : Z extends z.ZodBranded<infer Inner, any> ? ConvexValidatorFromZod<Inner> : Z extends z.ZodDefault<infer Inner> ? ConvexValidatorFromZod<Inner> extends Validator<infer InnerConvex, false, infer InnerFieldPaths> ? Validator<InnerConvex | undefined, true, InnerFieldPaths> : never : Z extends z.ZodReadonly<infer Inner> ? ConvexValidatorFromZod<Inner> : Z extends z.ZodPipeline<infer Inner, any> ? ConvexValidatorFromZod<Inner> : never;
|