+5
-3
@@ -6,4 +6,6 @@ import Tuple from './Tuple.mjs'; | ||
| base.toString = Object.prototype.toString; | ||
| base[Symbol.toStringTag] = 'Dict'; | ||
| base.toJSON = function() { | ||
| return {...this}; | ||
| }; | ||
| base[Symbol.iterator] = function() { | ||
@@ -26,3 +28,3 @@ let index = 0; | ||
| { | ||
| throw new Error('"Dict" is not a constructor. Create a Record by invoking the function directly.'); | ||
| throw new Error('"Dict" is not a constructor. Create a Dict by invoking the function directly.'); | ||
| } | ||
@@ -39,3 +41,3 @@ | ||
| const tagged = Tuple.bind({args: obj, base, length: keys.length, keys}); | ||
| return tagged('dict', Tuple(...keys), Tuple(...values)); | ||
| return tagged(Tuple(...keys), Tuple(...values), 'dict'); | ||
| } |
+0
-1
@@ -5,2 +5,1 @@ export { default as Tuple } from './Tuple.mjs'; | ||
| export { default as Dict } from './Dict.mjs'; | ||
| export { default as Schema } from './Schema.mjs'; |
+2
-3
| { | ||
| "name": "libtuple", | ||
| "version": "0.0.8", | ||
| "version": "1.0.0", | ||
| "author": "Sean Morris", | ||
@@ -10,3 +10,3 @@ "description": "Memory-efficient tuple implementation.", | ||
| "scripts": { | ||
| "test": "node --test --expose-gc test/test.mjs test/schema.test.mjs" | ||
| "test": "node --test --expose-gc test/test.mjs" | ||
| }, | ||
@@ -21,3 +21,2 @@ "license": "Apache 2.0", | ||
| "Record.mjs", | ||
| "Schema.mjs", | ||
| "Tuple.mjs", | ||
@@ -24,0 +23,0 @@ "index.mjs", |
+39
-473
| # libtuple | ||
| [](https://github.com/seanmorris/libtuple/actions/workflows/test.yaml) *Memory-efficient immutables in 13.5kB* | ||
| *Memory-efficient immutables in 9kB* | ||
| ### Install & Use | ||
| [](https://www.npmjs.com/package/libtuple) [](https://github.com/seanmorris/libtuple/actions/workflows/test.yaml) [](https://github.com/seanmorris/libtuple/blob/master/LICENSE) | ||
| `libtuple` is now ESM compliant! | ||
| ### ⚠️ Notice: Schemas have been moved to the [libtuple-schema](https://github.com/seanmorris/libtuple-schema) project. ⚠️ | ||
| #### npm: | ||
| > ## I am giving up my bed for one night. | ||
| > My Sleep Out helps youth facing homelessness find safe shelter and loving care at Covenant House. That care includes essential services like education, job training, medical care, mental health and substance use counseling, and legal aid — everything they need to build independent, sustainable futures. | ||
| > | ||
| > By supporting my Sleep Out, you are supporting the dreams of young people overcoming homelessness. | ||
| > | ||
| > <a href = "https://www.sleepout.org/participants/62915"><img width = "50%" alt="Donate to Covenant House" src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.sleepout.org%2Fapi%2F1.3%2Fparticipants%2F62915%3F_%3D1760039017428&query=%24.sumDonations&prefix=%24&suffix=%20Raised&style=for-the-badge&label=Sleep%20Out%3A%20NYC&link=https%3A%2F%2Fwww.sleepout.org%2Fparticipants%2F62915"></a> | ||
| > | ||
| > Click here to help out: https://www.sleepout.org/participants/62915 | ||
| > | ||
| > More info: https://www.sleepout.org/ | https://www.covenanthouse.org/ | https://www.charitynavigator.org/ein/132725416 | ||
| > | ||
| > Together, we are working towards a future where every young person has a safe place to sleep. | ||
| > | ||
| > Thank you. | ||
| > | ||
| > and now back to your documentation... | ||
| ### Install & Use | ||
| You can install libtuple via `npm`: | ||
@@ -21,2 +38,13 @@ | ||
| ### Value Objects | ||
| A tuple with the same values is the same tuple: | ||
| ```javascript | ||
| const t1 = Tuple('a', 'b', 'c'); | ||
| const t2 = Tuple('a', 'b', 'c'); | ||
| console.log( t1 === t2 ); //true | ||
| ``` | ||
| ### Immutable | ||
@@ -85,6 +113,6 @@ | ||
| You can also import them via URL imports, or [dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import): *(npm not required)* | ||
| You can also import them via URL imports, or [dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) *(npm not required)* | ||
| ```javascript | ||
| import { Tuple, Group, Record, Dict } from 'https://cdn.jsdelivr.net/npm/libtuple@0.0.7-alpha-4/index.mjs'; | ||
| import { Tuple, Group, Record, Dict } from 'https://cdn.jsdelivr.net/npm/libtuple@1.0.0/index.mjs'; | ||
| ``` | ||
@@ -145,465 +173,2 @@ | ||
| ### Schema | ||
| A `Schema` allows you to define a complex structure for your immutables. It is defined by one or more SchemaMappers, which take a value and will either return it, or throw an error: | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const boolSchema = s.boolean(); | ||
| boolSchema(true); // returns true | ||
| boolSchema(false); // returns false | ||
| boolSchema(123); // throws an error | ||
| ``` | ||
| You can create schemas for Tuples, Groups, Records, and Dicts: | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const pointSchema = s.tuple( | ||
| s.number(), | ||
| s.number(), | ||
| ); | ||
| const pointTuple = pointSchema([5, 10]); | ||
| console.log(pointTuple); | ||
| const userSchema = s.record({ | ||
| id: s.number(), | ||
| email: s.string(), | ||
| }); | ||
| const userRecord = userSchema({id: 1, email: 'fake@example.com'}); | ||
| console.log(userRecord); | ||
| ``` | ||
| #### Schema.parse(schema, value) | ||
| `Schema.parse()` will return the parsed value, or NaN on error, since `NaN` is falsey, and `NaN !== NaN`. | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const boolSchema = s.boolean(); | ||
| s.parse(boolSchema, true); // returns true | ||
| s.parse(boolSchema, false); // returns false | ||
| s.parse(boolSchema, 123); // returns NaN | ||
| ``` | ||
| ### SchemaMappers | ||
| *Expand the sections below to see SchemaMapper documentation.* | ||
| <details> | ||
| <summary>Schema Mappers for Values</summary> | ||
| #### Schema.value(options) | ||
| * options.map - Callback to transform the value after its been validated. | ||
| * options.check - Throw a TypeError if this returns false. | ||
| #### Schema.drop() | ||
| Drop the value (always maps to `undefined`) | ||
| #### Schema.boolean(options) | ||
| * options.map - Callback to transform the value after its been validated. | ||
| #### Schema.number(options) | ||
| * options.min - Min value | ||
| * options.max - Max value | ||
| * options.map - Callback to transform the value after its been validated. | ||
| * options.check - Throw a TypeError if this returns false. | ||
| #### Schema.bigint(options) | ||
| * options.min - Min value | ||
| * options.max - Max value | ||
| * options.map - Callback to transform the value after its been validated. | ||
| * options.check - Throw a TypeError if this returns false. | ||
| #### Schema.string(options) | ||
| * options.min - Min length | ||
| * options.max - Max length | ||
| * options.map - Callback to transform the value after its been validated. | ||
| * options.match - Throw a TypeError if this does NOT match | ||
| * options.noMatch - Throw a TypeError if this DOES match | ||
| * options.check - Throw a TypeError if this returns false. | ||
| #### Schema.array(options) | ||
| * options.min - Min length | ||
| * options.max - Max length | ||
| * options.map - Callback to transform the value after its been validated. | ||
| * options.each - Callback to transform each element. | ||
| * options.check - Throw a TypeError if this returns false. | ||
| #### Schema.object(options) | ||
| * options.class - Throw a TypeError if the class does not match. | ||
| * options.map - Callback to transform the value after its been validated. | ||
| * options.each - Callback to transform each element. | ||
| * options.check - Throw a TypeError if this returns false. | ||
| #### Schema.function(options) | ||
| * options.map - Callback to transform the value after its been validated. | ||
| * options.check - Throw a TypeError if this returns false. | ||
| #### Schema.symbol(options) | ||
| * options.map - Callback to transform the value after its been validated. | ||
| * options.check - Throw a TypeError if this returns false. | ||
| #### Schema.null(options) | ||
| * options.map - Callback to transform the value after its been validated. | ||
| #### Schema.undefined(options) | ||
| * options.map - Callback to transform the value after its been validated. | ||
| --- | ||
| </details> | ||
| <details> | ||
| <summary>Schema Mappers for Convenience</summary> | ||
| #### Convenience methods for numbers | ||
| The following methods will call `s.number` with additional constraints added: | ||
| * s.integer | ||
| * s.float | ||
| * s.NaN | ||
| * s.infinity | ||
| #### Convenience methods for strings | ||
| The following methods will call `s.string` with additional constraints added: | ||
| * s.numericString | ||
| ```javascript | ||
| // options.min & options.max are overridden for numeric comparison. | ||
| const positive = s.numericString({map: Number, min: Number.EPSILON}); | ||
| const negative = s.numericString({map: Number, max: -Number.EPSILON}); | ||
| negative('-100'); // -100 | ||
| positive('100'); // 100 | ||
| negative('5'); // ERROR | ||
| positive('-5'); // ERROR | ||
| ``` | ||
| * s.dateString | ||
| ```javascript | ||
| // options.min & options.max are overridden for comparison with Date objects. | ||
| const after1994 = s.dateString({min: new Date('01/01/1995')}); | ||
| after1994('07/04/1995'); // '01/01/1996' | ||
| after1994('07/04/1989'); // ERROR | ||
| ``` | ||
| * s.uuidString | ||
| ```javascript | ||
| const uuidSchema = s.uuidString(); | ||
| uuidSchema('0ff5d941-f46a-4f4a-aec8-1d1ec117e2a3'); // '0ff5d941-f46a-4f4a-aec8-1d1ec117e2a3' | ||
| uuidSchema('0ff5d941'); // ERROR | ||
| ``` | ||
| * s.urlString | ||
| ```javascript | ||
| const urlSchema = s.urlString(); | ||
| urlSchema('https://example.com'); // 'https://example.com' | ||
| urlSchema('not a url'); // ERROR | ||
| ``` | ||
| * s.emailString | ||
| ```javascript | ||
| const emailSchema = s.emailString(); | ||
| emailSchema('person@example.com'); // 'https://example.com' | ||
| emailSchema('not an email'); // ERROR | ||
| ``` | ||
| * s.regexString | ||
| ```javascript | ||
| const regexSchema = s.regexString(); | ||
| regexSchema('.+?'); // 'https://example.com' | ||
| regexSchema('+++'); // ERROR | ||
| ``` | ||
| * s.base64String | ||
| ```javascript | ||
| const base64Schema = s.base64String(); | ||
| base64Schema('RXhhbXBsZSBzdHJpbmc='); // 'RXhhbXBsZSBzdHJpbmc='; | ||
| base64Schema('notbase64'); // ERROR; | ||
| ``` | ||
| * s.jsonString | ||
| ```javascript | ||
| const jsonSchema = s.jsonString(); | ||
| jsonSchema('[0, 1, 2]'); // '[0, 1, 2]'; | ||
| jsonSchema('not json'); // ERROR; | ||
| ``` | ||
| </details> | ||
| <details> | ||
| <summary>Special Schema Mappers</summary> | ||
| #### Schema.or(...schemaMappers) | ||
| Map the value with the first matching SchemaMapper | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const dateSchema = s.or( | ||
| s.string({match: /\d\d \w+ \d\d\d\d \d\d:\d\d:\d\d \w+?/, map: s => new Date(s)}), | ||
| s.object({class: Date}) | ||
| ); | ||
| console.log( dateSchema('04 Apr 1995 00:12:00 GMT') ); | ||
| console.log( dateSchema(new Date) ); | ||
| ``` | ||
| #### Schema.repeat(r, schemaMapper) | ||
| Repeat a SchemaMapper r times | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const pointSchema = s.tuple(s.repeat(2, s.number())); | ||
| const point = pointSchema([5, 10]); | ||
| ``` | ||
| #### Schema.oneOf(literals = [], options = {}) | ||
| Match the value to a set of literals with strict-equals comparison. | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const schema = s.oneOf(['something', 1234]); | ||
| s.parse(schema, 1234); // 1234 | ||
| s.parse(schema, 'something'); // 'something' | ||
| s.parse(schema, 'not on list'); // ERROR! | ||
| ``` | ||
| --- | ||
| </details> | ||
| <details> | ||
| <summary>Schema Mappers for Tuples, Groups, Records and Dicts</summary> | ||
| #### Schema.tuple(...values) | ||
| Map one or more values to a Tuple. | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const pointSchema = s.tuple(s.number(), s.number()); | ||
| const point = pointSchema([5, 10]); | ||
| ``` | ||
| #### Schema.group(...values) | ||
| Map one or more values to a Group. | ||
| #### Schema.record(properties) | ||
| Map one or more properties to a Record. | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const companySchema = s.sRecord({ | ||
| name: s.string(), | ||
| phone: s.string(), | ||
| address: s.string(), | ||
| }); | ||
| const company = companySchema({ | ||
| name: 'Acme Corporation', | ||
| phone: '+1-000-555-1234', | ||
| address: '123 Fake St, Anytown, USA', | ||
| }); | ||
| console.log({company}); | ||
| ``` | ||
| #### Schema.dict(properties) | ||
| Map one or more values to a Dict. | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const companySchema = s.sDict({ | ||
| name: s.string(), | ||
| phone: s.string(), | ||
| address: s.string(), | ||
| }); | ||
| const company = companySchema({ | ||
| name: 'Acme Corporation', | ||
| phone: '+1-000-555-1234', | ||
| address: '123 Fake St, Anytown, USA', | ||
| }); | ||
| console.log({company}); | ||
| ``` | ||
| #### Schema.nTuple(...values) | ||
| Map n values to a Tuple. Will append each value in the input to the Tuple using the same mapper. | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const vectorSchema = s.nTuple(s.number()); | ||
| const vec2 = vectorSchema([5, 10]); | ||
| const vec3 = vectorSchema([5, 10, 11]); | ||
| const vec4 = vectorSchema([5, 10, 11, 17]); | ||
| console.log({vec2, vec3, vec4}); | ||
| ``` | ||
| #### Schema.nGroup(...values) | ||
| Map n values to a Group. Will append each value in the input to the Group using the same mapper. | ||
| #### Schema.nRecord(properties) | ||
| Map n properties to a Record. Will append additional properties without mapping or validation, if present. | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const companySchema = s.nRecord({ | ||
| name: s.string(), | ||
| phone: s.string(), | ||
| address: s.string(), | ||
| }); | ||
| const company = companySchema({ | ||
| name: 'Acme Corporation', | ||
| phone: '+1-000-555-1234', | ||
| address: '123 Fake St, Anytown, USA', | ||
| openHours: '9AM-7PM', | ||
| slogan: 'We do business.', | ||
| }); | ||
| ``` | ||
| #### Schema.nDict(properties) | ||
| Map n properties to a Dict. Will append additional properties without mapping or validation, if present. | ||
| #### Schema.sTuple(...values) | ||
| Strictly map values to a Tuple. Will throw an error if the number of values does not match. | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const pointSchema = s.sTuple(s.number(), s.number()); | ||
| const pointA = pointSchema([5, 10]); | ||
| const pointB = pointSchema([5, 10, 1]); // ERROR! | ||
| ``` | ||
| #### Schema.sGroup(...values) | ||
| Strictly map values to a Group. Will throw an error if the number of values does not match. | ||
| #### Schema.sRecord(properties) | ||
| Strictly map values to a Record. Will throw an error if the number of values does not match. | ||
| ```javascript | ||
| import { Schema as s } from 'libtuple'; | ||
| const companySchema = s.nRecord({ | ||
| name: s.string(), | ||
| phone: s.string(), | ||
| address: s.string(), | ||
| }); | ||
| const company = companySchema({ | ||
| name: 'Acme Corporation', | ||
| phone: '+1-000-555-1234', | ||
| address: '123 Fake St, Anytown, USA', | ||
| }); | ||
| // ERROR! | ||
| companySchema({ | ||
| name: 'Acme Corporation', | ||
| phone: '+1-000-555-1234', | ||
| address: '123 Fake St, Anytown, USA', | ||
| openHours: '9AM-7PM', | ||
| slogan: 'We do business.', | ||
| }); | ||
| ``` | ||
| #### Schema.sDict(properties) | ||
| Strictly map values to a Dict. Will throw an error if the number of values does not match. | ||
| #### Schema.xTuple(...values) | ||
| Exclusively map values to a Tuple. Will drop any keys not present in the schema. | ||
| ```javascript | ||
| import { Schema as s } from './index.mjs'; | ||
| const pointSchema = s.xTuple(s.number(), s.number()); | ||
| const pointA = pointSchema([5, 10]); // [5, 10] | ||
| const pointB = pointSchema([5, 10, 1]); // Also [5, 10] | ||
| console.log(pointB[0]); // 5 | ||
| console.log(pointB[1]); // 10 | ||
| console.log(pointB[2]); // undefined | ||
| ``` | ||
| #### Schema.xGroup(...values) | ||
| Exclusively map values to a Group. Will drop any keys not present in the schema. | ||
| #### Schema.xRecord(properties) | ||
| Exclusively map values to a Record. Will drop any keys not present in the schema. | ||
| ```javascript | ||
| const companySchema = s.xRecord({ | ||
| name: s.string(), | ||
| phone: s.string(), | ||
| address: s.string(), | ||
| }); | ||
| const company = companySchema({ | ||
| name: 'Acme Corporation', | ||
| phone: '+1-000-555-1234', | ||
| address: '123 Fake St, Anytown, USA', | ||
| openHours: '9AM-7PM', | ||
| slogan: 'We do business.', | ||
| }); | ||
| console.log({company}); | ||
| ``` | ||
| #### Schema.xDict(properties) | ||
| Exclusively map values to a Dict. Will drop any keys not present in the schema. | ||
| --- | ||
| </details> | ||
| ## Gotchas | ||
@@ -645,9 +210,11 @@ | ||
| * Registered `Symbol`s cannot be used in `Tuples`. (i.e. created with `Symbol.for()`; [more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry)) [⚠️ Node v19 & Earlier](https://github.com/nodejs/node/issues/49135) | ||
| * Registered `Symbol`s cannot be used in `Tuples`, i.e. symbols created with `Symbol.for()`; | ||
| * [more info](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol#shared_symbols_in_the_global_symbol_registry) | ||
| * [⚠️ Node v19 & Earlier](https://github.com/nodejs/node/issues/49135) | ||
| ## Testing | ||
| Run `npm run test` or `node --test test.mjs` in the terminal. | ||
| Run `npm run test` or `node --test --expose-gc test/test.mjs` in the terminal. | ||
| # 🍻 Licensed under the Apache License, Version 2.0 | ||
| ## 🍻 Licensed under the Apache License, Version 2.0 | ||
@@ -659,2 +226,1 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at | ||
| Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. | ||
+4
-2
@@ -6,3 +6,5 @@ import Tuple from './Tuple.mjs'; | ||
| base.toString = Object.prototype.toString; | ||
| base.toJSON = function() { | ||
| return {...this}; | ||
| }; | ||
| base[Symbol.toStringTag] = 'Record'; | ||
@@ -37,4 +39,4 @@ base[Symbol.iterator] = function() { | ||
| const tagged = Tuple.bind({args: obj, base, length: keys.length, keys}); | ||
| const tagged = Tuple.bind({args: entries, base, length: keys.length, keys}); | ||
| return tagged(Tuple(...keys), Tuple(...values), 'record'); | ||
| } |
+2
-2
@@ -14,3 +14,3 @@ const refTree = new WeakMap; | ||
| return [...this]; | ||
| } | ||
| }; | ||
| base[Symbol.iterator] = function() { | ||
@@ -76,3 +76,3 @@ let index = 0; | ||
| { | ||
| prefix = JSON.stringify(part.map(p => `${typeof p}::${p}`)) | ||
| prefix = JSON.stringify(part.map(p => `${typeof p}::${p}`)); | ||
| } | ||
@@ -79,0 +79,0 @@ else |
-1014
| import Tuple from './Tuple.mjs'; | ||
| import Group from './Group.mjs'; | ||
| import Record from './Record.mjs'; | ||
| import Dict from './Dict.mjs'; | ||
| /** | ||
| * @callback SchemaMapper | ||
| */ | ||
| const Schema = { | ||
| /** | ||
| * Map one or more values to a Tuple. | ||
| * Will append additional properties as unmapped values if more values are provided than appear in the schema. | ||
| * @param {...SchemaMapper} schema A list of SchemaMappers | ||
| */ | ||
| tuple(...schema) | ||
| { | ||
| return (args = [], path = '') => { | ||
| return Tuple(...args.map( | ||
| (arg, index) => schema[index] ? schema[index](arg, `${path || 'root'}[${index}]`): arg | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Map one or more values to a Group. | ||
| * Will append additional properties as unmapped values if more values are provided than appear in the schema. | ||
| * @param {...SchemaMapper} schema A list of SchemaMappers | ||
| */ | ||
| group(...schema) | ||
| { | ||
| return (args = [], path = '') => { | ||
| return Group(...args.map( | ||
| (arg, index) => schema[index] ? schema[index](arg, `${path || 'root'}[${index}]`): arg | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Map an object to a Record. | ||
| * Will append additional properties as unmapped values if more values are provided than appear in the schema. | ||
| * @param {Object.<string, SchemaMapper>} schema - An Object holding SchemaMappers | ||
| */ | ||
| record(schema = {}) | ||
| { | ||
| return (arg, path = '') => { | ||
| const entries = Object.entries(schema); | ||
| return Record(Object.assign( | ||
| {}, | ||
| arg, | ||
| Object.fromEntries( | ||
| entries.map(([key, schema]) => [key, schema(arg[key], `${path || 'root'}[${key}]`)]) | ||
| ) | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Map an object to a Record. | ||
| * Will append additional properties as unmapped values if more values are provided than appear in the schema. | ||
| * @param {Object.<string, SchemaMapper>} schema - An Object holding SchemaMappers | ||
| */ | ||
| dict(schema = {}) | ||
| { | ||
| return (arg, path = '') => { | ||
| const entries = Object.entries(arg); | ||
| return Dict(Object.fromEntries( | ||
| entries.map(([key, value]) => [key, schema[key] ? schema[key](value, `${path || 'root'}[${key}]`) : value]) | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Map n values to a Tuple. | ||
| * Will append each value in the input to the Tuple using the same mapper. | ||
| * @param {SchemaMapper} schema - A SchemaMapper | ||
| */ | ||
| nTuple(schema) | ||
| { | ||
| return (args, path) => { | ||
| if(!Array.isArray(args)) | ||
| { | ||
| args = [args] | ||
| } | ||
| return Tuple(...args.map((arg, index) => schema ? schema(arg, `${path || 'root'}[${index}]`) : arg)); | ||
| } | ||
| }, | ||
| /** | ||
| * Map n values to a Group. | ||
| * Will append each value in the input to the Group using the same mapper. | ||
| * @param {SchemaMapper} schema - A list of SchemaMappers | ||
| */ | ||
| nGroup(schema) | ||
| { | ||
| return (args, path = '') => { | ||
| if(!Array.isArray(args)) | ||
| { | ||
| args = [args] | ||
| } | ||
| return Group(...args.map((arg, index) => schema ? schema(arg, `${path || 'root'}[${index}]`) : arg)); | ||
| } | ||
| }, | ||
| /** | ||
| * Map n keys to a Record. | ||
| * @param {Object.<string, SchemaMapper>} schema - An Object holding SchemaMappers | ||
| */ | ||
| nRecord(schema) | ||
| { | ||
| return (arg, path = '') => { | ||
| const entries = Object.entries(arg); | ||
| return Record(Object.fromEntries( | ||
| entries.map(([key, value]) => [key, schema[key] ? schema[key](value, `${path || 'root'}[${key}]`) : value]) | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Map n keys to a Dict. | ||
| * @param {Object.<string, SchemaMapper>} schema - An Object holding SchemaMappers | ||
| */ | ||
| nDict(schema) | ||
| { | ||
| /** | ||
| * @type SchemaMapper | ||
| */ | ||
| return (arg, path = '') => { | ||
| const entries = Object.entries(arg); | ||
| return Dict(Object.fromEntries( | ||
| entries.map(([key, value]) => [key, schema[key] ? schema[key](value, `${path || 'root'}[${key}]`) : value]) | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Strictly map values to a Tuple. | ||
| * @throws {TypeError} Will throw if the number of values does not match the number of SchemaMappers | ||
| * @param {...SchemaMapper} schema A list of SchemaMappers | ||
| */ | ||
| sTuple(...schema) | ||
| { | ||
| return (args, path = '') => { | ||
| if(schema.length !== args.length) | ||
| { | ||
| throw new TypeError(`Expected ${schema.length} elements, got ${args.length} elements at ${path || 'root'}.`); | ||
| } | ||
| return Tuple(...args.map( | ||
| (arg, index) => schema[index] ? schema[index](arg, `${path || 'root'}[${index}]`): arg | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Strictly map values to a Group. | ||
| * @throws {TypeError} Will throw if the number of values does not match the number of SchemaMappers | ||
| * @param {...SchemaMapper} schema A list of SchemaMappers | ||
| */ | ||
| sGroup(...schema) | ||
| { | ||
| return (args, path = '') => { | ||
| if(schema.length !== args.length) | ||
| { | ||
| throw new TypeError(`Expected ${schema.length} elements, got ${args.length} elements at ${path || 'root'}.`); | ||
| } | ||
| return Group(...args.map( | ||
| (arg, index) => schema[index] ? schema[index](arg, `${path || 'root'}[${index}]`): arg | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Strictly map an object to a Record. | ||
| * @throws {TypeError} Will throw if the properties provided do not match the keys of the SchemaMappers. | ||
| * @param {Object.<string, SchemaMapper>} schema - An Object holding SchemaMappers | ||
| */ | ||
| sRecord(schema = {}) | ||
| { | ||
| return (arg, path = '') => { | ||
| const entries = Object.entries(arg); | ||
| const schemaLength = Object.keys(schema).length; | ||
| if(schemaLength > entries.length) | ||
| { | ||
| throw new TypeError(`Expected ${schemaLength} elements, got ${entries.length} elements at ${path || 'root'}.`); | ||
| } | ||
| return Record(Object.fromEntries( | ||
| entries.map(([key, value]) => { | ||
| if(!(key in schema)) | ||
| { | ||
| throw new TypeError(`Extra key "${key}" not found in schema at ` + (path ? `${path}[${key}]` : key)); | ||
| } | ||
| return [key, schema[key](value, path ? `${path}[${key}]` : key)] | ||
| }) | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Strictly map an object to a Dict. | ||
| * @throws {TypeError} Will throw if the properties provided do not match the keys of the SchemaMappers. | ||
| * @param {Object.<string, SchemaMapper>} schema - An Object holding SchemaMappers | ||
| */ | ||
| sDict(schema = {}) | ||
| { | ||
| return (arg, path = '') => { | ||
| const entries = Object.entries(arg); | ||
| const schemaLength = Object.keys(schema).length; | ||
| if(schemaLength > entries.length) | ||
| { | ||
| throw new TypeError(`Expected ${schemaLength} elements, got ${entries.length} elements at ${path || 'root'}.`); | ||
| } | ||
| return Dict(Object.fromEntries( | ||
| entries.map(([key, value]) => { | ||
| if(!(key in schema)) | ||
| { | ||
| throw new TypeError(`Extra key "${key}" not found in schema at ` + (path ? `${path}[${key}]` : key)); | ||
| } | ||
| return [key, schema[key](value, path ? `${path}[${key}]` : key)] | ||
| }) | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Exclusively map values to a Tuple. | ||
| * Will drop any keys not present in the schema. | ||
| * @param {...SchemaMapper} schema A list of SchemaMappers | ||
| */ | ||
| xTuple(...schema) | ||
| { | ||
| return (args, path = '') => { | ||
| if(schema.length > args.length) | ||
| { | ||
| throw new TypeError(`Expected ${schema.length} elements, got ${args.length} elements at ${path || 'root'}.`); | ||
| } | ||
| return Tuple(...schema.map( | ||
| (schema, index) => schema(args[index], `${path || 'root'}[${index}]`) | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Exclusively map values to a Group. | ||
| * Will drop any keys not present in the schema. | ||
| * @param {...SchemaMapper} schema A list of SchemaMappers | ||
| */ | ||
| xGroup(...schema) | ||
| { | ||
| return (args, path = '') => { | ||
| if(schema.length > args.length) | ||
| { | ||
| throw new TypeError(`Expected ${schema.length} elements, got ${args.length} elements at ${path || 'root'}.`); | ||
| } | ||
| return Group(...schema.map( | ||
| (schema, index) => schema(args[index], `${path || 'root'}[${index}]`) | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Exclusively map an object to a Record. | ||
| * Will drop any keys not present in the schema. | ||
| * @param {Object.<string, SchemaMapper>} schema - An Object holding SchemaMappers | ||
| */ | ||
| xRecord(schema) | ||
| { | ||
| return (arg, path = '') => { | ||
| const schemaEntries = Object.entries(schema); | ||
| return Record(Object.fromEntries( | ||
| schemaEntries.map(([key, schema]) => [key, schema(arg[key], `${path || 'root'}[${key}]`)]) | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Exclusively map an object to a Dict. | ||
| * Will drop any keys not present in the schema. | ||
| * @param {Object.<string, SchemaMapper>} schema - An Object holding SchemaMappers | ||
| */ | ||
| xDict(schema) | ||
| { | ||
| return (arg, path = '') => { | ||
| const schemaKeys = Object.keys(schema); | ||
| return Dict(Object.fromEntries( | ||
| schemaKeys.map((key) => [key, schema[key](arg[key], `${path || 'root'}[${key}]`)]) | ||
| )); | ||
| } | ||
| }, | ||
| /** | ||
| * Validate a boolean | ||
| * @param {Object} options | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| boolean(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(typeof value !== 'boolean') | ||
| { | ||
| throw new TypeError(`Expected boolean, got ${typeof value} at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Validate a number | ||
| * @param {Object} options | ||
| * @param {number} options.max Max value | ||
| * @param {number} options.min Min value | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| number(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(typeof value !== 'number') | ||
| { | ||
| throw new TypeError(`Expected number, got ${typeof value} at ${path || 'root'}`); | ||
| } | ||
| if(options.check && !options.check(value)) | ||
| { | ||
| throw new TypeError(`Validation failed! got ${value} at ${path || 'root'}`); | ||
| } | ||
| if('max' in options && options.max < value) | ||
| { | ||
| throw new TypeError(`Expected max ${options.max}, got ${value} at ${path || 'root'}`); | ||
| } | ||
| if('min' in options && options.min > value) | ||
| { | ||
| throw new TypeError(`Expected min ${options.min}, got ${value} at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Validate an integer | ||
| * @param {Object} options | ||
| * @param {number} options.max Max value | ||
| * @param {number} options.min Min value | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| integer(options = {}) | ||
| { | ||
| const _options = {...options}; | ||
| const checks = [Number.isInteger]; | ||
| if(options.check) | ||
| { | ||
| checks.push(options.check); | ||
| } | ||
| _options.check = v => checks.map(c => c(v)).reduce((a, b) => a && b, true); | ||
| return Schema.number(_options); | ||
| }, | ||
| /** | ||
| * Validate a float | ||
| * @param {Object} options | ||
| * @param {number} options.max Max value | ||
| * @param {number} options.min Min value | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| float(options = {}) | ||
| { | ||
| const _options = {...options}; | ||
| const checks = [Number.isFinite]; | ||
| if(options.check) | ||
| { | ||
| checks.push(options.check); | ||
| } | ||
| _options.check = v => checks.map(c => c(v)).reduce((a, b) => a && b, true); | ||
| return Schema.number(_options); | ||
| }, | ||
| /** | ||
| * Validate a NaN | ||
| * @param {Object} options | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| NaN(options = {}) | ||
| { | ||
| const _options = {...options}; | ||
| const checks = [Number.isNaN]; | ||
| if(options.check) | ||
| { | ||
| checks.push(options.check); | ||
| } | ||
| _options.check = v => checks.map(c => c(v)).reduce((a, b) => a && b, true); | ||
| return Schema.number(_options); | ||
| }, | ||
| /** | ||
| * Validate an infinite value | ||
| * @param {Object} options | ||
| * @param {number} options.max Max value | ||
| * @param {number} options.min Min value | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| infinity(options = {}) | ||
| { | ||
| const checks = [n => !Number.isFinite(n) && !Number.isNaN(n)]; | ||
| if(options.check) | ||
| { | ||
| checks.push(options.check); | ||
| } | ||
| const check = v => checks.map(c => c(v)).reduce((a, b) => a && b, true); | ||
| return Schema.number({...options, check}); | ||
| }, | ||
| /** | ||
| * Validate a bigint | ||
| * @param {Object} options | ||
| * @param {number} options.max Max value | ||
| * @param {number} options.min Min value | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| bigint(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(typeof value !== 'bigint') | ||
| { | ||
| throw new TypeError(`Expected bigint, got ${typeof value} at ${path || 'root'}`); | ||
| } | ||
| if(options.check && !options.check(value)) | ||
| { | ||
| throw new TypeError(`Validation failed! got ${value} at ${path || 'root'}`); | ||
| } | ||
| if('max' in options && options.max < value) | ||
| { | ||
| throw new TypeError(`Expected max ${options.max}, got ${value} at ${path || 'root'}`); | ||
| } | ||
| if('min' in options && options.min > value) | ||
| { | ||
| throw new TypeError(`Expected min ${options.min}, got ${value} at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Validate a string | ||
| * @param {Object} options | ||
| * @param {number} options.max Max length | ||
| * @param {number} options.min Min length | ||
| * @param {Regex} options.match Throw a TypeError if this does NOT match | ||
| * @param {Regex} options.noMatch Throw a TypeError if this DOES match | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| string(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(typeof value !== 'string') | ||
| { | ||
| throw new TypeError(`Expected string, got ${typeof value} at ${path || 'root'}`); | ||
| } | ||
| if(options.check && !options.check(value)) | ||
| { | ||
| throw new TypeError(`Validation failed! got ${value} at ${path || 'root'}`); | ||
| } | ||
| if('max' in options && options.max < value.length) | ||
| { | ||
| throw new TypeError(`Expected max length ${options.max}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if('min' in options && options.min > value.length) | ||
| { | ||
| throw new TypeError(`Expected min length ${options.min}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if('prefix' in options && options.prefix !== value.substr(0, options.prefix.length)) | ||
| { | ||
| throw new TypeError(`Expected prefix ${options.prefix}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if('suffix' in options && options.suffix !== value.substr(value.length - options.suffix.length)) | ||
| { | ||
| throw new TypeError(`Expected suffix ${options.suffix}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if('infix' in options && value.indexOf(options.infix) === -1) | ||
| { | ||
| throw new TypeError(`Expected infix ${options.infix}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if(options.match && !value.match(options.match)) | ||
| { | ||
| throw new TypeError(`Expected string to match ${options.match}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if(options.noMatch && value.noMatch(options.noMatch)) | ||
| { | ||
| throw new TypeError(`Expected string NOT to match ${options.noMatch}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Validate a numeric string | ||
| * @param {Object} options | ||
| * @param {number} options.max Max value | ||
| * @param {number} options.min Min value | ||
| * @param {Regex} options.match Throw a TypeError if this does NOT match | ||
| * @param {Regex} options.noMatch Throw a TypeError if this DOES match | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| numericString(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(isNaN(value) || value === null || value != Number(value)) | ||
| { | ||
| throw new TypeError(`Expected numeric, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if('max' in options && options.max < Number(value)) | ||
| { | ||
| throw new TypeError(`Expected max ${options.max}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if('min' in options && options.min > Number(value)) | ||
| { | ||
| throw new TypeError(`Expected min ${options.min}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| const {min, max, ...newOptions} = options; | ||
| return Schema.string(newOptions)(value); | ||
| }; | ||
| }, | ||
| /** | ||
| * Validate a date string | ||
| * @param {Object} options | ||
| * @param {number} options.max Max length | ||
| * @param {number} options.min Min length | ||
| * @param {Regex} options.match Throw a TypeError if this does NOT match | ||
| * @param {Regex} options.noMatch Throw a TypeError if this DOES match | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| dateString(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(isNaN(Date.parse(value))) | ||
| { | ||
| throw new TypeError(`Expected dateString, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if('max' in options && options.max < value) | ||
| { | ||
| throw new TypeError(`Expected max ${options.max}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if('min' in options && options.min > value) | ||
| { | ||
| throw new TypeError(`Expected min ${options.min}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| const {min, max, ...newOptions} = options; | ||
| return Schema.string(newOptions)(value); | ||
| }; | ||
| }, | ||
| uuidString(options = {}) | ||
| { | ||
| const checks = [ value => String(value).match(/^[a-z,0-9]{8}-[a-z,0-9]{4}-[a-z,0-9]{4}-[a-z,0-9]{4}-[a-z,0-9]{12}$/i) ]; | ||
| if(options.check) | ||
| { | ||
| checks.push(options.check); | ||
| } | ||
| const check = v => checks.map(c => c(v)).reduce((a, b) => a && b, true); | ||
| return Schema.string({...options, check}); | ||
| }, | ||
| /** | ||
| * Validate a url string | ||
| * @param {Object} options | ||
| * @param {number} options.max Max length | ||
| * @param {number} options.min Min length | ||
| * @param {Regex} options.match Throw a TypeError if this does NOT match | ||
| * @param {Regex} options.noMatch Throw a TypeError if this DOES match | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| urlString(options = {}) | ||
| { | ||
| const checks = [ URL.canParse ? value => URL.canParse(value) : value => { try { new URL(value); return true; } catch { return false; } } ]; | ||
| if(options.check) | ||
| { | ||
| checks.push(options.check); | ||
| } | ||
| const check = v => checks.map(c => c(v)).reduce((a, b) => a && b, true); | ||
| return Schema.string({...options, check}); | ||
| }, | ||
| /** | ||
| * Validate a regex string | ||
| * @param {Object} options | ||
| * @param {number} options.max Max length | ||
| * @param {number} options.min Min length | ||
| * @param {Regex} options.match Throw a TypeError if this does NOT match | ||
| * @param {Regex} options.noMatch Throw a TypeError if this DOES match | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| emailString(options = {}) | ||
| { | ||
| const checks = [ value => { | ||
| const atPos = value.indexOf('@'); | ||
| const atPos2 = value.indexOf('@', atPos + 1); | ||
| const dotPos = value.indexOf('.', atPos); | ||
| return (atPos > 0) && (atPos2 === -1) && (dotPos - atPos > 0) && (value.length - dotPos > 2); | ||
| } ]; | ||
| if(options.check) | ||
| { | ||
| checks.push(options.check); | ||
| } | ||
| const check = v => checks.map(c => c(v)).reduce((a, b) => a && b, true); | ||
| return Schema.string({...options, check}); | ||
| }, | ||
| regexString(options = {}) | ||
| { | ||
| const checks = [ value => { try { RegExp(value); return !!value; } catch { return false; } } ]; | ||
| if(options.check) | ||
| { | ||
| checks.push(options.check); | ||
| } | ||
| const check = v => checks.map(c => c(v)).reduce((a, b) => a && b, true); | ||
| return Schema.string({...options, check}); | ||
| }, | ||
| /** | ||
| * Validate a base64 string | ||
| * @param {Object} options | ||
| * @param {number} options.max Max length | ||
| * @param {number} options.min Min length | ||
| * @param {Regex} options.match Throw a TypeError if this does NOT match | ||
| * @param {Regex} options.noMatch Throw a TypeError if this DOES match | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| base64String(options = {}) | ||
| { | ||
| const checks = [ value => value !== '' && value.trim() !== '' && btoa(atob(value)) === value]; | ||
| if(options.check) | ||
| { | ||
| checks.push(options.check); | ||
| } | ||
| const check = v => checks.map(c => c(v)).reduce((a, b) => a && b, true); | ||
| return Schema.string({...options, check}); | ||
| }, | ||
| /** | ||
| * Validate a JSON string | ||
| * @param {Object} options | ||
| * @param {number} options.max Max length | ||
| * @param {number} options.min Min length | ||
| * @param {Regex} options.match Throw a TypeError if this does NOT match | ||
| * @param {Regex} options.noMatch Throw a TypeError if this DOES match | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| jsonString(options = {}) | ||
| { | ||
| const checks = [ value => { try { JSON.parse(value); return true; } catch { return false; } } ]; | ||
| if(options.check) | ||
| { | ||
| checks.push(options.check); | ||
| } | ||
| const check = v => checks.map(c => c(v)).reduce((a, b) => a && b, true); | ||
| return Schema.string({...options, check}); | ||
| }, | ||
| /** | ||
| * Validate an array | ||
| * @param {Object} options | ||
| * @param {number} options.max Max length | ||
| * @param {number} options.min Min length | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| * @param {function(any):each} options.each Transform each value in the array, after its been validated.. | ||
| */ | ||
| array(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(!Array.isArray(value)) | ||
| { | ||
| throw new TypeError(`Expected Array, got ${typeof value} at ${path || 'root'}`); | ||
| } | ||
| if(options.check && !options.check(value)) | ||
| { | ||
| throw new TypeError(`Validation failed! got ${value} at ${path || 'root'}`); | ||
| } | ||
| if('max' in options && options.max < value.length) | ||
| { | ||
| throw new TypeError(`Expected max length ${options.max}, got "${value.length}" at ${path || 'root'}`); | ||
| } | ||
| if('min' in options && options.min > value.length) | ||
| { | ||
| throw new TypeError(`Expected min length ${options.min}, got "${value.length}" at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| if(options.each) | ||
| { | ||
| value = value.map(options.each); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Validate an object | ||
| * @param {Object} options | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):class} options.class Throw a TypeError if the class does not match. | ||
| * @param {function(any):any} options.map Transform the object after its been validated. | ||
| * @param {function(any):each} options.each Transform each entry in the object, after its been validated. | ||
| */ | ||
| object(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(typeof value !== 'object') | ||
| { | ||
| throw new TypeError(`Expected object, got ${typeof value} at ${path || 'root'}`); | ||
| } | ||
| if(options.check && !options.check(value)) | ||
| { | ||
| throw new TypeError(`Validation failed! got ${value} at ${path || 'root'}`); | ||
| } | ||
| if(options.class && !(value instanceof options.class)) | ||
| { | ||
| throw new TypeError(`Expected object of type ${options.class.name}, got Object of type ${value.constructor ? value.constructor.name : 'null'} at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| if(options.each) | ||
| { | ||
| value = Object.fromEntries(Object.entries(value).map(options.each)); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| date(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(!(value instanceof Date)) | ||
| { | ||
| throw new TypeError(`Expected Date, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if('max' in options && options.max < value) | ||
| { | ||
| throw new TypeError(`Expected max ${options.max}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| if('min' in options && options.min > value) | ||
| { | ||
| throw new TypeError(`Expected min ${options.min}, got "${value}" at ${path || 'root'}`); | ||
| } | ||
| return Schema.object({...options, class: Date})(value); | ||
| }; | ||
| }, | ||
| /** | ||
| * Validate a function | ||
| * @param {Object} options | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| function(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(typeof value !== 'function') | ||
| { | ||
| throw new TypeError(`Expected function, got ${typeof value} at ${path || 'root'}`); | ||
| } | ||
| if(options.check && !options.check(value)) | ||
| { | ||
| throw new TypeError(`Validation failed! got ${value} at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Validate a symbol | ||
| * @param {Object} options | ||
| * @param {function(any):boolean} options.check Throw a TypeError if this returns false. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| symbol(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(typeof value !== 'symbol') | ||
| { | ||
| throw new TypeError(`Expected symbol, got ${typeof value} at ${path || 'root'}`); | ||
| } | ||
| if(options.check && !options.check(value)) | ||
| { | ||
| throw new TypeError(`Validation failed! got ${value} at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Validate a null | ||
| * @param {Object} options | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| null(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(value !== null) | ||
| { | ||
| throw new TypeError(`Expected null, got ${typeof value} at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Validate an undefined | ||
| * @param {Object} options | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| */ | ||
| undefined(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(value !== undefined) | ||
| { | ||
| throw new TypeError(`Expected undefined, got ${typeof value} at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Return the value | ||
| * @param {Object} options | ||
| * @param {function(any):any} options.map Transform the value. | ||
| */ | ||
| value(options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Match the value to a set of literals with strict-equals comparison. | ||
| * @param {...any} literals Value must be strictly equal to one of these. | ||
| * @param {function(any):any} options.map Transform the value after its been validated. | ||
| * @returns | ||
| */ | ||
| oneOf(literals = [], options = {}) | ||
| { | ||
| return (value, path = '') => { | ||
| if(!literals.includes(value)) | ||
| { | ||
| throw new TypeError(`Expected oneOf ${values.join(', ')}, got ${value} at ${path || 'root'}`); | ||
| } | ||
| if(options.map) | ||
| { | ||
| value = options.map(value); | ||
| } | ||
| return value; | ||
| }; | ||
| }, | ||
| /** | ||
| * Drop the value (always maps to `undefined`) | ||
| */ | ||
| drop() | ||
| { | ||
| return () => undefined; | ||
| }, | ||
| /** | ||
| * Map the value with the first matching SchemaMapper | ||
| * @param {...function(options):value} mappers | ||
| */ | ||
| or(...mappers) | ||
| { | ||
| return (value, path) => { | ||
| const errors = []; | ||
| for(const mapper of mappers) | ||
| { | ||
| try | ||
| { | ||
| return mapper(value, path); | ||
| } | ||
| catch(error) | ||
| { | ||
| errors.push(error); | ||
| } | ||
| } | ||
| const multi = new Error(errors.map(e => e.message).join(', ')); | ||
| multi.errors = errors; | ||
| throw multi; | ||
| }; | ||
| }, | ||
| /** | ||
| * Repeat a SchemaMapper n times | ||
| * @param {number} n The number of times to repeat. | ||
| * @param {SchemaMapper} schema The SchemaMapper to repeat. | ||
| */ | ||
| repeat(n, schema) | ||
| { | ||
| return Array(n).fill(schema); | ||
| }, | ||
| /** | ||
| * Safely parse a Schema into an immutable structure. | ||
| * Returns NaN on error. This is helpful because `NaN !== NaN`, and its falsey. | ||
| * @param {SchemaMapper} schema The Schema to parse by. | ||
| * @param {values} any The values to parse. | ||
| * @returns {object|NaN} | ||
| */ | ||
| parse(schema, values) | ||
| { | ||
| try | ||
| { | ||
| return schema(values); | ||
| } | ||
| catch(error) | ||
| { | ||
| console.error(error); | ||
| return NaN; | ||
| } | ||
| } | ||
| }; | ||
| Object.freeze(Schema); | ||
| export default Schema; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1
-50%26835
-58.61%9
-10%258
-78.08%222
-66.16%