@rewloy/node 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/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Değişiklik günlüğü / Changelog
2
+
3
+ Bu kütüphanenin sürümleri. API'nin kendi değişiklikleri:
4
+ https://rewloy.com/gelistiriciler/degisiklikler
5
+
6
+ This library's releases. The API's own changes are listed at the link above.
7
+
8
+ ## 0.1.0 (2026-10-04)
9
+
10
+
11
+ İlk önizleme, npm'de ilk sürüm. Rewloy API 1.0.0'a göre üretildi: 237 işlem.
12
+
13
+ First preview and first npm release, generated from Rewloy API 1.0.0 (237 operations):
14
+
15
+ - **Client.** `new Rewloy({ apiKey } | { staffSession, merchant } | { holderSession })`
16
+ with `baseUrl`, `timeoutMs`, `maxRetries`, `fetch` and `userAgent`.
17
+ - **Methods.** One method per operation, named by its operationId, typed
18
+ from the OpenAPI document. `request()` returns the whole answer (`status`,
19
+ `requestId`, `mode`, `replayed`).
20
+ - **Retries** on network errors, timeouts, 429 and 502–504, with
21
+ exponential backoff, jitter and `Retry-After`. Only safe requests are
22
+ retried.
23
+ - **`Idempotency-Key`** for till actions and campaigns: generated when
24
+ omitted, reused across retries.
25
+ - **Pagination** with `paginate()`.
26
+ - **Server-sent events** with `stream()`, `liveFeed()` and
27
+ `holderCardEvents()`, with reconnection and `Last-Event-ID`.
28
+ - **Webhooks:** `verifyWebhook()` and `signWebhook()`.
29
+ - **Errors:** `RewloyError`, `RateLimitError`, `RewloyConnectionError` and
30
+ `RewloyTimeoutError`.
31
+ - **Deprecations:** a `DeprecationWarning` per deprecated operation, and
32
+ `@deprecated` in the types.
33
+ - **Regeneration:** `npm run generate`, plus a daily workflow that opens a
34
+ pull request when the live document changes.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rewloy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,453 @@
1
+ # Rewloy Node.js
2
+
3
+ **Rewloy API'nin resmî Node.js ve TypeScript kütüphanesi.**
4
+
5
+ > **Durum: önizleme (0.x), npm'de yayımlandı. API kararlı; kütüphane arayüzü 1.0'a kadar değişebilir.**
6
+
7
+ [Rewloy](https://rewloy.com), işletmelerin dijital sadakat kartlarını
8
+ müşterinin telefonuna koyar. Kart türleri damga, puan, VIP, cashback, hediye
9
+ kartı, kupon ve indirimdir:
10
+ - iPhone'da Apple Cüzdan;
11
+ - Android'de Rewloy Cüzdan ve Google Cüzdan;
12
+ - her yerde web kartı.
13
+
14
+ Kasada QR okutulur; bakiye, ödül ve kampanyalar kartın kendisinde güncellenir.
15
+ Panelde yapılabilen her şey [Rewloy API v1](https://rewloy.com/gelistiriciler)
16
+ ile de yapılabilir; bu kütüphane onu Node.js'ten kullanır:
17
+
18
+ - **Tam tipli.** API'nin her işlemi, `operationId` adıyla bir metottur.
19
+ Parametreler, gövdeler ve yanıtlar OpenAPI belgesinden
20
+ ([`openapi.json`](https://app.rewloy.com/v1/openapi.json)) üretilen
21
+ tiplerle gelir. CI belgeyi her gün okur ve değişince yeniden üretir.
22
+ - **Bağımlılıksız.** Node 22 ve üstü; yerleşik `fetch` ve `node:crypto`.
23
+ - **Güvenli tekrar.** Geçici hatalarda ölçülü yeniden deneme; kasa işleminde
24
+ ve kampanyada `Idempotency-Key`.
25
+ - **Ötesi:** sayfalama, canlı akış (SSE), webhook imzası doğrulama,
26
+ kullanımdan kalkma uyarıları.
27
+
28
+ ## Kurulum
29
+
30
+ Node 22 ya da üstü gerekir:
31
+
32
+ ```sh
33
+ npm install @rewloy/node
34
+ ```
35
+
36
+ ## Başlarken
37
+
38
+ ```ts
39
+ import { Rewloy } from '@rewloy/node';
40
+
41
+ const rewloy = new Rewloy({ apiKey: process.env.REWLOY_API_KEY! });
42
+
43
+ const kart = await rewloy.getPass({ params: { serial: 'ABCD-EFGH-JKLM' } });
44
+ console.log(kart.type, kart.balance, kart.rewardReady);
45
+ ```
46
+
47
+ Her işlem, adı `operationId` olan bir metottur
48
+ ([API referansı](https://rewloy.com/gelistiriciler/api)). Tek bir argüman alır,
49
+ işlemin gerektirdikleriyle:
50
+ - `params`: adresteki parametreler (`{serial}`, `{id}`…);
51
+ - `query`: sorgu parametreleri;
52
+ - `body`: JSON gövde;
53
+ - `merchant`: `Rewloy-Merchant` başlığı;
54
+ - `idempotencyKey`: `Idempotency-Key` başlığı (kasa işlemi ve kampanya);
55
+ - `signal`, `timeoutMs`, `maxRetries`.
56
+
57
+ Metot yanıttaki `data`yı döndürür. Sayfalı listelerde `{ data, meta }`,
58
+ gövdesiz yanıtta (`204`) `undefined`, dosyada (QR, harita, CSV, `.pkpass`) bir
59
+ `Blob` döner.
60
+
61
+ Tipler de dışa açıktır: `IssuePassBody`, `GetPassData`, `ListCustomersItem`,
62
+ `ErrorCode`… Hepsi işlem adıyla `Operations` içinde de bulunur.
63
+
64
+ ### Kimlik
65
+
66
+ | İstemci | Ne için |
67
+ |---|---|
68
+ | `new Rewloy({ apiKey: 'rwk_…' })` | API anahtarı: kasa, e-ticaret, kendi sisteminiz |
69
+ | `new Rewloy({ staffSession: 'rws_…', merchant })` | ekip oturumu: bir kişinin işletme uygulaması |
70
+ | `new Rewloy({ holderSession: 'rwh_…' })` | kart sahibi oturumu: Rewloy Cüzdan gibi müşteri uygulamaları |
71
+ | `new Rewloy()` | kimlik istemeyen uç noktalar: giriş, katılım, kod |
72
+
73
+ `merchant`, ekip oturumu birden fazla işletmede koltuk taşıyorsa hangi işletme
74
+ için çalıştığını söyler (`Rewloy-Merchant`). Her çağrıda `merchant` ile
75
+ değiştirilebilir. Oturumlar kimliksiz bir istemciyle açılır:
76
+
77
+ ```ts
78
+ const { token, mfaRequired } = await new Rewloy().login({ body: { email, password } });
79
+ const ekip = new Rewloy({ staffSession: token, merchant: isletmeId });
80
+ if (mfaRequired) await ekip.proveMfa({ body: { code: '123456' } });
81
+ ```
82
+
83
+ Bir işlem istemcinin kimlik türünü kabul etmiyor ama kimliksiz de çalışıyorsa
84
+ (örneğin `login`), istemci onu kimliksiz çağırır. API, işlemin kabul etmediği
85
+ bir kimliği reddeder (`CREDENTIAL_NOT_ALLOWED`).
86
+
87
+ Diğer seçenekler:
88
+ - `baseUrl` (varsayılan `https://app.rewloy.com`);
89
+ - `timeoutMs` (60000);
90
+ - `maxRetries` (2);
91
+ - `fetch`: kendi `fetch`iniz;
92
+ - `userAgent`: gönderilen `User-Agent`a eklenir, örneğin `"KasaPOS/4.2"`.
93
+
94
+ ## Kart vermek ve kasada işlem
95
+
96
+ ```ts
97
+ const { serial, cardUrl } = await rewloy.issuePass({
98
+ body: { programId, email: 'ayse@ornek.com', firstName: 'Ayşe', kvkkConsent: true },
99
+ });
100
+
101
+ const sonuc = await rewloy.passAction({
102
+ params: { serial },
103
+ body: { action: 'earn-stamps', locationId, count: 1 },
104
+ idempotencyKey: `fis-${fisNo}`,
105
+ });
106
+ if (sonuc.duplicate) console.log('Bu fiş zaten işlenmiş');
107
+ ```
108
+
109
+ `passAction` ve `sendCampaign` bir `Idempotency-Key` ister. Verilmezse
110
+ kütüphane bir UUID üretir ve aynı çağrının her denemesinde aynısını gönderir.
111
+ Kasada fiş numarası gibi kendi anahtarınızı vermek daha iyidir: uygulama
112
+ çöküp yeniden başlasa bile aynı fiş ikinci kez işlenmez, aynı anahtarla tekrar
113
+ ilk sonucu `duplicate: true` ile döndürür.
114
+
115
+ ## Sayfalama
116
+
117
+ ```ts
118
+ for await (const musteri of rewloy.paginate('listCustomers', { query: { consent: 'yes', limit: 200 } })) {
119
+ console.log(musteri.displayName, musteri.email);
120
+ }
121
+ ```
122
+
123
+ `paginate` sayfalı her listeyi (`page`/`limit` ve `meta`) öğe öğe dolaşır ve
124
+ son sayfada durur. Tek bir sayfa için metodun kendisi yeter:
125
+ `const { data, meta } = await rewloy.listCustomers({ query: { page: 2 } })`.
126
+
127
+ ## Canlı akış
128
+
129
+ ```ts
130
+ const ac = new AbortController();
131
+ for await (const olay of rewloy.liveFeed({ signal: ac.signal })) {
132
+ if (olay.event === 'event') {
133
+ const { kind, location, program, delta, unit, name } = JSON.parse(olay.data);
134
+ console.log(kind, location, program, delta, unit, name);
135
+ }
136
+ }
137
+ ```
138
+
139
+ `liveFeed` (işletmenin tezgâh akışı) ve `holderCardEvents` (kart sahibinin
140
+ kartındaki değişiklik) sunucu olayları (`text/event-stream`) yayınlar.
141
+ `rewloy.stream('liveFeed', argüman)` aynı işi görür. Her olay `event`, `data`
142
+ ve `id` taşır.
143
+
144
+ - **Yeniden bağlanma.** Bağlantı koparsa akış kendiliğinden yeniden bağlanır:
145
+ sunucunun `retry:` süresi kadar bekler, bir olay `id` taşıdıysa
146
+ `Last-Event-ID` gönderir. `reconnect: false` bunu kapatır.
147
+ - **Sessiz bağlantı.** API 25 saniyede bir `: hb` gönderir; 60 saniye hiç veri
148
+ gelmezse bağlantı kopmuş sayılır (`idleTimeoutMs`).
149
+ - **Durdurmak:** `signal`, döngüden `break` ya da `akis.close()`.
150
+ - **Bitiren hatalar.** Yeniden bağlanmanın düzeltemeyeceği bir hata (`401`,
151
+ `403`, `404`) akışı `RewloyError` ile bitirir.
152
+
153
+ ## Webhook doğrulama
154
+
155
+ Rewloy her teslimi imzalar:
156
+
157
+ ```
158
+ Rewloy-Signature: t=<unix saniye>,v1=<hex HMAC-SHA256(sır, "<t>.<ham gövde>")>
159
+ ```
160
+
161
+ `verifyWebhook` imzayı **ham gövdeyle** ve webhook oluşturulurken bir kez
162
+ gösterilen sırla (`whsec_…`) doğrular:
163
+ - karşılaştırmayı sabit sürede yapar;
164
+ - `t` şimdiden 300 saniyeden (`toleranceSeconds`) uzaksa reddeder;
165
+ - gövdeyi ayrıştırılmış olarak döndürür.
166
+
167
+ Tutmazsa `WebhookSignatureError` atar: 400 ile yanıtlayın ve hiçbir işlem
168
+ yapmayın. Gövde mutlaka ham olmalıdır. JSON olarak ayrıştırılıp yeniden yazılan
169
+ bir gövde imzayı tutturmaz.
170
+
171
+ Express:
172
+
173
+ ```ts
174
+ import express from 'express';
175
+ import { verifyWebhook, WebhookSignatureError } from '@rewloy/node';
176
+
177
+ const app = express();
178
+ app.post('/rewloy/webhook', express.raw({ type: 'application/json' }), (req, res) => {
179
+ let olay;
180
+ try {
181
+ olay = verifyWebhook({
182
+ payload: req.body,
183
+ header: req.get('Rewloy-Signature'),
184
+ secret: process.env.REWLOY_WEBHOOK_SECRET!,
185
+ });
186
+ } catch (err) {
187
+ if (err instanceof WebhookSignatureError) return res.sendStatus(400);
188
+ throw err;
189
+ }
190
+ // Rewloy-Delivery bir teslimin her denemesinde aynıdır: işlediyseniz atlayın.
191
+ if (dahaOnceIslendi(req.get('Rewloy-Delivery'))) return res.sendStatus(200);
192
+ if (olay.type === 'pass.activity') {
193
+ console.log(olay.data.card, olay.data.kind, olay.data.delta);
194
+ }
195
+ res.sendStatus(200);
196
+ });
197
+ ```
198
+
199
+ Fastify (gövde yalnız bu yolda ham kalsın diye ayrı bir kapsamda):
200
+
201
+ ```ts
202
+ app.register(async (scope) => {
203
+ scope.addContentTypeParser('application/json', { parseAs: 'buffer' }, (_req, body, done) => done(null, body));
204
+ scope.post('/rewloy/webhook', async (req, reply) => {
205
+ try {
206
+ const olay = verifyWebhook({
207
+ payload: req.body as Buffer,
208
+ header: req.headers['rewloy-signature'],
209
+ secret: process.env.REWLOY_WEBHOOK_SECRET!,
210
+ });
211
+ // …
212
+ return reply.code(200).send();
213
+ } catch (err) {
214
+ if (err instanceof WebhookSignatureError) return reply.code(400).send();
215
+ throw err;
216
+ }
217
+ });
218
+ });
219
+ ```
220
+
221
+ Başlıklar:
222
+ - `Rewloy-Event`: olay türü (`pass.issued`, `pass.activity`, `pass.voided`,
223
+ `webhook.test`); gövdedeki `type` ile aynı.
224
+ - `Rewloy-Delivery`: teslimin kimliği. Teslim "en az bir kez"dir: çift gelen
225
+ teslimi bununla ayıklayın.
226
+
227
+ Gövde kişinin iletişim bilgisini taşımaz; kişiyi `customer_id` ile API'den
228
+ okuyun. 2xx dışı bir yanıt yaklaşık 45 saat boyunca 8 kez yeniden denenir ve
229
+ her deneme yeni bir `t` ile imzalanır. Kendi işleyicinizi test etmek için
230
+ `signWebhook({ payload, secret })` aynı başlığı üretir.
231
+
232
+ ## Hatalar ve yeniden deneme
233
+
234
+ ```ts
235
+ import { RateLimitError, RewloyError } from '@rewloy/node';
236
+
237
+ try {
238
+ await rewloy.passAction({
239
+ params: { serial },
240
+ body: { action: 'spend', locationId, amountMinor: 5000 },
241
+ idempotencyKey: `fis-${fisNo}`,
242
+ });
243
+ } catch (err) {
244
+ if (err instanceof RateLimitError) console.log(`${err.retryAfter} saniye sonra yeniden deneyin`);
245
+ else if (err instanceof RewloyError && err.code === 'INSUFFICIENT_BALANCE') console.log(err.detail);
246
+ else throw err;
247
+ }
248
+ ```
249
+
250
+ `RewloyError` şunları taşır:
251
+ - `status`: HTTP durumu;
252
+ - `code`: API'nin sabit kodu ([hata kodları](https://rewloy.com/gelistiriciler/hatalar));
253
+ kodunuz buna göre davranmalı;
254
+ - `title`: kodun katalogdaki başlığı;
255
+ - `detail`: API'nin açıklaması (Türkçe, değişebilir);
256
+ - `details`: varsa ayrıntı; doğrulama hatasında `[{ field, rule, message }]`;
257
+ - `requestId`: `x-request-id`; destek talebinde bunu verin;
258
+ - `body`, `headers`, `docs` ve `operation`.
259
+
260
+ Alt sınıflar:
261
+ - `RateLimitError`: `429`; `retryAfter` saniye;
262
+ - `RewloyConnectionError`: yanıt gelmedi (`status` 0, `code`
263
+ `CONNECTION_ERROR`);
264
+ - `RewloyTimeoutError`: zaman aşımı (`TIMEOUT`).
265
+
266
+ Rewloy'un olmayan bir hata gövdesi (örneğin bir vekil sunucunun 502 sayfası)
267
+ `HTTP_502` gibi bir kodla gelir.
268
+
269
+ **Yeniden deneme.** Şunlar en çok `maxRetries` kez (varsayılan 2) yeniden
270
+ denenir: bağlantı hatası, zaman aşımı, `429`, `502`, `503`, `504` ve
271
+ Cloudflare'in `520`–`524` hataları.
272
+ - **Bekleme:** üstel ve rastgele (0,5 sn, 1 sn, 2 sn… en çok 8 sn); yanıt
273
+ `Retry-After` taşıyorsa o kadar. `Retry-After` 60 saniyeden uzunsa
274
+ beklenmez, hata size gelir.
275
+ - **Yalnız tekrarı güvenli istekler:** `GET`, `PUT`, `DELETE` ve
276
+ `Idempotency-Key` taşıyan `POST`. İlk istek hâlâ işlenirken gelen
277
+ `409 IDEMPOTENCY_IN_PROGRESS` de beklenip yeniden denenir. Diğer `POST` ve
278
+ `PATCH` istekleri hiç tekrar edilmez.
279
+ - **Süre:** her deneme `timeoutMs` (varsayılan 60 sn) içinde bitmelidir.
280
+
281
+ ## Kullanımdan kalkma
282
+
283
+ Kalkacak bir uç nokta en az 180 gün önceden duyurulur. O süre boyunca her
284
+ yanıtı `Deprecation`, `Sunset` ve `Link` başlıklarını taşır.
285
+
286
+ - **Uyarı.** Kütüphane her işlem için bir kez `process.emitWarning` ile bir
287
+ `DeprecationWarning` (kodu `REWLOY_DEPRECATED`) yayar. Uyarı işlemi, `Sunset`
288
+ tarihini ve değişiklik günlüğündeki kaydı söyler.
289
+ - **Tipler.** O metot `@deprecated` olarak işaretlenir; editörünüz üstünü
290
+ çizer.
291
+ - **Yönetmek.** Uyarıları kendi kayıtlarınıza almak için
292
+ `process.on('warning', …)`; kapatmak için `node --no-deprecation`.
293
+
294
+ ## Yanıtın tamamı ve test modu
295
+
296
+ ```ts
297
+ const yanit = await rewloy.request('sendCampaign', {
298
+ body: { body: 'Bu hafta kahveler 2 damga!' },
299
+ idempotencyKey: 'kampanya-2026-10-03',
300
+ });
301
+ yanit.status; // 201
302
+ yanit.replayed; // true: aynı anahtarın ilk yanıtı yeniden döndü (Idempotent-Replayed)
303
+ yanit.requestId; // x-request-id
304
+ yanit.mode; // Rewloy-Mode
305
+ yanit.data; // kampanya
306
+ ```
307
+
308
+ `request(işlem, argüman)` her işlemi çağırır ve yanıtın tamamını döndürür:
309
+ `data`, sayfalı listede `meta`, `status`, `headers`, `requestId`, `mode` ve
310
+ `replayed`.
311
+
312
+ `mode`, yanıtın `Rewloy-Mode` başlığıdır. Platformda test modu hazırlanıyor:
313
+ gerçek mesaj göndermeyen, gerçek kart vermeyen test anahtarları. Geldiğinde
314
+ test yanıtları bunu bu başlıkla söyleyecek. Başlık yoksa `null`. Canlı akışta
315
+ aynı bilgi `akis.mode`dadır.
316
+
317
+ İşlem tablosu da dışa açıktır: `OPERATIONS.passAction` →
318
+ `{ method, path, auth, merchant, idempotency, paged, stream, deprecated, … }`.
319
+
320
+ ## Geliştirme
321
+
322
+ ```sh
323
+ npm install
324
+ npm run generate # canlı belgeden: openapi/openapi.json ve src/generated/
325
+ npm run generate -- --file openapi/openapi.json # kayıtlı belgeden
326
+ npm run typecheck && npm run build && npm test
327
+ ```
328
+
329
+ - `src/generated/` elle düzenlenmez; üreteç `scripts/generator.ts`'tir.
330
+ - Testler ağa çıkmaz: yerel bir sahte API ile çalışır. TypeScript'i doğrudan
331
+ çalıştırdıkları için Node 22.18 ya da üstünü ister.
332
+ - CI her gün canlı belgeyi okur ve bir değişiklik varsa bir pull request açar.
333
+ - Kararlar: [docs/DECISIONS.md](docs/DECISIONS.md).
334
+
335
+ ## Belgeler
336
+
337
+ | | |
338
+ |---|---|
339
+ | Başlarken | https://rewloy.com/gelistiriciler |
340
+ | API referansı | https://rewloy.com/gelistiriciler/api |
341
+ | OpenAPI 3.1 | https://app.rewloy.com/v1/openapi.json |
342
+ | Hata kodları | https://rewloy.com/gelistiriciler/hatalar |
343
+ | API'nin değişiklik günlüğü | https://rewloy.com/gelistiriciler/degisiklikler |
344
+ | Bu kütüphanenin değişiklikleri | [CHANGELOG.md](CHANGELOG.md) |
345
+
346
+ **Sürümler:**
347
+ - Kütüphane anlamsal sürümleme ([SemVer](https://semver.org)) kullanır. 1.0'a
348
+ kadar arayüzü değişebilir.
349
+ - API'ye alan eklemek geriye uyumludur; kütüphanenin tipleri her gün
350
+ güncellenir.
351
+ - Kalkacak bir uç nokta en az 180 gün önce duyurulur ve bu süre boyunca
352
+ `Deprecation` ve `Sunset` başlıklarını taşır.
353
+
354
+ ## Güvenlik
355
+
356
+ Bir güvenlik açığı bulursanız [SECURITY.md](SECURITY.md) dosyasındaki yoldan
357
+ özel olarak bildirin. Lütfen herkese açık issue açmayın.
358
+
359
+ ## Lisans
360
+
361
+ [MIT](LICENSE)
362
+
363
+ ---
364
+
365
+ ## English
366
+
367
+ **The official Node.js and TypeScript library for the Rewloy API.**
368
+
369
+ > **Status: preview (0.x), published on npm. The API is stable; the
370
+ > library's interface may change until 1.0.**
371
+
372
+ The documentation of the API itself is in Turkish (links above). In short:
373
+
374
+ - Every operation of the API is a method named by its `operationId`, typed
375
+ from the OpenAPI document, which CI reads daily and regenerates from.
376
+ - No dependencies: Node 22 or later, built-in `fetch` and `node:crypto`.
377
+ - Safe retries, `Idempotency-Key` handling, pagination, server-sent events,
378
+ webhook signature verification and deprecation warnings.
379
+
380
+ ### Install
381
+
382
+ Node 22 or later:
383
+
384
+ ```sh
385
+ npm install @rewloy/node
386
+ ```
387
+
388
+ ### Use
389
+
390
+ ```ts
391
+ import { Rewloy } from '@rewloy/node';
392
+
393
+ const rewloy = new Rewloy({ apiKey: process.env.REWLOY_API_KEY! }); // or { staffSession, merchant } or { holderSession }
394
+
395
+ const { serial } = await rewloy.issuePass({ body: { programId, email, kvkkConsent: true } });
396
+ const result = await rewloy.passAction({
397
+ params: { serial },
398
+ body: { action: 'earn-stamps', locationId },
399
+ idempotencyKey: `receipt-${receiptNo}`, // generated when omitted, reused across retries
400
+ });
401
+ ```
402
+
403
+ - **Arguments.** Each method takes one object: `params`, `query` and `body` as
404
+ the operation needs, plus `merchant`, `idempotencyKey`, `signal`, `timeoutMs`
405
+ and `maxRetries`.
406
+ - **Results.** It resolves to the answer's `data`: `{ data, meta }` for paged
407
+ lists, `undefined` for 204, a `Blob` for files.
408
+ - **The whole answer.** `rewloy.request(id, args)` returns `status`,
409
+ `headers`, `requestId`, `mode` (the `Rewloy-Mode` header, for the coming
410
+ test mode) and `replayed` (`Idempotent-Replayed`).
411
+ - **Pagination.** `rewloy.paginate('listCustomers', args)` iterates the items
412
+ of every page.
413
+ - **Streams.** `rewloy.liveFeed({ signal })` (or `rewloy.stream('liveFeed',
414
+ args)`) iterates server-sent events (`event`, `data`, `id`). It reconnects
415
+ with `Last-Event-ID` unless `reconnect: false`.
416
+
417
+ ### Webhooks
418
+
419
+ Verify the **raw** body (for example `express.raw({ type: 'application/json' })`)
420
+ with the secret shown when the webhook was created:
421
+
422
+ ```ts
423
+ const event = verifyWebhook({ payload: req.body, header: req.get('Rewloy-Signature'), secret });
424
+ ```
425
+
426
+ - **Check.** `Rewloy-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret,
427
+ "<t>.<raw body>")>` is compared in constant time, and `t` must be within
428
+ 300 seconds.
429
+ - **Refusal.** On failure it throws `WebhookSignatureError`: answer 400.
430
+ - **Headers.** `Rewloy-Event` is the event type. `Rewloy-Delivery` is the
431
+ same on every retry of a delivery: deduplicate on it. Delivery is at least
432
+ once.
433
+
434
+ ### Errors, retries, deprecations
435
+
436
+ - **Errors.** Failures throw `RewloyError` with `status`, `code` (the API's
437
+ stable code), `title`, `detail`, `details`, `requestId` and `body`.
438
+ Subclasses: `RateLimitError` (`retryAfter`), `RewloyConnectionError` and
439
+ `RewloyTimeoutError`.
440
+ - **What is retried.** Network errors, timeouts, 429, 502–504 and
441
+ Cloudflare's 520–524, up to `maxRetries` (default 2), with exponential
442
+ backoff and jitter, honouring `Retry-After`.
443
+ - **Only when safe.** Only GET, PUT, DELETE, and POST with an
444
+ `Idempotency-Key`, are retried.
445
+ - **Deprecations.** A deprecated operation's answers carry `Deprecation`,
446
+ `Sunset` and `Link`. The client emits one `DeprecationWarning`
447
+ (`REWLOY_DEPRECATED`) per operation, and the generated method is marked
448
+ `@deprecated`.
449
+
450
+ ### Security and licence
451
+
452
+ Report vulnerabilities privately, as [SECURITY.md](SECURITY.md) says.
453
+ [MIT](LICENSE) licensed.
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The client: credentials, the request (headers, retries, timeouts, errors,
3
+ * deprecation notices), pagination and streams. The operations themselves
4
+ * come from the generated RewloyMethods, one method per operationId.
5
+ */
6
+ import { RewloyMethods } from './generated/methods.js';
7
+ import type { OperationId, Operations, PagedOperationId, StreamOperationId } from './generated/types.js';
8
+ import { EventStream } from './sse.js';
9
+ import type { ApiResponse, AuthKind } from './types.js';
10
+ export declare const DEFAULT_BASE_URL = "https://app.rewloy.com";
11
+ type Credential = Exclude<AuthKind, 'public'>;
12
+ type Sleep = (ms: number, signal?: AbortSignal) => Promise<void>;
13
+ interface CommonOptions {
14
+ /** The API's origin, without `/v1`. Default `https://app.rewloy.com`. */
15
+ baseUrl?: string | undefined;
16
+ /** Time allowed for one attempt, in milliseconds; `0` or `Infinity` for none. Default 60000. */
17
+ timeoutMs?: number | undefined;
18
+ /** Retries after a failed attempt, when retrying is safe. Default 2. */
19
+ maxRetries?: number | undefined;
20
+ /** A `fetch` to use instead of the global one (tests, proxies, instrumentation). */
21
+ fetch?: typeof fetch | undefined;
22
+ /** Added to the `User-Agent` this client sends, e.g. `"KasaPOS/4.2"`. */
23
+ userAgent?: string | undefined;
24
+ /** Replaces the wait between retries (tests, custom schedulers). */
25
+ sleep?: Sleep | undefined;
26
+ }
27
+ /**
28
+ * How to build a client: with one credential, or none for the endpoints that
29
+ * need none (sign-in, joining a programme…).
30
+ */
31
+ export type RewloyOptions = CommonOptions & ({
32
+ /** An API key, `rwk_…`: a till, a shop, your own system. */
33
+ apiKey: string;
34
+ staffSession?: undefined;
35
+ holderSession?: undefined;
36
+ merchant?: undefined;
37
+ } | {
38
+ /** A staff session, `rws_…` (`login`): a person's business app. */
39
+ staffSession: string;
40
+ /** The business this session acts for (`Rewloy-Merchant`), when the person has seats in several. */
41
+ merchant?: string | undefined;
42
+ apiKey?: undefined;
43
+ holderSession?: undefined;
44
+ } | {
45
+ /** A card holder's session, `rwh_…` (`holderSession`): a Rewloy Cüzdan app. */
46
+ holderSession: string;
47
+ apiKey?: undefined;
48
+ staffSession?: undefined;
49
+ merchant?: undefined;
50
+ } | {
51
+ apiKey?: undefined;
52
+ staffSession?: undefined;
53
+ holderSession?: undefined;
54
+ merchant?: undefined;
55
+ });
56
+ /** The argument of an operation: optional when nothing in it is required. */
57
+ type ArgsParam<A> = object extends A ? [args?: A] : [args: A];
58
+ type ItemOf<K extends PagedOperationId> = Operations[K]['data'] extends (infer T)[] ? T : never;
59
+ /** `Retry-After` in milliseconds: delta-seconds or an HTTP date. */
60
+ export declare function parseRetryAfter(value: string | null, now?: number): number | null;
61
+ /** Exponential backoff with jitter for the retry after attempt `attempt` (0-based). */
62
+ export declare function backoff(attempt: number, random?: () => number): number;
63
+ /**
64
+ * A client of the Rewloy API (`https://app.rewloy.com/v1`).
65
+ *
66
+ * ```ts
67
+ * const rewloy = new Rewloy({ apiKey: process.env.REWLOY_API_KEY! });
68
+ * const card = await rewloy.getPass({ params: { serial: 'ABCD-EFGH-JKLM' } });
69
+ * ```
70
+ *
71
+ * Every operation of the API is a method named by its operationId; each takes
72
+ * one argument with `params`, `query` and `body` as the operation needs, and
73
+ * the options of {@link RequestOptions}.
74
+ */
75
+ export declare class Rewloy extends RewloyMethods {
76
+ #private;
77
+ readonly baseUrl: string;
78
+ readonly timeoutMs: number;
79
+ readonly maxRetries: number;
80
+ /** The kind of credential this client sends, or `null` for none. */
81
+ readonly credential: Credential | null;
82
+ /** The default `Rewloy-Merchant` of a staff session. */
83
+ readonly merchant: string | null;
84
+ constructor(options?: RewloyOptions);
85
+ /**
86
+ * Calls an operation and returns the whole answer: `data`, `meta` on paged
87
+ * lists, the status, headers, `requestId`, `mode` and `replayed`.
88
+ *
89
+ * ```ts
90
+ * const res = await rewloy.request('sendCampaign', { body: { body: 'Bu hafta kahveler 2 damga!' } });
91
+ * res.status; res.replayed; res.data.id;
92
+ * ```
93
+ */
94
+ request<K extends Exclude<OperationId, StreamOperationId>>(id: K, ...args: ArgsParam<Operations[K]['args']>): Promise<ApiResponse<Operations[K]['data']>>;
95
+ /**
96
+ * Walks a paged list item by item, asking for the next page (`page`) while
97
+ * `meta` says there is one. `query.page` sets where to start and
98
+ * `query.limit` the page size.
99
+ *
100
+ * ```ts
101
+ * for await (const customer of rewloy.paginate('listCustomers', { query: { consent: 'yes' } })) { … }
102
+ * ```
103
+ */
104
+ paginate<K extends PagedOperationId>(id: K, ...args: ArgsParam<Operations[K]['args']>): AsyncIterableIterator<ItemOf<K>>;
105
+ /**
106
+ * Opens a server-sent event stream (`liveFeed`, `holderCardEvents`) and
107
+ * iterates its events. It reconnects by itself unless `reconnect: false`;
108
+ * stop it with `signal`, `break` or `close()`.
109
+ *
110
+ * ```ts
111
+ * for await (const ev of rewloy.stream('liveFeed', { signal })) {
112
+ * if (ev.event === 'event') console.log(JSON.parse(ev.data));
113
+ * }
114
+ * ```
115
+ */
116
+ stream<K extends StreamOperationId>(id: K, ...args: ArgsParam<Operations[K]['args']>): EventStream;
117
+ protected _call<K extends Exclude<OperationId, StreamOperationId>>(id: K, args: Operations[K]['args'] | undefined): Promise<Operations[K]['result']>;
118
+ protected _open<K extends StreamOperationId>(id: K, args: Operations[K]['args'] | undefined): EventStream;
119
+ }
120
+ export {};