@kravc/dos 1.6.2 → 1.7.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kravc/dos",
3
- "version": "1.6.2",
3
+ "version": "1.7.1",
4
4
  "description": "Convention-based, easy-to-use library for building API-driven serverless services.",
5
5
  "keywords": [
6
6
  "Service",
package/src/Document.js CHANGED
@@ -64,9 +64,14 @@ class Document extends Component {
64
64
  }
65
65
 
66
66
  static async create(context, query, mutation) {
67
+ /* NOTE: existing document in the context allows to return document without
68
+ duplicate been created */
67
69
  const { document: existingDocument } = context
68
70
 
69
- if (existingDocument) {
71
+ const skipCreate =
72
+ !!existingDocument && existingDocument.constructor.name === this.name
73
+
74
+ if (skipCreate) {
70
75
  return existingDocument
71
76
  }
72
77
 
@@ -169,13 +174,14 @@ class Document extends Component {
169
174
  await this.beforeUpdate(context, query, mutation)
170
175
  }
171
176
 
172
- const updatedItem = await this._update(query, mutation)
177
+ /* NOTE: ensure that document to be updated exists and save it in the
178
+ context so can be referenced in the after action helper */
179
+ const originalDocument = await this.read(context, query)
173
180
 
174
- if (!updatedItem) {
175
- throw new DocumentNotFoundError(this, { query })
176
- }
181
+ const updatedItem = await this._update(query, mutation)
177
182
 
178
183
  const document = new this(context, updatedItem)
184
+ document._originalDocument = originalDocument
179
185
 
180
186
  if (this.afterUpdate) {
181
187
  await this.afterUpdate(context, query, mutation, document)
@@ -184,9 +190,11 @@ class Document extends Component {
184
190
  return document
185
191
  }
186
192
 
187
- static _update({ id = 'NONE' }, mutation) {
193
+ static _update({ id }, mutation) {
188
194
  const item = STORE[this.name][id]
189
195
 
196
+ /* istanbul ignore next: not used anymore by update interface as read
197
+ operation throws an error if document not found */
190
198
  if (!item) {
191
199
  return false
192
200
  }
@@ -201,20 +209,24 @@ class Document extends Component {
201
209
  await this.beforeDelete(context, query)
202
210
  }
203
211
 
204
- const isDeleted = await this._delete(query)
212
+ /* NOTE: ensure that document to be removed exists and save it in the
213
+ context so can be referenced in the after action helper */
214
+ const originalDocument = await this.read(context, query)
205
215
 
206
- if (!isDeleted) {
207
- throw new DocumentNotFoundError(this, { query })
208
- }
216
+ await this._delete(query)
209
217
 
210
218
  if (this.afterDelete) {
211
- await this.afterDelete(context, query)
219
+ await this.afterDelete(context, query, originalDocument)
212
220
  }
221
+
222
+ return originalDocument
213
223
  }
214
224
 
215
- static _delete({ id = 'NONE' }) {
225
+ static _delete({ id }) {
216
226
  const item = STORE[this.name][id]
217
227
 
228
+ /* istanbul ignore next: not used anymore by delete interface as read
229
+ operation throws an error if document not found */
218
230
  if (!item) {
219
231
  return false
220
232
  }
@@ -240,12 +252,32 @@ class Document extends Component {
240
252
  const document = await this.constructor.update(this.context, this._query, mutation)
241
253
 
242
254
  if (shouldMutate) {
243
- this._attributes = document.attributes
255
+ this._attributes = document._attributes
256
+ this._originalDocument = document._originalDocument
244
257
  }
245
258
 
246
259
  return document
247
260
  }
248
261
 
262
+ get originalDocument() {
263
+ if (!this._originalDocument) {
264
+ throw new Error('Orginal document is undefined')
265
+ }
266
+
267
+ return this._originalDocument
268
+ }
269
+
270
+ hasAttributeChanged(attributePath) {
271
+ const { originalDocument } = this
272
+
273
+ const originalValue = get(originalDocument.attributes, attributePath)
274
+ const updatedValue = get(this.attributes, attributePath)
275
+
276
+ const hasChanged = originalValue !== updatedValue
277
+
278
+ return hasChanged
279
+ }
280
+
249
281
  get _query() {
250
282
  const { idKey } = this.constructor
251
283
 
@@ -23,12 +23,13 @@ Profile.schema = loadSync('examples/Profile.yaml')
23
23
  describe('Document', () => {
24
24
  const validator = new Validator([ Profile.schema ])
25
25
  const identity = { accountId: 'ACCOUNT_ID' }
26
- const context = { validator, identity }
26
+ const getContext = () => ({ validator, identity })
27
27
 
28
28
  let id
29
29
 
30
30
  describe('Document.create(context, query, mutation)', () => {
31
31
  it('creates document', async () => {
32
+ const context = getContext()
32
33
  const profile = await Profile.create(context, { name: 'Oleksandr' })
33
34
 
34
35
  expect(profile.id).to.exist
@@ -40,6 +41,7 @@ describe('Document', () => {
40
41
  })
41
42
 
42
43
  it('creates document via mutation', async () => {
44
+ const context = getContext()
43
45
  const profile = await Profile.create(context, {}, { name: 'Olga' })
44
46
 
45
47
  expect(profile.id).to.exist
@@ -49,6 +51,7 @@ describe('Document', () => {
49
51
  })
50
52
 
51
53
  it('creates document with custom ID', async () => {
54
+ const context = getContext()
52
55
  const profile = await Profile.create(context, {}, { id: 'CUSTOM_ID', name: 'Olga' })
53
56
 
54
57
  expect(profile.id).to.eql('CUSTOM_ID')
@@ -56,7 +59,8 @@ describe('Document', () => {
56
59
  })
57
60
 
58
61
  it('creates document without identity in context', async () => {
59
- const profile = await Profile.create({ validator }, { name: 'Oleg' })
62
+ const context = { validator }
63
+ const profile = await Profile.create(context, { name: 'Oleg' })
60
64
 
61
65
  expect(profile.id).to.exist
62
66
  expect(profile.attributes.name).to.eql('Oleg')
@@ -65,6 +69,7 @@ describe('Document', () => {
65
69
  })
66
70
 
67
71
  it('returns exiting document from the context', async () => {
72
+ const context = getContext()
68
73
  const profile = await Profile.create(context, { name: 'Volodymyr' })
69
74
  const existingProfile = await Profile.create({ ...context, document: profile }, { name: 'Volodymyr' })
70
75
 
@@ -72,6 +77,7 @@ describe('Document', () => {
72
77
  })
73
78
 
74
79
  it('throws "DocumentExistsError" if document already exists', async () => {
80
+ const context = getContext()
75
81
  const { id } = await Profile.create(context, {}, { name: 'Artem' })
76
82
  const error = await expectError(() => Profile.create(context, {}, { id, name: 'Liam' }))
77
83
 
@@ -82,6 +88,7 @@ describe('Document', () => {
82
88
 
83
89
  describe('Document.read(context, query)', () => {
84
90
  it('returns document', async () => {
91
+ const context = getContext()
85
92
  const profile = await Profile.read(context, { id })
86
93
 
87
94
  expect(profile).to.exist
@@ -98,6 +105,7 @@ describe('Document', () => {
98
105
 
99
106
  describe('Document.update(context, query, mutation)', () => {
100
107
  it('updates document', async () => {
108
+ const context = getContext()
101
109
  await Profile.update(context, { id }, { name: 'Margarita' })
102
110
 
103
111
  const profile = await Profile.read(context, { id })
@@ -108,9 +116,10 @@ describe('Document', () => {
108
116
  })
109
117
 
110
118
  it('updates document without identity in context', async () => {
111
- const { id } = await Profile.create({ validator }, { name: 'Gustav' })
119
+ const context = { validator }
120
+ const { id } = await Profile.create(context, { name: 'Gustav' })
112
121
 
113
- const profile = await Profile.update({ validator }, { id }, { name: 'Jack' })
122
+ const profile = await Profile.update(context, { id }, { name: 'Jack' })
114
123
 
115
124
  expect(profile.attributes.updatedAt).to.exist
116
125
  expect(profile.attributes.updatedBy).to.not.exist
@@ -127,6 +136,7 @@ describe('Document', () => {
127
136
 
128
137
  describe('Document.delete(context, query)', () => {
129
138
  it('deletes document', async () => {
139
+ const context = getContext()
130
140
  await Profile.delete(context, { id })
131
141
 
132
142
  try {
@@ -152,6 +162,7 @@ describe('Document', () => {
152
162
 
153
163
  describe('Document.index(context, query)', () => {
154
164
  it('returns documents', async () => {
165
+ const context = getContext()
155
166
  await Profile.reset()
156
167
 
157
168
  let result
@@ -162,7 +173,7 @@ describe('Document', () => {
162
173
  expect(result.count).to.eql(0)
163
174
 
164
175
  await Profile.create(context, { name: 'Oleksandr' })
165
- await Profile.create(context, { name: 'Margarita' })
176
+ await Profile.create(context, { name: 'Dasha' })
166
177
  await Profile.create(context, { name: 'Veronica' })
167
178
 
168
179
  result = await Profile.index(context)
@@ -180,8 +191,10 @@ describe('Document', () => {
180
191
 
181
192
  describe('.delete()', () => {
182
193
  it('deletes document', async () => {
194
+ const context = getContext()
183
195
  const profile = await Profile.create(context, { name: 'Oleksandr' })
184
- const { id } = profile
196
+
197
+ const { id } = profile
185
198
 
186
199
  await profile.delete()
187
200
 
@@ -200,6 +213,7 @@ describe('Document', () => {
200
213
 
201
214
  describe('.update(mutation, shouldMutate = false)', () => {
202
215
  it('updates document', async () => {
216
+ const context = getContext()
203
217
  const profile = await Profile.create(context, { name: 'Oleksandr' })
204
218
 
205
219
  const updatedProfile = await profile.update({ name: 'Anton' })
@@ -209,6 +223,7 @@ describe('Document', () => {
209
223
  })
210
224
 
211
225
  it('updates and mutates document', async () => {
226
+ const context = getContext()
212
227
  const profile = await Profile.create(context, { name: 'Oleksandr' })
213
228
 
214
229
  await profile.update({ name: 'Anton' }, true)
@@ -252,6 +267,8 @@ describe('Document', () => {
252
267
  const validator = new Validator([ CustomProfile.schema ])
253
268
 
254
269
  const { id } = await CustomProfile.create({ validator }, { name: 'Irina' })
270
+
271
+ const context = getContext()
255
272
  await CustomProfile.update(context, { id, mutation: { name: 'Maria' } })
256
273
  await CustomProfile.delete(context, { id })
257
274
 
@@ -263,4 +280,42 @@ describe('Document', () => {
263
280
  expect(callbacks.afterDelete).to.exist
264
281
  })
265
282
  })
283
+
284
+ describe('.hasAttributeChanged(attributePath)', () => {
285
+ it('throws "Error" if context has no original document', async () => {
286
+ const context = getContext()
287
+ const profile = await Profile.create(context, { name: 'Sonya' })
288
+
289
+ try {
290
+ profile.hasAttributeChanged('name')
291
+
292
+ } catch (error) {
293
+ expect(error.message).to.eql('Orginal document is undefined')
294
+
295
+ return
296
+ }
297
+
298
+ throw new Error('Error has not been thrown')
299
+ })
300
+
301
+ it('returns true if attribute value has changed', async () => {
302
+ const context = getContext()
303
+ const profile = await Profile.create(context, { name: 'Sonya' })
304
+
305
+ await profile.update({ name: 'Sasha', age: 7 }, true)
306
+
307
+ expect(profile.hasAttributeChanged('name')).to.be.true
308
+ expect(profile.hasAttributeChanged('age')).to.be.true
309
+ })
310
+
311
+ it('returns false if attribute value has not changed', async () => {
312
+ const context = getContext()
313
+ const profile = await Profile.create(context, { name: 'Sonya' })
314
+
315
+ await profile.update({ name: 'Sonya', age: 5 }, true)
316
+ const hasNameChanged = profile.hasAttributeChanged('name')
317
+
318
+ expect(hasNameChanged).to.be.false
319
+ })
320
+ })
266
321
  })