@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/LICENSE +21 -0
- package/PackageFetcher.js +241 -0
- package/PackageSpec.js +182 -0
- package/README.md +94 -0
- package/RegistryEvent.js +146 -0
- package/TsEnvironment.js +95 -0
- package/TypeCache.js +256 -0
- package/TypeRegistry.js +276 -0
- package/TypeResolver.js +183 -0
- package/Vfs.js +33 -0
- package/VirtualPackage.js +126 -0
- package/index.d.ts +990 -0
- package/index.js +11 -0
- package/internal/jsdelivr.js +62 -0
- package/internal/limits.js +16 -0
- package/internal/resolution.js +181 -0
- package/package.json +67 -0
- package/tsdoc-metadata.json +11 -0
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
|