@lucaapp/service-utils 5.34.1 → 5.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import type { Model, ModelStatic, Sequelize } from 'sequelize';
|
|
2
2
|
import { DataTypes } from 'sequelize';
|
|
3
|
+
type AuditOperation = 'create' | 'update' | 'destroy';
|
|
3
4
|
export interface PartitionTable {
|
|
4
5
|
tableName: string;
|
|
5
6
|
timestamp: Date;
|
|
6
7
|
}
|
|
8
|
+
interface DiffEntry {
|
|
9
|
+
key: string;
|
|
10
|
+
values: {
|
|
11
|
+
old?: unknown;
|
|
12
|
+
new?: unknown;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
7
15
|
interface AddHistoryOptions {
|
|
8
16
|
exclude?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Attributes whose change is recorded, but whose values are not. The diff
|
|
19
|
+
* keeps the key with a placeholder, so the log shows THAT the attribute
|
|
20
|
+
* changed without storing large or sensitive payloads.
|
|
21
|
+
*/
|
|
22
|
+
redact?: string[];
|
|
9
23
|
}
|
|
10
24
|
interface AtomicAuditLogOptions {
|
|
11
25
|
attributeRevisionModelTableName?: string;
|
|
@@ -18,6 +32,8 @@ export interface PartitionResult {
|
|
|
18
32
|
archiveTableName: string;
|
|
19
33
|
timestamp: Date;
|
|
20
34
|
}
|
|
35
|
+
export declare const REDACTED_VALUE = "[redacted]";
|
|
36
|
+
export declare const computeDiff: <T extends Model>(instance: T, operation: AuditOperation, exclude: string[], redact: string[]) => DiffEntry[];
|
|
21
37
|
export declare class AtomicAuditLog {
|
|
22
38
|
private sequelize;
|
|
23
39
|
private tableName;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AtomicAuditLog = void 0;
|
|
3
|
+
exports.AtomicAuditLog = exports.computeDiff = exports.REDACTED_VALUE = void 0;
|
|
4
4
|
const sequelize_1 = require("sequelize");
|
|
5
5
|
const DEFAULT_EXCLUDE = [
|
|
6
6
|
'id',
|
|
@@ -11,25 +11,32 @@ const DEFAULT_EXCLUDE = [
|
|
|
11
11
|
'revision',
|
|
12
12
|
];
|
|
13
13
|
const isValidValue = (value) => value !== undefined && value !== null;
|
|
14
|
-
|
|
14
|
+
exports.REDACTED_VALUE = '[redacted]';
|
|
15
|
+
const computeCreateDiff = (currentValues, excludeSet, redactSet) => {
|
|
15
16
|
const diff = [];
|
|
16
17
|
for (const [key, value] of Object.entries(currentValues)) {
|
|
17
18
|
if (!excludeSet.has(key) && isValidValue(value)) {
|
|
18
|
-
diff.push({
|
|
19
|
+
diff.push({
|
|
20
|
+
key,
|
|
21
|
+
values: { new: redactSet.has(key) ? exports.REDACTED_VALUE : value },
|
|
22
|
+
});
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
25
|
return diff;
|
|
22
26
|
};
|
|
23
|
-
const computeDestroyDiff = (previousValues, excludeSet) => {
|
|
27
|
+
const computeDestroyDiff = (previousValues, excludeSet, redactSet) => {
|
|
24
28
|
const diff = [];
|
|
25
29
|
for (const [key, value] of Object.entries(previousValues)) {
|
|
26
30
|
if (!excludeSet.has(key) && isValidValue(value)) {
|
|
27
|
-
diff.push({
|
|
31
|
+
diff.push({
|
|
32
|
+
key,
|
|
33
|
+
values: { old: redactSet.has(key) ? exports.REDACTED_VALUE : value },
|
|
34
|
+
});
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
37
|
return diff;
|
|
31
38
|
};
|
|
32
|
-
const computeUpdateDiff = (instance, previousValues, currentValues, excludeSet) => {
|
|
39
|
+
const computeUpdateDiff = (instance, previousValues, currentValues, excludeSet, redactSet) => {
|
|
33
40
|
const diff = [];
|
|
34
41
|
const changedKeys = instance.changed();
|
|
35
42
|
if (!changedKeys || !Array.isArray(changedKeys)) {
|
|
@@ -39,24 +46,29 @@ const computeUpdateDiff = (instance, previousValues, currentValues, excludeSet)
|
|
|
39
46
|
if (!excludeSet.has(key)) {
|
|
40
47
|
diff.push({
|
|
41
48
|
key,
|
|
42
|
-
values:
|
|
49
|
+
values: redactSet.has(key)
|
|
50
|
+
? { old: exports.REDACTED_VALUE, new: exports.REDACTED_VALUE }
|
|
51
|
+
: { old: previousValues[key], new: currentValues[key] },
|
|
43
52
|
});
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
55
|
return diff;
|
|
47
56
|
};
|
|
48
|
-
|
|
57
|
+
// exported for tests
|
|
58
|
+
const computeDiff = (instance, operation, exclude, redact) => {
|
|
49
59
|
const previousValues = instance.previous();
|
|
50
60
|
const currentValues = instance.dataValues;
|
|
51
61
|
const excludeSet = new Set([...DEFAULT_EXCLUDE, ...exclude]);
|
|
62
|
+
const redactSet = new Set(redact);
|
|
52
63
|
if (operation === 'create') {
|
|
53
|
-
return computeCreateDiff(currentValues, excludeSet);
|
|
64
|
+
return computeCreateDiff(currentValues, excludeSet, redactSet);
|
|
54
65
|
}
|
|
55
66
|
if (operation === 'destroy') {
|
|
56
|
-
return computeDestroyDiff(previousValues, excludeSet);
|
|
67
|
+
return computeDestroyDiff(previousValues, excludeSet, redactSet);
|
|
57
68
|
}
|
|
58
|
-
return computeUpdateDiff(instance, previousValues, currentValues, excludeSet);
|
|
69
|
+
return computeUpdateDiff(instance, previousValues, currentValues, excludeSet, redactSet);
|
|
59
70
|
};
|
|
71
|
+
exports.computeDiff = computeDiff;
|
|
60
72
|
class AtomicAuditLog {
|
|
61
73
|
constructor(sequelize, options = {}) {
|
|
62
74
|
this.sequelize = sequelize;
|
|
@@ -86,12 +98,12 @@ class AtomicAuditLog {
|
|
|
86
98
|
return `${this.tableName}_View`;
|
|
87
99
|
}
|
|
88
100
|
addHistory(model, options = {}) {
|
|
89
|
-
const { exclude = [] } = options;
|
|
101
|
+
const { exclude = [], redact = [] } = options;
|
|
90
102
|
const modelName = model.name;
|
|
91
103
|
const primaryKeyAttribute = model.primaryKeyAttribute || 'uuid';
|
|
92
|
-
const createHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, 'create');
|
|
93
|
-
const updateHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, 'update');
|
|
94
|
-
const destroyHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, 'destroy');
|
|
104
|
+
const createHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, redact, 'create');
|
|
105
|
+
const updateHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, redact, 'update');
|
|
106
|
+
const destroyHook = this.createAuditHook(modelName, primaryKeyAttribute, exclude, redact, 'destroy');
|
|
95
107
|
model.addHook('afterCreate', createHook);
|
|
96
108
|
model.addHook('afterUpdate', updateHook);
|
|
97
109
|
model.addHook('afterDestroy', destroyHook);
|
|
@@ -144,10 +156,10 @@ class AtomicAuditLog {
|
|
|
144
156
|
transaction,
|
|
145
157
|
});
|
|
146
158
|
}
|
|
147
|
-
createAuditHook(modelName, primaryKeyAttribute, exclude, operation) {
|
|
159
|
+
createAuditHook(modelName, primaryKeyAttribute, exclude, redact, operation) {
|
|
148
160
|
return async (instance, options) => {
|
|
149
161
|
try {
|
|
150
|
-
const diff = computeDiff(instance, operation, exclude);
|
|
162
|
+
const diff = (0, exports.computeDiff)(instance, operation, exclude, redact);
|
|
151
163
|
if (diff.length === 0) {
|
|
152
164
|
return;
|
|
153
165
|
}
|
package/dist/lib/logger/index.js
CHANGED
|
@@ -60,16 +60,28 @@ const ignore = (request) => {
|
|
|
60
60
|
false);
|
|
61
61
|
};
|
|
62
62
|
exports.ignore = ignore;
|
|
63
|
+
// Sentry is an optional peer dependency: services that do not report to
|
|
64
|
+
// Sentry do not install @sentry/node or pino-sentry-transport.
|
|
65
|
+
const isInstalled = (moduleName) => {
|
|
66
|
+
try {
|
|
67
|
+
require.resolve(moduleName);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
63
74
|
class LoggerFactory {
|
|
64
75
|
static getLogger(logLevel, sentryOptions) {
|
|
65
76
|
const { sentryDsn, releaseVersion, minLevel } = sentryOptions ?? {};
|
|
77
|
+
const withSentry = !!sentryDsn && isInstalled('pino-sentry-transport');
|
|
66
78
|
const transports = [
|
|
67
79
|
{
|
|
68
80
|
target: 'pino/file',
|
|
69
81
|
options: { destination: 1 },
|
|
70
82
|
level: logLevel,
|
|
71
83
|
},
|
|
72
|
-
...(
|
|
84
|
+
...(withSentry
|
|
73
85
|
? [
|
|
74
86
|
{
|
|
75
87
|
target: 'pino-sentry-transport',
|
|
@@ -89,7 +101,7 @@ class LoggerFactory {
|
|
|
89
101
|
]
|
|
90
102
|
: []),
|
|
91
103
|
];
|
|
92
|
-
|
|
104
|
+
const logger = (0, pino_1.default)({
|
|
93
105
|
level: logLevel,
|
|
94
106
|
mixin: requestTracer_1.getRequestTracerMixin,
|
|
95
107
|
serializers: {
|
|
@@ -100,6 +112,10 @@ class LoggerFactory {
|
|
|
100
112
|
targets: transports,
|
|
101
113
|
},
|
|
102
114
|
});
|
|
115
|
+
if (sentryDsn && !withSentry) {
|
|
116
|
+
logger.warn('sentryDsn is set but pino-sentry-transport is not installed, Sentry reporting is off');
|
|
117
|
+
}
|
|
118
|
+
return logger;
|
|
103
119
|
}
|
|
104
120
|
static getHttpLogger(options) {
|
|
105
121
|
const { requestSerializer, responseSerializer, errorSerializer, defaultHttpLogLevel, customMessage, } = new HttpLoggerHelpers(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lucaapp/service-utils",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.36.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
"@aws-sdk/client-s3": "^3.1035.0",
|
|
24
24
|
"@aws-sdk/lib-storage": "^3.1035.0",
|
|
25
25
|
"@hapi/boom": "^10.0.1",
|
|
26
|
-
"@sentry/node": "^9.10.1",
|
|
27
26
|
"@tsconfig/node24": "24.0.0",
|
|
28
27
|
"@types/express": "4.17.23",
|
|
29
28
|
"@types/express-serve-static-core": "^4.17.43",
|
|
@@ -47,7 +46,6 @@
|
|
|
47
46
|
"moment-timezone": "^0.5.44",
|
|
48
47
|
"negotiator": "^0.6.4",
|
|
49
48
|
"pino-http": "9.0.0",
|
|
50
|
-
"pino-sentry-transport": "^1.5.1",
|
|
51
49
|
"prom-client": "14.1.0",
|
|
52
50
|
"response-time": "2.3.4",
|
|
53
51
|
"sequelize": "6.37.8",
|
|
@@ -61,6 +59,7 @@
|
|
|
61
59
|
"pg-boss": "12.14.0"
|
|
62
60
|
},
|
|
63
61
|
"devDependencies": {
|
|
62
|
+
"@sentry/node": "^9.10.1",
|
|
64
63
|
"@types/busboy": "^1.5.4",
|
|
65
64
|
"@types/lodash": "^4.14.187",
|
|
66
65
|
"@types/negotiator": "^0.6.4",
|
|
@@ -69,20 +68,33 @@
|
|
|
69
68
|
"@types/uuid": "^10.0.0",
|
|
70
69
|
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
|
71
70
|
"@typescript-eslint/parser": "^7.15.0",
|
|
71
|
+
"@typescript/native": "npm:typescript@7.0.2",
|
|
72
72
|
"@vitest/coverage-v8": "4.1.1",
|
|
73
73
|
"conventional-changelog-conventionalcommits": "^8.0.0",
|
|
74
74
|
"eslint": "8.57.0",
|
|
75
75
|
"eslint-plugin-prettier": "4.2.1",
|
|
76
76
|
"eslint-plugin-vitest": "0.4.1",
|
|
77
|
-
"
|
|
77
|
+
"pino-sentry-transport": "^1.5.1",
|
|
78
78
|
"prettier": "2.7.1",
|
|
79
79
|
"semantic-release": "^25.0.3",
|
|
80
80
|
"semantic-release-monorepo": "8.0.2",
|
|
81
81
|
"supertest": "6.3.4",
|
|
82
|
-
"@typescript/native": "npm:typescript@7.0.2",
|
|
83
82
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
84
83
|
"vite-tsconfig-paths": "4.3.2",
|
|
85
|
-
"vitest": "4.1.1"
|
|
84
|
+
"vitest": "4.1.1",
|
|
85
|
+
"yarn-osv-audit": "0.1.8"
|
|
86
|
+
},
|
|
87
|
+
"peerDependencies": {
|
|
88
|
+
"@sentry/node": "^9.10.1",
|
|
89
|
+
"pino-sentry-transport": "^1.5.1"
|
|
90
|
+
},
|
|
91
|
+
"peerDependenciesMeta": {
|
|
92
|
+
"@sentry/node": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"pino-sentry-transport": {
|
|
96
|
+
"optional": true
|
|
97
|
+
}
|
|
86
98
|
},
|
|
87
99
|
"resolutions": {
|
|
88
100
|
"tar": "7.5.21",
|