diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b5ab30..4350f62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ --- +## [v0.3.0](https://github.com/foxifyjs/odin/releases/tag/v0.3.0) - *(2018-11-16)* + +- :zap: Added `toJsonSchema` static method to Model so you can easily use it with `Foxify` router schema option +- :star2: Improved throwing validation error for `Foxify` usage + ## [v0.2.0](https://github.com/foxifyjs/odin/releases/tag/v0.2.0) - *(2018-11-14)* - :zap: Added more advanced filtering ability to queries and joins diff --git a/demo/Bill.js b/demo/Bill.js index 915c778..acfa83b 100644 --- a/demo/Bill.js +++ b/demo/Bill.js @@ -1,18 +1,14 @@ -const Model = require("../dist/index"); - -const { - Types -} = Model; +const Model = require("../dist"); class Bill extends Model { user() { - return this.hasOne(Model.models.User); + return this.hasOne("User"); } } Bill.schema = { - user_id: Types.ObjectId.required, - bill: Types.Number.positive.required, + user_id: Bill.Types.Id.required, + bill: Bill.Types.Number.positive.required, }; Model.register(Bill); diff --git a/demo/User.js b/demo/User.js index 1207f6b..62f6068 100644 --- a/demo/User.js +++ b/demo/User.js @@ -1,22 +1,18 @@ -const Model = require("../dist/index"); - -const { - Types -} = Model; +const Model = require("../dist"); class User extends Model { bills() { - return this.hasMany(Model.models.Bill); + return this.hasMany("Bill"); } } User.schema = { name: { - first: Types.String.min(3).required, - last: Types.String.min(3), + first: User.Types.String.min(3).required, + last: User.Types.String.min(3), }, - username: Types.String.token.required, - email: Types.String.email.required, + username: User.Types.String.token.required, + email: User.Types.String.email.required, }; Model.register(User); diff --git a/demo/index.js b/demo/index.js index 72fe236..02352c9 100644 --- a/demo/index.js +++ b/demo/index.js @@ -3,8 +3,6 @@ const env = require("dotenv").load({ }).parsed; const Model = require("../dist/index"); -const User = require("./User"); -const Bill = require("./Bill"); const time = () => new Date().getTime(); @@ -21,6 +19,9 @@ Model.connections({ } }) +const User = require("./User"); +const Bill = require("./Bill"); + const end = time(); console.log( diff --git a/package-lock.json b/package-lock.json index 8fcc338..f80f363 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@foxify/odin", - "version": "0.2.2", + "version": "0.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -50,24 +50,41 @@ } }, "@fimbul/bifrost": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@fimbul/bifrost/-/bifrost-0.11.0.tgz", - "integrity": "sha512-GspMaQafpaUoXWWOUgNLQ4vsV52tIHUt0zpKPeJUYEyMvOSp7FIcZ1eQa7SK3GTusrEiksjMrDX/fwanigC3nQ==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@fimbul/bifrost/-/bifrost-0.15.0.tgz", + "integrity": "sha512-sHTwnwA9YhxcVEJkBlfKH1KLmGQGnNYPxk+09w5NnkXelYiiP8a5f351weYfxG0CUPLt1Fgkha20Y/9+jhjn/Q==", "dev": true, "requires": { - "@fimbul/ymir": "^0.11.0", - "get-caller-file": "^1.0.2", + "@fimbul/ymir": "^0.15.0", + "get-caller-file": "^2.0.0", "tslib": "^1.8.1", - "tsutils": "^2.24.0" + "tsutils": "^3.1.0" + }, + "dependencies": { + "get-caller-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.0.tgz", + "integrity": "sha512-cF41L/f/7nXpSwMMHMY0FIurpTPZq/eHwJdeh2+0kKYhL9eD7RqsgMujd3qdqvWdjGIHjwvd/iEMTNECl2EhzA==", + "dev": true + }, + "tsutils": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.5.0.tgz", + "integrity": "sha512-/FZ+pEJQixWruFejFxNPRSwg+iF6aw7PYZVRqUscJ7EnzV3zieI8byfZziUR7QjCuJFulq8SEe9JcGflO4ze4Q==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } } }, "@fimbul/ymir": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@fimbul/ymir/-/ymir-0.11.0.tgz", - "integrity": "sha512-aIYQMCWbBXe7DIofgu+4DLCPDCfqbKhPjBg4ajskJdq6CAJgySz6KyhGLNnKiDYZMF93ZsaEB/y3SafyMi98Mg==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@fimbul/ymir/-/ymir-0.15.0.tgz", + "integrity": "sha512-Ow0TfxxQ65vIktHcZyXHeDsGKuzJ9Vt6y77R/aOrXQXLMdYHG+XdbiUWzQbtaGOmNzYVkQfINiFnIdvn5Bn24g==", "dev": true, "requires": { - "inversify": "^4.10.0", + "inversify": "^5.0.0", "reflect-metadata": "^0.1.12", "tslib": "^1.8.1" } @@ -130,9 +147,9 @@ } }, "@types/node": { - "version": "10.12.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.7.tgz", - "integrity": "sha512-Zh5Z4kACfbeE8aAOYh9mqotRxaZMro8MbBQtR8vEXOMiZo2rGEh2LayJijKdlu48YnS6y2EFU/oo2NCe5P6jGw==" + "version": "10.12.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.9.tgz", + "integrity": "sha512-eajkMXG812/w3w4a1OcBlaTwsFPO5F7fJ/amy+tieQxEMWBlbV1JGSjkFM+zkHNf81Cad+dfIRA+IBkvmvdAeA==" }, "abab": { "version": "2.0.0", @@ -976,7 +993,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, "requires": { @@ -1045,7 +1062,7 @@ }, "buffer": { "version": "3.6.0", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz", "integrity": "sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=", "dev": true, "requires": { @@ -1483,13 +1500,13 @@ "dependencies": { "file-type": { "version": "3.9.0", - "resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", "dev": true }, "get-stream": { "version": "2.3.1", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", "dev": true, "requires": { @@ -1605,7 +1622,7 @@ }, "doctrine": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", + "resolved": "http://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", "dev": true, "requires": { @@ -1703,7 +1720,7 @@ }, "es6-promisify": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { @@ -2991,9 +3008,9 @@ } }, "inversify": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/inversify/-/inversify-4.13.0.tgz", - "integrity": "sha512-O5d8y7gKtyRwrvTLZzYET3kdFjqUy58sGpBYMARF13mzqDobpfBXVOPLH7HmnD2VR6Q+1HzZtslGvsdQfeb0SA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", + "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==", "dev": true }, "invert-kv": { @@ -5823,7 +5840,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -6015,25 +6032,25 @@ } }, "tslint-config-airbnb": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/tslint-config-airbnb/-/tslint-config-airbnb-5.11.0.tgz", - "integrity": "sha512-o2FhaQtxXi6FQ1v0T2n/rACNos6PhuKRmvemMpWxI+9NJn2OOlJ3+OtEmnCdoF7GPXT3Eyk+Q0q4P96flrPl3w==", + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/tslint-config-airbnb/-/tslint-config-airbnb-5.11.1.tgz", + "integrity": "sha512-hkaittm2607vVMe8eotANGN1CimD5tor7uoY3ypg2VTtEcDB/KGWYbJOz58t8LI4cWSyWtgqYQ5F0HwKxxhlkQ==", "dev": true, "requires": { - "tslint-consistent-codestyle": "^1.13.3", + "tslint-consistent-codestyle": "^1.14.1", "tslint-eslint-rules": "^5.4.0", - "tslint-microsoft-contrib": "~5.2.0" + "tslint-microsoft-contrib": "~5.2.1" } }, "tslint-consistent-codestyle": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/tslint-consistent-codestyle/-/tslint-consistent-codestyle-1.13.3.tgz", - "integrity": "sha512-+ocXSNGHqUCUyTJsPhS7xqcC3qf6FyP4vd1jEaXaWaJ5NNN36gKZhqNt3nAWH/YgSV0tYaapjSWMbJQJmn/5MQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslint-consistent-codestyle/-/tslint-consistent-codestyle-1.14.1.tgz", + "integrity": "sha512-UxGRX2fF5LpZtqYpuPFaIva+2D7ASX3pTVw41yis6Hmw7PPA3cBnFEX1jqRsnyxGrca6mHxz7xDnwCHtOjWJMQ==", "dev": true, "requires": { - "@fimbul/bifrost": "^0.11.0", + "@fimbul/bifrost": "^0.15.0", "tslib": "^1.7.1", - "tsutils": "^2.27.0" + "tsutils": "^2.29.0" } }, "tslint-eslint-rules": { @@ -6054,9 +6071,9 @@ "dev": true }, "tsutils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.0.0.tgz", - "integrity": "sha512-LjHBWR0vWAUHWdIAoTjoqi56Kz+FDKBgVEuL+gVPG/Pv7QW5IdaDDeK9Txlr6U0Cmckp5EgCIq1T25qe3J6hyw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.5.0.tgz", + "integrity": "sha512-/FZ+pEJQixWruFejFxNPRSwg+iF6aw7PYZVRqUscJ7EnzV3zieI8byfZziUR7QjCuJFulq8SEe9JcGflO4ze4Q==", "dev": true, "requires": { "tslib": "^1.8.1" diff --git a/package.json b/package.json index 2d63a35..aa1b1d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@foxify/odin", - "version": "0.2.2", + "version": "0.3.0", "description": "Active Record Model", "author": "Ardalan Amini [https://github.com/ardalanamini]", "contributors": [ @@ -43,7 +43,7 @@ "@types/graphql": "^0.13.4", "@types/graphql-iso-date": "^3.3.1", "@types/mongodb": "^3.1.14", - "@types/node": "^10.12.7", + "@types/node": "^10.12.9", "async": "^2.6.1", "caller-id": "^0.1.0", "deasync": "^0.1.14", @@ -67,7 +67,7 @@ "rimraf": "^2.6.2", "ts-jest": "^23.10.4", "tslint": "^5.11.0", - "tslint-config-airbnb": "^5.11.0", + "tslint-config-airbnb": "^5.11.1", "typescript": "^3.1.6", "uglify-es": "^3.3.9" }, diff --git a/src/Model.ts b/src/Model.ts index 9acb1fe..f648c01 100644 --- a/src/Model.ts +++ b/src/Model.ts @@ -9,6 +9,9 @@ import GraphQL from "./GraphQL"; import GraphQLInstance from "./GraphQL/Model"; import * as Types from "./types"; import TypeAny from "./types/Any"; +import TypeArray from "./types/Array"; +import TypeDate from "./types/Date"; +import TypeObjectId from "./types/ObjectId"; import * as utils from "./utils"; const EVENTS = ["created"]; @@ -79,6 +82,60 @@ class Model extends Base return this._table; } + public static toJsonSchema() { + const jsonSchemaGenerator = (schema: Odin.Schema) => { + const value: { [key: string]: any } = {}; + const required: string[] = []; + + for (const key in schema) { + const type = schema[key]; + + if (type instanceof TypeAny) { + // Type + + let schemaType: string = (type as any)._type.toLowerCase(); + + if ( + type instanceof TypeObjectId + || type instanceof TypeDate + ) schemaType = "string"; + + value[key] = { + type: schemaType, + }; + + if (type instanceof TypeArray) value[key].items = { + type: (type.ofType as any)._type.toLowerCase(), + }; + + if ((type as any)._required) required.push(key); + + continue; + } + + // Object + + const generated = jsonSchemaGenerator(type); + + if (utils.object.size(generated) === 1) continue; + + value[key] = { + type: "object", + properties: generated, + }; + + if (generated.required.length) required.push(key); + } + + return { + ...value, + required, + }; + }; + + return jsonSchemaGenerator(this.schema); + } + public static validate(document: T, updating: boolean = false) { const validator = (schema: Odin.Schema, doc: T) => { const value: { [key: string]: any } = {}; @@ -128,7 +185,14 @@ class Model extends Base if (utils.object.size(validation.errors) === 0) validation.errors = null; } - if (validation.errors) throw validation.errors; + if (validation.errors) { + const error = new Error("Unprocessable Entity") as any; + + error.errors = validation.errors; + error.code = 422; + + throw error; + } const value = validation.value;