Skip to content

Commit

Permalink
Documenting schema.
Browse files Browse the repository at this point in the history
  • Loading branch information
seanmorris committed Sep 5, 2024
1 parent 3b9272c commit 08b92aa
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 6 deletions.
239 changes: 238 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# libtuple

[![Test](https://github.com/seanmorris/libtuple/actions/workflows/test.yaml/badge.svg)](https://github.com/seanmorris/libtuple/actions/workflows/test.yaml) *Memory-efficient tuple implementation in 6.4kB*
[![Test](https://github.com/seanmorris/libtuple/actions/workflows/test.yaml/badge.svg)](https://github.com/seanmorris/libtuple/actions/workflows/test.yaml) *Memory-efficient immutables in 10.4kB*

### Install & Use

Expand Down Expand Up @@ -81,6 +81,243 @@ Dict({a, b, c}) === Dict({a, b, c}); // true
Dict({a, b, c}) === Dict({c, b, a}); // false
```

### 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 either return it, or throw an error:

```javascript
import { Schema } 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
const userSchema = s.record({
id: s.number(),
email: s.string(),
});

const users = [
{id: 1, email: "[email protected]"},
{id: 2, email: "[email protected]"},
{id: 3, email: "[email protected]"},
]

const userRecord = userSchema(users[0]);

const userListSchema = s.nTuple(userSchema);

const userListTuple = userListSchema(users);
```
##### 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.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.

##### 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.or()

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(, schemaMapper)

Repeat a SchemaMapper n times

##### 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
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',
});
```

##### Schema.dict(properties)

Map one or more values to a Dict.

##### Schema.nTuple()

Map n values to a Tuple. Will append each value in the input to the Tuple using the same mapper.

##### Schema.nGroup()

Map n values to a Group. Will append each value in the input to the Group using the same mapper.

##### Schema.nRecord()

Map n properties to a Record. Will append additional properties without mapping or validation, if present.

```javascript
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',
openHours: "9AM-7PM",
});
```

##### Schema.nDict()

Map n properties to a Dict. Will append additional properties without mapping or validation, if present.

##### Schema.sTuple()

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()

Strictly map values to a Group. Will throw an error if the number of values does not match.

##### Schema.sRecord()

Strictly map values to a Record. Will throw an error if the number of values does not match.

##### Schema.sDict()

Strictly map values to a Dict. Will throw an error if the number of values does not match.

##### Schema.xTuple()

Exclusively map values to a Tuple. Will drop any keys not present in the schema.

```javascript
import { Schema as s } from 'libtuple';

const pointSchema = s.sTuple(s.number(), s.number());

const pointA = pointSchema([5, 10]); // [5, 10]
const pointB = pointSchema([5, 10, 1]); // Also [5, 10]

console.log(pointB[2]); //undefined
```

##### Schema.xGroup()

Exclusively map values to a Group. Will drop any keys not present in the schema.

##### Schema.xRecord()

Exclusively map values to a Record. Will drop any keys not present in the schema.

##### Schema.xDict()

Exclusively map values to a Dict. Will drop any keys not present in the schema.

## Gotchas

In JavaScript, object comparisons are based on reference, not on the actual content of the objects. This means that even if two objects have the same properties and values, they are considered different if they do not reference the same memory location.
Expand Down
5 changes: 2 additions & 3 deletions Schema.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ const Schema = {

/**
* Map n keys to a Record.
* Will append each value in the input to the Record using the same mapper.
* @param {Object.<string, SchemaMapper>} schema - An Object holding SchemaMappers
*/
nRecord(schema)
Expand All @@ -133,7 +132,6 @@ const Schema = {

/**
* Map n keys to a Dict.
* Will append each value in the input to the Dict using the same mapper.
* @param {Object.<string, SchemaMapper>} schema - An Object holding SchemaMappers
*/
nDict(schema)
Expand Down Expand Up @@ -305,7 +303,8 @@ const Schema = {

/**
* Validate a boolean
* @param {*} options
* @param {Object} options
* @param {function(any):any} options.map Transform the value after its been validated.
*/
boolean(options = {})
{
Expand Down
1 change: 1 addition & 0 deletions index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as Tuple } from "./Tuple.mjs";
export { default as Group } from "./Group.mjs";
export { default as Record } from "./Record.mjs";
export { default as Dict } from "./Dict.mjs";
export { default as Schema } from "./Schema.mjs";
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"Group.mjs",
"Record.mjs",
"Dict.mjs",
"Schema.mjs",
"index.mjs",
"README.md",
"LICENSE",
Expand Down
4 changes: 2 additions & 2 deletions test/schema.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ test('s.or param test', t => {
});

test('usersSchema test', t => {
const usersSchema = s.sTuple(
...s.repeat(10, s.sRecord({
const usersSchema = s.nTuple(
(s.sRecord({
id: s.number({}),
name: s.string({
map: s => Tuple(...s.split(' '))
Expand Down

0 comments on commit 08b92aa

Please sign in to comment.