convex-helpers 0.1.22 → 0.1.23
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 +66 -5
- package/dist/server/hono.d.ts +105 -0
- package/dist/server/hono.js +154 -0
- package/dist/server/index.d.ts +55 -5
- package/dist/server/index.js +75 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -1
- package/server/hono.ts +191 -0
- package/server/index.ts +137 -2
package/README.md
CHANGED
|
@@ -136,6 +136,68 @@ export const myComplexQuery = zodQuery({
|
|
|
136
136
|
})
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
+
## Hono for advanced HTTP endpoint definitions
|
|
140
|
+
|
|
141
|
+
[Hono](https://hono.dev/) is an optimized web framework you can use to define
|
|
142
|
+
HTTP api endpoints easily
|
|
143
|
+
([`httpAction` in Convex](https://docs.convex.dev/functions/http-actions)).
|
|
144
|
+
|
|
145
|
+
See the [guide on Stack](https://stack.convex.dev/hono-with-convex) for tips on using Hono for HTTP endpoints.
|
|
146
|
+
|
|
147
|
+
To use it, put this in your `convex/http.ts` file:
|
|
148
|
+
```ts
|
|
149
|
+
import {
|
|
150
|
+
Hono,
|
|
151
|
+
HonoWithConvex,
|
|
152
|
+
HttpRouterWithHono,
|
|
153
|
+
} from "convex-helpers/server/hono";
|
|
154
|
+
import { ActionCtx } from "./_generated/server";
|
|
155
|
+
|
|
156
|
+
const app: HonoWithConvex<ActionCtx> = new Hono();
|
|
157
|
+
|
|
158
|
+
// See the [guide on Stack](https://stack.convex.dev/hono-with-convex)
|
|
159
|
+
// for tips on using Hono for HTTP endpoints.
|
|
160
|
+
app.get("/", async (c) => {
|
|
161
|
+
return c.json("Hello world!");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
export default new HttpRouterWithHono(app);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## CRUD utilities
|
|
168
|
+
|
|
169
|
+
To generate a basic CRUD api for your tables, you can use this helper to define
|
|
170
|
+
these functions for a given table:
|
|
171
|
+
|
|
172
|
+
- `create`
|
|
173
|
+
- `read`
|
|
174
|
+
- `update`
|
|
175
|
+
- `delete`
|
|
176
|
+
- `paginate`
|
|
177
|
+
|
|
178
|
+
**Note: I recommend only doing this for prototyping or [internal functions](https://docs.convex.dev/functions/internal-functions)**
|
|
179
|
+
|
|
180
|
+
Example:
|
|
181
|
+
```ts
|
|
182
|
+
|
|
183
|
+
// in convex/users.ts
|
|
184
|
+
import { crud } from "convex-helpers/server";
|
|
185
|
+
import { internalMutation, internalQuery } from "../convex/_generated/server";
|
|
186
|
+
|
|
187
|
+
const Users = Table("users", {...});
|
|
188
|
+
|
|
189
|
+
export const { read, update } = crud(Users, internalQuery, internalMutation);
|
|
190
|
+
|
|
191
|
+
// in convex/schema.ts
|
|
192
|
+
import { Users } from "./users";
|
|
193
|
+
export default defineSchema({users: Users.table});
|
|
194
|
+
|
|
195
|
+
// in some file, in an action:
|
|
196
|
+
const user = await ctx.runQuery(internal.users.read, { id: userId });
|
|
197
|
+
|
|
198
|
+
await ctx.runMutation(internal.users.update, { status: "inactive" });
|
|
199
|
+
```
|
|
200
|
+
|
|
139
201
|
## Validator utilities
|
|
140
202
|
|
|
141
203
|
When using validators for defining database schema or function arguments,
|
|
@@ -145,18 +207,17 @@ these validators help:
|
|
|
145
207
|
to avoid re-defining validators. To learn more about sharing validators, read
|
|
146
208
|
[this article](https://stack.convex.dev/argument-validation-without-repetition),
|
|
147
209
|
an extension of [this article](https://stack.convex.dev/types-cookbook).
|
|
148
|
-
2.
|
|
149
|
-
runtime values.
|
|
150
|
-
3. Add utilties for partial, pick and omit to match the TypeScript type
|
|
210
|
+
2. Add utilties for partial, pick and omit to match the TypeScript type
|
|
151
211
|
utilities.
|
|
152
|
-
|
|
212
|
+
3. Add shorthand for a union of `literals`, a `nullable` field, a `deprecated`
|
|
153
213
|
field, and `brandedString`. To learn more about branded strings see
|
|
154
214
|
[this article](https://stack.convex.dev/using-branded-types-in-validators).
|
|
215
|
+
4. Make the validators look more like TypeScript types, even though they're
|
|
216
|
+
runtime values. (This is controvercial and not required to use the above).
|
|
155
217
|
|
|
156
218
|
Example:
|
|
157
219
|
```js
|
|
158
220
|
import { Table } from "convex-helpers/server";
|
|
159
|
-
// Note some redefinitions in the import for even more terse definitions.
|
|
160
221
|
import {
|
|
161
222
|
literals, partial, deprecated, brandedString,
|
|
162
223
|
} from "convex-helpers/validators";
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains a helper class for integrating Convex with Hono.
|
|
3
|
+
*
|
|
4
|
+
* See the [guide on Stack](https://stack.convex.dev/hono-with-convex)
|
|
5
|
+
* for tips on using Hono for HTTP endpoints.
|
|
6
|
+
*
|
|
7
|
+
* To use this helper, create a new Hono app in convex/http.ts like so:
|
|
8
|
+
* ```ts
|
|
9
|
+
* import {
|
|
10
|
+
* Hono,
|
|
11
|
+
* HonoWithConvex,
|
|
12
|
+
* HttpRouterWithHono,
|
|
13
|
+
* } from "convex-helpers/server/hono";
|
|
14
|
+
* import { ActionCtx } from "./_generated/server";
|
|
15
|
+
*
|
|
16
|
+
* const app: HonoWithConvex<ActionCtx> = new Hono();
|
|
17
|
+
*
|
|
18
|
+
* app.get("/", async (c) => {
|
|
19
|
+
* return c.json("Hello world!");
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* export default new HttpRouterWithHono(app);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import { HttpRouter, PublicHttpAction, RoutableMethod, GenericActionCtx } from "convex/server";
|
|
26
|
+
import { Hono } from "hono";
|
|
27
|
+
export { Hono };
|
|
28
|
+
/**
|
|
29
|
+
* Hono uses the `FetchEvent` type internally, which has to do with service workers
|
|
30
|
+
* and isn't included in the Convex tsconfig.
|
|
31
|
+
*
|
|
32
|
+
* As a workaround, define this type here so Hono + Convex compiles.
|
|
33
|
+
*/
|
|
34
|
+
declare global {
|
|
35
|
+
type FetchEvent = any;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A type representing a Hono app with `c.env` containing Convex's
|
|
39
|
+
* `HttpEndpointCtx` (e.g. `c.env.runQuery` is valid).
|
|
40
|
+
*/
|
|
41
|
+
export type HonoWithConvex<ActionCtx extends GenericActionCtx<any>> = Hono<{
|
|
42
|
+
Bindings: {
|
|
43
|
+
[Name in keyof ActionCtx]: ActionCtx[Name];
|
|
44
|
+
};
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* An implementation of the Convex `HttpRouter` that integrates with Hono by
|
|
48
|
+
* overridding `getRoutes` and `lookup`.
|
|
49
|
+
*
|
|
50
|
+
* This defers all routing and request handling to the provided Hono app, and
|
|
51
|
+
* passes along the Convex `HttpEndpointCtx` to the Hono handlers as part of
|
|
52
|
+
* `env`.
|
|
53
|
+
*
|
|
54
|
+
* It will attempt to log each request with the most specific Hono route it can
|
|
55
|
+
* find. For example,
|
|
56
|
+
*
|
|
57
|
+
* ```
|
|
58
|
+
* app.on("GET", "*", ...)
|
|
59
|
+
* app.on("GET", "/profile/:userId", ...)
|
|
60
|
+
*
|
|
61
|
+
* const http = new HttpRouterWithHono(app);
|
|
62
|
+
* http.lookup("/profile/abc", "GET") // [handler, "GET", "/profile/:userId"]
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* An example `convex/http.ts` file would look like this:
|
|
66
|
+
* ```
|
|
67
|
+
* const app: HonoWithConvex = new Hono();
|
|
68
|
+
*
|
|
69
|
+
* // add Hono routes on `app`
|
|
70
|
+
*
|
|
71
|
+
* export default new HttpRouterWithHono(app);
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare class HttpRouterWithHono<ActionCtx extends GenericActionCtx<any>> extends HttpRouter {
|
|
75
|
+
private _app;
|
|
76
|
+
private _handler;
|
|
77
|
+
private _handlerInfoCache;
|
|
78
|
+
constructor(app: HonoWithConvex<ActionCtx>);
|
|
79
|
+
/**
|
|
80
|
+
* Returns a list of routed HTTP endpoints.
|
|
81
|
+
*
|
|
82
|
+
* These are used to populate the list of routes shown in the Functions page of the Convex dashboard.
|
|
83
|
+
*
|
|
84
|
+
* @returns - an array of [path, method, endpoint] tuples.
|
|
85
|
+
*/
|
|
86
|
+
getRoutes: () => [string, "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "PATCH", (...args: any) => any][];
|
|
87
|
+
/**
|
|
88
|
+
* Returns the appropriate HTTP endpoint and its routed request path and method.
|
|
89
|
+
*
|
|
90
|
+
* The path and method returned are used for logging and metrics, and should
|
|
91
|
+
* match up with one of the routes returned by `getRoutes`.
|
|
92
|
+
*
|
|
93
|
+
* For example,
|
|
94
|
+
*
|
|
95
|
+
* ```js
|
|
96
|
+
* http.route({ pathPrefix: "/profile/", method: "GET", handler: getProfile});
|
|
97
|
+
*
|
|
98
|
+
* http.lookup("/profile/abc", "GET") // returns [getProfile, "GET", "/profile/*"]
|
|
99
|
+
*```
|
|
100
|
+
*
|
|
101
|
+
* @returns - a tuple [PublicHttpEndpoint, method, path] or null.
|
|
102
|
+
*/
|
|
103
|
+
lookup: (path: string, method: RoutableMethod | "HEAD") => readonly [PublicHttpAction, "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "PATCH", string];
|
|
104
|
+
}
|
|
105
|
+
export declare function normalizeMethod(method: RoutableMethod | "HEAD"): RoutableMethod;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file contains a helper class for integrating Convex with Hono.
|
|
3
|
+
*
|
|
4
|
+
* See the [guide on Stack](https://stack.convex.dev/hono-with-convex)
|
|
5
|
+
* for tips on using Hono for HTTP endpoints.
|
|
6
|
+
*
|
|
7
|
+
* To use this helper, create a new Hono app in convex/http.ts like so:
|
|
8
|
+
* ```ts
|
|
9
|
+
* import {
|
|
10
|
+
* Hono,
|
|
11
|
+
* HonoWithConvex,
|
|
12
|
+
* HttpRouterWithHono,
|
|
13
|
+
* } from "convex-helpers/server/hono";
|
|
14
|
+
* import { ActionCtx } from "./_generated/server";
|
|
15
|
+
*
|
|
16
|
+
* const app: HonoWithConvex<ActionCtx> = new Hono();
|
|
17
|
+
*
|
|
18
|
+
* app.get("/", async (c) => {
|
|
19
|
+
* return c.json("Hello world!");
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* export default new HttpRouterWithHono(app);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import { httpActionGeneric, HttpRouter, ROUTABLE_HTTP_METHODS, } from "convex/server";
|
|
26
|
+
import { Hono } from "hono";
|
|
27
|
+
export { Hono };
|
|
28
|
+
/**
|
|
29
|
+
* An implementation of the Convex `HttpRouter` that integrates with Hono by
|
|
30
|
+
* overridding `getRoutes` and `lookup`.
|
|
31
|
+
*
|
|
32
|
+
* This defers all routing and request handling to the provided Hono app, and
|
|
33
|
+
* passes along the Convex `HttpEndpointCtx` to the Hono handlers as part of
|
|
34
|
+
* `env`.
|
|
35
|
+
*
|
|
36
|
+
* It will attempt to log each request with the most specific Hono route it can
|
|
37
|
+
* find. For example,
|
|
38
|
+
*
|
|
39
|
+
* ```
|
|
40
|
+
* app.on("GET", "*", ...)
|
|
41
|
+
* app.on("GET", "/profile/:userId", ...)
|
|
42
|
+
*
|
|
43
|
+
* const http = new HttpRouterWithHono(app);
|
|
44
|
+
* http.lookup("/profile/abc", "GET") // [handler, "GET", "/profile/:userId"]
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* An example `convex/http.ts` file would look like this:
|
|
48
|
+
* ```
|
|
49
|
+
* const app: HonoWithConvex = new Hono();
|
|
50
|
+
*
|
|
51
|
+
* // add Hono routes on `app`
|
|
52
|
+
*
|
|
53
|
+
* export default new HttpRouterWithHono(app);
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export class HttpRouterWithHono extends HttpRouter {
|
|
57
|
+
_app;
|
|
58
|
+
_handler;
|
|
59
|
+
_handlerInfoCache;
|
|
60
|
+
constructor(app) {
|
|
61
|
+
super();
|
|
62
|
+
this._app = app;
|
|
63
|
+
// Single Convex httpEndpoint handler that just forwards the request to the
|
|
64
|
+
// Hono framework
|
|
65
|
+
this._handler = httpActionGeneric(async (ctx, request) => {
|
|
66
|
+
return await app.fetch(request, ctx);
|
|
67
|
+
});
|
|
68
|
+
this._handlerInfoCache = new Map();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns a list of routed HTTP endpoints.
|
|
72
|
+
*
|
|
73
|
+
* These are used to populate the list of routes shown in the Functions page of the Convex dashboard.
|
|
74
|
+
*
|
|
75
|
+
* @returns - an array of [path, method, endpoint] tuples.
|
|
76
|
+
*/
|
|
77
|
+
getRoutes = () => {
|
|
78
|
+
const convexRoutes = [];
|
|
79
|
+
// Likely a better way to do this, but hono will have multiple handlers with the same
|
|
80
|
+
// name (i.e. for middleware), so de-duplicate so we don't show multiple routes in the dashboard.
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
this._app.routes.forEach((route) => {
|
|
83
|
+
// Hono uses "ALL" in its router, which is not supported by the Convex router.
|
|
84
|
+
// Expand this into a route for every routable method supported by Convex.
|
|
85
|
+
if (route.method === "ALL") {
|
|
86
|
+
for (const method of ROUTABLE_HTTP_METHODS) {
|
|
87
|
+
const name = `${method} ${route.path}`;
|
|
88
|
+
if (!seen.has(name)) {
|
|
89
|
+
seen.add(name);
|
|
90
|
+
convexRoutes.push([route.path, method, route.handler]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const name = `${route.method} ${route.path}`;
|
|
96
|
+
if (!seen.has(name)) {
|
|
97
|
+
seen.add(name);
|
|
98
|
+
convexRoutes.push([
|
|
99
|
+
route.path,
|
|
100
|
+
route.method,
|
|
101
|
+
route.handler,
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
return convexRoutes;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Returns the appropriate HTTP endpoint and its routed request path and method.
|
|
110
|
+
*
|
|
111
|
+
* The path and method returned are used for logging and metrics, and should
|
|
112
|
+
* match up with one of the routes returned by `getRoutes`.
|
|
113
|
+
*
|
|
114
|
+
* For example,
|
|
115
|
+
*
|
|
116
|
+
* ```js
|
|
117
|
+
* http.route({ pathPrefix: "/profile/", method: "GET", handler: getProfile});
|
|
118
|
+
*
|
|
119
|
+
* http.lookup("/profile/abc", "GET") // returns [getProfile, "GET", "/profile/*"]
|
|
120
|
+
*```
|
|
121
|
+
*
|
|
122
|
+
* @returns - a tuple [PublicHttpEndpoint, method, path] or null.
|
|
123
|
+
*/
|
|
124
|
+
lookup = (path, method) => {
|
|
125
|
+
const match = this._app.router.match(method, path);
|
|
126
|
+
if (match === null) {
|
|
127
|
+
return [this._handler, normalizeMethod(method), path];
|
|
128
|
+
}
|
|
129
|
+
// There might be multiple handlers for a route (in the case of middleware),
|
|
130
|
+
// so choose the most specific one for the purposes of logging
|
|
131
|
+
const handlersAndRoutes = match[0];
|
|
132
|
+
const mostSpecificHandler = handlersAndRoutes[handlersAndRoutes.length - 1][0][0];
|
|
133
|
+
// On the first request let's populate a lookup from handler to info
|
|
134
|
+
if (this._handlerInfoCache.size === 0) {
|
|
135
|
+
for (const r of this._app.routes) {
|
|
136
|
+
this._handlerInfoCache.set(r.handler, {
|
|
137
|
+
method: normalizeMethod(method),
|
|
138
|
+
path: r.path,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const info = this._handlerInfoCache.get(mostSpecificHandler);
|
|
143
|
+
if (info) {
|
|
144
|
+
return [this._handler, info.method, info.path];
|
|
145
|
+
}
|
|
146
|
+
return [this._handler, normalizeMethod(method), path];
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
export function normalizeMethod(method) {
|
|
150
|
+
// HEAD is handled by Convex by running GET and stripping the body.
|
|
151
|
+
if (method === "HEAD")
|
|
152
|
+
return "GET";
|
|
153
|
+
return method;
|
|
154
|
+
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QueryBuilder, MutationBuilder, GenericDataModel, WithoutSystemFields, DocumentByName, RegisteredMutation, RegisteredQuery, FunctionVisibility, paginationOptsValidator, PaginationResult } from "convex/server";
|
|
2
|
+
import { GenericId, Infer, ObjectType, Validator } from "convex/values";
|
|
2
3
|
import { Expand } from "..";
|
|
3
4
|
/**
|
|
4
5
|
* Define a table with system fields _id and _creationTime. This also returns
|
|
@@ -16,21 +17,22 @@ import { Expand } from "..";
|
|
|
16
17
|
* }
|
|
17
18
|
*/
|
|
18
19
|
export declare function Table<T extends Record<string, Validator<any, any, any>>, TableName extends string>(name: TableName, fields: T): {
|
|
20
|
+
name: TableName;
|
|
19
21
|
table: import("convex/server").TableDefinition<import("convex/dist/cjs-types/type_utils").Expand<import("convex/dist/cjs-types/server/system_fields").SystemFields & import("convex/dist/cjs-types/type_utils").Expand<{ [Property_1 in { [Property in keyof T]: T[Property]["isOptional"] extends true ? Property : never; }[keyof T]]?: T[Property_1]["type"] | undefined; } & { [Property_2 in Exclude<keyof T, { [Property in keyof T]: T[Property]["isOptional"] extends true ? Property : never; }[keyof T]>]: T[Property_2]["type"]; }>>, "_creationTime" | ({ [Property_3 in keyof T]: Property_3 | `${Property_3 & string}.${T[Property_3]["fieldPaths"]}`; }[keyof T] & string), {}, {}, {}>;
|
|
20
22
|
doc: import("convex/dist/cjs-types/values/validator").ObjectValidator<Expand<T & {
|
|
21
|
-
_id: Validator<
|
|
23
|
+
_id: Validator<GenericId<TableName>, false, never>;
|
|
22
24
|
_creationTime: Validator<number, false, never>;
|
|
23
25
|
}>>;
|
|
24
26
|
withoutSystemFields: T;
|
|
25
27
|
withSystemFields: Expand<T & {
|
|
26
|
-
_id: Validator<
|
|
28
|
+
_id: Validator<GenericId<TableName>, false, never>;
|
|
27
29
|
_creationTime: Validator<number, false, never>;
|
|
28
30
|
}>;
|
|
29
31
|
systemFields: {
|
|
30
|
-
_id: Validator<
|
|
32
|
+
_id: Validator<GenericId<TableName>, false, never>;
|
|
31
33
|
_creationTime: Validator<number, false, never>;
|
|
32
34
|
};
|
|
33
|
-
_id: Validator<
|
|
35
|
+
_id: Validator<GenericId<TableName>, false, never>;
|
|
34
36
|
};
|
|
35
37
|
/**
|
|
36
38
|
*
|
|
@@ -44,3 +46,51 @@ export declare function missingEnvVariableUrl(envVarName: string, whereToGet: st
|
|
|
44
46
|
* @returns The deployment name, like "screaming-lemur-123"
|
|
45
47
|
*/
|
|
46
48
|
export declare function deploymentName(): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Create CRUD operations for a table.
|
|
51
|
+
* You can expose these operations in your API. For example, in convex/users.ts:
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* // in convex/users.ts
|
|
55
|
+
* import { crud } from "convex-helpers/server";
|
|
56
|
+
* import { query, mutation } from "./convex/_generated/server";
|
|
57
|
+
*
|
|
58
|
+
* const Users = Table("users", {
|
|
59
|
+
* name: v.string(),
|
|
60
|
+
* ///...
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* export const { create, read, paginate, update, destroy } =
|
|
64
|
+
* crud(Users, query, mutation);
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* Then from a client, you can access `api.users.create`.
|
|
68
|
+
*
|
|
69
|
+
* @param table The table to create CRUD operations for.
|
|
70
|
+
* Of type returned from Table() in "convex-helpers/server".
|
|
71
|
+
* @param query The query to use - use internalQuery or query from
|
|
72
|
+
* "./convex/_generated/server" or a customQuery.
|
|
73
|
+
* @param mutation The mutation to use - use internalMutation or mutation from
|
|
74
|
+
* "./convex/_generated/server" or a customMutation.
|
|
75
|
+
* @returns An object with create, read, update, and delete functions.
|
|
76
|
+
*/
|
|
77
|
+
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: {
|
|
78
|
+
name: TableName;
|
|
79
|
+
_id: Validator<GenericId<TableName>>;
|
|
80
|
+
withoutSystemFields: Fields;
|
|
81
|
+
}, query: QueryBuilder<DataModel, QueryVisibility>, mutation: MutationBuilder<DataModel, MutationVisibility>): {
|
|
82
|
+
create: RegisteredMutation<MutationVisibility, ObjectType<Fields>, Promise<DocumentByName<DataModel, TableName>>>;
|
|
83
|
+
read: RegisteredQuery<QueryVisibility, {
|
|
84
|
+
id: GenericId<TableName>;
|
|
85
|
+
}, Promise<DocumentByName<DataModel, TableName> | null>>;
|
|
86
|
+
paginate: RegisteredQuery<QueryVisibility, {
|
|
87
|
+
paginationOpts: Infer<typeof paginationOptsValidator>;
|
|
88
|
+
}, Promise<PaginationResult<DocumentByName<DataModel, TableName>>>>;
|
|
89
|
+
update: RegisteredMutation<MutationVisibility, {
|
|
90
|
+
id: GenericId<TableName>;
|
|
91
|
+
patch: Partial<WithoutSystemFields<DocumentByName<DataModel, TableName>>>;
|
|
92
|
+
}, Promise<void>>;
|
|
93
|
+
destroy: RegisteredMutation<MutationVisibility, {
|
|
94
|
+
id: GenericId<TableName>;
|
|
95
|
+
}, Promise<DocumentByName<DataModel, TableName> | null>>;
|
|
96
|
+
};
|
package/dist/server/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineTable } from "convex/server";
|
|
1
|
+
import { defineTable, paginationOptsValidator, } from "convex/server";
|
|
2
2
|
import { v } from "convex/values";
|
|
3
3
|
/**
|
|
4
4
|
* Define a table with system fields _id and _creationTime. This also returns
|
|
@@ -27,6 +27,7 @@ export function Table(name, fields) {
|
|
|
27
27
|
...systemFields,
|
|
28
28
|
};
|
|
29
29
|
return {
|
|
30
|
+
name,
|
|
30
31
|
table,
|
|
31
32
|
doc: v.object(withSystemFields),
|
|
32
33
|
withoutSystemFields: fields,
|
|
@@ -60,3 +61,76 @@ export function deploymentName() {
|
|
|
60
61
|
const regex = new RegExp("https://(.+).convex.cloud");
|
|
61
62
|
return regex.exec(url)?.[1];
|
|
62
63
|
}
|
|
64
|
+
import { partial } from "../validators";
|
|
65
|
+
/**
|
|
66
|
+
* Create CRUD operations for a table.
|
|
67
|
+
* You can expose these operations in your API. For example, in convex/users.ts:
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* // in convex/users.ts
|
|
71
|
+
* import { crud } from "convex-helpers/server";
|
|
72
|
+
* import { query, mutation } from "./convex/_generated/server";
|
|
73
|
+
*
|
|
74
|
+
* const Users = Table("users", {
|
|
75
|
+
* name: v.string(),
|
|
76
|
+
* ///...
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* export const { create, read, paginate, update, destroy } =
|
|
80
|
+
* crud(Users, query, mutation);
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* Then from a client, you can access `api.users.create`.
|
|
84
|
+
*
|
|
85
|
+
* @param table The table to create CRUD operations for.
|
|
86
|
+
* Of type returned from Table() in "convex-helpers/server".
|
|
87
|
+
* @param query The query to use - use internalQuery or query from
|
|
88
|
+
* "./convex/_generated/server" or a customQuery.
|
|
89
|
+
* @param mutation The mutation to use - use internalMutation or mutation from
|
|
90
|
+
* "./convex/_generated/server" or a customMutation.
|
|
91
|
+
* @returns An object with create, read, update, and delete functions.
|
|
92
|
+
*/
|
|
93
|
+
export function crud(table, query, mutation) {
|
|
94
|
+
return {
|
|
95
|
+
create: mutation({
|
|
96
|
+
args: table.withoutSystemFields,
|
|
97
|
+
handler: async (ctx, args) => {
|
|
98
|
+
const id = await ctx.db.insert(table.name, args);
|
|
99
|
+
return (await ctx.db.get(id));
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
read: query({
|
|
103
|
+
args: { id: table._id },
|
|
104
|
+
handler: async (ctx, args) => {
|
|
105
|
+
return await ctx.db.get(args.id);
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
paginate: query({
|
|
109
|
+
args: {
|
|
110
|
+
paginationOpts: paginationOptsValidator,
|
|
111
|
+
},
|
|
112
|
+
handler: async (ctx, args) => {
|
|
113
|
+
return ctx.db.query(table.name).paginate(args.paginationOpts);
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
update: mutation({
|
|
117
|
+
args: {
|
|
118
|
+
id: v.id(table.name),
|
|
119
|
+
patch: v.object(partial(table.withoutSystemFields)),
|
|
120
|
+
},
|
|
121
|
+
handler: async (ctx, args) => {
|
|
122
|
+
await ctx.db.patch(args.id, args.patch);
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
destroy: mutation({
|
|
126
|
+
args: { id: table._id },
|
|
127
|
+
handler: async (ctx, args) => {
|
|
128
|
+
const old = await ctx.db.get(args.id);
|
|
129
|
+
if (old) {
|
|
130
|
+
await ctx.db.delete(args.id);
|
|
131
|
+
}
|
|
132
|
+
return old;
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
};
|
|
136
|
+
}
|