@classytic/mongokit 1.0.2 → 2.0.0
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/README.md +562 -155
- package/package.json +17 -10
- package/src/Repository.js +296 -225
- package/src/actions/aggregate.js +266 -191
- package/src/actions/create.js +47 -47
- package/src/actions/delete.js +88 -88
- package/src/actions/index.js +11 -11
- package/src/actions/read.js +176 -144
- package/src/actions/update.js +144 -144
- package/src/hooks/lifecycle.js +146 -146
- package/src/index.js +71 -60
- package/src/pagination/PaginationEngine.js +348 -0
- package/src/pagination/utils/cursor.js +119 -0
- package/src/pagination/utils/filter.js +42 -0
- package/src/pagination/utils/limits.js +82 -0
- package/src/pagination/utils/sort.js +101 -0
- package/src/plugins/aggregate-helpers.plugin.js +71 -71
- package/src/plugins/audit-log.plugin.js +60 -60
- package/src/plugins/batch-operations.plugin.js +66 -66
- package/src/plugins/field-filter.plugin.js +27 -27
- package/src/plugins/index.js +19 -19
- package/src/plugins/method-registry.plugin.js +140 -140
- package/src/plugins/mongo-operations.plugin.js +317 -313
- package/src/plugins/soft-delete.plugin.js +46 -46
- package/src/plugins/subdocument.plugin.js +66 -66
- package/src/plugins/timestamp.plugin.js +19 -19
- package/src/plugins/validation-chain.plugin.js +145 -145
- package/src/types.d.ts +87 -0
- package/src/utils/error.js +12 -0
- package/src/utils/field-selection.js +156 -156
- package/src/utils/index.js +12 -12
- package/types/Repository.d.ts +95 -0
- package/types/Repository.d.ts.map +1 -0
- package/types/actions/aggregate.d.ts +112 -0
- package/types/actions/aggregate.d.ts.map +1 -0
- package/types/actions/create.d.ts +21 -0
- package/types/actions/create.d.ts.map +1 -0
- package/types/actions/delete.d.ts +37 -0
- package/types/actions/delete.d.ts.map +1 -0
- package/types/actions/index.d.ts +6 -121
- package/types/actions/index.d.ts.map +1 -0
- package/types/actions/read.d.ts +135 -0
- package/types/actions/read.d.ts.map +1 -0
- package/types/actions/update.d.ts +58 -0
- package/types/actions/update.d.ts.map +1 -0
- package/types/hooks/lifecycle.d.ts +44 -0
- package/types/hooks/lifecycle.d.ts.map +1 -0
- package/types/index.d.ts +25 -104
- package/types/index.d.ts.map +1 -0
- package/types/pagination/PaginationEngine.d.ts +386 -0
- package/types/pagination/PaginationEngine.d.ts.map +1 -0
- package/types/pagination/utils/cursor.d.ts +40 -0
- package/types/pagination/utils/cursor.d.ts.map +1 -0
- package/types/pagination/utils/filter.d.ts +28 -0
- package/types/pagination/utils/filter.d.ts.map +1 -0
- package/types/pagination/utils/limits.d.ts +64 -0
- package/types/pagination/utils/limits.d.ts.map +1 -0
- package/types/pagination/utils/sort.d.ts +41 -0
- package/types/pagination/utils/sort.d.ts.map +1 -0
- package/types/plugins/aggregate-helpers.plugin.d.ts +6 -0
- package/types/plugins/aggregate-helpers.plugin.d.ts.map +1 -0
- package/types/plugins/audit-log.plugin.d.ts +6 -0
- package/types/plugins/audit-log.plugin.d.ts.map +1 -0
- package/types/plugins/batch-operations.plugin.d.ts +6 -0
- package/types/plugins/batch-operations.plugin.d.ts.map +1 -0
- package/types/plugins/field-filter.plugin.d.ts +6 -0
- package/types/plugins/field-filter.plugin.d.ts.map +1 -0
- package/types/plugins/index.d.ts +11 -88
- package/types/plugins/index.d.ts.map +1 -0
- package/types/plugins/method-registry.plugin.d.ts +3 -0
- package/types/plugins/method-registry.plugin.d.ts.map +1 -0
- package/types/plugins/mongo-operations.plugin.d.ts +4 -0
- package/types/plugins/mongo-operations.plugin.d.ts.map +1 -0
- package/types/plugins/soft-delete.plugin.d.ts +6 -0
- package/types/plugins/soft-delete.plugin.d.ts.map +1 -0
- package/types/plugins/subdocument.plugin.d.ts +6 -0
- package/types/plugins/subdocument.plugin.d.ts.map +1 -0
- package/types/plugins/timestamp.plugin.d.ts +6 -0
- package/types/plugins/timestamp.plugin.d.ts.map +1 -0
- package/types/plugins/validation-chain.plugin.d.ts +31 -0
- package/types/plugins/validation-chain.plugin.d.ts.map +1 -0
- package/types/utils/error.d.ts +11 -0
- package/types/utils/error.d.ts.map +1 -0
- package/types/utils/field-selection.d.ts +9 -0
- package/types/utils/field-selection.d.ts.map +1 -0
- package/types/utils/index.d.ts +2 -24
- package/types/utils/index.d.ts.map +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/mongokit",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Production-grade MongoDB repositories with zero dependencies - smart pagination, events, and plugins",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./types/index.d.ts",
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"types": "./types/index.d.ts",
|
|
11
11
|
"import": "./src/index.js"
|
|
12
12
|
},
|
|
13
|
+
"./pagination": {
|
|
14
|
+
"types": "./types/pagination/PaginationEngine.d.ts",
|
|
15
|
+
"import": "./src/pagination/PaginationEngine.js"
|
|
16
|
+
},
|
|
13
17
|
"./plugins": {
|
|
14
18
|
"types": "./types/plugins/index.d.ts",
|
|
15
19
|
"import": "./src/plugins/index.js"
|
|
@@ -31,10 +35,16 @@
|
|
|
31
35
|
"repository",
|
|
32
36
|
"repository-pattern",
|
|
33
37
|
"data-access",
|
|
38
|
+
"pagination",
|
|
39
|
+
"cursor-pagination",
|
|
40
|
+
"infinite-scroll",
|
|
41
|
+
"offset-pagination",
|
|
42
|
+
"keyset-pagination",
|
|
34
43
|
"multi-tenant",
|
|
35
44
|
"multi-tenancy",
|
|
36
45
|
"event-driven",
|
|
37
46
|
"plugin-based",
|
|
47
|
+
"zero-dependencies",
|
|
38
48
|
"express",
|
|
39
49
|
"fastify",
|
|
40
50
|
"nestjs",
|
|
@@ -52,24 +62,21 @@
|
|
|
52
62
|
},
|
|
53
63
|
"homepage": "https://github.com/classytic/mongokit#readme",
|
|
54
64
|
"peerDependencies": {
|
|
55
|
-
"mongoose": "^8.0.0 || ^9.0.0"
|
|
56
|
-
"mongoose-paginate-v2": "^1.9.0",
|
|
57
|
-
"mongoose-aggregate-paginate-v2": "^1.1.0"
|
|
58
|
-
},
|
|
59
|
-
"dependencies": {
|
|
60
|
-
"http-errors": "^2.0.0"
|
|
65
|
+
"mongoose": "^8.0.0 || ^9.0.0"
|
|
61
66
|
},
|
|
62
67
|
"engines": {
|
|
63
68
|
"node": ">=18"
|
|
64
69
|
},
|
|
65
70
|
"scripts": {
|
|
71
|
+
"build": "tsc",
|
|
66
72
|
"test": "node --test test/*.test.js",
|
|
67
73
|
"test:watch": "node --test --watch test/*.test.js",
|
|
74
|
+
"prepublishOnly": "npm run build",
|
|
68
75
|
"publish:npm": "npm publish --access public"
|
|
69
76
|
},
|
|
70
77
|
"devDependencies": {
|
|
78
|
+
"@types/node": "^24.10.1",
|
|
71
79
|
"mongoose": "^9.0.0-rc1",
|
|
72
|
-
"
|
|
73
|
-
"mongoose-aggregate-paginate-v2": "^1.1.0"
|
|
80
|
+
"typescript": "^5.9.3"
|
|
74
81
|
}
|
|
75
82
|
}
|
package/src/Repository.js
CHANGED
|
@@ -1,225 +1,296 @@
|
|
|
1
|
-
import mongoose from 'mongoose';
|
|
2
|
-
import createError from '
|
|
3
|
-
import * as createActions from './actions/create.js';
|
|
4
|
-
import * as readActions from './actions/read.js';
|
|
5
|
-
import * as updateActions from './actions/update.js';
|
|
6
|
-
import * as deleteActions from './actions/delete.js';
|
|
7
|
-
import * as aggregateActions from './actions/aggregate.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
this
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
import { createError } from './utils/error.js';
|
|
3
|
+
import * as createActions from './actions/create.js';
|
|
4
|
+
import * as readActions from './actions/read.js';
|
|
5
|
+
import * as updateActions from './actions/update.js';
|
|
6
|
+
import * as deleteActions from './actions/delete.js';
|
|
7
|
+
import * as aggregateActions from './actions/aggregate.js';
|
|
8
|
+
import { PaginationEngine } from './pagination/PaginationEngine.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('./types.js').OffsetPaginationResult} OffsetPaginationResult
|
|
12
|
+
* @typedef {import('./types.js').KeysetPaginationResult} KeysetPaginationResult
|
|
13
|
+
* @typedef {import('./types.js').AggregatePaginationResult} AggregatePaginationResult
|
|
14
|
+
* @typedef {import('./types.js').ObjectId} ObjectId
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export class Repository {
|
|
18
|
+
constructor(Model, plugins = [], paginationConfig = {}) {
|
|
19
|
+
this.Model = Model;
|
|
20
|
+
this.model = Model.modelName;
|
|
21
|
+
this._hooks = new Map();
|
|
22
|
+
this._pagination = new PaginationEngine(Model, paginationConfig);
|
|
23
|
+
plugins.forEach(plugin => this.use(plugin));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
use(plugin) {
|
|
27
|
+
if (typeof plugin === 'function') {
|
|
28
|
+
plugin(this);
|
|
29
|
+
} else if (plugin && typeof plugin.apply === 'function') {
|
|
30
|
+
plugin.apply(this);
|
|
31
|
+
}
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
on(event, listener) {
|
|
36
|
+
if (!this._hooks.has(event)) {
|
|
37
|
+
this._hooks.set(event, []);
|
|
38
|
+
}
|
|
39
|
+
this._hooks.get(event).push(listener);
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
emit(event, data) {
|
|
44
|
+
const listeners = this._hooks.get(event) || [];
|
|
45
|
+
listeners.forEach(listener => listener(data));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async create(data, options = {}) {
|
|
49
|
+
const context = await this._buildContext('create', { data, ...options });
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const result = await createActions.create(this.Model, context.data, options);
|
|
53
|
+
this.emit('after:create', { context, result });
|
|
54
|
+
return result;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
this.emit('error:create', { context, error });
|
|
57
|
+
throw this._handleError(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async createMany(dataArray, options = {}) {
|
|
62
|
+
const context = await this._buildContext('createMany', { dataArray, ...options });
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const result = await createActions.createMany(this.Model, context.dataArray || dataArray, options);
|
|
66
|
+
this.emit('after:createMany', { context, result });
|
|
67
|
+
return result;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
this.emit('error:createMany', { context, error });
|
|
70
|
+
throw this._handleError(error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async getById(id, options = {}) {
|
|
75
|
+
const context = await this._buildContext('getById', { id, ...options });
|
|
76
|
+
return readActions.getById(this.Model, id, context);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getByQuery(query, options = {}) {
|
|
80
|
+
const context = await this._buildContext('getByQuery', { query, ...options });
|
|
81
|
+
return readActions.getByQuery(this.Model, query, context);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Unified pagination - auto-detects offset vs keyset based on params
|
|
86
|
+
*
|
|
87
|
+
* Auto-detection logic:
|
|
88
|
+
* - If params has 'cursor' or 'after' → uses keyset pagination (stream)
|
|
89
|
+
* - If params has 'pagination' or 'page' → uses offset pagination (paginate)
|
|
90
|
+
* - Else → defaults to offset pagination with page=1
|
|
91
|
+
*
|
|
92
|
+
* @param {Object} params - Query and pagination parameters
|
|
93
|
+
* @param {Object} [params.filters] - MongoDB query filters
|
|
94
|
+
* @param {string|Object} [params.sort] - Sort specification
|
|
95
|
+
* @param {string} [params.cursor] - Cursor token for keyset pagination
|
|
96
|
+
* @param {string} [params.after] - Alias for cursor
|
|
97
|
+
* @param {number} [params.page] - Page number for offset pagination
|
|
98
|
+
* @param {Object} [params.pagination] - Pagination config { page, limit }
|
|
99
|
+
* @param {number} [params.limit] - Documents per page
|
|
100
|
+
* @param {string} [params.search] - Full-text search query
|
|
101
|
+
* @param {Object} [options] - Additional options (select, populate, lean, session)
|
|
102
|
+
* @returns {Promise<OffsetPaginationResult|KeysetPaginationResult>} Discriminated union based on method
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* // Offset pagination (page-based)
|
|
106
|
+
* await repo.getAll({ page: 1, limit: 50, filters: { status: 'active' } });
|
|
107
|
+
* await repo.getAll({ pagination: { page: 2, limit: 20 } });
|
|
108
|
+
*
|
|
109
|
+
* // Keyset pagination (cursor-based)
|
|
110
|
+
* await repo.getAll({ cursor: 'eyJ2Ij...', limit: 50 });
|
|
111
|
+
* await repo.getAll({ after: 'eyJ2Ij...', sort: { createdAt: -1 } });
|
|
112
|
+
*
|
|
113
|
+
* // Simple query (defaults to page 1)
|
|
114
|
+
* await repo.getAll({ filters: { status: 'active' } });
|
|
115
|
+
*/
|
|
116
|
+
async getAll(params = {}, options = {}) {
|
|
117
|
+
const context = await this._buildContext('getAll', { ...params, ...options });
|
|
118
|
+
|
|
119
|
+
// Auto-detect pagination mode
|
|
120
|
+
// Priority:
|
|
121
|
+
// 1. If 'page' param → offset pagination
|
|
122
|
+
// 2. If 'after' or 'cursor' param → keyset pagination
|
|
123
|
+
// 3. If explicit 'sort' provided without 'page' → keyset pagination (first page)
|
|
124
|
+
// 4. Otherwise → offset pagination (default, page=1)
|
|
125
|
+
const hasPageParam = params.page !== undefined || params.pagination;
|
|
126
|
+
const hasCursorParam = 'cursor' in params || 'after' in params;
|
|
127
|
+
const hasExplicitSort = params.sort !== undefined;
|
|
128
|
+
|
|
129
|
+
const useKeyset = !hasPageParam && (hasCursorParam || hasExplicitSort);
|
|
130
|
+
|
|
131
|
+
// Extract common params
|
|
132
|
+
const filters = params.filters || {};
|
|
133
|
+
const search = params.search;
|
|
134
|
+
const sort = params.sort || '-createdAt';
|
|
135
|
+
const limit = params.limit || params.pagination?.limit || this._pagination.config.defaultLimit;
|
|
136
|
+
|
|
137
|
+
// Build query with search support
|
|
138
|
+
let query = { ...filters };
|
|
139
|
+
if (search) query.$text = { $search: search };
|
|
140
|
+
|
|
141
|
+
// Common options
|
|
142
|
+
const paginationOptions = {
|
|
143
|
+
filters: query,
|
|
144
|
+
sort: this._parseSort(sort),
|
|
145
|
+
limit,
|
|
146
|
+
populate: this._parsePopulate(context.populate || options.populate),
|
|
147
|
+
select: context.select || options.select,
|
|
148
|
+
lean: context.lean ?? options.lean ?? true,
|
|
149
|
+
session: options.session,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (useKeyset) {
|
|
153
|
+
// Keyset pagination (cursor-based)
|
|
154
|
+
return this._pagination.stream({
|
|
155
|
+
...paginationOptions,
|
|
156
|
+
after: params.cursor || params.after,
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
// Offset pagination (page-based) - default
|
|
160
|
+
const page = params.pagination?.page || params.page || 1;
|
|
161
|
+
return this._pagination.paginate({
|
|
162
|
+
...paginationOptions,
|
|
163
|
+
page,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async getOrCreate(query, createData, options = {}) {
|
|
169
|
+
return readActions.getOrCreate(this.Model, query, createData, options);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async count(query = {}, options = {}) {
|
|
173
|
+
return readActions.count(this.Model, query, options);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async exists(query, options = {}) {
|
|
177
|
+
return readActions.exists(this.Model, query, options);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async update(id, data, options = {}) {
|
|
181
|
+
const context = await this._buildContext('update', { id, data, ...options });
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const result = await updateActions.update(this.Model, id, context.data, context);
|
|
185
|
+
this.emit('after:update', { context, result });
|
|
186
|
+
return result;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
this.emit('error:update', { context, error });
|
|
189
|
+
throw this._handleError(error);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async delete(id, options = {}) {
|
|
194
|
+
const context = await this._buildContext('delete', { id, ...options });
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const result = await deleteActions.deleteById(this.Model, id, options);
|
|
198
|
+
this.emit('after:delete', { context, result });
|
|
199
|
+
return result;
|
|
200
|
+
} catch (error) {
|
|
201
|
+
this.emit('error:delete', { context, error });
|
|
202
|
+
throw this._handleError(error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async aggregate(pipeline, options = {}) {
|
|
207
|
+
return aggregateActions.aggregate(this.Model, pipeline, options);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Aggregate pipeline with pagination
|
|
212
|
+
* Best for: Complex queries, grouping, joins
|
|
213
|
+
*
|
|
214
|
+
* @param {Object} options - Aggregate pagination options
|
|
215
|
+
* @returns {Promise<AggregatePaginationResult>}
|
|
216
|
+
*/
|
|
217
|
+
async aggregatePaginate(options = {}) {
|
|
218
|
+
const context = await this._buildContext('aggregatePaginate', options);
|
|
219
|
+
return this._pagination.aggregatePaginate(context);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async distinct(field, query = {}, options = {}) {
|
|
223
|
+
return aggregateActions.distinct(this.Model, field, query, options);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async withTransaction(callback) {
|
|
227
|
+
const session = await mongoose.startSession();
|
|
228
|
+
session.startTransaction();
|
|
229
|
+
try {
|
|
230
|
+
const result = await callback(session);
|
|
231
|
+
await session.commitTransaction();
|
|
232
|
+
return result;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
await session.abortTransaction();
|
|
235
|
+
throw error;
|
|
236
|
+
} finally {
|
|
237
|
+
session.endSession();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async _executeQuery(buildQuery) {
|
|
242
|
+
const operation = buildQuery.name || 'custom';
|
|
243
|
+
const context = await this._buildContext(operation, {});
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const result = await buildQuery(this.Model);
|
|
247
|
+
this.emit(`after:${operation}`, { context, result });
|
|
248
|
+
return result;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
this.emit(`error:${operation}`, { context, error });
|
|
251
|
+
throw this._handleError(error);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async _buildContext(operation, options) {
|
|
256
|
+
const context = { operation, model: this.model, ...options };
|
|
257
|
+
const event = `before:${operation}`;
|
|
258
|
+
const hooks = this._hooks.get(event) || [];
|
|
259
|
+
|
|
260
|
+
for (const hook of hooks) {
|
|
261
|
+
await hook(context);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return context;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
_parseSort(sort) {
|
|
268
|
+
if (!sort) return { createdAt: -1 };
|
|
269
|
+
if (typeof sort === 'object') return sort;
|
|
270
|
+
|
|
271
|
+
const sortOrder = sort.startsWith('-') ? -1 : 1;
|
|
272
|
+
const sortField = sort.startsWith('-') ? sort.substring(1) : sort;
|
|
273
|
+
return { [sortField]: sortOrder };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
_parsePopulate(populate) {
|
|
277
|
+
if (!populate) return [];
|
|
278
|
+
if (typeof populate === 'string') return populate.split(',').map(p => p.trim());
|
|
279
|
+
if (Array.isArray(populate)) return populate.map(p => (typeof p === 'string' ? p.trim() : p));
|
|
280
|
+
return [populate];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
_handleError(error) {
|
|
284
|
+
if (error instanceof mongoose.Error.ValidationError) {
|
|
285
|
+
const messages = Object.values(error.errors).map(err => /** @type {any} */(err).message);
|
|
286
|
+
return createError(400, `Validation Error: ${messages.join(', ')}`);
|
|
287
|
+
}
|
|
288
|
+
if (error instanceof mongoose.Error.CastError) {
|
|
289
|
+
return createError(400, `Invalid ${error.path}: ${error.value}`);
|
|
290
|
+
}
|
|
291
|
+
if (error.status && error.message) return error;
|
|
292
|
+
return createError(500, error.message || 'Internal Server Error');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export default Repository;
|