@tsdoctor/registry 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts ADDED
@@ -0,0 +1,990 @@
1
+ import { Cause, Context, Duration, Effect, FileSystem, Layer, Option, Path, PlatformError, Schema } from "effect";
2
+ import * as HttpClient from "effect/unstable/http/HttpClient";
3
+ import { CompilerOptions } from "@effected/tsconfig-json";
4
+ import { VirtualTypeScriptEnvironment } from "@typescript/vfs";
5
+ import { Cache } from "@effected/store";
6
+ import { AppDirs, AppDirsError } from "@effected/xdg";
7
+ //#region src/PackageSpec.d.ts
8
+ declare const PackageSpec_base: Schema.Class<PackageSpec, Schema.Struct<{
9
+ /** The npm package name (e.g. `"zod"`, `"@effect/schema"`). */
10
+ readonly name: Schema.String;
11
+ /** The version reference as requested: exact, range, or dist-tag. */
12
+ readonly version: Schema.String;
13
+ }>, {}>;
14
+ /**
15
+ * Identifies a package at a version reference.
16
+ *
17
+ * @remarks
18
+ * `version` is the reference **as requested** — an exact version, a range or
19
+ * a dist-tag — and is pinned later by `TypeRegistry.resolveVersion`. Both
20
+ * fields are validated just enough that they can never traverse outside a
21
+ * cache directory when joined into a path; otherwise validation is lenient
22
+ * (CDN reality).
23
+ *
24
+ * Construct via `PackageSpec.make({ name, version })` or
25
+ * {@link PackageSpec.fromString} — never `new`.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { PackageSpec } from "@tsdoctor/registry";
30
+ *
31
+ * const pkg = PackageSpec.fromString("zod@3.23.8");
32
+ * console.log(pkg.name, pkg.version, pkg.cacheKey);
33
+ * // => "zod" "3.23.8" "zod:3.23.8"
34
+ * ```
35
+ *
36
+ * @public
37
+ */
38
+ declare class PackageSpec extends PackageSpec_base {
39
+ /**
40
+ * Parse a `name@version` specifier (`"zod@3.23.8"`, `"@scope/pkg@^1.0.0"`).
41
+ *
42
+ * @remarks
43
+ * A specifier without a version part defaults to `"latest"`. An invalid
44
+ * specifier is developer wiring, not input — it throws (defect posture),
45
+ * exactly like `PackageSpec.make` with invalid fields.
46
+ */
47
+ static fromString(spec: string): PackageSpec;
48
+ /**
49
+ * Extract the npm package name from an arbitrary import specifier.
50
+ *
51
+ * @remarks
52
+ * `node:` specifiers and Node built-ins normalize to `"node"` (the
53
+ * `@types/node` convention); scoped specifiers keep scope and name but drop
54
+ * deep-import segments; bare specifiers keep only the first path segment.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import { PackageSpec } from "@tsdoctor/registry";
59
+ *
60
+ * PackageSpec.normalizeSpecifier("node:fs"); // "node"
61
+ * PackageSpec.normalizeSpecifier("@effect/platform/Http"); // "@effect/platform"
62
+ * PackageSpec.normalizeSpecifier("lodash/fp"); // "lodash"
63
+ * ```
64
+ */
65
+ static normalizeSpecifier(specifier: string): string;
66
+ /**
67
+ * Parse a {@link PackageSpec.cacheKey} back into a spec.
68
+ *
69
+ * @remarks
70
+ * Scoped keys (leading `@`) have three colon segments, unscoped keys two.
71
+ * `Option.none()` for keys matching neither shape — the metadata store may
72
+ * hold keys this package never wrote.
73
+ */
74
+ static parseCacheKey(key: string): Option.Option<PackageSpec>;
75
+ /** The `name@version` string form. */
76
+ toString(): string;
77
+ /**
78
+ * The colon-delimited metadata-store key: `@scope:name:version` for scoped
79
+ * packages, `name:version` otherwise.
80
+ *
81
+ * @remarks
82
+ * The scheme mirrors v3's on-disk layout but there is no compat contract
83
+ * with databases written by `type-registry-effect` — nothing was published.
84
+ */
85
+ get cacheKey(): string;
86
+ }
87
+ //#endregion
88
+ //#region src/PackageFetcher.d.ts
89
+ declare const FetchError_base: Schema.Class<FetchError, Schema.TaggedStruct<"FetchError", {
90
+ /** The request URL. */
91
+ readonly url: Schema.String;
92
+ /** The HTTP status, when the failure has one. */
93
+ readonly status: Schema.optionalKey<Schema.Number>;
94
+ /** What failed, structurally. */
95
+ readonly kind: Schema.Literals<readonly ["transport", "status", "body", "schema"]>;
96
+ /** The underlying failure, preserved structurally. */
97
+ readonly cause: Schema.Defect;
98
+ }>, Cause.YieldableError>;
99
+ /**
100
+ * Raised when an HTTP request or response fails at the jsDelivr boundary.
101
+ *
102
+ * @remarks
103
+ * `kind` classifies the failure structurally — `transport` (connection,
104
+ * timeout), `status` (non-2xx, with `status` populated), `body` (reading or
105
+ * bounding the body) or `schema` (response validation) — and `status` is a
106
+ * structured field, so classification consumers branch on typed data. v3
107
+ * folded the HTTP status into a message string and substring-matched `"404"`
108
+ * back out of it.
109
+ *
110
+ * @public
111
+ */
112
+ declare class FetchError extends FetchError_base {
113
+ get message(): string;
114
+ }
115
+ declare const PackageNotFoundError_base: Schema.Class<PackageNotFoundError, Schema.TaggedStruct<"PackageNotFoundError", {
116
+ /** The package name. */
117
+ readonly name: Schema.String;
118
+ /** The version reference that was requested. */
119
+ readonly version: Schema.String;
120
+ }>, Cause.YieldableError>;
121
+ /**
122
+ * Raised when a pinned package version does not exist on the CDN (HTTP 404).
123
+ *
124
+ * @remarks
125
+ * The 404 → `PackageNotFoundError` promotion happens on the typed
126
+ * `FetchError` `status` field. Also raised by `TypeRegistry.getPackageVfs`
127
+ * on a cache miss with `autoFetch: false`.
128
+ *
129
+ * @public
130
+ */
131
+ declare class PackageNotFoundError extends PackageNotFoundError_base {
132
+ get message(): string;
133
+ }
134
+ declare const VersionNotFoundError_base: Schema.Class<VersionNotFoundError, Schema.TaggedStruct<"VersionNotFoundError", {
135
+ /** The package name. */
136
+ readonly name: Schema.String;
137
+ /** The requested reference: a range, dist-tag or exact version. */
138
+ readonly ref: Schema.String;
139
+ /** A bounded sample of the versions that ARE published. */
140
+ readonly available: Schema.$Array<Schema.String>;
141
+ }>, Cause.YieldableError>;
142
+ /**
143
+ * Raised when local version resolution finds no published version matching
144
+ * the requested reference.
145
+ *
146
+ * @remarks
147
+ * Raised by `TypeRegistry.resolveVersion` — typed, with the requested ref and
148
+ * bounded available-version context. v3 detected this case by
149
+ * substring-matching CDN error prose.
150
+ *
151
+ * @public
152
+ */
153
+ declare class VersionNotFoundError extends VersionNotFoundError_base {
154
+ get message(): string;
155
+ }
156
+ /**
157
+ * Version and dist-tag metadata for an npm package.
158
+ *
159
+ * @public
160
+ */
161
+ interface PackageVersions {
162
+ /** Every published version string. */
163
+ readonly versions: ReadonlyArray<string>;
164
+ /** Dist-tags (`latest`, `next`, …) mapped to version strings. */
165
+ readonly tags: {
166
+ readonly [tag: string]: string;
167
+ };
168
+ }
169
+ /**
170
+ * The lenient `package.json` subset the type resolver reads.
171
+ *
172
+ * @remarks
173
+ * Deliberately NOT `@effected/package-json`: its schemas validate strictly
174
+ * (branded names, SPDX licenses), and the manifests this package decodes come
175
+ * off a CDN and include every historical malformation npm ever published.
176
+ * Validation here is lenient and scoped to exactly the fields resolution
177
+ * needs.
178
+ *
179
+ * @public
180
+ */
181
+ declare const PackageManifest: Schema.Struct<{
182
+ readonly name: Schema.optionalKey<Schema.String>;
183
+ readonly version: Schema.optionalKey<Schema.String>;
184
+ readonly types: Schema.optionalKey<Schema.String>;
185
+ readonly typings: Schema.optionalKey<Schema.String>;
186
+ readonly main: Schema.optionalKey<Schema.String>;
187
+ readonly module: Schema.optionalKey<Schema.String>;
188
+ readonly exports: Schema.optionalKey<Schema.Union<readonly [Schema.String, Schema.$Record<Schema.String, Schema.Unknown>, Schema.$Array<Schema.Unknown>]>>;
189
+ readonly typesVersions: Schema.optionalKey<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Union<readonly [Schema.$Array<Schema.String>, Schema.String]>>>>;
190
+ readonly dependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
191
+ readonly peerDependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
192
+ readonly devDependencies: Schema.optionalKey<Schema.$Record<Schema.String, Schema.String>>;
193
+ }>;
194
+ /**
195
+ * The decoded form of {@link (PackageManifest:variable)}.
196
+ *
197
+ * @public
198
+ */
199
+ type PackageManifest = typeof PackageManifest.Type;
200
+ /**
201
+ * The service shape {@link PackageFetcher} provides.
202
+ *
203
+ * @public
204
+ */
205
+ interface PackageFetcherShape {
206
+ /** Fetch every published version and dist-tag for a package name. */
207
+ readonly getVersions: (name: string) => Effect.Effect<PackageVersions, FetchError>;
208
+ /** List the file paths a pinned package version publishes (leading `/` stripped). */
209
+ readonly getFileTree: (pkg: PackageSpec) => Effect.Effect<ReadonlyArray<string>, FetchError | PackageNotFoundError>;
210
+ /** Download one file's contents from the CDN. */
211
+ readonly downloadFile: (pkg: PackageSpec, path: string) => Effect.Effect<string, FetchError | PackageNotFoundError>;
212
+ /** Download and leniently decode a pinned version's `package.json`. */
213
+ readonly getPackageJson: (pkg: PackageSpec) => Effect.Effect<PackageManifest, FetchError | PackageNotFoundError>;
214
+ /**
215
+ * Download every declaration file a pinned version publishes, keyed by
216
+ * tree path.
217
+ *
218
+ * @remarks
219
+ * Concurrency 10, bounded by the materialization budget: more than the
220
+ * per-package file cap, or a cumulative body size past the byte cap, fails
221
+ * typed (`FetchError`, `kind: "body"`) rather than exhausting memory.
222
+ */
223
+ readonly getTypeFiles: (pkg: PackageSpec) => Effect.Effect<ReadonlyMap<string, string>, FetchError | PackageNotFoundError>;
224
+ }
225
+ declare const PackageFetcher_base: Context.ServiceClass<PackageFetcher, "type-registry-effect/PackageFetcher", PackageFetcherShape>;
226
+ /**
227
+ * The jsDelivr CDN client.
228
+ *
229
+ * @remarks
230
+ * Requests time out after 30 seconds; transport and timeout failures retry
231
+ * up to 3 times with exponential back-off (starting at 100 ms). Non-2xx
232
+ * responses fail fast with a typed status and emit a `FetchFailed` event
233
+ * carrying the status and a body snippet. If a second registry backend ever
234
+ * appears it arrives as another layer for this service — the service seam is
235
+ * the extension point.
236
+ *
237
+ * @public
238
+ */
239
+ declare class PackageFetcher extends PackageFetcher_base {
240
+ /** The jsDelivr-backed layer; requires an `HttpClient`. */
241
+ static readonly layer: Layer.Layer<PackageFetcher, never, HttpClient.HttpClient>;
242
+ }
243
+ //#endregion
244
+ //#region src/RegistryEvent.d.ts
245
+ /**
246
+ * Discriminated union of typed progress events emitted during registry
247
+ * operations.
248
+ *
249
+ * @remarks
250
+ * The consumer-facing progress surface. Emission is opt-in and zero-cost:
251
+ * internal call sites resolve the {@link RegistryObserver} via
252
+ * `Effect.serviceOption`, so no requirement is added to any signature and
253
+ * absence is a no-op. The library performs no `Effect.log` of its own — the
254
+ * host owns presentation.
255
+ *
256
+ * Schema-backed (the store `CacheEventPayload` precedent) because events
257
+ * cross the library/host boundary and hosts ship them to telemetry. Narrow
258
+ * with `switch (event._tag)` or `Match`.
259
+ *
260
+ * @public
261
+ */
262
+ declare const RegistryEvent: Schema.Union<readonly [Schema.TaggedStruct<"VersionResolved", {
263
+ readonly package: Schema.String;
264
+ readonly requested: Schema.String;
265
+ readonly resolved: Schema.String;
266
+ }>, Schema.TaggedStruct<"VersionResolveFailed", {
267
+ readonly package: Schema.String;
268
+ readonly requested: Schema.String;
269
+ readonly kind: Schema.Literals<readonly ["not-found", "no-match", "network"]>;
270
+ }>, Schema.TaggedStruct<"CacheHit", {
271
+ readonly package: Schema.String;
272
+ readonly version: Schema.String;
273
+ /** How long ago the entry was cached. */
274
+ readonly age: Schema.Duration;
275
+ }>, Schema.TaggedStruct<"CacheStale", {
276
+ readonly package: Schema.String;
277
+ readonly version: Schema.String;
278
+ }>, Schema.TaggedStruct<"CacheMiss", {
279
+ readonly package: Schema.String;
280
+ readonly version: Schema.String;
281
+ }>, Schema.TaggedStruct<"FetchStart", {
282
+ readonly package: Schema.String;
283
+ readonly version: Schema.String;
284
+ }>, Schema.TaggedStruct<"FetchFailed", {
285
+ readonly url: Schema.String;
286
+ readonly status: Schema.Number;
287
+ readonly bodySnippet: Schema.String;
288
+ }>, Schema.TaggedStruct<"PackageLoaded", {
289
+ readonly package: Schema.String;
290
+ readonly version: Schema.String;
291
+ readonly files: Schema.Number;
292
+ readonly source: Schema.Literals<readonly ["cache", "network"]>;
293
+ readonly duration: Schema.Duration;
294
+ }>, Schema.TaggedStruct<"PackageLoadFailed", {
295
+ readonly package: Schema.String;
296
+ readonly version: Schema.String;
297
+ readonly kind: Schema.Literals<readonly ["not-found", "version-range", "schema", "network", "cache", "unknown"]>;
298
+ /** The typed error itself, preserved structurally. */
299
+ readonly error: Schema.Defect;
300
+ }>, Schema.TaggedStruct<"BatchStart", {
301
+ readonly total: Schema.Number;
302
+ readonly packages: Schema.$Array<Schema.String>;
303
+ }>, Schema.TaggedStruct<"BatchComplete", {
304
+ readonly loaded: Schema.Number;
305
+ readonly failed: Schema.Number;
306
+ readonly total: Schema.Number;
307
+ readonly totalFiles: Schema.Number;
308
+ readonly duration: Schema.Duration;
309
+ }>]>;
310
+ /**
311
+ * The decoded form of {@link (RegistryEvent:variable)}: a tagged union the
312
+ * host narrows with `switch (event._tag)`.
313
+ *
314
+ * @public
315
+ */
316
+ type RegistryEvent = typeof RegistryEvent.Type;
317
+ /**
318
+ * The service shape {@link RegistryObserver} provides: a single `emit` the
319
+ * host implements.
320
+ *
321
+ * @public
322
+ */
323
+ interface RegistryObserverShape {
324
+ /** Handle one {@link (RegistryEvent:type)}. */
325
+ readonly emit: (event: RegistryEvent) => Effect.Effect<void>;
326
+ }
327
+ declare const RegistryObserver_base: Context.ServiceClass<RegistryObserver, "type-registry-effect/RegistryObserver", RegistryObserverShape>;
328
+ /**
329
+ * The opt-in registry event observer.
330
+ *
331
+ * @remarks
332
+ * Providing no observer layer is the default and costs nothing — every
333
+ * internal emission site resolves this service via `Effect.serviceOption`
334
+ * and no-ops on absence. Events here are progress reporting for a host UI —
335
+ * a push callback with no subscription lifecycle or `Scope`, usable from
336
+ * non-Effect hosts. (The store `Cache` exposes a `PubSub` instead because
337
+ * its events are intrinsic to an eviction-bearing store; the two postures
338
+ * are deliberate and should not be unified.)
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * import { RegistryObserver } from "@tsdoctor/registry";
343
+ *
344
+ * const ObserverLayer = RegistryObserver.layerCallback((event) => {
345
+ * if (event._tag === "PackageLoadFailed") console.warn(event.package, event.kind);
346
+ * });
347
+ * ```
348
+ *
349
+ * @public
350
+ */
351
+ declare class RegistryObserver extends RegistryObserver_base {
352
+ /**
353
+ * Build an observer layer from a plain callback — the lowest-friction
354
+ * bridge for non-Effect hosts.
355
+ *
356
+ * @remarks
357
+ * A throwing callback is a programmer bug and stays a defect; it is not
358
+ * laundered into any typed error channel.
359
+ */
360
+ static layerCallback(onEvent: (event: RegistryEvent) => void): Layer.Layer<RegistryObserver>;
361
+ /**
362
+ * A no-op observer. Equivalent to providing nothing, but explicit — makes
363
+ * "events are intentionally dropped" visible in a composition.
364
+ */
365
+ static readonly layerNoop: Layer.Layer<RegistryObserver>;
366
+ }
367
+ //#endregion
368
+ //#region src/Vfs.d.ts
369
+ /**
370
+ * The package's currency type: a virtual file system mapping
371
+ * `node_modules/`-prefixed paths to file contents.
372
+ */
373
+ /**
374
+ * A virtual file system: file paths (prefixed `node_modules/<package>/`)
375
+ * mapped to their string contents.
376
+ *
377
+ * @remarks
378
+ * This is the value every loading operation produces and every TypeScript
379
+ * integration consumes. Maps from multiple packages merge with {@link mergeVfs};
380
+ * `@typescript/vfs` consumes the merged map directly (see `TsEnvironment`).
381
+ *
382
+ * @public
383
+ */
384
+ type Vfs = Map<string, string>;
385
+ /**
386
+ * The v3 name for {@link Vfs}, kept as an alias for the consumer migration.
387
+ *
388
+ * @public
389
+ */
390
+ type VirtualFileSystem = Vfs;
391
+ /**
392
+ * Merge VFS maps left to right into a new map; later entries win on path
393
+ * collisions.
394
+ *
395
+ * @example
396
+ * ```ts
397
+ * import { mergeVfs } from "@tsdoctor/registry";
398
+ *
399
+ * const combined = mergeVfs(vfsA, vfsB);
400
+ * ```
401
+ *
402
+ * @public
403
+ */
404
+ declare const mergeVfs: (...maps: ReadonlyArray<ReadonlyMap<string, string>>) => Vfs;
405
+ /**
406
+ * Prefix every path in `entries` with `node_modules/<name>/`, normalizing
407
+ * away leading slashes.
408
+ *
409
+ * @public
410
+ */
411
+ declare const prefixVfs: (name: string, entries: ReadonlyMap<string, string>) => Vfs;
412
+ //#endregion
413
+ //#region src/TsEnvironment.d.ts
414
+ declare const TsEnvironmentError_base: Schema.Class<TsEnvironmentError, Schema.TaggedStruct<"TsEnvironmentError", {
415
+ /** The underlying failure, preserved structurally. */
416
+ readonly cause: Schema.Defect;
417
+ }>, import("effect/Cause").YieldableError>;
418
+ /**
419
+ * Raised when building a virtual TypeScript environment fails — including
420
+ * when the optional `typescript` / `@typescript/vfs` /
421
+ * `@effected/tsconfig-json` peers are not installed.
422
+ *
423
+ * @public
424
+ */
425
+ declare class TsEnvironmentError extends TsEnvironmentError_base {
426
+ get message(): string;
427
+ }
428
+ /**
429
+ * Options for {@link TsEnvironment.make}.
430
+ *
431
+ * @public
432
+ */
433
+ interface TsEnvironmentOptions {
434
+ /** The virtual file system to typecheck against. */
435
+ readonly vfs: Vfs;
436
+ /**
437
+ * Compiler options for the language service, in tsconfig JSON form
438
+ * (`{ target: "es2022" }`, not `ts.ScriptTarget.ES2022`). Enum-valued
439
+ * fields are converted to the compiler's numeric enums internally, so this
440
+ * type has no dependency on the `typescript` package.
441
+ */
442
+ readonly compilerOptions: CompilerOptions.Type;
443
+ /**
444
+ * The directory VFS paths are rooted under and the filesystem fallback
445
+ * root. Defaults to `process.cwd()` (which v3 hardcoded).
446
+ */
447
+ readonly projectRoot?: string;
448
+ }
449
+ /**
450
+ * The `@typescript/vfs` seam: builds a `VirtualTypeScriptEnvironment` over a
451
+ * {@link Vfs} plus the TypeScript default lib files.
452
+ *
453
+ * @remarks
454
+ * The ONLY module touching the optional `typescript` / `@typescript/vfs` /
455
+ * `@effected/tsconfig-json` peers, and it loads all three lazily inside
456
+ * {@link TsEnvironment.make} — a consumer that never calls it never loads
457
+ * the compiler, and a missing peer fails typed as
458
+ * {@link TsEnvironmentError} instead of crashing at import time. Keep every
459
+ * one of them behind that dynamic `import()`: a static value import here is
460
+ * reachable from `index.ts`, so it would turn an omitted optional peer into
461
+ * an `ERR_MODULE_NOT_FOUND` on the entry graph for consumers who never
462
+ * touch this module. Only the type-only `CompilerOptions` import is safe
463
+ * statically, because it erases. The underlying `createDefaultMapFromNodeModules` /
464
+ * `createFSBackedSystem` read the real filesystem through TypeScript's own
465
+ * `sys`, outside the Effect `FileSystem` service — accepted and documented;
466
+ * this module is why the package is integrated tier on its own surface.
467
+ *
468
+ * No cache map (v3's `createTypeScriptCache` returned a one-entry `Map`
469
+ * keyed by `JSON.stringify(compilerOptions)`): a consumer that wants keyed
470
+ * reuse holds its own map.
471
+ *
472
+ * `VirtualTypeScriptEnvironment` is deliberately not re-exported — import
473
+ * the type from `@typescript/vfs`, which consumers of this module already
474
+ * declare.
475
+ *
476
+ * @example
477
+ * ```ts
478
+ * import { TsEnvironment } from "@tsdoctor/registry";
479
+ *
480
+ * const environment = TsEnvironment.make({
481
+ * vfs,
482
+ * compilerOptions: { strict: true, target: "es2022" },
483
+ * });
484
+ * ```
485
+ *
486
+ * @public
487
+ */
488
+ declare class TsEnvironment {
489
+ private constructor();
490
+ /** Build a `VirtualTypeScriptEnvironment` over a {@link Vfs}. */
491
+ static make(options: TsEnvironmentOptions): Effect.Effect<VirtualTypeScriptEnvironment, TsEnvironmentError>;
492
+ }
493
+ //#endregion
494
+ //#region src/TypeCache.d.ts
495
+ declare const TypeCacheMetadata_base: Schema.Class<TypeCacheMetadata, Schema.Struct<{
496
+ /** The pinned version the files on disk belong to. */
497
+ readonly version: Schema.String;
498
+ /** When the package was cached. */
499
+ readonly cachedAt: Schema.DateTimeUtcFromString;
500
+ /** Time-to-live; absent = never expires. */
501
+ readonly ttl: Schema.optionalKey<Schema.DurationFromMillis>;
502
+ }>, {}>;
503
+ /**
504
+ * Per-package cache metadata: the pinned version, when it was cached and how
505
+ * long it lives.
506
+ *
507
+ * @remarks
508
+ * Stored JSON-encoded in the metadata plane (`@effected/store`'s `Cache`);
509
+ * `ttl` is also forwarded to the store's native TTL so expiry happens there
510
+ * (evict-on-read, bulk prune). An absent `ttl` means the entry never expires.
511
+ *
512
+ * @public
513
+ */
514
+ declare class TypeCacheMetadata extends TypeCacheMetadata_base {}
515
+ declare const TypeCacheError_base: Schema.Class<TypeCacheError, Schema.TaggedStruct<"TypeCacheError", {
516
+ /** The cache operation that failed. */
517
+ readonly operation: Schema.Literals<readonly ["exists", "read", "write", "list", "readMetadata", "writeMetadata", "getVfs", "remove", "prune"]>;
518
+ /** The file path or metadata key involved. */
519
+ readonly path: Schema.String;
520
+ /** The underlying failure, preserved structurally. */
521
+ readonly cause: Schema.Defect;
522
+ }>, import("effect/Cause").YieldableError>;
523
+ /**
524
+ * Raised when a cache operation fails: disk IO, metadata-store IO, or a file
525
+ * path that tries to escape the cache directory.
526
+ *
527
+ * @remarks
528
+ * `cause` carries the underlying failure structurally (a `PlatformError`,
529
+ * the store's `CacheError`, or a `SchemaError` from metadata decoding); v3
530
+ * flattened everything to `message: String(error)`.
531
+ *
532
+ * @public
533
+ */
534
+ declare class TypeCacheError extends TypeCacheError_base {
535
+ get message(): string;
536
+ }
537
+ /**
538
+ * Result of {@link TypeCacheShape.prune}: how many metadata entries were
539
+ * evicted and which packages they were.
540
+ *
541
+ * @public
542
+ */
543
+ interface CachePruneResult {
544
+ /** How many expired metadata entries were removed. */
545
+ readonly count: number;
546
+ /** The packages whose cache directories were deleted. */
547
+ readonly removed: ReadonlyArray<{
548
+ readonly name: string;
549
+ readonly version: string;
550
+ }>;
551
+ }
552
+ /**
553
+ * The service shape {@link TypeCache} provides.
554
+ *
555
+ * @public
556
+ */
557
+ interface TypeCacheShape {
558
+ /**
559
+ * Report whether the package's files exist on disk.
560
+ *
561
+ * @remarks
562
+ * A pure disk check — it does not consult metadata, so it distinguishes
563
+ * "files present" from "metadata live" (the stale-vs-miss ladder). Unlike
564
+ * v3, a filesystem failure surfaces as `TypeCacheError` instead of being
565
+ * laundered to `false`.
566
+ *
567
+ * "Files present" is trusted to mean "package complete" only because
568
+ * {@link TypeCacheShape.writePackage} promotes a staged tree atomically:
569
+ * the live directory appears only once every file is written, so a present
570
+ * directory is never a half-written one.
571
+ */
572
+ readonly exists: (pkg: PackageSpec) => Effect.Effect<boolean, TypeCacheError>;
573
+ /** Read one cached file's contents. */
574
+ readonly read: (pkg: PackageSpec, filePath: string) => Effect.Effect<string, TypeCacheError>;
575
+ /**
576
+ * Write one file into the package's cache directory.
577
+ *
578
+ * @remarks
579
+ * `filePath` is data from a CDN file tree: absolute paths and `..`
580
+ * segments are rejected as a typed `TypeCacheError` before any join — a
581
+ * hostile tree must not write outside `<cacheDir>/<name>/<version>/`.
582
+ *
583
+ * This is the low-level single-file primitive: it writes directly into the
584
+ * live directory and does not guard completeness.
585
+ * {@link TypeCacheShape.writePackage} is the atomic whole-package path the
586
+ * registry uses.
587
+ */
588
+ readonly write: (pkg: PackageSpec, filePath: string, content: string) => Effect.Effect<void, TypeCacheError>;
589
+ /**
590
+ * Write a package's entire file set atomically: stage every file in a
591
+ * sibling directory, then promote it onto the live directory in one
592
+ * `rename`.
593
+ *
594
+ * @remarks
595
+ * The invariant this method exists to hold: a reader either sees the
596
+ * package's previous complete state or its new complete state, never a
597
+ * half-written mixture. It works in three phases:
598
+ *
599
+ * 1. **Stage.** Files are written into a `.staging-<version>` directory that
600
+ * is a *sibling* of the live `<cacheDir>/<name>/<version>/` dir (same
601
+ * parent, so the final `rename` is same-filesystem and atomic). A
602
+ * `.staging-*` sibling is invisible to reads, which operate on the live
603
+ * directory specifically. Any leftover staging tree from a prior crash is
604
+ * cleared first. Each `filePath` runs through the same path guard
605
+ * {@link TypeCacheShape.write} uses (resolved against the staging root),
606
+ * so a hostile tree cannot escape; a path-safety or IO failure here
607
+ * aborts before promotion, leaving the live directory untouched.
608
+ * 2. **Promote.** The live directory is removed, then the staging directory
609
+ * is renamed onto it. Because the whole directory is replaced rather than
610
+ * merged, obsolete files from a previous, larger file set are dropped.
611
+ * 3. There is a tiny window between the remove and the rename where the live
612
+ * directory is briefly absent. A concurrent reader that observes it there
613
+ * classifies the package as a *miss* (via the stale-vs-miss ladder),
614
+ * which self-heals on the next fetch — strictly better than observing a
615
+ * partial tree and serving it as usable stale data.
616
+ *
617
+ * All failures map to a typed `TypeCacheError` with operation `"write"`.
618
+ */
619
+ readonly writePackage: (pkg: PackageSpec, files: Iterable<readonly [string, string]>) => Effect.Effect<void, TypeCacheError>;
620
+ /** List the package's cached files, relative to its cache directory. */
621
+ readonly listFiles: (pkg: PackageSpec) => Effect.Effect<ReadonlyArray<string>, TypeCacheError>;
622
+ /**
623
+ * Read the package's metadata entry.
624
+ *
625
+ * @remarks
626
+ * `Option.none()` when absent **or expired** — the store's TTL expiry
627
+ * evicts on read, which is what drives the stale-vs-miss distinction.
628
+ */
629
+ readonly readMetadata: (pkg: PackageSpec) => Effect.Effect<Option.Option<TypeCacheMetadata>, TypeCacheError>;
630
+ /** Write the package's metadata entry, forwarding its `ttl` to the store. */
631
+ readonly writeMetadata: (pkg: PackageSpec, metadata: TypeCacheMetadata) => Effect.Effect<void, TypeCacheError>;
632
+ /** Build the package's {@link Vfs}: every cached file keyed `node_modules/<name>/<path>`. */
633
+ readonly getVfs: (pkg: PackageSpec) => Effect.Effect<Vfs, TypeCacheError>;
634
+ /**
635
+ * Remove the package: metadata first, then files.
636
+ *
637
+ * @remarks
638
+ * The ordering is load-bearing. Files can outlive their metadata (TTL
639
+ * expiry evicts on read, leaving files behind), so the deletion cannot ride
640
+ * the store's transactional `onRemoved` callback — it only fires when a
641
+ * metadata row actually matched. Removing metadata first means a crash
642
+ * between the two steps leaves harmless orphaned files (a later refetch
643
+ * overwrites them), never a phantom cache hit.
644
+ */
645
+ readonly remove: (pkg: PackageSpec) => Effect.Effect<void, TypeCacheError>;
646
+ /**
647
+ * Evict every expired metadata entry and delete the corresponding
648
+ * directories.
649
+ *
650
+ * @remarks
651
+ * Deliberately best-effort, NOT transactional: file removals are side
652
+ * effects outside the SQL transaction, so a mid-loop rollback would
653
+ * restore all metadata while leaving earlier directories already deleted.
654
+ * Metadata is pruned first; per-directory removal failures are ignored (an
655
+ * orphaned directory is harmless — a later refetch overwrites it).
656
+ */
657
+ readonly prune: Effect.Effect<CachePruneResult, TypeCacheError>;
658
+ }
659
+ declare const TypeCache_base: Context.ServiceClass<TypeCache, "type-registry-effect/TypeCache", TypeCacheShape>;
660
+ /**
661
+ * The two-plane cache for fetched type definitions: files on disk under
662
+ * `<cacheDir>/<name>/<version>/`, metadata in `@effected/store`'s `Cache`
663
+ * with native TTL expiry.
664
+ *
665
+ * @remarks
666
+ * The layer statics are parameterized factories — bind the built layer to a
667
+ * `const` and provide that, or two provide sites mint two caches (the layer
668
+ * memoization discipline). The metadata plane is swappable in tests: store's
669
+ * `Cache.layerTest` (`:memory:`) satisfies {@link TypeCache.layer} with no
670
+ * real database file.
671
+ *
672
+ * @example
673
+ * ```ts
674
+ * import { TypeCache } from "@tsdoctor/registry";
675
+ *
676
+ * const TypeCacheLayer = TypeCache.layer({ cacheDir: "/var/cache/my-app/types" });
677
+ * ```
678
+ *
679
+ * @public
680
+ */
681
+ declare class TypeCache extends TypeCache_base {
682
+ /**
683
+ * A cache rooted at an explicit directory.
684
+ *
685
+ * @remarks
686
+ * `cacheDir` must be an absolute path — a relative one is developer wiring
687
+ * and dies at layer construction.
688
+ */
689
+ static layer(options: {
690
+ readonly cacheDir: string;
691
+ }): Layer.Layer<TypeCache, never, Cache | FileSystem.FileSystem | Path.Path>;
692
+ /**
693
+ * A cache rooted under the application's XDG cache directory:
694
+ * `<AppDirs cache>/<namespace>/`.
695
+ *
696
+ * @remarks
697
+ * Uses `AppDirs.ensureCache`, which also discharges the store's recorded
698
+ * constraint that the database directory must exist before
699
+ * `SqliteClient.layer` is built. This package never builds the store layer
700
+ * itself — the consumer composes `Cache.layerSqlite` (or `layerTest`) at
701
+ * the edge.
702
+ */
703
+ static layerXdg(options?: {
704
+ readonly namespace?: string;
705
+ }): Layer.Layer<TypeCache, AppDirsError, Cache | AppDirs | FileSystem.FileSystem | Path.Path>;
706
+ }
707
+ //#endregion
708
+ //#region src/TypeResolver.d.ts
709
+ declare const ResolvedModule_base: Schema.Class<ResolvedModule, Schema.Struct<{
710
+ /** The file path relative to the package root (no `./` prefix). */
711
+ readonly filePath: Schema.String;
712
+ /** Whether the path names a TypeScript declaration file. */
713
+ readonly isTypeDefinition: Schema.Boolean;
714
+ /** The package the path belongs to. */
715
+ readonly package: typeof PackageSpec;
716
+ }>, {}>;
717
+ /**
718
+ * A resolved module: the declaration file a specifier resolves to within a
719
+ * package.
720
+ *
721
+ * @public
722
+ */
723
+ declare class ResolvedModule extends ResolvedModule_base {}
724
+ /**
725
+ * Pure `package.json` → declaration-file resolution.
726
+ *
727
+ * @remarks
728
+ * Stateless pure functions — no service, no layer (v3's `Layer.succeed` over
729
+ * stateless functions was ceremony), and no fictional error channel: v3
730
+ * declared a `ResolutionError` its total implementation could never raise.
731
+ * Here {@link TypeResolver.resolveImport} is honest in the other direction
732
+ * too — it returns `Option.none()` where v3 fabricated a guessed fallback
733
+ * path, leaving fallback policy to the caller.
734
+ *
735
+ * All map inputs are untrusted CDN data; the machinery underneath is depth-
736
+ * guarded, wildcard-bounded and prototype-pollution-safe (see
737
+ * `internal/resolution.ts`), and every resolved path is validated to stay
738
+ * inside the package before a `ResolvedModule` is constructed.
739
+ *
740
+ * @public
741
+ */
742
+ declare class TypeResolver {
743
+ private constructor();
744
+ /**
745
+ * Resolve an import specifier (`"zod"`, `"zod/lib/types"`) against a
746
+ * manifest.
747
+ *
748
+ * @remarks
749
+ * Resolution order: the `exports` map (`types` condition, then
750
+ * `import`/`default`, fallback arrays in order), then `typesVersions["*"]`
751
+ * (exact, then bounded wildcards), then — for the root specifier only —
752
+ * the top-level `types`/`typings` fields. `Option.none()` when the
753
+ * manifest offers no evidence for the subpath, or when the evidence names
754
+ * a path outside the package (hostile manifest — fails closed).
755
+ */
756
+ static resolveImport(specifier: string, manifest: PackageManifest, pkg: PackageSpec): Option.Option<ResolvedModule>;
757
+ /**
758
+ * Resolve the manifest's main type entry.
759
+ *
760
+ * @remarks
761
+ * Total by the documented `index.d.ts` convention floor: `types`/`typings`,
762
+ * then the root export's types condition, then a declaration-extension
763
+ * swap of `main`, then `index.d.ts`. A main path that escapes the package
764
+ * (hostile manifest) also falls to the floor rather than surviving.
765
+ */
766
+ static resolveMainEntry(manifest: PackageManifest, pkg: PackageSpec): ResolvedModule;
767
+ /**
768
+ * Enumerate every entry point that exposes type definitions: the main
769
+ * entry plus each `exports` subpath with a types-bearing condition,
770
+ * deduplicated by file path.
771
+ *
772
+ * @remarks
773
+ * Wildcard export keys (`"./*"`) are skipped: enumeration has no captured
774
+ * segment to substitute, so a pattern entry would emit a literal
775
+ * `dist/*.d.ts`. Pattern subpaths resolve through
776
+ * {@link TypeResolver.resolveImport}, which has the concrete specifier.
777
+ * Entries whose paths escape the package are skipped.
778
+ */
779
+ static resolveTypeEntries(manifest: PackageManifest, pkg: PackageSpec): ReadonlyArray<ResolvedModule>;
780
+ /**
781
+ * The conventional declaration-file path for a JavaScript file path
782
+ * (`lib/index.js` → `lib/index.d.ts`, `.mjs` → `.d.mts`, `.cjs` →
783
+ * `.d.cts`).
784
+ *
785
+ * @remarks
786
+ * The input is a tree path from the CDN — untrusted — so a path that is
787
+ * absolute or escapes the package yields `Option.none()` instead of a
788
+ * `ResolvedModule` that could reach a download URL.
789
+ */
790
+ static findTypeDefinition(jsFilePath: string, pkg: PackageSpec): Option.Option<ResolvedModule>;
791
+ }
792
+ //#endregion
793
+ //#region src/TypeRegistry.d.ts
794
+ declare const BatchLoadError_base: Schema.Class<BatchLoadError, Schema.TaggedStruct<"BatchLoadError", {
795
+ /** One entry per failed package, with its typed error preserved. */
796
+ readonly failures: Schema.$Array<Schema.Struct<{
797
+ readonly name: Schema.String;
798
+ readonly version: Schema.String;
799
+ readonly error: Schema.Defect;
800
+ }>>;
801
+ }>, import("effect/Cause").YieldableError>;
802
+ /**
803
+ * Raised by {@link TypeRegistryShape.getVfs} when **every** requested package
804
+ * fails.
805
+ *
806
+ * @remarks
807
+ * Carries the per-package failures structurally. v3 abused
808
+ * `PackageNotFoundError` for this case, with a comma-joined `name` and an
809
+ * empty `version`.
810
+ *
811
+ * @public
812
+ */
813
+ declare class BatchLoadError extends BatchLoadError_base {
814
+ get message(): string;
815
+ }
816
+ /**
817
+ * Options for {@link TypeRegistryShape.getPackageVfs} and
818
+ * {@link TypeRegistryShape.getVfs}.
819
+ *
820
+ * @public
821
+ */
822
+ interface PackageVfsOptions {
823
+ /**
824
+ * Fetch from the CDN when the package is missing or stale. Defaults to
825
+ * `true`. With `false`, a stale entry is served from disk and a miss fails
826
+ * with `PackageNotFoundError`.
827
+ */
828
+ readonly autoFetch?: boolean;
829
+ /** Time-to-live recorded for newly cached entries; absent = never expires. */
830
+ readonly ttl?: Duration.Duration;
831
+ }
832
+ /**
833
+ * The service shape {@link TypeRegistry} provides.
834
+ *
835
+ * @public
836
+ */
837
+ interface TypeRegistryShape {
838
+ /** Report whether the package's files are already on disk. */
839
+ readonly hasCached: (pkg: PackageSpec) => Effect.Effect<boolean, TypeCacheError>;
840
+ /** Fetch a pinned package's manifest and declaration files and cache them. */
841
+ readonly fetchAndCache: (pkg: PackageSpec, options?: {
842
+ readonly ttl?: Duration.Duration;
843
+ }) => Effect.Effect<void, FetchError | PackageNotFoundError | TypeCacheError>;
844
+ /**
845
+ * Build the {@link Vfs} for one package, fetching it first when missing or
846
+ * stale (the stale-vs-miss ladder: live metadata → hit; files on disk with
847
+ * no live metadata → stale, refetched when `autoFetch`, served as-is
848
+ * otherwise; nothing → miss, fetched or failed typed on
849
+ * `autoFetch: false`).
850
+ */
851
+ readonly getPackageVfs: (pkg: PackageSpec, options?: PackageVfsOptions) => Effect.Effect<Vfs, FetchError | PackageNotFoundError | TypeCacheError>;
852
+ /**
853
+ * Build a merged {@link Vfs} for several packages, best-effort.
854
+ *
855
+ * @remarks
856
+ * Loads concurrently (limit 5), accumulates per-package failures, merges
857
+ * the partial results, and fails — with a structured
858
+ * {@link BatchLoadError} — only when every package fails. An empty
859
+ * `packages` array is not an error: it yields an empty `Vfs`.
860
+ */
861
+ readonly getVfs: (packages: ReadonlyArray<PackageSpec>, options?: PackageVfsOptions) => Effect.Effect<Vfs, BatchLoadError>;
862
+ /**
863
+ * Resolve an import specifier against a cached package's manifest.
864
+ * `Option.none()` when the manifest offers no evidence for the subpath.
865
+ */
866
+ readonly resolveImport: (pkg: PackageSpec, specifier: string) => Effect.Effect<Option.Option<ResolvedModule>, TypeCacheError | FetchError>;
867
+ /** Enumerate a cached package's type entry points. */
868
+ readonly getTypeEntries: (pkg: PackageSpec) => Effect.Effect<ReadonlyArray<ResolvedModule>, TypeCacheError | FetchError>;
869
+ /**
870
+ * Resolve a version reference — dist-tag, exact version or semver range —
871
+ * to a pinned version string, locally.
872
+ *
873
+ * @remarks
874
+ * Dist-tags resolve through the CDN's tag map; exact versions match the
875
+ * published list; ranges resolve with `@effected/semver`
876
+ * (max-satisfying). No CDN `/resolve` endpoint, no error-prose parsing —
877
+ * an unmatched ref fails as a typed {@link VersionNotFoundError}.
878
+ */
879
+ readonly resolveVersion: (name: string, ref: string) => Effect.Effect<string, FetchError | VersionNotFoundError>;
880
+ /** Remove one package from the cache (metadata first, then files). */
881
+ readonly clearCache: (pkg: PackageSpec) => Effect.Effect<void, TypeCacheError>;
882
+ /** Evict every expired package from the cache. */
883
+ readonly pruneCache: Effect.Effect<CachePruneResult, TypeCacheError>;
884
+ }
885
+ declare const TypeRegistry_base: Context.ServiceClass<TypeRegistry, "type-registry-effect/TypeRegistry", TypeRegistryShape>;
886
+ /**
887
+ * The facade: one service collapsing the cache, fetcher and resolver behind
888
+ * the operations documentation tooling actually calls.
889
+ *
890
+ * @remarks
891
+ * `yield* TypeRegistry` replaces the v3 floating-function namespace (which
892
+ * the rspress consumer immediately re-wrapped in its own service). Per-method
893
+ * error unions stay precise. Compose at the edge: platform layers + store
894
+ * `Cache.layerSqlite` + `TypeCache.layerXdg` + `PackageFetcher.layer` +
895
+ * `TypeRegistry.layer`.
896
+ *
897
+ * @example
898
+ * ```ts
899
+ * import { PackageSpec, TypeRegistry } from "@tsdoctor/registry";
900
+ * import { Effect } from "effect";
901
+ *
902
+ * const program = Effect.gen(function* () {
903
+ * const registry = yield* TypeRegistry;
904
+ * return yield* registry.getVfs([PackageSpec.fromString("zod@3.23.8")]);
905
+ * });
906
+ * ```
907
+ *
908
+ * @public
909
+ */
910
+ declare class TypeRegistry extends TypeRegistry_base {
911
+ /** The live facade over {@link TypeCache} and {@link PackageFetcher}. */
912
+ static readonly layer: Layer.Layer<TypeRegistry, never, TypeCache | PackageFetcher>;
913
+ }
914
+ //#endregion
915
+ //#region src/VirtualPackage.d.ts
916
+ declare const VirtualPackage_base: Schema.Class<VirtualPackage, Schema.Struct<{
917
+ /** The package name (e.g. `"@my-org/api-types"`). */
918
+ readonly name: Schema.String;
919
+ /** The package version. */
920
+ readonly version: Schema.String;
921
+ /** Entry file names (e.g. `"index.d.ts"`) mapped to declaration source. */
922
+ readonly entries: Schema.$ReadonlyMap<Schema.String, Schema.String>;
923
+ }>, {}>;
924
+ /**
925
+ * A synthetic npm package built from locally supplied TypeScript declaration
926
+ * content, for inclusion in a {@link Vfs} without fetching from the CDN.
927
+ *
928
+ * @remarks
929
+ * Useful when you have locally generated `.d.ts` files — API Extractor
930
+ * output, hand-written ambient declarations — and want them in the same VFS
931
+ * `TypeRegistry` builds from remote packages. Instances are transient: they
932
+ * are never persisted to the disk cache.
933
+ *
934
+ * The class is deliberately subclass-friendly (the rspress consumer extends
935
+ * it): construct via `VirtualPackage.make(...)` or the statics, and extend
936
+ * with `class Mine extends VirtualPackage { ... }`.
937
+ *
938
+ * @example
939
+ * ```ts
940
+ * import { VirtualPackage } from "@tsdoctor/registry";
941
+ *
942
+ * const pkg = VirtualPackage.create("@my-org/api-types", "1.0.0", "export interface User { id: string }");
943
+ * const vfs = pkg.toVfs();
944
+ * // node_modules/@my-org/api-types/package.json, node_modules/@my-org/api-types/index.d.ts
945
+ * ```
946
+ *
947
+ * @public
948
+ */
949
+ declare class VirtualPackage extends VirtualPackage_base {
950
+ /**
951
+ * Single-entry factory: a virtual package whose sole entry point is
952
+ * `index.d.ts`.
953
+ */
954
+ static create(name: string, version: string, declarations: string): VirtualPackage;
955
+ /**
956
+ * Multi-entry factory: one `.d.ts` per entry point, exposed through a
957
+ * synthetic `exports` map.
958
+ *
959
+ * @remarks
960
+ * An empty entries map is developer wiring, not input — it would produce a
961
+ * package whose `types` points at a file that does not exist — so it
962
+ * throws at construction (defect posture), as does an entry set whose
963
+ * names collide after extension normalization (see
964
+ * {@link VirtualPackage.toVfs}).
965
+ */
966
+ static createMultiEntry(name: string, version: string, entries: ReadonlyMap<string, string>): VirtualPackage;
967
+ /**
968
+ * Load a single `.d.ts` file from disk as a virtual package with one
969
+ * `index.d.ts` entry.
970
+ *
971
+ * @remarks
972
+ * Reads through the platform-agnostic `FileSystem` service; the
973
+ * `PlatformError` surfaces typed.
974
+ */
975
+ static fromFile(name: string, version: string, filePath: string): Effect.Effect<VirtualPackage, PlatformError.PlatformError, FileSystem.FileSystem>;
976
+ /**
977
+ * The package's {@link Vfs}: a synthetic `package.json` plus every entry
978
+ * file, each path prefixed `node_modules/<name>/`.
979
+ *
980
+ * @remarks
981
+ * The `package.json` uses `types` for a single entry and an `exports` map
982
+ * for multiple entries, so TypeScript module resolution works against the
983
+ * generated VFS.
984
+ */
985
+ toVfs(): Vfs;
986
+ private toPackageJson;
987
+ }
988
+ //#endregion
989
+ export { BatchLoadError, type CachePruneResult, FetchError, PackageFetcher, type PackageFetcherShape, PackageManifest, PackageNotFoundError, PackageSpec, type PackageVersions, type PackageVfsOptions, RegistryEvent, RegistryObserver, type RegistryObserverShape, ResolvedModule, TsEnvironment, TsEnvironmentError, type TsEnvironmentOptions, TypeCache, TypeCacheError, TypeCacheMetadata, type TypeCacheShape, TypeRegistry, type TypeRegistryShape, TypeResolver, VersionNotFoundError, type Vfs, type VirtualFileSystem, VirtualPackage, mergeVfs, prefixVfs };
990
+ //# sourceMappingURL=index.d.ts.map