@classytic/mongokit 2.0.0 → 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.
- package/README.md +221 -7
- package/dist/actions/index.cjs +479 -0
- package/dist/actions/index.cjs.map +1 -0
- package/dist/actions/index.d.cts +3 -0
- package/dist/actions/index.d.ts +3 -0
- package/dist/actions/index.js +473 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/index-BfVJZF-3.d.cts +337 -0
- package/dist/index-CgOJ2pqz.d.ts +337 -0
- package/dist/index.cjs +2142 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +239 -0
- package/dist/index.d.ts +239 -0
- package/dist/index.js +2108 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-cache-DG2oSSbx.d.ts +142 -0
- package/dist/memory-cache-DqfFfKes.d.cts +142 -0
- package/dist/pagination/PaginationEngine.cjs +375 -0
- package/dist/pagination/PaginationEngine.cjs.map +1 -0
- package/dist/pagination/PaginationEngine.d.cts +117 -0
- package/dist/pagination/PaginationEngine.d.ts +117 -0
- package/dist/pagination/PaginationEngine.js +369 -0
- package/dist/pagination/PaginationEngine.js.map +1 -0
- package/dist/plugins/index.cjs +874 -0
- package/dist/plugins/index.cjs.map +1 -0
- package/dist/plugins/index.d.cts +275 -0
- package/dist/plugins/index.d.ts +275 -0
- package/dist/plugins/index.js +857 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/types-Nxhmi1aI.d.cts +510 -0
- package/dist/types-Nxhmi1aI.d.ts +510 -0
- package/dist/utils/index.cjs +667 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +189 -0
- package/dist/utils/index.d.ts +189 -0
- package/dist/utils/index.js +643 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +44 -21
- package/src/Repository.js +0 -296
- package/src/actions/aggregate.js +0 -266
- package/src/actions/create.js +0 -59
- package/src/actions/delete.js +0 -88
- package/src/actions/index.js +0 -11
- package/src/actions/read.js +0 -188
- package/src/actions/update.js +0 -176
- package/src/hooks/lifecycle.js +0 -146
- package/src/index.js +0 -71
- package/src/pagination/PaginationEngine.js +0 -348
- package/src/pagination/utils/cursor.js +0 -119
- package/src/pagination/utils/filter.js +0 -42
- package/src/pagination/utils/limits.js +0 -82
- package/src/pagination/utils/sort.js +0 -101
- package/src/plugins/aggregate-helpers.plugin.js +0 -71
- package/src/plugins/audit-log.plugin.js +0 -60
- package/src/plugins/batch-operations.plugin.js +0 -66
- package/src/plugins/field-filter.plugin.js +0 -27
- package/src/plugins/index.js +0 -19
- package/src/plugins/method-registry.plugin.js +0 -140
- package/src/plugins/mongo-operations.plugin.js +0 -317
- package/src/plugins/soft-delete.plugin.js +0 -46
- package/src/plugins/subdocument.plugin.js +0 -66
- package/src/plugins/timestamp.plugin.js +0 -19
- package/src/plugins/validation-chain.plugin.js +0 -145
- package/src/types.d.ts +0 -87
- package/src/utils/error.js +0 -12
- package/src/utils/field-selection.js +0 -156
- package/src/utils/index.js +0 -12
- package/types/Repository.d.ts +0 -95
- package/types/Repository.d.ts.map +0 -1
- package/types/actions/aggregate.d.ts +0 -112
- package/types/actions/aggregate.d.ts.map +0 -1
- package/types/actions/create.d.ts +0 -21
- package/types/actions/create.d.ts.map +0 -1
- package/types/actions/delete.d.ts +0 -37
- package/types/actions/delete.d.ts.map +0 -1
- package/types/actions/index.d.ts +0 -6
- package/types/actions/index.d.ts.map +0 -1
- package/types/actions/read.d.ts +0 -135
- package/types/actions/read.d.ts.map +0 -1
- package/types/actions/update.d.ts +0 -58
- package/types/actions/update.d.ts.map +0 -1
- package/types/hooks/lifecycle.d.ts +0 -44
- package/types/hooks/lifecycle.d.ts.map +0 -1
- package/types/index.d.ts +0 -25
- package/types/index.d.ts.map +0 -1
- package/types/pagination/PaginationEngine.d.ts +0 -386
- package/types/pagination/PaginationEngine.d.ts.map +0 -1
- package/types/pagination/utils/cursor.d.ts +0 -40
- package/types/pagination/utils/cursor.d.ts.map +0 -1
- package/types/pagination/utils/filter.d.ts +0 -28
- package/types/pagination/utils/filter.d.ts.map +0 -1
- package/types/pagination/utils/limits.d.ts +0 -64
- package/types/pagination/utils/limits.d.ts.map +0 -1
- package/types/pagination/utils/sort.d.ts +0 -41
- package/types/pagination/utils/sort.d.ts.map +0 -1
- package/types/plugins/aggregate-helpers.plugin.d.ts +0 -6
- package/types/plugins/aggregate-helpers.plugin.d.ts.map +0 -1
- package/types/plugins/audit-log.plugin.d.ts +0 -6
- package/types/plugins/audit-log.plugin.d.ts.map +0 -1
- package/types/plugins/batch-operations.plugin.d.ts +0 -6
- package/types/plugins/batch-operations.plugin.d.ts.map +0 -1
- package/types/plugins/field-filter.plugin.d.ts +0 -6
- package/types/plugins/field-filter.plugin.d.ts.map +0 -1
- package/types/plugins/index.d.ts +0 -11
- package/types/plugins/index.d.ts.map +0 -1
- package/types/plugins/method-registry.plugin.d.ts +0 -3
- package/types/plugins/method-registry.plugin.d.ts.map +0 -1
- package/types/plugins/mongo-operations.plugin.d.ts +0 -4
- package/types/plugins/mongo-operations.plugin.d.ts.map +0 -1
- package/types/plugins/soft-delete.plugin.d.ts +0 -6
- package/types/plugins/soft-delete.plugin.d.ts.map +0 -1
- package/types/plugins/subdocument.plugin.d.ts +0 -6
- package/types/plugins/subdocument.plugin.d.ts.map +0 -1
- package/types/plugins/timestamp.plugin.d.ts +0 -6
- package/types/plugins/timestamp.plugin.d.ts.map +0 -1
- package/types/plugins/validation-chain.plugin.d.ts +0 -31
- package/types/plugins/validation-chain.plugin.d.ts.map +0 -1
- package/types/utils/error.d.ts +0 -11
- package/types/utils/error.d.ts.map +0 -1
- package/types/utils/field-selection.d.ts +0 -9
- package/types/utils/field-selection.d.ts.map +0 -1
- package/types/utils/index.d.ts +0 -2
- package/types/utils/index.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
- ✅ **Zero external dependencies** (only Mongoose peer dependency)
|
|
12
12
|
- ✅ **Smart pagination** - auto-detects offset vs cursor-based
|
|
13
|
+
- ✅ **HTTP utilities** - query parser & schema generator for controllers
|
|
13
14
|
- ✅ **Event-driven** hooks for every operation
|
|
14
15
|
- ✅ **Plugin architecture** for reusable behaviors
|
|
15
16
|
- ✅ **TypeScript** first-class support with discriminated unions
|
|
16
|
-
- ✅ **
|
|
17
|
+
- ✅ **Optional caching** - Redis/Memcached with auto-invalidation
|
|
18
|
+
- ✅ **Battle-tested** in production with 182 passing tests
|
|
17
19
|
|
|
18
20
|
---
|
|
19
21
|
|
|
@@ -26,7 +28,13 @@ npm install @classytic/mongokit mongoose
|
|
|
26
28
|
> **Peer Dependencies:**
|
|
27
29
|
> - `mongoose ^8.0.0 || ^9.0.0` (supports both Mongoose 8 and 9)
|
|
28
30
|
|
|
29
|
-
**
|
|
31
|
+
**Available imports:**
|
|
32
|
+
```javascript
|
|
33
|
+
import { MongooseRepository } from '@classytic/mongokit'; // Core repository
|
|
34
|
+
import { queryParser, buildCrudSchemasFromModel } from '@classytic/mongokit/utils'; // HTTP utilities
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**That's it.** No additional pagination, validation, or query parsing libraries needed.
|
|
30
38
|
|
|
31
39
|
---
|
|
32
40
|
|
|
@@ -282,6 +290,101 @@ const feedView = await postRepo.getAll({
|
|
|
282
290
|
|
|
283
291
|
---
|
|
284
292
|
|
|
293
|
+
## 🌐 HTTP Utilities for Controllers & Routes
|
|
294
|
+
|
|
295
|
+
MongoKit provides utilities to quickly build production-ready controllers and routes for Express, Fastify, NestJS, and other frameworks.
|
|
296
|
+
|
|
297
|
+
### Query Parser
|
|
298
|
+
|
|
299
|
+
Parse HTTP query strings into MongoDB filters automatically:
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
import { queryParser } from '@classytic/mongokit/utils';
|
|
303
|
+
|
|
304
|
+
// Express/Fastify route
|
|
305
|
+
app.get('/users', async (req, res) => {
|
|
306
|
+
const { filters, limit, page, sort } = queryParser.parseQuery(req.query);
|
|
307
|
+
|
|
308
|
+
const result = await userRepo.getAll({ filters, limit, page, sort });
|
|
309
|
+
res.json(result);
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Supported query patterns:**
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# Simple filtering
|
|
317
|
+
GET /users?email=john@example.com&role=admin
|
|
318
|
+
|
|
319
|
+
# Operators
|
|
320
|
+
GET /users?age[gte]=18&age[lte]=65 # Range queries
|
|
321
|
+
GET /users?email[contains]=gmail # Text search
|
|
322
|
+
GET /users?role[in]=admin,user # Multiple values
|
|
323
|
+
GET /users?status[ne]=deleted # Not equal
|
|
324
|
+
|
|
325
|
+
# Pagination
|
|
326
|
+
GET /users?page=2&limit=50 # Offset pagination
|
|
327
|
+
GET /users?after=eyJfaWQiOiI2M... # Cursor pagination
|
|
328
|
+
|
|
329
|
+
# Sorting
|
|
330
|
+
GET /users?sort=-createdAt,name # Multi-field sort (- = descending)
|
|
331
|
+
|
|
332
|
+
# Combined
|
|
333
|
+
GET /users?role=admin&createdAt[gte]=2024-01-01&sort=-createdAt&limit=20
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Schema Generator (Fastify/OpenAPI)
|
|
337
|
+
|
|
338
|
+
Generate JSON schemas from Mongoose models with field rules:
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
import { buildCrudSchemasFromModel } from '@classytic/mongokit/utils';
|
|
342
|
+
|
|
343
|
+
const { crudSchemas } = buildCrudSchemasFromModel(UserModel, {
|
|
344
|
+
strictAdditionalProperties: true, // Reject unknown fields
|
|
345
|
+
fieldRules: {
|
|
346
|
+
organizationId: { immutable: true }, // Cannot be updated
|
|
347
|
+
status: { systemManaged: true }, // Omitted from create/update
|
|
348
|
+
email: { optional: false }, // Required field
|
|
349
|
+
},
|
|
350
|
+
create: {
|
|
351
|
+
omitFields: ['verifiedAt'], // Custom omissions
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Use in Fastify routes
|
|
356
|
+
fastify.post('/users', {
|
|
357
|
+
schema: crudSchemas.create,
|
|
358
|
+
}, async (request, reply) => {
|
|
359
|
+
const user = await userRepo.create(request.body);
|
|
360
|
+
return reply.status(201).send(user);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
fastify.get('/users', {
|
|
364
|
+
schema: crudSchemas.list,
|
|
365
|
+
}, async (request, reply) => {
|
|
366
|
+
const { filters, limit, page, sort } = queryParser.parseQuery(request.query);
|
|
367
|
+
const result = await userRepo.getAll({ filters, limit, page, sort });
|
|
368
|
+
return reply.send(result);
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Generated schemas:**
|
|
373
|
+
- `crudSchemas.create` - POST validation (body only)
|
|
374
|
+
- `crudSchemas.update` - PATCH validation (body + params)
|
|
375
|
+
- `crudSchemas.get` - GET by ID validation (params)
|
|
376
|
+
- `crudSchemas.list` - GET list validation (query)
|
|
377
|
+
- `crudSchemas.remove` - DELETE validation (params)
|
|
378
|
+
|
|
379
|
+
**Field Rules:**
|
|
380
|
+
- `immutable` - Field cannot be updated after creation (omitted from update schema)
|
|
381
|
+
- `systemManaged` - System-only field (omitted from both create and update schemas)
|
|
382
|
+
- `optional` - Remove from required array
|
|
383
|
+
|
|
384
|
+
**See full example:** [`examples/fastify-controller-example.js`](examples/fastify-controller-example.js)
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
285
388
|
## 📘 Complete API Reference
|
|
286
389
|
|
|
287
390
|
### CRUD Operations
|
|
@@ -391,6 +494,71 @@ const result = await repo.getAll({ page: 1, limit: 20 });
|
|
|
391
494
|
|
|
392
495
|
---
|
|
393
496
|
|
|
497
|
+
## 📊 Indexing Guide
|
|
498
|
+
|
|
499
|
+
**Critical:** MongoDB only auto-indexes `_id`. You must create indexes for efficient pagination.
|
|
500
|
+
|
|
501
|
+
### Single-Tenant Applications
|
|
502
|
+
|
|
503
|
+
```javascript
|
|
504
|
+
const PostSchema = new mongoose.Schema({
|
|
505
|
+
title: String,
|
|
506
|
+
publishedAt: { type: Date, default: Date.now }
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// Required for keyset pagination
|
|
510
|
+
PostSchema.index({ publishedAt: -1, _id: -1 });
|
|
511
|
+
// ^^^^^^^^^^^^^^ ^^^^^^
|
|
512
|
+
// Sort field Tie-breaker
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Multi-Tenant Applications
|
|
516
|
+
|
|
517
|
+
```javascript
|
|
518
|
+
const UserSchema = new mongoose.Schema({
|
|
519
|
+
organizationId: String,
|
|
520
|
+
email: String,
|
|
521
|
+
createdAt: { type: Date, default: Date.now }
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// Required for multi-tenant keyset pagination
|
|
525
|
+
UserSchema.index({ organizationId: 1, createdAt: -1, _id: -1 });
|
|
526
|
+
// ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^ ^^^^^^
|
|
527
|
+
// Tenant filter Sort field Tie-breaker
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Common Index Patterns
|
|
531
|
+
|
|
532
|
+
```javascript
|
|
533
|
+
// Basic sorting
|
|
534
|
+
Schema.index({ createdAt: -1, _id: -1 });
|
|
535
|
+
|
|
536
|
+
// Multi-tenant
|
|
537
|
+
Schema.index({ tenantId: 1, createdAt: -1, _id: -1 });
|
|
538
|
+
|
|
539
|
+
// Multi-tenant + status filter
|
|
540
|
+
Schema.index({ tenantId: 1, status: 1, createdAt: -1, _id: -1 });
|
|
541
|
+
|
|
542
|
+
// Text search
|
|
543
|
+
Schema.index({ title: 'text', content: 'text' });
|
|
544
|
+
Schema.index({ createdAt: -1, _id: -1 }); // Still need this for sorting
|
|
545
|
+
|
|
546
|
+
// Multi-field sort
|
|
547
|
+
Schema.index({ priority: -1, createdAt: -1, _id: -1 });
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Performance Impact
|
|
551
|
+
|
|
552
|
+
| Scenario | Without Index | With Index |
|
|
553
|
+
|----------|--------------|------------|
|
|
554
|
+
| 10K docs | ~50ms | ~5ms |
|
|
555
|
+
| 1M docs | ~5000ms | ~5ms |
|
|
556
|
+
| 100M docs | timeout | ~5ms |
|
|
557
|
+
|
|
558
|
+
**Rule:** Index = (tenant_field +) sort_field + _id
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
394
562
|
## 🔌 Built-in Plugins
|
|
395
563
|
|
|
396
564
|
### Field Filtering (Role-based Access)
|
|
@@ -474,6 +642,46 @@ class UserRepository extends Repository {
|
|
|
474
642
|
// All CUD operations automatically logged
|
|
475
643
|
```
|
|
476
644
|
|
|
645
|
+
### Caching (Redis, Memcached, or In-Memory)
|
|
646
|
+
|
|
647
|
+
Add caching with automatic invalidation on mutations:
|
|
648
|
+
|
|
649
|
+
```javascript
|
|
650
|
+
import { Repository, cachePlugin, createMemoryCache } from '@classytic/mongokit';
|
|
651
|
+
|
|
652
|
+
const userRepo = new Repository(UserModel, [
|
|
653
|
+
cachePlugin({
|
|
654
|
+
adapter: createMemoryCache(), // or your Redis adapter
|
|
655
|
+
ttl: 60, // 60 seconds default
|
|
656
|
+
byIdTtl: 300, // 5 min for getById
|
|
657
|
+
queryTtl: 30, // 30s for lists
|
|
658
|
+
})
|
|
659
|
+
]);
|
|
660
|
+
|
|
661
|
+
// Reads are cached automatically
|
|
662
|
+
const user = await userRepo.getById(id); // cached on second call
|
|
663
|
+
|
|
664
|
+
// Skip cache for fresh data
|
|
665
|
+
const fresh = await userRepo.getById(id, { skipCache: true });
|
|
666
|
+
|
|
667
|
+
// Mutations auto-invalidate cache
|
|
668
|
+
await userRepo.update(id, { name: 'New' });
|
|
669
|
+
|
|
670
|
+
// Manual invalidation (microservices)
|
|
671
|
+
await userRepo.invalidateCache(id); // single doc
|
|
672
|
+
await userRepo.invalidateAllCache(); // full model
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
**Redis adapter example:**
|
|
676
|
+
```javascript
|
|
677
|
+
const redisAdapter = {
|
|
678
|
+
async get(key) { return JSON.parse(await redis.get(key) || 'null'); },
|
|
679
|
+
async set(key, value, ttl) { await redis.setex(key, ttl, JSON.stringify(value)); },
|
|
680
|
+
async del(key) { await redis.del(key); },
|
|
681
|
+
async clear(pattern) { /* optional: bulk delete by pattern */ }
|
|
682
|
+
};
|
|
683
|
+
```
|
|
684
|
+
|
|
477
685
|
### More Plugins
|
|
478
686
|
|
|
479
687
|
- **`timestampPlugin()`** - Auto-manage `createdAt`/`updatedAt`
|
|
@@ -652,19 +860,24 @@ await repo.getAll({ page: 1000, limit: 50 }); // O(50000)
|
|
|
652
860
|
await repo.getAll({ after: cursor, limit: 50 }); // O(1)
|
|
653
861
|
```
|
|
654
862
|
|
|
655
|
-
### 2. Create
|
|
863
|
+
### 2. Create Required Indexes
|
|
864
|
+
|
|
865
|
+
**IMPORTANT:** MongoDB only auto-indexes `_id`. You must manually create indexes for pagination.
|
|
656
866
|
|
|
657
867
|
```javascript
|
|
658
|
-
//
|
|
868
|
+
// ✅ Single-Tenant: Sort field + _id
|
|
659
869
|
PostSchema.index({ createdAt: -1, _id: -1 });
|
|
660
870
|
|
|
661
|
-
//
|
|
871
|
+
// ✅ Multi-Tenant: Tenant field + Sort field + _id
|
|
662
872
|
UserSchema.index({ organizationId: 1, createdAt: -1, _id: -1 });
|
|
663
873
|
|
|
664
|
-
//
|
|
874
|
+
// ✅ Text Search: Text index
|
|
665
875
|
PostSchema.index({ title: 'text', content: 'text' });
|
|
666
876
|
```
|
|
667
877
|
|
|
878
|
+
**Without indexes = slow (full collection scan)**
|
|
879
|
+
**With indexes = fast (O(1) index seek)**
|
|
880
|
+
|
|
668
881
|
### 3. Use Estimated Counts for Large Collections
|
|
669
882
|
|
|
670
883
|
```javascript
|
|
@@ -795,11 +1008,12 @@ npm test
|
|
|
795
1008
|
```
|
|
796
1009
|
|
|
797
1010
|
**Test Coverage:**
|
|
798
|
-
-
|
|
1011
|
+
- 184 tests (182 passing, 2 skipped - require replica set)
|
|
799
1012
|
- CRUD operations
|
|
800
1013
|
- Offset pagination
|
|
801
1014
|
- Keyset pagination
|
|
802
1015
|
- Aggregation pagination
|
|
1016
|
+
- Caching (hit/miss, invalidation)
|
|
803
1017
|
- Multi-tenancy
|
|
804
1018
|
- Text search + infinite scroll
|
|
805
1019
|
- Real-world scenarios
|