@agntn/registries 0.2.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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +303 -0
  3. package/dist/_chunks/cached-registry.d.mts +35 -0
  4. package/dist/_chunks/cached-registry.d.mts.map +1 -0
  5. package/dist/_chunks/errors.d.mts +90 -0
  6. package/dist/_chunks/errors.d.mts.map +1 -0
  7. package/dist/_chunks/helpers.mjs +95 -0
  8. package/dist/_chunks/helpers.mjs.map +1 -0
  9. package/dist/_chunks/index.d.mts +142 -0
  10. package/dist/_chunks/index.d.mts.map +1 -0
  11. package/dist/_chunks/index2.d.mts +50 -0
  12. package/dist/_chunks/index2.d.mts.map +1 -0
  13. package/dist/_chunks/index3.d.mts +18 -0
  14. package/dist/_chunks/index3.d.mts.map +1 -0
  15. package/dist/_chunks/lockfile.d.mts +50 -0
  16. package/dist/_chunks/lockfile.d.mts.map +1 -0
  17. package/dist/_chunks/paths.d.mts +11 -0
  18. package/dist/_chunks/paths.d.mts.map +1 -0
  19. package/dist/_chunks/purl.mjs +192 -0
  20. package/dist/_chunks/purl.mjs.map +1 -0
  21. package/dist/_chunks/registries.mjs +1197 -0
  22. package/dist/_chunks/registries.mjs.map +1 -0
  23. package/dist/_chunks/registry.d.mts +39 -0
  24. package/dist/_chunks/registry.d.mts.map +1 -0
  25. package/dist/_chunks/registry.mjs +201 -0
  26. package/dist/_chunks/registry.mjs.map +1 -0
  27. package/dist/_chunks/storage.d.mts +22 -0
  28. package/dist/_chunks/storage.d.mts.map +1 -0
  29. package/dist/_chunks/types.d.mts +73 -0
  30. package/dist/_chunks/types.d.mts.map +1 -0
  31. package/dist/ai.d.mts +35 -0
  32. package/dist/ai.d.mts.map +1 -0
  33. package/dist/ai.mjs +50 -0
  34. package/dist/ai.mjs.map +1 -0
  35. package/dist/cache/cached-registry.d.mts +4 -0
  36. package/dist/cache/cached-registry.mjs +92 -0
  37. package/dist/cache/cached-registry.mjs.map +1 -0
  38. package/dist/cache/index.d.mts +8 -0
  39. package/dist/cache/index.mjs +12 -0
  40. package/dist/cache/index.mjs.map +1 -0
  41. package/dist/cache/lockfile.d.mts +2 -0
  42. package/dist/cache/lockfile.mjs +68 -0
  43. package/dist/cache/lockfile.mjs.map +1 -0
  44. package/dist/cache/paths.d.mts +2 -0
  45. package/dist/cache/paths.mjs +26 -0
  46. package/dist/cache/paths.mjs.map +1 -0
  47. package/dist/cache/storage.d.mts +2 -0
  48. package/dist/cache/storage.mjs +40 -0
  49. package/dist/cache/storage.mjs.map +1 -0
  50. package/dist/cli.d.mts +1 -0
  51. package/dist/cli.mjs +19 -0
  52. package/dist/cli.mjs.map +1 -0
  53. package/dist/commands/cache.d.mts +7 -0
  54. package/dist/commands/cache.d.mts.map +1 -0
  55. package/dist/commands/cache.mjs +80 -0
  56. package/dist/commands/cache.mjs.map +1 -0
  57. package/dist/commands/deps.d.mts +23 -0
  58. package/dist/commands/deps.d.mts.map +1 -0
  59. package/dist/commands/deps.mjs +72 -0
  60. package/dist/commands/deps.mjs.map +1 -0
  61. package/dist/commands/info.d.mts +26 -0
  62. package/dist/commands/info.d.mts.map +1 -0
  63. package/dist/commands/info.mjs +54 -0
  64. package/dist/commands/info.mjs.map +1 -0
  65. package/dist/commands/maintainers.d.mts +23 -0
  66. package/dist/commands/maintainers.d.mts.map +1 -0
  67. package/dist/commands/maintainers.mjs +46 -0
  68. package/dist/commands/maintainers.mjs.map +1 -0
  69. package/dist/commands/shared.d.mts +27 -0
  70. package/dist/commands/shared.d.mts.map +1 -0
  71. package/dist/commands/shared.mjs +69 -0
  72. package/dist/commands/shared.mjs.map +1 -0
  73. package/dist/commands/versions.d.mts +31 -0
  74. package/dist/commands/versions.d.mts.map +1 -0
  75. package/dist/commands/versions.mjs +58 -0
  76. package/dist/commands/versions.mjs.map +1 -0
  77. package/dist/core/index.d.mts +4 -0
  78. package/dist/core/index.mjs +3 -0
  79. package/dist/index.d.mts +11 -0
  80. package/dist/index.mjs +10 -0
  81. package/dist/registries/index.d.mts +4 -0
  82. package/dist/registries/index.mjs +4 -0
  83. package/dist/types.d.mts +3 -0
  84. package/dist/types.mjs +1 -0
  85. package/package.json +102 -0
package/dist/ai.mjs ADDED
@@ -0,0 +1,50 @@
1
+ import "./_chunks/registry.mjs";
2
+ import "./_chunks/purl.mjs";
3
+ import "./_chunks/registries.mjs";
4
+ import { a as fetchVersionsFromPURL, i as fetchPackageFromPURL, n as fetchDependenciesFromPURL, r as fetchMaintainersFromPURL, t as bulkFetchPackages } from "./_chunks/helpers.mjs";
5
+ import { tool } from "ai";
6
+ import { z } from "zod";
7
+ const packageTool = tool({
8
+ description: "Query package metadata from npm, PyPI, crates.io, RubyGems, Packagist, and Arch Linux using PURLs. Supports package info, versions, dependencies, maintainers, and bulk package metadata lookups.",
9
+ inputSchema: z.discriminatedUnion("operation", [
10
+ z.object({
11
+ operation: z.literal("package"),
12
+ purl: z.string().describe("Package PURL, for example pkg:npm/lodash or pkg:cargo/serde")
13
+ }),
14
+ z.object({
15
+ operation: z.literal("versions"),
16
+ purl: z.string().describe("Package PURL, for example pkg:npm/lodash or pkg:cargo/serde")
17
+ }),
18
+ z.object({
19
+ operation: z.literal("dependencies"),
20
+ purl: z.string().describe("Package PURL including a version, for example pkg:pypi/flask@3.1.1")
21
+ }),
22
+ z.object({
23
+ operation: z.literal("maintainers"),
24
+ purl: z.string().describe("Package PURL, for example pkg:gem/rails or pkg:composer/laravel/framework")
25
+ }),
26
+ z.object({
27
+ operation: z.literal("bulk-packages"),
28
+ purls: z.array(z.string()).min(1).max(50).describe("List of package PURLs to fetch in bulk."),
29
+ concurrency: z.number().int().min(1).max(50).optional().describe("Maximum concurrent lookups. Defaults to 15.")
30
+ })
31
+ ]),
32
+ execute: async (input, { abortSignal }) => {
33
+ switch (input.operation) {
34
+ case "package": return fetchPackageFromPURL(input.purl, abortSignal);
35
+ case "versions": return fetchVersionsFromPURL(input.purl, abortSignal);
36
+ case "dependencies": return fetchDependenciesFromPURL(input.purl, abortSignal);
37
+ case "maintainers": return fetchMaintainersFromPURL(input.purl, abortSignal);
38
+ case "bulk-packages": {
39
+ const results = await bulkFetchPackages(input.purls, {
40
+ concurrency: input.concurrency,
41
+ signal: abortSignal
42
+ });
43
+ return Object.fromEntries(results);
44
+ }
45
+ }
46
+ }
47
+ });
48
+ export { packageTool };
49
+
50
+ //# sourceMappingURL=ai.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.mjs","names":[],"sources":["../src/ai.ts"],"sourcesContent":["import { tool } from \"ai\";\nimport { z } from \"zod\";\nimport {\n bulkFetchPackages,\n fetchDependenciesFromPURL,\n fetchMaintainersFromPURL,\n fetchPackageFromPURL,\n fetchVersionsFromPURL,\n} from \"./helpers.ts\";\nimport \"./registries/index.ts\";\n\nexport const packageTool = tool({\n description:\n \"Query package metadata from npm, PyPI, crates.io, RubyGems, Packagist, and Arch Linux using PURLs. Supports package info, versions, dependencies, maintainers, and bulk package metadata lookups.\",\n inputSchema: z.discriminatedUnion(\"operation\", [\n z.object({\n operation: z.literal(\"package\"),\n purl: z.string().describe(\"Package PURL, for example pkg:npm/lodash or pkg:cargo/serde\"),\n }),\n z.object({\n operation: z.literal(\"versions\"),\n purl: z.string().describe(\"Package PURL, for example pkg:npm/lodash or pkg:cargo/serde\"),\n }),\n z.object({\n operation: z.literal(\"dependencies\"),\n purl: z\n .string()\n .describe(\"Package PURL including a version, for example pkg:pypi/flask@3.1.1\"),\n }),\n z.object({\n operation: z.literal(\"maintainers\"),\n purl: z\n .string()\n .describe(\"Package PURL, for example pkg:gem/rails or pkg:composer/laravel/framework\"),\n }),\n z.object({\n operation: z.literal(\"bulk-packages\"),\n purls: z.array(z.string()).min(1).max(50).describe(\"List of package PURLs to fetch in bulk.\"),\n concurrency: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(\"Maximum concurrent lookups. Defaults to 15.\"),\n }),\n ]),\n execute: async (input, { abortSignal }) => {\n switch (input.operation) {\n case \"package\":\n return fetchPackageFromPURL(input.purl, abortSignal);\n case \"versions\":\n return fetchVersionsFromPURL(input.purl, abortSignal);\n case \"dependencies\":\n return fetchDependenciesFromPURL(input.purl, abortSignal);\n case \"maintainers\":\n return fetchMaintainersFromPURL(input.purl, abortSignal);\n case \"bulk-packages\": {\n const results = await bulkFetchPackages(input.purls, {\n concurrency: input.concurrency,\n signal: abortSignal,\n });\n return Object.fromEntries(results);\n }\n }\n },\n});\n"],"mappings":";;;;;;AAWA,MAAa,cAAc,KAAK;CAC9B,aACE;CACF,aAAa,EAAE,mBAAmB,aAAa;EAC7C,EAAE,OAAO;GACP,WAAW,EAAE,QAAQ,UAAU;GAC/B,MAAM,EAAE,QAAQ,CAAC,SAAS,8DAAA;GAC3B,CAAC;EACF,EAAE,OAAO;GACP,WAAW,EAAE,QAAQ,WAAW;GAChC,MAAM,EAAE,QAAQ,CAAC,SAAS,8DAAA;GAC3B,CAAC;EACF,EAAE,OAAO;GACP,WAAW,EAAE,QAAQ,eAAe;GACpC,MAAM,EACH,QAAQ,CACR,SAAS,qEAAA;GACb,CAAC;EACF,EAAE,OAAO;GACP,WAAW,EAAE,QAAQ,cAAc;GACnC,MAAM,EACH,QAAQ,CACR,SAAS,4EAAA;GACb,CAAC;EACF,EAAE,OAAO;GACP,WAAW,EAAE,QAAQ,gBAAgB;GACrC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,GAAG,CAAC,SAAS,0CAA0C;GAC7F,aAAa,EACV,QAAQ,CACR,KAAK,CACL,IAAI,EAAE,CACN,IAAI,GAAG,CACP,UAAU,CACV,SAAS,8CAAA;GACb,CAAA;EACF,CAAC;CACF,SAAS,OAAO,OAAO,EAAE,kBAAkB;AACzC,UAAQ,MAAM,WAAd;GACE,KAAK,UACH,QAAO,qBAAqB,MAAM,MAAM,YAAY;GACtD,KAAK,WACH,QAAO,sBAAsB,MAAM,MAAM,YAAY;GACvD,KAAK,eACH,QAAO,0BAA0B,MAAM,MAAM,YAAY;GAC3D,KAAK,cACH,QAAO,yBAAyB,MAAM,MAAM,YAAY;GAC1D,KAAK,iBAAiB;IACpB,MAAM,UAAU,MAAM,kBAAkB,MAAM,OAAO;KACnD,aAAa,MAAM;KACnB,QAAQ;KACT,CAAC;AACF,WAAO,OAAO,YAAY,QAAQ;;;;CAIzC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import "../_chunks/types.mjs";
2
+ import "../_chunks/registry.mjs";
3
+ import { t as CachedRegistry } from "../_chunks/cached-registry.mjs";
4
+ export { CachedRegistry };
@@ -0,0 +1,92 @@
1
+ import { t as Registry } from "../_chunks/registry.mjs";
2
+ import { computeIntegrity, getStorage } from "./storage.mjs";
3
+ import { DEFAULT_TTL, cacheKey, getFreshEntry, readLockfile, setEntry, writeLockfile } from "./lockfile.mjs";
4
+ /**
5
+ * CachedRegistry wraps any Registry and adds caching backed by unstorage
6
+ * with a lockfile for freshness tracking.
7
+ *
8
+ * Pass a custom `storage` to use a non-default driver (e.g. Cloudflare KV).
9
+ * If omitted, uses the globally configured storage (see `configureStorage()`).
10
+ */
11
+ var CachedRegistry = class extends Registry {
12
+ inner;
13
+ storage;
14
+ lockfileStorage;
15
+ inflight = /* @__PURE__ */ new Map();
16
+ constructor(inner, storage) {
17
+ super();
18
+ this.inner = inner;
19
+ this.storage = storage ?? getStorage();
20
+ this.lockfileStorage = storage ?? getStorage();
21
+ }
22
+ ecosystem() {
23
+ return this.inner.ecosystem();
24
+ }
25
+ urls() {
26
+ return this.inner.urls();
27
+ }
28
+ async fetchPackage(name, signal) {
29
+ return this.cached(name, "package", void 0, () => this.inner.fetchPackage(name, signal), signal, (value) => ({ latestVersion: value.latestVersion }));
30
+ }
31
+ async fetchVersions(name, signal) {
32
+ return (await this.cached(name, "versions", void 0, () => this.inner.fetchVersions(name, signal), signal)).map((v) => ({
33
+ ...v,
34
+ publishedAt: v.publishedAt ? new Date(v.publishedAt) : null
35
+ }));
36
+ }
37
+ async fetchDependencies(name, version, signal) {
38
+ return this.cached(name, "dependencies", version, () => this.inner.fetchDependencies(name, version, signal), signal);
39
+ }
40
+ async fetchMaintainers(name, signal) {
41
+ return this.cached(name, "maintainers", void 0, () => this.inner.fetchMaintainers(name, signal), signal);
42
+ }
43
+ /**
44
+ * Generic cache-through method:
45
+ * 1. Check lockfile for fresh entry
46
+ * 2. If fresh -> read from storage
47
+ * 3. If stale/missing -> fetch, store, update lockfile
48
+ */
49
+ async cached(name, type, version, fetcher, signal, extraMeta) {
50
+ const key = cacheKey(this.inner.ecosystem(), name, type, version);
51
+ const storageKey = key;
52
+ const entry = getFreshEntry(await readLockfile(this.lockfileStorage), key);
53
+ if (entry) {
54
+ const cached = await this.storage.getItem(storageKey);
55
+ if (cached !== null) {
56
+ if (computeIntegrity(cached) === entry.integrity) return cached;
57
+ }
58
+ }
59
+ if (!signal) {
60
+ const pending = this.inflight.get(key);
61
+ if (pending) return pending;
62
+ }
63
+ const flight = (async () => {
64
+ const value = await fetcher();
65
+ const meta = extraMeta ? extraMeta(value) : {};
66
+ const newEntry = {
67
+ key,
68
+ type,
69
+ fetchedAt: Date.now(),
70
+ ttl: DEFAULT_TTL[type],
71
+ integrity: computeIntegrity(value),
72
+ ..."latestVersion" in meta ? { latestVersion: meta["latestVersion"] } : {}
73
+ };
74
+ try {
75
+ await this.storage.setItem(storageKey, value);
76
+ const freshLockfile = await readLockfile(this.lockfileStorage);
77
+ setEntry(freshLockfile, newEntry);
78
+ await writeLockfile(freshLockfile, this.lockfileStorage);
79
+ } catch {}
80
+ return value;
81
+ })();
82
+ if (!signal) this.inflight.set(key, flight);
83
+ try {
84
+ return await flight;
85
+ } finally {
86
+ if (!signal) this.inflight.delete(key);
87
+ }
88
+ }
89
+ };
90
+ export { CachedRegistry };
91
+
92
+ //# sourceMappingURL=cached-registry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cached-registry.mjs","names":[],"sources":["../../src/cache/cached-registry.ts"],"sourcesContent":["import type { Dependency, Maintainer, Package, URLBuilder, Version } from \"../core/types.ts\";\nimport { Registry } from \"../core/registry.ts\";\nimport { getStorage, computeIntegrity } from \"./storage.ts\";\nimport {\n readLockfile,\n writeLockfile,\n cacheKey,\n getFreshEntry,\n setEntry,\n DEFAULT_TTL,\n} from \"./lockfile.ts\";\nimport type { EntryType, LockfileEntry } from \"./lockfile.ts\";\nimport type { Storage } from \"unstorage\";\n\n/**\n * CachedRegistry wraps any Registry and adds caching backed by unstorage\n * with a lockfile for freshness tracking.\n *\n * Pass a custom `storage` to use a non-default driver (e.g. Cloudflare KV).\n * If omitted, uses the globally configured storage (see `configureStorage()`).\n */\nexport class CachedRegistry extends Registry {\n readonly inner: Registry;\n readonly storage: Storage;\n private readonly lockfileStorage: Storage;\n private readonly inflight = new Map<string, Promise<unknown>>();\n\n constructor(inner: Registry, storage?: Storage) {\n super();\n this.inner = inner;\n this.storage = storage ?? getStorage();\n this.lockfileStorage = storage ?? getStorage();\n }\n\n ecosystem(): string {\n return this.inner.ecosystem();\n }\n\n urls(): URLBuilder {\n return this.inner.urls();\n }\n\n async fetchPackage(name: string, signal?: AbortSignal): Promise<Package> {\n return this.cached<Package>(\n name,\n \"package\",\n undefined,\n () => this.inner.fetchPackage(name, signal),\n signal,\n (value) => ({ latestVersion: value.latestVersion }),\n );\n }\n\n async fetchVersions(name: string, signal?: AbortSignal): Promise<Version[]> {\n const versions = await this.cached<Version[]>(\n name,\n \"versions\",\n undefined,\n () => this.inner.fetchVersions(name, signal),\n signal,\n );\n // JSON round-trip through storage turns Date objects into ISO strings.\n // Revive them so consumers can safely call .getTime() / .toISOString().\n return versions.map((v) => ({\n ...v,\n publishedAt: v.publishedAt ? new Date(v.publishedAt as unknown as string) : null,\n }));\n }\n\n async fetchDependencies(\n name: string,\n version: string,\n signal?: AbortSignal,\n ): Promise<Dependency[]> {\n return this.cached<Dependency[]>(\n name,\n \"dependencies\",\n version,\n () => this.inner.fetchDependencies(name, version, signal),\n signal,\n );\n }\n\n async fetchMaintainers(name: string, signal?: AbortSignal): Promise<Maintainer[]> {\n return this.cached<Maintainer[]>(\n name,\n \"maintainers\",\n undefined,\n () => this.inner.fetchMaintainers(name, signal),\n signal,\n );\n }\n\n /**\n * Generic cache-through method:\n * 1. Check lockfile for fresh entry\n * 2. If fresh -> read from storage\n * 3. If stale/missing -> fetch, store, update lockfile\n */\n private async cached<T>(\n name: string,\n type: EntryType,\n version: string | undefined,\n fetcher: () => Promise<T>,\n signal: AbortSignal | undefined,\n extraMeta?: (value: T) => Record<string, unknown>,\n ): Promise<T> {\n const eco = this.inner.ecosystem();\n const key = cacheKey(eco, name, type, version);\n const storageKey = key;\n\n // 1. Check lockfile\n const lockfile = await readLockfile(this.lockfileStorage);\n const entry = getFreshEntry(lockfile, key);\n\n if (entry) {\n // 2. Read from storage\n const cached = await this.storage.getItem<T>(storageKey);\n if (cached !== null) {\n // Verify integrity\n const currentHash = computeIntegrity(cached);\n if (currentHash === entry.integrity) {\n return cached;\n }\n // Integrity mismatch - refetch\n }\n }\n\n // 3. Coalesce concurrent fetches for the same key (single-flight).\n // Skip dedup when caller provides an AbortSignal to avoid sharing\n // cancellation context across unrelated callers.\n if (!signal) {\n const pending = this.inflight.get(key);\n if (pending) {\n return pending as Promise<T>;\n }\n }\n\n const flight = (async () => {\n const value = await fetcher();\n\n // Persist to cache (best-effort - storage failures must not discard fetched data)\n const meta = extraMeta ? extraMeta(value) : {};\n const newEntry: LockfileEntry = {\n key,\n type,\n fetchedAt: Date.now(),\n ttl: DEFAULT_TTL[type],\n integrity: computeIntegrity(value),\n ...(\"latestVersion\" in meta ? { latestVersion: meta[\"latestVersion\"] as string } : {}),\n };\n try {\n await this.storage.setItem(storageKey, value as Record<string, unknown>);\n // Re-read lockfile before writing to avoid overwriting entries\n // added by concurrent calls for different keys.\n const freshLockfile = await readLockfile(this.lockfileStorage);\n setEntry(freshLockfile, newEntry);\n await writeLockfile(freshLockfile, this.lockfileStorage);\n } catch {\n // Storage I/O failed (disk full, permissions, remote driver timeout, etc.).\n // The data was already fetched successfully - return it anyway.\n }\n\n return value;\n })();\n\n if (!signal) {\n this.inflight.set(key, flight);\n }\n try {\n return await flight;\n } finally {\n if (!signal) {\n this.inflight.delete(key);\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAqBA,IAAa,iBAAb,cAAoC,SAAS;CAC3C;CACA;CACA;CACA,2BAA4B,IAAI,KAA+B;CAE/D,YAAY,OAAiB,SAAmB;AAC9C,SAAO;AACP,OAAK,QAAQ;AACb,OAAK,UAAU,WAAW,YAAY;AACtC,OAAK,kBAAkB,WAAW,YAAY;;CAGhD,YAAoB;AAClB,SAAO,KAAK,MAAM,WAAW;;CAG/B,OAAmB;AACjB,SAAO,KAAK,MAAM,MAAM;;CAG1B,MAAM,aAAa,MAAc,QAAwC;AACvE,SAAO,KAAK,OACV,MACA,WACA,KAAA,SACM,KAAK,MAAM,aAAa,MAAM,OAAO,EAC3C,SACC,WAAW,EAAE,eAAe,MAAM,eAAe,EACnD;;CAGH,MAAM,cAAc,MAAc,QAA0C;AAU1E,UATiB,MAAM,KAAK,OAC1B,MACA,YACA,KAAA,SACM,KAAK,MAAM,cAAc,MAAM,OAAO,EAC5C,OACD,EAGe,KAAK,OAAO;GAC1B,GAAG;GACH,aAAa,EAAE,cAAc,IAAI,KAAK,EAAE,YAAiC,GAAG;GAC7E,EAAE;;CAGL,MAAM,kBACJ,MACA,SACA,QACuB;AACvB,SAAO,KAAK,OACV,MACA,gBACA,eACM,KAAK,MAAM,kBAAkB,MAAM,SAAS,OAAO,EACzD,OACD;;CAGH,MAAM,iBAAiB,MAAc,QAA6C;AAChF,SAAO,KAAK,OACV,MACA,eACA,KAAA,SACM,KAAK,MAAM,iBAAiB,MAAM,OAAO,EAC/C,OACD;;;;;;;;CASH,MAAc,OACZ,MACA,MACA,SACA,SACA,QACA,WACY;EAEZ,MAAM,MAAM,SADA,KAAK,MAAM,WAAW,EACR,MAAM,MAAM,QAAQ;EAC9C,MAAM,aAAa;EAInB,MAAM,QAAQ,cADG,MAAM,aAAa,KAAK,gBAAgB,EACnB,IAAI;AAE1C,MAAI,OAAO;GAET,MAAM,SAAS,MAAM,KAAK,QAAQ,QAAW,WAAW;AACxD,OAAI,WAAW;QAEO,iBAAiB,OAAO,KACxB,MAAM,UACxB,QAAO;;;AASb,MAAI,CAAC,QAAQ;GACX,MAAM,UAAU,KAAK,SAAS,IAAI,IAAI;AACtC,OAAI,QACF,QAAO;;EAIX,MAAM,UAAU,YAAY;GAC1B,MAAM,QAAQ,MAAM,SAAS;GAG7B,MAAM,OAAO,YAAY,UAAU,MAAM,GAAG,EAAE;GAC9C,MAAM,WAA0B;IAC9B;IACA;IACA,WAAW,KAAK,KAAK;IACrB,KAAK,YAAY;IACjB,WAAW,iBAAiB,MAAM;IAClC,GAAI,mBAAmB,OAAO,EAAE,eAAe,KAAK,kBAA4B,GAAG,EAAA;IACpF;AACD,OAAI;AACF,UAAM,KAAK,QAAQ,QAAQ,YAAY,MAAiC;IAGxE,MAAM,gBAAgB,MAAM,aAAa,KAAK,gBAAgB;AAC9D,aAAS,eAAe,SAAS;AACjC,UAAM,cAAc,eAAe,KAAK,gBAAgB;WAClD;AAKR,UAAO;MACL;AAEJ,MAAI,CAAC,OACH,MAAK,SAAS,IAAI,KAAK,OAAO;AAEhC,MAAI;AACF,UAAO,MAAM;YACL;AACR,OAAI,CAAC,OACH,MAAK,SAAS,OAAO,IAAI"}
@@ -0,0 +1,8 @@
1
+ import "../_chunks/types.mjs";
2
+ import "../_chunks/registry.mjs";
3
+ import { t as getCacheDir } from "../_chunks/paths.mjs";
4
+ import { a as getEcosystemStorage, i as disposeStorage, n as computeIntegrity, o as getStorage, r as configureStorage, t as clearStorage } from "../_chunks/storage.mjs";
5
+ import { t as CachedRegistry } from "../_chunks/cached-registry.mjs";
6
+ import { a as cacheKey, c as pruneStale, d as setEntry, f as writeLockfile, i as LockfileEntry, l as readLockfile, n as EntryType, o as getFreshEntry, r as Lockfile, s as isFresh, t as DEFAULT_TTL, u as removeEntry } from "../_chunks/lockfile.mjs";
7
+ import { n as createCached, t as CreateCachedOptions } from "../_chunks/index3.mjs";
8
+ export { CachedRegistry, CreateCachedOptions, DEFAULT_TTL, EntryType, Lockfile, LockfileEntry, cacheKey, clearStorage, computeIntegrity, configureStorage, createCached, disposeStorage, getCacheDir, getEcosystemStorage, getFreshEntry, getStorage, isFresh, pruneStale, readLockfile, removeEntry, setEntry, writeLockfile };
@@ -0,0 +1,12 @@
1
+ import { n as create } from "../_chunks/registry.mjs";
2
+ import { getCacheDir } from "./paths.mjs";
3
+ import { clearStorage, computeIntegrity, configureStorage, disposeStorage, getEcosystemStorage, getStorage } from "./storage.mjs";
4
+ import { DEFAULT_TTL, cacheKey, getFreshEntry, isFresh, pruneStale, readLockfile, removeEntry, setEntry, writeLockfile } from "./lockfile.mjs";
5
+ import { CachedRegistry } from "./cached-registry.mjs";
6
+ /** Create a registry instance with caching + lockfile tracking. */
7
+ function createCached(ecosystem, options) {
8
+ return new CachedRegistry(create(ecosystem, options?.baseURL, options?.client), options?.storage);
9
+ }
10
+ export { CachedRegistry, DEFAULT_TTL, cacheKey, clearStorage, computeIntegrity, configureStorage, createCached, disposeStorage, getCacheDir, getEcosystemStorage, getFreshEntry, getStorage, isFresh, pruneStale, readLockfile, removeEntry, setEntry, writeLockfile };
11
+
12
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/cache/index.ts"],"sourcesContent":["// Cache\nexport { getCacheDir } from \"./paths.ts\";\nexport {\n getStorage,\n getEcosystemStorage,\n computeIntegrity,\n disposeStorage,\n clearStorage,\n configureStorage,\n} from \"./storage.ts\";\nexport { CachedRegistry } from \"./cached-registry.ts\";\nexport {\n readLockfile,\n writeLockfile,\n cacheKey,\n isFresh,\n getFreshEntry,\n setEntry,\n removeEntry,\n pruneStale,\n DEFAULT_TTL,\n} from \"./lockfile.ts\";\nexport type { Lockfile, LockfileEntry, EntryType } from \"./lockfile.ts\";\n\n// Convenience\nimport type { Storage } from \"unstorage\";\nimport type { Client } from \"../core/client.ts\";\nimport { create, type Registry } from \"../core/registry.ts\";\nimport { CachedRegistry } from \"./cached-registry.ts\";\n\n/** Options for creating a cached registry. */\nexport interface CreateCachedOptions {\n /** Custom base URL for the registry API. */\n baseURL?: string;\n /** Custom HTTP client. */\n client?: Client;\n /** Custom unstorage instance. Overrides the globally configured storage. */\n storage?: Storage;\n}\n\n/** Create a registry instance with caching + lockfile tracking. */\nexport function createCached(ecosystem: string, options?: CreateCachedOptions): Registry {\n const inner = create(ecosystem, options?.baseURL, options?.client);\n return new CachedRegistry(inner, options?.storage);\n}\n"],"mappings":";;;;;;AAyCA,SAAgB,aAAa,WAAmB,SAAyC;AAEvF,QAAO,IAAI,eADG,OAAO,WAAW,SAAS,SAAS,SAAS,OAAO,EACjC,SAAS,QAAQ"}
@@ -0,0 +1,2 @@
1
+ import { a as cacheKey, c as pruneStale, d as setEntry, f as writeLockfile, i as LockfileEntry, l as readLockfile, n as EntryType, o as getFreshEntry, r as Lockfile, s as isFresh, t as DEFAULT_TTL, u as removeEntry } from "../_chunks/lockfile.mjs";
2
+ export { DEFAULT_TTL, EntryType, Lockfile, LockfileEntry, cacheKey, getFreshEntry, isFresh, pruneStale, readLockfile, removeEntry, setEntry, writeLockfile };
@@ -0,0 +1,68 @@
1
+ import { getStorage } from "./storage.mjs";
2
+ const LOCKFILE_VERSION = 1;
3
+ const LOCKFILE_KEY = "__lockfile__";
4
+ /** Default TTL per data type (seconds). */
5
+ const DEFAULT_TTL = {
6
+ package: 3600,
7
+ versions: 1800,
8
+ dependencies: 86400,
9
+ maintainers: 86400
10
+ };
11
+ function emptyLockfile() {
12
+ return {
13
+ version: LOCKFILE_VERSION,
14
+ entries: {}
15
+ };
16
+ }
17
+ /** Read the lockfile from storage. Returns empty lockfile if not found or invalid. */
18
+ async function readLockfile(storage) {
19
+ const s = storage ?? getStorage();
20
+ try {
21
+ const data = await s.getItem(LOCKFILE_KEY);
22
+ if (!data || data.version !== LOCKFILE_VERSION) return emptyLockfile();
23
+ return data;
24
+ } catch {
25
+ return emptyLockfile();
26
+ }
27
+ }
28
+ /** Write the lockfile to storage. */
29
+ async function writeLockfile(lockfile, storage) {
30
+ await (storage ?? getStorage()).setItem(LOCKFILE_KEY, lockfile);
31
+ }
32
+ /** Build a cache key from ecosystem + name + type + optional version. */
33
+ function cacheKey(ecosystem, name, type, version) {
34
+ const base = `${ecosystem}:${name}:${type}`;
35
+ return version ? `${base}:${version}` : base;
36
+ }
37
+ /** Check if a lockfile entry is still fresh. */
38
+ function isFresh(entry) {
39
+ const expiresAt = entry.fetchedAt + entry.ttl * 1e3;
40
+ return Date.now() < expiresAt;
41
+ }
42
+ /** Get a lockfile entry if it exists and is fresh. Returns null otherwise. */
43
+ function getFreshEntry(lockfile, key) {
44
+ const entry = lockfile.entries[key];
45
+ if (!entry) return null;
46
+ if (!isFresh(entry)) return null;
47
+ return entry;
48
+ }
49
+ /** Upsert an entry in the lockfile. Does NOT persist — call writeLockfile() after. */
50
+ function setEntry(lockfile, entry) {
51
+ lockfile.entries[entry.key] = entry;
52
+ }
53
+ /** Remove an entry from the lockfile. */
54
+ function removeEntry(lockfile, key) {
55
+ delete lockfile.entries[key];
56
+ }
57
+ /** Remove all stale entries from the lockfile. Returns count of removed entries. */
58
+ function pruneStale(lockfile) {
59
+ let removed = 0;
60
+ for (const key of Object.keys(lockfile.entries)) if (!isFresh(lockfile.entries[key])) {
61
+ delete lockfile.entries[key];
62
+ removed++;
63
+ }
64
+ return removed;
65
+ }
66
+ export { DEFAULT_TTL, cacheKey, getFreshEntry, isFresh, pruneStale, readLockfile, removeEntry, setEntry, writeLockfile };
67
+
68
+ //# sourceMappingURL=lockfile.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lockfile.mjs","names":[],"sources":["../../src/cache/lockfile.ts"],"sourcesContent":["import type { Storage } from \"unstorage\";\nimport { getStorage } from \"./storage.ts\";\n\nconst LOCKFILE_VERSION = 1;\nconst LOCKFILE_KEY = \"__lockfile__\";\n\n/** Default TTL per data type (seconds). */\nexport const DEFAULT_TTL = {\n package: 3600, // 1 hour — package metadata changes rarely\n versions: 1800, // 30 min — new versions published more often\n dependencies: 86400, // 24 hours — deps of a specific version never change\n maintainers: 86400, // 24 hours\n} as const;\n\nexport type EntryType = keyof typeof DEFAULT_TTL;\n\n/** A single cached entry tracked in the lockfile. */\nexport interface LockfileEntry {\n /** PURL-style key: 'npm:lodash', 'cargo:serde' */\n key: string;\n /** What kind of data: package, versions, dependencies, maintainers */\n type: EntryType;\n /** Unix timestamp (ms) when fetched */\n fetchedAt: number;\n /** TTL in seconds */\n ttl: number;\n /** Latest version at time of caching (for package entries) */\n latestVersion?: string;\n /** Content hash (sha256 of JSON-serialized value) for integrity */\n integrity: string;\n}\n\n/** The full lockfile structure. */\nexport interface Lockfile {\n version: number;\n entries: Record<string, LockfileEntry>;\n}\n\nfunction emptyLockfile(): Lockfile {\n return { version: LOCKFILE_VERSION, entries: {} };\n}\n\n/** Read the lockfile from storage. Returns empty lockfile if not found or invalid. */\nexport async function readLockfile(storage?: Storage): Promise<Lockfile> {\n const s = storage ?? getStorage();\n try {\n const data = await s.getItem<Lockfile>(LOCKFILE_KEY);\n if (!data || data.version !== LOCKFILE_VERSION) return emptyLockfile();\n return data;\n } catch {\n return emptyLockfile();\n }\n}\n\n/** Write the lockfile to storage. */\nexport async function writeLockfile(lockfile: Lockfile, storage?: Storage): Promise<void> {\n const s = storage ?? getStorage();\n await s.setItem(LOCKFILE_KEY, lockfile);\n}\n\n/** Build a cache key from ecosystem + name + type + optional version. */\nexport function cacheKey(\n ecosystem: string,\n name: string,\n type: EntryType,\n version?: string,\n): string {\n const base = `${ecosystem}:${name}:${type}`;\n return version ? `${base}:${version}` : base;\n}\n\n/** Check if a lockfile entry is still fresh. */\nexport function isFresh(entry: LockfileEntry): boolean {\n const expiresAt = entry.fetchedAt + entry.ttl * 1000;\n return Date.now() < expiresAt;\n}\n\n/** Get a lockfile entry if it exists and is fresh. Returns null otherwise. */\nexport function getFreshEntry(lockfile: Lockfile, key: string): LockfileEntry | null {\n const entry = lockfile.entries[key];\n if (!entry) return null;\n if (!isFresh(entry)) return null;\n return entry;\n}\n\n/** Upsert an entry in the lockfile. Does NOT persist — call writeLockfile() after. */\nexport function setEntry(lockfile: Lockfile, entry: LockfileEntry): void {\n lockfile.entries[entry.key] = entry;\n}\n\n/** Remove an entry from the lockfile. */\nexport function removeEntry(lockfile: Lockfile, key: string): void {\n delete lockfile.entries[key];\n}\n\n/** Remove all stale entries from the lockfile. Returns count of removed entries. */\nexport function pruneStale(lockfile: Lockfile): number {\n let removed = 0;\n for (const key of Object.keys(lockfile.entries)) {\n if (!isFresh(lockfile.entries[key]!)) {\n delete lockfile.entries[key];\n removed++;\n }\n }\n return removed;\n}\n"],"mappings":";AAGA,MAAM,mBAAmB;AACzB,MAAM,eAAe;;AAGrB,MAAa,cAAc;CACzB,SAAS;CACT,UAAU;CACV,cAAc;CACd,aAAa;CACd;AA0BD,SAAS,gBAA0B;AACjC,QAAO;EAAE,SAAS;EAAkB,SAAS,EAAA;EAAI;;;AAInD,eAAsB,aAAa,SAAsC;CACvE,MAAM,IAAI,WAAW,YAAY;AACjC,KAAI;EACF,MAAM,OAAO,MAAM,EAAE,QAAkB,aAAa;AACpD,MAAI,CAAC,QAAQ,KAAK,YAAY,iBAAkB,QAAO,eAAe;AACtE,SAAO;SACD;AACN,SAAO,eAAe;;;;AAK1B,eAAsB,cAAc,UAAoB,SAAkC;AAExF,QADU,WAAW,YAAY,EACzB,QAAQ,cAAc,SAAS;;;AAIzC,SAAgB,SACd,WACA,MACA,MACA,SACQ;CACR,MAAM,OAAO,GAAG,UAAU,GAAG,KAAK,GAAG;AACrC,QAAO,UAAU,GAAG,KAAK,GAAG,YAAY;;;AAI1C,SAAgB,QAAQ,OAA+B;CACrD,MAAM,YAAY,MAAM,YAAY,MAAM,MAAM;AAChD,QAAO,KAAK,KAAK,GAAG;;;AAItB,SAAgB,cAAc,UAAoB,KAAmC;CACnF,MAAM,QAAQ,SAAS,QAAQ;AAC/B,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,CAAC,QAAQ,MAAM,CAAE,QAAO;AAC5B,QAAO;;;AAIT,SAAgB,SAAS,UAAoB,OAA4B;AACvE,UAAS,QAAQ,MAAM,OAAO;;;AAIhC,SAAgB,YAAY,UAAoB,KAAmB;AACjE,QAAO,SAAS,QAAQ;;;AAI1B,SAAgB,WAAW,UAA4B;CACrD,IAAI,UAAU;AACd,MAAK,MAAM,OAAO,OAAO,KAAK,SAAS,QAAQ,CAC7C,KAAI,CAAC,QAAQ,SAAS,QAAQ,KAAM,EAAE;AACpC,SAAO,SAAS,QAAQ;AACxB;;AAGJ,QAAO"}
@@ -0,0 +1,2 @@
1
+ import { t as getCacheDir } from "../_chunks/paths.mjs";
2
+ export { getCacheDir };
@@ -0,0 +1,26 @@
1
+ import { homedir, platform } from "node:os";
2
+ import { join } from "node:path";
3
+ const APP_NAME = "registries";
4
+ /**
5
+ * Resolve the cache directory following platform conventions:
6
+ * - Linux: $XDG_CACHE_HOME/registries or ~/.cache/registries
7
+ * - macOS: ~/Library/Caches/registries
8
+ * - Windows: %LOCALAPPDATA%/registries/cache
9
+ */
10
+ function getCacheDir() {
11
+ const envOverride = process.env["REGISTRIES_CACHE_DIR"];
12
+ if (envOverride) return envOverride;
13
+ const os = platform();
14
+ if (os === "darwin") return join(homedir(), "Library", "Caches", APP_NAME);
15
+ if (os === "win32") {
16
+ const localAppData = process.env["LOCALAPPDATA"];
17
+ if (localAppData) return join(localAppData, APP_NAME, "cache");
18
+ return join(homedir(), "AppData", "Local", APP_NAME, "cache");
19
+ }
20
+ const xdgCache = process.env["XDG_CACHE_HOME"];
21
+ if (xdgCache) return join(xdgCache, APP_NAME);
22
+ return join(homedir(), ".cache", APP_NAME);
23
+ }
24
+ export { getCacheDir };
25
+
26
+ //# sourceMappingURL=paths.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.mjs","names":[],"sources":["../../src/cache/paths.ts"],"sourcesContent":["import { homedir, platform } from \"node:os\";\nimport { join } from \"node:path\";\n\nconst APP_NAME = \"registries\";\n\n/**\n * Resolve the cache directory following platform conventions:\n * - Linux: $XDG_CACHE_HOME/registries or ~/.cache/registries\n * - macOS: ~/Library/Caches/registries\n * - Windows: %LOCALAPPDATA%/registries/cache\n */\nexport function getCacheDir(): string {\n // Explicit override always wins\n const envOverride = process.env[\"REGISTRIES_CACHE_DIR\"];\n if (envOverride) return envOverride;\n\n const os = platform();\n\n if (os === \"darwin\") {\n return join(homedir(), \"Library\", \"Caches\", APP_NAME);\n }\n\n if (os === \"win32\") {\n const localAppData = process.env[\"LOCALAPPDATA\"];\n if (localAppData) return join(localAppData, APP_NAME, \"cache\");\n return join(homedir(), \"AppData\", \"Local\", APP_NAME, \"cache\");\n }\n\n // Linux / FreeBSD / other — XDG Base Directory\n const xdgCache = process.env[\"XDG_CACHE_HOME\"];\n if (xdgCache) return join(xdgCache, APP_NAME);\n return join(homedir(), \".cache\", APP_NAME);\n}\n"],"mappings":";;AAGA,MAAM,WAAW;;;;;;;AAQjB,SAAgB,cAAsB;CAEpC,MAAM,cAAc,QAAQ,IAAI;AAChC,KAAI,YAAa,QAAO;CAExB,MAAM,KAAK,UAAU;AAErB,KAAI,OAAO,SACT,QAAO,KAAK,SAAS,EAAE,WAAW,UAAU,SAAS;AAGvD,KAAI,OAAO,SAAS;EAClB,MAAM,eAAe,QAAQ,IAAI;AACjC,MAAI,aAAc,QAAO,KAAK,cAAc,UAAU,QAAQ;AAC9D,SAAO,KAAK,SAAS,EAAE,WAAW,SAAS,UAAU,QAAQ;;CAI/D,MAAM,WAAW,QAAQ,IAAI;AAC7B,KAAI,SAAU,QAAO,KAAK,UAAU,SAAS;AAC7C,QAAO,KAAK,SAAS,EAAE,UAAU,SAAS"}
@@ -0,0 +1,2 @@
1
+ import { a as getEcosystemStorage, i as disposeStorage, n as computeIntegrity, o as getStorage, r as configureStorage, t as clearStorage } from "../_chunks/storage.mjs";
2
+ export { clearStorage, computeIntegrity, configureStorage, disposeStorage, getEcosystemStorage, getStorage };
@@ -0,0 +1,40 @@
1
+ import { getCacheDir } from "./paths.mjs";
2
+ import { createStorage, prefixStorage } from "unstorage";
3
+ import fsDriver from "unstorage/drivers/fs";
4
+ import { hash } from "node:crypto";
5
+ let _storage;
6
+ /**
7
+ * Configure a custom unstorage instance for @agntn/registries.
8
+ * Call this before any cache operations to use a non-default driver
9
+ * (e.g. Cloudflare KV, Redis, memory).
10
+ */
11
+ function configureStorage(storage) {
12
+ _storage = storage;
13
+ }
14
+ /** Get or create the shared storage instance. Uses configured storage or falls back to fs driver. */
15
+ function getStorage() {
16
+ if (!_storage) _storage = createStorage({ driver: fsDriver({ base: getCacheDir() }) });
17
+ return _storage;
18
+ }
19
+ /** Get a namespaced storage for a specific ecosystem (e.g. 'npm', 'cargo'). */
20
+ function getEcosystemStorage(ecosystem) {
21
+ return prefixStorage(getStorage(), ecosystem);
22
+ }
23
+ /** Compute a sha256 integrity hash of a JSON-serializable value. */
24
+ function computeIntegrity(value) {
25
+ return `sha256-${hash("sha256", JSON.stringify(value), "hex")}`;
26
+ }
27
+ /** Dispose the storage instance. Call on process exit. */
28
+ async function disposeStorage() {
29
+ if (_storage) {
30
+ await _storage.dispose();
31
+ _storage = void 0;
32
+ }
33
+ }
34
+ /** Clear all cached data. */
35
+ async function clearStorage() {
36
+ await getStorage().clear();
37
+ }
38
+ export { clearStorage, computeIntegrity, configureStorage, disposeStorage, getEcosystemStorage, getStorage };
39
+
40
+ //# sourceMappingURL=storage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.mjs","names":[],"sources":["../../src/cache/storage.ts"],"sourcesContent":["import { createStorage, prefixStorage } from \"unstorage\";\nimport fsDriver from \"unstorage/drivers/fs\";\nimport type { Storage } from \"unstorage\";\nimport { getCacheDir } from \"./paths.ts\";\nimport { hash } from \"node:crypto\";\n\nlet _storage: Storage | undefined;\n\n/**\n * Configure a custom unstorage instance for @agntn/registries.\n * Call this before any cache operations to use a non-default driver\n * (e.g. Cloudflare KV, Redis, memory).\n */\nexport function configureStorage(storage: Storage): void {\n _storage = storage;\n}\n\n/** Get or create the shared storage instance. Uses configured storage or falls back to fs driver. */\nexport function getStorage(): Storage {\n if (!_storage) {\n _storage = createStorage({\n driver: fsDriver({ base: getCacheDir() }),\n });\n }\n return _storage;\n}\n\n/** Get a namespaced storage for a specific ecosystem (e.g. 'npm', 'cargo'). */\nexport function getEcosystemStorage(ecosystem: string): Storage {\n return prefixStorage(getStorage(), ecosystem);\n}\n\n/** Compute a sha256 integrity hash of a JSON-serializable value. */\nexport function computeIntegrity(value: unknown): string {\n return `sha256-${hash(\"sha256\", JSON.stringify(value), \"hex\")}`;\n}\n\n/** Dispose the storage instance. Call on process exit. */\nexport async function disposeStorage(): Promise<void> {\n if (_storage) {\n await _storage.dispose();\n _storage = undefined;\n }\n}\n\n/** Clear all cached data. */\nexport async function clearStorage(): Promise<void> {\n await getStorage().clear();\n}\n"],"mappings":";;;;AAMA,IAAI;;;;;;AAOJ,SAAgB,iBAAiB,SAAwB;AACvD,YAAW;;;AAIb,SAAgB,aAAsB;AACpC,KAAI,CAAC,SACH,YAAW,cAAc,EACvB,QAAQ,SAAS,EAAE,MAAM,aAAa,EAAE,CAAC,EAC1C,CAAC;AAEJ,QAAO;;;AAIT,SAAgB,oBAAoB,WAA4B;AAC9D,QAAO,cAAc,YAAY,EAAE,UAAU;;;AAI/C,SAAgB,iBAAiB,OAAwB;AACvD,QAAO,UAAU,KAAK,UAAU,KAAK,UAAU,MAAM,EAAE,MAAM;;;AAI/D,eAAsB,iBAAgC;AACpD,KAAI,UAAU;AACZ,QAAM,SAAS,SAAS;AACxB,aAAW,KAAA;;;;AAKf,eAAsB,eAA8B;AAClD,OAAM,YAAY,CAAC,OAAO"}
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { defineCommand, runMain } from "citty";
3
+ runMain(defineCommand({
4
+ meta: {
5
+ name: "registries",
6
+ version: "0.1.0",
7
+ description: "Universal package registry client — query npm, PyPI, crates.io, RubyGems, Packagist with a single PURL-native API."
8
+ },
9
+ subCommands: {
10
+ info: () => import("./commands/info.mjs").then((m) => m.default),
11
+ versions: () => import("./commands/versions.mjs").then((m) => m.default),
12
+ deps: () => import("./commands/deps.mjs").then((m) => m.default),
13
+ maintainers: () => import("./commands/maintainers.mjs").then((m) => m.default),
14
+ cache: () => import("./commands/cache.mjs").then((m) => m.default)
15
+ }
16
+ }));
17
+ export {};
18
+
19
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { defineCommand, runMain } from \"citty\";\n\nconst main = defineCommand({\n meta: {\n name: \"registries\",\n version: \"0.1.0\",\n description:\n \"Universal package registry client — query npm, PyPI, crates.io, RubyGems, Packagist with a single PURL-native API.\",\n },\n subCommands: {\n info: () => import(\"./commands/info.ts\").then((m) => m.default),\n versions: () => import(\"./commands/versions.ts\").then((m) => m.default),\n deps: () => import(\"./commands/deps.ts\").then((m) => m.default),\n maintainers: () => import(\"./commands/maintainers.ts\").then((m) => m.default),\n cache: () => import(\"./commands/cache.ts\").then((m) => m.default),\n },\n});\n\nrunMain(main);\n"],"mappings":";;AAmBA,QAhBa,cAAc;CACzB,MAAM;EACJ,MAAM;EACN,SAAS;EACT,aACE;EACH;CACD,aAAa;EACX,YAAY,OAAO,uBAAsB,MAAM,MAAM,EAAE,QAAQ;EAC/D,gBAAgB,OAAO,2BAA0B,MAAM,MAAM,EAAE,QAAQ;EACvE,YAAY,OAAO,uBAAsB,MAAM,MAAM,EAAE,QAAQ;EAC/D,mBAAmB,OAAO,8BAA6B,MAAM,MAAM,EAAE,QAAQ;EAC7E,aAAa,OAAO,wBAAuB,MAAM,MAAM,EAAE,QAAA;;CAE5D,CAAC,CAEW"}
@@ -0,0 +1,7 @@
1
+ import * as citty from "citty";
2
+
3
+ //#region src/commands/cache.d.ts
4
+ declare const _default: citty.CommandDef<citty.ArgsDef>;
5
+ //#endregion
6
+ export { _default as default };
7
+ //# sourceMappingURL=cache.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.mts","names":[],"sources":["../../src/commands/cache.ts"],"mappings":""}
@@ -0,0 +1,80 @@
1
+ import { getCacheDir } from "../cache/paths.mjs";
2
+ import { clearStorage } from "../cache/storage.mjs";
3
+ import { pruneStale, readLockfile, writeLockfile } from "../cache/lockfile.mjs";
4
+ import { defineCommand } from "citty";
5
+ import consola from "consola";
6
+ var cache_default = defineCommand({
7
+ meta: {
8
+ name: "cache",
9
+ description: "Manage the registries cache"
10
+ },
11
+ subCommands: {
12
+ clear: defineCommand({
13
+ meta: {
14
+ name: "clear",
15
+ description: "Remove all cached data"
16
+ },
17
+ async run() {
18
+ await clearStorage();
19
+ await writeLockfile({
20
+ version: 1,
21
+ entries: {}
22
+ });
23
+ consola.success("Cache cleared");
24
+ }
25
+ }),
26
+ path: defineCommand({
27
+ meta: {
28
+ name: "path",
29
+ description: "Show cache directory path"
30
+ },
31
+ run() {
32
+ console.log(getCacheDir());
33
+ }
34
+ }),
35
+ status: defineCommand({
36
+ meta: {
37
+ name: "status",
38
+ description: "Show cache status and entry count"
39
+ },
40
+ async run() {
41
+ const lockfile = await readLockfile();
42
+ const entries = Object.values(lockfile.entries);
43
+ const fresh = entries.filter((e) => {
44
+ const expiresAt = e.fetchedAt + e.ttl * 1e3;
45
+ return Date.now() < expiresAt;
46
+ });
47
+ const stale = entries.length - fresh.length;
48
+ consola.log("");
49
+ consola.log(` \x1b[1mCache status\x1b[0m`);
50
+ consola.log(` \x1b[90mDirectory:\x1b[0m ${getCacheDir()}`);
51
+ consola.log(` \x1b[90mEntries:\x1b[0m ${entries.length} total, ${fresh.length} fresh, ${stale} stale`);
52
+ consola.log("");
53
+ if (fresh.length > 0) {
54
+ const byEco = /* @__PURE__ */ new Map();
55
+ for (const e of fresh) {
56
+ const eco = e.key.split(":")[0];
57
+ byEco.set(eco, (byEco.get(eco) ?? 0) + 1);
58
+ }
59
+ for (const [eco, count] of byEco) consola.log(` \x1b[36m${eco}\x1b[0m: ${count} cached`);
60
+ consola.log("");
61
+ }
62
+ }
63
+ }),
64
+ prune: defineCommand({
65
+ meta: {
66
+ name: "prune",
67
+ description: "Remove stale cache entries"
68
+ },
69
+ async run() {
70
+ const lockfile = await readLockfile();
71
+ const removed = pruneStale(lockfile);
72
+ await writeLockfile(lockfile);
73
+ consola.success(`Pruned ${removed} stale entries`);
74
+ }
75
+ })
76
+ }
77
+ });
78
+ export { cache_default as default };
79
+
80
+ //# sourceMappingURL=cache.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.mjs","names":[],"sources":["../../src/commands/cache.ts"],"sourcesContent":["import { defineCommand } from \"citty\";\nimport consola from \"consola\";\nimport { getCacheDir } from \"../cache/paths.ts\";\nimport { clearStorage } from \"../cache/storage.ts\";\nimport { readLockfile, writeLockfile, pruneStale } from \"../cache/lockfile.ts\";\n\nexport default defineCommand({\n meta: {\n name: \"cache\",\n description: \"Manage the registries cache\",\n },\n subCommands: {\n clear: defineCommand({\n meta: {\n name: \"clear\",\n description: \"Remove all cached data\",\n },\n async run() {\n await clearStorage();\n await writeLockfile({ version: 1, entries: {} });\n consola.success(\"Cache cleared\");\n },\n }),\n path: defineCommand({\n meta: {\n name: \"path\",\n description: \"Show cache directory path\",\n },\n run() {\n console.log(getCacheDir());\n },\n }),\n status: defineCommand({\n meta: {\n name: \"status\",\n description: \"Show cache status and entry count\",\n },\n async run() {\n const lockfile = await readLockfile();\n const entries = Object.values(lockfile.entries);\n const fresh = entries.filter((e) => {\n const expiresAt = e.fetchedAt + e.ttl * 1000;\n return Date.now() < expiresAt;\n });\n const stale = entries.length - fresh.length;\n\n consola.log(\"\");\n consola.log(` \\x1b[1mCache status\\x1b[0m`);\n consola.log(` \\x1b[90mDirectory:\\x1b[0m ${getCacheDir()}`);\n consola.log(\n ` \\x1b[90mEntries:\\x1b[0m ${entries.length} total, ${fresh.length} fresh, ${stale} stale`,\n );\n consola.log(\"\");\n\n if (fresh.length > 0) {\n // Group by ecosystem\n const byEco = new Map<string, number>();\n for (const e of fresh) {\n const eco = e.key.split(\":\")[0]!;\n byEco.set(eco, (byEco.get(eco) ?? 0) + 1);\n }\n for (const [eco, count] of byEco) {\n consola.log(` \\x1b[36m${eco}\\x1b[0m: ${count} cached`);\n }\n consola.log(\"\");\n }\n },\n }),\n prune: defineCommand({\n meta: {\n name: \"prune\",\n description: \"Remove stale cache entries\",\n },\n async run() {\n const lockfile = await readLockfile();\n const removed = pruneStale(lockfile);\n await writeLockfile(lockfile);\n consola.success(`Pruned ${removed} stale entries`);\n },\n }),\n },\n});\n"],"mappings":";;;;;AAMA,IAAA,gBAAe,cAAc;CAC3B,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,aAAa;EACX,OAAO,cAAc;GACnB,MAAM;IACJ,MAAM;IACN,aAAa;IACd;GACD,MAAM,MAAM;AACV,UAAM,cAAc;AACpB,UAAM,cAAc;KAAE,SAAS;KAAG,SAAS,EAAA;KAAI,CAAC;AAChD,YAAQ,QAAQ,gBAAgB;;GAEnC,CAAC;EACF,MAAM,cAAc;GAClB,MAAM;IACJ,MAAM;IACN,aAAa;IACd;GACD,MAAM;AACJ,YAAQ,IAAI,aAAa,CAAC;;GAE7B,CAAC;EACF,QAAQ,cAAc;GACpB,MAAM;IACJ,MAAM;IACN,aAAa;IACd;GACD,MAAM,MAAM;IACV,MAAM,WAAW,MAAM,cAAc;IACrC,MAAM,UAAU,OAAO,OAAO,SAAS,QAAQ;IAC/C,MAAM,QAAQ,QAAQ,QAAQ,MAAM;KAClC,MAAM,YAAY,EAAE,YAAY,EAAE,MAAM;AACxC,YAAO,KAAK,KAAK,GAAG;MACpB;IACF,MAAM,QAAQ,QAAQ,SAAS,MAAM;AAErC,YAAQ,IAAI,GAAG;AACf,YAAQ,IAAI,+BAA+B;AAC3C,YAAQ,IAAI,gCAAgC,aAAa,GAAG;AAC5D,YAAQ,IACN,gCAAgC,QAAQ,OAAO,UAAU,MAAM,OAAO,UAAU,MAAM,QACvF;AACD,YAAQ,IAAI,GAAG;AAEf,QAAI,MAAM,SAAS,GAAG;KAEpB,MAAM,wBAAQ,IAAI,KAAqB;AACvC,UAAK,MAAM,KAAK,OAAO;MACrB,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,CAAC;AAC7B,YAAM,IAAI,MAAM,MAAM,IAAI,IAAI,IAAI,KAAK,EAAE;;AAE3C,UAAK,MAAM,CAAC,KAAK,UAAU,MACzB,SAAQ,IAAI,aAAa,IAAI,WAAW,MAAM,SAAS;AAEzD,aAAQ,IAAI,GAAG;;;GAGpB,CAAC;EACF,OAAO,cAAc;GACnB,MAAM;IACJ,MAAM;IACN,aAAa;IACd;GACD,MAAM,MAAM;IACV,MAAM,WAAW,MAAM,cAAc;IACrC,MAAM,UAAU,WAAW,SAAS;AACpC,UAAM,cAAc,SAAS;AAC7B,YAAQ,QAAQ,UAAU,QAAQ,gBAAgB;;GAErD,CAAA;;CAEJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ import * as citty from "citty";
2
+
3
+ //#region src/commands/deps.d.ts
4
+ declare const _default: citty.CommandDef<{
5
+ readonly purl: {
6
+ readonly type: "positional";
7
+ readonly description: "Package PURL with version (e.g. pkg:npm/lodash@4.17.21, cargo/serde@1.0)";
8
+ readonly required: true;
9
+ };
10
+ readonly json: {
11
+ readonly type: "boolean";
12
+ readonly description: "Output as JSON";
13
+ readonly default: false;
14
+ };
15
+ readonly "no-cache": {
16
+ readonly type: "boolean";
17
+ readonly description: "Bypass cache, fetch fresh data";
18
+ readonly default: false;
19
+ };
20
+ }>;
21
+ //#endregion
22
+ export { _default as default };
23
+ //# sourceMappingURL=deps.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deps.d.mts","names":[],"sources":["../../src/commands/deps.ts"],"mappings":""}
@@ -0,0 +1,72 @@
1
+ import "../_chunks/registry.mjs";
2
+ import "../_chunks/purl.mjs";
3
+ import "../_chunks/registries.mjs";
4
+ import { resolvePURL, sharedArgs, withErrorHandling } from "./shared.mjs";
5
+ import { defineCommand } from "citty";
6
+ import consola from "consola";
7
+ var deps_default = defineCommand({
8
+ meta: {
9
+ name: "deps",
10
+ description: "List package dependencies"
11
+ },
12
+ args: {
13
+ ...sharedArgs,
14
+ purl: {
15
+ type: "positional",
16
+ description: "Package PURL with version (e.g. pkg:npm/lodash@4.17.21, cargo/serde@1.0)",
17
+ required: true
18
+ }
19
+ },
20
+ async run({ args }) {
21
+ await withErrorHandling(async () => {
22
+ const [reg, name, version] = resolvePURL(args.purl, !args["no-cache"]);
23
+ if (!version) {
24
+ const pkg = await reg.fetchPackage(name);
25
+ if (!pkg.latestVersion) {
26
+ consola.error("PURL must include a version for dependency lookup, and no latest version found.");
27
+ consola.info("Example: pkg:npm/lodash@4.17.21");
28
+ process.exit(1);
29
+ }
30
+ consola.info(`No version specified, using latest: ${pkg.latestVersion}`);
31
+ const deps = await reg.fetchDependencies(name, pkg.latestVersion);
32
+ outputDeps(name, pkg.latestVersion, deps, args.json);
33
+ return;
34
+ }
35
+ outputDeps(name, version, await reg.fetchDependencies(name, version), args.json);
36
+ });
37
+ }
38
+ });
39
+ function outputDeps(name, version, deps, json) {
40
+ if (json) {
41
+ console.log(JSON.stringify(deps, null, 2));
42
+ return;
43
+ }
44
+ consola.log("");
45
+ consola.log(` \x1b[1m${name}@${version}\x1b[0m — ${deps.length} dependenc${deps.length === 1 ? "y" : "ies"}`);
46
+ consola.log("");
47
+ const byScope = /* @__PURE__ */ new Map();
48
+ for (const dep of deps) {
49
+ const scope = dep.scope || "runtime";
50
+ if (!byScope.has(scope)) byScope.set(scope, []);
51
+ byScope.get(scope).push(dep);
52
+ }
53
+ const scopeColors = {
54
+ runtime: "\x1B[32m",
55
+ development: "\x1B[33m",
56
+ build: "\x1B[35m",
57
+ test: "\x1B[36m",
58
+ optional: "\x1B[90m"
59
+ };
60
+ for (const [scope, scopeDeps] of byScope) {
61
+ const color = scopeColors[scope] ?? "\x1B[0m";
62
+ consola.log(` ${color}${scope}\x1b[0m (${scopeDeps.length})`);
63
+ for (const dep of scopeDeps) {
64
+ const opt = dep.optional ? " \x1B[90m(optional)\x1B[0m" : "";
65
+ consola.log(` ${dep.name} \x1b[90m${dep.requirements}\x1b[0m${opt}`);
66
+ }
67
+ consola.log("");
68
+ }
69
+ }
70
+ export { deps_default as default };
71
+
72
+ //# sourceMappingURL=deps.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deps.mjs","names":[],"sources":["../../src/commands/deps.ts"],"sourcesContent":["import { defineCommand } from \"citty\";\nimport consola from \"consola\";\nimport { sharedArgs, resolvePURL, withErrorHandling } from \"./shared.ts\";\n\nexport default defineCommand({\n meta: {\n name: \"deps\",\n description: \"List package dependencies\",\n },\n args: {\n ...sharedArgs,\n purl: {\n type: \"positional\",\n description: \"Package PURL with version (e.g. pkg:npm/lodash@4.17.21, cargo/serde@1.0)\",\n required: true,\n },\n },\n async run({ args }) {\n await withErrorHandling(async () => {\n const [reg, name, version] = resolvePURL(args.purl, !args[\"no-cache\"]);\n\n if (!version) {\n // Try to resolve latest version\n const pkg = await reg.fetchPackage(name);\n if (!pkg.latestVersion) {\n consola.error(\n \"PURL must include a version for dependency lookup, and no latest version found.\",\n );\n consola.info(\"Example: pkg:npm/lodash@4.17.21\");\n process.exit(1);\n }\n consola.info(`No version specified, using latest: ${pkg.latestVersion}`);\n const deps = await reg.fetchDependencies(name, pkg.latestVersion);\n outputDeps(name, pkg.latestVersion, deps, args.json);\n return;\n }\n\n const deps = await reg.fetchDependencies(name, version);\n outputDeps(name, version, deps, args.json);\n });\n },\n});\n\nimport type { Dependency } from \"../core/types.ts\";\n\nfunction outputDeps(name: string, version: string, deps: Dependency[], json: boolean): void {\n if (json) {\n console.log(JSON.stringify(deps, null, 2));\n return;\n }\n\n consola.log(\"\");\n consola.log(\n ` \\x1b[1m${name}@${version}\\x1b[0m — ${deps.length} dependenc${deps.length === 1 ? \"y\" : \"ies\"}`,\n );\n consola.log(\"\");\n\n const byScope = new Map<string, Dependency[]>();\n for (const dep of deps) {\n const scope = dep.scope || \"runtime\";\n if (!byScope.has(scope)) byScope.set(scope, []);\n byScope.get(scope)!.push(dep);\n }\n\n const scopeColors: Record<string, string> = {\n runtime: \"\\x1b[32m\",\n development: \"\\x1b[33m\",\n build: \"\\x1b[35m\",\n test: \"\\x1b[36m\",\n optional: \"\\x1b[90m\",\n };\n\n for (const [scope, scopeDeps] of byScope) {\n const color = scopeColors[scope] ?? \"\\x1b[0m\";\n consola.log(` ${color}${scope}\\x1b[0m (${scopeDeps.length})`);\n for (const dep of scopeDeps) {\n const opt = dep.optional ? \" \\x1b[90m(optional)\\x1b[0m\" : \"\";\n consola.log(` ${dep.name} \\x1b[90m${dep.requirements}\\x1b[0m${opt}`);\n }\n consola.log(\"\");\n }\n}\n"],"mappings":";;;;;;AAIA,IAAA,eAAe,cAAc;CAC3B,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,GAAG;EACH,MAAM;GACJ,MAAM;GACN,aAAa;GACb,UAAU;;EAEb;CACD,MAAM,IAAI,EAAE,QAAQ;AAClB,QAAM,kBAAkB,YAAY;GAClC,MAAM,CAAC,KAAK,MAAM,WAAW,YAAY,KAAK,MAAM,CAAC,KAAK,YAAY;AAEtE,OAAI,CAAC,SAAS;IAEZ,MAAM,MAAM,MAAM,IAAI,aAAa,KAAK;AACxC,QAAI,CAAC,IAAI,eAAe;AACtB,aAAQ,MACN,kFACD;AACD,aAAQ,KAAK,kCAAkC;AAC/C,aAAQ,KAAK,EAAE;;AAEjB,YAAQ,KAAK,uCAAuC,IAAI,gBAAgB;IACxE,MAAM,OAAO,MAAM,IAAI,kBAAkB,MAAM,IAAI,cAAc;AACjE,eAAW,MAAM,IAAI,eAAe,MAAM,KAAK,KAAK;AACpD;;AAIF,cAAW,MAAM,SADJ,MAAM,IAAI,kBAAkB,MAAM,QAAQ,EACvB,KAAK,KAAK;IAC1C;;CAEL,CAAC;AAIF,SAAS,WAAW,MAAc,SAAiB,MAAoB,MAAqB;AAC1F,KAAI,MAAM;AACR,UAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,EAAE,CAAC;AAC1C;;AAGF,SAAQ,IAAI,GAAG;AACf,SAAQ,IACN,YAAY,KAAK,GAAG,QAAQ,YAAY,KAAK,OAAO,YAAY,KAAK,WAAW,IAAI,MAAM,QAC3F;AACD,SAAQ,IAAI,GAAG;CAEf,MAAM,0BAAU,IAAI,KAA2B;AAC/C,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,IAAI,SAAS;AAC3B,MAAI,CAAC,QAAQ,IAAI,MAAM,CAAE,SAAQ,IAAI,OAAO,EAAE,CAAC;AAC/C,UAAQ,IAAI,MAAM,CAAE,KAAK,IAAI;;CAG/B,MAAM,cAAsC;EAC1C,SAAS;EACT,aAAa;EACb,OAAO;EACP,MAAM;EACN,UAAU;EACX;AAED,MAAK,MAAM,CAAC,OAAO,cAAc,SAAS;EACxC,MAAM,QAAQ,YAAY,UAAU;AACpC,UAAQ,IAAI,KAAK,QAAQ,MAAM,WAAW,UAAU,OAAO,GAAG;AAC9D,OAAK,MAAM,OAAO,WAAW;GAC3B,MAAM,MAAM,IAAI,WAAW,+BAA+B;AAC1D,WAAQ,IAAI,OAAO,IAAI,KAAK,WAAW,IAAI,aAAa,SAAS,MAAM;;AAEzE,UAAQ,IAAI,GAAG"}