@orion-js/paginated-mongodb 3.0.16 → 3.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/lib/paginatedResolver/getModel.d.ts +5 -0
- package/lib/paginatedResolver/getModel.js +77 -0
- package/lib/paginatedResolver/hash.d.ts +1 -0
- package/lib/paginatedResolver/hash.js +8 -0
- package/lib/paginatedResolver/index.d.ts +20 -0
- package/lib/paginatedResolver/index.js +39 -0
- package/lib/paginatedResolver/params.d.ts +4 -0
- package/lib/paginatedResolver/params.js +16 -0
- package/lib/paginatedResolver/setOptions.d.ts +7 -0
- package/lib/paginatedResolver/setOptions.js +16 -0
- package/lib/tokenPaginatedResolver/getReturnModel.d.ts +5 -0
- package/lib/tokenPaginatedResolver/getReturnModel.js +19 -0
- package/lib/tokenPaginatedResolver/index.d.ts +10 -0
- package/lib/tokenPaginatedResolver/index.js +128 -0
- package/package.json +7 -5
package/LICENSE
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const models_1 = require("@orion-js/models");
|
|
7
|
+
const hash_1 = __importDefault(require("./hash"));
|
|
8
|
+
const resolvers_1 = require("@orion-js/resolvers");
|
|
9
|
+
exports.default = ({ returns, modelName }) => {
|
|
10
|
+
const getTotalCount = async function (paginated) {
|
|
11
|
+
if (typeof paginated.count === 'undefined') {
|
|
12
|
+
paginated.count = await paginated.cursor.count();
|
|
13
|
+
}
|
|
14
|
+
return paginated.count;
|
|
15
|
+
};
|
|
16
|
+
const _id = (0, resolvers_1.modelResolver)({
|
|
17
|
+
returns: 'ID',
|
|
18
|
+
async resolve(paginated, p, viewer) {
|
|
19
|
+
const { params } = paginated;
|
|
20
|
+
const num = (0, hash_1.default)({
|
|
21
|
+
modelName: modelName,
|
|
22
|
+
typename: returns.name,
|
|
23
|
+
userId: viewer.userId,
|
|
24
|
+
params: params
|
|
25
|
+
});
|
|
26
|
+
return String(Math.abs(num));
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const totalCount = (0, resolvers_1.modelResolver)({
|
|
30
|
+
returns: 'integer',
|
|
31
|
+
resolve: getTotalCount
|
|
32
|
+
});
|
|
33
|
+
const totalPages = (0, resolvers_1.modelResolver)({
|
|
34
|
+
returns: 'integer',
|
|
35
|
+
async resolve(paginated) {
|
|
36
|
+
const count = await getTotalCount(paginated);
|
|
37
|
+
if (!paginated.options.limit)
|
|
38
|
+
return 1;
|
|
39
|
+
return Math.ceil(count / paginated.options.limit);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const hasNextPage = (0, resolvers_1.modelResolver)({
|
|
43
|
+
returns: Boolean,
|
|
44
|
+
async resolve(paginated) {
|
|
45
|
+
const count = await getTotalCount(paginated);
|
|
46
|
+
const { skip, limit } = paginated.options;
|
|
47
|
+
if (!limit)
|
|
48
|
+
return false;
|
|
49
|
+
return skip + limit < count;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const hasPreviousPage = (0, resolvers_1.modelResolver)({
|
|
53
|
+
returns: Boolean,
|
|
54
|
+
async resolve(paginated) {
|
|
55
|
+
const count = await getTotalCount(paginated);
|
|
56
|
+
const { skip } = paginated.options;
|
|
57
|
+
return count && skip !== 0;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
const items = (0, resolvers_1.modelResolver)({
|
|
61
|
+
returns: [returns],
|
|
62
|
+
async resolve({ cursor }) {
|
|
63
|
+
return await cursor.toArray();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return (0, models_1.createModel)({
|
|
67
|
+
name: modelName || `Paginated${returns.name}`,
|
|
68
|
+
resolvers: {
|
|
69
|
+
_id,
|
|
70
|
+
totalCount,
|
|
71
|
+
totalPages,
|
|
72
|
+
hasNextPage,
|
|
73
|
+
hasPreviousPage,
|
|
74
|
+
items
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function (obj?: object): number;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
function default_1(obj = {}) {
|
|
4
|
+
return JSON.stringify(obj)
|
|
5
|
+
.split('')
|
|
6
|
+
.reduce((prevHash, currVal) => (prevHash << 5) - prevHash + currVal.charCodeAt(0), 0);
|
|
7
|
+
}
|
|
8
|
+
exports.default = default_1;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Collection, FindCursor } from '@orion-js/mongodb';
|
|
2
|
+
export interface PaginatedResolverOpts<T = any> {
|
|
3
|
+
returns: any;
|
|
4
|
+
collection?: Collection<T>;
|
|
5
|
+
params?: object;
|
|
6
|
+
getCursor: (params?: any, viewer?: any) => Promise<FindCursor<T>>;
|
|
7
|
+
modelName?: string;
|
|
8
|
+
}
|
|
9
|
+
export default function paginatedResolver<T = any>({ returns, collection, params, getCursor, modelName, ...otherOptions }: PaginatedResolverOpts<T> & {
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}): import("@orion-js/resolvers").Resolver<(params: any, viewer: any) => Promise<{
|
|
12
|
+
cursor: FindCursor<T>;
|
|
13
|
+
params: any;
|
|
14
|
+
viewer: any;
|
|
15
|
+
options: {
|
|
16
|
+
skip: number;
|
|
17
|
+
limit: any;
|
|
18
|
+
};
|
|
19
|
+
count: number;
|
|
20
|
+
}>, undefined>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const resolvers_1 = require("@orion-js/resolvers");
|
|
7
|
+
const getModel_1 = __importDefault(require("./getModel"));
|
|
8
|
+
const params_1 = __importDefault(require("./params"));
|
|
9
|
+
const setOptions_1 = __importDefault(require("./setOptions"));
|
|
10
|
+
const getArgs_1 = require("@orion-js/resolvers/lib/resolver/getArgs");
|
|
11
|
+
function paginatedResolver({ returns, collection, params, getCursor, modelName, ...otherOptions }) {
|
|
12
|
+
const getPaginatedCursor = async (...args) => {
|
|
13
|
+
if (getCursor) {
|
|
14
|
+
return await getCursor(...args);
|
|
15
|
+
}
|
|
16
|
+
return collection.find({});
|
|
17
|
+
};
|
|
18
|
+
return (0, resolvers_1.resolver)({
|
|
19
|
+
params: (0, params_1.default)({ params }),
|
|
20
|
+
returns: (0, getModel_1.default)({ modelName, returns }),
|
|
21
|
+
async resolve(...args) {
|
|
22
|
+
const { params, viewer } = (0, getArgs_1.getArgs)(...args);
|
|
23
|
+
const cursor = await getPaginatedCursor(...args);
|
|
24
|
+
/* counting the total number of elements of this cursor, so we make sure
|
|
25
|
+
that it is going to be computed only once */
|
|
26
|
+
const count = await cursor.count();
|
|
27
|
+
const options = (0, setOptions_1.default)(params, cursor);
|
|
28
|
+
return {
|
|
29
|
+
cursor,
|
|
30
|
+
params,
|
|
31
|
+
viewer,
|
|
32
|
+
options,
|
|
33
|
+
count
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
...otherOptions
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
exports.default = paginatedResolver;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = ({ params }) => ({
|
|
4
|
+
page: {
|
|
5
|
+
type: 'integer',
|
|
6
|
+
defaultValue: 1,
|
|
7
|
+
min: 1
|
|
8
|
+
},
|
|
9
|
+
limit: {
|
|
10
|
+
type: 'integer',
|
|
11
|
+
defaultValue: 0,
|
|
12
|
+
min: 0,
|
|
13
|
+
max: 200
|
|
14
|
+
},
|
|
15
|
+
...params
|
|
16
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
function default_1({ page, limit }, cursor) {
|
|
4
|
+
const skip = limit * (page - 1);
|
|
5
|
+
if (limit) {
|
|
6
|
+
cursor.limit(limit);
|
|
7
|
+
}
|
|
8
|
+
if (skip) {
|
|
9
|
+
cursor.skip(skip);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
skip,
|
|
13
|
+
limit
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
exports.default = default_1;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const models_1 = require("@orion-js/models");
|
|
4
|
+
const resolvers_1 = require("@orion-js/resolvers");
|
|
5
|
+
exports.default = ({ collection, modelName }) => {
|
|
6
|
+
const items = (0, resolvers_1.modelResolver)({
|
|
7
|
+
returns: [collection.model],
|
|
8
|
+
async resolve(params) {
|
|
9
|
+
const { cursor } = params;
|
|
10
|
+
return await cursor.toArray();
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
return (0, models_1.createModel)({
|
|
14
|
+
name: modelName || `tokenPaginated${collection.model.name}`,
|
|
15
|
+
resolvers: {
|
|
16
|
+
items
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default function ({ collection, params, resolve, ...otherOptions }: {
|
|
2
|
+
[x: string]: any;
|
|
3
|
+
collection: any;
|
|
4
|
+
params: any;
|
|
5
|
+
resolve: any;
|
|
6
|
+
}): import("@orion-js/resolvers").Resolver<(params: any, viewer: any) => Promise<{
|
|
7
|
+
params: any;
|
|
8
|
+
cursor: any;
|
|
9
|
+
viewer: any;
|
|
10
|
+
}>, undefined>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const resolvers_1 = require("@orion-js/resolvers");
|
|
7
|
+
const getReturnModel_1 = __importDefault(require("./getReturnModel"));
|
|
8
|
+
const getArgs_1 = require("@orion-js/resolvers/lib/resolver/getArgs");
|
|
9
|
+
function default_1({ collection, params, resolve, ...otherOptions }) {
|
|
10
|
+
/* executes the resolve function, obtaining the query that will
|
|
11
|
+
be applied to the collection */
|
|
12
|
+
const runResolve = async (...args) => {
|
|
13
|
+
if (resolve) {
|
|
14
|
+
return await resolve(...args);
|
|
15
|
+
}
|
|
16
|
+
return { query: {} };
|
|
17
|
+
};
|
|
18
|
+
/* This function does the query to the collection. The logic is based
|
|
19
|
+
in this article:
|
|
20
|
+
https://medium.com/swlh/mongodb-pagination-fast-consistent-ece2a97070f3 */
|
|
21
|
+
const getCursor = async ({ query, sort: sortingCriteria, limit, idOffset }) => {
|
|
22
|
+
if (sortingCriteria && Object.keys(sortingCriteria).length > 1)
|
|
23
|
+
throw new Error('sorting criteria supports at most one field');
|
|
24
|
+
if (!sortingCriteria || !Object.keys(sortingCriteria).length) {
|
|
25
|
+
sortingCriteria = { _id: 1 };
|
|
26
|
+
if (idOffset)
|
|
27
|
+
query = { ...query, _id: { $gt: idOffset } };
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const sortingField = Object.keys(sortingCriteria)[0];
|
|
31
|
+
sortingCriteria = { ...sortingCriteria, _id: 1 };
|
|
32
|
+
if (idOffset) {
|
|
33
|
+
const offsetDocument = await collection.findOne({ _id: idOffset });
|
|
34
|
+
const { [sortingField]: originalSortingFieldQuery, ...restOfQuery } = query;
|
|
35
|
+
/* Suppose the following documents, and pages with 2 elements on each page:
|
|
36
|
+
[
|
|
37
|
+
{_id: 1, name: 'a', v: 1},
|
|
38
|
+
{_id: 2, name: 'b', v: 2},
|
|
39
|
+
{_id: 3, name: 'c', v: 2},
|
|
40
|
+
{_id: 4, name: 'd', v: 3},
|
|
41
|
+
{_id: 5, name: 'e', v: 4},
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
If the query results are sorted by {v: 1}, then the first page will
|
|
45
|
+
contain the documents with ids 1 & 2, and the last one will be the document
|
|
46
|
+
with id 2.
|
|
47
|
+
In order to get the documents of the second page, we cannot get the documents
|
|
48
|
+
with the sorting criteria (v) greater than the last document, because we
|
|
49
|
+
would skip the document with id 3. In that case, we need to get all the
|
|
50
|
+
documents where either:
|
|
51
|
+
1.- The sorting field is the same than the last one, and the _id field is greater
|
|
52
|
+
2.- The sorting field is greater than the last one.
|
|
53
|
+
|
|
54
|
+
In this case, we can get the documents of the second page with the following query:
|
|
55
|
+
|
|
56
|
+
{
|
|
57
|
+
$or: [
|
|
58
|
+
{
|
|
59
|
+
v: 2,
|
|
60
|
+
_id: {$gt: 2}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
v: {$gt: 2}
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
For decreasing order it is exactly the same, but the second part of the or has
|
|
69
|
+
to be changed for $lt.
|
|
70
|
+
*/
|
|
71
|
+
const sortOperator = sortingCriteria[sortingField] === 1 ? '$gt' : '$lt';
|
|
72
|
+
query = {
|
|
73
|
+
$or: [
|
|
74
|
+
{ ...restOfQuery, [sortingField]: offsetDocument[sortingField], _id: { $gt: idOffset } },
|
|
75
|
+
{ ...query, [sortingField]: { [sortOperator]: offsetDocument[sortingField] } }
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return collection.find(query).sort(sortingCriteria).limit(limit);
|
|
81
|
+
};
|
|
82
|
+
const validateQuery = async (query) => {
|
|
83
|
+
if (typeof query === 'object') {
|
|
84
|
+
const fields = Object.keys(query);
|
|
85
|
+
fields.forEach(field => {
|
|
86
|
+
if (['$or', '$expr'].includes(field)) {
|
|
87
|
+
throw new Error("tokenPaginatedResolvers don't support $or nor $expr on query");
|
|
88
|
+
}
|
|
89
|
+
if (typeof query[field] === 'object')
|
|
90
|
+
validateQuery(query[field]);
|
|
91
|
+
else if (Array.isArray(query[field]))
|
|
92
|
+
query[field].forEach(queryElement => validateQuery(queryElement));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const { modelName } = otherOptions;
|
|
97
|
+
return (0, resolvers_1.resolver)({
|
|
98
|
+
params: {
|
|
99
|
+
...params,
|
|
100
|
+
idOffset: {
|
|
101
|
+
type: 'ID',
|
|
102
|
+
optional: true
|
|
103
|
+
},
|
|
104
|
+
limit: {
|
|
105
|
+
type: 'integer',
|
|
106
|
+
defaultValue: 10,
|
|
107
|
+
min: 1,
|
|
108
|
+
max: 200
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
returns: (0, getReturnModel_1.default)({ modelName, collection }),
|
|
112
|
+
async resolve(...args) {
|
|
113
|
+
const { params, viewer } = (0, getArgs_1.getArgs)(...args);
|
|
114
|
+
const { query, sort } = await runResolve(...args);
|
|
115
|
+
if (!query)
|
|
116
|
+
throw new Error("'query' object not found in return of resolve function");
|
|
117
|
+
await validateQuery(query);
|
|
118
|
+
const cursor = await getCursor({ ...params, query, sort });
|
|
119
|
+
return {
|
|
120
|
+
params,
|
|
121
|
+
cursor,
|
|
122
|
+
viewer
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
...otherOptions
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
exports.default = default_1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orion-js/paginated-mongodb",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.23",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"types": "lib/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -13,11 +13,13 @@
|
|
|
13
13
|
"prepare": "yarn run build",
|
|
14
14
|
"clean": "rm -rf ./lib",
|
|
15
15
|
"build": "yarn run clean && tsc",
|
|
16
|
-
"watch": "
|
|
16
|
+
"watch": "tsc -w",
|
|
17
|
+
"upgrade-interactive": "yarn upgrade-interactive"
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
19
|
-
"@orion-js/
|
|
20
|
-
"@orion-js/
|
|
20
|
+
"@orion-js/models": "^3.0.20",
|
|
21
|
+
"@orion-js/mongodb": "^3.0.23",
|
|
22
|
+
"@orion-js/resolvers": "^3.0.17"
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
23
25
|
"@shelf/jest-mongodb": "^2.1.0",
|
|
@@ -30,5 +32,5 @@
|
|
|
30
32
|
"publishConfig": {
|
|
31
33
|
"access": "public"
|
|
32
34
|
},
|
|
33
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "26691f9507db91bd6ed1659d231f7e5e1dcc2e55"
|
|
34
36
|
}
|