Coder Social home page Coder Social logo

talyssonoc / structure Goto Github PK

View Code? Open in Web Editor NEW
301.0 13.0 20.0 2.21 MB

A simple schema/attributes library built on top of modern JavaScript

Home Page: https://structure.js.org/

License: MIT License

JavaScript 100.00%
javascript schema coercion domain-entity validation model

structure's Introduction

A simple schema/attributes library built on top of modern JavaScript

Structure provides a simple interface which allows you to add attributes to your classes based on a schema, with validations and type coercion.

Packages

Example Structure usage

For each attribute on your schema, a getter and a setter will be created into the given class. It'll also auto-assign those attributes passed to the constructor.

const { attributes } = require('structure');

const User = attributes({
  name: String,
  age: {
    type: Number,
    default: 18,
  },
  birthday: Date,
})(
  class User {
    greet() {
      return `Hello ${this.name}`;
    }
  }
);

const user = new User({
  name: 'John Foo',
});

user.name; // 'John Foo'
user.greet(); // 'Hello John Foo'

structure's People

Contributors

c-tcassiano-godaddy avatar jeremyruppel avatar kostadriano avatar pedsmoreira avatar t3h2mas avatar talyssonoc avatar victorkunzler avatar wenderjean avatar

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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

structure's Issues

Nested dynamics types isn't work

When execute the code below throws the following error message.
Error: "userPersonalInformation.vehicle" contains link reference "ref:local:Vehicle" which is outside of schema boundaries

The same not happen when removing the dynamic type;

const { attributes } = require('structure');

const Vehicle = attributes({
  year: {
    type: Number,
    required: true,
  },
})(class Vehicle {});

const UserPersonalInformation = attributes(
  {
    name: String,
    vehicle: 'Vehicle',
  },
  {
    dynamics: {
      Vehicle: () => Vehicle,
    },
  },
)(class UserPersonalInformation {});


const AutoRiskProfile = attributes(
  {
    userPersonalInformation: {
      type: 'UserPersonalInformation',
      required: true,
    },
  },
  {
    dynamics: {
      UserPersonalInformation: () => UserPersonalInformation,
    },
  },
)(class AutoRiskProfile {});

const autoRiskProfile = AutoRiskProfile.buildStrict({
  userPersonalInformation: new UserPersonalInformation({
    name: 'a',
    vehicle: new Vehicle({
      year: 2018,
    }),
  }),
});

validation for specific attribute

Is there a way to validate the value of an attribute on the attribute level?

for example, suppose Person has a "name" attribute which should be at least 3 characters.
can I test whether a specific value is a valid value for "name"?

If not, how can we validate a partial update operation? sometimes the client-side would send a request to update just a specific field of an entity (update the name of a specific person record). without this, we would have to fetch the existing person record, modify it with the updated attribute, and then run validate.. redundant extra fetch from DB

thank you

validation for empty strings

Was the api changed between 1.2.1 to 1.2.0 to set the default of empty to false? And should it default to true? If required defaults to false, is the assumption is that that prop isn't included in the object on creation? Then if it is included in the object on creation but is left blank validation will fail. It isn't a huge deal, I'm curious about your thinking on this issue. Thanks for the help.

https://structure.js.org/docs/validation.html#string-validations

Custom types with custom coercions and validations

There should be a way to introduce new custom types along with custom coercion and validations for the given type. It could be kind of like:

const { createType, attributes } = require('structure');
const { isPlainObject, isString } = require('lodash');
const GJV = require('geojson-validation');

const GeoJSON = createType({
  test(value) {
    return isPlainObject(value);
  },

  coerce(value) {
    if(isString(value)) {
      return JSON.parse(value);
    }

    throw new TypeError("Value can't be coerced to GEOJson");
  },

  validations: {
    // `required` would be included by default
    feature(validationArgument, value) {
      var valid;
      var details;

      GJV.isFeature(value, (isFeature, errors) => {
        if(isFeature) {
          valid = true;
          return;
        }

        details = errors;
      });

      if(valid) {
        return { valid };
      }

      return {
        errors: { details }
      };
    }
  }
});

const House = attributes({
  location: {
    type: GeoJSON,
    required: true,
    feature: true
  }
})(class House { });

The validation errors could somehow use joi extension feature.

Apply other validations over arrays

Feature suggestion: it would be useful to be able to apply the other validations that structure provides over the content of an array.

For example, instead of just itemType: String, being able to use the equal and nullable validations:

const User = attributes({
    roles: {
        type: Array
        item: {
            type: String,
            nullable: false,
            equals: ['reader', 'editor', 'moderator' ... ]
        }
    }
    ...
});

Support Map types with itemType

It would be really useful to support Map types with itemType. Currently specifying an attribute as type Map results in "attribute.map is not a function" on attribute serialization. Is this something that could be added? If it's just a case of implementing a .map function for Map then that would be relatively trivial?

My use case is storing itemType's ID value as a key on Map.

Thanks!

Custom validation message

When I validate raw data against my defined structure, a generalized failure message is returned. {path,message}

Can I set a custom message for each attribute rule? That would be very helpful in building API responses.

Option to ignore type coercion

User should be able to declare a structure that doesn't coerce attributes. Proposal:

const User = attributes({
  age: Number
}, {
  coerceAttributes: false
})(class User { });

const user = new User({ age: 'Not a number' });

user.isValid(); // false

user.errors; // [ { message: '"age" must be a number', path: 'age' }]

problem with new js debugger vscode

Hi
After upgrade to version 1.48(vscode),
When debugging the value returned from structureJS; just displays get or set Instead of displaying the correct value

Creating timestamp plugin

My proposal is to create a new plugin to add timestamp behavior to a structure, what I mean?

I am current in a project that adds a document to a NoSQL database, I want to have a created_at and updated_at to that document, what if I could create a new structure and invoke something like mystructure.updateTimestamps() to set created and updated times to me.

Permit a type to reference itself in its schema

Currently it's not possible to do something like this:

const User = attributes({
  name: String,
  friends: {
    type: Array,
    itemType: User
  }
})(class User { });

It would be nice to have a clean way to do that.

Support TypeScript

It would be neat to support TypeScript, including using the attributes function as a decorator (it's already compliant with the decorator spec).

We could have an interface for the schema, the shorthand descriptor, the complete descriptor, for some validations (including required, default...) and somehow for the decorated structure.

Allow custom toJSON method on class for serialization.

This feature would allow you to specify a static toJSON method on the instance that would be called with the serialized JSON. It would allow you to modify the JSON before being output. Adding removing and modifying attributes to the final output.

e.g.

var User = attributes({
  name: String,
  age: Number
})(class User {
  static toJSON(json) {
    json.someProp = true;
    return json;
  }
});

const user = new User({
  name: 'John',
  age: 42
});

expect(user.toJSON()).to.eql({
  name: 'John',
  age: 42,
  someProp: true
});

It should also work on nested structures:

var Book = attributes({
  title: String,
})(class Book {
  static toJSON(json) {
    json.added = '123456789';
    return json;
  }
});

var User = attributes({
  name: String,
  age: Number,
  favouriteBooks: {
    type: Array,
    itemType: Book
  }
})(class User {
  static toJSON(json) {
    json.name = 'Hello ' + json.name;
    return json;
  }
});

const user = new User({
  name: 'John',
  age: 42,
  favouriteBooks: [new Book({ title: 'Jack' })]
});

expect(user.toJSON()).to.eql({
  name: 'Hello John',
  age: 42,
  favouriteBooks: [{
    title: 'Jack',
    added: '123456789',
  }]
});

Array coercion fail with only 1 item

I noticed a strange behavior when trying to parse a numeric array with only 1 item.

Entry:

{
  id: 1,
  seats: [ 1 ]
}

Result:

{
  id: 1,
  seats: [undefined]
}

Problem happens at that point:
array.js:

if(value[Symbol.iterator]) {
    value = Array(...value);
}

spec:

it.only('coerces items', () => {
  const user = new User({
    books: [1]
  });

  expect(user.books).to.eql([
    1
  ]);
});

Result: AssertionError: expected [ undefined ] to deeply equal [ 1 ]

Dynamics Defaults Values aren't working

Hy guys,

I try to set a dynamic default value, but I received de an exception with telling me the instance method is not defined. And when I logged the instance and only showed the attributes and not methods. I try do same example present in docs but isn`t not working.

The docs example with a console.log

const User = attributes({
  name: {
    type: String,
    default: 'Anonymous' // static default value
  },
  greeting: {
    type: String,
    default: (instance) => {
      console.log("instance ------->", instance);
      instance.greeting();
    }, // dynamic default value
  },
})(class User {
  greeting() {
    return `Hello ${this.name}`;
  }
});

The result:

instance -------> User { [Symbol(attributes)]: { name: 'Anonymous' } }
TypeError: instance.greeting is not a function
    at Object.default (/Users/rodarte/Frente/EXCHANGE/src/domain/remmitanceExchangeRate/RemmitanceExchangeRate.js:81:16)
    at derivedInitialization (/Users/rodarte/Frente/EXCHANGE/node_modules/structure/src/initialization/initializationOrder.js:12:32)
    at initializedValue (/Users/rodarte/Frente/EXCHANGE/node_modules/structure/src/initialization/initialization.js:9:10)
    at Object.initialize (/Users/rodarte/Frente/EXCHANGE/node_modules/structure/src/initialization/initialization.js:24:30)
    at Object.initialize (/Users/rodarte/Frente/EXCHANGE/node_modules/structure/src/initialization/initialize.js:4:22)
    at Object.construct (/Users/rodarte/Frente/EXCHANGE/node_modules/structure/src/attributes/decorator.js:25:24)

I testing in node 10.3.0 and in node 8.9.4 and produces the same result, and running the last version of structure 1.3.2

custom validation

I suggest to add an option to add a custom business validation. for example, if field A is "abc" and bigger than B, then validation is failed

it would be very usefull when used in the domain layer, such as in @talyssonoc 's boilerplate
https://github.com/talyssonoc/node-api-boilerplate/blob/master/src/domain/user/User.js

note: here it was solved by adding additional "isLegal" function to the class.. but it can be part of the validation. otherwise every time you create an instance you would call both "validate()" and "isLegal()" which is wierd and error prone if you forget one of the two

Method to clone an existing structure.

It'd be super useful to create a deep clone of a structure. I had something like this in mind:

const book = new Book({ some: "stuff" })

const ClonedBook = new Book(book);

// or

const ClonedBook2 = book.clone()

Deep cloning should clone nested structures and objects.

Thoughts?

Question: How to validate object keys?

I have a case where I need to validate the structure of an object, which in turn can also contain keys that are objects. The structure is like this:

const test = {
    firstname : "TestName",        // Required
    lastname  : {                  // Required
        paternal : "TestPaternal", // Required
        maternal : "TestMaternal"  // Required
    }
};

To validate the structure of the object I've written something like below but the paternal and maternal keys are not validated. You can pretty much put whatever you want inside this object.

const Schema = attributes( {
    firstname : {                                  // Validated OK
        type     : String,
        required : true
    },
    lastname  : {                                  // Validated OK in the sense that the value for this key should be an object and not missing as key, but the keys of the object itself are not validated.
        type       : Object,
        required   : true,
        attributes : {
            paternal : {                           // Not Validated
                type     : String,
                required : true
            },
            maternal : {                           // Not Validated
                type     : String,
                required : true
            },
        }
    }
} )( class Person {} );

Even if I change it to something like this it still won't work.

const Schema = attributes( {
    firstname : {                                  // Validated OK
        type     : String,
        required : true
    },
    lastname  : {                                  // Validated OK in the sense that the value for this key should be an object and not missing as key, but the keys of the object itself are not validated.
        type       : Object,
        required   : true,
        paternal : {                               // Not Validated
            type     : String,
            required : true
        },
        maternal : {                               // Not Validated
            type     : String,
            required : true
        },
    }
} )( class Person {} );

Any suggestion on how can I achive this? Thank you!

Module not found: Can't resolve 'dns' in 'node_modules/isemail/lib'

I just installed this repository with yarn and I received this message when I run it with "npm start"

./node_modules/isemail/lib/index.js
Module not found: Can't resolve 'dns' in '.../node_modules/isemail/lib'

My package.json is like this

{
...
"dependencies": {
"@mapbox/mapbox-gl-draw": "https://github.com/mlepinay/mapbox-gl-draw-es5.git",
"ably": "^1.0.5",
"autosuggest-highlight": "^3.1.0",
"axios": "^0.16.2",
"babel-polyfill": "^6.26.0",
"bluebird": "^3.5.0",
"classnames": "^2.2.5",
"deck.gl": "^4.0.6",
"deepmerge": "^1.5.0",
"es6-enum": "^1.1.0",
"eslint-plugin-compat": "^2.0.1",
"external-editor": "^2.0.4",
"fast-deep-equal": "^1.0.0",
"has-flag": "^2.0.0",
"immutable": "^3.8.1",
"is-promise": "^2.1.0",
"js-base64": "^2.1.9",
"json-schema-traverse": "^0.3.1",
"klaw": "^2.0.0",
"localforage": "^1.5.0",
"lodash": "^4.17.4",
"luma.gl": "^3.0.2",
"mapbox-gl": "^0.38.0",
"material-ui": "1.0.0-beta.6",
"material-ui-icons": "1.0.0-beta.4",
"material-ui-password-field": "^1.3.0",
"mimic-fn": "^1.1.0",
"moment": "^2.18.1",
"p-map": "^1.1.1",
"promise-waterfall": "^0.1.0",
"promise.waterfall": "^3.2.0",
"prop-types": "^15.5.10",
"query-string": "^5.0.0",
"r-dom": "^2.3.2",
"react": "^15.6.1",
"react-autosuggest": "^9.3.1",
"react-custom-scrollbars": "^4.1.2",
"react-dimensions": "^1.3.0",
"react-dom": "^15.6.1",
"react-dropzone": "^3.13.3",
"react-filter-box": "^2.0.0",
"react-hotkeys": "^0.10.0",
"react-localization": "0.0.17",
"react-map-gl": "^2.0.3",
"react-mapbox-autocomplete": "^0.2.3",
"react-mapbox-gl": "^2.5.3",
"react-mapbox-gl-draw": "^1.0.3",
"react-modal": "^2.2.1",
"react-progress-bar-plus": "^1.3.1",
"react-redux": "^5.0.5",
"react-router": "^4.1.1",
"react-router-dom": "^4.1.1",
"react-router-redux": "^4.0.8",
"react-shortcut": "^1.0.6",
"react-shortcuts": "^1.5.0",
"react-sortable-hoc": "^0.6.5",
"react-stack-grid": "^0.2.2",
"react-stepper-horizontal": "^1.0.9",
"react-tap-event-plugin": "^2.0.1",
"react-typeahead-component": "^0.9.0",
"reactjs-localstorage": "0.0.5",
"recharts": "^1.0.0-alpha.1",
"redux": "^3.7.1",
"redux-persist": "^4.8.2",
"redux-thunk": "^2.2.0",
"rx-lite-aggregates": "^4.0.8",
"socket.io-client": "^2.0.3",
"socket.io-react": "^1.2.0",
"string-similarity": "^1.2.0",
"structure": "^1.2.1",
"superagent": "^3.5.2",
"typeface-roboto": "0.0.29",
"uuid": "^3.1.0",
"whatwg-fetch": "^2.0.3"
},
"devDependencies": {
"react-scripts": "1.0.14"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}

Empty String Converted to 0 and returns Valid

I'm creating a user. The entity looks like this:

const User = attributes({
  id: {
    type: Number,
    required: true,
    empty: false
  },
//// other attributes exist, but left out for brevity
})(class User {})

I'm creating a new User with an empty string as an id (for testing valid/invalid) by doing this:

        const user = new User({
          id:'',
        })

I would imagine that this would cause the test to fail since an empty string isn't a number and empty is false, but when I check the user ID after creating the user it's 0. Am I misunderstanding something about how to use this library?

Class constructor

Hey,

I'm trying to do some data manipulation inside the constructor of the class I'm wrapping. It seems "attributes" have not been defined yet.

This is just an example, obviously this code is kind of useless, but hopefully gives an indication that Id' like to modify an attribute based on a passed in value

User = attributes({
  name: String,
  isPublished: Boolean
})(class User {
  constructor(attributes) {
    this.isPublished = attributes.publish
  }
});

This results in an error similar to:
TypeError: Cannot set property 'isPublished' of undefined
at User.set [as isPublished] (src/attributes/descriptors.js:14:38)
at Object.construct (src/attributes/decorator.js:23:34)

Is there a way setup the proxy before calling the instance constructor?

Getters / attributes not serialized

It would be convenient for getters and or attributes to be serializable via #toJSON or JSON.stringify

cont Thing = attributes({
    name: String
})(
    class Thing {
        get wild() {
            return 'wild thing'
        }
        get name() {
            return `Thing { ${this.get('name')} }`
        }
    }
)

Currently #toJSON would return an empty object, same with JSON.stringify

Could one or both of the getter or attribute be added to the sterilization results?

[Feature Proposal] Allow users to extend default options

Description

  • We should allow users to extend defaults and set generic options for their own Structure instance.

Use cases

  • Empty strings: The default behavior for Strings is not to allow empty values, we can set empty: true for each domain but it would be great if we can do a generic configuration.
  • It can also prepare the codebase for this feature.

Example

const MyStructure = Structure.extends({
  validations: [
    {
      type: String,
      empty: true
    }
  ]
});

module.exports = MyStructure;

cc/ @talyssonoc

option to override property on validation

Here's the case, imagine a schema, which requires e.g. ID to be required during update operation, but it is not required during create operation. Any ways to validate it somehow like this tho?

Create a collection class that does coercion on mutation

It's still just a proposal, the API have to be discussed, but I think having a Collection class that does coercion of items on mutation would be good:

const { Collection, attributes, items } = require('structure');

const Book = attributes({
  name: String
})(class Book { });

const BooksCollection = items({
  type: Book
})(class BooksCollection extends Collection { });

const books = new BooksCollection();

books.push({ name: 'Elric' });

books; // BooksCollection [ Book { name: 'Elric' } ]

Validate raw data without having to instantiate a structure

We could have a way if some raw data is valid given a structure, it could be a static method like:

const Car = attributes({
  model: String
})(class Car { });

const { valid, errors } = Car.validate({ model: 123 });

valid; // false
errors; // [ { message: '"model" must be a string', path: 'model' } ]

I'm not sure about the API and the name of the method yet.

Get other attribute value from a function called within a default value

As I saw in the documentation, the default value can be filled with a function, but I can't figure it out how to get a value from another attribute in that function.

I am trying to do something like this:

const { attributes } = require('structure');

const Foo = attributes({
  paymentTotal: Number,
  quantity: Number,
  finalPrice: {
    type: Number,
    default: (i) => i.calcFinalPrice(),
  },
})(class Foo {
  calcFinalPrice() {
    return this.paymentTotal / this.quantity;
  }
});

module.exports = Foot;

But this.paymentTotal is undefined. If I understood correctly, this functions gets called when all other fields are filled right?.

Version: 1.2.1
Over node 8.9.4

comparison with ObjectModel

Hey there,

I just found out your library on EchoJS and it appears to be very similar to a project of mine: http://objectmodel.js.org/

I thought it would be interesting to compare our goals / implementations / philosophy, so we could learn from each other and exchange ideas. Then with your agreement and if you find the comparison fair enough, I would like to publish it on ObjectModel's readme.

Here is what I found so far, please correct me if I get something wrong:

  • Structures are for Structure what Models are for ObjectModel (what a surprise ๐Ÿ˜„)
  • Schemas are for Structure what Definitions are for ObjectModel
  • both libs use reference to constructors to describe types (i.e. Number, Date)
  • both libs have a similar Type concept, but ObjectModel also validates values (for enumerations for example)
  • Structure has a coercion mechanism while ObjectModel throw errors when types do not match
  • Structure has a lot of built-in validation helpers while ObjectModel comes with very few and encourage to declare generic models and use extension/composition (see http://objectmodel.js.org/examples/common.js)
  • Structure acts as an extension to classes while ObjectModel acts as a transparent proxy on different kinds of objects
  • Structure validation is triggered manually and do not throw exception by default, while ObjectModel validate everytime an object is touched and throw TypeError by default
  • Some features of ObjectModel do not (not yet ?) exist in Structure, such as assertions, union types, inheritance, null-safe traversal... Not a big deal since Structure is quite young compared to ObjectModel (started > 2 years ago) and may have very different goals in mind for the future of the library.
  • Structure goals are focused on interfacing app classes with external interfaces (i.e. JSON services) while ObjectModel goals are focused on strong type checking and bug prevention.

Considering the last point, its quite funny that our libraries end up with a very similar API while having very different goals and priorities in mind.

Did I miss anything ? I would love to have a chat with you, here or somewhere else, and discuss about our motivations and what do we aim for our respective libraries.

Schema subclass loses validate method

Currently, if you try to subclass a structure with another structure, you get an error when you call .validate() on the subclass.

const { attributes } = require('structure')
const Foo = attributes({})(class Foo {})
const Bar = attributes({})(class Bar extends Foo {})
const bar = new Bar
bar.validate()

/* throws:
  const errors = validation.validate(data);
                            ^

TypeError: Cannot read property 'validate' of undefined
*/

The problem is this bit, where all enumerable properties get assigned from schema and WrapperClass[SCHEMA] to a new object and non-enumerable properties get left behind.

I can think of two ways to fix this:

  1. Make VALIDATE enumerable here, which will make Object.assign do the right thing. The only thing I'd be concerned about is now Object.keys would pick up VALIDATE as well and place it on the instance where it probably shouldn't be.
  2. Move the Schema.normalize call down a few lines so it takes effect after the schema extension.

I'm happy to submit a patch for either approach, just let me know what you think would be best. Thanks!

Support I18n

Looks like Joi support I18n/translation of validation messages, it would be good to have support for this on Structure too.

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.