@ontemper/edi 1.1.5 → 1.1.7
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 +39 -19
- package/dist/index.d.ts +3 -6
- package/dist/index.js +27 -41
- package/examples/node-edi.ts +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,10 +54,15 @@ const x12Document = await edi.toX12({
|
|
|
54
54
|
input: x12Interchanges[0],
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
// Generate
|
|
58
|
-
const
|
|
57
|
+
// Generate and serialize acknowledgments
|
|
58
|
+
const acknowledgments = await edi.acknowledgeX12({
|
|
59
59
|
input: x12Interchanges[0],
|
|
60
60
|
});
|
|
61
|
+
|
|
62
|
+
for (const acknowledgment of acknowledgments) {
|
|
63
|
+
// Apply partner-specific outbound envelope IDs here when needed.
|
|
64
|
+
const acknowledgmentX12 = await edi.toX12({ input: acknowledgment });
|
|
65
|
+
}
|
|
61
66
|
```
|
|
62
67
|
|
|
63
68
|
## API Reference
|
|
@@ -104,7 +109,7 @@ const x12Document = await edi.toX12({
|
|
|
104
109
|
|
|
105
110
|
**Returns:** X12 document as string
|
|
106
111
|
|
|
107
|
-
**Control number auto-stamping:** Leave `InterchangeControlNumber_13`, `GroupControlNumber_6`, and `TransactionSetControlNumber_02` empty or omit them.
|
|
112
|
+
**Control number auto-stamping:** Leave `InterchangeControlNumber_13`, `GroupControlNumber_6`, and `TransactionSetControlNumber_02` empty or omit them. Empty and all-zero ISA13, GS06, or ST02 values are treated as unassigned. Valid non-zero values are preserved.
|
|
108
113
|
|
|
109
114
|
#### `validateX12(options: ValidateX12Options): Promise<OperationResult>`
|
|
110
115
|
|
|
@@ -122,30 +127,36 @@ const validationResult = await edi.validateX12({
|
|
|
122
127
|
|
|
123
128
|
**Returns:** Validation result with operation details
|
|
124
129
|
|
|
125
|
-
#### `acknowledgeX12(options: AcknolwedgeX12Options): Promise<
|
|
130
|
+
#### `acknowledgeX12(options: AcknolwedgeX12Options): Promise<X12Interchange[]>`
|
|
126
131
|
|
|
127
|
-
Generates acknowledgment
|
|
132
|
+
Generates acknowledgment interchange objects. Apply any partner-specific outbound envelope IDs, then serialize
|
|
133
|
+
each acknowledgment with `toX12()`. During serialization, `toX12()` assigns control numbers using the final
|
|
134
|
+
sender and receiver. The method always returns an array because one input can produce more than one
|
|
135
|
+
acknowledgment.
|
|
128
136
|
|
|
129
137
|
```typescript
|
|
130
|
-
const
|
|
138
|
+
const acknowledgments = await edi.acknowledgeX12({
|
|
131
139
|
input: x12InterchangeObject,
|
|
132
140
|
});
|
|
141
|
+
|
|
142
|
+
for (const acknowledgment of acknowledgments) {
|
|
143
|
+
// Apply partner-specific outbound envelope IDs here when needed.
|
|
144
|
+
const serialized = await edi.toX12({ input: acknowledgment });
|
|
145
|
+
}
|
|
133
146
|
```
|
|
134
147
|
|
|
135
148
|
**Parameters:**
|
|
136
149
|
|
|
137
150
|
- `options.input` - The X12 interchange object to acknowledge
|
|
138
151
|
|
|
139
|
-
**Returns:**
|
|
152
|
+
**Returns:** The generated acknowledgment interchanges
|
|
140
153
|
|
|
141
154
|
## Error Handling
|
|
142
155
|
|
|
143
156
|
The EDI client throws two distinct error classes. The distinction matters for file routing:
|
|
144
157
|
a `TemperEdiClientError` is a verdict on the input document (bad file — don't retry) or a
|
|
145
|
-
persistent
|
|
146
|
-
|
|
147
|
-
transiently unavailable (network failure, timeout, 5xx, rate limit). It says nothing about
|
|
148
|
-
the document, so keep the file and retry (`error.retryable === true`).
|
|
158
|
+
persistent authentication failure, while an `EdiInfrastructureError` is retryable and does
|
|
159
|
+
not indicate that the document is invalid (`error.retryable === true`).
|
|
149
160
|
|
|
150
161
|
```typescript
|
|
151
162
|
import { isEdiInfrastructureError, isTemperEdiClientError } from '@ontemper/edi';
|
|
@@ -155,11 +166,14 @@ async function processFile(rawX12: string) {
|
|
|
155
166
|
return await edi.fromX12({ input: rawX12 });
|
|
156
167
|
} catch (error) {
|
|
157
168
|
if (isEdiInfrastructureError(error)) {
|
|
158
|
-
//
|
|
159
|
-
// so your poll loop / caller retries.
|
|
169
|
+
// Keep the file and rethrow so the caller can retry.
|
|
160
170
|
throw error;
|
|
161
171
|
}
|
|
162
172
|
if (isTemperEdiClientError(error)) {
|
|
173
|
+
if (error.code === 'edi_unauthorized_error') {
|
|
174
|
+
// Surface authentication failures instead of quarantining the file.
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
163
177
|
// A verdict on the document (see codes below) — handle it terminally (e.g.
|
|
164
178
|
// quarantine the file) and STOP. Do not rethrow into retry paths.
|
|
165
179
|
console.error('EDI Error:', error.code, error.message);
|
|
@@ -183,11 +197,11 @@ async function processFile(rawX12: string) {
|
|
|
183
197
|
| `edi_unauthorized_error` | Unauthorized access to EDI service |
|
|
184
198
|
| `edi_unknown_error` | Legacy — no longer produced |
|
|
185
199
|
|
|
186
|
-
`EdiInfrastructureError` (
|
|
200
|
+
`EdiInfrastructureError` (`retryable: true`):
|
|
187
201
|
|
|
188
202
|
| Code | Description |
|
|
189
203
|
| ---------------------- | ------------------------------------------------------------------------------------ |
|
|
190
|
-
| `infrastructure_error` |
|
|
204
|
+
| `infrastructure_error` | Retryable failure unrelated to the input document |
|
|
191
205
|
|
|
192
206
|
## Type Definitions
|
|
193
207
|
|
|
@@ -319,9 +333,13 @@ IEA*1*000001000`;
|
|
|
319
333
|
const x12Output = await edi.toX12({ input: interchange });
|
|
320
334
|
logger.info({ x12Output }, 'Converted back to X12');
|
|
321
335
|
|
|
322
|
-
// Generate
|
|
323
|
-
|
|
324
|
-
|
|
336
|
+
// Generate and serialize acknowledgments. toX12 assigns control numbers
|
|
337
|
+
// after any partner-specific outbound envelope changes.
|
|
338
|
+
const acknowledgments = await edi.acknowledgeX12({ input: interchange });
|
|
339
|
+
for (const acknowledgment of acknowledgments) {
|
|
340
|
+
const acknowledgmentX12 = await edi.toX12({ input: acknowledgment });
|
|
341
|
+
logger.info({ acknowledgmentX12 }, 'Generated acknowledgment');
|
|
342
|
+
}
|
|
325
343
|
}
|
|
326
344
|
} catch (error) {
|
|
327
345
|
logger.error({ err: error }, 'EDI processing failed');
|
|
@@ -349,7 +367,9 @@ const current = await getCurrentControlNumber('ISA13', 'ZZ:SENDER', 'ZZ:RECEIVER
|
|
|
349
367
|
await setControlNumber('ISA13', 'ZZ:SENDER', 'ZZ:RECEIVER', 1000);
|
|
350
368
|
```
|
|
351
369
|
|
|
352
|
-
In most cases you don't need these directly — `toX12()`
|
|
370
|
+
In most cases you don't need these directly — `toX12()` automatically stamps any empty or all-zero
|
|
371
|
+
ISA13/GS06/ST02 fields. ISA13 and GS06 use `getNextControlNumber`; ST02 is assigned `"0001"`. This includes the
|
|
372
|
+
zero placeholders returned by acknowledgment generation.
|
|
353
373
|
Once a counter has incremented, the API rejects later `setControlNumber` calls to avoid resetting active sequences.
|
|
354
374
|
Retried `setControlNumber` calls with the same already-stored seed are treated as idempotent no-ops.
|
|
355
375
|
The SDK surfaces that as a `ControlNumberError` with `code: "counter_already_started"`:
|
package/dist/index.d.ts
CHANGED
|
@@ -16,10 +16,7 @@ export declare class TemperEdiClientError extends UnnboundError<TemperEdiClientE
|
|
|
16
16
|
}
|
|
17
17
|
export declare const isTemperEdiClientError: (error: unknown) => error is TemperEdiClientError;
|
|
18
18
|
/**
|
|
19
|
-
* A failure
|
|
20
|
-
* on the EDI payload. Deliberately NOT a TemperEdiClientError and NOT an `edi_*` code:
|
|
21
|
-
* workflows route `edi_*` errors as bad files, while these are transient platform
|
|
22
|
-
* faults the workflow should retry (T-3235).
|
|
19
|
+
* A retryable failure that does not indicate an invalid EDI document.
|
|
23
20
|
*/
|
|
24
21
|
export declare class EdiInfrastructureError extends UnnboundError<'infrastructure_error'> {
|
|
25
22
|
readonly retryable = true;
|
|
@@ -45,10 +42,10 @@ export declare class TemperEdiClient {
|
|
|
45
42
|
private unwrapError;
|
|
46
43
|
fromX12({ input }: FromX12Options): Promise<X12Interchange[]>;
|
|
47
44
|
/**
|
|
48
|
-
* Stamp ISA13/GS06 control numbers on the interchange if not already set.
|
|
45
|
+
* Stamp ISA13/GS06/ST02 control numbers on the interchange if not already set.
|
|
49
46
|
* Uses the Restate-backed counter service for atomic, per-trading-partner sequencing.
|
|
50
47
|
* ST02 is always "0001" (one transaction per file per ticket).
|
|
51
|
-
*
|
|
48
|
+
* Zero-filled values from acknowledgment generation are placeholders, not manual overrides.
|
|
52
49
|
*/
|
|
53
50
|
private stampControlNumbers;
|
|
54
51
|
toX12({ input }: ToX12Options): Promise<unknown>;
|
package/dist/index.js
CHANGED
|
@@ -66,10 +66,7 @@ exports.TemperEdiClientError = TemperEdiClientError;
|
|
|
66
66
|
const isTemperEdiClientError = (error) => error instanceof TemperEdiClientError;
|
|
67
67
|
exports.isTemperEdiClientError = isTemperEdiClientError;
|
|
68
68
|
/**
|
|
69
|
-
* A failure
|
|
70
|
-
* on the EDI payload. Deliberately NOT a TemperEdiClientError and NOT an `edi_*` code:
|
|
71
|
-
* workflows route `edi_*` errors as bad files, while these are transient platform
|
|
72
|
-
* faults the workflow should retry (T-3235).
|
|
69
|
+
* A retryable failure that does not indicate an invalid EDI document.
|
|
73
70
|
*/
|
|
74
71
|
class EdiInfrastructureError extends UnnboundError {
|
|
75
72
|
retryable = true;
|
|
@@ -83,10 +80,10 @@ const isEdiInfrastructureError = (error) => error instanceof EdiInfrastructureEr
|
|
|
83
80
|
exports.isEdiInfrastructureError = isEdiInfrastructureError;
|
|
84
81
|
const buildEdiPayload = (edi) => ({ type: 'edi', edi });
|
|
85
82
|
const buildEdiX12Payload = (operation, x12) => buildEdiPayload({ operation, type: 'x12', x12 });
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
const needsControlNumber = (value) => {
|
|
84
|
+
const normalized = value?.trim();
|
|
85
|
+
return !normalized || /^0+$/.test(normalized);
|
|
86
|
+
};
|
|
90
87
|
const ediAxios = (0, unnbound_logger_sdk_1.traceAxios)(axios_1.default.create(), { getPayload: internal_1.internal });
|
|
91
88
|
class TemperEdiClient {
|
|
92
89
|
X12;
|
|
@@ -112,24 +109,12 @@ class TemperEdiClient {
|
|
|
112
109
|
if (error instanceof EdiInfrastructureError)
|
|
113
110
|
throw error;
|
|
114
111
|
if ((0, axios_1.isAxiosError)(error)) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (error.status === 401 || error.status === 403)
|
|
112
|
+
const status = error.response?.status ?? error.status;
|
|
113
|
+
if (status === 401 || status === 403)
|
|
118
114
|
throw new TemperEdiClientError({
|
|
119
115
|
message: 'Unauthorized access to EDI service (credentials missing, expired, or invalid). Reach out to support.',
|
|
120
116
|
code: 'edi_unauthorized_error',
|
|
121
117
|
});
|
|
122
|
-
// Transport failures (no response: ECONNREFUSED/timeout/DNS), 5xx, and
|
|
123
|
-
// rate-limit/timeout statuses are transient service faults, not verdicts on
|
|
124
|
-
// the document — a brief EDI-service outage must never quarantine valid
|
|
125
|
-
// files in /error (T-3235).
|
|
126
|
-
const status = error.response?.status;
|
|
127
|
-
if (status === undefined || status >= 500 || status === 408 || status === 429) {
|
|
128
|
-
throw new EdiInfrastructureError({
|
|
129
|
-
message: `EDI service unreachable or transiently failing (${status ?? error.code ?? 'no response'}): ${error.message}`,
|
|
130
|
-
cause: error,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
118
|
// Gateway normalises EdiFabric's camelCase to PascalCase before forwarding
|
|
134
119
|
const responseData = error.response?.data;
|
|
135
120
|
const details = typeof responseData === 'object' &&
|
|
@@ -138,18 +123,18 @@ class TemperEdiClient {
|
|
|
138
123
|
Array.isArray(responseData.Details)
|
|
139
124
|
? responseData.Details
|
|
140
125
|
: undefined;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
: error
|
|
146
|
-
|
|
126
|
+
if (status === 400 && details?.length) {
|
|
127
|
+
throw new TemperEdiClientError({
|
|
128
|
+
message: `EDI validation failed: ${details.join('; ')}`,
|
|
129
|
+
code,
|
|
130
|
+
cause: error,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
throw new EdiInfrastructureError({
|
|
134
|
+
message: `EDI service request failed (${status ?? error.code ?? 'no response'}): ${error.message}`,
|
|
135
|
+
cause: error,
|
|
136
|
+
});
|
|
147
137
|
}
|
|
148
|
-
// Not an axios error → the failure never reached the EDI HTTP exchange. It was
|
|
149
|
-
// raised by the SDK's own plumbing (span/logging instrumentation, request
|
|
150
|
-
// serialization), so it must not be reported as a verdict on the file: workflows
|
|
151
|
-
// route `edi_*` errors to /error, and that misrouted valid partner files when the
|
|
152
|
-
// instrumentation layer crashed (T-3235).
|
|
153
138
|
throw new EdiInfrastructureError({
|
|
154
139
|
message: `EDI client infrastructure failure (not caused by the input document): ${error instanceof Error ? error.message : String(error)}`,
|
|
155
140
|
cause: error,
|
|
@@ -163,10 +148,10 @@ class TemperEdiClient {
|
|
|
163
148
|
}, (o) => buildEdiX12Payload('fromX12', { input, output: o?.result }));
|
|
164
149
|
}
|
|
165
150
|
/**
|
|
166
|
-
* Stamp ISA13/GS06 control numbers on the interchange if not already set.
|
|
151
|
+
* Stamp ISA13/GS06/ST02 control numbers on the interchange if not already set.
|
|
167
152
|
* Uses the Restate-backed counter service for atomic, per-trading-partner sequencing.
|
|
168
153
|
* ST02 is always "0001" (one transaction per file per ticket).
|
|
169
|
-
*
|
|
154
|
+
* Zero-filled values from acknowledgment generation are placeholders, not manual overrides.
|
|
170
155
|
*/
|
|
171
156
|
async stampControlNumbers(input) {
|
|
172
157
|
const { getNextControlNumber } = await import('./control-numbers.js');
|
|
@@ -179,12 +164,13 @@ class TemperEdiClient {
|
|
|
179
164
|
const receiverKey = `${receiverQual}:${receiverId}`;
|
|
180
165
|
// Phase 1: Fetch all needed control numbers (no mutations yet).
|
|
181
166
|
// If any fetch fails, no fields are mutated — avoids partial stamping.
|
|
182
|
-
const isa13 =
|
|
167
|
+
const isa13 = needsControlNumber(isa.InterchangeControlNumber_13)
|
|
183
168
|
? await getNextControlNumber('ISA13', senderKey, receiverKey, 9)
|
|
184
169
|
: null;
|
|
185
170
|
const gs06Values = [];
|
|
186
|
-
|
|
187
|
-
|
|
171
|
+
const groups = input.Groups ?? [];
|
|
172
|
+
for (let i = 0; i < groups.length; i++) {
|
|
173
|
+
if (needsControlNumber(groups[i].GS.GroupControlNumber_6)) {
|
|
188
174
|
const value = await getNextControlNumber('GS06', senderKey, receiverKey, 9);
|
|
189
175
|
gs06Values.push({ index: i, value });
|
|
190
176
|
}
|
|
@@ -197,16 +183,16 @@ class TemperEdiClient {
|
|
|
197
183
|
}
|
|
198
184
|
}
|
|
199
185
|
for (const { index, value } of gs06Values) {
|
|
200
|
-
const group =
|
|
186
|
+
const group = groups[index];
|
|
201
187
|
group.GS.GroupControlNumber_6 = value;
|
|
202
188
|
if (group.GETrailers?.length) {
|
|
203
189
|
group.GETrailers[0].GroupControlNumber_2 = value;
|
|
204
190
|
}
|
|
205
191
|
}
|
|
206
192
|
// ST02 — always "0001" (pure assignment, no external calls)
|
|
207
|
-
for (const group of
|
|
193
|
+
for (const group of groups) {
|
|
208
194
|
for (const tx of group.Transactions) {
|
|
209
|
-
if (
|
|
195
|
+
if (needsControlNumber(tx?.ST?.TransactionSetControlNumber_02)) {
|
|
210
196
|
if (tx?.ST)
|
|
211
197
|
tx.ST.TransactionSetControlNumber_02 = '0001';
|
|
212
198
|
if (tx?.SE)
|
package/examples/node-edi.ts
CHANGED
|
@@ -46,9 +46,13 @@ IEA*1*000001000`;
|
|
|
46
46
|
|
|
47
47
|
logger.info({ validated }, 'X12 validated.');
|
|
48
48
|
|
|
49
|
-
const
|
|
49
|
+
const acknowledgments = await edi.acknowledgeX12({ input: x12Interchange });
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
for (const acknowledgment of acknowledgments) {
|
|
52
|
+
// Apply partner-specific outbound envelope IDs before serialization when needed.
|
|
53
|
+
const acknowledgmentX12 = await edi.toX12({ input: acknowledgment });
|
|
54
|
+
logger.info({ acknowledgmentX12 }, 'X12 acknowledgment generated.');
|
|
55
|
+
}
|
|
52
56
|
}),
|
|
53
57
|
);
|
|
54
58
|
};
|