@lonca/core 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/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/index.cjs +298 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +171 -0
- package/dist/index.d.ts +171 -0
- package/dist/index.js +271 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lonca contributors
|
|
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,37 @@
|
|
|
1
|
+
# @lonca/core
|
|
2
|
+
|
|
3
|
+
Shared primitives for Lonca marketplace SDKs.
|
|
4
|
+
|
|
5
|
+
> Type-safe building blocks reused across every `@lonca/<marketplace>` SDK: money, errors, pagination, retry, logger, rate limiter.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @lonca/core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## What's inside
|
|
14
|
+
|
|
15
|
+
| Primitive | Purpose |
|
|
16
|
+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `Money`, `Currency` | ISO 4217 currency codes; integer minor-unit money representation |
|
|
18
|
+
| `CursorPage`, `paginate` | Cursor-based pagination + async iterator helper |
|
|
19
|
+
| `LoncaError` hierarchy | Structured errors (`AuthError`, `RateLimitError`, `ValidationError`, `NotFoundError`, `ServerError`, `NetworkError`, `TimeoutError`) with `retryable` flag |
|
|
20
|
+
| `retry` | Exponential backoff with jitter, honors `retryAfterMs`, supports `AbortSignal` |
|
|
21
|
+
| `Logger` | Structured logger interface (`debug`/`info`/`warn`/`error`/`child`) with `noopLogger` and `consoleLogger` |
|
|
22
|
+
| `TokenBucketRateLimiter` | Async token-bucket rate limiter with `AbortSignal` support |
|
|
23
|
+
|
|
24
|
+
## Design principles
|
|
25
|
+
|
|
26
|
+
- **Money is an integer in minor units** — `{ amount: 12550, currency: 'TRY' }` means 125.50 TRY. No floats, no surprises.
|
|
27
|
+
- **Cursor pagination over offset** — opaque cursors compose better with async iterators and stay stable as datasets grow.
|
|
28
|
+
- **Errors are typed and tagged** — `err.code` plus a `retryable` boolean tells retry helpers what to do without sniffing messages.
|
|
29
|
+
- **Logger is an interface, not an implementation** — wire your own (pino, winston, console). A no-op default is provided.
|
|
30
|
+
|
|
31
|
+
## Stability
|
|
32
|
+
|
|
33
|
+
`0.x` — alpha. Public APIs may change between minor versions until `1.0.0`.
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/currency.ts
|
|
4
|
+
var TRY = "TRY";
|
|
5
|
+
var USD = "USD";
|
|
6
|
+
var EUR = "EUR";
|
|
7
|
+
var GBP = "GBP";
|
|
8
|
+
var ISO_4217_RE = /^[A-Z]{3}$/;
|
|
9
|
+
function isValidCurrencyCode(code) {
|
|
10
|
+
return ISO_4217_RE.test(code);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/money.ts
|
|
14
|
+
function money(amount, currency) {
|
|
15
|
+
if (!Number.isInteger(amount)) {
|
|
16
|
+
throw new TypeError(`Money amount must be an integer minor unit, got ${amount}`);
|
|
17
|
+
}
|
|
18
|
+
return { amount, currency };
|
|
19
|
+
}
|
|
20
|
+
function moneyFromMajor(major, currency, minorScale = 2) {
|
|
21
|
+
const factor = 10 ** minorScale;
|
|
22
|
+
const amount = Math.round(major * factor);
|
|
23
|
+
return money(amount, currency);
|
|
24
|
+
}
|
|
25
|
+
function moneyToMajor(value, minorScale = 2) {
|
|
26
|
+
return value.amount / 10 ** minorScale;
|
|
27
|
+
}
|
|
28
|
+
function isSameCurrency(a, b) {
|
|
29
|
+
return a.currency === b.currency;
|
|
30
|
+
}
|
|
31
|
+
function addMoney(a, b) {
|
|
32
|
+
if (!isSameCurrency(a, b)) {
|
|
33
|
+
throw new TypeError(`Cannot add Money of different currencies: ${a.currency} vs ${b.currency}`);
|
|
34
|
+
}
|
|
35
|
+
return money(a.amount + b.amount, a.currency);
|
|
36
|
+
}
|
|
37
|
+
function subMoney(a, b) {
|
|
38
|
+
if (!isSameCurrency(a, b)) {
|
|
39
|
+
throw new TypeError(
|
|
40
|
+
`Cannot subtract Money of different currencies: ${a.currency} vs ${b.currency}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return money(a.amount - b.amount, a.currency);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/pagination.ts
|
|
47
|
+
async function* paginate(fetchPage, params = {}) {
|
|
48
|
+
let cursor;
|
|
49
|
+
do {
|
|
50
|
+
const page = await fetchPage({ ...params, cursor });
|
|
51
|
+
for (const item of page.items) {
|
|
52
|
+
yield item;
|
|
53
|
+
}
|
|
54
|
+
cursor = page.nextCursor;
|
|
55
|
+
} while (cursor);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/errors.ts
|
|
59
|
+
var LoncaError = class extends Error {
|
|
60
|
+
code;
|
|
61
|
+
retryable;
|
|
62
|
+
status;
|
|
63
|
+
retryAfterMs;
|
|
64
|
+
data;
|
|
65
|
+
constructor(opts) {
|
|
66
|
+
super(opts.message, opts.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
67
|
+
this.name = "LoncaError";
|
|
68
|
+
this.code = opts.code;
|
|
69
|
+
this.retryable = opts.retryable ?? false;
|
|
70
|
+
this.status = opts.status;
|
|
71
|
+
this.retryAfterMs = opts.retryAfterMs;
|
|
72
|
+
this.data = opts.data;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var AuthError = class extends LoncaError {
|
|
76
|
+
constructor(opts) {
|
|
77
|
+
super({ ...opts, code: "AUTH_FAILED", retryable: false });
|
|
78
|
+
this.name = "AuthError";
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var RateLimitError = class extends LoncaError {
|
|
82
|
+
constructor(opts) {
|
|
83
|
+
super({ ...opts, code: "RATE_LIMITED", retryable: true });
|
|
84
|
+
this.name = "RateLimitError";
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var ValidationError = class extends LoncaError {
|
|
88
|
+
constructor(opts) {
|
|
89
|
+
super({ ...opts, code: "VALIDATION_FAILED", retryable: false });
|
|
90
|
+
this.name = "ValidationError";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var NotFoundError = class extends LoncaError {
|
|
94
|
+
constructor(opts) {
|
|
95
|
+
super({ ...opts, code: "NOT_FOUND", retryable: false });
|
|
96
|
+
this.name = "NotFoundError";
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var ServerError = class extends LoncaError {
|
|
100
|
+
constructor(opts) {
|
|
101
|
+
super({ ...opts, code: "SERVER_ERROR", retryable: true });
|
|
102
|
+
this.name = "ServerError";
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var NetworkError = class extends LoncaError {
|
|
106
|
+
constructor(opts) {
|
|
107
|
+
super({ ...opts, code: "NETWORK_ERROR", retryable: true });
|
|
108
|
+
this.name = "NetworkError";
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var TimeoutError = class extends LoncaError {
|
|
112
|
+
constructor(opts) {
|
|
113
|
+
super({ ...opts, code: "TIMEOUT", retryable: true });
|
|
114
|
+
this.name = "TimeoutError";
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
function isLoncaError(value) {
|
|
118
|
+
return value instanceof LoncaError;
|
|
119
|
+
}
|
|
120
|
+
function isRetryableError(value) {
|
|
121
|
+
return isLoncaError(value) && value.retryable;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/retry.ts
|
|
125
|
+
var DEFAULTS = {
|
|
126
|
+
maxAttempts: 3,
|
|
127
|
+
baseDelayMs: 200,
|
|
128
|
+
maxDelayMs: 1e4,
|
|
129
|
+
jitter: true
|
|
130
|
+
};
|
|
131
|
+
function sleep(ms, signal) {
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
if (signal?.aborted) {
|
|
134
|
+
reject(signal.reason);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const timer = setTimeout(resolve, ms);
|
|
138
|
+
signal?.addEventListener(
|
|
139
|
+
"abort",
|
|
140
|
+
() => {
|
|
141
|
+
clearTimeout(timer);
|
|
142
|
+
reject(signal.reason);
|
|
143
|
+
},
|
|
144
|
+
{ once: true }
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async function retry(fn, options = {}) {
|
|
149
|
+
const maxAttempts = options.maxAttempts ?? DEFAULTS.maxAttempts;
|
|
150
|
+
const baseDelayMs = options.baseDelayMs ?? DEFAULTS.baseDelayMs;
|
|
151
|
+
const maxDelayMs = options.maxDelayMs ?? DEFAULTS.maxDelayMs;
|
|
152
|
+
const jitter = options.jitter ?? DEFAULTS.jitter;
|
|
153
|
+
const isRetryable = options.isRetryable ?? isRetryableError;
|
|
154
|
+
let lastErr;
|
|
155
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
156
|
+
try {
|
|
157
|
+
return await fn(attempt);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
lastErr = err;
|
|
160
|
+
if (attempt === maxAttempts || !isRetryable(err)) {
|
|
161
|
+
throw err;
|
|
162
|
+
}
|
|
163
|
+
const retryAfterMs = isLoncaError(err) ? err.retryAfterMs : void 0;
|
|
164
|
+
const exponential = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1));
|
|
165
|
+
const base = retryAfterMs ?? exponential;
|
|
166
|
+
const jitterMs = jitter ? Math.random() * base * 0.5 : 0;
|
|
167
|
+
const delay = Math.min(maxDelayMs, base + jitterMs);
|
|
168
|
+
options.onRetry?.(err, attempt, delay);
|
|
169
|
+
await sleep(delay, options.signal);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
throw lastErr;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/logger.ts
|
|
176
|
+
var noopLogger = {
|
|
177
|
+
debug: () => {
|
|
178
|
+
},
|
|
179
|
+
info: () => {
|
|
180
|
+
},
|
|
181
|
+
warn: () => {
|
|
182
|
+
},
|
|
183
|
+
error: () => {
|
|
184
|
+
},
|
|
185
|
+
child: () => noopLogger
|
|
186
|
+
};
|
|
187
|
+
function consoleLogger(bindings = {}) {
|
|
188
|
+
const emit = (level, message, context) => {
|
|
189
|
+
const entry = {
|
|
190
|
+
level,
|
|
191
|
+
message,
|
|
192
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
193
|
+
...bindings,
|
|
194
|
+
...context
|
|
195
|
+
};
|
|
196
|
+
const line = JSON.stringify(entry);
|
|
197
|
+
if (level === "error" || level === "warn") {
|
|
198
|
+
console.error(line);
|
|
199
|
+
} else {
|
|
200
|
+
console.log(line);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
return {
|
|
204
|
+
debug: (m, c) => emit("debug", m, c),
|
|
205
|
+
info: (m, c) => emit("info", m, c),
|
|
206
|
+
warn: (m, c) => emit("warn", m, c),
|
|
207
|
+
error: (m, c) => emit("error", m, c),
|
|
208
|
+
child: (b) => consoleLogger({ ...bindings, ...b })
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/rate-limiter.ts
|
|
213
|
+
var TokenBucketRateLimiter = class {
|
|
214
|
+
tokens;
|
|
215
|
+
lastRefillAt;
|
|
216
|
+
capacity;
|
|
217
|
+
intervalMs;
|
|
218
|
+
constructor(options) {
|
|
219
|
+
if (options.capacity <= 0 || options.intervalMs <= 0) {
|
|
220
|
+
throw new RangeError("capacity and intervalMs must be > 0");
|
|
221
|
+
}
|
|
222
|
+
this.capacity = options.capacity;
|
|
223
|
+
this.intervalMs = options.intervalMs;
|
|
224
|
+
this.tokens = options.capacity;
|
|
225
|
+
this.lastRefillAt = Date.now();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Refill tokens based on elapsed time, then return ms to wait until the next
|
|
229
|
+
* token is available (0 if one is available right now).
|
|
230
|
+
*/
|
|
231
|
+
timeUntilNextToken() {
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
const elapsed = now - this.lastRefillAt;
|
|
234
|
+
if (elapsed > 0) {
|
|
235
|
+
const refill = elapsed / this.intervalMs * this.capacity;
|
|
236
|
+
this.tokens = Math.min(this.capacity, this.tokens + refill);
|
|
237
|
+
this.lastRefillAt = now;
|
|
238
|
+
}
|
|
239
|
+
if (this.tokens >= 1) return 0;
|
|
240
|
+
const needed = 1 - this.tokens;
|
|
241
|
+
return Math.ceil(needed / this.capacity * this.intervalMs);
|
|
242
|
+
}
|
|
243
|
+
async acquire(signal) {
|
|
244
|
+
for (; ; ) {
|
|
245
|
+
if (signal?.aborted) throw signal.reason;
|
|
246
|
+
const wait = this.timeUntilNextToken();
|
|
247
|
+
if (wait === 0) {
|
|
248
|
+
this.tokens -= 1;
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
await new Promise((resolve, reject) => {
|
|
252
|
+
const timer = setTimeout(resolve, wait);
|
|
253
|
+
signal?.addEventListener(
|
|
254
|
+
"abort",
|
|
255
|
+
() => {
|
|
256
|
+
clearTimeout(timer);
|
|
257
|
+
reject(signal.reason);
|
|
258
|
+
},
|
|
259
|
+
{ once: true }
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/** Snapshot of available tokens (refills the bucket as a side effect). */
|
|
265
|
+
available() {
|
|
266
|
+
this.timeUntilNextToken();
|
|
267
|
+
return this.tokens;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
exports.AuthError = AuthError;
|
|
272
|
+
exports.EUR = EUR;
|
|
273
|
+
exports.GBP = GBP;
|
|
274
|
+
exports.LoncaError = LoncaError;
|
|
275
|
+
exports.NetworkError = NetworkError;
|
|
276
|
+
exports.NotFoundError = NotFoundError;
|
|
277
|
+
exports.RateLimitError = RateLimitError;
|
|
278
|
+
exports.ServerError = ServerError;
|
|
279
|
+
exports.TRY = TRY;
|
|
280
|
+
exports.TimeoutError = TimeoutError;
|
|
281
|
+
exports.TokenBucketRateLimiter = TokenBucketRateLimiter;
|
|
282
|
+
exports.USD = USD;
|
|
283
|
+
exports.ValidationError = ValidationError;
|
|
284
|
+
exports.addMoney = addMoney;
|
|
285
|
+
exports.consoleLogger = consoleLogger;
|
|
286
|
+
exports.isLoncaError = isLoncaError;
|
|
287
|
+
exports.isRetryableError = isRetryableError;
|
|
288
|
+
exports.isSameCurrency = isSameCurrency;
|
|
289
|
+
exports.isValidCurrencyCode = isValidCurrencyCode;
|
|
290
|
+
exports.money = money;
|
|
291
|
+
exports.moneyFromMajor = moneyFromMajor;
|
|
292
|
+
exports.moneyToMajor = moneyToMajor;
|
|
293
|
+
exports.noopLogger = noopLogger;
|
|
294
|
+
exports.paginate = paginate;
|
|
295
|
+
exports.retry = retry;
|
|
296
|
+
exports.subMoney = subMoney;
|
|
297
|
+
//# sourceMappingURL=index.cjs.map
|
|
298
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/currency.ts","../src/money.ts","../src/pagination.ts","../src/errors.ts","../src/retry.ts","../src/logger.ts","../src/rate-limiter.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * ISO 4217 three-letter currency code.\n *\n * Typed as a string for flexibility — Trendyol and similar marketplaces may\n * expose currencies beyond the common set, so we don't lock down to a union.\n * Use `isValidCurrencyCode` for runtime validation.\n */\nexport type Currency = string;\n\nexport const TRY = 'TRY';\nexport const USD = 'USD';\nexport const EUR = 'EUR';\nexport const GBP = 'GBP';\n\nconst ISO_4217_RE = /^[A-Z]{3}$/;\n\nexport function isValidCurrencyCode(code: string): boolean {\n return ISO_4217_RE.test(code);\n}\n","import type { Currency } from './currency.js';\n\n/**\n * A monetary amount in integer minor units (e.g., kuruş for TRY, cents for USD).\n *\n * Using integers avoids floating-point surprises. Convert to/from major units\n * with `moneyFromMajor` and `moneyToMajor`.\n */\nexport interface Money {\n amount: number;\n currency: Currency;\n}\n\nexport function money(amount: number, currency: Currency): Money {\n if (!Number.isInteger(amount)) {\n throw new TypeError(`Money amount must be an integer minor unit, got ${amount}`);\n }\n return { amount, currency };\n}\n\nexport function moneyFromMajor(major: number, currency: Currency, minorScale = 2): Money {\n const factor = 10 ** minorScale;\n const amount = Math.round(major * factor);\n return money(amount, currency);\n}\n\nexport function moneyToMajor(value: Money, minorScale = 2): number {\n return value.amount / 10 ** minorScale;\n}\n\nexport function isSameCurrency(a: Money, b: Money): boolean {\n return a.currency === b.currency;\n}\n\nexport function addMoney(a: Money, b: Money): Money {\n if (!isSameCurrency(a, b)) {\n throw new TypeError(`Cannot add Money of different currencies: ${a.currency} vs ${b.currency}`);\n }\n return money(a.amount + b.amount, a.currency);\n}\n\nexport function subMoney(a: Money, b: Money): Money {\n if (!isSameCurrency(a, b)) {\n throw new TypeError(\n `Cannot subtract Money of different currencies: ${a.currency} vs ${b.currency}`,\n );\n }\n return money(a.amount - b.amount, a.currency);\n}\n","export interface CursorPaginationParams {\n /** Opaque cursor from previous response's `nextCursor`. Omit for the first page. */\n cursor?: string;\n /** Maximum items in the page. Marketplace SDKs may clamp this to their own bounds. */\n limit?: number;\n}\n\nexport interface CursorPage<T> {\n items: T[];\n /** Opaque cursor for the next page. Absent when there are no more pages. */\n nextCursor?: string;\n}\n\n/**\n * Drive a cursor-paginated endpoint as an async iterator.\n *\n * @example\n * for await (const order of paginate((p) => client.orders.list(p))) {\n * console.log(order.id);\n * }\n */\nexport async function* paginate<T>(\n fetchPage: (params: CursorPaginationParams) => Promise<CursorPage<T>>,\n params: Omit<CursorPaginationParams, 'cursor'> = {},\n): AsyncGenerator<T, void, undefined> {\n let cursor: string | undefined;\n do {\n const page = await fetchPage({ ...params, cursor });\n for (const item of page.items) {\n yield item;\n }\n cursor = page.nextCursor;\n } while (cursor);\n}\n","export type LoncaErrorCode =\n | 'AUTH_FAILED'\n | 'RATE_LIMITED'\n | 'VALIDATION_FAILED'\n | 'NOT_FOUND'\n | 'SERVER_ERROR'\n | 'NETWORK_ERROR'\n | 'TIMEOUT'\n | 'UNKNOWN';\n\nexport interface LoncaErrorOptions {\n code: LoncaErrorCode;\n message: string;\n cause?: unknown;\n retryable?: boolean;\n status?: number;\n retryAfterMs?: number;\n data?: Record<string, unknown>;\n}\n\n/**\n * Root of the Lonca error hierarchy.\n *\n * Carries a tagged `code` and a `retryable` boolean so retry helpers\n * can decide what to do without sniffing message strings.\n */\nexport class LoncaError extends Error {\n readonly code: LoncaErrorCode;\n readonly retryable: boolean;\n readonly status?: number;\n readonly retryAfterMs?: number;\n readonly data?: Record<string, unknown>;\n\n constructor(opts: LoncaErrorOptions) {\n super(opts.message, opts.cause !== undefined ? { cause: opts.cause } : undefined);\n this.name = 'LoncaError';\n this.code = opts.code;\n this.retryable = opts.retryable ?? false;\n this.status = opts.status;\n this.retryAfterMs = opts.retryAfterMs;\n this.data = opts.data;\n }\n}\n\ntype SubclassOptions = Omit<LoncaErrorOptions, 'code' | 'retryable'>;\n\nexport class AuthError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'AUTH_FAILED', retryable: false });\n this.name = 'AuthError';\n }\n}\n\nexport class RateLimitError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'RATE_LIMITED', retryable: true });\n this.name = 'RateLimitError';\n }\n}\n\nexport class ValidationError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'VALIDATION_FAILED', retryable: false });\n this.name = 'ValidationError';\n }\n}\n\nexport class NotFoundError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'NOT_FOUND', retryable: false });\n this.name = 'NotFoundError';\n }\n}\n\nexport class ServerError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'SERVER_ERROR', retryable: true });\n this.name = 'ServerError';\n }\n}\n\nexport class NetworkError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'NETWORK_ERROR', retryable: true });\n this.name = 'NetworkError';\n }\n}\n\nexport class TimeoutError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'TIMEOUT', retryable: true });\n this.name = 'TimeoutError';\n }\n}\n\nexport function isLoncaError(value: unknown): value is LoncaError {\n return value instanceof LoncaError;\n}\n\nexport function isRetryableError(value: unknown): boolean {\n return isLoncaError(value) && value.retryable;\n}\n","import { isLoncaError, isRetryableError } from './errors.js';\n\nexport interface RetryOptions {\n /** Total number of attempts including the initial call. Defaults to 3. */\n maxAttempts?: number;\n /** Initial delay before the second attempt, in ms. Defaults to 200. */\n baseDelayMs?: number;\n /** Upper bound on delay between any two attempts, in ms. Defaults to 10_000. */\n maxDelayMs?: number;\n /** Add up to 50% random jitter on each delay. Defaults to true. */\n jitter?: boolean;\n /** Override the default predicate. Default treats `retryable` LoncaErrors as retryable. */\n isRetryable?: (err: unknown) => boolean;\n /** Called before each retry sleep. Useful for structured logging. */\n onRetry?: (err: unknown, attempt: number, nextDelayMs: number) => void;\n /** Abort all pending sleeps and bubble the signal's reason. */\n signal?: AbortSignal;\n}\n\nconst DEFAULTS = {\n maxAttempts: 3,\n baseDelayMs: 200,\n maxDelayMs: 10_000,\n jitter: true,\n} as const;\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(signal.reason);\n },\n { once: true },\n );\n });\n}\n\nexport async function retry<T>(\n fn: (attempt: number) => Promise<T>,\n options: RetryOptions = {},\n): Promise<T> {\n const maxAttempts = options.maxAttempts ?? DEFAULTS.maxAttempts;\n const baseDelayMs = options.baseDelayMs ?? DEFAULTS.baseDelayMs;\n const maxDelayMs = options.maxDelayMs ?? DEFAULTS.maxDelayMs;\n const jitter = options.jitter ?? DEFAULTS.jitter;\n const isRetryable = options.isRetryable ?? isRetryableError;\n\n let lastErr: unknown;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn(attempt);\n } catch (err) {\n lastErr = err;\n if (attempt === maxAttempts || !isRetryable(err)) {\n throw err;\n }\n\n const retryAfterMs = isLoncaError(err) ? err.retryAfterMs : undefined;\n const exponential = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1));\n const base = retryAfterMs ?? exponential;\n const jitterMs = jitter ? Math.random() * base * 0.5 : 0;\n const delay = Math.min(maxDelayMs, base + jitterMs);\n\n options.onRetry?.(err, attempt, delay);\n await sleep(delay, options.signal);\n }\n }\n throw lastErr;\n}\n","export type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport type LogContext = Record<string, unknown>;\n\nexport interface Logger {\n debug(message: string, context?: LogContext): void;\n info(message: string, context?: LogContext): void;\n warn(message: string, context?: LogContext): void;\n error(message: string, context?: LogContext): void;\n /** Create a child logger that merges `bindings` into every log entry. */\n child(bindings: LogContext): Logger;\n}\n\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n child: () => noopLogger,\n};\n\n/**\n * Minimal JSON-line logger to stdout/stderr. Suitable for development and\n * containerized environments. For production prefer wiring a real logger\n * (pino, winston) by implementing the `Logger` interface.\n */\nexport function consoleLogger(bindings: LogContext = {}): Logger {\n const emit = (level: LogLevel, message: string, context?: LogContext): void => {\n const entry = {\n level,\n message,\n timestamp: new Date().toISOString(),\n ...bindings,\n ...context,\n };\n const line = JSON.stringify(entry);\n if (level === 'error' || level === 'warn') {\n console.error(line);\n } else {\n console.log(line);\n }\n };\n return {\n debug: (m, c) => emit('debug', m, c),\n info: (m, c) => emit('info', m, c),\n warn: (m, c) => emit('warn', m, c),\n error: (m, c) => emit('error', m, c),\n child: (b) => consoleLogger({ ...bindings, ...b }),\n };\n}\n","export interface RateLimiterOptions {\n /** Maximum number of tokens the bucket holds. Each `acquire()` consumes 1. */\n capacity: number;\n /** Time window in milliseconds over which `capacity` tokens are refilled. */\n intervalMs: number;\n}\n\n/**\n * Token-bucket rate limiter. `acquire()` returns immediately when a token\n * is available, otherwise it sleeps until enough tokens have been refilled.\n *\n * @example\n * const limiter = new TokenBucketRateLimiter({ capacity: 50, intervalMs: 60_000 });\n * for (const item of items) {\n * await limiter.acquire();\n * await fetchSomething(item);\n * }\n */\nexport class TokenBucketRateLimiter {\n private tokens: number;\n private lastRefillAt: number;\n private readonly capacity: number;\n private readonly intervalMs: number;\n\n constructor(options: RateLimiterOptions) {\n if (options.capacity <= 0 || options.intervalMs <= 0) {\n throw new RangeError('capacity and intervalMs must be > 0');\n }\n this.capacity = options.capacity;\n this.intervalMs = options.intervalMs;\n this.tokens = options.capacity;\n this.lastRefillAt = Date.now();\n }\n\n /**\n * Refill tokens based on elapsed time, then return ms to wait until the next\n * token is available (0 if one is available right now).\n */\n private timeUntilNextToken(): number {\n const now = Date.now();\n const elapsed = now - this.lastRefillAt;\n if (elapsed > 0) {\n const refill = (elapsed / this.intervalMs) * this.capacity;\n this.tokens = Math.min(this.capacity, this.tokens + refill);\n this.lastRefillAt = now;\n }\n if (this.tokens >= 1) return 0;\n const needed = 1 - this.tokens;\n return Math.ceil((needed / this.capacity) * this.intervalMs);\n }\n\n async acquire(signal?: AbortSignal): Promise<void> {\n for (;;) {\n if (signal?.aborted) throw signal.reason as Error;\n const wait = this.timeUntilNextToken();\n if (wait === 0) {\n this.tokens -= 1;\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, wait);\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(signal.reason);\n },\n { once: true },\n );\n });\n }\n }\n\n /** Snapshot of available tokens (refills the bucket as a side effect). */\n available(): number {\n this.timeUntilNextToken();\n return this.tokens;\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO 4217 three-letter currency code.
|
|
3
|
+
*
|
|
4
|
+
* Typed as a string for flexibility — Trendyol and similar marketplaces may
|
|
5
|
+
* expose currencies beyond the common set, so we don't lock down to a union.
|
|
6
|
+
* Use `isValidCurrencyCode` for runtime validation.
|
|
7
|
+
*/
|
|
8
|
+
type Currency = string;
|
|
9
|
+
declare const TRY = "TRY";
|
|
10
|
+
declare const USD = "USD";
|
|
11
|
+
declare const EUR = "EUR";
|
|
12
|
+
declare const GBP = "GBP";
|
|
13
|
+
declare function isValidCurrencyCode(code: string): boolean;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A monetary amount in integer minor units (e.g., kuruş for TRY, cents for USD).
|
|
17
|
+
*
|
|
18
|
+
* Using integers avoids floating-point surprises. Convert to/from major units
|
|
19
|
+
* with `moneyFromMajor` and `moneyToMajor`.
|
|
20
|
+
*/
|
|
21
|
+
interface Money {
|
|
22
|
+
amount: number;
|
|
23
|
+
currency: Currency;
|
|
24
|
+
}
|
|
25
|
+
declare function money(amount: number, currency: Currency): Money;
|
|
26
|
+
declare function moneyFromMajor(major: number, currency: Currency, minorScale?: number): Money;
|
|
27
|
+
declare function moneyToMajor(value: Money, minorScale?: number): number;
|
|
28
|
+
declare function isSameCurrency(a: Money, b: Money): boolean;
|
|
29
|
+
declare function addMoney(a: Money, b: Money): Money;
|
|
30
|
+
declare function subMoney(a: Money, b: Money): Money;
|
|
31
|
+
|
|
32
|
+
interface CursorPaginationParams {
|
|
33
|
+
/** Opaque cursor from previous response's `nextCursor`. Omit for the first page. */
|
|
34
|
+
cursor?: string;
|
|
35
|
+
/** Maximum items in the page. Marketplace SDKs may clamp this to their own bounds. */
|
|
36
|
+
limit?: number;
|
|
37
|
+
}
|
|
38
|
+
interface CursorPage<T> {
|
|
39
|
+
items: T[];
|
|
40
|
+
/** Opaque cursor for the next page. Absent when there are no more pages. */
|
|
41
|
+
nextCursor?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Drive a cursor-paginated endpoint as an async iterator.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* for await (const order of paginate((p) => client.orders.list(p))) {
|
|
48
|
+
* console.log(order.id);
|
|
49
|
+
* }
|
|
50
|
+
*/
|
|
51
|
+
declare function paginate<T>(fetchPage: (params: CursorPaginationParams) => Promise<CursorPage<T>>, params?: Omit<CursorPaginationParams, 'cursor'>): AsyncGenerator<T, void, undefined>;
|
|
52
|
+
|
|
53
|
+
type LoncaErrorCode = 'AUTH_FAILED' | 'RATE_LIMITED' | 'VALIDATION_FAILED' | 'NOT_FOUND' | 'SERVER_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT' | 'UNKNOWN';
|
|
54
|
+
interface LoncaErrorOptions {
|
|
55
|
+
code: LoncaErrorCode;
|
|
56
|
+
message: string;
|
|
57
|
+
cause?: unknown;
|
|
58
|
+
retryable?: boolean;
|
|
59
|
+
status?: number;
|
|
60
|
+
retryAfterMs?: number;
|
|
61
|
+
data?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Root of the Lonca error hierarchy.
|
|
65
|
+
*
|
|
66
|
+
* Carries a tagged `code` and a `retryable` boolean so retry helpers
|
|
67
|
+
* can decide what to do without sniffing message strings.
|
|
68
|
+
*/
|
|
69
|
+
declare class LoncaError extends Error {
|
|
70
|
+
readonly code: LoncaErrorCode;
|
|
71
|
+
readonly retryable: boolean;
|
|
72
|
+
readonly status?: number;
|
|
73
|
+
readonly retryAfterMs?: number;
|
|
74
|
+
readonly data?: Record<string, unknown>;
|
|
75
|
+
constructor(opts: LoncaErrorOptions);
|
|
76
|
+
}
|
|
77
|
+
type SubclassOptions = Omit<LoncaErrorOptions, 'code' | 'retryable'>;
|
|
78
|
+
declare class AuthError extends LoncaError {
|
|
79
|
+
constructor(opts: SubclassOptions);
|
|
80
|
+
}
|
|
81
|
+
declare class RateLimitError extends LoncaError {
|
|
82
|
+
constructor(opts: SubclassOptions);
|
|
83
|
+
}
|
|
84
|
+
declare class ValidationError extends LoncaError {
|
|
85
|
+
constructor(opts: SubclassOptions);
|
|
86
|
+
}
|
|
87
|
+
declare class NotFoundError extends LoncaError {
|
|
88
|
+
constructor(opts: SubclassOptions);
|
|
89
|
+
}
|
|
90
|
+
declare class ServerError extends LoncaError {
|
|
91
|
+
constructor(opts: SubclassOptions);
|
|
92
|
+
}
|
|
93
|
+
declare class NetworkError extends LoncaError {
|
|
94
|
+
constructor(opts: SubclassOptions);
|
|
95
|
+
}
|
|
96
|
+
declare class TimeoutError extends LoncaError {
|
|
97
|
+
constructor(opts: SubclassOptions);
|
|
98
|
+
}
|
|
99
|
+
declare function isLoncaError(value: unknown): value is LoncaError;
|
|
100
|
+
declare function isRetryableError(value: unknown): boolean;
|
|
101
|
+
|
|
102
|
+
interface RetryOptions {
|
|
103
|
+
/** Total number of attempts including the initial call. Defaults to 3. */
|
|
104
|
+
maxAttempts?: number;
|
|
105
|
+
/** Initial delay before the second attempt, in ms. Defaults to 200. */
|
|
106
|
+
baseDelayMs?: number;
|
|
107
|
+
/** Upper bound on delay between any two attempts, in ms. Defaults to 10_000. */
|
|
108
|
+
maxDelayMs?: number;
|
|
109
|
+
/** Add up to 50% random jitter on each delay. Defaults to true. */
|
|
110
|
+
jitter?: boolean;
|
|
111
|
+
/** Override the default predicate. Default treats `retryable` LoncaErrors as retryable. */
|
|
112
|
+
isRetryable?: (err: unknown) => boolean;
|
|
113
|
+
/** Called before each retry sleep. Useful for structured logging. */
|
|
114
|
+
onRetry?: (err: unknown, attempt: number, nextDelayMs: number) => void;
|
|
115
|
+
/** Abort all pending sleeps and bubble the signal's reason. */
|
|
116
|
+
signal?: AbortSignal;
|
|
117
|
+
}
|
|
118
|
+
declare function retry<T>(fn: (attempt: number) => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
119
|
+
|
|
120
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
121
|
+
type LogContext = Record<string, unknown>;
|
|
122
|
+
interface Logger {
|
|
123
|
+
debug(message: string, context?: LogContext): void;
|
|
124
|
+
info(message: string, context?: LogContext): void;
|
|
125
|
+
warn(message: string, context?: LogContext): void;
|
|
126
|
+
error(message: string, context?: LogContext): void;
|
|
127
|
+
/** Create a child logger that merges `bindings` into every log entry. */
|
|
128
|
+
child(bindings: LogContext): Logger;
|
|
129
|
+
}
|
|
130
|
+
declare const noopLogger: Logger;
|
|
131
|
+
/**
|
|
132
|
+
* Minimal JSON-line logger to stdout/stderr. Suitable for development and
|
|
133
|
+
* containerized environments. For production prefer wiring a real logger
|
|
134
|
+
* (pino, winston) by implementing the `Logger` interface.
|
|
135
|
+
*/
|
|
136
|
+
declare function consoleLogger(bindings?: LogContext): Logger;
|
|
137
|
+
|
|
138
|
+
interface RateLimiterOptions {
|
|
139
|
+
/** Maximum number of tokens the bucket holds. Each `acquire()` consumes 1. */
|
|
140
|
+
capacity: number;
|
|
141
|
+
/** Time window in milliseconds over which `capacity` tokens are refilled. */
|
|
142
|
+
intervalMs: number;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Token-bucket rate limiter. `acquire()` returns immediately when a token
|
|
146
|
+
* is available, otherwise it sleeps until enough tokens have been refilled.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const limiter = new TokenBucketRateLimiter({ capacity: 50, intervalMs: 60_000 });
|
|
150
|
+
* for (const item of items) {
|
|
151
|
+
* await limiter.acquire();
|
|
152
|
+
* await fetchSomething(item);
|
|
153
|
+
* }
|
|
154
|
+
*/
|
|
155
|
+
declare class TokenBucketRateLimiter {
|
|
156
|
+
private tokens;
|
|
157
|
+
private lastRefillAt;
|
|
158
|
+
private readonly capacity;
|
|
159
|
+
private readonly intervalMs;
|
|
160
|
+
constructor(options: RateLimiterOptions);
|
|
161
|
+
/**
|
|
162
|
+
* Refill tokens based on elapsed time, then return ms to wait until the next
|
|
163
|
+
* token is available (0 if one is available right now).
|
|
164
|
+
*/
|
|
165
|
+
private timeUntilNextToken;
|
|
166
|
+
acquire(signal?: AbortSignal): Promise<void>;
|
|
167
|
+
/** Snapshot of available tokens (refills the bucket as a side effect). */
|
|
168
|
+
available(): number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export { AuthError, type Currency, type CursorPage, type CursorPaginationParams, EUR, GBP, type LogContext, type LogLevel, type Logger, LoncaError, type LoncaErrorCode, type LoncaErrorOptions, type Money, NetworkError, NotFoundError, RateLimitError, type RateLimiterOptions, type RetryOptions, ServerError, TRY, TimeoutError, TokenBucketRateLimiter, USD, ValidationError, addMoney, consoleLogger, isLoncaError, isRetryableError, isSameCurrency, isValidCurrencyCode, money, moneyFromMajor, moneyToMajor, noopLogger, paginate, retry, subMoney };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ISO 4217 three-letter currency code.
|
|
3
|
+
*
|
|
4
|
+
* Typed as a string for flexibility — Trendyol and similar marketplaces may
|
|
5
|
+
* expose currencies beyond the common set, so we don't lock down to a union.
|
|
6
|
+
* Use `isValidCurrencyCode` for runtime validation.
|
|
7
|
+
*/
|
|
8
|
+
type Currency = string;
|
|
9
|
+
declare const TRY = "TRY";
|
|
10
|
+
declare const USD = "USD";
|
|
11
|
+
declare const EUR = "EUR";
|
|
12
|
+
declare const GBP = "GBP";
|
|
13
|
+
declare function isValidCurrencyCode(code: string): boolean;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A monetary amount in integer minor units (e.g., kuruş for TRY, cents for USD).
|
|
17
|
+
*
|
|
18
|
+
* Using integers avoids floating-point surprises. Convert to/from major units
|
|
19
|
+
* with `moneyFromMajor` and `moneyToMajor`.
|
|
20
|
+
*/
|
|
21
|
+
interface Money {
|
|
22
|
+
amount: number;
|
|
23
|
+
currency: Currency;
|
|
24
|
+
}
|
|
25
|
+
declare function money(amount: number, currency: Currency): Money;
|
|
26
|
+
declare function moneyFromMajor(major: number, currency: Currency, minorScale?: number): Money;
|
|
27
|
+
declare function moneyToMajor(value: Money, minorScale?: number): number;
|
|
28
|
+
declare function isSameCurrency(a: Money, b: Money): boolean;
|
|
29
|
+
declare function addMoney(a: Money, b: Money): Money;
|
|
30
|
+
declare function subMoney(a: Money, b: Money): Money;
|
|
31
|
+
|
|
32
|
+
interface CursorPaginationParams {
|
|
33
|
+
/** Opaque cursor from previous response's `nextCursor`. Omit for the first page. */
|
|
34
|
+
cursor?: string;
|
|
35
|
+
/** Maximum items in the page. Marketplace SDKs may clamp this to their own bounds. */
|
|
36
|
+
limit?: number;
|
|
37
|
+
}
|
|
38
|
+
interface CursorPage<T> {
|
|
39
|
+
items: T[];
|
|
40
|
+
/** Opaque cursor for the next page. Absent when there are no more pages. */
|
|
41
|
+
nextCursor?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Drive a cursor-paginated endpoint as an async iterator.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* for await (const order of paginate((p) => client.orders.list(p))) {
|
|
48
|
+
* console.log(order.id);
|
|
49
|
+
* }
|
|
50
|
+
*/
|
|
51
|
+
declare function paginate<T>(fetchPage: (params: CursorPaginationParams) => Promise<CursorPage<T>>, params?: Omit<CursorPaginationParams, 'cursor'>): AsyncGenerator<T, void, undefined>;
|
|
52
|
+
|
|
53
|
+
type LoncaErrorCode = 'AUTH_FAILED' | 'RATE_LIMITED' | 'VALIDATION_FAILED' | 'NOT_FOUND' | 'SERVER_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT' | 'UNKNOWN';
|
|
54
|
+
interface LoncaErrorOptions {
|
|
55
|
+
code: LoncaErrorCode;
|
|
56
|
+
message: string;
|
|
57
|
+
cause?: unknown;
|
|
58
|
+
retryable?: boolean;
|
|
59
|
+
status?: number;
|
|
60
|
+
retryAfterMs?: number;
|
|
61
|
+
data?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Root of the Lonca error hierarchy.
|
|
65
|
+
*
|
|
66
|
+
* Carries a tagged `code` and a `retryable` boolean so retry helpers
|
|
67
|
+
* can decide what to do without sniffing message strings.
|
|
68
|
+
*/
|
|
69
|
+
declare class LoncaError extends Error {
|
|
70
|
+
readonly code: LoncaErrorCode;
|
|
71
|
+
readonly retryable: boolean;
|
|
72
|
+
readonly status?: number;
|
|
73
|
+
readonly retryAfterMs?: number;
|
|
74
|
+
readonly data?: Record<string, unknown>;
|
|
75
|
+
constructor(opts: LoncaErrorOptions);
|
|
76
|
+
}
|
|
77
|
+
type SubclassOptions = Omit<LoncaErrorOptions, 'code' | 'retryable'>;
|
|
78
|
+
declare class AuthError extends LoncaError {
|
|
79
|
+
constructor(opts: SubclassOptions);
|
|
80
|
+
}
|
|
81
|
+
declare class RateLimitError extends LoncaError {
|
|
82
|
+
constructor(opts: SubclassOptions);
|
|
83
|
+
}
|
|
84
|
+
declare class ValidationError extends LoncaError {
|
|
85
|
+
constructor(opts: SubclassOptions);
|
|
86
|
+
}
|
|
87
|
+
declare class NotFoundError extends LoncaError {
|
|
88
|
+
constructor(opts: SubclassOptions);
|
|
89
|
+
}
|
|
90
|
+
declare class ServerError extends LoncaError {
|
|
91
|
+
constructor(opts: SubclassOptions);
|
|
92
|
+
}
|
|
93
|
+
declare class NetworkError extends LoncaError {
|
|
94
|
+
constructor(opts: SubclassOptions);
|
|
95
|
+
}
|
|
96
|
+
declare class TimeoutError extends LoncaError {
|
|
97
|
+
constructor(opts: SubclassOptions);
|
|
98
|
+
}
|
|
99
|
+
declare function isLoncaError(value: unknown): value is LoncaError;
|
|
100
|
+
declare function isRetryableError(value: unknown): boolean;
|
|
101
|
+
|
|
102
|
+
interface RetryOptions {
|
|
103
|
+
/** Total number of attempts including the initial call. Defaults to 3. */
|
|
104
|
+
maxAttempts?: number;
|
|
105
|
+
/** Initial delay before the second attempt, in ms. Defaults to 200. */
|
|
106
|
+
baseDelayMs?: number;
|
|
107
|
+
/** Upper bound on delay between any two attempts, in ms. Defaults to 10_000. */
|
|
108
|
+
maxDelayMs?: number;
|
|
109
|
+
/** Add up to 50% random jitter on each delay. Defaults to true. */
|
|
110
|
+
jitter?: boolean;
|
|
111
|
+
/** Override the default predicate. Default treats `retryable` LoncaErrors as retryable. */
|
|
112
|
+
isRetryable?: (err: unknown) => boolean;
|
|
113
|
+
/** Called before each retry sleep. Useful for structured logging. */
|
|
114
|
+
onRetry?: (err: unknown, attempt: number, nextDelayMs: number) => void;
|
|
115
|
+
/** Abort all pending sleeps and bubble the signal's reason. */
|
|
116
|
+
signal?: AbortSignal;
|
|
117
|
+
}
|
|
118
|
+
declare function retry<T>(fn: (attempt: number) => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
119
|
+
|
|
120
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
121
|
+
type LogContext = Record<string, unknown>;
|
|
122
|
+
interface Logger {
|
|
123
|
+
debug(message: string, context?: LogContext): void;
|
|
124
|
+
info(message: string, context?: LogContext): void;
|
|
125
|
+
warn(message: string, context?: LogContext): void;
|
|
126
|
+
error(message: string, context?: LogContext): void;
|
|
127
|
+
/** Create a child logger that merges `bindings` into every log entry. */
|
|
128
|
+
child(bindings: LogContext): Logger;
|
|
129
|
+
}
|
|
130
|
+
declare const noopLogger: Logger;
|
|
131
|
+
/**
|
|
132
|
+
* Minimal JSON-line logger to stdout/stderr. Suitable for development and
|
|
133
|
+
* containerized environments. For production prefer wiring a real logger
|
|
134
|
+
* (pino, winston) by implementing the `Logger` interface.
|
|
135
|
+
*/
|
|
136
|
+
declare function consoleLogger(bindings?: LogContext): Logger;
|
|
137
|
+
|
|
138
|
+
interface RateLimiterOptions {
|
|
139
|
+
/** Maximum number of tokens the bucket holds. Each `acquire()` consumes 1. */
|
|
140
|
+
capacity: number;
|
|
141
|
+
/** Time window in milliseconds over which `capacity` tokens are refilled. */
|
|
142
|
+
intervalMs: number;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Token-bucket rate limiter. `acquire()` returns immediately when a token
|
|
146
|
+
* is available, otherwise it sleeps until enough tokens have been refilled.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const limiter = new TokenBucketRateLimiter({ capacity: 50, intervalMs: 60_000 });
|
|
150
|
+
* for (const item of items) {
|
|
151
|
+
* await limiter.acquire();
|
|
152
|
+
* await fetchSomething(item);
|
|
153
|
+
* }
|
|
154
|
+
*/
|
|
155
|
+
declare class TokenBucketRateLimiter {
|
|
156
|
+
private tokens;
|
|
157
|
+
private lastRefillAt;
|
|
158
|
+
private readonly capacity;
|
|
159
|
+
private readonly intervalMs;
|
|
160
|
+
constructor(options: RateLimiterOptions);
|
|
161
|
+
/**
|
|
162
|
+
* Refill tokens based on elapsed time, then return ms to wait until the next
|
|
163
|
+
* token is available (0 if one is available right now).
|
|
164
|
+
*/
|
|
165
|
+
private timeUntilNextToken;
|
|
166
|
+
acquire(signal?: AbortSignal): Promise<void>;
|
|
167
|
+
/** Snapshot of available tokens (refills the bucket as a side effect). */
|
|
168
|
+
available(): number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export { AuthError, type Currency, type CursorPage, type CursorPaginationParams, EUR, GBP, type LogContext, type LogLevel, type Logger, LoncaError, type LoncaErrorCode, type LoncaErrorOptions, type Money, NetworkError, NotFoundError, RateLimitError, type RateLimiterOptions, type RetryOptions, ServerError, TRY, TimeoutError, TokenBucketRateLimiter, USD, ValidationError, addMoney, consoleLogger, isLoncaError, isRetryableError, isSameCurrency, isValidCurrencyCode, money, moneyFromMajor, moneyToMajor, noopLogger, paginate, retry, subMoney };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// src/currency.ts
|
|
2
|
+
var TRY = "TRY";
|
|
3
|
+
var USD = "USD";
|
|
4
|
+
var EUR = "EUR";
|
|
5
|
+
var GBP = "GBP";
|
|
6
|
+
var ISO_4217_RE = /^[A-Z]{3}$/;
|
|
7
|
+
function isValidCurrencyCode(code) {
|
|
8
|
+
return ISO_4217_RE.test(code);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/money.ts
|
|
12
|
+
function money(amount, currency) {
|
|
13
|
+
if (!Number.isInteger(amount)) {
|
|
14
|
+
throw new TypeError(`Money amount must be an integer minor unit, got ${amount}`);
|
|
15
|
+
}
|
|
16
|
+
return { amount, currency };
|
|
17
|
+
}
|
|
18
|
+
function moneyFromMajor(major, currency, minorScale = 2) {
|
|
19
|
+
const factor = 10 ** minorScale;
|
|
20
|
+
const amount = Math.round(major * factor);
|
|
21
|
+
return money(amount, currency);
|
|
22
|
+
}
|
|
23
|
+
function moneyToMajor(value, minorScale = 2) {
|
|
24
|
+
return value.amount / 10 ** minorScale;
|
|
25
|
+
}
|
|
26
|
+
function isSameCurrency(a, b) {
|
|
27
|
+
return a.currency === b.currency;
|
|
28
|
+
}
|
|
29
|
+
function addMoney(a, b) {
|
|
30
|
+
if (!isSameCurrency(a, b)) {
|
|
31
|
+
throw new TypeError(`Cannot add Money of different currencies: ${a.currency} vs ${b.currency}`);
|
|
32
|
+
}
|
|
33
|
+
return money(a.amount + b.amount, a.currency);
|
|
34
|
+
}
|
|
35
|
+
function subMoney(a, b) {
|
|
36
|
+
if (!isSameCurrency(a, b)) {
|
|
37
|
+
throw new TypeError(
|
|
38
|
+
`Cannot subtract Money of different currencies: ${a.currency} vs ${b.currency}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return money(a.amount - b.amount, a.currency);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/pagination.ts
|
|
45
|
+
async function* paginate(fetchPage, params = {}) {
|
|
46
|
+
let cursor;
|
|
47
|
+
do {
|
|
48
|
+
const page = await fetchPage({ ...params, cursor });
|
|
49
|
+
for (const item of page.items) {
|
|
50
|
+
yield item;
|
|
51
|
+
}
|
|
52
|
+
cursor = page.nextCursor;
|
|
53
|
+
} while (cursor);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/errors.ts
|
|
57
|
+
var LoncaError = class extends Error {
|
|
58
|
+
code;
|
|
59
|
+
retryable;
|
|
60
|
+
status;
|
|
61
|
+
retryAfterMs;
|
|
62
|
+
data;
|
|
63
|
+
constructor(opts) {
|
|
64
|
+
super(opts.message, opts.cause !== void 0 ? { cause: opts.cause } : void 0);
|
|
65
|
+
this.name = "LoncaError";
|
|
66
|
+
this.code = opts.code;
|
|
67
|
+
this.retryable = opts.retryable ?? false;
|
|
68
|
+
this.status = opts.status;
|
|
69
|
+
this.retryAfterMs = opts.retryAfterMs;
|
|
70
|
+
this.data = opts.data;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var AuthError = class extends LoncaError {
|
|
74
|
+
constructor(opts) {
|
|
75
|
+
super({ ...opts, code: "AUTH_FAILED", retryable: false });
|
|
76
|
+
this.name = "AuthError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var RateLimitError = class extends LoncaError {
|
|
80
|
+
constructor(opts) {
|
|
81
|
+
super({ ...opts, code: "RATE_LIMITED", retryable: true });
|
|
82
|
+
this.name = "RateLimitError";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var ValidationError = class extends LoncaError {
|
|
86
|
+
constructor(opts) {
|
|
87
|
+
super({ ...opts, code: "VALIDATION_FAILED", retryable: false });
|
|
88
|
+
this.name = "ValidationError";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var NotFoundError = class extends LoncaError {
|
|
92
|
+
constructor(opts) {
|
|
93
|
+
super({ ...opts, code: "NOT_FOUND", retryable: false });
|
|
94
|
+
this.name = "NotFoundError";
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var ServerError = class extends LoncaError {
|
|
98
|
+
constructor(opts) {
|
|
99
|
+
super({ ...opts, code: "SERVER_ERROR", retryable: true });
|
|
100
|
+
this.name = "ServerError";
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var NetworkError = class extends LoncaError {
|
|
104
|
+
constructor(opts) {
|
|
105
|
+
super({ ...opts, code: "NETWORK_ERROR", retryable: true });
|
|
106
|
+
this.name = "NetworkError";
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var TimeoutError = class extends LoncaError {
|
|
110
|
+
constructor(opts) {
|
|
111
|
+
super({ ...opts, code: "TIMEOUT", retryable: true });
|
|
112
|
+
this.name = "TimeoutError";
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
function isLoncaError(value) {
|
|
116
|
+
return value instanceof LoncaError;
|
|
117
|
+
}
|
|
118
|
+
function isRetryableError(value) {
|
|
119
|
+
return isLoncaError(value) && value.retryable;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/retry.ts
|
|
123
|
+
var DEFAULTS = {
|
|
124
|
+
maxAttempts: 3,
|
|
125
|
+
baseDelayMs: 200,
|
|
126
|
+
maxDelayMs: 1e4,
|
|
127
|
+
jitter: true
|
|
128
|
+
};
|
|
129
|
+
function sleep(ms, signal) {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
if (signal?.aborted) {
|
|
132
|
+
reject(signal.reason);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const timer = setTimeout(resolve, ms);
|
|
136
|
+
signal?.addEventListener(
|
|
137
|
+
"abort",
|
|
138
|
+
() => {
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
reject(signal.reason);
|
|
141
|
+
},
|
|
142
|
+
{ once: true }
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
async function retry(fn, options = {}) {
|
|
147
|
+
const maxAttempts = options.maxAttempts ?? DEFAULTS.maxAttempts;
|
|
148
|
+
const baseDelayMs = options.baseDelayMs ?? DEFAULTS.baseDelayMs;
|
|
149
|
+
const maxDelayMs = options.maxDelayMs ?? DEFAULTS.maxDelayMs;
|
|
150
|
+
const jitter = options.jitter ?? DEFAULTS.jitter;
|
|
151
|
+
const isRetryable = options.isRetryable ?? isRetryableError;
|
|
152
|
+
let lastErr;
|
|
153
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
154
|
+
try {
|
|
155
|
+
return await fn(attempt);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
lastErr = err;
|
|
158
|
+
if (attempt === maxAttempts || !isRetryable(err)) {
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
const retryAfterMs = isLoncaError(err) ? err.retryAfterMs : void 0;
|
|
162
|
+
const exponential = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1));
|
|
163
|
+
const base = retryAfterMs ?? exponential;
|
|
164
|
+
const jitterMs = jitter ? Math.random() * base * 0.5 : 0;
|
|
165
|
+
const delay = Math.min(maxDelayMs, base + jitterMs);
|
|
166
|
+
options.onRetry?.(err, attempt, delay);
|
|
167
|
+
await sleep(delay, options.signal);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
throw lastErr;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/logger.ts
|
|
174
|
+
var noopLogger = {
|
|
175
|
+
debug: () => {
|
|
176
|
+
},
|
|
177
|
+
info: () => {
|
|
178
|
+
},
|
|
179
|
+
warn: () => {
|
|
180
|
+
},
|
|
181
|
+
error: () => {
|
|
182
|
+
},
|
|
183
|
+
child: () => noopLogger
|
|
184
|
+
};
|
|
185
|
+
function consoleLogger(bindings = {}) {
|
|
186
|
+
const emit = (level, message, context) => {
|
|
187
|
+
const entry = {
|
|
188
|
+
level,
|
|
189
|
+
message,
|
|
190
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
191
|
+
...bindings,
|
|
192
|
+
...context
|
|
193
|
+
};
|
|
194
|
+
const line = JSON.stringify(entry);
|
|
195
|
+
if (level === "error" || level === "warn") {
|
|
196
|
+
console.error(line);
|
|
197
|
+
} else {
|
|
198
|
+
console.log(line);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
debug: (m, c) => emit("debug", m, c),
|
|
203
|
+
info: (m, c) => emit("info", m, c),
|
|
204
|
+
warn: (m, c) => emit("warn", m, c),
|
|
205
|
+
error: (m, c) => emit("error", m, c),
|
|
206
|
+
child: (b) => consoleLogger({ ...bindings, ...b })
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/rate-limiter.ts
|
|
211
|
+
var TokenBucketRateLimiter = class {
|
|
212
|
+
tokens;
|
|
213
|
+
lastRefillAt;
|
|
214
|
+
capacity;
|
|
215
|
+
intervalMs;
|
|
216
|
+
constructor(options) {
|
|
217
|
+
if (options.capacity <= 0 || options.intervalMs <= 0) {
|
|
218
|
+
throw new RangeError("capacity and intervalMs must be > 0");
|
|
219
|
+
}
|
|
220
|
+
this.capacity = options.capacity;
|
|
221
|
+
this.intervalMs = options.intervalMs;
|
|
222
|
+
this.tokens = options.capacity;
|
|
223
|
+
this.lastRefillAt = Date.now();
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Refill tokens based on elapsed time, then return ms to wait until the next
|
|
227
|
+
* token is available (0 if one is available right now).
|
|
228
|
+
*/
|
|
229
|
+
timeUntilNextToken() {
|
|
230
|
+
const now = Date.now();
|
|
231
|
+
const elapsed = now - this.lastRefillAt;
|
|
232
|
+
if (elapsed > 0) {
|
|
233
|
+
const refill = elapsed / this.intervalMs * this.capacity;
|
|
234
|
+
this.tokens = Math.min(this.capacity, this.tokens + refill);
|
|
235
|
+
this.lastRefillAt = now;
|
|
236
|
+
}
|
|
237
|
+
if (this.tokens >= 1) return 0;
|
|
238
|
+
const needed = 1 - this.tokens;
|
|
239
|
+
return Math.ceil(needed / this.capacity * this.intervalMs);
|
|
240
|
+
}
|
|
241
|
+
async acquire(signal) {
|
|
242
|
+
for (; ; ) {
|
|
243
|
+
if (signal?.aborted) throw signal.reason;
|
|
244
|
+
const wait = this.timeUntilNextToken();
|
|
245
|
+
if (wait === 0) {
|
|
246
|
+
this.tokens -= 1;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
await new Promise((resolve, reject) => {
|
|
250
|
+
const timer = setTimeout(resolve, wait);
|
|
251
|
+
signal?.addEventListener(
|
|
252
|
+
"abort",
|
|
253
|
+
() => {
|
|
254
|
+
clearTimeout(timer);
|
|
255
|
+
reject(signal.reason);
|
|
256
|
+
},
|
|
257
|
+
{ once: true }
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** Snapshot of available tokens (refills the bucket as a side effect). */
|
|
263
|
+
available() {
|
|
264
|
+
this.timeUntilNextToken();
|
|
265
|
+
return this.tokens;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export { AuthError, EUR, GBP, LoncaError, NetworkError, NotFoundError, RateLimitError, ServerError, TRY, TimeoutError, TokenBucketRateLimiter, USD, ValidationError, addMoney, consoleLogger, isLoncaError, isRetryableError, isSameCurrency, isValidCurrencyCode, money, moneyFromMajor, moneyToMajor, noopLogger, paginate, retry, subMoney };
|
|
270
|
+
//# sourceMappingURL=index.js.map
|
|
271
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/currency.ts","../src/money.ts","../src/pagination.ts","../src/errors.ts","../src/retry.ts","../src/logger.ts","../src/rate-limiter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * ISO 4217 three-letter currency code.\n *\n * Typed as a string for flexibility — Trendyol and similar marketplaces may\n * expose currencies beyond the common set, so we don't lock down to a union.\n * Use `isValidCurrencyCode` for runtime validation.\n */\nexport type Currency = string;\n\nexport const TRY = 'TRY';\nexport const USD = 'USD';\nexport const EUR = 'EUR';\nexport const GBP = 'GBP';\n\nconst ISO_4217_RE = /^[A-Z]{3}$/;\n\nexport function isValidCurrencyCode(code: string): boolean {\n return ISO_4217_RE.test(code);\n}\n","import type { Currency } from './currency.js';\n\n/**\n * A monetary amount in integer minor units (e.g., kuruş for TRY, cents for USD).\n *\n * Using integers avoids floating-point surprises. Convert to/from major units\n * with `moneyFromMajor` and `moneyToMajor`.\n */\nexport interface Money {\n amount: number;\n currency: Currency;\n}\n\nexport function money(amount: number, currency: Currency): Money {\n if (!Number.isInteger(amount)) {\n throw new TypeError(`Money amount must be an integer minor unit, got ${amount}`);\n }\n return { amount, currency };\n}\n\nexport function moneyFromMajor(major: number, currency: Currency, minorScale = 2): Money {\n const factor = 10 ** minorScale;\n const amount = Math.round(major * factor);\n return money(amount, currency);\n}\n\nexport function moneyToMajor(value: Money, minorScale = 2): number {\n return value.amount / 10 ** minorScale;\n}\n\nexport function isSameCurrency(a: Money, b: Money): boolean {\n return a.currency === b.currency;\n}\n\nexport function addMoney(a: Money, b: Money): Money {\n if (!isSameCurrency(a, b)) {\n throw new TypeError(`Cannot add Money of different currencies: ${a.currency} vs ${b.currency}`);\n }\n return money(a.amount + b.amount, a.currency);\n}\n\nexport function subMoney(a: Money, b: Money): Money {\n if (!isSameCurrency(a, b)) {\n throw new TypeError(\n `Cannot subtract Money of different currencies: ${a.currency} vs ${b.currency}`,\n );\n }\n return money(a.amount - b.amount, a.currency);\n}\n","export interface CursorPaginationParams {\n /** Opaque cursor from previous response's `nextCursor`. Omit for the first page. */\n cursor?: string;\n /** Maximum items in the page. Marketplace SDKs may clamp this to their own bounds. */\n limit?: number;\n}\n\nexport interface CursorPage<T> {\n items: T[];\n /** Opaque cursor for the next page. Absent when there are no more pages. */\n nextCursor?: string;\n}\n\n/**\n * Drive a cursor-paginated endpoint as an async iterator.\n *\n * @example\n * for await (const order of paginate((p) => client.orders.list(p))) {\n * console.log(order.id);\n * }\n */\nexport async function* paginate<T>(\n fetchPage: (params: CursorPaginationParams) => Promise<CursorPage<T>>,\n params: Omit<CursorPaginationParams, 'cursor'> = {},\n): AsyncGenerator<T, void, undefined> {\n let cursor: string | undefined;\n do {\n const page = await fetchPage({ ...params, cursor });\n for (const item of page.items) {\n yield item;\n }\n cursor = page.nextCursor;\n } while (cursor);\n}\n","export type LoncaErrorCode =\n | 'AUTH_FAILED'\n | 'RATE_LIMITED'\n | 'VALIDATION_FAILED'\n | 'NOT_FOUND'\n | 'SERVER_ERROR'\n | 'NETWORK_ERROR'\n | 'TIMEOUT'\n | 'UNKNOWN';\n\nexport interface LoncaErrorOptions {\n code: LoncaErrorCode;\n message: string;\n cause?: unknown;\n retryable?: boolean;\n status?: number;\n retryAfterMs?: number;\n data?: Record<string, unknown>;\n}\n\n/**\n * Root of the Lonca error hierarchy.\n *\n * Carries a tagged `code` and a `retryable` boolean so retry helpers\n * can decide what to do without sniffing message strings.\n */\nexport class LoncaError extends Error {\n readonly code: LoncaErrorCode;\n readonly retryable: boolean;\n readonly status?: number;\n readonly retryAfterMs?: number;\n readonly data?: Record<string, unknown>;\n\n constructor(opts: LoncaErrorOptions) {\n super(opts.message, opts.cause !== undefined ? { cause: opts.cause } : undefined);\n this.name = 'LoncaError';\n this.code = opts.code;\n this.retryable = opts.retryable ?? false;\n this.status = opts.status;\n this.retryAfterMs = opts.retryAfterMs;\n this.data = opts.data;\n }\n}\n\ntype SubclassOptions = Omit<LoncaErrorOptions, 'code' | 'retryable'>;\n\nexport class AuthError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'AUTH_FAILED', retryable: false });\n this.name = 'AuthError';\n }\n}\n\nexport class RateLimitError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'RATE_LIMITED', retryable: true });\n this.name = 'RateLimitError';\n }\n}\n\nexport class ValidationError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'VALIDATION_FAILED', retryable: false });\n this.name = 'ValidationError';\n }\n}\n\nexport class NotFoundError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'NOT_FOUND', retryable: false });\n this.name = 'NotFoundError';\n }\n}\n\nexport class ServerError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'SERVER_ERROR', retryable: true });\n this.name = 'ServerError';\n }\n}\n\nexport class NetworkError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'NETWORK_ERROR', retryable: true });\n this.name = 'NetworkError';\n }\n}\n\nexport class TimeoutError extends LoncaError {\n constructor(opts: SubclassOptions) {\n super({ ...opts, code: 'TIMEOUT', retryable: true });\n this.name = 'TimeoutError';\n }\n}\n\nexport function isLoncaError(value: unknown): value is LoncaError {\n return value instanceof LoncaError;\n}\n\nexport function isRetryableError(value: unknown): boolean {\n return isLoncaError(value) && value.retryable;\n}\n","import { isLoncaError, isRetryableError } from './errors.js';\n\nexport interface RetryOptions {\n /** Total number of attempts including the initial call. Defaults to 3. */\n maxAttempts?: number;\n /** Initial delay before the second attempt, in ms. Defaults to 200. */\n baseDelayMs?: number;\n /** Upper bound on delay between any two attempts, in ms. Defaults to 10_000. */\n maxDelayMs?: number;\n /** Add up to 50% random jitter on each delay. Defaults to true. */\n jitter?: boolean;\n /** Override the default predicate. Default treats `retryable` LoncaErrors as retryable. */\n isRetryable?: (err: unknown) => boolean;\n /** Called before each retry sleep. Useful for structured logging. */\n onRetry?: (err: unknown, attempt: number, nextDelayMs: number) => void;\n /** Abort all pending sleeps and bubble the signal's reason. */\n signal?: AbortSignal;\n}\n\nconst DEFAULTS = {\n maxAttempts: 3,\n baseDelayMs: 200,\n maxDelayMs: 10_000,\n jitter: true,\n} as const;\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(signal.reason);\n },\n { once: true },\n );\n });\n}\n\nexport async function retry<T>(\n fn: (attempt: number) => Promise<T>,\n options: RetryOptions = {},\n): Promise<T> {\n const maxAttempts = options.maxAttempts ?? DEFAULTS.maxAttempts;\n const baseDelayMs = options.baseDelayMs ?? DEFAULTS.baseDelayMs;\n const maxDelayMs = options.maxDelayMs ?? DEFAULTS.maxDelayMs;\n const jitter = options.jitter ?? DEFAULTS.jitter;\n const isRetryable = options.isRetryable ?? isRetryableError;\n\n let lastErr: unknown;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn(attempt);\n } catch (err) {\n lastErr = err;\n if (attempt === maxAttempts || !isRetryable(err)) {\n throw err;\n }\n\n const retryAfterMs = isLoncaError(err) ? err.retryAfterMs : undefined;\n const exponential = Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1));\n const base = retryAfterMs ?? exponential;\n const jitterMs = jitter ? Math.random() * base * 0.5 : 0;\n const delay = Math.min(maxDelayMs, base + jitterMs);\n\n options.onRetry?.(err, attempt, delay);\n await sleep(delay, options.signal);\n }\n }\n throw lastErr;\n}\n","export type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nexport type LogContext = Record<string, unknown>;\n\nexport interface Logger {\n debug(message: string, context?: LogContext): void;\n info(message: string, context?: LogContext): void;\n warn(message: string, context?: LogContext): void;\n error(message: string, context?: LogContext): void;\n /** Create a child logger that merges `bindings` into every log entry. */\n child(bindings: LogContext): Logger;\n}\n\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n child: () => noopLogger,\n};\n\n/**\n * Minimal JSON-line logger to stdout/stderr. Suitable for development and\n * containerized environments. For production prefer wiring a real logger\n * (pino, winston) by implementing the `Logger` interface.\n */\nexport function consoleLogger(bindings: LogContext = {}): Logger {\n const emit = (level: LogLevel, message: string, context?: LogContext): void => {\n const entry = {\n level,\n message,\n timestamp: new Date().toISOString(),\n ...bindings,\n ...context,\n };\n const line = JSON.stringify(entry);\n if (level === 'error' || level === 'warn') {\n console.error(line);\n } else {\n console.log(line);\n }\n };\n return {\n debug: (m, c) => emit('debug', m, c),\n info: (m, c) => emit('info', m, c),\n warn: (m, c) => emit('warn', m, c),\n error: (m, c) => emit('error', m, c),\n child: (b) => consoleLogger({ ...bindings, ...b }),\n };\n}\n","export interface RateLimiterOptions {\n /** Maximum number of tokens the bucket holds. Each `acquire()` consumes 1. */\n capacity: number;\n /** Time window in milliseconds over which `capacity` tokens are refilled. */\n intervalMs: number;\n}\n\n/**\n * Token-bucket rate limiter. `acquire()` returns immediately when a token\n * is available, otherwise it sleeps until enough tokens have been refilled.\n *\n * @example\n * const limiter = new TokenBucketRateLimiter({ capacity: 50, intervalMs: 60_000 });\n * for (const item of items) {\n * await limiter.acquire();\n * await fetchSomething(item);\n * }\n */\nexport class TokenBucketRateLimiter {\n private tokens: number;\n private lastRefillAt: number;\n private readonly capacity: number;\n private readonly intervalMs: number;\n\n constructor(options: RateLimiterOptions) {\n if (options.capacity <= 0 || options.intervalMs <= 0) {\n throw new RangeError('capacity and intervalMs must be > 0');\n }\n this.capacity = options.capacity;\n this.intervalMs = options.intervalMs;\n this.tokens = options.capacity;\n this.lastRefillAt = Date.now();\n }\n\n /**\n * Refill tokens based on elapsed time, then return ms to wait until the next\n * token is available (0 if one is available right now).\n */\n private timeUntilNextToken(): number {\n const now = Date.now();\n const elapsed = now - this.lastRefillAt;\n if (elapsed > 0) {\n const refill = (elapsed / this.intervalMs) * this.capacity;\n this.tokens = Math.min(this.capacity, this.tokens + refill);\n this.lastRefillAt = now;\n }\n if (this.tokens >= 1) return 0;\n const needed = 1 - this.tokens;\n return Math.ceil((needed / this.capacity) * this.intervalMs);\n }\n\n async acquire(signal?: AbortSignal): Promise<void> {\n for (;;) {\n if (signal?.aborted) throw signal.reason as Error;\n const wait = this.timeUntilNextToken();\n if (wait === 0) {\n this.tokens -= 1;\n return;\n }\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, wait);\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(signal.reason);\n },\n { once: true },\n );\n });\n }\n }\n\n /** Snapshot of available tokens (refills the bucket as a side effect). */\n available(): number {\n this.timeUntilNextToken();\n return this.tokens;\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lonca/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared primitives for Lonca marketplace SDKs: money, errors, retry, logger, rate limiter.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/loncadev/lonca.git",
|
|
9
|
+
"directory": "packages/core"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/loncadev/lonca/tree/main/packages/core#readme",
|
|
12
|
+
"bugs": "https://github.com/loncadev/lonca/issues",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"require": "./dist/index.cjs"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^24.0.0",
|
|
39
|
+
"tsup": "^8.3.5",
|
|
40
|
+
"typescript": "^6.0.3",
|
|
41
|
+
"vitest": "^4.1.7"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"dev": "tsup --watch",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"lint": "eslint .",
|
|
50
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('.turbo',{recursive:true,force:true})\""
|
|
51
|
+
}
|
|
52
|
+
}
|