@stewardhq/sdk 0.1.0-rc.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/README.md +437 -0
- package/dist/_chunks/errors.js +73 -0
- package/dist/_chunks/events.d.ts +537 -0
- package/dist/_chunks/events.js +350 -0
- package/dist/_chunks/index.d.ts +6005 -0
- package/dist/_chunks/locale.d.ts +17 -0
- package/dist/_chunks/locale.js +17 -0
- package/dist/_chunks/src.js +1202 -0
- package/dist/_chunks/steward.d.ts +2588 -0
- package/dist/_chunks/steward.js +578 -0
- package/dist/_chunks/validators.d.ts +23 -0
- package/dist/_chunks/validators.js +106 -0
- package/dist/_chunks/webhook-core.d.ts +32 -0
- package/dist/_chunks/webhook-core.js +64 -0
- package/dist/_chunks/webhook.d.ts +22 -0
- package/dist/_chunks/webhook.js +53 -0
- package/dist/contract.d.ts +4 -0
- package/dist/contract.js +4 -0
- package/dist/index.d.ts +268 -0
- package/dist/index.js +784 -0
- package/dist/server.d.ts +236 -0
- package/dist/server.js +384 -0
- package/dist/testing/fixtures/events/LOCK.json +27 -0
- package/dist/testing/fixtures/events/account.deleted.json +36 -0
- package/dist/testing/fixtures/events/account.state_changed.json +53 -0
- package/dist/testing/fixtures/events/account.updated.json +37 -0
- package/dist/testing/fixtures/events/checkout.completed.json +36 -0
- package/dist/testing/fixtures/events/checkout.expired.json +26 -0
- package/dist/testing/fixtures/events/checkout.failed.json +27 -0
- package/dist/testing/fixtures/events/invoice.created.json +37 -0
- package/dist/testing/fixtures/events/invoice.issued.json +38 -0
- package/dist/testing/fixtures/events/invoice.voided.json +39 -0
- package/dist/testing/fixtures/events/subscription.activated.json +40 -0
- package/dist/testing/fixtures/events/subscription.cancel_scheduled.json +38 -0
- package/dist/testing/fixtures/events/subscription.canceled.json +29 -0
- package/dist/testing/fixtures/events/subscription.expired.json +27 -0
- package/dist/testing/fixtures/events/subscription.payment_failed.json +38 -0
- package/dist/testing/fixtures/events/subscription.reactivated.json +36 -0
- package/dist/testing/fixtures/events/subscription.renewed.json +39 -0
- package/dist/testing/fixtures/events/subscription.suspended.json +36 -0
- package/dist/testing/fixtures/events/subscription.terminated.json +28 -0
- package/dist/testing.d.ts +629 -0
- package/dist/testing.js +3759 -0
- package/dist/webhook/web.d.ts +17 -0
- package/dist/webhook/web.js +66 -0
- package/dist/webhook.d.ts +3 -0
- package/dist/webhook.js +3 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
# @stewardhq/sdk
|
|
2
|
+
|
|
3
|
+
Steward ↔ ürün entegrasyonu: sözleşme şemaları (zod), tipli API istemcisi ve
|
|
4
|
+
webhook imzası. Kaynak: [z9cloud/steward](https://github.com/z9cloud/steward/tree/main/sdk).
|
|
5
|
+
|
|
6
|
+
## Kurulum
|
|
7
|
+
|
|
8
|
+
npmjs'te public; token gerekmez.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @stewardhq/sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
ESM, Node ≥ 22. Tek çalışma zamanı bağımlılığı zod.
|
|
15
|
+
|
|
16
|
+
## Alt yollar
|
|
17
|
+
|
|
18
|
+
| Import | İçerik |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `@stewardhq/sdk` | Tüm şema/tipler + istemciler (`createSteward`, `createCatalogCache`, `stateCache`, fırlatmayan `createBillingClient`) + yardımcılar: hata sınıfları, `httpStatusFor`, `errorMessages`/`messageFor`, `defineCatalog`, `formatPrice`/`taxBreakdown`, `isValidTckn`/`isValidVkn`/`isE164` |
|
|
21
|
+
| `@stewardhq/sdk/contract` | Yalnızca şemalar ve tipler |
|
|
22
|
+
| `@stewardhq/sdk/webhook` | `signWebhook` / `verifyWebhook` / `verifyWebhookRaw` (Node HMAC, senkron) |
|
|
23
|
+
| `@stewardhq/sdk/webhook/web` | Aynı API WebCrypto ile (async; edge/workerd/tarayıcı) |
|
|
24
|
+
| `@stewardhq/sdk/testing/fixtures/events/<tür>.json` | Her event türünün örnek gövdesi |
|
|
25
|
+
| `@stewardhq/sdk/server` | `Webhooks()`: imzalı event alıcısı, canlı durum + önbellek, tipli hook'lar (Node); `Checkout()`: hosted checkout oturumu (`create` → `url`) ve dönüş sonucu (`result`); `CustomerPortal()`: hosted müşteri portalı oturumu (`create` → `url`) |
|
|
26
|
+
| `@stewardhq/sdk/testing` | Test çiftleri (Node): `createFakeSteward()` (HTTP düzeyinde sahte steward, `simulate.*`, `connect`), `signedEvent()` |
|
|
27
|
+
|
|
28
|
+
Kök giriş, `./contract` ve `./webhook/web` `node:*` kullanmaz (edge/tarayıcı);
|
|
29
|
+
`./webhook` senkron Node HMAC'idir.
|
|
30
|
+
|
|
31
|
+
## Kullanım
|
|
32
|
+
|
|
33
|
+
Hesap durumu (okuma modeli) — ürün DB'sinde hak projeksiyonu tutulmaz; kapılar steward'ı
|
|
34
|
+
pod başına bir önbellek üzerinden canlı okur:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createSteward, stateCache } from "@stewardhq/sdk";
|
|
38
|
+
|
|
39
|
+
export const steward = createSteward();
|
|
40
|
+
export const state = stateCache(steward, {
|
|
41
|
+
catalog, // entitlements() tipli olur
|
|
42
|
+
// steward kapalı ve bayat değer yoksa (AccountState | null); catalog.stateOf: katalogdan tam durum (version 0)
|
|
43
|
+
fallback: async (ref) => {
|
|
44
|
+
const org = await findOrg(ref);
|
|
45
|
+
return org ? catalog.stateOf(ref, org.plan, { displayName: org.name }) : null;
|
|
46
|
+
},
|
|
47
|
+
onFallback: ({ ref, reason }) => metrics.billingFallback.inc({ reason }),
|
|
48
|
+
// missing: "fallback" → steward'da hesabı olmayan kayıt da fallback'e (hesap ilk checkout'ta açılıyorsa)
|
|
49
|
+
// ttlMs: 30_000, staleIfErrorMs: 600_000, maxEntries: 10_000, logger
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const e = await state.entitlements(orgId); // { max_projects: number | null; sso: boolean }
|
|
53
|
+
await state.state(orgId, { fresh: true }); // TTL'i beklemeden
|
|
54
|
+
state.prime((await steward.grants.create(orgId, input)).account); // mutasyon yanıtı önbelleğe
|
|
55
|
+
const byRef = await state.many(orgIds); // Map<ref, AccountState>, en çok 8 paralel
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| Durum | Davranış |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| TTL içinde | Bellekten, istek yok; aynı hesabın eşzamanlı okumaları tek istek |
|
|
61
|
+
| TTL doldu | `If-None-Match`: 304 süreyi yeniler, 200 yeni durumu yazar |
|
|
62
|
+
| Ağ hatası, zaman aşımı, 5xx, 429 | Süre dolduktan sonra `staleIfErrorMs` boyunca bayat değer (WARN), sonra `fallback` (WARN + `onFallback`), o da yoksa (ya da `null`) asıl `StewardNetworkError`/`StewardApiError`. Başarısız istekten sonra o hesap için `min(ttlMs, 5 sn)` steward'a gidilmez |
|
|
63
|
+
| 404 `account_not_found` (varsayılan `missing: "throw"`) | Kesinti değil: fallback yok, önbelleğe girmez, `StewardApiError` fırlar (eksik `upsert` görünür kalır) |
|
|
64
|
+
| 404 `account_not_found`, `missing: "fallback"` | `fallback(ref)` (zorunlu seçenek); WARN yok, `onFallback` nedeni `account_not_found` (kesinti metriğinden ayrılır); bayat değer kullanılmaz. Sonuç önbelleğe girmez, "yok" bilgisi `min(ttlMs, 5 sn)` tutulur; `prime`/`invalidate`/`fresh` bitirir (checkout sonucu ve webhook `prime` eder). Fallback `null` → 404 fırlar |
|
|
65
|
+
| `prime(account)` | Yalnızca `version >= önbellekteki` ise yazar (geri gitmez); `null`/`undefined` yok sayılır |
|
|
66
|
+
|
|
67
|
+
Önbellek pod başına bellek içidir (LRU); çok pod'da tutarlılık TTL kadardır.
|
|
68
|
+
`invalidate(ref)` sonraki okumanın hemen (ETag'le) sormasını sağlar.
|
|
69
|
+
|
|
70
|
+
Webhook alıcısı (`/server`, Node) — imza ham gövdede, sonra sözleşme; durum steward'dan
|
|
71
|
+
canlı okunur, önbelleğe yazılır; hook'lar tipli:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { Webhooks } from "@stewardhq/sdk/server";
|
|
75
|
+
|
|
76
|
+
const webhooks = Webhooks({
|
|
77
|
+
steward, cache: state, // refetch (steward verilince varsayılan): payload yalnız tetikleyici
|
|
78
|
+
// secrets verilmezse her teslimatta STEWARD_WEBHOOK_SECRET ("yeni,eski" rotasyonu); toleranceSeconds: 300
|
|
79
|
+
onStateChanged: async ({ ref, state, cause }) => {
|
|
80
|
+
const org = await findOrg(ref);
|
|
81
|
+
if (!org) return "ignore"; // bilinmeyen hesap: 200, diğer hook'lar ve after koşmaz
|
|
82
|
+
await applyOrgPlan(org, state.planCode, state.accessState);
|
|
83
|
+
},
|
|
84
|
+
onSubscriptionTerminated: ({ ref }) => flagOps(ref), // on<Tip>: eski tipten ya da state_changed cause'undan
|
|
85
|
+
onPaymentFailed: ({ ref, state, profile }) => mailOwner(ref, profile), // + onSuspended, onTerminated
|
|
86
|
+
after: ({ ref, state }) => propagate(ref, state.accessState), // diğer hook'lardan sonra, her teslimatta
|
|
87
|
+
// includeProfile: true → bildirim hook'ları fatura profilini (e-posta) alır; refetch: false → gövdedeki durum
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
app.post("/v1/billing/events", (c) => webhooks.handle(c.req.raw)); // ya da: const { status, body } = await webhooks.receive(rawBody, headers)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Durum | Yanıt |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| Hook'lar koştu | 200 `{ok: true, handled: "received"}` |
|
|
96
|
+
| `onStateChanged` `"ignore"` döndü | 200 `ignored` + WARN; neden hook'ları ve `after` koşmaz |
|
|
97
|
+
| Hesap silindi: `account.deleted` ya da `cause: "account.deleted"` | 200 `deleted`; canlı okuma yok, `onAccountDeleted` + `after` gövdedeki anonim son durumla (`onStateChanged` koşmaz, `cache.invalidate` varsa çağrılır). Silmeden önce kuyruğa girmiş teslimatın canlı okuması `account_deleted` (410) alırsa hook'suz 200 `deleted` + WARN |
|
|
98
|
+
| `endpoint.ping` | 200 `ping`, hook yok |
|
|
99
|
+
| `refetch: false` ve gövdede yalnızca snapshot (yükseltme öncesi kuyruk) | 200 `no_state` + WARN: `onStateChanged` koşmaz, neden hook'ları ve `after` `state: null` ile koşar |
|
|
100
|
+
| Tanınmayan tip | 200; `onUnknownEvent` (durum varsa önce `onStateChanged`) |
|
|
101
|
+
| İmza/başlık/zaman penceresi | 401 |
|
|
102
|
+
| İmzalı ama sözleşmeye uymayan gövde, id uyuşmazlığı | 400 + ALARM (steward yeniden dener) |
|
|
103
|
+
| Sır yok, güncel durum okunamadı (`state_unavailable`), profil okunamadı, bir hook fırlattı | 503 (steward yeniden dener, hook'ların hepsi yeniden koşar) |
|
|
104
|
+
|
|
105
|
+
Tipler: `steward` verilmiş ve `refetch: false` değilse her hook'un (`onStateChanged`, `on<Tip>`,
|
|
106
|
+
`onPaymentFailed`…, `onUnknownEvent`, `after`) `state`'i `AccountState`'tir — durum okunamazsa 503 döner,
|
|
107
|
+
hook koşmaz; `after`'ın `handled`'ı `"received"`. `refetch: false` ya da `steward`'sız alıcıda neden hook'ları,
|
|
108
|
+
`onUnknownEvent` ve `after` `AccountState | null` alır (yükseltme öncesi kuyruk; `onStateChanged` yine tam
|
|
109
|
+
durumla koşar). Ayrım `refetch` ve `steward` alanlarının tiplerinden çıkarılır (tip belirsizse null'lı).
|
|
110
|
+
|
|
111
|
+
**SDK tekrar ayıklamaz, version kapısı ve kilit tutmaz.** `refetch` ile her teslimat
|
|
112
|
+
durumu steward'dan canlı okur: sırasız ya da tekrar teslimat en güncel durumu görür,
|
|
113
|
+
yalnızca snapshot taşıyan eski gövde de tam kullanılır (`event.data.account` bayat
|
|
114
|
+
olabilir; `state`'i kullan). Aynı event yine birden çok kez ve aynı hesabın teslimatları
|
|
115
|
+
eşzamanlı gelebilir: hook'lar idempotent yazmalı (plan alanını durumdan yaz, "zaten
|
|
116
|
+
askıdaysa dokunma", sonlandırma işareti `coalesce`). Yeniden denenmesi istenen durumda hook
|
|
117
|
+
fırlatır → 503. `refetch: false` gövdedeki durumu kullanır (sıra garantisi yok; eski
|
|
118
|
+
kuyruğun boşalmasını bekle). Aynı olay için hem eski tipi hem `account.state_changed`'i
|
|
119
|
+
isteyen endpoint neden hook'larını iki kez çalıştırır: endpoint filtresinde birini seç.
|
|
120
|
+
|
|
121
|
+
Hosted checkout (`/server`) — profil, TCKN/VKN, onaylar ve ödeme formu steward'ın sayfasında;
|
|
122
|
+
ürün yalnızca oturumu açar, kullanıcıyı `url`'e yönlendirir ve dönüşte sonucu sorar:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import { Checkout } from "@stewardhq/sdk/server";
|
|
126
|
+
|
|
127
|
+
const checkout = Checkout({
|
|
128
|
+
steward, cache: state,
|
|
129
|
+
// mutlak; origin steward ayarında izinli (appUrl + extraReturnOrigins). Yer tutucular varsa
|
|
130
|
+
// steward yalnızca onları doldurur, yoksa ?session=&status= ekler.
|
|
131
|
+
successUrl: `${process.env.APP_URL}/{LOCALE}/app/orgs/{ACCOUNT_REF}/billing?checkout={CHECKOUT_ID}`,
|
|
132
|
+
consents: (locale) => legalDocs(locale), // [{document, version, url}] ya da dizi; sayfada onaylatılır
|
|
133
|
+
// cancelUrl (yoksa ayar appUrl), locale ve currency (yoksa ürün ayarı)
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
app.post("/v1/orgs/:orgId/billing/checkout", async (c) => {
|
|
137
|
+
if (!(await isOwner(c))) return c.json({ error: "forbidden" }, 403); // yetki ÇAĞRIDAN ÖNCE ürünün işi
|
|
138
|
+
const { url } = await checkout.create({ ref: c.req.param("orgId"), planCode: "team", interval: "month",
|
|
139
|
+
locale: c.get("locale"), actorRef: `user:${c.get("user").id}` }); // displayName?, prefill?: {email, name, kind}
|
|
140
|
+
return c.json({ url }); // tarayıcı 303 url
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
app.get("/v1/orgs/:orgId/billing/checkout/:id", async (c) => // dönüş sayfası ?checkout=<id>
|
|
144
|
+
c.json(await checkout.result(c.req.param("id"), { ref: c.req.param("orgId") })));
|
|
145
|
+
// { status: "open" | "completed" | "failed" | "expired" | "canceled", planCode?, account? }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Durum | Davranış |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `create` | `POST …/checkout-sessions` hosted gövdeyle (profil/kimlik yok); sağlayıcıya çağrı yok; `{id, url, expiresAt}`. Hesap yoksa `displayName` ile açılır, o da yoksa `account_not_found`. Steward hataları aynen (`already_subscribed` 409, `plan_not_sellable` 422, `return_url_not_allowed` → `httpStatusFor` 500 + ALARM) |
|
|
151
|
+
| `result`, oturum başka hesabın / id geçersiz / oturum yok | Aynı hata: `StewardApiError` `checkout_session_not_found` (404) — `?checkout=` kullanıcı girdisidir, sahiplik ele verilmez |
|
|
152
|
+
| `result`, `completed` | Yanıttaki `account` `cache.prime` edilir (dönüş sayfası webhook'u beklemeden yeni planı gösterir; webhook yine kaynak) |
|
|
153
|
+
| `locale` | `CustomerPortal` ile aynı kural: BCP-47 benzeri etiketin birincil alt etiketi (`tr-TR` → `tr`, `en-US` → `en`). Çağrıdaki desteklenmeyen dil yok sayılır: seçenekteki dil, o da yoksa gönderilmez (steward `defaultLocale`); `consents` işlevi bu dille çağrılır |
|
|
154
|
+
| Göreli/http(s) olmayan `successUrl`, `cancelUrl`, belge adresi; seçenekte desteklenmeyen `locale` | Oluşturmada `StewardConfigError` |
|
|
155
|
+
| Yanıtta `url` yok (F2b öncesi steward) | `StewardContractError` |
|
|
156
|
+
|
|
157
|
+
Kullanıcı sayfayı kapatırsa oturum süresi dolunca (`checkoutTtlMinutes`) steward kapatır;
|
|
158
|
+
ürün sonucu `account.state_changed` ile öğrenir. `profile` + `identityNumber` taşıyan eski gövde
|
|
159
|
+
(`steward.checkoutSessions.create`) açılışta sağlayıcı formu döndürmeye devam eder (N3 + 1
|
|
160
|
+
release sonra kaldırılır).
|
|
161
|
+
|
|
162
|
+
Hosted müşteri portalı (`/server`) — abonelik durumu, dönem sonunda iptal, ödemeyi yeniden dene,
|
|
163
|
+
fatura listesi/PDF ve kurumsal fatura profili steward'ın sayfasında; ürün yalnızca yetkiyi
|
|
164
|
+
denetler, oturumu açar ve yönlendirir. Ürün tarafında iptal/yeniden dene/fatura ucu yoktur:
|
|
165
|
+
sonuçlar `account.state_changed` ile `Webhooks()`'a gelir:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { CustomerPortal } from "@stewardhq/sdk/server";
|
|
169
|
+
|
|
170
|
+
const portal = CustomerPortal({ steward, returnUrl: `${process.env.APP_URL}/billing` }); // returnUrl yoksa ayar appUrl
|
|
171
|
+
|
|
172
|
+
app.post("/v1/orgs/:orgId/billing/portal", async (c) => {
|
|
173
|
+
if (!(await isOwner(c))) return c.json({ error: "forbidden" }, 403); // yetki ÇAĞRIDAN ÖNCE ürünün işi
|
|
174
|
+
const { url } = await portal.create({ ref: c.req.param("orgId"), locale: c.get("locale"), actorRef: `user:${c.get("user").id}` });
|
|
175
|
+
return c.json({ url }); // tarayıcı 303 url
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
| Durum | Davranış |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `create` | `POST …/portal-sessions {locale?, returnUrl?}` → `{id, url, expiresAt}`; durum değişmez, event yok. Süre ürün ayarı `portalTtlMinutes` (varsayılan 30 dk); süresi geçen ya da anahtarı yanlış bağlantı 404 |
|
|
182
|
+
| Hesap yok | `StewardApiError` `account_not_found` (404) — checkout'tan farklı olarak hesap açılmaz |
|
|
183
|
+
| `returnUrl` origin'i izinli değil | `return_url_not_allowed` (422; `httpStatusFor` 500 + ALARM) |
|
|
184
|
+
| `locale` | `tr`/`en` (ör. `tr-TR` → `tr`; `Checkout` ile aynı kural); başka dil gönderilmez, steward `defaultLocale`'i kullanır |
|
|
185
|
+
| `actorRef` eksik/biçimsiz, göreli ya da http(s) olmayan `returnUrl` | `StewardConfigError` (istek gitmez) |
|
|
186
|
+
| Portal aksiyonları | Aktör steward denetim izinde `customer:<ref>`; iptal `subscription.cancel_scheduled` (neden `customer_portal`), profil `billing_profile.updated`, yeniden dene durum değiştirmez (tahsilat sonucu `subscription.reactivated`) |
|
|
187
|
+
|
|
188
|
+
Düşük seviyede imza ham gövde üzerinde doğrulanır, sonra sözleşmeyle parse edilir:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { verifyWebhook } from "@stewardhq/sdk/webhook";
|
|
192
|
+
|
|
193
|
+
const rawBody = await request.text();
|
|
194
|
+
const result = verifyWebhook({ secrets: [process.env.STEWARD_WEBHOOK_SECRET!], headers: request.headers, rawBody });
|
|
195
|
+
if (!result.ok) return new Response(null, { status: 400 });
|
|
196
|
+
// result.event: BillingEvent — tekrarı `id` ile ayıkla, eski `version`'ı uygulama.
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
API istemcisi — `createSteward()` ayarları seçeneklerden, yoksa ortamdan okur
|
|
200
|
+
(`STEWARD_URL`, `STEWARD_API_KEY`; edge'de `env` verilir) ve eksikse **açılışta**
|
|
201
|
+
`StewardConfigError` fırlatır (yalnızca admin ayarlı istemci hariç, aşağıda). Başarısız çağrı fırlatır:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { createSteward } from "@stewardhq/sdk";
|
|
205
|
+
|
|
206
|
+
export const steward = createSteward({ actorRef: "system" }); // ya da createSteward({ env, fetch, timeoutMs: 10_000 })
|
|
207
|
+
|
|
208
|
+
const state = await steward.accounts.state(accountRef); // AccountState
|
|
209
|
+
await steward.grants.create(accountRef, { planCode: "team", reason: "kurumsal sözleşme" }, { actorRef: `staff:${staffId}` });
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
| Kaynak | Metotlar |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| `accounts` | `upsert(ref, input)`, `get(ref, {include?})`, `state(ref)`, `readState(ref, {etag?})` (304 → `{notModified: true}`), `entitlements(ref)`, `putBillingProfile(ref, profile)`, `setPlan(ref, {planCode \| null, reason, mode?, endsAt?, overrides?})` → `{grant, account, warnings}` (canlı abonelik planı hedefi geçiyorsa `mode: "ensure"` uyarı, `"strict"` `subscription_plan_higher`), `resync(ref)` → `{queued, eventId}` (saklı durum `account.state_changed`, `cause: "resync"`), `delete(ref)` → `{ref, deletedAt}` (KVKK anonimleştirme; faturalar kalır; önce canlı abonelik `immediate` iptal — yoksa `subscription_active`, formu açık checkout'ta `checkout_in_progress`; sonra hesabın her ucu `account_deleted` 410) |
|
|
215
|
+
| `checkoutSessions` | `create(ref, input)`, `get(id)` |
|
|
216
|
+
| `portalSessions` | `create(ref, {locale?, returnUrl?})` → `{id, url, expiresAt}` (hosted müşteri portalı; `CustomerPortal()` bunu sarar) |
|
|
217
|
+
| `subscriptions` | `cancel(id, input)` |
|
|
218
|
+
| `grants` | `create(ref, input)`, `revoke(id)` |
|
|
219
|
+
| `invoices` | `issue(id, input)`, `document(id)` → `{contentType, bytes}`, `createManual(ref, {lines, currency, taxRateBps, taxInclusive?, dueAt?, note?})` (manuel taslak; profil gerekir, `issue` ile kesilir), `void(id, {reason})` (yalnızca manuel fatura; `invoice.voided` yalnızca filtresinde isteyen endpoint'e) |
|
|
220
|
+
| `catalog` | `get()`, `read({etag?})` |
|
|
221
|
+
| `events` | `list({accountRef?, cursor?, limit?})`, `redeliver(id)` |
|
|
222
|
+
| — | `stats()`, `me()`, `doctor()` → kurulum teşhisi (`GET /v1/doctor`; aşağıda) |
|
|
223
|
+
| `admin` | `catalog.sync(input)`, `accounts.refresh()`, `settings.{get(), update(patch)}`, `endpoints.{list(), create(input), rotate(id, {secret}), deactivate(id)}` — `STEWARD_ADMIN_URL` + `STEWARD_ADMIN_API_KEY` (ya da `adminBaseUrl`/`adminApiKey`) ister; yoksa çağrıda `StewardConfigError` |
|
|
224
|
+
| `raw` | Fırlatmayan, tekrar denemeyen sözleşme istemcisi (`createBillingClient`; `{ ok, data }` / `{ ok: false, status, error }`) |
|
|
225
|
+
|
|
226
|
+
- **Mutasyonlar** son argümanda `{ idempotencyKey?, actorRef? }` alır. Anahtar
|
|
227
|
+
verilmezse çağrı başına üretilir; `actorRef` verilmezse `createSteward`'daki varsayılan gider.
|
|
228
|
+
İkisi de yoksa ya da biçimi geçersizse (`tür[:kimlik]`) istek **gitmez**: `StewardConfigError`
|
|
229
|
+
(`code` servisinkiyle aynı: `actor_ref_required` / `invalid_actor_ref`). `raw` bu denetimi yapmaz.
|
|
230
|
+
- **Yalnızca yönetim:** `STEWARD_URL` ve `STEWARD_API_KEY` ikisi de yoksa ve `STEWARD_ADMIN_URL` +
|
|
231
|
+
`STEWARD_ADMIN_API_KEY` (ya da `adminBaseUrl`/`adminApiKey`) verilmişse istemci kurulur (CLI, katalog
|
|
232
|
+
senkronu): `admin.*` çalışır, iki kapsama açık `doctor()` ve `me()` admin anahtarıyla gider, ürün uçları
|
|
233
|
+
ve `raw.*` çağrıda `StewardConfigError` ile reddeder (istek gitmez). Ürün ayarının yalnızca yarısı
|
|
234
|
+
verilmişse yine açılışta hata.
|
|
235
|
+
- **Hatalar:** yanıtlı hata `StewardApiError` (`code`, `httpStatus`, `category`,
|
|
236
|
+
`retryable`, `requestId`, `retryAfterMs`), yanıt yok `StewardNetworkError`
|
|
237
|
+
(`network_error`/`timeout`), şemaya uymayan yanıt `StewardContractError`.
|
|
238
|
+
- **Tekrar deneme** (`retries: { attempts: 2, on: ["network", "5xx", "429"], maxDelayMs: 5000 }`):
|
|
239
|
+
ağ hatası/zaman aşımı, 5xx ve 429'da GET'ler ve `Idempotency-Key` taşıyan mutasyonlar
|
|
240
|
+
en çok `attempts` kez daha (varsayılan en çok 3 istek) denenir; anahtar bütün denemelerde
|
|
241
|
+
aynıdır. Bekleme `Retry-After` kadar, yoksa üstel (250 ms, 500 ms…) + jitter; ikisi de
|
|
242
|
+
`maxDelayMs` ile sınırlı. 429 dışındaki 4xx ve `retryable: false` yanıtlar
|
|
243
|
+
(`provider_error`, `provider_not_configured`) denenmez. `timeoutMs` deneme başınadır.
|
|
244
|
+
- İstekler `Steward-Api-Version` ve `X-Steward-Sdk: @stewardhq/sdk/<sürüm>` taşır (yalnızca gözlem).
|
|
245
|
+
- Yalnızca web standartları (`fetch`, `crypto.randomUUID`, `AbortSignal.timeout`): Node 22,
|
|
246
|
+
edge ve workerd'de aynı kod. Kök giriş ve `./webhook/web` her paket kontrolünde
|
|
247
|
+
workerd'de `nodejs_compat` kapalı koşturulur (`scripts/check-workerd.mjs`).
|
|
248
|
+
|
|
249
|
+
Yönetim (admin anahtarı; cluster içi, port-forward) — ürün ayarları ve webhook endpoint'leri.
|
|
250
|
+
Webhook sırrını **çağıran üretir** ve kendi Secret'ına yazar; steward sırrı hiçbir yanıtta
|
|
251
|
+
geri vermez:
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { generateWebhookSecret } from "@stewardhq/sdk";
|
|
255
|
+
|
|
256
|
+
await steward.admin.settings.update({
|
|
257
|
+
appUrl: "https://app.example.com", // origin'i checkout dönüşüne izinli; + extraReturnOrigins
|
|
258
|
+
privacyUrl: "https://app.example.com/kvkk",
|
|
259
|
+
branding: { name: "Acme", accentColor: "#0f766e" }, // sığ birleşir; alanı null temizler
|
|
260
|
+
checkoutTtlMinutes: 30,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const secret = generateWebhookSecret(); // whsec_ + 32 rastgele bayt
|
|
264
|
+
const endpoint = await steward.admin.endpoints.create({ url: "http://api.acme.svc.cluster.local/billing/events", secret });
|
|
265
|
+
// eventTypes verilmezse ["account.state_changed"]; adres https:// ya da http://*.svc.cluster.local
|
|
266
|
+
await steward.admin.endpoints.rotate(endpoint.id, { secret: generateWebhookSecret() }); // eski sır geçiş için ikinci imza
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Kurulum teşhisi — ürün anahtarı yeter; ilk checkout'tan önce `ok: true` beklenir:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
const report = await steward.doctor();
|
|
273
|
+
// report.ok: `error` önemde uyarı yok
|
|
274
|
+
// report.warnings: [{ code: "endpoint_wrong_secret", severity: "error", message, endpointId }, ...]
|
|
275
|
+
// report.endpoints[].diagnosis: ok | wrong_secret (son teslimat 401) | old_contract (400) | unreachable | failing | no_deliveries
|
|
276
|
+
// report.catalog, report.provider (environment: sandbox | live | fake), report.hosted.ready, report.dunning.dryRun
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Kodlar ve teşhisler açık kümedir (bilinmeyeni yalnızca gösterin). `provider.healthy` ve
|
|
280
|
+
`lastError` yanıtı veren steward pod'unun gözlemidir.
|
|
281
|
+
|
|
282
|
+
Katalog önbelleği — TTL içinde bellekten, sonra `If-None-Match` (304 gövdeyi korur);
|
|
283
|
+
eşzamanlı çağrılar tek istek; başarılı okumadan sonraki hatada bayat değer + `logger.warn`:
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
import { createCatalogCache } from "@stewardhq/sdk";
|
|
287
|
+
|
|
288
|
+
const catalogCache = createCatalogCache(steward, { ttlMs: 60_000, logger });
|
|
289
|
+
const { plans, prices } = await catalogCache.get();
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Katalog — kodda tipli tanım; eksik/fazla hak, yanlış tip (`limit` → tam sayı ya da
|
|
293
|
+
`null` = sınırsız, `flag` → boolean, `text` → string) ve tanımsız plana fiyat derleme
|
|
294
|
+
hatasıdır, tipler atlatılırsa tanım anında `StewardConfigError` fırlatır (TypeScript ≥ 5.4):
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
import { defineCatalog, flag, limit } from "@stewardhq/sdk";
|
|
298
|
+
|
|
299
|
+
export const catalog = defineCatalog({
|
|
300
|
+
defaultPlan: "free",
|
|
301
|
+
features: { max_projects: limit(), sso: flag() },
|
|
302
|
+
plans: {
|
|
303
|
+
free: { name: "Free", rank: 0, sellable: false, entitlements: { max_projects: 1, sso: false } },
|
|
304
|
+
team: { name: "Team", rank: 10, sellable: true, entitlements: { max_projects: null, sso: true } },
|
|
305
|
+
},
|
|
306
|
+
prices: [{ plan: "team", interval: "month", currency: "TRY", amountMinor: 125_000, taxInclusive: true, taxRateBps: 2000 }],
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
await steward.admin.catalog.sync(catalog.toSyncInput()); // admin anahtarıyla (CLI/deploy adımı)
|
|
310
|
+
const e = catalog.resolve(snapshot); // { max_projects: number | null; sso: boolean }
|
|
311
|
+
const placeholder = catalog.stateOf("org_1", "team", { accessState: "active", displayName: "Acme" });
|
|
312
|
+
// tam AccountState: version 0, updatedAt 1970, catalogVersion null, haklar planınki, abonelik/açık checkout yok,
|
|
313
|
+
// profil { present: false }; plan verilmezse varsayılan plan (stateCache fallback'i için)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`resolve` eksik ya da tipi uymayan anahtarda değeri snapshot'ın planından (plan
|
|
317
|
+
katalogda yoksa varsayılan plandan) alır, asla "sınırsız" üretmez; `onFallback`
|
|
318
|
+
ile alarm verilebilir. Snapshot yoksa varsayılan planın hakları.
|
|
319
|
+
|
|
320
|
+
Hatalar ve metinler — ürün API'sinin durum kodu ve kullanıcı metni:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
import { httpStatusFor, messageFor, StewardError } from "@stewardhq/sdk";
|
|
324
|
+
|
|
325
|
+
if (error instanceof StewardError) {
|
|
326
|
+
const { status, headers } = httpStatusFor(error, { logger }); // already_subscribed 409, rate_limited 503 + Retry-After…
|
|
327
|
+
return Response.json({ error: error.code, message: messageFor(error.code, locale) }, { status, headers });
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Tabloda olmayan kod `category`'ye göre eşlenir (`not_found` 404, `conflict` 409,
|
|
332
|
+
`provider` 502, `unavailable` 503; `validation`/`config` 500 + ERROR log).
|
|
333
|
+
`errorMessages.{tr,en}` sözleşmenin `ERROR_CODES` kaydındaki her kodu ve istemci
|
|
334
|
+
kodlarını (`network_error`, `timeout`, `invalid_response`, `config_error`) kapsar.
|
|
335
|
+
|
|
336
|
+
Para ve doğrulayıcılar: `formatPrice(price, "tr")` → `₺1.250,00 · KDV dahil`;
|
|
337
|
+
`taxBreakdown(price)` servisin faturadaki matrah/KDV ayrıştırmasıyla kuruşu kuruşuna
|
|
338
|
+
aynıdır. `isValidTckn`, `isValidVkn` (sağlama hanesi), `isE164(value, {country: "TR"}?)`
|
|
339
|
+
değeri olduğu gibi denetler (boşluk kırpmaz).
|
|
340
|
+
|
|
341
|
+
Testler (`/testing`, Node) — sahte steward sunucu açmaz, `fetch` verir; başlıkları
|
|
342
|
+
(`Bearer`, `X-Actor-Ref`, `Idempotency-Key` tekrarı), ETag/304, hata gövdelerini ve
|
|
343
|
+
event kurallarını servis gibi uygular. Aynı uyumluluk paketi hem sahteye hem gerçek
|
|
344
|
+
servis imajına karşı koşar:
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
import { createFakeSteward, signedEvent } from "@stewardhq/sdk/testing";
|
|
348
|
+
|
|
349
|
+
const fake = createFakeSteward({
|
|
350
|
+
catalog, // oluşturmada senkronlanır
|
|
351
|
+
webhook: { secret }, // eventTypes verilmezse ["account.state_changed"]; "legacy" = 15 eski tip
|
|
352
|
+
});
|
|
353
|
+
fake.connect((req) => webhooks.handle(req)); // alıcı sonradan (ya da webhook.handler); uygulama fake'ten sonra kurulabilir
|
|
354
|
+
const steward = fake.steward; // ya da ürünün kendi createSteward({ fetch: fake.fetch, baseUrl: fake.baseUrl, … })
|
|
355
|
+
await steward.accounts.upsert("org_1", { displayName: "Org" });
|
|
356
|
+
const session = await steward.checkoutSessions.create("org_1", input);
|
|
357
|
+
await fake.simulate.checkoutCompleted(session.id); // ya da checkoutFailed; hosted oturumda checkoutCanceled da
|
|
358
|
+
|
|
359
|
+
// Hosted (Checkout().create): url sahte steward'ın sayfası; tarayıcı gibi sürülebilir
|
|
360
|
+
const { id, url } = await checkout.create({ ref: "org_1", planCode: "team", interval: "month", actorRef: "user:1" });
|
|
361
|
+
const { redirectUrl, session: done } = await fake.simulate.completeHostedCheckout(url); // sayfa GET + "Tamamla" (outcome: "failed" → Reddet)
|
|
362
|
+
const page = await fake.fetch(url); // ya da elle: "Tamamla / Reddet / Vazgeç" formları (k gizli alanda)
|
|
363
|
+
const paid = await fake.fetch(new URL(`${id}/pay`, url), { method: "POST", body: new URLSearchParams({ k: new URL(url).searchParams.get("k")!, outcome: "succeeded" }) });
|
|
364
|
+
paid.headers.get("location"); // successUrl, yer tutucular servisle aynı kuralla dolu
|
|
365
|
+
|
|
366
|
+
// Portal (CustomerPortal().create): url sahte portal sayfası; servisle aynı rotalar ve bildirim kodları
|
|
367
|
+
const portalSession = await portal.create({ ref: "org_1", actorRef: "user:1" });
|
|
368
|
+
const k = new URL(portalSession.url).searchParams.get("k")!;
|
|
369
|
+
const base = `${fake.baseUrl}/public/v1/portal/${portalSession.id}`;
|
|
370
|
+
await fake.fetch(`${base}/cancel`, { method: "POST", body: new URLSearchParams({ k }) }); // 200 onay sayfası
|
|
371
|
+
await fake.fetch(`${base}/cancel`, { method: "POST", body: new URLSearchParams({ k, confirm: "1" }) }); // 303 …&done=canceled
|
|
372
|
+
await fake.simulate.portalCancel(portalSession.id); // ya da doğrudan: { result: "ok" | "already_scheduled" | "no_subscription", account }
|
|
373
|
+
await fake.simulate.portalRetryPayment(portalSession.id); // past_due'da { result: "ok" } — durum değişmez; sonuç renewal ile
|
|
374
|
+
|
|
375
|
+
await fake.simulate.flush(); // bekleyen event'ler alıcıya, sırayla (2xx dışı → bekler)
|
|
376
|
+
|
|
377
|
+
await fake.simulate.renewal("org_1", { fail: true }); // saat dönem sonuna; past_due + dunning
|
|
378
|
+
await fake.simulate.advance({ days: 7 }); // zamanlanmış işler kendi anında (dunning, dönem sonu iptali, checkout süresi, grant penceresi, portal oturumu silme)
|
|
379
|
+
fake.simulate.outage(true, { mode: "503" }); // ya da "network": fetch reddeder
|
|
380
|
+
await fake.simulate.deliver("account.state_changed", { ref: "org_1", duplicate: true, outOfOrder: true });
|
|
381
|
+
|
|
382
|
+
const { request } = signedEvent("subscription.renewed", { accountRef: "org_1" }, { secret });
|
|
383
|
+
await webhooks.handle(request());
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`fake.requests` gelen istekleri, `fake.events(ref?)` yayınlanan event'leri tutar. Endpoint `webhook`
|
|
387
|
+
seçeneğiyle oluşturmada açılır; filtre verilmezse yönetim ucunun yeni ürüne verdiği
|
|
388
|
+
`["account.state_changed"]`, `eventTypes: "legacy"` pod CLI'nın filtresiz (15 eski tip) endpoint'i.
|
|
389
|
+
Alıcı bağlanmadan `flush`/`deliver` fırlatır (event'ler outbox'ta bekler); `connect` alıcıyı değiştirir.
|
|
390
|
+
Sahtenin bilerek farklı olduğu yerler: teslimat asenkron değil (`flush()`), `dead`
|
|
391
|
+
olmaz ve geri çekilme beklenmez; sağlayıcı hata vermez; dönüş origin'i (`settings.appUrl`,
|
|
392
|
+
`settings.extraReturnOrigins` ya da kısayolu `returnOrigins`) hiç ayarlanmazsa her
|
|
393
|
+
`returnUrl` kabul; `admin.endpoints` ile açılan endpoint yalnızca kayıttır (teslimat
|
|
394
|
+
`webhook` seçeneğinin alıcısına gider, `lastDelivery` hep null; `doctor()`'da teşhisi
|
|
395
|
+
`no_deliveries`, sağlayıcı `fake` ve sağlıklı); dunning'de yeniden deneme
|
|
396
|
+
sonucu `renewal` ile gelir; her yanıt `X-Request-Id` (hata gövdesinde `requestId`) taşır;
|
|
397
|
+
idempotency kayıtları süpürülmez. Hosted checkout'ta sahte sayfa servisin sayfasını taklit
|
|
398
|
+
etmez, yalnızca `url` ↔ `simulate` sözleşmesini sürülebilir kılar: profil formu, TCKN/VKN
|
|
399
|
+
doğrulaması, onay kutuları, deneme hakkı, CSP ve POST kaynak denetimi yok. Servis profili
|
|
400
|
+
müşterinin `details` gönderiminde yazar; sahte bunu tamamlanma/red anında yapar — hesabın
|
|
401
|
+
profili yoksa `prefill`den (ad, e-posta, tür; geri kalanı sabit test verisi, kurumsalda VKN
|
|
402
|
+
`1234567890`) sahte profil yazılır (`billing_profile.updated`), varsa aynen kalır; belgeler
|
|
403
|
+
o anda onaylanmış sayılır. Sayfada sağlayıcı formu olmadığından vazgeç her zaman `canceled`
|
|
404
|
+
olur ve süresi dolan hosted oturumun nedeni `expired`dır.
|
|
405
|
+
|
|
406
|
+
Portal oturumu (`portalSessions.create`) servisle aynı kurallarla açılır (hesap yoksa 404,
|
|
407
|
+
`returnUrl` origin politikası, süre `portalTtlMinutes`, 43 karakterlik sayfa anahtarı; süresi
|
|
408
|
+
geçen, anahtarsız ya da anahtarı yanlış oturum aynı 404). Sahte portal sayfası da servisin
|
|
409
|
+
sayfasını taklit etmez — marka, tr/en metinler, CSP, POST kaynak denetimi (`Sec-Fetch-Site`),
|
|
410
|
+
IP başına yanlış anahtar ve oturum başına aksiyon sayaçları (429) yok — ama rotaları
|
|
411
|
+
(`GET ?k=`, `POST …/cancel` iki adım `confirm=1`, `…/retry-payment`, `…/profile`,
|
|
412
|
+
`GET …/invoices/:id/document?k=`), 303 bildirim kodları (`done=canceled|retry_requested|profile_saved`,
|
|
413
|
+
`error=no_subscription|already_canceled|nothing_to_retry|retry_too_soon`), profil alan hata kodları
|
|
414
|
+
(422) ve sonuçları servisle aynıdır; uyumluluk paketi iki hedefi de sayfa üzerinden sürer.
|
|
415
|
+
Yönlendirmeler mutlak adrestir (servis köke göre yol verir). Sahte sağlayıcı hata vermediğinden
|
|
416
|
+
`provider_error` bildirimi yok. "Ödemeyi yeniden dene" isteği kabul eder (`ok`, abonelik başına
|
|
417
|
+
10 dakikada bir; dry-run olmayan dunning retry adımı da sayılır) ama tahsilatı kendisi
|
|
418
|
+
sonuçlandırmaz: servisteki gibi sonuç sonradan gelir — `simulate.renewal(ref)` aynı başarısız
|
|
419
|
+
siparişi başarılı sayar (`subscription.reactivated`), `{fail: true}` yine başarısız.
|
|
420
|
+
`simulate.portalCancel/portalRetryPayment` bilinmeyen ya da süresi geçmiş oturumda fırlatır.
|
|
421
|
+
|
|
422
|
+
## `@z9cloud/steward-contract`'tan geçiş
|
|
423
|
+
|
|
424
|
+
| Eski | Yeni |
|
|
425
|
+
| --- | --- |
|
|
426
|
+
| `@z9cloud/steward-contract` | `@stewardhq/sdk` (ya da yalnız şema için `@stewardhq/sdk/contract`) |
|
|
427
|
+
| `@z9cloud/steward-contract/client` | `@stewardhq/sdk` (`createBillingClient` aynen; fırlatan istemci `createSteward`) |
|
|
428
|
+
| `@z9cloud/steward-contract/webhook` | `@stewardhq/sdk/webhook` |
|
|
429
|
+
| `@z9cloud/steward-contract/fixtures/events/*.json` | `@stewardhq/sdk/testing/fixtures/events/*.json` |
|
|
430
|
+
|
|
431
|
+
GitHub Packages `.npmrc` satırı ve Docker `--secret` bağlaması artık gerekmez.
|
|
432
|
+
|
|
433
|
+
## Sürümleme
|
|
434
|
+
|
|
435
|
+
Semver. Alan kaldırma/yeniden adlandırma kırıcıdır (major; 1.0 öncesi minor).
|
|
436
|
+
Yayın: `sdk/package.json` sürümü artırılır, `sdk-v<sürüm>` etiketi push edilir
|
|
437
|
+
(`.github/workflows/sdk-publish.yml`, npm provenance ile).
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { R as errorCodeSpec } from "./src.js";
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
var StewardError = class extends Error {
|
|
4
|
+
code;
|
|
5
|
+
httpStatus;
|
|
6
|
+
retryable;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
super(options.message ?? options.code, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
9
|
+
this.name = "StewardError";
|
|
10
|
+
this.code = options.code;
|
|
11
|
+
this.httpStatus = options.httpStatus ?? 0;
|
|
12
|
+
this.retryable = options.retryable ?? false;
|
|
13
|
+
if (options.category !== void 0) this.category = options.category;
|
|
14
|
+
if (options.requestId !== void 0) this.requestId = options.requestId;
|
|
15
|
+
if (options.retryAfterMs !== void 0) this.retryAfterMs = options.retryAfterMs;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Steward 2xx dışı yanıt döndü (`{error, message?}`). Yanıt `category` /
|
|
20
|
+
* `retryable` taşımıyorsa sözleşmenin `ERROR_CODES` kaydı kullanılır; kod orada
|
|
21
|
+
* da yoksa 429 ve 5xx tekrar denenebilir sayılır.
|
|
22
|
+
*/
|
|
23
|
+
var StewardApiError = class extends StewardError {
|
|
24
|
+
constructor(options) {
|
|
25
|
+
const spec = errorCodeSpec(options.code);
|
|
26
|
+
const category = options.category ?? spec?.category;
|
|
27
|
+
super({
|
|
28
|
+
...options,
|
|
29
|
+
...category !== void 0 ? { category } : {},
|
|
30
|
+
retryable: options.retryable ?? spec?.retryable ?? (options.httpStatus === 429 || options.httpStatus >= 500)
|
|
31
|
+
});
|
|
32
|
+
this.name = "StewardApiError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/** Steward'a ulaşılamadı ya da zaman aşımı; yanıt yok (`httpStatus` 0), tekrar denenebilir. */
|
|
36
|
+
var StewardNetworkError = class extends StewardError {
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
super({
|
|
39
|
+
...options,
|
|
40
|
+
code: options.code ?? "network_error",
|
|
41
|
+
httpStatus: 0,
|
|
42
|
+
category: "unavailable",
|
|
43
|
+
retryable: options.retryable ?? true
|
|
44
|
+
});
|
|
45
|
+
this.name = "StewardNetworkError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/** Yanıt sözleşme şemasına uymadı (sürüm kayması); aynı istek aynı sonucu verir. */
|
|
49
|
+
var StewardContractError = class extends StewardError {
|
|
50
|
+
constructor(options = {}) {
|
|
51
|
+
super({
|
|
52
|
+
...options,
|
|
53
|
+
code: options.code ?? "invalid_response",
|
|
54
|
+
retryable: options.retryable ?? false
|
|
55
|
+
});
|
|
56
|
+
this.name = "StewardContractError";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/** SDK yanlış yapılandırıldı (eksik `STEWARD_URL`, geçersiz katalog…); istek hiç gönderilmedi. */
|
|
60
|
+
var StewardConfigError = class extends StewardError {
|
|
61
|
+
constructor(options = {}) {
|
|
62
|
+
super({
|
|
63
|
+
...options,
|
|
64
|
+
code: options.code ?? "config_error",
|
|
65
|
+
httpStatus: 0,
|
|
66
|
+
category: "config",
|
|
67
|
+
retryable: false
|
|
68
|
+
});
|
|
69
|
+
this.name = "StewardConfigError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
export { StewardNetworkError as a, StewardError as i, StewardConfigError as n, StewardContractError as r, StewardApiError as t };
|