Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | 6x 6x 36x 24x 24x 2x 22x 22x 33x 33x 33x 33x 2x 31x 1x 30x 30x 2x 28x 33x 33x 2x 33x 2x 33x 2x 1x 2x 1x 33x 10x 22x 22x 10x 10x 10x 22x 70x 102x 12x 19x 19x 1x 1x 1x 18x 18x 18x 18x 18x | /**
* Shared schema field validation for create/edit collection flows.
*/
import type { FieldDefinition } from '@/services/collections.service';
export const SYSTEM_FIELD_NAMES = [
'id',
'account_id',
'created_at',
'created_by',
'updated_at',
'updated_by',
] as const;
export type SchemaFieldErrorKey =
| 'name'
| 'type'
| 'collection'
| 'mask_type'
| 'expression'
| 'return_type'
| 'default';
export type SchemaFieldErrors = Record<
number,
Partial<Record<SchemaFieldErrorKey, string>>
>;
export interface SchemaValidationResult {
valid: boolean;
formError?: string;
fieldErrors: SchemaFieldErrors;
}
const SYSTEM_NAME_SET = new Set<string>(
SYSTEM_FIELD_NAMES.map((n) => n.toLowerCase()),
);
/**
* Validate user-defined schema fields before create/update submit.
* Does not include system fields — callers must never put them in the payload.
*/
export function validateSchemaFields(
fields: FieldDefinition[],
): SchemaValidationResult {
const fieldErrors: SchemaFieldErrors = {};
if (fields.length === 0) {
return {
valid: false,
formError: 'At least one field is required',
fieldErrors,
};
}
const seenNames = new Map<string, number>();
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const errors: Partial<Record<SchemaFieldErrorKey, string>> = {};
const name = field.name?.trim() ?? '';
if (!name) {
errors.name = 'Field name is required';
} else if (SYSTEM_NAME_SET.has(name.toLowerCase())) {
errors.name = `"${name}" is a reserved system field name`;
} else {
const normalized = name.toLowerCase();
if (seenNames.has(normalized)) {
errors.name = `Duplicate field name: "${field.name}"`;
} else {
seenNames.set(normalized, i);
}
}
Iif (!field.type) {
errors.type = 'Field type is required';
}
if (field.type === 'reference' && !field.collection) {
errors.collection = 'Target collection is required for reference fields';
}
if (field.pii && !field.mask_type) {
errors.mask_type = 'Mask type is required when PII is enabled';
}
if (field.type === 'computed') {
if (!field.expression?.trim()) {
errors.expression = 'Expression is required for computed fields';
}
if (!field.return_type) {
errors.return_type = 'Return type is required for computed fields';
}
}
if (Object.keys(errors).length > 0) {
fieldErrors[i] = errors;
}
}
const hasFieldErrors = Object.keys(fieldErrors).length > 0;
let formError: string | undefined;
if (hasFieldErrors) {
// Prefer a stable first-message summary for form-level display
const firstIndex = Math.min(...Object.keys(fieldErrors).map(Number));
const firstErrors = fieldErrors[firstIndex];
formError = firstErrors
? (Object.values(firstErrors)[0] as string)
: 'Schema validation failed';
}
return {
valid: !hasFieldErrors,
formError,
fieldErrors,
};
}
/**
* Normalize field list for dirty comparison (stable key order).
*/
export function normalizeSchemaForCompare(fields: FieldDefinition[]): string {
return JSON.stringify(
fields.map((f) => ({
name: f.name,
type: f.type,
required: f.required ?? false,
unique: f.unique ?? false,
pii: f.pii ?? false,
default: f.default ?? null,
collection: f.collection ?? null,
on_delete: f.on_delete ?? null,
mask_type: f.mask_type ?? null,
expression: f.expression ?? null,
return_type: f.return_type ?? null,
})),
);
}
/**
* Strip empty default so payload omits unused defaults.
*/
export function prepareSchemaPayload(fields: FieldDefinition[]): FieldDefinition[] {
return fields.map((field) => {
const next: FieldDefinition = {
name: field.name.trim(),
type: field.type,
required: field.required ?? false,
unique: field.unique ?? false,
pii: field.pii ?? false,
};
if (field.type === 'computed') {
Eif (field.expression) next.expression = field.expression;
Eif (field.return_type) next.return_type = field.return_type;
return next;
}
Iif (field.default !== undefined && field.default !== null && field.default !== '') {
next.default = field.default;
}
Iif (field.type === 'reference') {
if (field.collection) next.collection = field.collection;
if (field.on_delete) next.on_delete = field.on_delete;
}
Iif (field.pii && field.mask_type) {
next.mask_type = field.mask_type;
}
Iif (field.options) {
next.options = field.options;
}
return next;
});
}
|