@ontemper/edi 1.2.0-beta.2 → 1.2.0-beta.3
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 +27 -56
- package/dist/control-numbers.d.ts +7 -25
- package/dist/control-numbers.js +14 -50
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/examples/node-edi.ts +0 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -356,75 +356,46 @@ The SDK manages ISA13 and GS06 control numbers for each sender and receiver pair
|
|
|
356
356
|
empty or all-zero ISA13, GS06, and ST02 fields. It preserves valid non-zero values. ISA13 and GS06 use the
|
|
357
357
|
centralized counters. ST02 is assigned `"0001"`.
|
|
358
358
|
|
|
359
|
-
|
|
360
|
-
Separating test traffic onto its own sequence is an optional, per-partner opt-in for agreements that demand it.
|
|
361
|
-
|
|
362
|
-
### Separate test counters (optional)
|
|
363
|
-
|
|
364
|
-
Skip this section unless a trading-partner agreement requires test traffic on an independent sequence. To opt in,
|
|
365
|
-
configure the trading partner once, then seed and verify the lanes before sending test traffic. A seeded value is the
|
|
366
|
-
last-used number, so the next allocation returns the seed plus one. To make the next ISA13 value 4601, seed 4600:
|
|
367
|
-
|
|
368
|
-
```typescript
|
|
369
|
-
import {
|
|
370
|
-
getControlNumberPolicy,
|
|
371
|
-
getCurrentControlNumber,
|
|
372
|
-
setControlNumber,
|
|
373
|
-
setControlNumberPolicy,
|
|
374
|
-
} from '@ontemper/edi';
|
|
375
|
-
|
|
376
|
-
const senderId = 'ZZ:SENDER';
|
|
377
|
-
const receiverId = 'ZZ:RECEIVER';
|
|
378
|
-
|
|
379
|
-
await setControlNumberPolicy(senderId, receiverId, { separateCounters: true });
|
|
380
|
-
|
|
381
|
-
// Seed the production lane explicitly before it starts.
|
|
382
|
-
await setControlNumber('ISA13', senderId, receiverId, 4600, 9, { lane: 'production' });
|
|
383
|
-
|
|
384
|
-
const policy = await getControlNumberPolicy(senderId, receiverId);
|
|
385
|
-
const production = await getCurrentControlNumber('ISA13', senderId, receiverId, 9, { lane: 'production' });
|
|
386
|
-
const test = await getCurrentControlNumber('ISA13', senderId, receiverId, 9, { lane: 'test' });
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
`getControlNumberPolicy()` returns `configured` and `separateCounters`. After test traffic starts, you cannot turn
|
|
390
|
-
separate counters off. `setControlNumberPolicy()` reports this as a `ControlNumberError` with the
|
|
391
|
-
`counter_policy_locked` code.
|
|
359
|
+
Each Temper environment now has its own ISA13 and GS06 counter ledger.
|
|
392
360
|
|
|
393
361
|
### Automatic environment routing
|
|
394
362
|
|
|
395
|
-
|
|
396
|
-
`
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
| --- | --- | --- |
|
|
400
|
-
| No policy row | Production lane | Production lane |
|
|
401
|
-
| `separateCounters: false` | Production lane | Production lane |
|
|
402
|
-
| `separateCounters: true` | Production lane | Test lane |
|
|
363
|
+
Automatic allocation reads `UNNBOUND_ENVIRONMENT`. Its value must be exactly `sandbox`, `staging`, or
|
|
364
|
+
`production`. If the variable is missing or invalid, the SDK throws a `ControlNumberError` with the
|
|
365
|
+
`control_number_environment_missing` code before making an HTTP request. This fail-closed behavior prevents an
|
|
366
|
+
automatic allocation from using the wrong environment.
|
|
403
367
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
(`UsageIndicator_15`) never selects a counter. Set ISA15 independently to the value required by the interchange.
|
|
407
|
-
`toX12()` does not change it.
|
|
368
|
+
`getNextControlNumber()` and `toX12()` always use this ambient environment. ISA15 (`UsageIndicator_15`) does not
|
|
369
|
+
select a counter, and `toX12()` does not change it.
|
|
408
370
|
|
|
409
371
|
### Manage counters directly
|
|
410
372
|
|
|
411
373
|
```typescript
|
|
412
374
|
import { getCurrentControlNumber, getNextControlNumber, setControlNumber } from '@ontemper/edi';
|
|
413
375
|
|
|
414
|
-
const
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
await
|
|
376
|
+
const senderId = 'ZZ:SENDER';
|
|
377
|
+
const receiverId = 'ZZ:RECEIVER';
|
|
378
|
+
|
|
379
|
+
const next = await getNextControlNumber('ISA13', senderId, receiverId);
|
|
380
|
+
const current = await getCurrentControlNumber('ISA13', senderId, receiverId);
|
|
381
|
+
|
|
382
|
+
// A sandbox onboarding workflow can inspect and seed the production ledger explicitly.
|
|
383
|
+
const productionCurrent = await getCurrentControlNumber('ISA13', senderId, receiverId, 9, {
|
|
384
|
+
environment: 'production',
|
|
385
|
+
});
|
|
386
|
+
await setControlNumber('ISA13', senderId, receiverId, 4600, 9, {
|
|
387
|
+
environment: 'production',
|
|
388
|
+
});
|
|
418
389
|
```
|
|
419
390
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
391
|
+
`getCurrentControlNumber()` and `setControlNumber()` accept an optional
|
|
392
|
+
`{ environment: 'sandbox' | 'staging' | 'production' }` argument for cross-environment inspection and seeding.
|
|
393
|
+
An explicit environment takes precedence over `UNNBOUND_ENVIRONMENT`. Without this option, both methods require
|
|
394
|
+
the ambient environment. `getNextControlNumber()` accepts no environment override.
|
|
423
395
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
starts with a `ControlNumberError` whose code is `counter_already_started`.
|
|
396
|
+
A seed is the last-used value: seeding 4600 makes the next allocation 4601. Seed a counter only before its first
|
|
397
|
+
increment. A repeated seed with the same stored value is idempotent; a different seed after the counter starts
|
|
398
|
+
throws a `ControlNumberError` with the `counter_already_started` code.
|
|
428
399
|
|
|
429
400
|
These helpers require `UNNBOUND_API_URL`, which is preconfigured in workflow environments.
|
|
430
401
|
|
|
@@ -1,27 +1,13 @@
|
|
|
1
1
|
/** @public */
|
|
2
|
-
export type
|
|
3
|
-
/** @public */
|
|
4
|
-
export type ControlNumberLane = 'production' | 'test';
|
|
5
|
-
/** @public */
|
|
6
|
-
export interface ControlNumberLaneOptions {
|
|
7
|
-
readonly lane?: ControlNumberLane;
|
|
8
|
-
}
|
|
9
|
-
/** @public */
|
|
10
|
-
export interface SetControlNumberPolicyOptions {
|
|
11
|
-
readonly separateCounters: boolean;
|
|
12
|
-
}
|
|
2
|
+
export type TemperEnvironment = 'sandbox' | 'staging' | 'production';
|
|
13
3
|
/** @public */
|
|
14
|
-
export
|
|
15
|
-
readonly tradingPartnerKey: string;
|
|
16
|
-
readonly separateCounters: boolean;
|
|
17
|
-
}
|
|
4
|
+
export type ControlNumberCounterName = 'ISA13' | 'GS06';
|
|
18
5
|
/** @public */
|
|
19
|
-
export interface
|
|
20
|
-
readonly
|
|
21
|
-
readonly separateCounters: boolean;
|
|
6
|
+
export interface ControlNumberEnvironmentOptions {
|
|
7
|
+
readonly environment?: TemperEnvironment;
|
|
22
8
|
}
|
|
23
9
|
/** @public */
|
|
24
|
-
export type ControlNumberErrorCode = '
|
|
10
|
+
export type ControlNumberErrorCode = 'control_number_environment_missing' | 'counter_already_started' | 'control_number_bad_request' | 'control_number_unauthorized' | 'control_number_unknown_error';
|
|
25
11
|
interface ControlNumberErrorOptions extends ErrorOptions {
|
|
26
12
|
code: ControlNumberErrorCode;
|
|
27
13
|
message: string;
|
|
@@ -50,14 +36,10 @@ export declare function getNextControlNumber(counterName: ControlNumberCounterNa
|
|
|
50
36
|
* Get the current control number value without incrementing.
|
|
51
37
|
*/
|
|
52
38
|
/** @public */
|
|
53
|
-
export declare function getCurrentControlNumber(counterName: ControlNumberCounterName, senderId: string, receiverId: string, maxDigits?: number, options?:
|
|
39
|
+
export declare function getCurrentControlNumber(counterName: ControlNumberCounterName, senderId: string, receiverId: string, maxDigits?: number, options?: ControlNumberEnvironmentOptions): Promise<string>;
|
|
54
40
|
/**
|
|
55
41
|
* Set a control number to a specific value before the counter starts (for migration/seeding).
|
|
56
42
|
*/
|
|
57
43
|
/** @public */
|
|
58
|
-
export declare function setControlNumber(counterName: ControlNumberCounterName, senderId: string, receiverId: string, value: number, maxDigits?: number, options?:
|
|
59
|
-
/** @public */
|
|
60
|
-
export declare function setControlNumberPolicy(senderId: string, receiverId: string, { separateCounters }: SetControlNumberPolicyOptions): Promise<SetControlNumberPolicyResponse>;
|
|
61
|
-
/** @public */
|
|
62
|
-
export declare function getControlNumberPolicy(senderId: string, receiverId: string): Promise<GetControlNumberPolicyResponse>;
|
|
44
|
+
export declare function setControlNumber(counterName: ControlNumberCounterName, senderId: string, receiverId: string, value: number, maxDigits?: number, options?: ControlNumberEnvironmentOptions): Promise<string>;
|
|
63
45
|
export {};
|
package/dist/control-numbers.js
CHANGED
|
@@ -37,8 +37,6 @@ exports.isControlNumberError = exports.ControlNumberError = void 0;
|
|
|
37
37
|
exports.getNextControlNumber = getNextControlNumber;
|
|
38
38
|
exports.getCurrentControlNumber = getCurrentControlNumber;
|
|
39
39
|
exports.setControlNumber = setControlNumber;
|
|
40
|
-
exports.setControlNumberPolicy = setControlNumberPolicy;
|
|
41
|
-
exports.getControlNumberPolicy = getControlNumberPolicy;
|
|
42
40
|
const axios_1 = __importStar(require("axios"));
|
|
43
41
|
let _client = null;
|
|
44
42
|
function getApiClient() {
|
|
@@ -80,23 +78,20 @@ exports.ControlNumberError = ControlNumberError;
|
|
|
80
78
|
/** @public */
|
|
81
79
|
const isControlNumberError = (error) => error instanceof ControlNumberError;
|
|
82
80
|
exports.isControlNumberError = isControlNumberError;
|
|
83
|
-
const
|
|
81
|
+
const requireEnvironment = () => {
|
|
84
82
|
const environment = process.env.UNNBOUND_ENVIRONMENT;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
if (environment === 'sandbox' || environment === 'staging' || environment === 'production') {
|
|
84
|
+
return environment;
|
|
85
|
+
}
|
|
86
|
+
throw new ControlNumberError({
|
|
87
|
+
code: 'control_number_environment_missing',
|
|
88
|
+
message: "UNNBOUND_ENVIRONMENT must be set to 'sandbox', 'staging', or 'production' because automatic control-number routing needs it.",
|
|
89
|
+
});
|
|
88
90
|
};
|
|
89
|
-
function unwrapControlNumberError(error
|
|
91
|
+
function unwrapControlNumberError(error) {
|
|
90
92
|
if (error instanceof ControlNumberError)
|
|
91
93
|
throw error;
|
|
92
94
|
if ((0, axios_1.isAxiosError)(error)) {
|
|
93
|
-
if (error.response?.status === 409 && operation === 'policy') {
|
|
94
|
-
throw new ControlNumberError({
|
|
95
|
-
code: 'counter_policy_locked',
|
|
96
|
-
message: 'Separate counters cannot be disabled after test traffic has started. Keep separate counters enabled or contact Temper support.',
|
|
97
|
-
cause: error,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
95
|
if (error.response?.status === 409) {
|
|
101
96
|
throw new ControlNumberError({
|
|
102
97
|
code: 'counter_already_started',
|
|
@@ -125,9 +120,8 @@ function unwrapControlNumberError(error, operation) {
|
|
|
125
120
|
cause: error,
|
|
126
121
|
});
|
|
127
122
|
}
|
|
128
|
-
async function requestControlNumber(operation, input) {
|
|
129
|
-
const
|
|
130
|
-
const request = { ...input, ...(environment ? { environment } : {}) };
|
|
123
|
+
async function requestControlNumber(operation, input, environment) {
|
|
124
|
+
const request = { ...input, environment: environment ?? requireEnvironment() };
|
|
131
125
|
try {
|
|
132
126
|
const response = operation === 'count'
|
|
133
127
|
? await getApiClient().get(`/api/v2/internal/edi/control-numbers/${operation}`, {
|
|
@@ -137,7 +131,7 @@ async function requestControlNumber(operation, input) {
|
|
|
137
131
|
return response.data;
|
|
138
132
|
}
|
|
139
133
|
catch (error) {
|
|
140
|
-
return unwrapControlNumberError(error
|
|
134
|
+
return unwrapControlNumberError(error);
|
|
141
135
|
}
|
|
142
136
|
}
|
|
143
137
|
/**
|
|
@@ -167,8 +161,7 @@ async function getCurrentControlNumber(counterName, senderId, receiverId, maxDig
|
|
|
167
161
|
const data = await requestControlNumber('count', {
|
|
168
162
|
tradingPartnerKey: `${senderId}:${receiverId}`,
|
|
169
163
|
counterName,
|
|
170
|
-
|
|
171
|
-
});
|
|
164
|
+
}, options?.environment);
|
|
172
165
|
return String(data.value).padStart(maxDigits, '0');
|
|
173
166
|
}
|
|
174
167
|
/**
|
|
@@ -180,35 +173,6 @@ async function setControlNumber(counterName, senderId, receiverId, value, maxDig
|
|
|
180
173
|
tradingPartnerKey: `${senderId}:${receiverId}`,
|
|
181
174
|
counterName,
|
|
182
175
|
value,
|
|
183
|
-
|
|
184
|
-
});
|
|
176
|
+
}, options?.environment);
|
|
185
177
|
return String(data.value).padStart(maxDigits, '0');
|
|
186
178
|
}
|
|
187
|
-
/** @public */
|
|
188
|
-
async function setControlNumberPolicy(senderId, receiverId, { separateCounters }) {
|
|
189
|
-
try {
|
|
190
|
-
const response = await getApiClient().post('/api/v2/internal/edi/control-numbers/policy', {
|
|
191
|
-
tradingPartnerKey: `${senderId}:${receiverId}`,
|
|
192
|
-
separateCounters,
|
|
193
|
-
});
|
|
194
|
-
return response.data;
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
return unwrapControlNumberError(error, 'policy');
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
/** @public */
|
|
201
|
-
async function getControlNumberPolicy(senderId, receiverId) {
|
|
202
|
-
try {
|
|
203
|
-
const response = await getApiClient().get('/api/v2/internal/edi/control-numbers/policy', {
|
|
204
|
-
params: { tradingPartnerKey: `${senderId}:${receiverId}` },
|
|
205
|
-
});
|
|
206
|
-
return {
|
|
207
|
-
configured: response.data.configured,
|
|
208
|
-
separateCounters: response.data.separateCounters,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
catch (error) {
|
|
212
|
-
return unwrapControlNumberError(error, 'policy');
|
|
213
|
-
}
|
|
214
|
-
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { X12Interchange } from './edination-client';
|
|
2
|
-
export type { ControlNumberCounterName,
|
|
3
|
-
export { ControlNumberError,
|
|
2
|
+
export type { ControlNumberCounterName, ControlNumberEnvironmentOptions, ControlNumberErrorCode, } from './control-numbers';
|
|
3
|
+
export { ControlNumberError, getCurrentControlNumber, getNextControlNumber, isControlNumberError, setControlNumber, } from './control-numbers';
|
|
4
4
|
export * from './edination-client/model';
|
|
5
5
|
interface UnnboundErrorOptions<C extends string = string> extends ErrorOptions {
|
|
6
6
|
message: string;
|
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.
|
|
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");
|
|
@@ -44,12 +44,10 @@ const control_numbers_1 = require("./control-numbers");
|
|
|
44
44
|
const edination_client_1 = require("./edination-client");
|
|
45
45
|
var control_numbers_2 = require("./control-numbers");
|
|
46
46
|
Object.defineProperty(exports, "ControlNumberError", { enumerable: true, get: function () { return control_numbers_2.ControlNumberError; } });
|
|
47
|
-
Object.defineProperty(exports, "getControlNumberPolicy", { enumerable: true, get: function () { return control_numbers_2.getControlNumberPolicy; } });
|
|
48
47
|
Object.defineProperty(exports, "getCurrentControlNumber", { enumerable: true, get: function () { return control_numbers_2.getCurrentControlNumber; } });
|
|
49
48
|
Object.defineProperty(exports, "getNextControlNumber", { enumerable: true, get: function () { return control_numbers_2.getNextControlNumber; } });
|
|
50
49
|
Object.defineProperty(exports, "isControlNumberError", { enumerable: true, get: function () { return control_numbers_2.isControlNumberError; } });
|
|
51
50
|
Object.defineProperty(exports, "setControlNumber", { enumerable: true, get: function () { return control_numbers_2.setControlNumber; } });
|
|
52
|
-
Object.defineProperty(exports, "setControlNumberPolicy", { enumerable: true, get: function () { return control_numbers_2.setControlNumberPolicy; } });
|
|
53
51
|
__exportStar(require("./edination-client/model"), exports);
|
|
54
52
|
class UnnboundError extends Error {
|
|
55
53
|
code;
|
|
@@ -205,7 +203,9 @@ class TemperEdiClient {
|
|
|
205
203
|
await this.stampControlNumbers(input);
|
|
206
204
|
}
|
|
207
205
|
catch (error) {
|
|
208
|
-
|
|
206
|
+
if (error instanceof control_numbers_1.ControlNumberError)
|
|
207
|
+
throw error;
|
|
208
|
+
// Workflows can still serialize documents whose control numbers were set manually.
|
|
209
209
|
unnbound_logger_sdk_1.logger.warn({ err: error }, '[EDI SDK] Failed to auto-stamp control numbers, proceeding without');
|
|
210
210
|
}
|
|
211
211
|
return (0, unnbound_logger_sdk_1.startSpan)('JSON to X12', () => {
|
package/examples/node-edi.ts
CHANGED
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.2.0-beta.
|
|
4
|
+
"version": "1.2.0-beta.3",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"author": "Unnbound Team",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"build": "tsc",
|
|
38
38
|
"test": "vitest run src",
|
|
39
39
|
"typecheck": "tsc --noEmit",
|
|
40
|
-
"format": "
|
|
41
|
-
"format:check": "
|
|
40
|
+
"format": "oxfmt --write .",
|
|
41
|
+
"format:check": "oxfmt --check .",
|
|
42
42
|
"start:example": "tsx watch examples/node-edi.ts",
|
|
43
43
|
"version:bump": "npm version patch",
|
|
44
44
|
"release": "pnpm run build && pnpm publish --access public",
|