Skip to content

Commit 28d3efc

Browse files
committed
feat(nestjs-json-rpc-sdk): Skip empty element in response
Skip item of "DeleteOne" in result in atomic operation
1 parent c3b9322 commit 28d3efc

File tree

5 files changed

+202
-20
lines changed

5 files changed

+202
-20
lines changed

libs/json-api/json-api-nestjs-sdk/src/lib/service/atomic-operations.service.spec.ts

+116
Original file line numberDiff line numberDiff line change
@@ -168,5 +168,121 @@ describe('atomicOperationService', () => {
168168
patchUser.addresses.id,
169169
]);
170170
});
171+
it('Should be return factory with skip false', async () => {
172+
const postAddress = new Addresses();
173+
postAddress.id = 1;
174+
const patchUser = new Users();
175+
patchUser.id = 1;
176+
177+
const deleteUser = new Users();
178+
deleteUser.id = 2;
179+
const patchRelationshipsBookList = new BookList();
180+
patchRelationshipsBookList.id = 'id';
181+
patchRelationshipsBookList.users = [patchUser];
182+
183+
const deleteRelationshipsAddress = new Addresses();
184+
deleteRelationshipsAddress.id = 2;
185+
deleteRelationshipsAddress.user = deleteUser;
186+
187+
patchUser.addresses = postAddress;
188+
189+
const spyHttpInnerClient = jest
190+
.spyOn(httpInnerClient, 'post')
191+
.mockImplementation(() =>
192+
of({
193+
[KEY_MAIN_OUTPUT_SCHEMA]: [
194+
{
195+
meta: {},
196+
data: {
197+
type: 'users',
198+
id: patchUser.id,
199+
attributes: {},
200+
},
201+
},
202+
{
203+
meta: {},
204+
data: {
205+
type: 'addresses',
206+
id: postAddress.id,
207+
attributes: {},
208+
},
209+
},
210+
{
211+
meta: {},
212+
data: [
213+
{
214+
type: 'users',
215+
id: patchUser.id,
216+
attributes: {},
217+
},
218+
],
219+
},
220+
{
221+
meta: {},
222+
data: {
223+
type: 'addresses',
224+
id: postAddress.id,
225+
attributes: {},
226+
},
227+
},
228+
],
229+
})
230+
);
231+
232+
const result$ = atomicOperationsService
233+
.patchOne(patchUser)
234+
.deleteOne(deleteUser, false)
235+
.postOne(postAddress)
236+
.patchRelationships(patchRelationshipsBookList, 'users')
237+
.postRelationships(patchUser, 'addresses')
238+
.deleteRelationships(deleteRelationshipsAddress, 'user')
239+
.run();
240+
241+
const result = await lastValueFrom(result$);
242+
expect(spyHttpInnerClient).toBeCalledTimes(1);
243+
const pathUserResult = new Users();
244+
pathUserResult.id = patchUser.id;
245+
expect(result).toEqual([
246+
pathUserResult,
247+
'EMPTY',
248+
postAddress,
249+
[patchRelationshipsBookList.users[0].id],
250+
patchUser.addresses.id,
251+
]);
252+
});
253+
});
254+
it('Should be return factory with skip false only delete', async () => {
255+
const postAddress = new Addresses();
256+
postAddress.id = 1;
257+
const patchUser = new Users();
258+
patchUser.id = 1;
259+
260+
const deleteUser = new Users();
261+
deleteUser.id = 2;
262+
const patchRelationshipsBookList = new BookList();
263+
patchRelationshipsBookList.id = 'id';
264+
patchRelationshipsBookList.users = [patchUser];
265+
266+
const deleteRelationshipsAddress = new Addresses();
267+
deleteRelationshipsAddress.id = 2;
268+
deleteRelationshipsAddress.user = deleteUser;
269+
270+
patchUser.addresses = postAddress;
271+
272+
const spyHttpInnerClient = jest
273+
.spyOn(httpInnerClient, 'post')
274+
.mockImplementation(() =>
275+
of({
276+
[KEY_MAIN_OUTPUT_SCHEMA]: [],
277+
})
278+
);
279+
280+
const result$ = atomicOperationsService.deleteOne(deleteUser, false).run();
281+
282+
const result = await lastValueFrom(result$);
283+
expect(spyHttpInnerClient).toBeCalledTimes(1);
284+
const pathUserResult = new Users();
285+
pathUserResult.id = patchUser.id;
286+
expect(result).toEqual(['EMPTY']);
171287
});
172288
});

libs/json-api/json-api-nestjs-sdk/src/lib/service/atomic-operations.service.ts

+66-15
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ export class AtomicOperationsService<T extends unknown[]>
4747
this.jsonApiSdkConfig.operationUrl
4848
);
4949

50+
const indexDeleteIfNotSkipEmpty = atomicBody
51+
.reduce<number[]>((acum, item, index) => {
52+
if (
53+
item.op === 'remove' &&
54+
!this.generateAtomicBody[index].isSkipEmpty
55+
) {
56+
acum.push(index);
57+
}
58+
return acum;
59+
}, [])
60+
.sort((a, b) => a - b);
5061
return this.httpInnerClient.post<T>(operationUrl, body).pipe(
5162
map((r) => r[KEY_MAIN_OUTPUT_SCHEMA]),
5263
map(
@@ -56,14 +67,39 @@ export class AtomicOperationsService<T extends unknown[]>
5667
? this.jsonApiUtilsService.getResultForRelation(item)
5768
: this.jsonApiUtilsService.convertResponseData(item)
5869
) as T
70+
),
71+
map((r) =>
72+
indexDeleteIfNotSkipEmpty.reduce(
73+
(acc, index, currentIndex) => {
74+
acc.splice(index + currentIndex, 0, 'EMPTY');
75+
return acc;
76+
},
77+
[...r] as T
78+
)
5979
)
6080
);
6181
}
6282

63-
public deleteOne<Entity extends EntityObject>(
83+
deleteOne<Entity extends EntityObject>(
6484
entity: Entity
65-
): AtomicOperations<T> {
66-
return this.setToBody('deleteOne', entity);
85+
): AtomicOperations<[...T]>;
86+
deleteOne<Entity extends EntityObject>(
87+
entity: Entity,
88+
skipEmpty: true
89+
): AtomicOperations<[...T]>;
90+
deleteOne<Entity extends EntityObject>(
91+
entity: Entity,
92+
skipEmpty: false
93+
): AtomicOperations<[...T, 'EMPTY']>;
94+
deleteOne<Entity extends EntityObject>(
95+
entity: Entity,
96+
skipEmpty?: boolean
97+
): AtomicOperations<[...T, 'EMPTY'] | [...T]> {
98+
return this.setToBody(
99+
'deleteOne',
100+
entity,
101+
skipEmpty === undefined ? true : skipEmpty
102+
);
67103
}
68104

69105
public patchOne<Entity extends EntityObject>(
@@ -109,6 +145,11 @@ export class AtomicOperationsService<T extends unknown[]>
109145
operationType: Extract<keyof AtomicVoidOperation, 'deleteOne'>,
110146
entity: Entity
111147
): AtomicOperations<T>;
148+
private setToBody<Entity extends EntityObject>(
149+
operationType: Extract<keyof AtomicVoidOperation, 'deleteOne'>,
150+
entity: Entity,
151+
skipEmpty: boolean
152+
): AtomicOperations<[...T, 'EMPTY']>;
112153
private setToBody<Entity extends EntityObject>(
113154
operationType: Exclude<keyof AtomicVoidOperation, 'deleteOne'>,
114155
entity: Entity
@@ -135,30 +176,40 @@ export class AtomicOperationsService<T extends unknown[]>
135176
>(
136177
operationType: keyof AtomicVoidOperation,
137178
entity: Entity,
138-
relationType?: Rel
179+
relationType?: Rel | boolean
139180
):
140181
| AtomicOperations<[...T, Entity]>
141182
| AtomicOperations<[...T, ReturnIfArray<Entity[Rel], string>]>
142-
| AtomicOperations<[...T]> {
183+
| AtomicOperations<[...T]>
184+
| AtomicOperations<[...T, 'EMPTY']> {
143185
const atomicBody = new GenerateAtomicBody<Entity, Rel>(
144186
this.jsonApiUtilsService,
145187
this.jsonApiSdkConfig
146188
);
147-
switch (operationType) {
148-
case 'postRelationships':
149-
case 'patchRelationships':
150-
case 'deleteRelationships':
151-
if (relationType) atomicBody[operationType](entity, relationType);
152-
break;
153-
default:
154-
atomicBody[operationType](entity);
155-
break;
189+
190+
if (typeof relationType === 'boolean') {
191+
if (operationType === 'deleteOne') {
192+
atomicBody[operationType](entity, relationType);
193+
}
194+
} else {
195+
switch (operationType) {
196+
case 'postRelationships':
197+
case 'patchRelationships':
198+
case 'deleteRelationships':
199+
if (relationType) atomicBody[operationType](entity, relationType);
200+
break;
201+
default:
202+
atomicBody[operationType](entity, true);
203+
break;
204+
}
156205
}
206+
157207
this.addBody(atomicBody);
158208

159209
return this as unknown as
160210
| AtomicOperations<[...T, Entity]>
161211
| AtomicOperations<[...T, ReturnIfArray<Entity[Rel], string>]>
162-
| AtomicOperations<T>;
212+
| AtomicOperations<T>
213+
| AtomicOperations<[...T, 'EMPTY']>;
163214
}
164215
}

libs/json-api/json-api-nestjs-sdk/src/lib/types/atomic.ts

+10
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,19 @@ export interface AtomicMainOperations<T extends unknown[]> {
1717
patchOne<Entity extends EntityObject>(
1818
entity: Entity
1919
): AtomicOperations<[...T, Entity]>;
20+
2021
deleteOne<Entity extends EntityObject>(
2122
entity: Entity
2223
): AtomicOperations<[...T]>;
24+
deleteOne<Entity extends EntityObject>(
25+
entity: Entity,
26+
skipEmpty: true
27+
): AtomicOperations<[...T]>;
28+
deleteOne<Entity extends EntityObject>(
29+
entity: Entity,
30+
skipEmpty: false
31+
): AtomicOperations<[...T, 'EMPTY']>;
32+
2333
patchRelationships<
2434
Entity extends EntityObject,
2535
Rel extends EntityRelation<Entity>

libs/json-api/json-api-nestjs-sdk/src/lib/utils/generate-atomic-body.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ describe('GenerateAtomicBody', () => {
173173
entity.text = 'text';
174174
entity.users = [user];
175175

176-
expect(() => generateAtomicBody.deleteOne(entity)).toThrowError();
176+
expect(() => generateAtomicBody.deleteOne(entity, true)).toThrowError();
177177
});
178178

179179
it('should not throw error if entity contains id', () => {
@@ -188,7 +188,7 @@ describe('GenerateAtomicBody', () => {
188188
ref: { type: 'book-list', id: entity.id },
189189
};
190190

191-
generateAtomicBody.deleteOne(entity);
191+
generateAtomicBody.deleteOne(entity, true);
192192
const result = generateAtomicBody.getBody();
193193
expect(result).toEqual(expectedBodyData);
194194
});
@@ -202,7 +202,7 @@ describe('GenerateAtomicBody', () => {
202202
entity.text = 'text';
203203
entity.users = [user];
204204

205-
expect(() => generateAtomicBody.deleteOne(entity)).toThrowError();
205+
expect(() => generateAtomicBody.deleteOne(entity, true)).toThrowError();
206206
});
207207

208208
it('should not throw error if entity contains id', () => {

libs/json-api/json-api-nestjs-sdk/src/lib/utils/generate-atomic-body.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ export class GenerateAtomicBody<
3838
private jsonApiSdkConfig: JsonApiSdkConfig
3939
) {}
4040
private bodyData!: BodyType;
41+
private skipEmpty = true;
42+
43+
get isSkipEmpty(): boolean {
44+
return this.skipEmpty;
45+
}
4146

4247
private setToBody(op: Operation, entity: Entity, relationType?: Rel) {
4348
const type = getTypeForReq(entity.constructor.name);
@@ -88,13 +93,13 @@ export class GenerateAtomicBody<
8893

8994
this.setToBody(Operation.update, entity);
9095
}
91-
deleteOne(entity: Entity): void {
96+
deleteOne(entity: Entity, skipEmpty: boolean): void {
9297
if (!entity[this.jsonApiSdkConfig.idKey]) {
9398
new Error(
9499
'Resource params should be instance of resource with id params'
95100
);
96101
}
97-
102+
this.skipEmpty = skipEmpty;
98103
this.setToBody(Operation.remove, entity);
99104
}
100105
patchRelationships(entity: Entity, relationType: Rel): void {

0 commit comments

Comments
 (0)