UNPKG

vnopts

Version:
538 lines (406 loc) 12 kB
# vnopts [![npm](https://img.shields.io/npm/v/vnopts.svg)](https://www.npmjs.com/package/vnopts) [![build](https://img.shields.io/github/actions/workflow/status/ikatyang/vnopts/test.yml)](https://github.com/ikatyang/vnopts/actions?query=branch%3Amain) validate and normalize options [Changelog](https://github.com/ikatyang/vnopts/blob/main/CHANGELOG.md) ## Install ```sh npm install vnopts ``` ## Usage ```js import * as vnopts from 'vnopts' const schemas = [ vnopts.ChoiceSchema.create({ name: 'parser', choices: ['babylon', 'flow', 'typescript'], }), vnopts.BooleanSchema.create({ name: 'useFlowParser', deprecated: true, redirect: value => !value ? undefined : { to: { key: 'parser', value: 'flow' } }, }), ] vnopts.normalize({ useFlowParser: true }, schemas) //=> { parser: "flow" } // warning: `useFlowParser` is deprecated, we now treat it as `{ parser: "flow" }`. vnopts.normalize({ parser: 'none' }, schemas) //=> error: Invalid `parser` value. Expected `"babylon", "flow" or "typescript"`, but received `"none"`. vnopts.normalize({ parserr: 'typescript' }, schemas) //=> {} // warning: Ignored unknown option `{ parserr: "typescript" }`. Did you mean `parser`? ``` ## API ### Normalizer ```ts /** a shortcut for `new Normalizer(...).normalize(...)` */ function normalize( options: object, schemas: Schema[], opts?: NormalizerOptions, ): object ``` ```ts class Normalizer { constructor(schemas: Schema[], opts?: NormalizerOptions) /** normalize the options based on schemas */ normalize(options: object): object /** clear the deprecation warning history so as to show the same warning again */ cleanHistory(): void } ``` ```ts interface NormalizerOptions { logger?: Logger | false loggerPrintWidth?: number descriptor?: Descriptor unknown?: UnknownHandler invalid?: InvalidHandler deprecated?: DeprecatedHandler missing?: IdentifyMissing required?: IdentifyRequired preprocess?: (options: Options, utils: Utils) => Options postprocess?: ( options: Options, utils: Utils, ) => typeof VALUE_UNCHANGED | { delete?: string[]; override?: Options } } ``` #### Logger Defaults to `console`. ```ts interface Logger { warn(message: string): void } ``` #### Descriptor Defaults to [`apiDescriptor`](https://github.com/ikatyang/vnopts/blob/main/src/descriptors/api.ts). ```ts interface Descriptor { key: (key: string) => string value: (value: any) => string pair: (pair: { key: string; value: any }) => string } ``` #### UnknownHandler Defaults to [`levenUnknownHandler`](https://github.com/ikatyang/vnopts/blob/main/src/handlers/unknown/leven.ts). ```ts type UnknownHandler = (key: string, value: any, utils: Utils) => void | object ``` The returned object will be merged into the output object (and validate its value if the key is known). #### InvalidHandler Defaults to [`commonInvalidHandler`](https://github.com/ikatyang/vnopts/blob/main/src/handlers/invalid/common.ts). ```ts type InvalidHandler = ( key: string, value: OptionValue, utils: Utils, ) => string | Error ``` Returns an error message or the error itself. #### DeprecatedHandler Defaults to [`commonDeprecatedHandler`](https://github.com/ikatyang/vnopts/blob/main/src/handlers/deprecated/common.ts). ```ts type DeprecatedHandler = ( keyOrPair: string | { key: string; value: any }, redirectToKeyOrPair: undefined | string | { key: string; value: any }, utils: Utils, ) => string ``` Returns a deprecation warning. #### IdentifyMissing Defaults to `() => false`. ```ts type IdentifyMissing = (key: string, options: Options) => boolean ``` Returns a boolean to indicate if `key` is _missing_ in `options`. (`!(key in options)` is always considered missing.) #### IdentifyRequired Defaults to `() => false`. ```ts type IdentifyRequired = (key: string) => boolean ``` Returns a boolean to indicate if `key` is required in the output. ### Schemas #### AnySchema ```ts interface AnySchemaParameters extends SchemaHandlers { name: string } ``` ```js const schemas = [vnopts.AnySchema.create({ name: 'any' })] vnopts.normalize({ any: 'hello world' }, schemas) //=> { any: "hello world" } vnopts.normalize({ unknown: 'hello world' }, schemas) //=> {} // warning: Ignored unknown option `{ unknown: "hello world" }`. ``` #### BooleanSchema ```ts interface BooleanSchemaParameters extends SchemaHandlers { name: string } ``` ```js const schemas = [vnopts.BooleanSchema.create({ name: 'bool' })] vnopts.normalize({ bool: true }, schemas) //=> { bool: true } vnopts.normalize({ bool: 'hello world' }, schemas) // error: Invalid `bool` value. Expected `true or false`, but received `"hello world"`. ``` #### NumberSchema ```ts interface NumberSchemaParameters extends SchemaHandlers { name: string } ``` ```js const schemas = [vnopts.NumberSchema.create({ name: 'num' })] vnopts.normalize({ num: 1 }, schemas) //=> { num: 1 } vnopts.normalize({ num: null }, schemas) // error: Invalid `num` value. Expected `a number`, but received `null`. ``` #### IntegerSchema ```ts interface IntegerSchemaParameters extends SchemaHandlers { name: string } ``` ```js const schemas = [vnopts.IntegerSchema.create({ name: 'int' })] vnopts.normalize({ int: 1 }, schemas) //=> { int: 1 } vnopts.normalize({ int: 1.5 }, schemas) // error: Invalid `int` value. Expected `an integer`, but received `1.5`. ``` #### StringSchema ```ts interface StringSchemaParameters extends SchemaHandlers { name: string } ``` ```js const schemas = [vnopts.StringSchema.create({ name: 'str' })] vnopts.normalize({ str: 'hi' }, schemas) //=> { str: "hi" } vnopts.normalize({ str: true }, schemas) // error: Invalid `str` value. Expected `a string`, but received `true`. ``` #### ChoiceSchema ```ts interface ChoiceSchemaParameters extends SchemaHandlers { name: string choices: Array< | undefined | null | boolean | number | string | { value: undefined | null | boolean | number | string deprecated?: boolean hidden?: boolean // do not show this value in `expected` redirect?: /* key */ string | { key: string; value: any } forward?: /* key */ string | { key: string; value: any } } > } ``` ```js const schemas = [ vnopts.ChoiceSchema.create({ name: 'choice', choices: [2, false, 'hey'] }), ] vnopts.normalize({ choice: 2 }, schemas) //=> { choice: 2 } vnopts.normalize({ choice: true }, schemas) // error: Invalid `choice` value. Expected `false, 2 or "hey"`, but received `true`. ``` #### AliasSchema `AliasSchema` validates values using the validator from the source schema and redirects all the value to the source key. ```ts interface AliasSchemaParameters extends SchemaHandlers { name: string /** the name of the source schema */ sourceName: string } ``` ```js const schemas = [ vnopts.BooleanSchema.create({ name: 'source' }), vnopts.AliasSchema.create({ name: 'alias', sourceName: 'source' }), ] vnopts.normalize({ alias: true }, schemas) //=> { source: true } vnopts.normalize({ alias: 'invalid' }, schemas) //=> error: Invalid `alias` value. Expected `true or false`, but received `"invalid"`. ``` #### ArraySchema `AliasSchema` validates values using the validator from the source schema and redirects all the value to the source key. ```ts interface ArraySchemaParameters extends SchemaHandlers { /** defaults to valueSchema's name */ name?: string valueSchema: Schema } ``` ```js const schemas = [ vnopts.ArraySchema.create({ valueSchema: vnopts.ChoiceSchema.create({ name: 'choices', choices: [1, true, 'foo'], }), }), ] vnopts.normalize({ choices: [1, 'foo'] }, schemas) //=> { choices: [1, "foo"] } vnopts.normalize({ choices: 1 }, schemas) //=> error: Invalid `choices` value. Expected `an array of true, 1 or "foo"`, but received `1`. ``` ### Handlers Every schema has its own handlers but you can still override/extend them. ```ts interface SchemaHandlers { default?: SchemaDefaultHandler expected?: SchemaExpectedHandler validate?: SchemaValidateHandler deprecated?: SchemaDeprecateHandler forward?: SchemaForwardHandler redirect?: SchemaRedirectHandler overlap?: SchemaOverlapHandler preprocess?: SchemaPreprocessHandler postprocess?: SchemaPostprocessHandler } ``` #### default ```ts type SchemaDefaultHandler = | DefaultResult | ((schema: Schema, utils: Utils) => DefaultResult) type DefaultResult = undefined | { value?: any } ``` `undefined` represents no default value, default values are wrapped in an object's `value` field to avoid the ambiguity between missing and `undefined`. #### expected ```ts type SchemaExpectedHandler = | ExpectedResult | ((schema: Schema, utils: Utils) => ExpectedResult) type ExpectedResult = | string | { text: string } | { text?: string list: { title: string values: ExpectedResult[] } } ``` Returns the description for the expected value in the form of text and/or list. For example the following `ExpectedResult`: ```json { "list": { "title": "one of the following values", "values": ["foo", "bar", "baz"] } } ``` will produce the following message in `commonInvalidHandler`: ``` Invalid `<key>` value. Expected `one of the following values`, but received `<value>`. - `"foo"` - `"bar"` - `"baz"` ``` If both `text` and `list` are returned, `text` will be chosen if its width is the minimum one or its width is less than `loggerPrintWidth`, otherwise `list`. #### validate ```ts type SchemaValidateHandler = | ValidateResult | ((value: unknown, schema: Schema, utils: Utils) => ValidateResult) type ValidateResult = boolean | { value: unknown } ``` Returns a boolean represents if the entire value is valid, or put the invalid value in an object's `value` field if only part of the value is invalid, this is useful for collection schema like `ArraySchema`. #### deprecated ```ts type SchemaDeprecatedHandler = | DeprecatedResult | ((value: unknown, schema: Schema, utils: Utils) => DeprecatedResult) type DeprecatedResult = boolean | { value: any } | Array<{ value: any }> ``` Returns `true` if the entire key is deprecated, `false` if it's not deprecated, or (an array of) an object with `value` field if only part of the value is deprecated, one object corresponds to one deprecation warning. #### forward ```ts type SchemaForwardHandler = | ForwardResult | ((value: any, schema: Schema, utils: Utils) => ForwardResult) type TransferTo = /* key */ string | { key: string; value: any } type TransferResult = TransferTo | { from?: /* value */ any; to: TransferTo } type ForwardResult = undefined | TransferResult | Array<TransferResult> ``` Returns a key or a key-value pair if the entire value needs to be forwarded there, or (an array of) an object with `from`/`to` field if only part of the value needs to be forwarded. #### redirect ```ts type SchemaRedirectHandler = | RedirectResult | ((value: any, schema: Schema, utils: Utils) => RedirectResult) type RedirectResult = | ForwardResult | { remain?: any redirect: ForwardResult } ``` Similar to `forward` but returns an object with `remain`/`redirect` field if not the entire value needs to be redirected. #### overlap ```ts type SchemaOverlapHandler = ( currentValue: any, newValue: any, schema: Schema, utils: Utils, ) => any ``` Describes what should the normalizer do if there're multiple values assigned to the same key. #### preprocess ```ts type SchemaPreprocessHandler = ( value: unknown, schema: Schema, utils: Utils, ) => unknown ``` The preprocess before passing into the validator. #### postprocess ```ts type SchemaPostprocessHandler = ( value: unknown, schema: Schema, utils: Utils, ) => unknown ``` The postprocess after normalization. ## Development ```sh # lint pnpm run lint # build pnpm run build # test pnpm run test ``` ## License MIT © [Ika](https://github.com/ikatyang)