Skip to content

Commit

Permalink
feat: add object support
Browse files Browse the repository at this point in the history
  • Loading branch information
typicode committed Jan 5, 2024
1 parent dfc99f0 commit b985487
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 54 deletions.
11 changes: 10 additions & 1 deletion README.md
Expand Up @@ -24,7 +24,10 @@ Create a `db.json` (or `db.json5`) file
"comments": [
{ "id": "1", "text": "a comment about post 1", "postId": "1" },
{ "id": "2", "text": "another comment about post 1", "postId": "1" }
]
],
"profile": {
"name": "typicode"
}
}
```

Expand Down Expand Up @@ -64,6 +67,12 @@ PATCH /posts/:id
DELETE /posts/:id
```

```
GET /profile
PUT /profile
PATCH /profile
```

## Params

### Conditions
Expand Down
13 changes: 13 additions & 0 deletions src/app.test.ts
Expand Up @@ -38,6 +38,7 @@ const db = new Low<Data>(new Memory<Data>(), {})
db.data = {
posts: [{ id: '1', title: 'foo' }],
comments: [{ id: '1', postId: '1' }],
object: { f1: 'foo' },
}
const app = createApp(db, { static: [tmpDir] })

Expand All @@ -58,6 +59,8 @@ await test('createApp', async (t) => {
const COMMENTS = '/comments'
const POST_COMMENTS = '/comments?postId=1'
const NOT_FOUND = '/not-found'
const OBJECT = '/object'
const OBJECT_1 = '/object/1'

const arr: Test[] = [
// Static
Expand All @@ -74,25 +77,35 @@ await test('createApp', async (t) => {
{ method: 'GET', url: POST_NOT_FOUND, statusCode: 404 },
{ method: 'GET', url: COMMENTS, statusCode: 200 },
{ method: 'GET', url: POST_COMMENTS, statusCode: 200 },
{ method: 'GET', url: OBJECT, statusCode: 200 },
{ method: 'GET', url: OBJECT_1, statusCode: 404 },
{ method: 'GET', url: NOT_FOUND, statusCode: 404 },

{ method: 'POST', url: POSTS, statusCode: 201 },
{ method: 'POST', url: POST_1, statusCode: 404 },
{ method: 'POST', url: POST_NOT_FOUND, statusCode: 404 },
{ method: 'POST', url: OBJECT, statusCode: 404 },
{ method: 'POST', url: OBJECT_1, statusCode: 404 },
{ method: 'POST', url: NOT_FOUND, statusCode: 404 },

{ method: 'PUT', url: POSTS, statusCode: 404 },
{ method: 'PUT', url: POST_1, statusCode: 200 },
{ method: 'PUT', url: OBJECT, statusCode: 200 },
{ method: 'PUT', url: OBJECT_1, statusCode: 404 },
{ method: 'PUT', url: POST_NOT_FOUND, statusCode: 404 },
{ method: 'PUT', url: NOT_FOUND, statusCode: 404 },

{ method: 'PATCH', url: POSTS, statusCode: 404 },
{ method: 'PATCH', url: POST_1, statusCode: 200 },
{ method: 'PATCH', url: OBJECT, statusCode: 200 },
{ method: 'PATCH', url: OBJECT_1, statusCode: 404 },
{ method: 'PATCH', url: POST_NOT_FOUND, statusCode: 404 },
{ method: 'PATCH', url: NOT_FOUND, statusCode: 404 },

{ method: 'DELETE', url: POSTS, statusCode: 404 },
{ method: 'DELETE', url: POST_1, statusCode: 200 },
{ method: 'DELETE', url: OBJECT, statusCode: 404 },
{ method: 'DELETE', url: OBJECT_1, statusCode: 404 },
{ method: 'DELETE', url: POST_NOT_FOUND, statusCode: 404 },
{ method: 'DELETE', url: NOT_FOUND, statusCode: 404 },
]
Expand Down
22 changes: 19 additions & 3 deletions src/app.ts
Expand Up @@ -66,25 +66,41 @@ export function createApp(db: Low<Data>, options: AppOptions = {}) {
next()
})

app.put('/:name', async (req, res, next) => {
const { name = '' } = req.params
if (isItem(req.body)) {
res.locals['data'] = await service.update(name, req.body)
}
next()
})

app.put('/:name/:id', async (req, res, next) => {
const { name = '', id = '' } = req.params
if (isItem(req.body)) {
res.locals['data'] = await service.update(name, id, req.body)
res.locals['data'] = await service.updateById(name, id, req.body)
}
next()
})

app.patch('/:name', async (req, res, next) => {
const { name = '' } = req.params
if (isItem(req.body)) {
res.locals['data'] = await service.patch(name, req.body)
}
next()
})

app.patch('/:name/:id', async (req, res, next) => {
const { name = '', id = '' } = req.params
if (isItem(req.body)) {
res.locals['data'] = await service.patch(name, id, req.body)
res.locals['data'] = await service.patchById(name, id, req.body)
}
next()
})

app.delete('/:name/:id', async (req, res, next) => {
const { name = '', id = '' } = req.params
res.locals['data'] = await service.destroy(name, id)
res.locals['data'] = await service.destroyById(name, id)
next()
})

Expand Down
58 changes: 46 additions & 12 deletions src/service.test.ts
Expand Up @@ -13,6 +13,8 @@ const service = new Service(db)

const POSTS = 'posts'
const COMMENTS = 'comments'
const OBJECT = 'object'

const UNKNOWN_RESOURCE = 'xxx'
const UNKNOWN_ID = 'xxx'

Expand Down Expand Up @@ -40,10 +42,15 @@ const post3 = {
const comment1 = { id: '1', title: 'a', postId: '1' }
const items = 3

const obj = {
f1: 'foo',
}

function reset() {
db.data = structuredClone({
posts: [post1, post2, post3],
comments: [comment1],
object: obj,
})
}

Expand All @@ -57,6 +64,8 @@ type Test = {

await test('findById', () => {
reset()
if (!Array.isArray(db.data?.[POSTS]))
throw new Error('posts should be an array')
assert.deepEqual(service.findById(POSTS, '1', {}), db.data?.[POSTS]?.[0])
assert.equal(service.findById(POSTS, UNKNOWN_ID, {}), undefined)
assert.deepEqual(service.findById(POSTS, '1', { _embed: ['comments'] }), {
Expand Down Expand Up @@ -236,6 +245,10 @@ await test('find', async (t) => {
name: UNKNOWN_RESOURCE,
res: undefined,
},
{
name: OBJECT,
res: obj,
},
]
for (const tc of arr) {
await t.test(`${tc.name} ${JSON.stringify(tc.params)}`, () => {
Expand All @@ -261,44 +274,65 @@ await test('create', async () => {
})

await test('update', async () => {
reset()
const obj = { f1: 'bar' }
const res = await service.update(OBJECT, obj)
assert.equal(res, obj)

assert.equal(
await service.update(UNKNOWN_RESOURCE, obj),
undefined,
'should ignore unknown resources',
)
assert.equal(
await service.update(POSTS, {}),
undefined,
'should ignore arrays',
)
})

await test('updateById', async () => {
reset()
const post = { id: 'xxx', title: 'updated post' }
const res = await service.update(POSTS, post1.id, post)
const res = await service.updateById(POSTS, post1.id, post)
assert.equal(res?.['id'], post1.id, 'id should not change')
assert.equal(res?.['title'], post.title)

assert.equal(
await service.update(UNKNOWN_RESOURCE, post1.id, post),
await service.updateById(UNKNOWN_RESOURCE, post1.id, post),
undefined,
)
assert.equal(await service.update(POSTS, UNKNOWN_ID, post), undefined)
assert.equal(await service.updateById(POSTS, UNKNOWN_ID, post), undefined)
})

await test('patch', async () => {
await test('patchById', async () => {
reset()
const post = { id: 'xxx', title: 'updated post' }
const res = await service.patch(POSTS, post1.id, post)
const res = await service.patchById(POSTS, post1.id, post)
assert.notEqual(res, undefined)
assert.equal(res?.['id'], post1.id)
assert.equal(res?.['title'], post.title)

assert.equal(await service.patch(UNKNOWN_RESOURCE, post1.id, post), undefined)
assert.equal(await service.patch(POSTS, UNKNOWN_ID, post), undefined)
assert.equal(
await service.patchById(UNKNOWN_RESOURCE, post1.id, post),
undefined,
)
assert.equal(await service.patchById(POSTS, UNKNOWN_ID, post), undefined)
})

await test('destroy', async () => {
reset()
let prevLength = db.data?.[POSTS]?.length || 0
await service.destroy(POSTS, post1.id)
let prevLength = Number(db.data?.[POSTS]?.length) || 0
await service.destroyById(POSTS, post1.id)
assert.equal(db.data?.[POSTS]?.length, prevLength - 1)
assert.deepEqual(db.data?.[COMMENTS], [{ ...comment1, postId: null }])

reset()
prevLength = db.data?.[POSTS]?.length || 0
await service.destroy(POSTS, post1.id, [COMMENTS])
await service.destroyById(POSTS, post1.id, [COMMENTS])
assert.equal(db.data[POSTS].length, prevLength - 1)
assert.equal(db.data[COMMENTS].length, 0)

assert.equal(await service.destroy(UNKNOWN_RESOURCE, post1.id), undefined)
assert.equal(await service.destroy(POSTS, UNKNOWN_ID), undefined)
assert.equal(await service.destroyById(UNKNOWN_RESOURCE, post1.id), undefined)
assert.equal(await service.destroyById(POSTS, UNKNOWN_ID), undefined)
})

0 comments on commit b985487

Please sign in to comment.