leoric 2.0.4 → 2.2.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 CHANGED
@@ -1,3 +1,37 @@
1
+ 2.2.0 / 2022-02-24
2
+ ==================
3
+
4
+ ## What's Changed
5
+ * fix: add missing `password` field for `ConnectOptions` by @luckydrq in https://github.com/cyjake/leoric/pull/280
6
+ * feat: integer types (mostly mysql specific) by @cyjake in https://github.com/cyjake/leoric/pull/281
7
+
8
+
9
+ **Full Changelog**: https://github.com/cyjake/leoric/compare/v2.1.1...v2.2.0
10
+
11
+ 2.1.1 / 2022-02-23
12
+ ==================
13
+
14
+ ## What's Changed
15
+ * fix: fix #274 update with fields option by @JimmyDaddy in https://github.com/cyjake/leoric/pull/275
16
+ * fix: upsert should set createdAt by default while createdAt not set by @JimmyDaddy in https://github.com/cyjake/leoric/pull/277
17
+ * fix: previousChanges should check instance is new record or not while specific attributes' values were undefined by @JimmyDaddy in https://github.com/cyjake/leoric/pull/276
18
+ * docs: add types for realm by @luckydrq in https://github.com/cyjake/leoric/pull/278
19
+
20
+ ## New Contributors
21
+ * @luckydrq made their first contribution in https://github.com/cyjake/leoric/pull/278
22
+
23
+ **Full Changelog**: https://github.com/cyjake/leoric/compare/v2.1.0...v2.1.1
24
+
25
+ 2.1.0 / 2022-02-17
26
+ ==================
27
+
28
+ ## What's Changed
29
+ * feat: fix #270 sequelize mode bulkBuild by @JimmyDaddy in https://github.com/cyjake/leoric/pull/273
30
+ * fix: mysql delete/remove/destroy with limit and orders by @JimmyDaddy in https://github.com/cyjake/leoric/pull/272
31
+
32
+
33
+ **Full Changelog**: https://github.com/cyjake/leoric/compare/v2.0.4...v2.1.0
34
+
1
35
  2.0.4 / 2022-02-16
2
36
  ==================
3
37
 
package/index.js CHANGED
@@ -36,7 +36,7 @@ const connect = async function connect(opts) {
36
36
  return realm;
37
37
  };
38
38
 
39
- Object.assign(Realm.prototype, migrations, { DataTypes });
39
+ Object.assign(Realm.prototype, migrations);
40
40
  Object.assign(Realm, {
41
41
  connect,
42
42
  Bone,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leoric",
3
- "version": "2.0.4",
3
+ "version": "2.2.0",
4
4
  "description": "JavaScript Object-relational mapping alchemy",
5
5
  "main": "index.js",
6
6
  "types": "types/index.d.ts",
@@ -257,6 +257,17 @@ module.exports = Bone => {
257
257
  return instance;
258
258
  }
259
259
 
260
+ /**
261
+ * see https://github.com/sequelize/sequelize/blob/a729c4df41fa3a58fbecaf879265d2fb73d80e5f/src/model.js#L2299
262
+ * @param {Array<Object>} valueSets
263
+ * @param {Object} options
264
+ * @returns
265
+ */
266
+ static bulkBuild(valueSets, options = {}) {
267
+ if (!valueSets.length) return [];
268
+ return valueSets.map(value => this.build(value, options));
269
+ }
270
+
260
271
  // EXISTS
261
272
  // static bulkCreate() {}
262
273
 
@@ -308,7 +319,9 @@ module.exports = Bone => {
308
319
  // proxy to class.destroy({ individualHooks=false }) see https://github.com/sequelize/sequelize/blob/4063c2ab627ad57919d5b45cc7755f077a69fa5e/lib/model.js#L2895 before(after)BulkDestroy
309
320
  static async bulkDestroy(options = {}) {
310
321
  const { where, force } = options;
311
- return await this.remove(where || {}, force, { ...options });
322
+ const spell = this._remove(where || {}, force, { ...options });
323
+ translateOptions(spell, options);
324
+ return spell;
312
325
  }
313
326
 
314
327
  // EXISTS
@@ -495,11 +508,11 @@ module.exports = Bone => {
495
508
  // get isNewRecord() {}
496
509
 
497
510
  async update(values = {}, options = {}) {
498
- const { fields } = options;
511
+ const { fields = [] } = options;
499
512
  const changeValues = {};
500
513
  const originalValues = Object.assign({}, this.getRaw());
501
514
  for (const name in values) {
502
- if (values[name] !== undefined && this.hasAttribute(name)) {
515
+ if (values[name] !== undefined && this.hasAttribute(name) && (!fields.length || fields.includes(name))) {
503
516
  // exec custom setters in case it exist
504
517
  this[name] = values[name];
505
518
  changeValues[name] = this.attribute(name);
package/src/bone.js CHANGED
@@ -183,7 +183,6 @@ class Bone {
183
183
  }
184
184
 
185
185
  if (args.length > 1) {
186
- // execute validators
187
186
  this.#raw[name] = value instanceof Raw ? value : attribute.cast(value);
188
187
  this.#rawUnset.delete(name);
189
188
  return this;
@@ -619,9 +618,10 @@ class Bone {
619
618
  async update(values, options = {}) {
620
619
  const changes = {};
621
620
  const originalValues = Object.assign({}, this.#raw);
621
+ const { fields = [] } = options;
622
622
  if (typeof values === 'object') {
623
623
  for (const name in values) {
624
- if (values[name] !== undefined && this.hasAttribute(name)) {
624
+ if (values[name] !== undefined && this.hasAttribute(name) && (!fields.length || fields.includes(name))) {
625
625
  // exec custom setters in case it exist
626
626
  this[name] = values[name];
627
627
  changes[name] = this.attribute(name);
@@ -789,7 +789,7 @@ class Bone {
789
789
  }, opts);
790
790
  return result;
791
791
  }
792
- return await Model.remove(condition, forceDelete, { hooks: false, ...opts });
792
+ return await Model._remove(condition, forceDelete, opts);
793
793
  }
794
794
 
795
795
  /**
@@ -1454,6 +1454,24 @@ class Bone {
1454
1454
  * @return {Spell}
1455
1455
  */
1456
1456
  static remove(conditions, forceDelete = false, options) {
1457
+ return this._remove(conditions, forceDelete, options);
1458
+ }
1459
+
1460
+ /**
1461
+ * private method for internal calling
1462
+ * Remove any record that matches `conditions`.
1463
+ * - If `forceDelete` is true, `DELETE` records from database permanently.
1464
+ * - If not, update `deletedAt` attribute with current date.
1465
+ * - If `forceDelete` isn't true and `deleteAt` isn't around, throw an Error.
1466
+ * @example
1467
+ * Post.remove({ title: 'Leah' }) // mark Post { title: 'Leah' } as deleted
1468
+ * Post.remove({ title: 'Leah' }, true) // delete Post { title: 'Leah' }
1469
+ * Post.remove({}, true) // delete all data of posts
1470
+ * @param {Object} conditions
1471
+ * @param {boolean} forceDelete
1472
+ * @return {Spell}
1473
+ */
1474
+ static _remove(conditions, forceDelete = false, options) {
1457
1475
  const { deletedAt } = this.timestamps;
1458
1476
  if (forceDelete !== true && this.attributes[deletedAt]) {
1459
1477
  return Bone.update.call(this, conditions, { [deletedAt]: new Date() }, {
package/src/data_types.js CHANGED
@@ -14,7 +14,13 @@ const Raw = require('./raw');
14
14
 
15
15
  class DataType {
16
16
  static findType(columnType) {
17
- const { STRING, TEXT, DATE, DATEONLY, INTEGER, BIGINT, BOOLEAN, BINARY, VARBINARY, BLOB } = this;
17
+ const {
18
+ STRING, TEXT,
19
+ DATE, DATEONLY,
20
+ TINYINT, SMALLINT, MEDIUMINT, INTEGER, BIGINT,
21
+ BOOLEAN,
22
+ BINARY, VARBINARY, BLOB,
23
+ } = this;
18
24
  const [ , dataType, appendix ] = columnType.match(/(\w+)(?:\((\d+)\))?/);
19
25
  const length = appendix && parseInt(appendix, 10);
20
26
 
@@ -39,10 +45,13 @@ class DataType {
39
45
  case 'int':
40
46
  case 'integer':
41
47
  case 'numeric':
48
+ return new INTEGER(length);
42
49
  case 'mediumint':
50
+ return new MEDIUMINT(length);
43
51
  case 'smallint':
52
+ return new SMALLINT(length);
44
53
  case 'tinyint':
45
- return new INTEGER(length);
54
+ return new TINYINT(length);
46
55
  case 'bigint':
47
56
  return new BIGINT(length);
48
57
  case 'boolean':
@@ -203,12 +212,58 @@ class INTEGER extends DataType {
203
212
  }
204
213
  }
205
214
 
215
+ /**
216
+ * 8 bit integer
217
+ * @example
218
+ * TINYINT
219
+ * TINYINT.UNSIGNED
220
+ * TINYINT(1)
221
+ * @param {number} length
222
+ */
223
+ class TINYINT extends INTEGER {
224
+ constructor(length) {
225
+ super(length);
226
+ this.dataType = 'tinyint';
227
+ }
228
+ }
229
+
230
+ /**
231
+ * 16 bit integer
232
+ * @example
233
+ * SMALLINT
234
+ * SMALLINT.UNSIGNED
235
+ * SMALLINT(2)
236
+ * @param {number} length
237
+ */
238
+ class SMALLINT extends INTEGER {
239
+ constructor(length) {
240
+ super(length);
241
+ this.dataType = 'smallint';
242
+ }
243
+ }
244
+
245
+ /**
246
+ * 24 bit integer
247
+ * @example
248
+ * MEDIUMINT
249
+ * MEDIUMINT.UNSIGNED
250
+ * MEDIUMINT(3)
251
+ * @param {number} length
252
+ */
253
+ class MEDIUMINT extends INTEGER {
254
+ constructor(length) {
255
+ super(length);
256
+ this.dataType = 'mediumint';
257
+ }
258
+ }
259
+
260
+
206
261
  /**
207
262
  * 64 bit integer
208
263
  * @example
209
264
  * BIGINT
210
265
  * BIGINT.UNSIGNED
211
- * BIGINT(10)
266
+ * BIGINT(8)
212
267
  * @param {number} length
213
268
  */
214
269
  class BIGINT extends INTEGER {
@@ -422,6 +477,9 @@ class JSONB extends JSON {
422
477
 
423
478
  const DataTypes = {
424
479
  STRING,
480
+ TINYINT,
481
+ SMALLINT,
482
+ MEDIUMINT,
425
483
  INTEGER,
426
484
  BIGINT,
427
485
  DATE,
@@ -65,6 +65,14 @@ function createType(DataTypes, params) {
65
65
  switch (type.constructor.name) {
66
66
  case 'DATE':
67
67
  return new DataType(type.precision, type.timezone);
68
+ case 'TINYINT':
69
+ case 'SMALLINT':
70
+ case 'MEDIUMINT':
71
+ case 'INTEGER':
72
+ case 'BIGINT':
73
+ case 'BINARY':
74
+ case 'VARBINARY':
75
+ return new DataType(type.length);
68
76
  default:
69
77
  return new DataType();
70
78
  }
@@ -123,7 +131,8 @@ class Attribute {
123
131
  }
124
132
 
125
133
  cast(value) {
126
- return this.type.cast(value);
134
+ const castedValue = this.type.cast(value);
135
+ return castedValue == null? null : castedValue;
127
136
  }
128
137
 
129
138
  uncast(value, strict = true) {
@@ -356,15 +356,14 @@ function formatInsert(spell) {
356
356
  }
357
357
  } else {
358
358
  for (const name in involved) {
359
- // upsert should not update createdAt
360
- if (updateOnDuplicate && createdAt && name === createdAt) continue;
361
359
  attributes.push(Model.attributes[name]);
362
360
  }
363
361
  }
364
362
 
365
363
  for (const entry of attributes) {
366
364
  columns.push(entry.columnName);
367
- if (updateOnDuplicate && createdAt && entry.name === createdAt) continue;
365
+ if (updateOnDuplicate && createdAt && entry.name === createdAt
366
+ && !(Array.isArray(updateOnDuplicate) && updateOnDuplicate.includes(createdAt))) continue;
368
367
  updateOnDuplicateColumns.push(entry.columnName);
369
368
  }
370
369
 
@@ -385,7 +384,6 @@ function formatInsert(spell) {
385
384
  }
386
385
  for (const name in sets) {
387
386
  const value = sets[name];
388
- // upsert should not update createdAt
389
387
  columns.push(Model.unalias(name));
390
388
  if (value instanceof Raw) {
391
389
  values.push(SqlString.raw(value.value));
@@ -49,7 +49,10 @@ module.exports = {
49
49
  const sets = [];
50
50
  // Make sure the correct LAST_INSERT_ID is returned.
51
51
  // - https://stackoverflow.com/questions/778534/mysql-on-duplicate-key-last-insert-id
52
- sets.push(`${escapeId(primaryColumn)} = LAST_INSERT_ID(${escapeId(primaryColumn)})`);
52
+ // if insert attributes include primary column, `primaryKey = LAST_INSERT_ID(primaryKey)` is not need any more
53
+ if (!columns.includes(primaryColumn)) {
54
+ sets.push(`${escapeId(primaryColumn)} = LAST_INSERT_ID(${escapeId(primaryColumn)})`);
55
+ }
53
56
  sets.push(...columns.map(column => `${escapeId(column)}=VALUES(${escapeId(column)})`));
54
57
 
55
58
  return `ON DUPLICATE KEY UPDATE ${sets.join(', ')}`;
@@ -75,4 +78,19 @@ module.exports = {
75
78
 
76
79
  return result;
77
80
  },
81
+ /**
82
+ * DELETE ... ORDER BY ...LIMIT
83
+ * @param {Spell} spell
84
+ */
85
+ formatDelete(spell) {
86
+ const result = spellbook.formatDelete.call(this, spell);
87
+ const { rowCount, orders } = spell;
88
+ const chunks = [];
89
+
90
+ if (orders.length > 0) chunks.push(`ORDER BY ${this.formatOrders(spell, orders).join(', ')}`);
91
+ if (rowCount > 0) chunks.push(`LIMIT ${rowCount}`);
92
+ if (chunks.length > 0) result.sql += ` ${chunks.join(' ')}`;
93
+
94
+ return result;
95
+ }
78
96
  };
@@ -61,12 +61,22 @@ class Postgres_BIGINT extends Postgres_INTEGER {
61
61
  }
62
62
  }
63
63
 
64
+ class Postgres_SMALLINT extends Postgres_INTEGER {
65
+ constructor() {
66
+ super();
67
+ this.dataType = 'smallint';
68
+ }
69
+ }
70
+
64
71
  class Postgres_DataTypes extends DataTypes {
65
72
  static DATE = Postgres_DATE;
66
73
  static JSONB = Postgres_JSONB;
67
74
  static BINARY = Postgres_BINARY;
68
75
  static VARBINARY = Postgres_BINARY;
69
76
  static BLOB = Postgres_BINARY;
77
+ static TINYINT = Postgres_SMALLINT;
78
+ static SMALLINT = Postgres_SMALLINT;
79
+ static MEDIUMINT = Postgres_INTEGER;
70
80
  static INTEGER = Postgres_INTEGER;
71
81
  static BIGINT = Postgres_BIGINT;
72
82
  }
package/src/realm.js CHANGED
@@ -129,6 +129,11 @@ class Realm {
129
129
  this.options = Spine.options = options;
130
130
  }
131
131
 
132
+ get DataTypes() {
133
+ if (!this.driver) throw new Error('database not connected yet');
134
+ return this.driver.DataTypes;
135
+ }
136
+
132
137
  define(name, attributes, opts = {}, descriptors = {}) {
133
138
  const Model = class extends this.Bone {
134
139
  static name = name;
package/types/index.d.ts CHANGED
@@ -593,13 +593,16 @@ export class Bone {
593
593
  toObject(): InstanceValues<this>;
594
594
  }
595
595
 
596
- interface ConnectOptions {
596
+ export interface ConnectOptions {
597
597
  client?: 'mysql' | 'mysql2' | 'pg' | 'sqlite3' | '@journeyapps/sqlcipher';
598
598
  dialect?: 'mysql' | 'postgres' | 'sqlite';
599
599
  host?: string;
600
+ port?: number | string;
600
601
  user?: string;
602
+ password?: string;
601
603
  database: string;
602
604
  models?: string | (typeof Bone)[];
605
+ subclass?: boolean;
603
606
  }
604
607
 
605
608
  interface InitOptions {
@@ -612,6 +615,11 @@ interface InitOptions {
612
615
  };
613
616
  }
614
617
 
618
+ interface SyncOptions {
619
+ force?: boolean;
620
+ alter?: boolean;
621
+ }
622
+
615
623
  type RawSql = {
616
624
  __raw: true,
617
625
  value: string,
@@ -626,6 +634,7 @@ interface RawQueryOptions {
626
634
 
627
635
  export default class Realm {
628
636
  Bone: typeof Bone;
637
+ DataTypes: typeof DataType;
629
638
  driver: Driver;
630
639
  models: Record<string, Bone>;
631
640
 
@@ -633,10 +642,10 @@ export default class Realm {
633
642
 
634
643
  define(
635
644
  name: string,
636
- attributes: Record<string, AttributeMeta>,
637
- options: InitOptions,
638
- descriptors: Record<string, Function>,
639
- ): Bone;
645
+ attributes: Record<string, DataTypes<DataType> | AttributeMeta>,
646
+ options?: InitOptions,
647
+ descriptors?: Record<string, Function>,
648
+ ): typeof Bone;
640
649
 
641
650
  raw(sql: string): RawSql;
642
651
 
@@ -646,6 +655,8 @@ export default class Realm {
646
655
 
647
656
  transaction(callback: GeneratorFunction): Promise<void>;
648
657
  transaction(callback: (connection: Connection) => Promise<void>): Promise<void>;
658
+
659
+ sync(options?: SyncOptions): Promise<void>;
649
660
  }
650
661
 
651
662
  /**