leoric 1.9.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/History.md +36 -1
- package/Readme.md +1 -1
- package/index.js +4 -232
- package/package.json +7 -2
- package/src/adapters/sequelize.js +11 -2
- package/src/bone.js +24 -13
- package/src/collection.js +129 -97
- package/src/data_types.js +33 -18
- package/src/drivers/abstract/attribute.js +4 -1
- package/src/drivers/abstract/index.js +3 -0
- package/src/drivers/abstract/spellbook.js +7 -265
- package/src/drivers/postgres/data_types.js +10 -4
- package/src/drivers/sqlite/connection.js +100 -0
- package/src/drivers/sqlite/data_types.js +17 -19
- package/src/drivers/sqlite/index.js +1 -125
- package/src/drivers/sqlite/pool.js +65 -0
- package/src/expr.js +23 -27
- package/src/expr_formatter.js +192 -0
- package/src/hint.js +3 -3
- package/src/query_object.js +229 -0
- package/src/realm.js +240 -0
- package/src/spell.js +37 -242
- package/src/validator.js +6 -3
- package/types/index.d.ts +105 -26
package/History.md
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
1.12.0 / 2021-10-12
|
|
2
|
+
===================
|
|
3
|
+
|
|
4
|
+
* feat: support custom fields query and sequelize mode export rawAttributes (#192)
|
|
5
|
+
* refactor: collection format query result (#194)
|
|
6
|
+
* refactor: object condition parsing and expression formatting (#191)
|
|
7
|
+
|
|
8
|
+
1.11.1 / 2021-09-28
|
|
9
|
+
===================
|
|
10
|
+
|
|
11
|
+
This version fixes lots of issues regarding logical operator in object conditions.
|
|
12
|
+
|
|
13
|
+
* fix: logical operator with multiple conditions such as (#190)
|
|
14
|
+
* fix: sequelize mode support HAVING, and select fields raw sql support (#187)
|
|
15
|
+
* fix: support len validator (#188)
|
|
16
|
+
* fix: normalize logical operator conditions before formatting with spellbook (#186)
|
|
17
|
+
|
|
18
|
+
1.11.0 / 2021-09-24
|
|
19
|
+
===================
|
|
20
|
+
|
|
21
|
+
* feat: support BINARY(length), VARBINARY(length), and BLOB (#169)
|
|
22
|
+
* fix: logic operate should adapt one argument (#183)
|
|
23
|
+
* fix: Bone.load() should be idempotent, make sure associations is intact (#184)
|
|
24
|
+
* fix: selected instance isNewRecord is false (#182)
|
|
25
|
+
* fix: set options.busyTimeout to mitigate SQLITE_BUSY (#176)
|
|
26
|
+
* fix: turn on long stack trace of sqlite driver (#175)
|
|
27
|
+
* docs: how to contribute (#180)
|
|
28
|
+
|
|
29
|
+
1.10.0 / 2021-09-14
|
|
30
|
+
===================
|
|
31
|
+
|
|
32
|
+
* feat: SQLite driver should emit "connection" event when new connection is created (#168)
|
|
33
|
+
* fix: bulkCreate(...records) should recognize custom setters (#168)
|
|
34
|
+
* fix: attribute.equals() check should ignore defaultValue (#172)
|
|
35
|
+
|
|
1
36
|
1.9.0 / 2021-09-04
|
|
2
37
|
==================
|
|
3
38
|
|
|
@@ -74,7 +109,7 @@
|
|
|
74
109
|
==================
|
|
75
110
|
|
|
76
111
|
* feat: support class static attributes and hooks (#131)
|
|
77
|
-
* fix: names defined in Bone.attributes should be
|
|
112
|
+
* fix: names defined in Bone.attributes should always be enumerable (#128)
|
|
78
113
|
* chore: add quality badge to readme (#129)
|
|
79
114
|
|
|
80
115
|
1.5.2 / 2021-07-02
|
package/Readme.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/leoric)
|
|
5
5
|
[](https://www.npmjs.com/package/leoric)
|
|
6
6
|
[](https://travis-ci.org/cyjake/leoric)
|
|
7
|
-
[](https://codecov.io/gh/cyjake/leoric)
|
|
8
8
|
|
|
9
9
|
Leoric is an object-relational mapping for Node.js, which is heavily influenced by Active Record of Ruby on Rails. See the [documentation](https://leoric.js.org) for detail.
|
|
10
10
|
|
package/index.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const fs = require('fs').promises;
|
|
4
|
-
const path = require('path');
|
|
5
3
|
|
|
6
4
|
const Logger = require('./src/drivers/abstract/logger');
|
|
7
5
|
const Spell = require('./src/spell');
|
|
8
6
|
const Bone = require('./src/bone');
|
|
9
7
|
const Collection = require('./src/collection');
|
|
10
8
|
const { invokable: DataTypes } = require('./src/data_types');
|
|
11
|
-
const { findDriver } = require('./src/drivers');
|
|
12
9
|
const migrations = require('./src/migrations');
|
|
13
10
|
const sequelize = require('./src/adapters/sequelize');
|
|
14
|
-
const {
|
|
11
|
+
const { heresql } = require('./src/utils/string');
|
|
15
12
|
const Hint = require('./src/hint');
|
|
13
|
+
const Realm = require('./src/realm');
|
|
16
14
|
|
|
17
15
|
/**
|
|
18
16
|
* @typedef {Object} RawSql
|
|
@@ -21,233 +19,6 @@ const Hint = require('./src/hint');
|
|
|
21
19
|
* @property {string} type
|
|
22
20
|
*/
|
|
23
21
|
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
* @typedef {Object} QueryResult
|
|
27
|
-
* @property {Array} rows
|
|
28
|
-
* @property {Array} fields
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* find models in directory
|
|
33
|
-
* @param {string} dir
|
|
34
|
-
* @returns {Array.<Bone>}
|
|
35
|
-
*/
|
|
36
|
-
async function findModels(dir) {
|
|
37
|
-
if (!dir || typeof dir !== 'string') {
|
|
38
|
-
throw new Error(`Unexpected dir (${dir})`);
|
|
39
|
-
}
|
|
40
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
41
|
-
const models = [];
|
|
42
|
-
|
|
43
|
-
for (const entry of entries) {
|
|
44
|
-
const extname = path.extname(entry.name);
|
|
45
|
-
if (entry.isFile() && ['.js', '.mjs'].includes(extname)) {
|
|
46
|
-
const model = require(path.join(dir, entry.name));
|
|
47
|
-
if (model.prototype instanceof Bone) models.push(model);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return models;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function initAttributes(model, columns) {
|
|
55
|
-
const attributes = {};
|
|
56
|
-
|
|
57
|
-
for (const columnInfo of columns) {
|
|
58
|
-
const { columnName, dataType, defaultValue, ...restInfo } = columnInfo;
|
|
59
|
-
const name = columnName == '_id' ? columnName : camelCase(columnName);
|
|
60
|
-
// leave out defaultValue to let database take over the default
|
|
61
|
-
attributes[name] = {
|
|
62
|
-
...restInfo,
|
|
63
|
-
columnName,
|
|
64
|
-
dataType,
|
|
65
|
-
type: model.driver.DataTypes.findType(dataType),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
model.init(attributes);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function loadModels(Spine, models, opts) {
|
|
73
|
-
const { database } = opts;
|
|
74
|
-
const tables = models.map(model => model.physicTable);
|
|
75
|
-
const schemaInfo = await Spine.driver.querySchemaInfo(database, tables);
|
|
76
|
-
|
|
77
|
-
for (const model of models) {
|
|
78
|
-
const columns = schemaInfo[model.physicTable];
|
|
79
|
-
if (!model.attributes) initAttributes(model, columns);
|
|
80
|
-
model.load(columns);
|
|
81
|
-
Spine.models[model.name] = model;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
for (const model of models) {
|
|
85
|
-
model.initialize();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function createSpine(opts) {
|
|
90
|
-
if (opts.Bone && opts.Bone.prototype instanceof Bone) return opts.Bone;
|
|
91
|
-
if (opts.sequelize) return sequelize(Bone);
|
|
92
|
-
if (opts.subclass !== true) return Bone;
|
|
93
|
-
return class Spine extends Bone {};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const rReplacementKey = /\s:(\w+)\b/g;
|
|
97
|
-
|
|
98
|
-
class Realm {
|
|
99
|
-
constructor(opts = {}) {
|
|
100
|
-
const { client, dialect, database, ...restOpts } = {
|
|
101
|
-
dialect: 'mysql',
|
|
102
|
-
database: opts.db || opts.storage,
|
|
103
|
-
...opts
|
|
104
|
-
};
|
|
105
|
-
const Spine = createSpine(opts);
|
|
106
|
-
const models = {};
|
|
107
|
-
|
|
108
|
-
const driver = new (findDriver(dialect))({
|
|
109
|
-
client,
|
|
110
|
-
database,
|
|
111
|
-
...restOpts
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const options = {
|
|
115
|
-
client,
|
|
116
|
-
dialect,
|
|
117
|
-
database,
|
|
118
|
-
...restOpts,
|
|
119
|
-
define: { underscored: true, ...opts.define },
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
this.Bone = Spine;
|
|
123
|
-
this.models = Spine.models = models;
|
|
124
|
-
this.driver = Spine.driver = driver;
|
|
125
|
-
this.options = Spine.options = options;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
define(name, attributes, opts = {}, descriptors = {}) {
|
|
129
|
-
const Model = class extends this.Bone {
|
|
130
|
-
static name = name;
|
|
131
|
-
};
|
|
132
|
-
Model.init(attributes, opts, descriptors);
|
|
133
|
-
this.Bone.models[name] = Model;
|
|
134
|
-
return Model;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async connect() {
|
|
138
|
-
const { models: dir } = this.options;
|
|
139
|
-
|
|
140
|
-
let models;
|
|
141
|
-
if (dir) {
|
|
142
|
-
models = Array.isArray(dir) ? dir : (await findModels(dir));
|
|
143
|
-
} else {
|
|
144
|
-
models = Object.values(this.models);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (models.length > 0) {
|
|
148
|
-
await loadModels(this.Bone, models, this.options);
|
|
149
|
-
}
|
|
150
|
-
this.connected = true;
|
|
151
|
-
return this.Bone;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async sync() {
|
|
155
|
-
if (!this.connected) await this.connect();
|
|
156
|
-
const { models } = this;
|
|
157
|
-
|
|
158
|
-
for (const model of Object.values(models)) {
|
|
159
|
-
await model.sync();
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* raw query
|
|
165
|
-
* @param {string} query
|
|
166
|
-
* @param {Array<any>} values
|
|
167
|
-
* @param {Connection} opts.connection specific connection of this query, may used in a transaction
|
|
168
|
-
* @param {Bone} opts.model target model to inject values
|
|
169
|
-
* @returns {QueryResult}
|
|
170
|
-
* @memberof Realm
|
|
171
|
-
*/
|
|
172
|
-
async query(query, values, opts = {}) {
|
|
173
|
-
if (values && typeof values === 'object' && !Array.isArray(values)) {
|
|
174
|
-
if ('replacements' in values) {
|
|
175
|
-
const { model, connection } = values;
|
|
176
|
-
opts.replacements = values.replacements;
|
|
177
|
-
if (model) opts.model = model;
|
|
178
|
-
if (connection) opts.connection = connection;
|
|
179
|
-
} else {
|
|
180
|
-
opts.replacements = values;
|
|
181
|
-
}
|
|
182
|
-
values = [];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const replacements = opts.replacements || {};
|
|
186
|
-
query = query.replace(rReplacementKey, function replacer(m, key) {
|
|
187
|
-
if (!replacements.hasOwnProperty(key)) {
|
|
188
|
-
throw new Error(`unable to replace :${key}`);
|
|
189
|
-
}
|
|
190
|
-
values.push(replacements[key]);
|
|
191
|
-
return '?';
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
const { rows, ...restRes } = await this.driver.query(query, values, opts);
|
|
195
|
-
const results = [];
|
|
196
|
-
|
|
197
|
-
if (rows && rows.length && opts.model && opts.model.prototype instanceof this.Bone) {
|
|
198
|
-
const { attributeMap } = opts.model;
|
|
199
|
-
|
|
200
|
-
for (const data of rows) {
|
|
201
|
-
const instance = opts.model.instantiate(data);
|
|
202
|
-
for (const key in data) {
|
|
203
|
-
if (!attributeMap.hasOwnProperty(key)) instance[key] = data[key];
|
|
204
|
-
}
|
|
205
|
-
results.push(instance);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
rows: results.length > 0 ? results : rows,
|
|
211
|
-
...restRes
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async transaction(callback) {
|
|
216
|
-
return await this.Bone.transaction(callback);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* raw sql object
|
|
221
|
-
* @static
|
|
222
|
-
* @param {string} sql
|
|
223
|
-
* @returns {RawSql}
|
|
224
|
-
* @memberof Realm
|
|
225
|
-
*/
|
|
226
|
-
static raw(sql) {
|
|
227
|
-
if (typeof sql !== 'string') {
|
|
228
|
-
throw new TypeError('sql must be a string');
|
|
229
|
-
}
|
|
230
|
-
return {
|
|
231
|
-
__raw: true,
|
|
232
|
-
value: sql,
|
|
233
|
-
type: 'raw',
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// instance.raw
|
|
238
|
-
raw(sql) {
|
|
239
|
-
return Realm.raw(sql);
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* escape value
|
|
243
|
-
* @param {string} value
|
|
244
|
-
* @returns {string} escaped value
|
|
245
|
-
* @memberof Realm
|
|
246
|
-
*/
|
|
247
|
-
escape(value) {
|
|
248
|
-
return this.driver.escape(value);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
22
|
|
|
252
23
|
/**
|
|
253
24
|
* Connect models to database. Need to provide both connect options and models.
|
|
@@ -261,7 +32,8 @@ const connect = async function connect(opts) {
|
|
|
261
32
|
opts = { Bone, ...opts };
|
|
262
33
|
if (opts.Bone.driver) throw new Error('connected already');
|
|
263
34
|
const realm = new Realm(opts);
|
|
264
|
-
|
|
35
|
+
await realm.connect();
|
|
36
|
+
return realm;
|
|
265
37
|
};
|
|
266
38
|
|
|
267
39
|
Object.assign(Realm.prototype, migrations, { DataTypes });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leoric",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "JavaScript Object-relational mapping alchemy",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"test:mysql2": "./test/start.sh test/integration/mysql2.test.js",
|
|
21
21
|
"test:postgres": "./test/start.sh test/integration/postgres.test.js",
|
|
22
22
|
"test:sqlite": "./test/start.sh test/integration/sqlite.test.js",
|
|
23
|
+
"test:dts": "./test/start.sh test/types/dts.test.js",
|
|
23
24
|
"test:coverage": "nyc ./test/start.sh && nyc report --reporter=lcov",
|
|
24
25
|
"lint": "eslint ./",
|
|
25
26
|
"lint:fix": "eslint . --fix"
|
|
@@ -56,6 +57,9 @@
|
|
|
56
57
|
"@babel/core": "^7.14.6",
|
|
57
58
|
"@babel/eslint-parser": "^7.14.7",
|
|
58
59
|
"@cara/minami": "^1.2.3",
|
|
60
|
+
"@journeyapps/sqlcipher": "^5.2.0",
|
|
61
|
+
"@types/node": "^16.10.1",
|
|
62
|
+
"coffee": "^5.4.0",
|
|
59
63
|
"dayjs": "^1.10.3",
|
|
60
64
|
"eslint": "^7.20.0",
|
|
61
65
|
"expect.js": "^0.3.1",
|
|
@@ -66,6 +70,7 @@
|
|
|
66
70
|
"nyc": "^15.1.0",
|
|
67
71
|
"pg": "^8.5.1",
|
|
68
72
|
"sinon": "^10.0.0",
|
|
69
|
-
"sqlite3": "^5.0.2"
|
|
73
|
+
"sqlite3": "^5.0.2",
|
|
74
|
+
"typescript": "^4.4.3"
|
|
70
75
|
}
|
|
71
76
|
}
|
|
@@ -4,14 +4,19 @@ const { setupSingleHook } = require('../setup_hooks');
|
|
|
4
4
|
const { compose, isPlainObject } = require('../utils');
|
|
5
5
|
|
|
6
6
|
function translateOptions(spell, options) {
|
|
7
|
-
const { attributes, where, group, order, offset, limit, include } = options;
|
|
7
|
+
const { attributes, where, group, order, offset, limit, include, having } = options;
|
|
8
8
|
|
|
9
9
|
if (attributes) spell.$select(attributes);
|
|
10
10
|
if (include) {
|
|
11
11
|
if (typeof include === 'string') spell.$with(include);
|
|
12
12
|
}
|
|
13
13
|
if (where) spell.$where(where);
|
|
14
|
-
if (group)
|
|
14
|
+
if (group) {
|
|
15
|
+
if (Array.isArray(group)) spell.$group(...group);
|
|
16
|
+
else spell.$group(group);
|
|
17
|
+
}
|
|
18
|
+
if (having) spell.$having(having);
|
|
19
|
+
|
|
15
20
|
if (order) {
|
|
16
21
|
if (typeof order === 'string') {
|
|
17
22
|
spell.$order(order);
|
|
@@ -125,6 +130,10 @@ module.exports = Bone => {
|
|
|
125
130
|
return this;
|
|
126
131
|
}
|
|
127
132
|
|
|
133
|
+
static get rawAttributes() {
|
|
134
|
+
return this.attributes;
|
|
135
|
+
}
|
|
136
|
+
|
|
128
137
|
/**
|
|
129
138
|
* add scope see https://sequelize.org/master/class/lib/model.js~Model.html#static-method-addScope
|
|
130
139
|
*
|
package/src/bone.js
CHANGED
|
@@ -914,7 +914,7 @@ class Bone {
|
|
|
914
914
|
*/
|
|
915
915
|
static load(columns = []) {
|
|
916
916
|
const { Attribute } = this.driver;
|
|
917
|
-
const { attributes, options } = this;
|
|
917
|
+
const { associations = {}, attributes, options } = this;
|
|
918
918
|
const attributeMap = {};
|
|
919
919
|
const table = this.table || snakeCase(pluralize(this.name));
|
|
920
920
|
const tableAlias = camelCase(pluralize(this.name || table));
|
|
@@ -943,7 +943,7 @@ class Bone {
|
|
|
943
943
|
primaryKey,
|
|
944
944
|
columns,
|
|
945
945
|
attributeMap,
|
|
946
|
-
associations
|
|
946
|
+
associations,
|
|
947
947
|
tableAlias,
|
|
948
948
|
synchronized: Object.keys(compare(attributes, columns)).length === 0,
|
|
949
949
|
}));
|
|
@@ -1147,20 +1147,31 @@ class Bone {
|
|
|
1147
1147
|
* @return {Bone}
|
|
1148
1148
|
*/
|
|
1149
1149
|
static instantiate(row) {
|
|
1150
|
-
const { attributes, driver } = this;
|
|
1150
|
+
const { attributes, driver, attributeMap } = this;
|
|
1151
1151
|
const instance = new this();
|
|
1152
1152
|
|
|
1153
|
-
for (const
|
|
1154
|
-
const
|
|
1155
|
-
if (columnName
|
|
1153
|
+
for (const columnName in row) {
|
|
1154
|
+
const value = row[columnName];
|
|
1155
|
+
if (attributeMap.hasOwnProperty(columnName)) {
|
|
1156
|
+
const { jsType, name } = attributeMap[columnName];
|
|
1156
1157
|
// to make sure raw and rawSaved hold two different objects
|
|
1157
|
-
instance._setRaw(name, driver.cast(
|
|
1158
|
-
instance._setRawSaved(name, driver.cast(
|
|
1158
|
+
instance._setRaw(name, driver.cast(value, jsType));
|
|
1159
|
+
instance._setRawSaved(name, driver.cast(value, jsType));
|
|
1159
1160
|
} else {
|
|
1161
|
+
if (!isNaN(value)) instance[columnName] = Number(value);
|
|
1162
|
+
else if (!isNaN(Date.parse(value))) instance[columnName] = new Date(value);
|
|
1163
|
+
else instance[columnName] = value;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
for (const name in attributes) {
|
|
1168
|
+
const { columnName } = attributes[name];
|
|
1169
|
+
if (!(columnName in row)) {
|
|
1160
1170
|
instance._getRawUnset().add(name);
|
|
1161
1171
|
}
|
|
1162
1172
|
}
|
|
1163
1173
|
|
|
1174
|
+
instance.isNewRecord = false;
|
|
1164
1175
|
return instance;
|
|
1165
1176
|
}
|
|
1166
1177
|
|
|
@@ -1313,16 +1324,16 @@ class Bone {
|
|
|
1313
1324
|
opts.uniqueKeys = opts.uniqueKeys.map((field) => this.unalias(field));
|
|
1314
1325
|
}
|
|
1315
1326
|
|
|
1327
|
+
// records might change when filter through custom setters
|
|
1328
|
+
records = instances.map(instance => instance.getRaw());
|
|
1329
|
+
|
|
1316
1330
|
// bulk create with instances is possible only if
|
|
1317
1331
|
// 1) either all of records primary key are set
|
|
1318
1332
|
// 2) or none of records priamry key is set and primary key is auto incremented
|
|
1319
1333
|
if (!(autoIncrement && unset || allset)) {
|
|
1320
1334
|
// validate first
|
|
1321
1335
|
if (options.validate !== false) {
|
|
1322
|
-
records.
|
|
1323
|
-
if (record instanceof Bone) record._validateAttributes();
|
|
1324
|
-
else this._validateAttributes(record);
|
|
1325
|
-
});
|
|
1336
|
+
for (const record of records) this._validateAttributes(record);
|
|
1326
1337
|
}
|
|
1327
1338
|
return await new Spell(this, options).$bulkInsert(records);
|
|
1328
1339
|
}
|
|
@@ -1493,7 +1504,7 @@ class Bone {
|
|
|
1493
1504
|
|
|
1494
1505
|
const { attributes, columns } = this;
|
|
1495
1506
|
|
|
1496
|
-
if (
|
|
1507
|
+
if (columns.length === 0) {
|
|
1497
1508
|
await driver.createTable(table, attributes);
|
|
1498
1509
|
} else {
|
|
1499
1510
|
await driver.alterTable(table, compare(attributes, columns));
|