New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

convex-helpers

Package Overview
Dependencies
Maintainers
5
Versions
144
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

convex-helpers - npm Package Compare versions

Comparing version 0.1.57-alpha.2 to 0.1.57-alpha.3

4

package.json
{
"name": "convex-helpers",
"version": "0.1.57-alpha.2",
"version": "0.1.57-alpha.3",
"description": "A collection of useful code to complement the official convex package.",

@@ -125,3 +125,3 @@ "type": "module",

"peerDependencies": {
"convex": "^1.13.0",
"convex": "^1.13.0 || >=1.16.0-alpha.1",
"hono": "^4.0.5",

@@ -128,0 +128,0 @@ "react": "^17.0.2 || ^18.0.0",

@@ -424,57 +424,6 @@ # convex-helpers

Use the [RowLevelSecurity](./server/rowLevelSecurity.ts) helper to define
database wrappers to add row-level checks for a server-side function.
Any access to `db` inside functions wrapped with these
`withQueryRLS` and `withMutationRLS` wrappers to add row-level checks for a
server-side function. Any access to `db` inside functions wrapped with these
will check your access rules on read/insert/modify per-document.
```ts
import {
customCtx,
customMutation,
customQuery,
} from "convex-helpers/server/customFunctions";
import {
Rules,
wrapDatabaseReader,
wrapDatabaseWriter,
} from "convex-helpers/server/rowLevelSecurity";
import { DataModel } from "./_generated/dataModel";
import { mutation, query, QueryCtx } from "./_generated/server";
async function rlsRules(ctx: QueryCtx) {
const identity = await ctx.auth.getUserIdentity();
return {
users: {
read: async (_, user) => {
// Unauthenticated users can only read users over 18
if (!identity && user.age < 18) return false;
return true;
},
insert: async (_, user) => {
return true;
},
modify: async (_, user) => {
if (!identity)
throw new Error("Must be authenticated to modify a user");
// Users can only modify their own user
return user.tokenIdentifier === identity.tokenIdentifier;
},
},
} satisfies Rules<QueryCtx, DataModel>;
}
const queryWithRLS = customQuery(
query,
customCtx(async (ctx) => ({
db: wrapDatabaseReader(ctx, ctx.db, await rlsRules(ctx)),
})),
);
const mutationWithRLS = customMutation(
mutation,
customCtx(async (ctx) => ({
db: wrapDatabaseWriter(ctx, ctx.db, await rlsRules(ctx)),
})),
);
```
## Zod Validation

@@ -481,0 +430,0 @@

@@ -1,3 +0,3 @@

import { QueryBuilder, MutationBuilder, WithoutSystemFields, DocumentByName, RegisteredMutation, RegisteredQuery, FunctionVisibility, paginationOptsValidator, PaginationResult, SchemaDefinition, GenericSchema, TableNamesInDataModel, DataModelFromSchemaDefinition } from "convex/server";
import { GenericId, Infer, Validator } from "convex/values";
import { QueryBuilder, MutationBuilder, GenericDataModel, WithoutSystemFields, DocumentByName, RegisteredMutation, RegisteredQuery, FunctionVisibility, paginationOptsValidator, PaginationResult } from "convex/server";
import { GenericId, Infer, ObjectType, Validator } from "convex/values";
import { Expand } from "./index.js";

@@ -95,15 +95,17 @@ /**

* import { crud } from "convex-helpers/server";
* import schema from "./schema";
* import { query, mutation } from "./convex/_generated/server";
*
* export const { create, read, update, destroy } = crud(schema, "users");
* const Users = Table("users", {
* name: v.string(),
* ///...
* });
*
* export const { create, read, paginate, update, destroy } =
* crud(Users, query, mutation);
* ```
*
* Then you can access the functions like `internal.users.create` from actions.
* Then from a client, you can access `api.users.create`.
*
* To expose these functions publicly, you can pass in custom query and
* mutation arguments. Be careful what you expose publicly: you wouldn't want
* any client to be able to delete users, for example.
*
* @param schema Your project's schema.
* @param table The table name to create CRUD operations for.
* @param table The table to create CRUD operations for.
* Of type returned from Table() in "convex-helpers/server".
* @param query The query to use - use internalQuery or query from

@@ -114,92 +116,23 @@ * "./convex/_generated/server" or a customQuery.

* @returns An object with create, read, update, and delete functions.
* You must export these functions at the top level of your file to use them.
*/
export declare function crud<Schema extends GenericSchema, TableName extends TableNamesInDataModel<DataModelFromSchemaDefinition<SchemaDefinition<Schema, any>>>, QueryVisibility extends FunctionVisibility = "internal", MutationVisibility extends FunctionVisibility = "internal">(schema: SchemaDefinition<Schema, any>, table: TableName, query?: QueryBuilder<DataModelFromSchemaDefinition<SchemaDefinition<Schema, any>>, QueryVisibility>, mutation?: MutationBuilder<DataModelFromSchemaDefinition<SchemaDefinition<Schema, any>>, MutationVisibility>): {
create: RegisteredMutation<MutationVisibility, WithoutSystemFields<DocumentByName<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } | import("convex/server").Expand<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } & import("convex/server").AnyDataModel>, TableName>>, Promise<DocumentByName<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } | import("convex/server").Expand<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } & import("convex/server").AnyDataModel>, TableName>>>;
export declare function crud<Fields extends Record<string, Validator<any, any, any>>, TableName extends string, DataModel extends GenericDataModel, QueryVisibility extends FunctionVisibility, MutationVisibility extends FunctionVisibility>(table: {
name: TableName;
_id: Validator<GenericId<TableName>>;
withoutSystemFields: Fields;
}, query: QueryBuilder<DataModel, QueryVisibility>, mutation: MutationBuilder<DataModel, MutationVisibility>): {
create: RegisteredMutation<MutationVisibility, ObjectType<Fields>, Promise<DocumentByName<DataModel, TableName>>>;
read: RegisteredQuery<QueryVisibility, {
id: GenericId<TableName>;
}, Promise<DocumentByName<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } | import("convex/server").Expand<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } & import("convex/server").AnyDataModel>, TableName> | null>>;
}, Promise<DocumentByName<DataModel, TableName> | null>>;
paginate: RegisteredQuery<QueryVisibility, {
paginationOpts: Infer<typeof paginationOptsValidator>;
}, Promise<PaginationResult<DocumentByName<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } | import("convex/server").Expand<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } & import("convex/server").AnyDataModel>, TableName>>>>;
}, Promise<PaginationResult<DocumentByName<DataModel, TableName>>>>;
update: RegisteredMutation<MutationVisibility, {
id: GenericId<TableName>;
patch: Partial<WithoutSystemFields<DocumentByName<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } | import("convex/server").Expand<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } & import("convex/server").AnyDataModel>, TableName>>>;
patch: Partial<WithoutSystemFields<DocumentByName<DataModel, TableName>>>;
}, Promise<void>>;
destroy: RegisteredMutation<MutationVisibility, {
id: GenericId<TableName>;
}, Promise<null | DocumentByName<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } | import("convex/server").Expand<{ [TableName_1 in keyof Schema & string]: Schema[TableName_1] extends import("convex/server").TableDefinition<infer DocumentType extends Validator<any, any, any>, infer Indexes extends import("convex/server").GenericTableIndexes, infer SearchIndexes extends import("convex/server").GenericTableSearchIndexes, infer VectorIndexes extends import("convex/server").GenericTableVectorIndexes> ? {
document: import("convex/server").Expand<import("convex/server").IdField<TableName_1> & import("convex/server").Expand<import("convex/server").SystemFields & DocumentType["type"]>>;
fieldPaths: keyof import("convex/server").IdField<TableName_2> | ("_creationTime" | DocumentType["fieldPaths"]);
indexes: import("convex/server").Expand<Indexes & import("convex/server").SystemIndexes>;
searchIndexes: SearchIndexes;
vectorIndexes: VectorIndexes;
} : never; } & import("convex/server").AnyDataModel>, TableName>>>;
}, Promise<null | DocumentByName<DataModel, TableName>>>;
};
//# sourceMappingURL=server.d.ts.map

@@ -1,2 +0,2 @@

import { defineTable, paginationOptsValidator, internalQueryGeneric, internalMutationGeneric, } from "convex/server";
import { defineTable, paginationOptsValidator, } from "convex/server";
import { v } from "convex/values";

@@ -74,15 +74,17 @@ import { partial } from "./validators.js";

* import { crud } from "convex-helpers/server";
* import schema from "./schema";
* import { query, mutation } from "./convex/_generated/server";
*
* export const { create, read, update, destroy } = crud(schema, "users");
* const Users = Table("users", {
* name: v.string(),
* ///...
* });
*
* export const { create, read, paginate, update, destroy } =
* crud(Users, query, mutation);
* ```
*
* Then you can access the functions like `internal.users.create` from actions.
* Then from a client, you can access `api.users.create`.
*
* To expose these functions publicly, you can pass in custom query and
* mutation arguments. Be careful what you expose publicly: you wouldn't want
* any client to be able to delete users, for example.
*
* @param schema Your project's schema.
* @param table The table name to create CRUD operations for.
* @param table The table to create CRUD operations for.
* Of type returned from Table() in "convex-helpers/server".
* @param query The query to use - use internalQuery or query from

@@ -93,20 +95,12 @@ * "./convex/_generated/server" or a customQuery.

* @returns An object with create, read, update, and delete functions.
* You must export these functions at the top level of your file to use them.
*/
export function crud(schema, table, query = internalQueryGeneric, mutation = internalMutationGeneric) {
export function crud(table, query, mutation) {
const systemFields = {
_id: v.id(table),
_id: v.id(table.name),
_creationTime: v.number(),
};
const validator = schema.tables[table]?.validator;
if (!validator) {
throw new Error(`Table ${table} not found in schema. Did you define it in defineSchema?`);
}
if (validator.kind !== "object") {
throw new Error(`CRUD only supports simple tables ${table} is a ${validator.type}`);
}
return {
create: mutation({
args: {
...validator.fields,
...table.withoutSystemFields,
...partial(systemFields),

@@ -119,3 +113,3 @@ },

delete args._creationTime;
const id = await ctx.db.insert(table, args);
const id = await ctx.db.insert(table.name, args);
return (await ctx.db.get(id));

@@ -125,3 +119,3 @@ },

read: query({
args: { id: v.id(table) },
args: { id: table._id },
handler: async (ctx, args) => {

@@ -136,3 +130,3 @@ return await ctx.db.get(args.id);

handler: async (ctx, args) => {
return ctx.db.query(table).paginate(args.paginationOpts);
return ctx.db.query(table.name).paginate(args.paginationOpts);
},

@@ -142,7 +136,7 @@ }),

args: {
id: v.id(table),
id: v.id(table.name),
// this could be partial(table.withSystemFields) but keeping
// the api less coupled to Table
patch: v.object({
...partial(validator.fields),
...partial(table.withoutSystemFields),
...partial(systemFields),

@@ -156,3 +150,3 @@ }),

destroy: mutation({
args: { id: v.id(table) },
args: { id: table._id },
handler: async (ctx, args) => {

@@ -159,0 +153,0 @@ const old = await ctx.db.get(args.id);

@@ -5,2 +5,3 @@ import {

MutationBuilder,
GenericDataModel,
WithoutSystemFields,

@@ -13,10 +14,4 @@ DocumentByName,

PaginationResult,
SchemaDefinition,
GenericSchema,
TableNamesInDataModel,
DataModelFromSchemaDefinition,
internalQueryGeneric,
internalMutationGeneric,
} from "convex/server";
import { GenericId, Infer, Validator, v } from "convex/values";
import { GenericId, Infer, ObjectType, Validator, v } from "convex/values";
import { Expand } from "./index.js";

@@ -107,15 +102,17 @@ import { partial } from "./validators.js";

* import { crud } from "convex-helpers/server";
* import schema from "./schema";
* import { query, mutation } from "./convex/_generated/server";
*
* export const { create, read, update, destroy } = crud(schema, "users");
* const Users = Table("users", {
* name: v.string(),
* ///...
* });
*
* export const { create, read, paginate, update, destroy } =
* crud(Users, query, mutation);
* ```
*
* Then you can access the functions like `internal.users.create` from actions.
* Then from a client, you can access `api.users.create`.
*
* To expose these functions publicly, you can pass in custom query and
* mutation arguments. Be careful what you expose publicly: you wouldn't want
* any client to be able to delete users, for example.
*
* @param schema Your project's schema.
* @param table The table name to create CRUD operations for.
* @param table The table to create CRUD operations for.
* Of type returned from Table() in "convex-helpers/server".
* @param query The query to use - use internalQuery or query from

@@ -126,44 +123,26 @@ * "./convex/_generated/server" or a customQuery.

* @returns An object with create, read, update, and delete functions.
* You must export these functions at the top level of your file to use them.
*/
export function crud<
Schema extends GenericSchema,
TableName extends TableNamesInDataModel<
DataModelFromSchemaDefinition<SchemaDefinition<Schema, any>>
>,
QueryVisibility extends FunctionVisibility = "internal",
MutationVisibility extends FunctionVisibility = "internal",
Fields extends Record<string, Validator<any, any, any>>,
TableName extends string,
DataModel extends GenericDataModel,
QueryVisibility extends FunctionVisibility,
MutationVisibility extends FunctionVisibility,
>(
schema: SchemaDefinition<Schema, any>,
table: TableName,
query: QueryBuilder<
DataModelFromSchemaDefinition<SchemaDefinition<Schema, any>>,
QueryVisibility
> = internalQueryGeneric as any,
mutation: MutationBuilder<
DataModelFromSchemaDefinition<SchemaDefinition<Schema, any>>,
MutationVisibility
> = internalMutationGeneric as any,
table: {
name: TableName;
_id: Validator<GenericId<TableName>>;
withoutSystemFields: Fields;
},
query: QueryBuilder<DataModel, QueryVisibility>,
mutation: MutationBuilder<DataModel, MutationVisibility>,
) {
type DataModel = DataModelFromSchemaDefinition<SchemaDefinition<Schema, any>>;
const systemFields = {
_id: v.id(table),
_id: v.id(table.name),
_creationTime: v.number(),
};
const validator = schema.tables[table]?.validator;
if (!validator) {
throw new Error(
`Table ${table} not found in schema. Did you define it in defineSchema?`,
);
}
if (validator.kind !== "object") {
throw new Error(
`CRUD only supports simple tables ${table} is a ${validator.type}`,
);
}
return {
create: mutation({
args: {
...validator.fields,
...table.withoutSystemFields,
...partial(systemFields),

@@ -175,3 +154,3 @@ },

const id = await ctx.db.insert(
table,
table.name,
args as unknown as WithoutSystemFields<

@@ -185,7 +164,7 @@ DocumentByName<DataModel, TableName>

MutationVisibility,
WithoutSystemFields<DocumentByName<DataModel, TableName>>,
ObjectType<Fields>,
Promise<DocumentByName<DataModel, TableName>>
>,
read: query({
args: { id: v.id(table) },
args: { id: table._id },
handler: async (ctx, args) => {

@@ -204,3 +183,3 @@ return await ctx.db.get(args.id);

handler: async (ctx, args) => {
return ctx.db.query(table).paginate(args.paginationOpts);
return ctx.db.query(table.name).paginate(args.paginationOpts);
},

@@ -214,7 +193,7 @@ }) as RegisteredQuery<

args: {
id: v.id(table),
id: v.id(table.name),
// this could be partial(table.withSystemFields) but keeping
// the api less coupled to Table
patch: v.object({
...partial(validator.fields),
...partial(table.withoutSystemFields),
...partial(systemFields),

@@ -240,3 +219,3 @@ }),

destroy: mutation({
args: { id: v.id(table) },
args: { id: table._id },
handler: async (ctx, args) => {

@@ -243,0 +222,0 @@ const old = await ctx.db.get(args.id);

import { convexTest } from "convex-test";
import { expect, test } from "vitest";
import { crud } from "../server.js";
import { crud } from ".";
import {

@@ -16,3 +16,2 @@ anyApi,

import { modules } from "./setup.test";
import { customCtx, customMutation, customQuery } from "./customFunctions.js";

@@ -40,4 +39,11 @@ const ExampleFields = {

export const { create, read, paginate, update, destroy } = crud(
schema,
CrudTable,
// We could use the Table helper instead, but showing it explicitly here.
// E.g. Table("crud_example", ExampleFields)
{
name: CrudTable,
_id: v.id(CrudTable),
withoutSystemFields: ExampleFields,
},
internalQuery,
internalMutation,
);

@@ -73,45 +79,1 @@

});
/**
* Custom function tests
*/
const customQ = customQuery(
internalQuery,
customCtx((ctx) => ({ foo: "bar" })),
);
const customM = customMutation(
internalMutation,
customCtx((ctx) => ({})),
);
const customCrud = crud(schema, CrudTable, 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, Validator } from "convex/values";
import { GenericValidator, ObjectType, PropertyValidators } 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 | Validator<any, any, any>, ReturnsValidator extends PropertyValidators | GenericValidator | void, ReturnValue extends ReturnValueForOptionalValidator<ReturnsValidator> = any, OneOrZeroArgs extends ArgsArrayForOptionalValidator<ArgsValidator> = DefaultArgsForOptionalValidator<ArgsValidator>>(func: {
<ArgsValidator extends PropertyValidators | void, 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<ModArgsValidator extends Record<string, never> ? OneOrZeroArgs : OneOrZeroArgs extends [infer A] ? [Expand<A & ObjectType<ModArgsValidator>>] : [ObjectType<ModArgsValidator>]>, ReturnValue>;
}): Registration<FuncType, Visibility, ArgsArrayToObject<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;

@@ -1,14 +0,1 @@

/**
* 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";

@@ -211,3 +198,6 @@ /**

return builder({
args: addArgs(fn.args, inputArgs),
args: {
...fn.args,
...inputArgs,
},
returns: fn.returns,

@@ -234,17 +224,1 @@ 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,4 +0,3 @@

import { Equals, assert } from "../index.js";
import { Equals, assert } from "..";
import {
customAction,
CustomCtx,

@@ -8,9 +7,7 @@ customCtx,

customQuery,
} from "./customFunctions.js";
import { wrapDatabaseWriter } from "./rowLevelSecurity.js";
import { SessionId, vSessionId } from "./sessions.js";
} from "./customFunctions";
import { wrapDatabaseWriter } from "./rowLevelSecurity";
import { SessionId, vSessionId } from "./sessions";
import { convexTest } from "convex-test";
import {
ActionBuilder,
actionGeneric,
anyApi,

@@ -30,3 +27,3 @@ DataModelFromSchemaDefinition,

import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { modules } from "./setup.test.js";
import { modules } from "./setup.test";

@@ -42,3 +39,2 @@ const schema = defineSchema({

const mutation = mutationGeneric as MutationBuilder<DataModel, "public">;
const action = actionGeneric as ActionBuilder<DataModel, "public">;

@@ -125,20 +121,2 @@ 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.

@@ -145,0 +123,0 @@ */

@@ -17,5 +17,2 @@ /**

PropertyValidators,
Validator,
asObjectValidator,
v,
} from "convex/values";

@@ -335,3 +332,6 @@ import {

return builder({
args: addArgs(fn.args, inputArgs),
args: {
...fn.args,
...inputArgs,
},
returns: fn.returns,

@@ -364,24 +364,2 @@ 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.",
);
}
}
/**

@@ -439,3 +417,3 @@ * A Convex function (query, mutation, or action) to be registered for the API.

<
ArgsValidator extends PropertyValidators | void | Validator<any, any, any>,
ArgsValidator extends PropertyValidators | void,
ReturnsValidator extends PropertyValidators | GenericValidator | void,

@@ -465,7 +443,5 @@ ReturnValue extends ReturnValueForOptionalValidator<ReturnsValidator> = any,

ArgsArrayToObject<
ModArgsValidator extends Record<string, never>
? OneOrZeroArgs
: OneOrZeroArgs extends [infer A]
? [Expand<A & ObjectType<ModArgsValidator>>]
: [ObjectType<ModArgsValidator>]
OneOrZeroArgs extends [infer A]
? [Expand<A & ObjectType<ModArgsValidator>>]
: [ObjectType<ModArgsValidator>]
>,

@@ -472,0 +448,0 @@ ReturnValue

import { defineTable, defineSchema, GenericDocument } from "convex/server";
import { convexTest } from "convex-test";
import { expect, test } from "vitest";
import { expect, test, vi } 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 } = await getPage(ctx, {
const { page: pageAt, indexKeys: indexKeysAt } = await getPage(ctx, {
table: "foo",

@@ -207,0 +207,0 @@ index: "abc",

@@ -10,10 +10,6 @@ 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";

@@ -31,21 +27,22 @@ const schema = defineSchema({

type DataModel = DataModelFromSchemaDefinition<typeof schema>;
type DatabaseReader = GenericDatabaseReader<DataModel>;
type DatabaseWriter = GenericDatabaseWriter<DataModel>;
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", () => {
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 () => {

@@ -84,3 +81,3 @@ const t = convexTest(schema, modules);

const aId = await ctx.db.insert("users", { tokenIdentifier: "Person A" });
await ctx.db.insert("users", { tokenIdentifier: "Person B" });
const bId = await ctx.db.insert("users", { tokenIdentifier: "Person B" });
return ctx.db.insert("notes", {

@@ -105,15 +102,1 @@ 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(schema, "users", queryGeneric, mutationWithRLS);

@@ -1,4 +0,4 @@

import { assert, omit, pick, pruneNull } from "../index.js";
import { Table } from "../server.js";
import { partial } from "../validators.js";
import { assert, omit, pick, pruneNull } from "..";
import { Table } from ".";
import { partial } from "../validators";
import { convexTest } from "convex-test";

@@ -17,3 +17,3 @@ import {

import { expect, test } from "vitest";
import { modules } from "./setup.test.js";
import { modules } from "./setup.test";

@@ -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 "../index.js";
import { Equals, assert, omit } from "..";
import { convexTest } from "convex-test";
import { describe, expect, test } from "vitest";
import { modules } from "./setup.test.js";
import { zCustomQuery, zid, zodToConvexFields } from "./zod.js";
import { customCtx } from "./customFunctions.js";
import { modules } from "./setup.test";
import { zCustomQuery, zid, zodToConvexFields } from "./zod";
import { customCtx } from "./customFunctions";
import { v } from "convex/values";

@@ -18,0 +18,0 @@ import { z } from "zod";

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc