@machinemetrics/mm-erp-sdk 0.3.0-beta.0 → 0.3.0-beta.2
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/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/data-sync-service/configuration-manager.d.ts.map +1 -1
- package/dist/services/data-sync-service/configuration-manager.js +30 -30
- package/dist/services/data-sync-service/configuration-manager.js.map +1 -1
- package/dist/services/data-sync-service/data-sync-service.d.ts.map +1 -1
- package/dist/services/data-sync-service/data-sync-service.js +9 -0
- package/dist/services/data-sync-service/data-sync-service.js.map +1 -1
- package/dist/services/data-sync-service/jobs/clean-up-expired-cache.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/clean-up-expired-cache.js +1 -2
- package/dist/services/data-sync-service/jobs/clean-up-expired-cache.js.map +1 -1
- package/dist/services/data-sync-service/jobs/from-erp.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/from-erp.js +7 -13
- package/dist/services/data-sync-service/jobs/from-erp.js.map +1 -1
- package/dist/services/data-sync-service/jobs/retry-failed-labor-tickets.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/retry-failed-labor-tickets.js +1 -2
- package/dist/services/data-sync-service/jobs/retry-failed-labor-tickets.js.map +1 -1
- package/dist/services/data-sync-service/jobs/run-migrations.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/run-migrations.js +1 -2
- package/dist/services/data-sync-service/jobs/run-migrations.js.map +1 -1
- package/dist/services/data-sync-service/jobs/to-erp.d.ts.map +1 -1
- package/dist/services/data-sync-service/jobs/to-erp.js +12 -3
- package/dist/services/data-sync-service/jobs/to-erp.js.map +1 -1
- package/dist/services/data-sync-service/nats-labor-ticket-listener.d.ts +30 -0
- package/dist/services/data-sync-service/nats-labor-ticket-listener.d.ts.map +1 -0
- package/dist/services/data-sync-service/nats-labor-ticket-listener.js +290 -0
- package/dist/services/data-sync-service/nats-labor-ticket-listener.js.map +1 -0
- package/dist/services/mm-api-service/company-info.d.ts +13 -0
- package/dist/services/mm-api-service/company-info.d.ts.map +1 -0
- package/dist/services/mm-api-service/company-info.js +60 -0
- package/dist/services/mm-api-service/company-info.js.map +1 -0
- package/dist/services/mm-api-service/index.d.ts +7 -0
- package/dist/services/mm-api-service/index.d.ts.map +1 -1
- package/dist/services/mm-api-service/index.js +5 -0
- package/dist/services/mm-api-service/index.js.map +1 -1
- package/dist/services/mm-api-service/mm-api-service.d.ts +6 -0
- package/dist/services/mm-api-service/mm-api-service.d.ts.map +1 -1
- package/dist/services/mm-api-service/mm-api-service.js +15 -3
- package/dist/services/mm-api-service/mm-api-service.js.map +1 -1
- package/dist/services/mm-api-service/types/receive-types.d.ts +3 -0
- package/dist/services/mm-api-service/types/receive-types.d.ts.map +1 -1
- package/dist/services/mm-api-service/types/receive-types.js +1 -0
- package/dist/services/mm-api-service/types/receive-types.js.map +1 -1
- package/dist/services/mm-api-service/types/send-types.d.ts +17 -8
- package/dist/services/mm-api-service/types/send-types.d.ts.map +1 -1
- package/dist/services/mm-api-service/types/send-types.js +41 -17
- package/dist/services/mm-api-service/types/send-types.js.map +1 -1
- package/dist/services/nats-service/nats-service.d.ts +114 -0
- package/dist/services/nats-service/nats-service.d.ts.map +1 -0
- package/dist/services/nats-service/nats-service.js +244 -0
- package/dist/services/nats-service/nats-service.js.map +1 -0
- package/dist/services/nats-service/test-nats-subscriber.d.ts +6 -0
- package/dist/services/nats-service/test-nats-subscriber.d.ts.map +1 -0
- package/dist/services/nats-service/test-nats-subscriber.js +79 -0
- package/dist/services/nats-service/test-nats-subscriber.js.map +1 -0
- package/dist/services/reporting-service/logger.d.ts.map +1 -1
- package/dist/services/reporting-service/logger.js +31 -6
- package/dist/services/reporting-service/logger.js.map +1 -1
- package/dist/types/erp-connector.d.ts +1 -8
- package/dist/types/erp-connector.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/error-formatter.d.ts +19 -0
- package/dist/utils/error-formatter.d.ts.map +1 -0
- package/dist/utils/error-formatter.js +184 -0
- package/dist/utils/error-formatter.js.map +1 -0
- package/dist/utils/http-client.js +2 -4
- package/dist/utils/http-client.js.map +1 -1
- package/dist/utils/index.d.ts +5 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/local-data-store/jobs-shared-data.d.ts +0 -2
- package/dist/utils/local-data-store/jobs-shared-data.d.ts.map +1 -1
- package/dist/utils/local-data-store/jobs-shared-data.js +0 -2
- package/dist/utils/local-data-store/jobs-shared-data.js.map +1 -1
- package/dist/utils/mm-labor-ticket-helpers.d.ts +4 -3
- package/dist/utils/mm-labor-ticket-helpers.d.ts.map +1 -1
- package/dist/utils/mm-labor-ticket-helpers.js +7 -12
- package/dist/utils/mm-labor-ticket-helpers.js.map +1 -1
- package/dist/utils/standard-process-drivers/labor-ticket-erp-synchronizer.d.ts +0 -15
- package/dist/utils/standard-process-drivers/labor-ticket-erp-synchronizer.d.ts.map +1 -1
- package/dist/utils/standard-process-drivers/labor-ticket-erp-synchronizer.js +46 -180
- package/dist/utils/standard-process-drivers/labor-ticket-erp-synchronizer.js.map +1 -1
- package/dist/utils/standard-process-drivers/mm-entity-processor.d.ts +1 -7
- package/dist/utils/standard-process-drivers/mm-entity-processor.d.ts.map +1 -1
- package/dist/utils/standard-process-drivers/mm-entity-processor.js +1 -7
- package/dist/utils/standard-process-drivers/mm-entity-processor.js.map +1 -1
- package/dist/utils/standard-process-drivers/standard-process-drivers.d.ts +2 -8
- package/dist/utils/standard-process-drivers/standard-process-drivers.d.ts.map +1 -1
- package/dist/utils/standard-process-drivers/standard-process-drivers.js +18 -27
- package/dist/utils/standard-process-drivers/standard-process-drivers.js.map +1 -1
- package/dist/utils/time-utils.d.ts.map +1 -1
- package/dist/utils/time-utils.js +0 -7
- package/dist/utils/time-utils.js.map +1 -1
- package/package.json +5 -4
- package/src/index.ts +3 -0
- package/src/services/data-sync-service/configuration-manager.ts +37 -50
- package/src/services/data-sync-service/data-sync-service.ts +10 -0
- package/src/services/data-sync-service/jobs/clean-up-expired-cache.ts +1 -2
- package/src/services/data-sync-service/jobs/from-erp.ts +7 -13
- package/src/services/data-sync-service/jobs/retry-failed-labor-tickets.ts +1 -2
- package/src/services/data-sync-service/jobs/run-migrations.ts +1 -2
- package/src/services/data-sync-service/jobs/to-erp.ts +12 -3
- package/src/services/data-sync-service/nats-labor-ticket-listener.ts +342 -0
- package/src/services/mm-api-service/company-info.ts +87 -0
- package/src/services/mm-api-service/index.ts +8 -0
- package/src/services/mm-api-service/mm-api-service.ts +20 -3
- package/src/services/mm-api-service/types/receive-types.ts +1 -0
- package/src/services/mm-api-service/types/send-types.ts +40 -16
- package/src/services/nats-service/nats-service.ts +351 -0
- package/src/services/nats-service/test-nats-subscriber.ts +96 -0
- package/src/services/reporting-service/logger.ts +39 -7
- package/src/types/erp-connector.ts +1 -8
- package/src/types/index.ts +0 -8
- package/src/utils/error-formatter.ts +205 -0
- package/src/utils/http-client.ts +3 -4
- package/src/utils/index.ts +6 -5
- package/src/utils/local-data-store/jobs-shared-data.ts +0 -2
- package/src/utils/mm-labor-ticket-helpers.ts +8 -11
- package/src/utils/standard-process-drivers/labor-ticket-erp-synchronizer.ts +64 -220
- package/src/utils/standard-process-drivers/mm-entity-processor.ts +1 -7
- package/src/utils/standard-process-drivers/standard-process-drivers.ts +19 -33
- package/src/utils/time-utils.ts +0 -11
- package/dist/types/flattened-work-order.d.ts +0 -99
- package/dist/types/flattened-work-order.d.ts.map +0 -1
- package/dist/types/flattened-work-order.js +0 -2
- package/dist/types/flattened-work-order.js.map +0 -1
- package/dist/utils/env.d.ts +0 -8
- package/dist/utils/env.d.ts.map +0 -1
- package/dist/utils/env.js +0 -58
- package/dist/utils/env.js.map +0 -1
- package/dist/utils/erp-timezone-utils.d.ts +0 -20
- package/dist/utils/erp-timezone-utils.d.ts.map +0 -1
- package/dist/utils/erp-timezone-utils.js +0 -75
- package/dist/utils/erp-timezone-utils.js.map +0 -1
- package/src/types/flattened-work-order.ts +0 -108
- package/src/utils/env.ts +0 -75
- package/src/utils/erp-timezone-utils.ts +0 -99
|
@@ -19,7 +19,8 @@ export class MMSendPerson implements IToRESTApiObject {
|
|
|
19
19
|
public personId: string,
|
|
20
20
|
public firstName: string,
|
|
21
21
|
public lastName: string,
|
|
22
|
-
public isActive: boolean
|
|
22
|
+
public isActive: boolean,
|
|
23
|
+
public customFields: Record<string, any> = {}
|
|
23
24
|
) {}
|
|
24
25
|
|
|
25
26
|
toRESTApiObject(): Record<string, string> {
|
|
@@ -28,6 +29,7 @@ export class MMSendPerson implements IToRESTApiObject {
|
|
|
28
29
|
firstName: this.firstName,
|
|
29
30
|
lastName: this.lastName,
|
|
30
31
|
isActive: this.isActive ? "1" : "0",
|
|
32
|
+
customFields: JSON.stringify(this.customFields),
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -36,7 +38,8 @@ export class MMSendPerson implements IToRESTApiObject {
|
|
|
36
38
|
data.personId || "",
|
|
37
39
|
data.firstName || "",
|
|
38
40
|
data.lastName || "",
|
|
39
|
-
data.isActive === "1"
|
|
41
|
+
data.isActive === "1",
|
|
42
|
+
data.customFields ? JSON.parse(data.customFields) : {}
|
|
40
43
|
);
|
|
41
44
|
}
|
|
42
45
|
}
|
|
@@ -49,7 +52,8 @@ export class MMSendResource implements IToRESTApiObject {
|
|
|
49
52
|
public description: string, // Text description of the resource (optional)
|
|
50
53
|
public type: string, // The type of resource (optional)
|
|
51
54
|
public productionBurdenRateHourly: number, // The cost associated with running this machine in production/hour (optional)
|
|
52
|
-
public setupBurdenRateHourly: number // The cost associated with running this machine in setup/hour (optional)
|
|
55
|
+
public setupBurdenRateHourly: number, // The cost associated with running this machine in setup/hour (optional)
|
|
56
|
+
public customFields: Record<string, any> = {}
|
|
53
57
|
) {}
|
|
54
58
|
|
|
55
59
|
toRESTApiObject(): Record<string, string> {
|
|
@@ -61,6 +65,7 @@ export class MMSendResource implements IToRESTApiObject {
|
|
|
61
65
|
type: this.type,
|
|
62
66
|
productionBurdenRateHourly: this.productionBurdenRateHourly.toString(),
|
|
63
67
|
setupBurdenRateHourly: this.setupBurdenRateHourly.toString(),
|
|
68
|
+
customFields: JSON.stringify(this.customFields),
|
|
64
69
|
};
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -72,7 +77,8 @@ export class MMSendResource implements IToRESTApiObject {
|
|
|
72
77
|
data.description || "",
|
|
73
78
|
data.type || "",
|
|
74
79
|
parseFloat(data.productionBurdenRateHourly || "0"),
|
|
75
|
-
parseFloat(data.setupBurdenRateHourly || "0")
|
|
80
|
+
parseFloat(data.setupBurdenRateHourly || "0"),
|
|
81
|
+
data.customFields ? JSON.parse(data.customFields) : {}
|
|
76
82
|
);
|
|
77
83
|
}
|
|
78
84
|
}
|
|
@@ -81,7 +87,8 @@ export class MMSendPart implements IToRESTApiObject {
|
|
|
81
87
|
constructor(
|
|
82
88
|
public partNumber: string,
|
|
83
89
|
public partRevision: string,
|
|
84
|
-
public method: string
|
|
90
|
+
public method: string,
|
|
91
|
+
public customFields: Record<string, any> = {}
|
|
85
92
|
) {}
|
|
86
93
|
|
|
87
94
|
toRESTApiObject(): Record<string, string> {
|
|
@@ -89,6 +96,7 @@ export class MMSendPart implements IToRESTApiObject {
|
|
|
89
96
|
partNumber: this.partNumber,
|
|
90
97
|
partRevision: this.partRevision,
|
|
91
98
|
method: this.method,
|
|
99
|
+
customFields: JSON.stringify(this.customFields),
|
|
92
100
|
};
|
|
93
101
|
}
|
|
94
102
|
|
|
@@ -96,7 +104,8 @@ export class MMSendPart implements IToRESTApiObject {
|
|
|
96
104
|
return new MMSendPart(
|
|
97
105
|
data.partNumber || "",
|
|
98
106
|
data.partRevision || "",
|
|
99
|
-
data.method || ""
|
|
107
|
+
data.method || "",
|
|
108
|
+
data.customFields ? JSON.parse(data.customFields) : {}
|
|
100
109
|
);
|
|
101
110
|
}
|
|
102
111
|
}
|
|
@@ -111,7 +120,8 @@ export class MMSendPartOperation implements IToRESTApiObject {
|
|
|
111
120
|
public cycleTimeMs: number,
|
|
112
121
|
public setupTimeMs: number,
|
|
113
122
|
public description: string,
|
|
114
|
-
public quantityPerPart: number
|
|
123
|
+
public quantityPerPart: number,
|
|
124
|
+
public customFields: Record<string, any> = {}
|
|
115
125
|
) {}
|
|
116
126
|
|
|
117
127
|
toRESTApiObject(): Record<string, string | null> {
|
|
@@ -125,6 +135,7 @@ export class MMSendPartOperation implements IToRESTApiObject {
|
|
|
125
135
|
setupTimeMs: this.setupTimeMs.toString(),
|
|
126
136
|
description: this.description,
|
|
127
137
|
quantityPerPart: this.quantityPerPart.toString(),
|
|
138
|
+
customFields: JSON.stringify(this.customFields),
|
|
128
139
|
};
|
|
129
140
|
}
|
|
130
141
|
|
|
@@ -140,7 +151,8 @@ export class MMSendPartOperation implements IToRESTApiObject {
|
|
|
140
151
|
parseInt(data.cycleTimeMs || "0"),
|
|
141
152
|
parseInt(data.setupTimeMs || "0"),
|
|
142
153
|
data.description || "",
|
|
143
|
-
parseFloat(data.quantityPerPart || "1")
|
|
154
|
+
parseFloat(data.quantityPerPart || "1"),
|
|
155
|
+
data.customFields ? JSON.parse(data.customFields) : {}
|
|
144
156
|
);
|
|
145
157
|
}
|
|
146
158
|
}
|
|
@@ -160,7 +172,8 @@ export class MMSendWorkOrder implements IToRESTApiObject {
|
|
|
160
172
|
public quantityRequired: number,
|
|
161
173
|
public partNumber: string,
|
|
162
174
|
public partRevision: string,
|
|
163
|
-
public method: string
|
|
175
|
+
public method: string,
|
|
176
|
+
public customFields: Record<string, any> = {}
|
|
164
177
|
) {}
|
|
165
178
|
|
|
166
179
|
toRESTApiObject(): Record<string, string | null> {
|
|
@@ -179,6 +192,7 @@ export class MMSendWorkOrder implements IToRESTApiObject {
|
|
|
179
192
|
partNumber: this.partNumber,
|
|
180
193
|
partRevision: this.partRevision,
|
|
181
194
|
method: this.method,
|
|
195
|
+
customFields: JSON.stringify(this.customFields),
|
|
182
196
|
};
|
|
183
197
|
}
|
|
184
198
|
|
|
@@ -197,7 +211,8 @@ export class MMSendWorkOrder implements IToRESTApiObject {
|
|
|
197
211
|
parseFloat(data.quantityRequired || "0"),
|
|
198
212
|
data.partNumber || "",
|
|
199
213
|
data.partRevision || "",
|
|
200
|
-
data.method || ""
|
|
214
|
+
data.method || "",
|
|
215
|
+
data.customFields ? JSON.parse(data.customFields) : {}
|
|
201
216
|
);
|
|
202
217
|
}
|
|
203
218
|
}
|
|
@@ -222,7 +237,8 @@ export class MMSendWorkOrderOperation implements IToRESTApiObject {
|
|
|
222
237
|
public setupburdenRatehourly: number,
|
|
223
238
|
public operationType: string,
|
|
224
239
|
public quantityPerPart: number,
|
|
225
|
-
public status: string
|
|
240
|
+
public status: string,
|
|
241
|
+
public customFields: Record<string, any> = {}
|
|
226
242
|
) {}
|
|
227
243
|
|
|
228
244
|
toRESTApiObject(): Record<string, string | null> {
|
|
@@ -246,6 +262,7 @@ export class MMSendWorkOrderOperation implements IToRESTApiObject {
|
|
|
246
262
|
operationType: this.operationType,
|
|
247
263
|
quantityPerPart: this.quantityPerPart.toString(),
|
|
248
264
|
status: this.status,
|
|
265
|
+
customFields: JSON.stringify(this.customFields),
|
|
249
266
|
};
|
|
250
267
|
}
|
|
251
268
|
|
|
@@ -271,7 +288,8 @@ export class MMSendWorkOrderOperation implements IToRESTApiObject {
|
|
|
271
288
|
parseFloat(data.setupburdenRatehourly || "0"),
|
|
272
289
|
data.operationType || "",
|
|
273
290
|
parseFloat(data.quantityPerPart || "1"),
|
|
274
|
-
data.status || ""
|
|
291
|
+
data.status || "",
|
|
292
|
+
data.customFields ? JSON.parse(data.customFields) : {}
|
|
275
293
|
);
|
|
276
294
|
}
|
|
277
295
|
}
|
|
@@ -282,7 +300,8 @@ export class MMSendReason implements IToRESTApiObject {
|
|
|
282
300
|
public category: string,
|
|
283
301
|
public code: string,
|
|
284
302
|
public description: string,
|
|
285
|
-
public entityType: string
|
|
303
|
+
public entityType: string,
|
|
304
|
+
public customFields: Record<string, any> = {}
|
|
286
305
|
) {}
|
|
287
306
|
|
|
288
307
|
toRESTApiObject(): Record<string, string> {
|
|
@@ -292,6 +311,7 @@ export class MMSendReason implements IToRESTApiObject {
|
|
|
292
311
|
code: this.code,
|
|
293
312
|
description: this.description,
|
|
294
313
|
entityType: this.entityType,
|
|
314
|
+
customFields: JSON.stringify(this.customFields),
|
|
295
315
|
};
|
|
296
316
|
}
|
|
297
317
|
|
|
@@ -301,7 +321,8 @@ export class MMSendReason implements IToRESTApiObject {
|
|
|
301
321
|
data.category || "",
|
|
302
322
|
data.code || "",
|
|
303
323
|
data.description || "",
|
|
304
|
-
data.entityType || ""
|
|
324
|
+
data.entityType || "",
|
|
325
|
+
data.customFields ? JSON.parse(data.customFields) : {}
|
|
305
326
|
);
|
|
306
327
|
}
|
|
307
328
|
}
|
|
@@ -323,7 +344,8 @@ export class MMSendLaborTicket implements IToRESTApiObject {
|
|
|
323
344
|
public badParts: number,
|
|
324
345
|
public type: string,
|
|
325
346
|
public comment: string,
|
|
326
|
-
public state: string
|
|
347
|
+
public state: string,
|
|
348
|
+
public customFields: Record<string, any> = {}
|
|
327
349
|
) {}
|
|
328
350
|
|
|
329
351
|
toRESTApiObject(): Record<string, string | null> {
|
|
@@ -344,6 +366,7 @@ export class MMSendLaborTicket implements IToRESTApiObject {
|
|
|
344
366
|
type: this.type,
|
|
345
367
|
comment: this.comment,
|
|
346
368
|
state: this.state,
|
|
369
|
+
customFields: JSON.stringify(this.customFields),
|
|
347
370
|
};
|
|
348
371
|
}
|
|
349
372
|
|
|
@@ -366,7 +389,8 @@ export class MMSendLaborTicket implements IToRESTApiObject {
|
|
|
366
389
|
parseInt(data.badParts || "0"),
|
|
367
390
|
data.type || "",
|
|
368
391
|
data.comment || "",
|
|
369
|
-
data.state || ""
|
|
392
|
+
data.state || "",
|
|
393
|
+
data.customFields ? JSON.parse(data.customFields) : {}
|
|
370
394
|
);
|
|
371
395
|
}
|
|
372
396
|
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NATS Service - Central connection and subscription management
|
|
3
|
+
* Allows connectors to register handlers for different NATS subjects
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { connect, NatsConnection, StringCodec, Subscription } from "nats";
|
|
7
|
+
import { logger } from "../reporting-service/index.js";
|
|
8
|
+
|
|
9
|
+
const sc = StringCodec();
|
|
10
|
+
|
|
11
|
+
export interface NatsMessageHandler {
|
|
12
|
+
/**
|
|
13
|
+
* Handler function for NATS messages
|
|
14
|
+
* - Return a value to reply (if msg has reply subject)
|
|
15
|
+
* - Return void/undefined for pub-sub (no reply)
|
|
16
|
+
*/
|
|
17
|
+
handle: (data: any, subject: string) => Promise<any | void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface NatsHandlerRegistration {
|
|
21
|
+
/**
|
|
22
|
+
* Subject pattern to subscribe to (supports wildcards: *, >)
|
|
23
|
+
* Examples:
|
|
24
|
+
* - "mm.16.epic01.labor-ticket.*" (single level wildcard)
|
|
25
|
+
* - "mm.16.epic01.*" (multi-level wildcard)
|
|
26
|
+
*/
|
|
27
|
+
subject: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Handler for this subject
|
|
31
|
+
*/
|
|
32
|
+
handler: NatsMessageHandler;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Optional description for logging/debugging
|
|
36
|
+
*/
|
|
37
|
+
description?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface NatsServiceConfig {
|
|
41
|
+
/**
|
|
42
|
+
* NATS server URLs
|
|
43
|
+
*/
|
|
44
|
+
servers: string | string[];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Connection name (for monitoring)
|
|
48
|
+
*/
|
|
49
|
+
name: string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Location reference (for subject namespacing)
|
|
53
|
+
*/
|
|
54
|
+
locationRef: string;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* ERP type (epicor, infor, etc.)
|
|
58
|
+
*/
|
|
59
|
+
erpType: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Enable/disable NATS
|
|
63
|
+
*/
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Auto-reconnect settings
|
|
68
|
+
*/
|
|
69
|
+
reconnect?: boolean;
|
|
70
|
+
maxReconnectAttempts?: number;
|
|
71
|
+
reconnectTimeWait?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class NatsService {
|
|
75
|
+
private connection: NatsConnection | null = null;
|
|
76
|
+
private subscriptions: Map<string, Subscription> = new Map();
|
|
77
|
+
private config: NatsServiceConfig;
|
|
78
|
+
private handlers: NatsHandlerRegistration[] = [];
|
|
79
|
+
private statusPublishTimer: NodeJS.Timeout | null = null;
|
|
80
|
+
|
|
81
|
+
constructor(config: NatsServiceConfig) {
|
|
82
|
+
this.config = config;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Register a handler for a specific subject pattern
|
|
87
|
+
*/
|
|
88
|
+
registerHandler(registration: NatsHandlerRegistration): void {
|
|
89
|
+
logger.info("Registering NATS handler", {
|
|
90
|
+
subject: registration.subject,
|
|
91
|
+
description: registration.description,
|
|
92
|
+
});
|
|
93
|
+
this.handlers.push(registration);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Connect to NATS and start all registered handlers
|
|
98
|
+
*/
|
|
99
|
+
async connect(): Promise<void> {
|
|
100
|
+
if (!this.config.enabled) {
|
|
101
|
+
logger.info("NATS is disabled, skipping connection");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
logger.info("Connecting to NATS...", {
|
|
107
|
+
servers: this.config.servers,
|
|
108
|
+
name: this.config.name,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.connection = await connect({
|
|
112
|
+
servers: this.config.servers,
|
|
113
|
+
name: this.config.name,
|
|
114
|
+
reconnect: this.config.reconnect ?? true,
|
|
115
|
+
maxReconnectAttempts: this.config.maxReconnectAttempts ?? -1,
|
|
116
|
+
reconnectTimeWait: this.config.reconnectTimeWait ?? 2000,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
logger.info("Connected to NATS", {
|
|
120
|
+
server: this.connection.getServer(),
|
|
121
|
+
clientId: this.connection.info?.client_id,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Start all registered handlers
|
|
125
|
+
for (const registration of this.handlers) {
|
|
126
|
+
await this.startHandler(registration);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Start automatic status publishing
|
|
130
|
+
this.startStatusPublishing();
|
|
131
|
+
|
|
132
|
+
// Monitor connection status
|
|
133
|
+
this.monitorConnection();
|
|
134
|
+
|
|
135
|
+
// Graceful shutdown
|
|
136
|
+
this.setupShutdown();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
logger.error("Failed to connect to NATS", { error });
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Start a single handler (subscribe to its subject)
|
|
145
|
+
*/
|
|
146
|
+
private async startHandler(registration: NatsHandlerRegistration): Promise<void> {
|
|
147
|
+
if (!this.connection) {
|
|
148
|
+
throw new Error("NATS connection not established");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const sub = this.connection.subscribe(registration.subject);
|
|
152
|
+
this.subscriptions.set(registration.subject, sub);
|
|
153
|
+
|
|
154
|
+
logger.info("Started NATS handler", {
|
|
155
|
+
subject: registration.subject,
|
|
156
|
+
description: registration.description,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Process messages
|
|
160
|
+
(async () => {
|
|
161
|
+
for await (const msg of sub) {
|
|
162
|
+
try {
|
|
163
|
+
const data = sc.decode(msg.data);
|
|
164
|
+
|
|
165
|
+
logger.info("Received NATS message", {
|
|
166
|
+
subject: msg.subject,
|
|
167
|
+
hasReply: !!msg.reply,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Parse JSON if possible
|
|
171
|
+
let parsedData: any;
|
|
172
|
+
try {
|
|
173
|
+
parsedData = JSON.parse(data);
|
|
174
|
+
} catch {
|
|
175
|
+
parsedData = data;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Call handler
|
|
179
|
+
const response = await registration.handler.handle(parsedData, msg.subject);
|
|
180
|
+
|
|
181
|
+
// If there's a reply subject and handler returned something, reply
|
|
182
|
+
if (msg.reply && response !== undefined) {
|
|
183
|
+
const responseStr = JSON.stringify(response);
|
|
184
|
+
msg.respond(sc.encode(responseStr));
|
|
185
|
+
logger.info("Sent reply", { replySubject: msg.reply });
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
logger.error("Error handling NATS message", {
|
|
189
|
+
subject: msg.subject,
|
|
190
|
+
error,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Send error response if reply expected
|
|
194
|
+
if (msg.reply) {
|
|
195
|
+
const errorResponse = {
|
|
196
|
+
status: "error",
|
|
197
|
+
error: {
|
|
198
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
199
|
+
code: "HANDLER_ERROR",
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
msg.respond(sc.encode(JSON.stringify(errorResponse)));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
})();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Publish a message to a subject (for pub/sub)
|
|
211
|
+
*/
|
|
212
|
+
async publish(subject: string, data: any): Promise<void> {
|
|
213
|
+
if (!this.connection) {
|
|
214
|
+
throw new Error("NATS connection not established");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const message = typeof data === "string" ? data : JSON.stringify(data);
|
|
218
|
+
this.connection.publish(subject, sc.encode(message));
|
|
219
|
+
|
|
220
|
+
logger.info("Published NATS message", { subject });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Send a request and wait for reply (for request-reply)
|
|
225
|
+
*/
|
|
226
|
+
async request(subject: string, data: any, timeoutMs: number = 30000): Promise<any> {
|
|
227
|
+
if (!this.connection) {
|
|
228
|
+
throw new Error("NATS connection not established");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const message = typeof data === "string" ? data : JSON.stringify(data);
|
|
232
|
+
const response = await this.connection.request(
|
|
233
|
+
subject,
|
|
234
|
+
sc.encode(message),
|
|
235
|
+
{ timeout: timeoutMs }
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const responseData = sc.decode(response.data);
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
return JSON.parse(responseData);
|
|
242
|
+
} catch {
|
|
243
|
+
return responseData;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check if connected to NATS
|
|
249
|
+
*/
|
|
250
|
+
isConnected(): boolean {
|
|
251
|
+
return this.connection !== null && !this.connection.isClosed();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Start automatic status publishing (every 30 seconds)
|
|
256
|
+
*/
|
|
257
|
+
private startStatusPublishing(): void {
|
|
258
|
+
logger.info("Starting status publishing (every 30 seconds)");
|
|
259
|
+
|
|
260
|
+
// Publish immediately on start
|
|
261
|
+
this.publishStatus();
|
|
262
|
+
|
|
263
|
+
// Then publish every 30 seconds
|
|
264
|
+
this.statusPublishTimer = setInterval(() => {
|
|
265
|
+
this.publishStatus();
|
|
266
|
+
}, 30000);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Publish connector status
|
|
271
|
+
*/
|
|
272
|
+
private async publishStatus(): Promise<void> {
|
|
273
|
+
try {
|
|
274
|
+
const status = {
|
|
275
|
+
timestamp: new Date().toISOString(),
|
|
276
|
+
locationRef: this.config.locationRef,
|
|
277
|
+
erpType: this.config.erpType,
|
|
278
|
+
natsConnected: this.isConnected(),
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
await this.publish(
|
|
282
|
+
`mm.16.${this.config.locationRef}.status`,
|
|
283
|
+
status
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
logger.debug("Published connector status");
|
|
287
|
+
} catch (error) {
|
|
288
|
+
logger.error("Failed to publish status", { error });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Monitor connection status
|
|
294
|
+
*/
|
|
295
|
+
private monitorConnection(): void {
|
|
296
|
+
if (!this.connection) return;
|
|
297
|
+
|
|
298
|
+
(async () => {
|
|
299
|
+
for await (const status of this.connection!.status()) {
|
|
300
|
+
// Only log important events, skip pingTimer
|
|
301
|
+
if (status.type !== "pingTimer") {
|
|
302
|
+
logger.info("NATS connection status", {
|
|
303
|
+
type: status.type,
|
|
304
|
+
data: status.data,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
})();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Setup graceful shutdown
|
|
313
|
+
*/
|
|
314
|
+
private setupShutdown(): void {
|
|
315
|
+
const shutdown = async () => {
|
|
316
|
+
logger.info("Shutting down NATS service...");
|
|
317
|
+
await this.disconnect();
|
|
318
|
+
process.exit(0);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
process.on("SIGINT", shutdown);
|
|
322
|
+
process.on("SIGTERM", shutdown);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Disconnect from NATS
|
|
327
|
+
*/
|
|
328
|
+
async disconnect(): Promise<void> {
|
|
329
|
+
// Stop status publishing
|
|
330
|
+
if (this.statusPublishTimer) {
|
|
331
|
+
clearInterval(this.statusPublishTimer);
|
|
332
|
+
this.statusPublishTimer = null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Close connection
|
|
336
|
+
if (this.connection) {
|
|
337
|
+
await this.connection.drain();
|
|
338
|
+
this.connection = null;
|
|
339
|
+
this.subscriptions.clear();
|
|
340
|
+
logger.info("Disconnected from NATS");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get the location reference
|
|
346
|
+
*/
|
|
347
|
+
getLocationRef(): string {
|
|
348
|
+
return this.config.locationRef;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple proof-of-concept NATS subscriber
|
|
3
|
+
* Subscribes to mm.16.> and replies with "hello world"
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { connect, StringCodec } from "nats";
|
|
7
|
+
import { logger } from "../../services/reporting-service/index.js";
|
|
8
|
+
|
|
9
|
+
const sc = StringCodec();
|
|
10
|
+
|
|
11
|
+
async function startTestSubscriber() {
|
|
12
|
+
try {
|
|
13
|
+
// Connect to NATS server
|
|
14
|
+
const nc = await connect({
|
|
15
|
+
servers: "nats://localhost:4222",
|
|
16
|
+
name: "mm-erp-sdk-test-subscriber",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
logger.info("Connected to NATS server", {
|
|
20
|
+
server: nc.getServer(),
|
|
21
|
+
clientId: nc.info?.client_id,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Subscribe to all messages on mm.16.>
|
|
25
|
+
const sub = nc.subscribe("mm.16.>");
|
|
26
|
+
|
|
27
|
+
logger.info("Subscribed to mm.16.> - waiting for messages...");
|
|
28
|
+
|
|
29
|
+
// Process incoming messages
|
|
30
|
+
(async () => {
|
|
31
|
+
for await (const msg of sub) {
|
|
32
|
+
const data = sc.decode(msg.data);
|
|
33
|
+
|
|
34
|
+
logger.info("📨 Received NATS message", {
|
|
35
|
+
subject: msg.subject,
|
|
36
|
+
data: data,
|
|
37
|
+
reply: msg.reply,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
console.log("\n=== NATS MESSAGE RECEIVED ===");
|
|
41
|
+
console.log("Subject:", msg.subject);
|
|
42
|
+
console.log("Data:", data);
|
|
43
|
+
console.log("Reply subject:", msg.reply);
|
|
44
|
+
console.log("============================\n");
|
|
45
|
+
|
|
46
|
+
// Reply if there's a reply subject
|
|
47
|
+
if (msg.reply) {
|
|
48
|
+
const replyMessage = JSON.stringify({
|
|
49
|
+
message: "hello world",
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
receivedSubject: msg.subject,
|
|
52
|
+
receivedData: data,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
msg.respond(sc.encode(replyMessage));
|
|
56
|
+
|
|
57
|
+
logger.info("✅ Sent reply: hello world", {
|
|
58
|
+
replySubject: msg.reply,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log("✅ Replied with: hello world\n");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
})();
|
|
65
|
+
|
|
66
|
+
// Handle connection events
|
|
67
|
+
(async () => {
|
|
68
|
+
for await (const status of nc.status()) {
|
|
69
|
+
logger.info("NATS connection status", {
|
|
70
|
+
type: status.type,
|
|
71
|
+
data: status.data,
|
|
72
|
+
});
|
|
73
|
+
console.log(`Connection status: ${status.type}`, status.data);
|
|
74
|
+
}
|
|
75
|
+
})();
|
|
76
|
+
|
|
77
|
+
// Graceful shutdown
|
|
78
|
+
const shutdown = async () => {
|
|
79
|
+
logger.info("Shutting down NATS subscriber...");
|
|
80
|
+
await nc.drain();
|
|
81
|
+
process.exit(0);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
process.on("SIGINT", shutdown);
|
|
85
|
+
process.on("SIGTERM", shutdown);
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
logger.error("Failed to connect to NATS", { error });
|
|
89
|
+
console.error("Error connecting to NATS:", error);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Start the subscriber
|
|
95
|
+
startTestSubscriber();
|
|
96
|
+
|