Skip to content
This repository has been archived by the owner on Apr 19, 2023. It is now read-only.

Commit

Permalink
✨ Add support for cursor
Browse files Browse the repository at this point in the history
  • Loading branch information
AnandChowdhary committed Oct 22, 2020
1 parent 3e840dd commit 6b732e2
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 6 deletions.
76 changes: 76 additions & 0 deletions src/helpers/parse-object-literal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Parse a string like "a: 1, b: 2" to { a: 1, b: 2 }
* @param objectLiteralString - String to parse
* @source https://github.com/mbest/js-object-literal-parse
*/
export const parseObjectLiteral = (
objectLiteralString: string,
): [string, string | undefined][] => {
const stringDouble = '"(?:[^"\\\\]|\\\\.)*"';
const stringSingle = "'(?:[^'\\\\]|\\\\.)*'";
const stringRegexp = '/(?:[^/\\\\]|\\\\.)*/w*';
const specials = ',"\'{}()/:[\\]';
const everyThingElse = '[^\\s:,/][^' + specials + ']*[^\\s' + specials + ']';
const oneNotSpace = '[^\\s]';
const token = RegExp(
stringDouble +
'|' +
stringSingle +
'|' +
stringRegexp +
'|' +
everyThingElse +
'|' +
oneNotSpace,
'g',
);
const divisionLookBehind = /[\])"'A-Za-z0-9_$]+$/;
const keywordRegexLookBehind = { in: 1, return: 1, typeof: 1 };
let str = objectLiteralString.trim();
if (str.charCodeAt(0) === 123) str = str.slice(1, -1);
const result: [string, string | undefined][] = [];
let toks = str.match(token);
let key: string | undefined = undefined;
let values = [];
let depth = 0;
if (toks) {
toks.push(',');
for (let i = 0, tok: string; (tok = toks[i]); ++i) {
const c = tok.charCodeAt(0);
if (c === 44) {
if (depth <= 0) {
if (!key && values.length === 1) {
key = values.pop();
}
result.push([key, values.length ? values.join('') : undefined]);
key = undefined;
values = [];
depth = 0;
continue;
}
} else if (c === 58) {
if (!depth && !key && values.length === 1) {
key = values.pop();
continue;
}
} else if (c === 47 && i && tok.length > 1) {
const match = toks[i - 1].match(divisionLookBehind);
if (match && !keywordRegexLookBehind[match[0]]) {
str = str.substr(str.indexOf(tok) + 1);
toks = str.match(token);
toks.push(',');
i = -1;
tok = '/';
}
} else if (c === 40 || c === 123 || c === 91) {
++depth;
} else if (c === 41 || c === 125 || c === 93) {
--depth;
} else if (!key && !values.length && (c === 34 || c === 39)) {
tok = tok.slice(1, -1);
}
values.push(tok);
}
}
return result;
};
2 changes: 2 additions & 0 deletions src/modules/user/user.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common';
import { users } from '@prisma/client';
import { OmitSecrets } from 'src/modules/prisma/prisma.interface';
import { CursorPipe } from 'src/pipes/cursor.pipe';
import { OptionalIntPipe } from 'src/pipes/optional-int.pipe';
import { OrderByPipe } from 'src/pipes/order-by.pipe';
import { UsersService } from './user.service';
Expand All @@ -13,6 +14,7 @@ export class UserController {
async getAll(
@Query('skip', OptionalIntPipe) skip?: number,
@Query('take', OptionalIntPipe) take?: number,
@Query('cursor', CursorPipe) cursor?: Record<string, number | string>,
@Query('orderBy', OrderByPipe) orderBy?: Record<string, 'asc' | 'desc'>,
): Promise<OmitSecrets<users>[]> {
return this.usersService.users({ skip, take, orderBy });
Expand Down
14 changes: 8 additions & 6 deletions src/pipes/cursor.pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {
HttpStatus,
ArgumentMetadata,
} from '@nestjs/common';
import { parseObjectLiteral } from 'src/helpers/parse-object-literal';

/** Convert a string like "id 12, name Anand" to { id: 12, name: "Anand" } */
/** Convert a string like "id: 12, b: 'Anand'" to { id: 12, name: "Anand" } */
@Injectable()
export class CursorPipe implements PipeTransform {
transform(
Expand All @@ -15,13 +16,14 @@ export class CursorPipe implements PipeTransform {
): Record<string, number | string> | undefined {
if (value == null) return undefined;
try {
const rules = value.split(' ').map(val => val.trim());
const cursor: Record<string, number | string> = {};
const rules = parseObjectLiteral(value);
const items: Record<string, number | string> = {};
rules.forEach(rule => {
const [key, val] = rule.split(' ', 1);
rules[key] = val;
const num = Number(rule[1]);
if (!isNaN(num)) items[rule[0]] = num;
else items[rule[0]] = rule[1];
});
return cursor;
return items;
} catch (_) {
throw new HttpException(
`"${metadata.data}" should be like "id 12, name Anand", provided "${value}"`,
Expand Down

0 comments on commit 6b732e2

Please sign in to comment.