convex-helpers
Advanced tools
Comparing version 0.1.57-alpha.3 to 0.1.57
{ | ||
"name": "convex-helpers", | ||
"version": "0.1.57-alpha.3", | ||
"version": "0.1.57", | ||
"description": "A collection of useful code to complement the official convex package.", | ||
@@ -125,3 +125,3 @@ "type": "module", | ||
"peerDependencies": { | ||
"convex": "^1.13.0 || >=1.16.0-alpha.1", | ||
"convex": "^1.13.0", | ||
"hono": "^4.0.5", | ||
@@ -128,0 +128,0 @@ "react": "^17.0.2 || ^18.0.0", |
import { convexTest } from "convex-test"; | ||
import { expect, test } from "vitest"; | ||
import { crud } from "."; | ||
import { crud } from "../server.js"; | ||
import { | ||
@@ -16,2 +16,3 @@ anyApi, | ||
import { modules } from "./setup.test"; | ||
import { customCtx, customMutation, customQuery } from "./customFunctions.js"; | ||
@@ -78,1 +79,53 @@ const ExampleFields = { | ||
}); | ||
/** | ||
* Custom function tests | ||
*/ | ||
const customQ = customQuery( | ||
internalQuery, | ||
customCtx((ctx) => ({ foo: "bar" })), | ||
); | ||
const customM = customMutation( | ||
internalMutation, | ||
customCtx((ctx) => ({})), | ||
); | ||
const customCrud = crud( | ||
{ | ||
name: CrudTable, | ||
_id: v.id(CrudTable), | ||
withoutSystemFields: ExampleFields, | ||
}, | ||
customQ, | ||
customM, | ||
); | ||
const customTestApi: ApiFromModules<{ | ||
fns: { | ||
create: typeof customCrud.create; | ||
read: typeof customCrud.read; | ||
update: typeof customCrud.update; | ||
paginate: typeof customCrud.paginate; | ||
destroy: typeof customCrud.destroy; | ||
}; | ||
}>["fns"] = anyApi["crud.test"] as any; | ||
test("custom crud for table", async () => { | ||
const t = convexTest(schema, modules); | ||
const doc = await t.mutation(customTestApi.create, { foo: "", bar: null }); | ||
expect(doc).toMatchObject({ foo: "", bar: null }); | ||
const read = await t.query(customTestApi.read, { id: doc._id }); | ||
expect(read).toMatchObject(doc); | ||
await t.mutation(customTestApi.update, { | ||
id: doc._id, | ||
patch: { foo: "new", bar: { n: 42 }, baz: true }, | ||
}); | ||
expect(await t.query(customTestApi.read, { id: doc._id })).toMatchObject({ | ||
foo: "new", | ||
bar: { n: 42 }, | ||
baz: true, | ||
}); | ||
await t.mutation(customTestApi.destroy, { id: doc._id }); | ||
expect(await t.query(customTestApi.read, { id: doc._id })).toBe(null); | ||
}); |
@@ -13,3 +13,3 @@ /** | ||
*/ | ||
import { GenericValidator, ObjectType, PropertyValidators } from "convex/values"; | ||
import { GenericValidator, ObjectType, PropertyValidators, Validator } from "convex/values"; | ||
import { ActionBuilder, ArgsArrayForOptionalValidator, ArgsArrayToObject, DefaultArgsForOptionalValidator, DefaultFunctionArgs, FunctionVisibility, GenericActionCtx, GenericDataModel, GenericMutationCtx, GenericQueryCtx, MutationBuilder, QueryBuilder, RegisteredAction, RegisteredMutation, RegisteredQuery, ReturnValueForOptionalValidator } from "convex/server"; | ||
@@ -232,3 +232,3 @@ /** | ||
export type CustomBuilder<FuncType extends "query" | "mutation" | "action", ModArgsValidator extends PropertyValidators, ModCtx extends Record<string, any>, ModMadeArgs extends Record<string, any>, InputCtx, Visibility extends FunctionVisibility> = { | ||
<ArgsValidator extends PropertyValidators | void, ReturnsValidator extends PropertyValidators | GenericValidator | void, ReturnValue extends ReturnValueForOptionalValidator<ReturnsValidator> = any, OneOrZeroArgs extends ArgsArrayForOptionalValidator<ArgsValidator> = DefaultArgsForOptionalValidator<ArgsValidator>>(func: { | ||
<ArgsValidator extends PropertyValidators | void | Validator<any, any, any>, ReturnsValidator extends PropertyValidators | GenericValidator | void, ReturnValue extends ReturnValueForOptionalValidator<ReturnsValidator> = any, OneOrZeroArgs extends ArgsArrayForOptionalValidator<ArgsValidator> = DefaultArgsForOptionalValidator<ArgsValidator>>(func: { | ||
args?: ArgsValidator; | ||
@@ -239,3 +239,3 @@ returns?: ReturnsValidator; | ||
(ctx: Overwrite<InputCtx, ModCtx>, ...args: ArgsForHandlerType<OneOrZeroArgs, ModMadeArgs>): ReturnValue; | ||
}): Registration<FuncType, Visibility, ArgsArrayToObject<OneOrZeroArgs extends [infer A] ? [Expand<A & ObjectType<ModArgsValidator>>] : [ObjectType<ModArgsValidator>]>, ReturnValue>; | ||
}): Registration<FuncType, Visibility, ArgsArrayToObject<ModArgsValidator extends Record<string, never> ? OneOrZeroArgs : OneOrZeroArgs extends [infer A] ? [Expand<A & ObjectType<ModArgsValidator>>] : [ObjectType<ModArgsValidator>]>, ReturnValue>; | ||
}; | ||
@@ -242,0 +242,0 @@ export type CustomCtx<Builder> = Builder extends CustomBuilder<any, any, infer ModCtx, any, infer InputCtx, any> ? Overwrite<InputCtx, ModCtx> : never; |
@@ -0,1 +1,14 @@ | ||
/** | ||
* This file contains helpers for defining custom functions that modify the | ||
* context and arguments of a Convex function. Allows you to: | ||
* | ||
* - Run authentication logic before the request starts. | ||
* - Look up commonly used data and add it to the ctx argument. | ||
* - Replace a ctx or argument field with a different value, such as a version | ||
* of `db` that runs custom functions on data access. | ||
* - Consume arguments from the client that are not passed to the query, such | ||
* as taking in an authentication parameter like an API key or session ID. | ||
* These arguments must be sent up by the client along with each request. | ||
*/ | ||
import { asObjectValidator, v, } from "convex/values"; | ||
import { pick } from "../index.js"; | ||
@@ -198,6 +211,3 @@ /** | ||
return builder({ | ||
args: { | ||
...fn.args, | ||
...inputArgs, | ||
}, | ||
args: addArgs(fn.args, inputArgs), | ||
returns: fn.returns, | ||
@@ -224,1 +234,17 @@ handler: async (ctx, allArgs) => { | ||
} | ||
// Adds args to a property validator or validator | ||
// Needs to call recursively in the case of unions. | ||
function addArgs(validatorOrPropertyValidator, args) { | ||
if (Object.keys(args).length === 0) { | ||
return asObjectValidator(validatorOrPropertyValidator); | ||
} | ||
const validator = asObjectValidator(validatorOrPropertyValidator); | ||
switch (validator.kind) { | ||
case "object": | ||
return v.object({ ...validator.fields, ...args }); | ||
case "union": | ||
return v.union(...validator.members.map((m) => addArgs(m, args))); | ||
default: | ||
throw new Error("Cannot add arguments to a validator that is not an object or union."); | ||
} | ||
} |
@@ -1,3 +0,4 @@ | ||
import { Equals, assert } from ".."; | ||
import { Equals, assert } from "../index.js"; | ||
import { | ||
customAction, | ||
CustomCtx, | ||
@@ -7,7 +8,9 @@ customCtx, | ||
customQuery, | ||
} from "./customFunctions"; | ||
import { wrapDatabaseWriter } from "./rowLevelSecurity"; | ||
import { SessionId, vSessionId } from "./sessions"; | ||
} from "./customFunctions.js"; | ||
import { wrapDatabaseWriter } from "./rowLevelSecurity.js"; | ||
import { SessionId, vSessionId } from "./sessions.js"; | ||
import { convexTest } from "convex-test"; | ||
import { | ||
ActionBuilder, | ||
actionGeneric, | ||
anyApi, | ||
@@ -27,3 +30,3 @@ DataModelFromSchemaDefinition, | ||
import { afterEach, beforeEach, describe, expect, test } from "vitest"; | ||
import { modules } from "./setup.test"; | ||
import { modules } from "./setup.test.js"; | ||
@@ -39,2 +42,3 @@ const schema = defineSchema({ | ||
const mutation = mutationGeneric as MutationBuilder<DataModel, "public">; | ||
const action = actionGeneric as ActionBuilder<DataModel, "public">; | ||
@@ -121,2 +125,20 @@ const authenticatedQueryBuilder = customQuery( | ||
/** | ||
* Testing that it conforms to query, mutation, action types when no args | ||
* are added | ||
*/ | ||
customQuery( | ||
query, | ||
customCtx((ctx) => ({ foo: "bar" })), | ||
) satisfies typeof query; | ||
customMutation( | ||
mutation, | ||
customCtx((ctx) => ({})), | ||
) satisfies typeof mutation; | ||
customAction( | ||
action, | ||
customCtx((ctx) => ({})), | ||
) satisfies typeof action; | ||
/** | ||
* Testing custom function modifications. | ||
@@ -123,0 +145,0 @@ */ |
@@ -17,2 +17,5 @@ /** | ||
PropertyValidators, | ||
Validator, | ||
asObjectValidator, | ||
v, | ||
} from "convex/values"; | ||
@@ -332,6 +335,3 @@ import { | ||
return builder({ | ||
args: { | ||
...fn.args, | ||
...inputArgs, | ||
}, | ||
args: addArgs(fn.args, inputArgs), | ||
returns: fn.returns, | ||
@@ -364,2 +364,24 @@ handler: async (ctx: any, allArgs: any) => { | ||
// Adds args to a property validator or validator | ||
// Needs to call recursively in the case of unions. | ||
function addArgs( | ||
validatorOrPropertyValidator: PropertyValidators | Validator<any, any, any>, | ||
args: PropertyValidators, | ||
): Validator<any, any, any> { | ||
if (Object.keys(args).length === 0) { | ||
return asObjectValidator(validatorOrPropertyValidator); | ||
} | ||
const validator = asObjectValidator(validatorOrPropertyValidator); | ||
switch (validator.kind) { | ||
case "object": | ||
return v.object({ ...validator.fields, ...args }); | ||
case "union": | ||
return v.union(...validator.members.map((m) => addArgs(m, args))); | ||
default: | ||
throw new Error( | ||
"Cannot add arguments to a validator that is not an object or union.", | ||
); | ||
} | ||
} | ||
/** | ||
@@ -417,3 +439,3 @@ * A Convex function (query, mutation, or action) to be registered for the API. | ||
< | ||
ArgsValidator extends PropertyValidators | void, | ||
ArgsValidator extends PropertyValidators | void | Validator<any, any, any>, | ||
ReturnsValidator extends PropertyValidators | GenericValidator | void, | ||
@@ -443,5 +465,7 @@ ReturnValue extends ReturnValueForOptionalValidator<ReturnsValidator> = any, | ||
ArgsArrayToObject< | ||
OneOrZeroArgs extends [infer A] | ||
? [Expand<A & ObjectType<ModArgsValidator>>] | ||
: [ObjectType<ModArgsValidator>] | ||
ModArgsValidator extends Record<string, never> | ||
? OneOrZeroArgs | ||
: OneOrZeroArgs extends [infer A] | ||
? [Expand<A & ObjectType<ModArgsValidator>>] | ||
: [ObjectType<ModArgsValidator>] | ||
>, | ||
@@ -448,0 +472,0 @@ ReturnValue |
import { defineTable, defineSchema, GenericDocument } from "convex/server"; | ||
import { convexTest } from "convex-test"; | ||
import { expect, test, vi } from "vitest"; | ||
import { expect, test } from "vitest"; | ||
import { IndexKey, getPage } from "./pagination.js"; | ||
@@ -150,3 +150,3 @@ import { modules } from "./setup.test.js"; | ||
// document already. | ||
await ctx.db.delete(page0[0]._id as GenericId<"foo">); | ||
await ctx.db.delete(page0[0]!._id as GenericId<"foo">); | ||
const { page: page0Refreshed } = await getPage(ctx, { | ||
@@ -204,3 +204,3 @@ table: "foo", | ||
} | ||
const { page: pageAt, indexKeys: indexKeysAt } = await getPage(ctx, { | ||
const { page: pageAt } = await getPage(ctx, { | ||
table: "foo", | ||
@@ -207,0 +207,0 @@ index: "abc", |
@@ -10,6 +10,10 @@ import { convexTest } from "convex-test"; | ||
defineTable, | ||
GenericDatabaseReader, | ||
GenericDatabaseWriter, | ||
MutationBuilder, | ||
mutationGeneric, | ||
queryGeneric, | ||
} from "convex/server"; | ||
import { modules } from "./setup.test.js"; | ||
import { customCtx, customMutation } from "./customFunctions.js"; | ||
import { crud } from "../server.js"; | ||
@@ -27,22 +31,21 @@ const schema = defineSchema({ | ||
type DataModel = DataModelFromSchemaDefinition<typeof schema>; | ||
type DatabaseReader = GenericDatabaseReader<DataModel>; | ||
type DatabaseWriter = GenericDatabaseWriter<DataModel>; | ||
describe("row level security", () => { | ||
const withRLS = async (ctx: { db: DatabaseWriter; auth: Auth }) => { | ||
const tokenIdentifier = (await ctx.auth.getUserIdentity())?.tokenIdentifier; | ||
if (!tokenIdentifier) throw new Error("Unauthenticated"); | ||
return { | ||
...ctx, | ||
db: wrapDatabaseWriter({ tokenIdentifier }, ctx.db, { | ||
notes: { | ||
read: async ({ tokenIdentifier }, doc) => { | ||
const author = await ctx.db.get(doc.userId); | ||
return tokenIdentifier === author?.tokenIdentifier; | ||
}, | ||
const withRLS = async (ctx: { db: DatabaseWriter; auth: Auth }) => { | ||
const tokenIdentifier = (await ctx.auth.getUserIdentity())?.tokenIdentifier; | ||
if (!tokenIdentifier) throw new Error("Unauthenticated"); | ||
return { | ||
...ctx, | ||
db: wrapDatabaseWriter({ tokenIdentifier }, ctx.db, { | ||
notes: { | ||
read: async ({ tokenIdentifier }, doc) => { | ||
const author = await ctx.db.get(doc.userId); | ||
return tokenIdentifier === author?.tokenIdentifier; | ||
}, | ||
}), | ||
}; | ||
}, | ||
}), | ||
}; | ||
}; | ||
describe("row level security", () => { | ||
test("can only read own notes", async () => { | ||
@@ -81,3 +84,3 @@ const t = convexTest(schema, modules); | ||
const aId = await ctx.db.insert("users", { tokenIdentifier: "Person A" }); | ||
const bId = await ctx.db.insert("users", { tokenIdentifier: "Person B" }); | ||
await ctx.db.insert("users", { tokenIdentifier: "Person B" }); | ||
return ctx.db.insert("notes", { | ||
@@ -102,1 +105,23 @@ note: "Hello from Person A", | ||
}); | ||
const mutation = mutationGeneric as MutationBuilder<DataModel, "public">; | ||
const mutationWithRLS = customMutation( | ||
mutation, | ||
customCtx((ctx) => withRLS(ctx)), | ||
); | ||
customMutation( | ||
mutationWithRLS, | ||
customCtx((ctx) => ({ foo: "bar" })), | ||
) satisfies typeof mutation; | ||
crud( | ||
{ | ||
_id: v.id("foo"), | ||
name: "foo", | ||
withoutSystemFields: { bar: v.number() }, | ||
}, | ||
queryGeneric, | ||
mutationWithRLS, | ||
); |
@@ -1,4 +0,4 @@ | ||
import { assert, omit, pick, pruneNull } from ".."; | ||
import { Table } from "."; | ||
import { partial } from "../validators"; | ||
import { assert, omit, pick, pruneNull } from "../index.js"; | ||
import { Table } from "../server.js"; | ||
import { partial } from "../validators.js"; | ||
import { convexTest } from "convex-test"; | ||
@@ -17,3 +17,3 @@ import { | ||
import { expect, test } from "vitest"; | ||
import { modules } from "./setup.test"; | ||
import { modules } from "./setup.test.js"; | ||
@@ -20,0 +20,0 @@ // Define a table with system fields _id and _creationTime. This also returns |
@@ -10,8 +10,8 @@ import { | ||
} from "convex/server"; | ||
import { Equals, assert, omit } from ".."; | ||
import { Equals, assert, omit } from "../index.js"; | ||
import { convexTest } from "convex-test"; | ||
import { describe, expect, test } from "vitest"; | ||
import { modules } from "./setup.test"; | ||
import { zCustomQuery, zid, zodToConvexFields } from "./zod"; | ||
import { customCtx } from "./customFunctions"; | ||
import { modules } from "./setup.test.js"; | ||
import { zCustomQuery, zid, zodToConvexFields } from "./zod.js"; | ||
import { customCtx } from "./customFunctions.js"; | ||
import { v } from "convex/values"; | ||
@@ -18,0 +18,0 @@ import { z } from "zod"; |
Sorry, the diff of this file is not supported yet
726266
17372