├── docs
├── .nojekyll
├── CNAME
├── logo.png
├── _sidebar.md
├── index.html
├── install.md
├── webpack.md
├── README.md
└── context.md
├── test
├── base
│ ├── exceptions-test.js
│ ├── base-model-test.js
│ ├── methods-test.js
│ ├── data_types-test.js
│ ├── scope-test.js
│ ├── events-test.js
│ └── convert-test.js
├── fixtures
│ ├── models
│ │ ├── camel_cased_model.js
│ │ ├── other_name.js
│ │ ├── _user
│ │ │ ├── attribute.js
│ │ │ └── model-functions.js
│ │ └── user.js
│ ├── migrations
│ │ ├── 20140225054125_destroy_bar.js
│ │ ├── 20140225054124_rename_foo_to_bar.js
│ │ ├── 20140225142344_fix_posts.js
│ │ ├── 20140225142454_remove_foo_from_posts.js
│ │ ├── 20140425233709_create_view.js
│ │ ├── 20140225054123_create_foo.js
│ │ ├── 20140223120915_add_last_name_to_users.js
│ │ ├── 20140425233701_seed.js
│ │ ├── 20140223120815_create_users.js
│ │ ├── 20181003115058_create_table_with_comments.js
│ │ ├── 20181003144711_create_table_with_references.js
│ │ ├── 20181003111622_create_compound_primary_key_table.js
│ │ ├── 20181003151428_create_table_with_indices.js
│ │ ├── 20181106180625_create_table_with_eums.js
│ │ ├── 20140225142344_create_posts.js
│ │ ├── 20190214220159_create_migration_helpers.js
│ │ └── 20140225144123_create_attribute_tests.js
│ ├── plugins
│ │ └── test-plugin.js
│ ├── classes
│ │ └── Post.es6
│ └── stores
│ │ ├── webpack-sqlite3.js
│ │ ├── webpack-sqlite3-migrations.js
│ │ └── webpack-postgres.js
├── mocha.opts
├── sql
│ ├── postgres
│ │ ├── fixtures
│ │ │ └── migrations
│ │ │ │ ├── 20170603143310_create_json_test.js
│ │ │ │ ├── 20181106183654_create_enum_test.js
│ │ │ │ └── 20141211203427_create_array_test.js
│ │ ├── raw-test.js
│ │ ├── __helper.js
│ │ ├── uuid_key-test.js
│ │ └── enum_type-test.js
│ ├── __shared
│ │ ├── empty-test.js
│ │ ├── exec-test.js
│ │ ├── plugins
│ │ │ ├── serialize-test.js
│ │ │ ├── paranoid-test.js
│ │ │ └── total_count-test.js
│ │ ├── data_types-test.js
│ │ ├── autoload-test.js
│ │ ├── dependent_nullify-test.js
│ │ └── autojoin-test.js
│ ├── mysql
│ │ └── __helper.js
│ ├── table_name-test.js
│ ├── sqlite3
│ │ └── __helper.js
│ ├── oracle
│ │ └── clear.sql
│ ├── aggregate_function-test.js
│ └── interceptors-test.js
├── graphql
│ ├── __schema_auto.js
│ └── context-test.js
├── rest
│ └── client
│ │ ├── destroy-test.js
│ │ ├── resource_name-test.js
│ │ ├── includes-test.js
│ │ ├── create-test.js
│ │ ├── exec-test.js
│ │ └── update-test.js
├── record-test.js
├── model-test.js
├── ldap
│ └── client
│ │ ├── __shared
│ │ └── include-test.js
│ │ ├── destroy-test.js
│ │ └── exec-test.js
└── definition-test.js
├── .prettierrc
├── lib
├── stores
│ ├── mysql
│ │ ├── index.js
│ │ ├── autoload.js
│ │ ├── connection.js
│ │ └── attributes.js
│ ├── sql
│ │ ├── interceptors.js
│ │ ├── migrations
│ │ │ ├── seed.js
│ │ │ ├── polymorph.js
│ │ │ ├── run.js
│ │ │ └── raw.js
│ │ ├── exceptions.js
│ │ ├── data_types
│ │ │ ├── index.js
│ │ │ ├── string.js
│ │ │ ├── bigint.js
│ │ │ ├── float.js
│ │ │ ├── integer.js
│ │ │ ├── boolean.js
│ │ │ ├── datetime.js
│ │ │ ├── binary.js
│ │ │ ├── date.js
│ │ │ └── time.js
│ │ ├── limit.js
│ │ ├── convert.js
│ │ ├── raw.js
│ │ ├── query.js
│ │ ├── plugins
│ │ │ ├── paranoid.js
│ │ │ ├── total_count.js
│ │ │ ├── serialize.js
│ │ │ └── stampable.js
│ │ ├── table_name.js
│ │ ├── attributes.js
│ │ ├── index.js
│ │ ├── conditions.js
│ │ ├── select.js
│ │ ├── exec.js
│ │ ├── transaction.js
│ │ └── validations.js
│ ├── sqlite3
│ │ ├── index.js
│ │ ├── foreign_keys.js
│ │ ├── connection.js
│ │ └── autoload.js
│ ├── postgres
│ │ ├── index.js
│ │ ├── data_types
│ │ │ ├── date_array.js
│ │ │ ├── time_array.js
│ │ │ ├── float_array.js
│ │ │ ├── string_array.js
│ │ │ ├── boolean_array.js
│ │ │ ├── datetime_array.js
│ │ │ ├── point.js
│ │ │ ├── circle.js
│ │ │ ├── uuid.js
│ │ │ ├── interval.js
│ │ │ ├── box.js
│ │ │ ├── enum.js
│ │ │ ├── index.js
│ │ │ ├── polygon.js
│ │ │ ├── _geom.js
│ │ │ ├── line.js
│ │ │ ├── path.js
│ │ │ ├── _array.js
│ │ │ ├── integer_array.js
│ │ │ ├── json.js
│ │ │ └── tsvector.js
│ │ ├── autoload.js
│ │ └── connection.js
│ ├── oracle
│ │ ├── index.js
│ │ ├── conditions.js
│ │ ├── autoload.js
│ │ ├── connection.js
│ │ └── data_types
│ │ │ └── date.js
│ ├── rest
│ │ ├── conditions.js
│ │ ├── index.js
│ │ ├── helper.js
│ │ ├── limit.js
│ │ ├── collection.js
│ │ ├── connection.js
│ │ ├── exec.js
│ │ ├── resource.js
│ │ ├── operators.js
│ │ ├── urls.js
│ │ └── destroy.js
│ ├── activedirectory
│ │ ├── index.js
│ │ ├── schema
│ │ │ ├── container.js
│ │ │ ├── group.js
│ │ │ └── organizational_unit.js
│ │ └── data_types
│ │ │ ├── date.js
│ │ │ ├── group_type.js
│ │ │ ├── sid.js
│ │ │ ├── timestamp.js
│ │ │ ├── guid.js
│ │ │ └── user_account_control.js
│ └── ldap
│ │ ├── search_root.js
│ │ ├── limit.js
│ │ ├── dn.js
│ │ ├── schema.js
│ │ ├── conditions.js
│ │ ├── data_types
│ │ ├── object_class.js
│ │ ├── dn.js
│ │ └── dn_array.js
│ │ ├── search_scope.js
│ │ ├── index.js
│ │ ├── relations.js
│ │ ├── connection.js
│ │ ├── select.js
│ │ ├── controls
│ │ └── tree_delete.js
│ │ ├── object_class.js
│ │ ├── errors.js
│ │ ├── destroy.js
│ │ ├── utils.js
│ │ ├── relations
│ │ ├── has_parent.js
│ │ └── has_many.js
│ │ └── attributes.js
├── graphql
│ ├── index.js
│ ├── relations.js
│ ├── data_types.js
│ └── type.js
└── base
│ ├── primary_keys.js
│ ├── relations
│ ├── has_one.js
│ └── has.js
│ ├── mixin.js
│ ├── parent.js
│ ├── cache.js
│ ├── connection.js
│ ├── method.js
│ ├── methods.js
│ ├── expect_result.js
│ ├── dynamic_loading.js
│ ├── index.js
│ ├── context.js
│ ├── save.js
│ ├── operators.js
│ └── every.js
├── .gitignore
├── .npmignore
├── store
├── base.js
├── ldap.js
├── rest.js
├── sql.js
├── mysql.js
├── oracle.js
├── sqlite3.js
├── postgres.js
├── index.js
└── activedirectory.js
├── .eslintrc.js
├── LICENSE
└── .travis.yml
/docs/.nojekyll:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | openrecord.js.org
--------------------------------------------------------------------------------
/test/base/exceptions-test.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/models/camel_cased_model.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {}
2 |
--------------------------------------------------------------------------------
/test/fixtures/models/other_name.js:
--------------------------------------------------------------------------------
1 | module.exports = function RightName() {}
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "semi": false,
4 | "singleQuote": true
5 | }
--------------------------------------------------------------------------------
/docs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilWaldmann/openrecord/HEAD/docs/logo.png
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --reporter spec
2 | --recursive
3 | --full-trace
4 | --timeout 10000
5 | --exit
6 |
--------------------------------------------------------------------------------
/test/fixtures/models/_user/attribute.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.attribute('login', String)
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/models/_user/model-functions.js:
--------------------------------------------------------------------------------
1 | exports.model = {
2 | foobar() {
3 | return 'foo'
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140225054125_destroy_bar.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.removeTable('bar')
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140225054124_rename_foo_to_bar.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.renameTable('foo', 'bar')
3 | }
4 |
--------------------------------------------------------------------------------
/lib/stores/mysql/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./attributes'),
3 | require('./autoload'),
4 | require('./connection')
5 | ]
6 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140225142344_fix_posts.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.renameColumn('posts', 'messages', 'message')
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140225142454_remove_foo_from_posts.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.removeColumn('posts', 'foo')
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140425233709_create_view.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.raw('CREATE VIEW tests AS SELECT * FROM users')
3 | }
4 |
--------------------------------------------------------------------------------
/lib/stores/sql/interceptors.js:
--------------------------------------------------------------------------------
1 | /*
2 | * STORE
3 | */
4 | exports.store = {
5 | mixinCallback: function() {
6 | this.addInterceptor('onJoin')
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixtures/models/user.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.mixin(require('./_user/attribute'))
3 | this.mixin(require('./_user/model-functions'))
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140225054123_create_foo.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('foo', function() {
3 | this.text('foo')
4 | })
5 | }
6 |
--------------------------------------------------------------------------------
/lib/stores/sql/migrations/seed.js:
--------------------------------------------------------------------------------
1 | exports.migration = {
2 | seed: function(fn) {
3 | return this.run(function() {
4 | return this.use(fn)
5 | })
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/stores/sql/migrations/polymorph.js:
--------------------------------------------------------------------------------
1 | exports.migration = {
2 | polymorph: function(name) {
3 | this.integer(name + '_id')
4 | this.string(name + '_type')
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/lib/stores/sqlite3/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./attributes'),
3 | require('./autoload'),
4 | require('./connection'),
5 | require('./foreign_keys')
6 | ]
7 |
--------------------------------------------------------------------------------
/lib/graphql/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./data_types'),
3 | require('./helper'),
4 | require('./relations'),
5 | require('./resolve'),
6 | require('./type')
7 | ]
8 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140223120915_add_last_name_to_users.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.addColumn('users', function() {
3 | this.string('last_name')
4 | })
5 | }
6 |
--------------------------------------------------------------------------------
/lib/stores/postgres/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./attributes'),
3 | require('./autoload'),
4 | require('./connection'),
5 | require('./save')
6 | ].concat(require('./data_types'))
7 |
--------------------------------------------------------------------------------
/lib/stores/oracle/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./attributes'),
3 | require('./autoload'),
4 | require('./conditions'),
5 | require('./connection'),
6 | require('./data_types/date')
7 | ]
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | *.csv
3 | TODO.md
4 |
5 | documentation/wiki
6 | wiki
7 | logs
8 | node_modules
9 |
10 | coverage
11 | coverage.html
12 | .coveralls.yml
13 |
14 | test/**/*.sqlite3
15 | .DS_Store
16 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140425233701_seed.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.seed(function(store) {
3 | var User = store.Model('User')
4 | return User.create({ login: 'phil' })
5 | })
6 | }
7 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140223120815_create_users.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('users', function() {
3 | this.string('login', { not_null: true })
4 | this.string('first_name')
5 | })
6 | }
7 |
--------------------------------------------------------------------------------
/test/sql/postgres/fixtures/migrations/20170603143310_create_json_test.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('json_tests', function() {
3 | this.json('json_attr')
4 | this.jsonb('jsonb_attr')
5 | })
6 | }
7 |
--------------------------------------------------------------------------------
/lib/stores/sql/migrations/run.js:
--------------------------------------------------------------------------------
1 | exports.migration = {
2 | run: function(fn) {
3 | var self = this
4 | this.queue.push(function() {
5 | return fn.call(self.store)
6 | })
7 |
8 | return this
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20181003115058_create_table_with_comments.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('with_comments', {comment: 'foobar table'}, function() {
3 | this.integer('foo', { comment: 'foobar' })
4 | })
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20181003144711_create_table_with_references.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('with_references', function() {
3 | this.integer('post_id', { references: 'posts.id', unsigned: true })
4 | })
5 | }
6 |
--------------------------------------------------------------------------------
/test/sql/postgres/fixtures/migrations/20181106183654_create_enum_test.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.enum('my_enum', ['foo', 'bar'])
3 | this.createTable('enum_tests', function() {
4 | this.type('my_enum', 'enum_attribute')
5 | })
6 | }
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | docs/
2 | test/
3 | coverage/
4 |
5 | npm-debug.log
6 | TODO.md
7 |
8 | coverage.html
9 | .coveralls.yml
10 | .npmignore
11 | .travis.yml
12 | .eslintrc.js
13 | test.js
14 | logo.png
15 | Makefile
16 | CODE_OF_CONDUCT.md
17 | CONTRIBUTING.md
18 |
--------------------------------------------------------------------------------
/lib/stores/sql/migrations/raw.js:
--------------------------------------------------------------------------------
1 | exports.migration = {
2 | raw: function(sql, args) {
3 | var self = this
4 |
5 | this.queue.push(function() {
6 | return self.connection.raw(sql, args)
7 | })
8 |
9 | return this
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/stores/rest/conditions.js:
--------------------------------------------------------------------------------
1 | exports.model = {
2 | find: function() {
3 | var self = this.callParent.apply(
4 | this,
5 | this.definition.store.utils.args(arguments)
6 | )
7 | self.setInternal('action', 'show')
8 | return self
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lib/stores/sql/exceptions.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | const Store = require('../../store')
4 |
5 | Store.addExceptionType(function SQLError(error) {
6 | Error.apply(this)
7 | this.message = error
8 | })
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20181003111622_create_compound_primary_key_table.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('compound_primary_keys', {id: false}, function() {
3 | this.integer('foo', { primary: true })
4 | this.string('bar', { primary: true })
5 | })
6 | }
7 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/date_array.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType('date_array', this.toArrayCastTypes('date'), {
4 | array: true,
5 | migration: {
6 | dateArray: 'date[]'
7 | }
8 | })
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/time_array.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType('time_array', this.toArrayCastTypes('time'), {
4 | array: true,
5 | migration: {
6 | timeArray: 'time[]'
7 | }
8 | })
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20181003151428_create_table_with_indices.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('with_indices', function() {
3 | this.integer('foo', { unique: true })
4 | this.integer('bar')
5 | })
6 |
7 | this.createIndex('with_indices', 'bar')
8 | }
9 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/float_array.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType('float_array', this.toArrayCastTypes('float'), {
4 | array: true,
5 | migration: {
6 | floatArray: 'float[]'
7 | }
8 | })
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/string_array.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType('string_array', this.toArrayCastTypes('string'), {
4 | array: true,
5 | migration: {
6 | stringArray: 'varchar[]'
7 | }
8 | })
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/boolean_array.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType('boolean_array', this.toArrayCastTypes('boolean'), {
4 | array: true,
5 | migration: {
6 | booleanArray: 'boolean[]'
7 | }
8 | })
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/datetime_array.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType('datetime_array', this.toArrayCastTypes('datetime'), {
4 | array: true,
5 | migration: {
6 | datetimeArray: 'timestamp[]'
7 | }
8 | })
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/store/base.js:
--------------------------------------------------------------------------------
1 | const Store = require('../lib/store')
2 |
3 | Store.registeredTypes.base = require('../lib/base')
4 |
5 | module.exports = function(config){
6 | config = config || {}
7 | config.type = 'base'
8 | return new Store(config)
9 | }
10 |
11 | module.exports.BaseModel = Store.BaseModel
12 |
--------------------------------------------------------------------------------
/store/ldap.js:
--------------------------------------------------------------------------------
1 | const Store = require('../lib/store')
2 |
3 | Store.registeredTypes.ldap = require('../lib/base').concat(
4 | require('../lib/stores/ldap')
5 | )
6 |
7 | module.exports = function(config){
8 | config.type = 'ldap'
9 | return new Store(config)
10 | }
11 |
12 | module.exports.BaseModel = Store.BaseModel
--------------------------------------------------------------------------------
/store/rest.js:
--------------------------------------------------------------------------------
1 | const Store = require('../lib/store')
2 |
3 | Store.registeredTypes.rest = require('../lib/base').concat(
4 | require('../lib/stores/rest')
5 | )
6 |
7 | module.exports = function(config){
8 | config.type = 'rest'
9 | return new Store(config)
10 | }
11 |
12 | module.exports.BaseModel = Store.BaseModel
--------------------------------------------------------------------------------
/store/sql.js:
--------------------------------------------------------------------------------
1 | const Store = require('../lib/store')
2 |
3 | Store.registeredTypes.sql = require('../lib/base').concat(
4 | require('../lib/stores/sql')
5 | )
6 |
7 | module.exports = function(config){
8 | config.type = 'sql'
9 | return new Store(config)
10 | }
11 |
12 | module.exports.BaseModel = Store.BaseModel
--------------------------------------------------------------------------------
/test/fixtures/migrations/20181106180625_create_table_with_eums.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('with_enums', function() {
3 | this.enum('foo', { values: ['A', 'B', 'C'] })
4 | this.enum('bar', { values: ['A1', 'B2', 'C3'] })
5 | this.enum('foo2', { values: ['A', 'B', 'C'] })
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/lib/stores/sql/data_types/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./_operators'),
3 | require('./binary'),
4 | require('./boolean'),
5 | require('./date'),
6 | require('./datetime'),
7 | require('./float'),
8 | require('./integer'),
9 | require('./bigint'),
10 | require('./string'),
11 | require('./time')
12 | ]
13 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140225142344_create_posts.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('posts', function() {
3 | this.string('messages', { default: 'no message' })
4 | this.string('foo')
5 |
6 | this.stampable()
7 | this.polymorph('foo')
8 | this.nestedSet()
9 | this.paranoid()
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/lib/stores/rest/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./collection'),
3 | require('./conditions'),
4 | require('./connection'),
5 | require('./destroy'),
6 | require('./exec'),
7 | require('./helper'),
8 | require('./limit'),
9 | require('./operators'),
10 | require('./resource'),
11 | require('./save'),
12 | require('./urls')
13 | ]
14 |
--------------------------------------------------------------------------------
/store/mysql.js:
--------------------------------------------------------------------------------
1 | const Store = require('../lib/store')
2 |
3 | Store.registeredTypes.mysql = require('../lib/base').concat(
4 | require('../lib/stores/sql'),
5 | require('../lib/stores/mysql')
6 | )
7 |
8 | module.exports = function(config){
9 | config.type = 'mysql'
10 | return new Store(config)
11 | }
12 |
13 | module.exports.BaseModel = Store.BaseModel
--------------------------------------------------------------------------------
/store/oracle.js:
--------------------------------------------------------------------------------
1 | const Store = require('../lib/store')
2 |
3 | Store.registeredTypes.oracle = require('../lib/base').concat(
4 | require('../lib/stores/sql'),
5 | require('../lib/stores/oracle')
6 | )
7 |
8 | module.exports = function(config){
9 | config.type = 'oracle'
10 | return new Store(config)
11 | }
12 |
13 | module.exports.BaseModel = Store.BaseModel
--------------------------------------------------------------------------------
/store/sqlite3.js:
--------------------------------------------------------------------------------
1 | const Store = require('../lib/store')
2 |
3 | Store.registeredTypes.sqlite3 = require('../lib/base').concat(
4 | require('../lib/stores/sql'),
5 | require('../lib/stores/sqlite3')
6 | )
7 |
8 | module.exports = function(config){
9 | config.type = 'sqlite3'
10 | return new Store(config)
11 | }
12 |
13 | module.exports.BaseModel = Store.BaseModel
--------------------------------------------------------------------------------
/store/postgres.js:
--------------------------------------------------------------------------------
1 | const Store = require('../lib/store')
2 |
3 | Store.registeredTypes.postgres = require('../lib/base').concat(
4 | require('../lib/stores/sql'),
5 | require('../lib/stores/postgres')
6 | )
7 |
8 | module.exports = function(config){
9 | config.type = 'postgres'
10 | return new Store(config)
11 | }
12 |
13 | module.exports.BaseModel = Store.BaseModel
--------------------------------------------------------------------------------
/test/fixtures/plugins/test-plugin.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | myStoreFunction: function() {},
3 |
4 | Model: function(a, b, callback) {
5 | if (typeof b === 'function') {
6 | callback = b
7 | b = ''
8 | }
9 | return this.callParent(a + b, callback)
10 | }
11 | }
12 |
13 | exports.definition = {
14 | myDefinitionFunction: function() {}
15 | }
16 |
--------------------------------------------------------------------------------
/store/index.js:
--------------------------------------------------------------------------------
1 | const Store = require('../lib/store')
2 |
3 | require('./base')
4 | require('./sql')
5 | require('./postgres')
6 | require('./sqlite3')
7 | require('./mysql')
8 | require('./oracle')
9 | require('./rest')
10 | require('./ldap')
11 | require('./activedirectory')
12 |
13 | // reset type to get it via constructor config object
14 | Store.type = null
15 |
16 | module.exports = Store
17 |
--------------------------------------------------------------------------------
/lib/stores/rest/helper.js:
--------------------------------------------------------------------------------
1 | exports.utils = {
2 | applyParams: function(options, primaryKeys) {
3 | var params = options.params
4 |
5 | options.url = options.url.replace(/:(\w+)/, function(match, param) {
6 | if (params[param]) {
7 | var tmp = params[param]
8 | delete params[param]
9 | return tmp
10 | }
11 | return match
12 | })
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/store/activedirectory.js:
--------------------------------------------------------------------------------
1 | const Store = require('../lib/store')
2 |
3 | Store.registeredTypes.activedirectory = require('../lib/base').concat(
4 | require('../lib/stores/ldap'),
5 | require('../lib/stores/activedirectory')
6 | )
7 |
8 | module.exports = function(config){
9 | config.type = 'activedirectory'
10 | return new Store(config)
11 | }
12 |
13 | module.exports.BaseModel = Store.BaseModel
--------------------------------------------------------------------------------
/lib/base/primary_keys.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.definition = {
5 | mixinCallback: function() {
6 | var self = this
7 |
8 | this.primaryKeys = []
9 |
10 | this.use(function() {
11 | for (var name in self.attributes) {
12 | if (self.attributes[name].primary) {
13 | self.primaryKeys.push(name)
14 | }
15 | }
16 | }, 0)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/fixtures/classes/Post.es6:
--------------------------------------------------------------------------------
1 | const Store = require('../../../store')
2 |
3 | class BasePost extends Store.BaseModel {
4 | static definition() {
5 | this.validatesPresenceOf('message')
6 | }
7 | }
8 |
9 | class Post extends BasePost {
10 | static definition() {
11 | this.belongsTo('user')
12 | this.belongsTo('thread')
13 | super.definition()
14 | }
15 | }
16 |
17 | module.exports = Post
18 |
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | * **Getting started**
2 | * [Install](install.md)
3 | * [Setup](setup.md)
4 | * **Usage**
5 | * [Model Definition](definition.md)
6 | * [Query](query.md)
7 | * [Modify](modify.md)
8 | * [Context](context.md)
9 |
10 | * **[LDAP](ldap.md)**
11 | *
12 | * **[GraphQL](graphql.md)**
13 |
14 | * **[Webpack](webpack.md)**
15 |
16 | * **[Plugins](plugins.md)**
17 |
18 | * **[Migrations](migrations.md)**
19 |
--------------------------------------------------------------------------------
/test/sql/postgres/fixtures/migrations/20141211203427_create_array_test.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('array_tests', function() {
3 | this.integerArray('int_arr')
4 | this.floatArray('float_arr')
5 | this.booleanArray('bool_arr')
6 | this.dateArray('date_arr')
7 | this.datetimeArray('datetime_arr')
8 | this.timeArray('time_arr')
9 | this.stringArray('str_arr')
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/point.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType(
4 | 'point',
5 | {
6 | read: function(value) {
7 | return value
8 | },
9 |
10 | write: function(value) {
11 | if(typeof value === 'object') return '(' + value.x +',' + value.y + ')'
12 | return value
13 | }
14 | }
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/circle.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType(
4 | 'circle',
5 | {
6 | read: function(value) {
7 | return value
8 | },
9 |
10 | write: function(value) {
11 | if(typeof value === 'object') return '<(' + value.x +',' + value.y + '),' + value.radius + '>'
12 | return value
13 | }
14 | }
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/stores/sqlite3/foreign_keys.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.use(function() {
4 | if (this.config.enableForeignKeys !== false) return this.enableForeignKeys()
5 | }, 100)
6 | },
7 |
8 | enableForeignKeys: function(){
9 | return this.connection.raw('PRAGMA foreign_keys = ON')
10 | .catch(function(error){
11 | console.error('Error while enabling SQLite3 foreign_keys: ', error)
12 | })
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lib/stores/oracle/conditions.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.definition = {
5 | mixinCallback: function() {
6 | this.onRawCondition(function(chain, condition) {
7 | var attrsRegExp = new RegExp(
8 | '(' + Object.keys(chain.definition.attributes).join('|') + ')',
9 | 'g'
10 | )
11 |
12 | // add quotes to attribute names
13 | condition.query = condition.query.replace(attrsRegExp, '"$1"')
14 | }, 10)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/test/sql/__shared/empty-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../store')
2 |
3 | module.exports = function(title, beforeFn, afterFn, storeConf) {
4 | describe(title + ': Empty', function() {
5 | var store
6 |
7 | before(beforeFn)
8 | after(function(next) {
9 | afterFn(next, store)
10 | })
11 |
12 | before(function() {
13 | store = new Store(storeConf)
14 |
15 | // Models here
16 | })
17 |
18 | // Tests here
19 | })
20 | }
21 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/uuid.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType(
4 | 'uuid',
5 | function(value) {
6 | if (value === null) return null
7 | if (value === undefined) return null
8 | return value.toString()
9 | },
10 | {
11 | migration: ['uuid'],
12 | operators: {
13 | defaults: ['eq', 'not', 'like', 'ilike']
14 | }
15 | }
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/stores/rest/limit.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.definition = {
5 | mixinCallback: function() {
6 | this.beforeFind(function(options) {
7 | var limit = this.getInternal('limit')
8 | var offset = this.getInternal('offset')
9 |
10 | if (limit) {
11 | options.params.limit = limit
12 | }
13 |
14 | if (offset) {
15 | options.params.limit = offset
16 | }
17 |
18 | return true
19 | }, -40)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/stores/sql/data_types/string.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType(
4 | 'string',
5 | function(value) {
6 | if (value === null) return null
7 | if (value === undefined) return null
8 | return value.toString()
9 | },
10 | {
11 | migration: ['string', 'text'],
12 | operators: {
13 | defaults: ['eq', 'not', 'like', 'ilike']
14 | }
15 | }
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/lib/stores/sql/limit.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.definition = {
5 | mixinCallback: function() {
6 | this.beforeFind(function(query) {
7 | var limit = this.getInternal('limit')
8 | var offset = this.getInternal('offset')
9 |
10 | if (typeof limit === 'number') {
11 | query.limit(limit)
12 | }
13 |
14 | if (offset && offset > 0) {
15 | query.offset(offset)
16 | }
17 |
18 | return true
19 | }, -40)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/stores/activedirectory/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./errors'),
3 | require('./data_types/date'),
4 | require('./data_types/group_type'),
5 | require('./data_types/guid'),
6 | require('./data_types/sid'),
7 | require('./data_types/timestamp'),
8 | require('./data_types/user_account_control'),
9 | require('./schema/computer'),
10 | require('./schema/container'),
11 | require('./schema/group'),
12 | require('./schema/organizational_unit'),
13 | require('./schema/user')
14 | ]
15 |
--------------------------------------------------------------------------------
/test/graphql/__schema_auto.js:
--------------------------------------------------------------------------------
1 | const graphQLTools = require('graphql-tools')
2 | const graphql = require('graphql')
3 |
4 | module.exports = function(store1, store2) {
5 | const schema = graphQLTools.makeExecutableSchema({
6 | typeDefs: [store1.toGraphQLTypeDefs(), store2.toGraphQLTypeDefs()],
7 | resolvers: store1.toGraphQLResolvers()
8 | })
9 |
10 | return function query(query, context, variables, operation) {
11 | return graphql.graphql(schema, query, null, context, variables, operation)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/stores/sqlite3/connection.js:
--------------------------------------------------------------------------------
1 | const Knex = require('knex')
2 |
3 | /*
4 | * STORE
5 | */
6 | exports.store = {
7 | connect: function() {
8 | this.connection = Knex({
9 | client: 'sqlite3',
10 | connection: this.config.connection || {
11 | filename: this.config.file
12 | },
13 | useNullAsDefault: true
14 | })
15 | },
16 |
17 | close: function(callback) {
18 | if (!this.connection) return callback ? callback() : null
19 | this.connection.client.destroy(callback)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/stores/sql/data_types/bigint.js:
--------------------------------------------------------------------------------
1 | const validator = require('validator')
2 |
3 | exports.store = {
4 | mixinCallback: function() {
5 | this.addType(
6 | 'bigint',
7 | function(value) {
8 | if (value === null) return null
9 | if (value === '') return null
10 | return validator.toInt(value + '')
11 | },
12 | {
13 | migration: 'bigint',
14 | operators: {
15 | defaults: ['eq', 'not', 'gt', 'gte', 'lt', 'lte', 'between']
16 | }
17 | }
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/stores/sql/data_types/float.js:
--------------------------------------------------------------------------------
1 | const validator = require('validator')
2 |
3 | exports.store = {
4 | mixinCallback: function() {
5 | this.addType(
6 | 'float',
7 | function(value) {
8 | if (value === null) return null
9 | if (value === '') return null
10 | return validator.toFloat(value + '')
11 | },
12 | {
13 | migration: 'float',
14 | operators: {
15 | defaults: ['eq', 'not', 'gt', 'gte', 'lt', 'lte', 'between']
16 | }
17 | }
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/interval.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | // TODO: V2.1. probably use https://github.com/bendrucker/postgres-interval
3 | mixinCallback: function() {
4 | this.addType(
5 | 'interval',
6 | function(value) {
7 | if (value === null) return null
8 | if (value === undefined) return null
9 | return value.toString()
10 | },
11 | {
12 | migration: ['interval'],
13 | operators: {
14 | defaults: ['eq', 'not']
15 | }
16 | }
17 | )
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/stores/rest/collection.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.definition = {
5 | mixinCallback: function() {
6 | this.afterFind(function(data) {
7 | var records = data.result || []
8 |
9 | if (!Array.isArray(records)) {
10 | records = records[this.rootParam || 'data'] || []
11 | }
12 |
13 | if (records && !Array.isArray(records)) {
14 | records = [records] // Every result is an array...
15 | }
16 |
17 | data.result = records
18 |
19 | return true
20 | }, 100)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/stores/sql/data_types/integer.js:
--------------------------------------------------------------------------------
1 | const validator = require('validator')
2 |
3 | exports.store = {
4 | mixinCallback: function() {
5 | this.addType(
6 | 'integer',
7 | function(value) {
8 | if (value === null) return null
9 | if (value === '') return null
10 | return validator.toInt(value + '')
11 | },
12 | {
13 | migration: 'integer',
14 | operators: {
15 | defaults: ['eq', 'not', 'gt', 'gte', 'lt', 'lte', 'between']
16 | }
17 | }
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/box.js:
--------------------------------------------------------------------------------
1 | const geom = require('./_geom')
2 |
3 | exports.store = {
4 | mixinCallback: function() {
5 | this.addType(
6 | 'box',
7 | {
8 | read: function(value) {
9 | return geom.convertPoints(value)
10 | },
11 |
12 | write: function(value) {
13 | if(Array.isArray(value)) return geom.pointsToString(value)
14 | return value
15 | }
16 | },
17 | {
18 | array: true,
19 | migration: ['box']
20 | }
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/fixtures/stores/webpack-sqlite3.js:
--------------------------------------------------------------------------------
1 | var Store
2 | try {
3 | Store = require('openrecord/store/sqlite3') // to simulate tests from the outside world
4 | require('openrecord/lib/base/dynamic_loading')
5 | } catch (e) {
6 | Store = require('../../../store/sqlite3')
7 | }
8 |
9 | module.exports = function(database, autoAttributes) {
10 | const store = new Store({
11 | file: database,
12 | autoAttributes: autoAttributes
13 | })
14 |
15 | store.Model('user', function() {})
16 | store.Model('post', function() {})
17 |
18 | return store
19 | }
20 |
--------------------------------------------------------------------------------
/lib/stores/ldap/search_root.js:
--------------------------------------------------------------------------------
1 | exports.model = {
2 | searchRoot: function(root, recursive) {
3 | var self = this.chain()
4 | self.setInternal('search_root', root)
5 |
6 | self.recursive(recursive === true)
7 |
8 | return self
9 | }
10 | }
11 |
12 | exports.definition = {
13 | mixinCallback: function() {
14 | var self = this
15 |
16 | this.beforeFind(function(options) {
17 | options.root = self.store.utils.normalizeDn(
18 | this.getInternal('search_root') || self.store.config.base
19 | )
20 | }, 80)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/stores/sql/convert.js:
--------------------------------------------------------------------------------
1 | exports.chain = {
2 | convertConditionAttribute: function(attribute, parentRelations) {
3 | parentRelations = parentRelations ||
4 | this.getInternal('parent_relations') || [this.definition.tableName]
5 | return this.definition.store.utils.toAttributeName(
6 | attribute,
7 | parentRelations
8 | )
9 | },
10 |
11 | escapeAttribute: function(attribute) {
12 | const query = this.query()
13 | return query.client.formatter(query).wrapString(attribute) // escape attribute. from `user.id` to `"user"."id"`
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/fixtures/stores/webpack-sqlite3-migrations.js:
--------------------------------------------------------------------------------
1 | var Store
2 | try {
3 | Store = require('openrecord/store/sqlite3') // to simulate tests from the outside world
4 | } catch (e) {
5 | Store = require('../../../store/sqlite3')
6 | }
7 |
8 | module.exports = function(database, autoAttributes) {
9 | const store = new Store({
10 | file: database,
11 | autoAttributes: autoAttributes,
12 | migrations: [
13 | require('../migrations/20140223120815_create_users')
14 | ]
15 | })
16 |
17 | store.Model('User', function() {})
18 |
19 | return store
20 | }
21 |
--------------------------------------------------------------------------------
/lib/stores/ldap/limit.js:
--------------------------------------------------------------------------------
1 | const ldap = require('ldapjs')
2 |
3 | /*
4 | * DEFINITION
5 | */
6 | exports.definition = {
7 | mixinCallback: function() {
8 | this.beforeFind(function(options) {
9 | var limit = this.getInternal('limit')
10 | // var offset = this.getInternal('offset') // not possible in ldap ?!
11 |
12 | if (typeof limit !== 'number') limit = 100
13 | options.controls = options.controls || []
14 | options.controls.push(
15 | new ldap.PagedResultsControl({ value: { size: limit } })
16 | )
17 | }, -40)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/enum.js:
--------------------------------------------------------------------------------
1 | exports.migration = {
2 | enum: function(name, values) {
3 | if (Array.isArray(values)) {
4 | var enumValues = values
5 | .map(function(v) {
6 | return `'${v}'`
7 | })
8 | .join(', ')
9 | this.raw(`CREATE TYPE ${name} AS ENUM (${enumValues})`)
10 | } else {
11 | var options = values || {}
12 | options.custom = function(table) {
13 | return table.enum(name, options.values, options)
14 | }
15 | this._addColumnTypeFn('enum').apply(this, arguments)
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20190214220159_create_migration_helpers.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('migration_helpers', function() {
3 | this.string('string_attr', 42) // includes string len
4 | this.string('string_attr_def', 69, { default: 'hrm' }) // adds default value
5 | this.string('req_string_attr!', 13, { default: 'nice' }) // adds not null
6 | this.id('foreign_id') // id is an integer
7 | this.id('req_foreign_id!') // adds not null
8 | this.index('string_attr') // adds index
9 | this.unique('req_string_attr') // adds unique
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/lib/base/relations/has_one.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | hasOne: function(name, options) {
3 | options = options || {}
4 |
5 | options.initialize = function() {
6 | this.callParent()
7 | }
8 |
9 | // hasMany relation only returns a single record!
10 | options.transform =
11 | options.transform ||
12 | function(result) {
13 | if (!result) return
14 | return result[0] || null
15 | }
16 |
17 | const result = this.hasMany(name, options)
18 |
19 | options.type = 'has_one' // overwrite type!
20 |
21 | return result
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/stores/sql/raw.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.model = {
5 | /**
6 | * execute raw sql
7 | * @class Model
8 | * @method raw
9 | * @param {string} sql - The raw sql query.
10 | * @param {array} attrs - Query attributes.
11 | * @param {function} callback - The callback.
12 | *
13 | * @see Model.exec
14 | *
15 | * @return {Model}
16 | */
17 | raw: function(sql, attrs, callback) {
18 | var promise = this.definition.store.connection.raw(sql, attrs)
19 |
20 | if (typeof callback === 'function') {
21 | promise.then(callback)
22 | }
23 |
24 | return promise
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/base/mixin.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | /**
3 | * mixin a module to extend your model definition.
4 | * The module needs to export either a function, which will be called with the definition scope
5 | * or an objects which will be mixed into the defintion object.
6 | *
7 | * @class Definition
8 | * @method mixin
9 | * @param {(object|function)} module - the module
10 | *
11 | * @return {Definition}
12 | */
13 | mixin: function(module) {
14 | if (typeof module === 'function') {
15 | module.call(this)
16 | } else {
17 | this.include(module)
18 | }
19 | return this
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/stores/ldap/dn.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | mixinCallback: function() {
3 | this.rdn_prefix = 'cn'
4 | },
5 |
6 | /**
7 | * Set the rdn prefix
8 | *
9 | * @public
10 | * @memberof LDAP.Definition
11 | * @param {string} prefix - The prefix
12 | *
13 | * @return {Definition}
14 | */
15 | rdnPrefix: function(prefix) {
16 | this.rdn_prefix = prefix
17 | return this
18 | },
19 |
20 | dn: function(record) {
21 | if (!record[this.rdn_prefix] && record.dn) return record.dn
22 | return (
23 | this.rdn_prefix + '=' + record[this.rdn_prefix] + ',' + record.parent_dn
24 | )
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test/base/base-model-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../store/base')
2 |
3 | describe('Attributes', function() {
4 | var store = new Store()
5 |
6 | store.Model('User', function() {
7 | this.attribute('my_str', String, {})
8 | })
9 |
10 | before(function() {
11 | return store.ready()
12 | })
13 |
14 | describe('store instance', function() {
15 | it('has BaseModel', function() {
16 | store.BaseModel.should.be.a.Function()
17 | })
18 | })
19 |
20 | describe('Store constructor', function() {
21 | it('has BaseModel', function() {
22 | Store.BaseModel.should.be.a.Function()
23 | })
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/test/fixtures/stores/webpack-postgres.js:
--------------------------------------------------------------------------------
1 | var Store
2 | try {
3 | Store = require('openrecord/store/postgres') // to simulate tests from the outside world
4 | } catch (e) {
5 | Store = require('../../../store/postgres')
6 | }
7 |
8 | module.exports = function(database, autoAttributes) {
9 | const store = new Store({
10 | host: 'localhost',
11 | type: 'postgres',
12 | database: database,
13 | user: 'postgres',
14 | password: '',
15 | autoAttributes: autoAttributes,
16 | autoConnect: false
17 | })
18 |
19 | store.Model('user', function() {})
20 | store.Model('post', function() {})
21 |
22 | return store
23 | }
24 |
--------------------------------------------------------------------------------
/lib/stores/oracle/autoload.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.use(function() {
4 | if (this.config.autoLoad) return this.loadAllTables()
5 | }, 100)
6 | },
7 |
8 | loadAllTables: function() {
9 | const self = this
10 | const sql = 'SELECT table_name FROM user_tables'
11 |
12 | return this.connection.raw(sql).then(function(tableNames) {
13 | tableNames.forEach(function(row) {
14 | var tableName = row.TABLE_NAME
15 | self.Model(self.utils.getModelName(tableName), function() {
16 | // Empty model definition!
17 | })
18 | })
19 | })
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/stores/sqlite3/autoload.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.use(function() {
4 | if (this.config.autoLoad) return this.loadAllTables()
5 | }, 100)
6 | },
7 |
8 | loadAllTables: function() {
9 | const self = this
10 | const sql = "SELECT name FROM sqlite_master WHERE type='table'"
11 |
12 | return this.connection.raw(sql).then(function(tableNames) {
13 | tableNames.forEach(function(row) {
14 | var tableName = row.name
15 | self.Model(self.utils.getModelName(tableName), function() {
16 | // Empty model definition!
17 | })
18 | })
19 | })
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./_array'),
3 | require('./boolean_array'),
4 | require('./composite'),
5 | require('./date_array'),
6 | require('./datetime_array'),
7 | require('./float_array'),
8 | require('./hstore'),
9 | require('./integer_array'),
10 | require('./json'),
11 | require('./string_array'),
12 | require('./time_array'),
13 | require('./uuid'),
14 | require('./interval'),
15 | require('./tsvector'),
16 | require('./point'),
17 | require('./line'),
18 | require('./path'),
19 | require('./box'),
20 | require('./polygon'),
21 | require('./circle'),
22 | require('./enum')
23 | ]
24 |
--------------------------------------------------------------------------------
/lib/stores/sql/data_types/boolean.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType(
4 | 'boolean',
5 | function(value) {
6 | if (value === null) return null
7 | if (value === 'f') return false
8 | if (value === '0') return false
9 | if (value === 'false') return false
10 | if (value === '') return false
11 | if (value === 0) return false
12 | if (value === false) return false
13 | return true
14 | },
15 | {
16 | migration: 'boolean',
17 | operators: {
18 | defaults: ['eq', 'not']
19 | }
20 | }
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/stores/ldap/schema.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | mixinCallback: function() {
3 | this.__isContainer = false
4 | },
5 |
6 | isContainer: function(rdnPrefix) {
7 | if (rdnPrefix) this.rdnPrefix(rdnPrefix)
8 |
9 | this.hasChildren('children', {
10 | from: 'dn',
11 | to: 'parent_dn',
12 | autoSave: true
13 | })
14 |
15 | this.hasChildren('all_children', {
16 | from: 'dn',
17 | to: 'parent_dn',
18 | recursive: true,
19 | autoSave: true
20 | })
21 |
22 | this.hasParent('parent', { model: this.modelName, autoSave: true })
23 |
24 | this.__isContainer = true
25 |
26 | return this
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/stores/ldap/conditions.js:
--------------------------------------------------------------------------------
1 | const parseDN = require('ldapjs').parseDN
2 |
3 | exports.model = {
4 | find: function(dn) {
5 | var self = this.chain()
6 |
7 | if (typeof dn === 'string') {
8 | try {
9 | parseDN(dn) // will throw an error if no valid dn given. Unfortunately there is no ldapjs function to check if a dn is valid...
10 | self.searchRoot(dn)
11 | self.searchScope('base')
12 | return self
13 | } catch (e) {
14 | // continue with normal find...
15 | }
16 | }
17 |
18 | return self.callParent.apply(
19 | self,
20 | this.definition.store.utils.args(arguments)
21 | )
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/stores/rest/connection.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios')
2 |
3 | /*
4 | * STORE
5 | */
6 | exports.store = {
7 | mixinCallback: function() {
8 | this.connection = axios.create(
9 | Object.assign(
10 | {
11 | baseURL: this.config.baseUrl || this.config.url
12 | },
13 | this.config
14 | )
15 | )
16 | },
17 |
18 | close: function(callback) {
19 | callback()
20 | }
21 | }
22 |
23 | /*
24 | * CHAIN
25 | */
26 | exports.model = {
27 | mixinCallback: function() {
28 | var self = this
29 | this.__defineGetter__('connection', function() {
30 | return self.definition.store.connection
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/stores/sql/data_types/datetime.js:
--------------------------------------------------------------------------------
1 | const parse = require('date-fns/parse')
2 |
3 | exports.store = {
4 | mixinCallback: function() {
5 | this.addType(
6 | 'datetime',
7 | {
8 | read: function(value) {
9 | return value
10 | },
11 | write: function(value) {
12 | return value
13 | },
14 | output: function(value) {
15 | if (value === null) return null
16 | return parse(value)
17 | }
18 | },
19 | {
20 | migration: 'datetime',
21 | operators: {
22 | defaults: ['eq', 'not', 'gt', 'gte', 'lt', 'lte', 'between']
23 | }
24 | }
25 | )
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/stores/sql/query.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.definition = {
5 | query: function(options) {
6 | if (!options) options = {}
7 | var connection = options.transaction || this.store.connection
8 |
9 | return connection(this.tableName)
10 | }
11 | }
12 |
13 | exports.chain = {
14 | query: function(options) {
15 | if (!options) options = {}
16 |
17 | var connection = options.transaction || this.definition.store.connection
18 |
19 | var query = this.getInternal('query')
20 | if (!query || options.force) {
21 | query = connection(this.definition.tableName)
22 | this.setInternal('query', query)
23 | }
24 |
25 | return query
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/polygon.js:
--------------------------------------------------------------------------------
1 | const geom = require('./_geom')
2 |
3 | exports.store = {
4 | mixinCallback: function() {
5 | this.addType(
6 | 'polygon',
7 | {
8 | read: function(value) {
9 | if(Array.isArray(value)) return value
10 | if(typeof value === 'string') return geom.convertPoints(value.replace(/(^\(|\)$)/g, ''))
11 | return null
12 | },
13 |
14 | write: function(value) {
15 | if(Array.isArray(value)) return '(' + geom.pointsToString(value) + ')'
16 | return value
17 | }
18 | },
19 | {
20 | array: true,
21 | migration: ['polygon']
22 | }
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/stores/rest/exec.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('openrecord:exec')
2 |
3 | exports.definition = {
4 | mixinCallback: function() {
5 | var self = this
6 |
7 | this.onFind(function(options, data) {
8 | self.store.utils.applyParams(options)
9 | return this.connection.request(options).then(function(result) {
10 | debug(options.method + ' ' + options.url)
11 | data.result = result.data
12 | })
13 | })
14 | }
15 | }
16 |
17 | /*
18 | * MODEL
19 | */
20 | exports.model = {
21 | getExecOptions: function() {
22 | var action = this.getInternal('action') || 'index'
23 | return this.definition.store.utils.clone(this.definition.actions[action])
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/test/fixtures/migrations/20140225144123_create_attribute_tests.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | this.createTable('attribute_tests', function() {
3 | var self = this
4 |
5 | this.run(function() {
6 | self.string('string_attr')
7 | })
8 |
9 | this.text('text_attr')
10 | this.integer('integer_attr')
11 | this.float('float_attr')
12 | this.boolean('boolean_attr')
13 | this.binary('binary_attr')
14 | this.date('date_attr')
15 | this.datetime('datetime_attr')
16 | this.time('time_attr')
17 |
18 | this.string('with_default_text', { default: 'foo' })
19 | this.integer('with_default_integer', { default: 55 })
20 | this.boolean('with_default_boolean', { default: true })
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/lib/stores/ldap/data_types/object_class.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType(
4 | 'object_class',
5 | {
6 | read: function(value) {
7 | /* istanbul ignore if */
8 | if (value === null) return null
9 | if (!Array.isArray(value)) value = value.split(',')
10 |
11 | return value
12 | },
13 | write: function(value) {
14 | /* istanbul ignore if */
15 | if (value === null) return null
16 | return value
17 | }
18 | },
19 | {
20 | array: true,
21 | operators: {
22 | default: 'eq',
23 | defaults: ['eq', 'not']
24 | }
25 | }
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/stores/mysql/autoload.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.use(function() {
4 | if (this.config.autoLoad) return this.loadAllTables()
5 | }, 100)
6 | },
7 |
8 | loadAllTables: function() {
9 | const self = this
10 | const sql =
11 | "select TABLE_NAME from information_schema.tables WHERE TABLE_SCHEMA='" +
12 | this.config.database +
13 | "'"
14 |
15 | return this.connection.raw(sql).then(function(tableNames) {
16 | tableNames[0].forEach(function(row) {
17 | var tableName = row.TABLE_NAME
18 | self.Model(self.utils.getModelName(tableName), function() {
19 | // Empty model definition!
20 | })
21 | })
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/stores/sql/data_types/binary.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType(
4 | 'binary',
5 | {
6 | read: function(value) {
7 | if (value === null) return null
8 |
9 | if (Buffer.from) return Buffer.from(value, 'binary')
10 | return new Buffer(value, 'binary') // eslint-disable-line node/no-deprecated-api
11 | },
12 | write: function(buffer) {
13 | if (buffer === null) return null
14 | return buffer.toString('binary')
15 | }
16 | },
17 | {
18 | migration: 'binary',
19 | extend: Buffer,
20 | operators: {
21 | defaults: ['eq', 'not']
22 | }
23 | }
24 | )
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/stores/ldap/search_scope.js:
--------------------------------------------------------------------------------
1 | exports.model = {
2 | searchScope: function(scope) {
3 | var self = this.chain()
4 |
5 | self.setInternal('search_scope', scope)
6 |
7 | if (scope === 'base') {
8 | self.singleResult(false)
9 | } else {
10 | self.clearInternal('single_result')
11 | }
12 |
13 | return self
14 | },
15 |
16 | recursive: function(recursiv) {
17 | const self = this.limit(null)
18 | if (recursiv === false) return self.searchScope('one')
19 | return self.searchScope('sub')
20 | }
21 | }
22 |
23 | exports.definition = {
24 | mixinCallback: function() {
25 | this.beforeFind(function(options) {
26 | options.scope = this.getInternal('search_scope') || 'sub'
27 | }, 90)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/base/parent.js:
--------------------------------------------------------------------------------
1 | exports.store = exports.definition = exports.model = exports.record = exports.utils = {
2 | callParent: function callParent() {
3 | var Utils = this // utils itself
4 | if (this.utils) Utils = this.utils // store
5 | if (this.store) Utils = this.store.utils // definition
6 | if (this.definition) Utils = this.definition.store.utils // record, model
7 |
8 | var parentFn = callParent.caller._parent
9 | if (typeof parentFn === 'function' && callParent.caller !== parentFn) {
10 | return parentFn.apply(this, Utils.args(arguments))
11 | }
12 | var lastArg = arguments[arguments.length - 1]
13 | if (lastArg && lastArg._parent) {
14 | return lastArg._parent.apply(this, Utils.args(arguments))
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/stores/sql/plugins/paranoid.js:
--------------------------------------------------------------------------------
1 | exports.migration = {
2 | paranoid: function() {
3 | this.datetime('deleted_at')
4 | this.integer('deleter_id')
5 | }
6 | }
7 |
8 | exports.definition = {
9 | paranoid: function() {
10 | var self = this
11 |
12 | this.scope('withDeleted', function() {
13 | this.setInternal('withDeleted', true)
14 | })
15 |
16 | this.beforeFind(function() {
17 | var withDeleted = this.getInternal('withDeleted') || false
18 |
19 | if (!withDeleted && self.attributes.deleted_at) {
20 | this.where({ deleted_at: null })
21 | }
22 | })
23 |
24 | this.destroy = function(options) {
25 | this.deleted_at = new Date()
26 | return this.save(options)
27 | }
28 |
29 | return this
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/sql/__shared/exec-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../store')
2 |
3 | module.exports = function(title, beforeFn, afterFn, storeConf) {
4 | describe(title + ': Exec', function() {
5 | var store
6 |
7 | before(beforeFn)
8 | after(function(next) {
9 | afterFn(next, store)
10 | })
11 |
12 | before(function() {
13 | store = new Store(storeConf)
14 |
15 | store.Model('User', function() {})
16 | })
17 |
18 | it('throws an error on unknown table', function() {
19 | return store
20 | .ready(function() {
21 | var User = store.Model('User')
22 | return User.where({ login_like: 'phi' }).exec()
23 | })
24 | .should.be.rejectedWith(store.Error) // TODO: custom error!!
25 | })
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/test/base/methods-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 |
3 | var Store = require('../../store/base')
4 |
5 | describe('Methods', function() {
6 | var store = new Store()
7 |
8 | store.Model('User', function() {
9 | this.my_method = function() {
10 | this.should.have.property('attributes')
11 | this.should.have.property('errors')
12 | }
13 | })
14 |
15 | var User, phil
16 |
17 | before(function() {
18 | return store.ready(function() {
19 | User = store.Model('User')
20 | phil = new User()
21 | })
22 | })
23 |
24 | it('is defined', function() {
25 | should.exist(phil.my_method)
26 | phil.my_method.should.be.a.Function()
27 | })
28 |
29 | it('has the right context', function() {
30 | phil.my_method()
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // http://eslint.org/docs/user-guide/configuring
2 |
3 | module.exports = {
4 | root: true,
5 | extends: [
6 | 'digitalbits',
7 | 'prettier'
8 | ],
9 |
10 | env: {
11 | node: true,
12 | mocha: true
13 | },
14 |
15 | globals: {
16 | testMYSQL: true,
17 | afterMYSQL: true,
18 | beforeMYSQL: true,
19 | beforePG: true,
20 | afterPG: true,
21 | testPG: true,
22 | beforeSQLite: true,
23 | afterSQLite: true,
24 | testSQLite: true,
25 | testOracle: true,
26 | afterOracle: true,
27 | beforeOracle: true,
28 | getOracleConfig: true,
29 | beforeActiveDirectory: true,
30 | afterActiveDirectory: true,
31 | testActiveDirectory: true,
32 | beforeGraphQL: true,
33 | afterGraphQL: true,
34 | LDAP_BASE: true
35 | }
36 | }
--------------------------------------------------------------------------------
/lib/stores/postgres/autoload.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.use(function() {
4 | if (this.config.autoLoad) return this.loadAllTables()
5 | }, 100)
6 | },
7 |
8 | loadAllTables: function() {
9 | const self = this
10 | var schema = 'public'
11 | if (typeof this.config.autoLoad === 'string') schema = this.config.autoLoad
12 |
13 | const sql =
14 | "select table_name from information_schema.tables WHERE table_schema ='" +
15 | schema +
16 | "';"
17 |
18 | return this.connection.raw(sql).then(function(tableNames) {
19 | tableNames.rows.forEach(function(row) {
20 | self.Model(self.utils.getModelName(row.table_name), function() {
21 | // Empty model definition!
22 | })
23 | })
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/stores/rest/resource.js:
--------------------------------------------------------------------------------
1 | const inflection = require('inflection')
2 |
3 | /*
4 | * DEFINITION
5 | */
6 | exports.definition = {
7 | mixinCallback: function() {
8 | this.resource = inflection.underscore(inflection.pluralize(this.modelName))
9 |
10 | if (
11 | this.store.config.inflection &&
12 | this.store.config.inflection[this.resource]
13 | ) {
14 | this.resource = this.store.config.inflection[this.resource]
15 | }
16 | },
17 |
18 | getName: function() {
19 | return this.resource
20 | }
21 | }
22 |
23 | /*
24 | * STORE
25 | */
26 | exports.store = {
27 | getByResource: function(resource) {
28 | for (var i in this.models) {
29 | if (this.models[i].definition.resource === resource) {
30 | return this.models[i]
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/stores/sql/plugins/total_count.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | mixinCallback: function() {
3 | var self = this
4 |
5 | this.scope(
6 | 'totalCount',
7 | function(field) {
8 | const through = this.getInternal('through')
9 | let tableName = self.getName()
10 |
11 | if (through) {
12 | tableName = through[through.length - 1]
13 | }
14 |
15 | if (typeof field !== 'string') field = null
16 | var key = field || self.primaryKeys[0] || '*'
17 |
18 | this.count(
19 | this.escapeAttribute(
20 | self.store.utils.toAttributeName(key, [tableName])
21 | ),
22 | true
23 | )
24 | .limit()
25 | .offset()
26 | .order(null)
27 | },
28 | true
29 | )
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/rest/client/destroy-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../lib/store')
2 |
3 | describe('REST Client: Destroy', function() {
4 | var store
5 |
6 | before(function() {
7 | store = new Store({
8 | type: 'rest',
9 | url: 'http://localhost:8889',
10 | version: '~1.0'
11 | })
12 |
13 | store.Model('User', function() {
14 | this.attribute('id', Number, { primary: true })
15 | this.attribute('login', String)
16 | this.attribute('email', String)
17 | })
18 | })
19 |
20 | it('destroys a record (destroy)', function() {
21 | return store.ready(function() {
22 | var User = store.Model('User')
23 | return User.find(3).exec(function(record) {
24 | record.id.should.be.equal(3)
25 | return record.destroy()
26 | })
27 | })
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/lib/stores/ldap/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./dn'),
3 | require('./schema'),
4 | require('./attributes'),
5 | require('./conditions'),
6 | require('./connection'),
7 | require('./destroy'),
8 | require('./errors'),
9 | require('./exec'),
10 | require('./limit'),
11 | require('./object_class'),
12 | require('./relations'),
13 | require('./save'),
14 | require('./search_root'),
15 | require('./search_scope'),
16 | require('./select'),
17 | require('./utils'),
18 | require('./data_types/_operators'),
19 | require('./data_types/dn_array'),
20 | require('./data_types/dn'),
21 | require('./data_types/object_class'),
22 | require('./relations/belongs_to_many'),
23 | require('./relations/has_children'),
24 | require('./relations/has_many'),
25 | require('./relations/has_parent')
26 | ]
27 |
--------------------------------------------------------------------------------
/lib/stores/sql/table_name.js:
--------------------------------------------------------------------------------
1 | const inflection = require('inflection')
2 |
3 | /*
4 | * DEFINITION
5 | */
6 | exports.definition = {
7 | mixinCallback: function() {
8 | this.tableName = inflection.underscore(inflection.pluralize(this.modelName))
9 |
10 | if (
11 | this.store.config.inflection &&
12 | this.store.config.inflection[this.tableName]
13 | ) {
14 | this.tableName = this.store.config.inflection[this.tableName]
15 | }
16 | },
17 |
18 | getName: function() {
19 | return this.tableName
20 | }
21 | }
22 |
23 | /*
24 | * STORE
25 | */
26 | exports.store = {
27 | getByTableName: function(tableName) {
28 | for (var i in this.models) {
29 | if (this.models[i].definition.tableName === tableName) {
30 | return this.models[i]
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/base/cache.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.definition = {
5 | mixinCallback: function() {
6 | this.cache = this.store.getCache(this.modelName) || {}
7 | if (this.cacheDisabled !== true)
8 | this.store.setCache(this.modelName, this.cache)
9 | },
10 |
11 | getCache: function(key) {
12 | return this.store.utils.clone(this.cache[key])
13 | },
14 |
15 | setCache: function(key, cache) {
16 | this.cache[key] = this.store.utils.clone(cache)
17 | }
18 | }
19 |
20 | /*
21 | * STORE
22 | */
23 | exports.store = {
24 | getCache: function(modelName) {
25 | if (this.cache && this.cache[modelName]) {
26 | return this.cache[modelName]
27 | }
28 | },
29 |
30 | setCache: function(modelName, cache) {
31 | if (!this.cache) this.cache = {}
32 | this.cache[modelName] = cache
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/stores/ldap/relations.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | relation: function(name, options) {
3 | options = options || {}
4 | const utils = this.store.utils
5 |
6 | options.afterDestroy = function(parent, transOptions) {
7 | if (options.dependent === 'destroy') {
8 | return parent[name].then(function(records) {
9 | const jobs = []
10 | records.forEach(function(record) {
11 | jobs.push(function() {
12 | return record.destroy(transOptions)
13 | })
14 | })
15 | return utils.parallel(jobs)
16 | })
17 | }
18 | }
19 |
20 | this.callParent(name, options)
21 | },
22 |
23 | belongsTo: function(name, options) {
24 | options = options || {}
25 | if (!options.to) options.to = 'dn'
26 | return this.callParent(name, options)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/stores/mysql/connection.js:
--------------------------------------------------------------------------------
1 | const Knex = require('knex')
2 |
3 | /*
4 | * STORE
5 | */
6 | exports.store = {
7 | connect: function() {
8 | this.connection = Knex({
9 | client: 'mysql',
10 | version: this.config.version || '5.7',
11 | connection: this.config.connection || {
12 | socketPath: this.config.socketPath,
13 | host: this.config.host || this.config.hostname,
14 | port: this.config.port,
15 | user: this.config.user || this.config.username,
16 | password: this.config.password,
17 | database: this.config.database,
18 | charset: this.config.charset
19 | }
20 | })
21 | this.supportsReturning = true
22 | },
23 |
24 | close: function(callback) {
25 | if (!this.connection) return callback ? callback() : null
26 | this.connection.client.destroy(callback)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/test/record-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 |
3 | var Store = require('../lib/store')
4 |
5 | describe('Record: Base', function() {
6 | var store = new Store()
7 |
8 | store.Model('User', function() {})
9 | var User, phil
10 |
11 | before(function() {
12 | return store.ready(function() {
13 | User = store.Model('User')
14 | phil = new User({
15 | email: 'phiw@gmx.net'
16 | })
17 | })
18 | })
19 |
20 | it('is a object', function() {
21 | phil.should.be.a.Object()
22 | })
23 |
24 | it('has validate() mixin method', function() {
25 | should.exist(phil.validate)
26 | phil.validate.should.be.a.Function()
27 | })
28 |
29 | it('has definition', function() {
30 | should.exist(phil.definition)
31 | })
32 |
33 | it('has correct model name', function() {
34 | phil.model.should.be.equal(User)
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/lib/stores/rest/operators.js:
--------------------------------------------------------------------------------
1 | /*
2 | * STORE
3 | */
4 | exports.store = {
5 | mixinCallback: function() {
6 | this.addOperator(
7 | 'eq',
8 | function(attr, value, options, cond) {
9 | options.params[attr] = value
10 | },
11 | {
12 | default: true,
13 | nullifyEmptyArray: true
14 | }
15 | )
16 |
17 | this.attributeTypes[String].operators = {
18 | default: 'eq',
19 | eq: this.operatorTypes['eq']
20 | }
21 |
22 | this.attributeTypes[Number].operators = {
23 | default: 'eq',
24 | eq: this.operatorTypes['eq']
25 | }
26 |
27 | this.attributeTypes[Date].operators = {
28 | default: 'eq',
29 | eq: this.operatorTypes['eq']
30 | }
31 |
32 | this.attributeTypes[Boolean].operators = {
33 | default: 'eq',
34 | eq: this.operatorTypes['eq']
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/stores/activedirectory/schema/container.js:
--------------------------------------------------------------------------------
1 | /* istanbul ignore next: unable to test via travis-ci */
2 | exports.store = {
3 | mixinCallback: function() {
4 | this.Model('Container', function() {
5 | this.isContainer('cn')
6 | this.objectClass = ['top', 'container']
7 |
8 | this.attribute('name', String)
9 | this.attribute('cn', String)
10 | this.attribute('description', String)
11 | this.attribute('objectGUID', 'guid', { persistent: false })
12 | this.attribute('uSNChanged', Number, { persistent: false })
13 | this.attribute('whenChanged', 'date', { persistent: false })
14 | this.attribute('whenCreated', 'date', { persistent: false })
15 |
16 | this.validatesPresenceOf('name')
17 |
18 | this.convertWrite('cn', function(cn) {
19 | if (!cn) cn = this.name
20 | return cn
21 | })
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/stores/ldap/connection.js:
--------------------------------------------------------------------------------
1 | const ldap = require('ldapjs')
2 |
3 | /*
4 | * STORE
5 | */
6 | exports.store = {
7 | ldapjs: ldap,
8 |
9 | connect: function() {
10 | this.connection = ldap.createClient(
11 | this.config.connection || {
12 | url: this.config.url,
13 | bindDN: this.config.user,
14 | bindCredentials: this.config.password,
15 | maxConnections: this.config.maxConnections || 10,
16 | tlsOptions: this.config.tlsOptions,
17 | reconnect: this.config.reconnect || true
18 | }
19 | )
20 | },
21 |
22 | close: function() {
23 | this.connection.unbind()
24 | }
25 | }
26 |
27 | /*
28 | * CHAIN
29 | */
30 | exports.model = {
31 | mixinCallback: function() {
32 | var self = this
33 | this.__defineGetter__('connection', function() {
34 | return self.definition.store.connection
35 | })
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/stores/sql/data_types/date.js:
--------------------------------------------------------------------------------
1 | const parse = require('date-fns/parse')
2 | const format = require('date-fns/format')
3 |
4 | exports.store = {
5 | mixinCallback: function() {
6 | this.addType(
7 | 'date',
8 | {
9 | read: function(value) {
10 | if (value === null) return null
11 | return format(parse(value), 'YYYY-MM-DD')
12 | },
13 | input: function(value) {
14 | if (value === null) return null
15 | return format(parse(value), 'YYYY-MM-DD')
16 | },
17 | write: function(value) {
18 | return value
19 | },
20 | output: function(value) {
21 | return value
22 | }
23 | },
24 | {
25 | migration: 'date',
26 | operators: {
27 | defaults: ['eq', 'not', 'gt', 'gte', 'lt', 'lte', 'between']
28 | }
29 | }
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | openrecord - Active record like ORM for nodejs
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/lib/base/connection.js:
--------------------------------------------------------------------------------
1 | /*
2 | * STORE
3 | */
4 | exports.store = {
5 | mixinCallback: function() {
6 | var self = this
7 | var connection
8 | var connectCalled = false
9 |
10 | // define setter and getter to manage the connection
11 | Object.defineProperty(this, 'connection', {
12 | enumerable: false,
13 | set: function(_connection) {
14 | if (connection) self.close() // close old connection before we overwrite it with a new one
15 | connection = _connection
16 | },
17 |
18 | get: function() {
19 | if (!connection && !connectCalled) {
20 | self.connect()
21 | connectCalled = true
22 | }
23 | return connection
24 | }
25 | })
26 |
27 | if (this.config.autoConnect !== false) {
28 | this.connect()
29 | }
30 | },
31 |
32 | connect: function() {},
33 |
34 | close: function() {}
35 | }
36 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/_geom.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // convert '(1,2)' to {x:1, y:2}
3 | convertPoint(str){
4 | if(typeof str !== 'string') return null
5 | const match = str.match(/^\(([0-9.]+),([0-9.]+)\)$/)
6 | if(match) return {x: parseFloat(match[1]), y: parseFloat(match[2])}
7 | },
8 |
9 | // convert '(1,2),(4,5)' to [{x:1, y:2},{x: 4, y: 5}]
10 | convertPoints: function(str){
11 | if(typeof str !== 'string') return null
12 | return str.replace(/,(\([0-9.]+,[0-9.]+\))/g, '|$1')
13 | .split('|')
14 | .map(module.exports.convertPoint)
15 | },
16 |
17 | pointToString(point){
18 | if(!point || typeof point !== 'object') return null
19 | return '(' + point.x + ',' + point.y + ')'
20 | },
21 |
22 | pointsToString(points){
23 | if(!points || !Array.isArray(points)) return null
24 | return points.map(module.exports.pointToString).join(',')
25 | }
26 | }
--------------------------------------------------------------------------------
/docs/install.md:
--------------------------------------------------------------------------------
1 | # Install
2 |
3 | In order to use OPENRECORD with your database you need to install `openrecord` **and** the corresponding database package.
4 |
5 |
6 | ## For a Postgres Database
7 | ```
8 | npm i openrecord pg
9 | ```
10 |
11 | ## For a MySQL Database
12 | ```
13 | npm i openrecord mysql
14 | ```
15 |
16 | ## For a SQLite3 Database
17 | ```
18 | npm i openrecord sqlite3
19 | ```
20 |
21 | ## For an Oracle Database (EXPERIMENTAL)
22 | ```
23 | npm i openrecord oracledb
24 | ```
25 |
26 | ## For a REST Backend (EXPERIMENTAL)
27 | ```
28 | npm i openrecord axios
29 | ```
30 |
31 | ## For a LDAP Compatible Backend (e.g. OpenLdap or Microsofts Active Directory)
32 | ```
33 | npm i openrecord ldapjs
34 | ```
35 |
36 |
37 | ## GraphQL
38 |
39 | If you you want to use OPENRECORD with [GraphQL](http://graphql.org/graphql-js/), you'll need to install `graphql` as well.
40 | See [GraphQL with OPENRECORD](graphql.md)
41 |
--------------------------------------------------------------------------------
/lib/stores/oracle/connection.js:
--------------------------------------------------------------------------------
1 | var Knex = require('knex')
2 |
3 | /*
4 | * STORE
5 | */
6 | exports.store = {
7 | connect: function() {
8 | this.connection = Knex({
9 | client: 'oracledb',
10 | connection: this.config.connection || {
11 | host: this.config.host || this.config.hostname,
12 | port: this.config.port,
13 | user: this.config.user || this.config.username,
14 | password: this.config.password,
15 | connectString: this.config.connectString
16 | // fetchAsString: [ 'number', 'clob' ],
17 | // pool: { min: 5, max: 5 }
18 | // database: this.config.database,
19 | // charset: this.config.charset
20 | }
21 | })
22 | this.supportsReturning = true
23 | },
24 |
25 | close: function(callback) {
26 | if (!this.connection) return callback ? callback() : null
27 | this.connection.client.destroy(callback)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/stores/ldap/select.js:
--------------------------------------------------------------------------------
1 | /*
2 | * MODEL
3 | */
4 | exports.model = {
5 | /**
6 | * Specify SQL select fields. Default: *
7 | * @class Model
8 | * @method select
9 | * @param {array} fields - The field names
10 | *
11 | *
12 | * @return {Model}
13 | */
14 | select: function() {
15 | var self = this.chain()
16 |
17 | var args = this.definition.store.utils.args(arguments)
18 | var fields = []
19 | fields = fields.concat.apply(fields, args) // flatten
20 |
21 | self.addInternal('select', fields)
22 |
23 | return self
24 | }
25 | }
26 |
27 | /*
28 | * DEFINITION
29 | */
30 | exports.definition = {
31 | mixinCallback: function() {
32 | // add all attribute names to the search attributes
33 | this.beforeFind(function(options) {
34 | var attributes = this.getInternal('select')
35 | if (attributes) options.attributes = attributes
36 | }, -100)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/stores/ldap/data_types/dn.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | const Utils = this.utils
4 |
5 | this.addType(
6 | 'dn',
7 | {
8 | read: function(value) {
9 | /* istanbul ignore if */
10 | if (value === null) return null
11 | return Utils.normalizeDn(value)
12 | },
13 | input: function(value) {
14 | /* istanbul ignore if */
15 | if (value === null) return null
16 | return Utils.normalizeDn(value, false) // of change the dn on our side, we wont lower case them => writes
17 | },
18 | write: function(value) {
19 | /* istanbul ignore if */
20 | if (value === null) return null
21 | return value
22 | }
23 | },
24 | {
25 | operators: {
26 | default: 'eq',
27 | defaults: ['eq']
28 | }
29 | }
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/test/model-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 |
3 | var Store = require('../lib/store')
4 |
5 | describe('Model: Base', function() {
6 | var store = new Store()
7 |
8 | store.Model('User', function() {})
9 |
10 | var User
11 | before(function() {
12 | return store.ready(function() {
13 | User = store.Model('User')
14 | })
15 | })
16 |
17 | it('is a function', function() {
18 | User.should.be.a.Function()
19 | })
20 |
21 | it('has new() mixin method', function() {
22 | should.exist(User.new)
23 | User.new.should.be.a.Function()
24 | })
25 |
26 | it('has chain() mixin method', function() {
27 | should.exist(User.chain)
28 | User.chain.should.be.a.Function()
29 | })
30 |
31 | it('has definition', function() {
32 | should.exist(User.definition)
33 | })
34 |
35 | it('has correct model name', function() {
36 | User.definition.modelName.should.be.equal('User')
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/lib/stores/ldap/controls/tree_delete.js:
--------------------------------------------------------------------------------
1 | const ldap = require('ldapjs')
2 | const util = require('util')
3 |
4 | var Control = ldap.Control
5 |
6 | /// --- API
7 | /* istanbul ignore next */
8 | function DeleteTreeControl(options) {
9 | if (!options) {
10 | options = {}
11 | }
12 |
13 | options.type = DeleteTreeControl.OID
14 | options.value = null
15 |
16 | Control.call(this, options)
17 | this.value = {}
18 | }
19 |
20 | util.inherits(DeleteTreeControl, Control)
21 | module.exports = DeleteTreeControl
22 |
23 | /* istanbul ignore next */
24 | DeleteTreeControl.prototype.parse = function parse(buffer) {
25 | return true
26 | }
27 |
28 | /* istanbul ignore next */
29 | DeleteTreeControl.prototype._toBer = function(ber) {}
30 |
31 | /* istanbul ignore next */
32 | DeleteTreeControl.prototype._json = function(obj) {
33 | obj.controlValue = this.value
34 | return obj
35 | }
36 |
37 | DeleteTreeControl.OID = '1.2.840.113556.1.4.805'
38 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/line.js:
--------------------------------------------------------------------------------
1 | const geom = require('./_geom')
2 |
3 | exports.store = {
4 | mixinCallback: function() {
5 | this.addType(
6 | 'line',
7 | {
8 | read: function(value) {
9 | if(Array.isArray(value)) return value
10 | if(typeof value === 'string'){
11 | const parts = value
12 | .replace(/(^{|}$)/g, '')
13 | .split(',')
14 | .map(parseFloat)
15 | return {A: parts[0], B: parts[1], C: parts[2]}
16 | }
17 | return null
18 | },
19 |
20 | write: function(value) {
21 | if(Array.isArray(value)) return '[' + geom.pointsToString(value) + ']'
22 | if(typeof value === 'object') return '{' + value.A +',' + value.B + ',' + value.B + '}'
23 | return value
24 | }
25 | },
26 | {
27 | array: true,
28 | migration: ['line']
29 | }
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/base/method.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.definition = {
5 | /**
6 | * Adds a new method to the record
7 | *
8 | * @class Definition
9 | * @method method
10 | * @param {string} name - The name of the method
11 | * @param {function} callback - The function
12 | *
13 | * @return {Definition}
14 | */
15 | method: function(name, fn) {
16 | if (this.attributes[name])
17 | throw new Error(
18 | 'A method with the same name as a attribure is not allowed'
19 | )
20 | this.instanceMethods[name] = fn
21 |
22 | return this
23 | },
24 |
25 | /**
26 | * Adds a new method to the class
27 | *
28 | * @class Definition
29 | * @method method
30 | * @param {string} name - The name of the method
31 | * @param {function} callback - The function
32 | *
33 | * @return {Definition}
34 | */
35 | staticMethod: function(name, fn) {
36 | this.staticMethods[name] = fn
37 |
38 | return this
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/base/methods.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | /**
3 | * Add custom methods to your Model`s Records
4 | * just define a function:
5 | * ```js
6 | * this.function_name = function(){
7 | * //this == Record
8 | * };
9 | * ```
10 | * This will automatically add the new method to your Record
11 | *
12 | * @class Definition
13 | * @name Custom Record Methods
14 | *
15 | */
16 | mixinCallback: function() {
17 | var objKeys = []
18 | var self = this
19 |
20 | this.use(function() {
21 | // get all current property names
22 | objKeys = Object.keys(this)
23 | }, 90)
24 |
25 | this.on('finished', function() {
26 | // an now search for new ones ==> instance methods for our new model class
27 |
28 | Object.keys(self).forEach(function(name) {
29 | if (objKeys.indexOf(name) === -1) {
30 | self.instanceMethods[name] = self[name]
31 | delete self[name]
32 | }
33 | })
34 | })
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/stores/sql/data_types/time.js:
--------------------------------------------------------------------------------
1 | const parse = require('date-fns/parse')
2 | const format = require('date-fns/format')
3 |
4 | exports.store = {
5 | mixinCallback: function() {
6 | this.addType(
7 | 'time',
8 | {
9 | read: function(value) {
10 | return value
11 | },
12 | input: function(value) {
13 | if (value === null) return null
14 | var dt
15 |
16 | if (typeof value === 'string') {
17 | dt = parse('2000-01-01 ' + value)
18 | } else {
19 | dt = parse(value)
20 | }
21 |
22 | return format(dt, 'HH:mm:ss')
23 | },
24 | write: function(value) {
25 | return value
26 | },
27 | output: function(value) {
28 | return value
29 | }
30 | },
31 | {
32 | migration: 'time',
33 | operators: {
34 | defaults: ['eq', 'not', 'gt', 'gte', 'lt', 'lte', 'between']
35 | }
36 | }
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/path.js:
--------------------------------------------------------------------------------
1 | const geom = require('./_geom')
2 |
3 | // TODO: somehow indicate `open` or `closed` paths
4 | // `where the points are the end points of the line segments comprising the path. Square brackets ([]) indicate an open path, while parentheses (()) indicate a closed path. When the outermost parentheses are omitted, as in the third through fifth syntaxes, a closed path is assumed.`
5 | exports.store = {
6 | mixinCallback: function() {
7 | this.addType(
8 | 'path',
9 | {
10 | read: function(value) {
11 | if(Array.isArray(value)) return value
12 | if(typeof value === 'string') return geom.convertPoints(value.replace(/(^(\[|\()|(\]|\))$)/g, ''))
13 | return null
14 | },
15 |
16 | write: function(value) {
17 | if(Array.isArray(value)) return '[' + geom.pointsToString(value) + ']'
18 | return value
19 | }
20 | },
21 | {
22 | array: true,
23 | migration: ['path', 'lseg']
24 | }
25 | )
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/stores/sql/plugins/serialize.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | serialize: function(attribute, serializer) {
3 | serializer = serializer || JSON
4 |
5 | this.convertOutput(
6 | attribute,
7 | function(value) {
8 | if (value === null) return null
9 | if (typeof value === 'object') return value
10 | try {
11 | return serializer.parse(value)
12 | } catch (e) {
13 | throw new Error(
14 | 'Serialize error for attribute "' + attribute + '": ' + e
15 | )
16 | }
17 | },
18 | false
19 | )
20 |
21 | this.convertInput(
22 | attribute,
23 | function(value) {
24 | if (value === null) return null
25 | if (typeof value === 'string') return value
26 | try {
27 | return serializer.stringify(value)
28 | } catch (e) {
29 | throw new Error(
30 | 'Serialize error for attribute "' + attribute + '": ' + e
31 | )
32 | }
33 | },
34 | false
35 | )
36 |
37 | return this
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/stores/ldap/object_class.js:
--------------------------------------------------------------------------------
1 | const inflection = require('inflection')
2 |
3 | /*
4 | * DEFINITION
5 | */
6 | exports.definition = {
7 | mixinCallback: function() {
8 | var self = this
9 |
10 | self.objectClass = inflection.underscore(self.modelName)
11 |
12 | // add objectClass=user filter
13 | self.beforeFind(function() {
14 | if (this.getInternal('without_object_class') !== true) {
15 | this.where({ objectClass: self.getName() }) // e.g. objectClass=user
16 | }
17 | }, -9)
18 | },
19 |
20 | getName: function() {
21 | if (Array.isArray(this.objectClass))
22 | return this.objectClass[this.objectClass.length - 1]
23 | return this.objectClass
24 | }
25 | }
26 |
27 | /*
28 | * STORE
29 | */
30 | exports.store = {
31 | getByObjectClass: function(objectClass) {
32 | if (!objectClass) return null
33 | for (var i in this.models) {
34 | if (
35 | this.models[i].definition.objectClass.toString() ===
36 | objectClass.toString()
37 | ) {
38 | return this.models[i]
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/stores/sql/attributes.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | mixinCallback: function() {
3 | this.use(function() {
4 | var attributes = this.getCache('attributes')
5 | if (attributes) {
6 | return this.setTableAttributes(attributes)
7 | }
8 |
9 | if (!this.tableName) return
10 | if (!this.store.loadTableAttributes) return
11 | if (this.store.config.autoAttributes === false) return
12 |
13 | const self = this
14 |
15 | return this.store
16 | .loadTableAttributes(this.tableName)
17 | .then(function(attributes) {
18 | self.setCache('attributes', attributes)
19 | return self.setTableAttributes(attributes)
20 | })
21 | }, 80)
22 | },
23 |
24 | setTableAttributes: function(attributes) {
25 | var self = this
26 |
27 | attributes.forEach(function(attr) {
28 | self.attribute(attr.name, attr.type, attr.options)
29 | attr.validations.forEach(function(validation) {
30 | self[validation.name].apply(self, [attr.name].concat(validation.args))
31 | })
32 | })
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2017 Philipp Waldmann (@APhilWaldmann)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/lib/stores/postgres/connection.js:
--------------------------------------------------------------------------------
1 | const Knex = require('knex')
2 |
3 | /*
4 | * STORE
5 | */
6 | exports.store = {
7 | connect: function() {
8 | const pg = require('pg')
9 |
10 | // convert e.g. count(*) to integer instead of a string
11 | // see https://github.com/tgriesser/knex/issues/387
12 | pg.types.setTypeParser(20, 'text', parseInt)
13 |
14 | var connectionConfig = {
15 | client: 'pg',
16 | connection: this.config.connection || {
17 | host: this.config.host || this.config.hostname,
18 | port: this.config.port,
19 | user: this.config.user || this.config.username,
20 | password: this.config.password,
21 | database: this.config.database,
22 | charset: this.config.charset
23 | }
24 | }
25 | if (this.config.pool) connectionConfig['pool'] = this.config.pool
26 |
27 | this.connection = Knex(connectionConfig)
28 | this.supportsReturning = true
29 | },
30 |
31 | close: function(callback) {
32 | if (!this.connection) return callback ? callback() : null
33 | this.connection.client.destroy(callback)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/stores/activedirectory/data_types/date.js:
--------------------------------------------------------------------------------
1 | const parse = require('date-fns/parse')
2 | const format = require('date-fns/format')
3 | /* istanbul ignore next: unable to test via travis-ci */
4 | exports.store = {
5 | mixinCallback: function() {
6 | this.addType(
7 | 'date',
8 | {
9 | read: function(value) {
10 | if (value === null) return null
11 | return new Date(
12 | value.replace(
13 | /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(.{2})$/,
14 | '$1.$2.$3 $4:$5:$6.$7'
15 | )
16 | )
17 | },
18 | write: function(value) {
19 | if (value === null) return null
20 | return format(parse(value), 'YYYYMMDDHHmmss.S') + 'Z'
21 | },
22 | output: function(value) {
23 | if (value === null) return null
24 | return format(parse(value), 'YYYY-MM-DD HH:mm:ss')
25 | }
26 | },
27 | {
28 | operators: {
29 | default: 'eq',
30 | defaults: ['eq', 'gt', 'gte', 'lt', 'lte', 'not']
31 | }
32 | }
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | dist: trusty
3 | sudo: required
4 |
5 | env:
6 | global:
7 | - ORACLE_COOKIE=sqldev
8 | - ORACLE_FILE=oracle11g/xe/oracle-xe-11.2.0-1.0.x86_64.rpm.zip
9 | #- ORACLE_HOME=/u01/app/oracle/product/11.2.0/xe
10 | - OCI_LIB_DIR=/u01/app/oracle/product/11.2.0/xe/lib
11 | - LD_LIBRARY_PATH=/u01/app/oracle/product/11.2.0/xe/lib
12 | - ORACLE_SID=XE
13 |
14 |
15 | services:
16 | - mysql
17 | - postgresql
18 |
19 | addons:
20 | postgresql: "9.6"
21 | apt:
22 | packages:
23 | - postgresql-9.6-postgis-2.3
24 |
25 | before_install:
26 | - mysql -e "GRANT ALL PRIVILEGES ON *.* TO 'travis'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;";
27 | # - wget https://raw.githubusercontent.com/Vincit/travis-oracledb-xe/master/accept_the_license_agreement_for_oracledb_xe_11g_and_install.sh
28 | # - bash ./accept_the_license_agreement_for_oracledb_xe_11g_and_install.sh
29 | - export PATH=$ORACLE_HOME/bin/:$PATH
30 | # - sqlplus -V
31 | - psql --version
32 | - mysql --version
33 | # - npm i oracledb
34 |
35 | node_js:
36 | - "10"
37 | - "12"
38 |
39 | after_success: npm run report-coverage
40 |
--------------------------------------------------------------------------------
/lib/stores/rest/urls.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | mixinCallback: function() {
3 | var res = this.resource
4 | var path = this.store.config.path || ''
5 | var baseParams = this.store.config.baseParams
6 |
7 | this.actions = {
8 | index: { method: 'get', url: '/' + path + res, params: baseParams || {} },
9 | show: {
10 | method: 'get',
11 | url: '/' + path + res + '/:id',
12 | params: baseParams || {}
13 | },
14 | create: {
15 | method: 'post',
16 | url: '/' + path + res,
17 | params: baseParams || {}
18 | },
19 | update: {
20 | method: 'put',
21 | url: '/' + path + res + '/:id',
22 | params: baseParams || {}
23 | },
24 | destroy: {
25 | method: 'delete',
26 | url: '/' + path + res + '/:id',
27 | params: baseParams || {}
28 | }
29 | }
30 | },
31 |
32 | addBaseParam: function(name, value) {
33 | for (var action in this.actions) {
34 | if (this.actions.hasOwnProperty(action)) {
35 | this.actions[action].params[name] = value
36 | }
37 | }
38 | return this
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/sql/postgres/raw-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../store/postgres')
2 |
3 | describe('Postgres: Raw Query', function() {
4 | var store
5 | var database = 'raw_test'
6 |
7 | before(function(next) {
8 | this.timeout(5000)
9 | beforePG(
10 | database,
11 | [
12 | 'CREATE TABLE users(id serial primary key, login TEXT NOT NULL, email TEXT)',
13 | "INSERT INTO users(login, email) VALUES('phil', 'phil@mail.com')"
14 | ],
15 | next
16 | )
17 | })
18 |
19 | before(function() {
20 | store = new Store({
21 | host: 'localhost',
22 | type: 'postgres',
23 | database: database,
24 | user: 'postgres',
25 | password: ''
26 | })
27 |
28 | store.Model('user', function() {})
29 | })
30 |
31 | after(function(next) {
32 | afterPG(database, next)
33 | })
34 |
35 | it('raw() runs the raw sql query', function() {
36 | return store.ready(function() {
37 | var User = store.Model('User')
38 |
39 | return User.raw('SELECT COUNT(*) FROM users').then(function(result) {
40 | result.rows.should.be.eql([{ count: 1 }])
41 | })
42 | })
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/lib/stores/oracle/data_types/date.js:
--------------------------------------------------------------------------------
1 | const parse = require('date-fns/parse')
2 | const format = require('date-fns/format')
3 |
4 | exports.store = {
5 | mixinCallback: function() {
6 | this.addType(
7 | 'date',
8 | {
9 | read: function(value) {
10 | if (value === null) return null
11 | return format(parse(value), 'YYYY-MM-DD')
12 | },
13 | input: function(value) {
14 | if (value === null) return null
15 | return format(parse(value), 'YYYY-MM-DD')
16 | },
17 | write: function(value) {
18 | return new Date(value)
19 | },
20 | output: function(value) {
21 | return value
22 | },
23 | condition: function(value) {
24 | const self = this
25 | if (Array.isArray(value))
26 | return value.map(function(d) {
27 | return self.condition(d)
28 | })
29 | return new Date(value)
30 | }
31 | },
32 | {
33 | migration: 'date',
34 | operators: {
35 | defaults: ['eq', 'not', 'gt', 'gte', 'lt', 'lte', 'between']
36 | }
37 | }
38 | )
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/sql/mysql/__helper.js:
--------------------------------------------------------------------------------
1 | var exec = require('child_process').exec
2 |
3 | global.beforeMYSQL = function(db, sql, next) {
4 | exec('mysql -u root -e "DROP DATABASE ' + db + '"', function(_, result) {
5 | // eslint-disable-line
6 | exec('mysql -u root -e "create database ' + db + '"', function(_) {
7 | // eslint-disable-line
8 | exec('mysql ' + db + ' -e "' + sql.join(';') + '" -u root', function(
9 | err,
10 | result
11 | ) {
12 | if (err) throw new Error(err)
13 | next()
14 | })
15 | })
16 | })
17 | }
18 |
19 | global.afterMYSQL = function(db, next) {
20 | next()
21 | }
22 |
23 | global.testMYSQL = function(name, queries, prefix) {
24 | var db = name.replace('/', '_') + '_test'
25 | require('../__shared/' + name + '-test' + (prefix || ''))(
26 | 'SQL (MySQL)',
27 | function(next) {
28 | beforeMYSQL(db, queries, next)
29 | },
30 | function(next, store) {
31 | store.close(function() {})
32 | afterMYSQL(db, next)
33 | },
34 | {
35 | host: 'localhost',
36 | type: 'mysql',
37 | database: db,
38 | user: 'travis',
39 | password: ''
40 | }
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/lib/base/expect_result.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | const Store = require('../store')
4 |
5 | Store.addExceptionType(function RecordNotFoundError(Model) {
6 | Error.apply(this)
7 | this.message = "Can't find any record for " + Model.definition.modelName
8 | })
9 | }
10 | }
11 |
12 | /*
13 | * MODEL
14 | */
15 | exports.model = {
16 | /**
17 | * When called, it will throw an error if the resultset is empty
18 | * @class Model
19 | * @method expectResult
20 | *
21 | * @see Model.get
22 | *
23 | * @return {Model}
24 | */
25 | expectResult: function() {
26 | var self = this.chain()
27 |
28 | self.setInternal('expectResult', true)
29 |
30 | return self
31 | }
32 | }
33 |
34 | /*
35 | * DEFINITION
36 | */
37 | exports.definition = {
38 | mixinCallback: function() {
39 | const Store = require('../store')
40 |
41 | this.afterFind(function(data) {
42 | var expectResult = this.getInternal('expectResult')
43 |
44 | if (expectResult && (!data.result || data.result.length === 0)) {
45 | return Promise.reject(new Store.RecordNotFoundError(this))
46 | }
47 | }, 10)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/test/sql/table_name-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../store/sql')
2 |
3 | describe('SQL: Table Name', function() {
4 | var store
5 |
6 | before(function() {
7 | store = new Store({
8 | type: 'sql'
9 | })
10 |
11 | store.Model('User', function() {})
12 | store.Model('CamelCasedTableName', function() {})
13 | })
14 |
15 | it('has the right table name', function() {
16 | return store.ready(function() {
17 | var User = store.Model('User')
18 | User.definition.tableName.should.be.equal('users')
19 | })
20 | })
21 |
22 | it('has the right table name on camelcased models', function() {
23 | return store.ready(function() {
24 | var CamelCasedTableName = store.Model('CamelCasedTableName')
25 | CamelCasedTableName.definition.tableName.should.be.equal(
26 | 'camel_cased_table_names'
27 | )
28 | })
29 | })
30 |
31 | it("returns a model by it's table name", function() {
32 | return store.ready(function() {
33 | var CamelCasedTableName = store.getByTableName('camel_cased_table_names')
34 | CamelCasedTableName.definition.tableName.should.be.equal(
35 | 'camel_cased_table_names'
36 | )
37 | })
38 | })
39 | })
40 |
--------------------------------------------------------------------------------
/lib/base/dynamic_loading.js:
--------------------------------------------------------------------------------
1 | const glob = require('glob')
2 |
3 | exports.utils = {
4 | require: function(path, options) {
5 | if (!options) options = {}
6 | if (!Array.isArray(path)) path = [path]
7 |
8 | var files = []
9 | var tmp = []
10 | var i
11 |
12 | if (options.includePathNames === true) {
13 | tmp = {}
14 | }
15 |
16 | for (i in path) {
17 | var f = glob.sync(path[i], options)
18 | if (f.length === 0 && !path[i].match(/[/\\]/)) f = [path[i]]
19 | files = files.concat(f)
20 | }
21 |
22 | for (i in files) {
23 | try {
24 | var plugin = require(files[i])
25 | } catch (e) {
26 | throw new Error(
27 | 'File not found: ' +
28 | files[i] +
29 | '. If you use webpack or a similar bundler, make sure that plugins aren\'t loaded via a path, but with require("path")'
30 | )
31 | }
32 | if (options.only) plugin = plugin[options.only]
33 |
34 | if (plugin) {
35 | if (options.includePathNames === true) {
36 | tmp[files[i]] = plugin
37 | } else {
38 | tmp.push(plugin)
39 | }
40 | }
41 | }
42 |
43 | return tmp
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/stores/ldap/data_types/dn_array.js:
--------------------------------------------------------------------------------
1 | /* istanbul ignore next */
2 | exports.store = {
3 | mixinCallback: function() {
4 | const Utils = this.utils
5 |
6 | this.addType(
7 | 'dn_array',
8 | {
9 | read: function(values) {
10 | if (values == null) return []
11 | if (!Array.isArray(values)) values = [values]
12 | return Utils.normalizeDn(values)
13 | },
14 | write: function(values) {
15 | if (!values) return []
16 | if (!Array.isArray(values)) values = [values]
17 | return values
18 | },
19 | input: function(values) {
20 | if (values == null) return []
21 | if (!Array.isArray(values)) values = [values]
22 | return Utils.normalizeDn(values)
23 | },
24 | output: function(values) {
25 | if (values === null) return []
26 | return values
27 | }
28 | },
29 | {
30 | array: true,
31 | defaults: {
32 | default: [],
33 | track_object_changes: true
34 | },
35 | operators: {
36 | default: 'eq',
37 | defaults: ['eq']
38 | }
39 | }
40 | )
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/stores/ldap/errors.js:
--------------------------------------------------------------------------------
1 | const ldap = require('ldapjs')
2 |
3 | exports.store = {
4 | mixinCallback: function() {
5 | var self = this
6 |
7 | self.ldap_errors = []
8 | self.connection.on('error', function(err) {
9 | throw err
10 | })
11 | },
12 |
13 | addLdapValidationError: function(errorType, msgMatch, attribute, error) {
14 | if (ldap[errorType]) {
15 | this.ldap_errors.push({
16 | cls: ldap[errorType],
17 | match: msgMatch,
18 | attribute: attribute,
19 | error: error
20 | })
21 | }
22 | },
23 |
24 | convertLdapErrorToValidationError: function(record, error) {
25 | for (var i = 0; i < this.ldap_errors.length; i++) {
26 | // check error class
27 | if (error instanceof this.ldap_errors[i].cls) {
28 | // check message match
29 | if (error.message.match(this.ldap_errors[i].match)) {
30 | // add validation error + return null to avoid promise rejection
31 | record.errors.add(
32 | this.ldap_errors[i].attribute,
33 | this.ldap_errors[i].error
34 | )
35 | return record.errors
36 | }
37 | }
38 | }
39 |
40 | return error
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/stores/sql/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./attributes'),
3 | require('./aggregate_functions'),
4 | require('./conditions'),
5 | require('./convert'),
6 | require('./destroy'),
7 | require('./exceptions'),
8 | require('./exec'),
9 | require('./group'),
10 | require('./interceptors'),
11 | require('./joins'),
12 | require('./limit'),
13 | require('./order'),
14 | require('./query'),
15 | require('./raw'),
16 | require('./relations'),
17 | require('./save'),
18 | require('./select'),
19 | require('./table_name'),
20 | require('./transaction'),
21 | require('./utils'),
22 | require('./validations'),
23 | require('./migrations/column_methods'),
24 | require('./migrations/data_types'),
25 | require('./migrations/migrations'),
26 | require('./migrations/polymorph'),
27 | require('./migrations/raw'),
28 | require('./migrations/run'),
29 | require('./migrations/seed'),
30 | require('./migrations/table_methods'),
31 | require('./plugins/nested_set'),
32 | require('./plugins/paranoid'),
33 | require('./plugins/serialize'),
34 | require('./plugins/sorted_list'),
35 | require('./plugins/stampable'),
36 | require('./plugins/total_count')
37 | ].concat(require('./data_types'))
38 |
--------------------------------------------------------------------------------
/lib/base/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./parent'),
3 | require('./interceptors'),
4 | require('./attributes'),
5 | require('./cache'),
6 | require('./chainable'),
7 | require('./collection'),
8 | require('./connection'),
9 | require('./context'),
10 | require('./convert'),
11 | require('./data_types'),
12 | require('./error'),
13 | require('./every'),
14 | require('./inspect'),
15 | require('./json'),
16 | require('./limit'),
17 | require('./method'),
18 | require('./methods'),
19 | require('./exec'),
20 | require('./expect_result'),
21 | require('./include'),
22 | require('./mixin'),
23 | require('./operators'),
24 | require('./primary_keys'),
25 | require('./conditions'),
26 | require('./save'),
27 | require('./relations'),
28 | require('./relations/utils'),
29 | require('./relations/belongs_to_many'),
30 | require('./relations/belongs_to'),
31 | require('./relations/belongs_to_polymorphic'),
32 | require('./relations/has_many'),
33 | require('./relations/has_many_through'),
34 | require('./relations/has_one'),
35 | require('./relations/has'),
36 | require('./relations/bulk_fetch'),
37 | require('./scope'),
38 | require('./validations'),
39 | require('./utils')
40 | ]
41 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/_array.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.toArrayCastTypes = function(fromType) {
4 | var type = this.attributeTypes[fromType]
5 | if (type) {
6 | var castfn = function(castType, attribute) {
7 | return function(value) {
8 | if (value === null) return null
9 | if (typeof value === 'string' && value.match(/^\{(.*)\}$/)) {
10 | value = value.replace(/(^\{|\}$)/g, '')
11 | if (value === '') {
12 | value = []
13 | } else {
14 | value = value.split(',')
15 | }
16 | }
17 |
18 | if (!Array.isArray(value)) value = [value]
19 |
20 | for (var i = 0; i < value.length; i++) {
21 | value[i] = type.cast[castType].call(this, value[i], attribute)
22 | }
23 |
24 | return value
25 | }
26 | }
27 |
28 | var tmp = {}
29 |
30 | for (var castType in type.cast) {
31 | if (typeof type.cast[castType] === 'function') {
32 | tmp[castType] = castfn(castType)
33 | }
34 | }
35 |
36 | return tmp
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/sql/__shared/plugins/serialize-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../../store')
2 |
3 | module.exports = function(title, beforeFn, afterFn, storeConf) {
4 | describe(title + ': Serialize', function() {
5 | var store
6 |
7 | before(beforeFn)
8 | after(function(next) {
9 | afterFn(next, store)
10 | })
11 |
12 | before(function() {
13 | store = new Store(storeConf)
14 |
15 | store.Model('User', function() {
16 | this.serialize('config')
17 | })
18 | })
19 |
20 | it('saves serialized data', function() {
21 | return store.ready(function() {
22 | var User = store.Model('User')
23 | return User.create({
24 | login: 'phil',
25 | config: {
26 | some: {
27 | nested: ['data']
28 | }
29 | }
30 | })
31 | })
32 | })
33 |
34 | it('reads serialized data', function() {
35 | return store.ready(function() {
36 | var User = store.Model('User')
37 | return User.find(1).exec(function(phil) {
38 | phil.config.should.be.eql({
39 | some: {
40 | nested: ['data']
41 | }
42 | })
43 | })
44 | })
45 | })
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/integer_array.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | this.addType('integer_array', this.toArrayCastTypes('integer'), {
4 | array: true,
5 | migration: {
6 | integerArray: 'integer[]'
7 | },
8 |
9 | operators: {
10 | default: 'in',
11 | in: {
12 | defaultMethod: function(attr, value, query, cond) {
13 | query.whereRaw(attr + ' @> ARRAY[?]::integer[]', [value])
14 | },
15 |
16 | nullifyEmptyArray: true,
17 | on: {
18 | all: false,
19 | number: true,
20 | string: true,
21 | array: function(attr, value, query, cond) {
22 | value = value.map(function(i) {
23 | return parseInt(i, 10)
24 | })
25 | query.whereRaw(attr + ' && ?::integer[]', [value])
26 | },
27 | attribute: function(attr, value, query, cond) {
28 | query.whereRaw(attr + ' @> ARRAY[' + value + ']::integer[]')
29 | },
30 | attribute_array: function(attr, value, query, cond) {
31 | query.whereRaw(attr + ' && ' + value)
32 | }
33 | }
34 | }
35 | }
36 | })
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/graphql/relations.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | const DEFAULT_OPTIONS = {
5 | nodes: function() {
6 | return this
7 | },
8 | totalCount: function() {
9 | return this.totalCount()
10 | }
11 | }
12 |
13 | exports.definition = {
14 | relation: function(name, options) {
15 | if (options.graphql) {
16 | if (options.graphql === 'relay') {
17 | options.graphql = Object.assign({}, DEFAULT_OPTIONS)
18 | }
19 |
20 | if (typeof options.graphql !== 'object')
21 | throw new Error('graphql option must be an object')
22 |
23 | options.getter = function() {
24 | // this.relations is the relations object of the record!
25 | var result = this['_' + name]
26 |
27 | if (result === undefined) {
28 | result = options.collection(this)
29 | }
30 |
31 | result.__multi_resolver = true
32 |
33 | const obj = {}
34 | Object.keys(options.graphql).forEach(function(key) {
35 | if (typeof options.graphql[key] !== 'function')
36 | throw new Error('graphql.' + key + ' must be a function')
37 | obj[key] = options.graphql[key].bind(result)
38 | })
39 |
40 | return Promise.resolve(obj)
41 | }
42 | }
43 |
44 | this.callParent(name, options)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/stores/sql/conditions.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.definition = {
5 | mixinCallback: function() {
6 | this.onRawCondition(function(chain, condition, query) {
7 | for (var i = 0; i < condition.args.length; i++) {
8 | // Hacky fix for a knex problem!
9 | // TODO: still necessary?
10 | if (Array.isArray(condition.args[i])) {
11 | var len = condition.args[i].length
12 | condition.args.splice.apply(
13 | condition.args,
14 | [i, 1].concat(condition.args[i])
15 | )
16 |
17 | var index = 0
18 | condition.query = condition.query.replace(/\?/g, function() {
19 | if (index === i) {
20 | var tmp = []
21 | for (var k = 0; k < len; k++) {
22 | tmp.push('?')
23 | }
24 |
25 | index++
26 | return tmp.join(',')
27 | }
28 | index++
29 | return '?'
30 | })
31 |
32 | i += len
33 | }
34 | }
35 |
36 | if (query) {
37 | return query.whereRaw(condition.query, condition.args)
38 | }
39 |
40 | // see sql/group.js: 94
41 | return function(query) {
42 | query.whereRaw(condition.query, condition.args)
43 | }
44 | })
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/stores/activedirectory/data_types/group_type.js:
--------------------------------------------------------------------------------
1 | var GroupTypeBitmask = {
2 | BUILTIN_LOCAL_GROUP: 1,
3 | ACCOUNT_GROUP: 2,
4 | RESOURCE_GROUP: 4,
5 | UNIVERSAL_GROUP: 8,
6 | APP_BASIC_GROUP: 16,
7 | APP_QUERY_GROUP: 32,
8 | SECURITY_ENABLED: -2147483648
9 | }
10 |
11 | /* istanbul ignore next: unable to test via travis-ci */
12 | exports.store = {
13 | mixinCallback: function() {
14 | this.addType(
15 | 'group_type',
16 | {
17 | read: function(value) {
18 | if (typeof value === 'string') value = parseInt(value, 10)
19 |
20 | var obj = {}
21 | for (var attrName in GroupTypeBitmask) {
22 | obj[attrName] =
23 | (value & GroupTypeBitmask[attrName]) ===
24 | GroupTypeBitmask[attrName]
25 | }
26 |
27 | return obj
28 | },
29 |
30 | write: function(value) {
31 | if (!value) value = {}
32 | var bitmask = 0
33 | for (var attrName in GroupTypeBitmask) {
34 | if (value[attrName] === true) bitmask += GroupTypeBitmask[attrName]
35 | }
36 | return bitmask
37 | }
38 | },
39 | {
40 | binary: true,
41 | operators: {
42 | default: 'eq',
43 | defaults: ['eq', 'not']
44 | }
45 | }
46 | )
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/rest/client/resource_name-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../lib/store')
2 |
3 | describe('REST Client: Resource Name', function() {
4 | var store
5 |
6 | before(function() {
7 | store = new Store({
8 | type: 'rest',
9 | url: 'http://localhost:8889',
10 | version: '~1.0'
11 | })
12 |
13 | store.Model('User', function() {})
14 | store.Model('CamelCasedResourceName', function() {})
15 | })
16 |
17 | it('has the right resource name', function() {
18 | return store.ready(function() {
19 | var User = store.Model('User')
20 | User.definition.resource.should.be.equal('users')
21 | })
22 | })
23 |
24 | it('has the right resource name on camelcased models', function() {
25 | return store.ready(function() {
26 | var CamelCasedResourceName = store.Model('CamelCasedResourceName')
27 | CamelCasedResourceName.definition.resource.should.be.equal(
28 | 'camel_cased_resource_names'
29 | )
30 | })
31 | })
32 |
33 | it("returns a model by it's resource name", function() {
34 | return store.ready(function() {
35 | var CamelCasedResourceName = store.getByResource(
36 | 'camel_cased_resource_names'
37 | )
38 | CamelCasedResourceName.definition.resource.should.be.equal(
39 | 'camel_cased_resource_names'
40 | )
41 | })
42 | })
43 | })
44 |
--------------------------------------------------------------------------------
/lib/stores/activedirectory/data_types/sid.js:
--------------------------------------------------------------------------------
1 | /* istanbul ignore next: unable to test via travis-ci */
2 | exports.store = {
3 | mixinCallback: function() {
4 | // Windows SID
5 | this.addType(
6 | 'sid',
7 | {
8 | read: function(value) {
9 | if (value === null) return null
10 |
11 | var hex
12 | var i
13 | var tmp
14 |
15 | if (Buffer.from) hex = Buffer.from(value, 'base64')
16 | else hex = new Buffer(value, 'base64') // eslint-disable-line node/no-deprecated-api
17 |
18 | hex = hex.toString('hex').toUpperCase()
19 | var parts = hex.match(/.{2}/g)
20 | var output = ['S']
21 |
22 | output.push(parseInt(parts[0], 16))
23 | output.push(parseInt(parts[7], 16))
24 |
25 | for (i = 8; i < parts.length; i += 4) {
26 | tmp = ''
27 | for (var x = 3; x >= 0; x--) {
28 | tmp += parts[i + x]
29 | }
30 | output.push(parseInt(tmp, 16))
31 | }
32 |
33 | return output.join('-')
34 | },
35 | write: function(value) {
36 | return value
37 | }
38 | },
39 | {
40 | binary: true,
41 | operators: {
42 | default: 'eq',
43 | defaults: ['eq', 'not']
44 | }
45 | }
46 | )
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/stores/activedirectory/data_types/timestamp.js:
--------------------------------------------------------------------------------
1 | const parse = require('date-fns/parse')
2 | const format = require('date-fns/format')
3 |
4 | /* istanbul ignore next: unable to test via travis-ci */
5 | exports.store = {
6 | mixinCallback: function() {
7 | // Windows timestamp (100 nanosecond passed since 1.1.1601 00:00:00) e.g. 129822505817745000
8 | this.addType(
9 | 'timestamp',
10 | {
11 | read: function(value) {
12 | if (
13 | value === null ||
14 | value === '0' ||
15 | value === '9223372036854775807'
16 | )
17 | return null
18 | return new Date(parseInt(value, 10) / 10000 - 11644473600000)
19 | },
20 | write: function(value) {
21 | if (value === null) return null
22 | if (value === 0 || value === -1) return value
23 | return (
24 | 11644473600000 +
25 | parseInt(format(parse(value), 'X'), 10) * 10000
26 | ).toString()
27 | },
28 | output: function(value) {
29 | if (value === null) return null
30 | return format(parse(value), 'YYYY-MM-DD HH:mm:ss')
31 | }
32 | },
33 | {
34 | operators: {
35 | default: 'eq',
36 | defaults: ['eq', 'gt', 'gte', 'lt', 'lte', 'not']
37 | }
38 | }
39 | )
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/stores/activedirectory/schema/group.js:
--------------------------------------------------------------------------------
1 | /* istanbul ignore next: unable to test via travis-ci */
2 | exports.store = {
3 | mixinCallback: function() {
4 | this.Model('Group', function() {
5 | this.rdnPrefix('cn')
6 | this.objectClass = ['top', 'group']
7 |
8 | this.attribute('name', String, { emit_events: true, persistent: false })
9 | this.attribute('sAMAccountName', String)
10 | this.attribute('groupType', 'group_type')
11 | this.attribute('description', String)
12 |
13 | this.attribute('objectGUID', 'guid', { persistent: false })
14 | this.attribute('objectSid', 'sid', { persistent: false })
15 |
16 | this.attribute('uSNChanged', Number, { persistent: false })
17 | this.attribute('whenChanged', 'date', { persistent: false })
18 | this.attribute('whenCreated', 'date', { persistent: false })
19 |
20 | this.attribute('member', 'dn_array')
21 |
22 | this.validatesPresenceOf('name')
23 |
24 | this.hasParent('ou', { model: 'OrganizationalUnit', autoSave: true })
25 | this.belongsToMany('members', { from: 'member', autoSave: true })
26 |
27 | var self = this
28 | this.on('name_changed', function(record, oldValue, value) {
29 | if (value) {
30 | record.cn = value
31 | if (record.parent_dn) record.dn = self.dn(record)
32 | }
33 | })
34 | })
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/ldap/client/__shared/include-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../../lib/store')
2 |
3 | module.exports = function(title, beforeFn, afterFn, storeConf) {
4 | describe(title + ': Include (' + storeConf.url + ')', function() {
5 | var store
6 |
7 | before(beforeFn)
8 | after(function(next) {
9 | afterFn(next, store)
10 | })
11 |
12 | before(function() {
13 | store = new Store(storeConf)
14 | })
15 |
16 | it('get child objects (one level)!', function() {
17 | return store.ready(function() {
18 | var Ou = store.Model('OrganizationalUnit')
19 | return Ou.searchRoot('ou=openrecord,' + LDAP_BASE)
20 | .include('children')
21 | .exec(function(ous) {
22 | ous.length.should.be.equal(1)
23 | ous[0].children.length.should.be.equal(4)
24 | ous[0].children[0].children.length.should.be.equal(0)
25 | })
26 | })
27 | })
28 |
29 | it('get all recursive child objects!', function() {
30 | return store.ready(function() {
31 | var Ou = store.Model('OrganizationalUnit')
32 | return Ou.searchRoot('ou=openrecord,' + LDAP_BASE)
33 | .include('all_children')
34 | .exec(function(ous) {
35 | ous.length.should.be.equal(1)
36 | ous[0].all_children.length.should.not.be.equal(0)
37 | })
38 | })
39 | })
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/test/sql/__shared/data_types-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../store')
2 |
3 | module.exports = function(title, beforeFn, afterFn, storeConf) {
4 | describe(title + ': Data Types', function() {
5 | var store
6 |
7 | before(beforeFn)
8 | after(function(next) {
9 | afterFn(next, store)
10 | })
11 |
12 | before(function() {
13 | store = new Store(storeConf)
14 |
15 | store.Model('User', function() {})
16 | })
17 |
18 | describe('cast()', function() {
19 | it('casts BLOB to string', function() {
20 | return store.ready(function() {
21 | var User = store.Model('User')
22 | return User.definition
23 | .cast('my_blob', 45454, 'read')
24 | .should.be.equal('45454')
25 | })
26 | })
27 |
28 | it('casts INTEGER to number', function() {
29 | return store.ready(function() {
30 | var User = store.Model('User')
31 | return User.definition
32 | .cast('my_integer', '123.55', 'read')
33 | .should.be.equal(123)
34 | })
35 | })
36 |
37 | it('casts REAL to float', function() {
38 | return store.ready(function() {
39 | var User = store.Model('User')
40 | return User.definition
41 | .cast('my_real', '123.55', 'read')
42 | .should.be.equal(123.55)
43 | })
44 | })
45 | })
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/test/sql/postgres/__helper.js:
--------------------------------------------------------------------------------
1 | var exec = require('child_process').exec
2 |
3 | global.beforePG = function(db, sql, next) {
4 | /* exec('psql -c "SELECT pid FROM pg_stat_activity where pid <> pg_backend_pid()" -U postgres', function(err, result){
5 | console.log('Connected', result);
6 | }); */
7 | exec('psql -c "DROP DATABASE ' + db + '" -U postgres', function(_, result) {
8 | // eslint-disable-line
9 | exec('psql -c "create database ' + db + '" -U postgres', function(
10 | _,
11 | result
12 | ) {
13 | // eslint-disable-line
14 | exec('psql ' + db + ' -c "' + sql.join(';') + '" -U postgres', function(
15 | error,
16 | result
17 | ) {
18 | if (error) throw new Error(error)
19 | next()
20 | })
21 | })
22 | })
23 | }
24 |
25 | global.afterPG = function(db, next) {
26 | next()
27 | }
28 |
29 | global.testPG = function(name, queries, prefix) {
30 | var db = name.replace('/', '_') + '_test'
31 | require('../__shared/' + name + '-test' + (prefix || ''))(
32 | 'SQL (Postgres)',
33 | function(next) {
34 | this.timeout(5000)
35 | beforePG(db, queries, next)
36 | },
37 | function(next, store) {
38 | store.close(function() {})
39 | afterPG(db, next)
40 | },
41 | {
42 | host: 'localhost',
43 | type: 'postgres',
44 | database: db,
45 | user: 'postgres',
46 | password: ''
47 | }
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/test/base/data_types-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 |
3 | var Store = require('../../store/base')
4 |
5 | describe('Data Types', function() {
6 | var store = new Store()
7 |
8 | describe('getType()', function() {
9 | it('exists', function() {
10 | should.exist(store.getType)
11 | store.getType.should.be.a.Function()
12 | })
13 |
14 | var type = store.getType(String)
15 |
16 | it('returns the correct type', function() {
17 | type.should.have.property('cast')
18 | type.cast.should.have.property('input')
19 | type.cast.should.have.property('output')
20 | })
21 |
22 | it('casts an integer to a string', function() {
23 | type.cast.input(1234).should.be.equal('1234')
24 | })
25 | })
26 |
27 | describe('addType()', function() {
28 | it('exists', function() {
29 | should.exist(store.addType)
30 | store.addType.should.be.a.Function()
31 | })
32 |
33 | var type = store.addType(RegExp, function(value) {
34 | return new RegExp(value)
35 | })
36 |
37 | type = store.getType(RegExp)
38 |
39 | it('returns the correct type', function() {
40 | type.should.have.property('cast')
41 | type.cast.should.have.property('input')
42 | type.cast.should.have.property('output')
43 | })
44 |
45 | it('casts an string to an regexp', function() {
46 | type.cast.input('(.*)').should.be.an.instanceOf(RegExp)
47 | })
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/test/base/scope-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 |
3 | var Store = require('../../store/base')
4 |
5 | describe('Scope', function() {
6 | var store = new Store()
7 |
8 | store.Model('User', function() {
9 | this.scope('active', function() {
10 | this.should.have.property('new')
11 | })
12 | })
13 |
14 | var User
15 | before(function() {
16 | return store.ready(function() {
17 | User = store.Model('User')
18 | })
19 | })
20 |
21 | describe('scope()', function() {
22 | it('has defined scope', function() {
23 | should.exist(User.active)
24 | })
25 |
26 | it('scope is chainable', function() {
27 | should.exist(User.active().new)
28 | })
29 | })
30 | })
31 |
32 | describe('Default Scope', function() {
33 | var store = new Store()
34 |
35 | store.Model('User', function() {
36 | this.defaultScope('test')
37 |
38 | this.scope('test', function() {
39 | this.temporaryDefinition().instanceMethods['test'] = function() {
40 | return 'test'
41 | }
42 | })
43 |
44 | this.scope('admin', function() {})
45 | })
46 |
47 | var User
48 | before(function() {
49 | return store.ready(function() {
50 | User = store.Model('User')
51 | })
52 | })
53 |
54 | describe('scope()', function() {
55 | it('calls the default scope', function() {
56 | var a = User.admin().new()
57 | a.test().should.equal('test')
58 | })
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/test/sql/__shared/autoload-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../store')
2 |
3 | module.exports = function(title, beforeFn, afterFn, storeConf) {
4 | describe(title + ': AutoLoad', function() {
5 | var store
6 |
7 | before(beforeFn)
8 | after(function(next) {
9 | afterFn(next, store)
10 | })
11 |
12 | before(function() {
13 | store = new Store(
14 | Object.assign(
15 | {
16 | autoLoad: true
17 | },
18 | storeConf
19 | )
20 | )
21 |
22 | store.Model('Avatar', function() {
23 | this.scope('test', function() {})
24 | })
25 | })
26 |
27 | it('Models are loaded', function() {
28 | return store.ready(function() {
29 | store.models.should.have.keys(
30 | 'user',
31 | 'post',
32 | 'thread',
33 | 'avatar',
34 | 'unreadpost',
35 | 'polything'
36 | )
37 | })
38 | })
39 |
40 | it('Model attributes are loaded', function() {
41 | return store.ready(function() {
42 | store.models.user.definition.attributes.should.have.keys(
43 | 'id',
44 | 'login',
45 | 'email',
46 | 'created_at'
47 | )
48 | })
49 | })
50 |
51 | it('Custom overwrites work', function() {
52 | return store.ready(function() {
53 | store.models.avatar.test.should.be.a.Function()
54 | })
55 | })
56 | })
57 | }
58 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/json.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | var store = this
4 |
5 | this.addType(
6 | 'json',
7 | {
8 | output: function(val) {
9 | if (!val) return {}
10 | return val
11 | },
12 |
13 | read: function(val) {
14 | if (val instanceof Object) return val
15 | if (val instanceof String) return JSON.parse(val)
16 | if (val === null) return null
17 |
18 | return val
19 | },
20 |
21 | write: function(object) {
22 | if (object === null) return null
23 | if (typeof object === 'string') return object
24 |
25 | return JSON.stringify(object)
26 | }
27 | },
28 | {
29 | array: true,
30 | migration: ['json', 'jsonb'],
31 | operators: {
32 | defaults: ['eq'],
33 |
34 | eq: {
35 | defaultMethod: function(attr, value, query, cond) {
36 | var key = Object.keys(value)[0]
37 | value = value[key]
38 | query.whereRaw(attr + "->>'" + key + "' = ?", [value])
39 | }
40 | }
41 | },
42 |
43 | sorter: function(name) {
44 | var tmp = name.match(/(.+)\.([a-zA-Z_-]+)$/)
45 | if (tmp) {
46 | return store.connection.raw(tmp[1] + "->>'" + tmp[2] + "'")
47 | }
48 | return name
49 | }
50 | }
51 | )
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/base/context.js:
--------------------------------------------------------------------------------
1 | /*
2 | * MODEL
3 | */
4 | exports.model = {
5 | /**
6 | * Adds a context object to your Model which could be used by your Hooks, Validation or Events via `this.context`
7 | * This is especially usefull need to differentiate things based on e.g. the cookie. Just set the context to the current request (`Model.setContext(req).create(params))` and use `this.context` inside your `beforeCreate()` hook.
8 | * The `context` Variable is available on your Model an all it's Records.
9 | *
10 | * @class Model
11 | * @method setContext
12 | * @param {object} context - Your context object
13 | *
14 | * @return {Model}
15 | */
16 | setContext: function(context) {
17 | var self = this.chain()
18 |
19 | self.setInternal('context', context)
20 |
21 | return self
22 | }
23 | }
24 |
25 | /*
26 | * CHAIN
27 | */
28 | exports.chain = {
29 | mixinCallback: function() {
30 | this.__defineGetter__('context', function() {
31 | const parentRecord = this.getInternal('relation_to')
32 | var context = this.getInternal('context')
33 |
34 | if (context) return context
35 |
36 | // otherwise ask it's parent record
37 | if (parentRecord) return parentRecord.context
38 | })
39 | }
40 | }
41 |
42 | /*
43 | * RECORD
44 | */
45 | exports.record = {
46 | mixinCallback: function() {
47 | this.__defineGetter__('context', function() {
48 | if (this.__chainedModel) return this.__chainedModel.context
49 | })
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/stores/postgres/data_types/tsvector.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | var store = this
4 |
5 | this.addType(
6 | 'tsvector',
7 | {
8 | output: function(val) {
9 | if (!val) return {}
10 | return val
11 | },
12 |
13 | read: function(value) {
14 | var obj = {}
15 |
16 | // quick and dirty tsvector to object converter
17 | const token = value.match(/('.+?'(:([0-9A-D]|,)+|))/g)
18 |
19 | if(token){
20 | token.forEach(function(v){
21 | var txt = v.replace(/('(.+?)'(:([0-9A-D]|,)+|))/, '$2')
22 | obj[txt] = v.replace(/('(.+?)'(:([0-9A-D]|,)+|))/, '$3').split(',')
23 | obj[txt][0] = obj[txt][0].replace(':', '')
24 | if(obj[txt][0] === '') obj[txt] = []
25 | })
26 | }else{
27 | obj[token] = []
28 | }
29 |
30 | return obj
31 | },
32 |
33 | write: function(value) {
34 | return store.connection.raw("to_tsvector(?)", [value])
35 | }
36 | },
37 | {
38 | array: true,
39 | migration: ['tsvector'],
40 | operators: {
41 | default: ['matches'],
42 |
43 | matches: {
44 | defaultMethod: function(attr, value, query, cond) {
45 | query.whereRaw(attr + "@@ to_tsquery(?)", [value])
46 | }
47 | }
48 | }
49 | }
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/test/base/events-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 |
3 | var Store = require('../../store/base')
4 |
5 | describe('Events', function() {
6 | var store = new Store()
7 |
8 | before(function() {
9 | return store.ready()
10 | })
11 |
12 | store.Model('User', function() {
13 | var self = this
14 |
15 | describe('Definition', function() {
16 | describe('emit()', function() {
17 | it('methods exists', function() {
18 | self.emit.should.be.a.Function()
19 | self.on.should.be.a.Function()
20 | })
21 | })
22 | })
23 |
24 | self.attribute('login', String)
25 |
26 | self.on('record_to_definition_test_event', function(arg1, arg2, done) {
27 | this.login.should.be.equal('phil')
28 | arg1.should.be.equal('argument1')
29 | arg2.should.be.equal('argument2')
30 | done()
31 | })
32 | })
33 |
34 | describe('Model', function() {
35 | describe('emit()', function() {
36 | it('does not exists', function() {
37 | var User = store.Model('User')
38 | should.not.exist(User.emit)
39 | should.not.exist(User.on)
40 | })
41 | })
42 | })
43 |
44 | describe('Record', function() {
45 | describe('emit()', function() {
46 | it('does not exists', function() {
47 | var User = store.Model('User')
48 | var record = new User()
49 |
50 | should.not.exist(record.emit)
51 | should.not.exist(record.on)
52 | })
53 | })
54 | })
55 | })
56 |
--------------------------------------------------------------------------------
/lib/stores/ldap/destroy.js:
--------------------------------------------------------------------------------
1 | const DeleteTreeControl = require('./controls/tree_delete')
2 |
3 | /*
4 | * RECORD
5 | */
6 | exports.record = {
7 | /**
8 | * Destroy a record
9 | * @class Record
10 | * @method destroy
11 | *
12 | * @callback
13 | * @param {boolean} result - will be true if the destroy was successful
14 | * @this Record
15 | *
16 | * @return {Record}
17 | */
18 | destroy: function(options) {
19 | var self = this
20 |
21 | options = options || {}
22 |
23 | return self
24 | .callInterceptors('beforeDestroy', [self, options])
25 | .then(function() {
26 | return new Promise(function(resolve, reject) {
27 | self.model.definition.store.connection.del(
28 | self.dn,
29 | options.controls || [],
30 | function(err) {
31 | if (err)
32 | return reject(
33 | self.definition.store.convertLdapErrorToValidationError(
34 | self,
35 | err
36 | )
37 | )
38 |
39 | resolve(self.callInterceptors('afterDestroy', [self, options]))
40 | }
41 | )
42 | })
43 | })
44 | .then(function(record) {
45 | record.__exists = false
46 | return record
47 | })
48 | },
49 |
50 | destroyAll: function(resolve, reject) {
51 | return this.destroy(resolve, reject, {
52 | controls: [new DeleteTreeControl()]
53 | })
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/test/sql/__shared/dependent_nullify-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 | var Store = require('../../../store')
3 |
4 | module.exports = function(title, beforeFn, afterFn, storeConf) {
5 | describe(title + ': Nullify dependent', function() {
6 | var store
7 |
8 | before(beforeFn)
9 | after(function(next) {
10 | afterFn(next, store)
11 | })
12 |
13 | before(function() {
14 | store = new Store(storeConf)
15 |
16 | store.Model('User', function() {
17 | this.hasMany('posts')
18 | this.hasMany('threads')
19 | })
20 | store.Model('Post', function() {
21 | this.belongsTo('user')
22 | this.belongsTo('thread')
23 | })
24 | store.Model('Thread', function() {
25 | this.belongsTo('user')
26 | this.hasMany('posts', { dependent: 'nullify' })
27 | })
28 | })
29 |
30 | it('hasMany', function() {
31 | return store.ready(function() {
32 | var Thread = store.Model('Thread')
33 | var Post = store.Model('Post')
34 |
35 | return Thread.find(1)
36 | .then(function(thread) {
37 | return thread.destroy()
38 | })
39 | .then(function() {
40 | return Post.find([1, 2])
41 | })
42 | .then(function(posts) {
43 | posts.length.should.be.equal(2)
44 | should.not.exist(posts[0].thread_id)
45 | should.not.exist(posts[1].thread_id)
46 | })
47 | })
48 | })
49 | })
50 | }
51 |
--------------------------------------------------------------------------------
/test/sql/sqlite3/__helper.js:
--------------------------------------------------------------------------------
1 | var sqlite = require('sqlite3')
2 | var fs = require('fs')
3 | var async = require('async')
4 |
5 | global.beforeSQLite = function(file, sql, next) {
6 | afterSQLite(file)
7 | var db = new sqlite.Database(file)
8 |
9 | var tmp = []
10 | for (var i in sql) {
11 | ;(function(sql) {
12 | tmp.push(function(next) {
13 | // convert mysql or postgres sql to sqlite3 format
14 | sql = sql
15 | .replace('serial primary key', 'INTEGER PRIMARY KEY AUTOINCREMENT')
16 | .replace(/VALUES\(.+\)/, function(values) {
17 | return values
18 | .replace(/'/g, '"')
19 | .replace(/true/g, '1')
20 | .replace(/false/g, '0')
21 | })
22 |
23 | db.run(sql, function(err, result) {
24 | if (err) throw err
25 | next()
26 | })
27 | })
28 | })(sql[i])
29 | }
30 |
31 | async.series(tmp, function() {
32 | db.close()
33 | next()
34 | })
35 | }
36 |
37 | global.afterSQLite = function(file) {
38 | if (fs.existsSync(file)) fs.unlinkSync(file)
39 | }
40 |
41 | global.testSQLite = function(name, queries, prefix) {
42 | var db = name.replace('/', '_') + '_test'
43 |
44 | require('../__shared/' + name + '-test' + (prefix || ''))(
45 | 'SQL (SQLite3)',
46 | function(next) {
47 | beforeSQLite(db, queries, next)
48 | },
49 | function(next) {
50 | afterSQLite(db)
51 | next()
52 | },
53 | {
54 | type: 'sqlite3',
55 | file: db
56 | }
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/lib/stores/ldap/utils.js:
--------------------------------------------------------------------------------
1 | const parseDN = require('ldapjs').parseDN
2 |
3 | exports.utils = {
4 | normalizeDn: function(dn, lowercased) {
5 | if (Array.isArray(dn)) {
6 | for (var i = 0; i < dn.length; i++) {
7 | dn[i] = this.normalizeDn(dn[i])
8 | }
9 |
10 | return dn
11 | }
12 | try {
13 | dn = parseDN(dn.toString())
14 | .toString()
15 | .replace(/, /g, ',')
16 | if (lowercased !== false) dn = dn.toLowerCase()
17 | return dn
18 | } catch (e) {
19 | return null
20 | }
21 | },
22 |
23 | mergeBinary: function(chain, entry, obj) {
24 | obj = obj || entry.object
25 |
26 | var attr
27 |
28 | obj.objectClass = obj.objectClass || obj.objectclass
29 |
30 | if (chain.options.polymorph) {
31 | attr = chain.definition.store.getAllAvailableAttributes(true)
32 | for (var i = 0; i < attr.length; i++) {
33 | if (entry.raw[attr[i]]) {
34 | // .raw from ldapjs
35 | obj[attr[i]] = entry.raw[attr[i]]
36 | }
37 | }
38 | } else {
39 | for (var name in chain.definition.attributes) {
40 | if (chain.definition.attributes.hasOwnProperty(name)) {
41 | attr = chain.definition.attributes[name]
42 | if (attr.type.binary && entry.raw[name]) {
43 | // .raw from ldapjs
44 | obj[name] = entry.raw[name]
45 | }
46 | }
47 | }
48 | }
49 |
50 | if (obj.dn) {
51 | obj.dn = this.normalizeDn(obj.dn)
52 | }
53 |
54 | return obj
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/base/save.js:
--------------------------------------------------------------------------------
1 | /*
2 | * RECORD
3 | */
4 | exports.record = {
5 | /**
6 | * Save the current record
7 | * @class Record
8 | * @method save
9 | */
10 | save: function(options) {
11 | var self = this
12 | options = options || {}
13 |
14 | if (typeof options === 'function') throw new Error('then!')
15 | return self
16 | .validate()
17 | .then(function() {
18 | return self._create_or_update(options)
19 | })
20 | .then(function() {
21 | // eliminate changes -> we just saved it to the database!
22 | self.changes = {}
23 | })
24 | .then(function() {
25 | return self
26 | })
27 | },
28 |
29 | update: function(data, options) {
30 | return this.set(data).save(options)
31 | },
32 |
33 | _create_or_update: function() {
34 | throw new Error('not implemented')
35 | }
36 | }
37 |
38 | /*
39 | * CHAIN
40 | */
41 | exports.chain = {
42 | save: function(options) {
43 | const self = this
44 | var records = self
45 |
46 | if (options.ignore) {
47 | records = records.filter(function(record) {
48 | return options.ignore.indexOf(record) === -1
49 | })
50 | }
51 |
52 | return self
53 | ._runLazyOperation(options)
54 | .then(function() {
55 | return self.store.utils.parallel(
56 | records.map(function(record) {
57 | return record.save(options)
58 | })
59 | )
60 | })
61 | .then(function() {
62 | self._resolved()
63 | return self
64 | })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/stores/ldap/relations/has_parent.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | hasParent: function(name, options) {
3 | options = options || {}
4 | options.from = 'parent_dn'
5 | options.to = 'dn'
6 | options.bulkFetch = false
7 |
8 | options.loadFromRecord =
9 | options.loadFromRecord ||
10 | function(parentRecord, include) {
11 | if (options.model) {
12 | const query = options.model.find(parentRecord.parent_dn)
13 | if (options.conditions) query.where(options.conditions) // conditions on the relation
14 | if (options.scope) query[options.scope].apply(query, include.args) // scope on the relation
15 | if (include.conditions) query.where(include.conditions) // nested conditions via `where({relation_name: {...conditions...}})`
16 | if (include.children) query.include(include.children) // nested includes via `include({relation_name: {nested_relation_name: { ... }}})`
17 | if (include.scope) query[include.scope].apply(query, include.args) // scope defined via `include({relation_name: {$scope: 'myscope'}})`
18 |
19 | return query.then(function(result) {
20 | parentRecord.relations[name] = query // result could be a single record, so we assign the query => collection
21 | return result
22 | })
23 | }
24 |
25 | throw new Error(
26 | 'You need to implement your own `loadFromRecord` function! (relation: ' +
27 | name +
28 | ')'
29 | )
30 | }
31 |
32 | return this.belongsTo(name, options)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/stores/activedirectory/schema/organizational_unit.js:
--------------------------------------------------------------------------------
1 | /* istanbul ignore next: unable to test via travis-ci */
2 | exports.store = {
3 | mixinCallback: function() {
4 | this.Model('OrganizationalUnit', function() {
5 | this.isContainer('ou')
6 | this.objectClass = ['top', 'organizationalUnit']
7 |
8 | this.attribute('name', String, { emit_events: true, persistent: false })
9 | this.attribute('description', String)
10 | this.attribute('objectGUID', 'guid', { persistent: false })
11 | this.attribute('uSNChanged', Number, { persistent: false })
12 | this.attribute('whenChanged', 'date', { persistent: false })
13 | this.attribute('whenCreated', 'date', { persistent: false })
14 |
15 | this.validatesPresenceOf('name')
16 |
17 | var self = this
18 | this.on('name_changed', function(record, oldValue, value) {
19 | if (value) {
20 | record.ou = value
21 | if (record.parent_dn) record.dn = self.dn(record)
22 | }
23 | })
24 |
25 | this.hasChildren('ous', {
26 | model: 'OrganizationalUnit',
27 | conditions: { objectClass: 'organizationalUnit' }
28 | })
29 | this.hasChildren('users', {
30 | model: 'User',
31 | conditions: { objectClass: 'user' }
32 | })
33 | this.hasChildren('groups', {
34 | model: 'Group',
35 | conditions: { objectClass: 'group' }
36 | })
37 | this.hasChildren('computers', {
38 | model: 'Computer',
39 | conditions: { objectClass: 'group' }
40 | })
41 | })
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/docs/webpack.md:
--------------------------------------------------------------------------------
1 | # Bundle your store with Webpack
2 |
3 | If you are using *sqlite3*, *postgres*, *mysql* and *oracle* OPENRECORD will automatically load all models and its attribute definitions from your database (If `autoLoad` or `autoAttributes` is active).
4 | To avoid this overhead (e.g. in a [serverless](https://serverless.com/) environment), you can use [webpack](https://webpack.js.org/) and the OPENRECORD webpack plugin to build your code with cached model and attribute definitions.
5 |
6 | In your [webpack config]() add the following plugin:
7 |
8 | ```js
9 | const OpenrecordCache = require('openrecord/webpack')
10 | const myStore = require('./store.js')
11 |
12 | module.exports = {
13 | // ... your webpack config
14 | plugins: [
15 | new OpenrecordCache(myStore)
16 | // ... other webpack plugins
17 | ]
18 | }
19 | ```
20 |
21 | The plugin constructor takes your store as its only input.
22 | So you have to `export` your store. e.g.
23 |
24 | ```js
25 | // ./store.js
26 | const Store = require('openrecord')
27 | const store = new Store({
28 | //... your store config
29 | })
30 |
31 | module.exports = store
32 | ```
33 |
34 | !> This plugin wont work in [watch](https://webpack.js.org/configuration/watch/) mode!
35 |
36 | Your store will be initialized on build, so make sure a connection to your database could be established.
37 | After the store is ready, it will take the model and attribute definition and write it into a cache. This cache will be bundled with your source and automatically used.
38 | So your bundled store will be faster at start, because it won't query your database for schema information.
--------------------------------------------------------------------------------
/test/sql/oracle/clear.sql:
--------------------------------------------------------------------------------
1 | BEGIN
2 | FOR cur_rec IN (SELECT object_name, object_type
3 | FROM user_objects
4 | WHERE object_type IN
5 | ('TABLE',
6 | 'VIEW',
7 | 'PACKAGE',
8 | 'PROCEDURE',
9 | 'FUNCTION',
10 | 'SEQUENCE',
11 | 'SYNONYM',
12 | 'PACKAGE BODY'
13 | ))
14 | LOOP
15 | BEGIN
16 | IF cur_rec.object_type = 'TABLE'
17 | THEN
18 | EXECUTE IMMEDIATE 'DROP '
19 | || cur_rec.object_type
20 | || ' "'
21 | || cur_rec.object_name
22 | || '" CASCADE CONSTRAINTS';
23 | ELSE
24 | EXECUTE IMMEDIATE 'DROP '
25 | || cur_rec.object_type
26 | || ' "'
27 | || cur_rec.object_name
28 | || '"';
29 | END IF;
30 | EXCEPTION
31 | WHEN OTHERS
32 | THEN
33 | DBMS_OUTPUT.put_line ( 'FAILED: DROP '
34 | || cur_rec.object_type
35 | || ' "'
36 | || cur_rec.object_name
37 | || '"'
38 | );
39 | END;
40 | END LOOP;
41 | END;
42 | /
43 |
--------------------------------------------------------------------------------
/lib/stores/sql/select.js:
--------------------------------------------------------------------------------
1 | /*
2 | * MODEL
3 | */
4 | exports.model = {
5 | /**
6 | * Specify SQL select fields. Default: *
7 | * @class Model
8 | * @method select
9 | * @param {array} fields - The field names
10 | *
11 | *
12 | * @return {Model}
13 | */
14 | select: function() {
15 | var self = this.chain()
16 |
17 | var args = this.definition.store.utils.args(arguments)
18 | var fields = []
19 | fields = fields.concat.apply(fields, args) // flatten
20 |
21 | self.addInternal('select', fields)
22 |
23 | return self
24 | }
25 | }
26 |
27 | /*
28 | * DEFINITION
29 | */
30 | exports.definition = {
31 | mixinCallback: function() {
32 | var self = this
33 |
34 | this.beforeFind(function(query) {
35 | const custom = this.getInternal('select')
36 | var select = []
37 | var star = true
38 |
39 | if (custom) {
40 | select = custom
41 | star = false
42 |
43 | var asJson = false
44 |
45 | select.forEach(function(field, index) {
46 | // check for function calls => don't escape them!
47 | if (field.match(/(\(|\))/)) {
48 | select[index] = self.store.connection.raw(field)
49 | asJson = true
50 | }
51 | })
52 |
53 | if (asJson) {
54 | this.asJson()
55 | this.asRaw()
56 | } else {
57 | this.setInternal('allowed_attributes', select)
58 | }
59 | }
60 |
61 | if (!star) {
62 | this.setInternal('has_selects', true)
63 | query.select(select)
64 | }
65 |
66 | return true
67 | }, -50)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/test/ldap/client/destroy-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 | var Store = require('../../../lib/store')
3 |
4 | describe('LDAP Client: Destroy', function() {
5 | var store
6 |
7 | before(function() {
8 | store = new Store({
9 | type: 'ldap',
10 | url: 'ldap://0.0.0.0:1389',
11 | base: 'dc=test',
12 | user: 'cn=root',
13 | password: 'secret',
14 | autoSave: true
15 | })
16 |
17 | store.Model('User', function() {
18 | this.attribute('username', String)
19 | this.attribute('memberOf', 'dn_array')
20 |
21 | this.hasParent('ou')
22 | this.hasMany('groups', { container: 'children', to: 'member' })
23 | })
24 |
25 | store.Model('Group', function() {
26 | this.attribute('name', String)
27 | this.attribute('member', 'dn_array')
28 |
29 | this.hasParent('ou')
30 | this.belongsToMany('members', { from: 'member' })
31 | })
32 |
33 | store.Model('Ou', function() {
34 | this.isContainer('ou')
35 | this.attribute('name', String)
36 | })
37 | })
38 |
39 | it('destroys a single record', function() {
40 | return store.ready(function() {
41 | var User = store.Model('User')
42 |
43 | return User.find('cn=destroy_me, ou=destroy, dc=test').exec(function(
44 | user
45 | ) {
46 | user.username.should.be.equal('destroy_me')
47 |
48 | return user.destroy(function() {
49 | return User.find('cn=destroy_me, ou=destroy, dc=test').exec(function(
50 | user
51 | ) {
52 | should.not.exists(user)
53 | })
54 | })
55 | })
56 | })
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/lib/stores/sql/exec.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('openrecord:exec')
2 |
3 | exports.definition = {
4 | mixinCallback: function() {
5 | const Store = require('../../store')
6 |
7 | // var self = this
8 |
9 | this.onFind(function(query, data) {
10 | if (data.result) return
11 |
12 | // measure just the sql execution time
13 | const startTime = process.hrtime()
14 |
15 | return query
16 | .then(function(response) {
17 | const endTime = process.hrtime(startTime)
18 | const ms =
19 | parseInt((endTime[0] * 1e9 + endTime[1]) / 1000000 * 100) / 100
20 | debug('[' + ms + 'ms] ' + query.toString())
21 |
22 | data.result = response
23 | })
24 | .catch(function(error) {
25 | debug(query.toString())
26 | data.error = new Store.SQLError(error)
27 | throw data.error
28 | })
29 | })
30 | }
31 | }
32 |
33 | /*
34 | * MODEL
35 | */
36 | exports.model = {
37 | getExecOptions: function() {
38 | return this.query()
39 | },
40 |
41 | toSql: function(callback) {
42 | var sql
43 | var query = this.query()
44 |
45 | if (typeof callback !== 'function') return
46 |
47 | // make async?
48 | return this.callInterceptors('beforeFind', [query])
49 | .then(function() {
50 | sql = query.toString()
51 |
52 | if (process.env.NODE_ENV === 'test') {
53 | sql = sql
54 | .replace(/`/g, '"')
55 | .replace(/'(\d+)'/g, '$1')
56 | .replace(/ as /g, ' ')
57 | }
58 |
59 | return sql
60 | })
61 | .then(callback)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/base/operators.js:
--------------------------------------------------------------------------------
1 | /*
2 | * STORE
3 | */
4 | exports.store = {
5 | mixinCallback: function() {
6 | this.operatorTypes = {}
7 | this.operatorDefault = null
8 | },
9 |
10 | // register global operators - could be overwritten per data type
11 | addOperator: function(name, fn, options) {
12 | /* istanbul ignore next */
13 | if (!name || typeof name !== 'string') throw new Error('No name given')
14 |
15 | if (typeof fn === 'object') {
16 | options = fn
17 | fn = null
18 | }
19 |
20 | options = options || {}
21 | name = name.toLowerCase()
22 |
23 | /* istanbul ignore next */
24 | if (this.operatorTypes[name])
25 | throw new Error("Operator '" + name + "' already exists")
26 |
27 | if (typeof fn === 'function') options.defaultMethod = fn
28 |
29 | if (options.on && options.on.all === undefined) options.on.all = true
30 |
31 | this.operatorTypes[name] = options
32 | if (options.default) this.operatorDefault = name
33 | },
34 |
35 | /* istanbul ignore next */
36 | getOperator: function(name) {
37 | if (typeof name === 'string') name = name.toLowerCase()
38 | return this.operatorTypes[name] || this.operatorTypes[this.operatorDefault]
39 | },
40 |
41 | appendOperator: function(type, operator) {
42 | /* istanbul ignore next */
43 | if (!this.attributeTypes[type])
44 | throw new Error("Can't find type '" + type + "'")
45 | /* istanbul ignore next */
46 | if (!this.operatorTypes[operator])
47 | throw new Error("Can't find operator '" + operator + "'")
48 |
49 | this.attributeTypes[type].operators[operator] = this.operatorTypes[operator]
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/stores/rest/destroy.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('openrecord:destroy')
2 |
3 | /*
4 | * RECORD
5 | */
6 | exports.record = {
7 | /**
8 | * Destroy a record
9 | * @class Record
10 | * @method destroy
11 | *
12 | * @callback
13 | * @param {boolean} result - will be true if the destroy was successful
14 | * @this Record
15 | *
16 | * @return {Promise}
17 | */
18 | destroy: function() {
19 | const Utils = this.definition.store.utils
20 | var self = this
21 | var primaryKeys = this.definition.primaryKeys
22 |
23 | var options = Utils.clone(self.definition.actions['destroy'])
24 |
25 | for (var i = 0; i < primaryKeys.length; i++) {
26 | options.params[primaryKeys[i]] = this[primaryKeys[i]]
27 | }
28 |
29 | Utils.applyParams(options)
30 |
31 | return self
32 | .callInterceptors('beforeDestroy', [self, options])
33 | .then(function() {
34 | return self.model.definition.store.connection.delete(options.url)
35 | })
36 | .then(function(result) {
37 | debug('delete ' + options.url)
38 | // var validationErrors = obj[self.errorParam || 'error']
39 | // if(typeof validationErrors === 'object' && Object.keys(validationErrors) > 0){
40 | // this.errors.set(validationErrors)
41 | // return resolve(false)
42 | // }
43 | var data = result.data[this.rootParam || 'data']
44 |
45 | if (data) {
46 | self.set(data)
47 | }
48 |
49 | return self.callInterceptors('afterDestroy', [self, options])
50 | })
51 | .then(function(record) {
52 | record.__exists = false
53 | return record
54 | })
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/graphql/data_types.js:
--------------------------------------------------------------------------------
1 | exports.store = {
2 | mixinCallback: function() {
3 | var store = this
4 | store.generateGraphQLTypes()
5 | },
6 |
7 | /*
8 | GraphQLBoolean
9 | GraphQLEnumType
10 | GraphQLFloat
11 | GraphQLID
12 | GraphQLInputObjectType
13 | GraphQLInt
14 | GraphQLInterfaceType
15 | GraphQLList
16 | GraphQLNonNull
17 | GraphQLObjectType
18 | GraphQLScalarType
19 | GraphQLSchema
20 | GraphQLString
21 | GraphQLUnionType
22 | */
23 | generateGraphQLTypes: function() {
24 | var store = this
25 | Object.keys(this.attributeTypes).forEach(function(key) {
26 | var typeDef = store.attributeTypes[key]
27 | var name = typeDef.name
28 | var type
29 | var isList = false
30 |
31 | if (/^\w+_array$/.test(name)) {
32 | name = name.replace('_array', '')
33 | isList = true
34 | }
35 |
36 | switch (name) {
37 | case String:
38 | case 'string':
39 | case 'text':
40 | type = 'String'
41 | break
42 |
43 | case 'integer':
44 | type = 'Int'
45 | break
46 |
47 | case Number:
48 | case 'float':
49 | type = 'Float'
50 | break
51 |
52 | case Boolean:
53 | case 'boolean':
54 | type = 'Boolean'
55 | break
56 |
57 | case Array:
58 | isList = true
59 | type = 'String' // TODO: Remove in 3.0 ?! Does not make any sense?!
60 | break
61 |
62 | default:
63 | type = 'String'
64 | }
65 |
66 | if (isList) {
67 | type = '[' + type + ']'
68 | }
69 |
70 | typeDef.graphQLTypeName = type
71 | })
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/test/definition-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 |
3 | var Store = require('../lib/store')
4 | var Definition = require('../lib/definition')
5 |
6 | describe('Definition: Base', function() {
7 | var store = new Store()
8 |
9 | var User, Post
10 |
11 | store.Model('User', function() {
12 | var self = this
13 |
14 | it('has the correct context', function() {
15 | self.should.be.an.instanceOf(Definition)
16 | })
17 |
18 | it('has validates() mixin method', function() {
19 | should.exist(self.validates)
20 | self.validates.should.be.a.Function()
21 | })
22 |
23 | it('has setter() mixin method', function() {
24 | should.exist(self.setter)
25 | self.setter.should.be.a.Function()
26 | })
27 |
28 | it('has getter() mixin method', function() {
29 | should.exist(self.getter)
30 | self.getter.should.be.a.Function()
31 | })
32 |
33 | it('has event methods', function() {
34 | should.exist(self.on)
35 | self.on.should.be.a.Function()
36 |
37 | should.exist(self.emit)
38 | self.emit.should.be.a.Function()
39 |
40 | should.exist(self._events)
41 | })
42 | })
43 |
44 | store.Model('Post', function() {
45 | this.validatesPresenceOf('title')
46 | })
47 |
48 | before(function() {
49 | return store.ready(function() {
50 | User = store.Model('User')
51 | Post = store.Model('Post')
52 | })
53 | })
54 |
55 | it('every model has it own definition', function() {
56 | User.definition.should.not.be.equal(Post.definition)
57 | })
58 |
59 | it('has no shared variables between models', function() {
60 | User.definition.validations.should.not.be.equal(Post.definition.validations)
61 | })
62 | })
63 |
--------------------------------------------------------------------------------
/lib/stores/sql/transaction.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('openrecord:transaction')
2 |
3 | /*
4 | * MODEL
5 | */
6 | exports.model = {
7 | /**
8 | * Add the current query into a transaction
9 | *
10 | * @class Model
11 | * @method transaction
12 | * @param {object} transaction - The transaction object
13 | *
14 | * @return {Collection}
15 | */
16 | useTransaction: function(transaction) {
17 | var self = this.chain()
18 |
19 | self.setInternal('transaction', transaction)
20 |
21 | return self
22 | },
23 |
24 | getExecOptions: function() {
25 | var transaction = this.getInternal('transaction')
26 |
27 | return this.query({ transaction: transaction })
28 | }
29 | }
30 |
31 | /*
32 | * RECORD
33 | */
34 | exports.store = {
35 | /**
36 | * TODO:... write documentation
37 | *
38 | * @class Record
39 | * @method transaction
40 | * @param {object} transaction - The transaction object
41 | *
42 | */
43 | startTransaction: function(options, callback) {
44 | // var self = this
45 |
46 | if (typeof options === 'function') {
47 | callback = options
48 | options = {}
49 | }
50 |
51 | if (options.transaction) {
52 | return callback(options.transaction)
53 | } else {
54 | return this.connection
55 | .transaction(function(transaction) {
56 | options.transaction = transaction
57 | return callback(transaction)
58 | })
59 | .then(function(result) {
60 | debug('commit')
61 | return result
62 | })
63 | .catch(function(e) {
64 | // TODO: do something with e.g. 'afterSave' rollback message!?!
65 | debug('rollback')
66 | throw e
67 | })
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/test/sql/__shared/autojoin-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 | var Store = require('../../../store')
3 |
4 | module.exports = function(title, beforeFn, afterFn, storeConf) {
5 | describe(title + ': AutoJoin', function() {
6 | var store
7 |
8 | before(beforeFn)
9 | after(function(next) {
10 | afterFn(next, store)
11 | })
12 |
13 | before(function() {
14 | store = new Store(storeConf)
15 |
16 | store.Model('User', function() {
17 | this.hasMany('posts')
18 | this.hasMany('threads')
19 | this.autoJoin()
20 | })
21 | store.Model('Post', function() {
22 | this.belongsTo('user')
23 | this.belongsTo('thread')
24 | this.autoJoin()
25 | })
26 | store.Model('Thread', function() {
27 | this.belongsTo('user')
28 | this.hasMany('posts')
29 | this.autoJoin()
30 | })
31 | })
32 |
33 | describe('autoJoin()', function() {
34 | it('returns the right results on nested joins with nested conditions', function() {
35 | return store.ready(function() {
36 | var Thread = store.Model('Thread')
37 | return Thread.where(
38 | { posts: { user: { login_like: 'phi' } } },
39 | { title_like: 'first' }
40 | )
41 | .order('title', 'posts_user.id')
42 | .exec(function(result) {
43 | result[0].title.should.be.equal('first thread')
44 | result[0]._posts.length.should.be.equal(2)
45 | result[0]._posts[0]._user.login.should.be.equal('phil')
46 | result[0]._posts[1]._user.login.should.be.equal('phil')
47 | should.not.exist(result[1])
48 | })
49 | })
50 | })
51 | })
52 | })
53 | }
54 |
--------------------------------------------------------------------------------
/test/sql/aggregate_function-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../store/sql')
2 |
3 | describe('SQL: Aggregate Functions', function() {
4 | var store
5 | var User
6 |
7 | before(function() {
8 | store = new Store({
9 | type: 'sql'
10 | })
11 |
12 | store.Model('User', function() {
13 | this.attribute('salary', Number)
14 | })
15 |
16 | return store.ready(function() {
17 | User = store.Model('User')
18 | })
19 | })
20 |
21 | describe('count()', function() {
22 | it('has method', function() {
23 | User.count.should.be.a.Function()
24 | })
25 |
26 | it('has the right internal variables', function() {
27 | var Chained = User.count('salary')
28 | Chained.getInternal('count').should.be.equal('salary')
29 | })
30 | })
31 |
32 | describe('sum()', function() {
33 | it('has method', function() {
34 | User.sum.should.be.a.Function()
35 | })
36 |
37 | it('has the right internal variables', function() {
38 | var Chained = User.sum('salary')
39 | Chained.getInternal('sum').should.be.equal('salary')
40 | })
41 | })
42 |
43 | describe('max()', function() {
44 | it('has method', function() {
45 | User.max.should.be.a.Function()
46 | })
47 |
48 | it('has the right internal variables', function() {
49 | var Chained = User.max('salary')
50 | Chained.getInternal('max').should.be.equal('salary')
51 | })
52 | })
53 |
54 | describe('min()', function() {
55 | it('has method', function() {
56 | User.min.should.be.a.Function()
57 | })
58 |
59 | it('has the right internal variables', function() {
60 | var Chained = User.min('salary')
61 | Chained.getInternal('min').should.be.equal('salary')
62 | })
63 | })
64 | })
65 |
--------------------------------------------------------------------------------
/test/rest/client/includes-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../lib/store')
2 |
3 | describe('REST Client: Include', function() {
4 | var store
5 |
6 | before(function() {
7 | store = new Store({
8 | type: 'rest',
9 | url: 'http://localhost:8889',
10 | version: '~1.0'
11 | })
12 |
13 | store.Model('User', function() {
14 | this.attribute('id', Number, { primary: true })
15 | this.attribute('login', String)
16 | this.attribute('email', String)
17 |
18 | this.hasMany('posts')
19 | })
20 |
21 | store.Model('Post', function() {
22 | this.attribute('id', Number, { primary: true })
23 | this.attribute('message', String)
24 | this.attribute('user_id', Number)
25 | this.attribute('thread_id', Number)
26 |
27 | this.belongsTo('user')
28 | })
29 | })
30 |
31 | it('includes a hasMany relation', function() {
32 | return store.ready(function() {
33 | var User = store.Model('User')
34 |
35 | return User.include('posts').exec(function(results) {
36 | results.length.should.be.above(3)
37 | results[0].posts.length.should.be.equal(3)
38 | results[1].posts.length.should.be.equal(1)
39 | results[2].posts.length.should.be.equal(0)
40 | })
41 | })
42 | })
43 |
44 | it('includes a belongsTo relation', function() {
45 | return store.ready(function() {
46 | var Post = store.Model('Post')
47 |
48 | return Post.include('user').exec(function(results) {
49 | results.length.should.be.equal(5)
50 | results[0]._user.id.should.be.equal(1)
51 | results[1]._user.id.should.be.equal(1)
52 | results[2]._user.id.should.be.equal(1)
53 | results[3]._user.id.should.be.equal(2)
54 | })
55 | })
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/test/sql/postgres/uuid_key-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 | var Store = require('../../../store/postgres')
3 |
4 | describe('Postgres: UUID Key', function() {
5 | var store
6 | var database = 'uuid_key_test'
7 |
8 | before(function(next) {
9 | this.timeout(5000)
10 | beforePG(
11 | database,
12 | [
13 | 'CREATE EXTENSION IF NOT EXISTS \\"uuid-ossp\\"',
14 | 'CREATE TABLE uuid_tests (id uuid not null primary key default uuid_generate_v1(), another_column varchar(255))'
15 | ],
16 | next
17 | )
18 | })
19 |
20 | before(function() {
21 | store = new Store({
22 | host: 'localhost',
23 | type: 'postgres',
24 | database: database,
25 | user: 'postgres',
26 | password: ''
27 | })
28 |
29 | store.Model('UuidTest', function() {})
30 | })
31 |
32 | after(function(next) {
33 | afterPG(database, next)
34 | })
35 |
36 | it('attribute id is type uuid', function() {
37 | return store.ready(function() {
38 | var UuidTest = store.Model('UuidTest')
39 | UuidTest.definition.attributes.id.type.name.should.be.equal('uuid')
40 | })
41 | })
42 |
43 | it('new() returns a null id (uuid)', function() {
44 | return store.ready(function() {
45 | var UuidTest = store.Model('UuidTest')
46 | var test = new UuidTest({ another_column: 'i am setting uuid' })
47 | should.not.exist(test.id)
48 | })
49 | })
50 |
51 | it('create() returns a new id (uuid)', function() {
52 | return store.ready(function() {
53 | var UuidTest = store.Model('UuidTest')
54 | var test = new UuidTest({ another_column: 'i am setting uuid' })
55 |
56 | return test.save().then(function() {
57 | should.exist(test.id)
58 | })
59 | })
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## A powerful but simple to use ORM for nodejs.
4 | The primary goal is to offer a clean and easy to use API to your datastore. No matter if you want to connect to Postgres, MySQL or an ActiveDirectory. The API should be similar, but supports all the important features of your database!
5 |
6 | As the name implies, it's open and very easy to extend. The whole package was built [that way](./plugins.md#plugins).
7 |
8 | Currently it supports the following databases/datastores: SQLite3, MySQL, Postgres, Oracle, REST and LDAP (+ ActiveDirectory)
9 | If you want to build a GraphQL endpoint for any of these databases, OPENRECORD has some [built in features](./graphql.md) to support you!
10 |
11 | [Get started](./install.md)
12 |
13 | # Why?
14 |
15 | There are a lot of different ORMs (or similar) in the nodejs ecosystem to choose from. So why OPENRECORD?
16 | This project started many years ago - when the current version of nodejs was < 1.0. At that time there were a handful of ORMs available.
17 | From my point of view, they all had a terrible API and were lacking some important features. Most of them tried to support many different databases and therefore important database specific features were missing.
18 | Supporting different databases if not a bad thing, but the ORM should embrace the differences, instead of unifying them all under a common denominator.
19 |
20 | OPENRECORD was inspired by [ActiveRecord](http://guides.rubyonrails.org/active_record_basics.html), but ultimately has some unique touches on certain areas.
21 |
22 | It's used in production since 2014 for many projects and even powers an application which manages an ActiveDirectory with approximately 10.000 computers and 30.000 - 40.000 users.
23 | Although most of the work was invested into supporting relational databases like Postgres or MySQL.
--------------------------------------------------------------------------------
/test/graphql/context-test.js:
--------------------------------------------------------------------------------
1 | // var should = require('should')
2 |
3 | var types = ['auto', 'custom']
4 |
5 | types.forEach(function(type) {
6 | describe('GraphQL: Context (' + type + ')', function() {
7 | var database = 'context' + type
8 | var query
9 |
10 | before(function(next) {
11 | beforeGraphQL(database, type, function(error, _query) {
12 | query = _query
13 | next(error)
14 | })
15 | })
16 |
17 | after(function(next) {
18 | afterGraphQL(database, next)
19 | })
20 |
21 | it('get `me` via context', function() {
22 | return query(
23 | `{
24 | me{
25 | name
26 | email
27 | }
28 | }`,
29 | { id: 1 }
30 | ).then(function(result) {
31 | result.should.be.eql({
32 | data: {
33 | me: { name: 'phil', email: 'phil@mail.com' }
34 | }
35 | })
36 | })
37 | })
38 |
39 | it('get `me` via context and return additonal relations', function() {
40 | return query(
41 | `{
42 | me{
43 | name
44 | email
45 | recipes{
46 | totalCount
47 | nodes{
48 | title
49 | }
50 | }
51 | }
52 | }`,
53 | { id: 1 }
54 | ).then(function(result) {
55 | result.should.be.eql({
56 | data: {
57 | me: {
58 | name: 'phil',
59 | email: 'phil@mail.com',
60 | recipes: {
61 | nodes: [
62 | { title: 'Toast Hawaii' },
63 | { title: 'scrambled eggs' },
64 | { title: 'Steak' }
65 | ],
66 | totalCount: 3
67 | }
68 | }
69 | }
70 | })
71 | })
72 | })
73 | })
74 | })
75 |
--------------------------------------------------------------------------------
/test/rest/client/create-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 | var Store = require('../../../lib/store')
3 |
4 | describe('REST Client: Create', function() {
5 | var store
6 |
7 | before(function() {
8 | store = new Store({
9 | type: 'rest',
10 | url: 'http://localhost:8889',
11 | version: '~1.0',
12 | autoSave: true
13 | })
14 |
15 | store.Model('User', function() {
16 | this.attribute('id', Number, { primary: true })
17 | this.attribute('login', String)
18 | this.attribute('email', String)
19 |
20 | this.hasMany('posts')
21 | })
22 |
23 | store.Model('Post', function() {
24 | this.attribute('id', Number, { primary: true })
25 | this.attribute('message', String)
26 | this.attribute('user_id', Number)
27 | this.attribute('thread_id', Number)
28 |
29 | this.belongsTo('user')
30 | })
31 | })
32 |
33 | it('creates a new record (create)', function() {
34 | return store.ready(function() {
35 | var User = store.Model('User')
36 |
37 | return User.create({ login: 'max', email: 'max@mail.com' }).then(function(
38 | user
39 | ) {
40 | should.exist(user.id)
41 | })
42 | })
43 | })
44 |
45 | it('creates nested records (create)', function() {
46 | return store.ready(function() {
47 | var User = store.Model('User')
48 |
49 | var user = User.new({ login: 'hugo', email: 'hugo@mail.com' })
50 |
51 | user.posts.add({
52 | message: 'hugo post',
53 | thread_id: 3
54 | })
55 |
56 | return user.save().then(function(user) {
57 | return User.find(user.id)
58 | .include('posts')
59 | .exec(function(user) {
60 | user.posts.length.should.be.equal(1)
61 | user.posts[0].message.should.be.equal('hugo post')
62 | })
63 | })
64 | })
65 | })
66 | })
67 |
--------------------------------------------------------------------------------
/lib/base/relations/has.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | has: function(name, options) {
3 | const store = this.store
4 | options = options || {}
5 | options.type = 'has'
6 |
7 | options.preInitialize = options.preInitialize || function() {}
8 | options.initialize = options.initialize || function() {}
9 |
10 | options.loadFromConditions = options.loadFromConditions || function(){
11 | return undefined
12 | }
13 |
14 | if(!options.loadFromRecords && !options.query) throw new Error('You need to implement a `query(store, parentRecords)` method!')
15 |
16 | options.loadFromRecords = options.loadFromRecords || function(parentCollection){
17 | return options.query(store, parentCollection)
18 | .then(function(result) {
19 | // add the result to corresponding record
20 | parentCollection.forEach(function(record) {
21 | record.relations[name] = options.convert(record, result)
22 | })
23 |
24 | return result
25 | })
26 | }
27 |
28 | options.loadFromRecord = options.loadFromRecord || function(parentRecord){
29 | return options.query(store, [parentRecord])
30 | .then(function(result) {
31 | parentRecord.relations[name] = options.convert(parentRecord, result)
32 | return result
33 | })
34 | }
35 |
36 | if(!options.filterCache && !options.convert) throw new Error('You need to implement a `convert(parent, results)` method!')
37 |
38 | options.filterCache = options.filterCache || function(parentRecord, records){
39 | return options.convert(parentRecord, records)
40 | }
41 |
42 | options.collection = options.collection || function(parentRecord){
43 | return options.convert(parentRecord, null)
44 | }
45 |
46 | options.getter = options.getter || function() {
47 | return this['_' + name]
48 | }
49 |
50 |
51 | return this.relation(name, options)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/docs/context.md:
--------------------------------------------------------------------------------
1 | # Context
2 |
3 | If you use OPENRECORD in a web application or similar, you most probably want to have access to some request information (e.g. the user session).
4 | Instead of calling a [scope](./definition#scopes) with this information it's better to use `setContext(context)`. OPENRECORD will provide you with the context everywhere you need it.
5 |
6 | Here is a very basic example:
7 | ```js
8 | // ./models(Post)
9 | module.exports = function(){
10 | this.beforeUpdate(function(){
11 | // here we use the `this.context`.
12 | // `this` is the record
13 | // check if the user is an admin or the creator of that post
14 | if(!this.context || (this.context.role !== 'admin' && this.context.id !== this.creator_id)){
15 | throw new Error('insufficient permissions')
16 | }
17 | })
18 |
19 | this.beforeFind(function(){
20 | // `this` is the model
21 | // `this.context` is also available
22 | })
23 | }
24 | ```
25 |
26 | ```js
27 | var myContext = {role: 'admin', id: 1}
28 |
29 | post = await Post.setContext(myContext).find(id)
30 | post.title = 'OPENRECORD!!'
31 | await post.save()
32 | ```
33 |
34 | In the above example we call `setContext()` on the `Post` model, but the context object won't be set globally!
35 | It will instead only be available for all [hooks](./definition.md#hooks) involved in the initial find, and all corresponding records (the result)!
36 | So if you do multiple queries, you have to use `setContext` for every new query:
37 |
38 | ```js
39 | const [users, totalCount] = await Promise.all([
40 | User.limit(10).setContext(myContext),
41 | User.count().setContext(myContext)
42 | ])
43 | ```
44 |
45 | If you are [reusing a query](./query.md#chaining) only one `setContext` is neccesary:
46 | ```js
47 | const query = User.setContext(myContext)
48 | const [users, totalCount] = await Promise.all([
49 | query.limit(10),
50 | query.clone().totalCount()
51 | ])
52 | ```
53 |
--------------------------------------------------------------------------------
/test/base/convert-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../store/base')
2 |
3 | describe('Convert', function() {
4 | describe('convertInput()', function() {
5 | var store = new Store()
6 |
7 | store.Model('User', function() {
8 | this.attribute('login', String)
9 | this.attribute('other', String)
10 |
11 | this.convertInput('login', function(value) {
12 | return '$' + value
13 | })
14 | })
15 |
16 | var User, user
17 | before(function() {
18 | return store.ready(function() {
19 | User = store.Model('User')
20 | user = new User({ login: 'admin', other: 'foo' })
21 | })
22 | })
23 |
24 | it('converts the value accordingly', function() {
25 | user.login.should.be.equal('$admin')
26 | })
27 |
28 | it('converts only the specified value', function() {
29 | user.other.should.be.equal('foo')
30 | })
31 | })
32 |
33 | describe('convertOutput()', function() {
34 | var store = new Store()
35 |
36 | store.Model('User', function() {
37 | this.attribute('login', String)
38 | this.attribute('other', String)
39 |
40 | this.convertOutput('login', function(value) {
41 | return '$' + value
42 | })
43 | })
44 |
45 | var User, user
46 | before(function() {
47 | return store.ready(function() {
48 | User = store.Model('User')
49 | user = new User({ login: 'admin', other: 'foo' })
50 | })
51 | })
52 |
53 | it('converts the value accordingly', function(done) {
54 | user.toJson().login.should.be.equal('$admin')
55 | done()
56 | })
57 |
58 | it('converts only the specified value', function(done) {
59 | user.toJson().other.should.be.equal('foo')
60 | done()
61 | })
62 |
63 | it('does not change the original value', function(done) {
64 | user.attributes.login.should.be.equal('admin')
65 | done()
66 | })
67 | })
68 | })
69 |
--------------------------------------------------------------------------------
/lib/base/every.js:
--------------------------------------------------------------------------------
1 | /*
2 | * CHAIN
3 | */
4 | exports.chain = {
5 | mixinCallback: function() {
6 | /**
7 | * You could call `.every` on every Collection of records to get a special Record (PseudoRecord).
8 | * This PseudoRecord has all the properties and methods a normal Record of this Model has, but it will behave different
9 | * E.g. getting an attributes value `Collection.every.id` will return an array of all record's ids.
10 | * The same will work with setting an attribute or calling a method.
11 | *
12 | * @class Collection
13 | * @name .every
14 | *
15 | * @return {PseudoRecord}
16 | */
17 |
18 | // returns a pseudo record
19 | this.__defineGetter__('every', function() {
20 | var self = this
21 | var pseudo = new this.model() // eslint-disable-line
22 |
23 | for (var name in pseudo) {
24 | ;(function(name, value) {
25 | if (typeof value === 'function') {
26 | // replace methods
27 | pseudo[name] = function() {
28 | for (var i = 0; i < self.length; i++) {
29 | var record = self[i]
30 | record[name].apply(record, arguments)
31 | }
32 | }
33 | } else {
34 | // replace attribute
35 | pseudo.__defineGetter__(name, function() {
36 | var tmp = []
37 | for (var i = 0; i < self.length; i++) {
38 | var record = self[i]
39 | tmp.push(record[name])
40 | }
41 | return tmp
42 | })
43 |
44 | pseudo.__defineSetter__(name, function(value) {
45 | for (var i = 0; i < self.length; i++) {
46 | var record = self[i]
47 | record[name] = value
48 | }
49 | })
50 | }
51 | })(name, pseudo[name])
52 | }
53 |
54 | return pseudo
55 | })
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/stores/ldap/attributes.js:
--------------------------------------------------------------------------------
1 | const parseDN = require('ldapjs').parseDN
2 |
3 | /*
4 | * DEFINITION
5 | */
6 | exports.definition = {
7 | mixinCallback: function() {
8 | var self = this
9 |
10 | this.attribute('dn', 'dn', { emit_events: true }) // Well... it's not a realy primary key, but it's used internaly for relations...
11 | this.attribute('parent_dn', 'dn', { persistent: false, emit_events: true })
12 | this.attribute('objectClass', 'object_class', { default: self.objectClass })
13 | self.use(function() {
14 | // delay!
15 | this.attribute(self.rdn_prefix, String, { persistent: false })
16 | })
17 |
18 | this.on('parent_dn_changed', function(record, oldValue, value) {
19 | if (value) {
20 | record.dn = self.dn(record)
21 | }
22 | })
23 |
24 | this.on('dn_changed', function(record, oldValue, value) {
25 | if (value) {
26 | record.attributes.parent_dn = self.store.utils.normalizeDn(
27 | parseDN(value)
28 | .parent()
29 | .toString()
30 | ) // sets the parent_dn, but we don't want to fire a change event
31 | }
32 | })
33 |
34 | // add all attribute names to the search attributes
35 | this.beforeFind(function(options) {
36 | options.attributes = Object.keys(this.definition.attributes)
37 | }, 90)
38 | }
39 | }
40 |
41 | exports.store = {
42 | getAllAvailableAttributes: function(binaryOnly) {
43 | var tmp = []
44 |
45 | for (var i in this.models) {
46 | if (this.models[i].definition) {
47 | for (var name in this.models[i].definition.attributes) {
48 | if (this.models[i].definition.attributes.hasOwnProperty(name)) {
49 | if (!binaryOnly || (this.models[i].definition.attributes[name].type.binary)) {
50 | tmp.push(name)
51 | }
52 | }
53 | }
54 | }
55 | }
56 |
57 | return this.utils.uniq(tmp)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/sql/__shared/plugins/paranoid-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 | var Store = require('../../../../store')
3 |
4 | module.exports = function(title, beforeFn, afterFn, storeConf) {
5 | describe(title + ': Paranoid', function() {
6 | var store
7 |
8 | before(beforeFn)
9 | after(function(next) {
10 | afterFn(next, store)
11 | })
12 |
13 | before(function() {
14 | store = new Store(storeConf)
15 |
16 | store.Model('User', function() {
17 | this.paranoid()
18 | })
19 | })
20 |
21 | it('returns only records with deleted_at IS NULL', function() {
22 | return store.ready(function() {
23 | var User = store.Model('User')
24 | return User.where({ login_not: 'hans' }).exec(function(records) {
25 | records.length.should.be.equal(2)
26 | })
27 | })
28 | })
29 |
30 | it('returns all records with withDeleted() scope', function() {
31 | return store.ready(function() {
32 | var User = store.Model('User')
33 | return User.where({ login_not: 'hans' })
34 | .withDeleted()
35 | .exec(function(records) {
36 | records.length.should.be.equal(4)
37 | })
38 | })
39 | })
40 |
41 | it('"deletes" a record', function() {
42 | return store.ready(function() {
43 | var User = store.Model('User')
44 | return User.find(5).exec(function(hans) {
45 | return hans.destroy().then(function() {
46 | return User.find(5).exec(function(delHans) {
47 | should.not.exist(delHans)
48 |
49 | return User.find(5)
50 | .withDeleted()
51 | .exec(function(existingHans) {
52 | existingHans.login.should.be.equal('hans')
53 | should.exist(existingHans.deleted_at)
54 | })
55 | })
56 | })
57 | })
58 | })
59 | })
60 | })
61 | }
62 |
--------------------------------------------------------------------------------
/lib/stores/activedirectory/data_types/guid.js:
--------------------------------------------------------------------------------
1 | const ldap = require('ldapjs')
2 | /* istanbul ignore next: unable to test via travis-ci */
3 | exports.store = {
4 | mixinCallback: function() {
5 | this.addType(
6 | 'guid',
7 | {
8 | read: function(value) {
9 | const data = Buffer.from(value, 'binary')
10 | var template =
11 | '{3}{2}{1}{0}-{5}{4}-{7}{6}-{8}{9}-{10}{11}{12}{13}{14}{15}'
12 |
13 | for (var i = 0; i < data.length; i++) {
14 | var dataStr = data[i].toString(16)
15 | dataStr = data[i] >= 16 ? dataStr : '0' + dataStr
16 | template = template.replace(
17 | new RegExp('\\{' + i + '\\}', 'g'),
18 | dataStr
19 | )
20 | }
21 | return template
22 | },
23 |
24 | write: function(value) {
25 | if (!value) return null
26 |
27 | value = value.replace(
28 | /(.{2})(.{2})(.{2})(.{2})-(.{2})(.{2})-(.{2})(.{2})-(.{2})(.{2})-(.{12})/,
29 | '$4$3$2$1$6$5$8$7$9$10$11'
30 | )
31 |
32 | if (Buffer.from) return Buffer.from(value, 'hex')
33 | return new Buffer(value, 'hex') // eslint-disable-line node/no-deprecated-api
34 | // return value; //not yet supported by ldapjs
35 | // return '\\' + value.toString('hex').match(/.{2}/g).join('\\')
36 | }
37 | },
38 | {
39 | binary: true,
40 | operators: {
41 | defaults: ['eq'],
42 | eq: {
43 | defaultMethod: function(attr, value, options) {
44 | value = this.definition.cast(attr, value, 'write', this)
45 | options.filter.filters.push(
46 | new ldap.EqualityFilter({ attribute: attr, value: value })
47 | )
48 | },
49 |
50 | on: {
51 | all: true
52 | }
53 | }
54 | }
55 | }
56 | )
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/test/sql/__shared/plugins/total_count-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../../store')
2 |
3 | module.exports = function(title, beforeFn, afterFn, storeConf) {
4 | describe(title + ': totalCount', function() {
5 | var store
6 |
7 | before(beforeFn)
8 | after(function(next) {
9 | afterFn(next, store)
10 | })
11 |
12 | before(function() {
13 | store = new Store(storeConf)
14 |
15 | store.Model('User', function() {})
16 | })
17 |
18 | it('returns the total count', function() {
19 | return store.ready(function() {
20 | var User = store.Model('User')
21 | return User.totalCount().then(function(count) {
22 | count.should.be.equal(5)
23 | })
24 | })
25 | })
26 |
27 | it('returns the total count by field', function() {
28 | return store.ready(function() {
29 | var User = store.Model('User')
30 | return User.totalCount('deleted_at').then(function(count) {
31 | count.should.be.equal(2)
32 | })
33 | })
34 | })
35 |
36 | it('ignores invalid field names', function() {
37 | return store.ready(function() {
38 | var User = store.Model('User')
39 | return User.totalCount({}).then(function(count) {
40 | count.should.be.equal(5)
41 | })
42 | })
43 | })
44 |
45 | it('resets limit', function() {
46 | return store.ready(function() {
47 | var User = store.Model('User')
48 | return User.limit(2)
49 | .totalCount()
50 | .then(function(count) {
51 | count.should.be.equal(5)
52 | })
53 | })
54 | })
55 |
56 | it('resets limit (called after)', function() {
57 | return store.ready(function() {
58 | var User = store.Model('User')
59 | return User.totalCount()
60 | .limit(2)
61 | .then(function(count) {
62 | count.should.be.equal(5)
63 | })
64 | })
65 | })
66 | })
67 | }
68 |
--------------------------------------------------------------------------------
/test/sql/interceptors-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 |
3 | var Store = require('../../store/sql')
4 |
5 | describe('SQL: Interceptors', function() {
6 | var store = new Store({
7 | type: 'sql'
8 | })
9 |
10 | store.Model('User', function() {
11 | var self = this
12 |
13 | it('has beforeCreate()', function() {
14 | should.exist(self.beforeCreate)
15 | self.beforeCreate.should.be.a.Function()
16 | })
17 |
18 | it('has afterCreate()', function() {
19 | should.exist(self.afterCreate)
20 | self.afterCreate.should.be.a.Function()
21 | })
22 |
23 | it('has beforeUpdate()', function() {
24 | should.exist(self.beforeUpdate)
25 | self.beforeUpdate.should.be.a.Function()
26 | })
27 |
28 | it('has afterUpdate()', function() {
29 | should.exist(self.afterUpdate)
30 | self.afterUpdate.should.be.a.Function()
31 | })
32 |
33 | it('has beforeDestroy()', function() {
34 | should.exist(self.beforeDestroy)
35 | self.beforeDestroy.should.be.a.Function()
36 | })
37 |
38 | it('has afterDestroy()', function() {
39 | should.exist(self.afterDestroy)
40 | self.afterDestroy.should.be.a.Function()
41 | })
42 |
43 | it('has beforeSave()', function() {
44 | should.exist(self.beforeSave)
45 | self.beforeSave.should.be.a.Function()
46 | })
47 |
48 | it('has afterSave()', function() {
49 | should.exist(self.afterSave)
50 | self.afterSave.should.be.a.Function()
51 | })
52 |
53 | it('has beforeFind()', function() {
54 | should.exist(self.beforeFind)
55 | self.beforeFind.should.be.a.Function()
56 | })
57 |
58 | it('has afterFind()', function() {
59 | should.exist(self.afterFind)
60 | self.afterFind.should.be.a.Function()
61 | })
62 |
63 | it('has beforeValidation()', function() {
64 | should.exist(self.beforeValidation)
65 | self.beforeValidation.should.be.a.Function()
66 | })
67 | })
68 | })
69 |
--------------------------------------------------------------------------------
/lib/stores/sql/plugins/stampable.js:
--------------------------------------------------------------------------------
1 | exports.migration = {
2 | stampable: function () {
3 | this.timestamp()
4 | this.userstamp()
5 | },
6 |
7 | timestamp: function () {
8 | this.datetime('created_at', {
9 | comment: 'Time of insert',
10 | default: 'NOW()'
11 | })
12 | this.datetime('updated_at', {
13 | comment: 'Time of last update',
14 | default: 'NOW()'
15 | })
16 | },
17 |
18 | userstamp: function () {
19 | this.id('creator_id', {
20 | comment: 'Identifier of the creator '
21 | })
22 | this.id('updater_id', {
23 | comment: 'Identifier of the last modifier'
24 | })
25 | }
26 | }
27 |
28 | exports.definition = {
29 | stampable: function () {
30 | var self = this
31 | this.beforeSave(function () {
32 | var now = new Date()
33 |
34 | var userId = null
35 |
36 | if (typeof self.store.getUserByFn === 'function') {
37 | userId = self.store.getUserByFn(this, self)
38 | } else {
39 | if (this.context && this.context.user) {
40 | userId = this.context.user.id
41 | }
42 | }
43 |
44 | if (!this.__exists) {
45 | if (self.attributes.created_at) {
46 | this.created_at = this.created_at || now
47 | }
48 |
49 | if (self.attributes.creator_id) {
50 | this.creator_id = this.creator_id || userId
51 | }
52 | }
53 |
54 | if (this.hasChanges()) {
55 | // only set updated_at or updater_id if there are any changes
56 | if (self.attributes.updated_at && !this.hasChanged('updated_at')) {
57 | this.updated_at = now
58 | }
59 |
60 | if (self.attributes.updater_id && !this.hasChanged('updater_id')) {
61 | this.updater_id = userId || this.updater_id
62 | }
63 | }
64 |
65 | return true
66 | })
67 |
68 | return this
69 | }
70 | }
71 |
72 | exports.store = {
73 | getUserBy: function (callback) {
74 | this.getUserByFn = callback
75 | }
76 | }
--------------------------------------------------------------------------------
/test/rest/client/exec-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../lib/store')
2 |
3 | describe('REST Client: Exec', function() {
4 | var store
5 |
6 | before(function() {
7 | store = new Store({
8 | type: 'rest',
9 | url: 'http://localhost:8889',
10 | version: '~1.0'
11 | })
12 |
13 | store.Model('User', function() {
14 | this.attribute('id', Number, { primary: true })
15 | this.attribute('login', String)
16 | this.attribute('email', String)
17 | })
18 | })
19 |
20 | it('loads records from the rest server (index)', function() {
21 | return store.ready(function() {
22 | var User = store.Model('User')
23 |
24 | return User.exec(function(results) {
25 | results.length.should.be.above(2)
26 | })
27 | })
28 | })
29 |
30 | it('loads one record from the rest server (show)', function() {
31 | return store.ready(function() {
32 | var User = store.Model('User')
33 |
34 | return User.find(2).exec(function(result) {
35 | result.id.should.be.equal(2)
36 | result.login.should.be.equal('michl')
37 | })
38 | })
39 | })
40 |
41 | it('loads filtered records from the rest server (index)', function() {
42 | return store.ready(function() {
43 | var User = store.Model('User')
44 |
45 | return User.where({ login: 'michl' }).exec(function(result) {
46 | result.length.should.be.equal(1)
47 | result[0].id.should.be.equal(2)
48 | result[0].login.should.be.equal('michl')
49 | })
50 | })
51 | })
52 |
53 | it('loads filtered records from the rest server via promise (index)', function() {
54 | return store.ready(function() {
55 | var User = store.Model('User')
56 |
57 | return User.where({ login: 'michl' })
58 | .exec()
59 | .then(function(result) {
60 | result.length.should.be.equal(1)
61 | result[0].id.should.be.equal(2)
62 | result[0].login.should.be.equal('michl')
63 | })
64 | })
65 | })
66 | })
67 |
--------------------------------------------------------------------------------
/test/rest/client/update-test.js:
--------------------------------------------------------------------------------
1 | var Store = require('../../../lib/store')
2 |
3 | describe('REST Client: Update', function() {
4 | var store
5 |
6 | before(function() {
7 | store = new Store({
8 | type: 'rest',
9 | url: 'http://localhost:8889',
10 | version: '~1.0',
11 | autoSave: true
12 | })
13 |
14 | store.Model('User', function() {
15 | this.attribute('id', Number, { primary: true })
16 | this.attribute('login', String)
17 | this.attribute('email', String)
18 |
19 | this.hasMany('posts')
20 | })
21 |
22 | store.Model('Post', function() {
23 | this.attribute('id', Number, { primary: true })
24 | this.attribute('message', String)
25 | this.attribute('user_id', Number)
26 | this.attribute('thread_id', Number)
27 |
28 | this.belongsTo('user')
29 | })
30 | })
31 |
32 | it('updates a record (update)', function() {
33 | return store.ready(function() {
34 | var User = store.Model('User')
35 | return User.find(1).exec(function(record) {
36 | record.id.should.be.equal(1)
37 | record.login = 'philipp'
38 | return record.save()
39 | })
40 | })
41 | })
42 |
43 | it('updates nested records (update)', function() {
44 | return store.ready(function() {
45 | var User = store.Model('User')
46 | return User.find(2)
47 | .include('posts')
48 | .exec(function(record) {
49 | record.login.should.be.equal('michl')
50 | record.posts.length.should.be.equal(1)
51 |
52 | record.login = 'michael'
53 | record.posts[0].message = 'michaels post'
54 |
55 | return record.save().then(function() {
56 | return User.find(2)
57 | .include('posts')
58 | .exec(function(record) {
59 | record.login.should.be.equal('michael')
60 | record.posts.length.should.be.equal(1)
61 | record.posts[0].message.should.be.equal('michaels post')
62 | })
63 | })
64 | })
65 | })
66 | })
67 | })
68 |
--------------------------------------------------------------------------------
/lib/stores/activedirectory/data_types/user_account_control.js:
--------------------------------------------------------------------------------
1 | var UserAccountControlBitmask = {
2 | SCRIPT: 1,
3 | ACCOUNTDISABLED: 2,
4 | HOMEDIR_REQUIRED: 8,
5 | LOCKOUT: 16,
6 | PASSWD_NOTREQUIRED: 32,
7 | PASSWD_CANT_CHANGE: 64,
8 | ENCRYPTED_TEXT_PWD_ALLOWED: 128,
9 | TEMP_DUPLICATE_ACCOUNT: 256,
10 | NORMAL_ACCOUNT: 512,
11 | INTERDOMAIN_TRUST_ACCOUNT: 2048,
12 | WORKSTATION_TRUST_ACCOUNT: 4096,
13 | SERVER_TRUST_ACCOUNT: 8192,
14 | DONT_EXPIRE_PASSWORD: 65536,
15 | MNS_LOGON_ACCOUNT: 131072,
16 | SMARTCARD_REQUIRED: 262144,
17 | TRUSTED_FOR_DELEGATION: 524288,
18 | NOT_DELEGATED: 1048576,
19 | USE_DES_KEY_ONLY: 2097152,
20 | DONT_REQ_PREAUTH: 4194304,
21 | PASSWORD_EXPIRED: 8388608,
22 | TRUSTED_TO_AUTH_FOR_DELEGATION: 16777216,
23 | PARTIAL_SECRETS_ACCOUNT: 67108864
24 | }
25 |
26 | /* istanbul ignore next: unable to test via travis-ci */
27 | exports.store = {
28 | mixinCallback: function() {
29 | this.addType(
30 | 'user_account_control',
31 | {
32 | read: function(value) {
33 | if (typeof value === 'string') value = parseInt(value, 10)
34 |
35 | var obj = {}
36 | for (var attrName in UserAccountControlBitmask) {
37 | obj[attrName] =
38 | (value & UserAccountControlBitmask[attrName]) ===
39 | UserAccountControlBitmask[attrName]
40 | }
41 |
42 | return obj
43 | },
44 |
45 | write: function(value) {
46 | if (typeof value === 'number') return value
47 | if (!value) value = {}
48 |
49 | var bitmask = 0
50 | for (var attrName in UserAccountControlBitmask) {
51 | if (value[attrName] === true)
52 | bitmask += UserAccountControlBitmask[attrName]
53 | }
54 | return bitmask
55 | }
56 | },
57 | {
58 | binary: true,
59 | defaults: {
60 | track_object_changes: true
61 | },
62 | operators: {
63 | default: 'eq',
64 | defaults: ['eq', 'not']
65 | }
66 | }
67 | )
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/stores/ldap/relations/has_many.js:
--------------------------------------------------------------------------------
1 | exports.definition = {
2 | hasMany: function(name, options) {
3 | options = options || {}
4 | if (!options.from) options.from = 'dn'
5 |
6 | const definition = this
7 | const utils = definition.store.utils
8 |
9 | options.setter =
10 | options.setter ||
11 | function(records) {
12 | if (records === this.relations[name]) return
13 |
14 | var chain = this.relations[name]
15 |
16 | // if no relational data exists (e.g. Parent.new())
17 | // create a new collection
18 | if (!chain) {
19 | chain = definition.model.chain({ polymorph: true })
20 | chain.setInternal('relation', options) // add the parent relation of this collection
21 | chain.setInternal('relation_to', this) // + the parent record
22 | chain._resolved()
23 | }
24 |
25 | // lazy remove unused child records (triggered on save by the parent record!)
26 | const recordsToRemove = utils.distinctRecords(chain, records, ['dn'])
27 |
28 | chain.setInternal('__clear_only', recordsToRemove)
29 | chain.clear()
30 | chain.add(records)
31 | }
32 |
33 | options.clear = function(parent, collection) {
34 | const records = collection.getInternal('__clear_only') || collection
35 | if (!records || records.length === 0) return
36 |
37 | collection.clearInternal('__clear_only')
38 |
39 | parent.relations[name]._lazyOperation(function(transOptions) {
40 | if (options.dependent === 'destroy') {
41 | const jobs = []
42 | records.forEach(function(record) {
43 | jobs.push(function() {
44 | return record.destroy(transOptions)
45 | })
46 | })
47 | return utils.parallel(jobs)
48 | }
49 | })
50 |
51 | // remove record from collection
52 | records.forEach(function(record) {
53 | const index = collection.indexOf(record)
54 | collection.splice(index, 1)
55 | })
56 | }
57 |
58 | return this.callParent(name, options)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/test/sql/postgres/enum_type-test.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | var Store = require('../../../store/postgres')
4 |
5 | describe('Postgres: ENUM Attribute', function() {
6 | var store
7 | var database = 'enum_attributes_test'
8 |
9 | before(function(next) {
10 | this.timeout(5000)
11 | beforePG(database, [], next)
12 | })
13 |
14 | before(function() {
15 | store = new Store({
16 | host: 'localhost',
17 | type: 'postgres',
18 | database: database,
19 | user: 'postgres',
20 | password: '',
21 | migrations: path.join(__dirname, 'fixtures', 'migrations', '*'),
22 | plugins: require('../../../lib/base/dynamic_loading')
23 | })
24 |
25 | store.Model('EnumTest', function() {})
26 | })
27 |
28 | after(function(next) {
29 | afterPG(database, next)
30 | })
31 |
32 | it('has enum_attribute', function() {
33 | return store.ready(function() {
34 | var EnumTest = store.Model('EnumTest')
35 |
36 | var attrs = EnumTest.definition.attributes
37 |
38 | attrs.should.have.property('enum_attribute')
39 | })
40 | })
41 |
42 | it('enum_attribute is a string', function() {
43 | return store.ready(function() {
44 | var EnumTest = store.Model('EnumTest')
45 |
46 | var attrs = EnumTest.definition.attributes
47 |
48 | attrs.enum_attribute.type.name.should.be.equal('string')
49 | })
50 | })
51 |
52 | it('does not allow to save any other value than defined in enum', function() {
53 | return store
54 | .ready(function() {
55 | var EnumTest = store.Model('EnumTest')
56 |
57 | return EnumTest.create({
58 | enum_attribute: 'unknown'
59 | })
60 | })
61 | .should.be.rejectedWith(store.ValidationError, {
62 | errors: { enum_attribute: ['only allow one of [foo, bar]'] }
63 | })
64 | })
65 |
66 | it('saves valid enum value', function() {
67 | return store.ready(function() {
68 | var EnumTest = store.Model('EnumTest')
69 |
70 | return EnumTest.create({
71 | enum_attribute: 'foo'
72 | })
73 | })
74 | })
75 | })
76 |
--------------------------------------------------------------------------------
/test/ldap/client/exec-test.js:
--------------------------------------------------------------------------------
1 | var should = require('should')
2 |
3 | var Store = require('../../../lib/store')
4 |
5 | describe('LDAP Client: Exec', function() {
6 | var store
7 |
8 | before(function() {
9 | store = new Store({
10 | type: 'ldap',
11 | url: 'ldap://0.0.0.0:1389',
12 | base: 'dc=test',
13 | user: 'cn=root',
14 | password: 'secret',
15 | autoSave: true
16 | })
17 |
18 | store.Model('User', function() {
19 | this.attribute('username')
20 | })
21 |
22 | store.Model('Ou', function() {
23 | this.rdnPrefix('ou')
24 | })
25 | })
26 |
27 | it('get all user objects of the root ou', function() {
28 | return store.ready(function() {
29 | var User = store.Model('User')
30 | return User.recursive(false).exec(function(users) {
31 | users.length.should.be.equal(2)
32 | })
33 | })
34 | })
35 |
36 | it('user object has standard attributes', function() {
37 | return store.ready(function() {
38 | var User = store.Model('User')
39 | return User.recursive(false).exec(function(users) {
40 | var user = users[0]
41 |
42 | user.dn.should.endWith('dc=test')
43 | user.objectClass.should.be.eql(['user'])
44 | })
45 | })
46 | })
47 |
48 | it('get all user objects!', function() {
49 | return store.ready(function() {
50 | var User = store.Model('User')
51 | return User.exec(function(users) {
52 | users.length.should.be.above(4)
53 | })
54 | })
55 | })
56 |
57 | it('get all user objects of another ou', function() {
58 | return store.ready(function() {
59 | var User = store.Model('User')
60 | return User.searchRoot('ou=others, dc=test').exec(function(users) {
61 | users.length.should.be.equal(2)
62 | })
63 | })
64 | })
65 |
66 | it('do a find on a not existing user object', function() {
67 | return store.ready(function() {
68 | var User = store.Model('User')
69 | return User.find('ou=others, dc=test').exec(function(user) {
70 | should.not.exist(user)
71 | })
72 | })
73 | })
74 | })
75 |
--------------------------------------------------------------------------------
/lib/graphql/type.js:
--------------------------------------------------------------------------------
1 | exports.model = {
2 | /**
3 | * returns a string represing the model as a graphql type
4 | * @param {Object} options Optional options
5 | * @param {String} options.name Overwrite the type name (Default: Model name)
6 | * @param {String} options.description Set a description for the type
7 | * @param {Array} options.exclude Array of fields to exclude
8 | * @return {String} The graphql type
9 | */
10 | toGraphQLType: function(options) {
11 | options = options || {}
12 | const definition = this.definition
13 | const graphqlHelper = definition.graphqlHelper || { fields: {} }
14 | const exclude = options.exclude || graphqlHelper.exclude || []
15 | const result = []
16 | const customDone = {}
17 |
18 | options.description = options.description || graphqlHelper.description
19 |
20 | if (options.description) result.push('# ' + options.description)
21 | result.push('type ' + (options.name || definition.modelName) + ' {')
22 |
23 | Object.keys(definition.attributes)
24 | .sort()
25 | .forEach(function(key) {
26 | var attribute = definition.attributes[key]
27 | if (attribute.hidden) return // hidden fields are for example auto generated `_ids` fields
28 | if (exclude.indexOf(key) !== -1) return
29 | if (!attribute.type.graphQLTypeName) return // auto generated graphql type via openrecord type
30 | if (graphqlHelper.fields[key]) {
31 | // field overwrite via this.graphQLField('field definition...')
32 | result.push(' ' + graphqlHelper.fields[key])
33 | customDone[key] = true
34 | return
35 | }
36 |
37 | var attr = [' ']
38 | attr.push(key)
39 | attr.push(': ')
40 | attr.push(attribute.type.graphQLTypeName)
41 | if (attribute.notnull) attr.push('!')
42 | if (attribute.comment) attr.push(' # ' + attribute.comment)
43 |
44 | result.push(attr.join(''))
45 | })
46 |
47 | Object.keys(graphqlHelper.fields).forEach(function(key) {
48 | if (customDone[key]) return
49 | result.push(' ' + graphqlHelper.fields[key])
50 | })
51 |
52 | result.push('}')
53 |
54 | return result.join('\n')
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/stores/sql/validations.js:
--------------------------------------------------------------------------------
1 | /*
2 | * DEFINITION
3 | */
4 | exports.definition = {
5 | /**
6 | * This validator checks the uniqness of the given field`s value before save.
7 | * @class Definition
8 | * @method validatesUniquenessOf
9 | * @param {array} fields - The fields to validate
10 | * @or
11 | * @param {string} fields - The field to validate
12 | * @param {object} options - Optional: Options hash
13 | *
14 | * @options
15 | * @param {string} scope - Set a scope column
16 | *
17 | * @return {Definition}
18 | */
19 | validatesUniquenessOf: function() {
20 | var args = this.store.utils.args(arguments)
21 | if (args.length > 1 && typeof args[1] === 'string') {
22 | return this.validateFieldsHelper(args, this.validatesUniquenessOf)
23 | }
24 |
25 | var field = args[0]
26 | var options = args[1] || {}
27 | var self = this
28 |
29 | if (Array.isArray(field)) {
30 | return this.validateFieldsHelper(
31 | field,
32 | [options],
33 | this.validatesUniquenessOf
34 | )
35 | }
36 |
37 | return this.validates(field, function() {
38 | var record = this
39 | var primaryKeys = self.primaryKeys
40 | var condition = {}
41 | var i
42 |
43 | if (this[field] === null) {
44 | return
45 | }
46 |
47 | if (!this.hasChanged(field)) {
48 | return
49 | }
50 |
51 | condition[field] = self.cast(field, this[field], 'write', this)
52 |
53 | for (i = 0; i < primaryKeys.length; i++) {
54 | if (this[primaryKeys[i]]) {
55 | condition[primaryKeys[i] + '_not'] = this[primaryKeys[i]]
56 | }
57 | }
58 |
59 | if (options.scope) {
60 | if (!Array.isArray(options.scope)) options.scope = [options.scope]
61 |
62 | for (i = 0; i < options.scope.length; i++) {
63 | condition[options.scope[i]] = this[options.scope[i]]
64 | }
65 | }
66 |
67 | return self.model
68 | .count()
69 | .where(condition)
70 | .exec(function(result) {
71 | if (result > 0) {
72 | record.errors.add(field, 'not uniq')
73 | throw record.errors
74 | }
75 | })
76 | })
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/stores/mysql/attributes.js:
--------------------------------------------------------------------------------
1 | /*
2 | * STORE
3 | */
4 | exports.store = {
5 | loadTableAttributes: function(name) {
6 | return this.connection
7 | .raw('SHOW FULL COLUMNS FROM ' + name)
8 | .then(function(response) {
9 | var result = response[0]
10 | var attributes = []
11 |
12 | for (var i in result) {
13 | var attrDef = {
14 | name: result[i].Field,
15 | type: simplifiedType(result[i].Type),
16 | options: {
17 | description: result[i].Comment,
18 | persistent: true,
19 | primary: result[i].Key === 'PRI',
20 | notnull: result[i].Null !== 'YES',
21 | default: result[i].Default,
22 | length: getMaxLength(result[i].Type),
23 | writable: !(
24 | result[i].Key === 'PRI' && result[i].Extra === 'auto_increment'
25 | ) // set to false if primary and integer
26 | },
27 | validations: []
28 | }
29 |
30 | if (result[i].Null !== 'YES' && result[i].Key !== 'PRI') {
31 | attrDef.validations.push({ name: 'validatesPresenceOf', args: [] })
32 | }
33 |
34 | attributes.push(attrDef)
35 | }
36 |
37 | return attributes
38 | })
39 | }
40 | }
41 |
42 | function getMaxLength(type) {
43 | var len = type.match(/\((\d+)\)/)
44 | if (len) {
45 | len = parseInt(len[1])
46 |
47 | if (len > 1) {
48 | return len
49 | }
50 | }
51 | return null
52 | }
53 |
54 | function simplifiedType(type) {
55 | if (type.toLowerCase() === 'tinyint(1)') type = 'boolean'
56 | type = type.replace(/\(.+\)/, '').toUpperCase()
57 |
58 | switch (type) {
59 | case 'BIGINT UNSIGNED':
60 | case 'INT UNSIGNED':
61 | case 'BIGINT':
62 | case 'TINYINT':
63 | case 'INT':
64 | return 'integer'
65 |
66 | case 'FLOAT':
67 | return 'float'
68 |
69 | case 'BOOLEAN':
70 | return 'boolean'
71 |
72 | case 'DATE':
73 | return 'date'
74 |
75 | case 'TIME':
76 | return 'time'
77 |
78 | case 'DATETIME':
79 | return 'datetime'
80 |
81 | case 'BLOB':
82 | return 'binary'
83 |
84 | default:
85 | return 'string'
86 | }
87 | }
88 |
--------------------------------------------------------------------------------