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
|
@@ -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
|