UNPKG

@plasius/schema

Version:

Entity schema definition & validation helpers for Plasius ecosystem

392 lines (370 loc) 15.3 kB
type PIIClassification = "none" | "low" | "high"; type PIIAction = "encrypt" | "hash" | "clear" | "none"; type PIILogHandling = "redact" | "omit" | "pseudonym" | "plain"; interface PII { classification: PIIClassification; action: PIIAction; logHandling?: PIILogHandling; purpose?: string; } type FieldType$1 = "string" | "number" | "boolean" | "object" | "array" | "ref"; declare class FieldBuilder<TExternal = unknown, TInternal = TExternal> { type: FieldType$1; _type: TExternal; _storageType: TInternal; isSystem: boolean; isImmutable: boolean; isRequired: boolean; _validator?: (value: any) => boolean; _description: string; _version: string; _default?: TInternal | (() => TInternal); _upgrade?: (value: any, ctx: { entityFrom: string; entityTo: string; fieldTo: string; fieldName: string; }) => { ok: boolean; value?: any; error?: string; }; _shape?: Record<string, FieldBuilder<any>>; itemType?: FieldBuilder<any>; refType?: string; _pii: PII; enumValues?: readonly TInternal[]; constructor(type: FieldType$1, options?: { shape?: Record<string, FieldBuilder<any>>; itemType?: FieldBuilder<any>; refType?: string; }); immutable(): FieldBuilder<TExternal, TInternal>; system(): FieldBuilder<TExternal, TInternal>; required(): FieldBuilder<TExternal, TInternal>; optional(): FieldBuilder<TExternal, TInternal>; validator(fn: (value: TInternal) => boolean): FieldBuilder<TExternal, TInternal>; description(desc: string): FieldBuilder<TExternal, TInternal>; default(value: TInternal | (() => TInternal)): FieldBuilder<TExternal, TInternal>; /** * Configure an upgrader used when validating older entities against a newer schema. * The upgrader receives the current field value and version context, and should * return { ok: true, value } with the upgraded value, or { ok: false, error }. */ upgrade(fn: (value: any, ctx: { entityFrom: string; entityTo: string; fieldTo: string; fieldName: string; }) => { ok: boolean; value?: any; error?: string; }): FieldBuilder<TExternal, TInternal>; getDefault(): TInternal | undefined; version(ver: string): FieldBuilder<TExternal, TInternal>; PID(pii: PII): FieldBuilder<TExternal, TInternal>; min(min: number): FieldBuilder<TExternal, TInternal>; max(max: number): FieldBuilder<TExternal, TInternal>; pattern(regex: RegExp): FieldBuilder<TExternal, TInternal>; enum<const U extends readonly TInternal[]>(values: U): FieldBuilder<U[number]>; /** * Create a shallow clone with a different external type parameter. * Note: shape and itemType are passed by reference (shallow). If you need * deep isolation of nested FieldBuilders, clone them explicitly. */ as<U>(): FieldBuilder<U, TInternal>; } type FieldTypeMap = { string: string; number: number; boolean: boolean; object: Record<string, unknown>; "string[]": string[]; "number[]": number[]; "boolean[]": boolean[]; "object[]": Record<string, unknown>[]; ref: RefEntityId; "ref[]": RefEntityId[]; }; type FieldType = keyof FieldTypeMap; type PIIEnforcement = "strict" | "warn" | "none"; /** Result returned by a schema upgrade function. */ type SchemaUpgradeResult = { ok: boolean; /** Upgraded entity (must include all required fields for the new schema). */ value?: Record<string, any>; /** Optional human-readable errors if upgrade fails. */ errors?: string[]; }; /** * A function that upgrades an entity from an older schema version to a newer one. * It should be **pure** and not mutate its arguments. */ type SchemaUpgradeFunction = (input: Record<string, any>, ctx: { from: string; to: string; entityType: string; /** Describe function for the target schema – useful for dynamic migrations. */ describe: () => { entityType: string; version: string; shape: Record<string, any>; }; log?: (msg: string) => void; }) => SchemaUpgradeResult; type SchemaUpgradeStep = { /** The target version this step upgrades to (e.g., "1.1.0"). */ to: string; /** The migration function that upgrades from the previous step's version to `to`. */ run: SchemaUpgradeFunction; }; /** * Schema-level upgrade specification can be either: * - a single `SchemaUpgradeFunction` that handles any from->to, OR * - an ordered array of steps, each targeting a specific `to` version, which * will be applied sequentially to cascade from the oldest known version to the latest. */ type SchemaUpgradeSpec = SchemaUpgradeFunction | SchemaUpgradeStep[]; interface SchemaOptions { version?: string; table?: string; schemaValidator?: (value: any) => boolean; piiEnforcement?: PIIEnforcement; schemaUpgrade?: SchemaUpgradeStep[] | SchemaUpgradeFunction | undefined; } type SchemaShape = Record<string, FieldBuilder<any>>; interface FieldDefinition<T = unknown> { type: FieldType; __valueType?: T; optional?: boolean; immutable?: boolean; description?: string; refType?: string; version?: string; deprecated?: boolean; deprecatedVersion?: string; system?: boolean; autoValidate?: boolean; refPolicy?: "eager" | "lazy"; enum?: string[]; _shape?: SchemaShape; pii?: PII; validator?: (value: any) => boolean; } interface ValidateCompositionOptions { resolveEntity: (type: string, id: string) => Promise<any | null>; validatorContext?: { visited: Set<string>; }; maxDepth?: number; log?: (msg: string) => void; onlyFields?: string[]; } interface Schema<T extends SchemaShape> { _shape: T; meta: { entityType: string; version: string; }; schemaValidator: (entity: Infer<T>) => boolean; /** * Runs the optional schema-level upgrade function once, without validating. * Useful for offline migrations or testing migration logic. */ upgrade(input: Record<string, any>, log?: (msg: string) => void): { ok: boolean; value?: Record<string, any>; errors?: string[]; }; validate: (input: unknown, existing?: Record<string, any>) => ValidationResult<Infer<T>>; validateComposition: (entity: Infer<T>, options: ValidateCompositionOptions) => Promise<void>; tableName?: () => string | undefined; prepareForRead(stored: Record<string, any>, decryptFn: (value: string) => any | null): Record<string, any>; prepareForStorage(input: Record<string, any>, encryptFn: (value: any) => string, hashFn: (value: any) => string): Record<string, any>; sanitizeForLog(data: Record<string, any>, pseudonymFn: (value: any) => string): Record<string, any>; getPiiAudit(): Array<{ field: string; classification: PIIClassification; action: PIIAction; logHandling?: PIILogHandling; purpose?: string; }> | null; scrubPiiForDelete(stored: Record<string, any>): Record<string, any>; describe(): { entityType: string; version: string; shape: Record<string, { type: FieldType; optional: boolean; immutable: boolean; description: string; version: string; deprecated: boolean; deprecatedVersion: string | null; system: boolean; enum: string[] | null; refType: string | null; pii: PII | null; }>; }; } type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? T[K] extends (...args: any[]) => any ? T[K] : DeepReadonly<T[K]> : T[K]; }; interface ValidationResult<T> { valid: boolean; value?: DeepReadonly<T>; errors?: string[]; } type RefEntityId<T extends string = string> = { type: T; id: string; }; type InferField<T> = T extends { _type: infer U; } ? U : T extends { type: "string"; } ? string : T extends { type: "number"; } ? number : T extends { type: "boolean"; } ? boolean : T extends { type: "array"; itemType: infer Item; } ? InferField<Item>[] : T extends { type: "object"; shape: infer Shape extends SchemaShape; } ? InferFromShape<Shape> : unknown; type IsOptional<T> = T extends { optional: true; } ? true : false; type InferFromShape<S extends SchemaShape> = { [K in keyof S]: IsOptional<S[K]> extends true ? InferField<S[K]> | undefined : InferField<S[K]>; }; type InferFromSchema<T extends { shape: SchemaShape; }> = InferFromShape<T["shape"]>; type IsSchema<T> = T extends { shape: SchemaShape; } ? true : false; type Infer<T> = IsSchema<T> extends true ? InferFromSchema<T & { shape: SchemaShape; }> : InferFromShape<T & SchemaShape>; declare const field: { string: () => FieldBuilder<string, string>; number: () => FieldBuilder<number, number>; boolean: () => FieldBuilder<boolean, boolean>; object: <T extends Record<string, FieldBuilder>>(fields: T) => FieldBuilder<T, T>; array: (itemType: FieldBuilder) => FieldBuilder<unknown, unknown>; ref: <S extends SchemaShape>(refType: string) => FieldBuilder<Infer<S>, Infer<S>>; email: () => FieldBuilder<string, string>; phone: () => FieldBuilder<string, string>; url: () => FieldBuilder<string, string>; uuid: () => FieldBuilder<string, string>; dateTimeISO: () => FieldBuilder<string, string>; dateISO: () => FieldBuilder<string, string>; timeISO: () => FieldBuilder<string, string>; richText: () => FieldBuilder<string, string>; generalText: () => FieldBuilder<string, string>; latitude: () => FieldBuilder<number, number>; longitude: () => FieldBuilder<number, number>; version: () => FieldBuilder<string, string>; countryCode: () => FieldBuilder<string, string>; languageCode: () => FieldBuilder<string, string>; }; declare function createSchema<S extends SchemaShape>(_shape: S, entityType: string, options?: SchemaOptions): Schema<S>; /** * Retrieves a previously registered schema by its `entityType` from the * in-process global schema registry. */ declare function getSchemaForType(type: string): Schema<any> | undefined; /** * Returns all schemas registered in the in-process global registry. */ declare function getAllSchemas(): Schema<any>[]; declare function createComponentSchema<T extends SchemaShape>(shape: SchemaShape, name: string, version: string, tableName?: string, schemaValidator?: (entity: Infer<T>) => boolean): Schema<SchemaShape>; declare function registerComponentSchema(type: string, schema: Schema<SchemaShape>): void; declare function getComponentSchema(type: string): Schema<SchemaShape> | undefined; declare function getAllComponentSchemas(): [string, Schema<SchemaShape>][]; /** * Validates an email string using a simplified RFC 5322-compliant regex. * Returns true if the input is a string in the format `name@domain.tld`. */ declare const validateEmail: (value: unknown) => boolean; /** * Validates a phone number string in strict E.164 format. * Returns true for strings like "+441632960960" (max 15 digits, starting with a '+'). */ declare const validatePhone: (value: unknown) => boolean; /** * Validates a URL string using the WHATWG URL API. * Accepts only 'http' or 'https' protocols. * Returns true if the URL is syntactically valid. */ declare const validateUrl: (value: unknown) => boolean; /** * Validates a string against the RFC 4122 format for UUIDs. * Matches UUIDs of versions 1 to 5, e.g., "123e4567-e89b-12d3-a456-426614174000". */ declare const validateUUID: (value: unknown) => boolean; /** * Validates whether a string is a properly formatted ISO 8601 datetime, date, or time. * * @param value - The value to validate. * @param options - Optional settings for validation mode. * @param options.mode - The mode of validation: * - "datetime" (default): Checks full ISO 8601 datetime and equality with toISOString(). * - "date": Validates that the string is in YYYY-MM-DD format and represents a valid date. * - "time": Validates that the string matches a valid HH:MM:SS(.sss)?Z? ISO 8601 time pattern. * @returns True if the value is valid according to the specified mode; otherwise, false. */ declare const validateDateTimeISO: (value: unknown, options?: { mode?: "datetime" | "date" | "time"; }) => boolean; declare const isoCountryCodes: Set<string>; /** * Validates whether a string is a valid ISO 3166-1 alpha-2 country code. * Performs a case-insensitive lookup against a predefined set of known codes. */ declare const validateCountryCode: (value: unknown) => boolean; declare const isoCurrencyCodes: Set<string>; /** * Validates whether a string is a valid ISO 4217 currency code. * Performs a case-insensitive lookup against a predefined set of known codes. */ declare const validateCurrencyCode: (value: unknown) => boolean; /** * Validates that a text field is safe for storage (per OWASP guidelines). * Applies to general text fields: names, descriptions, titles, etc. * Global Standard: OWASP Input Validation Cheat Sheet (2024) */ declare function validateSafeText(value: unknown): boolean; /** * Validates that a version string conforms to Semantic Versioning (SemVer 2.0.0). * Global Standard: https://semver.org/ */ declare function validateSemVer(value: unknown): boolean; /** * Validates that a number is a percentage value (0 to 100 inclusive). * Global Standard: ISO 80000-1 percentage definition. */ declare function validatePercentage(value: unknown): boolean; /** * Validates rich text input to ensure it contains only safe HTML/Markdown. * Global Standard: OWASP HTML Sanitization Guidelines (2024). * This validator checks for dangerous patterns — does not sanitize — assumes text will be sanitized downstream. */ declare function validateRichText(value: unknown): boolean; /** * Validates that a name is safe, culturally inclusive, and matches global best practice. * Global Standard: OWASP Input Validation Cheat Sheet + ICAO Doc 9303 + IETF PRECIS */ declare function validateName(value: unknown): boolean; /** * Validates that a user ID is a valid `sub` from one of the supported identity providers. * Global Standard: OpenID Connect Core 1.0 `sub` claim. */ declare function validateUserId(value: unknown): boolean; declare function validateUserIdArray(value: unknown): boolean; export { type DeepReadonly, FieldBuilder, type FieldDefinition, type FieldType, type FieldTypeMap, type Infer, type PIIEnforcement, type RefEntityId, type Schema, type SchemaOptions, type SchemaShape, type SchemaUpgradeFunction, type SchemaUpgradeResult, type SchemaUpgradeSpec, type SchemaUpgradeStep, type ValidateCompositionOptions, type ValidationResult, createComponentSchema, createSchema, field, getAllComponentSchemas, getAllSchemas, getComponentSchema, getSchemaForType, isoCountryCodes, isoCurrencyCodes, registerComponentSchema, validateCountryCode, validateCurrencyCode, validateDateTimeISO, validateEmail, validateName, validatePercentage, validatePhone, validateRichText, validateSafeText, validateSemVer, validateUUID, validateUrl, validateUserId, validateUserIdArray };