convex-helpers 0.1.24 → 0.1.26

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 CHANGED
@@ -18,6 +18,7 @@ define custom behavior, allowing you to:
18
18
  See the associated [Stack Post](https://stack.convex.dev/custom-functions)
19
19
 
20
20
  For example:
21
+
21
22
  ```js
22
23
  import { customQuery } from "convex-helpers/server/customFunctions.js
23
24
 
@@ -49,6 +50,7 @@ See the [Stack post on relationship helpers](https://stack.convex.dev/functional
49
50
  and the [relationship schema structures post](https://stack.convex.dev/relationship-structures-let-s-talk-about-schemas).
50
51
 
51
52
  Example:
53
+
52
54
  ```js
53
55
  import {
54
56
  getOneFromOrThrow,
@@ -66,7 +68,11 @@ const posts = await asyncMap(
66
68
  const comments = await getManyFrom(db, "comments", "postId", post._id);
67
69
  // many-to-many via join table
68
70
  const categories = await getManyViaOrThrow(
69
- db, "postCategories", "categoryId", "postId", post._id
71
+ db,
72
+ "postCategories",
73
+ "categoryId",
74
+ "postId",
75
+ post._id
70
76
  );
71
77
  return { ...post, comments, categories };
72
78
  }
@@ -86,46 +92,51 @@ See the associated [Stack post](https://stack.convex.dev/track-sessions-without-
86
92
  Example for a query (action & mutation are similar):
87
93
 
88
94
  In your React's root, add the `SessionProvider`:
95
+
89
96
  ```js
90
97
  import { SessionProvider } from "convex-helpers/react/sessions";
91
98
  //...
92
99
  <ConvexProvider client={convex}>
93
- <SessionProvider>
94
- <App />
95
- </SessionProvider>
96
- </ConvexProvider>
100
+ <SessionProvider>
101
+ <App />
102
+ </SessionProvider>
103
+ </ConvexProvider>;
97
104
  ```
98
105
 
99
106
  Pass the session ID from the client automatically to a server query:
107
+
100
108
  ```js
101
- import { useSessionQuery } from "convex-helpers/react/sessions";
109
+ import { useSessionQuery } from "convex-helpers/react/sessions";
102
110
 
103
111
  const results = useSessionQuery(api.myModule.mySessionQuery, { arg1: 1 });
104
112
  ```
105
113
 
106
114
  Define a server query function in `convex/myModule.ts`:
115
+
107
116
  ```js
108
117
  export const mySessionQuery = queryWithSession({
109
- args: { arg1: v.number() },
110
- handler: async (ctx, args) => {
111
- // ctx.anonymousUser
112
- }
113
- })
118
+ args: { arg1: v.number() },
119
+ handler: async (ctx, args) => {
120
+ // ctx.anonymousUser
121
+ },
122
+ });
114
123
  ```
115
124
 
116
125
  Using `customQuery` to make `queryWithSession`:
126
+
117
127
  ```js
118
128
  import { customQuery } from "convex-helpers/server/customFunctions";
119
129
  import { SessionIdArg } from "convex-helpers/server/sessions";
120
130
 
121
131
  export const queryWithSession = customQuery(query, {
122
- args: SessionIdArg,
123
- input: async (ctx, { sessionId }) => {
124
- const anonymousUser = await getAnonUser(ctx, sessionId);
125
- return { ctx: { ...ctx, anonymousUser }, args: {} };
126
- },
132
+ args: SessionIdArg,
133
+ input: async (ctx, { sessionId }) => {
134
+ const anonymousUser = await getAnonUser(ctx, sessionId);
135
+ return { ctx: { ...ctx, anonymousUser }, args: {} };
136
+ },
127
137
  });
128
138
  ```
139
+
129
140
  **Note:** `getAnonUser` is some function you write to look up a user by session.
130
141
 
131
142
  ## Row-level security
@@ -145,6 +156,7 @@ features for validating arguments, this is for you!
145
156
  See the [Stack post on Zod validation](https://stack.convex.dev/wrappers-as-middleware-zod-validation) to see how to validate your Convex functions using the [zod](https://www.npmjs.com/package/zod) library.
146
157
 
147
158
  Example:
159
+
148
160
  ```js
149
161
  import { z } from "zod";
150
162
  import { zCustomQuery, zid } from "convex-helpers/server/zod";
@@ -177,8 +189,8 @@ export const myComplexQuery = zodQuery({
177
189
  //... args at this point has been validated and has the types of what
178
190
  // zod parses the values into.
179
191
  // e.g. boolWithDefault is `bool` but has an input type `bool | undefined`.
180
- }
181
- })
192
+ },
193
+ });
182
194
  ```
183
195
 
184
196
  ## Hono for advanced HTTP endpoint definitions
@@ -190,6 +202,7 @@ HTTP api endpoints easily
190
202
  See the [guide on Stack](https://stack.convex.dev/hono-with-convex) for tips on using Hono for HTTP endpoints.
191
203
 
192
204
  To use it, put this in your `convex/http.ts` file:
205
+
193
206
  ```ts
194
207
  import {
195
208
  Hono,
@@ -223,6 +236,7 @@ these functions for a given table:
223
236
  **Note: I recommend only doing this for prototyping or [internal functions](https://docs.convex.dev/functions/internal-functions)**
224
237
 
225
238
  Example:
239
+
226
240
  ```ts
227
241
 
228
242
  // in convex/users.ts
@@ -249,22 +263,26 @@ When using validators for defining database schema or function arguments,
249
263
  these validators help:
250
264
 
251
265
  1. Add a `Table` utility that defines a table and keeps references to the fields
252
- to avoid re-defining validators. To learn more about sharing validators, read
253
- [this article](https://stack.convex.dev/argument-validation-without-repetition),
254
- an extension of [this article](https://stack.convex.dev/types-cookbook).
266
+ to avoid re-defining validators. To learn more about sharing validators, read
267
+ [this article](https://stack.convex.dev/argument-validation-without-repetition),
268
+ an extension of [this article](https://stack.convex.dev/types-cookbook).
255
269
  2. Add utilties for partial, pick and omit to match the TypeScript type
256
- utilities.
270
+ utilities.
257
271
  3. Add shorthand for a union of `literals`, a `nullable` field, a `deprecated`
258
- field, and `brandedString`. To learn more about branded strings see
259
- [this article](https://stack.convex.dev/using-branded-types-in-validators).
272
+ field, and `brandedString`. To learn more about branded strings see
273
+ [this article](https://stack.convex.dev/using-branded-types-in-validators).
260
274
  4. Make the validators look more like TypeScript types, even though they're
261
- runtime values. (This is controvercial and not required to use the above).
275
+ runtime values. (This is controvercial and not required to use the above).
262
276
 
263
277
  Example:
278
+
264
279
  ```js
265
280
  import { Table } from "convex-helpers/server";
266
281
  import {
267
- literals, partial, deprecated, brandedString,
282
+ literals,
283
+ partial,
284
+ deprecated,
285
+ brandedString,
268
286
  } from "convex-helpers/validators";
269
287
  import { omit, pick } from "convex-helpers";
270
288
  import { Infer } from "convex/values";
@@ -309,3 +327,39 @@ const balanceAndEmail = pick(Account.withoutSystemFields, ["balance", "email"]);
309
327
  // A validator for all the fields except balance.
310
328
  const accountWithoutBalance = omit(Account.withSystemFields, ["balance"]);
311
329
  ```
330
+
331
+ ## Filter
332
+
333
+ See the [guide on Stack](https://stack.convex.dev/complex-filters-in-convex)
334
+ for an analysis of complex filters on Convex.
335
+
336
+ The `filter` helper composes with `ctx.db.query` to apply arbitrary TypeScript
337
+ or JavaScript filters to a database query.
338
+
339
+ Examples:
340
+
341
+ ```js
342
+ import { filter } from "convex-helpers/server/filter";
343
+
344
+ export const evens = query({
345
+ args: {},
346
+ handler: async (ctx) => {
347
+ return await filter(
348
+ ctx.db.query("counter_table"),
349
+ (c) => c.counter % 2 === 0
350
+ ).collect();
351
+ },
352
+ });
353
+
354
+ export const lastCountLongerThanName = query({
355
+ args: {},
356
+ handler: async (ctx) => {
357
+ return await filter(
358
+ ctx.db.query("counter_table"),
359
+ (c) => c.counter > c.name.length
360
+ )
361
+ .order("desc")
362
+ .first();
363
+ },
364
+ });
365
+ ```
@@ -29,15 +29,18 @@ type SessionArgsArray<Fn extends SessionFunction<"mutation" | "action">> = keyof
29
29
  /**
30
30
  * Context for a Convex session, creating a server session and providing the id.
31
31
  *
32
- * @param props - Where you want your session ID to be persisted. Roughly:
32
+ * @param useStorage - Where you want your session ID to be persisted. Roughly:
33
33
  * - sessionStorage is saved per-tab
34
34
  * - localStorage is shared between tabs, but not browser profiles.
35
+ * @param storageKey - Key under which to store the session ID in the store
36
+ * @param idGenerator - Function to return a new, unique session ID string. Defaults to crypto.randomUUID
35
37
  * @returns A provider to wrap your React nodes which provides the session ID.
36
38
  * To be used with useSessionQuery and useSessionMutation.
37
39
  */
38
40
  export declare const SessionProvider: React.FC<{
39
41
  useStorage?: UseStorage<SessionId>;
40
42
  storageKey?: string;
43
+ idGenerator?: () => string;
41
44
  children?: React.ReactNode;
42
45
  }>;
43
46
  export declare function useSessionQuery<Query extends SessionFunction<"query">>(query: Query, ...args: SessionQueryArgsArray<Query>): FunctionReturnType<Query> | undefined;
@@ -23,20 +23,23 @@ const SessionContext = React.createContext(null);
23
23
  /**
24
24
  * Context for a Convex session, creating a server session and providing the id.
25
25
  *
26
- * @param props - Where you want your session ID to be persisted. Roughly:
26
+ * @param useStorage - Where you want your session ID to be persisted. Roughly:
27
27
  * - sessionStorage is saved per-tab
28
28
  * - localStorage is shared between tabs, but not browser profiles.
29
+ * @param storageKey - Key under which to store the session ID in the store
30
+ * @param idGenerator - Function to return a new, unique session ID string. Defaults to crypto.randomUUID
29
31
  * @returns A provider to wrap your React nodes which provides the session ID.
30
32
  * To be used with useSessionQuery and useSessionMutation.
31
33
  */
32
- export const SessionProvider = ({ useStorage, storageKey, children }) => {
34
+ export const SessionProvider = ({ useStorage, storageKey, idGenerator, children }) => {
33
35
  const storeKey = storageKey ?? "convex-session-id";
34
- const initialId = useMemo(() => crypto.randomUUID(), []);
36
+ const idGen = idGenerator ?? crypto.randomUUID.bind(crypto);
37
+ const initialId = useMemo(() => idGen(), []);
35
38
  // Get or set the ID from our desired storage location.
36
39
  const useStorageOrDefault = useStorage ?? useSessionStorage;
37
40
  const [sessionId, setSessionId] = useStorageOrDefault(storeKey, initialId);
38
41
  const refreshSessionId = useCallback(async (beforeUpdate) => {
39
- const newSessionId = crypto.randomUUID();
42
+ const newSessionId = idGen();
40
43
  if (beforeUpdate) {
41
44
  await beforeUpdate(newSessionId);
42
45
  }
@@ -1,5 +1,5 @@
1
1
  import { ZodTypeDef, z } from "zod";
2
- import { v, GenericId, ObjectType, PropertyValidators, Validator } from "convex/values";
2
+ import { 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
5
  import { EmptyObject } from "..";
@@ -194,9 +194,19 @@ type ValidatedBuilder<FuncType extends "query" | "mutation" | "action", ModArgsV
194
194
  * builder will require argument validation too.
195
195
  */
196
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>;
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<{
197
+ /**
198
+ * START Include some types from convex that aren't exported currently.
199
+ */
200
+ type JoinFieldPaths<Start extends string, End extends string> = `${Start}.${End}`;
201
+ type ObjectValidator<Validators extends PropertyValidators> = Validator<ObjectType<Validators>, false, {
202
+ [Property in keyof Validators]: JoinFieldPaths<Property & string, Validators[Property]["fieldPaths"]> | Property;
203
+ }[keyof Validators] & string>;
204
+ /**
205
+ * END types copied from Convex. Delete when Convex exports these types.
206
+ */
207
+ 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> ? ObjectValidator<{
198
208
  [key in keyof ZodShape]: ConvexValidatorFromZod<ZodShape[key]>;
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;
209
+ }> : 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;
200
210
  /**
201
211
  * Turn a Zod validator into a Convex Validator.
202
212
  * @param zod Zod validator can be a Zod object, or a Zod type like `z.string()`
@@ -0,0 +1,33 @@
1
+ import { ConvexClient } from "convex/browser";
2
+ import { FunctionArgs, FunctionReference, FunctionReturnType, UserIdentity } from "convex/server";
3
+ /**
4
+ * This is a helper for testing Convex functions against a locally running backend.
5
+ *
6
+ * An example of calling a function:
7
+ * ```
8
+ * const t = new ConvexTestingHelper();
9
+ * const result = await t.query(api.foo.bar, { arg1: "baz" })
10
+ * ```
11
+ *
12
+ * An example of calling a function with auth:
13
+ * ```
14
+ * const t = new ConvexTestingHelper();
15
+ * const identityA = t.newIdentity({ name: "Person A"})
16
+ * const result = await t.withIdentity(identityA).query(api.users.getProfile);
17
+ * ```
18
+ */
19
+ export declare class ConvexTestingHelper {
20
+ private _nextSubjectId;
21
+ client: ConvexClient;
22
+ private _adminKey;
23
+ constructor(options?: {
24
+ adminKey?: string;
25
+ backendUrl?: string;
26
+ });
27
+ newIdentity(args: Partial<Omit<UserIdentity, "tokenIdentifier">>): Omit<UserIdentity, "tokenIdentifier">;
28
+ withIdentity(identity: Omit<UserIdentity, "tokenIdentifier">): Pick<ConvexClient, "mutation" | "action" | "query">;
29
+ mutation<Mutation extends FunctionReference<"mutation">>(mutation: Mutation, args: FunctionArgs<Mutation>): Promise<Awaited<FunctionReturnType<Mutation>>>;
30
+ query<Query extends FunctionReference<"query", "public">>(query: Query, args: FunctionArgs<Query>): Promise<Awaited<FunctionReturnType<Query>>>;
31
+ action<Action extends FunctionReference<"action">>(action: Action, args: FunctionArgs<Action>): Promise<Awaited<FunctionReturnType<Action>>>;
32
+ close(): Promise<void>;
33
+ }
@@ -0,0 +1,73 @@
1
+ import { ConvexClient } from "convex/browser";
2
+ /**
3
+ * This is a helper for testing Convex functions against a locally running backend.
4
+ *
5
+ * An example of calling a function:
6
+ * ```
7
+ * const t = new ConvexTestingHelper();
8
+ * const result = await t.query(api.foo.bar, { arg1: "baz" })
9
+ * ```
10
+ *
11
+ * An example of calling a function with auth:
12
+ * ```
13
+ * const t = new ConvexTestingHelper();
14
+ * const identityA = t.newIdentity({ name: "Person A"})
15
+ * const result = await t.withIdentity(identityA).query(api.users.getProfile);
16
+ * ```
17
+ */
18
+ export class ConvexTestingHelper {
19
+ _nextSubjectId = 0;
20
+ client;
21
+ _adminKey;
22
+ constructor(options = {}) {
23
+ this.client = new ConvexClient(options.backendUrl ?? "http://127.0.0.1:3210");
24
+ this._adminKey =
25
+ options.adminKey ??
26
+ // default admin key for local backends - from https://github.com/get-convex/convex-backend/blob/main/Justfile
27
+ "0135d8598650f8f5cb0f30c34ec2e2bb62793bc28717c8eb6fb577996d50be5f4281b59181095065c5d0f86a2c31ddbe9b597ec62b47ded69782cd";
28
+ }
29
+ newIdentity(args) {
30
+ const subject = `test subject ${this._nextSubjectId}`;
31
+ this._nextSubjectId += 1;
32
+ const issuer = "test issuer";
33
+ return {
34
+ ...args,
35
+ subject,
36
+ issuer,
37
+ };
38
+ }
39
+ withIdentity(identity) {
40
+ return {
41
+ mutation: (functionReference, args) => {
42
+ this.client.setAdminAuth(this._adminKey, identity);
43
+ return this.client.mutation(functionReference, args).finally(() => {
44
+ this.client.client.clearAuth();
45
+ });
46
+ },
47
+ action: (functionReference, args) => {
48
+ this.client.setAdminAuth(this._adminKey, identity);
49
+ return this.client.action(functionReference, args).finally(() => {
50
+ this.client.client.clearAuth();
51
+ });
52
+ },
53
+ query: (functionReference, args) => {
54
+ this.client.setAdminAuth(this._adminKey, identity);
55
+ return this.client.query(functionReference, args).finally(() => {
56
+ this.client.client.clearAuth();
57
+ });
58
+ },
59
+ };
60
+ }
61
+ async mutation(mutation, args) {
62
+ return this.client.mutation(mutation, args);
63
+ }
64
+ async query(query, args) {
65
+ return this.client.query(query, args);
66
+ }
67
+ async action(action, args) {
68
+ return this.client.action(action, args);
69
+ }
70
+ async close() {
71
+ return this.client.close();
72
+ }
73
+ }