convex-verify 1.1.0 → 1.2.2

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.
Files changed (46) hide show
  1. package/README.md +148 -80
  2. package/dist/core/index.d.mts +14 -55
  3. package/dist/core/index.d.ts +14 -55
  4. package/dist/core/index.js +492 -92
  5. package/dist/core/index.js.map +1 -1
  6. package/dist/core/index.mjs +491 -92
  7. package/dist/core/index.mjs.map +1 -1
  8. package/dist/index.d.mts +9 -6
  9. package/dist/index.d.ts +9 -6
  10. package/dist/index.js +378 -271
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +378 -267
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/types-B8ZkLuJ2.d.mts +141 -0
  15. package/dist/types-B8ZkLuJ2.d.ts +141 -0
  16. package/dist/utils/index.d.mts +3 -2
  17. package/dist/utils/index.d.ts +3 -2
  18. package/dist/utils/index.js +1 -1
  19. package/dist/utils/index.js.map +1 -1
  20. package/dist/utils/index.mjs +1 -1
  21. package/dist/utils/index.mjs.map +1 -1
  22. package/dist/verifyConfig-CTrtqMr_.d.ts +94 -0
  23. package/dist/verifyConfig-Kn3Ikj00.d.mts +94 -0
  24. package/package.json +1 -16
  25. package/dist/configs/index.d.mts +0 -51
  26. package/dist/configs/index.d.ts +0 -51
  27. package/dist/configs/index.js +0 -38
  28. package/dist/configs/index.js.map +0 -1
  29. package/dist/configs/index.mjs +0 -11
  30. package/dist/configs/index.mjs.map +0 -1
  31. package/dist/plugin-BOb1Kw1A.d.ts +0 -47
  32. package/dist/plugin-DlsboiCF.d.mts +0 -47
  33. package/dist/plugins/index.d.mts +0 -85
  34. package/dist/plugins/index.d.ts +0 -85
  35. package/dist/plugins/index.js +0 -312
  36. package/dist/plugins/index.js.map +0 -1
  37. package/dist/plugins/index.mjs +0 -284
  38. package/dist/plugins/index.mjs.map +0 -1
  39. package/dist/transforms/index.d.mts +0 -38
  40. package/dist/transforms/index.d.ts +0 -38
  41. package/dist/transforms/index.js +0 -46
  42. package/dist/transforms/index.js.map +0 -1
  43. package/dist/transforms/index.mjs +0 -19
  44. package/dist/transforms/index.mjs.map +0 -1
  45. package/dist/types-DvJMYubf.d.mts +0 -151
  46. package/dist/types-DvJMYubf.d.ts +0 -151
@@ -1,9 +1,347 @@
1
+ // src/core/builtins.ts
2
+ import { ConvexError } from "convex/values";
3
+
4
+ // src/core/types.ts
5
+ function normalizeIndexConfigEntry(entry, defaultIdentifiers = ["_id"]) {
6
+ if (typeof entry === "string") {
7
+ return {
8
+ index: entry,
9
+ identifiers: defaultIdentifiers
10
+ };
11
+ }
12
+ const { index, identifiers, ...rest } = entry;
13
+ return {
14
+ index: String(index),
15
+ identifiers: identifiers?.map(String) ?? defaultIdentifiers,
16
+ ...rest
17
+ };
18
+ }
19
+
20
+ // src/utils/helpers.ts
21
+ var getTableIndexes = (schema, tableName) => {
22
+ return schema.tables[tableName][" indexes"]();
23
+ };
24
+ var constructColumnData = (fields, data, {
25
+ allowNullishValue = false,
26
+ allOrNothing = true
27
+ }) => {
28
+ const lengthOfFields = fields.length;
29
+ const columnData = fields.map((_, index) => {
30
+ const column = fields?.[index];
31
+ const value = data?.[column];
32
+ if (!column || !allowNullishValue && (value === void 0 || value === null)) {
33
+ return;
34
+ }
35
+ return {
36
+ column,
37
+ value
38
+ };
39
+ }).filter((e) => !!e);
40
+ if (allOrNothing && columnData.length !== lengthOfFields) {
41
+ return null;
42
+ }
43
+ return columnData.length > 0 ? columnData : null;
44
+ };
45
+ var constructIndexData = (schema, tableName, indexConfig) => {
46
+ if (!indexConfig) {
47
+ return;
48
+ }
49
+ const tableConfig = indexConfig?.[tableName];
50
+ if (!tableConfig) {
51
+ return;
52
+ }
53
+ return tableConfig.map((entry) => {
54
+ const normalized = normalizeIndexConfigEntry(entry);
55
+ const { index, identifiers, ...rest } = normalized;
56
+ const fields = getTableIndexes(schema, tableName).find(
57
+ (i) => i.indexDescriptor == index
58
+ )?.fields;
59
+ if (!fields) {
60
+ throw new Error(
61
+ `Error in 'constructIndexData()'. No fields found for index: [${index}]`
62
+ );
63
+ }
64
+ const identifierMap = new Map(
65
+ [...identifiers, "_id"].map((i) => [String(i), String(i)])
66
+ );
67
+ return {
68
+ name: index,
69
+ fields,
70
+ identifiers: Array.from(identifierMap.values()),
71
+ ...rest
72
+ };
73
+ });
74
+ };
75
+
76
+ // src/core/builtins.ts
77
+ var stripProtectedPatchColumns = (protectedColumns, tableName, data) => {
78
+ const protectedKeys = protectedColumns[tableName] ?? [];
79
+ if (protectedKeys.length === 0) {
80
+ return {
81
+ filteredData: data,
82
+ removedColumns: []
83
+ };
84
+ }
85
+ const protectedKeyStrings = protectedKeys.map(String);
86
+ const protectedKeySet = new Set(protectedKeyStrings);
87
+ const removedColumns = protectedKeyStrings.filter((key) => key in data);
88
+ if (removedColumns.length === 0) {
89
+ return {
90
+ filteredData: data,
91
+ removedColumns
92
+ };
93
+ }
94
+ const filteredData = Object.fromEntries(
95
+ Object.entries(data).filter(([key]) => !protectedKeySet.has(key))
96
+ );
97
+ return {
98
+ filteredData,
99
+ removedColumns
100
+ };
101
+ };
102
+ var buildDefaultValuesVerifier = (config) => {
103
+ const verify = (async (...args) => {
104
+ const input = args.length === 2 ? {
105
+ tableName: args[0],
106
+ operation: "insert",
107
+ data: args[1]
108
+ } : args[0];
109
+ if (input.operation === "patch") {
110
+ return input.data;
111
+ }
112
+ const resolvedConfig = typeof config === "function" ? await config() : config;
113
+ return {
114
+ ...resolvedConfig[input.tableName] ?? {},
115
+ ...input.data
116
+ };
117
+ });
118
+ return {
119
+ _type: "defaultValues",
120
+ config,
121
+ verify
122
+ };
123
+ };
124
+ var buildProtectedColumnsVerifier = (config) => {
125
+ const verify = (async (...args) => {
126
+ const input = args.length === 2 ? {
127
+ tableName: args[0],
128
+ operation: "patch",
129
+ data: args[1]
130
+ } : args[0];
131
+ if (input.operation === "insert") {
132
+ return input.data;
133
+ }
134
+ return stripProtectedPatchColumns(config, input.tableName, input.data).filteredData;
135
+ });
136
+ return {
137
+ _type: "protectedColumns",
138
+ config,
139
+ verify
140
+ };
141
+ };
142
+ var buildUniqueRowVerifier = (schema, config) => {
143
+ const uniqueRowError = (message) => {
144
+ throw new ConvexError({
145
+ message,
146
+ code: "UNIQUE_ROW_VERIFICATION_ERROR"
147
+ });
148
+ };
149
+ const verify = (async (...args) => {
150
+ const input = args.length === 3 ? {
151
+ ctx: args[0],
152
+ tableName: args[1],
153
+ operation: "insert",
154
+ data: args[2]
155
+ } : args.length === 4 ? {
156
+ ctx: args[0],
157
+ tableName: args[1],
158
+ operation: "patch",
159
+ patchId: args[2],
160
+ data: args[3]
161
+ } : args[0];
162
+ const { ctx, tableName, operation, patchId, onFail, data } = input;
163
+ const indexesData = constructIndexData(schema, tableName, config);
164
+ if (!indexesData) {
165
+ return data;
166
+ }
167
+ for (const indexInfo of indexesData) {
168
+ const { name, fields, identifiers } = indexInfo;
169
+ if (fields.length < 2) {
170
+ uniqueRowError(
171
+ `Error in 'verifyRowUniqueness()'. There must be two columns to test against. If you are attempting to enforce a unique column, use the 'uniqueColumn' config option.`
172
+ );
173
+ }
174
+ const columnData = constructColumnData(fields, data, {});
175
+ const getExisting = async (cd) => {
176
+ let existingByIndex = [];
177
+ if (cd) {
178
+ existingByIndex = await ctx.db.query(tableName).withIndex(
179
+ name,
180
+ (q) => cd.reduce((query, { column, value }) => query.eq(column, value), q)
181
+ ).collect();
182
+ }
183
+ if (existingByIndex.length > 1) {
184
+ console.warn(
185
+ `There was more than one existing result found for index ${name}. Check the following IDs:`,
186
+ existingByIndex.map((row) => row._id)
187
+ );
188
+ console.warn(
189
+ "It is recommended that you triage the rows listed above since they have data that go against a rule of row uniqueness."
190
+ );
191
+ }
192
+ return existingByIndex.length > 0 ? existingByIndex[0] : null;
193
+ };
194
+ const existing = await getExisting(columnData);
195
+ if (operation === "insert") {
196
+ if (!existing) {
197
+ continue;
198
+ }
199
+ onFail?.({
200
+ uniqueRow: {
201
+ existingData: existing
202
+ }
203
+ });
204
+ uniqueRowError(
205
+ `Unable to [${operation}] document. In table [${tableName}], there is an existing row that has the same data combination in the columns: [${fields.join(", ")}].`
206
+ );
207
+ }
208
+ if (!patchId) {
209
+ uniqueRowError("Unable to patch document without an id.");
210
+ }
211
+ const matchedToExisting = (_existing, _data) => {
212
+ let idMatchedToExisting = null;
213
+ if (_existing) {
214
+ for (const identifier of identifiers) {
215
+ if (_existing[identifier] !== void 0 && _data[identifier] !== void 0 && _existing[identifier] === _data[identifier] || identifier === "_id" && _existing[identifier] === patchId) {
216
+ idMatchedToExisting = String(identifier);
217
+ break;
218
+ }
219
+ }
220
+ }
221
+ return idMatchedToExisting;
222
+ };
223
+ const checkExisting = (_existing, _data) => {
224
+ const matchedId = matchedToExisting(_existing, _data);
225
+ if (!_existing || matchedId) {
226
+ return;
227
+ }
228
+ onFail?.({
229
+ uniqueRow: {
230
+ existingData: _existing
231
+ }
232
+ });
233
+ uniqueRowError(
234
+ `In '${tableName}' table, there already exists a value match of the columns: [${fields.join(",")}].`
235
+ );
236
+ };
237
+ if (!existing && !columnData) {
238
+ const match = await ctx.db.get(patchId);
239
+ if (!match) {
240
+ uniqueRowError(`No document found for id ${patchId}`);
241
+ }
242
+ const extensiveColumnData = constructColumnData(
243
+ fields,
244
+ {
245
+ ...match,
246
+ ...data
247
+ },
248
+ {}
249
+ );
250
+ if (!extensiveColumnData) {
251
+ uniqueRowError("Incomplete data when there should have been enough.");
252
+ }
253
+ const extensiveExisting = await getExisting(extensiveColumnData);
254
+ checkExisting(extensiveExisting, data);
255
+ continue;
256
+ }
257
+ checkExisting(existing, data);
258
+ }
259
+ return data;
260
+ });
261
+ return {
262
+ _type: "uniqueRow",
263
+ config,
264
+ verify
265
+ };
266
+ };
267
+ var buildUniqueColumnVerifier = (config) => {
268
+ const uniqueColumnError = (message) => {
269
+ throw new ConvexError({
270
+ message,
271
+ code: "UNIQUE_COLUMN_VERIFICATION_ERROR"
272
+ });
273
+ };
274
+ const verify = (async (...args) => {
275
+ const input = args.length === 3 ? {
276
+ ctx: args[0],
277
+ tableName: args[1],
278
+ operation: "insert",
279
+ data: args[2]
280
+ } : args.length === 4 ? {
281
+ ctx: args[0],
282
+ tableName: args[1],
283
+ operation: "patch",
284
+ patchId: args[2],
285
+ data: args[3]
286
+ } : args[0];
287
+ const { ctx, tableName, patchId, onFail, data } = input;
288
+ const tableConfig = config[tableName];
289
+ if (!tableConfig) {
290
+ return data;
291
+ }
292
+ for (const entry of tableConfig) {
293
+ const { index, identifiers } = normalizeIndexConfigEntry(
294
+ entry
295
+ );
296
+ const columnName = index.replace("by_", "");
297
+ const value = data[columnName];
298
+ if (value === void 0 || value === null) {
299
+ continue;
300
+ }
301
+ const existing = await ctx.db.query(tableName).withIndex(index, (q) => q.eq(columnName, value)).unique();
302
+ if (!existing) {
303
+ continue;
304
+ }
305
+ let isOwnDocument = false;
306
+ for (const identifier of identifiers) {
307
+ if (identifier === "_id" && patchId && existing._id === patchId) {
308
+ isOwnDocument = true;
309
+ break;
310
+ }
311
+ if (existing[identifier] !== void 0 && data[identifier] !== void 0 && existing[identifier] === data[identifier]) {
312
+ isOwnDocument = true;
313
+ break;
314
+ }
315
+ }
316
+ if (isOwnDocument) {
317
+ continue;
318
+ }
319
+ onFail?.({
320
+ uniqueColumn: {
321
+ conflictingColumn: columnName,
322
+ existingData: existing
323
+ }
324
+ });
325
+ uniqueColumnError(
326
+ `In [${tableName}] table, there already exists value "${value}" in column [${columnName}].`
327
+ );
328
+ }
329
+ return data;
330
+ });
331
+ return {
332
+ _type: "uniqueColumn",
333
+ config,
334
+ verify
335
+ };
336
+ };
337
+
1
338
  // src/core/plugin.ts
2
- function createExtension(verify) {
339
+ function createExtension(schemaOrVerify, verify) {
340
+ const extensionVerify = typeof verify === "function" ? verify : schemaOrVerify;
3
341
  return {
4
342
  _type: "extension",
5
343
  verify(input) {
6
- return verify(input);
344
+ return extensionVerify(input);
7
345
  }
8
346
  };
9
347
  }
@@ -23,53 +361,77 @@ async function runExtensions(extensions, input) {
23
361
 
24
362
  // src/core/verifyConfig.ts
25
363
  var verifyConfig = (_schema, configs) => {
26
- const customExtensions = configs.extensions ?? [];
27
- const builtInExtensions = [
28
- ...configs.uniqueRow ? [configs.uniqueRow] : [],
29
- ...configs.uniqueColumn ? [configs.uniqueColumn] : []
30
- ];
31
- const extensions = [...customExtensions, ...builtInExtensions];
32
- const protectedColumns = configs.protectedColumns?.config ?? {};
33
- const stripProtectedPatchColumns = (tableName, data) => {
34
- const protectedKeys = protectedColumns[tableName] ?? [];
35
- if (protectedKeys.length === 0) {
36
- return {
37
- filteredData: data,
38
- removedColumns: []
39
- };
40
- }
41
- const removedColumns = protectedKeys.filter((key) => key in data).map(String);
42
- if (removedColumns.length === 0) {
43
- return {
44
- filteredData: data,
45
- removedColumns
46
- };
47
- }
48
- const filteredData = Object.fromEntries(
49
- Object.entries(data).filter(([key]) => !protectedKeys.includes(key))
50
- );
51
- return {
52
- filteredData,
53
- removedColumns
54
- };
364
+ const builtins = {
365
+ ...configs.defaultValues ? {
366
+ defaultValues: buildDefaultValuesVerifier(configs.defaultValues)
367
+ } : {},
368
+ ...configs.protectedColumns ? {
369
+ protectedColumns: buildProtectedColumnsVerifier(configs.protectedColumns)
370
+ } : {},
371
+ ...configs.uniqueRow ? {
372
+ uniqueRow: buildUniqueRowVerifier(
373
+ _schema,
374
+ configs.uniqueRow
375
+ )
376
+ } : {},
377
+ ...configs.uniqueColumn ? {
378
+ uniqueColumn: buildUniqueColumnVerifier(configs.uniqueColumn)
379
+ } : {}
380
+ };
381
+ const verify = {
382
+ ...builtins.defaultValues ? { defaultValues: builtins.defaultValues.verify } : {},
383
+ ...builtins.protectedColumns ? { protectedColumns: builtins.protectedColumns.verify } : {},
384
+ ...builtins.uniqueRow ? { uniqueRow: builtins.uniqueRow.verify } : {},
385
+ ...builtins.uniqueColumn ? { uniqueColumn: builtins.uniqueColumn.verify } : {}
386
+ };
387
+ const config = {
388
+ ...configs.defaultValues ? { defaultValues: configs.defaultValues } : {},
389
+ ...configs.protectedColumns ? { protectedColumns: configs.protectedColumns } : {},
390
+ ...configs.uniqueRow ? { uniqueRow: configs.uniqueRow } : {},
391
+ ...configs.uniqueColumn ? { uniqueColumn: configs.uniqueColumn } : {}
55
392
  };
393
+ const customExtensions = configs.extensions ?? [];
56
394
  const insert = async (ctx, tableName, data, options) => {
57
395
  let verifiedData = data;
58
- if (configs.defaultValues) {
59
- verifiedData = await configs.defaultValues.verify(tableName, verifiedData);
60
- }
61
- if (extensions.length > 0) {
62
- verifiedData = await runExtensions(
63
- extensions,
64
- {
65
- ctx,
66
- tableName,
67
- operation: "insert",
68
- onFail: options?.onFail,
69
- schema: _schema,
70
- data: verifiedData
71
- }
72
- );
396
+ if (builtins.defaultValues) {
397
+ verifiedData = await builtins.defaultValues.verify({
398
+ ctx,
399
+ tableName,
400
+ operation: "insert",
401
+ onFail: options?.onFail,
402
+ schema: _schema,
403
+ data: verifiedData
404
+ });
405
+ }
406
+ if (customExtensions.length > 0) {
407
+ verifiedData = await runExtensions(customExtensions, {
408
+ ctx,
409
+ tableName,
410
+ operation: "insert",
411
+ onFail: options?.onFail,
412
+ schema: _schema,
413
+ data: verifiedData
414
+ });
415
+ }
416
+ if (builtins.uniqueRow) {
417
+ verifiedData = await builtins.uniqueRow.verify({
418
+ ctx,
419
+ tableName,
420
+ operation: "insert",
421
+ onFail: options?.onFail,
422
+ schema: _schema,
423
+ data: verifiedData
424
+ });
425
+ }
426
+ if (builtins.uniqueColumn) {
427
+ verifiedData = await builtins.uniqueColumn.verify({
428
+ ctx,
429
+ tableName,
430
+ operation: "insert",
431
+ onFail: options?.onFail,
432
+ schema: _schema,
433
+ data: verifiedData
434
+ });
73
435
  }
74
436
  return await ctx.db.insert(tableName, verifiedData);
75
437
  };
@@ -77,26 +439,52 @@ var verifyConfig = (_schema, configs) => {
77
439
  let verifiedData = data;
78
440
  const removedProtectedColumns = /* @__PURE__ */ new Set();
79
441
  const stripProtectedColumns = () => {
80
- const filtered = stripProtectedPatchColumns(tableName, verifiedData);
442
+ if (!builtins.protectedColumns) {
443
+ return;
444
+ }
445
+ const filtered = stripProtectedPatchColumns(
446
+ builtins.protectedColumns.config,
447
+ tableName,
448
+ verifiedData
449
+ );
81
450
  for (const column of filtered.removedColumns) {
82
451
  removedProtectedColumns.add(column);
83
452
  }
84
453
  verifiedData = filtered.filteredData;
85
454
  };
86
455
  stripProtectedColumns();
87
- if (extensions.length > 0) {
88
- verifiedData = await runExtensions(
89
- extensions,
90
- {
91
- ctx,
92
- tableName,
93
- operation: "patch",
94
- patchId: id,
95
- onFail: options?.onFail,
96
- schema: _schema,
97
- data: verifiedData
98
- }
99
- );
456
+ if (customExtensions.length > 0) {
457
+ verifiedData = await runExtensions(customExtensions, {
458
+ ctx,
459
+ tableName,
460
+ operation: "patch",
461
+ patchId: id,
462
+ onFail: options?.onFail,
463
+ schema: _schema,
464
+ data: verifiedData
465
+ });
466
+ }
467
+ if (builtins.uniqueRow) {
468
+ verifiedData = await builtins.uniqueRow.verify({
469
+ ctx,
470
+ tableName,
471
+ operation: "patch",
472
+ patchId: id,
473
+ onFail: options?.onFail,
474
+ schema: _schema,
475
+ data: verifiedData
476
+ });
477
+ }
478
+ if (builtins.uniqueColumn) {
479
+ verifiedData = await builtins.uniqueColumn.verify({
480
+ ctx,
481
+ tableName,
482
+ operation: "patch",
483
+ patchId: id,
484
+ onFail: options?.onFail,
485
+ schema: _schema,
486
+ data: verifiedData
487
+ });
100
488
  }
101
489
  stripProtectedColumns();
102
490
  if (removedProtectedColumns.size > 0) {
@@ -111,49 +499,60 @@ var verifyConfig = (_schema, configs) => {
111
499
  };
112
500
  const dangerouslyPatch = async (ctx, tableName, id, data, options) => {
113
501
  let verifiedData = data;
114
- if (extensions.length > 0) {
115
- verifiedData = await runExtensions(
116
- extensions,
117
- {
118
- ctx,
119
- tableName,
120
- operation: "patch",
121
- patchId: id,
122
- onFail: options?.onFail,
123
- schema: _schema,
124
- data: verifiedData
125
- }
126
- );
502
+ if (customExtensions.length > 0) {
503
+ verifiedData = await runExtensions(customExtensions, {
504
+ ctx,
505
+ tableName,
506
+ operation: "patch",
507
+ patchId: id,
508
+ onFail: options?.onFail,
509
+ schema: _schema,
510
+ data: verifiedData
511
+ });
512
+ }
513
+ if (builtins.uniqueRow) {
514
+ verifiedData = await builtins.uniqueRow.verify({
515
+ ctx,
516
+ tableName,
517
+ operation: "patch",
518
+ patchId: id,
519
+ onFail: options?.onFail,
520
+ schema: _schema,
521
+ data: verifiedData
522
+ });
523
+ }
524
+ if (builtins.uniqueColumn) {
525
+ verifiedData = await builtins.uniqueColumn.verify({
526
+ ctx,
527
+ tableName,
528
+ operation: "patch",
529
+ patchId: id,
530
+ onFail: options?.onFail,
531
+ schema: _schema,
532
+ data: verifiedData
533
+ });
127
534
  }
128
535
  await ctx.db.patch(id, verifiedData);
129
536
  };
130
537
  return {
131
538
  insert,
132
539
  patch,
133
- dangerouslyPatch
540
+ dangerouslyPatch,
541
+ verify,
542
+ config
134
543
  };
135
544
  };
136
545
 
137
- // src/core/types.ts
138
- function normalizeIndexConfigEntry(entry, defaultIdentifiers = ["_id"]) {
139
- if (typeof entry === "string") {
140
- return {
141
- index: entry,
142
- identifiers: defaultIdentifiers
143
- };
144
- }
145
- const { index, identifiers, ...rest } = entry;
146
- return {
147
- index: String(index),
148
- identifiers: identifiers?.map(String) ?? defaultIdentifiers,
149
- ...rest
150
- };
151
- }
546
+ // src/core/index.ts
547
+ var createExtension2 = createExtension;
548
+ var isExtension2 = isExtension;
549
+ var runExtensions2 = runExtensions;
152
550
  export {
153
- createExtension,
154
- isExtension,
551
+ createExtension2 as createExtension,
552
+ isExtension2 as isExtension,
155
553
  normalizeIndexConfigEntry,
156
- runExtensions,
554
+ runExtensions2 as runExtensions,
555
+ stripProtectedPatchColumns,
157
556
  verifyConfig
158
557
  };
159
558
  //# sourceMappingURL=index.mjs.map