Skip to content

Commit 947b3fb

Browse files
committed
[add] Usage document of Buffer & Stream List
[fix] Page Size mutation logic
1 parent 49f4430 commit 947b3fb

5 files changed

Lines changed: 154 additions & 49 deletions

File tree

ReadMe.md

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ Just define your **Data models** & **Client HTTP methods**, then leave rest of t
1212

1313
## Usage
1414

15-
### `model/client.ts`
15+
### Simple List
16+
17+
#### `model/client.ts`
1618

1719
```javascript
1820
import { HTTPClient } from 'koajax';
@@ -23,7 +25,7 @@ export const client = new HTTPClient({
2325
});
2426
```
2527

26-
### `model/Repository.ts`
28+
#### `model/Repository.ts`
2729

2830
```typescript
2931
import { buildURLData } from 'web-utility';
@@ -37,7 +39,7 @@ export class RepositoryModel extends ListModel<Repository> {
3739
client = client;
3840
baseURI = 'orgs/idea2app/repos';
3941

40-
async loadPage(page: number, per_page: number) {
42+
protected async loadPage(page: number, per_page: number) {
4143
const { body } = await this.client.get<Repository[]>(
4244
`${this.baseURI}?${buildURLData({ page, per_page })}`
4345
);
@@ -48,7 +50,7 @@ export class RepositoryModel extends ListModel<Repository> {
4850
export default new RepositoryModel();
4951
```
5052

51-
### `page/Repository.tsx`
53+
#### `page/Repository.tsx`
5254

5355
Use [WebCell][6] as an Example
5456

@@ -88,6 +90,89 @@ export class RepositoryPage extends WebCell() {
8890
}
8991
```
9092

93+
### Preload List
94+
95+
#### `model/Repository.ts`
96+
97+
```diff
98+
import { buildURLData } from 'web-utility';
99+
-import { ListModel } from 'mobx-restful';
100+
+import { BufferListModel } from 'mobx-restful';
101+
102+
import { client } from './client';
103+
104+
export type Repository = Record<'full_name' | 'html_url', string>;
105+
106+
-export class RepositoryModel extends ListModel<Repository> {
107+
+export class RepositoryModel extends BufferListModel<Repository>() {
108+
client = client;
109+
baseURI = 'orgs/idea2app/repos';
110+
111+
protected async loadPage(page: number, per_page: number) {
112+
const { body } = await this.client.get<Repository[]>(
113+
`${this.baseURI}?${buildURLData({ page, per_page })}`
114+
);
115+
return { pageData: body };
116+
}
117+
}
118+
119+
export default new RepositoryModel();
120+
```
121+
122+
### Multiple Source List
123+
124+
#### `model/Repository.ts`
125+
126+
```diff
127+
-import { buildURLData } from 'web-utility';
128+
+import { buildURLData, mergeStream } from 'web-utility';
129+
-import { ListModel } from 'mobx-restful';
130+
+import { StreamListModel } from 'mobx-restful';
131+
132+
import { client } from './client';
133+
134+
export type Repository = Record<'full_name' | 'html_url', string>;
135+
136+
-export class RepositoryModel extends ListModel<Repository> {
137+
+export class RepositoryModel extends StreamListModel<Repository>() {
138+
client = client;
139+
- baseURI = 'orgs/idea2app/repos';
140+
141+
- protected async loadPage(page: number, per_page: number) {
142+
- const { body } = await this.client.get<Repository[]>(
143+
- `${this.baseURI}?${buildURLData({ page, per_page })}`
144+
- );
145+
- return { pageData: body };
146+
- }
147+
+ protected openStream() {
148+
+ return mergeStream(
149+
+ async function* () {
150+
+ for (let i = 1; ; i++) {
151+
+ const { body } = await client.get<Repository[]>(
152+
+ 'orgs/idea2app/repos?page=' + i
153+
+ );
154+
+ if (!body[0]) break;
155+
+
156+
+ yield* body;
157+
+ }
158+
+ },
159+
+ async function* () {
160+
+ for (let i = 1; ; i++) {
161+
+ const { body } = await client.get<Repository[]>(
162+
+ 'users/TechQuery/repos?page=' + i
163+
+ );
164+
+ if (!body[0]) break;
165+
+
166+
+ yield* body;
167+
+ }
168+
+ }
169+
+ );
170+
+ }
171+
}
172+
173+
export default new RepositoryModel();
174+
```
175+
91176
## Scaffold
92177

93178
- Progressive Web App (React): https://github.com/idea2app/React-MobX-Bootstrap-ts

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"@parcel/packager-ts": "^2.7.0",
4242
"@parcel/transformer-typescript-types": "^2.7.0",
4343
"@types/jest": "^28.1.7",
44-
"@types/node": "^14.18.23",
44+
"@types/node": "^14.18.24",
4545
"dotenv": "^16.0.1",
4646
"husky": "^8.0.1",
4747
"jest": "^28.1.3",

pnpm-lock.yaml

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

source/List.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ export abstract class ListModel<
8989
pageSize,
9090
filter
9191
);
92+
this.pageSize = pageSize;
93+
9294
const list = [...this.pageList];
9395
list[pageIndex - 1] = pageData;
9496
this.pageList = list;
@@ -194,6 +196,8 @@ export abstract class StreamList<
194196
D extends DataObject,
195197
F extends NewData<D> = NewData<D>
196198
> extends ListModel<D, F> {
199+
baseURI = '';
200+
197201
protected stream?: AsyncGenerator<D, void, D>;
198202
protected abstract openStream(filter: F): AsyncGenerator<D, void, D>;
199203

@@ -218,22 +222,24 @@ export function StreamListModel<
218222
pageSize: number,
219223
filter: F
220224
) {
221-
const newPageCount = pageIndex - this.pageList.length;
225+
const { allItems } = this,
226+
newList: D[] = [];
227+
const newCount = pageIndex * pageSize - allItems.length;
222228

223229
this.stream ||= this.openStream(filter);
224230

225-
for (let i = 0; i < newPageCount; i++) {
226-
const pageData: D[] = [];
231+
for (let i = 0; i < newCount; i++) {
232+
const { done, value } = await this.stream.next();
227233

228-
for (let j = 0; j < pageSize; j++) {
229-
const { done, value } = await this.stream.next();
234+
if (done) break;
230235

231-
if (done) break;
232-
233-
pageData.push(value as D);
234-
}
235-
this.pageList = [...this.pageList, pageData];
236+
newList.push(value as D);
236237
}
238+
239+
this.pageList = splitArray(
240+
[...this.allItems, ...newList],
241+
pageSize
242+
);
237243
return { pageData: this.pageList[pageIndex - 1] };
238244
}
239245
}

test/List.spec.ts

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { isEmpty, buildURLData, mergeStream } from 'web-utility';
33
import { ListModel, StreamListModel } from '../source/List';
44
import { client } from './service';
55

6+
type Repository = Record<'full_name' | 'html_url', string>;
7+
68
describe('List model', () => {
79
describe('Single List model', () => {
8-
type Repository = Record<'full_name' | 'html_url', string>;
9-
1010
class RepositoryModel extends ListModel<Repository> {
1111
client = client;
1212
baseURI = 'orgs/idea2app/repos';
@@ -98,48 +98,62 @@ describe('List model', () => {
9898
});
9999

100100
describe('Multiple List model', () => {
101-
type User = Record<'name', string>;
102-
103-
class UserModel extends StreamListModel<User>() {
101+
class RepositoryModel extends StreamListModel<Repository>() {
104102
client = client;
105-
baseURI = 'user';
106103

107104
protected openStream() {
108105
return mergeStream(
109106
async function* () {
110-
yield* new Array<User>(3).fill({ name: 'Ukrainian' });
107+
for (let i = 1; ; i++) {
108+
const { body } = await client.get<Repository[]>(
109+
'orgs/idea2app/repos?page=' + i
110+
);
111+
if (!body[0]) break;
112+
113+
yield* body;
114+
}
111115
},
112116
async function* () {
113-
yield* new Array<User>(3).fill({ name: 'Russian' });
117+
for (let i = 1; ; i++) {
118+
const { body } = await client.get<Repository[]>(
119+
'users/TechQuery/repos?page=' + i
120+
);
121+
if (!body[0]) break;
122+
123+
yield* body;
124+
}
114125
}
115126
);
116127
}
117128
}
118-
const store = new UserModel();
129+
const store = new RepositoryModel();
119130

120131
it('should load a Page with items in every stream', async () => {
121132
const list = await store.getList({}, 1, 3);
122133

123-
expect(list).toEqual([
124-
{ name: 'Ukrainian' },
125-
{ name: 'Russian' },
126-
{ name: 'Ukrainian' }
127-
]);
134+
expect(
135+
list.map(({ full_name }) => full_name.split('/')[0])
136+
).toEqual(['idea2app', 'TechQuery', 'idea2app']);
128137
});
129138

130-
it('should load all pages before current page', async () => {
139+
it('should load all items before current page', async () => {
131140
store.clear();
132141

133142
expect(store.pageList).toHaveLength(0);
134143

135-
const list = await store.getList({}, 2, 3);
144+
const list = await store.getList({}, 2, 4);
136145

137-
expect(list).toEqual([
138-
{ name: 'Russian' },
139-
{ name: 'Ukrainian' },
140-
{ name: 'Russian' }
141-
]);
142-
expect(store.allItems).toHaveLength(6);
146+
expect(
147+
list.map(({ full_name }) => full_name.split('/')[0])
148+
).toEqual(['idea2app', 'TechQuery', 'idea2app', 'TechQuery']);
149+
150+
expect(
151+
store.pageList[0].map(
152+
({ full_name }) => full_name.split('/')[0]
153+
)
154+
).toEqual(['idea2app', 'TechQuery', 'idea2app', 'TechQuery']);
155+
156+
expect(store.allItems).toHaveLength(8);
143157
});
144158
});
145159
});

0 commit comments

Comments
 (0)