Skip to content

Commit

Permalink
feat: add additionalFields option to populate computed fields
Browse files Browse the repository at this point in the history
  • Loading branch information
nicgirault committed Aug 19, 2023
1 parent f4e9b0a commit 8d759be
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 4 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ crud('/admin/posts', actions, {
})
```

### Additional attributes

Additional attributes can be populated in the read views. For example one can add a count of related records like this:

```ts
crud('/admin/categories', actions, {
additionalAttributes: async category => {
return {
postsCount: await Post.count({ categoryId: category.id })
}
},
additionalAttributesConcurrency: 10 // 10 queries Post.count will be perform at the same time
})
```

### Custom behavior & other ORMs

```ts
Expand Down
23 changes: 20 additions & 3 deletions src/getList/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RequestHandler, Request, Response } from 'express'
import pLimit from 'p-limit';

import { setGetListHeaders } from './headers'

Expand All @@ -12,15 +13,17 @@ export type Get<R> = (conf: {
res: Response
}) => Promise<{ rows: R[]; count: number }>

export interface GetListOptions {
export interface GetListOptions<R> {
filters: FiltersOption
additionalAttributes: (record: R) => object | Promise<object>
additionalAttributesConcurrency: number
}

type FiltersOption = Record<string, (value: any) => any>

export const getMany = <R>(
doGetFilteredList: Get<R>,
options?: Partial<GetListOptions>
options?: Partial<GetListOptions<R>>
): RequestHandler => async (req, res, next) => {
try {
const { limit, offset, filter, order } = await parseQuery(
Expand All @@ -35,7 +38,11 @@ export const getMany = <R>(
order,
}, { req, res })
setGetListHeaders(res, offset, count, rows.length)
res.json(rows)
res.json(
options?.additionalAttributes
? await computeAdditionalAttributes(options.additionalAttributes, options.additionalAttributesConcurrency ?? 1)(rows)
: rows
)

} catch (error) {
next(error)
Expand Down Expand Up @@ -74,3 +81,13 @@ const getFilter = async (

return result
}


const computeAdditionalAttributes =
<R>(additionalAttributes: GetListOptions<R>["additionalAttributes"], concurrency: number) => {
const limit = pLimit(concurrency)

return (records: R[]) => Promise.all(records.map(record =>
limit(async () => ({ ...record, ...await additionalAttributes(record) }))
))
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export { Create, Destroy, Update, Get }
export const crud = <I extends string | number, R>(
path: string,
actions: Partial<Actions<I, R>>,
options?: Partial<GetListOptions>
options?: Partial<GetListOptions<R>>
) => {
const router = Router()
router.use(bodyParser.json())
Expand Down
21 changes: 21 additions & 0 deletions tests/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,27 @@ describe('crud', () => {
order: [['name', 'DESC']],
}, expectReqRes)
})


it('populates additional fields when provided', async () => {
const dataProvider = await setupApp(
crud<number, { id: number }>('/users', {
get: jest.fn().mockResolvedValue({ rows: [{ id: 1 }], count: 1 }),
}, {
additionalAttributes: async (record) => {
return { additionalProperty: await new Promise(resolve => resolve(`value ${record.id}`)) }
}
}),
ctx
)

const response = await dataProvider.getList('users', {
pagination: { page: 0, perPage: 25 },
sort: { field: 'id', order: 'DESC' },
filter: {},
})
expect(response.data[0]).toEqual({ id: 1, additionalProperty: 'value 1' })
})
})

describe('DELETE', () => {
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5304,6 +5304,13 @@ p-is-promise@^3.0.0:
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-3.0.0.tgz#58e78c7dfe2e163cf2a04ff869e7c1dba64a5971"
integrity sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==

p-limit@3:
version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
dependencies:
yocto-queue "^0.1.0"

p-limit@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
Expand Down Expand Up @@ -6925,3 +6932,8 @@ yargs@^16.2.0:
string-width "^4.2.0"
y18n "^5.0.5"
yargs-parser "^20.2.2"

yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==

0 comments on commit 8d759be

Please sign in to comment.