@carrierllc/sdk 0.2.12 → 0.2.13
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 +2 -2
- package/dist/client.js +25 -5
- package/dist/data/cli.json +41 -1
- package/dist/data/operations.json +1 -1
- package/dist/error.d.ts +8 -1
- package/dist/error.js +11 -2
- package/dist/idempotency.d.ts +4 -1
- package/dist/idempotency.js +11 -1
- package/dist/operations.js +5 -3
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ const intent = await carrier.intents.create(
|
|
|
13
13
|
)
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
`intents.create` posts to `/v1/connectivity/intents`. The same
|
|
16
|
+
`intents.create` posts to `/v1/connectivity/intents`. Issuance requires a stable caller key matching `[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}` (1–200 ASCII characters). The same key and body identify the original operation; they do not prove provider exactly-once completion. After transport/body loss, `CarrierError` has `code: "outcome_unknown"`, the original `idempotencyKey`, and `retryable: false`. The SDK sends once, disables redirects, and never generates a replacement key. Preserve the key and reconcile; do not automatically retry or reissue. `products.create` posts to `/v1/connectivity/products`. The price on a product is catalogue data. Neither call debits a wallet.
|
|
17
17
|
|
|
18
18
|
```ts
|
|
19
19
|
import { Cli, Mcp, restTools } from "@carrierllc/sdk"
|
|
@@ -35,4 +35,4 @@ const agentTools = restTools(carrier)
|
|
|
35
35
|
|
|
36
36
|
`new Mcp(token, { auth: "bearer" })` sends an OAuth access token or a Clerk organization API key. The default sends `?TOKEN=` to `https://mcp.carrier.llc/mcp`. `Cli` never invokes a shell. Catalog entries marked `internal` do not accept a customer API key.
|
|
37
37
|
|
|
38
|
-
`npm install @carrierllc/sdk@0.2.
|
|
38
|
+
`npm install @carrierllc/sdk@0.2.11` installs this surface. `0.1.0` only creates connectivity intents and products. The published tarball is the compiled `dist` output, including the generated operation catalog.
|
package/dist/client.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* debits a wallet.
|
|
7
7
|
*/
|
|
8
8
|
import { CarrierError, errorFromBody } from "./error.js";
|
|
9
|
-
import { requireIdempotencyKey } from "./idempotency.js";
|
|
9
|
+
import { isIssuance, requireIssuanceKey, requireIdempotencyKey } from "./idempotency.js";
|
|
10
10
|
import { prepareCall } from "./operations.js";
|
|
11
11
|
import { USER_AGENT } from "./version.js";
|
|
12
12
|
export class Carrier {
|
|
@@ -67,17 +67,37 @@ export class Carrier {
|
|
|
67
67
|
}
|
|
68
68
|
if (options.idempotencyKey !== undefined)
|
|
69
69
|
headers["Idempotency-Key"] = requireIdempotencyKey(options.idempotencyKey);
|
|
70
|
+
const issuance = isIssuance(method, path);
|
|
71
|
+
const issuanceKey = issuance ? requireIssuanceKey(options.idempotencyKey ?? headers["X-Idempotency-Key"]) : undefined;
|
|
72
|
+
const unknownOutcome = (status, cause) => new CarrierError(status, "outcome_unknown", "Issuance may have been dispatched. Do not retry or change the key; reconcile the original operation.", "api_error", { idempotencyKey: issuanceKey, retryable: false, cause });
|
|
70
73
|
let response;
|
|
71
74
|
try {
|
|
72
|
-
response = await this.fetchImpl(url.toString(), { method, headers, body: payload });
|
|
75
|
+
response = await this.fetchImpl(url.toString(), { method, headers, body: payload, ...(issuance ? { redirect: "error" } : {}) });
|
|
73
76
|
}
|
|
74
77
|
catch (error) {
|
|
78
|
+
if (issuance)
|
|
79
|
+
throw unknownOutcome(0, error);
|
|
75
80
|
const message = error instanceof Error ? error.message : "Network request failed.";
|
|
76
81
|
throw new CarrierError(0, "network_error", message, "api_error");
|
|
77
82
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = issuance ? JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(await response.arrayBuffer())) : await readJson(response);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (issuance)
|
|
89
|
+
throw unknownOutcome(response.status, error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
if (issuance && response.status >= 300 && response.status < 400)
|
|
93
|
+
throw unknownOutcome(response.status);
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
const error = errorFromBody(response.status, parsed);
|
|
96
|
+
if (issuance && ["outcome_unknown", "manual_reconciliation_required"].includes(error.code.toLowerCase())) {
|
|
97
|
+
throw new CarrierError(error.status, error.code, error.message, error.type, { idempotencyKey: issuanceKey, retryable: false });
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
81
101
|
return parsed;
|
|
82
102
|
}
|
|
83
103
|
}
|
package/dist/data/cli.json
CHANGED
|
@@ -568,7 +568,47 @@
|
|
|
568
568
|
},
|
|
569
569
|
{
|
|
570
570
|
"flags": "--account-id <id>",
|
|
571
|
-
"description": "
|
|
571
|
+
"description": "Issue from inventory through the authenticated Carrier API; requires --request-key; preserve it after unknown outcomes",
|
|
572
|
+
"required": false
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
"flags": "--request-key <key>",
|
|
576
|
+
"description": "Required stable inventory key (1–200 safe characters); preserved after unknown outcomes",
|
|
577
|
+
"required": false
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
"flags": "--start-date <date>",
|
|
581
|
+
"description": "Inventory package start date",
|
|
582
|
+
"required": false
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
"flags": "--end-date <date>",
|
|
586
|
+
"description": "Inventory package end date",
|
|
587
|
+
"required": false
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
"flags": "--validity-period <days>",
|
|
591
|
+
"description": "Inventory package validity period",
|
|
592
|
+
"required": false
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
"flags": "--business-kind <kind>",
|
|
596
|
+
"description": "Authorized reference kind: storefront_order",
|
|
597
|
+
"required": false
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
"flags": "--business-id <id>",
|
|
601
|
+
"description": "Existing API-authorized order ID",
|
|
602
|
+
"required": false
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
"flags": "--business-line <line>",
|
|
606
|
+
"description": "Authorized reference line: esim",
|
|
607
|
+
"required": false
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
"flags": "--business-verb <verb>",
|
|
611
|
+
"description": "Authorized reference verb: issue",
|
|
572
612
|
"required": false
|
|
573
613
|
}
|
|
574
614
|
]
|
package/dist/error.d.ts
CHANGED
|
@@ -3,6 +3,13 @@ export declare class CarrierError extends Error {
|
|
|
3
3
|
readonly status: number;
|
|
4
4
|
readonly code: string;
|
|
5
5
|
readonly type: string;
|
|
6
|
-
|
|
6
|
+
/** Present for a possibly dispatched issuance; keep this key for reconciliation. */
|
|
7
|
+
readonly idempotencyKey?: string;
|
|
8
|
+
readonly retryable?: boolean;
|
|
9
|
+
constructor(status: number, code: string, message: string, type?: string, details?: {
|
|
10
|
+
idempotencyKey?: string;
|
|
11
|
+
retryable?: boolean;
|
|
12
|
+
cause?: unknown;
|
|
13
|
+
});
|
|
7
14
|
}
|
|
8
15
|
export declare function errorFromBody(status: number, body: unknown): CarrierError;
|
package/dist/error.js
CHANGED
|
@@ -3,17 +3,26 @@ export class CarrierError extends Error {
|
|
|
3
3
|
status;
|
|
4
4
|
code;
|
|
5
5
|
type;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/** Present for a possibly dispatched issuance; keep this key for reconciliation. */
|
|
7
|
+
idempotencyKey;
|
|
8
|
+
retryable;
|
|
9
|
+
constructor(status, code, message, type = "invalid_request_error", details = {}) {
|
|
10
|
+
super(message, { cause: details.cause });
|
|
8
11
|
this.name = "CarrierError";
|
|
9
12
|
this.status = status;
|
|
10
13
|
this.code = code;
|
|
11
14
|
this.type = type;
|
|
15
|
+
this.idempotencyKey = details.idempotencyKey;
|
|
16
|
+
this.retryable = details.retryable;
|
|
12
17
|
}
|
|
13
18
|
}
|
|
14
19
|
export function errorFromBody(status, body) {
|
|
15
20
|
if (body && typeof body === "object" && "error" in body) {
|
|
16
21
|
const error = body.error;
|
|
22
|
+
if (typeof error === "string") {
|
|
23
|
+
const code = body.code;
|
|
24
|
+
return new CarrierError(status, typeof code === "string" ? code : "api_error", error, "api_error");
|
|
25
|
+
}
|
|
17
26
|
if (error && typeof error === "object") {
|
|
18
27
|
const record = error;
|
|
19
28
|
const code = typeof record.code === "string" ? record.code : "api_error";
|
package/dist/idempotency.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Issuance keys are the verbatim stable operation identity, never normalized. */
|
|
2
|
+
export declare function requireIssuanceKey(key: string | undefined): string;
|
|
3
|
+
export declare function isIssuance(method: string, path: string): boolean;
|
|
4
|
+
/** Nonissuance operations retain their existing API limits. */
|
|
2
5
|
export declare function requireIdempotencyKey(key: string | undefined): string;
|
|
3
6
|
/** Reject a path segment that could escape the resource URL. */
|
|
4
7
|
export declare function requirePathId(value: string, label: string): string;
|
package/dist/idempotency.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { CarrierError } from "./error.js";
|
|
2
|
-
/**
|
|
2
|
+
/** Issuance keys are the verbatim stable operation identity, never normalized. */
|
|
3
|
+
export function requireIssuanceKey(key) {
|
|
4
|
+
if (typeof key !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$/.test(key) || /[\r\n]/.test(key)) {
|
|
5
|
+
throw new CarrierError(400, "invalid_idempotency_key", "Issuance requires a 1–200 character ASCII key starting with a letter or digit and containing only letters, digits, _, ., :, or -.");
|
|
6
|
+
}
|
|
7
|
+
return key;
|
|
8
|
+
}
|
|
9
|
+
export function isIssuance(method, path) {
|
|
10
|
+
return method === "POST" && ["/v1/connectivity/intents", "/v1/subscribers/issue-from-inventory"].includes(path);
|
|
11
|
+
}
|
|
12
|
+
/** Nonissuance operations retain their existing API limits. */
|
|
3
13
|
export function requireIdempotencyKey(key) {
|
|
4
14
|
if (key === undefined || key.trim() === "") {
|
|
5
15
|
throw new CarrierError(400, "invalid_idempotency_key", "Idempotency-Key is required.");
|
package/dist/operations.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Generic caller for every operation id in the public OpenAPI document.
|
|
3
3
|
*/
|
|
4
4
|
import { CarrierError } from "./error.js";
|
|
5
|
-
import { requireIdempotencyKey, requirePathId } from "./idempotency.js";
|
|
5
|
+
import { isIssuance, requireIssuanceKey, requireIdempotencyKey, requirePathId } from "./idempotency.js";
|
|
6
6
|
import { HEADER_FIELDS, operation } from "./catalog.js";
|
|
7
7
|
const IDEMPOTENCY_HEADERS = new Set(["Idempotency-Key", "X-Idempotency-Key"]);
|
|
8
8
|
export function prepareCall(operationId, params = {}) {
|
|
@@ -18,7 +18,7 @@ export function prepareCall(operationId, params = {}) {
|
|
|
18
18
|
requireHeaders(headerValues, spec);
|
|
19
19
|
for (const name of Object.keys(headerValues)) {
|
|
20
20
|
if (IDEMPOTENCY_HEADERS.has(name)) {
|
|
21
|
-
headerValues[name] = requireIdempotencyKey(headerValues[name]);
|
|
21
|
+
headerValues[name] = isIssuance(spec.method, spec.path) ? requireIssuanceKey(headerValues[name]) : requireIdempotencyKey(headerValues[name]);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
if (spec.bodyRequired && params.body === undefined) {
|
|
@@ -57,7 +57,9 @@ export function inputSchema(spec) {
|
|
|
57
57
|
}
|
|
58
58
|
for (const param of spec.headerParams) {
|
|
59
59
|
const field = HEADER_FIELDS[param.name] ?? param.name;
|
|
60
|
-
properties[field] = { type: "string", description: param.name
|
|
60
|
+
properties[field] = { type: "string", description: param.name,
|
|
61
|
+
...(isIssuance(spec.method, spec.path) && IDEMPOTENCY_HEADERS.has(param.name) ? { minLength: 1, maxLength: 200, pattern: "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,199}$" } : {}),
|
|
62
|
+
};
|
|
61
63
|
if (param.required)
|
|
62
64
|
required.push(field);
|
|
63
65
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** Published version. Keep this equal to package.json. */
|
|
2
|
-
export declare const SDK_VERSION = "0.2.
|
|
3
|
-
export declare const USER_AGENT = "@carrierllc/sdk/0.2.
|
|
2
|
+
export declare const SDK_VERSION = "0.2.13";
|
|
3
|
+
export declare const USER_AGENT = "@carrierllc/sdk/0.2.13";
|
package/dist/version.js
CHANGED