PropertyDescriptors w walidacji schematów: type-safe obsługa błędów
Programiści często napotykają problem walidacji złożonych, zagnieżdżonych obiektów w projektach JavaScript. Standardowe metody oparte na ścieżkach stringowych do wyciągania błędów prowadzą do błędów w runtime podczas refaktoryzacji. Alternatywą są PropertyDescriptors: strukturalne drzewo deskryptorów właściwości, zapewniające type-safe selektory z autouzupełnianiem w IDE.
Kluczowa zasada — każda schemat tworzy drzewo węzłów, gdzie każdy deskryptor zna ścieżkę do właściwości, powiązaną schemę, rodzica i metody dostępu. Dzięki temu selektory kompilowane są przez lambdy zamiast stringów.
import { object, string, number, InferType } from '@cleverbrush/schema';
const OrderSchema = object({
id: string(),
customer: object({
name: string().minLength(2),
email: string().email(),
address: object({
city: string().required('Miasto jest wymagane'),
zip: number()
})
}),
total: number().min(0)
});
const result = OrderSchema.validate({
id: '123',
customer: {
name: 'A',
email: 'not-an-email',
address: {
zip: -1
}
},
total: -5
});
const nameErrors = result.getErrorsFor(t => t.customer.name);
const cityErrors = result.getErrorsFor(t => t.customer.address.city);
Po zmianie nazwy pola TypeScript zgłosi błąd kompilacji, a IDE zapewni autouzupełnianie.
Niezmienny fluent API i buildery
Wszystkie wywołania metod na schematach zwracają nowe instancje, eliminując mutacje. Ułatwia to kompozycję i ponowne użycie.
Biblioteka zawiera 14 typów builderów:
string()— minLength, maxLength, matches, email, url, uuidnumber()— min, max, positive, negative, finite, multipleOfboolean()— podstawowa walidacjadate()— minDate, maxDateobject()— nazwane właściwościarray()— typizowane elementy, nonemptyunion()— dyskryminowane unietuple()— stałej długościrecord()— dynamiczne kluczefunc()— typizacja funkcjiany()— z hasTypenul()— nulllazy()— rekurencyjne schematyextern()— wrapper Standard Schema v1
Wspólne metody bazowego SchemaBuilder:
.optional()— akceptuje undefined.required('msg')— wymagane pole.nullable()— akceptuje null.default(value)— wartość domyślna.catch(value)— przy błędzie.readonly()— Readonly<T>.brand<'T'>()— branded types.describe('text')— zachowanie JSDoc.addValidator(fn)— niestandardowa logika.addPreprocessor(fn)— przedobróbka.introspect()— metadane
Introspekcja schematów w runtime
Metoda .introspect() zwraca pełne opisanie schematu: typ, ograniczenia, flagi, opisy, rozszerzenia. Dane dostępne w runtime dla narzędzi.
const Name = string().minLength(2).maxLength(100).describe('Imię użytkownika').optional();
const info = Name.introspect();
// info.type === 'string'
// info.minLength === 2
// info.description === 'Imię użytkownika'
Introspekcja jest rozszerzana dla każdego buildera. Używana do konwersji JSON Schema, generowania formularzy, niestandardowych rendererów.
System typizowanych rozszerzeń
Rozszerzenia to komponowalne, introspekcjonowalne dodatki do rdzenia. Przykład koloru HEX i portu:
const hexColorExt = defineExtension({
string: {
hexColor(this: StringSchemaBuilder) {
return this.addValidator((v) => {
const valid = /^#[0-9a-f]{6}$/i.test(v as string);
return {
valid,
errors: valid ? [] : [{ message: 'Musi być kolor HEX' }]
};
});
}
}
});
const s = withExtensions(hexColorExt, portExt);
const colorSchema = s.string().hexColor();
Rozszerzenia widoczne w .introspect().extensions. Wbudowane: email, url, uuid, positive, nonempty.
Kompatybilność ze Standard Schema v1
Schematy implementują Standard Schema v1 dla integracji z tRPC, TanStack Form, React Hook Form, Hono. extern() owija Zod/Valibot w buildera.
const UserSchema = object({
name: string().minLength(2),
address: extern(zodAddress)
});
PropertyDescriptors zapewniają type-safe dostęp do błędów nawet w mieszanych schematach.
Co najważniejsze
- Type-safe selektory przez PropertyDescriptors eliminują ścieżki stringowe i błędy runtime.
- Niezmienność fluent API ułatwia kompozycję bez mutacji.
- Introspekcja w runtime pozwala wyciągać metadane dla narzędzi.
- Rozszerzenia — typizowane, komponowalne dodatki do rdzenia.
- Standard Schema v1 zapewnia kompatybilność z ekosystemem.
— Editorial Team
Brak komentarzy.