UNPKG

uniforms-bridge-simple-schema

Version:
185 lines (154 loc) 4.87 kB
import invariant from 'invariant'; import cloneDeep from 'lodash/cloneDeep'; import memoize from 'lodash/memoize'; // @ts-ignore -- This package _is_ typed, but not in all environments. import { SimpleSchema } from 'meteor/aldeed:simple-schema'; import { Bridge, UnknownObject, joinName } from 'uniforms'; const propsToRemove = ['optional', 'uniforms', 'allowedValues']; /** Option type used in SelectField or RadioField */ type Option<Value> = { disabled?: boolean; label?: string; key?: string; value: Value; }; export default class SimpleSchemaBridge extends Bridge { schema: SimpleSchema; constructor({ schema }: { schema: SimpleSchema }) { super(); this.schema = schema; // Memoize for performance and referential equality. this.getField = memoize(this.getField.bind(this)); this.getInitialValue = memoize(this.getInitialValue.bind(this)); this.getProps = memoize(this.getProps.bind(this)); this.getSubfields = memoize(this.getSubfields.bind(this)); this.getType = memoize(this.getType.bind(this)); } // TODO: Get rid of this `any`. getError(name: string, error: any) { const details = error?.details; if (!Array.isArray(details)) { return null; } return details.find(error => error.name === name) || null; } // TODO: Get rid of this `any`. getErrorMessage(name: string, error: any) { const scopedError = this.getError(name, error); return !scopedError ? '' : this.schema.messageForError( scopedError.type, scopedError.name, null, scopedError.details && scopedError.details.value, ); } // TODO: Get rid of this `any`. getErrorMessages(error: any) { if (!error) { return []; } const { details } = error; return Array.isArray(details) ? details.map(error => this.schema.messageForError( error.type, error.name, null, error.details && error.details.value, ), ) : [error.message || error]; } getField(name: string) { const definition = this.schema.getDefinition(name); invariant(definition, 'Field not found in schema: "%s"', name); return definition; } getInitialValue(name: string): unknown { const field = this.getField(name); const defaultValue = field.defaultValue; if (defaultValue !== undefined) { return cloneDeep(defaultValue); } if (field.type === Array) { const item = this.getInitialValue(joinName(name, '$')); if (item === undefined) { return []; } const length = field.minCount || 0; return Array.from({ length }, () => item); } if (field.type === Object) { const value: UnknownObject = {}; this.getSubfields(name).forEach(key => { const initialValue = this.getInitialValue(joinName(name, key)); if (initialValue !== undefined) { value[key] = initialValue; } }); return value; } return undefined; } getProps(name: string) { const { type: fieldType, ...props } = this.getField(name); props.required = !props.optional; if ( typeof props.uniforms === 'function' || typeof props.uniforms === 'string' ) { props.component = props.uniforms; } else { Object.assign(props, props.uniforms); } type OptionList = Option<unknown>[]; type Options = OptionList | (() => OptionList); let options: Options | undefined = props.options; let allowedValues: unknown[] | (() => unknown[]) | undefined = props.allowedValues; if (typeof options === 'function') { options = options(); } if (!options && typeof allowedValues === 'function') { allowedValues = allowedValues(); } if (!options && Array.isArray(allowedValues)) { options = allowedValues.map(value => ({ value })); } else if (fieldType === Array) { try { const itemProps = this.getProps(`${name}.$`); if (itemProps.options) { options = itemProps.options as OptionList; } } catch (_) { // It's fine. } } propsToRemove.forEach(key => { if (key in props) { delete props[key]; } }); return Object.assign(props, { options }); } getSubfields(name?: string): string[] { return this.schema.objectKeys(SimpleSchema._makeGeneric(name)); } getType(name: string) { return this.getField(name).type; } getValidator(options: UnknownObject = { clean: true }) { const validator = this.schema.validator(options); return (model: any) => { try { // Clean mutate its argument. validator(options.clean ? cloneDeep({ ...model }) : model); return null; } catch (error) { return error; } }; } }