@kravc/dos 1.6.2 → 1.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kravc/dos",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
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,6 +64,8 @@ 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
71
  if (existingDocument) {
@@ -169,13 +171,14 @@ class Document extends Component {
169
171
  await this.beforeUpdate(context, query, mutation)
170
172
  }
171
173
 
172
- const updatedItem = await this._update(query, mutation)
174
+ /* NOTE: ensure that document to be updated exists and save it in the
175
+ context so can be referenced in the after action helper */
176
+ const originalDocument = await this.read(context, query)
173
177
 
174
- if (!updatedItem) {
175
- throw new DocumentNotFoundError(this, { query })
176
- }
178
+ const updatedItem = await this._update(query, mutation)
177
179
 
178
180
  const document = new this(context, updatedItem)
181
+ document._originalDocument = originalDocument
179
182
 
180
183
  if (this.afterUpdate) {
181
184
  await this.afterUpdate(context, query, mutation, document)
@@ -184,9 +187,11 @@ class Document extends Component {
184
187
  return document
185
188
  }
186
189
 
187
- static _update({ id = 'NONE' }, mutation) {
190
+ static _update({ id }, mutation) {
188
191
  const item = STORE[this.name][id]
189
192
 
193
+ /* istanbul ignore next: not used anymore by update interface as read
194
+ operation throws an error if document not found */
190
195
  if (!item) {
191
196
  return false
192
197
  }
@@ -201,20 +206,24 @@ class Document extends Component {
201
206
  await this.beforeDelete(context, query)
202
207
  }
203
208
 
204
- const isDeleted = await this._delete(query)
209
+ /* NOTE: ensure that document to be removed exists and save it in the
210
+ context so can be referenced in the after action helper */
211
+ const originalDocument = await this.read(context, query)
205
212
 
206
- if (!isDeleted) {
207
- throw new DocumentNotFoundError(this, { query })
208
- }
213
+ await this._delete(query)
209
214
 
210
215
  if (this.afterDelete) {
211
- await this.afterDelete(context, query)
216
+ await this.afterDelete(context, query, originalDocument)
212
217
  }
218
+
219
+ return originalDocument
213
220
  }
214
221
 
215
- static _delete({ id = 'NONE' }) {
222
+ static _delete({ id }) {
216
223
  const item = STORE[this.name][id]
217
224
 
225
+ /* istanbul ignore next: not used anymore by delete interface as read
226
+ operation throws an error if document not found */
218
227
  if (!item) {
219
228
  return false
220
229
  }
@@ -240,12 +249,32 @@ class Document extends Component {
240
249
  const document = await this.constructor.update(this.context, this._query, mutation)
241
250
 
242
251
  if (shouldMutate) {
243
- this._attributes = document.attributes
252
+ this._attributes = document._attributes
253
+ this._originalDocument = document._originalDocument
244
254
  }
245
255
 
246
256
  return document
247
257
  }
248
258
 
259
+ get originalDocument() {
260
+ if (!this._originalDocument) {
261
+ throw new Error('Orginal document is undefined')
262
+ }
263
+
264
+ return this._originalDocument
265
+ }
266
+
267
+ hasAttributeChanged(attributePath) {
268
+ const { originalDocument } = this
269
+
270
+ const originalValue = get(originalDocument.attributes, attributePath)
271
+ const updatedValue = get(this.attributes, attributePath)
272
+
273
+ const hasChanged = originalValue !== updatedValue
274
+
275
+ return hasChanged
276
+ }
277
+
249
278
  get _query() {
250
279
  const { idKey } = this.constructor
251
280
 
@@ -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
  })