@cronvello/shop-sdk 0.1.0 → 0.2.2
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/CONTRACT_SHA256 +1 -1
- package/README.md +34 -0
- package/dist/browser.cjs +115 -4
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +3 -3
- package/dist/browser.d.ts +3 -3
- package/dist/browser.js +114 -5
- package/dist/browser.js.map +1 -1
- package/dist/contract.cjs +15 -2
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +1 -1
- package/dist/contract.d.ts +1 -1
- package/dist/contract.js +15 -2
- package/dist/contract.js.map +1 -1
- package/dist/{error-C-cJ6OgX.d.cts → error-1WqH-FhJ.d.cts} +55 -2
- package/dist/{error-BFzxyQkW.d.ts → error-D3Jrcd-j.d.ts} +55 -2
- package/dist/{index-CIpUb0vL.d.cts → index-DC6jGVkP.d.cts} +85 -5
- package/dist/{index-CIpUb0vL.d.ts → index-DC6jGVkP.d.ts} +85 -5
- package/dist/index.cjs +115 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +114 -5
- package/dist/index.js.map +1 -1
- package/dist/openapi.json +193 -3
- package/dist/server.cjs +115 -4
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/server.js +114 -5
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/CONTRACT_SHA256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
05f34929e71fb2fcb3a4c04707facca22ee46f8960a4cc47ce0a3f1a6df36056
|
package/README.md
CHANGED
|
@@ -107,6 +107,40 @@ try {
|
|
|
107
107
|
}
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
## Is the contract still the right one?
|
|
111
|
+
|
|
112
|
+
Pinning a version fixes *which* contract you compiled against. It does not tell you whether the
|
|
113
|
+
node-shop you actually call still speaks it — and nothing else will: your types are green, your
|
|
114
|
+
build is green, and the first thing that fails is a real request in production.
|
|
115
|
+
|
|
116
|
+
`diagnoseShop()` closes that gap by asking the deployment itself:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { diagnoseShop } from "@cronvello/shop-sdk";
|
|
120
|
+
|
|
121
|
+
const report = await diagnoseShop({ client: shop }); // or { baseUrl }
|
|
122
|
+
|
|
123
|
+
report.ok; // true only when the match was VERIFIED
|
|
124
|
+
report.contract.status; // "match" | "drift" | "unknown"
|
|
125
|
+
report.contract.sdk; // hash this package was built against
|
|
126
|
+
report.contract.live; // hash the deployment reports
|
|
127
|
+
report.sdkVersion;
|
|
128
|
+
report.problems; // plain sentences, empty when ok
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`unknown` (offline, or a node-shop too old to serve `GET /health/contract`) is deliberately not
|
|
132
|
+
`ok`. A green light that means "could not check" is the exact failure this exists to remove.
|
|
133
|
+
|
|
134
|
+
It never throws for an operational reason — an unreachable shop is a result, not an exception — so
|
|
135
|
+
it is safe to put behind your own health endpoint and let a monitor pull it:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
export async function GET() {
|
|
139
|
+
const report = await diagnoseShop({ client: shop });
|
|
140
|
+
return Response.json(report, { status: report.ok ? 200 : 503 });
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
110
144
|
## Updating the contract
|
|
111
145
|
|
|
112
146
|
node-shop remains the producer. From this repository, after regenerating node-shop's contract:
|
package/dist/browser.cjs
CHANGED
|
@@ -498,7 +498,7 @@ var apiRoutes_b2b = {
|
|
|
498
498
|
meta: {
|
|
499
499
|
tags: ["b2b"],
|
|
500
500
|
summary: "Create B2B dynamic checkout session",
|
|
501
|
-
description: "Creates a Stripe checkout session with dynamic price_data for trusted external apps. Supports mixed carts (one-time + recurring). Auth: Bearer BACKEND_TO_BACKEND_API_KEY.",
|
|
501
|
+
description: "Creates a Stripe checkout session with dynamic price_data for trusted external apps. Supports mixed carts (one-time + recurring). Auth: Bearer BACKEND_TO_BACKEND_API_KEY or App-Scoped API Key.",
|
|
502
502
|
bodyContentType: "application/json",
|
|
503
503
|
validated: { "params": false, "query": false, "body": true }
|
|
504
504
|
},
|
|
@@ -1710,6 +1710,19 @@ var apiRoutes_external_apps = {
|
|
|
1710
1710
|
},
|
|
1711
1711
|
types: null
|
|
1712
1712
|
},
|
|
1713
|
+
"external_apps_service_rotate_key": {
|
|
1714
|
+
method: "POST",
|
|
1715
|
+
path: "/external-apps/service/rotate-key/:appId",
|
|
1716
|
+
auth: { "type": "x_api_key_https" },
|
|
1717
|
+
meta: {
|
|
1718
|
+
tags: ["external-apps"],
|
|
1719
|
+
summary: "Rotate external app api key (B2B zero-downtime)",
|
|
1720
|
+
description: "Rotates the API key of an external app. The previous key remains valid during the specified gracePeriodHours (default: 2h) to prevent downtime during deployments. Authenticated via backend-to-backend API key.",
|
|
1721
|
+
bodyContentType: "application/json",
|
|
1722
|
+
validated: { "params": true, "query": true, "body": true }
|
|
1723
|
+
},
|
|
1724
|
+
types: null
|
|
1725
|
+
},
|
|
1713
1726
|
"external_apps_service_delete": {
|
|
1714
1727
|
method: "DELETE",
|
|
1715
1728
|
path: "/external-apps/service/delete/:appId",
|
|
@@ -1729,7 +1742,7 @@ var apiRoutes_external_apps = {
|
|
|
1729
1742
|
meta: {
|
|
1730
1743
|
tags: ["external-apps"],
|
|
1731
1744
|
summary: "Get external app registration status (B2B)",
|
|
1732
|
-
description: "Returns registration status for an app identified by its string appId. Used by AMP to discover existing connections."
|
|
1745
|
+
description: "Returns registration status for an app identified by its string appId. Used by AMP to discover existing connections. Includes authMethod and, for OAuth apps, the oauth client_id we will present to that app plus a truncated SHA-256 fingerprint of the stored client secret, so the other side can prove a rotation actually landed here. Never the secret itself. Also reports the entitlement paths we have stored, because registration is an upsert: a caller that only wants to hand us new credentials must send these back unchanged, or it silently repoints where we fetch entitlements from."
|
|
1733
1746
|
},
|
|
1734
1747
|
types: null
|
|
1735
1748
|
},
|
|
@@ -5272,7 +5285,7 @@ function createShopClient(options = {}) {
|
|
|
5272
5285
|
const defaultTimeoutMs = options.timeoutMs ?? 3e4;
|
|
5273
5286
|
const defaultRetries = options.retries ?? 2;
|
|
5274
5287
|
const retryDelayMs = options.retryDelayMs ?? 250;
|
|
5275
|
-
async function
|
|
5288
|
+
async function resolveBaseUrl2() {
|
|
5276
5289
|
const value = typeof baseUrl === "function" ? await baseUrl() : baseUrl;
|
|
5277
5290
|
return normalizeBaseUrl(value);
|
|
5278
5291
|
}
|
|
@@ -5294,7 +5307,7 @@ function createShopClient(options = {}) {
|
|
|
5294
5307
|
const body = hasBody ? JSON.stringify(values.body) : void 0;
|
|
5295
5308
|
const retries = Math.max(0, requestOptions.retries ?? defaultRetries);
|
|
5296
5309
|
const canRetry = SAFE_METHODS.has(route.method) || requestOptions.retryUnsafe === true;
|
|
5297
|
-
const resolvedBaseUrl = await
|
|
5310
|
+
const resolvedBaseUrl = await resolveBaseUrl2();
|
|
5298
5311
|
for (let attempt = 0; ; attempt += 1) {
|
|
5299
5312
|
const timeout = createRequestSignal(requestOptions.signal, requestOptions.timeoutMs ?? defaultTimeoutMs);
|
|
5300
5313
|
try {
|
|
@@ -5371,6 +5384,102 @@ function createShopClient(options = {}) {
|
|
|
5371
5384
|
return { baseUrl, request, requestText, requestRaw };
|
|
5372
5385
|
}
|
|
5373
5386
|
|
|
5387
|
+
// src/contract-hash.ts
|
|
5388
|
+
var CONTRACT_SHA256 = "05f34929e71fb2fcb3a4c04707facca22ee46f8960a4cc47ce0a3f1a6df36056";
|
|
5389
|
+
|
|
5390
|
+
// src/client/diagnose.ts
|
|
5391
|
+
var SDK_VERSION = "0.2.2" ;
|
|
5392
|
+
async function resolveBaseUrl(options) {
|
|
5393
|
+
const provider = options.client?.baseUrl ?? options.baseUrl ?? SHOP_API_URL;
|
|
5394
|
+
const value = typeof provider === "function" ? await provider() : provider;
|
|
5395
|
+
return normalizeBaseUrl(value);
|
|
5396
|
+
}
|
|
5397
|
+
async function diagnoseShop(options = {}) {
|
|
5398
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5399
|
+
const baseUrl = await resolveBaseUrl(options);
|
|
5400
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
5401
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
5402
|
+
const base = {
|
|
5403
|
+
ok: false,
|
|
5404
|
+
baseUrl,
|
|
5405
|
+
sdkVersion: SDK_VERSION,
|
|
5406
|
+
reachable: false,
|
|
5407
|
+
contract: { status: "unknown", sdk: CONTRACT_SHA256, live: null, files: null },
|
|
5408
|
+
problems: [],
|
|
5409
|
+
checkedAt
|
|
5410
|
+
};
|
|
5411
|
+
if (typeof doFetch !== "function") {
|
|
5412
|
+
return { ...base, problems: ["No fetch implementation available; pass `fetch` explicitly."] };
|
|
5413
|
+
}
|
|
5414
|
+
const controller = new AbortController();
|
|
5415
|
+
const onAbort = () => controller.abort(options.signal?.reason);
|
|
5416
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
5417
|
+
if (options.signal?.aborted) controller.abort(options.signal.reason);
|
|
5418
|
+
const timer = setTimeout(() => controller.abort(new Error(`Contract probe timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
5419
|
+
let response;
|
|
5420
|
+
try {
|
|
5421
|
+
response = await doFetch(`${baseUrl}/health/contract`, {
|
|
5422
|
+
method: "GET",
|
|
5423
|
+
headers: { accept: "application/json" },
|
|
5424
|
+
signal: controller.signal
|
|
5425
|
+
});
|
|
5426
|
+
} catch (error) {
|
|
5427
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
5428
|
+
return { ...base, problems: [`node-shop at ${baseUrl} could not be reached: ${detail}`] };
|
|
5429
|
+
} finally {
|
|
5430
|
+
clearTimeout(timer);
|
|
5431
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
5432
|
+
}
|
|
5433
|
+
if (response.status === 404) {
|
|
5434
|
+
return {
|
|
5435
|
+
...base,
|
|
5436
|
+
reachable: true,
|
|
5437
|
+
problems: [
|
|
5438
|
+
`node-shop at ${baseUrl} does not serve GET /health/contract, so the contract cannot be verified. Deploy a node-shop that includes it.`
|
|
5439
|
+
]
|
|
5440
|
+
};
|
|
5441
|
+
}
|
|
5442
|
+
if (!response.ok) {
|
|
5443
|
+
return {
|
|
5444
|
+
...base,
|
|
5445
|
+
reachable: true,
|
|
5446
|
+
problems: [`node-shop at ${baseUrl} answered ${response.status} for GET /health/contract; contract not verified.`]
|
|
5447
|
+
};
|
|
5448
|
+
}
|
|
5449
|
+
let body;
|
|
5450
|
+
try {
|
|
5451
|
+
body = await response.json();
|
|
5452
|
+
} catch {
|
|
5453
|
+
return { ...base, reachable: true, problems: [`node-shop at ${baseUrl} returned a non-JSON contract fingerprint.`] };
|
|
5454
|
+
}
|
|
5455
|
+
const live = typeof body.contractSha256 === "string" && body.contractSha256 ? body.contractSha256 : null;
|
|
5456
|
+
const files = typeof body.files === "number" ? body.files : null;
|
|
5457
|
+
if (!live) {
|
|
5458
|
+
return {
|
|
5459
|
+
...base,
|
|
5460
|
+
reachable: true,
|
|
5461
|
+
contract: { ...base.contract, files },
|
|
5462
|
+
problems: [`node-shop at ${baseUrl} could not compute its own contract fingerprint; contract not verified.`]
|
|
5463
|
+
};
|
|
5464
|
+
}
|
|
5465
|
+
if (live !== CONTRACT_SHA256) {
|
|
5466
|
+
return {
|
|
5467
|
+
...base,
|
|
5468
|
+
reachable: true,
|
|
5469
|
+
contract: { status: "drift", sdk: CONTRACT_SHA256, live, files },
|
|
5470
|
+
problems: [
|
|
5471
|
+
`Contract drift: node-shop at ${baseUrl} speaks ${live.slice(0, 12)}, @cronvello/shop-sdk ${SDK_VERSION} was built against ${CONTRACT_SHA256.slice(0, 12)}. Pin a newer SDK version, or deploy the node-shop the SDK was built from.`
|
|
5472
|
+
]
|
|
5473
|
+
};
|
|
5474
|
+
}
|
|
5475
|
+
return {
|
|
5476
|
+
...base,
|
|
5477
|
+
ok: true,
|
|
5478
|
+
reachable: true,
|
|
5479
|
+
contract: { status: "match", sdk: CONTRACT_SHA256, live, files }
|
|
5480
|
+
};
|
|
5481
|
+
}
|
|
5482
|
+
|
|
5374
5483
|
// src/browser.ts
|
|
5375
5484
|
function createShopBrowserClient(options = {}) {
|
|
5376
5485
|
const configured = options.headers;
|
|
@@ -5387,9 +5496,11 @@ function createShopBrowserClient(options = {}) {
|
|
|
5387
5496
|
});
|
|
5388
5497
|
}
|
|
5389
5498
|
|
|
5499
|
+
exports.CONTRACT_SHA256 = CONTRACT_SHA256;
|
|
5390
5500
|
exports.SHOP_API_URL = SHOP_API_URL;
|
|
5391
5501
|
exports.ShopApiError = ShopApiError;
|
|
5392
5502
|
exports.ShopConfigurationError = ShopConfigurationError;
|
|
5393
5503
|
exports.createShopBrowserClient = createShopBrowserClient;
|
|
5504
|
+
exports.diagnoseShop = diagnoseShop;
|
|
5394
5505
|
//# sourceMappingURL=browser.cjs.map
|
|
5395
5506
|
//# sourceMappingURL=browser.cjs.map
|