@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 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 EDI documents with comprehensive error handling and automatic span tracking.
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
@@ -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 buildEdiX12Payload = (operation, x12) => buildEdiPayload({ operation, type: 'x12', x12 });
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) => buildEdiX12Payload('fromX12', { input, output: o?.result }));
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) => buildEdiX12Payload('toX12', { input, output: o?.result }));
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
- ...buildEdiX12Payload('validateX12', { input, output: o?.result }),
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) => buildEdiX12Payload('acknowledgeX12', { input, output: o?.result }));
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.7",
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.37"
19
+ "unnbound-logger-sdk": "3.2.0-beta.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/jest": "^29.5.12",