@airoom/nextmin-node 0.1.4 → 0.1.5

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.
@@ -0,0 +1,2 @@
1
+ import type { RouterCtx } from "./ctx";
2
+ export declare function mountFindRoutes(ctx: RouterCtx): void;
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mountFindRoutes = mountFindRoutes;
4
+ const utils_1 = require("./utils");
5
+ const authorize_1 = require("../../policy/authorize");
6
+ function mountFindRoutes(ctx) {
7
+ const { router } = ctx;
8
+ const ctxFromReq = (req) => {
9
+ const raw = req.user?.role;
10
+ let roleStr = null;
11
+ if (typeof raw === "string")
12
+ roleStr = raw;
13
+ else if (Array.isArray(raw)) {
14
+ const first = raw[0];
15
+ if (typeof first === "string")
16
+ roleStr = first;
17
+ else if (first && typeof first === "object" && typeof first.name === "string") {
18
+ roleStr = first.name;
19
+ }
20
+ }
21
+ else if (raw && typeof raw === "object" && typeof raw.name === "string") {
22
+ roleStr = raw.name;
23
+ }
24
+ roleStr = roleStr ? roleStr.toLowerCase() : null;
25
+ return {
26
+ isAuthenticated: !!req.user,
27
+ role: roleStr,
28
+ userId: req.user?.id ?? req.user?._id ?? null,
29
+ isSuperadmin: roleStr === "superadmin",
30
+ apiKeyOk: true,
31
+ };
32
+ };
33
+ const safeGetContainerRefIDs = async (containerModelLC, id, refField) => {
34
+ const containerSchema = ctx.getSchema(containerModelLC);
35
+ const adapterAny = ctx.dbAdapter;
36
+ if (typeof adapterAny.findOne === "function") {
37
+ try {
38
+ const raw = await adapterAny.findOne(containerSchema.modelName, { id }, { projection: { [refField]: 1 } });
39
+ return (0, utils_1.extractIds)(raw?.[refField]);
40
+ }
41
+ catch { }
42
+ }
43
+ const containerDoc = (await ctx.getModel(containerModelLC).read({ id }, 1, 0, true))?.[0];
44
+ return (0, utils_1.extractIds)(containerDoc?.[refField]);
45
+ };
46
+ // FORWARD
47
+ router.get("/find/:container/:refField/:id", ctx.optionalAuthMiddleware, async (req, res) => {
48
+ try {
49
+ const containerLC = String(req.params.container || "").toLowerCase();
50
+ const refField = String(req.params.refField || "");
51
+ const id = String(req.params.id || "");
52
+ const schema = ctx.getSchema(containerLC);
53
+ const attr = schema.attributes?.[refField];
54
+ const rinfo = (0, utils_1.refInfoFromAttr)(attr);
55
+ if (!rinfo) {
56
+ return res.status(400).json({ error: true, message: "refField is not a reference field" });
57
+ }
58
+ const { limit, page, skip, projection, sort } = (0, utils_1.parseQuery)(req);
59
+ const cPolicy = { allowedMethods: schema.allowedMethods, access: schema.access };
60
+ const cDec = (0, authorize_1.authorize)(containerLC, "read", cPolicy, ctxFromReq(req));
61
+ if (!cDec.allow)
62
+ return res.status(403).json({ error: true, message: "forbidden" });
63
+ const ids = await safeGetContainerRefIDs(containerLC, id, refField);
64
+ const targetLC = String(rinfo.ref || "").toLowerCase();
65
+ const targetSchema = ctx.getSchema(targetLC);
66
+ const tPolicy = { allowedMethods: targetSchema.allowedMethods, access: targetSchema.access };
67
+ const tDec = (0, authorize_1.authorize)(targetLC, "read", tPolicy, ctxFromReq(req));
68
+ if (!tDec.allow)
69
+ return res.status(403).json({ error: true, message: "forbidden" });
70
+ if (ids.length === 0) {
71
+ return res.status(200).json({
72
+ success: true,
73
+ message: `Data fetched for ${targetSchema.modelName}`,
74
+ data: [],
75
+ pagination: { totalRows: 0, page, limit },
76
+ });
77
+ }
78
+ const adapterAny = ctx.dbAdapter;
79
+ let items = [];
80
+ if (typeof adapterAny.findMany === "function") {
81
+ items = await adapterAny.findMany(targetSchema.modelName, { id: { $in: ids } }, { sort, skip, limit, projection });
82
+ }
83
+ else {
84
+ items = await ctx.getModel(targetLC).read({ id: { $in: ids } }, limit, skip, !!tDec.exposePrivate);
85
+ }
86
+ // extended hydration if needed
87
+ if (targetSchema.extends && Array.isArray(items) && items.length) {
88
+ const baseName = String(targetSchema.extends);
89
+ const baseLC = baseName.toLowerCase();
90
+ const baseModel = ctx.getModel(baseLC);
91
+ const baseIds = Array.from(new Set(items.map((it) => it?.baseId).filter(Boolean)));
92
+ if (baseIds.length) {
93
+ const baseDocs = await baseModel.read({ id: { $in: baseIds } }, baseIds.length, 0, !!tDec.exposePrivate);
94
+ const baseMap = new Map(baseDocs.map((b) => [String(b?.id ?? b?._id), b]));
95
+ items = items.map((it) => {
96
+ const bid = String(it?.baseId || "");
97
+ const b = bid ? baseMap.get(bid) : null;
98
+ const merged = b ? { ...b, ...it } : { ...it };
99
+ delete merged.baseId;
100
+ return merged;
101
+ });
102
+ }
103
+ else {
104
+ items = items.map((it) => {
105
+ const copy = { ...it };
106
+ delete copy.baseId;
107
+ return copy;
108
+ });
109
+ }
110
+ }
111
+ const masked = tDec.exposePrivate ? (0, authorize_1.applyReadMaskMany)(items, tDec.sensitiveMask) : (0, authorize_1.applyReadMaskMany)(items, tDec.readMask);
112
+ const totalRows = ids.length;
113
+ return res.status(200).json({
114
+ success: true,
115
+ message: `Data fetched for ${targetSchema.modelName}`,
116
+ data: masked,
117
+ pagination: { totalRows, page, limit },
118
+ });
119
+ }
120
+ catch (err) {
121
+ if (err?.code === "MODEL_REMOVED") {
122
+ return res.status(410).json({ error: true, message: err.message });
123
+ }
124
+ return res.status(400).json({ error: true, message: err?.message || "Error" });
125
+ }
126
+ });
127
+ // REVERSE
128
+ router.get("/find/reverse/:target/:byField/:id", ctx.optionalAuthMiddleware, async (req, res) => {
129
+ try {
130
+ const targetLC = String(req.params.target || "").toLowerCase();
131
+ const byField = String(req.params.byField || "");
132
+ const id = String(req.params.id || "");
133
+ const targetSchema = ctx.getSchema(targetLC);
134
+ const attr = targetSchema.attributes?.[byField];
135
+ const rinfo = (0, utils_1.refInfoFromAttr)(attr);
136
+ if (!rinfo) {
137
+ return res.status(400).json({
138
+ error: true,
139
+ message: "byField is not a reference field",
140
+ });
141
+ }
142
+ const { limit, page, skip, projection, sort } = (0, utils_1.parseQuery)(req);
143
+ const tPolicy = { allowedMethods: targetSchema.allowedMethods, access: targetSchema.access };
144
+ const tDec = (0, authorize_1.authorize)(targetLC, "read", tPolicy, ctxFromReq(req));
145
+ if (!tDec.allow)
146
+ return res.status(403).json({ error: true, message: "forbidden" });
147
+ const filter = rinfo.isArray ? { [byField]: { $in: [id] } } : { [byField]: id };
148
+ const adapterAny = ctx.dbAdapter;
149
+ let items = [];
150
+ let totalRows = 0;
151
+ if (typeof adapterAny.findMany === "function") {
152
+ items = await adapterAny.findMany(targetSchema.modelName, filter, { sort, skip, limit, projection });
153
+ if (typeof adapterAny.count === "function") {
154
+ totalRows = await adapterAny.count(targetSchema.modelName, filter);
155
+ }
156
+ else {
157
+ const all = await ctx.getModel(targetLC).read(filter, 0, 0, !!tDec.exposePrivate);
158
+ totalRows = all.length;
159
+ }
160
+ }
161
+ else {
162
+ const all = await ctx.getModel(targetLC).read(filter, 0, 0, !!tDec.exposePrivate);
163
+ totalRows = all.length;
164
+ items = all.slice(skip, skip + limit);
165
+ }
166
+ if (targetSchema.extends && Array.isArray(items) && items.length) {
167
+ const baseName = String(targetSchema.extends);
168
+ const baseLC = baseName.toLowerCase();
169
+ const baseModel = ctx.getModel(baseLC);
170
+ const baseIds = Array.from(new Set(items.map((it) => it?.baseId).filter(Boolean)));
171
+ if (baseIds.length) {
172
+ const baseDocs = await baseModel.read({ id: { $in: baseIds } }, baseIds.length, 0, !!tDec.exposePrivate);
173
+ const baseMap = new Map(baseDocs.map((b) => [String(b?.id ?? b?._id), b]));
174
+ items = items.map((it) => {
175
+ const bid = String(it?.baseId || "");
176
+ const b = bid ? baseMap.get(bid) : null;
177
+ const merged = b ? { ...b, ...it } : { ...it };
178
+ delete merged.baseId;
179
+ return merged;
180
+ });
181
+ }
182
+ else {
183
+ items = items.map((it) => {
184
+ const copy = { ...it };
185
+ delete copy.baseId;
186
+ return copy;
187
+ });
188
+ }
189
+ }
190
+ const masked = tDec.exposePrivate ? (0, authorize_1.applyReadMaskMany)(items, tDec.sensitiveMask) : (0, authorize_1.applyReadMaskMany)(items, tDec.readMask);
191
+ return res.status(200).json({
192
+ success: true,
193
+ message: `Data fetched for ${targetSchema.modelName}`,
194
+ data: masked,
195
+ pagination: { totalRows, page, limit },
196
+ });
197
+ }
198
+ catch (err) {
199
+ if (err?.code === "MODEL_REMOVED") {
200
+ return res.status(410).json({ error: true, message: err.message });
201
+ }
202
+ return res.status(400).json({ error: true, message: err?.message || "Error" });
203
+ }
204
+ });
205
+ }
@@ -0,0 +1,2 @@
1
+ import type { RouterCtx } from './ctx';
2
+ export declare function setupAuthRoutes(ctx: RouterCtx): void;
@@ -0,0 +1,247 @@
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
+ exports.setupAuthRoutes = setupAuthRoutes;
7
+ const bcrypt_1 = __importDefault(require("bcrypt"));
8
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
9
+ function setupAuthRoutes(ctx) {
10
+ const basePath = '/auth/users';
11
+ // REGISTER
12
+ ctx.router.post(`${basePath}/register`, ctx.apiKeyMiddleware, async (req, res) => {
13
+ try {
14
+ let { email, username, password, ...rest } = req.body;
15
+ if (String(password).length < 8) {
16
+ return res
17
+ .status(400)
18
+ .json({
19
+ error: true,
20
+ message: 'Password must be at least 8 characters long',
21
+ });
22
+ }
23
+ const userModel = ctx.getModel('users');
24
+ const usersSchema = ctx.getSchema('users');
25
+ if (email)
26
+ email = String(email).trim().toLowerCase();
27
+ if (username)
28
+ username = String(username).trim();
29
+ const data = { ...rest };
30
+ if (email)
31
+ data.email = email;
32
+ if (username)
33
+ data.username = username;
34
+ if (data.status == null)
35
+ data.status = 'pending';
36
+ if (usersSchema) {
37
+ const missing = ctx.validateRequiredFields(usersSchema, {
38
+ ...data,
39
+ password,
40
+ });
41
+ if (missing.length > 0) {
42
+ return res.status(400).json({
43
+ error: true,
44
+ message: `Missing required field(s): ${missing.join(', ')}`,
45
+ });
46
+ }
47
+ const conflicts = await ctx.checkUniqueFields(usersSchema, data);
48
+ if (conflicts && conflicts.length) {
49
+ return res.status(400).json({
50
+ error: true,
51
+ message: `Duplicate value for field(s): ${conflicts.join(', ')}`,
52
+ });
53
+ }
54
+ }
55
+ const salt = await bcrypt_1.default.genSalt(10);
56
+ const hashed = await bcrypt_1.default.hash(String(password) + ctx.jwtSecret, salt);
57
+ data.password = hashed;
58
+ const created = await userModel.create(data);
59
+ const roleName = (await ctx.normalizeRoleName(created.role)) ?? '';
60
+ const token = jsonwebtoken_1.default.sign({ id: created.id, role: roleName }, ctx.jwtSecret, { expiresIn: '7days' });
61
+ delete created.password;
62
+ return res.json({
63
+ success: true,
64
+ message: 'Registration successful.',
65
+ data: { token, user: created },
66
+ });
67
+ }
68
+ catch (error) {
69
+ ctx.handleWriteError(error, res);
70
+ }
71
+ });
72
+ // LOGIN
73
+ ctx.router.post(`${basePath}/login`, ctx.apiKeyMiddleware, async (req, res) => {
74
+ let { email, username, password } = req.body;
75
+ if ((!email && !username) || !password) {
76
+ return res
77
+ .status(400)
78
+ .json({
79
+ error: true,
80
+ message: 'Email/username and password are required',
81
+ });
82
+ }
83
+ try {
84
+ const userModel = ctx.getModel('users');
85
+ const findBy = email
86
+ ? { email: String(email).trim().toLowerCase() }
87
+ : { username: String(username).trim() };
88
+ const users = await userModel.read(findBy, 1, 0, true);
89
+ const user = users?.[0];
90
+ if (!user)
91
+ return res
92
+ .status(400)
93
+ .json({ error: true, message: 'Invalid credentials' });
94
+ const hashedPassword = user.password;
95
+ if (!hashedPassword)
96
+ return res
97
+ .status(400)
98
+ .json({ error: true, message: 'User password not set' });
99
+ const isMatch = await bcrypt_1.default.compare(String(password) + ctx.jwtSecret, hashedPassword);
100
+ if (!isMatch)
101
+ return res
102
+ .status(400)
103
+ .json({ error: true, message: 'Invalid credentials' });
104
+ const status = user.status;
105
+ if (status && status !== 'active') {
106
+ const msg = status === 'pending'
107
+ ? 'Your account is awaiting approval.'
108
+ : 'Your account is suspended.';
109
+ return res.status(403).json({ error: true, message: msg });
110
+ }
111
+ const roleName = (await ctx.normalizeRoleName(user.role)) ?? '';
112
+ const token = jsonwebtoken_1.default.sign({ id: user.id, role: roleName }, ctx.jwtSecret, { expiresIn: '7days' });
113
+ delete user.password;
114
+ res.json({
115
+ success: true,
116
+ message: 'You are successfully logged in.',
117
+ data: { token, user },
118
+ });
119
+ }
120
+ catch (error) {
121
+ if (error?.code === 'MODEL_REMOVED')
122
+ return res.status(410).json({ error: true, message: error.message });
123
+ res.status(500).json({ error: true, message: error.message });
124
+ }
125
+ });
126
+ // ME
127
+ ctx.router.get(`${basePath}/me`, ctx.apiKeyMiddleware, ctx.authenticateMiddleware, async (req, res) => {
128
+ try {
129
+ const userId = req.user?.id ?? req.user?._id ?? null;
130
+ if (!userId) {
131
+ return res
132
+ .status(401)
133
+ .json({ error: true, message: 'Not authenticated' });
134
+ }
135
+ const userModel = ctx.getModel('users');
136
+ const arr = await userModel.read({ id: userId }, 1, 0, true);
137
+ const user = arr?.[0];
138
+ if (!user) {
139
+ return res
140
+ .status(404)
141
+ .json({ error: true, message: 'User not found' });
142
+ }
143
+ delete user.password;
144
+ return res.json({ success: true, data: user });
145
+ }
146
+ catch (error) {
147
+ return res
148
+ .status(400)
149
+ .json({ error: true, message: error?.message || 'Error' });
150
+ }
151
+ });
152
+ // CHANGE PASSWORD
153
+ ctx.router.post(`${basePath}/change-password`, ctx.apiKeyMiddleware, ctx.authenticateMiddleware, async (req, res) => {
154
+ try {
155
+ const { oldPassword, newPassword } = req.body;
156
+ if (!oldPassword || !newPassword) {
157
+ return res
158
+ .status(400)
159
+ .json({
160
+ error: true,
161
+ message: 'oldPassword and newPassword are required',
162
+ });
163
+ }
164
+ if (String(newPassword).length < 8) {
165
+ return res
166
+ .status(400)
167
+ .json({
168
+ error: true,
169
+ message: 'New password must be at least 8 characters long',
170
+ });
171
+ }
172
+ if (String(newPassword) === String(oldPassword)) {
173
+ return res
174
+ .status(400)
175
+ .json({
176
+ error: true,
177
+ message: 'New password must be different from old password',
178
+ });
179
+ }
180
+ const userId = req.user?.id ?? req.user?._id ?? null;
181
+ if (!userId) {
182
+ return res
183
+ .status(401)
184
+ .json({ error: true, message: 'Not authenticated' });
185
+ }
186
+ const userModel = ctx.getModel('users');
187
+ const arr = await userModel.read({ id: userId }, 1, 0, true);
188
+ const user = arr?.[0];
189
+ if (!user) {
190
+ return res
191
+ .status(404)
192
+ .json({ error: true, message: 'User not found' });
193
+ }
194
+ const storedHash = user.password;
195
+ if (!storedHash) {
196
+ return res
197
+ .status(400)
198
+ .json({ error: true, message: 'User password not set' });
199
+ }
200
+ const match = await bcrypt_1.default.compare(String(oldPassword) + ctx.jwtSecret, storedHash);
201
+ if (!match) {
202
+ return res
203
+ .status(400)
204
+ .json({ error: true, message: 'Old password is incorrect' });
205
+ }
206
+ const salt = await bcrypt_1.default.genSalt(10);
207
+ const newHash = await bcrypt_1.default.hash(String(newPassword) + ctx.jwtSecret, salt);
208
+ await userModel.update(user.id, { password: newHash });
209
+ return res.json({
210
+ success: true,
211
+ message: 'Password changed successfully.',
212
+ });
213
+ }
214
+ catch (error) {
215
+ res
216
+ .status(400)
217
+ .json({ error: true, message: error?.message || 'Error' });
218
+ }
219
+ });
220
+ // FORGOT PASSWORD
221
+ const forgotPasswordHandler = async (req, res) => {
222
+ try {
223
+ const { email } = (req.body || {});
224
+ if (!email || !String(email).trim()) {
225
+ return res
226
+ .status(400)
227
+ .json({ error: true, message: 'Email is required' });
228
+ }
229
+ const normalizedEmail = String(email).trim().toLowerCase();
230
+ try {
231
+ const userModel = ctx.getModel('users');
232
+ await userModel.read({ email: normalizedEmail }, 1, 0, true);
233
+ }
234
+ catch { }
235
+ return res.json({
236
+ success: true,
237
+ message: 'If an account exists, reset instructions have been sent.',
238
+ });
239
+ }
240
+ catch (error) {
241
+ return res
242
+ .status(400)
243
+ .json({ error: true, message: error?.message || 'Error' });
244
+ }
245
+ };
246
+ ctx.router.post(`${basePath}/forgot-password`, ctx.apiKeyMiddleware, forgotPasswordHandler);
247
+ }
@@ -0,0 +1,2 @@
1
+ import type { RouterCtx } from "./ctx";
2
+ export declare function setupFileRoutes(ctx: RouterCtx): void;
@@ -0,0 +1,85 @@
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
+ exports.setupFileRoutes = setupFileRoutes;
7
+ const multer_1 = __importDefault(require("multer"));
8
+ const filename_1 = require("../../files/filename");
9
+ function setupFileRoutes(ctx) {
10
+ if (!ctx.fileStorage)
11
+ return;
12
+ const upload = (0, multer_1.default)({
13
+ storage: multer_1.default.memoryStorage(),
14
+ limits: { fileSize: 50 * 1024 * 1024, files: 20 },
15
+ });
16
+ const fileAuthMiddleware = (req, res, next) => ctx.authenticateMiddleware(req, res, next);
17
+ ctx.router.post("/files", fileAuthMiddleware, upload.any(), async (req, res) => {
18
+ try {
19
+ const files = req.files ?? [];
20
+ if (!files.length) {
21
+ return res.status(400).json({ error: true, message: "No files uploaded" });
22
+ }
23
+ const results = await Promise.all(files.map(async (f) => {
24
+ const folder = shortFolder();
25
+ const ext = (f.originalname.match(/\.([A-Za-z0-9]{1,8})$/)?.[1] ??
26
+ (0, filename_1.extFromMime)(f.mimetype) ??
27
+ "bin").toLowerCase();
28
+ const key = `${folder}/${shortUid()}.${ext}`;
29
+ const out = await ctx.fileStorage.upload({
30
+ key,
31
+ body: f.buffer,
32
+ contentType: f.mimetype,
33
+ metadata: { originalName: f.originalname || "" },
34
+ });
35
+ return {
36
+ provider: out.provider,
37
+ bucket: out.bucket,
38
+ key: out.key,
39
+ url: out.url,
40
+ etag: out.etag,
41
+ contentType: out.contentType,
42
+ size: out.size,
43
+ metadata: out.metadata,
44
+ originalName: f.originalname,
45
+ };
46
+ }));
47
+ return res.json({
48
+ success: true,
49
+ message: "Files uploaded successfully",
50
+ data: results,
51
+ });
52
+ }
53
+ catch (err) {
54
+ return res.status(400).json({ error: true, message: err?.message ?? "Upload failed" });
55
+ }
56
+ });
57
+ ctx.router.delete("/files/:key(*)", fileAuthMiddleware, async (req, res) => {
58
+ try {
59
+ const key = String(req.params.key || "");
60
+ if (!key) {
61
+ return res.status(400).json({ error: true, message: "Key is required" });
62
+ }
63
+ const { deleted } = await ctx.fileStorage.delete(key);
64
+ return res.json({
65
+ success: true,
66
+ message: deleted ? "File deleted" : "Delete attempted",
67
+ key,
68
+ deleted,
69
+ });
70
+ }
71
+ catch (err) {
72
+ return res.status(400).json({ error: true, message: err?.message ?? "Delete failed" });
73
+ }
74
+ });
75
+ }
76
+ function shortFolder() {
77
+ const d = new Date();
78
+ const y = d.getFullYear();
79
+ const m = String(d.getMonth() + 1).padStart(2, "0");
80
+ const day = String(d.getDate()).padStart(2, "0");
81
+ return `uploads/${y}/${m}/${day}`;
82
+ }
83
+ function shortUid() {
84
+ return (Date.now().toString(36) + Math.random().toString(36).slice(2, 6)).toLowerCase();
85
+ }
@@ -0,0 +1,63 @@
1
+ import type { Request } from "express";
2
+ export type AnyRec = Record<string, any>;
3
+ export type SortDir = 1 | -1;
4
+ export type SortSpec = Record<string, SortDir>;
5
+ export declare const isPlainObject: (v: unknown) => v is AnyRec;
6
+ export declare const splitCSV: (raw: string) => string[];
7
+ export declare function normalizeAttrType(attr: any): string;
8
+ export declare function toIdString(v: any): string | null;
9
+ export declare function splitFilterForExtended(filter: AnyRec, baseKeys: Set<string>): {
10
+ child: AnyRec;
11
+ base: AnyRec;
12
+ };
13
+ export declare function splitSortForExtended(sort: SortSpec, baseKeys: Set<string>): {
14
+ child: SortSpec;
15
+ base: SortSpec;
16
+ };
17
+ export declare function sortInMemory(rows: AnyRec[], sort: SortSpec): AnyRec[];
18
+ export declare function matchDoc(doc: AnyRec, filter: AnyRec): boolean;
19
+ export declare function buildPredicateForField(field: string, attr: any, raw: string): {
20
+ [field]: {
21
+ $in: string[];
22
+ };
23
+ } | {
24
+ [field]: {
25
+ $regex: string;
26
+ $options: string;
27
+ };
28
+ } | {
29
+ [field]: {
30
+ $in: number[];
31
+ };
32
+ } | {
33
+ [field]: number;
34
+ } | {
35
+ [field]: {
36
+ $in: boolean[];
37
+ };
38
+ } | {
39
+ [field]: boolean;
40
+ } | {
41
+ [field]: string;
42
+ } | {
43
+ [field]: {
44
+ $in: Date[];
45
+ };
46
+ } | {
47
+ [field]: Date;
48
+ } | null;
49
+ export declare function parseSort(expr?: string): Record<string, 1 | -1> | undefined;
50
+ export declare function parseQuery(req: Request): {
51
+ limit: number;
52
+ page: number;
53
+ skip: number;
54
+ projection: {
55
+ [k: string]: 1;
56
+ } | undefined;
57
+ sort: Record<string, 1 | -1> | undefined;
58
+ };
59
+ export declare function extractIds(val: unknown): string[];
60
+ export declare function refInfoFromAttr(attr: any): {
61
+ ref: string;
62
+ isArray: boolean;
63
+ } | null;