@ontemper/edi 1.1.7 → 1.2.0-beta.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 +3 -2
- package/dist/control-numbers.js +3 -3
- package/dist/index.d.ts +29 -1
- package/dist/index.js +87 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Temper EDI Client
|
|
2
2
|
|
|
3
|
-
A TypeScript EDI client with structured logging and distributed tracing capabilities, built for processing X12
|
|
3
|
+
A TypeScript EDI client with structured logging and distributed tracing capabilities, built for processing X12 (including HIPAA) and EDIFACT documents with comprehensive error handling and automatic span tracking.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **X12 EDI Processing**: Complete X12 document parsing, validation, and generation
|
|
7
|
+
- **X12 EDI Processing**: Complete X12 document parsing, validation, and generation — HIPAA guide models resolve automatically from the ST03 implementation reference
|
|
8
|
+
- **EDIFACT Processing**: `fromEdifact` / `toEdifact` / `validateEdifact` / `acknowledgeEdifact` with the same error handling, tracing, and automatic control-reference stamping (UNB-0020 from the central counter service, UNH-0062 local sequence)
|
|
8
9
|
- **Structured Logging**: Built-in integration with Temper Logger for comprehensive operation tracking
|
|
9
10
|
- **Distributed Tracing**: Automatic span creation for all EDI operations with detailed payload information
|
|
10
11
|
- **Type Safety**: Full TypeScript support with comprehensive type definitions
|
package/dist/control-numbers.js
CHANGED
|
@@ -123,7 +123,7 @@ function unwrapControlNumberError(error) {
|
|
|
123
123
|
*/
|
|
124
124
|
/** @public */
|
|
125
125
|
async function getNextControlNumber(counterName, senderId, receiverId, maxDigits = 9) {
|
|
126
|
-
const { data } = await getApiClient().post('/api/internal/edi/control-numbers/increment', {
|
|
126
|
+
const { data } = await getApiClient().post('/api/v2/internal/edi/control-numbers/increment', {
|
|
127
127
|
tradingPartnerKey: `${senderId}:${receiverId}`,
|
|
128
128
|
counterName,
|
|
129
129
|
});
|
|
@@ -134,7 +134,7 @@ async function getNextControlNumber(counterName, senderId, receiverId, maxDigits
|
|
|
134
134
|
*/
|
|
135
135
|
/** @public */
|
|
136
136
|
async function getCurrentControlNumber(counterName, senderId, receiverId, maxDigits = 9) {
|
|
137
|
-
const { data } = await getApiClient().get('/api/internal/edi/control-numbers/count', {
|
|
137
|
+
const { data } = await getApiClient().get('/api/v2/internal/edi/control-numbers/count', {
|
|
138
138
|
params: {
|
|
139
139
|
tradingPartnerKey: `${senderId}:${receiverId}`,
|
|
140
140
|
counterName,
|
|
@@ -148,7 +148,7 @@ async function getCurrentControlNumber(counterName, senderId, receiverId, maxDig
|
|
|
148
148
|
/** @public */
|
|
149
149
|
async function setControlNumber(counterName, senderId, receiverId, value, maxDigits = 9) {
|
|
150
150
|
const { data } = await getApiClient()
|
|
151
|
-
.post('/api/internal/edi/control-numbers/set', {
|
|
151
|
+
.post('/api/v2/internal/edi/control-numbers/set', {
|
|
152
152
|
tradingPartnerKey: `${senderId}:${receiverId}`,
|
|
153
153
|
counterName,
|
|
154
154
|
value,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type X12Interchange } from './edination-client';
|
|
1
|
+
import { type EdifactInterchange, type X12Interchange } from './edination-client';
|
|
2
2
|
export type { ControlNumberErrorCode } from './control-numbers';
|
|
3
3
|
export { ControlNumberError, getCurrentControlNumber, getNextControlNumber, isControlNumberError, setControlNumber, } from './control-numbers';
|
|
4
4
|
export * from './edination-client/model';
|
|
@@ -35,8 +35,21 @@ export interface ValidateX12Options {
|
|
|
35
35
|
export interface AcknolwedgeX12Options {
|
|
36
36
|
input: X12Interchange;
|
|
37
37
|
}
|
|
38
|
+
export interface FromEdifactOptions {
|
|
39
|
+
input: unknown;
|
|
40
|
+
}
|
|
41
|
+
export interface ToEdifactOptions {
|
|
42
|
+
input: EdifactInterchange;
|
|
43
|
+
}
|
|
44
|
+
export interface ValidateEdifactOptions {
|
|
45
|
+
input: EdifactInterchange;
|
|
46
|
+
}
|
|
47
|
+
export interface AcknowledgeEdifactOptions {
|
|
48
|
+
input: EdifactInterchange;
|
|
49
|
+
}
|
|
38
50
|
export declare class TemperEdiClient {
|
|
39
51
|
private X12;
|
|
52
|
+
private Edifact;
|
|
40
53
|
constructor();
|
|
41
54
|
private unwrap;
|
|
42
55
|
private unwrapError;
|
|
@@ -51,4 +64,19 @@ export declare class TemperEdiClient {
|
|
|
51
64
|
toX12({ input }: ToX12Options): Promise<unknown>;
|
|
52
65
|
validateX12({ input }: ValidateX12Options): Promise<import("./edination-client").OperationResult>;
|
|
53
66
|
acknowledgeX12({ input }: AcknolwedgeX12Options): Promise<X12Interchange[]>;
|
|
67
|
+
fromEdifact({ input }: FromEdifactOptions): Promise<EdifactInterchange[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Stamp UNB-0020/UNH-0062 control references on the interchange if not already set.
|
|
70
|
+
*
|
|
71
|
+
* UNB-0020 (interchange control reference) comes from the same Restate-backed
|
|
72
|
+
* per-trading-partner counter service X12 uses, under the platform counter name
|
|
73
|
+
* `UNB0020` — unpadded, since EDIFACT 0020 is an..14 with no fixed width. The
|
|
74
|
+
* UNZ trailer echo is set to match. UNH-0062 (message reference number) only
|
|
75
|
+
* needs uniqueness within the interchange, so it is a local 1..n sequence with
|
|
76
|
+
* no service call, echoed into each UNT — the EDIFACT sibling of ST02="0001".
|
|
77
|
+
*/
|
|
78
|
+
private stampEdifactControlNumbers;
|
|
79
|
+
toEdifact({ input }: ToEdifactOptions): Promise<unknown>;
|
|
80
|
+
validateEdifact({ input }: ValidateEdifactOptions): Promise<import("./edination-client").OperationResult>;
|
|
81
|
+
acknowledgeEdifact({ input }: AcknowledgeEdifactOptions): Promise<EdifactInterchange[]>;
|
|
54
82
|
}
|
package/dist/index.js
CHANGED
|
@@ -79,7 +79,7 @@ exports.EdiInfrastructureError = EdiInfrastructureError;
|
|
|
79
79
|
const isEdiInfrastructureError = (error) => error instanceof EdiInfrastructureError;
|
|
80
80
|
exports.isEdiInfrastructureError = isEdiInfrastructureError;
|
|
81
81
|
const buildEdiPayload = (edi) => ({ type: 'edi', edi });
|
|
82
|
-
const
|
|
82
|
+
const buildEdiTransactionPayload = (operation, format, transaction) => buildEdiPayload({ operation, type: format, transaction });
|
|
83
83
|
const needsControlNumber = (value) => {
|
|
84
84
|
const normalized = value?.trim();
|
|
85
85
|
return !normalized || /^0+$/.test(normalized);
|
|
@@ -87,6 +87,7 @@ const needsControlNumber = (value) => {
|
|
|
87
87
|
const ediAxios = (0, unnbound_logger_sdk_1.traceAxios)(axios_1.default.create(), { getPayload: internal_1.internal });
|
|
88
88
|
class TemperEdiClient {
|
|
89
89
|
X12;
|
|
90
|
+
Edifact;
|
|
90
91
|
constructor() {
|
|
91
92
|
const apiKey = process.env.UNNBOUND_EDI_API_KEY;
|
|
92
93
|
// When UNNBOUND_EDI_BASE_URL is set, use self-hosted EdiFabric InHouse API
|
|
@@ -99,6 +100,7 @@ class TemperEdiClient {
|
|
|
99
100
|
// but the OpenAPI client requires a non-empty value — use a placeholder.
|
|
100
101
|
const config = new edination_client_1.Configuration({ apiKey: apiKey ?? 'self-hosted', basePath });
|
|
101
102
|
this.X12 = new edination_client_1.X12Api(config, undefined, ediAxios);
|
|
103
|
+
this.Edifact = new edination_client_1.EdifactApi(config, undefined, ediAxios);
|
|
102
104
|
}
|
|
103
105
|
unwrap(response) {
|
|
104
106
|
return response.data;
|
|
@@ -145,7 +147,7 @@ class TemperEdiClient {
|
|
|
145
147
|
return this.X12.x12ReadPost({ body: input })
|
|
146
148
|
.then(this.unwrap.bind(this))
|
|
147
149
|
.catch((error) => this.unwrapError(error, 'edi_read_error'));
|
|
148
|
-
}, (o) =>
|
|
150
|
+
}, (o) => buildEdiTransactionPayload('fromX12', 'x12', { input, output: o?.result }));
|
|
149
151
|
}
|
|
150
152
|
/**
|
|
151
153
|
* Stamp ISA13/GS06/ST02 control numbers on the interchange if not already set.
|
|
@@ -215,7 +217,7 @@ class TemperEdiClient {
|
|
|
215
217
|
return this.X12.x12WritePost({ x12Interchange: input })
|
|
216
218
|
.then(this.unwrap.bind(this))
|
|
217
219
|
.catch((error) => this.unwrapError(error, 'edi_write_error'));
|
|
218
|
-
}, (o) =>
|
|
220
|
+
}, (o) => buildEdiTransactionPayload('toX12', 'x12', { input, output: o?.result }));
|
|
219
221
|
}
|
|
220
222
|
validateX12({ input }) {
|
|
221
223
|
return (0, unnbound_logger_sdk_1.startSpan)('Validate X12', () => {
|
|
@@ -223,7 +225,7 @@ class TemperEdiClient {
|
|
|
223
225
|
.then(this.unwrap.bind(this))
|
|
224
226
|
.catch((error) => this.unwrapError(error, 'edi_validate_error'));
|
|
225
227
|
}, (o) => ({
|
|
226
|
-
...
|
|
228
|
+
...buildEdiTransactionPayload('validateX12', 'x12', { input, output: o?.result }),
|
|
227
229
|
...(o?.result?.Status === 'error' && { level: 'warn' }),
|
|
228
230
|
}));
|
|
229
231
|
}
|
|
@@ -232,7 +234,87 @@ class TemperEdiClient {
|
|
|
232
234
|
return this.X12.x12AckPost({ x12Interchange: input })
|
|
233
235
|
.then(this.unwrap.bind(this))
|
|
234
236
|
.catch((error) => this.unwrapError(error, 'edi_acknowledge_error'));
|
|
235
|
-
}, (o) =>
|
|
237
|
+
}, (o) => buildEdiTransactionPayload('acknowledgeX12', 'x12', { input, output: o?.result }));
|
|
238
|
+
}
|
|
239
|
+
fromEdifact({ input }) {
|
|
240
|
+
return (0, unnbound_logger_sdk_1.startSpan)('EDIFACT to JSON', () => {
|
|
241
|
+
return this.Edifact.edifactReadPost({ body: input })
|
|
242
|
+
.then(this.unwrap.bind(this))
|
|
243
|
+
.catch((error) => this.unwrapError(error, 'edi_read_error'));
|
|
244
|
+
}, (o) => buildEdiTransactionPayload('fromEdifact', 'edifact', { input, output: o?.result }));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Stamp UNB-0020/UNH-0062 control references on the interchange if not already set.
|
|
248
|
+
*
|
|
249
|
+
* UNB-0020 (interchange control reference) comes from the same Restate-backed
|
|
250
|
+
* per-trading-partner counter service X12 uses, under the platform counter name
|
|
251
|
+
* `UNB0020` — unpadded, since EDIFACT 0020 is an..14 with no fixed width. The
|
|
252
|
+
* UNZ trailer echo is set to match. UNH-0062 (message reference number) only
|
|
253
|
+
* needs uniqueness within the interchange, so it is a local 1..n sequence with
|
|
254
|
+
* no service call, echoed into each UNT — the EDIFACT sibling of ST02="0001".
|
|
255
|
+
*/
|
|
256
|
+
async stampEdifactControlNumbers(input) {
|
|
257
|
+
const { getNextControlNumber } = await import('./control-numbers.js');
|
|
258
|
+
const sender = input.UNB?.INTERCHANGESENDER_2;
|
|
259
|
+
const recipient = input.UNB?.INTERCHANGERECIPIENT_3;
|
|
260
|
+
const senderKey = `${(sender?.IdentificationCodeQualifier_2 || '').trim()}:${(sender?.InterchangeSenderIdentification_1 || '').trim()}`;
|
|
261
|
+
const receiverKey = `${(recipient?.IdentificationCodeQualifier_2 || '').trim()}:${(recipient?.InterchangeRecipientIdentification_1 || '').trim()}`;
|
|
262
|
+
// Fetch before mutating — if the fetch fails, no fields change.
|
|
263
|
+
const unb0020 = needsControlNumber(input.UNB?.InterchangeControlReference_5)
|
|
264
|
+
? await getNextControlNumber('UNB0020', senderKey, receiverKey, 0)
|
|
265
|
+
: null;
|
|
266
|
+
if (unb0020) {
|
|
267
|
+
input.UNB.InterchangeControlReference_5 = unb0020;
|
|
268
|
+
if (input.UNZTrailers?.length) {
|
|
269
|
+
input.UNZTrailers[0].InterchangeControlReference_2 = unb0020;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// UNH-0062 — local sequence across the interchange's messages (no external calls)
|
|
273
|
+
let messageReference = 0;
|
|
274
|
+
for (const group of input.Groups ?? []) {
|
|
275
|
+
for (const message of group.Transactions ?? []) {
|
|
276
|
+
messageReference += 1;
|
|
277
|
+
if (needsControlNumber(message?.UNH?.MessageReferenceNumber_01)) {
|
|
278
|
+
if (message?.UNH)
|
|
279
|
+
message.UNH.MessageReferenceNumber_01 = String(messageReference);
|
|
280
|
+
if (message?.UNT)
|
|
281
|
+
message.UNT.MessageReferenceNumber_02 = String(messageReference);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async toEdifact({ input }) {
|
|
287
|
+
// Auto-stamp control references before writing EDIFACT
|
|
288
|
+
try {
|
|
289
|
+
await this.stampEdifactControlNumbers(input);
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
// Log but don't fail — workflows can still set control references manually
|
|
293
|
+
const { logger } = await import('unnbound-logger-sdk');
|
|
294
|
+
logger.warn({ err: error }, '[EDI SDK] Failed to auto-stamp control references, proceeding without');
|
|
295
|
+
}
|
|
296
|
+
return (0, unnbound_logger_sdk_1.startSpan)('JSON to EDIFACT', () => {
|
|
297
|
+
return this.Edifact.edifactWritePost({ edifactInterchange: input })
|
|
298
|
+
.then(this.unwrap.bind(this))
|
|
299
|
+
.catch((error) => this.unwrapError(error, 'edi_write_error'));
|
|
300
|
+
}, (o) => buildEdiTransactionPayload('toEdifact', 'edifact', { input, output: o?.result }));
|
|
301
|
+
}
|
|
302
|
+
validateEdifact({ input }) {
|
|
303
|
+
return (0, unnbound_logger_sdk_1.startSpan)('Validate EDIFACT', () => {
|
|
304
|
+
return this.Edifact.edifactValidatePost({ edifactInterchange: input })
|
|
305
|
+
.then(this.unwrap.bind(this))
|
|
306
|
+
.catch((error) => this.unwrapError(error, 'edi_validate_error'));
|
|
307
|
+
}, (o) => ({
|
|
308
|
+
...buildEdiTransactionPayload('validateEdifact', 'edifact', { input, output: o?.result }),
|
|
309
|
+
...(o?.result?.Status === 'error' && { level: 'warn' }),
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
acknowledgeEdifact({ input }) {
|
|
313
|
+
return (0, unnbound_logger_sdk_1.startSpan)('Acknowledge EDIFACT', () => {
|
|
314
|
+
return this.Edifact.edifactAckPost({ edifactInterchange: input })
|
|
315
|
+
.then(this.unwrap.bind(this))
|
|
316
|
+
.catch((error) => this.unwrapError(error, 'edi_acknowledge_error'));
|
|
317
|
+
}, (o) => buildEdiTransactionPayload('acknowledgeEdifact', 'edifact', { input, output: o?.result }));
|
|
236
318
|
}
|
|
237
319
|
}
|
|
238
320
|
exports.TemperEdiClient = TemperEdiClient;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontemper/edi",
|
|
3
3
|
"description": "An EDI client with structured logging.",
|
|
4
|
-
"version": "1.1
|
|
4
|
+
"version": "1.2.0-beta.1",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"author": "Unnbound Team",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"axios": "1.16.0",
|
|
19
|
-
"unnbound-logger-sdk": "3.0.
|
|
19
|
+
"unnbound-logger-sdk": "3.2.0-beta.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/jest": "^29.5.12",
|