@palmetto/nestjs-log-history 0.0.1
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 +186 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +13 -0
- package/dist/dto.d.ts +56 -0
- package/dist/dto.js +52 -0
- package/dist/entities/field-log.d.ts +15 -0
- package/dist/entities/field-log.js +33 -0
- package/dist/entities/log-history.d.ts +31 -0
- package/dist/entities/log-history.js +50 -0
- package/dist/history-model.d.ts +10 -0
- package/dist/history-model.js +12 -0
- package/dist/history-plugin.d.ts +6 -0
- package/dist/history-plugin.js +18 -0
- package/dist/history-saver.d.ts +8 -0
- package/dist/history-saver.js +34 -0
- package/dist/history-schema.d.ts +126 -0
- package/dist/history-schema.js +21 -0
- package/dist/main.d.ts +9 -0
- package/dist/main.js +25 -0
- package/dist/module.d.ts +9 -0
- package/dist/module.js +42 -0
- package/dist/plugin-options.d.ts +3 -0
- package/dist/plugin-options.js +76 -0
- package/dist/service.d.ts +18 -0
- package/dist/service.js +91 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# @palmetto/nestjs-log-history
|
|
2
|
+
|
|
3
|
+
Works with @palmetto/mongoose-log-history to create log history audits for changes via the mongoose library.
|
|
4
|
+
|
|
5
|
+
1. Provides a method to automatically build TrackedFields for your schema
|
|
6
|
+
2. Provides a nestjs `HistoryModule` and `HistoryService` that automatically saves audit histories into a mongodb collection.
|
|
7
|
+
3. Provides a `HistoryDto` to help map audit entities into an API response.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
yarn add @palmetto/nestjs-log-history @palmetto/mongoose-log-history
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage for automatic field binding
|
|
16
|
+
|
|
17
|
+
The `@palmetto/mongoose-log-history` plugin requires that you provide a list of fields to track. This can be tedious for larger schemas.
|
|
18
|
+
You can use the `applyLogHistoryPlugin` or call `getLogHistoryPluginOptions` to manage the options more directly.
|
|
19
|
+
|
|
20
|
+
- getLogHistoryPluginOptions - reflects on the mongoose Schema to build a list of TrackedFields for the mongoose-log-history to track.
|
|
21
|
+
- applyLogHistoryPlugin
|
|
22
|
+
- adds the log history to your schema, calling getLogHistoryPluginOptions to build the list of tracked fields.
|
|
23
|
+
- sets the logger to a `@nestjs/common` Logger instance
|
|
24
|
+
- sets the logHistorySaver that you can provide later via the `setLogHistorySaver` method.
|
|
25
|
+
|
|
26
|
+
1. Setup the mongoose schema to support history by adding the mongoose-log-history plugin
|
|
27
|
+
|
|
28
|
+
Define your mongo schema and know your mongodb collection name:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
export const CustomerEntitySchema = z.object({ ... });
|
|
32
|
+
export const CustomerMongoCollection = "customers";
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Build your schema using schemaBuilder:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
export const CustomerMongooseSchema = schemaBuilder.build(
|
|
39
|
+
CustomerEntitySchema,
|
|
40
|
+
{
|
|
41
|
+
versionKey: false,
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Define a model definition that can be used by the `MongooseModule.forFeature` call, and for configuring the history plugin:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
export const CustomerModelDefinition = {
|
|
50
|
+
name: "Customer",
|
|
51
|
+
schema: CustomerMongooseSchema,
|
|
52
|
+
collection: CustomerMongoCollection,
|
|
53
|
+
};
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Apply the plugin to the mongoose schema object:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { applyLogHistoryPlugin } from "@palmetto/nestjs-log-history";
|
|
60
|
+
|
|
61
|
+
// assign the mongoose-log-history plugin to the schema and collection
|
|
62
|
+
applyLogHistoryPlugin({
|
|
63
|
+
schema: CustomerModelDefinition.schema,
|
|
64
|
+
collection: CustomerModelDefinition.collection,
|
|
65
|
+
skipFields: new Set<string>(["status", "address.coordinates"]), // don't track changes to these fields [dot-notation supported for nested schemas]
|
|
66
|
+
maskedFields: {
|
|
67
|
+
ssn: "***-**-****", // mask the SSN value with asterisks
|
|
68
|
+
dob: (dob) =>
|
|
69
|
+
dob instanceof Date
|
|
70
|
+
? dob.toISOString().replace(/\d{4}/, "YYYY").split("T")[0]
|
|
71
|
+
: "****", // replace year of the date-of-birth with YYYY, if it's isn't a date, just use ****
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
At this point, the mongoose schema is setup to track changes to entities, but still needs a saver method configured.
|
|
77
|
+
Set the saver method using `setLogHistorySaver`.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { setLogHistorySaver } from "@palmetto/nestjs-log-history";
|
|
81
|
+
|
|
82
|
+
async function mySaveHistoriesMethod(
|
|
83
|
+
plugin: LogHistoryPlugin,
|
|
84
|
+
histories: LogHistoryEntry[],
|
|
85
|
+
) {
|
|
86
|
+
// transform and store the histories
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setLogHistorySaver(
|
|
90
|
+
CustomerModelDefinition.collection,
|
|
91
|
+
mySaveHistoriesMethod,
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Nestjs module
|
|
96
|
+
|
|
97
|
+
You can use the `HistoryModule` to provide a fully automatic log history service. This module provides a service that saves the histories to a schema that more closely aligns with Palmetto standards.
|
|
98
|
+
|
|
99
|
+
You must first apply the plugin to your mongoose schema as shown ealier. Next you can use the HistoryModule to configure the HistoryService for your schema.
|
|
100
|
+
|
|
101
|
+
1. Configure the HistoryModule in your Module
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { HistoryModule } from "@palmetto/nestjs-log-history";
|
|
105
|
+
|
|
106
|
+
@Module({
|
|
107
|
+
imports: [
|
|
108
|
+
MongooseModule.forFeature([
|
|
109
|
+
{
|
|
110
|
+
name: CustomerModelDefinition.name, // name for the mongoose model
|
|
111
|
+
schema: CustomerModelDefinition.schema, // mongoose schema to use
|
|
112
|
+
collection: CustomerModelDefinition.collection, // name of the collection to use
|
|
113
|
+
},
|
|
114
|
+
]),
|
|
115
|
+
HistoryModule.forFeature({
|
|
116
|
+
schema: CustomerModelDefinition.schema, // provide the same schema here as in the call to MongooseModule
|
|
117
|
+
collection: CustomerModelDefinition.name, // provide the same collection name here as in the call to MongooseModule
|
|
118
|
+
}),
|
|
119
|
+
],
|
|
120
|
+
providers: [CustomerService],
|
|
121
|
+
})
|
|
122
|
+
export class CustomerModule {}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
2. Inject the HistoryService into the CustomerService
|
|
126
|
+
|
|
127
|
+
Access the HistoryService instance exported from the HistoryModule. The HistoryService extends `BaseMongooseService<LogHistoryEntity>` so you can work with the mongoose collection however you prefer.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import {
|
|
131
|
+
HistoryFindAllOptions,
|
|
132
|
+
HistoryService,
|
|
133
|
+
} from "@palmetto/nestjs-log-history";
|
|
134
|
+
|
|
135
|
+
@Injectable()
|
|
136
|
+
export class CustomerService extends MongooseService<
|
|
137
|
+
CustomerEntity,
|
|
138
|
+
NewCustomerEntity
|
|
139
|
+
> {
|
|
140
|
+
private readonly logger = new Logger(CustomerService.name);
|
|
141
|
+
private readonly allowedPatchFields: Set<keyof CustomerEntity> = new Set(
|
|
142
|
+
Object.keys(NewCustomerEntityObject) as Array<keyof CustomerEntity>,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
constructor(
|
|
146
|
+
@InjectModel(CustomerModelDefinition.name)
|
|
147
|
+
model: PaginatedMetaModel<CustomerEntity>,
|
|
148
|
+
private readonly historyService: HistoryService,
|
|
149
|
+
) {
|
|
150
|
+
super(model);
|
|
151
|
+
this.logger.debug("CustomerService initialized");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get the history for an entity by _id, the most recent entries are returned first.
|
|
156
|
+
*/
|
|
157
|
+
async getHistory(
|
|
158
|
+
id: string | Types.ObjectId,
|
|
159
|
+
options?: HistoryFindAllOptions,
|
|
160
|
+
) {
|
|
161
|
+
return await this.historyService.findAllById(id, options);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// gets the first page of 20 history records for January 2024 UTC [start <= createdAt < end]
|
|
166
|
+
const historyPagedResult = await PaginatedListDto.fromPageResult(
|
|
167
|
+
await service.getHistory(id, {
|
|
168
|
+
limit: 20,
|
|
169
|
+
page: 0,
|
|
170
|
+
start: new Date("2024-01-01T00:00:00Z"),
|
|
171
|
+
end: new Date("2024-02-01T00:00:00Z"),
|
|
172
|
+
}),
|
|
173
|
+
HistoryDto.fromEntity,
|
|
174
|
+
);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
3. Dealing with compressed documents
|
|
178
|
+
|
|
179
|
+
When using the HistoryService and compressed documents (`{ saveWholeDocs: true, compressDocs: true }`) in the log history, you can use the `HistoryDto.fromEntity` method to transform and decompress the `originalDocument` and `updatedDocument` properties.
|
|
180
|
+
Or, use the `HistoryDto.tryDecompress` to uncompress the document field.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const updatedDocument = HistoryDto.tryDecompress(
|
|
184
|
+
parsedEntity.updatedDocument,
|
|
185
|
+
);
|
|
186
|
+
```
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { MaskedFields } from "@palmetto/mongoose-log-history";
|
|
2
|
+
import { Schema } from "mongoose";
|
|
3
|
+
export interface HistoryConfigBase {
|
|
4
|
+
/**
|
|
5
|
+
* The Mongoose schema to which the log history plugin will be applied.
|
|
6
|
+
*/
|
|
7
|
+
schema: Schema;
|
|
8
|
+
/**
|
|
9
|
+
* The name of the MongoDB collection for the model being tracked.
|
|
10
|
+
*/
|
|
11
|
+
collection: string;
|
|
12
|
+
/**
|
|
13
|
+
* The name of the MongoDB collection for storing history entries. Default: `${collection}_histories`
|
|
14
|
+
*/
|
|
15
|
+
historyCollection?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface HistoryPluginConfig extends HistoryConfigBase {
|
|
18
|
+
/**
|
|
19
|
+
* If true, compresses the stored document snapshots to save space.
|
|
20
|
+
*/
|
|
21
|
+
compressDocs?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* If true, saves the entire document snapshot on changes. Default: true.
|
|
24
|
+
*/
|
|
25
|
+
saveWholeDoc?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Fields to skip from tracking changes.
|
|
28
|
+
*/
|
|
29
|
+
skipFields?: Set<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Fields to mask when storing history (e.g., for PII data).
|
|
32
|
+
*/
|
|
33
|
+
maskedFields?: MaskedFields;
|
|
34
|
+
}
|
|
35
|
+
export declare function isHistoryPluginConfig(obj: unknown): obj is HistoryPluginConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isHistoryPluginConfig = isHistoryPluginConfig;
|
|
4
|
+
const mongoose_1 = require("mongoose");
|
|
5
|
+
function isHistoryPluginConfig(obj) {
|
|
6
|
+
return (obj !== undefined &&
|
|
7
|
+
typeof obj === "object" &&
|
|
8
|
+
obj !== null &&
|
|
9
|
+
"schema" in obj &&
|
|
10
|
+
obj.schema instanceof mongoose_1.Schema &&
|
|
11
|
+
"collection" in obj &&
|
|
12
|
+
typeof obj.collection === "string");
|
|
13
|
+
}
|
package/dist/dto.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { LogHistoryEntity } from "./entities/log-history.js";
|
|
3
|
+
export declare const HistoryDtoSchema: z.ZodObject<{
|
|
4
|
+
modelId: z.ZodCodec<z.ZodString, z.ZodCustom<import("mongoose").Types.ObjectId, import("mongoose").Types.ObjectId>>;
|
|
5
|
+
createdAt: z.ZodCodec<z.ZodCustomStringFormat<"date-time">, z.ZodDate>;
|
|
6
|
+
createdBy: z.ZodString;
|
|
7
|
+
isDeleted: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
changeType: z.ZodEnum<{
|
|
9
|
+
create: "create";
|
|
10
|
+
update: "update";
|
|
11
|
+
delete: "delete";
|
|
12
|
+
}>;
|
|
13
|
+
context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
|
+
updatedDocument: z.ZodOptional<z.ZodUnknown>;
|
|
15
|
+
originalDocument: z.ZodOptional<z.ZodUnknown>;
|
|
16
|
+
logs: z.ZodArray<z.ZodObject<{
|
|
17
|
+
fieldName: z.ZodString;
|
|
18
|
+
oldValue: z.ZodOptional<z.ZodNullable<z.ZodAny>>;
|
|
19
|
+
newValue: z.ZodOptional<z.ZodNullable<z.ZodAny>>;
|
|
20
|
+
changeType: z.ZodEnum<{
|
|
21
|
+
add: "add";
|
|
22
|
+
edit: "edit";
|
|
23
|
+
remove: "remove";
|
|
24
|
+
}>;
|
|
25
|
+
}, z.core.$strict>>;
|
|
26
|
+
}, z.core.$strict>;
|
|
27
|
+
export type History = z.infer<typeof HistoryDtoSchema>;
|
|
28
|
+
declare const HistoryDto_base: import("nestjs-zod").ZodDto<z.ZodObject<{
|
|
29
|
+
modelId: z.ZodCodec<z.ZodString, z.ZodCustom<import("mongoose").Types.ObjectId, import("mongoose").Types.ObjectId>>;
|
|
30
|
+
createdAt: z.ZodCodec<z.ZodCustomStringFormat<"date-time">, z.ZodDate>;
|
|
31
|
+
createdBy: z.ZodString;
|
|
32
|
+
isDeleted: z.ZodOptional<z.ZodBoolean>;
|
|
33
|
+
changeType: z.ZodEnum<{
|
|
34
|
+
create: "create";
|
|
35
|
+
update: "update";
|
|
36
|
+
delete: "delete";
|
|
37
|
+
}>;
|
|
38
|
+
context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
39
|
+
updatedDocument: z.ZodOptional<z.ZodUnknown>;
|
|
40
|
+
originalDocument: z.ZodOptional<z.ZodUnknown>;
|
|
41
|
+
logs: z.ZodArray<z.ZodObject<{
|
|
42
|
+
fieldName: z.ZodString;
|
|
43
|
+
oldValue: z.ZodOptional<z.ZodNullable<z.ZodAny>>;
|
|
44
|
+
newValue: z.ZodOptional<z.ZodNullable<z.ZodAny>>;
|
|
45
|
+
changeType: z.ZodEnum<{
|
|
46
|
+
add: "add";
|
|
47
|
+
edit: "edit";
|
|
48
|
+
remove: "remove";
|
|
49
|
+
}>;
|
|
50
|
+
}, z.core.$strict>>;
|
|
51
|
+
}, z.core.$strict>>;
|
|
52
|
+
export declare class HistoryDto extends HistoryDto_base {
|
|
53
|
+
static fromEntity(parsedEntity: LogHistoryEntity): HistoryDto;
|
|
54
|
+
static tryDecompress(doc: unknown): unknown;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
package/dist/dto.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HistoryDto = exports.HistoryDtoSchema = void 0;
|
|
4
|
+
const mongoose_log_history_1 = require("@palmetto/mongoose-log-history");
|
|
5
|
+
const nestjs_zod_dto_1 = require("@palmetto/nestjs-zod-dto");
|
|
6
|
+
const nestjs_toolbelt_1 = require("@palmetto/nestjs-toolbelt");
|
|
7
|
+
const zod_1 = require("zod");
|
|
8
|
+
exports.HistoryDtoSchema = zod_1.z.strictObject({
|
|
9
|
+
modelId: nestjs_toolbelt_1.HexStringToObjectIdCodec,
|
|
10
|
+
createdAt: nestjs_zod_dto_1.DateStringToDateCodec,
|
|
11
|
+
createdBy: zod_1.z.string(),
|
|
12
|
+
isDeleted: zod_1.z.boolean().optional(),
|
|
13
|
+
changeType: zod_1.z.enum(["create", "update", "delete"]),
|
|
14
|
+
context: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
|
|
15
|
+
updatedDocument: zod_1.z.unknown().optional(),
|
|
16
|
+
originalDocument: zod_1.z.unknown().optional(),
|
|
17
|
+
logs: zod_1.z.array(zod_1.z.strictObject({
|
|
18
|
+
fieldName: zod_1.z.string(),
|
|
19
|
+
oldValue: zod_1.z.any().nullish(),
|
|
20
|
+
newValue: zod_1.z.any().nullish(),
|
|
21
|
+
changeType: zod_1.z.enum(["add", "edit", "remove"]),
|
|
22
|
+
})),
|
|
23
|
+
});
|
|
24
|
+
class HistoryDto extends (0, nestjs_zod_dto_1.createDtoFromZodSchema)(exports.HistoryDtoSchema, {
|
|
25
|
+
title: "History",
|
|
26
|
+
}) {
|
|
27
|
+
static fromEntity(parsedEntity) {
|
|
28
|
+
return {
|
|
29
|
+
modelId: parsedEntity.modelId,
|
|
30
|
+
createdAt: parsedEntity.createdAt,
|
|
31
|
+
createdBy: parsedEntity.createdBy,
|
|
32
|
+
isDeleted: parsedEntity.isDeleted,
|
|
33
|
+
changeType: parsedEntity.changeType,
|
|
34
|
+
context: parsedEntity.context || undefined,
|
|
35
|
+
logs: parsedEntity.logs.map((change) => ({
|
|
36
|
+
fieldName: change.fieldName,
|
|
37
|
+
fromValue: change.fromValue,
|
|
38
|
+
toValue: change.toValue,
|
|
39
|
+
changeType: change.changeType,
|
|
40
|
+
})),
|
|
41
|
+
updatedDocument: HistoryDto.tryDecompress(parsedEntity.updatedDocument),
|
|
42
|
+
originalDocument: HistoryDto.tryDecompress(parsedEntity.originalDocument),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
static tryDecompress(doc) {
|
|
46
|
+
if ((0, mongoose_log_history_1.isMongoBinary)(doc) || Buffer.isBuffer(doc)) {
|
|
47
|
+
return (0, mongoose_log_history_1.decompressObject)(doc);
|
|
48
|
+
}
|
|
49
|
+
return doc;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.HistoryDto = HistoryDto;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FieldLog as LogHistoryFieldLog } from "@palmetto/mongoose-log-history";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export declare const FieldLogSchema: z.ZodObject<{
|
|
4
|
+
fieldName: z.ZodString;
|
|
5
|
+
changeType: z.ZodEnum<{
|
|
6
|
+
add: "add";
|
|
7
|
+
edit: "edit";
|
|
8
|
+
remove: "remove";
|
|
9
|
+
}>;
|
|
10
|
+
fromValue: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
11
|
+
toValue: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
12
|
+
context: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export type FieldLog = z.infer<typeof FieldLogSchema>;
|
|
15
|
+
export declare function fieldLogFromEntry(entry: LogHistoryFieldLog): FieldLog;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FieldLogSchema = void 0;
|
|
4
|
+
exports.fieldLogFromEntry = fieldLogFromEntry;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
exports.FieldLogSchema = zod_1.z.object({
|
|
7
|
+
fieldName: zod_1.z
|
|
8
|
+
.string()
|
|
9
|
+
.meta({ description: "The name of the field that was changed" }),
|
|
10
|
+
changeType: zod_1.z
|
|
11
|
+
.enum(["add", "edit", "remove"])
|
|
12
|
+
.meta({ description: "The type of change made to the field" }),
|
|
13
|
+
fromValue: zod_1.z
|
|
14
|
+
.string()
|
|
15
|
+
.nullish()
|
|
16
|
+
.meta({ description: "The previous value of the field" }),
|
|
17
|
+
toValue: zod_1.z
|
|
18
|
+
.string()
|
|
19
|
+
.nullish()
|
|
20
|
+
.meta({ description: "The new value of the field" }),
|
|
21
|
+
context: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).nullish().meta({
|
|
22
|
+
description: "Additional context specific to this field change",
|
|
23
|
+
}),
|
|
24
|
+
});
|
|
25
|
+
function fieldLogFromEntry(entry) {
|
|
26
|
+
return {
|
|
27
|
+
fieldName: entry.field_name,
|
|
28
|
+
changeType: entry.change_type,
|
|
29
|
+
fromValue: entry.from_value,
|
|
30
|
+
toValue: entry.to_value,
|
|
31
|
+
context: entry.context,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SchemaWithId } from "@palmetto/zod-mongoose-schema";
|
|
2
|
+
import { LogHistoryEntry } from "@palmetto/mongoose-log-history";
|
|
3
|
+
import { Types } from "mongoose";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export declare const LogHistoryEntitySchema: z.ZodObject<{
|
|
6
|
+
modelId: z.ZodCustom<Types.ObjectId, Types.ObjectId>;
|
|
7
|
+
changeType: z.ZodEnum<{
|
|
8
|
+
create: "create";
|
|
9
|
+
update: "update";
|
|
10
|
+
delete: "delete";
|
|
11
|
+
}>;
|
|
12
|
+
logs: z.ZodArray<z.ZodObject<{
|
|
13
|
+
fieldName: z.ZodString;
|
|
14
|
+
changeType: z.ZodEnum<{
|
|
15
|
+
add: "add";
|
|
16
|
+
edit: "edit";
|
|
17
|
+
remove: "remove";
|
|
18
|
+
}>;
|
|
19
|
+
fromValue: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
20
|
+
toValue: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
21
|
+
context: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
22
|
+
}, z.core.$strip>>;
|
|
23
|
+
context: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
24
|
+
originalDocument: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
|
|
25
|
+
updatedDocument: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
|
|
26
|
+
isDeleted: z.ZodBoolean;
|
|
27
|
+
createdBy: z.ZodString;
|
|
28
|
+
createdAt: z.ZodDate;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
export type LogHistoryEntity = SchemaWithId<typeof LogHistoryEntitySchema>;
|
|
31
|
+
export declare function entryToEntity(entry: LogHistoryEntry): LogHistoryEntity;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogHistoryEntitySchema = void 0;
|
|
4
|
+
exports.entryToEntity = entryToEntity;
|
|
5
|
+
const zod_mongoose_schema_1 = require("@palmetto/zod-mongoose-schema");
|
|
6
|
+
const mongoose_1 = require("mongoose");
|
|
7
|
+
const zod_1 = require("zod");
|
|
8
|
+
const field_log_js_1 = require("./field-log.js");
|
|
9
|
+
exports.LogHistoryEntitySchema = zod_1.z.object({
|
|
10
|
+
modelId: zod_mongoose_schema_1.ObjectIdSchema,
|
|
11
|
+
changeType: zod_1.z
|
|
12
|
+
.enum(["create", "update", "delete"])
|
|
13
|
+
.meta({ description: "The type of change operation" }),
|
|
14
|
+
logs: zod_1.z
|
|
15
|
+
.array(field_log_js_1.FieldLogSchema)
|
|
16
|
+
.meta({ description: "Array of individual field changes" }),
|
|
17
|
+
context: zod_1.z
|
|
18
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
19
|
+
.nullish()
|
|
20
|
+
.meta({ description: "Global context information for this change" }),
|
|
21
|
+
originalDocument: zod_1.z.unknown().nullish().meta({
|
|
22
|
+
description: "Complete original document snapshot (if saveWholeDoc is enabled)",
|
|
23
|
+
}),
|
|
24
|
+
updatedDocument: zod_1.z.unknown().nullish().meta({
|
|
25
|
+
description: "Complete updated document snapshot (if saveWholeDoc is enabled)",
|
|
26
|
+
}),
|
|
27
|
+
isDeleted: zod_1.z.boolean().meta({
|
|
28
|
+
description: "Whether this log entry represents a deleted document",
|
|
29
|
+
}),
|
|
30
|
+
createdBy: zod_1.z
|
|
31
|
+
.string()
|
|
32
|
+
.meta({ description: "Information about who made the change" }),
|
|
33
|
+
createdAt: zod_1.z
|
|
34
|
+
.date()
|
|
35
|
+
.meta({ description: "Timestamp when the log entry was created" }),
|
|
36
|
+
});
|
|
37
|
+
function entryToEntity(entry) {
|
|
38
|
+
return {
|
|
39
|
+
_id: new mongoose_1.Types.ObjectId(),
|
|
40
|
+
modelId: new mongoose_1.Types.ObjectId(entry.model_id),
|
|
41
|
+
changeType: entry.change_type,
|
|
42
|
+
logs: entry.logs.map(field_log_js_1.fieldLogFromEntry),
|
|
43
|
+
context: entry.context,
|
|
44
|
+
originalDocument: entry.original_doc,
|
|
45
|
+
updatedDocument: entry.updated_doc,
|
|
46
|
+
isDeleted: entry.is_deleted,
|
|
47
|
+
createdBy: typeof entry.created_by === "string" ? entry.created_by : "unknown",
|
|
48
|
+
createdAt: entry.created_at,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Connection, PaginateModel } from "mongoose";
|
|
2
|
+
import { HistoryPluginConfig } from "./config.js";
|
|
3
|
+
import { LogHistoryEntity } from "./entities/log-history.js";
|
|
4
|
+
import { createLogHistorySchema } from "./history-schema.js";
|
|
5
|
+
/**
|
|
6
|
+
* Creates the Mongoose model for log history entries based on the provided options and log history schema.
|
|
7
|
+
* @param options
|
|
8
|
+
* @param schema
|
|
9
|
+
*/
|
|
10
|
+
export declare function createLogHistoryModelFromSchema(connection: Connection, options: HistoryPluginConfig, schema: ReturnType<typeof createLogHistorySchema>): PaginateModel<LogHistoryEntity>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLogHistoryModelFromSchema = createLogHistoryModelFromSchema;
|
|
4
|
+
/**
|
|
5
|
+
* Creates the Mongoose model for log history entries based on the provided options and log history schema.
|
|
6
|
+
* @param options
|
|
7
|
+
* @param schema
|
|
8
|
+
*/
|
|
9
|
+
function createLogHistoryModelFromSchema(connection, options, schema) {
|
|
10
|
+
const historyCollection = options.historyCollection || `${options.collection}_histories`;
|
|
11
|
+
return connection.model(historyCollection, schema, historyCollection);
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyLogHistoryPlugin = applyLogHistoryPlugin;
|
|
4
|
+
const mongoose_log_history_1 = require("@palmetto/mongoose-log-history");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const plugin_options_js_1 = require("./plugin-options.js");
|
|
7
|
+
const main_js_1 = require("./main.js");
|
|
8
|
+
//import { saveHistories } from "./history-saver.js";
|
|
9
|
+
/**
|
|
10
|
+
* Adds the log history plugin to a Mongoose schema.
|
|
11
|
+
* @param options
|
|
12
|
+
*/
|
|
13
|
+
function applyLogHistoryPlugin(options) {
|
|
14
|
+
common_1.Logger.debug(`Applying log history plugin to ${options.collection}`, "applyLogHistoryPlugin");
|
|
15
|
+
const pluginOptions = (0, plugin_options_js_1.getLogHistoryPluginOptions)(options);
|
|
16
|
+
common_1.Logger.debug(`Log history plugin options for ${options.collection}: ${JSON.stringify(pluginOptions)}`, "applyLogHistoryPlugin");
|
|
17
|
+
options.schema.plugin(mongoose_log_history_1.changeLoggingPlugin, Object.assign(Object.assign({}, pluginOptions), { logger: new common_1.Logger(`LogHistoryPlugin.${options.collection}`), logHistorySaver: main_js_1.saveHistories }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { LogHistoryEntry, LogHistoryPlugin, LogHistorySaver } from "@palmetto/mongoose-log-history";
|
|
2
|
+
export declare function saveHistories(plugin: LogHistoryPlugin, histories: LogHistoryEntry[]): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Sets the log history saver function for a specific model.
|
|
5
|
+
* @param modelName
|
|
6
|
+
* @param saver
|
|
7
|
+
*/
|
|
8
|
+
export declare function setLogHistorySaver(modelName: string, saver: LogHistorySaver): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.saveHistories = saveHistories;
|
|
13
|
+
exports.setLogHistorySaver = setLogHistorySaver;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const lazySavers = new Map();
|
|
16
|
+
function saveHistories(plugin, histories) {
|
|
17
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
18
|
+
const fn = lazySavers.get(plugin.modelName);
|
|
19
|
+
if (!fn) {
|
|
20
|
+
common_1.Logger.warn(`No log history saver function set for model ${plugin.modelName}`, "saveHistories");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
common_1.Logger.debug(`Using log history saver for model ${plugin.modelName} to save ${histories.length} entries`, "saveHistories");
|
|
24
|
+
yield fn(plugin, histories);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Sets the log history saver function for a specific model.
|
|
29
|
+
* @param modelName
|
|
30
|
+
* @param saver
|
|
31
|
+
*/
|
|
32
|
+
function setLogHistorySaver(modelName, saver) {
|
|
33
|
+
lazySavers.set(modelName, saver);
|
|
34
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { HistoryPluginConfig } from "./config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates the mongoose schema for log history entries based on the provided options.
|
|
4
|
+
* @param options
|
|
5
|
+
*/
|
|
6
|
+
export declare function createLogHistorySchema(options: HistoryPluginConfig): import("mongoose").Schema<{
|
|
7
|
+
modelId: import("mongoose").Types.ObjectId;
|
|
8
|
+
changeType: "create" | "update" | "delete";
|
|
9
|
+
logs: {
|
|
10
|
+
fieldName: string;
|
|
11
|
+
changeType: "add" | "edit" | "remove";
|
|
12
|
+
fromValue?: string | null | undefined;
|
|
13
|
+
toValue?: string | null | undefined;
|
|
14
|
+
context?: Record<string, unknown> | null | undefined;
|
|
15
|
+
}[];
|
|
16
|
+
isDeleted: boolean;
|
|
17
|
+
createdBy: string;
|
|
18
|
+
createdAt: Date;
|
|
19
|
+
context?: Record<string, unknown> | null | undefined;
|
|
20
|
+
originalDocument?: unknown;
|
|
21
|
+
updatedDocument?: unknown;
|
|
22
|
+
}, import("mongoose").Model<{
|
|
23
|
+
modelId: import("mongoose").Types.ObjectId;
|
|
24
|
+
changeType: "create" | "update" | "delete";
|
|
25
|
+
logs: {
|
|
26
|
+
fieldName: string;
|
|
27
|
+
changeType: "add" | "edit" | "remove";
|
|
28
|
+
fromValue?: string | null | undefined;
|
|
29
|
+
toValue?: string | null | undefined;
|
|
30
|
+
context?: Record<string, unknown> | null | undefined;
|
|
31
|
+
}[];
|
|
32
|
+
isDeleted: boolean;
|
|
33
|
+
createdBy: string;
|
|
34
|
+
createdAt: Date;
|
|
35
|
+
context?: Record<string, unknown> | null | undefined;
|
|
36
|
+
originalDocument?: unknown;
|
|
37
|
+
updatedDocument?: unknown;
|
|
38
|
+
}, {}, {}, {}, import("mongoose").Document<unknown, {}, {
|
|
39
|
+
modelId: import("mongoose").Types.ObjectId;
|
|
40
|
+
changeType: "create" | "update" | "delete";
|
|
41
|
+
logs: {
|
|
42
|
+
fieldName: string;
|
|
43
|
+
changeType: "add" | "edit" | "remove";
|
|
44
|
+
fromValue?: string | null | undefined;
|
|
45
|
+
toValue?: string | null | undefined;
|
|
46
|
+
context?: Record<string, unknown> | null | undefined;
|
|
47
|
+
}[];
|
|
48
|
+
isDeleted: boolean;
|
|
49
|
+
createdBy: string;
|
|
50
|
+
createdAt: Date;
|
|
51
|
+
context?: Record<string, unknown> | null | undefined;
|
|
52
|
+
originalDocument?: unknown;
|
|
53
|
+
updatedDocument?: unknown;
|
|
54
|
+
}, {}, {}> & {
|
|
55
|
+
modelId: import("mongoose").Types.ObjectId;
|
|
56
|
+
changeType: "create" | "update" | "delete";
|
|
57
|
+
logs: {
|
|
58
|
+
fieldName: string;
|
|
59
|
+
changeType: "add" | "edit" | "remove";
|
|
60
|
+
fromValue?: string | null | undefined;
|
|
61
|
+
toValue?: string | null | undefined;
|
|
62
|
+
context?: Record<string, unknown> | null | undefined;
|
|
63
|
+
}[];
|
|
64
|
+
isDeleted: boolean;
|
|
65
|
+
createdBy: string;
|
|
66
|
+
createdAt: Date;
|
|
67
|
+
context?: Record<string, unknown> | null | undefined;
|
|
68
|
+
originalDocument?: unknown;
|
|
69
|
+
updatedDocument?: unknown;
|
|
70
|
+
} & {
|
|
71
|
+
_id: import("mongoose").Types.ObjectId;
|
|
72
|
+
} & {
|
|
73
|
+
__v: number;
|
|
74
|
+
}, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, {
|
|
75
|
+
modelId: import("mongoose").Types.ObjectId;
|
|
76
|
+
changeType: "create" | "update" | "delete";
|
|
77
|
+
logs: {
|
|
78
|
+
fieldName: string;
|
|
79
|
+
changeType: "add" | "edit" | "remove";
|
|
80
|
+
fromValue?: string | null | undefined;
|
|
81
|
+
toValue?: string | null | undefined;
|
|
82
|
+
context?: Record<string, unknown> | null | undefined;
|
|
83
|
+
}[];
|
|
84
|
+
isDeleted: boolean;
|
|
85
|
+
createdBy: string;
|
|
86
|
+
createdAt: Date;
|
|
87
|
+
context?: Record<string, unknown> | null | undefined;
|
|
88
|
+
originalDocument?: unknown;
|
|
89
|
+
updatedDocument?: unknown;
|
|
90
|
+
}, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<{
|
|
91
|
+
modelId: import("mongoose").Types.ObjectId;
|
|
92
|
+
changeType: "create" | "update" | "delete";
|
|
93
|
+
logs: {
|
|
94
|
+
fieldName: string;
|
|
95
|
+
changeType: "add" | "edit" | "remove";
|
|
96
|
+
fromValue?: string | null | undefined;
|
|
97
|
+
toValue?: string | null | undefined;
|
|
98
|
+
context?: Record<string, unknown> | null | undefined;
|
|
99
|
+
}[];
|
|
100
|
+
isDeleted: boolean;
|
|
101
|
+
createdBy: string;
|
|
102
|
+
createdAt: Date;
|
|
103
|
+
context?: Record<string, unknown> | null | undefined;
|
|
104
|
+
originalDocument?: unknown;
|
|
105
|
+
updatedDocument?: unknown;
|
|
106
|
+
}>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<{
|
|
107
|
+
modelId: import("mongoose").Types.ObjectId;
|
|
108
|
+
changeType: "create" | "update" | "delete";
|
|
109
|
+
logs: {
|
|
110
|
+
fieldName: string;
|
|
111
|
+
changeType: "add" | "edit" | "remove";
|
|
112
|
+
fromValue?: string | null | undefined;
|
|
113
|
+
toValue?: string | null | undefined;
|
|
114
|
+
context?: Record<string, unknown> | null | undefined;
|
|
115
|
+
}[];
|
|
116
|
+
isDeleted: boolean;
|
|
117
|
+
createdBy: string;
|
|
118
|
+
createdAt: Date;
|
|
119
|
+
context?: Record<string, unknown> | null | undefined;
|
|
120
|
+
originalDocument?: unknown;
|
|
121
|
+
updatedDocument?: unknown;
|
|
122
|
+
}> & {
|
|
123
|
+
_id: import("mongoose").Types.ObjectId;
|
|
124
|
+
} & {
|
|
125
|
+
__v: number;
|
|
126
|
+
}>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLogHistorySchema = createLogHistorySchema;
|
|
4
|
+
const nestjs_mongoose_1 = require("@palmetto/nestjs-mongoose");
|
|
5
|
+
const zod_mongoose_schema_1 = require("@palmetto/zod-mongoose-schema");
|
|
6
|
+
const log_history_js_1 = require("./entities/log-history.js");
|
|
7
|
+
const schemaBuilder = new zod_mongoose_schema_1.SchemaBuilder();
|
|
8
|
+
/**
|
|
9
|
+
* Creates the mongoose schema for log history entries based on the provided options.
|
|
10
|
+
* @param options
|
|
11
|
+
*/
|
|
12
|
+
function createLogHistorySchema(options) {
|
|
13
|
+
const historyCollection = `${options.collection}_histories`;
|
|
14
|
+
const schema = schemaBuilder.build(log_history_js_1.LogHistoryEntitySchema, {
|
|
15
|
+
collection: historyCollection,
|
|
16
|
+
versionKey: false,
|
|
17
|
+
});
|
|
18
|
+
schema.plugin(nestjs_mongoose_1.paginatePlugin);
|
|
19
|
+
schema.index({ modelId: 1, createdAt: -1 });
|
|
20
|
+
return schema;
|
|
21
|
+
}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./config.js";
|
|
2
|
+
export * from "./dto.js";
|
|
3
|
+
export * from "./history-model.js";
|
|
4
|
+
export * from "./history-plugin.js";
|
|
5
|
+
export * from "./history-saver.js";
|
|
6
|
+
export * from "./history-schema.js";
|
|
7
|
+
export * from "./module.js";
|
|
8
|
+
export * from "./plugin-options.js";
|
|
9
|
+
export * from "./service.js";
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./config.js"), exports);
|
|
18
|
+
__exportStar(require("./dto.js"), exports);
|
|
19
|
+
__exportStar(require("./history-model.js"), exports);
|
|
20
|
+
__exportStar(require("./history-plugin.js"), exports);
|
|
21
|
+
__exportStar(require("./history-saver.js"), exports);
|
|
22
|
+
__exportStar(require("./history-schema.js"), exports);
|
|
23
|
+
__exportStar(require("./module.js"), exports);
|
|
24
|
+
__exportStar(require("./plugin-options.js"), exports);
|
|
25
|
+
__exportStar(require("./service.js"), exports);
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DynamicModule, FactoryProvider, ModuleMetadata } from "@nestjs/common";
|
|
2
|
+
import { HistoryPluginConfig } from "./config.js";
|
|
3
|
+
export interface HistoryModuleOptions extends Pick<ModuleMetadata, "imports"> {
|
|
4
|
+
inject?: FactoryProvider["inject"];
|
|
5
|
+
useFactory: (...args: any[]) => Promise<HistoryPluginConfig> | HistoryPluginConfig;
|
|
6
|
+
}
|
|
7
|
+
export declare class HistoryModule {
|
|
8
|
+
static forFeature(config: HistoryModuleOptions | HistoryPluginConfig): DynamicModule;
|
|
9
|
+
}
|
package/dist/module.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HistoryModule = void 0;
|
|
4
|
+
const mongoose_1 = require("@nestjs/mongoose");
|
|
5
|
+
const config_js_1 = require("./config.js");
|
|
6
|
+
const service_js_1 = require("./service.js");
|
|
7
|
+
const history_schema_js_1 = require("./history-schema.js");
|
|
8
|
+
const history_model_js_1 = require("./history-model.js");
|
|
9
|
+
const HISTORY_OPTIONS_TOKEN = "HISTORY_OPTIONS";
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
|
11
|
+
class HistoryModule {
|
|
12
|
+
static forFeature(config) {
|
|
13
|
+
var _a;
|
|
14
|
+
const isConfig = (0, config_js_1.isHistoryPluginConfig)(config);
|
|
15
|
+
const historyProvider = isConfig
|
|
16
|
+
? {
|
|
17
|
+
provide: HISTORY_OPTIONS_TOKEN,
|
|
18
|
+
useValue: config,
|
|
19
|
+
}
|
|
20
|
+
: Object.assign({ provide: HISTORY_OPTIONS_TOKEN }, config);
|
|
21
|
+
const providers = [
|
|
22
|
+
historyProvider,
|
|
23
|
+
{
|
|
24
|
+
provide: service_js_1.HistoryService,
|
|
25
|
+
inject: [HISTORY_OPTIONS_TOKEN, (0, mongoose_1.getConnectionToken)()],
|
|
26
|
+
useFactory(options, connection) {
|
|
27
|
+
const schema = (0, history_schema_js_1.createLogHistorySchema)(options);
|
|
28
|
+
const model = (0, history_model_js_1.createLogHistoryModelFromSchema)(connection, options, schema);
|
|
29
|
+
return new service_js_1.HistoryService(model, options.collection);
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
return {
|
|
34
|
+
global: false,
|
|
35
|
+
providers,
|
|
36
|
+
imports: [...(isConfig ? [] : (_a = config.imports) !== null && _a !== void 0 ? _a : [])],
|
|
37
|
+
module: HistoryModule,
|
|
38
|
+
exports: [service_js_1.HistoryService],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.HistoryModule = HistoryModule;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLogHistoryPluginOptions = getLogHistoryPluginOptions;
|
|
4
|
+
function getLogHistoryPluginOptions(config) {
|
|
5
|
+
var _a, _b, _c, _d;
|
|
6
|
+
const skipFields = (_a = config.skipFields) !== null && _a !== void 0 ? _a : new Set();
|
|
7
|
+
const maskedFields = (_b = config.maskedFields) !== null && _b !== void 0 ? _b : {};
|
|
8
|
+
const collectionName = config.collection;
|
|
9
|
+
const schema = config.schema;
|
|
10
|
+
function getTrackedFields(parentField, schema) {
|
|
11
|
+
const trackedFields = [];
|
|
12
|
+
schema.eachPath((path) => {
|
|
13
|
+
const fullPath = `${parentField}${path}`;
|
|
14
|
+
if (fullPath === "meta" ||
|
|
15
|
+
fullPath === "_id" ||
|
|
16
|
+
fullPath === "__v" ||
|
|
17
|
+
skipFields.has(fullPath)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const entry = {
|
|
21
|
+
value: path,
|
|
22
|
+
};
|
|
23
|
+
const pathSchema = schema.path(path);
|
|
24
|
+
if (fullPath in maskedFields) {
|
|
25
|
+
entry.mask = maskedFields[fullPath];
|
|
26
|
+
}
|
|
27
|
+
else if (pathSchema.options.encryption) {
|
|
28
|
+
entry.mask = "****";
|
|
29
|
+
}
|
|
30
|
+
if (pathSchema.schema) {
|
|
31
|
+
entry.trackedFields = getTrackedFields(`${fullPath}.`, pathSchema.schema);
|
|
32
|
+
if (Array.isArray(pathSchema.options.type)) {
|
|
33
|
+
entry.arrayType = "custom-key";
|
|
34
|
+
entry.arrayKey = "id";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else if (Array.isArray(pathSchema.options.type)) {
|
|
38
|
+
entry.arrayType = "simple";
|
|
39
|
+
}
|
|
40
|
+
trackedFields.push(entry);
|
|
41
|
+
});
|
|
42
|
+
if (schema.discriminators) {
|
|
43
|
+
for (const discSchema of Object.values(schema.discriminators)) {
|
|
44
|
+
const discTrackedFields = getTrackedFields(parentField, discSchema);
|
|
45
|
+
for (const discField of discTrackedFields) {
|
|
46
|
+
if (!trackedFields.find((tf) => tf.value === discField.value)) {
|
|
47
|
+
trackedFields.push(discField);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return trackedFields;
|
|
53
|
+
}
|
|
54
|
+
const trackedFields = getTrackedFields("", schema);
|
|
55
|
+
return {
|
|
56
|
+
modelName: collectionName,
|
|
57
|
+
saveWholeDoc: (_c = config.saveWholeDoc) !== null && _c !== void 0 ? _c : true,
|
|
58
|
+
compressDocs: (_d = config.compressDocs) !== null && _d !== void 0 ? _d : false,
|
|
59
|
+
softDelete: (doc) => isMetaDeleted(doc) ? true : false,
|
|
60
|
+
userField: "meta.updatedBy",
|
|
61
|
+
trackedFields,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function isMetaDeleted(doc) {
|
|
65
|
+
if ("meta.deletedAt" in doc && doc["meta.deletedAt"] instanceof Date) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
if ("meta" in doc &&
|
|
69
|
+
doc.meta !== null &&
|
|
70
|
+
typeof doc.meta === "object" &&
|
|
71
|
+
"deletedAt" in doc.meta &&
|
|
72
|
+
doc.meta.deletedAt instanceof Date) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FilterQuery, PaginateModel, Types } from "mongoose";
|
|
2
|
+
import { BaseMongooseService } from "@palmetto/nestjs-mongoose/dist/services/base-mongoose.service";
|
|
3
|
+
import { LogHistoryEntry, LogHistoryPlugin } from "@palmetto/mongoose-log-history";
|
|
4
|
+
import { LogHistoryEntity } from "./entities/log-history.js";
|
|
5
|
+
export interface HistoryFindAllOptions {
|
|
6
|
+
start?: Date;
|
|
7
|
+
end?: Date;
|
|
8
|
+
limit?: number;
|
|
9
|
+
page?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class HistoryService extends BaseMongooseService<LogHistoryEntity> {
|
|
12
|
+
private readonly logger;
|
|
13
|
+
constructor(model: PaginateModel<LogHistoryEntity>, modelName: string);
|
|
14
|
+
saveHistories(_: LogHistoryPlugin, histories: LogHistoryEntry[]): Promise<void>;
|
|
15
|
+
findAllById(id: string | Types.ObjectId, { limit, page, start, end }?: HistoryFindAllOptions): Promise<import("@palmetto/nestjs-mongoose").PageResult<LogHistoryEntity>>;
|
|
16
|
+
removeAll(filter?: FilterQuery<LogHistoryEntity>): Promise<number>;
|
|
17
|
+
removeAllById(id: string | Types.ObjectId): Promise<number>;
|
|
18
|
+
}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
var HistoryService_1;
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.HistoryService = void 0;
|
|
23
|
+
const mongoose_1 = require("mongoose");
|
|
24
|
+
const common_1 = require("@nestjs/common");
|
|
25
|
+
const base_mongoose_service_1 = require("@palmetto/nestjs-mongoose/dist/services/base-mongoose.service");
|
|
26
|
+
const log_history_js_1 = require("./entities/log-history.js");
|
|
27
|
+
const history_saver_js_1 = require("./history-saver.js");
|
|
28
|
+
let HistoryService = HistoryService_1 = class HistoryService extends base_mongoose_service_1.BaseMongooseService {
|
|
29
|
+
constructor(model, modelName) {
|
|
30
|
+
super(model);
|
|
31
|
+
this.logger = new common_1.Logger(HistoryService_1.name);
|
|
32
|
+
(0, history_saver_js_1.setLogHistorySaver)(modelName, this.saveHistories.bind(this));
|
|
33
|
+
this.logger.debug(`HistoryService initialized for ${model.collection.name} for ${modelName}`);
|
|
34
|
+
}
|
|
35
|
+
saveHistories(_, histories) {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
if (histories.length === 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const bulkOps = histories.map((entry) => {
|
|
41
|
+
return {
|
|
42
|
+
insertOne: {
|
|
43
|
+
document: (0, log_history_js_1.entryToEntity)(entry),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
yield this.model.bulkWrite(bulkOps);
|
|
48
|
+
this.logger.debug(`saved ${histories.length} history entries`);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
findAllById(id_1) {
|
|
52
|
+
return __awaiter(this, arguments, void 0, function* (id, { limit, page, start, end } = {}) {
|
|
53
|
+
const query = {
|
|
54
|
+
modelId: new mongoose_1.Types.ObjectId(id),
|
|
55
|
+
};
|
|
56
|
+
if (start || end) {
|
|
57
|
+
const createdAtFilter = [];
|
|
58
|
+
if (start) {
|
|
59
|
+
createdAtFilter.push({ createdAt: { $gte: start } });
|
|
60
|
+
}
|
|
61
|
+
if (end) {
|
|
62
|
+
createdAtFilter.push({ createdAt: { $lt: end } });
|
|
63
|
+
}
|
|
64
|
+
query.$and = createdAtFilter;
|
|
65
|
+
}
|
|
66
|
+
return yield this.findAllPaged(query, { limit, page });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
removeAll() {
|
|
70
|
+
return __awaiter(this, arguments, void 0, function* (filter = {}) {
|
|
71
|
+
const result = yield this.model.collection.deleteMany(filter);
|
|
72
|
+
this.logger.debug(`removed ${result.deletedCount} history entries`);
|
|
73
|
+
return result.deletedCount;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
removeAllById(id) {
|
|
77
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
const objectId = new mongoose_1.Types.ObjectId(id);
|
|
79
|
+
const result = yield this.model.collection.deleteMany({
|
|
80
|
+
modelId: objectId,
|
|
81
|
+
});
|
|
82
|
+
this.logger.debug(`removed ${result.deletedCount} history entries for modelId ${objectId.toHexString()}`);
|
|
83
|
+
return result.deletedCount;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
exports.HistoryService = HistoryService;
|
|
88
|
+
exports.HistoryService = HistoryService = HistoryService_1 = __decorate([
|
|
89
|
+
(0, common_1.Injectable)(),
|
|
90
|
+
__metadata("design:paramtypes", [Object, String])
|
|
91
|
+
], HistoryService);
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@palmetto/nestjs-log-history",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "./dist/main.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"lint": "yarn run -T eslint --fix ./src",
|
|
7
|
+
"format": "yarn run -T prettier --write --loglevel warn .",
|
|
8
|
+
"tc": "tsc --noEmit",
|
|
9
|
+
"build": "yarn clean && yarn ci:build",
|
|
10
|
+
"clean": "rm -rf ./dist/",
|
|
11
|
+
"ci:build": "tsc -p tsconfig.build.json",
|
|
12
|
+
"ci:lint": "yarn run -T eslint . && yarn run -T prettier --check --loglevel warn .",
|
|
13
|
+
"ci:tc": "yarn tc",
|
|
14
|
+
"hook:lint": "eslint --cache --fix",
|
|
15
|
+
"hook:format": "prettier --write --loglevel warn",
|
|
16
|
+
"hook:tc": "yarn tc",
|
|
17
|
+
"prepublishOnly": "yarn build",
|
|
18
|
+
"test-runner": "../../scripts/test-runner.sh",
|
|
19
|
+
"test": "yarn run test-runner vitest run",
|
|
20
|
+
"test:watch": "yarn run test-runner vitest watch"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@nestjs/common": "^11.1.6",
|
|
24
|
+
"@nestjs/core": "^11.1.6",
|
|
25
|
+
"@nestjs/mongoose": "^11",
|
|
26
|
+
"@nestjs/testing": "^11.1.5",
|
|
27
|
+
"@palmetto/mongoose-log-history": "^1.2.4",
|
|
28
|
+
"@palmetto/nestjs-mongoose": "^2.2.6",
|
|
29
|
+
"@palmetto/nestjs-toolbelt": "^1.1.9",
|
|
30
|
+
"@palmetto/nestjs-zod-dto": "^2.0.1",
|
|
31
|
+
"@palmetto/zod-mongoose-schema": "^0.2.4",
|
|
32
|
+
"@swc/core": "^1.13.3",
|
|
33
|
+
"@types/node": "^24.2.1",
|
|
34
|
+
"mongodb": "~6.20.0",
|
|
35
|
+
"mongoose": "^8.19.3",
|
|
36
|
+
"typescript": "5.7.3",
|
|
37
|
+
"unplugin-swc": "^1.5.6",
|
|
38
|
+
"vitest": "^3.2.4",
|
|
39
|
+
"zod": "^4.0.13"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@nestjs/common": "^11.1.6",
|
|
43
|
+
"@nestjs/core": "^11.1.6",
|
|
44
|
+
"@nestjs/mongoose": "^11",
|
|
45
|
+
"@palmetto/mongoose-log-history": "*",
|
|
46
|
+
"@palmetto/nestjs-mongoose": "^2.2.6",
|
|
47
|
+
"@palmetto/nestjs-toolbelt": "^1.1.9",
|
|
48
|
+
"@palmetto/nestjs-zod-dto": "^2.0.1",
|
|
49
|
+
"@palmetto/zod-mongoose-schema": "^0.2.4",
|
|
50
|
+
"mongodb": "~6.20.0",
|
|
51
|
+
"mongoose": "^8.19.3",
|
|
52
|
+
"zod": "^4.0.13"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"reflect-metadata": "^0.2.2",
|
|
56
|
+
"rxjs": "^7.8.2"
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"dist/**/*",
|
|
60
|
+
"README.md"
|
|
61
|
+
],
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=20"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
}
|
|
68
|
+
}
|