@dynamatix/cat-shared 0.0.82 → 0.0.84

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.
@@ -12,10 +12,31 @@ const customResolvers = {
12
12
  // expenditureRationale: (doc) => `Rationale for ${doc.type}: ${doc.rationale}`
13
13
  };
14
14
 
15
- // Flexible description resolver
15
+ // Expression-based description resolver: resolves ${fieldName} in the expression using doc, and via Lookup if needed
16
+ async function resolveExpressionDescription(doc, expression, resolverType) {
17
+ const matches = [...expression.matchAll(/\$\{([^}]+)\}/g)];
18
+ let result = expression;
19
+ for (const match of matches) {
20
+ const fieldName = match[1];
21
+ let value = doc[fieldName];
22
+ if (resolverType === 'lookup' && value && mongoose.models['Lookup']) {
23
+ const lookupDoc = await mongoose.models['Lookup'].findById(value).lean();
24
+ value = lookupDoc?.name || value;
25
+ }
26
+ result = result.replace(match[0], value ?? '');
27
+ }
28
+ return result;
29
+ }
30
+
31
+ // Update resolveDescription to support expression-based descriptionField
16
32
  async function resolveDescription(field, doc) {
17
- const config = await ValueReferenceMap.findOne({ field });
18
- if (!config) return '';
33
+ const modelName = doc.constructor.modelName;
34
+ const config = await ValueReferenceMap.findOne({ field, model: modelName });
35
+ if (!config) return field;
36
+
37
+ if (config.descriptionField && config.descriptionField.includes('${')) {
38
+ return await resolveExpressionDescription(doc, config.descriptionField, config.descriptionResolverType);
39
+ }
19
40
 
20
41
  switch (config.descriptionResolverType) {
21
42
  case 'lookup': {
@@ -41,6 +62,31 @@ async function resolveDescription(field, doc) {
41
62
  }
42
63
  }
43
64
 
65
+ // Update resolveEntityDescription to support expression-based descriptionField
66
+ async function resolveEntityDescription(doc, auditConfig) {
67
+ if (!auditConfig.descriptionResolutorForExternalData) return '';
68
+ const field = auditConfig.descriptionResolutorForExternalData;
69
+ const value = doc[field];
70
+ // Try to resolve via ValueReferenceMap
71
+ const map = await ValueReferenceMap.findOne({ field });
72
+ if (map && map.descriptionField && map.descriptionField.includes('${')) {
73
+ return await resolveExpressionDescription(doc, map.descriptionField, map.descriptionResolverType);
74
+ }
75
+ if (map && value) {
76
+ const Model = mongoose.models[map.model];
77
+ if (Model) {
78
+ const refDoc = await Model.findById(value).lean();
79
+ return refDoc?.[map.displayField] || value;
80
+ }
81
+ }
82
+ // Fallback: If it's an ObjectId and Lookup model exists, resolve to name
83
+ if (mongoose.Types.ObjectId.isValid(value) && mongoose.models['Lookup']) {
84
+ const lookupDoc = await mongoose.models['Lookup'].findById(value).lean();
85
+ return lookupDoc?.name || value;
86
+ }
87
+ return value;
88
+ }
89
+
44
90
  function applyAuditMiddleware(schema, collectionName) {
45
91
  // Handle creation audit
46
92
  schema.post('save', async function (doc) {
@@ -51,20 +97,50 @@ function applyAuditMiddleware(schema, collectionName) {
51
97
  const userId = context?.userId || 'anonymous';
52
98
  const contextId = context?.contextId;
53
99
 
54
- const log = {
55
- name: `${collectionName} created`,
100
+ const entityDescription = await resolveEntityDescription(doc, auditConfig);
101
+ const logs = [];
102
+
103
+ // Per-field logs
104
+ if (auditConfig.fields?.length) {
105
+ for (const field of auditConfig.fields) {
106
+ const newValue = doc[field];
107
+ const fieldDescription = await resolveDescription(field, doc);
108
+ logs.push({
109
+ name: fieldDescription,
110
+ entity: entityDescription,
111
+ recordId: contextId || doc._id,
112
+ oldValue: null,
113
+ newValue,
114
+ createdBy: userId,
115
+ externalData: {
116
+ description: entityDescription,
117
+ contextId
118
+ }
119
+ });
120
+ }
121
+ }
122
+
123
+ // Overall document creation log (optional)
124
+ logs.push({
125
+ name: 'created',
126
+ entity: entityDescription,
56
127
  recordId: contextId || doc._id,
57
128
  oldValue: null,
58
129
  newValue: 'Document created',
59
130
  createdBy: userId,
60
131
  contextId,
61
- description: await resolveDescription('created', doc)
62
- };
63
- await AuditLog.create(log);
64
- await updateContextAuditCount(contextId, 1);
132
+ externalData: {
133
+ description: entityDescription,
134
+ contextId
135
+ }
136
+ });
65
137
 
66
- if (onAuditLogCreated) {
67
- await onAuditLogCreated([log], contextId);
138
+ if (logs.length) {
139
+ await AuditLog.insertMany(logs);
140
+ await updateContextAuditCount(contextId, logs.length);
141
+ if (onAuditLogCreated) {
142
+ await onAuditLogCreated(logs, contextId);
143
+ }
68
144
  }
69
145
  });
70
146
 
@@ -87,6 +163,8 @@ function applyAuditMiddleware(schema, collectionName) {
87
163
  const userId = context?.userId || 'anonymous';
88
164
  const contextId = context?.contextId;
89
165
 
166
+ const entityDescription = await resolveEntityDescription(result, auditConfig);
167
+
90
168
  for (const field of auditConfig.fields) {
91
169
  const hasChanged =
92
170
  update?.$set?.hasOwnProperty(field) || update?.hasOwnProperty(field);
@@ -96,16 +174,18 @@ function applyAuditMiddleware(schema, collectionName) {
96
174
  const oldValue = this._originalDoc ? this._originalDoc[field] : '';
97
175
 
98
176
  if (oldValue !== newValue) {
99
- // Flexible description
100
- const description = await resolveDescription(field, result);
101
-
177
+ const fieldDescription = await resolveDescription(field, result);
102
178
  logs.push({
103
- name: `${collectionName}.${field}`,
179
+ name: fieldDescription,
180
+ entity: entityDescription,
104
181
  recordId: contextId || result._id,
105
182
  oldValue,
106
183
  newValue,
107
184
  createdBy: userId,
108
- description
185
+ externalData: {
186
+ description: entityDescription,
187
+ contextId: contextId || result._id
188
+ }
109
189
  });
110
190
  }
111
191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamatix/cat-shared",
3
- "version": "0.0.82",
3
+ "version": "0.0.84",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"