@ontemper/edi 1.2.0-beta.4 → 1.2.0-beta.6

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
@@ -362,32 +362,26 @@ sequence. Sandbox and staging allocations do not consume production control numb
362
362
 
363
363
  ### Automatic environment routing
364
364
 
365
- Temper injects `TEMPER_ENVIRONMENT` into workflows. The SDK uses `UNNBOUND_ENVIRONMENT` only when
366
- `TEMPER_ENVIRONMENT` is absent. The value must be `sandbox`, `staging`, or `production`. A missing or invalid value
367
- causes a `ControlNumberError` with the `control_number_environment_missing` code before any HTTP request.
365
+ Current workflow deployments inject `UNNBOUND_ENVIRONMENT`. The SDK also accepts `TEMPER_ENVIRONMENT` as the
366
+ future canonical name and prefers it when present. The value must be `sandbox`, `staging`, or `production`. A missing
367
+ or invalid value causes a `ControlNumberError` with the `control_number_environment_missing` code before any HTTP request.
368
368
 
369
369
  `getNextControlNumber()`, `getCurrentControlNumber()`, and `toX12()` use this environment. They do not accept an
370
370
  environment override. ISA15 (`UsageIndicator_15`) does not select a counter, and `toX12()` does not change it.
371
371
 
372
372
  ```typescript
373
- import { getCurrentControlNumber, getNextControlNumber, setControlNumber } from '@ontemper/edi';
373
+ import { getCurrentControlNumber, getNextControlNumber } from '@ontemper/edi';
374
374
 
375
375
  const senderId = 'ZZ:SENDER';
376
376
  const receiverId = 'ZZ:RECEIVER';
377
377
 
378
378
  const next = await getNextControlNumber('ISA13', senderId, receiverId);
379
379
  const current = await getCurrentControlNumber('ISA13', senderId, receiverId);
380
-
381
- // Legacy production migration: store 4599 as the last-used value.
382
- await setControlNumber('ISA13', senderId, receiverId, 4599);
383
380
  ```
384
381
 
385
- `setControlNumber()` is the legacy production setter. Its value is the last-used number, so setting `4599` makes the
386
- next allocation `4600`. It uses the flat production route and does not use the environment variables.
387
-
388
- Use the Builder agent `set_edi_control_number` tool for per-environment seeding. The tool value is the next number.
389
- For example, seeding `4600` makes the next allocation `4600`. Seeding is forward-only. Re-seeding the same value is
390
- a no-op.
382
+ The SDK has no seeding helper. Seed a counter through the Builder agent `set_edi_control_number` tool, which asks
383
+ the user to approve each seed. The tool value is the next number: seeding `4600` makes the next allocation `4600`.
384
+ Seeding is forward-only. Re-seeding the same value is a no-op.
391
385
 
392
386
  These helpers require `UNNBOUND_API_URL`. Temper configures it in workflow environments.
393
387
 
@@ -397,7 +391,7 @@ These helpers require `UNNBOUND_API_URL`. Temper configures it in workflow envir
397
391
  - TypeScript (for TypeScript projects)
398
392
  - 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.
399
393
  - `UNNBOUND_API_URL` (for control number auto-stamping, pre-configured in workflow environments)
400
- - `TEMPER_ENVIRONMENT` for counter routing, with `UNNBOUND_ENVIRONMENT` as the legacy fallback
394
+ - `UNNBOUND_ENVIRONMENT` for current counter routing; `TEMPER_ENVIRONMENT` is the preferred future name
401
395
 
402
396
  ## Dependencies
403
397
 
@@ -3,7 +3,7 @@ export type TemperEnvironment = 'sandbox' | 'staging' | 'production';
3
3
  /** @public */
4
4
  export type ControlNumberCounterName = 'ISA13' | 'GS06';
5
5
  /** @public */
6
- export type ControlNumberErrorCode = 'counter_already_started' | 'control_number_environment_missing' | 'control_number_bad_request' | 'control_number_unauthorized' | 'control_number_unknown_error';
6
+ export type ControlNumberErrorCode = 'control_number_environment_missing' | 'control_number_bad_request' | 'control_number_unauthorized' | 'control_number_unknown_error';
7
7
  interface ControlNumberErrorOptions extends ErrorOptions {
8
8
  code: ControlNumberErrorCode;
9
9
  message: string;
@@ -33,11 +33,4 @@ export declare function getNextControlNumber(counterName: ControlNumberCounterNa
33
33
  */
34
34
  /** @public */
35
35
  export declare function getCurrentControlNumber(counterName: ControlNumberCounterName, senderId: string, receiverId: string, maxDigits?: number): Promise<string>;
36
- /**
37
- * Set the last-used production control number through the legacy endpoint.
38
- *
39
- * Use the Builder agent `set_edi_control_number` tool for per-environment seeding.
40
- */
41
- /** @public */
42
- export declare function setControlNumber(counterName: ControlNumberCounterName, senderId: string, receiverId: string, value: number, maxDigits?: number): Promise<string>;
43
36
  export {};
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.isControlNumberError = exports.ControlNumberError = void 0;
37
37
  exports.getNextControlNumber = getNextControlNumber;
38
38
  exports.getCurrentControlNumber = getCurrentControlNumber;
39
- exports.setControlNumber = setControlNumber;
40
39
  const axios_1 = __importStar(require("axios"));
41
40
  let _client = null;
42
41
  function getApiClient() {
@@ -88,24 +87,17 @@ const requireEnvironment = () => {
88
87
  message: "TEMPER_ENVIRONMENT or UNNBOUND_ENVIRONMENT must be set to 'sandbox', 'staging', or 'production' because automatic control-number routing needs it.",
89
88
  });
90
89
  };
91
- function unwrapControlNumberError(error, environmentScoped = false) {
90
+ function unwrapControlNumberError(error) {
92
91
  if (error instanceof ControlNumberError)
93
92
  throw error;
94
93
  if ((0, axios_1.isAxiosError)(error)) {
95
- if (environmentScoped && error.response?.status === 404) {
94
+ if (error.response?.status === 404) {
96
95
  throw new ControlNumberError({
97
96
  code: 'control_number_unknown_error',
98
97
  message: 'The Temper API does not support environment-scoped EDI control numbers yet.',
99
98
  cause: error,
100
99
  });
101
100
  }
102
- if (error.response?.status === 409) {
103
- throw new ControlNumberError({
104
- code: 'counter_already_started',
105
- message: 'Cannot set EDI control number after counter has started. Set the starting value before calling getNextControlNumber or toX12 for this trading partner.',
106
- cause: error,
107
- });
108
- }
109
101
  if (error.response?.status === 400) {
110
102
  throw new ControlNumberError({
111
103
  code: 'control_number_bad_request',
@@ -127,19 +119,6 @@ function unwrapControlNumberError(error, environmentScoped = false) {
127
119
  cause: error,
128
120
  });
129
121
  }
130
- async function requestControlNumber(operation, input) {
131
- const environment = requireEnvironment();
132
- const path = `/api/v2/internal/edi/control-numbers/${environment}/${operation}`;
133
- try {
134
- const response = operation === 'count'
135
- ? await getApiClient().get(path, { params: input })
136
- : await getApiClient().post(path, input);
137
- return response.data;
138
- }
139
- catch (error) {
140
- return unwrapControlNumberError(error, true);
141
- }
142
- }
143
122
  /**
144
123
  * Get the next control number for an EDI counter.
145
124
  *
@@ -153,35 +132,30 @@ async function requestControlNumber(operation, input) {
153
132
  */
154
133
  /** @public */
155
134
  async function getNextControlNumber(counterName, senderId, receiverId, maxDigits = 9) {
156
- const data = await requestControlNumber('increment', {
157
- tradingPartnerKey: `${senderId}:${receiverId}`,
158
- counterName,
159
- });
160
- return String(data.value).padStart(maxDigits, '0');
135
+ const environment = requireEnvironment();
136
+ try {
137
+ const { data } = await getApiClient().post(`/api/v2/internal/edi/control-numbers/${environment}/increment`, {
138
+ tradingPartnerKey: `${senderId}:${receiverId}`,
139
+ counterName,
140
+ });
141
+ return String(data.value).padStart(maxDigits, '0');
142
+ }
143
+ catch (error) {
144
+ return unwrapControlNumberError(error);
145
+ }
161
146
  }
162
147
  /**
163
148
  * Get the current control number value without incrementing.
164
149
  */
165
150
  /** @public */
166
151
  async function getCurrentControlNumber(counterName, senderId, receiverId, maxDigits = 9) {
167
- const data = await requestControlNumber('count', {
168
- tradingPartnerKey: `${senderId}:${receiverId}`,
169
- counterName,
170
- });
171
- return String(data.value).padStart(maxDigits, '0');
172
- }
173
- /**
174
- * Set the last-used production control number through the legacy endpoint.
175
- *
176
- * Use the Builder agent `set_edi_control_number` tool for per-environment seeding.
177
- */
178
- /** @public */
179
- async function setControlNumber(counterName, senderId, receiverId, value, maxDigits = 9) {
152
+ const environment = requireEnvironment();
180
153
  try {
181
- const { data } = await getApiClient().post('/api/v2/internal/edi/control-numbers/set', {
182
- tradingPartnerKey: `${senderId}:${receiverId}`,
183
- counterName,
184
- value,
154
+ const { data } = await getApiClient().get(`/api/v2/internal/edi/control-numbers/${environment}/count`, {
155
+ params: {
156
+ tradingPartnerKey: `${senderId}:${receiverId}`,
157
+ counterName,
158
+ },
185
159
  });
186
160
  return String(data.value).padStart(maxDigits, '0');
187
161
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type X12Interchange } from './edination-client';
2
2
  export type { ControlNumberErrorCode } from './control-numbers';
3
- export { ControlNumberError, getCurrentControlNumber, getNextControlNumber, isControlNumberError, setControlNumber, } from './control-numbers';
3
+ export { ControlNumberError, getCurrentControlNumber, getNextControlNumber, isControlNumberError, } from './control-numbers';
4
4
  export * from './edination-client/model';
5
5
  interface UnnboundErrorOptions<C extends string = string> extends ErrorOptions {
6
6
  message: string;
@@ -41,12 +41,7 @@ export declare class TemperEdiClient {
41
41
  private unwrap;
42
42
  private unwrapError;
43
43
  fromX12({ input }: FromX12Options): Promise<X12Interchange[]>;
44
- /**
45
- * Stamp ISA13/GS06/ST02 control numbers on the interchange if not already set.
46
- * Uses the centralized per-environment service for atomic partner sequencing.
47
- * ST02 is always "0001" (one transaction per file per ticket).
48
- * Zero-filled values from acknowledgment generation are placeholders, not manual overrides.
49
- */
44
+ /** Allocate every remote number before mutating the interchange. */
50
45
  private stampControlNumbers;
51
46
  toX12({ input }: ToX12Options): Promise<File>;
52
47
  validateX12({ input }: ValidateX12Options): Promise<import("./edination-client").OperationResult>;
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ 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.isEdiInfrastructureError = exports.EdiInfrastructureError = exports.isTemperEdiClientError = exports.TemperEdiClientError = exports.setControlNumber = exports.isControlNumberError = exports.getNextControlNumber = exports.getCurrentControlNumber = exports.ControlNumberError = void 0;
39
+ exports.TemperEdiClient = exports.isEdiInfrastructureError = exports.EdiInfrastructureError = exports.isTemperEdiClientError = exports.TemperEdiClientError = 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");
@@ -47,7 +47,6 @@ Object.defineProperty(exports, "ControlNumberError", { enumerable: true, get: fu
47
47
  Object.defineProperty(exports, "getCurrentControlNumber", { enumerable: true, get: function () { return control_numbers_2.getCurrentControlNumber; } });
48
48
  Object.defineProperty(exports, "getNextControlNumber", { enumerable: true, get: function () { return control_numbers_2.getNextControlNumber; } });
49
49
  Object.defineProperty(exports, "isControlNumberError", { enumerable: true, get: function () { return control_numbers_2.isControlNumberError; } });
50
- Object.defineProperty(exports, "setControlNumber", { enumerable: true, get: function () { return control_numbers_2.setControlNumber; } });
51
50
  __exportStar(require("./edination-client/model"), exports);
52
51
  class UnnboundError extends Error {
53
52
  code;
@@ -150,30 +149,24 @@ class TemperEdiClient {
150
149
  .catch((error) => this.unwrapError(error, 'edi_read_error'));
151
150
  }, (o) => buildEdiX12Payload('fromX12', { input, output: o?.result }));
152
151
  }
153
- /**
154
- * Stamp ISA13/GS06/ST02 control numbers on the interchange if not already set.
155
- * Uses the centralized per-environment service for atomic partner sequencing.
156
- * ST02 is always "0001" (one transaction per file per ticket).
157
- * Zero-filled values from acknowledgment generation are placeholders, not manual overrides.
158
- */
152
+ /** Allocate every remote number before mutating the interchange. */
159
153
  async stampControlNumbers(input) {
160
154
  const isa = input.ISA;
161
- const senderQual = (isa.SenderIDQualifier_5 || '').trim();
162
- const senderId = (isa.InterchangeSenderID_6 || '').trim();
163
- const receiverQual = (isa.ReceiverIDQualifier_7 || '').trim();
164
- const receiverId = (isa.InterchangeReceiverID_8 || '').trim();
155
+ const senderQual = (isa.SenderIDQualifier_5 ?? '').trim();
156
+ const senderId = (isa.InterchangeSenderID_6 ?? '').trim();
157
+ const receiverQual = (isa.ReceiverIDQualifier_7 ?? '').trim();
158
+ const receiverId = (isa.InterchangeReceiverID_8 ?? '').trim();
165
159
  const senderKey = `${senderQual}:${senderId}`;
166
160
  const receiverKey = `${receiverQual}:${receiverId}`;
167
- // Allocate before mutating so a failed request cannot leave partial stamps.
168
161
  const isa13 = needsControlNumber(isa.InterchangeControlNumber_13)
169
162
  ? await (0, control_numbers_1.getNextControlNumber)('ISA13', senderKey, receiverKey, 9)
170
163
  : null;
164
+ const groups = input.Groups;
171
165
  const gs06Values = [];
172
- const groups = input.Groups ?? [];
173
- for (let i = 0; i < groups.length; i++) {
174
- if (needsControlNumber(groups[i].GS.GroupControlNumber_6)) {
166
+ for (const group of groups) {
167
+ if (needsControlNumber(group.GS.GroupControlNumber_6)) {
175
168
  const value = await (0, control_numbers_1.getNextControlNumber)('GS06', senderKey, receiverKey, 9);
176
- gs06Values.push({ index: i, value });
169
+ gs06Values.push({ group, value });
177
170
  }
178
171
  }
179
172
  if (isa13) {
@@ -182,8 +175,7 @@ class TemperEdiClient {
182
175
  input.IEATrailers[0].InterchangeControlNumber_2 = isa13;
183
176
  }
184
177
  }
185
- for (const { index, value } of gs06Values) {
186
- const group = groups[index];
178
+ for (const { group, value } of gs06Values) {
187
179
  group.GS.GroupControlNumber_6 = value;
188
180
  if (group.GETrailers?.length) {
189
181
  group.GETrailers[0].GroupControlNumber_2 = value;
@@ -201,14 +193,7 @@ class TemperEdiClient {
201
193
  }
202
194
  }
203
195
  async toX12({ input }) {
204
- // Auto-stamp control numbers before writing X12
205
- try {
206
- await this.stampControlNumbers(input);
207
- }
208
- catch (error) {
209
- // Workflows can still serialize documents whose control numbers were set manually.
210
- unnbound_logger_sdk_1.logger.warn({ err: error }, '[EDI SDK] Failed to auto-stamp control numbers, proceeding without');
211
- }
196
+ await this.stampControlNumbers(input);
212
197
  return (0, unnbound_logger_sdk_1.startSpan)('JSON to X12', () => {
213
198
  return this.X12.x12WritePost({ x12Interchange: input })
214
199
  .then(this.unwrap.bind(this))
package/package.json CHANGED
@@ -1,9 +1,21 @@
1
1
  {
2
2
  "name": "@ontemper/edi",
3
3
  "description": "An EDI client with structured logging.",
4
- "version": "1.2.0-beta.4",
4
+ "version": "1.2.0-beta.6",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "vitest run src",
10
+ "typecheck": "tsc --noEmit",
11
+ "format": "oxfmt --write .",
12
+ "format:check": "oxfmt --check .",
13
+ "prepublishOnly": "pnpm run build",
14
+ "start:example": "tsx watch examples/node-edi.ts",
15
+ "version:bump": "npm version patch",
16
+ "release": "pnpm run build && pnpm publish --access public",
17
+ "codegen": "npx --yes @openapitools/openapi-generator-cli generate -c openapitools.json -o ./src/edination-client"
18
+ },
7
19
  "author": "Unnbound Team",
8
20
  "license": "MIT",
9
21
  "repository": {
@@ -16,7 +28,7 @@
16
28
  },
17
29
  "dependencies": {
18
30
  "axios": "1.18.0",
19
- "unnbound-logger-sdk": "3.1.1"
31
+ "unnbound-logger-sdk": "workspace:3.1.1"
20
32
  },
21
33
  "devDependencies": {
22
34
  "@types/jest": "^29.5.12",
@@ -33,16 +45,5 @@
33
45
  "engines": {
34
46
  "node": ">=22"
35
47
  },
36
- "sideEffects": false,
37
- "scripts": {
38
- "build": "tsc",
39
- "test": "vitest run src",
40
- "typecheck": "tsc --noEmit",
41
- "format": "oxfmt --write .",
42
- "format:check": "oxfmt --check .",
43
- "start:example": "tsx watch examples/node-edi.ts",
44
- "version:bump": "npm version patch",
45
- "release": "pnpm run build && pnpm publish --access public",
46
- "codegen": "npx --yes @openapitools/openapi-generator-cli generate -c openapitools.json -o ./src/edination-client"
47
- }
48
- }
48
+ "sideEffects": false
49
+ }
package/CHANGELOG.md DELETED
@@ -1,23 +0,0 @@
1
- ## [1.1.7](https://github.com/ontemper/temper/releases/tag/%40ontemper%2Fedi-v1.1.7) (2026-07-28)
2
-
3
-
4
- ### Bug Fixes
5
-
6
- * stamp zero acknowledgment envelope control numbers ([#2421](https://github.com/ontemper/temper/pull/2421))
7
-
8
- ## [1.0.13](https://github.com/unnbounddev/unnbound-sdks/compare/unnbound-edi-sdk-v1.0.12...unnbound-edi-sdk-v1.0.13) (2025-11-25)
9
-
10
-
11
- ### Bug Fixes
12
-
13
- * setup ci/cd ([dd2640f](https://github.com/unnbounddev/unnbound-sdks/commit/dd2640f01e39f1342f3e08dc882164b50331c7bb))
14
- * setup ci/cd ([520c03a](https://github.com/unnbounddev/unnbound-sdks/commit/520c03aacd978d7923b3bcf45c4f74a870be019d))
15
-
16
- ## [1.0.12](https://github.com/unnbounddev/unnbound-sdks/compare/unnbound-edi-sdk-v1.0.11...unnbound-edi-sdk-v1.0.12) (2025-11-25)
17
-
18
-
19
- ### Bug Fixes
20
-
21
- * remove unused files ([6dc00c2](https://github.com/unnbounddev/unnbound-sdks/commit/6dc00c20b092b17e9eb2108ef83dc5beafdef3f5))
22
- * setup ci/cd ([d3759c1](https://github.com/unnbounddev/unnbound-sdks/commit/d3759c14f7863e4c8d2f5e80bdbbf2446df79f1a))
23
- * setup ci/cd ([7e06fc8](https://github.com/unnbounddev/unnbound-sdks/commit/7e06fc84ae7bafba3e9c2c0225f9f9d614fa9207))