@kravc/dos 1.6.1 → 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 +2 -2
- package/src/Document.js +41 -12
- package/src/Document.spec.js +61 -6
- package/src/Service.js +4 -3
- package/src/errors/OperationError.yaml +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kravc/dos",
|
|
3
|
-
"version": "1.
|
|
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",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"author": "Alexander Kravets <a@kra.vc>",
|
|
26
26
|
"license": "ISC",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@kravc/schema": "^2.
|
|
28
|
+
"@kravc/schema": "^2.5.1",
|
|
29
29
|
"cookie": "^0.5.0",
|
|
30
30
|
"js-yaml": "^4.1.0",
|
|
31
31
|
"jsonwebtoken": "^9.0.2",
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
package/src/Document.spec.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
119
|
+
const context = { validator }
|
|
120
|
+
const { id } = await Profile.create(context, { name: 'Gustav' })
|
|
112
121
|
|
|
113
|
-
const profile = await Profile.update(
|
|
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: '
|
|
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
|
-
|
|
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
|
})
|
package/src/Service.js
CHANGED
|
@@ -107,7 +107,8 @@ class Service {
|
|
|
107
107
|
if (!Operation) { throw new OperationNotFoundError({ operationId, httpMethod, httpPath }) }
|
|
108
108
|
|
|
109
109
|
context.identity = await authorize(Operation, context)
|
|
110
|
-
const
|
|
110
|
+
const isUpdate = Operation.type === Operation.types.UPDATE
|
|
111
|
+
const parameters = this._getParameters(Operation.inputSchema, context, isUpdate)
|
|
111
112
|
|
|
112
113
|
const operation = new Operation(context)
|
|
113
114
|
response = await operation.exec(parameters)
|
|
@@ -143,7 +144,7 @@ class Service {
|
|
|
143
144
|
return { statusCode, headers, multiValueHeaders, body }
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
_getParameters(inputSchema, context) {
|
|
147
|
+
_getParameters(inputSchema, context, shouldNullifyEmptyValues) {
|
|
147
148
|
if (!inputSchema) { return {} }
|
|
148
149
|
|
|
149
150
|
const { query, mutation } = context
|
|
@@ -152,7 +153,7 @@ class Service {
|
|
|
152
153
|
let result
|
|
153
154
|
|
|
154
155
|
try {
|
|
155
|
-
result = this._validator.validate(input, inputSchema.id)
|
|
156
|
+
result = this._validator.validate(input, inputSchema.id, shouldNullifyEmptyValues)
|
|
156
157
|
|
|
157
158
|
} catch (validationError) {
|
|
158
159
|
throw new InvalidInputError(validationError, context)
|