@mcptoolshop/registry-stats 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -48,22 +48,64 @@ var RegistryError = class extends Error {
48
48
  var RETRYABLE = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
49
49
  var MAX_RETRIES = 3;
50
50
  var BASE_DELAY = 1e3;
51
+ var registryLocks = /* @__PURE__ */ new Map();
52
+ var REGISTRY_DELAYS = {
53
+ npm: 400,
54
+ // ~2.5 req/s — safe for 54+ scoped packages
55
+ pypi: 2200,
56
+ // 30 req/60s = 1 per 2s, with headroom
57
+ docker: 4e3
58
+ // 10 req/3600s — very tight
59
+ };
60
+ var DEFAULT_DELAY = 100;
61
+ function acquireSlot(registry) {
62
+ const minDelay = REGISTRY_DELAYS[registry] ?? DEFAULT_DELAY;
63
+ const prev = registryLocks.get(registry) ?? Promise.resolve();
64
+ const slot = prev.then(() => new Promise((r) => setTimeout(r, minDelay)));
65
+ registryLocks.set(registry, slot);
66
+ return prev;
67
+ }
51
68
  async function fetchWithRetry(url, registry, init) {
69
+ let lastError;
70
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
71
+ await acquireSlot(registry);
72
+ const res = await fetch(url, init);
73
+ if (res.status === 404) return null;
74
+ if (res.ok) return res.json();
75
+ const retryAfter = res.headers.get("retry-after");
76
+ const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : void 0;
77
+ lastError = new RegistryError(
78
+ registry,
79
+ res.status,
80
+ `${res.statusText}: ${url}`,
81
+ retryAfterSeconds
82
+ );
83
+ if (!RETRYABLE.has(res.status) || attempt === MAX_RETRIES) break;
84
+ const backoff = BASE_DELAY * Math.pow(2, attempt);
85
+ const retryAfterMs = retryAfterSeconds ? retryAfterSeconds * 1e3 : 0;
86
+ const delay = Math.max(backoff, retryAfterMs);
87
+ await new Promise((r) => setTimeout(r, delay));
88
+ }
89
+ throw lastError;
90
+ }
91
+ async function fetchDirect(url, registry, init) {
52
92
  let lastError;
53
93
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
54
94
  const res = await fetch(url, init);
55
95
  if (res.status === 404) return null;
56
96
  if (res.ok) return res.json();
57
97
  const retryAfter = res.headers.get("retry-after");
58
- const retryAfterMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : void 0;
98
+ const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : void 0;
59
99
  lastError = new RegistryError(
60
100
  registry,
61
101
  res.status,
62
102
  `${res.statusText}: ${url}`,
63
- retryAfter ? parseInt(retryAfter, 10) : void 0
103
+ retryAfterSeconds
64
104
  );
65
105
  if (!RETRYABLE.has(res.status) || attempt === MAX_RETRIES) break;
66
- const delay = retryAfterMs ?? BASE_DELAY * Math.pow(2, attempt);
106
+ const backoff = BASE_DELAY * Math.pow(2, attempt);
107
+ const retryAfterMs = retryAfterSeconds ? retryAfterSeconds * 1e3 : 0;
108
+ const delay = Math.max(backoff, retryAfterMs);
67
109
  await new Promise((r) => setTimeout(r, delay));
68
110
  }
69
111
  throw lastError;
@@ -74,20 +116,22 @@ var API = "https://api.npmjs.org/downloads";
74
116
  var npm = {
75
117
  name: "npm",
76
118
  async getStats(pkg) {
77
- const [day, week, month] = await Promise.all([
78
- fetchWithRetry(`${API}/point/last-day/${pkg}`, "npm"),
79
- fetchWithRetry(`${API}/point/last-week/${pkg}`, "npm"),
80
- fetchWithRetry(`${API}/point/last-month/${pkg}`, "npm")
81
- ]);
82
- if (!day && !week && !month) return null;
119
+ const end = /* @__PURE__ */ new Date();
120
+ const start = new Date(end);
121
+ start.setDate(start.getDate() - 30);
122
+ const data = await fetchWithRetry(
123
+ `${API}/range/${fmt(start)}:${fmt(end)}/${pkg}`,
124
+ "npm"
125
+ );
126
+ if (!data || !data.downloads || data.downloads.length === 0) return null;
127
+ const days = data.downloads;
128
+ const lastDay = days[days.length - 1]?.downloads ?? 0;
129
+ const lastWeek = days.slice(-7).reduce((s, d) => s + d.downloads, 0);
130
+ const lastMonth = days.reduce((s, d) => s + d.downloads, 0);
83
131
  return {
84
132
  registry: "npm",
85
133
  package: pkg,
86
- downloads: {
87
- lastDay: day?.downloads,
88
- lastWeek: week?.downloads,
89
- lastMonth: month?.downloads
90
- },
134
+ downloads: { lastDay, lastWeek, lastMonth },
91
135
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
92
136
  };
93
137
  },
@@ -115,6 +159,27 @@ var npm = {
115
159
  return chunks;
116
160
  }
117
161
  };
162
+ async function npmBulkPoint(packages, period = "last-month") {
163
+ const result = /* @__PURE__ */ new Map();
164
+ if (packages.length === 0) return result;
165
+ const BATCH_SIZE = 128;
166
+ for (let i = 0; i < packages.length; i += BATCH_SIZE) {
167
+ const batch = packages.slice(i, i + BATCH_SIZE);
168
+ const joined = batch.join(",");
169
+ const data = await fetchDirect(
170
+ `${API}/point/${period}/${joined}`,
171
+ "npm"
172
+ );
173
+ if (data) {
174
+ for (const [name, entry] of Object.entries(data)) {
175
+ if (entry && typeof entry.downloads === "number") {
176
+ result.set(name, entry.downloads);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ return result;
182
+ }
118
183
  function fmt(d) {
119
184
  return d.toISOString().slice(0, 10);
120
185
  }
@@ -508,7 +573,12 @@ function createHandler(opts) {
508
573
  }
509
574
  error(res, "Not found", 404);
510
575
  } catch (e) {
511
- error(res, e.message, 500);
576
+ if (e instanceof RegistryError) {
577
+ const status = e.statusCode === 429 ? 429 : e.statusCode === 404 ? 404 : e.statusCode >= 500 ? 502 : 500;
578
+ error(res, e.message, status);
579
+ } else {
580
+ error(res, e.message, 500);
581
+ }
512
582
  }
513
583
  };
514
584
  }
@@ -607,10 +677,54 @@ stats.bulk = async function bulk(registry, packages, options) {
607
677
  if (!provider) {
608
678
  throw new RegistryError(registry, 0, `Unknown registry "${registry}".`);
609
679
  }
680
+ if (registry === "npm" && packages.length > 1) {
681
+ return npmBulkStats(packages, options);
682
+ }
610
683
  const concurrency = options?.concurrency ?? 5;
611
684
  const limit = pLimit(concurrency);
612
685
  return Promise.all(packages.map((pkg) => limit(() => stats(registry, pkg, options))));
613
686
  };
687
+ async function npmBulkStats(packages, options) {
688
+ const scoped = [];
689
+ const unscoped = [];
690
+ for (const pkg of packages) {
691
+ if (pkg.startsWith("@")) {
692
+ scoped.push(pkg);
693
+ } else {
694
+ unscoped.push(pkg);
695
+ }
696
+ }
697
+ const bulkMonth = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-month") : /* @__PURE__ */ new Map();
698
+ const bulkWeek = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-week") : /* @__PURE__ */ new Map();
699
+ const bulkDay = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-day") : /* @__PURE__ */ new Map();
700
+ const unscopedResults = /* @__PURE__ */ new Map();
701
+ for (const pkg of unscoped) {
702
+ const month = bulkMonth.get(pkg);
703
+ const week = bulkWeek.get(pkg);
704
+ const day = bulkDay.get(pkg);
705
+ if (month === void 0 && week === void 0 && day === void 0) {
706
+ unscopedResults.set(pkg, null);
707
+ } else {
708
+ unscopedResults.set(pkg, {
709
+ registry: "npm",
710
+ package: pkg,
711
+ downloads: {
712
+ lastDay: day,
713
+ lastWeek: week,
714
+ lastMonth: month
715
+ },
716
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
717
+ });
718
+ }
719
+ }
720
+ const limit = pLimit(1);
721
+ const scopedResults = await Promise.all(
722
+ scoped.map((pkg) => limit(() => stats("npm", pkg, options)))
723
+ );
724
+ const scopedMap = /* @__PURE__ */ new Map();
725
+ scoped.forEach((pkg, i) => scopedMap.set(pkg, scopedResults[i]));
726
+ return packages.map((pkg) => unscopedResults.get(pkg) ?? scopedMap.get(pkg) ?? null);
727
+ }
614
728
  stats.range = async function range(registry, pkg, start, end, options) {
615
729
  const provider = providers[registry];
616
730
  if (!provider) {
@@ -654,6 +768,31 @@ stats.compare = async function compare(pkg, registries, options) {
654
768
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
655
769
  };
656
770
  };
771
+ stats.mine = async function mine(maintainer, options) {
772
+ const packages = [];
773
+ const PAGE_SIZE = 250;
774
+ let offset = 0;
775
+ while (true) {
776
+ const url = `https://registry.npmjs.org/-/v1/search?text=maintainer:${encodeURIComponent(maintainer)}&size=${PAGE_SIZE}&from=${offset}`;
777
+ const data = await fetchDirect(url, "npm");
778
+ if (!data || data.objects.length === 0) break;
779
+ for (const obj of data.objects) {
780
+ packages.push(obj.package.name);
781
+ }
782
+ offset += data.objects.length;
783
+ if (offset >= data.total) break;
784
+ }
785
+ if (packages.length === 0) return [];
786
+ const results = [];
787
+ const bulkResults = await stats.bulk("npm", packages, options);
788
+ for (let i = 0; i < packages.length; i++) {
789
+ const r = bulkResults[i];
790
+ if (r) results.push(r);
791
+ options?.onProgress?.(i + 1, packages.length, packages[i]);
792
+ }
793
+ results.sort((a, b) => (b.downloads.lastMonth ?? 0) - (a.downloads.lastMonth ?? 0));
794
+ return results;
795
+ };
657
796
  // Annotate the CommonJS export names for ESM import in node:
658
797
  0 && (module.exports = {
659
798
  RegistryError,
package/dist/index.d.cts CHANGED
@@ -125,6 +125,9 @@ declare namespace stats {
125
125
  var bulk: (registry: string, packages: string[], options?: StatsOptions) => Promise<(PackageStats | null)[]>;
126
126
  var range: (registry: string, pkg: string, start: string, end: string, options?: StatsOptions) => Promise<DailyDownloads[]>;
127
127
  var compare: (pkg: string, registries?: string[], options?: StatsOptions) => Promise<ComparisonResult>;
128
+ var mine: (maintainer: string, options?: StatsOptions & {
129
+ onProgress?: (done: number, total: number, pkg: string) => void;
130
+ }) => Promise<PackageStats[]>;
128
131
  }
129
132
 
130
133
  export { type ChartData, type ComparisonResult, type Config, type DailyDownloads, type PackageConfig, type PackageStats, type RateLimitConfig, RegistryError, type RegistryName, type RegistryProvider, type ServerOptions, type StatsCache, type StatsOptions, calc, createCache, createHandler, defaultConfig, loadConfig, registerProvider, serve, starterConfig, stats };
package/dist/index.d.ts CHANGED
@@ -125,6 +125,9 @@ declare namespace stats {
125
125
  var bulk: (registry: string, packages: string[], options?: StatsOptions) => Promise<(PackageStats | null)[]>;
126
126
  var range: (registry: string, pkg: string, start: string, end: string, options?: StatsOptions) => Promise<DailyDownloads[]>;
127
127
  var compare: (pkg: string, registries?: string[], options?: StatsOptions) => Promise<ComparisonResult>;
128
+ var mine: (maintainer: string, options?: StatsOptions & {
129
+ onProgress?: (done: number, total: number, pkg: string) => void;
130
+ }) => Promise<PackageStats[]>;
128
131
  }
129
132
 
130
133
  export { type ChartData, type ComparisonResult, type Config, type DailyDownloads, type PackageConfig, type PackageStats, type RateLimitConfig, RegistryError, type RegistryName, type RegistryProvider, type ServerOptions, type StatsCache, type StatsOptions, calc, createCache, createHandler, defaultConfig, loadConfig, registerProvider, serve, starterConfig, stats };
package/dist/index.js CHANGED
@@ -13,22 +13,64 @@ var RegistryError = class extends Error {
13
13
  var RETRYABLE = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
14
14
  var MAX_RETRIES = 3;
15
15
  var BASE_DELAY = 1e3;
16
+ var registryLocks = /* @__PURE__ */ new Map();
17
+ var REGISTRY_DELAYS = {
18
+ npm: 400,
19
+ // ~2.5 req/s — safe for 54+ scoped packages
20
+ pypi: 2200,
21
+ // 30 req/60s = 1 per 2s, with headroom
22
+ docker: 4e3
23
+ // 10 req/3600s — very tight
24
+ };
25
+ var DEFAULT_DELAY = 100;
26
+ function acquireSlot(registry) {
27
+ const minDelay = REGISTRY_DELAYS[registry] ?? DEFAULT_DELAY;
28
+ const prev = registryLocks.get(registry) ?? Promise.resolve();
29
+ const slot = prev.then(() => new Promise((r) => setTimeout(r, minDelay)));
30
+ registryLocks.set(registry, slot);
31
+ return prev;
32
+ }
16
33
  async function fetchWithRetry(url, registry, init) {
34
+ let lastError;
35
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
36
+ await acquireSlot(registry);
37
+ const res = await fetch(url, init);
38
+ if (res.status === 404) return null;
39
+ if (res.ok) return res.json();
40
+ const retryAfter = res.headers.get("retry-after");
41
+ const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : void 0;
42
+ lastError = new RegistryError(
43
+ registry,
44
+ res.status,
45
+ `${res.statusText}: ${url}`,
46
+ retryAfterSeconds
47
+ );
48
+ if (!RETRYABLE.has(res.status) || attempt === MAX_RETRIES) break;
49
+ const backoff = BASE_DELAY * Math.pow(2, attempt);
50
+ const retryAfterMs = retryAfterSeconds ? retryAfterSeconds * 1e3 : 0;
51
+ const delay = Math.max(backoff, retryAfterMs);
52
+ await new Promise((r) => setTimeout(r, delay));
53
+ }
54
+ throw lastError;
55
+ }
56
+ async function fetchDirect(url, registry, init) {
17
57
  let lastError;
18
58
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
19
59
  const res = await fetch(url, init);
20
60
  if (res.status === 404) return null;
21
61
  if (res.ok) return res.json();
22
62
  const retryAfter = res.headers.get("retry-after");
23
- const retryAfterMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : void 0;
63
+ const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : void 0;
24
64
  lastError = new RegistryError(
25
65
  registry,
26
66
  res.status,
27
67
  `${res.statusText}: ${url}`,
28
- retryAfter ? parseInt(retryAfter, 10) : void 0
68
+ retryAfterSeconds
29
69
  );
30
70
  if (!RETRYABLE.has(res.status) || attempt === MAX_RETRIES) break;
31
- const delay = retryAfterMs ?? BASE_DELAY * Math.pow(2, attempt);
71
+ const backoff = BASE_DELAY * Math.pow(2, attempt);
72
+ const retryAfterMs = retryAfterSeconds ? retryAfterSeconds * 1e3 : 0;
73
+ const delay = Math.max(backoff, retryAfterMs);
32
74
  await new Promise((r) => setTimeout(r, delay));
33
75
  }
34
76
  throw lastError;
@@ -39,20 +81,22 @@ var API = "https://api.npmjs.org/downloads";
39
81
  var npm = {
40
82
  name: "npm",
41
83
  async getStats(pkg) {
42
- const [day, week, month] = await Promise.all([
43
- fetchWithRetry(`${API}/point/last-day/${pkg}`, "npm"),
44
- fetchWithRetry(`${API}/point/last-week/${pkg}`, "npm"),
45
- fetchWithRetry(`${API}/point/last-month/${pkg}`, "npm")
46
- ]);
47
- if (!day && !week && !month) return null;
84
+ const end = /* @__PURE__ */ new Date();
85
+ const start = new Date(end);
86
+ start.setDate(start.getDate() - 30);
87
+ const data = await fetchWithRetry(
88
+ `${API}/range/${fmt(start)}:${fmt(end)}/${pkg}`,
89
+ "npm"
90
+ );
91
+ if (!data || !data.downloads || data.downloads.length === 0) return null;
92
+ const days = data.downloads;
93
+ const lastDay = days[days.length - 1]?.downloads ?? 0;
94
+ const lastWeek = days.slice(-7).reduce((s, d) => s + d.downloads, 0);
95
+ const lastMonth = days.reduce((s, d) => s + d.downloads, 0);
48
96
  return {
49
97
  registry: "npm",
50
98
  package: pkg,
51
- downloads: {
52
- lastDay: day?.downloads,
53
- lastWeek: week?.downloads,
54
- lastMonth: month?.downloads
55
- },
99
+ downloads: { lastDay, lastWeek, lastMonth },
56
100
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
57
101
  };
58
102
  },
@@ -80,6 +124,27 @@ var npm = {
80
124
  return chunks;
81
125
  }
82
126
  };
127
+ async function npmBulkPoint(packages, period = "last-month") {
128
+ const result = /* @__PURE__ */ new Map();
129
+ if (packages.length === 0) return result;
130
+ const BATCH_SIZE = 128;
131
+ for (let i = 0; i < packages.length; i += BATCH_SIZE) {
132
+ const batch = packages.slice(i, i + BATCH_SIZE);
133
+ const joined = batch.join(",");
134
+ const data = await fetchDirect(
135
+ `${API}/point/${period}/${joined}`,
136
+ "npm"
137
+ );
138
+ if (data) {
139
+ for (const [name, entry] of Object.entries(data)) {
140
+ if (entry && typeof entry.downloads === "number") {
141
+ result.set(name, entry.downloads);
142
+ }
143
+ }
144
+ }
145
+ }
146
+ return result;
147
+ }
83
148
  function fmt(d) {
84
149
  return d.toISOString().slice(0, 10);
85
150
  }
@@ -473,7 +538,12 @@ function createHandler(opts) {
473
538
  }
474
539
  error(res, "Not found", 404);
475
540
  } catch (e) {
476
- error(res, e.message, 500);
541
+ if (e instanceof RegistryError) {
542
+ const status = e.statusCode === 429 ? 429 : e.statusCode === 404 ? 404 : e.statusCode >= 500 ? 502 : 500;
543
+ error(res, e.message, status);
544
+ } else {
545
+ error(res, e.message, 500);
546
+ }
477
547
  }
478
548
  };
479
549
  }
@@ -572,10 +642,54 @@ stats.bulk = async function bulk(registry, packages, options) {
572
642
  if (!provider) {
573
643
  throw new RegistryError(registry, 0, `Unknown registry "${registry}".`);
574
644
  }
645
+ if (registry === "npm" && packages.length > 1) {
646
+ return npmBulkStats(packages, options);
647
+ }
575
648
  const concurrency = options?.concurrency ?? 5;
576
649
  const limit = pLimit(concurrency);
577
650
  return Promise.all(packages.map((pkg) => limit(() => stats(registry, pkg, options))));
578
651
  };
652
+ async function npmBulkStats(packages, options) {
653
+ const scoped = [];
654
+ const unscoped = [];
655
+ for (const pkg of packages) {
656
+ if (pkg.startsWith("@")) {
657
+ scoped.push(pkg);
658
+ } else {
659
+ unscoped.push(pkg);
660
+ }
661
+ }
662
+ const bulkMonth = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-month") : /* @__PURE__ */ new Map();
663
+ const bulkWeek = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-week") : /* @__PURE__ */ new Map();
664
+ const bulkDay = unscoped.length > 0 ? await npmBulkPoint(unscoped, "last-day") : /* @__PURE__ */ new Map();
665
+ const unscopedResults = /* @__PURE__ */ new Map();
666
+ for (const pkg of unscoped) {
667
+ const month = bulkMonth.get(pkg);
668
+ const week = bulkWeek.get(pkg);
669
+ const day = bulkDay.get(pkg);
670
+ if (month === void 0 && week === void 0 && day === void 0) {
671
+ unscopedResults.set(pkg, null);
672
+ } else {
673
+ unscopedResults.set(pkg, {
674
+ registry: "npm",
675
+ package: pkg,
676
+ downloads: {
677
+ lastDay: day,
678
+ lastWeek: week,
679
+ lastMonth: month
680
+ },
681
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
682
+ });
683
+ }
684
+ }
685
+ const limit = pLimit(1);
686
+ const scopedResults = await Promise.all(
687
+ scoped.map((pkg) => limit(() => stats("npm", pkg, options)))
688
+ );
689
+ const scopedMap = /* @__PURE__ */ new Map();
690
+ scoped.forEach((pkg, i) => scopedMap.set(pkg, scopedResults[i]));
691
+ return packages.map((pkg) => unscopedResults.get(pkg) ?? scopedMap.get(pkg) ?? null);
692
+ }
579
693
  stats.range = async function range(registry, pkg, start, end, options) {
580
694
  const provider = providers[registry];
581
695
  if (!provider) {
@@ -619,6 +733,31 @@ stats.compare = async function compare(pkg, registries, options) {
619
733
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
620
734
  };
621
735
  };
736
+ stats.mine = async function mine(maintainer, options) {
737
+ const packages = [];
738
+ const PAGE_SIZE = 250;
739
+ let offset = 0;
740
+ while (true) {
741
+ const url = `https://registry.npmjs.org/-/v1/search?text=maintainer:${encodeURIComponent(maintainer)}&size=${PAGE_SIZE}&from=${offset}`;
742
+ const data = await fetchDirect(url, "npm");
743
+ if (!data || data.objects.length === 0) break;
744
+ for (const obj of data.objects) {
745
+ packages.push(obj.package.name);
746
+ }
747
+ offset += data.objects.length;
748
+ if (offset >= data.total) break;
749
+ }
750
+ if (packages.length === 0) return [];
751
+ const results = [];
752
+ const bulkResults = await stats.bulk("npm", packages, options);
753
+ for (let i = 0; i < packages.length; i++) {
754
+ const r = bulkResults[i];
755
+ if (r) results.push(r);
756
+ options?.onProgress?.(i + 1, packages.length, packages[i]);
757
+ }
758
+ results.sort((a, b) => (b.downloads.lastMonth ?? 0) - (a.downloads.lastMonth ?? 0));
759
+ return results;
760
+ };
622
761
  export {
623
762
  RegistryError,
624
763
  calc,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcptoolshop/registry-stats",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Multi-registry download stats for npm, PyPI, NuGet, VS Code Marketplace, and Docker Hub",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",