@rewloy/node 0.2.0 → 0.2.1

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 CHANGED
@@ -5,6 +5,32 @@ https://rewloy.com/gelistiriciler/degisiklikler
5
5
 
6
6
  This library's releases. The API's own changes are listed at the link above.
7
7
 
8
+ ## 0.2.1 (2026-10-05)
9
+
10
+ Dışarıdan geliştiricilerin bulduğu üç sorun düzeltildi.
11
+
12
+ Three problems found by outside developers, fixed.
13
+
14
+ - **`Idempotency-Key` is checked before sending.** A key with non-ASCII
15
+ characters (`fiş-0042`) made `fetch` throw a bare `TypeError` about the header
16
+ value. Now the client refuses any key that is not printable ASCII
17
+ (0x21–0x7E), 8–64 characters, with a clear `TypeError` ("Idempotency-Key
18
+ yalnız ASCII karakterler içerebilir …") and sends nothing. The API will also
19
+ answer `400 VALIDATION` for such a key in its next release.
20
+ - **`baseUrl` takes the address with or without `/v1`.** The documentation and
21
+ the OpenAPI document show `https://app.rewloy.com/v1`; the client wanted the
22
+ origin only. Now both work; a trailing `/v1` or `/v1/` and trailing slashes
23
+ are stripped (`rewloy.baseUrl` is the origin).
24
+ - **`idempotencyKey` is required where the API requires it.** For `recordSale`,
25
+ `passAction`, `sendCampaign` and `refundShopRedemption` the OpenAPI document
26
+ marks the header required, but the client made up a random UUID when it was
27
+ missing, which does not survive a restart of your app. `idempotencyKey` is now
28
+ a required argument of those methods (a type error in TypeScript, a
29
+ `TypeError` before sending in JavaScript). Where the header is optional
30
+ (`issuePass`, …) a UUID is still generated and reused on every retry.
31
+ **Breaking for callers that relied on the generated key** (a small break,
32
+ taken in a patch release because the old behaviour could write a sale twice).
33
+
8
34
  ## 0.2.0 (2026-10-05)
9
35
 
10
36
  Rewloy API 1.0.5'e göre yeniden üretildi: 255 işlem (0.1.0'da 237). Kasa için
package/README.md CHANGED
@@ -52,7 +52,7 @@ işlemin gerektirdikleriyle:
52
52
  - `query`: sorgu parametreleri;
53
53
  - `body`: JSON gövde;
54
54
  - `merchant`: `Rewloy-Merchant` başlığı;
55
- - `idempotencyKey`: `Idempotency-Key` başlığı (satış, kasa işlemi ve kampanya);
55
+ - `idempotencyKey`: `Idempotency-Key` başlığı (satış, kasa işlemi, kampanya ve mağaza iadesinde zorunlu);
56
56
  - `signal`, `timeoutMs`, `maxRetries`.
57
57
 
58
58
  Metot yanıttaki `data`yı döndürür. Sayfalı listelerde `{ data, meta }`,
@@ -86,7 +86,7 @@ Bir işlem istemcinin kimlik türünü kabul etmiyor ama kimliksiz de çalışı
86
86
  bir kimliği reddeder (`CREDENTIAL_NOT_ALLOWED`).
87
87
 
88
88
  Diğer seçenekler:
89
- - `baseUrl` (varsayılan `https://app.rewloy.com`; `/v1` olmadan, kütüphane ekler);
89
+ - `baseUrl` (varsayılan `https://app.rewloy.com`; sonuna `/v1` eklemeniz ya da eklememeniz fark etmez: `https://app.rewloy.com/v1` de olur, kütüphane `/v1`i kendisi ekler);
90
90
  - `timeoutMs` (60000);
91
91
  - `maxRetries` (2);
92
92
  - `fetch`: kendi `fetch`iniz;
@@ -100,7 +100,7 @@ API'nin başka bir kopyasına (kendi staging ortamınız ya da bir vekil sunucu)
100
100
  ```ts
101
101
  const rewloy = new Rewloy({
102
102
  apiKey: process.env.REWLOY_API_KEY!,
103
- baseUrl: 'https://rewloy-staging.ornek.com', // /v1 olmadan
103
+ baseUrl: 'https://rewloy-staging.ornek.com', // sonuna /v1 yazsanız da olur
104
104
  });
105
105
  ```
106
106
 
@@ -175,11 +175,19 @@ hiçbir şey yazılmaz.
175
175
 
176
176
  ### `Idempotency-Key`
177
177
 
178
- `recordSale`, `passAction` ve `sendCampaign` bir `Idempotency-Key` ister.
179
- Verilmezse kütüphane bir UUID üretir ve aynı çağrının her denemesinde aynısını
180
- gönderir; ama uygulama çöküp yeniden başlarsa yeni bir anahtar üretilir ve
181
- satış ikinci kez yazılabilir. Kasada anahtarı kendiniz üretip satışla birlikte
182
- saklayın:
178
+ `recordSale`, `passAction`, `sendCampaign` ve `refundShopRedemption` bir
179
+ `Idempotency-Key` **ister**: API'nin tanımında (OpenAPI) bu başlık bu işlemlerde
180
+ zorunludur, bu yüzden `idempotencyKey` bu metotlarda zorunlu bir argümandır.
181
+ Verilmezse kütüphane istek göndermeden `TypeError` fırlatır; **sizin yerinize
182
+ anahtar üretmez**. Üretilmiş rastgele bir anahtar yalnızca tek çağrının yeniden
183
+ denemelerini korurdu: uygulama çöküp yeniden başlarsa yeni bir anahtar çıkar ve
184
+ satış ikinci kez yazılabilirdi. Anahtarı kendiniz üretip satışla birlikte
185
+ saklayın. Anahtar 8–64 karakterlik görünür ASCII olmalıdır (0x21–0x7E: harf,
186
+ rakam ve noktalama; boşluk, Türkçe harf ya da `fiş` gibi ASCII dışı karakter
187
+ olmaz); aksi halde kütüphane yine istek göndermeden `TypeError` fırlatır.
188
+ Başlığın isteğe bağlı olduğu işlemlerde (örneğin `issuePass`) anahtar verilmezse
189
+ kütüphane bir UUID üretir ve aynı çağrının her denemesinde aynısını gönderir.
190
+
183
191
  - **Anahtar bir kimlik için kalıcı olarak tekildir** (8–64 karakter; defterden
184
192
  hiç silinmez). Aynı anahtarla aynı isteğin tekrarı ikinci kez yazmaz ve
185
193
  ilk sonucu `duplicate: true` ile döndürür. Aynı anahtar başka bir gövdeyle
@@ -522,14 +530,23 @@ const sale = await rewloy.recordSale({
522
530
  card's structured fields (`programName`, `currency`, `stamps`, `points`,
523
531
  `money`, `customer`); `reverseSale` takes a refunded sale back:
524
532
  `rewloy.reverseSale({ params: { serial }, body: { saleKey: key } })`.
525
- - **Idempotency keys.** `recordSale`, `passAction` and `sendCampaign` need an
526
- `Idempotency-Key`. A key is unique **for good per credential**: do not use the
533
+ - **Idempotency keys.** `recordSale`, `passAction`, `sendCampaign` and
534
+ `refundShopRedemption` need an `Idempotency-Key`: the API's OpenAPI document
535
+ marks the header required for them, so `idempotencyKey` is a required
536
+ argument and the client throws a `TypeError` before sending if it is missing.
537
+ It never makes one up for you (a generated key would not survive a restart of
538
+ your app). The key must be 8–64 printable ASCII characters (0x21–0x7E); a
539
+ non-ASCII key such as `fiş-0042` is refused client-side, with a `TypeError`,
540
+ before anything is sent. Where the header is optional (for example
541
+ `issuePass`) the client still generates a UUID and reuses it on every retry
542
+ of the call. A key is unique **for good per credential**: do not use the
527
543
  receipt number alone (fiscal receipt numbers restart after the Z report) but
528
544
  register + Z number + receipt number, or a UUID stored with the sale. The
529
- receipt number goes in `reference`. A generated key only covers the retries
530
- of one call, not a restart of your app.
545
+ receipt number goes in `reference`.
531
546
  - **Base URL.** `new Rewloy({ apiKey, baseUrl: 'https://staging.example.com' })`
532
- (the origin, without `/v1`). Default `https://app.rewloy.com`.
547
+ or `baseUrl: 'https://staging.example.com/v1'`: with or without a trailing
548
+ `/v1` (and trailing slashes), the client appends `/v1/...` itself. Default
549
+ `https://app.rewloy.com`.
533
550
  - **Test mode.** Open the test environment (panel → Developer, or
534
551
  `POST /v1/test/environment`) and use its `rwk_test_` key at the same address:
535
552
  a separate test business that sends nothing and never reaches real
package/dist/client.d.ts CHANGED
@@ -60,6 +60,17 @@ type ItemOf<K extends PagedOperationId> = Operations[K]['data'] extends (infer T
60
60
  export declare function parseRetryAfter(value: string | null, now?: number): number | null;
61
61
  /** Exponential backoff with jitter for the retry after attempt `attempt` (0-based). */
62
62
  export declare function backoff(attempt: number, random?: () => number): number;
63
+ /**
64
+ * The base URL without trailing slashes and without a trailing `/v1`: the
65
+ * paths of the operations carry `/v1` themselves, and the documentation shows
66
+ * the address both ways (`https://app.rewloy.com` and `https://app.rewloy.com/v1`).
67
+ */
68
+ export declare function normalizeBaseUrl(url: string): string;
69
+ /**
70
+ * An `Idempotency-Key` is 8–64 printable ASCII characters (0x21–0x7E): an HTTP
71
+ * header value cannot carry anything else, and `fetch` would throw a bare TypeError.
72
+ */
73
+ export declare function checkIdempotencyKey(key: unknown): string;
63
74
  /**
64
75
  * A client of the Rewloy API (`https://app.rewloy.com/v1`).
65
76
  *
package/dist/client.js CHANGED
@@ -56,6 +56,24 @@ export function backoff(attempt, random = Math.random) {
56
56
  const cap = Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * 2 ** attempt);
57
57
  return Math.round(cap / 2 + random() * (cap / 2));
58
58
  }
59
+ /**
60
+ * The base URL without trailing slashes and without a trailing `/v1`: the
61
+ * paths of the operations carry `/v1` themselves, and the documentation shows
62
+ * the address both ways (`https://app.rewloy.com` and `https://app.rewloy.com/v1`).
63
+ */
64
+ export function normalizeBaseUrl(url) {
65
+ return url.replace(/\/+$/, '').replace(/\/v1$/, '').replace(/\/+$/, '');
66
+ }
67
+ /**
68
+ * An `Idempotency-Key` is 8–64 printable ASCII characters (0x21–0x7E): an HTTP
69
+ * header value cannot carry anything else, and `fetch` would throw a bare TypeError.
70
+ */
71
+ export function checkIdempotencyKey(key) {
72
+ if (typeof key !== 'string' || !/^[\x21-\x7e]{8,64}$/.test(key)) {
73
+ throw new TypeError('Rewloy: Idempotency-Key yalnız ASCII karakterler içerebilir (görünür karakterler, 8–64) / the Idempotency-Key must be printable ASCII (0x21–0x7E), 8–64 characters');
74
+ }
75
+ return key;
76
+ }
59
77
  /** The URL a `Link` header gives for `rel="deprecation"` (else its first). */
60
78
  function deprecationLink(link) {
61
79
  if (!link)
@@ -135,7 +153,7 @@ export class Rewloy extends RewloyMethods {
135
153
  if (options.merchant !== undefined && which !== 'staffSession')
136
154
  throw new TypeError('Rewloy: `merchant` goes with a staffSession');
137
155
  this.merchant = options.merchant ?? null;
138
- this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
156
+ this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);
139
157
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
140
158
  this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
141
159
  const f = options.fetch ?? globalThis.fetch;
@@ -251,7 +269,7 @@ export class Rewloy extends RewloyMethods {
251
269
  const qs = query.toString();
252
270
  return `${this.baseUrl}${path}${qs ? `?${qs}` : ''}`;
253
271
  }
254
- #headers(op, a, lastEventId) {
272
+ #headers(id, op, a, lastEventId) {
255
273
  const h = new Headers();
256
274
  h.set('accept', op.stream ? 'text/event-stream' : op.response === 'json' || op.response === 'raw-json' ? 'application/json' : '*/*');
257
275
  if (this.#userAgent)
@@ -264,15 +282,21 @@ export class Rewloy extends RewloyMethods {
264
282
  const merchant = a.merchant ?? (this.credential === 'staff' ? this.merchant : null);
265
283
  if (op.merchant && merchant)
266
284
  h.set('rewloy-merchant', merchant);
267
- if (op.idempotency)
268
- h.set('idempotency-key', a.idempotencyKey ?? randomUUID());
285
+ if (op.idempotency) {
286
+ if (a.idempotencyKey === undefined && op.idempotency === 'required') {
287
+ throw new TypeError(`Rewloy: ${id} needs idempotencyKey: Idempotency-Key gerekli, kütüphane uydurmaz (8–64 ASCII karakter) / the Idempotency-Key is required and is never generated for you (8–64 printable ASCII characters)`);
288
+ }
289
+ h.set('idempotency-key', a.idempotencyKey === undefined ? randomUUID() : checkIdempotencyKey(a.idempotencyKey));
290
+ }
269
291
  if (op.body)
270
292
  h.set('content-type', 'application/json');
271
293
  if (lastEventId)
272
294
  h.set('last-event-id', lastEventId);
273
- for (const [k, v] of Object.entries(a.headers ?? {}))
274
- if (v !== undefined && v !== null)
275
- h.set(k, String(v));
295
+ for (const [k, v] of Object.entries(a.headers ?? {})) {
296
+ if (v === undefined || v === null)
297
+ continue;
298
+ h.set(k, k.toLowerCase() === 'idempotency-key' ? checkIdempotencyKey(v) : String(v));
299
+ }
276
300
  return h;
277
301
  }
278
302
  #notice(id, op, headers) {
@@ -290,7 +314,7 @@ export class Rewloy extends RewloyMethods {
290
314
  async #exchange(id, op, args, stream) {
291
315
  const a = args ?? {};
292
316
  const url = this.#url(id, op, a);
293
- const headers = this.#headers(op, a, stream?.lastEventId);
317
+ const headers = this.#headers(id, op, a, stream?.lastEventId);
294
318
  const body = op.body ? JSON.stringify(a.body ?? {}) : undefined;
295
319
  const retryable = IDEMPOTENT_METHODS.has(op.method) || headers.has('idempotency-key');
296
320
  const maxRetries = Math.max(0, a.maxRetries ?? this.maxRetries);
@@ -80,7 +80,7 @@ export interface IssuePassArgs extends RequestOptions {
80
80
  /**
81
81
  * Aynı işlemin iki kez yapılmasını önler: aynı anahtarla tekrar, ilk sonucu döndürür.
82
82
  *
83
- * `Idempotency-Key`. When omitted, the client generates a UUID and sends the same one on every retry of this call.
83
+ * `Idempotency-Key`: 8–64 printable ASCII characters. When omitted, the client generates a UUID and sends the same one on every retry of this call.
84
84
  */
85
85
  idempotencyKey?: string | undefined;
86
86
  /**
@@ -256,9 +256,9 @@ export interface PassActionArgs extends RequestOptions {
256
256
  /**
257
257
  * Aynı işlemin iki kez yapılmasını önler: aynı anahtarla tekrar, ilk sonucu döndürür.
258
258
  *
259
- * `Idempotency-Key`. When omitted, the client generates a UUID and sends the same one on every retry of this call.
259
+ * `Idempotency-Key`, required: 8–64 printable ASCII characters. The client never makes one up (a generated key would not survive a restart of your app); it sends this one on every retry of the call.
260
260
  */
261
- idempotencyKey?: string | undefined;
261
+ idempotencyKey: string;
262
262
  /**
263
263
  * Ekip oturumu birden fazla işletmede koltuk taşıyorsa hangi işletme için olduğu (tek işletmede gerekmez).
264
264
  *
@@ -320,9 +320,9 @@ export interface RecordSaleArgs extends RequestOptions {
320
320
  /**
321
321
  * Aynı işlemin iki kez yapılmasını önler: aynı anahtarla tekrar, ilk sonucu döndürür.
322
322
  *
323
- * `Idempotency-Key`. When omitted, the client generates a UUID and sends the same one on every retry of this call.
323
+ * `Idempotency-Key`, required: 8–64 printable ASCII characters. The client never makes one up (a generated key would not survive a restart of your app); it sends this one on every retry of the call.
324
324
  */
325
- idempotencyKey?: string | undefined;
325
+ idempotencyKey: string;
326
326
  /**
327
327
  * Ekip oturumu birden fazla işletmede koltuk taşıyorsa hangi işletme için olduğu (tek işletmede gerekmez).
328
328
  *
@@ -770,7 +770,7 @@ export interface HolderLoginArgs extends RequestOptions {
770
770
  /**
771
771
  * Aynı işlemin iki kez yapılmasını önler: aynı anahtarla tekrar, ilk sonucu döndürür.
772
772
  *
773
- * `Idempotency-Key`. When omitted, the client generates a UUID and sends the same one on every retry of this call.
773
+ * `Idempotency-Key`: 8–64 printable ASCII characters. When omitted, the client generates a UUID and sends the same one on every retry of this call.
774
774
  */
775
775
  idempotencyKey?: string | undefined;
776
776
  }
@@ -4114,9 +4114,9 @@ export interface SendCampaignArgs extends RequestOptions {
4114
4114
  /**
4115
4115
  * Aynı işlemin iki kez yapılmasını önler: aynı anahtarla tekrar, ilk sonucu döndürür.
4116
4116
  *
4117
- * `Idempotency-Key`. When omitted, the client generates a UUID and sends the same one on every retry of this call.
4117
+ * `Idempotency-Key`, required: 8–64 printable ASCII characters. The client never makes one up (a generated key would not survive a restart of your app); it sends this one on every retry of the call.
4118
4118
  */
4119
- idempotencyKey?: string | undefined;
4119
+ idempotencyKey: string;
4120
4120
  /**
4121
4121
  * Ekip oturumu birden fazla işletmede koltuk taşıyorsa hangi işletme için olduğu (tek işletmede gerekmez).
4122
4122
  *
@@ -7738,9 +7738,9 @@ export interface RefundShopRedemptionArgs extends RequestOptions {
7738
7738
  /**
7739
7739
  * Aynı işlemin iki kez yapılmasını önler: aynı anahtarla tekrar, ilk sonucu döndürür.
7740
7740
  *
7741
- * `Idempotency-Key`. When omitted, the client generates a UUID and sends the same one on every retry of this call.
7741
+ * `Idempotency-Key`, required: 8–64 printable ASCII characters. The client never makes one up (a generated key would not survive a restart of your app); it sends this one on every retry of the call.
7742
7742
  */
7743
- idempotencyKey?: string | undefined;
7743
+ idempotencyKey: string;
7744
7744
  /**
7745
7745
  * Ekip oturumu birden fazla işletmede koltuk taşıyorsa hangi işletme için olduğu (tek işletmede gerekmez).
7746
7746
  *
@@ -10122,7 +10122,7 @@ export interface AddHolderEmailArgs extends RequestOptions {
10122
10122
  /**
10123
10123
  * Aynı işlemin iki kez yapılmasını önler: aynı anahtarla tekrar, ilk sonucu döndürür.
10124
10124
  *
10125
- * `Idempotency-Key`. When omitted, the client generates a UUID and sends the same one on every retry of this call.
10125
+ * `Idempotency-Key`: 8–64 printable ASCII characters. When omitted, the client generates a UUID and sends the same one on every retry of this call.
10126
10126
  */
10127
10127
  idempotencyKey?: string | undefined;
10128
10128
  }
@@ -10169,7 +10169,7 @@ export interface AddHolderPhoneArgs extends RequestOptions {
10169
10169
  /**
10170
10170
  * Aynı işlemin iki kez yapılmasını önler: aynı anahtarla tekrar, ilk sonucu döndürür.
10171
10171
  *
10172
- * `Idempotency-Key`. When omitted, the client generates a UUID and sends the same one on every retry of this call.
10172
+ * `Idempotency-Key`: 8–64 printable ASCII characters. When omitted, the client generates a UUID and sends the same one on every retry of this call.
10173
10173
  */
10174
10174
  idempotencyKey?: string | undefined;
10175
10175
  }
@@ -10268,7 +10268,7 @@ export interface ReplaceHolderIdentityArgs extends RequestOptions {
10268
10268
  /**
10269
10269
  * Aynı işlemin iki kez yapılmasını önler: aynı anahtarla tekrar, ilk sonucu döndürür.
10270
10270
  *
10271
- * `Idempotency-Key`. When omitted, the client generates a UUID and sends the same one on every retry of this call.
10271
+ * `Idempotency-Key`: 8–64 printable ASCII characters. When omitted, the client generates a UUID and sends the same one on every retry of this call.
10272
10272
  */
10273
10273
  idempotencyKey?: string | undefined;
10274
10274
  }
@@ -10764,7 +10764,7 @@ export interface StartHolderRecoveryArgs extends RequestOptions {
10764
10764
  /**
10765
10765
  * Aynı işlemin iki kez yapılmasını önler: aynı anahtarla tekrar, ilk sonucu döndürür.
10766
10766
  *
10767
- * `Idempotency-Key`. When omitted, the client generates a UUID and sends the same one on every retry of this call.
10767
+ * `Idempotency-Key`: 8–64 printable ASCII characters. When omitted, the client generates a UUID and sends the same one on every retry of this call.
10768
10768
  */
10769
10769
  idempotencyKey?: string | undefined;
10770
10770
  }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** This library's version (package.json's; test/client.test.ts keeps them equal). */
2
- export declare const VERSION = "0.2.0";
2
+ export declare const VERSION = "0.2.1";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  /** This library's version (package.json's; test/client.test.ts keeps them equal). */
2
- export const VERSION = '0.2.0';
2
+ export const VERSION = '0.2.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rewloy/node",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Rewloy API için resmî Node.js ve TypeScript kütüphanesi / The official Node.js and TypeScript library for the Rewloy API",
5
5
  "license": "MIT",
6
6
  "author": "Rewloy",