@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 +8 -14
- package/dist/control-numbers.d.ts +1 -8
- package/dist/control-numbers.js +19 -45
- package/dist/index.d.ts +2 -7
- package/dist/index.js +12 -27
- package/package.json +16 -15
- package/CHANGELOG.md +0 -23
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
|
-
|
|
366
|
-
|
|
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
|
|
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
|
-
|
|
386
|
-
|
|
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
|
-
- `
|
|
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 = '
|
|
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 {};
|
package/dist/control-numbers.js
CHANGED
|
@@ -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
|
|
90
|
+
function unwrapControlNumberError(error) {
|
|
92
91
|
if (error instanceof ControlNumberError)
|
|
93
92
|
throw error;
|
|
94
93
|
if ((0, axios_1.isAxiosError)(error)) {
|
|
95
|
-
if (
|
|
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
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
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().
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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,
|
|
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.
|
|
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
|
|
162
|
-
const senderId = (isa.InterchangeSenderID_6
|
|
163
|
-
const receiverQual = (isa.ReceiverIDQualifier_7
|
|
164
|
-
const receiverId = (isa.InterchangeReceiverID_8
|
|
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
|
|
173
|
-
|
|
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({
|
|
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 {
|
|
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
|
-
|
|
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
|
+
"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
|
-
|
|
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))
|