polaris-data 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/dist/index.js ADDED
@@ -0,0 +1,717 @@
1
+ // src/client.ts
2
+ import { readFile, writeFile, mkdir as mkdir2 } from "fs/promises";
3
+ import { dirname as dirname2 } from "path";
4
+ import { decompress } from "fzstd";
5
+
6
+ // src/errors.ts
7
+ var PolarisError = class extends Error {
8
+ name = "PolarisError";
9
+ /** HTTP status code, if the error originated from an API response. */
10
+ statusCode;
11
+ /** Raw response body string. */
12
+ body;
13
+ constructor(message, statusCode, body) {
14
+ super(message);
15
+ this.statusCode = statusCode;
16
+ this.body = body;
17
+ }
18
+ toString() {
19
+ return this.statusCode != null ? `${this.message} (status=${this.statusCode})` : this.message;
20
+ }
21
+ };
22
+ var UnauthorizedError = class extends PolarisError {
23
+ name = "UnauthorizedError";
24
+ };
25
+ var NotFoundError = class extends PolarisError {
26
+ name = "NotFoundError";
27
+ };
28
+ var RateLimitedError = class extends PolarisError {
29
+ name = "RateLimitedError";
30
+ /** ISO 8601 timestamp (or epoch) indicating when the rate limit resets. */
31
+ resetAt;
32
+ constructor(message, statusCode, body, resetAt) {
33
+ super(message, statusCode, body);
34
+ this.resetAt = resetAt;
35
+ }
36
+ };
37
+ var StreamDecodeError = class extends PolarisError {
38
+ name = "StreamDecodeError";
39
+ };
40
+ var DownloadNotAllowedError = class extends PolarisError {
41
+ name = "DownloadNotAllowedError";
42
+ };
43
+
44
+ // src/utils.ts
45
+ var EPOCH_RE = /^\d+$/;
46
+ function toIso8601(value) {
47
+ if (typeof value === "string") {
48
+ if (EPOCH_RE.test(value) && value.length >= 13) return epochUsToIso(Number(value));
49
+ return value;
50
+ }
51
+ if (value instanceof Date) return dateToIso(value);
52
+ return value > 1e12 ? epochUsToIso(value) : epochMsToIso(value);
53
+ }
54
+ function toEpochUs(value) {
55
+ if (typeof value === "number") return value > 1e12 ? value : value * 1e3;
56
+ if (typeof value === "string") {
57
+ if (EPOCH_RE.test(value) && value.length >= 13) return Number(value);
58
+ return Math.round(new Date(value).getTime() * 1e3);
59
+ }
60
+ return Math.round(value.getTime() * 1e3);
61
+ }
62
+ function datesInRange(fromUs, toUs) {
63
+ const dates = [];
64
+ const start = new Date(fromUs / 1e3);
65
+ const end = new Date(toUs / 1e3);
66
+ const cur = new Date(
67
+ Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate())
68
+ );
69
+ const last = new Date(
70
+ Date.UTC(end.getUTCFullYear(), end.getUTCMonth(), end.getUTCDate())
71
+ );
72
+ while (cur <= last) {
73
+ dates.push(formatUtcDate(cur));
74
+ cur.setUTCDate(cur.getUTCDate() + 1);
75
+ }
76
+ return dates;
77
+ }
78
+ function epochUsToIso(us) {
79
+ return epochMsToIso(us / 1e3);
80
+ }
81
+ function epochMsToIso(ms) {
82
+ return new Date(ms).toISOString().replace(/\.000Z$/, "Z");
83
+ }
84
+ function dateToIso(d) {
85
+ return d.toISOString().replace(/\.000Z$/, "Z");
86
+ }
87
+ function formatUtcDate(d) {
88
+ const y = d.getUTCFullYear();
89
+ const m = String(d.getUTCMonth() + 1).padStart(2, "0");
90
+ const day = String(d.getUTCDate()).padStart(2, "0");
91
+ return `${y}-${m}-${day}`;
92
+ }
93
+
94
+ // src/storage.ts
95
+ import { mkdir, stat, link, copyFile } from "fs/promises";
96
+ import { join, dirname } from "path";
97
+ import { homedir, platform } from "os";
98
+ function resolveRoot(explicit) {
99
+ if (explicit) return explicit;
100
+ if (process.env.POLARIS_ROOT) return process.env.POLARIS_ROOT;
101
+ if (process.env.POLARIS_DATASET_DOWNLOAD_DIR)
102
+ return process.env.POLARIS_DATASET_DOWNLOAD_DIR;
103
+ return defaultRoot();
104
+ }
105
+ function defaultRoot() {
106
+ switch (platform()) {
107
+ case "darwin":
108
+ return join(
109
+ homedir(),
110
+ "Library",
111
+ "Application Support",
112
+ "polaris"
113
+ );
114
+ case "win32":
115
+ return join(
116
+ process.env.APPDATA || join(homedir(), "AppData", "Roaming"),
117
+ "polaris"
118
+ );
119
+ default:
120
+ return process.env.XDG_DATA_HOME ? join(process.env.XDG_DATA_HOME, "polaris") : join(homedir(), ".local", "share", "polaris");
121
+ }
122
+ }
123
+ async function ensureLayout(root) {
124
+ const dataDir = join(root, "data");
125
+ const dailyDir = join(root, "daily");
126
+ const tmpDir = join(root, "tmp");
127
+ const cacheDir = join(root, "cache");
128
+ await Promise.all([
129
+ mkdir(dataDir, { recursive: true }),
130
+ mkdir(dailyDir, { recursive: true }),
131
+ mkdir(tmpDir, { recursive: true }),
132
+ mkdir(cacheDir, { recursive: true })
133
+ ]);
134
+ return { root, dataDir, dailyDir, tmpDir, cacheDir };
135
+ }
136
+ function dataFilePath(dataDir, key) {
137
+ return join(dataDir, key);
138
+ }
139
+ function dailyFilePath(dailyDir, source, market, date) {
140
+ return join(dailyDir, source, market, `${date}.jsonl.zst`);
141
+ }
142
+ async function linkOrCopy(src, dest) {
143
+ await mkdir(dirname(dest), { recursive: true });
144
+ try {
145
+ await link(src, dest);
146
+ } catch {
147
+ await copyFile(src, dest);
148
+ }
149
+ }
150
+ async function fileExists(path) {
151
+ try {
152
+ await stat(path);
153
+ return true;
154
+ } catch {
155
+ return false;
156
+ }
157
+ }
158
+
159
+ // src/aggregator.ts
160
+ function intervalToUs(interval) {
161
+ const m = interval.match(/^(\d+)(ms|s|m|h)$/);
162
+ if (!m) throw new Error(`Invalid OHLCV interval: ${interval}`);
163
+ const n = parseInt(m[1], 10);
164
+ switch (m[2]) {
165
+ case "ms":
166
+ return n * 1e3;
167
+ case "s":
168
+ return n * 1e6;
169
+ case "m":
170
+ return n * 6e7;
171
+ case "h":
172
+ return n * 36e8;
173
+ default:
174
+ throw new Error(`Unreachable`);
175
+ }
176
+ }
177
+ var OhlcvAggregator = class {
178
+ _intervalUs;
179
+ _bars = /* @__PURE__ */ new Map();
180
+ constructor(interval) {
181
+ this._intervalUs = intervalToUs(interval);
182
+ }
183
+ /** Ingest a single trade event. */
184
+ add(timestamp, price, quantity) {
185
+ const bucketTs = Math.floor(timestamp / this._intervalUs) * this._intervalUs;
186
+ let b = this._bars.get(bucketTs);
187
+ if (!b) {
188
+ b = {
189
+ ts: bucketTs,
190
+ open: price,
191
+ high: price,
192
+ low: price,
193
+ close: price,
194
+ volScaled: 0,
195
+ trades: 0
196
+ };
197
+ this._bars.set(bucketTs, b);
198
+ }
199
+ if (price > b.high) b.high = price;
200
+ if (price < b.low) b.low = price;
201
+ b.close = price;
202
+ b.volScaled += Math.round(quantity * 1e12);
203
+ b.trades += 1;
204
+ }
205
+ /**
206
+ * Finalise the aggregation and return sorted bars.
207
+ *
208
+ * The `open` of each subsequent bar is overwritten with the `close` of the
209
+ * previous bar, matching the convention used by the Python SDK.
210
+ */
211
+ finish() {
212
+ const bars = Array.from(this._bars.values()).sort(
213
+ (a, b) => a.ts - b.ts
214
+ );
215
+ for (let i = 1; i < bars.length; i++) {
216
+ bars[i].open = bars[i - 1].close;
217
+ }
218
+ return bars.map((b) => ({
219
+ timestamp: b.ts,
220
+ open: b.open,
221
+ high: b.high,
222
+ low: b.low,
223
+ close: b.close,
224
+ volume: b.volScaled / 1e12,
225
+ trades: b.trades
226
+ }));
227
+ }
228
+ };
229
+
230
+ // src/client.ts
231
+ var VERSION = "0.1.0";
232
+ var PolarisClient = class {
233
+ _apiKey;
234
+ _baseUrl;
235
+ _timeout;
236
+ _fetch;
237
+ _root;
238
+ _layout;
239
+ constructor(options = {}) {
240
+ this._apiKey = options.apiKey ?? readEnvApiKey();
241
+ this._baseUrl = new URL(options.baseUrl ?? "https://api.polaris.supply");
242
+ this._timeout = options.timeout ?? 3e4;
243
+ this._fetch = options.fetch ?? globalThis.fetch;
244
+ this._root = resolveRoot(options.datasetRoot);
245
+ }
246
+ // -----------------------------------------------------------------------
247
+ // Discovery
248
+ // -----------------------------------------------------------------------
249
+ /** Check API availability. */
250
+ async health() {
251
+ return this._getJson("/health", { auth: "none" });
252
+ }
253
+ /**
254
+ * Browse supported sources and markets.
255
+ *
256
+ * If neither `source` nor `market` is provided, returns the full catalog.
257
+ * `market` requires `source`.
258
+ */
259
+ async catalog(options = {}) {
260
+ const params = {};
261
+ if (options.source) params.source = options.source;
262
+ if (options.market) params.market = options.market;
263
+ return this._getJson("/catalog", { params, auth: "if-available" });
264
+ }
265
+ /**
266
+ * List available snapshot files for a source and market over a time range.
267
+ * Auto-paginates to return **all** matching entries.
268
+ */
269
+ async listSnapshots(options) {
270
+ const entries = [];
271
+ let cursor;
272
+ do {
273
+ const params = buildSnapshotParams(options);
274
+ if (cursor) params.cursor = cursor;
275
+ const res = await this._getJson("/snapshots", { params, auth: "if-available" });
276
+ entries.push(...res.snapshots);
277
+ cursor = res.has_more ? res.next_cursor ?? void 0 : void 0;
278
+ } while (cursor);
279
+ return entries;
280
+ }
281
+ // -----------------------------------------------------------------------
282
+ // Historical data – snapshot-first
283
+ // -----------------------------------------------------------------------
284
+ /**
285
+ * Return all standardised historical events for a time range.
286
+ *
287
+ * Reads from locally-cached daily `.jsonl.zst` snapshot files.
288
+ * Missing daily artifacts are discovered via `GET /snapshots` and
289
+ * downloaded automatically.
290
+ */
291
+ async events(options) {
292
+ const fromUs = toEpochUs(options.from);
293
+ const toUs = toEpochUs(options.to);
294
+ const result = [];
295
+ for await (const event of this._readDailyEvents(
296
+ options.source,
297
+ options.market,
298
+ fromUs,
299
+ toUs
300
+ )) {
301
+ result.push(event);
302
+ }
303
+ return result;
304
+ }
305
+ /**
306
+ * Return all standardised trade events for a time range.
307
+ *
308
+ * Reads from locally-cached daily snapshot files, filtering to
309
+ * `type === "trade"`.
310
+ */
311
+ async trades(options) {
312
+ const fromUs = toEpochUs(options.from);
313
+ const toUs = toEpochUs(options.to);
314
+ const result = [];
315
+ for await (const event of this._readDailyEvents(
316
+ options.source,
317
+ options.market,
318
+ fromUs,
319
+ toUs,
320
+ (e) => e.type === "trade"
321
+ )) {
322
+ result.push(event);
323
+ }
324
+ return result;
325
+ }
326
+ /**
327
+ * Aggregate OHLCV bars from standardised trade data.
328
+ *
329
+ * Reads from locally-cached daily snapshot files and aggregates in memory
330
+ * using the same interval-bucketing strategy as the Python SDK.
331
+ */
332
+ async ohlcv(options) {
333
+ const fromUs = toEpochUs(options.from);
334
+ const toUs = toEpochUs(options.to);
335
+ const agg = new OhlcvAggregator(options.interval);
336
+ for await (const event of this._readDailyEvents(
337
+ options.source,
338
+ options.market,
339
+ fromUs,
340
+ toUs,
341
+ (e) => e.type === "trade"
342
+ )) {
343
+ const data = event.data;
344
+ agg.add(event.timestamp, data.price, data.quantity);
345
+ }
346
+ return agg.finish();
347
+ }
348
+ /**
349
+ * Return a TradingView-shaped OHLCV response.
350
+ *
351
+ * Aggregates bars from local snapshot data and reshapes to
352
+ * `{ candles, volumes }`.
353
+ */
354
+ async ohlcvTradingView(options) {
355
+ const bars = await this.ohlcv(options);
356
+ return {
357
+ candles: bars.map((b) => ({
358
+ t: b.timestamp,
359
+ o: b.open,
360
+ h: b.high,
361
+ l: b.low,
362
+ c: b.close
363
+ })),
364
+ volumes: bars.map((b) => ({
365
+ t: b.timestamp,
366
+ v: b.volume,
367
+ trades: b.trades
368
+ }))
369
+ };
370
+ }
371
+ /**
372
+ * Stream historical events as an async iterable.
373
+ *
374
+ * Defaults to standardised events from local snapshots (`standard: true`).
375
+ * Pass `standard: false` to stream raw payloads via the `/raw` endpoint.
376
+ *
377
+ * @example
378
+ * ```ts
379
+ * for await (const row of client.replay({
380
+ * source: "binance",
381
+ * market: "BTC-USDT",
382
+ * from: "2024-01-01T00:00:00Z",
383
+ * to: "2024-01-01T01:00:00Z",
384
+ * })) {
385
+ * console.log(row);
386
+ * }
387
+ * ```
388
+ */
389
+ async *replay(options) {
390
+ if (options.standard !== false) {
391
+ const fromUs = toEpochUs(options.from);
392
+ const toUs = toEpochUs(options.to);
393
+ yield* this._readDailyEvents(
394
+ options.source,
395
+ options.market,
396
+ fromUs,
397
+ toUs
398
+ );
399
+ } else {
400
+ yield* this._streamRaw(options);
401
+ }
402
+ }
403
+ // -----------------------------------------------------------------------
404
+ // Raw (API-only, not snapshot-first)
405
+ // -----------------------------------------------------------------------
406
+ /**
407
+ * Return raw venue-native payloads for a time range.
408
+ * Requires an API key. Uses the `/raw` endpoint with pagination.
409
+ */
410
+ async raw(options) {
411
+ const params = buildRawParams(options);
412
+ return this._paginateAll("/raw", params, "required");
413
+ }
414
+ // -----------------------------------------------------------------------
415
+ // Downloads
416
+ // -----------------------------------------------------------------------
417
+ /**
418
+ * Download a single snapshot file by key.
419
+ *
420
+ * Returns the native `Response` so you can consume the body as needed
421
+ * (`.arrayBuffer()`, `.blob()`, or pipe to a writable stream).
422
+ */
423
+ async downloadSnapshot(options) {
424
+ const params = { key: options.key };
425
+ if (options.filename) params.filename = options.filename;
426
+ if (options.mode) params.mode = options.mode;
427
+ const { response, body } = await this._fetchRaw("/snapshots/download", {
428
+ params,
429
+ auth: "if-available"
430
+ });
431
+ assertOk(response, body);
432
+ return response;
433
+ }
434
+ /**
435
+ * Get a pre-signed download URL for a snapshot file
436
+ * without fetching the file itself.
437
+ */
438
+ async getSnapshotDownloadUrl(options) {
439
+ const params = { key: options.key, mode: "url" };
440
+ if (options.filename) params.filename = options.filename;
441
+ return this._getJson("/snapshots/download", {
442
+ params,
443
+ auth: "if-available"
444
+ });
445
+ }
446
+ // -----------------------------------------------------------------------
447
+ // Lifecycle
448
+ // -----------------------------------------------------------------------
449
+ /** Release resources. Currently a no-op (reserved for future use). */
450
+ close() {
451
+ }
452
+ /** Async disposable support (Node ≥ 18 / TypeScript ≥ 5.2). */
453
+ async [Symbol.asyncDispose]() {
454
+ this.close();
455
+ }
456
+ // -----------------------------------------------------------------------
457
+ // Internals – snapshot-first local reads
458
+ // -----------------------------------------------------------------------
459
+ /**
460
+ * Core routine: ensure daily `.jsonl.zst` artifacts exist, decompress them,
461
+ * and yield matching events one at a time.
462
+ */
463
+ async *_readDailyEvents(source, market, fromUs, toUs, filter) {
464
+ const layout = await this._getLayout();
465
+ const dates = datesInRange(fromUs, toUs);
466
+ const filePaths = await this._ensureDailyArtifacts(
467
+ source,
468
+ market,
469
+ dates,
470
+ layout
471
+ );
472
+ for (const filePath of filePaths) {
473
+ const lines = await readDailyLines(filePath);
474
+ for (const line of lines) {
475
+ let event;
476
+ try {
477
+ event = JSON.parse(line);
478
+ } catch {
479
+ continue;
480
+ }
481
+ const ts = event.timestamp;
482
+ if (ts < fromUs || ts >= toUs) continue;
483
+ if (filter && !filter(event)) continue;
484
+ yield event;
485
+ }
486
+ }
487
+ }
488
+ /**
489
+ * Ensure every requested date has a materialised daily artifact.
490
+ * Downloads missing snapshots and materialises them into `daily/`.
491
+ */
492
+ async _ensureDailyArtifacts(source, market, dates, layout) {
493
+ const paths = [];
494
+ for (const date of dates) {
495
+ const dailyPath = dailyFilePath(layout.dailyDir, source, market, date);
496
+ if (await fileExists(dailyPath)) {
497
+ paths.push(dailyPath);
498
+ continue;
499
+ }
500
+ const snapshots = await this.listSnapshots({
501
+ source,
502
+ market,
503
+ from: `${date}T00:00:00Z`,
504
+ to: `${date}T23:59:59Z`
505
+ });
506
+ const entry = snapshots.find((s) => s.date === date);
507
+ if (!entry) {
508
+ throw new PolarisError(
509
+ `No snapshot available for ${source}/${market} on ${date}`
510
+ );
511
+ }
512
+ await this._downloadAndMaterialise(entry.key, dailyPath, layout);
513
+ paths.push(dailyPath);
514
+ }
515
+ return paths;
516
+ }
517
+ /**
518
+ * Download a snapshot to `data/` and create a hardlink (or copy) into
519
+ * `daily/`.
520
+ */
521
+ async _downloadAndMaterialise(key, dailyPath, layout) {
522
+ const dataPath = dataFilePath(layout.dataDir, key);
523
+ if (!await fileExists(dataPath)) {
524
+ const urlInfo = await this.getSnapshotDownloadUrl({ key });
525
+ const response = await this._fetch(urlInfo.url);
526
+ if (!response.ok) {
527
+ throw new PolarisError(
528
+ `Failed to download snapshot ${key}: HTTP ${response.status}`
529
+ );
530
+ }
531
+ const buffer = Buffer.from(await response.arrayBuffer());
532
+ await mkdir2(dirname2(dataPath), { recursive: true });
533
+ await writeFile(dataPath, buffer);
534
+ }
535
+ await linkOrCopy(dataPath, dailyPath);
536
+ }
537
+ // -----------------------------------------------------------------------
538
+ // Internals – raw endpoint streaming
539
+ // -----------------------------------------------------------------------
540
+ async *_streamRaw(options) {
541
+ const params = {
542
+ source: options.source,
543
+ market: options.market,
544
+ from: toIso8601(options.from),
545
+ to: toIso8601(options.to)
546
+ };
547
+ let cursor;
548
+ do {
549
+ if (cursor) params.cursor = cursor;
550
+ const res = await this._getJson("/raw", {
551
+ params,
552
+ auth: "required"
553
+ });
554
+ for (const item of res.data) {
555
+ yield item;
556
+ }
557
+ cursor = res.next_cursor ?? void 0;
558
+ } while (cursor);
559
+ }
560
+ // -----------------------------------------------------------------------
561
+ // Internals – HTTP layer
562
+ // -----------------------------------------------------------------------
563
+ async _getJson(path, opts = {}) {
564
+ const { response, body } = await this._fetchRaw(path, {
565
+ ...opts,
566
+ headers: { Accept: "application/json", ...opts.headers }
567
+ });
568
+ assertOk(response, body);
569
+ let json;
570
+ try {
571
+ json = JSON.parse(body);
572
+ } catch {
573
+ throw new PolarisError("Failed to parse response as JSON");
574
+ }
575
+ if (typeof json !== "object" || json === null || Array.isArray(json)) {
576
+ throw new PolarisError("Expected a JSON object response");
577
+ }
578
+ return json;
579
+ }
580
+ async _fetchRaw(path, opts = {}) {
581
+ const headers = this._buildHeaders(opts.auth ?? "none", opts.headers);
582
+ const url = this._buildUrl(path, opts.params);
583
+ const controller = new AbortController();
584
+ const timer = setTimeout(() => controller.abort(), this._timeout);
585
+ let response;
586
+ try {
587
+ response = await this._fetch(url, {
588
+ headers,
589
+ signal: controller.signal,
590
+ redirect: "follow"
591
+ });
592
+ } catch (e) {
593
+ clearTimeout(timer);
594
+ if (e instanceof Error && e.name === "AbortError") {
595
+ throw new PolarisError("Request timed out");
596
+ }
597
+ throw new PolarisError(`Request failed: ${e}`);
598
+ }
599
+ clearTimeout(timer);
600
+ const body = await response.text();
601
+ return { response, body };
602
+ }
603
+ _buildHeaders(authMode, extra) {
604
+ const out = {
605
+ "User-Agent": `polaris-ts/${VERSION}`,
606
+ ...extra
607
+ };
608
+ if (authMode === "required" && !this._apiKey) {
609
+ throw new UnauthorizedError("API key is required for this endpoint");
610
+ }
611
+ if (this._apiKey && authMode !== "none") {
612
+ out["Authorization"] = `Bearer ${this._apiKey}`;
613
+ }
614
+ return out;
615
+ }
616
+ _buildUrl(path, params) {
617
+ const url = new URL(path, this._baseUrl);
618
+ if (params) {
619
+ for (const [k, v] of Object.entries(params)) {
620
+ if (v !== void 0) url.searchParams.set(k, v);
621
+ }
622
+ }
623
+ return url.toString();
624
+ }
625
+ // -----------------------------------------------------------------------
626
+ // Internals – pagination
627
+ // -----------------------------------------------------------------------
628
+ async _paginateAll(path, baseParams, auth) {
629
+ const items = [];
630
+ let cursor;
631
+ do {
632
+ const params = cursor ? { ...baseParams, cursor } : { ...baseParams };
633
+ const res = await this._getJson(path, {
634
+ params,
635
+ auth
636
+ });
637
+ items.push(...res.data);
638
+ cursor = res.next_cursor ?? void 0;
639
+ } while (cursor);
640
+ return items;
641
+ }
642
+ // -----------------------------------------------------------------------
643
+ // Internals – layout lazy init
644
+ // -----------------------------------------------------------------------
645
+ async _getLayout() {
646
+ if (!this._layout) this._layout = await ensureLayout(this._root);
647
+ return this._layout;
648
+ }
649
+ };
650
+ function readEnvApiKey() {
651
+ try {
652
+ return process?.env?.POLARIS_API_KEY;
653
+ } catch {
654
+ return void 0;
655
+ }
656
+ }
657
+ function buildRawParams(options) {
658
+ const p = {
659
+ source: options.source,
660
+ market: options.market
661
+ };
662
+ if (options.from !== void 0) p.from = toIso8601(options.from);
663
+ if (options.to !== void 0) p.to = toIso8601(options.to);
664
+ if (options.limit !== void 0) p.limit = String(options.limit);
665
+ if (options.format) p.format = options.format;
666
+ return p;
667
+ }
668
+ function buildSnapshotParams(options) {
669
+ const p = {
670
+ source: options.source,
671
+ market: options.market
672
+ };
673
+ if (options.from !== void 0) p.from = toIso8601(options.from);
674
+ if (options.to !== void 0) p.to = toIso8601(options.to);
675
+ if (options.limit !== void 0) p.limit = String(options.limit);
676
+ return p;
677
+ }
678
+ function assertOk(response, body) {
679
+ if (response.ok) return;
680
+ let message = `HTTP ${response.status}`;
681
+ let resetAt;
682
+ try {
683
+ const json = JSON.parse(body);
684
+ if (typeof json === "object" && json !== null) {
685
+ message = String(json.error ?? json.message ?? message);
686
+ resetAt = json.reset_at;
687
+ }
688
+ } catch {
689
+ }
690
+ switch (response.status) {
691
+ case 401:
692
+ throw new UnauthorizedError(message, response.status, body);
693
+ case 404:
694
+ throw new NotFoundError(message, response.status, body);
695
+ case 429:
696
+ throw new RateLimitedError(message, response.status, body, resetAt);
697
+ default:
698
+ throw new PolarisError(message, response.status, body);
699
+ }
700
+ }
701
+ async function readDailyLines(filePath) {
702
+ const compressed = await readFile(filePath);
703
+ const decompressed = decompress(compressed);
704
+ const text = new TextDecoder().decode(decompressed);
705
+ return text.split("\n").filter((l) => l.trim().length > 0);
706
+ }
707
+ export {
708
+ DownloadNotAllowedError,
709
+ NotFoundError,
710
+ OhlcvAggregator,
711
+ PolarisClient,
712
+ PolarisError,
713
+ RateLimitedError,
714
+ StreamDecodeError,
715
+ UnauthorizedError
716
+ };
717
+ //# sourceMappingURL=index.js.map