@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.
Files changed (87) hide show
  1. package/README.md +562 -155
  2. package/package.json +17 -10
  3. package/src/Repository.js +296 -225
  4. package/src/actions/aggregate.js +266 -191
  5. package/src/actions/create.js +47 -47
  6. package/src/actions/delete.js +88 -88
  7. package/src/actions/index.js +11 -11
  8. package/src/actions/read.js +176 -144
  9. package/src/actions/update.js +144 -144
  10. package/src/hooks/lifecycle.js +146 -146
  11. package/src/index.js +71 -60
  12. package/src/pagination/PaginationEngine.js +348 -0
  13. package/src/pagination/utils/cursor.js +119 -0
  14. package/src/pagination/utils/filter.js +42 -0
  15. package/src/pagination/utils/limits.js +82 -0
  16. package/src/pagination/utils/sort.js +101 -0
  17. package/src/plugins/aggregate-helpers.plugin.js +71 -71
  18. package/src/plugins/audit-log.plugin.js +60 -60
  19. package/src/plugins/batch-operations.plugin.js +66 -66
  20. package/src/plugins/field-filter.plugin.js +27 -27
  21. package/src/plugins/index.js +19 -19
  22. package/src/plugins/method-registry.plugin.js +140 -140
  23. package/src/plugins/mongo-operations.plugin.js +317 -313
  24. package/src/plugins/soft-delete.plugin.js +46 -46
  25. package/src/plugins/subdocument.plugin.js +66 -66
  26. package/src/plugins/timestamp.plugin.js +19 -19
  27. package/src/plugins/validation-chain.plugin.js +145 -145
  28. package/src/types.d.ts +87 -0
  29. package/src/utils/error.js +12 -0
  30. package/src/utils/field-selection.js +156 -156
  31. package/src/utils/index.js +12 -12
  32. package/types/Repository.d.ts +95 -0
  33. package/types/Repository.d.ts.map +1 -0
  34. package/types/actions/aggregate.d.ts +112 -0
  35. package/types/actions/aggregate.d.ts.map +1 -0
  36. package/types/actions/create.d.ts +21 -0
  37. package/types/actions/create.d.ts.map +1 -0
  38. package/types/actions/delete.d.ts +37 -0
  39. package/types/actions/delete.d.ts.map +1 -0
  40. package/types/actions/index.d.ts +6 -121
  41. package/types/actions/index.d.ts.map +1 -0
  42. package/types/actions/read.d.ts +135 -0
  43. package/types/actions/read.d.ts.map +1 -0
  44. package/types/actions/update.d.ts +58 -0
  45. package/types/actions/update.d.ts.map +1 -0
  46. package/types/hooks/lifecycle.d.ts +44 -0
  47. package/types/hooks/lifecycle.d.ts.map +1 -0
  48. package/types/index.d.ts +25 -104
  49. package/types/index.d.ts.map +1 -0
  50. package/types/pagination/PaginationEngine.d.ts +386 -0
  51. package/types/pagination/PaginationEngine.d.ts.map +1 -0
  52. package/types/pagination/utils/cursor.d.ts +40 -0
  53. package/types/pagination/utils/cursor.d.ts.map +1 -0
  54. package/types/pagination/utils/filter.d.ts +28 -0
  55. package/types/pagination/utils/filter.d.ts.map +1 -0
  56. package/types/pagination/utils/limits.d.ts +64 -0
  57. package/types/pagination/utils/limits.d.ts.map +1 -0
  58. package/types/pagination/utils/sort.d.ts +41 -0
  59. package/types/pagination/utils/sort.d.ts.map +1 -0
  60. package/types/plugins/aggregate-helpers.plugin.d.ts +6 -0
  61. package/types/plugins/aggregate-helpers.plugin.d.ts.map +1 -0
  62. package/types/plugins/audit-log.plugin.d.ts +6 -0
  63. package/types/plugins/audit-log.plugin.d.ts.map +1 -0
  64. package/types/plugins/batch-operations.plugin.d.ts +6 -0
  65. package/types/plugins/batch-operations.plugin.d.ts.map +1 -0
  66. package/types/plugins/field-filter.plugin.d.ts +6 -0
  67. package/types/plugins/field-filter.plugin.d.ts.map +1 -0
  68. package/types/plugins/index.d.ts +11 -88
  69. package/types/plugins/index.d.ts.map +1 -0
  70. package/types/plugins/method-registry.plugin.d.ts +3 -0
  71. package/types/plugins/method-registry.plugin.d.ts.map +1 -0
  72. package/types/plugins/mongo-operations.plugin.d.ts +4 -0
  73. package/types/plugins/mongo-operations.plugin.d.ts.map +1 -0
  74. package/types/plugins/soft-delete.plugin.d.ts +6 -0
  75. package/types/plugins/soft-delete.plugin.d.ts.map +1 -0
  76. package/types/plugins/subdocument.plugin.d.ts +6 -0
  77. package/types/plugins/subdocument.plugin.d.ts.map +1 -0
  78. package/types/plugins/timestamp.plugin.d.ts +6 -0
  79. package/types/plugins/timestamp.plugin.d.ts.map +1 -0
  80. package/types/plugins/validation-chain.plugin.d.ts +31 -0
  81. package/types/plugins/validation-chain.plugin.d.ts.map +1 -0
  82. package/types/utils/error.d.ts +11 -0
  83. package/types/utils/error.d.ts.map +1 -0
  84. package/types/utils/field-selection.d.ts +9 -0
  85. package/types/utils/field-selection.d.ts.map +1 -0
  86. package/types/utils/index.d.ts +2 -24
  87. package/types/utils/index.d.ts.map +1 -0
@@ -1,88 +1,88 @@
1
- /**
2
- * Delete Actions
3
- * Pure functions for document deletion
4
- */
5
-
6
- import createError from 'http-errors';
7
-
8
- /**
9
- * Delete by ID
10
- */
11
- export async function deleteById(Model, id, options = {}) {
12
- const document = await Model.findByIdAndDelete(id).session(options.session);
13
-
14
- if (!document) {
15
- throw createError(404, 'Document not found');
16
- }
17
-
18
- return { success: true, message: 'Deleted successfully' };
19
- }
20
-
21
- /**
22
- * Delete many documents
23
- */
24
- export async function deleteMany(Model, query, options = {}) {
25
- const result = await Model.deleteMany(query).session(options.session);
26
-
27
- return {
28
- success: true,
29
- count: result.deletedCount,
30
- message: 'Deleted successfully',
31
- };
32
- }
33
-
34
- /**
35
- * Delete by query
36
- */
37
- export async function deleteByQuery(Model, query, options = {}) {
38
- const document = await Model.findOneAndDelete(query).session(options.session);
39
-
40
- if (!document && options.throwOnNotFound !== false) {
41
- throw createError(404, 'Document not found');
42
- }
43
-
44
- return { success: true, message: 'Deleted successfully' };
45
- }
46
-
47
- /**
48
- * Soft delete (set deleted flag)
49
- */
50
- export async function softDelete(Model, id, options = {}) {
51
- const document = await Model.findByIdAndUpdate(
52
- id,
53
- {
54
- deleted: true,
55
- deletedAt: new Date(),
56
- deletedBy: options.userId,
57
- },
58
- { new: true, session: options.session }
59
- );
60
-
61
- if (!document) {
62
- throw createError(404, 'Document not found');
63
- }
64
-
65
- return { success: true, message: 'Soft deleted successfully' };
66
- }
67
-
68
- /**
69
- * Restore soft deleted document
70
- */
71
- export async function restore(Model, id, options = {}) {
72
- const document = await Model.findByIdAndUpdate(
73
- id,
74
- {
75
- deleted: false,
76
- deletedAt: null,
77
- deletedBy: null,
78
- },
79
- { new: true, session: options.session }
80
- );
81
-
82
- if (!document) {
83
- throw createError(404, 'Document not found');
84
- }
85
-
86
- return { success: true, message: 'Restored successfully' };
87
- }
88
-
1
+ /**
2
+ * Delete Actions
3
+ * Pure functions for document deletion
4
+ */
5
+
6
+ import { createError } from '../utils/error.js';
7
+
8
+ /**
9
+ * Delete by ID
10
+ */
11
+ export async function deleteById(Model, id, options = {}) {
12
+ const document = await Model.findByIdAndDelete(id).session(options.session);
13
+
14
+ if (!document) {
15
+ throw createError(404, 'Document not found');
16
+ }
17
+
18
+ return { success: true, message: 'Deleted successfully' };
19
+ }
20
+
21
+ /**
22
+ * Delete many documents
23
+ */
24
+ export async function deleteMany(Model, query, options = {}) {
25
+ const result = await Model.deleteMany(query).session(options.session);
26
+
27
+ return {
28
+ success: true,
29
+ count: result.deletedCount,
30
+ message: 'Deleted successfully',
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Delete by query
36
+ */
37
+ export async function deleteByQuery(Model, query, options = {}) {
38
+ const document = await Model.findOneAndDelete(query).session(options.session);
39
+
40
+ if (!document && options.throwOnNotFound !== false) {
41
+ throw createError(404, 'Document not found');
42
+ }
43
+
44
+ return { success: true, message: 'Deleted successfully' };
45
+ }
46
+
47
+ /**
48
+ * Soft delete (set deleted flag)
49
+ */
50
+ export async function softDelete(Model, id, options = {}) {
51
+ const document = await Model.findByIdAndUpdate(
52
+ id,
53
+ {
54
+ deleted: true,
55
+ deletedAt: new Date(),
56
+ deletedBy: options.userId,
57
+ },
58
+ { new: true, session: options.session }
59
+ );
60
+
61
+ if (!document) {
62
+ throw createError(404, 'Document not found');
63
+ }
64
+
65
+ return { success: true, message: 'Soft deleted successfully' };
66
+ }
67
+
68
+ /**
69
+ * Restore soft deleted document
70
+ */
71
+ export async function restore(Model, id, options = {}) {
72
+ const document = await Model.findByIdAndUpdate(
73
+ id,
74
+ {
75
+ deleted: false,
76
+ deletedAt: null,
77
+ deletedBy: null,
78
+ },
79
+ { new: true, session: options.session }
80
+ );
81
+
82
+ if (!document) {
83
+ throw createError(404, 'Document not found');
84
+ }
85
+
86
+ return { success: true, message: 'Restored successfully' };
87
+ }
88
+
@@ -1,11 +1,11 @@
1
- /**
2
- * Repository Actions
3
- * Modular, composable data access operations
4
- */
5
-
6
- export * as create from './create.js';
7
- export * as read from './read.js';
8
- export * as update from './update.js';
9
- export * as deleteActions from './delete.js';
10
- export * as aggregate from './aggregate.js';
11
-
1
+ /**
2
+ * Repository Actions
3
+ * Modular, composable data access operations
4
+ */
5
+
6
+ export * as create from './create.js';
7
+ export * as read from './read.js';
8
+ export * as update from './update.js';
9
+ export * as deleteActions from './delete.js';
10
+ export * as aggregate from './aggregate.js';
11
+
@@ -1,86 +1,135 @@
1
- /**
2
- * Read Actions
3
- * Pure functions for document retrieval
4
- */
5
-
6
- import createError from 'http-errors';
7
-
8
- /**
9
- * Get by ID
10
- */
11
- export async function getById(Model, id, options = {}) {
12
- const query = Model.findById(id);
13
-
14
- if (options.select) query.select(options.select);
15
- if (options.populate) query.populate(parsePopulate(options.populate));
16
- if (options.lean) query.lean();
17
- if (options.session) query.session(options.session);
18
-
19
- const document = await query.exec();
20
- if (!document && options.throwOnNotFound !== false) {
21
- throw createError(404, 'Document not found');
22
- }
23
-
24
- return document;
25
- }
26
-
27
- /**
28
- * Get by query
29
- */
30
- export async function getByQuery(Model, query, options = {}) {
31
- const mongoQuery = Model.findOne(query);
32
-
33
- if (options.select) mongoQuery.select(options.select);
34
- if (options.populate) mongoQuery.populate(parsePopulate(options.populate));
35
- if (options.lean) mongoQuery.lean();
36
- if (options.session) mongoQuery.session(options.session);
37
-
38
- const document = await mongoQuery.exec();
39
- if (!document && options.throwOnNotFound !== false) {
40
- throw createError(404, 'Document not found');
41
- }
42
-
43
- return document;
44
- }
45
-
46
- /**
47
- * Get all with pagination
48
- */
49
- export async function getAll(Model, queryParams, options = {}) {
50
- const {
51
- pagination = { page: 1, limit: 10 },
52
- search,
53
- sort = '-createdAt',
54
- filters = {},
55
- } = queryParams;
56
-
57
- let query = {};
58
-
59
- if (search) {
60
- query.$text = { $search: search };
61
- }
62
-
63
- if (filters) {
64
- query = { ...query, ...parseFilters(filters) };
65
- }
66
-
67
- const paginateOptions = {
68
- page: parseInt(pagination.page, 10),
69
- limit: parseInt(pagination.limit, 10),
70
- sort: parseSort(sort),
71
- populate: parsePopulate(options.populate),
72
- select: options.select,
73
- lean: options.lean !== false,
74
- session: options.session,
75
- };
76
-
77
- return Model.paginate(query, paginateOptions);
78
- }
79
-
80
- /**
81
- * Get or create
82
- */
83
- export async function getOrCreate(Model, query, createData, options = {}) {
1
+ /**
2
+ * Read Actions
3
+ * Pure functions for document retrieval
4
+ */
5
+
6
+ import { createError } from '../utils/error.js';
7
+
8
+ /**
9
+ * @typedef {import('mongoose').Model} Model
10
+ * @typedef {import('mongoose').PopulateOptions} PopulateOptions
11
+ * @typedef {import('mongoose').ClientSession} ClientSession
12
+ */
13
+
14
+ /**
15
+ * Get document by ID
16
+ *
17
+ * @param {Model} Model - Mongoose model
18
+ * @param {string} id - Document ID
19
+ * @param {Object} [options={}] - Query options
20
+ * @param {string|string[]} [options.select] - Fields to select
21
+ * @param {string|string[]|PopulateOptions|PopulateOptions[]} [options.populate] - Fields to populate
22
+ * @param {boolean} [options.lean] - Return plain JavaScript object
23
+ * @param {ClientSession} [options.session] - MongoDB session
24
+ * @param {boolean} [options.throwOnNotFound=true] - Throw error if not found
25
+ * @returns {Promise<any>} Document or null
26
+ * @throws {Error} If document not found and throwOnNotFound is true
27
+ */
28
+ export async function getById(Model, id, options = {}) {
29
+ const query = Model.findById(id);
30
+
31
+ if (options.select) query.select(options.select);
32
+ if (options.populate) query.populate(parsePopulate(options.populate));
33
+ if (options.lean) query.lean();
34
+ if (options.session) query.session(options.session);
35
+
36
+ const document = await query.exec();
37
+ if (!document && options.throwOnNotFound !== false) {
38
+ throw createError(404, 'Document not found');
39
+ }
40
+
41
+ return document;
42
+ }
43
+
44
+ /**
45
+ * Get document by query
46
+ *
47
+ * @param {Model} Model - Mongoose model
48
+ * @param {Record<string, any>} query - MongoDB query
49
+ * @param {Object} [options={}] - Query options
50
+ * @param {string|string[]} [options.select] - Fields to select
51
+ * @param {string|string[]|PopulateOptions|PopulateOptions[]} [options.populate] - Fields to populate
52
+ * @param {boolean} [options.lean] - Return plain JavaScript object
53
+ * @param {ClientSession} [options.session] - MongoDB session
54
+ * @param {boolean} [options.throwOnNotFound=true] - Throw error if not found
55
+ * @returns {Promise<any>} Document or null
56
+ * @throws {Error} If document not found and throwOnNotFound is true
57
+ */
58
+ export async function getByQuery(Model, query, options = {}) {
59
+ const mongoQuery = Model.findOne(query);
60
+
61
+ if (options.select) mongoQuery.select(options.select);
62
+ if (options.populate) mongoQuery.populate(parsePopulate(options.populate));
63
+ if (options.lean) mongoQuery.lean();
64
+ if (options.session) mongoQuery.session(options.session);
65
+
66
+ const document = await mongoQuery.exec();
67
+ if (!document && options.throwOnNotFound !== false) {
68
+ throw createError(404, 'Document not found');
69
+ }
70
+
71
+ return document;
72
+ }
73
+
74
+ /**
75
+ * Get document by query without throwing (returns null if not found)
76
+ *
77
+ * @param {Model} Model - Mongoose model
78
+ * @param {Record<string, any>} query - MongoDB query
79
+ * @param {Object} [options={}] - Query options
80
+ * @param {string|string[]} [options.select] - Fields to select
81
+ * @param {string|string[]|PopulateOptions|PopulateOptions[]} [options.populate] - Fields to populate
82
+ * @param {boolean} [options.lean] - Return plain JavaScript object
83
+ * @param {ClientSession} [options.session] - MongoDB session
84
+ * @returns {Promise<any|null>} Document or null
85
+ */
86
+ export async function tryGetByQuery(Model, query, options = {}) {
87
+ return getByQuery(Model, query, { ...options, throwOnNotFound: false });
88
+ }
89
+
90
+ /**
91
+ * Get all documents (basic query without pagination)
92
+ * For pagination, use Repository.paginate() or Repository.stream()
93
+ *
94
+ * @param {Model} Model - Mongoose model
95
+ * @param {Record<string, any>} [query={}] - MongoDB query
96
+ * @param {Object} [options={}] - Query options
97
+ * @param {string|string[]} [options.select] - Fields to select
98
+ * @param {string|string[]|PopulateOptions|PopulateOptions[]} [options.populate] - Fields to populate
99
+ * @param {Record<string, 1|-1>} [options.sort] - Sort specification
100
+ * @param {number} [options.limit] - Maximum documents to return
101
+ * @param {number} [options.skip] - Documents to skip
102
+ * @param {boolean} [options.lean=true] - Return plain JavaScript objects
103
+ * @param {ClientSession} [options.session] - MongoDB session
104
+ * @returns {Promise<any[]>} Array of documents
105
+ */
106
+ export async function getAll(Model, query = {}, options = {}) {
107
+ let mongoQuery = Model.find(query);
108
+
109
+ if (options.select) mongoQuery = mongoQuery.select(options.select);
110
+ if (options.populate) mongoQuery = mongoQuery.populate(parsePopulate(options.populate));
111
+ if (options.sort) mongoQuery = mongoQuery.sort(options.sort);
112
+ if (options.limit) mongoQuery = mongoQuery.limit(options.limit);
113
+ if (options.skip) mongoQuery = mongoQuery.skip(options.skip);
114
+
115
+ mongoQuery = mongoQuery.lean(options.lean !== false);
116
+ if (options.session) mongoQuery = mongoQuery.session(options.session);
117
+
118
+ return mongoQuery.exec();
119
+ }
120
+
121
+ /**
122
+ * Get or create document (upsert)
123
+ *
124
+ * @param {Model} Model - Mongoose model
125
+ * @param {Record<string, any>} query - Query to find document
126
+ * @param {Record<string, any>} createData - Data to insert if not found
127
+ * @param {Object} [options={}] - Query options
128
+ * @param {ClientSession} [options.session] - MongoDB session
129
+ * @param {boolean} [options.updatePipeline] - Use update pipeline
130
+ * @returns {Promise<any>} Created or found document
131
+ */
132
+ export async function getOrCreate(Model, query, createData, options = {}) {
84
133
  return Model.findOneAndUpdate(
85
134
  query,
86
135
  { $setOnInsert: createData },
@@ -93,64 +142,47 @@ export async function getOrCreate(Model, query, createData, options = {}) {
93
142
  }
94
143
  );
95
144
  }
96
-
97
- /**
98
- * Count documents
99
- */
100
- export async function count(Model, query = {}, options = {}) {
101
- return Model.countDocuments(query).session(options.session);
102
- }
103
-
104
- /**
105
- * Check existence
106
- */
107
- export async function exists(Model, query, options = {}) {
108
- return Model.exists(query).session(options.session);
109
- }
110
-
111
- // Utilities
112
- function parsePopulate(populate) {
113
- if (!populate) return [];
114
- if (typeof populate === 'string') {
115
- return populate.split(',').map(p => p.trim());
116
- }
117
- if (Array.isArray(populate)) {
118
- return populate.map(p => typeof p === 'string' ? p.trim() : p);
119
- }
120
- return [populate];
121
- }
122
-
123
- function parseSort(sort) {
124
- if (!sort) return { createdAt: -1 };
125
- const sortOrder = sort.startsWith('-') ? -1 : 1;
126
- const sortField = sort.startsWith('-') ? sort.substring(1) : sort;
127
- return { [sortField]: sortOrder };
128
- }
129
-
130
- function parseFilters(filters) {
131
- const parsed = {};
132
- for (const [key, value] of Object.entries(filters)) {
133
- parsed[key] = parseFilterValue(value);
134
- }
135
- return parsed;
136
- }
137
-
138
- function parseFilterValue(value) {
139
- if (typeof value === 'string') return value;
140
-
141
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
142
- const processed = {};
143
- for (const [operator, operatorValue] of Object.entries(value)) {
144
- if (operator === 'contains' || operator === 'like') {
145
- processed.$regex = operatorValue;
146
- processed.$options = 'i';
147
- } else {
148
- processed[operator.startsWith('$') ? operator : `$${operator}`] = operatorValue;
149
- }
150
- }
151
- return processed;
152
- }
153
-
154
- return value;
155
- }
156
-
145
+
146
+ /**
147
+ * Count documents matching query
148
+ *
149
+ * @param {Model} Model - Mongoose model
150
+ * @param {Record<string, any>} [query={}] - MongoDB query
151
+ * @param {Object} [options={}] - Query options
152
+ * @param {ClientSession} [options.session] - MongoDB session
153
+ * @returns {Promise<number>} Document count
154
+ */
155
+ export async function count(Model, query = {}, options = {}) {
156
+ return Model.countDocuments(query).session(options.session);
157
+ }
158
+
159
+ /**
160
+ * Check if document exists
161
+ *
162
+ * @param {Model} Model - Mongoose model
163
+ * @param {Record<string, any>} query - MongoDB query
164
+ * @param {Object} [options={}] - Query options
165
+ * @param {ClientSession} [options.session] - MongoDB session
166
+ * @returns {Promise<{_id: any} | null>} Document ID if exists, null otherwise
167
+ */
168
+ export async function exists(Model, query, options = {}) {
169
+ return Model.exists(query).session(options.session);
170
+ }
171
+
172
+ /**
173
+ * Parses populate parameter into Mongoose-compatible format
174
+ *
175
+ * @param {string|string[]|PopulateOptions|PopulateOptions[]} populate - Populate specification
176
+ * @returns {(string|PopulateOptions)[]} Normalized populate array
177
+ */
178
+ function parsePopulate(populate) {
179
+ if (!populate) return [];
180
+ if (typeof populate === 'string') {
181
+ return populate.split(',').map(/** @param {string} p */ (p) => p.trim());
182
+ }
183
+ if (Array.isArray(populate)) {
184
+ return populate.map(/** @param {string|PopulateOptions} p */ (p) => typeof p === 'string' ? p.trim() : p);
185
+ }
186
+ return [populate];
187
+ }
188
+