Skip to content

Commit 47a5dd0

Browse files
Boris AbramovBoris Abramov
authored andcommitted
Refactor code
1 parent a5ad3e9 commit 47a5dd0

12 files changed

Lines changed: 404 additions & 187 deletions

File tree

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
"./types": "./build/src/types.js",
2121
"./commands": "./build/commands/main.js"
2222
},
23+
"imports": {
24+
"#common/*": "./src/common/*.js",
25+
"#utils/*": "./src/utils/*.js",
26+
"#processors/*": "./src/processors/*.js"
27+
},
2328
"scripts": {
2429
"clean": "del-cli build",
2530
"copy:templates": "copyfiles \"stubs/**/*.stub\" build",

src/common/types.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* adonis-api-resources
3+
*
4+
* (c) Boris Abramov <boris@rykantas.com>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
export type Data = object | object[]
11+
12+
export interface Processor {
13+
pick: Function
14+
omit: Function
15+
remap: Function
16+
}
17+
18+
export interface PaginationMeta {
19+
total: number
20+
perPage: number
21+
currentPage: number
22+
lastPage: number
23+
firstPage: number
24+
firstPageUrl: string
25+
lastPageUrl: string
26+
nextPageUrl: string | null
27+
previousPageUrl: string | null
28+
}
29+
30+
export interface PaginatedORMData {
31+
rows: object[]
32+
meta: PaginationMeta
33+
}
34+
35+
export interface PaginatedODMData {
36+
data: object[]
37+
meta: PaginationMeta
38+
}
39+
40+
export interface PaginatedData {
41+
data: object[]
42+
meta: PaginationMeta
43+
}

src/processors/collection.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* adonis-api-resources
3+
*
4+
* (c) Boris Abramov <boris@rykantas.com>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { Processor } from '#common/types'
11+
import { pick as pickEntity, omit as omitEntity } from '#utils/parsers'
12+
13+
export default class CollectionProcessor implements Processor {
14+
pick(data: object[], ...keys: string[]) {
15+
let result = []
16+
for (let entity of data) {
17+
result.push(pickEntity(entity, keys))
18+
}
19+
return result
20+
}
21+
22+
omit(data: object[], ...keys: string[]) {
23+
let result = []
24+
for (let entity of data) {
25+
result.push(omitEntity(entity, keys))
26+
}
27+
return result
28+
}
29+
30+
remap(data: object[], defineMap: Function): object[] {
31+
return data.map((item) => {
32+
return defineMap(item)
33+
})
34+
}
35+
}

src/processors/entity.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* adonis-api-resources
3+
*
4+
* (c) Boris Abramov <boris@rykantas.com>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { Processor } from '#common/types'
11+
import { pick as pickEntity, omit as omitEntity } from '#utils/parsers'
12+
13+
export default class EntityProcessor implements Processor {
14+
pick(data: object, ...keys: string[]): object {
15+
return pickEntity(data, keys)
16+
}
17+
18+
omit(data: object, ...keys: string[]): object {
19+
return omitEntity(data, keys)
20+
}
21+
22+
remap(data: object, defineMap: Function): object {
23+
return defineMap(data)
24+
}
25+
}

src/processors/paginated.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* adonis-api-resources
3+
*
4+
* (c) Boris Abramov <boris@rykantas.com>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { PaginatedData, PaginatedODMData, PaginatedORMData, Processor } from '#common/types'
11+
import CollectionProcessor from '#processors/collection'
12+
13+
export default class PaginatedProcessor implements Processor {
14+
constructor(private collectionProcessor: CollectionProcessor) {}
15+
16+
private extractCollection(data: PaginatedORMData | PaginatedODMData): object[] {
17+
return 'rows' in data ? data.rows : data.data
18+
}
19+
20+
private extractMeta(data: PaginatedORMData | PaginatedODMData) {
21+
if ('getMeta' in data) {
22+
if (typeof data.getMeta === 'function') {
23+
return data.getMeta()
24+
} else {
25+
return data.meta
26+
}
27+
} else return {}
28+
}
29+
30+
pick(data: PaginatedORMData | PaginatedODMData, ...keys: string[]): PaginatedData {
31+
return {
32+
meta: this.extractMeta(data),
33+
data: this.collectionProcessor.pick(this.extractCollection(data), ...keys),
34+
}
35+
}
36+
37+
omit(data: PaginatedORMData | PaginatedODMData, ...keys: string[]): PaginatedData {
38+
return {
39+
meta: this.extractMeta(data),
40+
data: this.collectionProcessor.omit(this.extractCollection(data), ...keys),
41+
}
42+
}
43+
44+
remap(data: PaginatedORMData | PaginatedODMData, defineMap: Function): PaginatedData {
45+
return {
46+
meta: this.extractMeta(data),
47+
data: this.collectionProcessor.remap(this.extractCollection(data), defineMap),
48+
}
49+
}
50+
}

src/resource.ts

Lines changed: 53 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,156 +1,91 @@
11
/*
22
* adonis-api-resources
33
*
4-
* (c) Boris Abramov <boris@ideainc.eu>
4+
* (c) Boris Abramov <boris@rykantas.com>
55
*
66
* For the full copyright and license information, please view the LICENSE
77
* file that was distributed with this source code.
88
*/
99

10+
import { type Data, Processor } from '#common/types'
11+
import EntityProcessor from '#processors/entity'
12+
import CollectionProcessor from '#processors/collection'
13+
import PaginatedProcessor from '#processors/paginated'
14+
import { paginate } from '#utils/paginate'
15+
1016
export abstract class Resource {
11-
constructor(private data: any) {}
17+
constructor(private data: Data) {}
18+
19+
private _processor?: Processor
1220

13-
abstract defineMap(data: any): object
21+
abstract defineMap(data: object): object
1422

15-
private isPaginated(): boolean {
16-
const ormPaginated = 'rows' in this.data && 'currentPage' in this.data
17-
const odmPaginated = 'data' in this.data && 'meta' in this.data
23+
private isPaginated(data: Data): boolean {
24+
const ormPaginated = 'rows' in data && 'currentPage' in data
25+
const odmPaginated = 'data' in data && 'meta' in data
1826
const indeedPaginated = ormPaginated || odmPaginated
19-
if (odmPaginated) this.data.getMeta = () => this.data.meta
27+
if (odmPaginated) (data as any).getMeta = () => (data as any).meta
2028
return indeedPaginated
2129
}
2230

23-
private isCollection(): boolean {
24-
return Array.isArray(this.data)
25-
}
26-
27-
private parsePaginated(meta: object, data: object): object {
28-
return {
29-
meta: meta,
30-
data: data,
31-
}
32-
}
33-
34-
private redefineEntity(data: any): object {
35-
return this.defineMap(data)
31+
private isCollection(data: Data): boolean {
32+
return Array.isArray(data)
3633
}
3734

38-
private redefineCollection(data: any[]): object {
39-
return data.map((item) => {
40-
return this.redefineEntity(item)
41-
})
42-
}
43-
44-
private pickEntity(entity: object, keys: string[]) {
45-
return Object.assign({}, ...keys.map((key) => ({ [key]: (entity as any)[key] })))
46-
}
47-
48-
private pickCollection(data: object[], keys: string[]) {
49-
let result = []
50-
for (let entity of data) {
51-
result.push(this.pickEntity(entity, keys))
35+
private processor(): Processor {
36+
if (!this._processor) {
37+
if (this.isPaginated(this.data)) {
38+
this._processor = new PaginatedProcessor(new CollectionProcessor())
39+
} else if (this.isCollection(this.data)) {
40+
this._processor = new CollectionProcessor()
41+
} else {
42+
this._processor = new EntityProcessor()
43+
}
5244
}
53-
return result
45+
return this._processor
5446
}
5547

56-
private omitEntity(entity: object, keys: string[]) {
57-
const exclude = new Set(keys)
58-
const o =
59-
'serialize' in entity && typeof entity.serialize === 'function' ? entity.serialize() : entity
60-
return Object.fromEntries(Object.entries(o).filter((e) => !exclude.has(e[0])))
48+
pick(...keys: string[]) {
49+
const processor = this.processor()
50+
this.data = processor.pick(this.data, ...keys)
51+
return this
6152
}
6253

63-
private omitCollection(data: object[], keys: string[]) {
64-
let result = []
65-
for (let entity of data) {
66-
result.push(this.omitEntity(entity, keys))
67-
}
68-
return result
54+
omit(...keys: string[]): this {
55+
const processor = this.processor()
56+
this.data = processor.omit(this.data, ...keys)
57+
return this
6958
}
7059

71-
pick(...keys: string[]): this {
72-
if (this.isPaginated()) {
73-
this.data = this.parsePaginated(
74-
(this.data as any).getMeta(),
75-
this.pickCollection((this.data as any).rows || (this.data as any).data, keys)
76-
)
77-
} else if (this.isCollection()) {
78-
this.data = this.pickCollection(this.data, keys)
79-
} else {
80-
this.data = this.pickEntity(this.data, keys)
81-
}
60+
remap(): this {
61+
const processor = this.processor()
62+
this.data = processor.remap(this.data, this.defineMap)
8263
return this
8364
}
8465

85-
omit(...keys: string[]): this {
86-
if (this.isPaginated()) {
87-
this.data = this.parsePaginated(
88-
(this.data as any).getMeta(),
89-
this.omitCollection((this.data as any).rows || (this.data as any).data, keys)
90-
)
91-
} else if (this.isCollection()) {
92-
this.data = this.omitCollection(this.data, keys)
93-
} else {
94-
this.data = this.omitEntity(this.data, keys)
66+
paginate(page: number = 1, limit: number = 10): this {
67+
if (!this.isCollection(this.data)) {
68+
throw new Error('Pagination requires an array of objects')
9569
}
70+
this.data = paginate(this.data as Array<object>, page, limit)
9671
return this
9772
}
9873

74+
// Obsolete methods for backward compatibility
9975
redefine(): this {
100-
if (this.isPaginated()) {
101-
this.data = this.parsePaginated(
102-
(this.data as any).getMeta(),
103-
this.redefineCollection((this.data as any).rows || (this.data as any).data)
104-
)
105-
} else if (this.isCollection()) {
106-
this.data = this.redefineCollection(this.data)
107-
} else {
108-
this.data = this.redefineEntity(this.data)
109-
}
110-
return this
76+
return this.remap()
11177
}
112-
113-
refine(): object {
114-
return this.redefine().get()
78+
get(): this {
79+
return this
11580
}
116-
117-
refinePaginate(page: number = 1, limit: number = 10): object {
118-
return this.redefine().paginate(page, limit)
81+
refine(): this {
82+
return this.remap()
11983
}
120-
121-
paginate(page: number = 1, limit: number = 10) {
122-
const lastPage = Math.max(Math.ceil(this.data.data.length / limit), 1)
123-
interface PaginationMeta {
124-
total: number
125-
perPage: number
126-
currentPage: number
127-
lastPage: number
128-
firstPage: number
129-
firstPageUrl: string
130-
lastPageUrl: string
131-
nextPageUrl: string | null
132-
previousPageUrl: string | null
133-
}
134-
const meta: PaginationMeta = {
135-
total: this.data.length,
136-
perPage: Math.floor(limit),
137-
currentPage: Math.floor(page),
138-
lastPage: lastPage,
139-
firstPage: 1,
140-
firstPageUrl: '/?page=1',
141-
lastPageUrl: '/?page=' + lastPage,
142-
nextPageUrl: page < lastPage ? `/?page=${page + 1}` : null,
143-
previousPageUrl: page > 1 ? `/?page=${page - 1}` : null,
144-
}
145-
const collection = (this.data.rows || this.data.data).slice((page - 1) * limit, page * limit)
146-
this.data = {
147-
meta: meta,
148-
data: collection,
149-
}
150-
return this.data
84+
refinePaginate(page: number = 1, limit: number = 10): this {
85+
return this.remap().paginate(page, limit)
15186
}
87+
}
15288

153-
get() {
154-
return this.data
155-
}
89+
;(Resource.prototype as any).toJSON = function () {
90+
return (this as any).data
15691
}

0 commit comments

Comments
 (0)