@coinlist-co/react 0.10.1 → 0.11.1-rc.22d81d4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/chunk-AQIHCFW4.js +279 -0
- package/dist/chunk-AQIHCFW4.js.map +1 -0
- package/dist/{chunk-AQVCOWOV.js → chunk-PRG3EDQJ.js} +216 -317
- package/dist/chunk-PRG3EDQJ.js.map +1 -0
- package/dist/chunk-ZVB6KWZ2.js +2206 -0
- package/dist/chunk-ZVB6KWZ2.js.map +1 -0
- package/dist/client/index.cjs +11285 -3319
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +4243 -881
- package/dist/client/index.d.ts +4243 -881
- package/dist/client/index.js +9103 -2389
- package/dist/client/index.js.map +1 -1
- package/dist/collections-DrJFEDHl.d.cts +116 -0
- package/dist/collections-pLtrj6fw.d.ts +116 -0
- package/dist/config-C6vlghJY.d.cts +2617 -0
- package/dist/config-C6vlghJY.d.ts +2617 -0
- package/dist/server/index.cjs +1588 -514
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +266 -162
- package/dist/server/index.d.ts +266 -162
- package/dist/server/index.js +235 -169
- package/dist/server/index.js.map +1 -1
- package/dist/shared/index.cjs +2203 -937
- package/dist/shared/index.cjs.map +1 -1
- package/dist/shared/index.d.cts +256 -132
- package/dist/shared/index.d.ts +256 -132
- package/dist/shared/index.js +102 -28
- package/package.json +12 -8
- package/dist/chunk-AQVCOWOV.js.map +0 -1
- package/dist/chunk-TBU3EBNM.js +0 -442
- package/dist/chunk-TBU3EBNM.js.map +0 -1
- package/dist/chunk-UOHD7US2.js +0 -855
- package/dist/chunk-UOHD7US2.js.map +0 -1
- package/dist/collections-Bv1Oxzu_.d.ts +0 -28
- package/dist/collections-DDyxbOPZ.d.cts +0 -28
- package/dist/requirement-oVZA1INj.d.cts +0 -1040
- package/dist/requirement-oVZA1INj.d.ts +0 -1040
package/README.md
CHANGED
|
@@ -5,3 +5,35 @@ This package is documented at:
|
|
|
5
5
|
https://docs.coinlist.co/
|
|
6
6
|
|
|
7
7
|
Please refer to the docs site for installation, configuration, and integration guides.
|
|
8
|
+
|
|
9
|
+
## Local development
|
|
10
|
+
|
|
11
|
+
The toolchain (Node 24, TypeScript) is pinned by `flake.nix` and frozen by `flake.lock`.
|
|
12
|
+
|
|
13
|
+
Prerequisites:
|
|
14
|
+
|
|
15
|
+
1. Nix — install with the [Determinate Systems installer](https://github.com/DeterminateSystems/nix-installer),
|
|
16
|
+
which enables flakes out of the box and supports clean uninstalls:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. [direnv](https://direnv.net/) and [nix-direnv](https://github.com/nix-community/nix-direnv)
|
|
23
|
+
(direnv on its own does not provide `use flake`).
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
direnv allow # activates the dev shell on every cd into the repo
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Without direnv, enter the same shell manually:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
nix develop
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The flake provides the toolchain, not `node_modules` — install JS dependencies as usual:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm ci
|
|
39
|
+
```
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Attributes,
|
|
3
|
+
HEADER_IDEMPOTENCY_KEY,
|
|
4
|
+
HttpClient,
|
|
5
|
+
HttpError,
|
|
6
|
+
Request,
|
|
7
|
+
getUUIDv4
|
|
8
|
+
} from "./chunk-ZVB6KWZ2.js";
|
|
9
|
+
|
|
10
|
+
// src/shared/api/nabu/config.ts
|
|
11
|
+
var NABU_BASE_URL = "https://asset.coinlist.co";
|
|
12
|
+
|
|
13
|
+
// src/shared/core/observability/pino-logger.ts
|
|
14
|
+
function toPinoRecord(event) {
|
|
15
|
+
try {
|
|
16
|
+
const record = {};
|
|
17
|
+
for (const key of ownKeys(event.fields)) {
|
|
18
|
+
record[key] = readProperty(event.fields, key, 0, /* @__PURE__ */ new WeakSet());
|
|
19
|
+
}
|
|
20
|
+
const cause = "cause" in event ? event.cause : void 0;
|
|
21
|
+
if (cause !== void 0) {
|
|
22
|
+
record.cause = sanitize(cause, 0, /* @__PURE__ */ new WeakSet());
|
|
23
|
+
}
|
|
24
|
+
return { ...record, scope: event.scope, ...event.bindings };
|
|
25
|
+
} catch (_) {
|
|
26
|
+
return { "log.render": "<unrenderable event>" };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
var PINO_LEVEL = {
|
|
30
|
+
none: "silent",
|
|
31
|
+
error: "error",
|
|
32
|
+
warn: "warn",
|
|
33
|
+
info: "info",
|
|
34
|
+
debug: "debug"
|
|
35
|
+
};
|
|
36
|
+
var MAX_DEPTH = 8;
|
|
37
|
+
function sanitize(value, depth, seen) {
|
|
38
|
+
switch (typeof value) {
|
|
39
|
+
case "string":
|
|
40
|
+
case "boolean":
|
|
41
|
+
return value;
|
|
42
|
+
case "number":
|
|
43
|
+
return Number.isFinite(value) ? value : String(value);
|
|
44
|
+
case "bigint":
|
|
45
|
+
return value.toString();
|
|
46
|
+
case "undefined":
|
|
47
|
+
return "<undefined>";
|
|
48
|
+
case "function":
|
|
49
|
+
return "<function>";
|
|
50
|
+
case "symbol":
|
|
51
|
+
return value.toString();
|
|
52
|
+
case "object":
|
|
53
|
+
return value === null ? null : sanitizeObject(value, depth, seen);
|
|
54
|
+
default:
|
|
55
|
+
return "<unrenderable>";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function sanitizeObject(value, depth, seen) {
|
|
59
|
+
if (seen.has(value)) return "<circular>";
|
|
60
|
+
if (depth >= MAX_DEPTH) return "<max depth>";
|
|
61
|
+
if (value instanceof Error) return describeError(value);
|
|
62
|
+
if (value instanceof Date) return describeDate(value);
|
|
63
|
+
seen.add(value);
|
|
64
|
+
try {
|
|
65
|
+
if (Array.isArray(value)) {
|
|
66
|
+
return value.map(
|
|
67
|
+
(_item, index) => readProperty(value, String(index), depth, seen)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const out = {};
|
|
71
|
+
for (const key of ownKeys(value)) {
|
|
72
|
+
out[key] = readProperty(value, key, depth, seen);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
} finally {
|
|
76
|
+
seen.delete(value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function readProperty(owner, key, depth, seen) {
|
|
80
|
+
try {
|
|
81
|
+
return sanitize(owner[key], depth + 1, seen);
|
|
82
|
+
} catch (_) {
|
|
83
|
+
return "<unreadable>";
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function ownKeys(value) {
|
|
87
|
+
try {
|
|
88
|
+
return Object.keys(value);
|
|
89
|
+
} catch (_) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function describeError(error) {
|
|
94
|
+
return {
|
|
95
|
+
name: safeRead(() => error.name),
|
|
96
|
+
message: safeRead(() => error.message),
|
|
97
|
+
stack: safeRead(() => error.stack)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function describeDate(date) {
|
|
101
|
+
return safeRead(() => date.toISOString());
|
|
102
|
+
}
|
|
103
|
+
function safeRead(read) {
|
|
104
|
+
try {
|
|
105
|
+
const value = read();
|
|
106
|
+
return typeof value === "string" ? value : "<unreadable>";
|
|
107
|
+
} catch (_) {
|
|
108
|
+
return "<unreadable>";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
var SDK_LOGGER_NAME = "@coinlist-co/react";
|
|
112
|
+
function loggerOverPino(sink, level) {
|
|
113
|
+
return {
|
|
114
|
+
level: () => level,
|
|
115
|
+
debug: (event) => emit(sink, "debug", event),
|
|
116
|
+
info: (event) => emit(sink, "info", event),
|
|
117
|
+
warn: (event) => emit(sink, "warn", event),
|
|
118
|
+
error: (event) => emit(sink, "error", event)
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function emit(sink, method, event) {
|
|
122
|
+
try {
|
|
123
|
+
const value = event();
|
|
124
|
+
sink[method](toPinoRecord(value), value.msg);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
reportRenderFailure(sink, error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function reportRenderFailure(sink, error) {
|
|
130
|
+
try {
|
|
131
|
+
sink.error(
|
|
132
|
+
{ "error.type": error instanceof Error ? error.name : typeof error },
|
|
133
|
+
"log event failed to render"
|
|
134
|
+
);
|
|
135
|
+
} catch (_) {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/shared/api/middleware/attach-session-middleware.ts
|
|
140
|
+
function attachSessionMiddleware(fetchAccessToken) {
|
|
141
|
+
return async (request) => {
|
|
142
|
+
if (!Attributes.isProtected(request.attributes)) {
|
|
143
|
+
return request;
|
|
144
|
+
}
|
|
145
|
+
let accessToken = await fetchAccessToken(false);
|
|
146
|
+
if (!accessToken) {
|
|
147
|
+
const clientCreds = Attributes.getClientCredentials(request.attributes);
|
|
148
|
+
if (clientCreds) {
|
|
149
|
+
accessToken = clientCreds;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (accessToken === null) {
|
|
153
|
+
return request;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
...request,
|
|
157
|
+
headers: {
|
|
158
|
+
...request.headers ?? {},
|
|
159
|
+
Authorization: `Bearer ${accessToken.value}`
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/shared/api/middleware/idempotency-key.ts
|
|
166
|
+
var idempotencyKeyMiddleware = async (request) => {
|
|
167
|
+
if (!Attributes.isIdempotent(request.attributes)) {
|
|
168
|
+
return request;
|
|
169
|
+
}
|
|
170
|
+
const existingHeaders = request.headers ?? {};
|
|
171
|
+
if (existingHeaders[HEADER_IDEMPOTENCY_KEY]) {
|
|
172
|
+
return request;
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
...request,
|
|
176
|
+
headers: {
|
|
177
|
+
...existingHeaders,
|
|
178
|
+
[HEADER_IDEMPOTENCY_KEY]: getUUIDv4()
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// src/shared/api/middleware/request-retry.ts
|
|
184
|
+
var MAX_ATTEMPTS = 3;
|
|
185
|
+
var INITIAL_DELAY_MS = 300;
|
|
186
|
+
var MAX_DELAY_MS = 2e3;
|
|
187
|
+
var RETRYABLE_4XX = /* @__PURE__ */ new Set([408, 409, 429]);
|
|
188
|
+
function isRetryableStatus(status) {
|
|
189
|
+
return status >= 500 && status < 600 || RETRYABLE_4XX.has(status);
|
|
190
|
+
}
|
|
191
|
+
function getRetryDelayMs(attempt) {
|
|
192
|
+
return Math.min(INITIAL_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
|
|
193
|
+
}
|
|
194
|
+
function createRequestRetryMiddleware(options = {}) {
|
|
195
|
+
const delayFn = options.delayFn ?? defaultDelay;
|
|
196
|
+
return async ({ request, response, retry }) => {
|
|
197
|
+
if (!isRetryableStatus(response.status)) {
|
|
198
|
+
return response;
|
|
199
|
+
}
|
|
200
|
+
const currentAttempt = Attributes.getRetryAttempt(request.attributes);
|
|
201
|
+
if (currentAttempt >= MAX_ATTEMPTS - 1) {
|
|
202
|
+
return response;
|
|
203
|
+
}
|
|
204
|
+
const nextAttempt = currentAttempt + 1;
|
|
205
|
+
await delayFn(getRetryDelayMs(nextAttempt));
|
|
206
|
+
const nextRequest = Request.concatAttributes(
|
|
207
|
+
request,
|
|
208
|
+
Attributes.retryAttempt(nextAttempt)
|
|
209
|
+
);
|
|
210
|
+
return retry(nextRequest);
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function defaultDelay(ms) {
|
|
214
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
215
|
+
}
|
|
216
|
+
var requestRetryMiddleware = createRequestRetryMiddleware();
|
|
217
|
+
|
|
218
|
+
// src/shared/api/middleware/session-renewal.ts
|
|
219
|
+
function renewSessionMiddleware(fetchAccessToken) {
|
|
220
|
+
return async ({ request, response, retry }) => {
|
|
221
|
+
if (response.status !== 401) {
|
|
222
|
+
return response;
|
|
223
|
+
}
|
|
224
|
+
if (!Attributes.isProtected(request.attributes)) {
|
|
225
|
+
return response;
|
|
226
|
+
}
|
|
227
|
+
if (Attributes.wasRenewAttempted(request.attributes)) {
|
|
228
|
+
return response;
|
|
229
|
+
}
|
|
230
|
+
const newToken = await fetchAccessToken(true);
|
|
231
|
+
if (newToken) {
|
|
232
|
+
const nextRequest = Request.concatAttributes(
|
|
233
|
+
request,
|
|
234
|
+
Attributes.renewAttempted(true)
|
|
235
|
+
);
|
|
236
|
+
return retry(nextRequest);
|
|
237
|
+
} else {
|
|
238
|
+
return response;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/shared/api/authenticated-api-client.ts
|
|
244
|
+
var AuthenticatedApiClient = class {
|
|
245
|
+
constructor(config, fetchAccessToken, additionalBeforeRequest = [], logger = null) {
|
|
246
|
+
this.httpClient = new HttpClient(
|
|
247
|
+
config,
|
|
248
|
+
{
|
|
249
|
+
beforeRequest: [
|
|
250
|
+
attachSessionMiddleware(fetchAccessToken),
|
|
251
|
+
...additionalBeforeRequest,
|
|
252
|
+
idempotencyKeyMiddleware
|
|
253
|
+
],
|
|
254
|
+
afterRequest: [
|
|
255
|
+
renewSessionMiddleware(fetchAccessToken),
|
|
256
|
+
requestRetryMiddleware
|
|
257
|
+
]
|
|
258
|
+
},
|
|
259
|
+
logger
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
async send(request) {
|
|
263
|
+
const response = await this.httpClient.send(request);
|
|
264
|
+
if (response.status >= 200 && response.status < 300) {
|
|
265
|
+
return response.body;
|
|
266
|
+
} else {
|
|
267
|
+
throw new HttpError(response);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export {
|
|
273
|
+
AuthenticatedApiClient,
|
|
274
|
+
NABU_BASE_URL,
|
|
275
|
+
PINO_LEVEL,
|
|
276
|
+
SDK_LOGGER_NAME,
|
|
277
|
+
loggerOverPino
|
|
278
|
+
};
|
|
279
|
+
//# sourceMappingURL=chunk-AQIHCFW4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/api/nabu/config.ts","../src/shared/core/observability/pino-logger.ts","../src/shared/api/middleware/attach-session-middleware.ts","../src/shared/api/middleware/idempotency-key.ts","../src/shared/api/middleware/request-retry.ts","../src/shared/api/middleware/session-renewal.ts","../src/shared/api/authenticated-api-client.ts"],"sourcesContent":["/**\n * Base URL of CoinList's public token registry (Nabu), a static CDN serving\n * token display metadata keyed by chain + contract address. Override per\n * environment via `Config.tokensBaseUrl`.\n */\nexport const NABU_BASE_URL = 'https://asset.coinlist.co';\n","import type {\n DebugEvent,\n Logger,\n LogLevel,\n SafeEvent,\n} from '@/shared/types/logger';\n\n/**\n * The SDK's logging seam expressed over pino, minus the environment.\n *\n * Imports no pino - {@link PinoSink} is a structural type - so `@/shared`\n * stays pino-free and the `./shared` entry point costs a consumer nothing.\n * Both shipped implementations\n * ({@link pinoClientLogger}, {@link pinoServerLogger}) differ only in the pino\n * instance they construct; everything worth testing lives here.\n *\n * ## Nothing here may throw\n *\n * A {@link Logger} implementation is called inline, on the codepath of the\n * work it reports, and the SDK does not catch it - so a log line that fails to\n * render would turn a successful request into an exception. That risk is real\n * rather than theoretical: `'debug'` fields carry whatever the SDK was\n * handling, and this codebase routinely handles `bigint` (every uint256 on\n * every DTO), which `JSON.stringify` throws on. Response bodies may be\n * circular, params may hold functions, and a host's object may have a getter\n * that throws.\n *\n * So {@link toPinoRecord} is total by construction: every value is rendered\n * into something a JSON serialiser cannot refuse, every property read is\n * guarded, and the whole translation has a fallback. Pino is never handed a\n * value it could fail on.\n */\n\n/**\n * Flattens an event into the object pino merges into its output line.\n *\n * Flat rather than nested, because nesting is what a host has to un-nest\n * before they can facet on it: `scope`, the {@link LogBindings} and every\n * field sit at the top level, and only `cause` stays an object because its\n * arms are a closed union a host switches on.\n *\n * `msg` is **not** included: it is pino's own second argument.\n *\n * Never throws. A value it cannot render becomes a `<tag>` string rather than\n * an exception, and a translation that fails entirely degrades to a record\n * naming the failure.\n */\nexport function toPinoRecord(event: SafeEvent | DebugEvent): PinoRecord {\n try {\n const record: Record<string, unknown> = {};\n\n for (const key of ownKeys(event.fields)) {\n record[key] = readProperty(event.fields, key, 0, new WeakSet());\n }\n\n const cause = 'cause' in event ? event.cause : undefined;\n if (cause !== undefined) {\n record.cause = sanitize(cause, 0, new WeakSet());\n }\n\n // Last, so the stamped keys win. `scope` and the bindings are what a host\n // routes and filters on, and they are stamped by `internalLogger` rather\n // than written at a call site - so a field that happened to be named\n // `scope` or `op` must not be able to take their place.\n return { ...record, scope: event.scope, ...event.bindings };\n } catch (_) {\n // The event itself is malformed - a Proxy for `fields`, a frozen exotic\n // object. Report that rather than losing the line, and never rethrow.\n return { 'log.render': '<unrenderable event>' };\n }\n}\n\n/** What pino merges into one output line. */\nexport type PinoRecord = Readonly<Record<string, unknown>>;\n\n/**\n * The SDK's five levels in pino's vocabulary.\n *\n * `'none'` is pino's `'silent'`; the other four are the same word in both, and\n * deliberately so. The SDK does not adopt pino's seven because `'trace'` would\n * sit below `'debug'`, and the guarantee \"`debug` is unredacted and every\n * other level is not\" only closes while `'debug'` is the bottom.\n */\nexport const PINO_LEVEL = {\n none: 'silent',\n error: 'error',\n warn: 'warn',\n info: 'info',\n debug: 'debug',\n} as const satisfies Record<LogLevel, string>;\n\n/**\n * How deep a `'debug'` field is followed before it is summarised.\n *\n * A bound rather than a guess at what is deep enough: a self-referential\n * structure is already caught by the cycle check, but a legitimately deep DTO\n * would otherwise cost real time inside a log line.\n */\nconst MAX_DEPTH = 8;\n\n/**\n * Renders any value into something a JSON serialiser cannot refuse.\n *\n * `bigint` is the one that matters in practice - every uint256 the SDK handles\n * is one - but the rest are all reachable from a `'debug'` field: `undefined`\n * and functions from an operation's params, `NaN` from arithmetic, symbols\n * from a host's object.\n */\nfunction sanitize(\n value: unknown,\n depth: number,\n seen: WeakSet<object>\n): unknown {\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return value;\n case 'number':\n // `NaN` and the infinities serialise to `null`, which reads as \"absent\"\n // rather than \"not a number\". Name them instead.\n return Number.isFinite(value) ? value : String(value);\n case 'bigint':\n return value.toString();\n case 'undefined':\n return '<undefined>';\n case 'function':\n return '<function>';\n case 'symbol':\n return value.toString();\n case 'object':\n return value === null ? null : sanitizeObject(value, depth, seen);\n default:\n return '<unrenderable>';\n }\n}\n\nfunction sanitizeObject(\n value: object,\n depth: number,\n seen: WeakSet<object>\n): unknown {\n if (seen.has(value)) return '<circular>';\n if (depth >= MAX_DEPTH) return '<max depth>';\n\n // `name`, `message` and `stack` are all non-enumerable, so an `Error` handed\n // to a structured sink as an object serialises to `{}`.\n if (value instanceof Error) return describeError(value);\n if (value instanceof Date) return describeDate(value);\n\n seen.add(value);\n try {\n if (Array.isArray(value)) {\n return value.map((_item, index) =>\n readProperty(value, String(index), depth, seen)\n );\n }\n\n const out: Record<string, unknown> = {};\n for (const key of ownKeys(value)) {\n out[key] = readProperty(value, key, depth, seen);\n }\n return out;\n } finally {\n // Removed on the way out, so a value referenced twice in a tree renders\n // twice. Only a genuine cycle is `<circular>`.\n seen.delete(value);\n }\n}\n\n/**\n * One property read, guarded. A getter may throw, and a log line is not\n * allowed to be the reason a request fails.\n */\nfunction readProperty(\n owner: object,\n key: string,\n depth: number,\n seen: WeakSet<object>\n): unknown {\n try {\n return sanitize((owner as Record<string, unknown>)[key], depth + 1, seen);\n } catch (_) {\n return '<unreadable>';\n }\n}\n\n/** Own enumerable keys, guarded: an exotic object may refuse to be enumerated. */\nfunction ownKeys(value: object): readonly string[] {\n try {\n return Object.keys(value);\n } catch (_) {\n return [];\n }\n}\n\nfunction describeError(error: Error): Record<string, unknown> {\n return {\n name: safeRead(() => error.name),\n message: safeRead(() => error.message),\n stack: safeRead(() => error.stack),\n };\n}\n\nfunction describeDate(date: Date): unknown {\n // An invalid `Date` throws from `toISOString`.\n return safeRead(() => date.toISOString());\n}\n\nfunction safeRead(read: () => unknown): unknown {\n try {\n const value = read();\n return typeof value === 'string' ? value : '<unreadable>';\n } catch (_) {\n return '<unreadable>';\n }\n}\n\n/**\n * The part of a pino logger this adapter uses.\n *\n * Structural rather than `import type { Logger } from 'pino'`, so that\n * `@/shared` takes no dependency on pino at all. A real pino instance - node\n * or browser build - satisfies it.\n */\nexport type PinoSink = {\n debug(record: PinoRecord, msg: string): void;\n info(record: PinoRecord, msg: string): void;\n warn(record: PinoRecord, msg: string): void;\n error(record: PinoRecord, msg: string): void;\n};\n\n/**\n * The `name` binding every SDK line carries, so a partner can separate the\n * SDK's output from their own in a shared aggregator without matching on\n * `scope`.\n *\n * Applied through `pino.child({ name })` rather than pino's `name` *option*,\n * which its browser build ignores. The browser lane's spec is what caught\n * that, and is what keeps it caught.\n */\nexport const SDK_LOGGER_NAME = '@coinlist-co/react';\n\n/**\n * Adapts a pino instance into the {@link Logger} the SDK consumes.\n *\n * **Total, and that is the point.** The SDK calls a `Logger` inline and does\n * not catch it, so an exception raised while reporting a successful request\n * would fail that request. Three things could raise one - the event lambda\n * itself, rendering the event, and the sink - and all three are caught here.\n * A render failure is reported through the sink rather than swallowed, since a\n * log line that silently vanishes is a bug nobody finds; if the sink is what\n * broke, there is nowhere left to report it and the error stops here.\n *\n * **The level is fixed here, and that is this adapter's choice rather than the\n * seam's.** {@link InternalLogger} calls {@link Logger.level} before *every*\n * log call and caches nothing, so a host-implemented `Logger` reading a mutable\n * field has a level they can change at runtime. This adapter captures `level`\n * instead, and the pino instance it wraps is built with a fixed one - so\n * `Logger.level` is a constant lookup and two loggers never interfere. A host\n * who wants to turn `'debug'` on without rebuilding implements {@link Logger}\n * directly over their own pino instance; the port is four methods and this\n * function is the worked example.\n */\nexport function loggerOverPino(sink: PinoSink, level: LogLevel): Logger {\n return {\n level: () => level,\n debug: (event) => emit(sink, 'debug', event),\n info: (event) => emit(sink, 'info', event),\n warn: (event) => emit(sink, 'warn', event),\n error: (event) => emit(sink, 'error', event),\n };\n}\n\nfunction emit(\n sink: PinoSink,\n method: keyof PinoSink,\n event: () => SafeEvent | DebugEvent\n): void {\n try {\n const value = event();\n sink[method](toPinoRecord(value), value.msg);\n } catch (error) {\n reportRenderFailure(sink, error);\n }\n}\n\nfunction reportRenderFailure(sink: PinoSink, error: unknown): void {\n try {\n sink.error(\n { 'error.type': error instanceof Error ? error.name : typeof error },\n 'log event failed to render'\n );\n } catch (_) {\n // The sink itself threw. There is nowhere left to report this, and the SDK\n // is on the other side of the call that got us here.\n }\n}\n","import type { HttpRequest } from '@/shared/api/http';\nimport { Attributes } from '@/shared/api/http-attributes';\nimport type { BeforeRequestMiddleware } from '@/shared/api/http-client';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\n\nexport function attachSessionMiddleware(\n fetchAccessToken: (refresh: boolean) => Promise<OAuthAccessToken | null>\n): BeforeRequestMiddleware {\n return async (request: HttpRequest): Promise<HttpRequest> => {\n if (!Attributes.isProtected(request.attributes)) {\n return request;\n }\n\n let accessToken = await fetchAccessToken(false);\n if (!accessToken) {\n const clientCreds = Attributes.getClientCredentials(request.attributes);\n if (clientCreds) {\n accessToken = clientCreds;\n }\n }\n\n if (accessToken === null) {\n return request;\n }\n\n return {\n ...request,\n headers: {\n ...(request.headers ?? {}),\n Authorization: `Bearer ${accessToken.value}`,\n },\n };\n };\n}\n","import { HEADER_IDEMPOTENCY_KEY } from '@/shared/api/frontline/config';\nimport type { HttpRequest } from '@/shared/api/http';\nimport { Attributes } from '@/shared/api/http-attributes';\nimport type { BeforeRequestMiddleware } from '@/shared/api/http-client';\nimport { getUUIDv4 } from '@/shared/core/utils/crypto';\n\nexport const idempotencyKeyMiddleware: BeforeRequestMiddleware = async (\n request: HttpRequest\n): Promise<HttpRequest> => {\n if (!Attributes.isIdempotent(request.attributes)) {\n return request;\n }\n\n const existingHeaders = request.headers ?? {};\n if (existingHeaders[HEADER_IDEMPOTENCY_KEY]) {\n return request;\n }\n\n return {\n ...request,\n headers: {\n ...existingHeaders,\n [HEADER_IDEMPOTENCY_KEY]: getUUIDv4(),\n },\n };\n};\n","import { Request } from '@/shared/api/http';\nimport { Attributes } from '@/shared/api/http-attributes';\nimport type { AfterRequestMiddleware } from '@/shared/api/http-client';\n\nconst MAX_ATTEMPTS = 3;\nconst INITIAL_DELAY_MS = 300;\nconst MAX_DELAY_MS = 2000;\n\nconst RETRYABLE_4XX = new Set([408, 409, 429]);\n\nexport function isRetryableStatus(status: number): boolean {\n return (status >= 500 && status < 600) || RETRYABLE_4XX.has(status);\n}\n\nfunction getRetryDelayMs(attempt: number): number {\n return Math.min(INITIAL_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);\n}\n\nexport type RequestRetryMiddlewareOptions = {\n /**\n * Optional delay function. Defaults to real setTimeout-based delay.\n * Use a no-op (e.g. () => Promise.resolve()) in tests to avoid slow tests.\n */\n delayFn?: (ms: number) => Promise<void>;\n};\n\n/**\n * Creates the request retry after-request middleware. Inject a no-op delayFn\n * in tests to avoid real delays (unit testing best practice).\n */\nexport function createRequestRetryMiddleware(\n options: RequestRetryMiddlewareOptions = {}\n): AfterRequestMiddleware {\n const delayFn = options.delayFn ?? defaultDelay;\n\n return async ({ request, response, retry }) => {\n if (!isRetryableStatus(response.status)) {\n return response;\n }\n\n const currentAttempt = Attributes.getRetryAttempt(request.attributes);\n if (currentAttempt >= MAX_ATTEMPTS - 1) {\n return response;\n }\n\n const nextAttempt = currentAttempt + 1;\n await delayFn(getRetryDelayMs(nextAttempt));\n\n const nextRequest = Request.concatAttributes(\n request,\n Attributes.retryAttempt(nextAttempt)\n );\n return retry(nextRequest);\n };\n}\n\nfunction defaultDelay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Retries the request up to 3 times with exponential backoff when the response\n * has a retryable status (5xx server errors). Uses request.attributes.retryAttempt\n * to decide delay and whether to retry, so the middleware does not loop when\n * retry() runs the full HTTP middleware chain again.\n */\nexport const requestRetryMiddleware: AfterRequestMiddleware =\n createRequestRetryMiddleware();\n","import { Request } from '@/shared/api/http';\nimport { Attributes } from '@/shared/api/http-attributes';\nimport type { AfterRequestMiddleware } from '@/shared/api/http-client';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\n\nexport function renewSessionMiddleware(\n fetchAccessToken: (refresh: boolean) => Promise<OAuthAccessToken | null>\n): AfterRequestMiddleware {\n return async ({ request, response, retry }) => {\n if (response.status !== 401) {\n return response;\n }\n if (!Attributes.isProtected(request.attributes)) {\n return response;\n }\n if (Attributes.wasRenewAttempted(request.attributes)) {\n return response;\n }\n\n const newToken = await fetchAccessToken(true);\n if (newToken) {\n const nextRequest = Request.concatAttributes(\n request,\n Attributes.renewAttempted(true)\n );\n return retry(nextRequest);\n } else {\n return response;\n }\n };\n}\n","import type { HttpClientConfig, HttpRequest } from '@/shared/api/http';\nimport { HttpError } from '@/shared/api/http';\nimport type { BeforeRequestMiddleware } from '@/shared/api/http-client';\nimport { HttpClient } from '@/shared/api/http-client';\nimport { attachSessionMiddleware } from '@/shared/api/middleware/attach-session-middleware';\nimport { idempotencyKeyMiddleware } from '@/shared/api/middleware/idempotency-key';\nimport { requestRetryMiddleware } from '@/shared/api/middleware/request-retry';\nimport { renewSessionMiddleware } from '@/shared/api/middleware/session-renewal';\nimport type { Logger } from '@/shared/types/logger';\nimport type { OAuthAccessToken } from '@/shared/types/oauth-session';\n\n/**\n * Isomorphic HTTP client with session attachment, session renewal on 401,\n * idempotency key injection, and request retry. Works in both browser and\n * Node/server environments. Pass `additionalBeforeRequest` to inject\n * environment-specific middleware (e.g. `userAgentMiddleware` on the client).\n *\n * Pass `logger` through to have every request reported; the client logs\n * nothing without one.\n */\nexport class AuthenticatedApiClient {\n private readonly httpClient: HttpClient;\n\n constructor(\n config: HttpClientConfig,\n fetchAccessToken: (refresh: boolean) => Promise<OAuthAccessToken | null>,\n additionalBeforeRequest: BeforeRequestMiddleware[] = [],\n logger: Logger | null = null\n ) {\n this.httpClient = new HttpClient(\n config,\n {\n beforeRequest: [\n attachSessionMiddleware(fetchAccessToken),\n ...additionalBeforeRequest,\n idempotencyKeyMiddleware,\n ],\n afterRequest: [\n renewSessionMiddleware(fetchAccessToken),\n requestRetryMiddleware,\n ],\n },\n logger\n );\n }\n\n async send<TResponse>(request: HttpRequest): Promise<TResponse> {\n const response = await this.httpClient.send<TResponse>(request);\n if (response.status >= 200 && response.status < 300) {\n return response.body as TResponse;\n } else {\n throw new HttpError(response);\n }\n }\n}\n"],"mappings":"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","names":[]}
|