nodester 0.7.13 → 0.7.14

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.
@@ -2,13 +2,13 @@
2
2
  * nodester
3
3
  * MIT Licensed
4
4
  */
5
-
5
+
6
6
  'use strict';
7
7
 
8
8
  /**
9
9
  * CRUD mixins for any model:
10
10
  */
11
-
11
+
12
12
  // Utils:
13
13
  const {
14
14
  modelHasAssociations,
@@ -59,7 +59,7 @@ function _implementsCRUD(modelDefinition) {
59
59
 
60
60
  /** Main mixinis: */
61
61
  async function _createWithIncludes(
62
- data={}
62
+ data = {}
63
63
  ) {
64
64
  try {
65
65
  const instance = await this.create(data);
@@ -77,7 +77,7 @@ async function _createWithIncludes(
77
77
  if (modelHasAssociations(this)) {
78
78
  const allModelAssociations = Object.entries(this.associations);
79
79
 
80
- for (const [ associationName, associationDefinition ] of allModelAssociations) {
80
+ for (const [associationName, associationDefinition] of allModelAssociations) {
81
81
 
82
82
  // If data of this association is present:
83
83
  if (!!data[associationName]) {
@@ -85,10 +85,11 @@ async function _createWithIncludes(
85
85
  const {
86
86
  associatedModel,
87
87
  foreignKey,
88
- associationType
88
+ associationType,
89
+ accessors
89
90
  } = getModelAssociationProps(associationDefinition);
90
91
 
91
- // If association type is HasMany or HasOne (We don't work with any other):
92
+ // If association type is HasMany or HasOne:
92
93
  if (associationType === 'HasMany' || associationType === 'HasOne') {
93
94
 
94
95
  // Process current instance.
@@ -99,11 +100,35 @@ async function _createWithIncludes(
99
100
  parentForeignKey: foreignKey,
100
101
  });
101
102
 
102
- fullInstanceData[associationName] =_unwrapUpdateOrCreateOrDeleteOperationsResults(
103
+ fullInstanceData[associationName] = _unwrapUpdateOrCreateOrDeleteOperationsResults(
103
104
  operationsResults,
104
105
  associationType
105
106
  );
106
107
  }
108
+ // BelongsToMany: link existing records via junction table.
109
+ else if (associationType === 'BelongsToMany') {
110
+ const associationData = Array.isArray(data[associationName])
111
+ ? data[associationName]
112
+ : [data[associationName]];
113
+
114
+ const pkField = associatedModel.primaryKeyField;
115
+ const ids = [];
116
+
117
+ for (const item of associationData) {
118
+ const exists = await associatedModel.findByPk(item[pkField]);
119
+ if (!exists) {
120
+ const err = new Error(
121
+ `Before you can associate ${associationName} with ${this.name}, ${associationName} must be created separately.`
122
+ );
123
+ err.status = 406;
124
+ throw err;
125
+ }
126
+ ids.push(item[pkField]);
127
+ }
128
+
129
+ await instance[accessors.set](ids);
130
+ fullInstanceData[associationName] = associationData;
131
+ }
107
132
 
108
133
  fullInstanceData[associationName] = fullInstanceData[associationName] ?? data[associationName];
109
134
  }
@@ -114,16 +139,16 @@ async function _createWithIncludes(
114
139
  // Variable, that is used by _updateById,
115
140
  // but we will also put it here to make API consistent.
116
141
  const isNewRecord = true;
117
- return Promise.resolve([ isNewRecord, fullInstanceData ]);
142
+ return Promise.resolve([isNewRecord, fullInstanceData]);
118
143
  }
119
- catch(error) {
144
+ catch (error) {
120
145
  return Promise.reject(error);
121
146
  }
122
147
  }
123
148
 
124
149
  function _findById(
125
- id=null,
126
- opts={}
150
+ id = null,
151
+ opts = {}
127
152
  ) {
128
153
  const { query } = opts;
129
154
 
@@ -154,7 +179,7 @@ function _findById(
154
179
  return this.findOne(_query);
155
180
  }
156
181
 
157
- function _findMany(opts={}) {
182
+ function _findMany(opts = {}) {
158
183
  const { query } = opts;
159
184
 
160
185
  let _query = {};
@@ -189,7 +214,7 @@ function _findMany(opts={}) {
189
214
  async function _updateOne(
190
215
  where,
191
216
  data,
192
- opts={}
217
+ opts = {}
193
218
  ) {
194
219
  try {
195
220
  const include = opts.include ?? [];
@@ -226,11 +251,11 @@ async function _updateOne(
226
251
 
227
252
  const associationDefinition = this.associations[association];
228
253
  const includeData = data[association];
229
-
254
+
230
255
  const {
231
256
  associatedModel,
232
257
  associationType,
233
-
258
+
234
259
  foreignKey,
235
260
  sourceKey
236
261
  } = getModelAssociationProps(associationDefinition);
@@ -242,13 +267,12 @@ async function _updateOne(
242
267
 
243
268
  const pkField = associatedModel.primaryKeyField;
244
269
 
245
- // If association type is HasMany or HasOne (We don't work with any other):
246
- switch(associationType) {
270
+ switch (associationType) {
247
271
  case 'HasMany': {
248
272
  // Handle empty array (remove all old associations):
249
273
  if (Array.isArray(includeData) && includeData.length === 0) {
250
274
  const where = {
251
- [foreignKey]: instance.id
275
+ [foreignKey]: instance.id
252
276
  }
253
277
  await associatedModel.destroy({ where });
254
278
  fullInstanceData[association] = [];
@@ -260,10 +284,10 @@ async function _updateOne(
260
284
  [pkField]: singleData[pkField]
261
285
  }
262
286
  return associatedModel.updateOne(
263
- where,
264
- singleData,
265
- associationUpdateOpts
266
- )
287
+ where,
288
+ singleData,
289
+ associationUpdateOpts
290
+ )
267
291
  });
268
292
  fullInstanceData[association] = await Promise.all(promises);
269
293
 
@@ -285,14 +309,39 @@ async function _updateOne(
285
309
  [pkField]: includeData[pkField]
286
310
  }
287
311
  fullInstanceData[association] = await associatedModel.updateOne(
288
- where,
289
- includeData,
290
- associationUpdateOpts
291
- );
312
+ where,
313
+ includeData,
314
+ associationUpdateOpts
315
+ );
292
316
  }
293
317
  continue;
294
318
  }
295
319
 
320
+ case 'BelongsToMany': {
321
+ const associationItems = Array.isArray(includeData) ? includeData : [includeData];
322
+ const ids = [];
323
+
324
+ for (const item of associationItems) {
325
+ const exists = await associatedModel.findByPk(item[pkField]);
326
+ if (!exists) {
327
+ const err = new Error(
328
+ `Before you can associate ${association} with ${this.name}, ${association} must be created separately.`
329
+ );
330
+ err.status = 406;
331
+ throw err;
332
+ }
333
+ ids.push(item[pkField]);
334
+ }
335
+
336
+ // Replaces the full set of associations via the junction table.
337
+ // Sending an empty array removes all associations.
338
+ const { accessors: btmAccessors } = getModelAssociationProps(associationDefinition);
339
+ await instance[btmAccessors.set](ids);
340
+ fullInstanceData[association] = associationItems;
341
+
342
+ continue;
343
+ }
344
+
296
345
  default:
297
346
  continue;
298
347
  }
@@ -309,15 +358,15 @@ async function _updateOne(
309
358
  ...fullInstanceData
310
359
  });
311
360
  }
312
- catch(error) {
361
+ catch (error) {
313
362
  return Promise.reject(error);
314
363
  }
315
364
  }
316
365
 
317
366
  async function _updateById(
318
- id=null,
319
- data={},
320
- opts={}
367
+ id = null,
368
+ data = {},
369
+ opts = {}
321
370
  ) {
322
371
  const where = { id };
323
372
  return this.updateOne(
@@ -328,7 +377,7 @@ async function _updateById(
328
377
  }
329
378
 
330
379
 
331
- function _deleteOne(query={}) {
380
+ function _deleteOne(query = {}) {
332
381
  const _query = {
333
382
  ...query,
334
383
  limit: 1
@@ -337,7 +386,7 @@ function _deleteOne(query={}) {
337
386
  }
338
387
 
339
388
  function _deleteById(
340
- id=null
389
+ id = null
341
390
  ) {
342
391
  const query = {
343
392
  where: { id }
@@ -378,7 +427,7 @@ async function _updateOrCreateOrDelete(
378
427
  operation,
379
428
  });
380
429
  }
381
- catch(error) {
430
+ catch (error) {
382
431
  return Promise.reject(error);
383
432
  }
384
433
  }
@@ -398,7 +447,7 @@ async function _updateOrCreateOrDeleteBasedOnAssociationData({
398
447
  // If single instance of associated model:
399
448
  if (isSingleInstance) {
400
449
  const operationResult = await _updateOrCreateOrDelete(
401
- associatedModel,
450
+ associatedModel,
402
451
  compileModelAssociationData({
403
452
  dataOfAssociation,
404
453
  parentForeignKey,
@@ -406,14 +455,14 @@ async function _updateOrCreateOrDeleteBasedOnAssociationData({
406
455
  })
407
456
  );
408
457
 
409
- result = [ operationResult ];
458
+ result = [operationResult];
410
459
  }
411
460
  // If multiple instances of associated model:
412
461
  else {
413
462
  // Update or create each:
414
463
  const promises = dataOfAssociation.map(
415
464
  (data) => _updateOrCreateOrDelete(
416
- associatedModel,
465
+ associatedModel,
417
466
  compileModelAssociationData({
418
467
  dataOfAssociation: data,
419
468
  parentForeignKey: parentForeignKey,
@@ -428,7 +477,7 @@ async function _updateOrCreateOrDeleteBasedOnAssociationData({
428
477
 
429
478
  return Promise.resolve(result);
430
479
  }
431
- catch(error) {
480
+ catch (error) {
432
481
  return Promise.reject(error);
433
482
  }
434
483
  }
@@ -439,7 +488,7 @@ function _unwrapUpdateOrCreateOrDeleteOperationsResults(
439
488
  ) {
440
489
  // If instance was not deleted, add it to final array.
441
490
  const result = operationsResults.filter(({ operation }) => operation !== 'deleted')
442
- .map(({ newInstance }) => newInstance[1]);
491
+ .map(({ newInstance }) => newInstance[1]);
443
492
 
444
493
  // If this association referenced only certain record,
445
494
  // unwrap "result" array and send first element:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodester",
3
- "version": "0.7.13",
3
+ "version": "0.7.14",
4
4
  "description": "A versatile REST framework for Node.js",
5
5
  "directories": {
6
6
  "docs": "docs",