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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +403 -0
  3. package/dist/configs/index.d.mts +51 -0
  4. package/dist/configs/index.d.ts +51 -0
  5. package/dist/configs/index.js +38 -0
  6. package/dist/configs/index.js.map +1 -0
  7. package/dist/configs/index.mjs +11 -0
  8. package/dist/configs/index.mjs.map +1 -0
  9. package/dist/core/index.d.mts +58 -0
  10. package/dist/core/index.d.ts +58 -0
  11. package/dist/core/index.js +144 -0
  12. package/dist/core/index.js.map +1 -0
  13. package/dist/core/index.mjs +113 -0
  14. package/dist/core/index.mjs.map +1 -0
  15. package/dist/index.d.mts +9 -0
  16. package/dist/index.d.ts +9 -0
  17. package/dist/index.js +442 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/index.mjs +404 -0
  20. package/dist/index.mjs.map +1 -0
  21. package/dist/plugin-BjJ7yjrc.d.ts +141 -0
  22. package/dist/plugin-mHMV2-SG.d.mts +141 -0
  23. package/dist/plugins/index.d.mts +85 -0
  24. package/dist/plugins/index.d.ts +85 -0
  25. package/dist/plugins/index.js +317 -0
  26. package/dist/plugins/index.js.map +1 -0
  27. package/dist/plugins/index.mjs +289 -0
  28. package/dist/plugins/index.mjs.map +1 -0
  29. package/dist/transforms/index.d.mts +38 -0
  30. package/dist/transforms/index.d.ts +38 -0
  31. package/dist/transforms/index.js +46 -0
  32. package/dist/transforms/index.js.map +1 -0
  33. package/dist/transforms/index.mjs +19 -0
  34. package/dist/transforms/index.mjs.map +1 -0
  35. package/dist/types-_64SXyva.d.mts +151 -0
  36. package/dist/types-_64SXyva.d.ts +151 -0
  37. package/dist/utils/index.d.mts +36 -0
  38. package/dist/utils/index.d.ts +36 -0
  39. package/dist/utils/index.js +113 -0
  40. package/dist/utils/index.js.map +1 -0
  41. package/dist/utils/index.mjs +83 -0
  42. package/dist/utils/index.mjs.map +1 -0
  43. package/package.json +75 -0
@@ -0,0 +1,141 @@
1
+ import { GenericMutationCtx, SchemaDefinition, GenericSchema } from 'convex/server';
2
+ import { GenericId } from 'convex/values';
3
+ import { a as OnFailCallback } from './types-_64SXyva.mjs';
4
+
5
+ /**
6
+ * Context passed to validate plugin functions.
7
+ *
8
+ * Provides access to:
9
+ * - `ctx` - Full Convex mutation context (includes `ctx.db` for queries)
10
+ * - `tableName` - The table being operated on
11
+ * - `operation` - 'insert' or 'patch'
12
+ * - `patchId` - Document ID (only for patch operations)
13
+ * - `onFail` - Callback to report validation failures before throwing
14
+ * - `schema` - Optional schema reference (if provided by verifyConfig)
15
+ */
16
+ type ValidateContext<TN extends string = string> = {
17
+ /** Full Convex mutation context - use ctx.db for database queries */
18
+ ctx: Omit<GenericMutationCtx<any>, never>;
19
+ /** Table name being operated on */
20
+ tableName: TN;
21
+ /** Operation type: 'insert' or 'patch' */
22
+ operation: 'insert' | 'patch';
23
+ /** Document ID (only available for patch operations) */
24
+ patchId?: GenericId<any>;
25
+ /** Callback for validation failures - call before throwing to provide details */
26
+ onFail?: OnFailCallback<any>;
27
+ /** Schema reference (if provided to verifyConfig) */
28
+ schema?: SchemaDefinition<GenericSchema, boolean>;
29
+ };
30
+ /**
31
+ * A validate plugin that can check data during insert/patch operations.
32
+ *
33
+ * Validate plugins:
34
+ * - Run AFTER transform plugins (like defaultValues)
35
+ * - Can be async (use await for API calls, db queries, etc.)
36
+ * - Can throw errors to prevent the operation
37
+ * - Should return the data unchanged (validation only, no transformation)
38
+ * - Do NOT affect the TypeScript types of the input data
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * // Simple sync plugin
43
+ * const requiredFields = createValidatePlugin(
44
+ * 'requiredFields',
45
+ * { fields: ['title', 'content'] },
46
+ * {
47
+ * insert: (context, data) => {
48
+ * for (const field of config.fields) {
49
+ * if (!data[field]) {
50
+ * throw new ConvexError({ message: `Missing required field: ${field}` });
51
+ * }
52
+ * }
53
+ * return data;
54
+ * },
55
+ * }
56
+ * );
57
+ *
58
+ * // Async plugin with database query
59
+ * const checkOwnership = createValidatePlugin(
60
+ * 'checkOwnership',
61
+ * {},
62
+ * {
63
+ * patch: async (context, data) => {
64
+ * const existing = await context.ctx.db.get(context.patchId);
65
+ * if (existing?.ownerId !== getCurrentUserId()) {
66
+ * throw new ConvexError({ message: 'Not authorized' });
67
+ * }
68
+ * return data;
69
+ * },
70
+ * }
71
+ * );
72
+ * ```
73
+ */
74
+ interface ValidatePlugin<Type extends string = string, Config = unknown> {
75
+ /** Unique identifier for this plugin */
76
+ readonly _type: Type;
77
+ /** Plugin configuration */
78
+ readonly config: Config;
79
+ /** Verify functions for insert and/or patch operations */
80
+ verify: {
81
+ /**
82
+ * Validate data for insert operations.
83
+ * Can be sync or async.
84
+ *
85
+ * @param context - Plugin context with ctx, tableName, schema, etc.
86
+ * @param data - The data to validate (after transforms applied)
87
+ * @returns The data unchanged (or Promise resolving to data)
88
+ * @throws ConvexError if validation fails
89
+ */
90
+ insert?: (context: ValidateContext, data: any) => Promise<any> | any;
91
+ /**
92
+ * Validate data for patch operations.
93
+ * Can be sync or async.
94
+ *
95
+ * @param context - Plugin context with ctx, tableName, patchId, schema, etc.
96
+ * @param data - The partial data to validate
97
+ * @returns The data unchanged (or Promise resolving to data)
98
+ * @throws ConvexError if validation fails
99
+ */
100
+ patch?: (context: ValidateContext, data: any) => Promise<any> | any;
101
+ };
102
+ }
103
+ /**
104
+ * Type guard to check if something is a ValidatePlugin
105
+ */
106
+ declare function isValidatePlugin(obj: unknown): obj is ValidatePlugin;
107
+ /**
108
+ * A collection of validate plugins
109
+ */
110
+ type ValidatePluginRecord = Record<string, ValidatePlugin>;
111
+ /**
112
+ * Run all validate plugins for an operation.
113
+ * Plugins are run in order and each receives the output of the previous.
114
+ * All plugin verify functions are awaited (supports async plugins).
115
+ */
116
+ declare function runValidatePlugins(plugins: ValidatePlugin[], context: ValidateContext, data: any): Promise<any>;
117
+ /**
118
+ * Helper to create a validate plugin with proper typing.
119
+ *
120
+ * @param type - Unique identifier for this plugin type
121
+ * @param config - Plugin configuration data
122
+ * @param verify - Object with insert and/or patch verify functions
123
+ * @returns A ValidatePlugin instance
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * const myPlugin = createValidatePlugin(
128
+ * 'myPlugin',
129
+ * { maxLength: 100 },
130
+ * {
131
+ * insert: async (context, data) => {
132
+ * // Validation logic here
133
+ * return data;
134
+ * },
135
+ * }
136
+ * );
137
+ * ```
138
+ */
139
+ declare function createValidatePlugin<Type extends string, Config>(type: Type, config: Config, verify: ValidatePlugin<Type, Config>['verify']): ValidatePlugin<Type, Config>;
140
+
141
+ export { type ValidateContext as V, type ValidatePlugin as a, type ValidatePluginRecord as b, createValidatePlugin as c, isValidatePlugin as i, runValidatePlugins as r };
@@ -0,0 +1,85 @@
1
+ import { SchemaDefinition, GenericSchema, DataModelFromSchemaDefinition } from 'convex/server';
2
+ import { a as ValidatePlugin } from '../plugin-mHMV2-SG.mjs';
3
+ import { U as UniqueRowConfigData, g as UniqueColumnConfigData } from '../types-_64SXyva.mjs';
4
+ export { h as UniqueColumnConfigEntry, i as UniqueColumnConfigOptions, e as UniqueRowConfigEntry, f as UniqueRowConfigOptions } from '../types-_64SXyva.mjs';
5
+ import 'convex/values';
6
+
7
+ /**
8
+ * Creates a validate plugin that enforces row uniqueness based on database indexes.
9
+ *
10
+ * This plugin checks that the combination of column values defined in your indexes
11
+ * doesn't already exist in the database before allowing insert/patch operations.
12
+ *
13
+ * @param schema - Your Convex schema definition
14
+ * @param config - Object mapping table names to arrays of index configs
15
+ * @returns A ValidatePlugin for use with verifyConfig
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // Simple shorthand - just index names
20
+ * const uniqueRow = uniqueRowConfig(schema, {
21
+ * posts: ['by_slug'],
22
+ * users: ['by_email', 'by_username'],
23
+ * });
24
+ *
25
+ * // With options
26
+ * const uniqueRow = uniqueRowConfig(schema, {
27
+ * posts: [
28
+ * { index: 'by_author_slug', identifiers: ['_id', 'authorId'] },
29
+ * ],
30
+ * });
31
+ *
32
+ * // Use with verifyConfig
33
+ * const { insert, patch } = verifyConfig(schema, {
34
+ * plugins: [uniqueRow],
35
+ * });
36
+ * ```
37
+ */
38
+ declare const uniqueRowConfig: <S extends SchemaDefinition<GenericSchema, boolean>, DataModel extends DataModelFromSchemaDefinition<S>, const C extends UniqueRowConfigData<DataModel>>(schema: S, config: C) => ValidatePlugin<"uniqueRow", C>;
39
+
40
+ /**
41
+ * Creates a validate plugin that enforces column uniqueness using single-column indexes.
42
+ *
43
+ * This is useful when you have a column that must be unique across all rows,
44
+ * like usernames or email addresses.
45
+ *
46
+ * The column name is derived from the index name by removing the 'by_' prefix.
47
+ * For example, 'by_username' checks the 'username' column.
48
+ *
49
+ * @param schema - Your Convex schema definition
50
+ * @param config - Object mapping table names to arrays of index configs
51
+ * @returns A ValidatePlugin for use with verifyConfig
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * // Shorthand: just pass index names as strings
56
+ * const uniqueColumn = uniqueColumnConfig(schema, {
57
+ * users: ['by_username', 'by_email'],
58
+ * organizations: ['by_slug'],
59
+ * });
60
+ *
61
+ * // Full config: pass objects with options
62
+ * const uniqueColumn = uniqueColumnConfig(schema, {
63
+ * users: [
64
+ * { index: 'by_username', identifiers: ['_id', 'userId'] },
65
+ * { index: 'by_email', identifiers: ['_id'] },
66
+ * ],
67
+ * });
68
+ *
69
+ * // Mix and match
70
+ * const uniqueColumn = uniqueColumnConfig(schema, {
71
+ * users: [
72
+ * 'by_username', // shorthand
73
+ * { index: 'by_email', identifiers: ['_id', 'clerkId'] }, // full config
74
+ * ],
75
+ * });
76
+ *
77
+ * // Use with verifyConfig
78
+ * const { insert, patch } = verifyConfig(schema, {
79
+ * plugins: [uniqueColumn],
80
+ * });
81
+ * ```
82
+ */
83
+ declare const uniqueColumnConfig: <S extends SchemaDefinition<GenericSchema, boolean>, DataModel extends DataModelFromSchemaDefinition<S>, const C extends UniqueColumnConfigData<DataModel>>(_schema: S, config: C) => ValidatePlugin<"uniqueColumn", C>;
84
+
85
+ export { UniqueColumnConfigData, UniqueRowConfigData, uniqueColumnConfig, uniqueRowConfig };
@@ -0,0 +1,85 @@
1
+ import { SchemaDefinition, GenericSchema, DataModelFromSchemaDefinition } from 'convex/server';
2
+ import { a as ValidatePlugin } from '../plugin-BjJ7yjrc.js';
3
+ import { U as UniqueRowConfigData, g as UniqueColumnConfigData } from '../types-_64SXyva.js';
4
+ export { h as UniqueColumnConfigEntry, i as UniqueColumnConfigOptions, e as UniqueRowConfigEntry, f as UniqueRowConfigOptions } from '../types-_64SXyva.js';
5
+ import 'convex/values';
6
+
7
+ /**
8
+ * Creates a validate plugin that enforces row uniqueness based on database indexes.
9
+ *
10
+ * This plugin checks that the combination of column values defined in your indexes
11
+ * doesn't already exist in the database before allowing insert/patch operations.
12
+ *
13
+ * @param schema - Your Convex schema definition
14
+ * @param config - Object mapping table names to arrays of index configs
15
+ * @returns A ValidatePlugin for use with verifyConfig
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // Simple shorthand - just index names
20
+ * const uniqueRow = uniqueRowConfig(schema, {
21
+ * posts: ['by_slug'],
22
+ * users: ['by_email', 'by_username'],
23
+ * });
24
+ *
25
+ * // With options
26
+ * const uniqueRow = uniqueRowConfig(schema, {
27
+ * posts: [
28
+ * { index: 'by_author_slug', identifiers: ['_id', 'authorId'] },
29
+ * ],
30
+ * });
31
+ *
32
+ * // Use with verifyConfig
33
+ * const { insert, patch } = verifyConfig(schema, {
34
+ * plugins: [uniqueRow],
35
+ * });
36
+ * ```
37
+ */
38
+ declare const uniqueRowConfig: <S extends SchemaDefinition<GenericSchema, boolean>, DataModel extends DataModelFromSchemaDefinition<S>, const C extends UniqueRowConfigData<DataModel>>(schema: S, config: C) => ValidatePlugin<"uniqueRow", C>;
39
+
40
+ /**
41
+ * Creates a validate plugin that enforces column uniqueness using single-column indexes.
42
+ *
43
+ * This is useful when you have a column that must be unique across all rows,
44
+ * like usernames or email addresses.
45
+ *
46
+ * The column name is derived from the index name by removing the 'by_' prefix.
47
+ * For example, 'by_username' checks the 'username' column.
48
+ *
49
+ * @param schema - Your Convex schema definition
50
+ * @param config - Object mapping table names to arrays of index configs
51
+ * @returns A ValidatePlugin for use with verifyConfig
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * // Shorthand: just pass index names as strings
56
+ * const uniqueColumn = uniqueColumnConfig(schema, {
57
+ * users: ['by_username', 'by_email'],
58
+ * organizations: ['by_slug'],
59
+ * });
60
+ *
61
+ * // Full config: pass objects with options
62
+ * const uniqueColumn = uniqueColumnConfig(schema, {
63
+ * users: [
64
+ * { index: 'by_username', identifiers: ['_id', 'userId'] },
65
+ * { index: 'by_email', identifiers: ['_id'] },
66
+ * ],
67
+ * });
68
+ *
69
+ * // Mix and match
70
+ * const uniqueColumn = uniqueColumnConfig(schema, {
71
+ * users: [
72
+ * 'by_username', // shorthand
73
+ * { index: 'by_email', identifiers: ['_id', 'clerkId'] }, // full config
74
+ * ],
75
+ * });
76
+ *
77
+ * // Use with verifyConfig
78
+ * const { insert, patch } = verifyConfig(schema, {
79
+ * plugins: [uniqueColumn],
80
+ * });
81
+ * ```
82
+ */
83
+ declare const uniqueColumnConfig: <S extends SchemaDefinition<GenericSchema, boolean>, DataModel extends DataModelFromSchemaDefinition<S>, const C extends UniqueColumnConfigData<DataModel>>(_schema: S, config: C) => ValidatePlugin<"uniqueColumn", C>;
84
+
85
+ export { UniqueColumnConfigData, UniqueRowConfigData, uniqueColumnConfig, uniqueRowConfig };
@@ -0,0 +1,317 @@
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/plugins/index.ts
21
+ var plugins_exports = {};
22
+ __export(plugins_exports, {
23
+ uniqueColumnConfig: () => uniqueColumnConfig,
24
+ uniqueRowConfig: () => uniqueRowConfig
25
+ });
26
+ module.exports = __toCommonJS(plugins_exports);
27
+
28
+ // src/plugins/uniqueRowConfig.ts
29
+ var import_values = require("convex/values");
30
+
31
+ // src/core/plugin.ts
32
+ function createValidatePlugin(type, config, verify) {
33
+ return {
34
+ _type: type,
35
+ config,
36
+ verify
37
+ };
38
+ }
39
+
40
+ // src/core/types.ts
41
+ function normalizeIndexConfigEntry(entry, defaultIdentifiers = ["_id"]) {
42
+ if (typeof entry === "string") {
43
+ return {
44
+ index: entry,
45
+ identifiers: defaultIdentifiers
46
+ };
47
+ }
48
+ const { index, identifiers, ...rest } = entry;
49
+ return {
50
+ index: String(index),
51
+ identifiers: identifiers?.map(String) ?? defaultIdentifiers,
52
+ ...rest
53
+ };
54
+ }
55
+
56
+ // src/utils/helpers.ts
57
+ var getTableIndexes = (schema, tableName) => {
58
+ return schema.tables[tableName][" indexes"]();
59
+ };
60
+ var constructColumnData = (fields, data, {
61
+ allowNullishValue = false,
62
+ allOrNothing = true
63
+ }) => {
64
+ const lengthOfFields = fields.length;
65
+ const columnData = fields.map((_, index) => {
66
+ const column = fields?.[index];
67
+ const value = data?.[column];
68
+ if (!column || !allowNullishValue && !value) {
69
+ return;
70
+ }
71
+ return {
72
+ column,
73
+ value
74
+ };
75
+ }).filter((e) => !!e);
76
+ if (allOrNothing && columnData.length !== lengthOfFields) {
77
+ console.warn(
78
+ "The index was NOT supplied with the same amount data as there was fields. This warning only appears when setting `allOrNothing` to `true`.",
79
+ "`fields: `",
80
+ fields,
81
+ "`columnData: `",
82
+ columnData
83
+ );
84
+ return null;
85
+ }
86
+ return columnData.length > 0 ? columnData : null;
87
+ };
88
+ var constructIndexData = (schema, tableName, indexConfig) => {
89
+ if (!indexConfig) {
90
+ return;
91
+ }
92
+ const tableConfig = indexConfig?.[tableName];
93
+ if (!tableConfig) {
94
+ return;
95
+ }
96
+ return tableConfig.map((entry) => {
97
+ const normalized = normalizeIndexConfigEntry(entry);
98
+ const { index, identifiers, ...rest } = normalized;
99
+ const fields = getTableIndexes(schema, tableName).find(
100
+ (i) => i.indexDescriptor == index
101
+ )?.fields;
102
+ if (!fields) {
103
+ throw new Error(`Error in 'constructIndexData()'. No fields found for index: [${index}]`);
104
+ }
105
+ const identifierMap = new Map(
106
+ [...identifiers, "_id"].map((i) => [String(i), String(i)])
107
+ );
108
+ return {
109
+ name: index,
110
+ fields,
111
+ identifiers: Array.from(identifierMap.values()),
112
+ ...rest
113
+ };
114
+ });
115
+ };
116
+
117
+ // src/plugins/uniqueRowConfig.ts
118
+ var uniqueRowConfig = (schema, config) => {
119
+ const uniqueRowError = (message) => {
120
+ throw new import_values.ConvexError({
121
+ message,
122
+ code: "UNIQUE_ROW_VERIFICATION_ERROR"
123
+ });
124
+ };
125
+ const verifyUniqueness = async (context, data, tableName) => {
126
+ const { ctx, operation, patchId, onFail } = context;
127
+ const indexesData = constructIndexData(schema, tableName, config);
128
+ if (!indexesData && !!config[tableName]) {
129
+ uniqueRowError(`Index data was not found where there should have been.`);
130
+ }
131
+ if (!indexesData) {
132
+ return data;
133
+ }
134
+ for (const indexInfo of indexesData) {
135
+ const { name, fields, identifiers, ...rest } = indexInfo;
136
+ const _options = rest;
137
+ if (!fields[0] && !fields[1]) {
138
+ uniqueRowError(
139
+ `Error in 'verifyRowUniqueness()'. There must be two columns to test against. If you are attempting to enforce a unique column, use the 'uniqueColumns' config option.`
140
+ );
141
+ }
142
+ const columnData = constructColumnData(fields, data, {});
143
+ const getExisting = async (cd) => {
144
+ let existingByIndex = [];
145
+ if (!cd) {
146
+ existingByIndex = [];
147
+ } else {
148
+ existingByIndex = await ctx.db.query(tableName).withIndex(
149
+ name,
150
+ (q) => cd.reduce((query, { column, value }) => query.eq(column, value), q)
151
+ ).collect();
152
+ }
153
+ if (existingByIndex.length > 1) {
154
+ console.warn(
155
+ `There was more than one existing result found for index ${name}. Check the following IDs:`,
156
+ existingByIndex.map((r) => r._id)
157
+ );
158
+ console.warn(
159
+ `It is recommended that you triage the rows listed above since they have data that go against a rule of row uniqueness.`
160
+ );
161
+ }
162
+ return existingByIndex.length > 0 ? existingByIndex[0] : null;
163
+ };
164
+ const existing = await getExisting(columnData);
165
+ if (operation === "insert") {
166
+ if (!existing) {
167
+ continue;
168
+ }
169
+ onFail?.({
170
+ uniqueRow: {
171
+ existingData: existing
172
+ }
173
+ });
174
+ uniqueRowError(
175
+ `Unable to [${operation}] document. In table [${tableName}], there is an existing row that has the same data combination in the columns: [${fields.join(`, `)}].`
176
+ );
177
+ }
178
+ if (operation === "patch") {
179
+ if (!patchId) {
180
+ uniqueRowError(`Unable to patch document without an id.`);
181
+ }
182
+ const matchedToExisting = (_existing, _data) => {
183
+ let idMatchedToExisting = null;
184
+ if (_existing) {
185
+ for (const identifier of identifiers) {
186
+ if (_existing[identifier] && _data[identifier] && _existing[identifier] === _data[identifier] || identifier === "_id" && _existing[identifier] === patchId) {
187
+ idMatchedToExisting = String(identifier);
188
+ break;
189
+ }
190
+ }
191
+ }
192
+ return idMatchedToExisting;
193
+ };
194
+ const checkExisting = (_existing, _data) => {
195
+ const matchedId = matchedToExisting(_existing, _data);
196
+ if (!_existing) {
197
+ return;
198
+ }
199
+ if (matchedId) {
200
+ return;
201
+ } else {
202
+ onFail?.({
203
+ uniqueRow: {
204
+ existingData: _existing
205
+ }
206
+ });
207
+ uniqueRowError(
208
+ `In '${tableName}' table, there already exists a value match of the columns: [${fields.join(`,`)}].`
209
+ );
210
+ }
211
+ };
212
+ if (!existing && !columnData && patchId) {
213
+ const match = await ctx.db.get(patchId);
214
+ if (!match) {
215
+ uniqueRowError(`No document found for id ${patchId}`);
216
+ return data;
217
+ }
218
+ const extensiveColumnData = constructColumnData(
219
+ fields,
220
+ {
221
+ ...match,
222
+ ...data
223
+ },
224
+ {}
225
+ );
226
+ if (extensiveColumnData) {
227
+ const extensiveExisting = await getExisting(extensiveColumnData);
228
+ checkExisting(extensiveExisting, data);
229
+ } else {
230
+ uniqueRowError(`Incomplete data when there should have been enough.`);
231
+ }
232
+ } else {
233
+ checkExisting(existing, data);
234
+ }
235
+ }
236
+ }
237
+ return data;
238
+ };
239
+ return createValidatePlugin("uniqueRow", config, {
240
+ insert: async (context, data) => {
241
+ return verifyUniqueness(context, data, context.tableName);
242
+ },
243
+ patch: async (context, data) => {
244
+ return verifyUniqueness(context, data, context.tableName);
245
+ }
246
+ });
247
+ };
248
+
249
+ // src/plugins/uniqueColumnConfig.ts
250
+ var import_values2 = require("convex/values");
251
+ var uniqueColumnConfig = (_schema, config) => {
252
+ const uniqueColumnError = (message) => {
253
+ throw new import_values2.ConvexError({
254
+ message,
255
+ code: "UNIQUE_COLUMN_VERIFICATION_ERROR"
256
+ });
257
+ };
258
+ const verifyUniqueness = async (context, data) => {
259
+ const { ctx, tableName, patchId, onFail } = context;
260
+ const tableConfig = config[tableName];
261
+ if (!tableConfig) {
262
+ return data;
263
+ }
264
+ for (const entry of tableConfig) {
265
+ const { index, identifiers } = normalizeIndexConfigEntry(
266
+ entry
267
+ );
268
+ const columnName = index.replace("by_", "");
269
+ const value = data[columnName];
270
+ if (value === void 0 || value === null) {
271
+ continue;
272
+ }
273
+ const existing = await ctx.db.query(tableName).withIndex(index, (q) => q.eq(columnName, value)).unique();
274
+ if (!existing) {
275
+ continue;
276
+ }
277
+ let isOwnDocument = false;
278
+ for (const identifier of identifiers) {
279
+ if (identifier === "_id" && patchId && existing._id === patchId) {
280
+ isOwnDocument = true;
281
+ break;
282
+ }
283
+ if (existing[identifier] && data[identifier] && existing[identifier] === data[identifier]) {
284
+ isOwnDocument = true;
285
+ break;
286
+ }
287
+ }
288
+ if (isOwnDocument) {
289
+ continue;
290
+ }
291
+ onFail?.({
292
+ uniqueColumn: {
293
+ conflictingColumn: columnName,
294
+ existingData: existing
295
+ }
296
+ });
297
+ uniqueColumnError(
298
+ `In [${tableName}] table, there already exists value "${value}" in column [${columnName}].`
299
+ );
300
+ }
301
+ return data;
302
+ };
303
+ return createValidatePlugin("uniqueColumn", config, {
304
+ insert: async (context, data) => {
305
+ return verifyUniqueness(context, data);
306
+ },
307
+ patch: async (context, data) => {
308
+ return verifyUniqueness(context, data);
309
+ }
310
+ });
311
+ };
312
+ // Annotate the CommonJS export names for ESM import in node:
313
+ 0 && (module.exports = {
314
+ uniqueColumnConfig,
315
+ uniqueRowConfig
316
+ });
317
+ //# sourceMappingURL=index.js.map