@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.
- package/LICENSE +21 -0
- package/README.md +303 -0
- package/dist/_chunks/cached-registry.d.mts +35 -0
- package/dist/_chunks/cached-registry.d.mts.map +1 -0
- package/dist/_chunks/errors.d.mts +90 -0
- package/dist/_chunks/errors.d.mts.map +1 -0
- package/dist/_chunks/helpers.mjs +95 -0
- package/dist/_chunks/helpers.mjs.map +1 -0
- package/dist/_chunks/index.d.mts +142 -0
- package/dist/_chunks/index.d.mts.map +1 -0
- package/dist/_chunks/index2.d.mts +50 -0
- package/dist/_chunks/index2.d.mts.map +1 -0
- package/dist/_chunks/index3.d.mts +18 -0
- package/dist/_chunks/index3.d.mts.map +1 -0
- package/dist/_chunks/lockfile.d.mts +50 -0
- package/dist/_chunks/lockfile.d.mts.map +1 -0
- package/dist/_chunks/paths.d.mts +11 -0
- package/dist/_chunks/paths.d.mts.map +1 -0
- package/dist/_chunks/purl.mjs +192 -0
- package/dist/_chunks/purl.mjs.map +1 -0
- package/dist/_chunks/registries.mjs +1197 -0
- package/dist/_chunks/registries.mjs.map +1 -0
- package/dist/_chunks/registry.d.mts +39 -0
- package/dist/_chunks/registry.d.mts.map +1 -0
- package/dist/_chunks/registry.mjs +201 -0
- package/dist/_chunks/registry.mjs.map +1 -0
- package/dist/_chunks/storage.d.mts +22 -0
- package/dist/_chunks/storage.d.mts.map +1 -0
- package/dist/_chunks/types.d.mts +73 -0
- package/dist/_chunks/types.d.mts.map +1 -0
- package/dist/ai.d.mts +35 -0
- package/dist/ai.d.mts.map +1 -0
- package/dist/ai.mjs +50 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/cache/cached-registry.d.mts +4 -0
- package/dist/cache/cached-registry.mjs +92 -0
- package/dist/cache/cached-registry.mjs.map +1 -0
- package/dist/cache/index.d.mts +8 -0
- package/dist/cache/index.mjs +12 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/cache/lockfile.d.mts +2 -0
- package/dist/cache/lockfile.mjs +68 -0
- package/dist/cache/lockfile.mjs.map +1 -0
- package/dist/cache/paths.d.mts +2 -0
- package/dist/cache/paths.mjs +26 -0
- package/dist/cache/paths.mjs.map +1 -0
- package/dist/cache/storage.d.mts +2 -0
- package/dist/cache/storage.mjs +40 -0
- package/dist/cache/storage.mjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +19 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/commands/cache.d.mts +7 -0
- package/dist/commands/cache.d.mts.map +1 -0
- package/dist/commands/cache.mjs +80 -0
- package/dist/commands/cache.mjs.map +1 -0
- package/dist/commands/deps.d.mts +23 -0
- package/dist/commands/deps.d.mts.map +1 -0
- package/dist/commands/deps.mjs +72 -0
- package/dist/commands/deps.mjs.map +1 -0
- package/dist/commands/info.d.mts +26 -0
- package/dist/commands/info.d.mts.map +1 -0
- package/dist/commands/info.mjs +54 -0
- package/dist/commands/info.mjs.map +1 -0
- package/dist/commands/maintainers.d.mts +23 -0
- package/dist/commands/maintainers.d.mts.map +1 -0
- package/dist/commands/maintainers.mjs +46 -0
- package/dist/commands/maintainers.mjs.map +1 -0
- package/dist/commands/shared.d.mts +27 -0
- package/dist/commands/shared.d.mts.map +1 -0
- package/dist/commands/shared.mjs +69 -0
- package/dist/commands/shared.mjs.map +1 -0
- package/dist/commands/versions.d.mts +31 -0
- package/dist/commands/versions.d.mts.map +1 -0
- package/dist/commands/versions.mjs +58 -0
- package/dist/commands/versions.mjs.map +1 -0
- package/dist/core/index.d.mts +4 -0
- package/dist/core/index.mjs +3 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.mjs +10 -0
- package/dist/registries/index.d.mts +4 -0
- package/dist/registries/index.mjs +4 -0
- package/dist/types.d.mts +3 -0
- package/dist/types.mjs +1 -0
- package/package.json +102 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present oritwoen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# @agntn/registries
|
|
2
|
+
|
|
3
|
+
[](https://npmjs.com/package/@agntn/registries)
|
|
4
|
+
[](https://npm.chart.dev/@agntn/registries)
|
|
5
|
+
[](https://github.com/agntn/registries/blob/main/LICENSE)
|
|
6
|
+
[](https://deepwiki.com/agntn/registries)
|
|
7
|
+
|
|
8
|
+
> Query npm, PyPI, crates.io, RubyGems, Packagist, and Arch Linux with one API. PURL-native, typed, cached.
|
|
9
|
+
|
|
10
|
+
## Why?
|
|
11
|
+
|
|
12
|
+
If you need package metadata from multiple registries, you currently have two options: call each registry's REST API yourself (they all work differently), or depend on a third-party aggregation service.
|
|
13
|
+
|
|
14
|
+
There is no embeddable TypeScript library that normalizes across registries. The closest thing is [git-pkgs/registries](https://github.com/git-pkgs/registries) in Go, which covers 25 ecosystems but is Go-only. Aggregation APIs like [ecosyste.ms](https://ecosyste.ms/) and [deps.dev](https://deps.dev/) exist, but they are external services you can't bundle into your own tool.
|
|
15
|
+
|
|
16
|
+
@agntn/registries fills that gap. One `fetchPackage` call, same response shape, regardless of whether the package lives on npm or Packagist. Uses [PURL (ECMA-427)](https://github.com/package-url/purl-spec) for addressing, so `pkg:npm/lodash` and `pkg:cargo/serde` resolve through the same code path. Storage-backed caching with a lockfile keeps things fast on repeated lookups.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- 🔍 **Single API, six registries** - npm, PyPI, crates.io, RubyGems, Packagist, Arch Linux (official + AUR)
|
|
21
|
+
- 📦 **PURL-native** - [ECMA-427](https://github.com/package-url/purl-spec) identifiers as first-class input
|
|
22
|
+
- 🏷️ **Normalized data model** - same `Package`, `Version`, `Dependency`, `Maintainer` types everywhere
|
|
23
|
+
- 💾 **Storage-backed cache + lockfile** - unstorage-native, sha256 integrity checks, configurable TTL
|
|
24
|
+
- ⌨️ **CLI included** - `registries info npm/lodash`, `registries versions cargo/serde`, `registries deps pypi/flask@3.1.1`
|
|
25
|
+
- 🔁 **Retry + backoff** - exponential backoff with jitter, rate limiter interface
|
|
26
|
+
- 🪶 **ESM-only, zero CJS** - built with [obuild](https://github.com/unjs/obuild)
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add @agntn/registries
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For the AI SDK tool (`@agntn/registries/ai` subpath), also install `ai` and `zod`:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm add ai zod
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
### API
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { fetchPackageFromPURL } from "@agntn/registries";
|
|
46
|
+
|
|
47
|
+
const pkg = await fetchPackageFromPURL("pkg:npm/lodash");
|
|
48
|
+
|
|
49
|
+
console.log(pkg.name); // "lodash"
|
|
50
|
+
console.log(pkg.latestVersion); // "4.17.23"
|
|
51
|
+
console.log(pkg.licenses); // "MIT"
|
|
52
|
+
console.log(pkg.repository); // "https://github.com/lodash/lodash"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Works the same for any supported registry:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
await fetchPackageFromPURL("pkg:cargo/serde");
|
|
59
|
+
await fetchPackageFromPURL("pkg:pypi/flask");
|
|
60
|
+
await fetchPackageFromPURL("pkg:gem/rails");
|
|
61
|
+
await fetchPackageFromPURL("pkg:composer/laravel/framework");
|
|
62
|
+
await fetchPackageFromPURL("pkg:alpm/arch/pacman");
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### CLI
|
|
66
|
+
|
|
67
|
+
The `pkg:` prefix is optional in the CLI. `npm/lodash` works just as well:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
registries info npm/lodash
|
|
71
|
+
registries versions cargo/serde
|
|
72
|
+
registries deps pypi/flask@3.1.1
|
|
73
|
+
registries maintainers gem/rails
|
|
74
|
+
registries deps alpm/aur/paru
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Add `--json` for machine-readable output, `--no-cache` to skip the cache.
|
|
78
|
+
|
|
79
|
+
### AI SDK tool
|
|
80
|
+
|
|
81
|
+
`@agntn/registries/ai` exports a ready-made tool for AI SDK apps:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { generateText } from "ai";
|
|
85
|
+
import { packageTool } from "@agntn/registries/ai";
|
|
86
|
+
|
|
87
|
+
const { text } = await generateText({
|
|
88
|
+
model: yourModel,
|
|
89
|
+
tools: { packageRegistry: packageTool },
|
|
90
|
+
prompt: "Show me the latest metadata for pkg:npm/lodash and then list its maintainers.",
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The tool supports these operations through one input schema:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// { operation: 'package', purl: 'pkg:npm/lodash' }
|
|
98
|
+
// { operation: 'versions', purl: 'pkg:cargo/serde' }
|
|
99
|
+
// { operation: 'dependencies', purl: 'pkg:pypi/flask@3.1.1' }
|
|
100
|
+
// { operation: 'maintainers', purl: 'pkg:gem/rails' }
|
|
101
|
+
// { operation: 'bulk-packages', purls: ['pkg:npm/lodash', 'pkg:cargo/serde'], concurrency?: number }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Registries
|
|
105
|
+
|
|
106
|
+
| Ecosystem | PURL type | Registry |
|
|
107
|
+
| ---------- | ------------------ | -------------------------------- |
|
|
108
|
+
| npm | `pkg:npm/...` | registry.npmjs.org |
|
|
109
|
+
| Cargo | `pkg:cargo/...` | crates.io |
|
|
110
|
+
| PyPI | `pkg:pypi/...` | pypi.org |
|
|
111
|
+
| RubyGems | `pkg:gem/...` | rubygems.org |
|
|
112
|
+
| Packagist | `pkg:composer/...` | packagist.org |
|
|
113
|
+
| Arch Linux | `pkg:alpm/...` | archlinux.org, aur.archlinux.org |
|
|
114
|
+
|
|
115
|
+
Scoped packages work as expected: `pkg:npm/%40vue/core` or `npm/@vue/core` in the CLI.
|
|
116
|
+
|
|
117
|
+
Arch Linux packages use a namespace: `pkg:alpm/arch/pacman` (or just `pkg:alpm/pacman`) for official repos, `pkg:alpm/aur/paru` for AUR. Official packages default to `arch` when the namespace is omitted; AUR requires the explicit `aur` namespace.
|
|
118
|
+
|
|
119
|
+
## API reference
|
|
120
|
+
|
|
121
|
+
### PURL helpers
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import {
|
|
125
|
+
fetchPackageFromPURL,
|
|
126
|
+
fetchVersionsFromPURL,
|
|
127
|
+
fetchDependenciesFromPURL,
|
|
128
|
+
fetchMaintainersFromPURL,
|
|
129
|
+
bulkFetchPackages,
|
|
130
|
+
} from "@agntn/registries";
|
|
131
|
+
|
|
132
|
+
// Single lookups
|
|
133
|
+
const pkg = await fetchPackageFromPURL("pkg:npm/lodash");
|
|
134
|
+
const versions = await fetchVersionsFromPURL("pkg:cargo/serde");
|
|
135
|
+
const deps = await fetchDependenciesFromPURL("pkg:pypi/flask@3.1.1");
|
|
136
|
+
const maintainers = await fetchMaintainersFromPURL("pkg:gem/rails");
|
|
137
|
+
|
|
138
|
+
// Bulk - fetches up to 15 packages concurrently
|
|
139
|
+
const packages = await bulkFetchPackages(["pkg:npm/lodash", "pkg:cargo/serde", "pkg:pypi/flask"]);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Direct registry access
|
|
143
|
+
|
|
144
|
+
For more control, instantiate a concrete registry class:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { Client } from "@agntn/registries";
|
|
148
|
+
import { NpmRegistry } from "@agntn/registries";
|
|
149
|
+
|
|
150
|
+
const npm = new NpmRegistry("https://registry.npmjs.org", new Client());
|
|
151
|
+
const pkg = await npm.fetchPackage("lodash");
|
|
152
|
+
const versions = await npm.fetchVersions("lodash");
|
|
153
|
+
const deps = await npm.fetchDependencies("lodash", "4.17.21");
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Use `create("npm")` when you prefer lookup through the registered ecosystem classes.
|
|
157
|
+
|
|
158
|
+
### Cached registry
|
|
159
|
+
|
|
160
|
+
Wrap any registry with caching:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { createCached } from "@agntn/registries";
|
|
164
|
+
|
|
165
|
+
const npm = createCached("npm");
|
|
166
|
+
|
|
167
|
+
// First call hits the network and writes to cache
|
|
168
|
+
const pkg = await npm.fetchPackage("lodash");
|
|
169
|
+
|
|
170
|
+
// Second call reads from cache (if TTL hasn't expired)
|
|
171
|
+
const same = await npm.fetchPackage("lodash");
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
By default, @agntn/registries uses filesystem storage and follows platform cache conventions: `~/.cache/registries` on Linux (XDG), `~/Library/Caches/registries` on macOS, `%LOCALAPPDATA%\registries\cache` on Windows. Override with `REGISTRIES_CACHE_DIR` env var.
|
|
175
|
+
|
|
176
|
+
For edge/serverless runtimes, configure a custom unstorage driver (example: Cloudflare KV binding):
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { configureStorage, createCached } from "@agntn/registries";
|
|
180
|
+
import { createStorage } from "unstorage";
|
|
181
|
+
import cloudflareKVBindingDriver from "unstorage/drivers/cloudflare-kv-binding";
|
|
182
|
+
|
|
183
|
+
configureStorage(
|
|
184
|
+
createStorage({
|
|
185
|
+
driver: cloudflareKVBindingDriver({ binding: "REGISTRIES_CACHE" }),
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const npm = createCached("npm");
|
|
190
|
+
const pkg = await npm.fetchPackage("lodash");
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### PURL parsing
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { parsePURL, buildPURL, fullName } from "@agntn/registries";
|
|
197
|
+
|
|
198
|
+
const parsed = parsePURL("pkg:npm/%40vue/core@3.5.0");
|
|
199
|
+
// { type: 'npm', namespace: '@vue', name: 'core', version: '3.5.0', qualifiers: {}, subpath: '' }
|
|
200
|
+
|
|
201
|
+
fullName(parsed); // "@vue/core"
|
|
202
|
+
|
|
203
|
+
buildPURL({ type: "cargo", name: "serde", version: "1.0.0" });
|
|
204
|
+
// "pkg:cargo/serde@1.0.0"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Types
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import type {
|
|
211
|
+
Package,
|
|
212
|
+
Version,
|
|
213
|
+
Dependency,
|
|
214
|
+
Maintainer,
|
|
215
|
+
Registry,
|
|
216
|
+
ParsedPURL,
|
|
217
|
+
} from "@agntn/registries";
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## CLI
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
registries <command> [options]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
| Command | Description |
|
|
227
|
+
| ------------------------------- | ------------------------------------------------------ |
|
|
228
|
+
| `registries info <purl>` | Package metadata (name, license, repo, latest version) |
|
|
229
|
+
| `registries versions <purl>` | List all published versions |
|
|
230
|
+
| `registries deps <purl>` | Dependencies for a specific version |
|
|
231
|
+
| `registries maintainers <purl>` | Package maintainers / authors |
|
|
232
|
+
| `registries cache status` | Show cache stats (entries, freshness) |
|
|
233
|
+
| `registries cache path` | Print cache directory path |
|
|
234
|
+
| `registries cache clear` | Remove all cached data |
|
|
235
|
+
| `registries cache prune` | Remove stale entries |
|
|
236
|
+
|
|
237
|
+
### Options
|
|
238
|
+
|
|
239
|
+
| Flag | Description |
|
|
240
|
+
| ------------ | ---------------------------------------- |
|
|
241
|
+
| `--json` | Output as JSON |
|
|
242
|
+
| `--no-cache` | Bypass cache, always fetch from registry |
|
|
243
|
+
|
|
244
|
+
## Caching
|
|
245
|
+
|
|
246
|
+
@agntn/registries stores fetched data and freshness metadata in unstorage. Default TTLs:
|
|
247
|
+
|
|
248
|
+
| Data type | TTL |
|
|
249
|
+
| ---------------- | ---------- |
|
|
250
|
+
| Package metadata | 1 hour |
|
|
251
|
+
| Version list | 30 minutes |
|
|
252
|
+
| Dependencies | 24 hours |
|
|
253
|
+
| Maintainers | 24 hours |
|
|
254
|
+
|
|
255
|
+
Each cached entry has a sha256 integrity hash. If the stored data doesn't match the hash, @agntn/registries refetches automatically.
|
|
256
|
+
|
|
257
|
+
## Data model
|
|
258
|
+
|
|
259
|
+
Every registry returns the same normalized types:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
interface Package {
|
|
263
|
+
name: string;
|
|
264
|
+
description: string;
|
|
265
|
+
homepage: string;
|
|
266
|
+
documentation: string; // docs URL (docs.rs, readthedocs, rubydoc, etc.)
|
|
267
|
+
repository: string;
|
|
268
|
+
licenses: string; // SPDX-normalized
|
|
269
|
+
keywords: string[];
|
|
270
|
+
namespace: string; // e.g. "@vue" for npm scoped packages
|
|
271
|
+
latestVersion: string;
|
|
272
|
+
metadata: Record<string, unknown>;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
interface Version {
|
|
276
|
+
number: string;
|
|
277
|
+
publishedAt: Date | null;
|
|
278
|
+
licenses: string;
|
|
279
|
+
integrity: string;
|
|
280
|
+
status: "" | "yanked" | "deprecated" | "retracted";
|
|
281
|
+
metadata: Record<string, unknown>;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
interface Dependency {
|
|
285
|
+
name: string;
|
|
286
|
+
requirements: string; // version constraint
|
|
287
|
+
scope: "runtime" | "development" | "test" | "build" | "optional";
|
|
288
|
+
optional: boolean;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
interface Maintainer {
|
|
292
|
+
uuid: string;
|
|
293
|
+
login: string;
|
|
294
|
+
name: string;
|
|
295
|
+
email: string;
|
|
296
|
+
url: string;
|
|
297
|
+
role: string;
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { c as URLBuilder, i as Package, l as Version, n as Dependency, r as Maintainer } from "./types.mjs";
|
|
2
|
+
import { t as Registry } from "./registry.mjs";
|
|
3
|
+
import { Storage } from "unstorage";
|
|
4
|
+
|
|
5
|
+
//#region src/cache/cached-registry.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* CachedRegistry wraps any Registry and adds caching backed by unstorage
|
|
8
|
+
* with a lockfile for freshness tracking.
|
|
9
|
+
*
|
|
10
|
+
* Pass a custom `storage` to use a non-default driver (e.g. Cloudflare KV).
|
|
11
|
+
* If omitted, uses the globally configured storage (see `configureStorage()`).
|
|
12
|
+
*/
|
|
13
|
+
declare class CachedRegistry extends Registry {
|
|
14
|
+
readonly inner: Registry;
|
|
15
|
+
readonly storage: Storage;
|
|
16
|
+
private readonly lockfileStorage;
|
|
17
|
+
private readonly inflight;
|
|
18
|
+
constructor(inner: Registry, storage?: Storage);
|
|
19
|
+
ecosystem(): string;
|
|
20
|
+
urls(): URLBuilder;
|
|
21
|
+
fetchPackage(name: string, signal?: AbortSignal): Promise<Package>;
|
|
22
|
+
fetchVersions(name: string, signal?: AbortSignal): Promise<Version[]>;
|
|
23
|
+
fetchDependencies(name: string, version: string, signal?: AbortSignal): Promise<Dependency[]>;
|
|
24
|
+
fetchMaintainers(name: string, signal?: AbortSignal): Promise<Maintainer[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Generic cache-through method:
|
|
27
|
+
* 1. Check lockfile for fresh entry
|
|
28
|
+
* 2. If fresh -> read from storage
|
|
29
|
+
* 3. If stale/missing -> fetch, store, update lockfile
|
|
30
|
+
*/
|
|
31
|
+
private cached;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { CachedRegistry as t };
|
|
35
|
+
//# sourceMappingURL=cached-registry.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cached-registry.d.mts","names":[],"sources":["../../src/cache/cached-registry.ts"],"mappings":";;;;;;;AAqBA;;;;;cAAa,cAAA,SAAuB,QAAA;EAAA,SACzB,KAAA,EAAO,QAAA;EAAA,SACP,OAAA,EAAS,OAAA;EAAA,iBACD,eAAA;EAAA,iBACA,QAAA;cAEL,KAAA,EAAO,QAAA,EAAU,OAAA,GAAU,OAAA;EAOvC,SAAA,CAAA;EAIA,IAAA,CAAA,GAAQ,UAAA;EAIF,YAAA,CAAa,IAAA,UAAc,MAAA,GAAS,WAAA,GAAc,OAAA,CAAQ,OAAA;EAW1D,aAAA,CAAc,IAAA,UAAc,MAAA,GAAS,WAAA,GAAc,OAAA,CAAQ,OAAA;EAgB3D,iBAAA,CACJ,IAAA,UACA,OAAA,UACA,MAAA,GAAS,WAAA,GACR,OAAA,CAAQ,UAAA;EAUL,gBAAA,CAAiB,IAAA,UAAc,MAAA,GAAS,WAAA,GAAc,OAAA,CAAQ,UAAA;EAAtB;;;;;;EAAA,QAgBhC,MAAA;AAAA"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { a as ParsedPURL } from "./types.mjs";
|
|
2
|
+
import { o as Client, t as Registry } from "./registry.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/core/purl.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Parse a PURL string into its components.
|
|
7
|
+
*
|
|
8
|
+
* Format: `pkg:<type>/<namespace>/<name>@<version>?<qualifiers>#<subpath>`
|
|
9
|
+
*
|
|
10
|
+
* @see https://github.com/package-url/purl-spec (ECMA-427)
|
|
11
|
+
*/
|
|
12
|
+
declare function parsePURL(purlStr: string): ParsedPURL;
|
|
13
|
+
/** Build the full name from namespace + name (e.g., "@scope/pkg" for npm). */
|
|
14
|
+
declare function fullName(parsed: ParsedPURL): string;
|
|
15
|
+
/** Create a registry instance from a PURL, returning [registry, name, version]. */
|
|
16
|
+
declare function createFromPURL(purlStr: string, client?: Client): [Registry, string, string];
|
|
17
|
+
/** Build a PURL string from components. Inverse of `parsePURL`. */
|
|
18
|
+
declare function buildPURL(parts: {
|
|
19
|
+
type: string;
|
|
20
|
+
name: string;
|
|
21
|
+
version?: string;
|
|
22
|
+
namespace?: string;
|
|
23
|
+
qualifiers?: Record<string, string>;
|
|
24
|
+
subpath?: string;
|
|
25
|
+
}): string;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/core/license.d.ts
|
|
28
|
+
/**
|
|
29
|
+
* Normalize a license string to SPDX expression.
|
|
30
|
+
*
|
|
31
|
+
* Handles common variations: "MIT License" → "MIT", "Apache 2.0" → "Apache-2.0".
|
|
32
|
+
* Passes through already-valid SPDX identifiers unchanged.
|
|
33
|
+
*/
|
|
34
|
+
declare function normalizeLicense(raw: string | null | undefined): string;
|
|
35
|
+
/**
|
|
36
|
+
* Combine multiple license strings into a single SPDX expression.
|
|
37
|
+
*
|
|
38
|
+
* Default operator is OR (disjunctive, user picks one).
|
|
39
|
+
* Pass "AND" for ecosystems where multiple licenses mean all apply (e.g. Arch Linux).
|
|
40
|
+
*/
|
|
41
|
+
declare function combineLicenses(licenses: (string | null | undefined)[], operator?: "OR" | "AND"): string;
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/core/repository.d.ts
|
|
44
|
+
/**
|
|
45
|
+
* Normalize a repository URL to a clean HTTPS git URL.
|
|
46
|
+
*
|
|
47
|
+
* Handles various formats:
|
|
48
|
+
* - "git+https://github.com/foo/bar.git" → "https://github.com/foo/bar"
|
|
49
|
+
* - "git://github.com/foo/bar" → "https://github.com/foo/bar"
|
|
50
|
+
* - "ssh://git@github.com/foo/bar.git" → "https://github.com/foo/bar"
|
|
51
|
+
* - "github:foo/bar" → "https://github.com/foo/bar"
|
|
52
|
+
* - { url: "..." } → extracted and normalized
|
|
53
|
+
*/
|
|
54
|
+
declare function normalizeRepositoryURL(raw: unknown): string;
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/core/errors.d.ts
|
|
57
|
+
/** Custom error types for registry operations. */
|
|
58
|
+
declare class PkioError extends Error {
|
|
59
|
+
constructor(message: string, options?: ErrorOptions);
|
|
60
|
+
}
|
|
61
|
+
declare class HTTPError extends PkioError {
|
|
62
|
+
readonly statusCode: number;
|
|
63
|
+
readonly url: string;
|
|
64
|
+
readonly body: string;
|
|
65
|
+
constructor(statusCode: number, url: string, body: string);
|
|
66
|
+
isNotFound(): boolean;
|
|
67
|
+
isRateLimit(): boolean;
|
|
68
|
+
isServerError(): boolean;
|
|
69
|
+
}
|
|
70
|
+
declare class NotFoundError extends PkioError {
|
|
71
|
+
readonly ecosystem: string;
|
|
72
|
+
readonly packageName: string;
|
|
73
|
+
readonly version: string;
|
|
74
|
+
constructor(ecosystem: string, packageName: string, version?: string);
|
|
75
|
+
}
|
|
76
|
+
declare class RateLimitError extends PkioError {
|
|
77
|
+
readonly retryAfter: number;
|
|
78
|
+
constructor(retryAfter: number);
|
|
79
|
+
}
|
|
80
|
+
declare class UnknownEcosystemError extends PkioError {
|
|
81
|
+
readonly ecosystem: string;
|
|
82
|
+
constructor(ecosystem: string);
|
|
83
|
+
}
|
|
84
|
+
declare class InvalidPURLError extends PkioError {
|
|
85
|
+
readonly purl: string;
|
|
86
|
+
constructor(purl: string, reason: string);
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { RateLimitError as a, combineLicenses as c, createFromPURL as d, fullName as f, PkioError as i, normalizeLicense as l, InvalidPURLError as n, UnknownEcosystemError as o, parsePURL as p, NotFoundError as r, normalizeRepositoryURL as s, HTTPError as t, buildPURL as u };
|
|
90
|
+
//# sourceMappingURL=errors.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/core/purl.ts","../../src/core/license.ts","../../src/core/repository.ts","../../src/core/errors.ts"],"mappings":";;;;;AAqBA;;;;;AA4FA;iBA5FgB,SAAA,CAAU,OAAA,WAAkB,UAAA;;iBA4F5B,QAAA,CAAS,MAAA,EAAQ,UAAA;;iBAQjB,cAAA,CAAe,OAAA,UAAiB,MAAA,GAAS,MAAA,IAAU,QAAA;;iBAQnD,SAAA,CAAU,KAAA;EACxB,IAAA;EACA,IAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA,GAAa,MAAA;EACb,OAAA;AAAA;;;;;;;AAlHF;;iBCfgB,gBAAA,CAAiB,GAAA;;;AD2GjC;;;;iBClDgB,eAAA,CACd,QAAA,iCACA,QAAA;;;;;;;AD5CF;;;;;AA4FA;iBEvGgB,sBAAA,CAAuB,GAAA;;;;cCR1B,SAAA,SAAkB,KAAA;cACjB,OAAA,UAAiB,OAAA,GAAU,YAAA;AAAA;AAAA,cAM5B,SAAA,SAAkB,SAAA;EAAA,SACpB,UAAA;EAAA,SACA,GAAA;EAAA,SACA,IAAA;cAEG,UAAA,UAAoB,GAAA,UAAa,IAAA;EAQ7C,UAAA,CAAA;EAIA,WAAA,CAAA;EAIA,aAAA,CAAA;AAAA;AAAA,cAKW,aAAA,SAAsB,SAAA;EAAA,SACxB,SAAA;EAAA,SACA,WAAA;EAAA,SACA,OAAA;cAEG,SAAA,UAAmB,WAAA,UAAqB,OAAA;AAAA;AAAA,cAUzC,cAAA,SAAuB,SAAA;EAAA,SACzB,UAAA;cAEG,UAAA;AAAA;AAAA,cAOD,qBAAA,SAA8B,SAAA;EAAA,SAChC,SAAA;cAEG,SAAA;AAAA;AAAA,cAOD,gBAAA,SAAyB,SAAA;EAAA,SAC3B,IAAA;cAEG,IAAA,UAAc,MAAA;AAAA"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { l as InvalidPURLError } from "./registry.mjs";
|
|
2
|
+
import { n as createFromPURL } from "./purl.mjs";
|
|
3
|
+
/** Fetch normalized package metadata from a PURL. */
|
|
4
|
+
async function fetchPackageFromPURL(purl, signal, client) {
|
|
5
|
+
const [reg, name] = createFromPURL(purl, client);
|
|
6
|
+
return reg.fetchPackage(name, signal);
|
|
7
|
+
}
|
|
8
|
+
/** Fetch all versions from a PURL. */
|
|
9
|
+
async function fetchVersionsFromPURL(purl, signal, client) {
|
|
10
|
+
const [reg, name] = createFromPURL(purl, client);
|
|
11
|
+
return reg.fetchVersions(name, signal);
|
|
12
|
+
}
|
|
13
|
+
/** Fetch dependencies for a specific version from a PURL. */
|
|
14
|
+
async function fetchDependenciesFromPURL(purl, signal, client) {
|
|
15
|
+
const [reg, name, version] = createFromPURL(purl, client);
|
|
16
|
+
if (!version) throw new InvalidPURLError(purl, "must include a version for dependency lookup");
|
|
17
|
+
return reg.fetchDependencies(name, version, signal);
|
|
18
|
+
}
|
|
19
|
+
/** Fetch maintainers from a PURL. */
|
|
20
|
+
async function fetchMaintainersFromPURL(purl, signal, client) {
|
|
21
|
+
const [reg, name] = createFromPURL(purl, client);
|
|
22
|
+
return reg.fetchMaintainers(name, signal);
|
|
23
|
+
}
|
|
24
|
+
const DEFAULT_CONCURRENCY = 15;
|
|
25
|
+
/** Bulk fetch packages from multiple PURLs, with concurrency limit. */
|
|
26
|
+
async function bulkFetchPackages(purls, options) {
|
|
27
|
+
const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY;
|
|
28
|
+
const results = /* @__PURE__ */ new Map();
|
|
29
|
+
const queue = [...purls];
|
|
30
|
+
const signal = options?.signal;
|
|
31
|
+
const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
|
|
32
|
+
while (queue.length > 0) {
|
|
33
|
+
if (signal?.aborted) break;
|
|
34
|
+
const purl = queue.shift();
|
|
35
|
+
try {
|
|
36
|
+
const pkg = await fetchPackageFromPURL(purl, signal, options?.client);
|
|
37
|
+
results.set(purl, pkg);
|
|
38
|
+
} catch {
|
|
39
|
+
if (signal?.aborted) break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
await Promise.all(workers);
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Select the best matching version from a list.
|
|
48
|
+
*
|
|
49
|
+
* Resolution order:
|
|
50
|
+
* 1. Exact match for `requested` (non-yanked/deprecated/retracted)
|
|
51
|
+
* 2. Exact match for `latest` (non-yanked/deprecated/retracted)
|
|
52
|
+
* 3. Newest available version with no negative status (by publishedAt)
|
|
53
|
+
*
|
|
54
|
+
* Returns `null` when no usable version exists.
|
|
55
|
+
*/
|
|
56
|
+
function selectVersion(versions, options) {
|
|
57
|
+
const { requested, latest } = options ?? {};
|
|
58
|
+
if (requested) {
|
|
59
|
+
const exact = versions.find((v) => v.number === requested && v.status === "");
|
|
60
|
+
if (exact) return exact;
|
|
61
|
+
}
|
|
62
|
+
if (latest) {
|
|
63
|
+
const latestV = versions.find((v) => v.number === latest && v.status === "");
|
|
64
|
+
if (latestV) return latestV;
|
|
65
|
+
}
|
|
66
|
+
const usable = versions.filter((v) => v.status === "");
|
|
67
|
+
if (usable.length === 0) return null;
|
|
68
|
+
usable.sort((a, b) => {
|
|
69
|
+
const at = a.publishedAt?.getTime() ?? 0;
|
|
70
|
+
return (b.publishedAt?.getTime() ?? 0) - at;
|
|
71
|
+
});
|
|
72
|
+
return usable[0] ?? null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the best documentation URL for a package.
|
|
76
|
+
*
|
|
77
|
+
* Fallback chain:
|
|
78
|
+
* 1. `package.documentation` (explicit docs URL from registry)
|
|
79
|
+
* 2. `package.homepage` (project homepage)
|
|
80
|
+
* 3. `urls.documentation()` (ecosystem default, e.g. docs.rs, rubydoc.info)
|
|
81
|
+
*/
|
|
82
|
+
function resolveDocsUrl(pkg, urls, version) {
|
|
83
|
+
return pkg.documentation || pkg.homepage || urls.documentation(pkg.name, version);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Resolve the best README URL for a package.
|
|
87
|
+
*
|
|
88
|
+
* Returns the ecosystem-specific URL where the package README can be fetched.
|
|
89
|
+
*/
|
|
90
|
+
function resolveReadmeUrl(pkg, urls, version) {
|
|
91
|
+
return urls.readme(pkg.name, version);
|
|
92
|
+
}
|
|
93
|
+
export { fetchVersionsFromPURL as a, selectVersion as c, fetchPackageFromPURL as i, fetchDependenciesFromPURL as n, resolveDocsUrl as o, fetchMaintainersFromPURL as r, resolveReadmeUrl as s, bulkFetchPackages as t };
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=helpers.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/helpers.ts"],"sourcesContent":["import type { Package, Version, Dependency, Maintainer, URLBuilder } from \"./core/types.ts\";\nimport type { Client } from \"./core/client.ts\";\nimport { createFromPURL } from \"./core/purl.ts\";\nimport { InvalidPURLError } from \"./core/errors.ts\";\n\n/** Fetch normalized package metadata from a PURL. */\nexport async function fetchPackageFromPURL(\n purl: string,\n signal?: AbortSignal,\n client?: Client,\n): Promise<Package> {\n const [reg, name] = createFromPURL(purl, client);\n return reg.fetchPackage(name, signal);\n}\n\n/** Fetch all versions from a PURL. */\nexport async function fetchVersionsFromPURL(\n purl: string,\n signal?: AbortSignal,\n client?: Client,\n): Promise<Version[]> {\n const [reg, name] = createFromPURL(purl, client);\n return reg.fetchVersions(name, signal);\n}\n\n/** Fetch dependencies for a specific version from a PURL. */\nexport async function fetchDependenciesFromPURL(\n purl: string,\n signal?: AbortSignal,\n client?: Client,\n): Promise<Dependency[]> {\n const [reg, name, version] = createFromPURL(purl, client);\n if (!version) {\n throw new InvalidPURLError(purl, \"must include a version for dependency lookup\");\n }\n return reg.fetchDependencies(name, version, signal);\n}\n\n/** Fetch maintainers from a PURL. */\nexport async function fetchMaintainersFromPURL(\n purl: string,\n signal?: AbortSignal,\n client?: Client,\n): Promise<Maintainer[]> {\n const [reg, name] = createFromPURL(purl, client);\n return reg.fetchMaintainers(name, signal);\n}\n\nconst DEFAULT_CONCURRENCY = 15;\n\n/** Bulk fetch packages from multiple PURLs, with concurrency limit. */\nexport async function bulkFetchPackages(\n purls: string[],\n options?: { concurrency?: number; signal?: AbortSignal; client?: Client },\n): Promise<Map<string, Package>> {\n const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY;\n const results = new Map<string, Package>();\n const queue = [...purls];\n\n const signal = options?.signal;\n\n const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {\n while (queue.length > 0) {\n if (signal?.aborted) break;\n\n const purl = queue.shift()!;\n try {\n const pkg = await fetchPackageFromPURL(purl, signal, options?.client);\n results.set(purl, pkg);\n } catch {\n if (signal?.aborted) break;\n // Silently skip failed lookups — absent from results map\n }\n }\n });\n\n await Promise.all(workers);\n return results;\n}\n\n/**\n * Select the best matching version from a list.\n *\n * Resolution order:\n * 1. Exact match for `requested` (non-yanked/deprecated/retracted)\n * 2. Exact match for `latest` (non-yanked/deprecated/retracted)\n * 3. Newest available version with no negative status (by publishedAt)\n *\n * Returns `null` when no usable version exists.\n */\nexport function selectVersion(\n versions: Version[],\n options?: {\n requested?: string;\n latest?: string;\n },\n): Version | null {\n const { requested, latest } = options ?? {};\n\n if (requested) {\n const exact = versions.find((v) => v.number === requested && v.status === \"\");\n if (exact) return exact;\n }\n\n if (latest) {\n const latestV = versions.find((v) => v.number === latest && v.status === \"\");\n if (latestV) return latestV;\n }\n\n const usable = versions.filter((v) => v.status === \"\");\n if (usable.length === 0) return null;\n\n usable.sort((a, b) => {\n const at = a.publishedAt?.getTime() ?? 0;\n const bt = b.publishedAt?.getTime() ?? 0;\n return bt - at;\n });\n\n return usable[0] ?? null;\n}\n\n/**\n * Resolve the best documentation URL for a package.\n *\n * Fallback chain:\n * 1. `package.documentation` (explicit docs URL from registry)\n * 2. `package.homepage` (project homepage)\n * 3. `urls.documentation()` (ecosystem default, e.g. docs.rs, rubydoc.info)\n */\nexport function resolveDocsUrl(pkg: Package, urls: URLBuilder, version?: string): string {\n return pkg.documentation || pkg.homepage || urls.documentation(pkg.name, version);\n}\n\n/**\n * Resolve the best README URL for a package.\n *\n * Returns the ecosystem-specific URL where the package README can be fetched.\n */\nexport function resolveReadmeUrl(pkg: Package, urls: URLBuilder, version?: string): string {\n return urls.readme(pkg.name, version);\n}\n"],"mappings":";;;AAMA,eAAsB,qBACpB,MACA,QACA,QACkB;CAClB,MAAM,CAAC,KAAK,QAAQ,eAAe,MAAM,OAAO;AAChD,QAAO,IAAI,aAAa,MAAM,OAAO;;;AAIvC,eAAsB,sBACpB,MACA,QACA,QACoB;CACpB,MAAM,CAAC,KAAK,QAAQ,eAAe,MAAM,OAAO;AAChD,QAAO,IAAI,cAAc,MAAM,OAAO;;;AAIxC,eAAsB,0BACpB,MACA,QACA,QACuB;CACvB,MAAM,CAAC,KAAK,MAAM,WAAW,eAAe,MAAM,OAAO;AACzD,KAAI,CAAC,QACH,OAAM,IAAI,iBAAiB,MAAM,+CAA+C;AAElF,QAAO,IAAI,kBAAkB,MAAM,SAAS,OAAO;;;AAIrD,eAAsB,yBACpB,MACA,QACA,QACuB;CACvB,MAAM,CAAC,KAAK,QAAQ,eAAe,MAAM,OAAO;AAChD,QAAO,IAAI,iBAAiB,MAAM,OAAO;;AAG3C,MAAM,sBAAsB;;AAG5B,eAAsB,kBACpB,OACA,SAC+B;CAC/B,MAAM,cAAc,SAAS,eAAe;CAC5C,MAAM,0BAAU,IAAI,KAAsB;CAC1C,MAAM,QAAQ,CAAC,GAAG,MAAM;CAExB,MAAM,SAAS,SAAS;CAExB,MAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,aAAa,MAAM,OAAO,EAAE,EAAE,YAAY;AACtF,SAAO,MAAM,SAAS,GAAG;AACvB,OAAI,QAAQ,QAAS;GAErB,MAAM,OAAO,MAAM,OAAO;AAC1B,OAAI;IACF,MAAM,MAAM,MAAM,qBAAqB,MAAM,QAAQ,SAAS,OAAO;AACrE,YAAQ,IAAI,MAAM,IAAI;WAChB;AACN,QAAI,QAAQ,QAAS;;;GAIzB;AAEF,OAAM,QAAQ,IAAI,QAAQ;AAC1B,QAAO;;;;;;;;;;;;AAaT,SAAgB,cACd,UACA,SAIgB;CAChB,MAAM,EAAE,WAAW,WAAW,WAAW,EAAE;AAE3C,KAAI,WAAW;EACb,MAAM,QAAQ,SAAS,MAAM,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,GAAG;AAC7E,MAAI,MAAO,QAAO;;AAGpB,KAAI,QAAQ;EACV,MAAM,UAAU,SAAS,MAAM,MAAM,EAAE,WAAW,UAAU,EAAE,WAAW,GAAG;AAC5E,MAAI,QAAS,QAAO;;CAGtB,MAAM,SAAS,SAAS,QAAQ,MAAM,EAAE,WAAW,GAAG;AACtD,KAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAO,MAAM,GAAG,MAAM;EACpB,MAAM,KAAK,EAAE,aAAa,SAAS,IAAI;AAEvC,UADW,EAAE,aAAa,SAAS,IAAI,KAC3B;GACZ;AAEF,QAAO,OAAO,MAAM;;;;;;;;;;AAWtB,SAAgB,eAAe,KAAc,MAAkB,SAA0B;AACvF,QAAO,IAAI,iBAAiB,IAAI,YAAY,KAAK,cAAc,IAAI,MAAM,QAAQ;;;;;;;AAQnF,SAAgB,iBAAiB,KAAc,MAAkB,SAA0B;AACzF,QAAO,KAAK,OAAO,IAAI,MAAM,QAAQ"}
|