@classytic/mongokit 3.2.2 → 3.2.3
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 +125 -4
- package/dist/{custom-id.plugin-BzZI4gnE.d.mts → custom-id.plugin-BmK0SjR9.d.mts} +147 -1
- package/dist/{custom-id.plugin-B_zIs6gE.mjs → custom-id.plugin-m0VW6yYm.mjs} +352 -1
- package/dist/index.d.mts +39 -2
- package/dist/index.mjs +149 -3
- package/dist/plugins/index.d.mts +2 -2
- package/dist/plugins/index.mjs +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
- **Zero dependencies** - Only Mongoose as peer dependency
|
|
13
13
|
- **Explicit + smart pagination** - Explicit `mode` control or auto-detection; offset, keyset, and aggregate
|
|
14
14
|
- **Event-driven** - Pre/post hooks for all operations (granular scalability hooks)
|
|
15
|
-
- **
|
|
15
|
+
- **17 built-in plugins** - Caching, soft delete, audit trail, validation, multi-tenant, custom IDs, observability, Elasticsearch, and more
|
|
16
16
|
- **Distributed cache safety** - List cache versions stored in the adapter (Redis) for multi-pod correctness
|
|
17
17
|
- **Search governance** - Text index guard (throws `400` if no index), allowlisted sort/filter fields, ReDoS protection
|
|
18
18
|
- **Vector search** - MongoDB Atlas `$vectorSearch` with auto-embedding and multimodal support
|
|
19
19
|
- **TypeScript first** - Full type safety with discriminated unions
|
|
20
|
-
- **
|
|
20
|
+
- **592 passing tests** - Battle-tested and production-ready
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
@@ -216,6 +216,7 @@ const repo = new Repository(UserModel, [
|
|
|
216
216
|
| `multiTenantPlugin(opts)` | Auto-inject tenant isolation on all operations |
|
|
217
217
|
| `customIdPlugin(opts)` | Auto-generate sequential/random IDs with atomic counters |
|
|
218
218
|
| `elasticSearchPlugin(opts)` | Delegate text/semantic search to Elasticsearch/OpenSearch |
|
|
219
|
+
| `auditTrailPlugin(opts)` | DB-persisted audit trail with change tracking and TTL |
|
|
219
220
|
| `observabilityPlugin(opts)` | Operation timing, metrics, slow query detection |
|
|
220
221
|
|
|
221
222
|
### Soft Delete
|
|
@@ -359,6 +360,126 @@ await repo.update(userId, { name: "New" }, { organizationId: "org_123" });
|
|
|
359
360
|
// Cross-tenant update/delete is blocked — returns "not found"
|
|
360
361
|
```
|
|
361
362
|
|
|
363
|
+
### Audit Trail (DB-Persisted)
|
|
364
|
+
|
|
365
|
+
The `auditTrailPlugin` persists operation audit entries to a shared MongoDB collection. Unlike `auditLogPlugin` (which logs to an external logger), this stores a queryable audit trail in the database with automatic TTL cleanup.
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
import {
|
|
369
|
+
Repository,
|
|
370
|
+
methodRegistryPlugin,
|
|
371
|
+
auditTrailPlugin,
|
|
372
|
+
} from "@classytic/mongokit";
|
|
373
|
+
|
|
374
|
+
const repo = new Repository(JobModel, [
|
|
375
|
+
methodRegistryPlugin(),
|
|
376
|
+
auditTrailPlugin({
|
|
377
|
+
operations: ["create", "update", "delete"], // Which ops to track
|
|
378
|
+
trackChanges: true, // Field-level before/after diff on updates
|
|
379
|
+
trackDocument: false, // Full doc snapshot on create (heavy)
|
|
380
|
+
ttlDays: 90, // Auto-purge after 90 days (MongoDB TTL index)
|
|
381
|
+
excludeFields: ["password", "token"], // Redact sensitive fields
|
|
382
|
+
metadata: (context) => ({
|
|
383
|
+
// Custom metadata per entry
|
|
384
|
+
ip: context.req?.ip,
|
|
385
|
+
userAgent: context.req?.headers?.["user-agent"],
|
|
386
|
+
}),
|
|
387
|
+
}),
|
|
388
|
+
]);
|
|
389
|
+
|
|
390
|
+
// Query audit trail for a specific document (requires methodRegistryPlugin)
|
|
391
|
+
const trail = await repo.getAuditTrail(documentId, {
|
|
392
|
+
page: 1,
|
|
393
|
+
limit: 20,
|
|
394
|
+
operation: "update", // Optional filter
|
|
395
|
+
});
|
|
396
|
+
// → { docs, page, limit, total, pages, hasNext, hasPrev }
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**What gets stored:**
|
|
400
|
+
|
|
401
|
+
```javascript
|
|
402
|
+
{
|
|
403
|
+
model: 'Job',
|
|
404
|
+
operation: 'update',
|
|
405
|
+
documentId: ObjectId('...'),
|
|
406
|
+
userId: ObjectId('...'),
|
|
407
|
+
orgId: ObjectId('...'),
|
|
408
|
+
changes: {
|
|
409
|
+
title: { from: 'Old Title', to: 'New Title' },
|
|
410
|
+
salary: { from: 50000, to: 65000 },
|
|
411
|
+
},
|
|
412
|
+
metadata: { ip: '192.168.1.1' },
|
|
413
|
+
timestamp: ISODate('2026-02-26T...'),
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
**Standalone queries** (admin dashboards, audit APIs — no repo needed):
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import { AuditTrailQuery } from "@classytic/mongokit";
|
|
421
|
+
|
|
422
|
+
const auditQuery = new AuditTrailQuery(); // 'audit_trails' collection
|
|
423
|
+
|
|
424
|
+
// All audits for an org
|
|
425
|
+
const orgAudits = await auditQuery.getOrgTrail(orgId);
|
|
426
|
+
|
|
427
|
+
// All actions by a user
|
|
428
|
+
const userAudits = await auditQuery.getUserTrail(userId);
|
|
429
|
+
|
|
430
|
+
// History of a specific document
|
|
431
|
+
const docHistory = await auditQuery.getDocumentTrail("Job", jobId);
|
|
432
|
+
|
|
433
|
+
// Custom query with date range
|
|
434
|
+
const recent = await auditQuery.query({
|
|
435
|
+
orgId,
|
|
436
|
+
operation: "delete",
|
|
437
|
+
from: new Date("2025-01-01"),
|
|
438
|
+
to: new Date(),
|
|
439
|
+
page: 1,
|
|
440
|
+
limit: 50,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// Direct model access for anything custom
|
|
444
|
+
const model = auditQuery.getModel();
|
|
445
|
+
const deleteCount = await model.countDocuments({ operation: "delete" });
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**Key design decisions:**
|
|
449
|
+
|
|
450
|
+
- **Fire & forget** — audit writes are async and never block or fail the main operation
|
|
451
|
+
- **Shared collection** — one `audit_trails` collection for all models (filtered by `model` field)
|
|
452
|
+
- **TTL index** — MongoDB auto-deletes old entries, no cron needed
|
|
453
|
+
- **Change diff** — compares before/after on updates, stores only changed fields
|
|
454
|
+
|
|
455
|
+
**Plugin options:**
|
|
456
|
+
|
|
457
|
+
| Option | Default | Description |
|
|
458
|
+
| --------------- | -------------------------------- | -------------------------------------- |
|
|
459
|
+
| `operations` | `['create', 'update', 'delete']` | Which operations to audit |
|
|
460
|
+
| `trackChanges` | `true` | Store before/after diff on updates |
|
|
461
|
+
| `trackDocument` | `false` | Store full document snapshot on create |
|
|
462
|
+
| `ttlDays` | `undefined` (keep forever) | Auto-purge after N days |
|
|
463
|
+
| `collectionName`| `'audit_trails'` | MongoDB collection name |
|
|
464
|
+
| `excludeFields` | `[]` | Fields to redact from diffs/snapshots |
|
|
465
|
+
| `metadata` | `undefined` | Callback to inject custom metadata |
|
|
466
|
+
|
|
467
|
+
**TypeScript type safety:**
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
import type { AuditTrailMethods } from "@classytic/mongokit";
|
|
471
|
+
|
|
472
|
+
type JobRepoWithAudit = JobRepo & AuditTrailMethods;
|
|
473
|
+
|
|
474
|
+
const repo = new JobRepo(JobModel, [
|
|
475
|
+
methodRegistryPlugin(),
|
|
476
|
+
auditTrailPlugin({ ttlDays: 90 }),
|
|
477
|
+
]) as JobRepoWithAudit;
|
|
478
|
+
|
|
479
|
+
// Full autocomplete for getAuditTrail
|
|
480
|
+
const trail = await repo.getAuditTrail(jobId, { operation: "update" });
|
|
481
|
+
```
|
|
482
|
+
|
|
362
483
|
### Observability
|
|
363
484
|
|
|
364
485
|
```javascript
|
|
@@ -715,7 +836,7 @@ await repo.restore(id);
|
|
|
715
836
|
await repo.invalidateCache(id);
|
|
716
837
|
```
|
|
717
838
|
|
|
718
|
-
**Individual plugin types:** `MongoOperationsMethods<T>`, `BatchOperationsMethods`, `AggregateHelpersMethods`, `SubdocumentMethods<T>`, `SoftDeleteMethods<T>`, `CacheMethods`
|
|
839
|
+
**Individual plugin types:** `MongoOperationsMethods<T>`, `BatchOperationsMethods`, `AggregateHelpersMethods`, `SubdocumentMethods<T>`, `SoftDeleteMethods<T>`, `CacheMethods`, `AuditTrailMethods`
|
|
719
840
|
|
|
720
841
|
## Event System
|
|
721
842
|
|
|
@@ -1126,7 +1247,7 @@ Extending Repository works exactly the same with Mongoose 8 and 9. The package:
|
|
|
1126
1247
|
- Uses its own event system (not Mongoose middleware)
|
|
1127
1248
|
- Defines its own `FilterQuery` type (unaffected by Mongoose 9 rename)
|
|
1128
1249
|
- Properly gates update pipelines (safe for Mongoose 9's stricter defaults)
|
|
1129
|
-
- All
|
|
1250
|
+
- All 597 tests pass on Mongoose 9
|
|
1130
1251
|
|
|
1131
1252
|
## License
|
|
1132
1253
|
|
|
@@ -732,6 +732,152 @@ interface ObservabilityOptions {
|
|
|
732
732
|
}
|
|
733
733
|
declare function observabilityPlugin(options: ObservabilityOptions): Plugin;
|
|
734
734
|
//#endregion
|
|
735
|
+
//#region src/plugins/audit-trail.plugin.d.ts
|
|
736
|
+
interface AuditTrailOptions {
|
|
737
|
+
/** Operations to track (default: ['create', 'update', 'delete']) */
|
|
738
|
+
operations?: AuditOperation[];
|
|
739
|
+
/** Store field-level before/after diff on updates (default: true) */
|
|
740
|
+
trackChanges?: boolean;
|
|
741
|
+
/** Store full document snapshot on create (default: false — can be heavy) */
|
|
742
|
+
trackDocument?: boolean;
|
|
743
|
+
/** Auto-purge after N days via MongoDB TTL index (default: undefined — keep forever) */
|
|
744
|
+
ttlDays?: number;
|
|
745
|
+
/** MongoDB collection name (default: 'audit_trails') */
|
|
746
|
+
collectionName?: string;
|
|
747
|
+
/**
|
|
748
|
+
* Extract custom metadata from the repository context.
|
|
749
|
+
* Returned object is stored on the audit entry as `metadata`.
|
|
750
|
+
*/
|
|
751
|
+
metadata?: (context: RepositoryContext) => Record<string, unknown>;
|
|
752
|
+
/**
|
|
753
|
+
* Fields to exclude from change tracking (e.g., passwords, tokens).
|
|
754
|
+
* These fields are redacted in the `changes` diff.
|
|
755
|
+
*/
|
|
756
|
+
excludeFields?: string[];
|
|
757
|
+
}
|
|
758
|
+
type AuditOperation = 'create' | 'update' | 'delete';
|
|
759
|
+
interface AuditEntry {
|
|
760
|
+
model: string;
|
|
761
|
+
operation: AuditOperation;
|
|
762
|
+
documentId: unknown;
|
|
763
|
+
userId?: unknown;
|
|
764
|
+
orgId?: unknown;
|
|
765
|
+
changes?: Record<string, {
|
|
766
|
+
from: unknown;
|
|
767
|
+
to: unknown;
|
|
768
|
+
}>;
|
|
769
|
+
document?: Record<string, unknown>;
|
|
770
|
+
metadata?: Record<string, unknown>;
|
|
771
|
+
timestamp: Date;
|
|
772
|
+
}
|
|
773
|
+
declare function auditTrailPlugin(options?: AuditTrailOptions): Plugin;
|
|
774
|
+
interface AuditQueryOptions {
|
|
775
|
+
model?: string;
|
|
776
|
+
documentId?: string | ObjectId;
|
|
777
|
+
userId?: string | ObjectId;
|
|
778
|
+
orgId?: string | ObjectId;
|
|
779
|
+
operation?: AuditOperation;
|
|
780
|
+
from?: Date;
|
|
781
|
+
to?: Date;
|
|
782
|
+
page?: number;
|
|
783
|
+
limit?: number;
|
|
784
|
+
}
|
|
785
|
+
interface AuditQueryResult {
|
|
786
|
+
docs: AuditEntry[];
|
|
787
|
+
page: number;
|
|
788
|
+
limit: number;
|
|
789
|
+
total: number;
|
|
790
|
+
pages: number;
|
|
791
|
+
hasNext: boolean;
|
|
792
|
+
hasPrev: boolean;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Standalone audit trail query utility.
|
|
796
|
+
* Use this to query audits across all models — e.g., admin dashboards, audit APIs.
|
|
797
|
+
*
|
|
798
|
+
* @example
|
|
799
|
+
* ```typescript
|
|
800
|
+
* import { AuditTrailQuery } from '@classytic/mongokit';
|
|
801
|
+
*
|
|
802
|
+
* const auditQuery = new AuditTrailQuery(); // defaults to 'audit_trails' collection
|
|
803
|
+
*
|
|
804
|
+
* // All audits for an org
|
|
805
|
+
* const orgAudits = await auditQuery.query({ orgId: '...' });
|
|
806
|
+
*
|
|
807
|
+
* // All updates by a user
|
|
808
|
+
* const userUpdates = await auditQuery.query({
|
|
809
|
+
* userId: '...',
|
|
810
|
+
* operation: 'update',
|
|
811
|
+
* });
|
|
812
|
+
*
|
|
813
|
+
* // All audits for a specific document
|
|
814
|
+
* const docHistory = await auditQuery.query({
|
|
815
|
+
* model: 'Job',
|
|
816
|
+
* documentId: '...',
|
|
817
|
+
* });
|
|
818
|
+
*
|
|
819
|
+
* // Date range
|
|
820
|
+
* const recent = await auditQuery.query({
|
|
821
|
+
* from: new Date('2025-01-01'),
|
|
822
|
+
* to: new Date(),
|
|
823
|
+
* page: 1,
|
|
824
|
+
* limit: 50,
|
|
825
|
+
* });
|
|
826
|
+
*
|
|
827
|
+
* // Direct model access for custom queries
|
|
828
|
+
* const model = auditQuery.getModel();
|
|
829
|
+
* const count = await model.countDocuments({ operation: 'delete' });
|
|
830
|
+
* ```
|
|
831
|
+
*/
|
|
832
|
+
declare class AuditTrailQuery {
|
|
833
|
+
private model;
|
|
834
|
+
constructor(collectionName?: string, ttlDays?: number);
|
|
835
|
+
/**
|
|
836
|
+
* Get the underlying Mongoose model for custom queries
|
|
837
|
+
*/
|
|
838
|
+
getModel(): mongoose.Model<AuditEntry>;
|
|
839
|
+
/**
|
|
840
|
+
* Query audit entries with filters and pagination
|
|
841
|
+
*/
|
|
842
|
+
query(options?: AuditQueryOptions): Promise<AuditQueryResult>;
|
|
843
|
+
/**
|
|
844
|
+
* Get audit trail for a specific document
|
|
845
|
+
*/
|
|
846
|
+
getDocumentTrail(model: string, documentId: string | ObjectId, options?: {
|
|
847
|
+
page?: number;
|
|
848
|
+
limit?: number;
|
|
849
|
+
operation?: AuditOperation;
|
|
850
|
+
}): Promise<AuditQueryResult>;
|
|
851
|
+
/**
|
|
852
|
+
* Get all audits for a user
|
|
853
|
+
*/
|
|
854
|
+
getUserTrail(userId: string | ObjectId, options?: {
|
|
855
|
+
page?: number;
|
|
856
|
+
limit?: number;
|
|
857
|
+
operation?: AuditOperation;
|
|
858
|
+
orgId?: string | ObjectId;
|
|
859
|
+
}): Promise<AuditQueryResult>;
|
|
860
|
+
/**
|
|
861
|
+
* Get all audits for an organization
|
|
862
|
+
*/
|
|
863
|
+
getOrgTrail(orgId: string | ObjectId, options?: {
|
|
864
|
+
page?: number;
|
|
865
|
+
limit?: number;
|
|
866
|
+
operation?: AuditOperation;
|
|
867
|
+
model?: string;
|
|
868
|
+
}): Promise<AuditQueryResult>;
|
|
869
|
+
}
|
|
870
|
+
interface AuditTrailMethods {
|
|
871
|
+
/**
|
|
872
|
+
* Get paginated audit trail for a document
|
|
873
|
+
*/
|
|
874
|
+
getAuditTrail(documentId: string | ObjectId, options?: {
|
|
875
|
+
page?: number;
|
|
876
|
+
limit?: number;
|
|
877
|
+
operation?: AuditOperation;
|
|
878
|
+
}): Promise<AuditQueryResult>;
|
|
879
|
+
}
|
|
880
|
+
//#endregion
|
|
735
881
|
//#region src/plugins/elastic.plugin.d.ts
|
|
736
882
|
interface ElasticSearchOptions {
|
|
737
883
|
/** Elasticsearch or OpenSearch client instance */
|
|
@@ -890,4 +1036,4 @@ declare function prefixedId(options: PrefixedIdOptions): IdGenerator;
|
|
|
890
1036
|
*/
|
|
891
1037
|
declare function customIdPlugin(options: CustomIdOptions): Plugin;
|
|
892
1038
|
//#endregion
|
|
893
|
-
export {
|
|
1039
|
+
export { subdocumentPlugin as A, requireField as B, observabilityPlugin as C, CacheMethods as D, cascadePlugin as E, MongoOperationsMethods as F, SoftDeleteMethods as G, validationChainPlugin as H, mongoOperationsPlugin as I, timestampPlugin as J, softDeletePlugin as K, autoInject as L, aggregateHelpersPlugin as M, BatchOperationsMethods as N, cachePlugin as O, batchOperationsPlugin as P, blockIf as R, OperationMetric as S, multiTenantPlugin as T, MethodRegistryRepository as U, uniqueField as V, methodRegistryPlugin as W, fieldFilterPlugin as Y, AuditTrailMethods as _, SequentialIdOptions as a, auditTrailPlugin as b, getNextSequence as c, ElasticSearchOptions as d, elasticSearchPlugin as f, AuditQueryResult as g, AuditQueryOptions as h, PrefixedIdOptions as i, AggregateHelpersMethods as j, SubdocumentMethods as k, prefixedId as l, AuditOperation as m, DateSequentialIdOptions as n, customIdPlugin as o, AuditEntry as p, auditLogPlugin as q, IdGenerator as r, dateSequentialId as s, CustomIdOptions as t, sequentialId as u, AuditTrailOptions as v, MultiTenantOptions as w, ObservabilityOptions as x, AuditTrailQuery as y, immutableField as z };
|
|
@@ -1487,6 +1487,357 @@ function observabilityPlugin(options) {
|
|
|
1487
1487
|
};
|
|
1488
1488
|
}
|
|
1489
1489
|
|
|
1490
|
+
//#endregion
|
|
1491
|
+
//#region src/plugins/audit-trail.plugin.ts
|
|
1492
|
+
/**
|
|
1493
|
+
* Audit Trail Plugin
|
|
1494
|
+
*
|
|
1495
|
+
* Persists operation audit entries to a MongoDB collection.
|
|
1496
|
+
* Fire-and-forget: writes happen async and never block or fail the main operation.
|
|
1497
|
+
*
|
|
1498
|
+
* Features:
|
|
1499
|
+
* - Tracks create, update, delete operations
|
|
1500
|
+
* - Field-level change tracking (before/after diff on updates)
|
|
1501
|
+
* - TTL auto-cleanup via MongoDB TTL index
|
|
1502
|
+
* - Custom metadata per entry (IP, user-agent, etc.)
|
|
1503
|
+
* - Shared `audit_trails` collection across all models
|
|
1504
|
+
*
|
|
1505
|
+
* @example
|
|
1506
|
+
* ```typescript
|
|
1507
|
+
* const repo = new Repository(Job, [
|
|
1508
|
+
* auditTrailPlugin({
|
|
1509
|
+
* operations: ['create', 'update', 'delete'],
|
|
1510
|
+
* trackChanges: true,
|
|
1511
|
+
* ttlDays: 90,
|
|
1512
|
+
* metadata: (context) => ({
|
|
1513
|
+
* ip: context.req?.ip,
|
|
1514
|
+
* }),
|
|
1515
|
+
* }),
|
|
1516
|
+
* ]);
|
|
1517
|
+
* ```
|
|
1518
|
+
*/
|
|
1519
|
+
const modelCache = /* @__PURE__ */ new Map();
|
|
1520
|
+
function getAuditModel(collectionName, ttlDays) {
|
|
1521
|
+
const existing = modelCache.get(collectionName);
|
|
1522
|
+
if (existing) return existing;
|
|
1523
|
+
const schema = new mongoose.Schema({
|
|
1524
|
+
model: {
|
|
1525
|
+
type: String,
|
|
1526
|
+
required: true,
|
|
1527
|
+
index: true
|
|
1528
|
+
},
|
|
1529
|
+
operation: {
|
|
1530
|
+
type: String,
|
|
1531
|
+
required: true,
|
|
1532
|
+
enum: [
|
|
1533
|
+
"create",
|
|
1534
|
+
"update",
|
|
1535
|
+
"delete"
|
|
1536
|
+
]
|
|
1537
|
+
},
|
|
1538
|
+
documentId: {
|
|
1539
|
+
type: mongoose.Schema.Types.Mixed,
|
|
1540
|
+
required: true,
|
|
1541
|
+
index: true
|
|
1542
|
+
},
|
|
1543
|
+
userId: {
|
|
1544
|
+
type: mongoose.Schema.Types.Mixed,
|
|
1545
|
+
index: true
|
|
1546
|
+
},
|
|
1547
|
+
orgId: {
|
|
1548
|
+
type: mongoose.Schema.Types.Mixed,
|
|
1549
|
+
index: true
|
|
1550
|
+
},
|
|
1551
|
+
changes: { type: mongoose.Schema.Types.Mixed },
|
|
1552
|
+
document: { type: mongoose.Schema.Types.Mixed },
|
|
1553
|
+
metadata: { type: mongoose.Schema.Types.Mixed },
|
|
1554
|
+
timestamp: {
|
|
1555
|
+
type: Date,
|
|
1556
|
+
default: Date.now,
|
|
1557
|
+
index: true
|
|
1558
|
+
}
|
|
1559
|
+
}, {
|
|
1560
|
+
collection: collectionName,
|
|
1561
|
+
versionKey: false
|
|
1562
|
+
});
|
|
1563
|
+
schema.index({
|
|
1564
|
+
model: 1,
|
|
1565
|
+
documentId: 1,
|
|
1566
|
+
timestamp: -1
|
|
1567
|
+
});
|
|
1568
|
+
schema.index({
|
|
1569
|
+
orgId: 1,
|
|
1570
|
+
userId: 1,
|
|
1571
|
+
timestamp: -1
|
|
1572
|
+
});
|
|
1573
|
+
if (ttlDays !== void 0 && ttlDays > 0) {
|
|
1574
|
+
const ttlSeconds = ttlDays * 24 * 60 * 60;
|
|
1575
|
+
schema.index({ timestamp: 1 }, { expireAfterSeconds: ttlSeconds });
|
|
1576
|
+
}
|
|
1577
|
+
const modelName = `AuditTrail_${collectionName}`;
|
|
1578
|
+
const model = mongoose.models[modelName] || mongoose.model(modelName, schema);
|
|
1579
|
+
modelCache.set(collectionName, model);
|
|
1580
|
+
return model;
|
|
1581
|
+
}
|
|
1582
|
+
/** Compute field-level diff between previous and updated document */
|
|
1583
|
+
function computeChanges(prev, next, excludeFields) {
|
|
1584
|
+
const changes = {};
|
|
1585
|
+
const exclude = new Set(excludeFields);
|
|
1586
|
+
for (const key of Object.keys(next)) {
|
|
1587
|
+
if (exclude.has(key)) continue;
|
|
1588
|
+
if (key === "_id" || key === "__v" || key === "updatedAt") continue;
|
|
1589
|
+
const prevVal = prev[key];
|
|
1590
|
+
const nextVal = next[key];
|
|
1591
|
+
if (!deepEqual(prevVal, nextVal)) changes[key] = {
|
|
1592
|
+
from: prevVal,
|
|
1593
|
+
to: nextVal
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
return Object.keys(changes).length > 0 ? changes : void 0;
|
|
1597
|
+
}
|
|
1598
|
+
/** Simple deep equality check for audit diffing */
|
|
1599
|
+
function deepEqual(a, b) {
|
|
1600
|
+
if (a === b) return true;
|
|
1601
|
+
if (a == null && b == null) return true;
|
|
1602
|
+
if (a == null || b == null) return false;
|
|
1603
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
1604
|
+
const aStr = a.toString?.();
|
|
1605
|
+
const bStr = b.toString?.();
|
|
1606
|
+
if (aStr && bStr && aStr === bStr) return true;
|
|
1607
|
+
}
|
|
1608
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
1609
|
+
try {
|
|
1610
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
1611
|
+
} catch {
|
|
1612
|
+
return false;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
/** Extract user ID from context */
|
|
1616
|
+
function getUserId(context) {
|
|
1617
|
+
return context.user?._id || context.user?.id;
|
|
1618
|
+
}
|
|
1619
|
+
/** Fire-and-forget: write audit entry, never throw */
|
|
1620
|
+
function writeAudit(AuditModel, entry) {
|
|
1621
|
+
Promise.resolve().then(() => {
|
|
1622
|
+
AuditModel.create({
|
|
1623
|
+
...entry,
|
|
1624
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1625
|
+
}).catch((err) => {
|
|
1626
|
+
warn(`[auditTrailPlugin] Failed to write audit entry: ${err.message}`);
|
|
1627
|
+
});
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
const snapshots = /* @__PURE__ */ new WeakMap();
|
|
1631
|
+
function auditTrailPlugin(options = {}) {
|
|
1632
|
+
const { operations = [
|
|
1633
|
+
"create",
|
|
1634
|
+
"update",
|
|
1635
|
+
"delete"
|
|
1636
|
+
], trackChanges = true, trackDocument = false, ttlDays, collectionName = "audit_trails", metadata, excludeFields = [] } = options;
|
|
1637
|
+
const opsSet = new Set(operations);
|
|
1638
|
+
return {
|
|
1639
|
+
name: "auditTrail",
|
|
1640
|
+
apply(repo) {
|
|
1641
|
+
const AuditModel = getAuditModel(collectionName, ttlDays);
|
|
1642
|
+
if (opsSet.has("create")) repo.on("after:create", ({ context, result }) => {
|
|
1643
|
+
const doc = toPlainObject(result);
|
|
1644
|
+
writeAudit(AuditModel, {
|
|
1645
|
+
model: context.model || repo.model,
|
|
1646
|
+
operation: "create",
|
|
1647
|
+
documentId: doc?._id,
|
|
1648
|
+
userId: getUserId(context),
|
|
1649
|
+
orgId: context.organizationId,
|
|
1650
|
+
document: trackDocument ? sanitizeDoc(doc, excludeFields) : void 0,
|
|
1651
|
+
metadata: metadata?.(context)
|
|
1652
|
+
});
|
|
1653
|
+
});
|
|
1654
|
+
if (opsSet.has("update")) {
|
|
1655
|
+
if (trackChanges) repo.on("before:update", async (context) => {
|
|
1656
|
+
if (!context.id) return;
|
|
1657
|
+
try {
|
|
1658
|
+
const prev = await repo.Model.findById(context.id).lean();
|
|
1659
|
+
if (prev) snapshots.set(context, prev);
|
|
1660
|
+
} catch (err) {
|
|
1661
|
+
warn(`[auditTrailPlugin] Failed to snapshot before update: ${err.message}`);
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
repo.on("after:update", ({ context, result }) => {
|
|
1665
|
+
const doc = result;
|
|
1666
|
+
let changes;
|
|
1667
|
+
if (trackChanges) {
|
|
1668
|
+
const prev = snapshots.get(context);
|
|
1669
|
+
if (prev && context.data) changes = computeChanges(prev, context.data, excludeFields);
|
|
1670
|
+
snapshots.delete(context);
|
|
1671
|
+
}
|
|
1672
|
+
writeAudit(AuditModel, {
|
|
1673
|
+
model: context.model || repo.model,
|
|
1674
|
+
operation: "update",
|
|
1675
|
+
documentId: context.id || doc?._id,
|
|
1676
|
+
userId: getUserId(context),
|
|
1677
|
+
orgId: context.organizationId,
|
|
1678
|
+
changes,
|
|
1679
|
+
metadata: metadata?.(context)
|
|
1680
|
+
});
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
if (opsSet.has("delete")) repo.on("after:delete", ({ context }) => {
|
|
1684
|
+
writeAudit(AuditModel, {
|
|
1685
|
+
model: context.model || repo.model,
|
|
1686
|
+
operation: "delete",
|
|
1687
|
+
documentId: context.id,
|
|
1688
|
+
userId: getUserId(context),
|
|
1689
|
+
orgId: context.organizationId,
|
|
1690
|
+
metadata: metadata?.(context)
|
|
1691
|
+
});
|
|
1692
|
+
});
|
|
1693
|
+
if (typeof repo.registerMethod === "function")
|
|
1694
|
+
/**
|
|
1695
|
+
* Get audit trail for a specific document
|
|
1696
|
+
*/
|
|
1697
|
+
repo.registerMethod("getAuditTrail", async function(documentId, queryOptions = {}) {
|
|
1698
|
+
const { page = 1, limit = 20, operation } = queryOptions;
|
|
1699
|
+
const skip = (page - 1) * limit;
|
|
1700
|
+
const filter = {
|
|
1701
|
+
model: this.model,
|
|
1702
|
+
documentId
|
|
1703
|
+
};
|
|
1704
|
+
if (operation) filter.operation = operation;
|
|
1705
|
+
const [docs, total] = await Promise.all([AuditModel.find(filter).sort({ timestamp: -1 }).skip(skip).limit(limit).lean(), AuditModel.countDocuments(filter)]);
|
|
1706
|
+
return {
|
|
1707
|
+
docs,
|
|
1708
|
+
page,
|
|
1709
|
+
limit,
|
|
1710
|
+
total,
|
|
1711
|
+
pages: Math.ceil(total / limit),
|
|
1712
|
+
hasNext: page < Math.ceil(total / limit),
|
|
1713
|
+
hasPrev: page > 1
|
|
1714
|
+
};
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
/** Convert Mongoose document to plain object */
|
|
1720
|
+
function toPlainObject(doc) {
|
|
1721
|
+
if (!doc) return {};
|
|
1722
|
+
if (typeof doc.toObject === "function") return doc.toObject();
|
|
1723
|
+
return doc;
|
|
1724
|
+
}
|
|
1725
|
+
/** Remove excluded fields from a document snapshot */
|
|
1726
|
+
function sanitizeDoc(doc, excludeFields) {
|
|
1727
|
+
if (excludeFields.length === 0) return doc;
|
|
1728
|
+
const result = { ...doc };
|
|
1729
|
+
for (const field of excludeFields) delete result[field];
|
|
1730
|
+
return result;
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Standalone audit trail query utility.
|
|
1734
|
+
* Use this to query audits across all models — e.g., admin dashboards, audit APIs.
|
|
1735
|
+
*
|
|
1736
|
+
* @example
|
|
1737
|
+
* ```typescript
|
|
1738
|
+
* import { AuditTrailQuery } from '@classytic/mongokit';
|
|
1739
|
+
*
|
|
1740
|
+
* const auditQuery = new AuditTrailQuery(); // defaults to 'audit_trails' collection
|
|
1741
|
+
*
|
|
1742
|
+
* // All audits for an org
|
|
1743
|
+
* const orgAudits = await auditQuery.query({ orgId: '...' });
|
|
1744
|
+
*
|
|
1745
|
+
* // All updates by a user
|
|
1746
|
+
* const userUpdates = await auditQuery.query({
|
|
1747
|
+
* userId: '...',
|
|
1748
|
+
* operation: 'update',
|
|
1749
|
+
* });
|
|
1750
|
+
*
|
|
1751
|
+
* // All audits for a specific document
|
|
1752
|
+
* const docHistory = await auditQuery.query({
|
|
1753
|
+
* model: 'Job',
|
|
1754
|
+
* documentId: '...',
|
|
1755
|
+
* });
|
|
1756
|
+
*
|
|
1757
|
+
* // Date range
|
|
1758
|
+
* const recent = await auditQuery.query({
|
|
1759
|
+
* from: new Date('2025-01-01'),
|
|
1760
|
+
* to: new Date(),
|
|
1761
|
+
* page: 1,
|
|
1762
|
+
* limit: 50,
|
|
1763
|
+
* });
|
|
1764
|
+
*
|
|
1765
|
+
* // Direct model access for custom queries
|
|
1766
|
+
* const model = auditQuery.getModel();
|
|
1767
|
+
* const count = await model.countDocuments({ operation: 'delete' });
|
|
1768
|
+
* ```
|
|
1769
|
+
*/
|
|
1770
|
+
var AuditTrailQuery = class {
|
|
1771
|
+
model;
|
|
1772
|
+
constructor(collectionName = "audit_trails", ttlDays) {
|
|
1773
|
+
this.model = getAuditModel(collectionName, ttlDays);
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Get the underlying Mongoose model for custom queries
|
|
1777
|
+
*/
|
|
1778
|
+
getModel() {
|
|
1779
|
+
return this.model;
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Query audit entries with filters and pagination
|
|
1783
|
+
*/
|
|
1784
|
+
async query(options = {}) {
|
|
1785
|
+
const { page = 1, limit = 20 } = options;
|
|
1786
|
+
const skip = (page - 1) * limit;
|
|
1787
|
+
const filter = {};
|
|
1788
|
+
if (options.model) filter.model = options.model;
|
|
1789
|
+
if (options.documentId) filter.documentId = options.documentId;
|
|
1790
|
+
if (options.userId) filter.userId = options.userId;
|
|
1791
|
+
if (options.orgId) filter.orgId = options.orgId;
|
|
1792
|
+
if (options.operation) filter.operation = options.operation;
|
|
1793
|
+
if (options.from || options.to) {
|
|
1794
|
+
const dateFilter = {};
|
|
1795
|
+
if (options.from) dateFilter.$gte = options.from;
|
|
1796
|
+
if (options.to) dateFilter.$lte = options.to;
|
|
1797
|
+
filter.timestamp = dateFilter;
|
|
1798
|
+
}
|
|
1799
|
+
const [docs, total] = await Promise.all([this.model.find(filter).sort({ timestamp: -1 }).skip(skip).limit(limit).lean(), this.model.countDocuments(filter)]);
|
|
1800
|
+
const pages = Math.ceil(total / limit);
|
|
1801
|
+
return {
|
|
1802
|
+
docs,
|
|
1803
|
+
page,
|
|
1804
|
+
limit,
|
|
1805
|
+
total,
|
|
1806
|
+
pages,
|
|
1807
|
+
hasNext: page < pages,
|
|
1808
|
+
hasPrev: page > 1
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Get audit trail for a specific document
|
|
1813
|
+
*/
|
|
1814
|
+
async getDocumentTrail(model, documentId, options = {}) {
|
|
1815
|
+
return this.query({
|
|
1816
|
+
model,
|
|
1817
|
+
documentId,
|
|
1818
|
+
...options
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Get all audits for a user
|
|
1823
|
+
*/
|
|
1824
|
+
async getUserTrail(userId, options = {}) {
|
|
1825
|
+
return this.query({
|
|
1826
|
+
userId,
|
|
1827
|
+
...options
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Get all audits for an organization
|
|
1832
|
+
*/
|
|
1833
|
+
async getOrgTrail(orgId, options = {}) {
|
|
1834
|
+
return this.query({
|
|
1835
|
+
orgId,
|
|
1836
|
+
...options
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
|
|
1490
1841
|
//#endregion
|
|
1491
1842
|
//#region src/plugins/elastic.plugin.ts
|
|
1492
1843
|
function elasticSearchPlugin(options) {
|
|
@@ -1815,4 +2166,4 @@ function customIdPlugin(options) {
|
|
|
1815
2166
|
}
|
|
1816
2167
|
|
|
1817
2168
|
//#endregion
|
|
1818
|
-
export {
|
|
2169
|
+
export { methodRegistryPlugin as C, fieldFilterPlugin as D, timestampPlugin as E, validationChainPlugin as S, auditLogPlugin as T, autoInject as _, sequentialId as a, requireField as b, auditTrailPlugin as c, cascadePlugin as d, cachePlugin as f, mongoOperationsPlugin as g, batchOperationsPlugin as h, prefixedId as i, observabilityPlugin as l, aggregateHelpersPlugin as m, dateSequentialId as n, elasticSearchPlugin as o, subdocumentPlugin as p, getNextSequence as r, AuditTrailQuery as s, customIdPlugin as t, multiTenantPlugin as u, blockIf as v, softDeletePlugin as w, uniqueField as x, immutableField as y };
|
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import { $ as SchemaBuilderOptions, A as KeysetPaginationOptions, B as Paginatio
|
|
|
2
2
|
import "./aggregate-CCHI7F51.mjs";
|
|
3
3
|
import { t as index_d_exports } from "./actions/index.mjs";
|
|
4
4
|
import { PaginationEngine } from "./pagination/PaginationEngine.mjs";
|
|
5
|
-
import { A as
|
|
5
|
+
import { A as subdocumentPlugin, B as requireField, C as observabilityPlugin, D as CacheMethods, E as cascadePlugin, F as MongoOperationsMethods, G as SoftDeleteMethods, H as validationChainPlugin, I as mongoOperationsPlugin, J as timestampPlugin, K as softDeletePlugin, L as autoInject, M as aggregateHelpersPlugin, N as BatchOperationsMethods, O as cachePlugin, P as batchOperationsPlugin, R as blockIf, S as OperationMetric, T as multiTenantPlugin, V as uniqueField, W as methodRegistryPlugin, Y as fieldFilterPlugin, _ as AuditTrailMethods, a as SequentialIdOptions, b as auditTrailPlugin, c as getNextSequence, d as ElasticSearchOptions, f as elasticSearchPlugin, g as AuditQueryResult, h as AuditQueryOptions, i as PrefixedIdOptions, j as AggregateHelpersMethods, k as SubdocumentMethods, l as prefixedId, m as AuditOperation, n as DateSequentialIdOptions, o as customIdPlugin, p as AuditEntry, q as auditLogPlugin, r as IdGenerator, s as dateSequentialId, t as CustomIdOptions, u as sequentialId, v as AuditTrailOptions, w as MultiTenantOptions, x as ObservabilityOptions, y as AuditTrailQuery, z as immutableField } from "./custom-id.plugin-BmK0SjR9.mjs";
|
|
6
6
|
import { a as isFieldUpdateAllowed, c as configureLogger, d as filterResponseData, f as getFieldsForUser, i as getSystemManagedFields, l as createError, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, p as getMongooseProjection, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel, u as createFieldPreset } from "./mongooseToJsonSchema-Wbvjfwkn.mjs";
|
|
7
7
|
import * as mongoose$1 from "mongoose";
|
|
8
8
|
import { ClientSession, Expression, Model, PipelineStage, PopulateOptions } from "mongoose";
|
|
@@ -841,6 +841,15 @@ interface QueryParserOptions {
|
|
|
841
841
|
allowedFilterFields?: string[];
|
|
842
842
|
/** Allowed fields for sorting. If set, ignores unknown fields. */
|
|
843
843
|
allowedSortFields?: string[];
|
|
844
|
+
/**
|
|
845
|
+
* Whitelist of allowed filter operators.
|
|
846
|
+
* When set, only these operators can be used in filters.
|
|
847
|
+
* When undefined, all built-in operators are allowed.
|
|
848
|
+
* Values are human-readable keys: 'eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin',
|
|
849
|
+
* 'like', 'contains', 'regex', 'exists', 'size', 'type'
|
|
850
|
+
* @example ['eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in']
|
|
851
|
+
*/
|
|
852
|
+
allowedOperators?: string[];
|
|
844
853
|
}
|
|
845
854
|
/**
|
|
846
855
|
* Modern Query Parser
|
|
@@ -872,6 +881,34 @@ declare class QueryParser {
|
|
|
872
881
|
* ```
|
|
873
882
|
*/
|
|
874
883
|
parse(query: Record<string, unknown> | null | undefined): ParsedQuery;
|
|
884
|
+
/**
|
|
885
|
+
* Generate OpenAPI-compatible JSON Schema for query parameters.
|
|
886
|
+
* Arc's defineResource() auto-detects this method and uses it
|
|
887
|
+
* to document list endpoint query parameters in OpenAPI/Swagger.
|
|
888
|
+
*
|
|
889
|
+
* The schema respects parser configuration:
|
|
890
|
+
* - `allowedOperators`: only documents allowed operators
|
|
891
|
+
* - `allowedFilterFields`: generates explicit field[op] entries
|
|
892
|
+
* - `enableLookups` / `enableAggregations`: includes/excludes lookup/aggregate params
|
|
893
|
+
* - `maxLimit` / `maxSearchLength`: reflected in schema constraints
|
|
894
|
+
*/
|
|
895
|
+
getQuerySchema(): {
|
|
896
|
+
type: "object";
|
|
897
|
+
properties: Record<string, unknown>;
|
|
898
|
+
required?: string[];
|
|
899
|
+
};
|
|
900
|
+
/**
|
|
901
|
+
* Get the JSON Schema type for a filter operator
|
|
902
|
+
*/
|
|
903
|
+
private _getOperatorSchemaType;
|
|
904
|
+
/**
|
|
905
|
+
* Get a human-readable description for a filter operator
|
|
906
|
+
*/
|
|
907
|
+
private _getOperatorDescription;
|
|
908
|
+
/**
|
|
909
|
+
* Build a summary description of all available filter operators
|
|
910
|
+
*/
|
|
911
|
+
private _buildOperatorDescription;
|
|
875
912
|
/**
|
|
876
913
|
* Parse lookup configurations from URL parameters
|
|
877
914
|
*
|
|
@@ -1009,4 +1046,4 @@ declare class QueryParser {
|
|
|
1009
1046
|
*/
|
|
1010
1047
|
declare function createRepository<TDoc>(Model: mongoose$1.Model<TDoc, any, any, any>, plugins?: PluginType[], paginationConfig?: PaginationConfig, options?: RepositoryOptions): Repository<TDoc>;
|
|
1011
1048
|
//#endregion
|
|
1012
|
-
export { type AggregateHelpersMethods, type AggregatePaginationOptions, type AggregatePaginationResult, AggregationBuilder, type AllPluginMethods, type AnyDocument, type AnyModel, type BatchOperationsMethods, type CacheAdapter, type CacheMethods, type CacheOperationOptions, type CacheOptions, type CacheStats, type CascadeOptions, type CascadeRelation, type CreateInput, type CreateOptions, type CrudSchemas, type CustomIdOptions, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DeleteResult, type ElasticSearchOptions, type EventHandlers, type EventPayload, type EventPhase, type FieldPreset, type FieldRules, type FilterQuery, type GroupResult, type HookMode, type HttpError, type IController, type IControllerResponse, type IRequestContext, type IResponseFormatter, type IdGenerator, type InferDocument, type InferRawDoc, type JsonSchema, type KeysOfType, type KeysetPaginationOptions, type KeysetPaginationResult, type Logger, LookupBuilder, type LookupOptions, type MinMaxResult, type MongoOperationsMethods, type MultiTenantOptions, type NonNullableFields, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OffsetPaginationResult, type OperationMetric, type OperationOptions, type PaginationConfig, PaginationEngine, type PaginationResult, type ParsedQuery, type PartialBy, type Plugin, type PluginFunction, type PluginType, type PopulateOption, type PopulateSpec, type PrefixedIdOptions, QueryParser, type QueryParserOptions, type ReadPreferenceType, Repository, Repository as default, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RequiredBy, type SchemaBuilderOptions, type SearchMode, type SelectSpec, type SequentialIdOptions, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec, type Strict, type SubdocumentMethods, type UpdateInput, type UpdateManyResult, type UpdateOptions, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidationResult, type ValidatorDefinition, type WithPlugins, type WithTransactionOptions, index_d_exports as actions, aggregateHelpersPlugin, auditLogPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getNextSequence, getSystemManagedFields, immutableField, isFieldUpdateAllowed, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validateUpdateBody, validationChainPlugin };
|
|
1049
|
+
export { type AggregateHelpersMethods, type AggregatePaginationOptions, type AggregatePaginationResult, AggregationBuilder, type AllPluginMethods, type AnyDocument, type AnyModel, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BatchOperationsMethods, type CacheAdapter, type CacheMethods, type CacheOperationOptions, type CacheOptions, type CacheStats, type CascadeOptions, type CascadeRelation, type CreateInput, type CreateOptions, type CrudSchemas, type CustomIdOptions, type DateSequentialIdOptions, type DecodedCursor, type DeepPartial, type DeleteResult, type ElasticSearchOptions, type EventHandlers, type EventPayload, type EventPhase, type FieldPreset, type FieldRules, type FilterQuery, type GroupResult, type HookMode, type HttpError, type IController, type IControllerResponse, type IRequestContext, type IResponseFormatter, type IdGenerator, type InferDocument, type InferRawDoc, type JsonSchema, type KeysOfType, type KeysetPaginationOptions, type KeysetPaginationResult, type Logger, LookupBuilder, type LookupOptions, type MinMaxResult, type MongoOperationsMethods, type MultiTenantOptions, type NonNullableFields, type ObjectId, type ObservabilityOptions, type OffsetPaginationOptions, type OffsetPaginationResult, type OperationMetric, type OperationOptions, type PaginationConfig, PaginationEngine, type PaginationResult, type ParsedQuery, type PartialBy, type Plugin, type PluginFunction, type PluginType, type PopulateOption, type PopulateSpec, type PrefixedIdOptions, QueryParser, type QueryParserOptions, type ReadPreferenceType, Repository, Repository as default, type RepositoryContext, type RepositoryEvent, type RepositoryInstance, type RepositoryOperation, type RepositoryOptions, type RequiredBy, type SchemaBuilderOptions, type SearchMode, type SelectSpec, type SequentialIdOptions, type SoftDeleteFilterMode, type SoftDeleteMethods, type SoftDeleteOptions, type SoftDeleteRepository, type SortDirection, type SortSpec, type Strict, type SubdocumentMethods, type UpdateInput, type UpdateManyResult, type UpdateOptions, type UpdateWithValidationResult, type UserContext, type ValidationChainOptions, type ValidationResult, type ValidatorDefinition, type WithPlugins, type WithTransactionOptions, index_d_exports as actions, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getNextSequence, getSystemManagedFields, immutableField, isFieldUpdateAllowed, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validateUpdateBody, validationChainPlugin };
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { n as createMany, t as create } from "./create-BuO6xt0v.mjs";
|
|
|
3
3
|
import { a as deleteById, d as getById, f as getByQuery, i as LookupBuilder, l as count, p as getOrCreate, r as distinct, s as update, t as aggregate, u as exists } from "./aggregate-BAi4Do-X.mjs";
|
|
4
4
|
import { PaginationEngine } from "./pagination/PaginationEngine.mjs";
|
|
5
5
|
import { c as filterResponseData, l as getFieldsForUser, s as createFieldPreset, u as getMongooseProjection } from "./cache-keys-C8Z9B5sw.mjs";
|
|
6
|
-
import { C as
|
|
6
|
+
import { C as methodRegistryPlugin, D as fieldFilterPlugin, E as timestampPlugin, S as validationChainPlugin, T as auditLogPlugin, _ as autoInject, a as sequentialId, b as requireField, c as auditTrailPlugin, d as cascadePlugin, f as cachePlugin, g as mongoOperationsPlugin, h as batchOperationsPlugin, i as prefixedId, l as observabilityPlugin, m as aggregateHelpersPlugin, n as dateSequentialId, o as elasticSearchPlugin, p as subdocumentPlugin, r as getNextSequence, s as AuditTrailQuery, t as customIdPlugin, u as multiTenantPlugin, v as blockIf, w as softDeletePlugin, x as uniqueField, y as immutableField } from "./custom-id.plugin-m0VW6yYm.mjs";
|
|
7
7
|
import { a as isFieldUpdateAllowed, i as getSystemManagedFields, n as buildCrudSchemasFromMongooseSchema, o as validateUpdateBody, r as getImmutableFields, s as createMemoryCache, t as buildCrudSchemasFromModel } from "./mongooseToJsonSchema-COdDEkIJ.mjs";
|
|
8
8
|
import { t as actions_exports } from "./actions/index.mjs";
|
|
9
9
|
import mongoose from "mongoose";
|
|
@@ -1266,7 +1266,8 @@ var QueryParser = class {
|
|
|
1266
1266
|
searchFields: options.searchFields,
|
|
1267
1267
|
allowedLookupCollections: options.allowedLookupCollections,
|
|
1268
1268
|
allowedFilterFields: options.allowedFilterFields,
|
|
1269
|
-
allowedSortFields: options.allowedSortFields
|
|
1269
|
+
allowedSortFields: options.allowedSortFields,
|
|
1270
|
+
allowedOperators: options.allowedOperators
|
|
1270
1271
|
};
|
|
1271
1272
|
if (this.options.searchMode === "regex" && (!this.options.searchFields || this.options.searchFields.length === 0)) {
|
|
1272
1273
|
warn("[mongokit] searchMode \"regex\" requires searchFields to be specified. Falling back to \"text\" mode.");
|
|
@@ -1336,6 +1337,143 @@ var QueryParser = class {
|
|
|
1336
1337
|
return parsed;
|
|
1337
1338
|
}
|
|
1338
1339
|
/**
|
|
1340
|
+
* Generate OpenAPI-compatible JSON Schema for query parameters.
|
|
1341
|
+
* Arc's defineResource() auto-detects this method and uses it
|
|
1342
|
+
* to document list endpoint query parameters in OpenAPI/Swagger.
|
|
1343
|
+
*
|
|
1344
|
+
* The schema respects parser configuration:
|
|
1345
|
+
* - `allowedOperators`: only documents allowed operators
|
|
1346
|
+
* - `allowedFilterFields`: generates explicit field[op] entries
|
|
1347
|
+
* - `enableLookups` / `enableAggregations`: includes/excludes lookup/aggregate params
|
|
1348
|
+
* - `maxLimit` / `maxSearchLength`: reflected in schema constraints
|
|
1349
|
+
*/
|
|
1350
|
+
getQuerySchema() {
|
|
1351
|
+
const properties = {
|
|
1352
|
+
page: {
|
|
1353
|
+
type: "integer",
|
|
1354
|
+
description: "Page number for offset pagination",
|
|
1355
|
+
default: 1,
|
|
1356
|
+
minimum: 1
|
|
1357
|
+
},
|
|
1358
|
+
limit: {
|
|
1359
|
+
type: "integer",
|
|
1360
|
+
description: "Number of items per page",
|
|
1361
|
+
default: 20,
|
|
1362
|
+
minimum: 1,
|
|
1363
|
+
maximum: this.options.maxLimit
|
|
1364
|
+
},
|
|
1365
|
+
sort: {
|
|
1366
|
+
type: "string",
|
|
1367
|
+
description: "Sort fields (comma-separated). Prefix with - for descending. Example: -createdAt,name"
|
|
1368
|
+
},
|
|
1369
|
+
search: {
|
|
1370
|
+
type: "string",
|
|
1371
|
+
description: this.options.searchMode === "regex" ? `Search across fields${this.options.searchFields ? ` (${this.options.searchFields.join(", ")})` : ""} using case-insensitive regex` : "Full-text search query (requires text index)",
|
|
1372
|
+
maxLength: this.options.maxSearchLength
|
|
1373
|
+
},
|
|
1374
|
+
select: {
|
|
1375
|
+
type: "string",
|
|
1376
|
+
description: "Fields to include/exclude (comma-separated). Prefix with - to exclude. Example: name,email,-password"
|
|
1377
|
+
},
|
|
1378
|
+
populate: {
|
|
1379
|
+
type: "string",
|
|
1380
|
+
description: "Fields to populate/join (comma-separated). Example: author,category"
|
|
1381
|
+
},
|
|
1382
|
+
after: {
|
|
1383
|
+
type: "string",
|
|
1384
|
+
description: "Cursor value for keyset pagination"
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
if (this.options.enableLookups) properties["lookup"] = {
|
|
1388
|
+
type: "object",
|
|
1389
|
+
description: "Custom field lookups ($lookup). Example: lookup[department]=slug or lookup[department][localField]=deptId&lookup[department][foreignField]=_id"
|
|
1390
|
+
};
|
|
1391
|
+
if (this.options.enableAggregations) properties["aggregate"] = {
|
|
1392
|
+
type: "object",
|
|
1393
|
+
description: "Aggregation pipeline stages. Supports: group, match, sort, project. Example: aggregate[group][_id]=$status"
|
|
1394
|
+
};
|
|
1395
|
+
const availableOperators = this.options.allowedOperators ? Object.entries(this.operators).filter(([key]) => this.options.allowedOperators.includes(key)) : Object.entries(this.operators);
|
|
1396
|
+
if (this.options.allowedFilterFields && this.options.allowedFilterFields.length > 0) for (const field of this.options.allowedFilterFields) {
|
|
1397
|
+
properties[field] = {
|
|
1398
|
+
type: "string",
|
|
1399
|
+
description: `Filter by ${field} (exact match)`
|
|
1400
|
+
};
|
|
1401
|
+
for (const [op, mongoOp] of availableOperators) {
|
|
1402
|
+
if (op === "eq") continue;
|
|
1403
|
+
properties[`${field}[${op}]`] = {
|
|
1404
|
+
type: this._getOperatorSchemaType(op),
|
|
1405
|
+
description: this._getOperatorDescription(op, field, mongoOp)
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
properties["_filterOperators"] = {
|
|
1410
|
+
type: "string",
|
|
1411
|
+
description: this._buildOperatorDescription(availableOperators),
|
|
1412
|
+
"x-internal": true
|
|
1413
|
+
};
|
|
1414
|
+
return {
|
|
1415
|
+
type: "object",
|
|
1416
|
+
properties
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Get the JSON Schema type for a filter operator
|
|
1421
|
+
*/
|
|
1422
|
+
_getOperatorSchemaType(op) {
|
|
1423
|
+
if ([
|
|
1424
|
+
"gt",
|
|
1425
|
+
"gte",
|
|
1426
|
+
"lt",
|
|
1427
|
+
"lte",
|
|
1428
|
+
"size"
|
|
1429
|
+
].includes(op)) return "number";
|
|
1430
|
+
if (["exists"].includes(op)) return "boolean";
|
|
1431
|
+
return "string";
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Get a human-readable description for a filter operator
|
|
1435
|
+
*/
|
|
1436
|
+
_getOperatorDescription(op, field, mongoOp) {
|
|
1437
|
+
return {
|
|
1438
|
+
ne: `${field} not equal to value (${mongoOp})`,
|
|
1439
|
+
gt: `${field} greater than value (${mongoOp})`,
|
|
1440
|
+
gte: `${field} greater than or equal to value (${mongoOp})`,
|
|
1441
|
+
lt: `${field} less than value (${mongoOp})`,
|
|
1442
|
+
lte: `${field} less than or equal to value (${mongoOp})`,
|
|
1443
|
+
in: `${field} in comma-separated list (${mongoOp}). Example: value1,value2`,
|
|
1444
|
+
nin: `${field} not in comma-separated list (${mongoOp})`,
|
|
1445
|
+
like: `${field} matches pattern (case-insensitive regex)`,
|
|
1446
|
+
contains: `${field} contains substring (case-insensitive regex)`,
|
|
1447
|
+
regex: `${field} matches regex pattern (${mongoOp})`,
|
|
1448
|
+
exists: `Field ${field} exists (true/false)`,
|
|
1449
|
+
size: `Array field ${field} has exactly N elements (${mongoOp})`,
|
|
1450
|
+
type: `Field ${field} is of BSON type (${mongoOp})`
|
|
1451
|
+
}[op] || `Filter ${field} with ${mongoOp}`;
|
|
1452
|
+
}
|
|
1453
|
+
/**
|
|
1454
|
+
* Build a summary description of all available filter operators
|
|
1455
|
+
*/
|
|
1456
|
+
_buildOperatorDescription(operators) {
|
|
1457
|
+
const lines = ["Available filter operators (use as field[operator]=value):"];
|
|
1458
|
+
for (const [op, mongoOp] of operators) lines.push(` ${op} → ${mongoOp}: ${{
|
|
1459
|
+
eq: "Equal (default when no operator specified)",
|
|
1460
|
+
ne: "Not equal",
|
|
1461
|
+
gt: "Greater than",
|
|
1462
|
+
gte: "Greater than or equal",
|
|
1463
|
+
lt: "Less than",
|
|
1464
|
+
lte: "Less than or equal",
|
|
1465
|
+
in: "In list (comma-separated values)",
|
|
1466
|
+
nin: "Not in list",
|
|
1467
|
+
like: "Pattern match (case-insensitive)",
|
|
1468
|
+
contains: "Contains substring (case-insensitive)",
|
|
1469
|
+
regex: "Regex pattern",
|
|
1470
|
+
exists: "Field exists (true/false)",
|
|
1471
|
+
size: "Array size equals",
|
|
1472
|
+
type: "BSON type check"
|
|
1473
|
+
}[op] || op}`);
|
|
1474
|
+
return lines.join("\n");
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1339
1477
|
* Parse lookup configurations from URL parameters
|
|
1340
1478
|
*
|
|
1341
1479
|
* Supported formats:
|
|
@@ -1608,6 +1746,10 @@ var QueryParser = class {
|
|
|
1608
1746
|
_handleOperatorSyntax(filters, regexFields, operatorMatch, value) {
|
|
1609
1747
|
const [, field, operator] = operatorMatch;
|
|
1610
1748
|
if (value === "" || value === null || value === void 0) return;
|
|
1749
|
+
if (this.options.allowedOperators && !this.options.allowedOperators.includes(operator.toLowerCase())) {
|
|
1750
|
+
warn(`[mongokit] Operator not in allowlist: ${operator}`);
|
|
1751
|
+
return;
|
|
1752
|
+
}
|
|
1611
1753
|
if (operator.toLowerCase() === "options" && regexFields[field]) {
|
|
1612
1754
|
const fieldValue = filters[field];
|
|
1613
1755
|
if (typeof fieldValue === "object" && fieldValue !== null && "$regex" in fieldValue) if (typeof value === "string" && /^[imsx]+$/.test(value)) fieldValue.$options = value;
|
|
@@ -1667,6 +1809,10 @@ var QueryParser = class {
|
|
|
1667
1809
|
parsedFilters[field].between = value;
|
|
1668
1810
|
continue;
|
|
1669
1811
|
}
|
|
1812
|
+
if (this.options.allowedOperators && !this.options.allowedOperators.includes(operator)) {
|
|
1813
|
+
warn(`[mongokit] Operator not in allowlist: ${operator}`);
|
|
1814
|
+
continue;
|
|
1815
|
+
}
|
|
1670
1816
|
if (this.operators[operator]) {
|
|
1671
1817
|
const mongoOperator = this.operators[operator];
|
|
1672
1818
|
let processedValue;
|
|
@@ -1903,4 +2049,4 @@ function createRepository(Model, plugins = [], paginationConfig = {}, options =
|
|
|
1903
2049
|
var src_default = Repository;
|
|
1904
2050
|
|
|
1905
2051
|
//#endregion
|
|
1906
|
-
export { AggregationBuilder, LookupBuilder, PaginationEngine, QueryParser, Repository, actions_exports as actions, aggregateHelpersPlugin, auditLogPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, customIdPlugin, dateSequentialId, src_default as default, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getNextSequence, getSystemManagedFields, immutableField, isFieldUpdateAllowed, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validateUpdateBody, validationChainPlugin };
|
|
2052
|
+
export { AggregationBuilder, AuditTrailQuery, LookupBuilder, PaginationEngine, QueryParser, Repository, actions_exports as actions, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, buildCrudSchemasFromModel, buildCrudSchemasFromMongooseSchema, cachePlugin, cascadePlugin, configureLogger, createError, createFieldPreset, createMemoryCache, createRepository, customIdPlugin, dateSequentialId, src_default as default, elasticSearchPlugin, fieldFilterPlugin, filterResponseData, getFieldsForUser, getImmutableFields, getMongooseProjection, getNextSequence, getSystemManagedFields, immutableField, isFieldUpdateAllowed, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validateUpdateBody, validationChainPlugin };
|
package/dist/plugins/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import "../types-D-gploPr.mjs";
|
|
2
|
-
import { A as
|
|
3
|
-
export { type AggregateHelpersMethods, type BatchOperationsMethods, type CacheMethods, type CustomIdOptions, type DateSequentialIdOptions, type ElasticSearchOptions, type IdGenerator, type MethodRegistryRepository, type MongoOperationsMethods, type MultiTenantOptions, type ObservabilityOptions, type OperationMetric, type PrefixedIdOptions, type SequentialIdOptions, type SoftDeleteMethods, type SubdocumentMethods, aggregateHelpersPlugin, auditLogPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
|
|
2
|
+
import { A as subdocumentPlugin, B as requireField, C as observabilityPlugin, D as CacheMethods, E as cascadePlugin, F as MongoOperationsMethods, G as SoftDeleteMethods, H as validationChainPlugin, I as mongoOperationsPlugin, J as timestampPlugin, K as softDeletePlugin, L as autoInject, M as aggregateHelpersPlugin, N as BatchOperationsMethods, O as cachePlugin, P as batchOperationsPlugin, R as blockIf, S as OperationMetric, T as multiTenantPlugin, U as MethodRegistryRepository, V as uniqueField, W as methodRegistryPlugin, Y as fieldFilterPlugin, _ as AuditTrailMethods, a as SequentialIdOptions, b as auditTrailPlugin, c as getNextSequence, d as ElasticSearchOptions, f as elasticSearchPlugin, g as AuditQueryResult, h as AuditQueryOptions, i as PrefixedIdOptions, j as AggregateHelpersMethods, k as SubdocumentMethods, l as prefixedId, m as AuditOperation, n as DateSequentialIdOptions, o as customIdPlugin, p as AuditEntry, q as auditLogPlugin, r as IdGenerator, s as dateSequentialId, t as CustomIdOptions, u as sequentialId, v as AuditTrailOptions, w as MultiTenantOptions, x as ObservabilityOptions, y as AuditTrailQuery, z as immutableField } from "../custom-id.plugin-BmK0SjR9.mjs";
|
|
3
|
+
export { type AggregateHelpersMethods, type AuditEntry, type AuditOperation, type AuditQueryOptions, type AuditQueryResult, type AuditTrailMethods, type AuditTrailOptions, AuditTrailQuery, type BatchOperationsMethods, type CacheMethods, type CustomIdOptions, type DateSequentialIdOptions, type ElasticSearchOptions, type IdGenerator, type MethodRegistryRepository, type MongoOperationsMethods, type MultiTenantOptions, type ObservabilityOptions, type OperationMetric, type PrefixedIdOptions, type SequentialIdOptions, type SoftDeleteMethods, type SubdocumentMethods, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
|
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as methodRegistryPlugin, D as fieldFilterPlugin, E as timestampPlugin, S as validationChainPlugin, T as auditLogPlugin, _ as autoInject, a as sequentialId, b as requireField, c as auditTrailPlugin, d as cascadePlugin, f as cachePlugin, g as mongoOperationsPlugin, h as batchOperationsPlugin, i as prefixedId, l as observabilityPlugin, m as aggregateHelpersPlugin, n as dateSequentialId, o as elasticSearchPlugin, p as subdocumentPlugin, r as getNextSequence, s as AuditTrailQuery, t as customIdPlugin, u as multiTenantPlugin, v as blockIf, w as softDeletePlugin, x as uniqueField, y as immutableField } from "../custom-id.plugin-m0VW6yYm.mjs";
|
|
2
2
|
|
|
3
|
-
export { aggregateHelpersPlugin, auditLogPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
|
|
3
|
+
export { AuditTrailQuery, aggregateHelpersPlugin, auditLogPlugin, auditTrailPlugin, autoInject, batchOperationsPlugin, blockIf, cachePlugin, cascadePlugin, customIdPlugin, dateSequentialId, elasticSearchPlugin, fieldFilterPlugin, getNextSequence, immutableField, methodRegistryPlugin, mongoOperationsPlugin, multiTenantPlugin, observabilityPlugin, prefixedId, requireField, sequentialId, softDeletePlugin, subdocumentPlugin, timestampPlugin, uniqueField, validationChainPlugin };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/mongokit",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.3",
|
|
4
4
|
"description": "Production-grade MongoDB repositories with zero dependencies - smart pagination, events, and plugins",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -93,10 +93,10 @@
|
|
|
93
93
|
"test:watch": "vitest",
|
|
94
94
|
"test:coverage": "vitest run --coverage",
|
|
95
95
|
"typecheck": "tsc --noEmit",
|
|
96
|
-
"prepublishOnly": "npm run build && npm run typecheck && npm test",
|
|
96
|
+
"prepublishOnly": "npm run build && npm run typecheck && npm test",
|
|
97
97
|
"publish:dry": "npm publish --dry-run --access public",
|
|
98
98
|
"publish:npm": "npm publish --access public",
|
|
99
|
-
"release": "npm run build && npm run typecheck && npm test && npm publish --access public",
|
|
99
|
+
"release": "npm run build && npm run typecheck && npm test && npm publish --access public",
|
|
100
100
|
"release:patch": "npm version patch && npm run release",
|
|
101
101
|
"release:minor": "npm version minor && npm run release",
|
|
102
102
|
"release:major": "npm version major && npm run release"
|