Skip to content

Commit

Permalink
fix: cleanup types + support more filter input values
Browse files Browse the repository at this point in the history
  • Loading branch information
tada5hi committed Oct 22, 2022
1 parent a7172b0 commit 1c0e59f
Show file tree
Hide file tree
Showing 18 changed files with 214 additions and 213 deletions.
3 changes: 2 additions & 1 deletion README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ import {
/**
* Get many users.
*
* ...
* Request example
* - url: /users?page[limit]=10&page[offset]=0&include=realm&filter[id]=1&fields=id,name
*
* @param req
* @param res
Expand Down
9 changes: 4 additions & 5 deletions docs/guide/fields-api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,11 @@ const input : FieldsBuildInput<User> = [
## `FieldsParseOptions`

```typescript
type FieldsParseOptions<
T extends Record<string, any> = Record<string, any>,
> = {
type FieldsParseOptions<T extends Record<string, any> = Record<string, any>,
> = {
mapping?: Record<string, string>,
allowed?: ParseOptionsAllowed<T>,
default?: ParseOptionsAllowed<T>,
allowed?: ParseAllowedOption<T>,
default?: ParseAllowedOption<T>,
defaultPath?: string,
relations?: RelationsParseOutput
};
Expand Down
6 changes: 3 additions & 3 deletions docs/guide/filters-api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ const input : FiltersBuildInput<User> = {
```typescript
type FiltersParseOptionsDefault<T extends Record<string, any>> = {
[K in keyof T]?: Flatten<T[K]> extends OnlyObject<T[K]> ?
FiltersParseOptionsDefault<Flatten<T[K]>> :
FiltersParseDefaultOption<Flatten<T[K]>> :
(K extends string ? FilterValue<TypeFromNestedKeyPath<T, K>> : never)
} | {
[K in NestedKeys<T>]?: FilterValue<TypeFromNestedKeyPath<T, K>>
Expand All @@ -159,8 +159,8 @@ type FiltersParseOptions<
T extends Record<string, any> = Record<string, any>,
> = {
mapping?: Record<string, string>,
allowed?: ParseOptionsAllowed<T>,
default?: FiltersParseOptionsDefault<T>,
allowed?: ParseAllowedOption<T>,
default?: FiltersParseDefaultOption<T>,
defaultByElement?: boolean,
defaultPath?: string,
relations?: RelationsParseOutput
Expand Down
160 changes: 61 additions & 99 deletions docs/guide/parse.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,98 +81,65 @@ export class Item {
}
```

## Extended Example
## Separate

In the following code snippet, all query parameter parse functions (`parseQueryFields`, `parseQueryFilters`, ...)
will be imported separately, to show the usage in detail.
The following variant will `parse` and `apply` the query parameters in **two** steps.

After the [ParseOutput](parse-api-reference.md#parseoutput) is generated using the [parseQuery](parse-api-reference.md#parsequery) function,
the output can be applied on the typeorm QueryBuilder using the `applyQueryParseOutput` function of
the [typeorm-extension](https://www.npmjs.com/package/typeorm-extension).

```typescript
import {Request, Response} from 'express';
import { Request, Response } from 'express';

import {
parseQueryFields,
parseQueryFilters,
parseQueryRelations,
parseQueryPagination,
parseQuerySort,
} from "rapiq";
parseQuery,
ParseOutput
} from 'rapiq';

import {
applyQueryParseOutput,
useDataSource
} from 'typeorm-extension';

import { User } from './entities';

/**
* Get many users.
*
* Request example
* - url: /users?page[limit]=10&page[offset]=0&include=realm&filter[id]=1&fields=id,name
*
* Return Example:
* {
* data: [
* {
* id: 1,
* name: 'tada5hi',
* realm: {
* id: 'xxx',
* name: 'xxx'
* }
* }
* ],
* meta: {
* total: 1,
* limit: 20,
* offset: 0
* }
* }
* @param req
* @param res
*/
export async function getUsers(req: Request, res: Response) {
const {fields, filter, include, page, sort} = req.query;

const dataSource = await useDataSource();
const repository = dataSource.getRepository(User);
const query = repository.createQueryBuilder('user');

// -----------------------------------------------------

const parsed = applyQueryParseOutput(query, {
const output: ParseOutput = parseQuery<User>(req.query, {
defaultPath: 'user',
fields: parseQueryFields<User>(fields, {
defaultPath: 'user',
// The fields id & name of the realm entity can only be used,
// if the relation 'realm' is included.
fields: {
allowed: ['id', 'name', 'realm.id', 'realm.name'],
relations: relationsParsed
}),
// only allow filtering users by id & name
filters: parseQueryFilters<User>(filter, {
defaultPath: 'user',
// realm.id can only be used as filter key,
// if the relation 'realm' is included.
},
filters: {
allowed: ['id', 'name', 'realm.id'],
relations: relationsParsed
}),
relations: parseQueryRelations<User>(include, {
},
relations: {
allowed: ['items', 'realm']
}),
// only allow to select 20 items at maximum.
pagination: parseQueryPagination(page, {
},
pagination: {
maxLimit: 20
}),
sort: parseQuerySort<User>(sort, {
defaultPath: 'user',
// profile.id can only be used as sorting key,
// if the relation 'realm' is included.
},
sort: {
allowed: ['id', 'name', 'realm.id'],
relations: relationsParsed
})
}
});

const dataSource = await useDataSource();
const repository = dataSource.getRepository(User);
const query = repository.createQueryBuilder('user');

// -----------------------------------------------------

// apply parsed data on the db query.
applyQueryParseOutput(query, output);

// -----------------------------------------------------

const [entities, total] = await query.getManyAndCount();
Expand All @@ -182,70 +149,73 @@ export async function getUsers(req: Request, res: Response) {
data: entities,
meta: {
total,
...parsed.pagination
...output.pagination
}
}
});
}
```

## Simple Example
## Compact

Another way is to directly import the [parseQuery](parse-api-reference#parsequery) method, which will handle a group of query parameter values & options.
The [ParseInput](parse-api-reference#parseinput) argument of the `parseQuery` method accepts multiple (alias-) property keys.
Another way is to use the `applyQuery` function which will `parse` and `apply` the query parameters in **one** step.

```typescript
import { Request, Response } from 'express';
This is much shorter than the previous example and has less explicit dependencies ⚡.

import {
parseQuery,
ParseOutput
} from 'rapiq';
```typescript
import {Request, Response} from 'express';

import {
applyQueryParseOutput,
applyQuery,
useDataSource
} from 'typeorm-extension';

import { User } from './entities';

/**
* Get many users.
*
* ...
* Request example
* - url: /users?page[limit]=10&page[offset]=0&include=realm&filter[id]=1&fields=id,name
*
* @param req
* @param res
*/
export async function getUsers(req: Request, res: Response) {
// const {fields, filter, include, page, sort} = req.query;
const dataSource = await useDataSource();
const repository = dataSource.getRepository(User);
const query = repository.createQueryBuilder('user');

const output: ParseOutput = parseQuery<User>(req.query, {
defaultPath: 'user',
// -----------------------------------------------------

const { pagination } = applyQuery(query, req.query, {
// defaultAlais is an alias for the defaultPath option
defaultAlias: 'user',
fields: {
allowed: ['id', 'name', 'realm.id', 'realm.name'],
// The fields id & name of the realm entity can only be used,
// if the relation 'realm' is included.
allowed: ['id', 'name', 'realm.id', 'realm.name']
},
// only allow filtering users by id & name
filters: {
allowed: ['id', 'name', 'realm.id'],
// realm.id can only be used as filter key,
// if the relation 'realm' is included.
allowed: ['id', 'name', 'realm.id']
},
relations: {
allowed: ['items', 'realm']
},
// only allow to select 20 items at maximum.
pagination: {
maxLimit: 20
},
sort: {
allowed: ['id', 'name', 'realm.id'],
// profile.id can only be used as sorting key,
// if the relation 'realm' is included.
allowed: ['id', 'name', 'realm.id']
}
});

const dataSource = await useDataSource();
const repository = dataSource.getRepository(User);
const query = repository.createQueryBuilder('user');

// -----------------------------------------------------

// apply parsed data on the db query.
const parsed = applyQueryParseOutput(query, output);

// -----------------------------------------------------

const [entities, total] = await query.getManyAndCount();
Expand All @@ -255,17 +225,9 @@ export async function getUsers(req: Request, res: Response) {
data: entities,
meta: {
total,
...output.pagination
...pagination
}
}
});
}
```

## Third Party Support

It can even be much simpler to parse the query key-value pairs with the `typeorm-extension` library, because it
uses this library under the hood ⚡.
This is much shorter than the previous example and has less explicit dependencies 😁.

[read more](https://www.npmjs.com/package/typeorm-extension)
8 changes: 4 additions & 4 deletions docs/guide/sort-api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ const input : SortBuildInput<User> = [

## `SortParseOptions`
```typescript
type SortParseOptionsDefault<T extends Record<string, any>> = {
type SortPaseDefaultOption<T extends Record<string, any>> = {
[K in keyof T]?: Flatten<T[K]> extends OnlyObject<T[K]> ?
SortParseOptionsDefault<Flatten<T[K]>> :
SortPaseDefaultOption<Flatten<T[K]>> :
`${SortDirection}`
} | {
[K in NestedKeys<T>]?: `${SortDirection}`
Expand All @@ -84,9 +84,9 @@ type SortParseOptionsDefault<T extends Record<string, any>> = {
type SortParseOptions<
T extends Record<string, any> = Record<string, any>,
> = {
allowed?: ParseOptionsAllowed<T> | ParseOptionsAllowed<T>[],
allowed?: ParseAllowedOption<T>,
mapping?: Record<string, string>,
default?: SortParseOptionsDefault<T>,
default?: SortPaseDefaultOption<T>,
defaultPath?: string,
relations?: RelationsParseOutput,
};
Expand Down
6 changes: 3 additions & 3 deletions src/parameter/fields/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ObjectLiteral } from '../../type';
import {
applyMapping, buildFieldWithPath, groupArrayByKeyPath, hasOwnProperty, isFieldPathAllowedByRelations,
} from '../../utils';
import { flattenParseOptionsAllowed } from '../utils';
import { flattenParseAllowedOption } from '../utils';
import {
FieldsInputTransformed, FieldsParseOptions, FieldsParseOutput,
} from './type';
Expand Down Expand Up @@ -42,11 +42,11 @@ export function parseQueryFields<T extends ObjectLiteral = ObjectLiteral>(
options ??= {};

const defaultDomainFields = groupArrayByKeyPath(
flattenParseOptionsAllowed(options.default),
flattenParseAllowedOption(options.default),
);

const allowedDomainFields = groupArrayByKeyPath(
flattenParseOptionsAllowed(options.allowed),
flattenParseAllowedOption(options.allowed),
);

const domainFields = merge(
Expand Down
6 changes: 3 additions & 3 deletions src/parameter/fields/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '../../type';
import { RelationsParseOutput } from '../relations';
import {
ParseAllowedKeys,
ParseAllowedOption,
} from '../type';
import { FieldOperator } from './constants';

Expand Down Expand Up @@ -48,8 +48,8 @@ export type FieldsParseOptions<
T extends Record<string, any> = Record<string, any>,
> = {
mapping?: Record<string, string>,
allowed?: ParseAllowedKeys<T>,
default?: ParseAllowedKeys<T>,
allowed?: ParseAllowedOption<T>,
default?: ParseAllowedOption<T>,
defaultPath?: string,
relations?: RelationsParseOutput,
};
Expand Down
Loading

0 comments on commit 1c0e59f

Please sign in to comment.