convex-verify 0.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/LICENSE +21 -0
- package/README.md +403 -0
- package/dist/configs/index.d.mts +51 -0
- package/dist/configs/index.d.ts +51 -0
- package/dist/configs/index.js +38 -0
- package/dist/configs/index.js.map +1 -0
- package/dist/configs/index.mjs +11 -0
- package/dist/configs/index.mjs.map +1 -0
- package/dist/core/index.d.mts +58 -0
- package/dist/core/index.d.ts +58 -0
- package/dist/core/index.js +144 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +113 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +442 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +404 -0
- package/dist/index.mjs.map +1 -0
- package/dist/plugin-BjJ7yjrc.d.ts +141 -0
- package/dist/plugin-mHMV2-SG.d.mts +141 -0
- package/dist/plugins/index.d.mts +85 -0
- package/dist/plugins/index.d.ts +85 -0
- package/dist/plugins/index.js +317 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/index.mjs +289 -0
- package/dist/plugins/index.mjs.map +1 -0
- package/dist/transforms/index.d.mts +38 -0
- package/dist/transforms/index.d.ts +38 -0
- package/dist/transforms/index.js +46 -0
- package/dist/transforms/index.js.map +1 -0
- package/dist/transforms/index.mjs +19 -0
- package/dist/transforms/index.mjs.map +1 -0
- package/dist/types-_64SXyva.d.mts +151 -0
- package/dist/types-_64SXyva.d.ts +151 -0
- package/dist/utils/index.d.mts +36 -0
- package/dist/utils/index.d.ts +36 -0
- package/dist/utils/index.js +113 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +83 -0
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# convex-verify
|
|
2
|
+
|
|
3
|
+
Type-safe verification and validation for Convex database operations.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-safe insert/patch** - Full TypeScript inference for your schema
|
|
8
|
+
- **Default values** - Make fields optional in `insert()` with automatic defaults
|
|
9
|
+
- **Protected columns** - Prevent accidental updates to critical fields in `patch()`
|
|
10
|
+
- **Validation plugins** - Unique row/column enforcement, custom validators
|
|
11
|
+
- **Extensible** - Create your own validation plugins
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install convex-verify
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Peer Dependencies:**
|
|
20
|
+
|
|
21
|
+
- `convex` >= 1.17.4
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import {
|
|
27
|
+
defaultValuesConfig,
|
|
28
|
+
protectedColumnsConfig,
|
|
29
|
+
uniqueColumnConfig,
|
|
30
|
+
uniqueRowConfig,
|
|
31
|
+
verifyConfig,
|
|
32
|
+
} from 'convex-verify';
|
|
33
|
+
|
|
34
|
+
import schema from './schema';
|
|
35
|
+
|
|
36
|
+
export const { insert, patch, dangerouslyPatch } = verifyConfig(schema, {
|
|
37
|
+
// Make fields optional with defaults
|
|
38
|
+
defaultValues: defaultValuesConfig(schema, () => ({
|
|
39
|
+
posts: { status: 'draft', views: 0 },
|
|
40
|
+
})),
|
|
41
|
+
|
|
42
|
+
// Prevent patching critical fields
|
|
43
|
+
protectedColumns: protectedColumnsConfig(schema, {
|
|
44
|
+
posts: ['authorId'],
|
|
45
|
+
}),
|
|
46
|
+
|
|
47
|
+
// Validation plugins
|
|
48
|
+
plugins: [
|
|
49
|
+
uniqueRowConfig(schema, {
|
|
50
|
+
posts: ['by_author_slug'],
|
|
51
|
+
}),
|
|
52
|
+
uniqueColumnConfig(schema, {
|
|
53
|
+
users: ['by_email', 'by_username'],
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then use in your mutations:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { insert, patch } from './verify';
|
|
63
|
+
|
|
64
|
+
export const createPost = mutation({
|
|
65
|
+
args: { title: v.string(), content: v.string() },
|
|
66
|
+
handler: async (ctx, args) => {
|
|
67
|
+
// status and views are optional - defaults are applied
|
|
68
|
+
return await insert(ctx, 'posts', {
|
|
69
|
+
title: args.title,
|
|
70
|
+
content: args.content,
|
|
71
|
+
authorId: ctx.auth.userId,
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const updatePost = mutation({
|
|
77
|
+
args: { id: v.id('posts'), title: v.string() },
|
|
78
|
+
handler: async (ctx, args) => {
|
|
79
|
+
// authorId is protected - TypeScript won't allow it here
|
|
80
|
+
await patch(ctx, 'posts', args.id, {
|
|
81
|
+
title: args.title,
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## API Reference
|
|
90
|
+
|
|
91
|
+
### `verifyConfig(schema, config)`
|
|
92
|
+
|
|
93
|
+
Main configuration function that returns typed `insert`, `patch`, and `dangerouslyPatch` functions.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const { insert, patch, dangerouslyPatch, configs } = verifyConfig(schema, {
|
|
97
|
+
defaultValues?: DefaultValuesConfig,
|
|
98
|
+
protectedColumns?: ProtectedColumnsConfig,
|
|
99
|
+
plugins?: ValidatePlugin[],
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Returns
|
|
104
|
+
|
|
105
|
+
| Function | Description |
|
|
106
|
+
| ------------------ | ----------------------------------------------------------------------------------- |
|
|
107
|
+
| `insert` | Insert with default values applied and validation plugins run |
|
|
108
|
+
| `patch` | Patch with protected columns removed from type and validation plugins run |
|
|
109
|
+
| `dangerouslyPatch` | Patch with full access to all columns (bypasses protected columns type restriction) |
|
|
110
|
+
| `configs` | The original config object (for debugging) |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Transforms
|
|
115
|
+
|
|
116
|
+
Transforms modify the input type of `insert()`.
|
|
117
|
+
|
|
118
|
+
### `defaultValuesConfig(schema, config)`
|
|
119
|
+
|
|
120
|
+
Makes specified fields optional in `insert()` by providing default values.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { defaultValuesConfig } from 'convex-verify';
|
|
124
|
+
// or
|
|
125
|
+
import { defaultValuesConfig } from 'convex-verify/transforms';
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Static Config
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const defaults = defaultValuesConfig(schema, {
|
|
132
|
+
posts: { status: 'draft', views: 0 },
|
|
133
|
+
comments: { likes: 0 },
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Dynamic Config (Fresh Values)
|
|
138
|
+
|
|
139
|
+
Use a function for values that should be generated fresh on each insert:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const defaults = defaultValuesConfig(schema, () => ({
|
|
143
|
+
posts: {
|
|
144
|
+
status: 'draft',
|
|
145
|
+
slug: generateRandomSlug(),
|
|
146
|
+
createdAt: Date.now(),
|
|
147
|
+
},
|
|
148
|
+
}));
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Async Config
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const defaults = defaultValuesConfig(schema, async () => ({
|
|
155
|
+
posts: {
|
|
156
|
+
category: await fetchDefaultCategory(),
|
|
157
|
+
},
|
|
158
|
+
}));
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Configs
|
|
164
|
+
|
|
165
|
+
Configs modify the input type of `patch()`.
|
|
166
|
+
|
|
167
|
+
### `protectedColumnsConfig(schema, config)`
|
|
168
|
+
|
|
169
|
+
Removes specified columns from the `patch()` input type, preventing accidental updates.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { protectedColumnsConfig } from 'convex-verify';
|
|
173
|
+
// or
|
|
174
|
+
import { protectedColumnsConfig } from 'convex-verify/configs';
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Example
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const protected = protectedColumnsConfig(schema, {
|
|
181
|
+
posts: ['authorId', 'createdAt'],
|
|
182
|
+
comments: ['postId', 'authorId'],
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### Bypassing Protection
|
|
187
|
+
|
|
188
|
+
Use `dangerouslyPatch()` when you need to update protected columns:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
// Regular patch - authorId not allowed
|
|
192
|
+
await patch(ctx, 'posts', id, {
|
|
193
|
+
authorId: newAuthorId, // ❌ TypeScript error
|
|
194
|
+
title: 'New Title', // ✅ OK
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Dangerous patch - full access
|
|
198
|
+
await dangerouslyPatch(ctx, 'posts', id, {
|
|
199
|
+
authorId: newAuthorId, // ✅ OK (bypasses protection)
|
|
200
|
+
title: 'New Title',
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Note:** `dangerouslyPatch()` still runs validation plugins - only the type restriction is bypassed.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Plugins
|
|
209
|
+
|
|
210
|
+
Plugins validate data during `insert()` and `patch()` operations. They run after transforms and can throw errors to prevent the operation.
|
|
211
|
+
|
|
212
|
+
### `uniqueRowConfig(schema, config)`
|
|
213
|
+
|
|
214
|
+
Enforces uniqueness across multiple columns using composite indexes.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { uniqueRowConfig } from 'convex-verify';
|
|
218
|
+
// or
|
|
219
|
+
import { uniqueRowConfig } from 'convex-verify/plugins';
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### Shorthand (Index Names)
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const uniqueRows = uniqueRowConfig(schema, {
|
|
226
|
+
posts: ['by_author_slug'], // Unique author + slug combo
|
|
227
|
+
projects: ['by_org_slug'], // Unique org + slug combo
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
#### With Options
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
const uniqueRows = uniqueRowConfig(schema, {
|
|
235
|
+
posts: [
|
|
236
|
+
{
|
|
237
|
+
index: 'by_author_slug',
|
|
238
|
+
identifiers: ['_id', 'authorId'], // Fields to check for "same document"
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### `uniqueColumnConfig(schema, config)`
|
|
245
|
+
|
|
246
|
+
Enforces uniqueness on single columns using indexes.
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { uniqueColumnConfig } from 'convex-verify';
|
|
250
|
+
// or
|
|
251
|
+
import { uniqueColumnConfig } from 'convex-verify/plugins';
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The column name is derived from the index name by removing `by_` prefix:
|
|
255
|
+
|
|
256
|
+
- `by_username` → checks `username` column
|
|
257
|
+
- `by_email` → checks `email` column
|
|
258
|
+
|
|
259
|
+
#### Example
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const uniqueColumns = uniqueColumnConfig(schema, {
|
|
263
|
+
users: ['by_username', 'by_email'],
|
|
264
|
+
organizations: ['by_slug'],
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### With Options
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
const uniqueColumns = uniqueColumnConfig(schema, {
|
|
272
|
+
users: [
|
|
273
|
+
'by_username', // shorthand
|
|
274
|
+
{ index: 'by_email', identifiers: ['_id', 'clerkId'] }, // with options
|
|
275
|
+
],
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### `createValidatePlugin(name, config, handlers)`
|
|
280
|
+
|
|
281
|
+
Create custom validation plugins.
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
import { createValidatePlugin } from 'convex-verify';
|
|
285
|
+
// or
|
|
286
|
+
import { createValidatePlugin } from 'convex-verify/core';
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### Example: Required Fields Plugin
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
const requiredFields = createValidatePlugin(
|
|
293
|
+
'requiredFields',
|
|
294
|
+
{ fields: ['title', 'content'] },
|
|
295
|
+
{
|
|
296
|
+
insert: (context, data) => {
|
|
297
|
+
for (const field of context.config.fields) {
|
|
298
|
+
if (!data[field]) {
|
|
299
|
+
throw new ConvexError({ message: `Missing required field: ${field}` });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return data;
|
|
303
|
+
},
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### Example: Async Validation
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
const checkOwnership = createValidatePlugin(
|
|
312
|
+
'checkOwnership',
|
|
313
|
+
{},
|
|
314
|
+
{
|
|
315
|
+
patch: async (context, data) => {
|
|
316
|
+
const existing = await context.ctx.db.get(context.patchId);
|
|
317
|
+
const user = await getCurrentUser(context.ctx);
|
|
318
|
+
|
|
319
|
+
if (existing?.authorId !== user._id) {
|
|
320
|
+
throw new ConvexError({ message: 'Not authorized to edit this document' });
|
|
321
|
+
}
|
|
322
|
+
return data;
|
|
323
|
+
},
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
#### Plugin Context
|
|
329
|
+
|
|
330
|
+
Plugins receive a `ValidateContext` object:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
type ValidateContext = {
|
|
334
|
+
ctx: GenericMutationCtx; // Convex mutation context
|
|
335
|
+
tableName: string; // Table being operated on
|
|
336
|
+
operation: 'insert' | 'patch';
|
|
337
|
+
patchId?: GenericId; // Document ID (patch only)
|
|
338
|
+
onFail?: OnFailCallback; // Callback for failure details
|
|
339
|
+
schema?: SchemaDefinition; // Schema reference
|
|
340
|
+
};
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Subpath Imports
|
|
346
|
+
|
|
347
|
+
For smaller bundle sizes, you can import from specific subpaths:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
// Import everything from root
|
|
351
|
+
import { uniqueRowConfig, verifyConfig } from 'convex-verify';
|
|
352
|
+
import { protectedColumnsConfig } from 'convex-verify/configs';
|
|
353
|
+
// Or import from specific subpaths
|
|
354
|
+
import { createValidatePlugin, verifyConfig } from 'convex-verify/core';
|
|
355
|
+
import { uniqueColumnConfig, uniqueRowConfig } from 'convex-verify/plugins';
|
|
356
|
+
import { defaultValuesConfig } from 'convex-verify/transforms';
|
|
357
|
+
import { getTableIndexes } from 'convex-verify/utils';
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Error Handling
|
|
363
|
+
|
|
364
|
+
### `onFail` Callback
|
|
365
|
+
|
|
366
|
+
All operations accept an optional `onFail` callback for handling validation failures:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
await insert(ctx, 'posts', data, {
|
|
370
|
+
onFail: (args) => {
|
|
371
|
+
if (args.uniqueRow) {
|
|
372
|
+
console.log('Duplicate row:', args.uniqueRow.existingData);
|
|
373
|
+
}
|
|
374
|
+
if (args.uniqueColumn) {
|
|
375
|
+
console.log('Duplicate column:', args.uniqueColumn.conflictingColumn);
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Error Types
|
|
382
|
+
|
|
383
|
+
Validation plugins throw `ConvexError` with specific codes:
|
|
384
|
+
|
|
385
|
+
- `UNIQUE_ROW_VERIFICATION_ERROR` - Duplicate row detected
|
|
386
|
+
- `UNIQUE_COLUMN_VERIFICATION_ERROR` - Duplicate column value detected
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## TypeScript
|
|
391
|
+
|
|
392
|
+
This library is written in TypeScript and provides full type inference:
|
|
393
|
+
|
|
394
|
+
- `insert()` types reflect optional fields from `defaultValues`
|
|
395
|
+
- `patch()` types exclude protected columns
|
|
396
|
+
- Plugin configs are type-checked against your schema
|
|
397
|
+
- Index names are validated against your schema's indexes
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## License
|
|
402
|
+
|
|
403
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { SchemaDefinition, GenericSchema, DataModelFromSchemaDefinition, WithoutSystemFields } from 'convex/server';
|
|
2
|
+
import { k as DMGeneric } from '../types-_64SXyva.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Config data type for protected columns.
|
|
6
|
+
* Maps table names to arrays of column names that should be protected from patching.
|
|
7
|
+
*/
|
|
8
|
+
type ProtectedColumnsConfigData<DM extends DMGeneric> = {
|
|
9
|
+
[K in keyof DM]?: (keyof WithoutSystemFields<DM[K]['document']>)[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates a protected columns config.
|
|
13
|
+
*
|
|
14
|
+
* Protected columns are removed from the patch() input type,
|
|
15
|
+
* preventing accidental updates to critical fields like foreign keys.
|
|
16
|
+
* Use dangerouslyPatch() to bypass this protection when needed.
|
|
17
|
+
*
|
|
18
|
+
* @param schema - Your Convex schema definition
|
|
19
|
+
* @param config - Object mapping table names to arrays of protected column names
|
|
20
|
+
* @returns Config object for use with verifyConfig
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const protectedColumns = protectedColumnsConfig(schema, {
|
|
25
|
+
* posts: ['authorId', 'createdAt'],
|
|
26
|
+
* comments: ['postId', 'authorId'],
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // In verifyConfig:
|
|
30
|
+
* const { patch, dangerouslyPatch } = verifyConfig(schema, {
|
|
31
|
+
* protectedColumns,
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // patch() won't allow authorId
|
|
35
|
+
* await patch(ctx, 'posts', id, {
|
|
36
|
+
* authorId: '...', // TS Error - property doesn't exist
|
|
37
|
+
* title: 'new', // OK
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* // dangerouslyPatch() allows all columns
|
|
41
|
+
* await dangerouslyPatch(ctx, 'posts', id, {
|
|
42
|
+
* authorId: '...', // OK - bypasses protection
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare const protectedColumnsConfig: <S extends SchemaDefinition<GenericSchema, boolean>, DataModel extends DataModelFromSchemaDefinition<S>, const C extends ProtectedColumnsConfigData<DataModel>>(_schema: S, config: C) => {
|
|
47
|
+
_type: "protectedColumns";
|
|
48
|
+
config: C;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export { type ProtectedColumnsConfigData, protectedColumnsConfig };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { SchemaDefinition, GenericSchema, DataModelFromSchemaDefinition, WithoutSystemFields } from 'convex/server';
|
|
2
|
+
import { k as DMGeneric } from '../types-_64SXyva.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Config data type for protected columns.
|
|
6
|
+
* Maps table names to arrays of column names that should be protected from patching.
|
|
7
|
+
*/
|
|
8
|
+
type ProtectedColumnsConfigData<DM extends DMGeneric> = {
|
|
9
|
+
[K in keyof DM]?: (keyof WithoutSystemFields<DM[K]['document']>)[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates a protected columns config.
|
|
13
|
+
*
|
|
14
|
+
* Protected columns are removed from the patch() input type,
|
|
15
|
+
* preventing accidental updates to critical fields like foreign keys.
|
|
16
|
+
* Use dangerouslyPatch() to bypass this protection when needed.
|
|
17
|
+
*
|
|
18
|
+
* @param schema - Your Convex schema definition
|
|
19
|
+
* @param config - Object mapping table names to arrays of protected column names
|
|
20
|
+
* @returns Config object for use with verifyConfig
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const protectedColumns = protectedColumnsConfig(schema, {
|
|
25
|
+
* posts: ['authorId', 'createdAt'],
|
|
26
|
+
* comments: ['postId', 'authorId'],
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // In verifyConfig:
|
|
30
|
+
* const { patch, dangerouslyPatch } = verifyConfig(schema, {
|
|
31
|
+
* protectedColumns,
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // patch() won't allow authorId
|
|
35
|
+
* await patch(ctx, 'posts', id, {
|
|
36
|
+
* authorId: '...', // TS Error - property doesn't exist
|
|
37
|
+
* title: 'new', // OK
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* // dangerouslyPatch() allows all columns
|
|
41
|
+
* await dangerouslyPatch(ctx, 'posts', id, {
|
|
42
|
+
* authorId: '...', // OK - bypasses protection
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare const protectedColumnsConfig: <S extends SchemaDefinition<GenericSchema, boolean>, DataModel extends DataModelFromSchemaDefinition<S>, const C extends ProtectedColumnsConfigData<DataModel>>(_schema: S, config: C) => {
|
|
47
|
+
_type: "protectedColumns";
|
|
48
|
+
config: C;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export { type ProtectedColumnsConfigData, protectedColumnsConfig };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/configs/index.ts
|
|
21
|
+
var configs_exports = {};
|
|
22
|
+
__export(configs_exports, {
|
|
23
|
+
protectedColumnsConfig: () => protectedColumnsConfig
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(configs_exports);
|
|
26
|
+
|
|
27
|
+
// src/configs/protectedColumnsConfig.ts
|
|
28
|
+
var protectedColumnsConfig = (_schema, config) => {
|
|
29
|
+
return {
|
|
30
|
+
_type: "protectedColumns",
|
|
31
|
+
config
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
35
|
+
0 && (module.exports = {
|
|
36
|
+
protectedColumnsConfig
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/configs/index.ts","../../src/configs/protectedColumnsConfig.ts"],"sourcesContent":["export { protectedColumnsConfig } from './protectedColumnsConfig';\nexport type { ProtectedColumnsConfigData } from './protectedColumnsConfig';\n","import {\n\tDataModelFromSchemaDefinition,\n\tGenericSchema,\n\tSchemaDefinition,\n\tWithoutSystemFields,\n} from 'convex/server';\n\nimport { DMGeneric } from '../core/types';\n\n/**\n * Config data type for protected columns.\n * Maps table names to arrays of column names that should be protected from patching.\n */\nexport type ProtectedColumnsConfigData<DM extends DMGeneric> = {\n\t[K in keyof DM]?: (keyof WithoutSystemFields<DM[K]['document']>)[];\n};\n\n/**\n * Creates a protected columns config.\n *\n * Protected columns are removed from the patch() input type,\n * preventing accidental updates to critical fields like foreign keys.\n * Use dangerouslyPatch() to bypass this protection when needed.\n *\n * @param schema - Your Convex schema definition\n * @param config - Object mapping table names to arrays of protected column names\n * @returns Config object for use with verifyConfig\n *\n * @example\n * ```ts\n * const protectedColumns = protectedColumnsConfig(schema, {\n * posts: ['authorId', 'createdAt'],\n * comments: ['postId', 'authorId'],\n * });\n *\n * // In verifyConfig:\n * const { patch, dangerouslyPatch } = verifyConfig(schema, {\n * protectedColumns,\n * });\n *\n * // patch() won't allow authorId\n * await patch(ctx, 'posts', id, {\n * authorId: '...', // TS Error - property doesn't exist\n * title: 'new', // OK\n * });\n *\n * // dangerouslyPatch() allows all columns\n * await dangerouslyPatch(ctx, 'posts', id, {\n * authorId: '...', // OK - bypasses protection\n * });\n * ```\n */\nexport const protectedColumnsConfig = <\n\tS extends SchemaDefinition<GenericSchema, boolean>,\n\tDataModel extends DataModelFromSchemaDefinition<S>,\n\tconst C extends ProtectedColumnsConfigData<DataModel>,\n>(\n\t_schema: S,\n\tconfig: C\n) => {\n\treturn {\n\t\t_type: 'protectedColumns' as const,\n\t\tconfig,\n\t};\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACoDO,IAAM,yBAAyB,CAKrC,SACA,WACI;AACJ,SAAO;AAAA,IACN,OAAO;AAAA,IACP;AAAA,EACD;AACD;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/configs/protectedColumnsConfig.ts"],"sourcesContent":["import {\n\tDataModelFromSchemaDefinition,\n\tGenericSchema,\n\tSchemaDefinition,\n\tWithoutSystemFields,\n} from 'convex/server';\n\nimport { DMGeneric } from '../core/types';\n\n/**\n * Config data type for protected columns.\n * Maps table names to arrays of column names that should be protected from patching.\n */\nexport type ProtectedColumnsConfigData<DM extends DMGeneric> = {\n\t[K in keyof DM]?: (keyof WithoutSystemFields<DM[K]['document']>)[];\n};\n\n/**\n * Creates a protected columns config.\n *\n * Protected columns are removed from the patch() input type,\n * preventing accidental updates to critical fields like foreign keys.\n * Use dangerouslyPatch() to bypass this protection when needed.\n *\n * @param schema - Your Convex schema definition\n * @param config - Object mapping table names to arrays of protected column names\n * @returns Config object for use with verifyConfig\n *\n * @example\n * ```ts\n * const protectedColumns = protectedColumnsConfig(schema, {\n * posts: ['authorId', 'createdAt'],\n * comments: ['postId', 'authorId'],\n * });\n *\n * // In verifyConfig:\n * const { patch, dangerouslyPatch } = verifyConfig(schema, {\n * protectedColumns,\n * });\n *\n * // patch() won't allow authorId\n * await patch(ctx, 'posts', id, {\n * authorId: '...', // TS Error - property doesn't exist\n * title: 'new', // OK\n * });\n *\n * // dangerouslyPatch() allows all columns\n * await dangerouslyPatch(ctx, 'posts', id, {\n * authorId: '...', // OK - bypasses protection\n * });\n * ```\n */\nexport const protectedColumnsConfig = <\n\tS extends SchemaDefinition<GenericSchema, boolean>,\n\tDataModel extends DataModelFromSchemaDefinition<S>,\n\tconst C extends ProtectedColumnsConfigData<DataModel>,\n>(\n\t_schema: S,\n\tconfig: C\n) => {\n\treturn {\n\t\t_type: 'protectedColumns' as const,\n\t\tconfig,\n\t};\n};\n"],"mappings":";AAoDO,IAAM,yBAAyB,CAKrC,SACA,WACI;AACJ,SAAO;AAAA,IACN,OAAO;AAAA,IACP;AAAA,EACD;AACD;","names":[]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { SchemaDefinition, GenericSchema, DataModelFromSchemaDefinition, TableNamesInDataModel, DocumentByName, GenericMutationCtx, WithoutSystemFields } from 'convex/server';
|
|
2
|
+
import { GenericId } from 'convex/values';
|
|
3
|
+
import { a as ValidatePlugin } from '../plugin-mHMV2-SG.mjs';
|
|
4
|
+
export { V as ValidateContext, b as ValidatePluginRecord, c as createValidatePlugin, i as isValidatePlugin, r as runValidatePlugins } from '../plugin-mHMV2-SG.mjs';
|
|
5
|
+
import { V as VerifyConfigInput, H as HasKey, M as MakeOptional, b as OptionalKeysForTable, a as OnFailCallback, d as ProtectedKeysForTable } from '../types-_64SXyva.mjs';
|
|
6
|
+
export { B as BaseConfigReturn, k as DMGeneric, D as DefaultValuesConfigData, l as DefaultValuesInput, E as ExtractDefaultValuesConfig, c as ExtractProtectedColumnsConfig, I as IndexConfigBaseOptions, j as IndexConfigEntry, N as NormalizedIndexConfig, O as OnFailArgs, P as Prettify, m as ProtectedColumnsInput, g as UniqueColumnConfigData, h as UniqueColumnConfigEntry, i as UniqueColumnConfigOptions, U as UniqueRowConfigData, e as UniqueRowConfigEntry, f as UniqueRowConfigOptions, n as normalizeIndexConfigEntry } from '../types-_64SXyva.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extended config input that includes optional validate plugins
|
|
10
|
+
*/
|
|
11
|
+
type VerifyConfigInputWithPlugins = VerifyConfigInput & {
|
|
12
|
+
/**
|
|
13
|
+
* Validate plugins to run after transforms.
|
|
14
|
+
* These plugins can validate data but don't affect input types.
|
|
15
|
+
*/
|
|
16
|
+
plugins?: ValidatePlugin[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Configure type-safe insert and patch functions with validation and transforms.
|
|
20
|
+
*
|
|
21
|
+
* @param schema - Your Convex schema definition
|
|
22
|
+
* @param configs - Configuration object with transforms, configs, and plugins
|
|
23
|
+
* @returns Object with `insert`, `patch`, and `dangerouslyPatch` functions
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { verifyConfig, defaultValuesConfig, protectedColumnsConfig, uniqueRowConfig } from 'convex-verify';
|
|
28
|
+
* import schema from './schema';
|
|
29
|
+
*
|
|
30
|
+
* export const { insert, patch, dangerouslyPatch } = verifyConfig(schema, {
|
|
31
|
+
* defaultValues: defaultValuesConfig(schema, () => ({
|
|
32
|
+
* posts: { status: 'draft', views: 0 },
|
|
33
|
+
* })),
|
|
34
|
+
* protectedColumns: protectedColumnsConfig(schema, {
|
|
35
|
+
* posts: ['authorId'],
|
|
36
|
+
* }),
|
|
37
|
+
* plugins: [
|
|
38
|
+
* uniqueRowConfig(schema, {
|
|
39
|
+
* posts: ['by_slug'],
|
|
40
|
+
* }),
|
|
41
|
+
* ],
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
declare const verifyConfig: <S extends SchemaDefinition<GenericSchema, boolean>, DataModel extends DataModelFromSchemaDefinition<S>, const VC extends VerifyConfigInputWithPlugins>(_schema: S, configs: VC) => {
|
|
46
|
+
insert: <const TN extends TableNamesInDataModel<DataModel>, const D extends DocumentByName<DataModel, TN>>(ctx: Omit<GenericMutationCtx<DataModel>, never>, tableName: TN, data: HasKey<VC, "defaultValues"> extends true ? MakeOptional<WithoutSystemFields<D>, OptionalKeysForTable<VC, TN> & keyof WithoutSystemFields<D>> : WithoutSystemFields<D>, options?: {
|
|
47
|
+
onFail?: OnFailCallback<D>;
|
|
48
|
+
}) => Promise<GenericId<TN>>;
|
|
49
|
+
patch: <const TN extends TableNamesInDataModel<DataModel>, const D_1 extends DocumentByName<DataModel, TN>>(ctx: Omit<GenericMutationCtx<DataModel>, never>, tableName: TN, id: GenericId<TN>, data: HasKey<VC, "protectedColumns"> extends true ? Omit<Partial<WithoutSystemFields<D_1>>, ProtectedKeysForTable<VC, TN> & keyof WithoutSystemFields<D_1>> : Partial<WithoutSystemFields<D_1>>, options?: {
|
|
50
|
+
onFail?: OnFailCallback<D_1>;
|
|
51
|
+
}) => Promise<void>;
|
|
52
|
+
dangerouslyPatch: <const TN extends TableNamesInDataModel<DataModel>, const D_2 extends DocumentByName<DataModel, TN>>(ctx: Omit<GenericMutationCtx<DataModel>, never>, tableName: TN, id: GenericId<TN>, data: Partial<WithoutSystemFields<D_2>>, options?: {
|
|
53
|
+
onFail?: OnFailCallback<D_2>;
|
|
54
|
+
}) => Promise<void>;
|
|
55
|
+
configs: VC;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export { HasKey, MakeOptional, OnFailCallback, OptionalKeysForTable, ProtectedKeysForTable, ValidatePlugin, VerifyConfigInput, verifyConfig };
|