@repodeckhz/core 0.6.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.
@@ -0,0 +1,616 @@
1
+ /**
2
+ * @repodeckhz/core — errors.ts
3
+ *
4
+ * Single typed error used across the whole package so consumers can branch on
5
+ * `error.code` instead of parsing message strings.
6
+ *
7
+ * The class is exported under two names — `RepoDeckError` is the canonical
8
+ * name going forward; `repodeckError` is kept as an alias for the legacy
9
+ * `repodeck` rebrand that still appears in some docs and modules. Both
10
+ * resolve to the same class, so `instanceof` checks work either way.
11
+ */
12
+ type RepoDeckErrorCode = 'NOT_FOUND' | 'RATE_LIMITED' | 'UNAUTHORIZED' | 'NETWORK_ERROR' | 'INVALID_CONFIG';
13
+ /** Legacy alias used by modules still on the `repodeck` branding. */
14
+ type repodeckErrorCode = RepoDeckErrorCode;
15
+ declare class RepoDeckError extends Error {
16
+ code: RepoDeckErrorCode;
17
+ status?: number;
18
+ field?: string;
19
+ constructor(code: RepoDeckErrorCode, message: string, opts?: {
20
+ status?: number;
21
+ field?: string;
22
+ cause?: unknown;
23
+ });
24
+ toJSON(): {
25
+ name: string;
26
+ code: RepoDeckErrorCode;
27
+ message: string;
28
+ status: number | undefined;
29
+ field: string | undefined;
30
+ };
31
+ }
32
+ declare const repodeckError: typeof RepoDeckError;
33
+
34
+ /**
35
+ * @repodeckhz/core — types.ts
36
+ *
37
+ * Shared data shapes used by every module of the core and by the API contract.
38
+ */
39
+
40
+ interface FetchOptions {
41
+ /** GitHub personal access token (optional — bumps rate limit). */
42
+ token?: string;
43
+ /** Branch / tag / commit to read from. */
44
+ branch?: string;
45
+ /** Per-request timeout in ms. */
46
+ timeoutMs?: number;
47
+ /** Number of retries for transient network errors (never retries 404/401). */
48
+ retries?: number;
49
+ }
50
+ interface DirectoryEntry {
51
+ name: string;
52
+ path: string;
53
+ download_url: string | null;
54
+ size: number;
55
+ }
56
+ interface RateLimitInfo {
57
+ remaining: number;
58
+ limit: number;
59
+ resetAt: number;
60
+ }
61
+ interface ResumeData {
62
+ text: string;
63
+ truncated: boolean;
64
+ /** Which locale file was used (plan §16.19). Undefined = default RESUME.txt. */
65
+ locale?: string;
66
+ }
67
+ interface ReadmeData {
68
+ html: string;
69
+ raw: string;
70
+ /** Optional frontmatter parsed from the top of README.md (plan §16.18).
71
+ * Supports: title, accent (hex color), order (number), description, tags. */
72
+ frontmatter?: Record<string, string | number>;
73
+ }
74
+ interface Screenshot {
75
+ url: string;
76
+ filename: string;
77
+ alt: string;
78
+ }
79
+ interface CardMeta {
80
+ owner: string;
81
+ repo: string;
82
+ url: string;
83
+ branch: string;
84
+ configPath: string;
85
+ fetchedAt: number;
86
+ }
87
+ /** A field either resolved to data or carries a per-field RepoDeckError. */
88
+ type FieldResult<T> = T | {
89
+ error: RepoDeckError;
90
+ };
91
+ interface CardData {
92
+ meta: CardMeta;
93
+ resume: FieldResult<ResumeData> | null;
94
+ readme: FieldResult<ReadmeData> | null;
95
+ screenshots: FieldResult<Screenshot[]> | null;
96
+ /** Optional repo stats (stars, forks, watchers) — only fetched when
97
+ * `include.stats` is true (typically via the `show-stats` attribute). */
98
+ stats?: FieldResult<RepoStats> | null;
99
+ }
100
+ interface RepoStats {
101
+ stars: number;
102
+ forks: number;
103
+ watchers: number;
104
+ openIssues: number;
105
+ defaultBranch: string;
106
+ pushedAt: number;
107
+ /** Repo name (e.g. "my-project"). */
108
+ repoName: string;
109
+ /** GitHub description field. */
110
+ repoDescription: string | null;
111
+ /** Repository topics (tags). */
112
+ topics: string[];
113
+ /** Primary language (e.g. "TypeScript"). */
114
+ language: string | null;
115
+ /** Homepage URL set in the repo settings. */
116
+ homepage: string | null;
117
+ /** Owner login. */
118
+ ownerLogin: string;
119
+ /** Owner avatar URL. */
120
+ ownerAvatarUrl: string;
121
+ }
122
+ interface BuildOptions {
123
+ include?: {
124
+ readme?: boolean;
125
+ resume?: boolean;
126
+ screenshots?: boolean;
127
+ stats?: boolean;
128
+ };
129
+ branch?: string;
130
+ configPath?: string;
131
+ token?: string;
132
+ /** Per-request timeout in ms (default 12000). */
133
+ timeoutMs?: number;
134
+ /** Retries on 5xx/network errors (default 2). */
135
+ retries?: number;
136
+ /** Resume char limit (default 280). */
137
+ resumeMaxChars?: number;
138
+ /** Locale for localized RESUME detection (plan §16.19). Tries
139
+ * `RESUME.<locale>.txt` before falling back to `RESUME.txt`. */
140
+ locale?: string;
141
+ /** Log per-field fetches, cache hits and errors (or REPODECK_DEBUG=1). */
142
+ debug?: boolean;
143
+ }
144
+ interface PresetDefinition {
145
+ /** Which fields to fetch from GitHub. */
146
+ include: {
147
+ readme: boolean;
148
+ resume: boolean;
149
+ screenshots: boolean;
150
+ };
151
+ /** Whether the card shows a README preview line. */
152
+ readmePreview: boolean;
153
+ /** Max chars of the README preview on the card. */
154
+ readmePreviewChars?: number;
155
+ /** Show repo title (always true, kept for completeness). */
156
+ showTitle: boolean;
157
+ }
158
+ interface ResolvedConfig extends PresetDefinition {
159
+ name: string;
160
+ }
161
+ type RadiusPresetName = 'sharp' | 'soft' | 'round';
162
+ interface RadiusTokens {
163
+ card: string;
164
+ button: string;
165
+ modal: string;
166
+ }
167
+ type ScreenshotLayoutMode = 'single' | 'bento';
168
+ interface LayoutItem {
169
+ screenshot: Screenshot;
170
+ /** grid-column span */
171
+ colSpan: number;
172
+ /** grid-row span */
173
+ rowSpan: number;
174
+ /** whether this is the "hero" tile */
175
+ hero: boolean;
176
+ /** "+N" overlay count, only set on the last visible tile when overflowing */
177
+ overflow?: number;
178
+ }
179
+ interface ScreenshotLayout {
180
+ mode: ScreenshotLayoutMode;
181
+ items: LayoutItem[];
182
+ total: number;
183
+ hidden: number;
184
+ }
185
+
186
+ /**
187
+ * @repodeckhz/core — cache.ts
188
+ *
189
+ * In-memory cache (Map) with TTL. Encapsulated in a `Cache` class so consumers
190
+ * can create isolated instances or share a singleton. The interface is
191
+ * designed to be swapped for a localStorage or Redis adapter without touching
192
+ * the rest of the core.
193
+ */
194
+ interface CacheEntry<T = unknown> {
195
+ value: T;
196
+ expiresAt: number;
197
+ }
198
+ /**
199
+ * TTL-based in-memory cache. Create one instance and share it, or create
200
+ * separate instances for different TTLs/scopes.
201
+ */
202
+ declare class Cache {
203
+ private defaultTtlMs;
204
+ private store;
205
+ constructor(defaultTtlMs?: number);
206
+ get<T = unknown>(key: string): T | undefined;
207
+ set<T = unknown>(key: string, value: T, ttlMs?: number): void;
208
+ /** Returns the cached entry including its expiry (for introspection). */
209
+ getEntry<T = unknown>(key: string): CacheEntry<T> | undefined;
210
+ delete(key: string): void;
211
+ clear(): void;
212
+ get size(): number;
213
+ /** Builds a deterministic cache key from card request parameters. */
214
+ static buildKey(parts: {
215
+ owner: string;
216
+ repo: string;
217
+ branch: string;
218
+ configPath: string;
219
+ include: string;
220
+ }): string;
221
+ }
222
+ declare function getCached<T = unknown>(key: string): CacheEntry<T> | undefined;
223
+ declare function setCached<T = unknown>(key: string, value: T, ttlMs?: number): void;
224
+ declare function clearCache(key?: string): void;
225
+ declare function buildCacheKey(parts: {
226
+ owner: string;
227
+ repo: string;
228
+ branch: string;
229
+ configPath: string;
230
+ include: string;
231
+ }): string;
232
+
233
+ /**
234
+ * @repodeckhz/core — presets.ts
235
+ *
236
+ * Presets define "what shows on the compact card" vs "what is available in the
237
+ * modal". Consumers can register their own preset without forking.
238
+ */
239
+
240
+ declare const defaultPresets: Record<string, PresetDefinition>;
241
+ declare function registerPreset(name: string, definition: PresetDefinition): void;
242
+ declare function getPreset(name: string): PresetDefinition;
243
+ declare function listPresets(): string[];
244
+ declare function mergePresetWithUserConfig(preset: PresetDefinition, overrides: Partial<PresetDefinition>, name?: string): ResolvedConfig;
245
+
246
+ /**
247
+ * @repodeckhz/core — radius.ts
248
+ *
249
+ * Translates the `radius` attribute (preset name OR raw CSS value) into the
250
+ * three tokens used internally. Each token can be individually overridden via
251
+ * CSS custom properties on the host.
252
+ */
253
+
254
+ declare function resolveRadiusTokens(radiusAttr: string): RadiusTokens;
255
+
256
+ /**
257
+ * @repodeckhz/core — layout.ts
258
+ *
259
+ * Decides how screenshots are laid out.
260
+ * - 1 image → `single` (cover)
261
+ * - 2+ images → `bento` (asymmetric grid, deterministic spans)
262
+ *
263
+ * Spans are deterministic (not random) so the layout never "jumps" between
264
+ * re-renders. The first image is always the hero (because screenshots are
265
+ * sorted alphabetically upstream, naming `01-capa.png` makes it the cover).
266
+ */
267
+
268
+ /**
269
+ * Deterministic bento span map.
270
+ * - 2 images: hero spans 2 cols, second spans 1
271
+ * - 3 images: hero 2x2, others 1x1 stacked
272
+ * - 4+ images: 2x2 mosaic, first tile always hero
273
+ */
274
+ declare function assignBentoSpans(count: number): Array<{
275
+ colSpan: number;
276
+ rowSpan: number;
277
+ hero: boolean;
278
+ }>;
279
+ declare function resolveScreenshotLayout(screenshots: Screenshot[], context: 'card' | 'modal'): ScreenshotLayout;
280
+
281
+ /**
282
+ * @repodeckhz/core — github-client.ts
283
+ *
284
+ * All communication with the GitHub REST API. Encapsulated in a `GitHubClient`
285
+ * class so consumers can configure token/retry/timeout once and inject the
286
+ * instance wherever needed (dependency injection).
287
+ *
288
+ * 404 → returns null (so a missing field doesn't break the whole card);
289
+ * any other failure throws repodeckError.
290
+ */
291
+
292
+ interface GitHubClientOptions {
293
+ /** GitHub personal access token — bumps rate limit from 60 to 5000 req/h. */
294
+ token?: string;
295
+ /** Per-request timeout in ms (default 12000). */
296
+ timeoutMs?: number;
297
+ /** Retries on 5xx/network errors (default 2). Never retries 404/401/403. */
298
+ retries?: number;
299
+ /** Log every request (method, path, status, duration, rate-limit headers). */
300
+ debug?: boolean;
301
+ }
302
+ /**
303
+ * Encapsulates all GitHub REST API access. Stateless aside from config —
304
+ * safe to share a single instance across calls.
305
+ */
306
+ declare class GitHubClient {
307
+ private readonly token?;
308
+ private readonly timeoutMs;
309
+ private readonly retries;
310
+ private readonly debug;
311
+ constructor(options?: GitHubClientOptions);
312
+ /** Creates a client from `FetchOptions` (backward-compatible with the old API). */
313
+ static from(options?: {
314
+ token?: string;
315
+ timeoutMs?: number;
316
+ retries?: number;
317
+ debug?: boolean;
318
+ }): GitHubClient;
319
+ private headers;
320
+ /** Retry wrapper — only retries on network errors / 5xx, never on 404/401/403. */
321
+ private fetchWithRetry;
322
+ private static decodeBase64;
323
+ /** Fetches a single file's content. 404 → null (not an error). */
324
+ fetchFileContent(owner: string, repo: string, path: string, ref?: string): Promise<string | null>;
325
+ /** Lists files in a directory. 404 → empty array. */
326
+ fetchDirectoryListing(owner: string, repo: string, path: string, ref?: string): Promise<DirectoryEntry[]>;
327
+ /** Fetches repo metadata and statistics in a single API call. */
328
+ fetchRepoStats(owner: string, repo: string): Promise<RepoStats>;
329
+ /** Checks the current rate-limit budget. */
330
+ checkRateLimit(): Promise<RateLimitInfo>;
331
+ private assertOk;
332
+ }
333
+ /** Builds a raw.githubusercontent.com URL (efficient for <img src>). */
334
+ declare function buildRawUrl(owner: string, repo: string, branch: string, path: string): string;
335
+ declare function fetchFileContent(owner: string, repo: string, path: string, options?: {
336
+ token?: string;
337
+ branch?: string;
338
+ timeoutMs?: number;
339
+ retries?: number;
340
+ }): Promise<string | null>;
341
+ declare function fetchDirectoryListing(owner: string, repo: string, path: string, options?: {
342
+ token?: string;
343
+ branch?: string;
344
+ timeoutMs?: number;
345
+ retries?: number;
346
+ }): Promise<DirectoryEntry[]>;
347
+ declare function fetchRepoStats(owner: string, repo: string, options?: {
348
+ token?: string;
349
+ timeoutMs?: number;
350
+ retries?: number;
351
+ }): Promise<RepoStats>;
352
+ declare function checkRateLimit(options?: {
353
+ token?: string;
354
+ timeoutMs?: number;
355
+ retries?: number;
356
+ }): Promise<RateLimitInfo>;
357
+
358
+ /**
359
+ * @repodeckhz/core — parser.ts
360
+ *
361
+ * Transforms raw text from GitHub into display-ready data.
362
+ *
363
+ * Security: README content comes from third-party repos, so markdown→HTML is
364
+ * followed by sanitization (isomorphic-dompurify) to neutralise XSS.
365
+ *
366
+ * Performance: `marked` and `isomorphic-dompurify` are lazy-loaded on first
367
+ * `parseReadme` call — consumers that only use `parseResume` /
368
+ * `resolveScreenshots` never pay the cost of loading these heavy deps.
369
+ */
370
+
371
+ /**
372
+ * Parses YAML-like frontmatter from the top of README.md (plan §16.18).
373
+ * Supports: title, accent (hex color), order (number), description, tags.
374
+ * Returns { metadata, content } where content is the markdown with frontmatter
375
+ * stripped.
376
+ */
377
+ declare function parseReadmeFrontmatter(rawMarkdown: string): {
378
+ metadata: Record<string, string | number>;
379
+ content: string;
380
+ };
381
+ /**
382
+ * Normalises RESUME.txt: collapses excessive whitespace, applies a configurable
383
+ * char limit. Returns a `truncated` flag so the UI can show "…" / a "see more"
384
+ * affordance that opens the modal.
385
+ */
386
+ declare function parseResume(rawText: string, options?: {
387
+ maxChars?: number;
388
+ }): ResumeData;
389
+ /**
390
+ * Converts Markdown → sanitized HTML. No truncation (the modal shows the full
391
+ * README with internal scroll — see plan §8).
392
+ * Also extracts frontmatter (plan §16.18) so the web component can apply
393
+ * per-project accent color + title overrides without extra config on the
394
+ * embedder's side.
395
+ *
396
+ * Async because `marked` + `isomorphic-dompurify` are lazy-loaded on first
397
+ * call — keeping the initial bundle light.
398
+ */
399
+ declare function parseReadme(rawMarkdown: string): Promise<ReadmeData>;
400
+ /**
401
+ * Filters the screenshots/ listing to valid image extensions, sorts
402
+ * alphabetically (so `01-`, `02-` prefixes give a stable order), and resolves
403
+ * each to a raw.githubusercontent.com URL.
404
+ */
405
+ declare function resolveScreenshots(entries: DirectoryEntry[], owner: string, repo: string, branch: string): Screenshot[];
406
+
407
+ /**
408
+ * @repodeckhz/core — builder.ts
409
+ *
410
+ * The orchestrator. `CardBuilder` takes a `GitHubClient` (dependency injection)
411
+ * and an optional `Cache`, then builds a `CardData` object by firing only the
412
+ * fetches the caller asked for — in parallel, with per-field error isolation.
413
+ *
414
+ * Per-field error model (closed decision — plan §3.3 / §15):
415
+ * If RESUME.txt is missing but README.md exists, the card still builds with
416
+ * `resume` carrying a repodeckError. Never "all-or-nothing".
417
+ */
418
+
419
+ declare function validateConfig(owner: unknown, repo: unknown, options?: BuildOptions): void;
420
+ /**
421
+ * Orchestrates card data assembly. Inject a `GitHubClient` (and optionally a
422
+ * `Cache`) so the builder is fully decoupled from I/O — easy to test with
423
+ * mocks.
424
+ */
425
+ declare class CardBuilder {
426
+ private readonly client;
427
+ private readonly cache?;
428
+ constructor(client?: GitHubClient, cache?: Cache | undefined);
429
+ build(owner: string, repo: string, options?: BuildOptions): Promise<CardData>;
430
+ private fetchResume;
431
+ private fetchReadme;
432
+ private fetchScreenshots;
433
+ private fetchStats;
434
+ /** Wraps a fetcher in try/catch, converting errors to per-field repodeckError. */
435
+ private safeFetch;
436
+ }
437
+ /**
438
+ * Builds card data for a GitHub repo. Creates a one-shot `CardBuilder` with a
439
+ * default `GitHubClient`. For repeated calls, create a `CardBuilder` once and
440
+ * reuse it (enables connection reuse + caching).
441
+ */
442
+ declare function buildCardData(owner: string, repo: string, options?: BuildOptions): Promise<CardData>;
443
+
444
+ /**
445
+ * @repodeckhz/core — prefetch.ts
446
+ *
447
+ * Build-time / batch fetching. Takes a list of
448
+ * `{ owner, repo, branch?, configPath?, include? }` and resolves them all
449
+ * concurrently (bounded by a small worker pool) so a static site can ship
450
+ * the resulting `CardData` to visitors without any runtime GitHub calls.
451
+ *
452
+ * Per the closed plan decision documented in §3.3 / §15, each repo's per-
453
+ * field errors are preserved: a missing RESUME.txt on one card doesn't
454
+ * stop the rest of the batch.
455
+ */
456
+
457
+ interface PrefetchTarget {
458
+ owner: string;
459
+ repo: string;
460
+ branch?: string;
461
+ configPath?: string;
462
+ /** `include` mask — defaults to { readme, resume, screenshots: true } for each. */
463
+ include?: BuildOptions['include'];
464
+ }
465
+ interface PrefetchOptions {
466
+ /** Concurrency ceiling; default 4 (≈16 request-units per minute, comfortable even anonymous). */
467
+ concurrency?: number;
468
+ /** Per-card Token (or pass per-target): the global one is used when the target doesn't override. */
469
+ token?: string;
470
+ /** Optional shared cache so two prefetch calls don't refetch the same repo. */
471
+ cache?: Cache;
472
+ /** Optional list of fields to include by default; targets without a per-target `include` inherit this. */
473
+ include?: BuildOptions['include'];
474
+ /** Per-card `timeoutMs` (forwarded to GitHubClient). */
475
+ timeoutMs?: number;
476
+ /** Per-card `retries` (forwarded to GitHubClient). */
477
+ retries?: number;
478
+ /** Log batch progress and per-repo errors (or REPODECK_DEBUG=1). */
479
+ debug?: boolean;
480
+ }
481
+ /**
482
+ * Resolves many repos in parallel and returns a map keyed by `owner/repo`.
483
+ *
484
+ * const cards = await prefetchCardData(
485
+ * [{ owner: 'me', repo: 'a' }, { owner: 'me', repo: 'b' }],
486
+ * { token: process.env.GITHUB_TOKEN, concurrency: 4 },
487
+ * );
488
+ * cards.get('me/a'); // → CardData
489
+ */
490
+ declare function prefetchCardData(targets: PrefetchTarget[], options?: PrefetchOptions): Promise<Map<string, CardData>>;
491
+ /**
492
+ * Public surface returned by `prefetchCardData`. Always a `Map<owner/repo,
493
+ * CardData>`, additionally decorated with the `errors` getter when at
494
+ * least one target threw a fatal error during the batch run.
495
+ *
496
+ * const result = await prefetchCardData([...]);
497
+ * for (const [key, card] of result) console.log(key, card.meta.url);
498
+ * if (result.errors) {
499
+ * for (const [key, err] of result.errors) console.warn(key, err);
500
+ * }
501
+ */
502
+ interface PrefetchResult extends Map<string, CardData> {
503
+ /** Map of `owner/repo` → fatal error. Empty on a fully successful run. */
504
+ readonly errors?: ReadonlyMap<string, unknown>;
505
+ }
506
+
507
+ /**
508
+ * @repodeckhz/core — graphql.ts
509
+ *
510
+ * Experimental GitHub GraphQL batch fetch (plan §16.6).
511
+ *
512
+ * One POST to `https://api.github.com/graphql` can resolve metadata + files
513
+ * for several repos at once, where N REST calls would be needed otherwise.
514
+ * For a 10-repo batch with resume + readme + screenshots + stats, REST costs
515
+ * roughly 4 × 10 = 40 request-units; GraphQL collapses that to 1 query (plus
516
+ * an auxiliary REST call per screenshots/ folder — the GraphQL Tree API can't
517
+ * enumerate the contents of a tree in the same request as the parent repo
518
+ * query without blowing past the complexity budget, so we keep that on REST
519
+ * and treat it as best-effort).
520
+ *
521
+ * Rate limit math (2026):
522
+ * • GraphQL point cost is 1 point per request, NOT proportional to query
523
+ * complexity (GitHub's points-per-call model, active since 2025).
524
+ * • 5,000 points/hour with a token; ~1 h without (anonymous GraphQL is
525
+ * rejected entirely). For batches, we split repos into groups of
526
+ * `batchSize` (default 10) — each group is one request.
527
+ *
528
+ * Public surface:
529
+ * • `GraphQLClient` — minimal client (POST + bearer + retry).
530
+ * • `fetchMultipleReposViaGraphQL(targets, options)` → Map<owner/repo, CardData>.
531
+ *
532
+ * The returned `CardData` is the same shape the REST path in `CardBuilder`
533
+ * produces, so consumers don't need to branch on transport.
534
+ */
535
+
536
+ interface GraphQLBatchTarget {
537
+ owner: string;
538
+ repo: string;
539
+ /** Branch / ref to read from; default `main`. */
540
+ branch?: string;
541
+ /** Config folder path; default `config-repodeck`. */
542
+ configPath?: string;
543
+ /** Optional per-target `include` mask; inherits options.include otherwise. */
544
+ include?: BuildOptions['include'];
545
+ /** Optional per-target resume locale (e.g. `pt`). */
546
+ locale?: string;
547
+ }
548
+ interface GraphQLBatchOptions {
549
+ /** GitHub personal access token — required (anonymous GraphQL is rejected). */
550
+ token: string;
551
+ /** Max repos per GraphQL request (default 10). */
552
+ batchSize?: number;
553
+ /** Per-request timeout (default 30000 — GraphQL can take a while on big batches). */
554
+ timeoutMs?: number;
555
+ /** Retries on 5xx / network errors (default 2). */
556
+ retries?: number;
557
+ /** Per-batch default include mask; targets without `include` inherit this. */
558
+ include?: BuildOptions['include'];
559
+ /** Shared `GitHubClient` for the auxiliary REST screenshots/ call. */
560
+ restClient?: GitHubClient;
561
+ /** Resume char limit forwarded to parseResume. */
562
+ resumeMaxChars?: number;
563
+ /** Log batch plans, query sizes and per-repo errors (or REPODECK_DEBUG=1). */
564
+ debug?: boolean;
565
+ }
566
+ /**
567
+ * Minimal WebSocket-without-the-WS — a POST-only client for the GraphQL
568
+ * endpoint. Kept separate from `GitHubClient` because GraphQL has a
569
+ * different endpoint, different rate-limit semantics and no 404-as-null
570
+ * convention (errors come back inside the 200 body).
571
+ */
572
+ declare class GraphQLClient {
573
+ private readonly token;
574
+ private readonly timeoutMs;
575
+ private readonly retries;
576
+ private readonly debug;
577
+ constructor(opts: {
578
+ token: string;
579
+ timeoutMs?: number;
580
+ retries?: number;
581
+ debug?: boolean;
582
+ });
583
+ /**
584
+ * POSTs a GraphQL query. Returns the `data` field of the response (already
585
+ * stripped of `errors`). Throws `RepoDeckError('NETWORK_ERROR', …)` on a
586
+ * non-2xx response, or `RepoDeckError` carrying the GraphQL errors when
587
+ * the response is 200 but carries `errors: [...]`.
588
+ */
589
+ request<T = unknown>(query: string, variables?: Record<string, unknown>): Promise<T>;
590
+ }
591
+ /**
592
+ * Fetches card data for multiple repos using a single GraphQL query per
593
+ * batch (default 10 repos per request). Returns a Map keyed by `owner/repo`
594
+ * with the same CardData shape the REST path produces.
595
+ *
596
+ * const cards = await fetchMultipleReposViaGraphQL(
597
+ * [{ owner: 'facebook', repo: 'react' }, { owner: 'vercel', repo: 'next.js' }],
598
+ * { token: process.env.GITHUB_TOKEN },
599
+ * );
600
+ * cards.get('facebook/react'); // → CardData
601
+ *
602
+ * Partial errors:
603
+ * Each repo resolves to either data or a per-field RepoDeckError, mirroring
604
+ * `CardBuilder`. A repo whose `repository` query returns null (repo deleted
605
+ * or inaccessible) produces a card with `stats`, `readme`, `resume` all
606
+ * carrying `error: RepoDeckError('NOT_FOUND')`.
607
+ *
608
+ * Screenshots: per the closed decision above, screenshots/ are fetched via
609
+ * an auxiliary REST call (one per repo) after the GraphQL response lands.
610
+ * This keeps the GraphQL query complexity within limits while only adding
611
+ * one request per repo (12 REST requests for a 12-repo batch still beats
612
+ * 4 × 12 = 48 for pure REST).
613
+ */
614
+ declare function fetchMultipleReposViaGraphQL(targets: GraphQLBatchTarget[], options: GraphQLBatchOptions): Promise<Map<string, CardData>>;
615
+
616
+ export { type BuildOptions, Cache, type CacheEntry, CardBuilder, type CardData, type CardMeta, type DirectoryEntry, type FetchOptions, type FieldResult, GitHubClient, type GitHubClientOptions, type GraphQLBatchOptions, type GraphQLBatchTarget, GraphQLClient, type LayoutItem, type PrefetchOptions, type PrefetchResult, type PrefetchTarget, type PresetDefinition, type RadiusPresetName, type RadiusTokens, type RateLimitInfo, type ReadmeData, RepoDeckError, type RepoDeckErrorCode, type RepoStats, type ResolvedConfig, type ResumeData, type Screenshot, type ScreenshotLayout, type ScreenshotLayoutMode, assignBentoSpans, buildCacheKey, buildCardData, buildRawUrl, checkRateLimit, clearCache, defaultPresets, fetchDirectoryListing, fetchFileContent, fetchMultipleReposViaGraphQL, fetchRepoStats, getCached, getPreset, listPresets, mergePresetWithUserConfig, parseReadme, parseReadmeFrontmatter, parseResume, prefetchCardData, registerPreset, repodeckError, type repodeckErrorCode, resolveRadiusTokens, resolveScreenshotLayout, resolveScreenshots, setCached, validateConfig };