@ontemper/edi 1.1.3 → 1.1.5

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
@@ -22,9 +22,12 @@ yarn add @ontemper/edi
22
22
 
23
23
  ## Environment Variables
24
24
 
25
- ### Required
25
+ `TemperEdiClient` is a thin HTTP client for Temper's EDI service, and it configures itself entirely from the environment — the platform injects the right values per environment, the same way it injects `UNNBOUND_API_URL`, so you don't set these by hand.
26
26
 
27
- - `UNNBOUND_EDI_API_KEY` - Your API key for authentication
27
+ - **`UNNBOUND_EDI_BASE_URL`** where the EDI service is reachable *from this environment*. A deployed workflow runs inside the cluster and gets the service's internal URL directly (no credential — cluster networking is the trust boundary). The builder sandbox runs outside the cluster, so it gets Temper's authenticated `/api/edi-gateway` proxy instead.
28
+ - **`UNNBOUND_EDI_API_KEY`** — the credential for whichever path needs one: a platform-minted JWT through the sandbox gateway, or an EDINation-cloud key in the legacy no-base-URL setup. The direct in-cluster path needs no key and ignores it.
29
+
30
+ The client throws only if it finds *neither*. There's nothing to provision, hardcode, or gate on — just `new TemperEdiClient()`.
28
31
 
29
32
  ## Quick Start
30
33
 
@@ -32,9 +35,8 @@ yarn add @ontemper/edi
32
35
  import { TemperEdiClient } from '@ontemper/edi';
33
36
  import { logger } from 'unnbound-logger-sdk';
34
37
 
35
- // Set your API key
36
- process.env.UNNBOUND_EDI_API_KEY = 'your-api-key';
37
-
38
+ // In a Temper workflow environment the EDI connection is already configured
39
+ // via env (platform-injected) — just construct the client:
38
40
  const edi = new TemperEdiClient();
39
41
 
40
42
  // Parse X12 document to JSON
@@ -66,7 +68,7 @@ const acknowledgment = await edi.acknowledgeX12({
66
68
  new TemperEdiClient();
67
69
  ```
68
70
 
69
- Creates a new EDI client instance. Requires `UNNBOUND_EDI_API_KEY` environment variable to be set.
71
+ Creates a new EDI client instance. It configures itself from two platform-owned environment variables — `UNNBOUND_EDI_BASE_URL` (where Temper's EDI service is reachable from this environment: the in-cluster URL in a deployed workflow, or the `/api/edi-gateway` proxy in the builder sandbox) and `UNNBOUND_EDI_API_KEY` (the credential for whichever path needs one — a gateway JWT, or a legacy EDINation-cloud key; the direct in-cluster path ignores it). In a Temper workflow environment the platform injects the right pair. Throws only if neither is set.
70
72
 
71
73
  ### Methods
72
74
 
@@ -138,32 +140,54 @@ const acknowledgment = await edi.acknowledgeX12({
138
140
 
139
141
  ## Error Handling
140
142
 
141
- The EDI client provides custom error types for different operation failures:
142
-
143
- ### TemperEdiClientError
143
+ The EDI client throws two distinct error classes. The distinction matters for file routing:
144
+ a `TemperEdiClientError` is a verdict on the input document (bad file — don't retry) or a
145
+ persistent auth failure, while an `EdiInfrastructureError` is a transient platform fault —
146
+ the SDK's own plumbing failed (tracing/logging/serialization) or the EDI service was
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`).
144
149
 
145
150
  ```typescript
146
- import { TemperEdiClientError, isTemperEdiClientError } from '@ontemper/edi';
151
+ import { isEdiInfrastructureError, isTemperEdiClientError } from '@ontemper/edi';
147
152
 
148
- try {
149
- await edi.fromX12({ input: invalidX12 });
150
- } catch (error) {
151
- if (isTemperEdiClientError(error)) {
152
- console.error('EDI Error:', error.code, error.message);
153
- // Handle specific EDI errors
153
+ async function processFile(rawX12: string) {
154
+ try {
155
+ return await edi.fromX12({ input: rawX12 });
156
+ } catch (error) {
157
+ if (isEdiInfrastructureError(error)) {
158
+ // Transient platform fault — the document is fine. Keep the file and rethrow
159
+ // so your poll loop / caller retries.
160
+ throw error;
161
+ }
162
+ if (isTemperEdiClientError(error)) {
163
+ // A verdict on the document (see codes below) — handle it terminally (e.g.
164
+ // quarantine the file) and STOP. Do not rethrow into retry paths.
165
+ console.error('EDI Error:', error.code, error.message);
166
+ return undefined;
167
+ }
168
+ throw error; // unrecognized — surface to the caller
154
169
  }
155
170
  }
156
171
  ```
157
172
 
158
173
  ### Error Codes
159
174
 
160
- | Code | Description |
161
- | ----------------------- | ------------------------------- |
162
- | `edi_read_error` | Error parsing X12 document |
163
- | `edi_write_error` | Error converting JSON to X12 |
164
- | `edi_validate_error` | Error validating X12 document |
165
- | `edi_acknowledge_error` | Error generating acknowledgment |
166
- | `edi_unknown_error` | Unknown error occurred |
175
+ `TemperEdiClientError` (verdict on the document):
176
+
177
+ | Code | Description |
178
+ | ------------------------ | ------------------------------------ |
179
+ | `edi_read_error` | Error parsing X12 document |
180
+ | `edi_write_error` | Error converting JSON to X12 |
181
+ | `edi_validate_error` | Error validating X12 document |
182
+ | `edi_acknowledge_error` | Error generating acknowledgment |
183
+ | `edi_unauthorized_error` | Unauthorized access to EDI service |
184
+ | `edi_unknown_error` | Legacy — no longer produced |
185
+
186
+ `EdiInfrastructureError` (transient platform fault, `retryable: true`):
187
+
188
+ | Code | Description |
189
+ | ---------------------- | ------------------------------------------------------------------------------------ |
190
+ | `infrastructure_error` | SDK plumbing failure or transient EDI-service fault — not caused by the input document |
167
191
 
168
192
  ## Type Definitions
169
193
 
@@ -321,11 +345,26 @@ const next = await getNextControlNumber('ISA13', 'ZZ:SENDER', 'ZZ:RECEIVER');
321
345
  // Peek at current value without incrementing
322
346
  const current = await getCurrentControlNumber('ISA13', 'ZZ:SENDER', 'ZZ:RECEIVER');
323
347
 
324
- // Set a starting value (for migrations)
348
+ // Set a starting value before the counter has incremented (for migrations)
325
349
  await setControlNumber('ISA13', 'ZZ:SENDER', 'ZZ:RECEIVER', 1000);
326
350
  ```
327
351
 
328
352
  In most cases you don't need these directly — `toX12()` calls `getNextControlNumber` automatically for any empty ISA13/GS06 fields.
353
+ Once a counter has incremented, the API rejects later `setControlNumber` calls to avoid resetting active sequences.
354
+ Retried `setControlNumber` calls with the same already-stored seed are treated as idempotent no-ops.
355
+ The SDK surfaces that as a `ControlNumberError` with `code: "counter_already_started"`:
356
+
357
+ ```typescript
358
+ import { isControlNumberError, setControlNumber } from '@ontemper/edi';
359
+
360
+ try {
361
+ await setControlNumber('ISA13', 'ZZ:SENDER', 'ZZ:RECEIVER', 1000);
362
+ } catch (error) {
363
+ if (isControlNumberError(error) && error.code === 'counter_already_started') {
364
+ // Counter is already active; do not reset it.
365
+ }
366
+ }
367
+ ```
329
368
 
330
369
  Requires `UNNBOUND_API_URL` environment variable (pre-configured in all workflow environments).
331
370
 
@@ -333,7 +372,7 @@ Requires `UNNBOUND_API_URL` environment variable (pre-configured in all workflow
333
372
 
334
373
  - Node.js >= 22.0.0
335
374
  - TypeScript (for TypeScript projects)
336
- - Temper EDI API key
375
+ - The EDI connection env — `UNNBOUND_EDI_BASE_URL`, plus `UNNBOUND_EDI_API_KEY` where the path needs a credential — is platform-injected per environment in Temper workflows; nothing to configure. Supply a key yourself only for the standalone legacy EDINation-cloud path.
337
376
  - `UNNBOUND_API_URL` (for control number auto-stamping, pre-configured in workflow environments)
338
377
 
339
378
  ## Dependencies
@@ -1,3 +1,16 @@
1
+ /** @public */
2
+ export type ControlNumberErrorCode = 'counter_already_started' | 'control_number_bad_request' | 'control_number_unauthorized' | 'control_number_unknown_error';
3
+ interface ControlNumberErrorOptions extends ErrorOptions {
4
+ code: ControlNumberErrorCode;
5
+ message: string;
6
+ }
7
+ /** @public */
8
+ export declare class ControlNumberError extends Error {
9
+ code: ControlNumberErrorCode;
10
+ constructor({ code, message, ...options }: ControlNumberErrorOptions);
11
+ }
12
+ /** @public */
13
+ export declare const isControlNumberError: (error: unknown) => error is ControlNumberError;
1
14
  /**
2
15
  * Get the next control number for an EDI counter.
3
16
  *
@@ -9,12 +22,16 @@
9
22
  * @param receiverId - Receiver qualifier + ID (e.g. "ZZ:RECEIVER456")
10
23
  * @param maxDigits - Zero-pad to this width (default: 9 for ISA13)
11
24
  */
25
+ /** @public */
12
26
  export declare function getNextControlNumber(counterName: string, senderId: string, receiverId: string, maxDigits?: number): Promise<string>;
13
27
  /**
14
28
  * Get the current control number value without incrementing.
15
29
  */
30
+ /** @public */
16
31
  export declare function getCurrentControlNumber(counterName: string, senderId: string, receiverId: string, maxDigits?: number): Promise<string>;
17
32
  /**
18
- * Set a control number to a specific value (for migration/seeding).
33
+ * Set a control number to a specific value before the counter starts (for migration/seeding).
19
34
  */
35
+ /** @public */
20
36
  export declare function setControlNumber(counterName: string, senderId: string, receiverId: string, value: number, maxDigits?: number): Promise<string>;
37
+ export {};
@@ -1,12 +1,43 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
5
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isControlNumberError = exports.ControlNumberError = void 0;
6
37
  exports.getNextControlNumber = getNextControlNumber;
7
38
  exports.getCurrentControlNumber = getCurrentControlNumber;
8
39
  exports.setControlNumber = setControlNumber;
9
- const axios_1 = __importDefault(require("axios"));
40
+ const axios_1 = __importStar(require("axios"));
10
41
  let _client = null;
11
42
  function getApiClient() {
12
43
  if (_client)
@@ -34,6 +65,51 @@ function getApiClient() {
34
65
  });
35
66
  return _client;
36
67
  }
68
+ /** @public */
69
+ class ControlNumberError extends Error {
70
+ code;
71
+ constructor({ code, message, ...options }) {
72
+ super(message, options);
73
+ this.name = 'ControlNumberError';
74
+ this.code = code;
75
+ }
76
+ }
77
+ exports.ControlNumberError = ControlNumberError;
78
+ /** @public */
79
+ const isControlNumberError = (error) => error instanceof ControlNumberError;
80
+ exports.isControlNumberError = isControlNumberError;
81
+ function unwrapControlNumberError(error) {
82
+ if (error instanceof ControlNumberError)
83
+ throw error;
84
+ if ((0, axios_1.isAxiosError)(error)) {
85
+ if (error.response?.status === 409) {
86
+ throw new ControlNumberError({
87
+ code: 'counter_already_started',
88
+ message: 'Cannot set EDI control number after counter has started. Set the starting value before calling getNextControlNumber or toX12 for this trading partner.',
89
+ cause: error,
90
+ });
91
+ }
92
+ if (error.response?.status === 400) {
93
+ throw new ControlNumberError({
94
+ code: 'control_number_bad_request',
95
+ message: 'Invalid EDI control number request. Counter name, trading partner IDs, and value are required.',
96
+ cause: error,
97
+ });
98
+ }
99
+ if (error.response?.status === 401) {
100
+ throw new ControlNumberError({
101
+ code: 'control_number_unauthorized',
102
+ message: 'Unauthorized access to EDI control number service.',
103
+ cause: error,
104
+ });
105
+ }
106
+ }
107
+ throw new ControlNumberError({
108
+ code: 'control_number_unknown_error',
109
+ message: error instanceof Error ? error.message : 'Unknown EDI control number error.',
110
+ cause: error,
111
+ });
112
+ }
37
113
  /**
38
114
  * Get the next control number for an EDI counter.
39
115
  *
@@ -45,6 +121,7 @@ function getApiClient() {
45
121
  * @param receiverId - Receiver qualifier + ID (e.g. "ZZ:RECEIVER456")
46
122
  * @param maxDigits - Zero-pad to this width (default: 9 for ISA13)
47
123
  */
124
+ /** @public */
48
125
  async function getNextControlNumber(counterName, senderId, receiverId, maxDigits = 9) {
49
126
  const { data } = await getApiClient().post('/api/internal/edi/control-numbers/increment', {
50
127
  tradingPartnerKey: `${senderId}:${receiverId}`,
@@ -55,6 +132,7 @@ async function getNextControlNumber(counterName, senderId, receiverId, maxDigits
55
132
  /**
56
133
  * Get the current control number value without incrementing.
57
134
  */
135
+ /** @public */
58
136
  async function getCurrentControlNumber(counterName, senderId, receiverId, maxDigits = 9) {
59
137
  const { data } = await getApiClient().get('/api/internal/edi/control-numbers/count', {
60
138
  params: {
@@ -65,13 +143,16 @@ async function getCurrentControlNumber(counterName, senderId, receiverId, maxDig
65
143
  return String(data.value).padStart(maxDigits, '0');
66
144
  }
67
145
  /**
68
- * Set a control number to a specific value (for migration/seeding).
146
+ * Set a control number to a specific value before the counter starts (for migration/seeding).
69
147
  */
148
+ /** @public */
70
149
  async function setControlNumber(counterName, senderId, receiverId, value, maxDigits = 9) {
71
- const { data } = await getApiClient().post('/api/internal/edi/control-numbers/set', {
150
+ const { data } = await getApiClient()
151
+ .post('/api/internal/edi/control-numbers/set', {
72
152
  tradingPartnerKey: `${senderId}:${receiverId}`,
73
153
  counterName,
74
154
  value,
75
- });
155
+ })
156
+ .catch(unwrapControlNumberError);
76
157
  return String(data.value).padStart(maxDigits, '0');
77
158
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type X12Interchange } from './edination-client';
2
- export { getCurrentControlNumber, getNextControlNumber, setControlNumber } from './control-numbers';
2
+ export type { ControlNumberErrorCode } from './control-numbers';
3
+ export { ControlNumberError, getCurrentControlNumber, getNextControlNumber, isControlNumberError, setControlNumber, } from './control-numbers';
3
4
  export * from './edination-client/model';
4
5
  interface UnnboundErrorOptions<C extends string = string> extends ErrorOptions {
5
6
  message: string;
@@ -14,6 +15,17 @@ export declare class TemperEdiClientError extends UnnboundError<TemperEdiClientE
14
15
  constructor({ message, code, ...o }: UnnboundErrorOptions<TemperEdiClientErrorCode>);
15
16
  }
16
17
  export declare const isTemperEdiClientError: (error: unknown) => error is TemperEdiClientError;
18
+ /**
19
+ * A failure in the SDK's own plumbing (logging/instrumentation/tracing), not a verdict
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).
23
+ */
24
+ export declare class EdiInfrastructureError extends UnnboundError<'infrastructure_error'> {
25
+ readonly retryable = true;
26
+ constructor({ message, ...o }: Omit<UnnboundErrorOptions<'infrastructure_error'>, 'code'>);
27
+ }
28
+ export declare const isEdiInfrastructureError: (error: unknown) => error is EdiInfrastructureError;
17
29
  export interface FromX12Options {
18
30
  input: unknown;
19
31
  }
package/dist/index.js CHANGED
@@ -36,14 +36,16 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
36
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.TemperEdiClient = exports.isTemperEdiClientError = exports.TemperEdiClientError = exports.setControlNumber = exports.getNextControlNumber = exports.getCurrentControlNumber = void 0;
39
+ exports.TemperEdiClient = exports.isEdiInfrastructureError = exports.EdiInfrastructureError = exports.isTemperEdiClientError = exports.TemperEdiClientError = exports.setControlNumber = exports.isControlNumberError = exports.getNextControlNumber = exports.getCurrentControlNumber = exports.ControlNumberError = void 0;
40
40
  const axios_1 = __importStar(require("axios"));
41
41
  const unnbound_logger_sdk_1 = require("unnbound-logger-sdk");
42
42
  const internal_1 = require("unnbound-logger-sdk/dist/internal");
43
43
  const edination_client_1 = require("./edination-client");
44
44
  var control_numbers_1 = require("./control-numbers");
45
+ Object.defineProperty(exports, "ControlNumberError", { enumerable: true, get: function () { return control_numbers_1.ControlNumberError; } });
45
46
  Object.defineProperty(exports, "getCurrentControlNumber", { enumerable: true, get: function () { return control_numbers_1.getCurrentControlNumber; } });
46
47
  Object.defineProperty(exports, "getNextControlNumber", { enumerable: true, get: function () { return control_numbers_1.getNextControlNumber; } });
48
+ Object.defineProperty(exports, "isControlNumberError", { enumerable: true, get: function () { return control_numbers_1.isControlNumberError; } });
47
49
  Object.defineProperty(exports, "setControlNumber", { enumerable: true, get: function () { return control_numbers_1.setControlNumber; } });
48
50
  __exportStar(require("./edination-client/model"), exports);
49
51
  class UnnboundError extends Error {
@@ -63,8 +65,29 @@ class TemperEdiClientError extends UnnboundError {
63
65
  exports.TemperEdiClientError = TemperEdiClientError;
64
66
  const isTemperEdiClientError = (error) => error instanceof TemperEdiClientError;
65
67
  exports.isTemperEdiClientError = isTemperEdiClientError;
68
+ /**
69
+ * A failure in the SDK's own plumbing (logging/instrumentation/tracing), not a verdict
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).
73
+ */
74
+ class EdiInfrastructureError extends UnnboundError {
75
+ retryable = true;
76
+ constructor({ message, ...o }) {
77
+ super({ message, code: 'infrastructure_error', ...o });
78
+ this.name = 'EdiInfrastructureError';
79
+ }
80
+ }
81
+ exports.EdiInfrastructureError = EdiInfrastructureError;
82
+ const isEdiInfrastructureError = (error) => error instanceof EdiInfrastructureError;
83
+ exports.isEdiInfrastructureError = isEdiInfrastructureError;
66
84
  const buildEdiPayload = (edi) => ({ type: 'edi', edi });
67
85
  const buildEdiX12Payload = (operation, x12) => buildEdiPayload({ operation, type: 'x12', x12 });
86
+ // One traced instance for the whole process, created once at module load. Never trace
87
+ // the global axios export: in-place wrapping there accumulated a span layer per
88
+ // TemperEdiClient construction (stack overflow after enough polls) and leaked EDI
89
+ // payload capture into unrelated code sharing global axios (T-3235).
90
+ const ediAxios = (0, unnbound_logger_sdk_1.traceAxios)(axios_1.default.create(), { getPayload: internal_1.internal });
68
91
  class TemperEdiClient {
69
92
  X12;
70
93
  constructor() {
@@ -78,8 +101,7 @@ class TemperEdiClient {
78
101
  // Self-hosted service ignores the API key header (ClusterIP, no auth needed),
79
102
  // but the OpenAPI client requires a non-empty value — use a placeholder.
80
103
  const config = new edination_client_1.Configuration({ apiKey: apiKey ?? 'self-hosted', basePath });
81
- const client = (0, unnbound_logger_sdk_1.traceAxios)(axios_1.default, { getPayload: internal_1.internal });
82
- this.X12 = new edination_client_1.X12Api(config, undefined, client);
104
+ this.X12 = new edination_client_1.X12Api(config, undefined, ediAxios);
83
105
  }
84
106
  unwrap(response) {
85
107
  return response.data;
@@ -87,12 +109,27 @@ class TemperEdiClient {
87
109
  unwrapError(error, code) {
88
110
  if (error instanceof TemperEdiClientError)
89
111
  throw error;
112
+ if (error instanceof EdiInfrastructureError)
113
+ throw error;
90
114
  if ((0, axios_1.isAxiosError)(error)) {
91
- if (error.status === 403)
115
+ // 401 = missing/expired/invalid credential (e.g. the sandbox EDI-gateway JWT),
116
+ // 403 = access denied. Neither is a verdict on the document.
117
+ if (error.status === 401 || error.status === 403)
92
118
  throw new TemperEdiClientError({
93
- message: 'Unauthorized access to EDI service. Reach out to support.',
119
+ message: 'Unauthorized access to EDI service (credentials missing, expired, or invalid). Reach out to support.',
94
120
  code: 'edi_unauthorized_error',
95
121
  });
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
+ }
96
133
  // Gateway normalises EdiFabric's camelCase to PascalCase before forwarding
97
134
  const responseData = error.response?.data;
98
135
  const details = typeof responseData === 'object' &&
@@ -108,15 +145,13 @@ class TemperEdiClient {
108
145
  : error.message;
109
146
  throw new TemperEdiClientError({ message, code, cause: error });
110
147
  }
111
- if (error instanceof Error)
112
- throw new TemperEdiClientError({
113
- message: error.message,
114
- code: 'edi_unknown_error',
115
- cause: error,
116
- });
117
- throw new TemperEdiClientError({
118
- message: 'Unknown error occured in EDI client.',
119
- code: 'edi_unknown_error',
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
+ throw new EdiInfrastructureError({
154
+ message: `EDI client infrastructure failure (not caused by the input document): ${error instanceof Error ? error.message : String(error)}`,
120
155
  cause: error,
121
156
  });
122
157
  }
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.3",
4
+ "version": "1.1.5",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "author": "Unnbound Team",
@@ -15,12 +15,13 @@
15
15
  "url": "https://github.com/unnbounddev/unnbound-sdks/issues"
16
16
  },
17
17
  "dependencies": {
18
- "axios": "1.15.1",
19
- "unnbound-logger-sdk": "3.0.36"
18
+ "axios": "1.16.0",
19
+ "unnbound-logger-sdk": "3.0.37"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/jest": "^29.5.12",
23
- "@types/node": "^24.12.2"
23
+ "@types/node": "^24.12.2",
24
+ "vitest": "^4.0.15"
24
25
  },
25
26
  "files": [
26
27
  "examples/*",
@@ -34,10 +35,8 @@
34
35
  "sideEffects": false,
35
36
  "scripts": {
36
37
  "build": "tsc",
37
- "test": "echo 'No tests'",
38
- "typecheck": "tsgo --noEmit",
39
- "lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
40
- "lint:fix": "pnpm run lint --fix",
38
+ "test": "vitest run src",
39
+ "typecheck": "tsc --noEmit",
41
40
  "format": "biome format --write .",
42
41
  "format:check": "biome format .",
43
42
  "start:example": "tsx watch examples/node-edi.ts",