@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,13 +1,13 @@
1
- /**
2
- * Update Actions
3
- * Pure functions for document updates with optimizations
4
- */
5
-
6
- import createError from 'http-errors';
7
-
8
- /**
9
- * Update by ID
10
- */
1
+ /**
2
+ * Update Actions
3
+ * Pure functions for document updates with optimizations
4
+ */
5
+
6
+ import { createError } from '../utils/error.js';
7
+
8
+ /**
9
+ * Update by ID
10
+ */
11
11
  export async function update(Model, id, data, options = {}) {
12
12
  const document = await Model.findByIdAndUpdate(id, data, {
13
13
  new: true,
@@ -18,21 +18,21 @@ export async function update(Model, id, data, options = {}) {
18
18
  .select(options.select)
19
19
  .populate(parsePopulate(options.populate))
20
20
  .lean(options.lean);
21
-
22
- if (!document) {
23
- throw createError(404, 'Document not found');
24
- }
25
-
26
- return document;
27
- }
28
-
29
- /**
30
- * Update with query constraints (optimized)
31
- * Returns null if constraints not met (not an error)
32
- */
33
- export async function updateWithConstraints(Model, id, data, constraints = {}, options = {}) {
34
- const query = { _id: id, ...constraints };
35
-
21
+
22
+ if (!document) {
23
+ throw createError(404, 'Document not found');
24
+ }
25
+
26
+ return document;
27
+ }
28
+
29
+ /**
30
+ * Update with query constraints (optimized)
31
+ * Returns null if constraints not met (not an error)
32
+ */
33
+ export async function updateWithConstraints(Model, id, data, constraints = {}, options = {}) {
34
+ const query = { _id: id, ...constraints };
35
+
36
36
  const document = await Model.findOneAndUpdate(query, data, {
37
37
  new: true,
38
38
  runValidators: true,
@@ -42,88 +42,88 @@ export async function updateWithConstraints(Model, id, data, constraints = {}, o
42
42
  .select(options.select)
43
43
  .populate(parsePopulate(options.populate))
44
44
  .lean(options.lean);
45
-
46
- return document;
47
- }
48
-
49
- /**
50
- * Update with validation (smart optimization)
51
- * 1-query on success, 2-queries for detailed errors
52
- */
53
- export async function updateWithValidation(
54
- Model,
55
- id,
56
- data,
57
- validationOptions = {},
58
- options = {}
59
- ) {
60
- const { buildConstraints, validateUpdate } = validationOptions;
61
-
62
- // Try optimized update with constraints
63
- if (buildConstraints) {
64
- const constraints = buildConstraints(data);
65
- const document = await updateWithConstraints(Model, id, data, constraints, options);
66
-
67
- if (document) {
68
- return { success: true, data: document };
69
- }
70
- }
71
-
72
- // Fetch for validation
73
- const existing = await Model.findById(id)
74
- .select(options.select)
75
- .lean();
76
-
77
- if (!existing) {
78
- return {
79
- success: false,
80
- error: {
81
- code: 404,
82
- message: 'Document not found',
83
- },
84
- };
85
- }
86
-
87
- // Run custom validation
88
- if (validateUpdate) {
89
- const validation = validateUpdate(existing, data);
90
- if (!validation.valid) {
91
- return {
92
- success: false,
93
- error: {
94
- code: 403,
95
- message: validation.message || 'Update not allowed',
96
- violations: validation.violations,
97
- },
98
- };
99
- }
100
- }
101
-
102
- // Validation passed - perform update
103
- const updated = await update(Model, id, data, options);
104
- return { success: true, data: updated };
105
- }
106
-
107
- /**
108
- * Update many documents
109
- */
110
- export async function updateMany(Model, query, data, options = {}) {
45
+
46
+ return document;
47
+ }
48
+
49
+ /**
50
+ * Update with validation (smart optimization)
51
+ * 1-query on success, 2-queries for detailed errors
52
+ */
53
+ export async function updateWithValidation(
54
+ Model,
55
+ id,
56
+ data,
57
+ validationOptions = {},
58
+ options = {}
59
+ ) {
60
+ const { buildConstraints, validateUpdate } = validationOptions;
61
+
62
+ // Try optimized update with constraints
63
+ if (buildConstraints) {
64
+ const constraints = buildConstraints(data);
65
+ const document = await updateWithConstraints(Model, id, data, constraints, options);
66
+
67
+ if (document) {
68
+ return { success: true, data: document };
69
+ }
70
+ }
71
+
72
+ // Fetch for validation
73
+ const existing = await Model.findById(id)
74
+ .select(options.select)
75
+ .lean();
76
+
77
+ if (!existing) {
78
+ return {
79
+ success: false,
80
+ error: {
81
+ code: 404,
82
+ message: 'Document not found',
83
+ },
84
+ };
85
+ }
86
+
87
+ // Run custom validation
88
+ if (validateUpdate) {
89
+ const validation = validateUpdate(existing, data);
90
+ if (!validation.valid) {
91
+ return {
92
+ success: false,
93
+ error: {
94
+ code: 403,
95
+ message: validation.message || 'Update not allowed',
96
+ violations: validation.violations,
97
+ },
98
+ };
99
+ }
100
+ }
101
+
102
+ // Validation passed - perform update
103
+ const updated = await update(Model, id, data, options);
104
+ return { success: true, data: updated };
105
+ }
106
+
107
+ /**
108
+ * Update many documents
109
+ */
110
+ export async function updateMany(Model, query, data, options = {}) {
111
111
  const result = await Model.updateMany(query, data, {
112
112
  runValidators: true,
113
113
  session: options.session,
114
114
  ...(options.updatePipeline !== undefined ? { updatePipeline: options.updatePipeline } : {}),
115
115
  });
116
-
117
- return {
118
- matchedCount: result.matchedCount,
119
- modifiedCount: result.modifiedCount,
120
- };
121
- }
122
-
123
- /**
124
- * Update by query
125
- */
126
- export async function updateByQuery(Model, query, data, options = {}) {
116
+
117
+ return {
118
+ matchedCount: result.matchedCount,
119
+ modifiedCount: result.modifiedCount,
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Update by query
125
+ */
126
+ export async function updateByQuery(Model, query, data, options = {}) {
127
127
  const document = await Model.findOneAndUpdate(query, data, {
128
128
  new: true,
129
129
  runValidators: true,
@@ -132,45 +132,45 @@ export async function updateByQuery(Model, query, data, options = {}) {
132
132
  })
133
133
  .select(options.select)
134
134
  .populate(parsePopulate(options.populate))
135
- .lean(options.lean);
136
-
137
- if (!document && options.throwOnNotFound !== false) {
138
- throw createError(404, 'Document not found');
139
- }
140
-
141
- return document;
142
- }
143
-
144
- /**
145
- * Increment field
146
- */
147
- export async function increment(Model, id, field, value = 1, options = {}) {
148
- return update(Model, id, { $inc: { [field]: value } }, options);
149
- }
150
-
151
- /**
152
- * Push to array
153
- */
154
- export async function pushToArray(Model, id, field, value, options = {}) {
155
- return update(Model, id, { $push: { [field]: value } }, options);
156
- }
157
-
158
- /**
159
- * Pull from array
160
- */
161
- export async function pullFromArray(Model, id, field, value, options = {}) {
162
- return update(Model, id, { $pull: { [field]: value } }, options);
163
- }
164
-
165
- // Utilities
166
- function parsePopulate(populate) {
167
- if (!populate) return [];
168
- if (typeof populate === 'string') {
169
- return populate.split(',').map(p => p.trim());
170
- }
171
- if (Array.isArray(populate)) {
172
- return populate.map(p => typeof p === 'string' ? p.trim() : p);
173
- }
174
- return [populate];
175
- }
176
-
135
+ .lean(options.lean);
136
+
137
+ if (!document && options.throwOnNotFound !== false) {
138
+ throw createError(404, 'Document not found');
139
+ }
140
+
141
+ return document;
142
+ }
143
+
144
+ /**
145
+ * Increment field
146
+ */
147
+ export async function increment(Model, id, field, value = 1, options = {}) {
148
+ return update(Model, id, { $inc: { [field]: value } }, options);
149
+ }
150
+
151
+ /**
152
+ * Push to array
153
+ */
154
+ export async function pushToArray(Model, id, field, value, options = {}) {
155
+ return update(Model, id, { $push: { [field]: value } }, options);
156
+ }
157
+
158
+ /**
159
+ * Pull from array
160
+ */
161
+ export async function pullFromArray(Model, id, field, value, options = {}) {
162
+ return update(Model, id, { $pull: { [field]: value } }, options);
163
+ }
164
+
165
+ // Utilities
166
+ function parsePopulate(populate) {
167
+ if (!populate) return [];
168
+ if (typeof populate === 'string') {
169
+ return populate.split(',').map(p => p.trim());
170
+ }
171
+ if (Array.isArray(populate)) {
172
+ return populate.map(p => typeof p === 'string' ? p.trim() : p);
173
+ }
174
+ return [populate];
175
+ }
176
+
@@ -1,146 +1,146 @@
1
- /**
2
- * Lifecycle Hooks
3
- * Event system for repository actions
4
- */
5
-
6
- import { EventEmitter } from 'events';
7
-
8
- export class RepositoryLifecycle extends EventEmitter {
9
- constructor() {
10
- super();
11
- this.hooks = new Map();
12
- }
13
-
14
- /**
15
- * Register hook
16
- */
17
- on(event, handler) {
18
- if (!this.hooks.has(event)) {
19
- this.hooks.set(event, []);
20
- }
21
- this.hooks.get(event).push(handler);
22
- return super.on(event, handler);
23
- }
24
-
25
- /**
26
- * Execute hooks before action
27
- */
28
- async runBeforeHooks(action, context) {
29
- const event = `before:${action}`;
30
- await this.emit(event, context);
31
-
32
- const hooks = this.hooks.get(event) || [];
33
- for (const hook of hooks) {
34
- await hook(context);
35
- }
36
- }
37
-
38
- /**
39
- * Execute hooks after action
40
- */
41
- async runAfterHooks(action, context, result) {
42
- const event = `after:${action}`;
43
- await this.emit(event, context, result);
44
-
45
- const hooks = this.hooks.get(event) || [];
46
- for (const hook of hooks) {
47
- await hook(context, result);
48
- }
49
- }
50
-
51
- /**
52
- * Execute hooks on error
53
- */
54
- async runErrorHooks(action, context, error) {
55
- const event = `error:${action}`;
56
- await this.emit(event, context, error);
57
-
58
- const hooks = this.hooks.get(event) || [];
59
- for (const hook of hooks) {
60
- await hook(context, error);
61
- }
62
- }
63
- }
64
-
65
- /**
66
- * Hook decorators for common patterns
67
- */
68
- export const hooks = {
69
- /**
70
- * Auto-timestamp before create/update
71
- */
72
- autoTimestamp: () => ({
73
- 'before:create': (context) => {
74
- context.data.createdAt = new Date();
75
- context.data.updatedAt = new Date();
76
- },
77
- 'before:update': (context) => {
78
- context.data.updatedAt = new Date();
79
- },
80
- }),
81
-
82
- /**
83
- * Auto-inject user context
84
- */
85
- autoUser: (userField = 'userId') => ({
86
- 'before:create': (context) => {
87
- if (context.user && !context.data[userField]) {
88
- context.data[userField] = context.user._id || context.user.id;
89
- }
90
- },
91
- }),
92
-
93
- /**
94
- * Auto-inject organization scope
95
- */
96
- autoOrganization: (orgField = 'organizationId') => ({
97
- 'before:create': (context) => {
98
- if (context.organizationId && !context.data[orgField]) {
99
- context.data[orgField] = context.organizationId;
100
- }
101
- },
102
- }),
103
-
104
- /**
105
- * Audit log
106
- */
107
- auditLog: (logger) => ({
108
- 'after:create': (context, result) => {
109
- logger.info('Document created', {
110
- model: context.model,
111
- id: result._id,
112
- user: context.user?.id,
113
- });
114
- },
115
- 'after:update': (context, result) => {
116
- logger.info('Document updated', {
117
- model: context.model,
118
- id: result._id,
119
- user: context.user?.id,
120
- });
121
- },
122
- 'after:delete': (context, result) => {
123
- logger.info('Document deleted', {
124
- model: context.model,
125
- user: context.user?.id,
126
- });
127
- },
128
- }),
129
-
130
- /**
131
- * Cache invalidation
132
- */
133
- cacheInvalidation: (cache) => ({
134
- 'after:create': async (context, result) => {
135
- await cache.invalidate(`${context.model}:*`);
136
- },
137
- 'after:update': async (context, result) => {
138
- await cache.invalidate(`${context.model}:${result._id}`);
139
- await cache.invalidate(`${context.model}:*`);
140
- },
141
- 'after:delete': async (context) => {
142
- await cache.invalidate(`${context.model}:*`);
143
- },
144
- }),
145
- };
146
-
1
+ /**
2
+ * Lifecycle Hooks
3
+ * Event system for repository actions
4
+ */
5
+
6
+ import { EventEmitter } from 'events';
7
+
8
+ export class RepositoryLifecycle extends EventEmitter {
9
+ constructor() {
10
+ super();
11
+ this.hooks = new Map();
12
+ }
13
+
14
+ /**
15
+ * Register hook
16
+ */
17
+ on(event, handler) {
18
+ if (!this.hooks.has(event)) {
19
+ this.hooks.set(event, []);
20
+ }
21
+ this.hooks.get(event).push(handler);
22
+ return super.on(event, handler);
23
+ }
24
+
25
+ /**
26
+ * Execute hooks before action
27
+ */
28
+ async runBeforeHooks(action, context) {
29
+ const event = `before:${action}`;
30
+ await this.emit(event, context);
31
+
32
+ const hooks = this.hooks.get(event) || [];
33
+ for (const hook of hooks) {
34
+ await hook(context);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Execute hooks after action
40
+ */
41
+ async runAfterHooks(action, context, result) {
42
+ const event = `after:${action}`;
43
+ await this.emit(event, context, result);
44
+
45
+ const hooks = this.hooks.get(event) || [];
46
+ for (const hook of hooks) {
47
+ await hook(context, result);
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Execute hooks on error
53
+ */
54
+ async runErrorHooks(action, context, error) {
55
+ const event = `error:${action}`;
56
+ await this.emit(event, context, error);
57
+
58
+ const hooks = this.hooks.get(event) || [];
59
+ for (const hook of hooks) {
60
+ await hook(context, error);
61
+ }
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Hook decorators for common patterns
67
+ */
68
+ export const hooks = {
69
+ /**
70
+ * Auto-timestamp before create/update
71
+ */
72
+ autoTimestamp: () => ({
73
+ 'before:create': (context) => {
74
+ context.data.createdAt = new Date();
75
+ context.data.updatedAt = new Date();
76
+ },
77
+ 'before:update': (context) => {
78
+ context.data.updatedAt = new Date();
79
+ },
80
+ }),
81
+
82
+ /**
83
+ * Auto-inject user context
84
+ */
85
+ autoUser: (userField = 'userId') => ({
86
+ 'before:create': (context) => {
87
+ if (context.user && !context.data[userField]) {
88
+ context.data[userField] = context.user._id || context.user.id;
89
+ }
90
+ },
91
+ }),
92
+
93
+ /**
94
+ * Auto-inject organization scope
95
+ */
96
+ autoOrganization: (orgField = 'organizationId') => ({
97
+ 'before:create': (context) => {
98
+ if (context.organizationId && !context.data[orgField]) {
99
+ context.data[orgField] = context.organizationId;
100
+ }
101
+ },
102
+ }),
103
+
104
+ /**
105
+ * Audit log
106
+ */
107
+ auditLog: (logger) => ({
108
+ 'after:create': (context, result) => {
109
+ logger.info('Document created', {
110
+ model: context.model,
111
+ id: result._id,
112
+ user: context.user?.id,
113
+ });
114
+ },
115
+ 'after:update': (context, result) => {
116
+ logger.info('Document updated', {
117
+ model: context.model,
118
+ id: result._id,
119
+ user: context.user?.id,
120
+ });
121
+ },
122
+ 'after:delete': (context, result) => {
123
+ logger.info('Document deleted', {
124
+ model: context.model,
125
+ user: context.user?.id,
126
+ });
127
+ },
128
+ }),
129
+
130
+ /**
131
+ * Cache invalidation
132
+ */
133
+ cacheInvalidation: (cache) => ({
134
+ 'after:create': async (context, result) => {
135
+ await cache.invalidate(`${context.model}:*`);
136
+ },
137
+ 'after:update': async (context, result) => {
138
+ await cache.invalidate(`${context.model}:${result._id}`);
139
+ await cache.invalidate(`${context.model}:*`);
140
+ },
141
+ 'after:delete': async (context) => {
142
+ await cache.invalidate(`${context.model}:*`);
143
+ },
144
+ }),
145
+ };
146
+