Giter Site home page Giter Site logo

diegohaz / schm Goto Github PK

View Code? Open in Web Editor NEW
512.0 5.0 23.0 1.36 MB

Composable schemas for JavaScript and Node.js

Home Page: https://git.io/schm

License: MIT License

JavaScript 100.00%
schema functional-programming parse validate compose joi json-schema lerna

schm's Introduction

Build Status Coverage Status



If you find this useful, please don't forget to star ⭐️ the repo, as this will help to promote the project.
Follow me on Twitter and GitHub to keep updated about this project and others.




schm is a library for creating immutable, composable, parseable and validatable (yeah, many *ables) schemas in JavaScript and Node.js. That's highly inspired by functional programming paradigm.

Play with schm on RunKit (click on Clone and edit this document at the bottom and skip login if you want).

const schema = require('schm')

const userSchema = schema({
  name: String,
  age: {
    type: Number,
    min: 18,
  },
})

userSchema.parse({
  name: 'Haz',
  age: '27',
})

await userSchema.validate({
  name: 'Jeane',
  age: 10,
})

Output:

// parsed
{
  name: 'Haz',
  age: 27,
}

// validate error
[
  {
    param: 'age',
    value: 10,
    validator: 'min',
    min: 18,
    message: 'age must be greater than or equal 18',
  },
]

The way you declare the schema object is very similar to mongoose Schemas. So, refer to their docs to learn more.

Packages

schm repository is a monorepo managed by lerna. Click on package name to see specific documentation.

Package Version Description
schm NPM version The main package
schm-computed NPM version Adds computed parameters to schemas
schm-express NPM version Express middlewares to handle querystring and response body
schm-koa NPM version Koa middlewares to handle querystring and response body
schm-methods NPM version Adds methods to parsed schema objects
schm-mongo NPM version Parses values to MongoDB queries
schm-translate NPM version Translates values keys to schema keys

Contributing

When submitting an issue, put the related package between brackets in the title:

[methods] Something wrong is not right # related to schm-methods
[translate] Something right is not wrong # related to schm-translate
Something wrong is wrong # general issue

PRs are welcome. You should have a look at lerna to understand how this repository works.

After cloning the repository, run yarn. That will install all the project dependencies, including the packages ones.

Before submitting a PR:

  1. Make sure to lint the code: yarn lint or lerna run lint;
  2. Make sure tests are passing: yarn test or lerna run test;

License

MIT © Diego Haz

schm's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

schm's Issues

schm-express

const { query, body } = require('schm-express')

router.get(
  '/users',
  query({ foo: String }),
  ({ req }) => req.query.foo,
)

router.post(
  '/users',
  body({ foo: String }),
  ({ req }) => req.body.foo,
)

deepmerge in defaultSchmea merge function

In defaultSchema merge function you have:

const merged = merge({}, this, ...schemas)

is there a reason for using lodash' deepMerge instead of a simple reduce:

const merged = schemas.reduce((merged, schema) => ({...merged, ...schema}), this)

Using lodash merge makes it incredibly slow with a lot of refs to other schemas and even worse if you want to use self referencing

Problems with installing package - missing file documentation.yml

Hello, I read article on medium and I wanted to give a try however I have a problem with installing package.

After running npm i --save schm I encountered error:


> [email protected] postinstall /directory/repo/node_modules/schm
> npm run build


> [email protected] prebuild /directory/repo/node_modules/schm
> npm run docs && npm run clean


> [email protected] docs /directory/repo/node_modules/schm
> documentation readme src --section=API --pe flow -c documentation.yml

{ Error: ENOENT: no such file or directory, open '/directory/repo/node_modules/schm/documentation.yml'
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: '/directory/repo/node_modules/schm/documentation.yml' }
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] docs: `documentation readme src --section=API --pe flow -c documentation.yml`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] docs script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm WARN Local package.json exists, but node_modules missing, did you mean to install?

npm ERR! A complete log of this run can be found in:
npm ERR!     /directory/.npm/_logs/2018-03-22T08_43_11_423Z-debug.log
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] prebuild: `npm run docs && npm run clean`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] prebuild script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm WARN Local package.json exists, but node_modules missing, did you mean to install?

npm ERR! A complete log of this run can be found in:
npm ERR!     /directory/.npm/_logs/2018-03-22T08_43_11_451Z-debug.log
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] postinstall: `npm run build`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] postinstall script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /directory/.npm/_logs/2018-03-22T08_43_11_666Z-debug.log

If you can help me, I would be grateful, thanks!

A helpful example on parsers and validators

Like me and you struggled to read the docs and the code to understand how to use parsers and validators, I've thrown together an example to help people get their head around how to write schemas with them.

const schema = require('schm')
const moment = require('moment')

const dateDefault = {
  date: moment().toISOString(),
  month: moment().format('MMMM'),
  year: moment().format('YYYY')
}

const extraStuff = prevSchema => prevSchema.merge({
  parsers: {
    transformDate: (formedDefaultOrPassedRequest, propValue, schemaProp, schema, request, someOtherStuff) => {
      console.log('----- parsers transformDate -----')
      // This is the default formed or what is in the request
      // The formed default is only passed if the request does not contain the schemaProp
      console.log('formedDefaultOrPassedRequest', formedDefaultOrPassedRequest)
      // transformDate value, in this case it is true
      console.log('propValue', propValue)
      // In this case it is occ, as the transformDate property is inside the occ object
      console.log('schemaProp', schemaProp)
      // the schema for this property (occ), this may contain the default if set
      console.log('schema', schema)
      // What has been requested to be parsed
      console.log('request', request)

      // Here we're going to just return some junk because we can
      // We could do some manipulation
      return {
        date: "aaaa",
        month: 'eee',
        year: 'ddd'
      }
    }
  },
  validators: {
    checkDate: (valueToCheck, options, schemaProp, schema, request, someOtherStuff) => {
      console.log('----- validators checkDate -----')
      // This is the value to check
      console.log('valueToCheck', valueToCheck)
      // What is the value of checkDate defined on the schema
      console.log('options', options)
      // In this case it is occ, as the checkDate property is inside the occ object
      console.log('schemaProp', schemaProp)
      // the schema for this property (occ), this may contain the default if set
      console.log('schema', schema)
      // What has been requested to be parsed/validated
      console.log('request', request)
      // Be careful, the validator will still run if the options said checkDate is false
      // Lets check if the date is a value ISO8601 date
      return {
        valid: moment(valueToCheck.date, 'YYYY-MM-DDTHH:mm:ssZ').isValid(),
        message: 'Not a valid date',
      }
    }
  }
})

const someSchema = schema({
  name: { type: String, default: null },
  occ: { 
    type: {
      date: { type: String, default: moment().toISOString() },
      month: { type: String, default: moment().format('MMMM') },
      year: { type: String, default: moment().format('YYYY')  },
    },
    default: dateDefault,
    transformDate: true,
    checkDate: true
  }
}, extraStuff)

var req = {
  name: "MrAwesome",
  occ: {
    date: "2016-12-01T13:30:30.705Z",
    month: "December",
    year: "2016"
  }
}

const valid = schema.validate(req, someSchema)
.then((res) => {
  console.log('----- result -----')
  console.log(res)
})
.catch((err) => { 
  console.log('----- error -----')
  console.log(err)
})

results in

----- parsers transformDate -----
formedDefaultOrPassedRequest { date: '2016-12-01T13:30:30.705Z',
  month: 'December',
  year: '2016' }
propValue true
schemaProp occ
schema { type:
   { date:
      { type: [Function: String],
        default: '2018-06-28T10:23:59.877Z' },
     month: { type: [Function: String], default: 'June' },
     year: { type: [Function: String], default: '2018' } },
  default: { date: '2018-06-28T10:23:59.873Z', month: 'June', year: '2018' },
  transformDate: true,
  checkDate: true }
request { name: 'MrAwesome',
  occ:
   { date: '2016-12-01T13:30:30.705Z',
     month: 'December',
     year: '2016' } }
----- validators checkDate -----
valueToCheck { date: 'aaaa', month: 'eee', year: 'ddd' }
options { optionValue: true }
schemaProp occ
schema { type:
   { date:
      { type: [Function: String],
        default: '2018-06-28T10:23:59.877Z' },
     month: { type: [Function: String], default: 'June' },
     year: { type: [Function: String], default: '2018' } },
  default: { date: '2018-06-28T10:23:59.873Z', month: 'June', year: '2018' },
  transformDate: true,
  checkDate: true }
request { name: 'MrAwesome',
  occ: { date: 'aaaa', month: 'eee', year: 'ddd' } }
----- error -----
[ { param: 'occ',
    validator: 'checkDate',
    checkDate: true,
    message: 'Not a valid date' } ]

Better errors?

Hi,

First of all, thanks for the library. I'm testing it against a new project and looks promising.

But I found something not very intuitive. Having this code:

const s = schema({
  age: { type: Number, required: true }
});
s.validate({ age: 'not-a-number' }) // => [ { param: 'age', message: 'age is required' }]

IMHO the message should be "age is not a number" because the age is provided (but not in the correct form)

What do you think?

Separate validation into own package

Hi, I am rising this issue in order to considerate removing the validation feature from the core, for the following reasons.

Not every project requires validation and most importantly not every project can or should support Promises. Keeping the current validation implementation as an optional package would additionally decrease the core size and help on the separation of concerns.

Thank you for considering, and for maintaining this project.

[methods] Install problem

Thank you for pulling this project together. We're very excited about testing it

I am encountering an install issue on schm-method. I noted you fixed and closed the same issue on schm -- related to scripts/documentation

Or since this is a mono-repo -- am I suppose to handle npm install differently? Any help would be appreciated

Thx

Validate type field doesn't work as expected

Hi! Nice work out there, i'm unfortunally front of a issue. Code now:

import schema from 'schm'

const foo = schema({
  shouldCry: {
    required: true,
    type: Boolean
  }
})
const output = await foo.validate({ shouldCry: "i'm not a boolean" })

It's validate this successfully and return:

{
  shouldCry: true
}

But the type is specified in the schema... And this behavior also happens on other types, in short the attribute 'type' does not seem to work 🤔.

Regards, Sacha.

JSON validation

I tried to comment on your article on Medium, but somehow that seems to be impossible. Anyway:

The note on AJV is incorrect. AJV is able to verify documents on the full JSON Schema spec (http://json-schema.org/) Including string patterns, enums, min, max etc.

An example can be found at: https://npm.runkit.com/ajv

It also offers conditional validation as decribed at: https://medium.com/@quentin.chevrin/ajv-a-solution-to-form-validation-f1d12e767f0

Added benefit is that there's loads of tools to help in creaction and validation of schemas.
http://json-schema.org/implementations.html

And if you are using schema validation in an API you might want to look at https://swagger.io/ which also uses JSON schema :-)

Of course you are free to reinvent the wheel :-) Just wanted to clarify that AJV does more than checking defaults and types ;-)

Recursive parse

No works with complex json example:

const schema = require("schm");
const { validate } = schema;
const userSchema = schema({
    name: String,
    age: Number,
    child: {
        name: String,
        age: Number,
    },
});

validate({ name: "John", age: 17, childs: {name: "Test", age: 1} }, userSchema)
    .then(parsedValues => {
        console.log("Yaay!", parsedValues);
    })
    .catch(errors => {
        console.log("Oops!", errors);
    });

[translate][suggestion] Use chaining instead of passing as param

I saw the code snippet

const userSchema = schema({
  name: String,
  email: String,
}, translate({
  email: "emails.0",
}));

One thing that bugged me was the passing of translate to schema function as a positional parameter. From my personal experience I have found that this method doesn't do well in long term. I suggest using chaining method to extend the functionality like so -

const userSchema = schema({
  name: String,
  email: String,
}).translate({
  email: "emails.0",
})

Libraries like moment, jQuery all use this pattern, I can dig up some solid advantages if you like

Composite Schema. Error: "Cannot read property of undefined"

Validation nesting schemas throws an error if

Composite Schema

const user = schema({
  email: String
});

const querySchema = schema({
  user,
  startDate: Date,
  endDate: Date
});

Validation

await querySchema.validate({
  startDate: (new Date())
});

the object I'm passing to validate method doesn't have key user. And since user not mandatory I expect validation to pass, but instead it throws Error: Cannot read property 'email' of undefined

Is it expecting behaviour?

Implementing a 'filter' option to choose which 'fields' you wish to validate or to be returned from parsing?

Hi, I've been playing around with this solution in combination with Mongoose and have been loving it so far.

One thing I'm missing though is the ability to choose which fields you either:

  • ... want back after parsing (something similar to how mongoose does it with their fields filter)
  • ... wish to validate (for edge cases when validating certain fields isn't necessary)

Is a way to implement this functionality already by any chance?

Thanks in advance and congrats on creating this awesome package!

Array default ignored: array default always = []

According to the Mongoose arrays documentation, an array default value is possible.
But in schm, when array of schema is validatate, the default value is always an empty array.

Inspired example from Mongoose

const ToySchema = schm({ name: String });
const ToyBoxSchema = schm({
  toys: {
    type: [ToySchema],
    default: undefined
  },
  buffers: [Buffer],
  strings: [String],
  numbers: [Number]
});

const result = await ToyBoxSchema.validate({
  // Validate empty object
});

console.log(result); 
// result = { toys: [], buffers: [], strings: [], numbers: [] }
// Expected: result =  { toys: undefined, buffers: [], strings: [], numbers: [] }

Any idea to get a custom default value rather than an empty array?
Thx

Validating startDate vs endDate

I needed to validate whether the startDate is before the endDate and whether both dates have the right format:

var dateSchema = new schema({
dateRange: {
    type: {
      startDate: {
        type: Date,
        required: true,
        validate: [dateFormatValidator, '{VALUE} is not a valid date!']
      },
      endDate: {
        type: Date,
        required: true,
        validate: [dateFormatValidator, '{VALUE} is not a valid date!']
      }
    },
    required: true,
    validate: [dateRangeValidator, 'startDate must be before endDate!']
  }
});

The validation of dateRange is executed, but the validation of the startDate and endDate format isn't. Is this a bug?

Implement validation

const schema = require('schm')

const userSchema = schema({
  name: {
    type: String,
    required: true,
  },
})

userSchema.validate({ name: '' }).then().catch()

schm-mongo

const schema = require('schm');
const { query, cursor, projection } = require('schm-mongo');

const term = paths => query({ term: { type: RegExp, paths } })

const date = (param, operator, paths = ['date']) => query({ [param]: { type: Date, operator, paths } })

const eventSchema = schema(
  {
    title: String,
    description: String,
    date: Date,
  }, 
  term(['title', 'description']),
  date('before', '$lte'),
  date('after', '$gte'),
  projection(),
  translate({
    max_items: 'limit',
  }),
  cursor({
    limit: {
      type: Number,
      max: 200,
    }
  }),
)

const event = eventSchema.parse({...})
const { fields, skip, limit, sort, ...query }  = event

await db.collection.find(query, fields).skip(skip).limit(limit).sort(sort).toArray()

[translate] Make it possible to pass function as argument

translate(values => ({
  name: ...
}))

So it would be possible to do something like:

const camelizeKeys = values => Object.keys(values).reduce((finalObject, key) => ({
  ...finalObject,
  [camelCase(key)]: key,
}), {})

translate(camelizeKeys)
``

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.