@stewardhq/sdk 0.2.0 → 0.5.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.
Files changed (39) hide show
  1. package/README.md +123 -88
  2. package/dist/_chunks/errors.js +1 -1
  3. package/dist/_chunks/events.d.ts +315 -4
  4. package/dist/_chunks/events.js +1155 -9
  5. package/dist/_chunks/index.d.ts +3473 -90
  6. package/dist/_chunks/locale.d.ts +269 -1
  7. package/dist/_chunks/src.js +842 -24
  8. package/dist/_chunks/validators.d.ts +45 -1
  9. package/dist/_chunks/validators.js +128 -27
  10. package/dist/contract.d.ts +4 -4
  11. package/dist/contract.js +4 -4
  12. package/dist/index.d.ts +436 -22
  13. package/dist/index.js +1659 -26
  14. package/dist/server.d.ts +44 -6
  15. package/dist/server.js +29 -2
  16. package/package.json +4 -10
  17. package/dist/_chunks/steward.d.ts +0 -189
  18. package/dist/_chunks/steward.js +0 -560
  19. package/dist/testing/fixtures/events/LOCK.json +0 -27
  20. package/dist/testing/fixtures/events/account.deleted.json +0 -36
  21. package/dist/testing/fixtures/events/account.state_changed.json +0 -53
  22. package/dist/testing/fixtures/events/account.updated.json +0 -37
  23. package/dist/testing/fixtures/events/checkout.completed.json +0 -36
  24. package/dist/testing/fixtures/events/checkout.expired.json +0 -26
  25. package/dist/testing/fixtures/events/checkout.failed.json +0 -27
  26. package/dist/testing/fixtures/events/invoice.created.json +0 -37
  27. package/dist/testing/fixtures/events/invoice.issued.json +0 -38
  28. package/dist/testing/fixtures/events/invoice.voided.json +0 -39
  29. package/dist/testing/fixtures/events/subscription.activated.json +0 -40
  30. package/dist/testing/fixtures/events/subscription.cancel_scheduled.json +0 -38
  31. package/dist/testing/fixtures/events/subscription.canceled.json +0 -29
  32. package/dist/testing/fixtures/events/subscription.expired.json +0 -27
  33. package/dist/testing/fixtures/events/subscription.payment_failed.json +0 -38
  34. package/dist/testing/fixtures/events/subscription.reactivated.json +0 -36
  35. package/dist/testing/fixtures/events/subscription.renewed.json +0 -39
  36. package/dist/testing/fixtures/events/subscription.suspended.json +0 -36
  37. package/dist/testing/fixtures/events/subscription.terminated.json +0 -28
  38. package/dist/testing.d.ts +0 -626
  39. package/dist/testing.js +0 -3638
package/README.md CHANGED
@@ -17,13 +17,11 @@ ESM, Node ≥ 22. Tek çalışma zamanı bağımlılığı zod.
17
17
 
18
18
  | Import | İçerik |
19
19
  | --- | --- |
20
- | `@stewardhq/sdk` | Tüm şema/tipler + fırlatan istemci `createSteward`, hesap durumu önbelleği `stateCache`, `defineCatalog`, hata sınıfları + `httpStatusFor`, `isValidTckn`/`isValidVkn`/`isE164` |
20
+ | `@stewardhq/sdk` | Tüm şema/tipler + fırlatan istemci `createSteward`, hesap durumu önbelleği `stateCache`, `defineCatalog`, hata sınıfları + `httpStatusFor`, `isValidTckn`/`isValidVkn`/`isE164`; metering: `Events()`, `Meters()` |
21
21
  | `@stewardhq/sdk/contract` | Yalnızca şemalar ve tipler |
22
22
  | `@stewardhq/sdk/webhook` | `signWebhook` / `verifyWebhook` / `verifyWebhookRaw` (Node HMAC, senkron) |
23
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
24
  | `@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
25
 
28
26
  Kök giriş, `./contract` ve `./webhook/web` `node:*` kullanmaz (edge/tarayıcı);
29
27
  `./webhook` senkron Node HMAC'idir.
@@ -304,6 +302,101 @@ const placeholder = catalog.stateOf("org_1", "team", { accessState: "active", di
304
302
  katalogda yoksa varsayılan plandan) alır, asla "sınırsız" üretmez; `onFallback`
305
303
  ile alarm verilebilir. Snapshot yoksa varsayılan planın hakları.
306
304
 
305
+ Benefits (billing core) — reusable definitions plans list by code; `rank` is optional (the
306
+ benefits resolution does not read it). A `featureFlag()` sets catalog features with their
307
+ types; an undefined benefit or feature key and a wrong value type do not compile:
308
+
309
+ ```ts
310
+ import { defineCatalog, featureFlag, flag, limit } from "@stewardhq/sdk";
311
+
312
+ export const catalog = defineCatalog({
313
+ defaultPlan: "free",
314
+ features: { max_projects: limit(), sso: flag() },
315
+ benefits: { sso: featureFlag({ sso: true }, { description: { tr: "Tek oturum açma", en: "Single sign-on" } }) },
316
+ plans: {
317
+ free: { name: "Free", sellable: false, entitlements: { max_projects: 1, sso: false } },
318
+ team: { name: "Team", sellable: true, entitlements: { max_projects: null, sso: false }, benefits: ["sso"] },
319
+ },
320
+ prices: [{ plan: "team", interval: "month", currency: "TRY", amountMinor: 125_000, taxInclusive: true, taxRateBps: 2000 }],
321
+ });
322
+ type BenefitCode = typeof catalog.$benefit; // "sso"
323
+ const { benefits } = await state.state(orgId); // [{ code, type, properties, source, grantedAt }]; code typed
324
+ ```
325
+
326
+ `entitlementsOf`, `resolve` and `stateOf` take a plan's entitlements from the contract's
327
+ resolver (`resolveEntitlements`), the rule steward applies. `steward catalog push` sends a
328
+ catalog with benefits only to a steward whose `me().capabilities` lists `"benefits"`.
329
+
330
+ `Webhooks()` gains `onInvoicePaid` (`invoice.paid`) and `onBenefitGranted` / `onBenefitRevoked`
331
+ (`benefit_grant.created` / `.revoked`, with `benefit` and `grantId`; list these types in the
332
+ endpoint's `eventTypes`); like every hook they get the live `state`. Invoices and subscriptions
333
+ (billing core): `steward.invoices.createPaymentSession(id, { locale?, returnUrl? })` → `{ id, url,
334
+ expiresAt }` (redirect to steward's pay page), `steward.invoices.waive(id, { reason })`,
335
+ `steward.subscriptions.change(id, { planCode?, interval?, price?: "current", when: "next_period" })`,
336
+ `steward.subscriptions.uncancel(id)`.
337
+
338
+ Metering (phase 2) — the product records WHAT HAPPENED as events; catalog meters (a saved filter
339
+ + an aggregation) decide what counts as usage; credits are `meterCredit()` benefits; a metered
340
+ price bills overage on the renewal invoice. Units may be decimals (≤ 6 places; the SDK computes in
341
+ micro units, never float math):
342
+
343
+ ```ts
344
+ import { count, defineCatalog, Events, meter, meterCredit, Meters, sum } from "@stewardhq/sdk";
345
+
346
+ export const catalog = defineCatalog({
347
+ defaultPlan: "free",
348
+ features: { max_projects: limit(), sso: flag() },
349
+ benefits: { tokens_50k: meterCredit("ai_tokens", { units: 50_000 }), tokens_2m: meterCredit("ai_tokens", { units: 2_000_000, rollover: { capUnits: 1_000_000 } }) },
350
+ meters: {
351
+ ai_tokens: meter({ event: "ai.completion", aggregate: sum("tokens"), label: { tr: "AI token", en: "AI tokens" } }),
352
+ api_calls: meter({ aggregate: count(), label: { tr: "API isteği", en: "API requests" },
353
+ filter: { and: [{ name: "api.request" }, { "metadata.status": { lt: 500 } }] } }),
354
+ },
355
+ plans: { free: { …, benefits: ["tokens_50k"] }, team: { …, benefits: ["tokens_2m"] } },
356
+ prices: [{ plan: "team", interval: "month", currency: "TRY", amountMinor: 125_000, taxInclusive: true, taxRateBps: 2000 }],
357
+ meteredPrices: [{ plan: "team", meter: "ai_tokens", currency: "TRY", amountMinor: 1_250, perUnits: 1_000, taxInclusive: true, taxRateBps: 2000 }],
358
+ });
359
+ type Meter = typeof catalog.$meter; // "ai_tokens" | "api_calls"
360
+ type Event = typeof catalog.$event; // "ai.completion" (the `event` shorthands)
361
+
362
+ export const events = Events({ steward, catalog, actorRef: "service:api" });
363
+ export const meters = Meters({ steward, catalog, cache: state, events });
364
+
365
+ const gate = await meters.check(org.id, "ai_tokens", { units: estimatedTokens });
366
+ if (!gate.allowed) return c.json({ error: "credits_exhausted", balance: gate.balance }, 402);
367
+ events.track(org.id, "ai.completion", { tokens: 1500, model: "gpt-4o" }, { externalId: `cmpl_${res.id}` });
368
+ process.once("SIGTERM", () => void events.flush()); // the SDK never hooks process events
369
+ ```
370
+
371
+ - `Events({ steward, catalog?, actorRef?, flushIntervalMs = 2000, maxBatch = 500, maxQueue = 10_000,
372
+ autoFlush = true, onRejected?, onDrop? })`: `track(ref, name, metadata?, { externalId?, timestamp? })`
373
+ is sync and never throws (an invalid event → WARN + `onDrop`); batches go to `POST
374
+ /v1/events/ingest` with one `Idempotency-Key` per batch, reused by every retry (`503
375
+ events_backlog` included); after the retries the batch waits at the front of the queue (backoff
376
+ ≤ 30 s); a full queue hands its OLDEST events to `onDrop(events, { reason: "queue_full" })`, an
377
+ invalid event and a batch refused for good (4xx) go there too (`"invalid_event"`,
378
+ `"request_failed"`); per-event rejections go to `onRejected(rejected)` (not retried). `track`
379
+ stamps `timestamp` with the call's time unless given. Give a deterministic `externalId`
380
+ (`cmpl_<id>`): the generated UUID only protects against the SDK's own retries. On the edge:
381
+ `Events({ autoFlush: false })` + `ctx.waitUntil(events.flush())`; `flush()` never rejects (what
382
+ failed stays queued). `ingest(events, { idempotencyKey? })` sends directly and throws;
383
+ `pending(ref)` lists the account's events steward has not counted yet (queued, in flight, acked in
384
+ the last minute) — `Meters({ events })` subtracts them.
385
+ - `Meters({ steward, catalog, cache?, events?, ttlMs = 10_000, staleIfErrorMs = 600_000,
386
+ onUnavailable = "allow", onFallback? })`: `get(ref)` (every meter, `Record<Meter, MeterBalance>`),
387
+ `balance(ref, meter)`, `check(ref, meter, { units = 1 })` → `{ allowed, balance, standing,
388
+ billable, source }` with `allowed = billable ? !capReached : includedUnits === null || balance −
389
+ pending ≥ units` (a billable meter closes once steward reports its period overage reached the
390
+ rate's `capMinor`). Reads are cached (ETag/304); this process's queued (and not yet rolled up) events are
391
+ subtracted; when steward is unreachable the stale value is used for 10 min, then the catalog's
392
+ default-plan credit (or the account's coarse facts from `cache`) and `onUnavailable` decide.
393
+ - `stateOf()` carries the `meters` block when the catalog has meters; `Webhooks()` gains
394
+ `onMeterThreshold` (standing `ok → low → exhausted`, also as the cause of
395
+ `account.state_changed`), `onMeterPeriodClosed` (with `period`) and `onBenefitCycled`
396
+ (`benefit_grant.cycled`). `steward.events.ingest`, `steward.meters.get/read/periods`,
397
+ `steward.credits.grant`, `steward.meterCharges.list` are the raw calls. Push a catalog with
398
+ meters only to a steward whose `me().capabilities` lists `"meters"` (an older one drops them).
399
+
307
400
  Hatalar — ürün API'sinin durum kodu:
308
401
 
309
402
  ```ts
@@ -324,88 +417,6 @@ sayfalarındadır; SDK para biçimlendirme yardımcısı taşımaz.
324
417
  Doğrulayıcılar: `isValidTckn`, `isValidVkn` (sağlama hanesi), `isE164(value, {country: "TR"}?)`
325
418
  değeri olduğu gibi denetler (boşluk kırpmaz).
326
419
 
327
- Testler (`/testing`, Node) — sahte steward sunucu açmaz, `fetch` verir; başlıkları
328
- (`Bearer`, `X-Actor-Ref`, `Idempotency-Key` tekrarı), ETag/304, hata gövdelerini ve
329
- event kurallarını servis gibi uygular. Aynı uyumluluk paketi hem sahteye hem gerçek
330
- servis imajına karşı koşar:
331
-
332
- ```ts
333
- import { createFakeSteward, signedEvent } from "@stewardhq/sdk/testing";
334
-
335
- const fake = createFakeSteward({
336
- catalog, // oluşturmada senkronlanır
337
- webhook: { secret }, // eventTypes verilmezse ["account.state_changed"]; "legacy" = 15 eski tip
338
- });
339
- fake.connect((req) => webhooks.handle(req)); // alıcı sonradan (ya da webhook.handler); uygulama fake'ten sonra kurulabilir
340
- const steward = fake.steward; // ya da ürünün kendi createSteward({ fetch: fake.fetch, baseUrl: fake.baseUrl, … })
341
- await steward.accounts.upsert("org_1", { displayName: "Org" });
342
- const session = await steward.checkoutSessions.create("org_1", input);
343
- await fake.simulate.checkoutCompleted(session.id); // ya da checkoutFailed; hosted oturumda checkoutCanceled da
344
-
345
- // Hosted (Checkout().create): url sahte steward'ın sayfası; tarayıcı gibi sürülebilir
346
- const { id, url } = await checkout.create({ ref: "org_1", planCode: "team", interval: "month", actorRef: "user:1" });
347
- const { redirectUrl, session: done } = await fake.simulate.completeHostedCheckout(url); // sayfa GET + "Tamamla" (outcome: "failed" → Reddet)
348
- const page = await fake.fetch(url); // ya da elle: "Tamamla / Reddet / Vazgeç" formları (k gizli alanda)
349
- const paid = await fake.fetch(new URL(`${id}/pay`, url), { method: "POST", body: new URLSearchParams({ k: new URL(url).searchParams.get("k")!, outcome: "succeeded" }) });
350
- paid.headers.get("location"); // successUrl, yer tutucular servisle aynı kuralla dolu
351
-
352
- // Portal (CustomerPortal().create): url sahte portal sayfası; servisle aynı rotalar ve bildirim kodları
353
- const portalSession = await portal.create({ ref: "org_1", actorRef: "user:1" });
354
- const k = new URL(portalSession.url).searchParams.get("k")!;
355
- const base = `${fake.baseUrl}/public/v1/portal/${portalSession.id}`;
356
- await fake.fetch(`${base}/cancel`, { method: "POST", body: new URLSearchParams({ k }) }); // 200 onay sayfası
357
- await fake.fetch(`${base}/cancel`, { method: "POST", body: new URLSearchParams({ k, confirm: "1" }) }); // 303 …&done=canceled
358
- await fake.simulate.portalCancel(portalSession.id); // ya da doğrudan: { result: "ok" | "already_scheduled" | "no_subscription", account }
359
- await fake.simulate.portalRetryPayment(portalSession.id); // past_due'da { result: "ok" } — durum değişmez; sonuç renewal ile
360
-
361
- await fake.simulate.flush(); // bekleyen event'ler alıcıya, sırayla (2xx dışı → bekler)
362
-
363
- await fake.simulate.renewal("org_1", { fail: true }); // saat dönem sonuna; past_due + dunning
364
- 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)
365
- fake.simulate.outage(true, { mode: "503" }); // ya da "network": fetch reddeder
366
- await fake.simulate.deliver("account.state_changed", { ref: "org_1", duplicate: true, outOfOrder: true });
367
- fake.simulate.redeliver(eventId); // operatörün pod CLI `redeliver-event`'i: aynı event sonraki flush'ta yeniden
368
-
369
- const { request } = signedEvent("subscription.renewed", { accountRef: "org_1" }, { secret });
370
- await webhooks.handle(request());
371
- ```
372
-
373
- `fake.requests` gelen istekleri, `fake.events(ref?)` yayınlanan event'leri tutar. Endpoint `webhook`
374
- seçeneğiyle oluşturmada açılır; filtre verilmezse yönetim ucunun yeni ürüne verdiği
375
- `["account.state_changed"]`, `eventTypes: "legacy"` pod CLI'nın filtresiz (15 eski tip) endpoint'i.
376
- Alıcı bağlanmadan `flush`/`deliver` fırlatır (event'ler outbox'ta bekler); `connect` alıcıyı değiştirir.
377
- Sahtenin bilerek farklı olduğu yerler: teslimat asenkron değil (`flush()`), `dead`
378
- olmaz ve geri çekilme beklenmez; sağlayıcı hata vermez; dönüş origin'i (`settings.appUrl`,
379
- `settings.extraReturnOrigins` ya da kısayolu `returnOrigins`) hiç ayarlanmazsa her
380
- `successUrl`/`cancelUrl`/`returnUrl` kabul; `admin.endpoints` ile açılan endpoint yalnızca kayıttır (teslimat
381
- `webhook` seçeneğinin alıcısına gider, `lastDelivery` hep null; `doctor()`'da teşhisi
382
- `no_deliveries`, sağlayıcı `fake` ve sağlıklı); dunning'de yeniden deneme
383
- sonucu `renewal` ile gelir; her yanıt `X-Request-Id` (hata gövdesinde `requestId`) taşır;
384
- idempotency kayıtları süpürülmez. Hosted checkout'ta sahte sayfa servisin sayfasını taklit
385
- etmez, yalnızca `url` ↔ `simulate` sözleşmesini sürülebilir kılar: profil formu, TCKN/VKN
386
- doğrulaması, onay kutuları, deneme hakkı, CSP ve POST kaynak denetimi yok. Servis profili
387
- müşterinin `details` gönderiminde yazar; sahte bunu tamamlanma/red anında yapar — hesabın
388
- profili yoksa `prefill`den (ad, e-posta, tür; geri kalanı sabit test verisi, kurumsalda VKN
389
- `1234567890`) sahte profil yazılır (`billing_profile.updated`), varsa aynen kalır; belgeler
390
- o anda onaylanmış sayılır. Sayfada sağlayıcı formu olmadığından vazgeç her zaman `canceled`
391
- olur ve süresi dolan hosted oturumun nedeni `expired`dır.
392
-
393
- Portal oturumu (`portalSessions.create`) servisle aynı kurallarla açılır (hesap yoksa 404,
394
- `returnUrl` origin politikası, süre `portalTtlMinutes`, 43 karakterlik sayfa anahtarı; süresi
395
- geçen, anahtarsız ya da anahtarı yanlış oturum aynı 404). Sahte portal sayfası da servisin
396
- sayfasını taklit etmez — marka, tr/en metinler, CSP, POST kaynak denetimi (`Sec-Fetch-Site`),
397
- IP başına yanlış anahtar ve oturum başına aksiyon sayaçları (429) yok — ama rotaları
398
- (`GET ?k=`, `POST …/cancel` iki adım `confirm=1`, `…/retry-payment`, `…/profile`,
399
- `GET …/invoices/:id/document?k=`), 303 bildirim kodları (`done=canceled|retry_requested|profile_saved`,
400
- `error=no_subscription|already_canceled|nothing_to_retry|retry_too_soon`), profil alan hata kodları
401
- (422) ve sonuçları servisle aynıdır; uyumluluk paketi iki hedefi de sayfa üzerinden sürer.
402
- Yönlendirmeler mutlak adrestir (servis köke göre yol verir). Sahte sağlayıcı hata vermediğinden
403
- `provider_error` bildirimi yok. "Ödemeyi yeniden dene" isteği kabul eder (`ok`, abonelik başına
404
- 10 dakikada bir; dry-run olmayan dunning retry adımı da sayılır) ama tahsilatı kendisi
405
- sonuçlandırmaz: servisteki gibi sonuç sonradan gelir — `simulate.renewal(ref)` aynı başarısız
406
- siparişi başarılı sayar (`subscription.reactivated`), `{fail: true}` yine başarısız.
407
- `simulate.portalCancel/portalRetryPayment` bilinmeyen ya da süresi geçmiş oturumda fırlatır.
408
-
409
420
  ## `@steward/contract`'tan geçiş
410
421
 
411
422
  | Eski | Yeni |
@@ -413,9 +424,10 @@ siparişi başarılı sayar (`subscription.reactivated`), `{fail: true}` yine ba
413
424
  | `@z9cloud/steward-contract` | `@stewardhq/sdk` (ya da yalnız şema için `@stewardhq/sdk/contract`) |
414
425
  | `@z9cloud/steward-contract/client` | `@stewardhq/sdk` `createSteward` (fırlatan; fırlatmayan `createBillingClient` dışa açılmaz) |
415
426
  | `@z9cloud/steward-contract/webhook` | `@stewardhq/sdk/webhook` |
416
- | `@z9cloud/steward-contract/fixtures/events/*.json` | `@stewardhq/sdk/testing/fixtures/events/*.json` |
427
+ | `@z9cloud/steward-contract/fixtures/events/*.json` | Yok (paketle yayınlanmaz) |
428
+ | `@stewardhq/sdk/testing` (`signedEvent`, `createFakeSteward`) | Yok (0.4.0): servise karşı entegrasyon testi ya da `fetch` saplaması |
417
429
  | `getEntitlements(ref)` / `accounts.entitlements(ref)` | `accounts.state(ref)` ya da `stateCache` (tek okuma modeli `AccountState`; uç 2026-09-16'da kaldırıldı) |
418
- | `listEvents` / `redeliverEvent` / `events.*` | Yok (2026-09-16): kaçırılan durum için `accounts.resync(ref)`; dead teslimatı operatör pod CLI `redeliver-event` ile yeniden gönderir (testte `fake.simulate.redeliver`) |
430
+ | `listEvents` / `redeliverEvent` / `events.*` | Yok (2026-09-16): kaçırılan durum için `accounts.resync(ref)`; dead teslimatı operatör pod CLI `redeliver-event` ile yeniden gönderir |
419
431
 
420
432
  GitHub Packages `.npmrc` satırı ve Docker `--secret` bağlaması artık gerekmez.
421
433
 
@@ -423,4 +435,27 @@ GitHub Packages `.npmrc` satırı ve Docker `--secret` bağlaması artık gerekm
423
435
 
424
436
  Semver. Alan kaldırma/yeniden adlandırma kırıcıdır (major; 1.0 öncesi minor).
425
437
  Yayın: `sdk/package.json` sürümü artırılır, `sdk-v<sürüm>` etiketi push edilir
426
- (`.github/workflows/sdk-publish.yml`, npm provenance ile).
438
+ (`.github/workflows/sdk-publish.yml`).
439
+
440
+ **0.5.0** (contract 0.4.0, metering phase 2; additive; `API_VERSION` `2026-10-01` unchanged):
441
+
442
+ - Catalog: `meters` + `meter()`, `count()` / `sum(key)` / `max(key)` / `unique(key)`, `meterCredit()`
443
+ benefits, `meteredPrices`; `typeof catalog.$meter` / `$event`; `stateOf()` adds `meters`.
444
+ - `Events()` (`track`, `flush`, `ingest`, `pending`; `onRejected`, `onDrop(events, {reason})`) and `Meters()`
445
+ (`get`, `balance` → `MeterBalance`, `check`, `invalidate`) in the root entry, no `node:*`; `stateCache().peek(ref)`.
446
+ - Client: `events.ingest`, `meters.get/read/periods`, `credits.grant`, `meterCharges.list`.
447
+ - Hooks: `onMeterThreshold`, `onMeterPeriodClosed` (+ `period`), `onBenefitCycled`.
448
+
449
+ **0.4.0** (sözleşme 0.3.0, faturalama çekirdeği Faz 1; `API_VERSION` `2026-10-01` aynı):
450
+
451
+ - Kırıcı (0.x minor): `@stewardhq/sdk/testing` alt yolu (`signedEvent`, fixture'lar, sahte steward)
452
+ kaldırıldı; testler gerçek servise ya da `fetch` saplamasına karşı yazılır.
453
+ - Katalog: `benefits` + `featureFlag()`, `plans[].benefits`, `typeof catalog.$benefit`; `PlanSpec.rank`
454
+ isteğe bağlı ve hak çözümünde okunmaz (katman birleşimi, sözleşmenin `resolveBenefits`'i).
455
+ - Durum: `AccountState.benefits`, `subscriptions[]` (tekil `subscription` birincil abonelik),
456
+ `openInvoices[]`; abonelikteki `amountMinor` aboneliğin kilitli tutarıdır.
457
+ - Olaylar (endpoint'in `eventTypes`'ında istenirse): `invoice.paid`, `benefit_grant.created/updated/revoked`;
458
+ hook'lar `onInvoicePaid`, `onBenefitGranted`, `onBenefitRevoked` (ve tipten türeyen
459
+ `onBenefitGrantCreated/Updated/Revoked`).
460
+ - İstemci: `invoices.createPaymentSession`, `invoices.waive`, `subscriptions.change`,
461
+ `subscriptions.uncancel`; `me()` yanıtında `capabilities`.
@@ -1,4 +1,4 @@
1
- import { R as errorCodeSpec } from "./src.js";
1
+ import { nt as errorCodeSpec } from "./src.js";
2
2
  //#region src/errors.ts
3
3
  var StewardError = class extends Error {
4
4
  code;