Skip to content

Commit 331b151

Browse files
authored
Merge pull request #32 from Alpha1337k/main
feat: batch operations
2 parents 4e40c5b + 820f093 commit 331b151

File tree

3 files changed

+251
-0
lines changed

3 files changed

+251
-0
lines changed

src/drivers/default/builders/queryBuilder.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,23 @@ export class QueryBuilder<
104104
return this.hydrate(response.data.data, response);
105105
}
106106

107+
public async batchStore(items: M[]): Promise<M[]> {
108+
const data = {
109+
resources: items.map(x => x.$attributes)
110+
};
111+
112+
const response = await this.httpClient.request<{data: Array<AllAttributes & Relations> }>(
113+
`/batch`,
114+
HttpMethod.POST,
115+
null,
116+
data
117+
);
118+
119+
return response.data.data.map((attributes) => {
120+
return this.hydrate(attributes, response);
121+
})
122+
}
123+
107124
public async update(key: Key, attributes: Attributes): Promise<M> {
108125
const response = await this.httpClient.request<{ data: AllAttributes & Relations }>(
109126
`/${key}`,
@@ -115,6 +132,24 @@ export class QueryBuilder<
115132
return this.hydrate(response.data.data, response);
116133
}
117134

135+
public async batchUpdate(items: M[]): Promise<M[]> {
136+
const data = {
137+
resources: {}
138+
};
139+
items.forEach((v) => data.resources[v.$getKey()] = v.$attributes);
140+
141+
const response = await this.httpClient.request<{ data: Array< AllAttributes & Relations > }>(
142+
`batch`,
143+
HttpMethod.PATCH,
144+
null,
145+
data
146+
)
147+
148+
return response.data.data.map((attributes: AllAttributes & Relations) => {
149+
return this.hydrate(attributes, response);
150+
});
151+
}
152+
118153
public async destroy(key: Key, force: boolean = false): Promise<M> {
119154
const response = await this.httpClient.request<{ data: AllAttributes & Relations }>(
120155
`/${key}`,
@@ -125,6 +160,27 @@ export class QueryBuilder<
125160
return this.hydrate(response.data.data, response);
126161
}
127162

163+
public async batchDelete(items: Key[]): Promise<M[]>
164+
{
165+
if (!items.length)
166+
return [];
167+
168+
const data = {
169+
resources: items
170+
};
171+
172+
const response = await this.httpClient.request<{ data: Array< AllAttributes & Relations > }>(
173+
`/batch`,
174+
HttpMethod.DELETE,
175+
null,
176+
data
177+
);
178+
179+
return response.data.data.map((attributes: AllAttributes & Relations) => {
180+
return this.hydrate(attributes, response);
181+
});
182+
}
183+
128184
public async restore(key: Key): Promise<M> {
129185
const response = await this.httpClient.request<{ data: AllAttributes & Relations }>(
130186
`/${key}/restore`,
@@ -135,6 +191,24 @@ export class QueryBuilder<
135191
return this.hydrate(response.data.data, response);
136192
}
137193

194+
public async batchRestore(items: Key[]): Promise<M[]> {
195+
const data = {
196+
resources: items
197+
};
198+
199+
const response = await this.httpClient.request<{ data: Array< AllAttributes & Relations > }>(
200+
`/batch/restore`,
201+
HttpMethod.POST,
202+
null,
203+
data
204+
);
205+
206+
return response.data.data.map((attributes: AllAttributes & Relations) => {
207+
return this.hydrate(attributes, response);
208+
});
209+
}
210+
211+
138212
public with(relations: string[]): this {
139213
this.includes = relations;
140214

tests/integration/batch.test.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import makeServer from './drivers/default/server';
2+
import Post from '../stubs/models/post';
3+
import { Orion } from '../../src/orion';
4+
5+
let server: any;
6+
7+
beforeEach(() => {
8+
server = makeServer();
9+
});
10+
11+
afterEach(() => {
12+
server.shutdown();
13+
});
14+
15+
describe('Batch tests', () => {
16+
test('saving a couple of models', async () => {
17+
const posts = [
18+
new Post(),
19+
new Post(),
20+
]
21+
posts[0].$attributes.title = "First";
22+
posts[1].$attributes.title = "Second";
23+
24+
const res = await Post.$query().batchStore(posts);
25+
26+
expect(server.schema.posts.all()).toHaveLength(2);
27+
expect(server.schema.posts.find('1').attrs.title).toBe("First")
28+
expect(server.schema.posts.find('2').attrs.title).toBe("Second")
29+
expect(server.schema.posts.find('1').attrs.title).toEqual(res[0].$attributes.title)
30+
expect(server.schema.posts.find('1').attrs.created_at).toEqual(res[0].$attributes.created_at)
31+
expect(server.schema.posts.find('2').attrs.title).toEqual(res[1].$attributes.title)
32+
expect(server.schema.posts.find('2').attrs.created_at).toEqual(res[1].$attributes.created_at)
33+
});
34+
35+
test('updating a couple of models', async () => {
36+
const posts = [
37+
new Post(),
38+
new Post(),
39+
new Post(),
40+
]
41+
posts[0].$attributes.title = "First";
42+
posts[1].$attributes.title = "Second";
43+
posts[2].$attributes.title = "Third";
44+
45+
let res = await Post.$query().batchStore(posts);
46+
47+
res[0].$attributes.title = "NewFirst";
48+
res[1].$attributes.title = "NewSecond";
49+
50+
res = await Post.$query().batchUpdate([res[0],res[1]]);
51+
52+
expect(res).toHaveLength(2);
53+
expect(server.schema.posts.find('1').attrs.title).toBe("NewFirst")
54+
expect(server.schema.posts.find('2').attrs.title).toBe("NewSecond")
55+
expect(server.schema.posts.find('1').attrs.title).toEqual(res[0].$attributes.title)
56+
expect(server.schema.posts.find('2').attrs.title).toEqual(res[1].$attributes.title)
57+
expect(server.schema.posts.find('3').attrs.title).toEqual("Third");
58+
59+
});
60+
61+
test('deleting a couple of models', async () => {
62+
const posts = [
63+
new Post(),
64+
new Post(),
65+
new Post(),
66+
]
67+
posts[0].$attributes.title = "First";
68+
posts[1].$attributes.title = "Second";
69+
posts[2].$attributes.title = "Third";
70+
71+
let res = await Post.$query().batchStore(posts);
72+
73+
let ModelDelete = await Post.$query().batchDelete([res[1].$getKey(), res[2].$getKey()]);
74+
75+
expect(server.schema.posts.find('1').attrs.deleted_at).toBeUndefined();
76+
expect(server.schema.posts.find('2').attrs.deleted_at).toBeDefined();
77+
expect(server.schema.posts.find('3').attrs.deleted_at).toBeDefined();
78+
expect(server.schema.posts.find('2').attrs.title).toEqual(ModelDelete[0].$attributes.title)
79+
expect(server.schema.posts.find('3').attrs.title).toEqual(ModelDelete[1].$attributes.title)
80+
81+
82+
});
83+
84+
test('restoring a couple of models', async () => {
85+
const posts = [
86+
new Post(),
87+
new Post(),
88+
new Post(),
89+
]
90+
posts[0].$attributes.title = "First";
91+
posts[1].$attributes.title = "Second";
92+
posts[2].$attributes.title = "Third";
93+
94+
let res = await Post.$query().batchStore(posts);
95+
96+
// delete ID 2 & 3
97+
let ModelDelete = await Post.$query().batchDelete([res[1].$getKey(), res[2].$getKey()]);
98+
99+
res = await Post.$query().batchRestore(ModelDelete.map(x => x.$getKey()));
100+
101+
expect(server.schema.posts.find('1').attrs.deleted_at).toBeFalsy();
102+
expect(server.schema.posts.find('2').attrs.deleted_at).toBeFalsy();
103+
expect(server.schema.posts.find('3').attrs.deleted_at).toBeFalsy();
104+
expect(server.schema.posts.find('2').attrs.title).toEqual(res[0].$attributes.title);
105+
expect(server.schema.posts.find('3').attrs.title).toEqual(res[1].$attributes.title);
106+
107+
108+
});
109+
});

tests/integration/drivers/default/server.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,74 @@ export default function makeServer() {
129129
updated: [request.params.tag_id],
130130
};
131131
});
132+
133+
this.post('/api/posts/batch', (schema: any, request) => {
134+
const body: {
135+
resources: any[]
136+
} = JSON.parse(request.requestBody);
137+
138+
const rval: any[] = [];
139+
for (let i = 0; i < body.resources.length; i++) {
140+
rval.push(schema.posts.create(body.resources[i]));
141+
}
142+
143+
return {data: rval};
144+
})
145+
146+
this.patch('/api/posts/batch', (schema: any, request) => {
147+
const body: {
148+
resources: object
149+
} = JSON.parse(request.requestBody);
150+
151+
const rval: any[] = [];
152+
for (const key in body.resources) {
153+
const attrs = body.resources[key];
154+
155+
const post = schema.posts.find(key);
156+
157+
158+
rval.push(post.update(attrs));
159+
}
160+
161+
return {data: rval};
162+
})
163+
164+
this.delete('/api/posts/batch', (schema: any, request) => {
165+
const body: {
166+
resources: number[]
167+
} = JSON.parse(request.requestBody);
168+
169+
const rval: any[] = [];
170+
for (let i = 0; i < body.resources.length; i++) {
171+
const id = body.resources[i];
172+
const post = schema.posts.find(id);
173+
174+
post.update({ deleted_at: '2021-01-01' });
175+
176+
rval.push(post);
177+
}
178+
179+
return {data: rval};
180+
})
181+
182+
this.post('/api/posts/batch/restore', (schema: any, request) => {
183+
const body: {
184+
resources: number[]
185+
} = JSON.parse(request.requestBody);
186+
187+
const rval: any[] = [];
188+
189+
for (let i = 0; i < body.resources.length; i++) {
190+
const id = body.resources[i];
191+
const post = schema.posts.find(id);
192+
193+
post.update({ deleted_at: null });
194+
195+
rval.push(post);
196+
}
197+
198+
return {data: rval};
199+
})
132200
},
133201
});
134202
}

0 commit comments

Comments
 (0)