Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#232 into #231 🧊 add embed function for playground #234

Open
wants to merge 2 commits into
base: #231
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ const flatMockServerConfig = [
postId: {
checkMode: 'function',
value: (actualValue) => +actualValue >= 0 && +actualValue <= 50
},
}
},
cookies: {
authToken: {
Expand Down Expand Up @@ -1029,6 +1029,14 @@ For multiple search
GET /users?_q=siberia&_q=24
```

### Embed

```
GET /posts?_embed=users
GET /posts/1?_embed=users
GET /posts/1?_embed=users&_embed=author
```

### File example

```javascript
Expand Down
9 changes: 0 additions & 9 deletions data.json

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"express-urlrewrite": "^2.0.3",
"flat": "^5.0.2",
"graphql": "^16.10.0",
"inflection": "^3.0.2",
"please-upgrade-node": "^3.2.0",
"prompts": "^2.4.2",
"yargs": "^17.7.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ const isVariableJsonFile = (variable: unknown): variable is `${string}.json` =>
typeof variable === 'string' && variable.endsWith('.json');

export const createDatabaseRoutes = (router: IRouter, { data, routes }: DatabaseConfig) => {
console.log(data, routes);

if (routes) {
const storage = createStorage(routes);
createRewrittenDatabaseRoutes(router, storage.read());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ describe('CreateNestedDatabaseRoutes', () => {
address: { city: 'Tomsk' },
hobbies: ['sport', 'games']
}
],
posts: [
{
id: 1,
title: 'John Doe Post',
users: [1, 2],
userId: 1
},
{
id: 2,
title: 'Jane Smith Post',
users: [1, 2],
userId: 2
}
]
});

Expand Down Expand Up @@ -676,4 +690,200 @@ describe('CreateNestedDatabaseRoutes', () => {
]);
});
});

describe('createNestedDatabaseRoutes: embed function', () => {
const nestedDatabase = createNestedDatabase();
const server = createServer(nestedDatabase);

it('Should embed related data for single object request', async () => {
const response = await request(server).get('/posts/1?_embed=user');

expect(response.body).toStrictEqual({
id: 1,
title: 'John Doe Post',
user: {
address: {
city: 'Novosibirsk'
},
age: 25,
hobbies: ['music', 'sport'],
id: 1,
name: 'John Doe'
},
users: [1, 2]
});
});

it('Should embed array related data for single object request', async () => {
const response = await request(server).get('/posts/1?_embed=users');

expect(response.body).toStrictEqual({
id: 1,
title: 'John Doe Post',
userId: 1,
users: [
{
id: 1,
age: 25,
name: 'John Doe',
address: { city: 'Novosibirsk' },
hobbies: ['music', 'sport']
},
{
id: 2,
age: 30,
name: 'Jane Smith',
address: { city: 'Tomsk' },
hobbies: ['sport', 'games']
}
]
});
});

it('Should embed related data for array request', async () => {
const response = await request(server).get('/posts?_embed=user');

expect(response.body).toStrictEqual([
{
id: 1,
title: 'John Doe Post',
user: {
id: 1,
age: 25,
name: 'John Doe',
address: { city: 'Novosibirsk' },
hobbies: ['music', 'sport']
},
users: [1, 2]
},
{
id: 2,
title: 'Jane Smith Post',
user: {
id: 2,
age: 30,
name: 'Jane Smith',
address: { city: 'Tomsk' },
hobbies: ['sport', 'games']
},
users: [1, 2]
}
]);
});

it('Should embed array related data for array request', async () => {
const response = await request(server).get('/posts?_embed=users');

expect(response.body).toStrictEqual([
{
id: 1,
title: 'John Doe Post',
userId: 1,
users: [
{
id: 1,
age: 25,
name: 'John Doe',
address: { city: 'Novosibirsk' },
hobbies: ['music', 'sport']
},
{
id: 2,
age: 30,
name: 'Jane Smith',
address: { city: 'Tomsk' },
hobbies: ['sport', 'games']
}
]
},
{
id: 2,
title: 'Jane Smith Post',
userId: 2,
users: [
{
id: 1,
age: 25,
name: 'John Doe',
address: { city: 'Novosibirsk' },
hobbies: ['music', 'sport']
},
{
id: 2,
age: 30,
name: 'Jane Smith',
address: { city: 'Tomsk' },
hobbies: ['sport', 'games']
}
]
}
]);
});

it('Should embed related data by multiple query', async () => {
const response = await request(server).get('/posts?_embed=user&_embed=users');

expect(response.body).toStrictEqual([
{
id: 1,
title: 'John Doe Post',
user: {
id: 1,
age: 25,
name: 'John Doe',
address: { city: 'Novosibirsk' },
hobbies: ['music', 'sport']
},
users: [
{
id: 1,
age: 25,
name: 'John Doe',
address: { city: 'Novosibirsk' },
hobbies: ['music', 'sport']
},
{
id: 2,
age: 30,
name: 'Jane Smith',
address: { city: 'Tomsk' },
hobbies: ['sport', 'games']
}
]
},
{
id: 2,
title: 'Jane Smith Post',
user: {
id: 2,
age: 30,
name: 'Jane Smith',
address: { city: 'Tomsk' },
hobbies: ['sport', 'games']
},
users: [
{
id: 1,
age: 25,
name: 'John Doe',
address: { city: 'Novosibirsk' },
hobbies: ['music', 'sport']
},
{
id: 2,
age: 30,
name: 'Jane Smith',
address: { city: 'Tomsk' },
hobbies: ['sport', 'games']
}
]
}
]);
});

it('Should return unchanged data when embed query is not passed', async () => {
const response = await request(server).get('/posts?_embed=author');
expect(response.body).toStrictEqual(nestedDatabase.posts);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import type { NestedDatabase } from '@/utils/types';
import type { MemoryStorage } from '../../storages';

import { createNewId, findIndexById } from '../array';
import { filter } from '../filter/filter';
import { pagination } from '../pagination/pagination';
import { search } from '../search/search';
import { sort } from '../sort/sort';
import { embed, filter, pagination, search, sort } from '../functions';

export const createNestedDatabaseRoutes = (
router: IRouter,
Expand All @@ -31,7 +28,7 @@ export const createNestedDatabaseRoutes = (
return response.json(data);
}

const { _page, _limit, _begin, _end, _sort, _order, _q, ...filters } = request.query;
const { _embed, _page, _limit, _begin, _end, _sort, _order, _q, ...filters } = request.query;

if (Object.keys(filters).length) {
data = filter(data, filters as ParsedUrlQuery);
Expand All @@ -50,6 +47,10 @@ export const createNestedDatabaseRoutes = (
response.set('X-Total-Count', data.length);
}

if (_embed) {
data = embed(database, data, _embed as ParsedUrlQuery);
}

// ✅ important:
// The pagination should be last because it changes the form of the response
if (_page) {
Expand Down Expand Up @@ -95,15 +96,24 @@ export const createNestedDatabaseRoutes = (
router.route(itemPath).get((request, response, next) => {
const currentResourceCollection = storage.read(key);
const currentResourceIndex = findIndexById(currentResourceCollection, request.params.id);

if (currentResourceIndex === -1) {
return next();
}

let data = storage.read([key, currentResourceIndex]);

const { _embed } = request.query;

if (_embed) {
data = embed(database, data, _embed as ParsedUrlQuery);
}

// ✅ important:
// set 'Cache-Control' header for explicit browsers response revalidate
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
response.set('Cache-control', 'no-cache');
response.json(storage.read([key, currentResourceIndex]));
response.json(data);
});

router.route(itemPath).put((request, response, next) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import type { ShallowDatabase } from '@/utils/types';

import type { MemoryStorage } from '../../storages';

import { filter } from '../filter/filter';
import { pagination } from '../pagination/pagination';
import { search } from '../search/search';
import { sort } from '../sort/sort';
import { filter, pagination, search, sort } from '../functions';

export const createShallowDatabaseRoutes = (
router: IRouter,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { ParsedUrlQuery } from 'node:querystring';

import { pluralize, singularize } from 'inflection';

export const embedItem = (database: Record<string, any[]>, item: any, related: string) => {
const isSingular = singularize(related) === related;
if (isSingular) {
const relatedData = database[pluralize(related)];
if (!relatedData) return item;

const foreignKey = `${related}Id`;
const relatedItem = relatedData.find((relatedItem) => relatedItem.id === item[foreignKey]);
return { ...item, [related]: relatedItem, [foreignKey]: undefined };
}

const relatedData = database[related];
if (!relatedData || !item[related]) return item;

const relatedItems = item[related].map((relatedItemId: string) =>
relatedData.find((relatedItem) => relatedItem.id === relatedItemId)
);

return { ...item, [related]: relatedItems };
};

export const embed = (database: Record<string, any[]>, item: any, related: ParsedUrlQuery) => {
if (Array.isArray(item) && typeof related === 'string') {
return item.map((item) => embedItem(database, item, related));
}

if (Array.isArray(item) && Array.isArray(related)) {
return item.map((item) => {
related.forEach((related) => {
item = embedItem(database, item, related);
});
return item;
});
}

if (typeof related === 'string') {
return embedItem(database, item, related);
}

if (Array.isArray(related)) {
related.forEach((related) => {
item = embedItem(database, item, related);
});
return item;
}

throw new Error('embed technical error');
};
Loading