New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@trapi/query

Package Overview
Dependencies
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@trapi/query - npm Package Compare versions

Comparing version 0.0.2 to 1.0.0

dist/build.d.ts

10

dist/index.d.ts

@@ -1,2 +0,8 @@

export * from './request';
export * from './utils';
export * from './build';
export * from './fields';
export * from './filters';
export * from './includes';
export * from './pagination';
export * from './sort';
export * from './type';
//# sourceMappingURL=index.d.ts.map

9

dist/index.js

@@ -19,4 +19,9 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./request"), exports);
__exportStar(require("./utils"), exports);
__exportStar(require("./build"), exports);
__exportStar(require("./fields"), exports);
__exportStar(require("./filters"), exports);
__exportStar(require("./includes"), exports);
__exportStar(require("./pagination"), exports);
__exportStar(require("./sort"), exports);
__exportStar(require("./type"), exports);
//# sourceMappingURL=index.js.map

@@ -0,1 +1,7 @@

export * from './field';
export * from './flatten';
export * from './include';
export * from './object';
export * from './type';
export * from './url';
//# sourceMappingURL=index.d.ts.map

@@ -19,3 +19,8 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./field"), exports);
__exportStar(require("./flatten"), exports);
__exportStar(require("./include"), exports);
__exportStar(require("./object"), exports);
__exportStar(require("./type"), exports);
__exportStar(require("./url"), exports);
//# sourceMappingURL=index.js.map
export declare function hasOwnProperty<X extends {}, Y extends PropertyKey>(obj: X, prop: Y): obj is X & Record<Y, unknown>;
/**
* Build alias mapping from strings in array or object representation to object representation.
*
* {field1: 'field1', ...} => {field1: 'field1', ...}
* ['field1', 'field2'] => {field1: 'field1', field2: 'field2'}
*
* @param rawFields
*/
export declare function buildObjectFromStringArray(rawFields: string[] | Record<string, string>): Record<string, string>;
//# sourceMappingURL=object.d.ts.map

@@ -9,3 +9,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.hasOwnProperty = void 0;
exports.buildObjectFromStringArray = exports.hasOwnProperty = void 0;
function hasOwnProperty(obj, prop) {

@@ -15,2 +15,23 @@ return obj.hasOwnProperty(prop);

exports.hasOwnProperty = hasOwnProperty;
/**
* Build alias mapping from strings in array or object representation to object representation.
*
* {field1: 'field1', ...} => {field1: 'field1', ...}
* ['field1', 'field2'] => {field1: 'field1', field2: 'field2'}
*
* @param rawFields
*/
function buildObjectFromStringArray(rawFields) {
if (Array.isArray(rawFields)) {
const record = {};
rawFields
.filter(field => typeof field === 'string')
.map(field => {
record[field] = field;
});
return record;
}
return rawFields;
}
exports.buildObjectFromStringArray = buildObjectFromStringArray;
//# sourceMappingURL=object.js.map
{
"name": "@trapi/query",
"version": "0.0.2",
"version": "1.0.0",
"description": "An tiny library which provides utility types/functions for request and response query handling.",

@@ -8,3 +8,3 @@ "main": "./dist/index.js",

"scripts": {
"build": "rm -rf ./dist && tsc",
"build": "rm -rf ./dist && tsc -p tsconfig.build.json",
"test": "cross-env NODE_ENV=test jest --config ./test/jest.config.js",

@@ -53,3 +53,3 @@ "test:coverage": "cross-env NODE_ENV=test jest --config ./test/jest.config.js --coverage"

},
"gitHead": "f931516f93b31fa0fc3c04926713a6cc91680f39"
"gitHead": "4355a7f0d19c3df26929981011a621e7f70590e6"
}

@@ -1,2 +0,2 @@

# @trapi/query 🏗
# @trapi/query 🌈

@@ -9,3 +9,4 @@ [![main](https://github.com/Tada5hi/typescript-rest-api/actions/workflows/main.yml/badge.svg)](https://github.com/Tada5hi/typescript-rest-api/actions/workflows/main.yml)

This is a library for building `JSON:API` like REST-APIs.
It extends the specification format between request- & response-handling for querying and fetching data according the following parameters:
It extends the specification format between request- & response-handling for querying and fetching data according the following query parameters:
- `filter`: Filter the data set, according specific criteria.

@@ -19,3 +20,18 @@ - `include` Include related resources of the primary data.

- [Installation](#installation)
- [Usage](#usage)
- [Build](#build-)
- [Parsing](#parsing-)
- Types
- Options
- [FieldsOptions](#fieldsoptions)
- [FiltersOptions](#filtersoptions)
- [IncludeOptions](#includesoptions)
- [PaginationOptions](#paginationoptions)
- [SortOptions](#sortoptions)
- Transformed
- [FieldsTransformed](#fieldstransformed)
- [FiltersTransformed](#filterstransformed)
- [IncludeTransformed](#includestransformed)
- [PaginationTransformed](#paginationtransformed)
- [SortTransformed](#sorttransformed)
## Installation

@@ -28,1 +44,411 @@

## Usage
### Build 🏗
The general idea is to construct a `QueryRecord` at the frontend side, which will be formatted to a string and passed to the backend application as URL query string.
The backend application is than always fully capable of processing the request, as far the provided query was not malformed.
Therefore, two components of this module are required in the frontend application:
- generic type: `QueryRecord<T>`
- function: `buildQuery`.
The method will generate the query string, which was addressed in the previous section.
In the following example a Class which will represent the structure of a `User`.
The `getAPIUsers` will handle the resource request to the resource API.
```typescript
import axios from "axios";
import {QueryRecord, buildQuery} from "@trapi/query";
class Profile {
id: number;
avatar: string;
cover: string;
}
class User {
id: number;
name: string;
age?: number;
profile: Profile;
}
type ResponsePayload = {
data: User[],
meta: {
limit: number,
offset: number,
total: number
}
}
export async function getAPIUsers(record: QueryRecord<User>): Promise<ResponsePayload> {
const response = await axios.get('users' + buildQuery(record));
return response.data;
}
(async () => {
const record: QueryRecord<User> = {
page: {
limit: 20,
offset: 10
},
filter: {
id: 1 // some possible values:
// 1 | [1,2,3] | '!1' | '~1' | ['!1',2,3] | {profile: {avatar: 'xxx.jpg'}}
},
fields: ['id', 'name'], // some possible values:
// 'id' | ['id', 'name'] | '+id' | {user: ['id', 'name'], profile: ['avatar']}
sort: '-id', // some possible values:
// 'id' | ['id', 'name'] | '-id' | {id: 'DESC', profile: {avatar: 'ASC'}}
include: {
profile: true
}
};
// console.log(tranformQueryRecord);
// ?filter[id]=1&fields=id,name&page[limit]=20&page[offset]=10&sort=-id&include=profile
let response = await getAPIUsers(record);
// do somethin with the response :)
})();
```
The next examples will be about how to parse and validate the transformed `QueryRecord<T>` on the backend side.
### Parsing 🔎
For explanation proposes how to use the query utils,
two simple entities with a simple relation between them are declared to demonstrate their usage:
```typescript
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn
} from "typeorm";
@Entity()
export class User {
@PrimaryGeneratedColumn({unsigned: true})
id: number;
@Column({type: 'varchar', length: 30})
@Index({unique: true})
name: string;
@Column({type: 'varchar', length: 255, default: null, nullable: true})
email: string;
@OneToOne(() => Profile)
profile: Profile;
}
@Entity()
export class Profile {
@PrimaryGeneratedColumn({unsigned: true})
id: number;
@Column({type: 'varchar', length: 255, default: null, nullable: true})
avatar: string;
@Column({type: 'varchar', length: 255, default: null, nullable: true})
cover: string;
@OneToOne(() => User)
@JoinColumn()
user: User;
}
```
In this example `typeorm` is used as the object-relational mapping (ORM) and `typeorm-extension` is used
to apply the transformed request query parameters on the db query.
When you use express or another library, you can use the API utils accordingly to the
following code snippet:
#### Parsing - Extended
```typescript
import {getRepository} from "typeorm";
import {Request, Response} from 'express';
import {
parseFields,
parseFilters,
parseIncludes,
parsePagination,
parseSort
} from "typeorm-extension";
import {
applyFieldsTransformed,
applyFiltersTransformed,
applyIncludesTransformed,
applyPaginationTransformed,
applySortTransformed
} from "typeorm-extension";
/**
* Get many users.
*
* Request example
* - url: /users?page[limit]=10&page[offset]=0&include=profile&filter[id]=1&fields[user]=id,name
*
* Return Example:
* {
* data: [
* {id: 1, name: 'tada5hi', profile: {avatar: 'avatar.jpg', cover: 'cover.jpg'}}
* ],
* 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 repository = getRepository(User);
const query = repository.createQueryBuilder('user');
// -----------------------------------------------------
const includes = applyIncludesTransformed(query, parseIncludes(include));
applySortTransformed(query, parseSort(sort, {
queryAlias: 'user',
allowed: ['id', 'name', 'profile.id'],
// profile.id can only be used as sorting key, if the relation 'profile' is included.
includes: includes
}));
applyFieldsTransformed(query, parseFields(fields, {
queryAlias: 'user',
allowed: ['id', 'name', 'profile.id', 'profile.avatar'],
// porfile fields can only be included, if the relation 'profile' is included.
includes: includes
}));
// only allow filtering users by id & name
applyFiltersTransformed(query, parseFilters(filter, {
queryAlias: 'user',
allowed: ['id', 'name', 'profile.id'],
// porfile.id can only be used as a filter, if the relation 'profile' is included.
includes: includes
}));
// only allow to select 20 items at maximum.
const pagination = applyPaginationTransformed(query, parsePagination(page, {maxLimit: 20}));
// -----------------------------------------------------
const [entities, total] = await query.getManyAndCount();
return res.json({
data: {
data: entities,
meta: {
total,
...pagination
}
}
});
}
```
This can even be much easier, because `typeorm-extension` uses `@trapi/query` under the hood ⚡.
#### Transform - Simple
This is much shorter than the previous example and has less direct dependencies 😁.
```typescript
import {getRepository} from "typeorm";
import {Request, Response} from 'express';
import {
applyFields,
applyFilters,
applyIncludes,
applyPagination,
applySort
} from "typeorm-extension";
/**
* Get many users.
*
* Request example
* - url: /users?page[limit]=10&page[offset]=0&include=profile&filter[id]=1&fields[user]=id,name
*
* Return Example:
* {
* data: [
* {id: 1, name: 'tada5hi', profile: {avatar: 'avatar.jpg', cover: 'cover.jpg'}}
* ],
* 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 repository = getRepository(User);
const query = repository.createQueryBuilder('user');
// -----------------------------------------------------
const includes = applyIncludes(query, include, {
queryAlias: 'user',
allowed: ['profile']
});
applySort(query, sort, {
queryAlias: 'user',
allowed: ['id', 'name', 'profile.id'],
// profile.id can only be used as sorting key, if the relation 'profile' is included.
includes: includes
});
applyFields(query, fields, {
queryAlias: 'user',
allowed: ['id', 'name', 'profile.id', 'profile.avatar'],
// porfile fields can only be included, if the relation 'profile' is included.
includes: includes
})
// only allow filtering users by id & name
applyFilters(query, filter, {
queryAlias: 'user',
allowed: ['id', 'name', 'profile.id'],
// porfile.id can only be used as a filter, if the relation 'profile' is included.
includes: includes
});
// only allow to select 20 items at maximum.
const pagination = applyPagination(query, page, {maxLimit: 20});
// -----------------------------------------------------
const [entities, total] = await query.getManyAndCount();
return res.json({
data: {
data: entities,
meta: {
total,
...pagination
}
}
});
}
```
### Options
#### FieldsOptions
```typescript
type FieldsOptions = {
aliasMapping?: Record<string, string>,
allowed?: Record<string, string[]> | string[],
includes?: IncludesTransformed,
queryAlias?: string
};
```
#### FiltersOptions
```typescript
export type FiltersOptions = {
aliasMapping?: Record<string, string>,
allowed?: string[],
includes?: IncludesTransformed,
queryAlias?: string,
queryBindingKeyFn?: (key: string) => string
};
```
#### IncludesOptions
```typescript
type IncludesOptions = {
aliasMapping?: Record<string, string>,
allowed?: string[],
includeParents?: boolean | string[] | string
queryAlias?: string,
};
```
#### PaginationOptions
```typescript
type PaginationOptions = {
maxLimit?: number
};
```
#### SortOptions
```typescript
type SortOptions = {
aliasMapping?: Record<string, string>,
allowed?: string[] | string[][],
includes?: IncludesTransformed,
queryAlias?: string
};
```
### Transformed
#### FieldsTransformed
```typescript
export type AliasFields = {
addFields?: boolean,
alias?: string,
fields: string[]
};
export type FieldsTransformed = AliasFields[];
```
#### FiltersTransformed
```typescript
export type FilterTransformed = {
statement: string,
binding: Record<string, any>
};
export type FiltersTransformed = FilterTransformed[];
```
#### IncludesTransformed
```typescript
export type IncludeTransformed = {
property: string,
alias: string
};
export type IncludesTransformed = IncludeTransformed[];
```
#### PaginationTransformed
```typescript
export type PaginationTransformed = {
limit?: number,
offset?: number
};
```
#### SortTransformed
```typescript
export type SortDirection = 'ASC' | 'DESC';
export type SortTransformed = Record<string, SortDirection>;
```

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc