@ontemper/edi 1.1.2 → 1.1.4

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,49 @@ 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
153
  try {
149
- await edi.fromX12({ input: invalidX12 });
154
+ await edi.fromX12({ input: rawX12 });
150
155
  } catch (error) {
156
+ if (isEdiInfrastructureError(error)) {
157
+ // Transient platform fault — the document is fine. Keep the file, retry later.
158
+ throw error;
159
+ }
151
160
  if (isTemperEdiClientError(error)) {
161
+ // A verdict on the document (see codes below) — route the file to your error handling.
152
162
  console.error('EDI Error:', error.code, error.message);
153
- // Handle specific EDI errors
154
163
  }
164
+ throw error;
155
165
  }
156
166
  ```
157
167
 
158
168
  ### Error Codes
159
169
 
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 |
170
+ `TemperEdiClientError` (verdict on the document):
171
+
172
+ | Code | Description |
173
+ | ------------------------ | ------------------------------------ |
174
+ | `edi_read_error` | Error parsing X12 document |
175
+ | `edi_write_error` | Error converting JSON to X12 |
176
+ | `edi_validate_error` | Error validating X12 document |
177
+ | `edi_acknowledge_error` | Error generating acknowledgment |
178
+ | `edi_unauthorized_error` | Unauthorized access to EDI service |
179
+ | `edi_unknown_error` | Legacy — no longer produced |
180
+
181
+ `EdiInfrastructureError` (transient platform fault, `retryable: true`):
182
+
183
+ | Code | Description |
184
+ | ---------------------- | ------------------------------------------------------------------------------------ |
185
+ | `infrastructure_error` | SDK plumbing failure or transient EDI-service fault — not caused by the input document |
167
186
 
168
187
  ## Type Definitions
169
188
 
@@ -321,11 +340,26 @@ const next = await getNextControlNumber('ISA13', 'ZZ:SENDER', 'ZZ:RECEIVER');
321
340
  // Peek at current value without incrementing
322
341
  const current = await getCurrentControlNumber('ISA13', 'ZZ:SENDER', 'ZZ:RECEIVER');
323
342
 
324
- // Set a starting value (for migrations)
343
+ // Set a starting value before the counter has incremented (for migrations)
325
344
  await setControlNumber('ISA13', 'ZZ:SENDER', 'ZZ:RECEIVER', 1000);
326
345
  ```
327
346
 
328
347
  In most cases you don't need these directly — `toX12()` calls `getNextControlNumber` automatically for any empty ISA13/GS06 fields.
348
+ Once a counter has incremented, the API rejects later `setControlNumber` calls to avoid resetting active sequences.
349
+ Retried `setControlNumber` calls with the same already-stored seed are treated as idempotent no-ops.
350
+ The SDK surfaces that as a `ControlNumberError` with `code: "counter_already_started"`:
351
+
352
+ ```typescript
353
+ import { isControlNumberError, setControlNumber } from '@ontemper/edi';
354
+
355
+ try {
356
+ await setControlNumber('ISA13', 'ZZ:SENDER', 'ZZ:RECEIVER', 1000);
357
+ } catch (error) {
358
+ if (isControlNumberError(error) && error.code === 'counter_already_started') {
359
+ // Counter is already active; do not reset it.
360
+ }
361
+ }
362
+ ```
329
363
 
330
364
  Requires `UNNBOUND_API_URL` environment variable (pre-configured in all workflow environments).
331
365
 
@@ -333,7 +367,7 @@ Requires `UNNBOUND_API_URL` environment variable (pre-configured in all workflow
333
367
 
334
368
  - Node.js >= 22.0.0
335
369
  - TypeScript (for TypeScript projects)
336
- - Temper EDI API key
370
+ - 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
371
  - `UNNBOUND_API_URL` (for control number auto-stamping, pre-configured in workflow environments)
338
372
 
339
373
  ## 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,6 +1,7 @@
1
1
  import { type X12Interchange } from './edination-client';
2
+ export type { ControlNumberErrorCode } from './control-numbers';
3
+ export { ControlNumberError, getCurrentControlNumber, getNextControlNumber, isControlNumberError, setControlNumber, } from './control-numbers';
2
4
  export * from './edination-client/model';
3
- export { getNextControlNumber, getCurrentControlNumber, setControlNumber } from './control-numbers';
4
5
  interface UnnboundErrorOptions<C extends string = string> extends ErrorOptions {
5
6
  message: string;
6
7
  code: C;
@@ -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,16 +36,18 @@ 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.getCurrentControlNumber = exports.getNextControlNumber = 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
- __exportStar(require("./edination-client/model"), exports);
45
44
  var control_numbers_1 = require("./control-numbers");
46
- Object.defineProperty(exports, "getNextControlNumber", { enumerable: true, get: function () { return control_numbers_1.getNextControlNumber; } });
45
+ Object.defineProperty(exports, "ControlNumberError", { enumerable: true, get: function () { return control_numbers_1.ControlNumberError; } });
47
46
  Object.defineProperty(exports, "getCurrentControlNumber", { enumerable: true, get: function () { return control_numbers_1.getCurrentControlNumber; } });
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; } });
48
49
  Object.defineProperty(exports, "setControlNumber", { enumerable: true, get: function () { return control_numbers_1.setControlNumber; } });
50
+ __exportStar(require("./edination-client/model"), exports);
49
51
  class UnnboundError extends Error {
50
52
  code;
51
53
  constructor({ message, code, ...o }) {
@@ -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,25 @@ 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
115
  if (error.status === 403)
92
116
  throw new TemperEdiClientError({
93
117
  message: 'Unauthorized access to EDI service. Reach out to support.',
94
118
  code: 'edi_unauthorized_error',
95
119
  });
120
+ // Transport failures (no response: ECONNREFUSED/timeout/DNS), 5xx, and
121
+ // rate-limit/timeout statuses are transient service faults, not verdicts on
122
+ // the document — a brief EDI-service outage must never quarantine valid
123
+ // files in /error (T-3235).
124
+ const status = error.response?.status;
125
+ if (status === undefined || status >= 500 || status === 408 || status === 429) {
126
+ throw new EdiInfrastructureError({
127
+ message: `EDI service unreachable or transiently failing (${status ?? error.code ?? 'no response'}): ${error.message}`,
128
+ cause: error,
129
+ });
130
+ }
96
131
  // Gateway normalises EdiFabric's camelCase to PascalCase before forwarding
97
132
  const responseData = error.response?.data;
98
133
  const details = typeof responseData === 'object' &&
@@ -108,15 +143,13 @@ class TemperEdiClient {
108
143
  : error.message;
109
144
  throw new TemperEdiClientError({ message, code, cause: error });
110
145
  }
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',
146
+ // Not an axios error the failure never reached the EDI HTTP exchange. It was
147
+ // raised by the SDK's own plumbing (span/logging instrumentation, request
148
+ // serialization), so it must not be reported as a verdict on the file: workflows
149
+ // route `edi_*` errors to /error, and that misrouted valid partner files when the
150
+ // instrumentation layer crashed (T-3235).
151
+ throw new EdiInfrastructureError({
152
+ message: `EDI client infrastructure failure (not caused by the input document): ${error instanceof Error ? error.message : String(error)}`,
120
153
  cause: error,
121
154
  });
122
155
  }
package/package.json CHANGED
@@ -1,23 +1,9 @@
1
1
  {
2
2
  "name": "@ontemper/edi",
3
3
  "description": "An EDI client with structured logging.",
4
- "version": "1.1.2",
4
+ "version": "1.1.4",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
- "scripts": {
8
- "build": "tsc",
9
- "test": "echo 'No tests'",
10
- "typecheck": "tsgo --noEmit",
11
- "lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
12
- "lint:fix": "pnpm run lint --fix",
13
- "format": "biome format --write .",
14
- "format:check": "biome format .",
15
- "prepublishOnly": "pnpm run build",
16
- "start:example": "tsx watch examples/node-edi.ts",
17
- "version:bump": "npm version patch",
18
- "release": "pnpm run build && pnpm publish --access public",
19
- "codegen": "npx --yes @openapitools/openapi-generator-cli generate -c openapitools.json -o ./src/edination-client"
20
- },
21
7
  "author": "Unnbound Team",
22
8
  "license": "MIT",
23
9
  "repository": {
@@ -29,12 +15,13 @@
29
15
  "url": "https://github.com/unnbounddev/unnbound-sdks/issues"
30
16
  },
31
17
  "dependencies": {
32
- "axios": "1.13.6",
33
- "unnbound-logger-sdk": "workspace:*"
18
+ "axios": "1.16.0",
19
+ "unnbound-logger-sdk": "3.0.37"
34
20
  },
35
21
  "devDependencies": {
36
22
  "@types/jest": "^29.5.12",
37
- "@types/node": "^24.12.2"
23
+ "@types/node": "^24.12.2",
24
+ "vitest": "^4.0.15"
38
25
  },
39
26
  "files": [
40
27
  "examples/*",
@@ -45,5 +32,16 @@
45
32
  "engines": {
46
33
  "node": ">=22"
47
34
  },
48
- "sideEffects": false
49
- }
35
+ "sideEffects": false,
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "test": "vitest run src",
39
+ "typecheck": "tsc --noEmit",
40
+ "format": "biome format --write .",
41
+ "format:check": "biome format .",
42
+ "start:example": "tsx watch examples/node-edi.ts",
43
+ "version:bump": "npm version patch",
44
+ "release": "pnpm run build && pnpm publish --access public",
45
+ "codegen": "npx --yes @openapitools/openapi-generator-cli generate -c openapitools.json -o ./src/edination-client"
46
+ }
47
+ }