convex-verify 0.1.1 → 1.1.0
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 +157 -189
- package/dist/configs/index.d.mts +1 -1
- package/dist/configs/index.d.ts +1 -1
- package/dist/core/index.d.mts +26 -56
- package/dist/core/index.d.ts +26 -56
- package/dist/core/index.js +90 -53
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +87 -50
- package/dist/core/index.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +115 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +112 -66
- package/dist/index.mjs.map +1 -1
- package/dist/plugin-BOb1Kw1A.d.ts +47 -0
- package/dist/plugin-DlsboiCF.d.mts +47 -0
- package/dist/plugins/index.d.mts +12 -12
- package/dist/plugins/index.d.ts +12 -12
- package/dist/plugins/index.js +25 -25
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/index.mjs +25 -25
- package/dist/plugins/index.mjs.map +1 -1
- package/dist/transforms/index.d.mts +1 -1
- package/dist/transforms/index.d.ts +1 -1
- package/dist/{types-_64SXyva.d.mts → types-DvJMYubf.d.mts} +7 -7
- package/dist/{types-_64SXyva.d.ts → types-DvJMYubf.d.ts} +7 -7
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +84 -81
- package/dist/plugin-BjJ7yjrc.d.ts +0 -141
- package/dist/plugin-mHMV2-SG.d.mts +0 -141
package/README.md
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Type-safe verification and validation for Convex database operations.
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
## Features
|
|
6
7
|
|
|
7
8
|
- **Type-safe insert/patch** - Full TypeScript inference for your schema
|
|
8
9
|
- **Default values** - Make fields optional in `insert()` with automatic defaults
|
|
9
10
|
- **Protected columns** - Prevent accidental updates to critical fields in `patch()`
|
|
10
|
-
- **
|
|
11
|
-
- **Extensible** - Create your own validation
|
|
11
|
+
- **Unique constraints** - Enforce unique rows and columns using your indexes
|
|
12
|
+
- **Extensible** - Create your own validation extensions
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -18,7 +19,7 @@ pnpm install convex-verify
|
|
|
18
19
|
|
|
19
20
|
**Peer Dependencies:**
|
|
20
21
|
|
|
21
|
-
- `convex` >= 1.
|
|
22
|
+
- `convex` >= 1.34.1
|
|
22
23
|
|
|
23
24
|
## Quick Start
|
|
24
25
|
|
|
@@ -34,28 +35,21 @@ import {
|
|
|
34
35
|
import schema from "./schema";
|
|
35
36
|
|
|
36
37
|
export const { insert, patch, dangerouslyPatch } = verifyConfig(schema, {
|
|
37
|
-
|
|
38
|
-
defaultValues: defaultValuesConfig(schema, () => ({
|
|
38
|
+
defaultValues: defaultValuesConfig(schema, {
|
|
39
39
|
posts: { status: "draft", views: 0 },
|
|
40
|
-
})
|
|
40
|
+
}),
|
|
41
41
|
|
|
42
|
-
// Prevent patching critical fields
|
|
43
42
|
protectedColumns: protectedColumnsConfig(schema, {
|
|
44
43
|
posts: ["authorId"],
|
|
45
44
|
}),
|
|
46
45
|
|
|
47
|
-
// Enforce unique row combinations
|
|
48
46
|
uniqueRow: uniqueRowConfig(schema, {
|
|
49
47
|
posts: ["by_author_slug"],
|
|
50
48
|
}),
|
|
51
49
|
|
|
52
|
-
// Enforce unique column values
|
|
53
50
|
uniqueColumn: uniqueColumnConfig(schema, {
|
|
54
51
|
users: ["by_email", "by_username"],
|
|
55
52
|
}),
|
|
56
|
-
|
|
57
|
-
// Custom/third-party plugins (optional)
|
|
58
|
-
plugins: [],
|
|
59
53
|
});
|
|
60
54
|
```
|
|
61
55
|
|
|
@@ -67,7 +61,7 @@ import { insert, patch } from "./verify";
|
|
|
67
61
|
export const createPost = mutation({
|
|
68
62
|
args: { title: v.string(), content: v.string() },
|
|
69
63
|
handler: async (ctx, args) => {
|
|
70
|
-
// status and views are optional
|
|
64
|
+
// status and views are optional since defaults have been set
|
|
71
65
|
return await insert(ctx, "posts", {
|
|
72
66
|
title: args.title,
|
|
73
67
|
content: args.content,
|
|
@@ -82,6 +76,7 @@ export const updatePost = mutation({
|
|
|
82
76
|
// authorId is protected - TypeScript won't allow it here
|
|
83
77
|
await patch(ctx, "posts", args.id, {
|
|
84
78
|
title: args.title,
|
|
79
|
+
// authorId: "someone_else", // TypeScript error!
|
|
85
80
|
});
|
|
86
81
|
},
|
|
87
82
|
});
|
|
@@ -96,17 +91,12 @@ export const updatePost = mutation({
|
|
|
96
91
|
Main configuration function that returns typed `insert`, `patch`, and `dangerouslyPatch` functions.
|
|
97
92
|
|
|
98
93
|
```ts
|
|
99
|
-
const { insert, patch, dangerouslyPatch
|
|
100
|
-
// Type-affecting configs
|
|
94
|
+
const { insert, patch, dangerouslyPatch } = verifyConfig(schema, {
|
|
101
95
|
defaultValues?: DefaultValuesConfig,
|
|
102
96
|
protectedColumns?: ProtectedColumnsConfig,
|
|
103
|
-
|
|
104
|
-
// Built-in validation configs
|
|
105
97
|
uniqueRow?: UniqueRowConfig,
|
|
106
98
|
uniqueColumn?: UniqueColumnConfig,
|
|
107
|
-
|
|
108
|
-
// Custom/third-party plugins
|
|
109
|
-
plugins?: ValidatePlugin[],
|
|
99
|
+
extensions?: Extension[],
|
|
110
100
|
});
|
|
111
101
|
```
|
|
112
102
|
|
|
@@ -114,42 +104,35 @@ const { insert, patch, dangerouslyPatch, configs } = verifyConfig(schema, {
|
|
|
114
104
|
|
|
115
105
|
| Function | Description |
|
|
116
106
|
| ------------------ | ----------------------------------------------------------------------------------- |
|
|
117
|
-
| `insert` | Insert with default values applied and
|
|
118
|
-
| `patch` | Patch with protected columns removed
|
|
107
|
+
| `insert` | Insert with default values applied and extensions run |
|
|
108
|
+
| `patch` | Patch with protected columns removed and extensions run |
|
|
119
109
|
| `dangerouslyPatch` | Patch with full access to all columns (bypasses protected columns type restriction) |
|
|
120
|
-
| `configs` | The original config object (for debugging) |
|
|
121
110
|
|
|
122
111
|
---
|
|
123
112
|
|
|
124
|
-
##
|
|
113
|
+
## `defaultValuesConfig`
|
|
125
114
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
### `defaultValuesConfig(schema, config)`
|
|
129
|
-
|
|
130
|
-
Makes specified fields optional in `insert()` by providing default values.
|
|
115
|
+
Makes specified fields optional in `insert()` by providing default values. The types update automatically - fields with defaults become optional.
|
|
131
116
|
|
|
132
117
|
```ts
|
|
133
118
|
import { defaultValuesConfig } from "convex-verify";
|
|
134
|
-
// or
|
|
135
|
-
import { defaultValuesConfig } from "convex-verify/transforms";
|
|
136
119
|
```
|
|
137
120
|
|
|
138
|
-
|
|
121
|
+
### Static Values
|
|
139
122
|
|
|
140
123
|
```ts
|
|
141
|
-
const
|
|
124
|
+
const config = defaultValuesConfig(schema, {
|
|
142
125
|
posts: { status: "draft", views: 0 },
|
|
143
126
|
comments: { likes: 0 },
|
|
144
127
|
});
|
|
145
128
|
```
|
|
146
129
|
|
|
147
|
-
|
|
130
|
+
### Dynamic Values
|
|
148
131
|
|
|
149
|
-
Use a function
|
|
132
|
+
Use a function when values should be generated fresh on each insert:
|
|
150
133
|
|
|
151
134
|
```ts
|
|
152
|
-
const
|
|
135
|
+
const config = defaultValuesConfig(schema, () => ({
|
|
153
136
|
posts: {
|
|
154
137
|
status: "draft",
|
|
155
138
|
slug: generateRandomSlug(),
|
|
@@ -158,10 +141,10 @@ const defaults = defaultValuesConfig(schema, () => ({
|
|
|
158
141
|
}));
|
|
159
142
|
```
|
|
160
143
|
|
|
161
|
-
|
|
144
|
+
### Async Values
|
|
162
145
|
|
|
163
146
|
```ts
|
|
164
|
-
const
|
|
147
|
+
const config = defaultValuesConfig(schema, async () => ({
|
|
165
148
|
posts: {
|
|
166
149
|
category: await fetchDefaultCategory(),
|
|
167
150
|
},
|
|
@@ -170,249 +153,234 @@ const defaults = defaultValuesConfig(schema, async () => ({
|
|
|
170
153
|
|
|
171
154
|
---
|
|
172
155
|
|
|
173
|
-
##
|
|
174
|
-
|
|
175
|
-
Configs modify the input type of `patch()`.
|
|
176
|
-
|
|
177
|
-
### `protectedColumnsConfig(schema, config)`
|
|
156
|
+
## `protectedColumnsConfig`
|
|
178
157
|
|
|
179
|
-
Removes specified columns from the `patch()` input type, preventing accidental updates
|
|
158
|
+
Removes specified columns from the `patch()` input type, preventing accidental updates to critical fields like `authorId` or `createdAt`.
|
|
180
159
|
|
|
181
160
|
```ts
|
|
182
161
|
import { protectedColumnsConfig } from "convex-verify";
|
|
183
|
-
// or
|
|
184
|
-
import { protectedColumnsConfig } from "convex-verify/configs";
|
|
185
162
|
```
|
|
186
163
|
|
|
187
|
-
|
|
164
|
+
### Usage
|
|
188
165
|
|
|
189
166
|
```ts
|
|
190
|
-
const
|
|
167
|
+
const config = protectedColumnsConfig(schema, {
|
|
191
168
|
posts: ["authorId", "createdAt"],
|
|
192
169
|
comments: ["postId", "authorId"],
|
|
193
170
|
});
|
|
194
171
|
```
|
|
195
172
|
|
|
196
|
-
|
|
173
|
+
### Bypassing Protection
|
|
197
174
|
|
|
198
|
-
Use `dangerouslyPatch()` when you need to update protected columns:
|
|
175
|
+
Use `dangerouslyPatch()` when you legitimately need to update protected columns:
|
|
199
176
|
|
|
200
177
|
```ts
|
|
201
178
|
// Regular patch - authorId not allowed
|
|
202
179
|
await patch(ctx, "posts", id, {
|
|
203
|
-
authorId: newAuthorId, //
|
|
204
|
-
title: "New Title", //
|
|
180
|
+
authorId: newAuthorId, // TypeScript error!
|
|
181
|
+
title: "New Title", // OK
|
|
205
182
|
});
|
|
206
183
|
|
|
207
184
|
// Dangerous patch - full access
|
|
208
185
|
await dangerouslyPatch(ctx, "posts", id, {
|
|
209
|
-
authorId: newAuthorId, //
|
|
186
|
+
authorId: newAuthorId, // OK (bypasses type restriction)
|
|
210
187
|
title: "New Title",
|
|
211
188
|
});
|
|
212
189
|
```
|
|
213
190
|
|
|
214
|
-
**Note:** `dangerouslyPatch()` still runs validation
|
|
191
|
+
**Note:** `dangerouslyPatch()` still runs validation extensions - only the type restriction is bypassed.
|
|
215
192
|
|
|
216
193
|
---
|
|
217
194
|
|
|
218
|
-
##
|
|
195
|
+
## `uniqueRowConfig`
|
|
219
196
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
### `uniqueRowConfig(schema, config)`
|
|
223
|
-
|
|
224
|
-
Enforces uniqueness across multiple columns using composite indexes.
|
|
197
|
+
Enforces uniqueness across multiple columns using composite indexes. Useful for things like "unique slug per author" or "unique name per organization".
|
|
225
198
|
|
|
226
199
|
```ts
|
|
227
200
|
import { uniqueRowConfig } from "convex-verify";
|
|
228
|
-
// or
|
|
229
|
-
import { uniqueRowConfig } from "convex-verify/plugins";
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
#### Usage
|
|
233
|
-
|
|
234
|
-
```ts
|
|
235
|
-
// As a named config key (recommended)
|
|
236
|
-
verifyConfig(schema, {
|
|
237
|
-
uniqueRow: uniqueRowConfig(schema, {
|
|
238
|
-
posts: ["by_author_slug"],
|
|
239
|
-
}),
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// Or in the plugins array
|
|
243
|
-
verifyConfig(schema, {
|
|
244
|
-
plugins: [
|
|
245
|
-
uniqueRowConfig(schema, {
|
|
246
|
-
posts: ["by_author_slug"],
|
|
247
|
-
}),
|
|
248
|
-
],
|
|
249
|
-
});
|
|
250
201
|
```
|
|
251
202
|
|
|
252
|
-
|
|
203
|
+
### Usage
|
|
253
204
|
|
|
254
205
|
```ts
|
|
255
|
-
const
|
|
256
|
-
posts: ["by_author_slug"], // Unique author + slug
|
|
257
|
-
projects: ["
|
|
206
|
+
const config = uniqueRowConfig(schema, {
|
|
207
|
+
posts: ["by_author_slug"], // Unique author + slug combination
|
|
208
|
+
projects: ["by_org_name"], // Unique org + name combination
|
|
258
209
|
});
|
|
259
210
|
```
|
|
260
211
|
|
|
261
|
-
|
|
212
|
+
### With Options
|
|
262
213
|
|
|
263
214
|
```ts
|
|
264
|
-
const
|
|
215
|
+
const config = uniqueRowConfig(schema, {
|
|
265
216
|
posts: [
|
|
266
217
|
{
|
|
267
218
|
index: "by_author_slug",
|
|
268
|
-
identifiers: ["_id", "authorId"], // Fields
|
|
219
|
+
identifiers: ["_id", "authorId"], // Fields that identify "same document"
|
|
269
220
|
},
|
|
270
221
|
],
|
|
271
222
|
});
|
|
272
223
|
```
|
|
273
224
|
|
|
274
|
-
|
|
225
|
+
The `identifiers` option controls which fields are checked when determining if a conflicting row is actually the same document (useful during patch operations).
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## `uniqueColumnConfig`
|
|
275
230
|
|
|
276
|
-
Enforces uniqueness on single columns using indexes.
|
|
231
|
+
Enforces uniqueness on single columns using indexes. Useful for email addresses, usernames, slugs, etc.
|
|
277
232
|
|
|
278
233
|
```ts
|
|
279
234
|
import { uniqueColumnConfig } from "convex-verify";
|
|
280
|
-
// or
|
|
281
|
-
import { uniqueColumnConfig } from "convex-verify/plugins";
|
|
282
235
|
```
|
|
283
236
|
|
|
284
|
-
|
|
237
|
+
### Usage
|
|
285
238
|
|
|
286
239
|
```ts
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
users: ["by_email", "by_username"],
|
|
291
|
-
}),
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
// Or in the plugins array
|
|
295
|
-
verifyConfig(schema, {
|
|
296
|
-
plugins: [
|
|
297
|
-
uniqueColumnConfig(schema, {
|
|
298
|
-
users: ["by_email", "by_username"],
|
|
299
|
-
}),
|
|
300
|
-
],
|
|
240
|
+
const config = uniqueColumnConfig(schema, {
|
|
241
|
+
users: ["by_email", "by_username"],
|
|
242
|
+
organizations: ["by_slug"],
|
|
301
243
|
});
|
|
302
244
|
```
|
|
303
245
|
|
|
304
|
-
The column name is derived from the index name by removing `by_` prefix:
|
|
246
|
+
The column name is derived from the index name by removing the `by_` prefix:
|
|
305
247
|
|
|
306
248
|
- `by_username` → checks `username` column
|
|
307
249
|
- `by_email` → checks `email` column
|
|
308
250
|
|
|
309
|
-
|
|
251
|
+
### With Options
|
|
310
252
|
|
|
311
253
|
```ts
|
|
312
|
-
const
|
|
313
|
-
users: ["by_username", "by_email"],
|
|
314
|
-
organizations: ["by_slug"],
|
|
315
|
-
});
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
#### With Options
|
|
319
|
-
|
|
320
|
-
```ts
|
|
321
|
-
const uniqueColumns = uniqueColumnConfig(schema, {
|
|
254
|
+
const config = uniqueColumnConfig(schema, {
|
|
322
255
|
users: [
|
|
323
|
-
"by_username",
|
|
324
|
-
{ index: "by_email", identifiers: ["_id", "clerkId"] },
|
|
256
|
+
"by_username",
|
|
257
|
+
{ index: "by_email", identifiers: ["_id", "clerkId"] },
|
|
325
258
|
],
|
|
326
259
|
});
|
|
327
260
|
```
|
|
328
261
|
|
|
329
|
-
|
|
262
|
+
---
|
|
330
263
|
|
|
331
|
-
|
|
264
|
+
## Custom Extensions
|
|
332
265
|
|
|
333
|
-
|
|
266
|
+
Custom extensions let you add your own validation and transformation logic that runs during `insert()` and `patch()` operations.
|
|
334
267
|
|
|
335
|
-
|
|
268
|
+
### Use Cases
|
|
336
269
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
270
|
+
- **Authorization checks** - Verify the user has permission to create/modify a document
|
|
271
|
+
- **Data validation** - Check that values meet business rules (e.g., positive numbers, valid URLs)
|
|
272
|
+
- **Cross-field validation** - Ensure fields are consistent with each other
|
|
273
|
+
- **Normalization / sanitization** - Lowercase emails, trim slugs, clean incoming strings
|
|
274
|
+
- **External validation** - Check against external APIs or services
|
|
275
|
+
- **Audit logging** - Log operations before they complete
|
|
342
276
|
|
|
343
|
-
|
|
277
|
+
### Limitations
|
|
344
278
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
for (const field of context.config.fields) {
|
|
352
|
-
if (!data[field]) {
|
|
353
|
-
throw new ConvexError({
|
|
354
|
-
message: `Missing required field: ${field}`,
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return data;
|
|
359
|
-
},
|
|
360
|
-
},
|
|
361
|
-
);
|
|
362
|
-
```
|
|
279
|
+
- Extensions run **after** type-affecting configs (like `defaultValues`) have been applied
|
|
280
|
+
- Extensions **cannot modify types** - they can change runtime data, but not the TypeScript types
|
|
281
|
+
- Extensions may **return modified data** - use this to sanitize, normalize, or enrich payloads
|
|
282
|
+
- Custom extensions from `extensions: []` run **before** built-in `uniqueRow` / `uniqueColumn` configs
|
|
283
|
+
- `patch()` still strips protected columns at runtime; use `dangerouslyPatch()` if an extension must change them
|
|
284
|
+
- Extension errors should use `ConvexError` for proper error handling on the client
|
|
363
285
|
|
|
364
|
-
|
|
286
|
+
### Creating an Extension
|
|
287
|
+
|
|
288
|
+
Use `createExtension`. If you want schema-aware typing in the callback, pass your schema type as the generic:
|
|
365
289
|
|
|
366
290
|
```ts
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
291
|
+
import { createExtension } from "convex-verify";
|
|
292
|
+
import { ConvexError } from "convex/values";
|
|
293
|
+
|
|
294
|
+
const normalizeEmail = createExtension<typeof schema>((input) => {
|
|
295
|
+
if (input.tableName !== "users") {
|
|
296
|
+
return input.data;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (input.operation === "insert") {
|
|
300
|
+
return {
|
|
301
|
+
...input.data,
|
|
302
|
+
email: input.data.email.toLowerCase().trim(),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
...input.data,
|
|
308
|
+
...(input.data.email !== undefined && {
|
|
309
|
+
email: input.data.email.toLowerCase().trim(),
|
|
310
|
+
}),
|
|
311
|
+
};
|
|
312
|
+
});
|
|
384
313
|
```
|
|
385
314
|
|
|
386
|
-
|
|
315
|
+
Use a single `input` parameter instead of destructuring when you want narrowing.
|
|
316
|
+
That lets TypeScript narrow `data` from both `tableName` and `operation`.
|
|
317
|
+
|
|
318
|
+
### Extension Context
|
|
387
319
|
|
|
388
|
-
|
|
320
|
+
Your extension function receives:
|
|
389
321
|
|
|
390
322
|
```ts
|
|
391
|
-
type
|
|
392
|
-
ctx: GenericMutationCtx; // Convex mutation context
|
|
323
|
+
type ExtensionInput = {
|
|
324
|
+
ctx: GenericMutationCtx; // Convex mutation context (has ctx.db, etc.)
|
|
393
325
|
tableName: string; // Table being operated on
|
|
394
326
|
operation: "insert" | "patch";
|
|
395
327
|
patchId?: GenericId; // Document ID (patch only)
|
|
396
|
-
|
|
397
|
-
|
|
328
|
+
schema: SchemaDefinition; // Schema reference
|
|
329
|
+
data: unknown;
|
|
398
330
|
};
|
|
399
331
|
```
|
|
400
332
|
|
|
401
|
-
|
|
333
|
+
### Example: Required Fields
|
|
402
334
|
|
|
403
|
-
|
|
335
|
+
```ts
|
|
336
|
+
const requiredFields = createExtension<typeof schema>((input) => {
|
|
337
|
+
if (input.tableName !== "posts" || input.operation === "patch") {
|
|
338
|
+
return input.data;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for (const field of ["title", "content"]) {
|
|
342
|
+
if (!input.data[field]) {
|
|
343
|
+
throw new ConvexError({
|
|
344
|
+
code: "VALIDATION_ERROR",
|
|
345
|
+
message: `Missing required field: ${field}`,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
404
349
|
|
|
405
|
-
|
|
350
|
+
return input.data;
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Example: Async Authorization Check
|
|
406
355
|
|
|
407
356
|
```ts
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
357
|
+
const ownership = createExtension<typeof schema>(async (input) => {
|
|
358
|
+
if (input.tableName !== "posts" || input.operation !== "patch") {
|
|
359
|
+
return input.data;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const doc = await input.ctx.db.get(input.patchId);
|
|
363
|
+
const identity = await input.ctx.auth.getUserIdentity();
|
|
364
|
+
|
|
365
|
+
if (doc?.ownerId !== identity?.subject) {
|
|
366
|
+
throw new ConvexError({
|
|
367
|
+
code: "UNAUTHORIZED",
|
|
368
|
+
message: "You don't have permission to edit this document",
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return input.data;
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Using Custom Extensions
|
|
377
|
+
|
|
378
|
+
Add extensions to the `extensions` array in your config:
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
const { insert, patch } = verifyConfig(schema, {
|
|
382
|
+
extensions: [requiredFields, ownership],
|
|
383
|
+
});
|
|
416
384
|
```
|
|
417
385
|
|
|
418
386
|
---
|
|
@@ -421,7 +389,7 @@ import { getTableIndexes } from "convex-verify/utils";
|
|
|
421
389
|
|
|
422
390
|
### `onFail` Callback
|
|
423
391
|
|
|
424
|
-
|
|
392
|
+
Operations accept an optional `onFail` callback for handling validation failures:
|
|
425
393
|
|
|
426
394
|
```ts
|
|
427
395
|
await insert(ctx, "posts", data, {
|
|
@@ -438,7 +406,7 @@ await insert(ctx, "posts", data, {
|
|
|
438
406
|
|
|
439
407
|
### Error Types
|
|
440
408
|
|
|
441
|
-
|
|
409
|
+
Built-in validation extensions throw `ConvexError` with these codes:
|
|
442
410
|
|
|
443
411
|
- `UNIQUE_ROW_VERIFICATION_ERROR` - Duplicate row detected
|
|
444
412
|
- `UNIQUE_COLUMN_VERIFICATION_ERROR` - Duplicate column value detected
|
|
@@ -447,11 +415,11 @@ Validation plugins throw `ConvexError` with specific codes:
|
|
|
447
415
|
|
|
448
416
|
## TypeScript
|
|
449
417
|
|
|
450
|
-
This library
|
|
418
|
+
This library provides full type inference:
|
|
451
419
|
|
|
452
420
|
- `insert()` types reflect optional fields from `defaultValues`
|
|
453
421
|
- `patch()` types exclude protected columns
|
|
454
|
-
-
|
|
422
|
+
- All configs are type-checked against your schema
|
|
455
423
|
- Index names are validated against your schema's indexes
|
|
456
424
|
|
|
457
425
|
---
|
package/dist/configs/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SchemaDefinition, GenericSchema, DataModelFromSchemaDefinition, WithoutSystemFields } from 'convex/server';
|
|
2
|
-
import { k as DMGeneric } from '../types-
|
|
2
|
+
import { k as DMGeneric } from '../types-DvJMYubf.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Config data type for protected columns.
|
package/dist/configs/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SchemaDefinition, GenericSchema, DataModelFromSchemaDefinition, WithoutSystemFields } from 'convex/server';
|
|
2
|
-
import { k as DMGeneric } from '../types-
|
|
2
|
+
import { k as DMGeneric } from '../types-DvJMYubf.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Config data type for protected columns.
|