@stableops/api-sdk 0.1.0

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/dist/index.mjs ADDED
@@ -0,0 +1,453 @@
1
+ // src/retry.ts
2
+ function isRetryableStatus(status) {
3
+ return status === 429 || status >= 500;
4
+ }
5
+ function isRetryableError(err) {
6
+ if (!(err instanceof Error)) return false;
7
+ return err.name === "AbortError" || err.name === "TypeError";
8
+ }
9
+ function computeDelayMs(attempt, opts, random, retryAfterMs) {
10
+ if (retryAfterMs !== void 0) return retryAfterMs;
11
+ const base = Math.min(opts.maxDelayMs, opts.baseDelayMs * 2 ** attempt);
12
+ return random() * base;
13
+ }
14
+ function parseRetryAfterMs(header) {
15
+ if (header === null) return void 0;
16
+ const trimmed = header.trim();
17
+ if (trimmed.length === 0) return void 0;
18
+ const seconds = Number(trimmed);
19
+ if (!Number.isFinite(seconds) || seconds < 0) return void 0;
20
+ return seconds * 1e3;
21
+ }
22
+
23
+ // src/http.ts
24
+ var ORG_HEADER = "x-stableops-org";
25
+ var ENV_HEADER = "x-stableops-env";
26
+ var IDEMPOTENCY_HEADER = "idempotency-key";
27
+ var DEFAULT_BASE_URL = "https://api.stableops.dev";
28
+ var DEFAULT_TIMEOUT_MS = 3e4;
29
+ var DEFAULT_MAX_RETRIES = 2;
30
+ var DEFAULT_BASE_DELAY_MS = 200;
31
+ var DEFAULT_MAX_DELAY_MS = 5e3;
32
+ var StableOpsError = class extends Error {
33
+ status;
34
+ code;
35
+ details;
36
+ constructor(status, code, message, details) {
37
+ super(message);
38
+ this.name = "StableOpsError";
39
+ this.status = status;
40
+ this.code = code;
41
+ this.details = details;
42
+ }
43
+ };
44
+ function defaultSleep(ms) {
45
+ return new Promise((resolve) => setTimeout(resolve, ms));
46
+ }
47
+ var HttpClient = class {
48
+ baseUrl;
49
+ headers;
50
+ fetchImpl;
51
+ timeoutMs;
52
+ maxRetries;
53
+ backoff;
54
+ sleep;
55
+ random;
56
+ constructor(options) {
57
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/u, "");
58
+ this.fetchImpl = options.fetch ?? fetch.bind(globalThis);
59
+ this.headers = {
60
+ "content-type": "application/json",
61
+ accept: "application/json",
62
+ [ORG_HEADER]: options.organizationSlug ?? "demo",
63
+ [ENV_HEADER]: options.environment ?? "sandbox"
64
+ };
65
+ if (options.apiKey) {
66
+ this.headers.authorization = `Bearer ${options.apiKey}`;
67
+ }
68
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
69
+ this.maxRetries = options.retry?.maxRetries ?? DEFAULT_MAX_RETRIES;
70
+ this.backoff = {
71
+ baseDelayMs: options.retry?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS,
72
+ maxDelayMs: options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS
73
+ };
74
+ this.sleep = options._sleep ?? defaultSleep;
75
+ this.random = options._random ?? Math.random;
76
+ }
77
+ async request(init) {
78
+ const url = new URL(`${this.baseUrl}${init.path}`);
79
+ if (init.query) {
80
+ for (const [k, v] of Object.entries(init.query)) {
81
+ if (v === void 0) continue;
82
+ url.searchParams.set(k, String(v));
83
+ }
84
+ }
85
+ const headers = { ...this.headers };
86
+ const idempotencyKey = init.idempotencyKey ?? (init.method === "GET" ? void 0 : globalThis.crypto.randomUUID());
87
+ if (idempotencyKey) headers[IDEMPOTENCY_HEADER] = idempotencyKey;
88
+ const body = init.body === void 0 ? void 0 : JSON.stringify(init.body);
89
+ const canRetry = init.method === "GET" || init.retryable === true;
90
+ for (let attempt = 0; ; attempt++) {
91
+ const controller = new AbortController();
92
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
93
+ let retryDelayMs;
94
+ try {
95
+ const res = await this.fetchImpl(url.toString(), {
96
+ method: init.method,
97
+ headers,
98
+ body,
99
+ signal: controller.signal
100
+ });
101
+ const text = await res.text();
102
+ const parsed = text.length === 0 ? null : safeJsonParse(text);
103
+ if (res.ok) return parsed;
104
+ if (canRetry && isRetryableStatus(res.status) && attempt < this.maxRetries) {
105
+ const retryAfterMs = parseRetryAfterMs(res.headers.get("retry-after"));
106
+ retryDelayMs = computeDelayMs(attempt, this.backoff, this.random, retryAfterMs);
107
+ } else {
108
+ const errBody = parsed;
109
+ throw new StableOpsError(
110
+ res.status,
111
+ errBody?.code ?? `http_${res.status}`,
112
+ errBody?.message ?? res.statusText,
113
+ errBody
114
+ );
115
+ }
116
+ } catch (err) {
117
+ if (err instanceof StableOpsError) throw err;
118
+ if (canRetry && isRetryableError(err) && attempt < this.maxRetries) {
119
+ retryDelayMs = computeDelayMs(attempt, this.backoff, this.random);
120
+ } else {
121
+ const isTimeout = err instanceof Error && err.name === "AbortError";
122
+ throw new StableOpsError(
123
+ 0,
124
+ isTimeout ? "timeout" : "network_error",
125
+ err instanceof Error ? err.message : String(err),
126
+ err
127
+ );
128
+ }
129
+ } finally {
130
+ clearTimeout(timer);
131
+ }
132
+ await this.sleep(retryDelayMs);
133
+ }
134
+ }
135
+ };
136
+ function safeJsonParse(text) {
137
+ try {
138
+ return JSON.parse(text);
139
+ } catch {
140
+ return text;
141
+ }
142
+ }
143
+
144
+ // src/payment-orders.ts
145
+ var PaymentOrdersApi = class {
146
+ constructor(http) {
147
+ this.http = http;
148
+ }
149
+ http;
150
+ async create(input, options) {
151
+ const wire = await this.http.request({
152
+ method: "POST",
153
+ path: "/v1/payment-orders",
154
+ idempotencyKey: options.idempotencyKey,
155
+ retryable: true,
156
+ body: toCreateWire(input)
157
+ });
158
+ return fromWire(wire);
159
+ }
160
+ async retrieve(id) {
161
+ const wire = await this.http.request({
162
+ method: "GET",
163
+ path: `/v1/payment-orders/${encodeURIComponent(id)}`
164
+ });
165
+ return fromWireDetail(wire);
166
+ }
167
+ async list(params = {}) {
168
+ const wire = await this.http.request({
169
+ method: "GET",
170
+ path: "/v1/payment-orders",
171
+ query: { status: params.status, limit: params.limit }
172
+ });
173
+ return wire.items.map(fromWire);
174
+ }
175
+ async cancel(id) {
176
+ const wire = await this.http.request({
177
+ method: "POST",
178
+ path: `/v1/payment-orders/${encodeURIComponent(id)}/cancel`
179
+ });
180
+ return fromWire(wire);
181
+ }
182
+ };
183
+ var EventsApi = class {
184
+ constructor(http) {
185
+ this.http = http;
186
+ }
187
+ http;
188
+ async list(params = {}) {
189
+ const wire = await this.http.request({
190
+ method: "GET",
191
+ path: "/v1/events",
192
+ query: {
193
+ chain: params.chain,
194
+ asset: params.asset,
195
+ payment_order_id: params.paymentOrderId,
196
+ limit: params.limit,
197
+ to_address: params.toAddress,
198
+ tx_hash: params.txHash
199
+ }
200
+ });
201
+ return wire.items.map(fromWireEvent);
202
+ }
203
+ async retrieve(id) {
204
+ const wire = await this.http.request({
205
+ method: "GET",
206
+ path: `/v1/events/${encodeURIComponent(id)}`
207
+ });
208
+ return fromWireEventDetail(wire);
209
+ }
210
+ };
211
+ function toCreateWire(input) {
212
+ return {
213
+ merchant_order_id: input.merchantOrderId,
214
+ scenario: input.scenario,
215
+ amount: input.amount,
216
+ amount_mode: input.amountMode,
217
+ settlement_asset: input.settlementAsset,
218
+ accepted_assets: input.acceptedAssets.map((entry) => ({
219
+ chain: entry.chain,
220
+ asset: entry.asset
221
+ })),
222
+ expires_at: input.expiresAt,
223
+ metadata: input.metadata
224
+ };
225
+ }
226
+ function fromWire(wire) {
227
+ return {
228
+ id: wire.id,
229
+ merchantOrderId: wire.merchant_order_id,
230
+ scenario: wire.scenario,
231
+ amount: wire.amount,
232
+ requestedAmount: wire.requested_amount,
233
+ settlementAsset: wire.settlement_asset,
234
+ status: wire.status,
235
+ expiresAt: wire.expires_at,
236
+ metadata: wire.metadata,
237
+ createdAt: wire.created_at,
238
+ acceptedAssets: wire.accepted_assets?.map((entry) => ({
239
+ chain: entry.chain,
240
+ asset: entry.asset
241
+ })),
242
+ paymentInstructions: wire.payment_instructions.map((instruction) => ({
243
+ chain: instruction.chain,
244
+ asset: instruction.asset,
245
+ address: instruction.address
246
+ }))
247
+ };
248
+ }
249
+ function fromWireDetail(wire) {
250
+ return {
251
+ ...fromWire(wire),
252
+ timeline: wire.timeline.map((entry) => ({
253
+ from: entry.from,
254
+ to: entry.to,
255
+ reason: entry.reason,
256
+ at: entry.at
257
+ }))
258
+ };
259
+ }
260
+ function fromWireEvent(wire) {
261
+ return {
262
+ id: wire.id,
263
+ chain: wire.chain,
264
+ asset: wire.asset,
265
+ fromAddress: wire.from_address,
266
+ toAddress: wire.to_address,
267
+ amount: wire.amount,
268
+ txHash: wire.tx_hash,
269
+ logIndex: wire.log_index,
270
+ blockNumber: wire.block_number,
271
+ paymentOrderId: wire.payment_order_id,
272
+ confirmations: wire.confirmations,
273
+ detectedAt: wire.detected_at
274
+ };
275
+ }
276
+ function fromWireEventDetail(wire) {
277
+ return {
278
+ ...fromWireEvent(wire),
279
+ rawChainEvent: {
280
+ id: wire.raw_chain_event.id,
281
+ source: wire.raw_chain_event.source,
282
+ blockHash: wire.raw_chain_event.block_hash,
283
+ receivedAt: wire.raw_chain_event.received_at,
284
+ payload: wire.raw_chain_event.payload
285
+ },
286
+ paymentOrder: wire.payment_order ? {
287
+ id: wire.payment_order.id,
288
+ merchantOrderId: wire.payment_order.merchant_order_id,
289
+ status: wire.payment_order.status,
290
+ settlementAsset: wire.payment_order.settlement_asset,
291
+ amount: wire.payment_order.amount
292
+ } : null,
293
+ deliveries: wire.deliveries.map((delivery) => ({
294
+ id: delivery.id,
295
+ webhookEndpointId: delivery.webhook_endpoint_id,
296
+ eventType: delivery.event_type,
297
+ status: delivery.status,
298
+ attempts: delivery.attempts,
299
+ responseStatus: delivery.response_status,
300
+ errorMessage: delivery.error_message,
301
+ lastAttemptAt: delivery.last_attempt_at,
302
+ createdAt: delivery.created_at
303
+ }))
304
+ };
305
+ }
306
+
307
+ // src/webhooks.ts
308
+ var WebhookEndpointsApi = class {
309
+ constructor(http) {
310
+ this.http = http;
311
+ }
312
+ http;
313
+ async create(input) {
314
+ const wire = await this.http.request({
315
+ method: "POST",
316
+ path: "/v1/webhook-endpoints",
317
+ body: {
318
+ url: input.url,
319
+ description: input.description,
320
+ enabled_events: input.enabledEvents,
321
+ redact_metadata: input.redactMetadata
322
+ }
323
+ });
324
+ return fromWire2(wire);
325
+ }
326
+ async list() {
327
+ const wire = await this.http.request({
328
+ method: "GET",
329
+ path: "/v1/webhook-endpoints"
330
+ });
331
+ return wire.items.map(fromWire2);
332
+ }
333
+ async update(endpointId, input) {
334
+ const wire = await this.http.request({
335
+ method: "PATCH",
336
+ path: `/v1/webhook-endpoints/${encodeURIComponent(endpointId)}`,
337
+ body: {
338
+ description: input.description,
339
+ enabled_events: input.enabledEvents,
340
+ redact_metadata: input.redactMetadata
341
+ }
342
+ });
343
+ return fromWire2(wire);
344
+ }
345
+ async rotateSecret(endpointId) {
346
+ const wire = await this.http.request({
347
+ method: "POST",
348
+ path: `/v1/webhook-endpoints/${encodeURIComponent(endpointId)}/rotate-secret`
349
+ });
350
+ return fromWire2(wire);
351
+ }
352
+ async replay(endpointId, eventId) {
353
+ return this.http.request({
354
+ method: "POST",
355
+ path: `/v1/webhook-endpoints/${encodeURIComponent(endpointId)}/replay`,
356
+ body: { event_id: eventId }
357
+ });
358
+ }
359
+ };
360
+ var WebhookDeliveriesApi = class {
361
+ constructor(http) {
362
+ this.http = http;
363
+ }
364
+ http;
365
+ async list(params = {}) {
366
+ const wire = await this.http.request({
367
+ method: "GET",
368
+ path: "/v1/webhook-deliveries",
369
+ query: {
370
+ status: params.status,
371
+ endpoint_id: params.endpointId,
372
+ payment_order_id: params.paymentOrderId,
373
+ limit: params.limit
374
+ }
375
+ });
376
+ return wire.items.map(fromWireDelivery);
377
+ }
378
+ async replay(deliveryId) {
379
+ return this.http.request({
380
+ method: "POST",
381
+ path: `/v1/webhook-deliveries/${encodeURIComponent(deliveryId)}/replay`
382
+ });
383
+ }
384
+ async replayDeadLetters(input = {}) {
385
+ const wire = await this.http.request({
386
+ method: "POST",
387
+ path: "/v1/webhook-deliveries/replay-dead-letters",
388
+ body: {
389
+ endpoint_id: input.endpointId,
390
+ limit: input.limit
391
+ }
392
+ });
393
+ return {
394
+ replayed: wire.replayed,
395
+ items: wire.items.map((item) => ({
396
+ originalId: item.original_id,
397
+ deliveryId: item.delivery_id
398
+ }))
399
+ };
400
+ }
401
+ };
402
+ function fromWire2(wire) {
403
+ const endpoint = {
404
+ id: wire.id,
405
+ url: wire.url,
406
+ description: wire.description,
407
+ enabledEvents: wire.enabled_events,
408
+ redactMetadata: wire.redact_metadata,
409
+ disabledAt: wire.disabled_at,
410
+ createdAt: wire.created_at
411
+ };
412
+ if (wire.secret !== void 0) endpoint.secret = wire.secret;
413
+ return endpoint;
414
+ }
415
+ function fromWireDelivery(wire) {
416
+ return {
417
+ id: wire.id,
418
+ webhookEndpointId: wire.webhook_endpoint_id,
419
+ eventId: wire.event_id,
420
+ eventType: wire.event_type,
421
+ paymentOrderId: wire.payment_order_id,
422
+ status: wire.status,
423
+ attempts: wire.attempts,
424
+ responseStatus: wire.response_status,
425
+ responseDurationMs: wire.response_duration_ms,
426
+ errorMessage: wire.error_message,
427
+ nextRetryAt: wire.next_retry_at,
428
+ lastAttemptAt: wire.last_attempt_at,
429
+ succeededAt: wire.succeeded_at,
430
+ deadLetteredAt: wire.dead_lettered_at,
431
+ createdAt: wire.created_at
432
+ };
433
+ }
434
+
435
+ // src/index.ts
436
+ var StableOps = class {
437
+ paymentOrders;
438
+ events;
439
+ webhookEndpoints;
440
+ webhookDeliveries;
441
+ constructor(options = {}) {
442
+ const http = new HttpClient(options);
443
+ this.paymentOrders = new PaymentOrdersApi(http);
444
+ this.events = new EventsApi(http);
445
+ this.webhookEndpoints = new WebhookEndpointsApi(http);
446
+ this.webhookDeliveries = new WebhookDeliveriesApi(http);
447
+ }
448
+ };
449
+ export {
450
+ StableOps,
451
+ StableOpsError
452
+ };
453
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/retry.ts","../src/http.ts","../src/payment-orders.ts","../src/webhooks.ts","../src/index.ts"],"sourcesContent":["// SDK 网络弹性的纯决策逻辑:与计时 / 网络解耦,可单独测试且测试不真等。\n// http.ts 的重试循环把这里的判定与延迟计算组合起来;本文件不做任何 I/O。\n\n// 可重试状态:429(限流)或任意 5xx(服务端临时故障)。\n// 4xx(含 401/409/400)是确定性失败,重试无意义,立即抛出。\nexport function isRetryableStatus(status: number): boolean {\n return status === 429 || status >= 500\n}\n\n// 可重试错误:网络层失败(fetch reject,跨运行时通常是 TypeError)\n// 或本次尝试超时(AbortController abort → AbortError)。\n// 已包装成 StableOpsError 的业务错误不归此类(由 http.ts 先行拦截原样抛出)。\nexport function isRetryableError(err: unknown): boolean {\n if (!(err instanceof Error)) return false\n return err.name === 'AbortError' || err.name === 'TypeError'\n}\n\nexport type BackoffOptions = {\n baseDelayMs: number\n maxDelayMs: number\n}\n\n// 第 attempt 次重试(attempt 从 0 开始)的退避延迟(毫秒):\n// base = min(maxDelayMs, baseDelayMs * 2 ** attempt) // 指数退避,封顶 maxDelayMs\n// delay = retryAfterMs ?? random() * base // full jitter,避免重试惊群\n// 存在 Retry-After 时优先遵守服务端指示(maxDelayMs 只约束指数退避,不二次裁剪服务端指令)。\nexport function computeDelayMs(\n attempt: number,\n opts: BackoffOptions,\n random: () => number,\n retryAfterMs?: number,\n): number {\n if (retryAfterMs !== undefined) return retryAfterMs\n const base = Math.min(opts.maxDelayMs, opts.baseDelayMs * 2 ** attempt)\n return random() * base\n}\n\n// 解析 Retry-After 响应头。只认「秒数」形式:HTTP 也允许 HTTP-date,但跨运行时\n// 解析日期易错、且本 API 不会下发 date 形态,故非数字一律视为缺省返回 undefined。\nexport function parseRetryAfterMs(header: string | null): number | undefined {\n if (header === null) return undefined\n const trimmed = header.trim()\n if (trimmed.length === 0) return undefined\n const seconds = Number(trimmed)\n if (!Number.isFinite(seconds) || seconds < 0) return undefined\n return seconds * 1000\n}\n","// 与服务端约束保持一致:所有写请求允许带 Idempotency-Key。\n// 读请求不需要传,避免在错误的请求上消耗服务端的去重存储。\n\nimport {\n computeDelayMs,\n isRetryableError,\n isRetryableStatus,\n parseRetryAfterMs,\n type BackoffOptions,\n} from './retry'\n\nexport const ORG_HEADER = 'x-stableops-org'\nexport const ENV_HEADER = 'x-stableops-env'\nexport const IDEMPOTENCY_HEADER = 'idempotency-key'\n\nexport type StableOpsEnvironment = 'sandbox' | 'live'\n\nexport type RetryOptions = {\n maxRetries?: number // 默认 2(最多 3 次尝试)\n baseDelayMs?: number // 默认 200\n maxDelayMs?: number // 默认 5000\n}\n\nexport type ClientOptions = {\n apiKey?: string\n baseUrl?: string\n organizationSlug?: string\n environment?: StableOpsEnvironment\n // 注入自定义 fetch(测试 / Node18- 兼容 / 自托管 proxy)。\n fetch?: typeof fetch\n // 单一总超时(毫秒),默认 30000。逐次尝试独立计时(含每次重试);\n // 标准 fetch 不暴露 connect/read 阶段,故不区分两者,保证跨运行时可移植。\n timeoutMs?: number\n retry?: RetryOptions\n // 内部测试钩子:注入确定性 sleep / random,使重试测试秒级完成且不 flaky。\n _sleep?: (ms: number) => Promise<void>\n _random?: () => number\n}\n\nexport type RequestInit = {\n method: 'GET' | 'POST' | 'PATCH' | 'DELETE'\n path: string\n body?: unknown\n query?: Record<string, string | number | undefined>\n idempotencyKey?: string\n retryable?: boolean\n}\n\nconst DEFAULT_BASE_URL = 'https://api.stableops.dev'\nconst DEFAULT_TIMEOUT_MS = 30_000\nconst DEFAULT_MAX_RETRIES = 2\nconst DEFAULT_BASE_DELAY_MS = 200\nconst DEFAULT_MAX_DELAY_MS = 5_000\n\nexport class StableOpsError extends Error {\n readonly status: number\n readonly code: string\n readonly details: unknown\n\n constructor(status: number, code: string, message: string, details: unknown) {\n super(message)\n this.name = 'StableOpsError'\n this.status = status\n this.code = code\n this.details = details\n }\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nexport class HttpClient {\n private readonly baseUrl: string\n private readonly headers: Record<string, string>\n private readonly fetchImpl: typeof fetch\n private readonly timeoutMs: number\n private readonly maxRetries: number\n private readonly backoff: BackoffOptions\n private readonly sleep: (ms: number) => Promise<void>\n private readonly random: () => number\n\n constructor(options: ClientOptions) {\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/u, '')\n // 原生 fetch 必须以 window/globalThis 为 this 调用;存成实例字段后用 this.fetchImpl(...)\n // 调用会把 this 变成本类实例,浏览器报 \"Illegal invocation\"。回退到全局 fetch 时绑定 this。\n this.fetchImpl = options.fetch ?? fetch.bind(globalThis)\n this.headers = {\n 'content-type': 'application/json',\n accept: 'application/json',\n [ORG_HEADER]: options.organizationSlug ?? 'demo',\n [ENV_HEADER]: options.environment ?? 'sandbox',\n }\n if (options.apiKey) {\n this.headers.authorization = `Bearer ${options.apiKey}`\n }\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n this.maxRetries = options.retry?.maxRetries ?? DEFAULT_MAX_RETRIES\n this.backoff = {\n baseDelayMs: options.retry?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS,\n maxDelayMs: options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS,\n }\n this.sleep = options._sleep ?? defaultSleep\n this.random = options._random ?? Math.random\n }\n\n async request<T>(init: RequestInit): Promise<T> {\n const url = new URL(`${this.baseUrl}${init.path}`)\n if (init.query) {\n for (const [k, v] of Object.entries(init.query)) {\n if (v === undefined) continue\n url.searchParams.set(k, String(v))\n }\n }\n const headers: Record<string, string> = { ...this.headers }\n // 幂等键:调用方显式提供则用之;否则所有写请求(非 GET)在循环外只生成一次,\n // 并在全部重试中复用同一个键 —— 保证「服务端已成功、响应在途丢失」时重发不产生重复副作用。\n const idempotencyKey =\n init.idempotencyKey ?? (init.method === 'GET' ? undefined : globalThis.crypto.randomUUID())\n if (idempotencyKey) headers[IDEMPOTENCY_HEADER] = idempotencyKey\n\n const body = init.body === undefined ? undefined : JSON.stringify(init.body)\n const canRetry = init.method === 'GET' || init.retryable === true\n\n for (let attempt = 0; ; attempt++) {\n // 每次尝试独立的 AbortController + 计时器:超时表现为 AbortError,归入可重试错误。\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\n let retryDelayMs: number\n try {\n const res = await this.fetchImpl(url.toString(), {\n method: init.method,\n headers,\n body,\n signal: controller.signal,\n })\n const text = await res.text()\n const parsed = text.length === 0 ? null : safeJsonParse(text)\n if (res.ok) return parsed as T\n\n if (canRetry && isRetryableStatus(res.status) && attempt < this.maxRetries) {\n // 可重试状态且仍有额度:Retry-After 优先,否则指数退避 + jitter。\n const retryAfterMs = parseRetryAfterMs(res.headers.get('retry-after'))\n retryDelayMs = computeDelayMs(attempt, this.backoff, this.random, retryAfterMs)\n } else {\n // 4xx(含 401/409)或重试耗尽:抛业务错误,携带后端 body 供调用方读取。\n const errBody = parsed as Record<string, unknown> | null\n throw new StableOpsError(\n res.status,\n (errBody?.code as string) ?? `http_${res.status}`,\n (errBody?.message as string) ?? res.statusText,\n errBody,\n )\n }\n } catch (err) {\n // 已是业务错误(4xx / 已耗尽):原样抛出,不再重试。\n if (err instanceof StableOpsError) throw err\n if (canRetry && isRetryableError(err) && attempt < this.maxRetries) {\n // 网络错误 / 超时且仍有额度:纯指数退避(无 Retry-After 可循)。\n retryDelayMs = computeDelayMs(attempt, this.backoff, this.random)\n } else {\n // 不可重试或已耗尽:统一包装为 StableOpsError(status=0),调用方只需 catch 一种错误类型。\n const isTimeout = err instanceof Error && err.name === 'AbortError'\n throw new StableOpsError(\n 0,\n isTimeout ? 'timeout' : 'network_error',\n err instanceof Error ? err.message : String(err),\n err,\n )\n }\n } finally {\n clearTimeout(timer)\n }\n // 走到这里说明本次尝试已决定重试(上方未 return / throw):退避等待后进入下一轮。\n await this.sleep(retryDelayMs)\n }\n }\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text)\n } catch {\n return text\n }\n}\n","import type { HttpClient } from './http'\nimport type {\n AcceptedAssetInput,\n Asset,\n ChainId,\n CreatePaymentOrderInput,\n NormalizedEvent,\n NormalizedEventDetail,\n PaymentOrder,\n PaymentOrderDetail,\n PaymentOrderStatus,\n} from './types'\n\nexport class PaymentOrdersApi {\n constructor(private readonly http: HttpClient) {}\n\n async create(\n input: CreatePaymentOrderInput,\n options: { idempotencyKey: string },\n ): Promise<PaymentOrder> {\n const wire = await this.http.request<WirePaymentOrder>({\n method: 'POST',\n path: '/v1/payment-orders',\n idempotencyKey: options.idempotencyKey,\n retryable: true,\n body: toCreateWire(input),\n })\n return fromWire(wire)\n }\n\n async retrieve(id: string): Promise<PaymentOrderDetail> {\n const wire = await this.http.request<WirePaymentOrderDetail>({\n method: 'GET',\n path: `/v1/payment-orders/${encodeURIComponent(id)}`,\n })\n return fromWireDetail(wire)\n }\n\n async list(\n params: { status?: PaymentOrderStatus; limit?: number } = {},\n ): Promise<PaymentOrder[]> {\n const wire = await this.http.request<{ items: WirePaymentOrder[] }>({\n method: 'GET',\n path: '/v1/payment-orders',\n query: { status: params.status, limit: params.limit },\n })\n return wire.items.map(fromWire)\n }\n\n async cancel(id: string): Promise<PaymentOrder> {\n const wire = await this.http.request<WirePaymentOrder>({\n method: 'POST',\n path: `/v1/payment-orders/${encodeURIComponent(id)}/cancel`,\n })\n return fromWire(wire)\n }\n}\n\nexport class EventsApi {\n constructor(private readonly http: HttpClient) {}\n\n async list(\n params: {\n chain?: ChainId\n asset?: Asset\n paymentOrderId?: string\n limit?: number\n toAddress?: string\n txHash?: string\n } = {},\n ): Promise<NormalizedEvent[]> {\n const wire = await this.http.request<{ items: WireNormalizedEvent[] }>({\n method: 'GET',\n path: '/v1/events',\n query: {\n chain: params.chain,\n asset: params.asset,\n payment_order_id: params.paymentOrderId,\n limit: params.limit,\n to_address: params.toAddress,\n tx_hash: params.txHash,\n },\n })\n return wire.items.map(fromWireEvent)\n }\n\n async retrieve(id: string): Promise<NormalizedEventDetail> {\n const wire = await this.http.request<WireNormalizedEventDetail>({\n method: 'GET',\n path: `/v1/events/${encodeURIComponent(id)}`,\n })\n return fromWireEventDetail(wire)\n }\n}\n\n// ---- 内部 wire 类型,避免 SDK 公开蛇形命名 ----\n\ntype WirePaymentOrder = {\n id: string\n merchant_order_id: string\n scenario: string\n amount: string\n requested_amount: string\n settlement_asset: string\n status: string\n expires_at: string | null\n metadata: unknown\n created_at: string\n accepted_assets?: { chain: string; asset: string }[]\n payment_instructions: { chain: string; asset: string; address: string }[]\n}\n\ntype WirePaymentOrderDetail = WirePaymentOrder & {\n timeline: { from: string | null; to: string; reason: string | null; at: string }[]\n}\n\ntype WireNormalizedEvent = {\n id: string\n chain: string\n asset: string\n from_address: string\n to_address: string\n amount: string\n tx_hash: string\n log_index: number\n block_number: string\n payment_order_id: string | null\n confirmations: number\n detected_at: string\n}\n\ntype WireNormalizedEventDetail = WireNormalizedEvent & {\n raw_chain_event: {\n id: string\n source: string\n block_hash: string | null\n received_at: string\n payload: unknown\n }\n payment_order: {\n id: string\n merchant_order_id: string\n status: string\n settlement_asset: string\n amount: string\n } | null\n deliveries: {\n id: string\n webhook_endpoint_id: string\n event_type: string\n status: string\n attempts: number\n response_status: number | null\n error_message: string | null\n last_attempt_at: string | null\n created_at: string\n }[]\n}\n\nfunction toCreateWire(input: CreatePaymentOrderInput) {\n return {\n merchant_order_id: input.merchantOrderId,\n scenario: input.scenario,\n amount: input.amount,\n amount_mode: input.amountMode,\n settlement_asset: input.settlementAsset,\n accepted_assets: input.acceptedAssets.map((entry) => ({\n chain: entry.chain,\n asset: entry.asset,\n })),\n expires_at: input.expiresAt,\n metadata: input.metadata,\n }\n}\n\nfunction fromWire(wire: WirePaymentOrder): PaymentOrder {\n return {\n id: wire.id,\n merchantOrderId: wire.merchant_order_id,\n scenario: wire.scenario as PaymentOrder['scenario'],\n amount: wire.amount,\n requestedAmount: wire.requested_amount,\n settlementAsset: wire.settlement_asset as PaymentOrder['settlementAsset'],\n status: wire.status as PaymentOrder['status'],\n expiresAt: wire.expires_at,\n metadata: wire.metadata,\n createdAt: wire.created_at,\n acceptedAssets: wire.accepted_assets?.map((entry) => ({\n chain: entry.chain as AcceptedAssetInput['chain'],\n asset: entry.asset as AcceptedAssetInput['asset'],\n })),\n paymentInstructions: wire.payment_instructions.map((instruction) => ({\n chain: instruction.chain as PaymentOrderInstructionChain,\n asset: instruction.asset as PaymentOrderInstructionAsset,\n address: instruction.address,\n })),\n }\n}\n\ntype PaymentOrderInstructionChain = PaymentOrder['paymentInstructions'][number]['chain']\ntype PaymentOrderInstructionAsset = PaymentOrder['paymentInstructions'][number]['asset']\n\nfunction fromWireDetail(wire: WirePaymentOrderDetail): PaymentOrderDetail {\n return {\n ...fromWire(wire),\n timeline: wire.timeline.map((entry) => ({\n from: entry.from as PaymentOrderDetail['timeline'][number]['from'],\n to: entry.to as PaymentOrderDetail['timeline'][number]['to'],\n reason: entry.reason,\n at: entry.at,\n })),\n }\n}\n\nfunction fromWireEvent(wire: WireNormalizedEvent): NormalizedEvent {\n return {\n id: wire.id,\n chain: wire.chain as NormalizedEvent['chain'],\n asset: wire.asset as NormalizedEvent['asset'],\n fromAddress: wire.from_address,\n toAddress: wire.to_address,\n amount: wire.amount,\n txHash: wire.tx_hash,\n logIndex: wire.log_index,\n blockNumber: wire.block_number,\n paymentOrderId: wire.payment_order_id,\n confirmations: wire.confirmations,\n detectedAt: wire.detected_at,\n }\n}\n\nfunction fromWireEventDetail(wire: WireNormalizedEventDetail): NormalizedEventDetail {\n return {\n ...fromWireEvent(wire),\n rawChainEvent: {\n id: wire.raw_chain_event.id,\n source: wire.raw_chain_event.source,\n blockHash: wire.raw_chain_event.block_hash,\n receivedAt: wire.raw_chain_event.received_at,\n payload: wire.raw_chain_event.payload,\n },\n paymentOrder: wire.payment_order\n ? {\n id: wire.payment_order.id,\n merchantOrderId: wire.payment_order.merchant_order_id,\n status: wire.payment_order.status as PaymentOrderStatus,\n settlementAsset: wire.payment_order.settlement_asset as Asset,\n amount: wire.payment_order.amount,\n }\n : null,\n deliveries: wire.deliveries.map((delivery) => ({\n id: delivery.id,\n webhookEndpointId: delivery.webhook_endpoint_id,\n eventType: delivery.event_type as NormalizedEventDetail['deliveries'][number]['eventType'],\n status: delivery.status as NormalizedEventDetail['deliveries'][number]['status'],\n attempts: delivery.attempts,\n responseStatus: delivery.response_status,\n errorMessage: delivery.error_message,\n lastAttemptAt: delivery.last_attempt_at,\n createdAt: delivery.created_at,\n })),\n }\n}\n","import type { HttpClient } from './http'\nimport type {\n CreateWebhookEndpointInput,\n ReplayDeadLettersInput,\n ReplayDeadLettersResult,\n ReplayDeliveryResult,\n UpdateWebhookEndpointInput,\n WebhookDelivery,\n WebhookDeliveryStatus,\n WebhookEndpoint,\n WebhookEventType,\n} from './types'\n\nexport class WebhookEndpointsApi {\n constructor(private readonly http: HttpClient) {}\n\n async create(input: CreateWebhookEndpointInput): Promise<WebhookEndpoint> {\n const wire = await this.http.request<WireWebhookEndpoint>({\n method: 'POST',\n path: '/v1/webhook-endpoints',\n body: {\n url: input.url,\n description: input.description,\n enabled_events: input.enabledEvents,\n redact_metadata: input.redactMetadata,\n },\n })\n return fromWire(wire)\n }\n\n async list(): Promise<WebhookEndpoint[]> {\n const wire = await this.http.request<{ items: WireWebhookEndpoint[] }>({\n method: 'GET',\n path: '/v1/webhook-endpoints',\n })\n return wire.items.map(fromWire)\n }\n\n async update(\n endpointId: string,\n input: UpdateWebhookEndpointInput,\n ): Promise<WebhookEndpoint> {\n const wire = await this.http.request<WireWebhookEndpoint>({\n method: 'PATCH',\n path: `/v1/webhook-endpoints/${encodeURIComponent(endpointId)}`,\n body: {\n description: input.description,\n enabled_events: input.enabledEvents,\n redact_metadata: input.redactMetadata,\n },\n })\n return fromWire(wire)\n }\n\n async rotateSecret(endpointId: string): Promise<WebhookEndpoint> {\n const wire = await this.http.request<WireWebhookEndpoint>({\n method: 'POST',\n path: `/v1/webhook-endpoints/${encodeURIComponent(endpointId)}/rotate-secret`,\n })\n return fromWire(wire)\n }\n\n async replay(endpointId: string, eventId: string): Promise<ReplayDeliveryResult> {\n return this.http.request<ReplayDeliveryResult>({\n method: 'POST',\n path: `/v1/webhook-endpoints/${encodeURIComponent(endpointId)}/replay`,\n body: { event_id: eventId },\n })\n }\n}\n\nexport class WebhookDeliveriesApi {\n constructor(private readonly http: HttpClient) {}\n\n async list(\n params: {\n status?: WebhookDeliveryStatus\n endpointId?: string\n paymentOrderId?: string\n limit?: number\n } = {},\n ): Promise<WebhookDelivery[]> {\n const wire = await this.http.request<{ items: WireWebhookDelivery[] }>({\n method: 'GET',\n path: '/v1/webhook-deliveries',\n query: {\n status: params.status,\n endpoint_id: params.endpointId,\n payment_order_id: params.paymentOrderId,\n limit: params.limit,\n },\n })\n return wire.items.map(fromWireDelivery)\n }\n\n async replay(deliveryId: string): Promise<ReplayDeliveryResult> {\n return this.http.request<ReplayDeliveryResult>({\n method: 'POST',\n path: `/v1/webhook-deliveries/${encodeURIComponent(deliveryId)}/replay`,\n })\n }\n\n async replayDeadLetters(\n input: ReplayDeadLettersInput = {},\n ): Promise<ReplayDeadLettersResult> {\n const wire = await this.http.request<WireReplayDeadLettersResult>({\n method: 'POST',\n path: '/v1/webhook-deliveries/replay-dead-letters',\n body: {\n endpoint_id: input.endpointId,\n limit: input.limit,\n },\n })\n return {\n replayed: wire.replayed,\n items: wire.items.map((item) => ({\n originalId: item.original_id,\n deliveryId: item.delivery_id,\n })),\n }\n }\n}\n\ntype WireWebhookEndpoint = {\n id: string\n url: string\n description: string | null\n enabled_events: WebhookEventType[]\n redact_metadata: boolean\n disabled_at: string | null\n created_at: string\n secret?: string\n}\n\ntype WireWebhookDelivery = {\n id: string\n webhook_endpoint_id: string\n event_id: string\n event_type: WebhookEventType\n payment_order_id: string | null\n status: WebhookDeliveryStatus\n attempts: number\n response_status: number | null\n response_duration_ms: number | null\n error_message: string | null\n next_retry_at: string | null\n last_attempt_at: string | null\n succeeded_at: string | null\n dead_lettered_at: string | null\n created_at: string\n}\n\ntype WireReplayDeadLettersResult = {\n replayed: number\n items: { original_id: string; delivery_id: string }[]\n}\n\nfunction fromWire(wire: WireWebhookEndpoint): WebhookEndpoint {\n const endpoint: WebhookEndpoint = {\n id: wire.id,\n url: wire.url,\n description: wire.description,\n enabledEvents: wire.enabled_events,\n redactMetadata: wire.redact_metadata,\n disabledAt: wire.disabled_at,\n createdAt: wire.created_at,\n }\n if (wire.secret !== undefined) endpoint.secret = wire.secret\n return endpoint\n}\n\nfunction fromWireDelivery(wire: WireWebhookDelivery): WebhookDelivery {\n return {\n id: wire.id,\n webhookEndpointId: wire.webhook_endpoint_id,\n eventId: wire.event_id,\n eventType: wire.event_type,\n paymentOrderId: wire.payment_order_id,\n status: wire.status,\n attempts: wire.attempts,\n responseStatus: wire.response_status,\n responseDurationMs: wire.response_duration_ms,\n errorMessage: wire.error_message,\n nextRetryAt: wire.next_retry_at,\n lastAttemptAt: wire.last_attempt_at,\n succeededAt: wire.succeeded_at,\n deadLetteredAt: wire.dead_lettered_at,\n createdAt: wire.created_at,\n }\n}\n","import { HttpClient, type ClientOptions } from './http'\nimport { EventsApi, PaymentOrdersApi } from './payment-orders'\nimport {\n WebhookDeliveriesApi,\n WebhookEndpointsApi,\n} from './webhooks'\n\nexport * from './types'\nexport { StableOpsError, type ClientOptions, type RetryOptions } from './http'\n\nexport class StableOps {\n readonly paymentOrders: PaymentOrdersApi\n readonly events: EventsApi\n readonly webhookEndpoints: WebhookEndpointsApi\n readonly webhookDeliveries: WebhookDeliveriesApi\n\n constructor(options: ClientOptions = {}) {\n const http = new HttpClient(options)\n this.paymentOrders = new PaymentOrdersApi(http)\n this.events = new EventsApi(http)\n this.webhookEndpoints = new WebhookEndpointsApi(http)\n this.webhookDeliveries = new WebhookDeliveriesApi(http)\n }\n}\n"],"mappings":"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","names":["fromWire"]}
@@ -0,0 +1,74 @@
1
+ type MockServerOptions = {
2
+ port?: number;
3
+ host?: string;
4
+ idFactory?: () => string;
5
+ secretFactory?: () => string;
6
+ };
7
+ type PaymentOrder = {
8
+ id: string;
9
+ merchant_order_id: string;
10
+ scenario: string;
11
+ amount: string;
12
+ requested_amount: string;
13
+ settlement_asset: string;
14
+ status: 'created' | 'detected' | 'confirmed' | 'finalized' | 'reverted' | 'expired' | 'canceled';
15
+ expires_at: string | null;
16
+ metadata: unknown;
17
+ created_at: string;
18
+ accepted_assets: {
19
+ chain: string;
20
+ asset: string;
21
+ }[];
22
+ payment_instructions: {
23
+ chain: string;
24
+ asset: string;
25
+ address: string;
26
+ }[];
27
+ timeline: {
28
+ from: string | null;
29
+ to: string;
30
+ reason: string | null;
31
+ at: string;
32
+ }[];
33
+ };
34
+ type WebhookEndpoint = {
35
+ id: string;
36
+ url: string;
37
+ description: string | null;
38
+ enabled_events: string[];
39
+ redact_metadata: boolean;
40
+ disabled_at: string | null;
41
+ created_at: string;
42
+ secret: string;
43
+ };
44
+ declare class MockServer {
45
+ private readonly options;
46
+ private readonly server;
47
+ private readonly orders;
48
+ private readonly endpoints;
49
+ private readonly idempotency;
50
+ private readonly idFactory;
51
+ private readonly secretFactory;
52
+ constructor(options?: MockServerOptions);
53
+ listen(): Promise<{
54
+ url: string;
55
+ }>;
56
+ close(): Promise<void>;
57
+ snapshot(): {
58
+ orders: PaymentOrder[];
59
+ endpoints: WebhookEndpoint[];
60
+ };
61
+ private handle;
62
+ private createOrder;
63
+ private cancelOrder;
64
+ private createEndpoint;
65
+ private updateEndpoint;
66
+ private rotateEndpointSecret;
67
+ buildSignedFixture(endpointId: string, eventType: string, payload: unknown): {
68
+ header: string;
69
+ rawBody: string;
70
+ secret: string;
71
+ };
72
+ }
73
+
74
+ export { MockServer, type MockServerOptions };
package/dist/mock.d.ts ADDED
@@ -0,0 +1,74 @@
1
+ type MockServerOptions = {
2
+ port?: number;
3
+ host?: string;
4
+ idFactory?: () => string;
5
+ secretFactory?: () => string;
6
+ };
7
+ type PaymentOrder = {
8
+ id: string;
9
+ merchant_order_id: string;
10
+ scenario: string;
11
+ amount: string;
12
+ requested_amount: string;
13
+ settlement_asset: string;
14
+ status: 'created' | 'detected' | 'confirmed' | 'finalized' | 'reverted' | 'expired' | 'canceled';
15
+ expires_at: string | null;
16
+ metadata: unknown;
17
+ created_at: string;
18
+ accepted_assets: {
19
+ chain: string;
20
+ asset: string;
21
+ }[];
22
+ payment_instructions: {
23
+ chain: string;
24
+ asset: string;
25
+ address: string;
26
+ }[];
27
+ timeline: {
28
+ from: string | null;
29
+ to: string;
30
+ reason: string | null;
31
+ at: string;
32
+ }[];
33
+ };
34
+ type WebhookEndpoint = {
35
+ id: string;
36
+ url: string;
37
+ description: string | null;
38
+ enabled_events: string[];
39
+ redact_metadata: boolean;
40
+ disabled_at: string | null;
41
+ created_at: string;
42
+ secret: string;
43
+ };
44
+ declare class MockServer {
45
+ private readonly options;
46
+ private readonly server;
47
+ private readonly orders;
48
+ private readonly endpoints;
49
+ private readonly idempotency;
50
+ private readonly idFactory;
51
+ private readonly secretFactory;
52
+ constructor(options?: MockServerOptions);
53
+ listen(): Promise<{
54
+ url: string;
55
+ }>;
56
+ close(): Promise<void>;
57
+ snapshot(): {
58
+ orders: PaymentOrder[];
59
+ endpoints: WebhookEndpoint[];
60
+ };
61
+ private handle;
62
+ private createOrder;
63
+ private cancelOrder;
64
+ private createEndpoint;
65
+ private updateEndpoint;
66
+ private rotateEndpointSecret;
67
+ buildSignedFixture(endpointId: string, eventType: string, payload: unknown): {
68
+ header: string;
69
+ rawBody: string;
70
+ secret: string;
71
+ };
72
+ }
73
+
74
+ export { MockServer, type MockServerOptions };