convex-helpers 0.1.2 → 0.1.4
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 +38 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +22 -0
- package/dist/server/customFunctions.d.ts +245 -0
- package/dist/server/customFunctions.js +295 -0
- package/dist/server/index.d.ts +0 -0
- package/dist/server/index.js +1 -0
- package/dist/server/relationships.d.ts +79 -0
- package/dist/server/relationships.js +98 -0
- package/dist/server/rowLevelSecurity.d.ts +107 -0
- package/dist/server/rowLevelSecurity.js +344 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +18 -3
- package/server/customFunctions.test.ts +200 -0
- package/server/customFunctions.ts +543 -0
- package/server/relationships.ts +4 -2
- package/server/rowLevelSecurity.ts +56 -3
- package/server/middlewareUtils.ts +0 -102
- package/tsconfig.json +0 -112
package/README.md
CHANGED
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
A collection of useful code to complement the official packages.
|
|
4
4
|
|
|
5
|
+
## Custom Functions
|
|
6
|
+
|
|
7
|
+
Build your own customized versions of `query`, `mutation`, and `action` that
|
|
8
|
+
define custom behavior, allowing you to:
|
|
9
|
+
|
|
10
|
+
- Run authentication logic before the request starts.
|
|
11
|
+
- Look up commonly used data and add it to the ctx argument.
|
|
12
|
+
- Replace a ctx or argument field with a different value, such as a version
|
|
13
|
+
of `db` that runs custom functions on data access.
|
|
14
|
+
- Consume arguments from the client that are not passed to the action, such
|
|
15
|
+
as taking in an authentication parameter like an API key or session ID.
|
|
16
|
+
These arguments must be sent up by the client along with each request.
|
|
17
|
+
|
|
18
|
+
For example:
|
|
19
|
+
```js
|
|
20
|
+
import { customQuery } from "convex-helpers/server/customFunctions.js
|
|
21
|
+
|
|
22
|
+
const myQueryBuilder = customQuery(query, {
|
|
23
|
+
args: { sessionId: v.id("sessions") },
|
|
24
|
+
input: async (ctx, args) => {
|
|
25
|
+
const user = await getUserOrNull(ctx);
|
|
26
|
+
const session = await db.get(sessionId);
|
|
27
|
+
const db = wrapDatabaseReader({ user }, ctx.db, rlsRules);
|
|
28
|
+
return { ctx: { db, user, session }, args: {} };
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Using the custom builder everywhere you would have used `query`
|
|
33
|
+
export const getSomeData = myQueryBuilder({
|
|
34
|
+
args: { someArg: v.string() },
|
|
35
|
+
handler: async (ctx, args) => {
|
|
36
|
+
const { db, user, session, scheduler } = ctx;
|
|
37
|
+
const { someArg } = args;
|
|
38
|
+
// ...
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
5
43
|
## Row-level security
|
|
6
44
|
|
|
7
45
|
See the [Stack post on row-level security](https://stack.convex.dev/row-level-security)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* asyncMap returns the results of applying an async function over an list.
|
|
3
|
+
*
|
|
4
|
+
* @param list - Iterable object of items, e.g. an Array, Set, Object.keys
|
|
5
|
+
* @param asyncTransform
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
export declare function asyncMap<FromType, ToType>(list: Iterable<FromType>, asyncTransform: (item: FromType) => Promise<ToType>): Promise<ToType[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Filters out null elements from an array.
|
|
11
|
+
* @param list List of elements that might be null.
|
|
12
|
+
* @returns List of elements with nulls removed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function pruneNull<T>(list: (T | null)[]): T[];
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* asyncMap returns the results of applying an async function over an list.
|
|
3
|
+
*
|
|
4
|
+
* @param list - Iterable object of items, e.g. an Array, Set, Object.keys
|
|
5
|
+
* @param asyncTransform
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
export async function asyncMap(list, asyncTransform) {
|
|
9
|
+
const promises = [];
|
|
10
|
+
for (const item of list) {
|
|
11
|
+
promises.push(asyncTransform(item));
|
|
12
|
+
}
|
|
13
|
+
return Promise.all(promises);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Filters out null elements from an array.
|
|
17
|
+
* @param list List of elements that might be null.
|
|
18
|
+
* @returns List of elements with nulls removed.
|
|
19
|
+
*/
|
|
20
|
+
export function pruneNull(list) {
|
|
21
|
+
return list.filter((i) => i !== null);
|
|
22
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains helpers for defining custom functions that modify the
|
|
3
|
+
* context and arguments of a Convex function. Allows you to:
|
|
4
|
+
*
|
|
5
|
+
* - Run authentication logic before the request starts.
|
|
6
|
+
* - Look up commonly used data and add it to the ctx argument.
|
|
7
|
+
* - Replace a ctx or argument field with a different value, such as a version
|
|
8
|
+
* of `db` that runs custom functions on data access.
|
|
9
|
+
* - Consume arguments from the client that are not passed to the query, such
|
|
10
|
+
* as taking in an authentication parameter like an API key or session ID.
|
|
11
|
+
* These arguments must be sent up by the client along with each request.
|
|
12
|
+
*/
|
|
13
|
+
import { ObjectType, PropertyValidators } from "convex/values";
|
|
14
|
+
import { ActionBuilder, FunctionVisibility, GenericActionCtx, GenericDataModel, GenericMutationCtx, GenericQueryCtx, MutationBuilder, QueryBuilder, RegisteredAction, RegisteredMutation, RegisteredQuery, UnvalidatedFunction } from "convex/server";
|
|
15
|
+
/**
|
|
16
|
+
* A modifier for a query, mutation, or action.
|
|
17
|
+
*
|
|
18
|
+
* This defines what arguments are required for the modifier, and how to modify
|
|
19
|
+
* the ctx and args. If the required args are not returned, they will not be
|
|
20
|
+
* provided for the modified function. All returned ctx and args will show up
|
|
21
|
+
* in the type signature for the modified function.
|
|
22
|
+
* To remove something from `ctx`, you can return it as `undefined`.
|
|
23
|
+
*/
|
|
24
|
+
export type Mod<Ctx extends Record<string, any>, ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>> = {
|
|
25
|
+
args: ModArgsValidator;
|
|
26
|
+
input: (ctx: Ctx, args: ObjectType<ModArgsValidator>) => Promise<{
|
|
27
|
+
ctx: ModCtx;
|
|
28
|
+
args: ModMadeArgs;
|
|
29
|
+
}> | {
|
|
30
|
+
ctx: ModCtx;
|
|
31
|
+
args: ModMadeArgs;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* A helper for defining a Mod when your mod doesn't need to add or remove
|
|
36
|
+
* anything from args.
|
|
37
|
+
* @param mod A function that defines how to modify the ctx.
|
|
38
|
+
* @returns A ctx delta to be applied to the original ctx.
|
|
39
|
+
*/
|
|
40
|
+
export declare function customCtx<InCtx extends Record<string, any>, OutCtx extends Record<string, any>>(mod: (original: InCtx) => Promise<OutCtx> | OutCtx): Mod<InCtx, {}, OutCtx, {}>;
|
|
41
|
+
/**
|
|
42
|
+
* A Mod that doesn't add or remove any context or args.
|
|
43
|
+
*/
|
|
44
|
+
export declare const NoOp: {
|
|
45
|
+
args: {};
|
|
46
|
+
input(): {
|
|
47
|
+
args: {};
|
|
48
|
+
ctx: {};
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* customQuery helps define custom behavior on top of `query` or `internalQuery`
|
|
53
|
+
* by passing a function that modifies the ctx and args.
|
|
54
|
+
*
|
|
55
|
+
* Example usage:
|
|
56
|
+
* ```js
|
|
57
|
+
* const myQueryBuilder = customQuery(query, {
|
|
58
|
+
* args: { sessionId: v.id("sessions") },
|
|
59
|
+
* input: async (ctx, args) => {
|
|
60
|
+
* const user = await getUserOrNull(ctx);
|
|
61
|
+
* const session = await db.get(sessionId);
|
|
62
|
+
* const db = wrapDatabaseReader({ user }, ctx.db, rlsRules);
|
|
63
|
+
* return { ctx: { db, user, session }, args: {} };
|
|
64
|
+
* },
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* // Using the custom builder
|
|
68
|
+
* export const getSomeData = myQueryBuilder({
|
|
69
|
+
* args: { someArg: v.string() },
|
|
70
|
+
* handler: async (ctx, args) => {
|
|
71
|
+
* const { db, user, session, scheduler } = ctx;
|
|
72
|
+
* const { someArg } = args;
|
|
73
|
+
* // ...
|
|
74
|
+
* }
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* Simple usage only modifying ctx:
|
|
79
|
+
* ```js
|
|
80
|
+
* const myInternalQuery = customQuery(
|
|
81
|
+
* internalQuery,
|
|
82
|
+
* customCtx(async (ctx) => {
|
|
83
|
+
* return {
|
|
84
|
+
* // Throws an exception if the user isn't logged in
|
|
85
|
+
* user: await getUserByTokenIdentifier(ctx),
|
|
86
|
+
* };
|
|
87
|
+
* })
|
|
88
|
+
* );
|
|
89
|
+
*
|
|
90
|
+
* // Using it
|
|
91
|
+
* export const getUser = myInternalQuery({
|
|
92
|
+
* args: {},
|
|
93
|
+
* handler: async (ctx, args) => {
|
|
94
|
+
* return ctx.user;
|
|
95
|
+
* },
|
|
96
|
+
* });
|
|
97
|
+
*
|
|
98
|
+
* @param query The query to be modified. Usually `query` or `internalQuery`
|
|
99
|
+
* from `_generated/server`.
|
|
100
|
+
* @param mod The modifier to be applied to the query, changing ctx and args.
|
|
101
|
+
* @returns A new query builder to define queries with modified ctx and args.
|
|
102
|
+
*/
|
|
103
|
+
export declare function customQuery<ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, Visibility extends FunctionVisibility, DataModel extends GenericDataModel>(query: QueryBuilder<DataModel, Visibility>, mod: Mod<GenericQueryCtx<DataModel>, ModArgsValidator, ModCtx, ModMadeArgs>): CustomBuilder<"query", ModArgsValidator, ModCtx, ModMadeArgs, GenericQueryCtx<DataModel>, Visibility>;
|
|
104
|
+
/**
|
|
105
|
+
* customMutation helps define custom behavior on top of `mutation`
|
|
106
|
+
* or `internalMutation` by passing a function that modifies the ctx and args.
|
|
107
|
+
*
|
|
108
|
+
* Example usage:
|
|
109
|
+
* ```js
|
|
110
|
+
* const myMutationBuilder = customMutation(mutation, {
|
|
111
|
+
* args: { sessionId: v.id("sessions") },
|
|
112
|
+
* input: async (ctx, args) => {
|
|
113
|
+
* const user = await getUserOrNull(ctx);
|
|
114
|
+
* const session = await db.get(sessionId);
|
|
115
|
+
* const db = wrapDatabaseReader({ user }, ctx.db, rlsRules);
|
|
116
|
+
* return { ctx: { db, user, session }, args: {} };
|
|
117
|
+
* },
|
|
118
|
+
* });
|
|
119
|
+
*
|
|
120
|
+
* // Using the custom builder
|
|
121
|
+
* export const setSomeData = myMutationBuilder({
|
|
122
|
+
* args: { someArg: v.string() },
|
|
123
|
+
* handler: async (ctx, args) => {
|
|
124
|
+
* const { db, user, session, scheduler } = ctx;
|
|
125
|
+
* const { someArg } = args;
|
|
126
|
+
* // ...
|
|
127
|
+
* }
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* Simple usage only modifying ctx:
|
|
132
|
+
* ```js
|
|
133
|
+
* const myUserMutation = customMutation(
|
|
134
|
+
* mutation,
|
|
135
|
+
* customCtx(async (ctx) => {
|
|
136
|
+
* return {
|
|
137
|
+
* // Throws an exception if the user isn't logged in
|
|
138
|
+
* user: await getUserByTokenIdentifier(ctx),
|
|
139
|
+
* };
|
|
140
|
+
* })
|
|
141
|
+
* );
|
|
142
|
+
*
|
|
143
|
+
* // Using it
|
|
144
|
+
* export const setMyName = myUserMutation({
|
|
145
|
+
* args: { name: v.string() },
|
|
146
|
+
* handler: async (ctx, args) => {
|
|
147
|
+
* await ctx.db.patch(ctx.user._id, { name: args.name });
|
|
148
|
+
* },
|
|
149
|
+
* });
|
|
150
|
+
*
|
|
151
|
+
* @param mutation The mutation to be modified. Usually `mutation` or `internalMutation`
|
|
152
|
+
* from `_generated/server`.
|
|
153
|
+
* @param mod The modifier to be applied to the mutation, changing ctx and args.
|
|
154
|
+
* @returns A new mutation builder to define queries with modified ctx and args.
|
|
155
|
+
*/
|
|
156
|
+
export declare function customMutation<ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, Visibility extends FunctionVisibility, DataModel extends GenericDataModel>(mutation: MutationBuilder<DataModel, Visibility>, mod: Mod<GenericMutationCtx<DataModel>, ModArgsValidator, ModCtx, ModMadeArgs>): CustomBuilder<"mutation", ModArgsValidator, ModCtx, ModMadeArgs, GenericMutationCtx<DataModel>, Visibility>;
|
|
157
|
+
/**
|
|
158
|
+
* customAction helps define custom behavior on top of `action`
|
|
159
|
+
* or `internalAction` by passing a function that modifies the ctx and args.
|
|
160
|
+
*
|
|
161
|
+
* Example usage:
|
|
162
|
+
* ```js
|
|
163
|
+
* const myActionBuilder = customAction(action, {
|
|
164
|
+
* args: { secretKey: v.string() },
|
|
165
|
+
* input: async (ctx, args) => {
|
|
166
|
+
* // Very basic authorization, e.g. from trusted backends.
|
|
167
|
+
* if (args.secretKey !== process.env.SECRET_KEY) {
|
|
168
|
+
* throw new Error("Invalid secret key");
|
|
169
|
+
* }
|
|
170
|
+
* const user = await ctx.runQuery(internal.users.getUser, {});
|
|
171
|
+
* return { ctx: { user }, args: {} };
|
|
172
|
+
* },
|
|
173
|
+
* });
|
|
174
|
+
*
|
|
175
|
+
* // Using the custom builder
|
|
176
|
+
* export const runSomeAction = myActionBuilder({
|
|
177
|
+
* args: { someArg: v.string() },
|
|
178
|
+
* handler: async (ctx, args) => {
|
|
179
|
+
* const { user, scheduler } = ctx;
|
|
180
|
+
* const { someArg } = args;
|
|
181
|
+
* // ...
|
|
182
|
+
* }
|
|
183
|
+
* });
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* Simple usage only modifying ctx:
|
|
187
|
+
* ```js
|
|
188
|
+
* const myUserAction = customAction(
|
|
189
|
+
* internalAction,
|
|
190
|
+
* customCtx(async (ctx) => {
|
|
191
|
+
* return {
|
|
192
|
+
* // Throws an exception if the user isn't logged in
|
|
193
|
+
* user: await ctx.runQuery(internal.users.getUser, {});
|
|
194
|
+
* };
|
|
195
|
+
* })
|
|
196
|
+
* );
|
|
197
|
+
*
|
|
198
|
+
* // Using it
|
|
199
|
+
* export const sendUserEmail = myUserAction({
|
|
200
|
+
* args: { subject: v.string(), body: v.string() },
|
|
201
|
+
* handler: async (ctx, args) => {
|
|
202
|
+
* await sendEmail(ctx.user.email, args.subject, args.body);
|
|
203
|
+
* },
|
|
204
|
+
* });
|
|
205
|
+
*
|
|
206
|
+
* @param action The action to be modified. Usually `action` or `internalAction`
|
|
207
|
+
* from `_generated/server`.
|
|
208
|
+
* @param mod The modifier to be applied to the action, changing ctx and args.
|
|
209
|
+
* @returns A new action builder to define queries with modified ctx and args.
|
|
210
|
+
*/
|
|
211
|
+
export declare function customAction<ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, Visibility extends FunctionVisibility, DataModel extends GenericDataModel>(action: ActionBuilder<DataModel, Visibility>, mod: Mod<GenericActionCtx<DataModel>, ModArgsValidator, ModCtx, ModMadeArgs>): CustomBuilder<"action", ModArgsValidator, ModCtx, ModMadeArgs, GenericActionCtx<DataModel>, Visibility>;
|
|
212
|
+
/**
|
|
213
|
+
* A Convex function (query, mutation, or action) to be registered for the API.
|
|
214
|
+
* Convenience to specify the registration type based on function type.
|
|
215
|
+
*/
|
|
216
|
+
type Registration<FuncType extends "query" | "mutation" | "action", Visibility extends FunctionVisibility, Args extends DefaultFunctionArgs, Output> = {
|
|
217
|
+
query: RegisteredQuery<Visibility, Args, Output>;
|
|
218
|
+
mutation: RegisteredMutation<Visibility, Args, Output>;
|
|
219
|
+
action: RegisteredAction<Visibility, Args, Output>;
|
|
220
|
+
}[FuncType];
|
|
221
|
+
/**
|
|
222
|
+
* A builder that customizes a Convex function using argument validation.
|
|
223
|
+
* e.g. `query({ args: {}, handler: async (ctx, args) => {} })`
|
|
224
|
+
*/
|
|
225
|
+
type ValidatedBuilder<FuncType extends "query" | "mutation" | "action", ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, InputCtx, Visibility extends FunctionVisibility> = <ExistingArgsValidator extends PropertyValidators, Output>(fn: {
|
|
226
|
+
args: ExistingArgsValidator;
|
|
227
|
+
handler: (ctx: InputCtx & ModCtx, args: ObjectType<ExistingArgsValidator> & ModMadeArgs) => Output;
|
|
228
|
+
}) => Registration<FuncType, Visibility, ObjectType<ExistingArgsValidator & ModArgsValidator>, Output>;
|
|
229
|
+
/**
|
|
230
|
+
* A builder that customizes a Convex function which doesn't validate arguments.
|
|
231
|
+
* e.g. `query(async (ctx, args) => {})`
|
|
232
|
+
* or `query({ handler: async (ctx, args) => {} })`
|
|
233
|
+
*/
|
|
234
|
+
type UnvalidatedBuilder<FuncType extends "query" | "mutation" | "action", ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, InputCtx, Visibility extends FunctionVisibility> = <Output, ExistingArgs extends DefaultFunctionArgs = DefaultFunctionArgs>(fn: UnvalidatedFunction<InputCtx & ModCtx, [
|
|
235
|
+
ExistingArgs & ModMadeArgs
|
|
236
|
+
], Output>) => Registration<FuncType, Visibility, ExistingArgs, Output>;
|
|
237
|
+
/**
|
|
238
|
+
* A builder that customizes a Convex function, whether or not it validates
|
|
239
|
+
* arguments. If the customization requires arguments, however, the resulting
|
|
240
|
+
* builder will require argument validation too.
|
|
241
|
+
*/
|
|
242
|
+
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>;
|
|
243
|
+
type EmptyObject = Record<string, never>;
|
|
244
|
+
type DefaultFunctionArgs = Record<string, unknown>;
|
|
245
|
+
export {};
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A helper for defining a Mod when your mod doesn't need to add or remove
|
|
3
|
+
* anything from args.
|
|
4
|
+
* @param mod A function that defines how to modify the ctx.
|
|
5
|
+
* @returns A ctx delta to be applied to the original ctx.
|
|
6
|
+
*/
|
|
7
|
+
export function customCtx(mod) {
|
|
8
|
+
return {
|
|
9
|
+
args: {},
|
|
10
|
+
input: async (ctx) => ({ ctx: await mod(ctx), args: {} }),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A Mod that doesn't add or remove any context or args.
|
|
15
|
+
*/
|
|
16
|
+
export const NoOp = {
|
|
17
|
+
args: {},
|
|
18
|
+
input() {
|
|
19
|
+
return { args: {}, ctx: {} };
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* customQuery helps define custom behavior on top of `query` or `internalQuery`
|
|
24
|
+
* by passing a function that modifies the ctx and args.
|
|
25
|
+
*
|
|
26
|
+
* Example usage:
|
|
27
|
+
* ```js
|
|
28
|
+
* const myQueryBuilder = customQuery(query, {
|
|
29
|
+
* args: { sessionId: v.id("sessions") },
|
|
30
|
+
* input: async (ctx, args) => {
|
|
31
|
+
* const user = await getUserOrNull(ctx);
|
|
32
|
+
* const session = await db.get(sessionId);
|
|
33
|
+
* const db = wrapDatabaseReader({ user }, ctx.db, rlsRules);
|
|
34
|
+
* return { ctx: { db, user, session }, args: {} };
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* // Using the custom builder
|
|
39
|
+
* export const getSomeData = myQueryBuilder({
|
|
40
|
+
* args: { someArg: v.string() },
|
|
41
|
+
* handler: async (ctx, args) => {
|
|
42
|
+
* const { db, user, session, scheduler } = ctx;
|
|
43
|
+
* const { someArg } = args;
|
|
44
|
+
* // ...
|
|
45
|
+
* }
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Simple usage only modifying ctx:
|
|
50
|
+
* ```js
|
|
51
|
+
* const myInternalQuery = customQuery(
|
|
52
|
+
* internalQuery,
|
|
53
|
+
* customCtx(async (ctx) => {
|
|
54
|
+
* return {
|
|
55
|
+
* // Throws an exception if the user isn't logged in
|
|
56
|
+
* user: await getUserByTokenIdentifier(ctx),
|
|
57
|
+
* };
|
|
58
|
+
* })
|
|
59
|
+
* );
|
|
60
|
+
*
|
|
61
|
+
* // Using it
|
|
62
|
+
* export const getUser = myInternalQuery({
|
|
63
|
+
* args: {},
|
|
64
|
+
* handler: async (ctx, args) => {
|
|
65
|
+
* return ctx.user;
|
|
66
|
+
* },
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* @param query The query to be modified. Usually `query` or `internalQuery`
|
|
70
|
+
* from `_generated/server`.
|
|
71
|
+
* @param mod The modifier to be applied to the query, changing ctx and args.
|
|
72
|
+
* @returns A new query builder to define queries with modified ctx and args.
|
|
73
|
+
*/
|
|
74
|
+
export function customQuery(query, mod) {
|
|
75
|
+
function customQueryBuilder(fn) {
|
|
76
|
+
// Looking forward to when input / args / ... are optional
|
|
77
|
+
const inputMod = mod.input ?? NoOp.input;
|
|
78
|
+
const inputArgs = mod.args ?? NoOp.args;
|
|
79
|
+
if ("args" in fn) {
|
|
80
|
+
return query({
|
|
81
|
+
args: {
|
|
82
|
+
...fn.args,
|
|
83
|
+
...inputArgs,
|
|
84
|
+
},
|
|
85
|
+
handler: async (ctx, allArgs) => {
|
|
86
|
+
const { split, rest } = splitArgs(inputArgs, allArgs);
|
|
87
|
+
const added = await inputMod(ctx, split);
|
|
88
|
+
return await fn.handler({ ...ctx, ...added.ctx }, { ...rest, ...added.args });
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (Object.keys(inputArgs).length > 0) {
|
|
93
|
+
throw new Error("If you're using a custom function with arguments for the input " +
|
|
94
|
+
"modifier, you must declare the arguments for the function too.");
|
|
95
|
+
}
|
|
96
|
+
const handler = fn.handler ?? fn;
|
|
97
|
+
return query({
|
|
98
|
+
handler: async (ctx, args) => {
|
|
99
|
+
const { ctx: modCtx } = await inputMod(ctx, args);
|
|
100
|
+
return await handler({ ...ctx, ...modCtx }, args);
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return customQueryBuilder;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* customMutation helps define custom behavior on top of `mutation`
|
|
108
|
+
* or `internalMutation` by passing a function that modifies the ctx and args.
|
|
109
|
+
*
|
|
110
|
+
* Example usage:
|
|
111
|
+
* ```js
|
|
112
|
+
* const myMutationBuilder = customMutation(mutation, {
|
|
113
|
+
* args: { sessionId: v.id("sessions") },
|
|
114
|
+
* input: async (ctx, args) => {
|
|
115
|
+
* const user = await getUserOrNull(ctx);
|
|
116
|
+
* const session = await db.get(sessionId);
|
|
117
|
+
* const db = wrapDatabaseReader({ user }, ctx.db, rlsRules);
|
|
118
|
+
* return { ctx: { db, user, session }, args: {} };
|
|
119
|
+
* },
|
|
120
|
+
* });
|
|
121
|
+
*
|
|
122
|
+
* // Using the custom builder
|
|
123
|
+
* export const setSomeData = myMutationBuilder({
|
|
124
|
+
* args: { someArg: v.string() },
|
|
125
|
+
* handler: async (ctx, args) => {
|
|
126
|
+
* const { db, user, session, scheduler } = ctx;
|
|
127
|
+
* const { someArg } = args;
|
|
128
|
+
* // ...
|
|
129
|
+
* }
|
|
130
|
+
* });
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* Simple usage only modifying ctx:
|
|
134
|
+
* ```js
|
|
135
|
+
* const myUserMutation = customMutation(
|
|
136
|
+
* mutation,
|
|
137
|
+
* customCtx(async (ctx) => {
|
|
138
|
+
* return {
|
|
139
|
+
* // Throws an exception if the user isn't logged in
|
|
140
|
+
* user: await getUserByTokenIdentifier(ctx),
|
|
141
|
+
* };
|
|
142
|
+
* })
|
|
143
|
+
* );
|
|
144
|
+
*
|
|
145
|
+
* // Using it
|
|
146
|
+
* export const setMyName = myUserMutation({
|
|
147
|
+
* args: { name: v.string() },
|
|
148
|
+
* handler: async (ctx, args) => {
|
|
149
|
+
* await ctx.db.patch(ctx.user._id, { name: args.name });
|
|
150
|
+
* },
|
|
151
|
+
* });
|
|
152
|
+
*
|
|
153
|
+
* @param mutation The mutation to be modified. Usually `mutation` or `internalMutation`
|
|
154
|
+
* from `_generated/server`.
|
|
155
|
+
* @param mod The modifier to be applied to the mutation, changing ctx and args.
|
|
156
|
+
* @returns A new mutation builder to define queries with modified ctx and args.
|
|
157
|
+
*/
|
|
158
|
+
export function customMutation(mutation, mod) {
|
|
159
|
+
function customMutationBuilder(fn) {
|
|
160
|
+
// Looking forward to when input / args / ... are optional
|
|
161
|
+
const inputMod = mod.input ?? NoOp.input;
|
|
162
|
+
const inputArgs = mod.args ?? NoOp.args;
|
|
163
|
+
if ("args" in fn) {
|
|
164
|
+
return mutation({
|
|
165
|
+
args: {
|
|
166
|
+
...fn.args,
|
|
167
|
+
...inputArgs,
|
|
168
|
+
},
|
|
169
|
+
handler: async (ctx, allArgs) => {
|
|
170
|
+
const { split, rest } = splitArgs(inputArgs, allArgs);
|
|
171
|
+
const added = await inputMod(ctx, split);
|
|
172
|
+
return await fn.handler({ ...ctx, ...added.ctx }, { ...rest, ...added.args });
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (Object.keys(inputArgs).length > 0) {
|
|
177
|
+
throw new Error("If you're using a custom function with arguments for the input " +
|
|
178
|
+
"modifier, you must declare the arguments for the function too.");
|
|
179
|
+
}
|
|
180
|
+
const handler = fn.handler ?? fn;
|
|
181
|
+
return mutation({
|
|
182
|
+
handler: async (ctx, args) => {
|
|
183
|
+
const { ctx: modCtx } = await inputMod(ctx, args);
|
|
184
|
+
return await handler({ ...ctx, ...modCtx }, args);
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return customMutationBuilder;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* customAction helps define custom behavior on top of `action`
|
|
192
|
+
* or `internalAction` by passing a function that modifies the ctx and args.
|
|
193
|
+
*
|
|
194
|
+
* Example usage:
|
|
195
|
+
* ```js
|
|
196
|
+
* const myActionBuilder = customAction(action, {
|
|
197
|
+
* args: { secretKey: v.string() },
|
|
198
|
+
* input: async (ctx, args) => {
|
|
199
|
+
* // Very basic authorization, e.g. from trusted backends.
|
|
200
|
+
* if (args.secretKey !== process.env.SECRET_KEY) {
|
|
201
|
+
* throw new Error("Invalid secret key");
|
|
202
|
+
* }
|
|
203
|
+
* const user = await ctx.runQuery(internal.users.getUser, {});
|
|
204
|
+
* return { ctx: { user }, args: {} };
|
|
205
|
+
* },
|
|
206
|
+
* });
|
|
207
|
+
*
|
|
208
|
+
* // Using the custom builder
|
|
209
|
+
* export const runSomeAction = myActionBuilder({
|
|
210
|
+
* args: { someArg: v.string() },
|
|
211
|
+
* handler: async (ctx, args) => {
|
|
212
|
+
* const { user, scheduler } = ctx;
|
|
213
|
+
* const { someArg } = args;
|
|
214
|
+
* // ...
|
|
215
|
+
* }
|
|
216
|
+
* });
|
|
217
|
+
* ```
|
|
218
|
+
*
|
|
219
|
+
* Simple usage only modifying ctx:
|
|
220
|
+
* ```js
|
|
221
|
+
* const myUserAction = customAction(
|
|
222
|
+
* internalAction,
|
|
223
|
+
* customCtx(async (ctx) => {
|
|
224
|
+
* return {
|
|
225
|
+
* // Throws an exception if the user isn't logged in
|
|
226
|
+
* user: await ctx.runQuery(internal.users.getUser, {});
|
|
227
|
+
* };
|
|
228
|
+
* })
|
|
229
|
+
* );
|
|
230
|
+
*
|
|
231
|
+
* // Using it
|
|
232
|
+
* export const sendUserEmail = myUserAction({
|
|
233
|
+
* args: { subject: v.string(), body: v.string() },
|
|
234
|
+
* handler: async (ctx, args) => {
|
|
235
|
+
* await sendEmail(ctx.user.email, args.subject, args.body);
|
|
236
|
+
* },
|
|
237
|
+
* });
|
|
238
|
+
*
|
|
239
|
+
* @param action The action to be modified. Usually `action` or `internalAction`
|
|
240
|
+
* from `_generated/server`.
|
|
241
|
+
* @param mod The modifier to be applied to the action, changing ctx and args.
|
|
242
|
+
* @returns A new action builder to define queries with modified ctx and args.
|
|
243
|
+
*/
|
|
244
|
+
export function customAction(action, mod) {
|
|
245
|
+
function customActionBuilder(fn) {
|
|
246
|
+
// Looking forward to when input / args / ... are optional
|
|
247
|
+
const inputMod = mod.input ?? NoOp.input;
|
|
248
|
+
const inputArgs = mod.args ?? NoOp.args;
|
|
249
|
+
if ("args" in fn) {
|
|
250
|
+
return action({
|
|
251
|
+
args: {
|
|
252
|
+
...fn.args,
|
|
253
|
+
...inputArgs,
|
|
254
|
+
},
|
|
255
|
+
handler: async (ctx, allArgs) => {
|
|
256
|
+
const { split, rest } = splitArgs(inputArgs, allArgs);
|
|
257
|
+
const added = await inputMod(ctx, split);
|
|
258
|
+
return await fn.handler({ ...ctx, ...added.ctx }, { ...rest, ...added.args });
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (Object.keys(inputArgs).length > 0) {
|
|
263
|
+
throw new Error("If you're using a custom function with arguments for the input " +
|
|
264
|
+
"modifier, you must declare the arguments for the function too.");
|
|
265
|
+
}
|
|
266
|
+
const handler = fn.handler ?? fn;
|
|
267
|
+
return action({
|
|
268
|
+
handler: async (ctx, args) => {
|
|
269
|
+
const { ctx: modCtx } = await inputMod(ctx, args);
|
|
270
|
+
return await handler({ ...ctx, ...modCtx }, args);
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return customActionBuilder;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
*
|
|
278
|
+
* @param splitArgsValidator The args that should be split out from the rest.
|
|
279
|
+
* As an object mapping arg names to validators (v.* from convex/values).
|
|
280
|
+
* @param args The arguments to a function, including values to be split out.
|
|
281
|
+
* @returns The args split into two objects: `split` and `rest` based on keys.
|
|
282
|
+
*/
|
|
283
|
+
function splitArgs(splitArgsValidator, args) {
|
|
284
|
+
const rest = {};
|
|
285
|
+
const split = {};
|
|
286
|
+
for (const arg in args) {
|
|
287
|
+
if (arg in splitArgsValidator) {
|
|
288
|
+
split[arg] = args[arg];
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
rest[arg] = args[arg];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return { split, rest };
|
|
295
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|