@classytic/mongokit 1.0.2 → 2.1.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 (64) hide show
  1. package/README.md +772 -151
  2. package/dist/actions/index.cjs +479 -0
  3. package/dist/actions/index.cjs.map +1 -0
  4. package/dist/actions/index.d.cts +3 -0
  5. package/dist/actions/index.d.ts +3 -0
  6. package/dist/actions/index.js +473 -0
  7. package/dist/actions/index.js.map +1 -0
  8. package/dist/index-BfVJZF-3.d.cts +337 -0
  9. package/dist/index-CgOJ2pqz.d.ts +337 -0
  10. package/dist/index.cjs +2142 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +239 -0
  13. package/dist/index.d.ts +239 -0
  14. package/dist/index.js +2108 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/memory-cache-DG2oSSbx.d.ts +142 -0
  17. package/dist/memory-cache-DqfFfKes.d.cts +142 -0
  18. package/dist/pagination/PaginationEngine.cjs +375 -0
  19. package/dist/pagination/PaginationEngine.cjs.map +1 -0
  20. package/dist/pagination/PaginationEngine.d.cts +117 -0
  21. package/dist/pagination/PaginationEngine.d.ts +117 -0
  22. package/dist/pagination/PaginationEngine.js +369 -0
  23. package/dist/pagination/PaginationEngine.js.map +1 -0
  24. package/dist/plugins/index.cjs +874 -0
  25. package/dist/plugins/index.cjs.map +1 -0
  26. package/dist/plugins/index.d.cts +275 -0
  27. package/dist/plugins/index.d.ts +275 -0
  28. package/dist/plugins/index.js +857 -0
  29. package/dist/plugins/index.js.map +1 -0
  30. package/dist/types-Nxhmi1aI.d.cts +510 -0
  31. package/dist/types-Nxhmi1aI.d.ts +510 -0
  32. package/dist/utils/index.cjs +667 -0
  33. package/dist/utils/index.cjs.map +1 -0
  34. package/dist/utils/index.d.cts +189 -0
  35. package/dist/utils/index.d.ts +189 -0
  36. package/dist/utils/index.js +643 -0
  37. package/dist/utils/index.js.map +1 -0
  38. package/package.json +54 -24
  39. package/src/Repository.js +0 -225
  40. package/src/actions/aggregate.js +0 -191
  41. package/src/actions/create.js +0 -59
  42. package/src/actions/delete.js +0 -88
  43. package/src/actions/index.js +0 -11
  44. package/src/actions/read.js +0 -156
  45. package/src/actions/update.js +0 -176
  46. package/src/hooks/lifecycle.js +0 -146
  47. package/src/index.js +0 -60
  48. package/src/plugins/aggregate-helpers.plugin.js +0 -71
  49. package/src/plugins/audit-log.plugin.js +0 -60
  50. package/src/plugins/batch-operations.plugin.js +0 -66
  51. package/src/plugins/field-filter.plugin.js +0 -27
  52. package/src/plugins/index.js +0 -19
  53. package/src/plugins/method-registry.plugin.js +0 -140
  54. package/src/plugins/mongo-operations.plugin.js +0 -313
  55. package/src/plugins/soft-delete.plugin.js +0 -46
  56. package/src/plugins/subdocument.plugin.js +0 -66
  57. package/src/plugins/timestamp.plugin.js +0 -19
  58. package/src/plugins/validation-chain.plugin.js +0 -145
  59. package/src/utils/field-selection.js +0 -156
  60. package/src/utils/index.js +0 -12
  61. package/types/actions/index.d.ts +0 -121
  62. package/types/index.d.ts +0 -104
  63. package/types/plugins/index.d.ts +0 -88
  64. package/types/utils/index.d.ts +0 -24
@@ -1,146 +0,0 @@
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
-
package/src/index.js DELETED
@@ -1,60 +0,0 @@
1
- /**
2
- * Repository Pattern - Data Access Layer
3
- *
4
- * Event-driven, plugin-based abstraction for MongoDB operations
5
- * Inspired by Meta & Stripe's repository patterns
6
- *
7
- * @module common/repositories
8
- *
9
- * Documentation:
10
- * - README.md - Main documentation (concise overview)
11
- * - QUICK_REFERENCE.md - One-page cheat sheet
12
- * - EXAMPLES.md - Detailed examples and patterns
13
- */
14
-
15
- /**
16
- * MongoKit - Event-driven repository pattern for MongoDB
17
- *
18
- * @module @classytic/mongokit
19
- * @author Sadman Chowdhury (Github: @siam923)
20
- * @license MIT
21
- */
22
-
23
- export { Repository } from './Repository.js';
24
-
25
- // Plugins
26
- export { fieldFilterPlugin } from './plugins/field-filter.plugin.js';
27
- export { timestampPlugin } from './plugins/timestamp.plugin.js';
28
- export { auditLogPlugin } from './plugins/audit-log.plugin.js';
29
- export { softDeletePlugin } from './plugins/soft-delete.plugin.js';
30
- export { methodRegistryPlugin } from './plugins/method-registry.plugin.js';
31
- export {
32
- validationChainPlugin,
33
- blockIf,
34
- requireField,
35
- autoInject,
36
- immutableField,
37
- uniqueField,
38
- } from './plugins/validation-chain.plugin.js';
39
- export { mongoOperationsPlugin } from './plugins/mongo-operations.plugin.js';
40
- export { batchOperationsPlugin } from './plugins/batch-operations.plugin.js';
41
- export { aggregateHelpersPlugin } from './plugins/aggregate-helpers.plugin.js';
42
- export { subdocumentPlugin } from './plugins/subdocument.plugin.js';
43
-
44
- // Utilities
45
- export {
46
- getFieldsForUser,
47
- getMongooseProjection,
48
- filterResponseData,
49
- createFieldPreset,
50
- } from './utils/field-selection.js';
51
-
52
- export * as actions from './actions/index.js';
53
-
54
- import { Repository } from './Repository.js';
55
-
56
- export const createRepository = (Model, plugins = []) => {
57
- return new Repository(Model, plugins);
58
- };
59
-
60
- export default Repository;
@@ -1,71 +0,0 @@
1
- /**
2
- * Aggregate Helpers Plugin
3
- * Adds common aggregation helper methods
4
- */
5
-
6
- export const aggregateHelpersPlugin = () => ({
7
- name: 'aggregate-helpers',
8
-
9
- apply(repo) {
10
- if (!repo.registerMethod) {
11
- throw new Error('aggregateHelpersPlugin requires methodRegistryPlugin');
12
- }
13
-
14
- /**
15
- * Group by field
16
- */
17
- repo.registerMethod('groupBy', async function (field, options = {}) {
18
- const pipeline = [
19
- { $group: { _id: `$${field}`, count: { $sum: 1 } } },
20
- { $sort: { count: -1 } }
21
- ];
22
-
23
- if (options.limit) {
24
- pipeline.push({ $limit: options.limit });
25
- }
26
-
27
- return this.aggregate(pipeline, options);
28
- });
29
-
30
- // Helper: Generic aggregation operation
31
- const aggregateOperation = async function (field, operator, resultKey, query = {}, options = {}) {
32
- const pipeline = [
33
- { $match: query },
34
- { $group: { _id: null, [resultKey]: { [operator]: `$${field}` } } }
35
- ];
36
-
37
- const result = await this.aggregate(pipeline, options);
38
- return result[0]?.[resultKey] || 0;
39
- };
40
-
41
- /**
42
- * Sum field values
43
- */
44
- repo.registerMethod('sum', async function (field, query = {}, options = {}) {
45
- return aggregateOperation.call(this, field, '$sum', 'total', query, options);
46
- });
47
-
48
- /**
49
- * Average field values
50
- */
51
- repo.registerMethod('average', async function (field, query = {}, options = {}) {
52
- return aggregateOperation.call(this, field, '$avg', 'avg', query, options);
53
- });
54
-
55
- /**
56
- * Get minimum value
57
- */
58
- repo.registerMethod('min', async function (field, query = {}, options = {}) {
59
- return aggregateOperation.call(this, field, '$min', 'min', query, options);
60
- });
61
-
62
- /**
63
- * Get maximum value
64
- */
65
- repo.registerMethod('max', async function (field, query = {}, options = {}) {
66
- return aggregateOperation.call(this, field, '$max', 'max', query, options);
67
- });
68
- }
69
- });
70
-
71
- export default aggregateHelpersPlugin;
@@ -1,60 +0,0 @@
1
- export const auditLogPlugin = (logger) => ({
2
- name: 'auditLog',
3
-
4
- apply(repo) {
5
- repo.on('after:create', ({ context, result }) => {
6
- logger?.info?.('Document created', {
7
- model: context.model || repo.model,
8
- id: result._id,
9
- userId: context.user?._id || context.user?.id,
10
- organizationId: context.organizationId,
11
- });
12
- });
13
-
14
- repo.on('after:update', ({ context, result }) => {
15
- logger?.info?.('Document updated', {
16
- model: context.model || repo.model,
17
- id: context.id || result._id,
18
- userId: context.user?._id || context.user?.id,
19
- organizationId: context.organizationId,
20
- });
21
- });
22
-
23
- repo.on('after:delete', ({ context, result }) => {
24
- logger?.info?.('Document deleted', {
25
- model: context.model || repo.model,
26
- id: context.id,
27
- userId: context.user?._id || context.user?.id,
28
- organizationId: context.organizationId,
29
- });
30
- });
31
-
32
- repo.on('error:create', ({ context, error }) => {
33
- logger?.error?.('Create failed', {
34
- model: context.model || repo.model,
35
- error: error.message,
36
- userId: context.user?._id || context.user?.id,
37
- });
38
- });
39
-
40
- repo.on('error:update', ({ context, error }) => {
41
- logger?.error?.('Update failed', {
42
- model: context.model || repo.model,
43
- id: context.id,
44
- error: error.message,
45
- userId: context.user?._id || context.user?.id,
46
- });
47
- });
48
-
49
- repo.on('error:delete', ({ context, error }) => {
50
- logger?.error?.('Delete failed', {
51
- model: context.model || repo.model,
52
- id: context.id,
53
- error: error.message,
54
- userId: context.user?._id || context.user?.id,
55
- });
56
- });
57
- },
58
- });
59
-
60
- export default auditLogPlugin;
@@ -1,66 +0,0 @@
1
- /**
2
- * Batch Operations Plugin
3
- * Adds bulk update/delete operations with proper event emission
4
- */
5
-
6
- export const batchOperationsPlugin = () => ({
7
- name: 'batch-operations',
8
-
9
- apply(repo) {
10
- if (!repo.registerMethod) {
11
- throw new Error('batchOperationsPlugin requires methodRegistryPlugin');
12
- }
13
-
14
- /**
15
- * Update multiple documents
16
- * @param {Object} query - MongoDB query to match documents
17
- * @param {Object} data - Update data
18
- * @param {Object} options - Additional options (session, context)
19
- * @returns {Promise<Object>} MongoDB update result
20
- */
21
- repo.registerMethod('updateMany', async function (query, data, options = {}) {
22
- const context = await this._buildContext('updateMany', { query, data, options });
23
-
24
- try {
25
- this.emit('before:updateMany', context);
26
-
27
- const result = await this.Model.updateMany(query, data, {
28
- runValidators: true,
29
- session: options.session,
30
- }).exec();
31
-
32
- this.emit('after:updateMany', { context, result });
33
- return result;
34
- } catch (error) {
35
- this.emit('error:updateMany', { context, error });
36
- throw this._handleError(error);
37
- }
38
- });
39
-
40
- /**
41
- * Delete multiple documents
42
- * @param {Object} query - MongoDB query to match documents
43
- * @param {Object} options - Additional options (session, context)
44
- * @returns {Promise<Object>} MongoDB delete result
45
- */
46
- repo.registerMethod('deleteMany', async function (query, options = {}) {
47
- const context = await this._buildContext('deleteMany', { query, options });
48
-
49
- try {
50
- this.emit('before:deleteMany', context);
51
-
52
- const result = await this.Model.deleteMany(query, {
53
- session: options.session,
54
- }).exec();
55
-
56
- this.emit('after:deleteMany', { context, result });
57
- return result;
58
- } catch (error) {
59
- this.emit('error:deleteMany', { context, error });
60
- throw this._handleError(error);
61
- }
62
- });
63
- }
64
- });
65
-
66
- export default batchOperationsPlugin;
@@ -1,27 +0,0 @@
1
- import { getFieldsForUser } from '../utils/field-selection.js';
2
-
3
- export const fieldFilterPlugin = (fieldPreset) => ({
4
- name: 'fieldFilter',
5
-
6
- apply(repo) {
7
- const applyFieldFiltering = (context) => {
8
- if (!fieldPreset) return;
9
-
10
- const user = context.context?.user || context.user;
11
- const fields = getFieldsForUser(user, fieldPreset);
12
- const presetSelect = fields.join(' ');
13
-
14
- if (context.select) {
15
- context.select = `${presetSelect} ${context.select}`;
16
- } else {
17
- context.select = presetSelect;
18
- }
19
- };
20
-
21
- repo.on('before:getAll', applyFieldFiltering);
22
- repo.on('before:getById', applyFieldFiltering);
23
- repo.on('before:getByQuery', applyFieldFiltering);
24
- },
25
- });
26
-
27
- export default fieldFilterPlugin;
@@ -1,19 +0,0 @@
1
- // Core plugins
2
- export { fieldFilterPlugin } from './field-filter.plugin.js';
3
- export { timestampPlugin } from './timestamp.plugin.js';
4
- export { auditLogPlugin } from './audit-log.plugin.js';
5
- export { softDeletePlugin } from './soft-delete.plugin.js';
6
- export { methodRegistryPlugin } from './method-registry.plugin.js';
7
- export {
8
- validationChainPlugin,
9
- blockIf,
10
- requireField,
11
- autoInject,
12
- immutableField,
13
- uniqueField,
14
- } from './validation-chain.plugin.js';
15
- export { mongoOperationsPlugin } from './mongo-operations.plugin.js';
16
- export { batchOperationsPlugin } from './batch-operations.plugin.js';
17
- export { aggregateHelpersPlugin } from './aggregate-helpers.plugin.js';
18
- export { subdocumentPlugin } from './subdocument.plugin.js';
19
-
@@ -1,140 +0,0 @@
1
- /**
2
- * Method Registry Plugin
3
- *
4
- * Enables plugins to dynamically add methods to repository instances.
5
- * Foundation for extensibility - allows other plugins to extend repositories
6
- * with custom methods while maintaining type safety and proper binding.
7
- *
8
- * **Pattern:** Inspired by Stripe's extension system
9
- * **Philosophy:** Repositories start minimal, plugins add capabilities
10
- *
11
- * @module common/repositories/plugins/method-registry
12
- *
13
- * @example Basic Usage
14
- * ```js
15
- * import { Repository } from '../Repository.js';
16
- * import { methodRegistryPlugin } from './method-registry.plugin.js';
17
- *
18
- * class UserRepository extends Repository {
19
- * constructor() {
20
- * super(User, [methodRegistryPlugin()]);
21
- *
22
- * // Now you can register custom methods
23
- * this.registerMethod('findActive', async function() {
24
- * return this.getAll({ filters: { status: 'active' } });
25
- * });
26
- * }
27
- * }
28
- * ```
29
- *
30
- * @example Plugin Using Method Registry
31
- * ```js
32
- * // Other plugins can use registerMethod to add functionality
33
- * export const mongoOperationsPlugin = () => ({
34
- * name: 'mongo-operations',
35
- * apply(repo) {
36
- * repo.registerMethod('increment', async function(id, field, value = 1, options = {}) {
37
- * return this.update(id, { $inc: { [field]: value } }, options);
38
- * });
39
- * }
40
- * });
41
- * ```
42
- */
43
-
44
- /**
45
- * Method Registry Plugin
46
- *
47
- * Adds `registerMethod()` to repository instance, allowing dynamic method addition.
48
- *
49
- * @returns {Object} Plugin configuration
50
- */
51
- export const methodRegistryPlugin = () => ({
52
- name: 'method-registry',
53
-
54
- apply(repo) {
55
- /**
56
- * Register a new method on the repository instance
57
- *
58
- * **Rules:**
59
- * - Method name must not conflict with existing methods
60
- * - Method is automatically bound to repository instance
61
- * - Method has access to all repository methods via `this`
62
- * - Async methods are recommended for consistency
63
- *
64
- * @param {string} name - Method name
65
- * @param {Function} fn - Method implementation (will be bound to repo)
66
- * @throws {Error} If method name already exists
67
- *
68
- * @example
69
- * repo.registerMethod('findByEmail', async function(email) {
70
- * return this.getByQuery({ email }, { lean: true });
71
- * });
72
- *
73
- * @example With options
74
- * repo.registerMethod('incrementViews', async function(id, amount = 1) {
75
- * return this.update(id, { $inc: { views: amount } });
76
- * });
77
- */
78
- repo.registerMethod = function (name, fn) {
79
- // Check for naming conflicts
80
- if (repo[name]) {
81
- throw new Error(
82
- `Cannot register method '${name}': Method already exists on repository. ` +
83
- `Choose a different name or use a plugin that doesn't conflict.`
84
- );
85
- }
86
-
87
- // Validate method name
88
- if (!name || typeof name !== 'string') {
89
- throw new Error('Method name must be a non-empty string');
90
- }
91
-
92
- // Validate function
93
- if (typeof fn !== 'function') {
94
- throw new Error(`Method '${name}' must be a function`);
95
- }
96
-
97
- // Bind function to repository instance
98
- repo[name] = fn.bind(repo);
99
-
100
- // Emit event for plugin system awareness
101
- repo.emit('method:registered', { name, fn });
102
- };
103
-
104
- /**
105
- * Check if a method is registered
106
- *
107
- * @param {string} name - Method name to check
108
- * @returns {boolean} True if method exists
109
- *
110
- * @example
111
- * if (repo.hasMethod('increment')) {
112
- * await repo.increment(id, 'count', 1);
113
- * }
114
- */
115
- repo.hasMethod = function (name) {
116
- return typeof repo[name] === 'function';
117
- };
118
-
119
- /**
120
- * Get list of all dynamically registered methods
121
- *
122
- * @returns {Array<string>} Array of method names
123
- *
124
- * @example
125
- * const methods = repo.getRegisteredMethods();
126
- * console.log('Available methods:', methods);
127
- */
128
- repo.getRegisteredMethods = function () {
129
- const registeredMethods = [];
130
-
131
- repo.on('method:registered', ({ name }) => {
132
- registeredMethods.push(name);
133
- });
134
-
135
- return registeredMethods;
136
- };
137
- }
138
- });
139
-
140
- export default methodRegistryPlugin;