runtime-resolver 0.1.0 → 0.3.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 CHANGED
@@ -1,324 +1,2287 @@
1
1
  import { Context } from 'effect';
2
- import type { Effect } from 'effect';
3
- import { Equals } from 'effect/Types';
2
+ import { DateTime } from 'effect';
3
+ import { Effect } from 'effect';
4
+ import type { EmptyCacheError } from 'semver-effect';
5
+ import type { InvalidRangeError } from 'semver-effect';
6
+ import type { InvalidVersionError } from 'semver-effect';
4
7
  import { Layer } from 'effect';
8
+ import { Option } from 'effect';
9
+ import { Ref } from 'effect';
5
10
  import { Schema } from 'effect';
11
+ import { SemVer } from 'semver-effect';
12
+ import type { UnsatisfiedRangeError } from 'semver-effect';
13
+ import { VersionCache } from 'semver-effect';
14
+ import { VoidIfEmpty } from 'effect/Types';
6
15
  import { YieldableError } from 'effect/Cause';
7
16
 
8
- export declare class AuthenticationError extends AuthenticationErrorBase<{
17
+ /**
18
+ * Raised when a GitHub authentication attempt fails.
19
+ *
20
+ * This error is produced by the {@link GitHubTokenAuth} and
21
+ * {@link GitHubAppAuth} layers when the supplied credentials are rejected by
22
+ * the GitHub API or when required configuration values (e.g. a token or app
23
+ * private key) are missing from the environment.
24
+ *
25
+ * @see {@link GitHubTokenAuth}
26
+ * @see {@link GitHubAppAuth}
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * import { Effect } from "effect";
31
+ * import type { AuthenticationError } from "./AuthenticationError.js";
32
+ * import { resolveNode } from "../resolvers/node.js";
33
+ *
34
+ * const program = resolveNode(">=20").pipe(
35
+ * Effect.catchTag("AuthenticationError", (err: AuthenticationError) =>
36
+ * Effect.logError(
37
+ * `Auth failed via ${err.method}: ${err.message}`
38
+ * ).pipe(Effect.andThen(Effect.fail(err)))
39
+ * )
40
+ * );
41
+ * ```
42
+ *
43
+ * @public
44
+ */
45
+ export declare class AuthenticationError extends AuthenticationError_base<{
9
46
  readonly method: "token" | "app";
10
47
  readonly message: string;
11
48
  }> {
12
49
  }
13
50
 
14
- /**
15
- * @internal
16
- * Exported for declaration bundling (api-extractor). When `export *` re-exports
17
- * a class whose `extends` expression is an inline call like
18
- * `Data.TaggedError(...)`, TypeScript emits an un-nameable `_base` symbol in
19
- * the declaration file. Splitting the base into a named export gives the
20
- * bundler a stable reference.
21
- */
22
- export declare const AuthenticationErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
51
+ declare const AuthenticationError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
23
52
  readonly _tag: "AuthenticationError";
24
53
  } & Readonly<A>;
25
54
 
26
55
  /**
27
- * Service interface for resolving Bun runtime versions.
56
+ * Provides the {@link BunReleaseCache} service using the "Auto" cache strategy.
57
+ *
58
+ * Attempts to fetch live release data from the GitHub API via
59
+ * {@link BunVersionFetcher}. If the fetch fails for any reason — authentication
60
+ * errors, network errors, parse errors, or rate-limit errors — it silently falls
61
+ * back to the bundled default release data so the layer always succeeds.
62
+ *
63
+ * Use this strategy when you want the freshest data when available but can
64
+ * tolerate slightly stale bundled data in degraded environments.
65
+ *
66
+ * The three Bun cache strategy layers are:
67
+ * - `AutoBunCacheLive` — tries API first, falls back to bundled defaults on any error
68
+ * - {@link FreshBunCacheLive} — requires live API data, fails with `FreshnessError` if unavailable
69
+ * - {@link OfflineBunCacheLive} — uses only bundled defaults, no network I/O
70
+ *
71
+ * @see {@link BunReleaseCache}
72
+ * @see {@link FreshBunCacheLive}
73
+ * @see {@link OfflineBunCacheLive}
74
+ * @public
75
+ */
76
+ export declare const AutoBunCacheLive: Layer.Layer<BunReleaseCache, never, BunVersionFetcher>;
77
+
78
+ /**
79
+ * Provides the {@link DenoReleaseCache} service using the "Auto" cache strategy.
80
+ *
81
+ * Attempts to fetch live release data from the GitHub API via
82
+ * {@link DenoVersionFetcher}. If the fetch fails for any reason — authentication
83
+ * errors, network errors, parse errors, or rate-limit errors — it silently falls
84
+ * back to the bundled default release data so the layer always succeeds.
85
+ *
86
+ * Use this strategy when you want the freshest data when available but can
87
+ * tolerate slightly stale bundled data in degraded environments.
88
+ *
89
+ * The three Deno cache strategy layers are:
90
+ * - `AutoDenoCacheLive` — tries API first, falls back to bundled defaults on any error
91
+ * - {@link FreshDenoCacheLive} — requires live API data, fails with `FreshnessError` if unavailable
92
+ * - {@link OfflineDenoCacheLive} — uses only bundled defaults, no network I/O
93
+ *
94
+ * @see {@link DenoReleaseCache}
95
+ * @see {@link FreshDenoCacheLive}
96
+ * @see {@link OfflineDenoCacheLive}
97
+ * @public
98
+ */
99
+ export declare const AutoDenoCacheLive: Layer.Layer<DenoReleaseCache, never, DenoVersionFetcher>;
100
+
101
+ /**
102
+ * Provides the {@link NodeReleaseCache} service using the "Auto" cache strategy.
103
+ *
104
+ * Attempts to fetch live release data from `nodejs.org/dist/index.json` via
105
+ * {@link NodeVersionFetcher} and the release schedule from the `nodejs/Release`
106
+ * repository via {@link NodeScheduleFetcher}. If either fetch fails — due to
107
+ * network errors or parse errors — the layer silently falls back to bundled
108
+ * default data (both version list and schedule) so it always succeeds.
109
+ *
110
+ * Node.js requires both a version fetcher and a schedule fetcher because LTS and
111
+ * maintenance phase information is tracked separately from the version index.
112
+ *
113
+ * The three Node cache strategy layers are:
114
+ * - `AutoNodeCacheLive` — tries API first, falls back to bundled defaults on any error
115
+ * - {@link FreshNodeCacheLive} — requires live API data, fails with `FreshnessError` if unavailable
116
+ * - {@link OfflineNodeCacheLive} — uses only bundled defaults, no network I/O
117
+ *
118
+ * @see {@link NodeReleaseCache}
119
+ * @see {@link FreshNodeCacheLive}
120
+ * @see {@link OfflineNodeCacheLive}
121
+ * @public
122
+ */
123
+ export declare const AutoNodeCacheLive: Layer.Layer<NodeReleaseCache, never, NodeVersionFetcher | NodeScheduleFetcher>;
124
+
125
+ /**
126
+ * A Bun release entry with a parsed {@link SemVer} version and an
127
+ * Effect {@link DateTime.DateTime} publication date.
128
+ *
129
+ * `BunRelease` extends `Data.TaggedClass` so instances support structural
130
+ * equality comparison and can be used safely inside Effect data structures.
131
+ * Construct instances via the {@link BunRelease.fromInput} factory rather than
132
+ * calling `new BunRelease(...)` directly, as the factory validates the version
133
+ * string through `SemVer.parse`.
134
+ *
135
+ * @see {@link BunReleaseCache}
136
+ * @see {@link BunResolver}
137
+ * @see {@link RuntimeReleaseInput}
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * import { Effect } from "effect";
142
+ * import type { BunRelease } from "./bun-release.js";
143
+ * import { BunRelease as BunReleaseClass } from "./bun-release.js";
144
+ *
145
+ * const program = BunReleaseClass.fromInput({ version: "1.1.0", date: "2024-03-01" }).pipe(
146
+ * Effect.map((release: BunRelease) => {
147
+ * console.log(release.version.major); // 1
148
+ * console.log(release.version.minor); // 1
149
+ * console.log(release._tag); // "BunRelease"
150
+ * }),
151
+ * );
152
+ *
153
+ * Effect.runPromise(program);
154
+ * ```
155
+ *
156
+ * @public
28
157
  */
29
- export declare interface BunResolver {
30
- readonly resolve: (options?: BunResolverOptions) => Effect.Effect<ResolvedVersions, BunResolverError>;
31
- readonly resolveVersion: (versionOrRange: string) => Effect.Effect<string, BunResolverError>;
158
+ export declare class BunRelease extends BunRelease_base<{
159
+ readonly version: SemVer;
160
+ readonly date: DateTime.DateTime;
161
+ }> {
162
+ /**
163
+ * Create a {@link BunRelease} from lean string inputs.
164
+ *
165
+ * Parses `input.version` via `SemVer.parse` and constructs the
166
+ * publication date with `DateTime.unsafeMake`. Falls back to the current
167
+ * timestamp when `input.date` is an empty string (rare in production data).
168
+ *
169
+ * @param input - A {@link RuntimeReleaseInput} object with raw string fields.
170
+ * @returns An `Effect` that succeeds with a `BunRelease` or fails with an
171
+ * `InvalidVersionError` when the version string cannot be parsed.
172
+ */
173
+ static fromInput(input: RuntimeReleaseInput): Effect.Effect<BunRelease, InvalidVersionError>;
32
174
  }
33
175
 
176
+ declare const BunRelease_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
177
+ readonly _tag: "BunRelease";
178
+ };
179
+
34
180
  /**
35
- * BunResolver tag for dependency injection.
181
+ * Specialisation of {@link RuntimeCache} for Bun release objects.
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * import type { BunRelease } from "runtime-resolver";
186
+ * import { BunReleaseCache, OfflineBunCacheLive } from "runtime-resolver";
187
+ * import { Effect } from "effect";
188
+ *
189
+ * const program = Effect.gen(function* () {
190
+ * const cache = yield* BunReleaseCache;
191
+ * const releases = yield* cache.releases();
192
+ * console.log(releases.length);
193
+ * });
194
+ *
195
+ * Effect.runPromise(program.pipe(Effect.provide(OfflineBunCacheLive)));
196
+ * ```
197
+ *
198
+ * @see {@link BunRelease}
199
+ * @see {@link RuntimeCache}
200
+ * @see {@link AutoBunCacheLive}
201
+ * @see {@link FreshBunCacheLive}
202
+ * @see {@link OfflineBunCacheLive}
203
+ *
204
+ * @public
205
+ */
206
+ export declare class BunReleaseCache extends BunReleaseCache_base {
207
+ }
208
+
209
+ declare const BunReleaseCache_base: Context.TagClass<BunReleaseCache, "runtime-resolver/BunReleaseCache", RuntimeCache<BunRelease>>;
210
+
211
+ /**
212
+ * Low-level layer that constructs the {@link BunReleaseCache} service backed by
213
+ * a `SemVerVersionCache` instance from `semver-effect`.
214
+ *
215
+ * This layer wires up the cache storage but does not populate it with any
216
+ * release data. Callers are responsible for loading releases after construction.
217
+ * In normal usage you should prefer the higher-level cache strategy layers
218
+ * instead of using this layer directly:
219
+ * - {@link AutoBunCacheLive} — API with fallback to bundled defaults
220
+ * - {@link FreshBunCacheLive} — API only, fails if unavailable
221
+ * - {@link OfflineBunCacheLive} — bundled defaults only
222
+ *
223
+ * @see {@link BunReleaseCache}
224
+ * @see {@link createRuntimeCache}
225
+ * @internal
226
+ */
227
+ export declare const BunReleaseCacheLive: Layer.Layer<BunReleaseCache, never, VersionCache>;
228
+
229
+ /**
230
+ * Service for resolving Bun runtime versions against the cached
231
+ * release index.
232
+ *
233
+ * For a one-shot Promise-based API see {@link resolveBun}.
234
+ *
235
+ * @example
236
+ * ```typescript
237
+ * import type { ResolvedVersions } from "runtime-resolver";
238
+ * import { BunResolver, BunResolverLive } from "runtime-resolver";
239
+ * import { Effect } from "effect";
240
+ *
241
+ * const program = Effect.gen(function* () {
242
+ * const resolver = yield* BunResolver;
243
+ * const result = yield* resolver.resolve({ semverRange: "^1.0.0", increments: "minor" });
244
+ * console.log(result.latest);
245
+ * console.log(result.versions);
246
+ * });
36
247
  *
37
- * @internal Uses GenericTag to avoid generating un-nameable `_base` types
38
- * that break the declaration bundler (api-extractor) when re-exported via
39
- * `export *`.
248
+ * Effect.runPromise(program.pipe(Effect.provide(BunResolverLive)));
249
+ * ```
250
+ *
251
+ * @see {@link BunResolverLive}
252
+ * @see {@link resolveBun}
253
+ * @see {@link BunResolverOptions}
254
+ * @see {@link ResolvedVersions}
255
+ *
256
+ * @public
40
257
  */
41
- export declare const BunResolver: Context.Tag<BunResolver, BunResolver>;
258
+ export declare class BunResolver extends BunResolver_base {
259
+ }
42
260
 
43
- export declare type BunResolverError = AuthenticationError | NetworkError | ParseError | RateLimitError | VersionNotFoundError | InvalidInputError | FreshnessError | CacheError;
261
+ declare const BunResolver_base: Context.TagClass<BunResolver, "runtime-resolver/BunResolver", {
262
+ /**
263
+ * Resolves Bun versions according to `options` and returns a
264
+ * {@link ResolvedVersions} object containing the matching version list,
265
+ * the latest version string, and the data source indicator.
266
+ *
267
+ * Fails with {@link BunResolverError} when no version can be resolved and
268
+ * no `defaultVersion` was provided.
269
+ *
270
+ * @param options - Optional resolution constraints.
271
+ *
272
+ * @see {@link BunResolverOptions}
273
+ */
274
+ readonly resolve: (options?: BunResolverOptions | undefined) => Effect.Effect<{
275
+ readonly source: "api" | "cache";
276
+ readonly versions: readonly string[];
277
+ readonly latest: string;
278
+ readonly lts?: string | undefined;
279
+ readonly default?: string | undefined;
280
+ }, VersionNotFoundError, never>;
281
+ }>;
44
282
 
45
- export declare const BunResolverLive: Layer.Layer<BunResolver, never, GitHubClient | VersionCache>;
283
+ /**
284
+ * Union of all typed errors that {@link BunResolver.resolve} can fail with.
285
+ *
286
+ * @see {@link VersionNotFoundError}
287
+ *
288
+ * @public
289
+ */
290
+ export declare type BunResolverError = VersionNotFoundError;
46
291
 
292
+ /**
293
+ * Provides the {@link BunResolver} service backed by a {@link BunReleaseCache}.
294
+ *
295
+ * This layer composes with any of the three Bun cache strategy layers to form
296
+ * a complete resolver stack. It implements semver range filtering and increment
297
+ * grouping (latest per major, latest per minor, or all patch versions) using
298
+ * the releases already loaded into the cache.
299
+ *
300
+ * @example
301
+ * ```ts
302
+ * import { BunResolverLive, AutoBunCacheLive, BunVersionFetcherLive, GitHubClientLive, GitHubAutoAuth } from "runtime-resolver";
303
+ * import type { ResolvedVersions } from "runtime-resolver";
304
+ * import { BunResolver } from "runtime-resolver";
305
+ * import { Effect, Layer } from "effect";
306
+ *
307
+ * const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));
308
+ * const CacheLayer = AutoBunCacheLive.pipe(Layer.provide(BunVersionFetcherLive.pipe(Layer.provide(GitHubLayer))));
309
+ * const ResolverLayer = BunResolverLive.pipe(Layer.provide(CacheLayer));
310
+ *
311
+ * const program = Effect.gen(function* () {
312
+ * const resolver = yield* BunResolver;
313
+ * const result = yield* resolver.resolve({ semverRange: "^1.0.0" });
314
+ * console.log(result.latest);
315
+ * });
316
+ *
317
+ * Effect.runPromise(program.pipe(Effect.provide(ResolverLayer)));
318
+ * ```
319
+ *
320
+ * @see {@link BunResolver}
321
+ * @see {@link BunReleaseCache}
322
+ * @see {@link AutoBunCacheLive}
323
+ * @see {@link FreshBunCacheLive}
324
+ * @see {@link OfflineBunCacheLive}
325
+ * @public
326
+ */
327
+ export declare const BunResolverLive: Layer.Layer<BunResolver, never, BunReleaseCache>;
328
+
329
+ /**
330
+ * Options accepted by {@link BunResolver.resolve}.
331
+ *
332
+ * All fields are optional; omitting them causes the resolver to fall back to
333
+ * its built-in defaults (latest stable release, `"latest"` increment
334
+ * granularity).
335
+ *
336
+ * @see {@link BunResolver}
337
+ * @see {@link Increments}
338
+ * @see {@link ResolvedVersions}
339
+ *
340
+ * @public
341
+ */
47
342
  export declare interface BunResolverOptions {
343
+ /**
344
+ * A semver range string used to constrain which Bun releases are
345
+ * considered (e.g. `"^1.0.0"` or `">=1.1.0 <2.0.0"`).
346
+ *
347
+ * When omitted all cached releases are eligible.
348
+ */
48
349
  readonly semverRange?: string;
350
+ /**
351
+ * A fallback version string returned when no release satisfies
352
+ * `semverRange`. Must be a valid semver version (e.g. `"1.0.0"`).
353
+ *
354
+ * When omitted and no match is found the effect fails with
355
+ * {@link VersionNotFoundError}.
356
+ */
49
357
  readonly defaultVersion?: string;
358
+ /**
359
+ * Controls the granularity of the version list included in
360
+ * {@link ResolvedVersions.versions}.
361
+ *
362
+ * - `"latest"` — only the single highest matching version.
363
+ * - `"minor"` — the latest patch for every minor line.
364
+ * - `"patch"` — every individual patch release.
365
+ *
366
+ * Defaults to `"latest"` when omitted.
367
+ *
368
+ * @see {@link Increments}
369
+ */
50
370
  readonly increments?: Increments;
51
- readonly freshness?: Freshness;
52
- }
53
-
54
- export declare type CachedData = CachedNodeData | CachedTagData;
55
-
56
- export declare const CachedNodeData: Schema.Struct<{
57
- versions: Schema.Array$<Schema.Struct<{
58
- version: typeof Schema.String;
59
- date: typeof Schema.String;
60
- files: Schema.Array$<typeof Schema.String>;
61
- npm: Schema.optional<typeof Schema.String>;
62
- v8: Schema.optional<typeof Schema.String>;
63
- uv: Schema.optional<typeof Schema.String>;
64
- zlib: Schema.optional<typeof Schema.String>;
65
- openssl: Schema.optional<typeof Schema.String>;
66
- modules: Schema.optional<typeof Schema.String>;
67
- lts: Schema.Union<[Schema.Literal<[false]>, typeof Schema.String]>;
68
- security: typeof Schema.Boolean;
69
- }>>;
70
- schedule: Schema.Record$<typeof Schema.String, Schema.Struct<{
71
- start: typeof Schema.String;
72
- lts: Schema.optional<typeof Schema.String>;
73
- maintenance: Schema.optional<typeof Schema.String>;
74
- end: typeof Schema.String;
75
- codename: Schema.optional<typeof Schema.String>;
76
- }>>;
77
- }>;
371
+ }
372
+
373
+ /**
374
+ * Service for fetching Bun release data from the GitHub API.
375
+ *
376
+ * Retrieves both the parsed semver version list and the raw release input
377
+ * records needed to populate a {@link BunReleaseCache}. The live
378
+ * implementation queries the `oven-sh/bun` GitHub repository via
379
+ * {@link GitHubClient}.
380
+ *
381
+ * @example
382
+ * ```typescript
383
+ * import type { RuntimeReleaseInput } from "runtime-resolver";
384
+ * import { BunVersionFetcher, BunVersionFetcherLive, GitHubAutoAuth } from "runtime-resolver";
385
+ * import { Effect } from "effect";
386
+ *
387
+ * const program = Effect.gen(function* () {
388
+ * const fetcher = yield* BunVersionFetcher;
389
+ * const { versions, inputs } = yield* fetcher.fetch();
390
+ * console.log(`Fetched ${versions.length} Bun releases`);
391
+ * });
392
+ *
393
+ * Effect.runPromise(
394
+ * program.pipe(Effect.provide(BunVersionFetcherLive), Effect.provide(GitHubAutoAuth)),
395
+ * );
396
+ * ```
397
+ *
398
+ * @see {@link BunVersionFetcherLive}
399
+ * @see {@link BunReleaseCache}
400
+ * @see {@link AuthenticationError}
401
+ * @see {@link NetworkError}
402
+ * @see {@link ParseError}
403
+ * @see {@link RateLimitError}
404
+ *
405
+ * @public
406
+ */
407
+ export declare class BunVersionFetcher extends BunVersionFetcher_base {
408
+ }
78
409
 
79
- export declare type CachedNodeData = typeof CachedNodeData.Type;
80
-
81
- export declare const CachedTagData: Schema.Struct<{
82
- tags: Schema.Array$<Schema.Struct<{
83
- name: typeof Schema.String;
84
- zipball_url: typeof Schema.String;
85
- tarball_url: typeof Schema.String;
86
- commit: Schema.Struct<{
87
- sha: typeof Schema.String;
88
- url: typeof Schema.String;
89
- }>;
90
- node_id: typeof Schema.String;
91
- }>>;
410
+ declare const BunVersionFetcher_base: Context.TagClass<BunVersionFetcher, "runtime-resolver/BunVersionFetcher", {
411
+ /**
412
+ * Fetches all available Bun releases from the GitHub API and returns both
413
+ * the parsed semver version objects and the raw release input records.
414
+ *
415
+ * Fails with:
416
+ * - {@link AuthenticationError} — missing or invalid GitHub credentials.
417
+ * - {@link NetworkError} — the upstream request could not be completed.
418
+ * - {@link ParseError} — the response payload could not be decoded.
419
+ * - {@link RateLimitError} — the GitHub API rate limit was exceeded.
420
+ */
421
+ readonly fetch: () => Effect.Effect<{
422
+ readonly versions: readonly SemVer[];
423
+ readonly inputs: readonly {
424
+ readonly version: string;
425
+ readonly date: string;
426
+ }[];
427
+ }, AuthenticationError | NetworkError | ParseError | RateLimitError, never>;
92
428
  }>;
93
429
 
94
- export declare type CachedTagData = typeof CachedTagData.Type;
430
+ /**
431
+ * Provides the {@link BunVersionFetcher} service using the live GitHub API.
432
+ *
433
+ * Fetches Bun releases from the `oven-sh/bun` GitHub repository using the
434
+ * provided {@link GitHubClient}. Draft releases and pre-releases are excluded.
435
+ * Tag names are normalized by stripping the `bun-` prefix and any leading `v`
436
+ * or `V` before parsing as semver.
437
+ *
438
+ * This layer is a required dependency of {@link AutoBunCacheLive} and
439
+ * {@link FreshBunCacheLive}. It is not needed by {@link OfflineBunCacheLive}.
440
+ *
441
+ * @see {@link BunVersionFetcher}
442
+ * @see {@link GitHubClient}
443
+ * @see {@link AutoBunCacheLive}
444
+ * @see {@link FreshBunCacheLive}
445
+ * @public
446
+ */
447
+ export declare const BunVersionFetcherLive: Layer.Layer<BunVersionFetcher, never, GitHubClient>;
95
448
 
96
- export declare class CacheError extends CacheErrorBase<{
449
+ /**
450
+ * Raised when a cache read or write operation fails.
451
+ *
452
+ * This error surfaces when the local on-disk version cache cannot be accessed
453
+ * or updated. The `operation` field distinguishes between failures that occur
454
+ * while loading cached data (`"read"`) and failures that occur while
455
+ * persisting new data (`"write"`).
456
+ *
457
+ * @example
458
+ * ```typescript
459
+ * import { Effect } from "effect";
460
+ * import type { CacheError } from "./CacheError.js";
461
+ * import { resolveNode } from "../resolvers/node.js";
462
+ *
463
+ * const program = resolveNode(">=20").pipe(
464
+ * Effect.catchTag("CacheError", (err: CacheError) =>
465
+ * Effect.logWarning(
466
+ * `Cache ${err.operation} failed: ${err.message}`
467
+ * ).pipe(Effect.andThen(Effect.fail(err)))
468
+ * )
469
+ * );
470
+ * ```
471
+ *
472
+ * @public
473
+ */
474
+ export declare class CacheError extends CacheError_base<{
97
475
  readonly operation: "read" | "write";
98
476
  readonly message: string;
99
477
  }> {
100
478
  }
101
479
 
480
+ declare const CacheError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
481
+ readonly _tag: "CacheError";
482
+ } & Readonly<A>;
483
+
484
+ /**
485
+ * Factory that creates a {@link RuntimeCache} implementation backed by
486
+ * `semver-effect`'s `VersionCache`.
487
+ *
488
+ * The returned `Effect` must be executed within an Effect context that provides
489
+ * a `SemVerVersionCache` service (i.e., it requires `SemVerVersionCache` in its
490
+ * environment). The cache maintains an internal lookup map from version string
491
+ * to the full release object so that resolved semver versions can be
492
+ * round-tripped back to their typed release records.
493
+ *
494
+ * This factory is used internally by {@link BunReleaseCacheLive},
495
+ * {@link DenoReleaseCacheLive}, and {@link NodeReleaseCacheLive} to build their
496
+ * respective cache service implementations. Prefer those higher-level layers
497
+ * over calling this factory directly.
498
+ *
499
+ * Note: `load` is not concurrency-safe. Callers must not invoke it concurrently.
500
+ * In practice this constraint is upheld because `load` is only called from layer
501
+ * setup and `NodeReleaseCacheLive.loadFromInputs`, both of which are serialized
502
+ * by the Effect runtime.
503
+ *
504
+ * @typeParam R - The runtime release type stored in the cache. Must extend
505
+ * {@link RuntimeRelease}.
506
+ *
507
+ * @see {@link RuntimeCache}
508
+ * @see {@link BunReleaseCacheLive}
509
+ * @see {@link DenoReleaseCacheLive}
510
+ * @see {@link NodeReleaseCacheLive}
511
+ * @public
512
+ */
513
+ export declare const createRuntimeCache: <R extends RuntimeRelease>() => Effect.Effect<RuntimeCache<R>, never, VersionCache>;
514
+
102
515
  /**
516
+ * A Deno release entry with a parsed {@link SemVer} version and an
517
+ * Effect {@link DateTime.DateTime} publication date.
518
+ *
519
+ * `DenoRelease` extends `Data.TaggedClass` so instances support structural
520
+ * equality comparison and can be used safely inside Effect data structures.
521
+ * Construct instances via the {@link DenoRelease.fromInput} factory rather than
522
+ * calling `new DenoRelease(...)` directly, as the factory validates the version
523
+ * string through `SemVer.parse`.
524
+ *
525
+ * @see {@link DenoReleaseCache}
526
+ * @see {@link DenoResolver}
527
+ * @see {@link RuntimeReleaseInput}
528
+ *
529
+ * @example
530
+ * ```typescript
531
+ * import { Effect } from "effect";
532
+ * import type { DenoRelease } from "./deno-release.js";
533
+ * import { DenoRelease as DenoReleaseClass } from "./deno-release.js";
534
+ *
535
+ * const program = DenoReleaseClass.fromInput({ version: "1.44.0", date: "2024-05-23" }).pipe(
536
+ * Effect.map((release: DenoRelease) => {
537
+ * console.log(release.version.major); // 1
538
+ * console.log(release.version.minor); // 44
539
+ * console.log(release._tag); // "DenoRelease"
540
+ * }),
541
+ * );
542
+ *
543
+ * Effect.runPromise(program);
544
+ * ```
545
+ *
546
+ * @public
547
+ */
548
+ export declare class DenoRelease extends DenoRelease_base<{
549
+ readonly version: SemVer;
550
+ readonly date: DateTime.DateTime;
551
+ }> {
552
+ /**
553
+ * Create a {@link DenoRelease} from lean string inputs.
554
+ *
555
+ * Parses `input.version` via `SemVer.parse` and constructs the
556
+ * publication date with `DateTime.unsafeMake`. Falls back to the current
557
+ * timestamp when `input.date` is an empty string (rare in production data).
558
+ *
559
+ * @param input - A {@link RuntimeReleaseInput} object with raw string fields.
560
+ * @returns An `Effect` that succeeds with a `DenoRelease` or fails with an
561
+ * `InvalidVersionError` when the version string cannot be parsed.
562
+ */
563
+ static fromInput(input: RuntimeReleaseInput): Effect.Effect<DenoRelease, InvalidVersionError>;
564
+ }
565
+
566
+ declare const DenoRelease_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
567
+ readonly _tag: "DenoRelease";
568
+ };
569
+
570
+ /**
571
+ * Specialisation of {@link RuntimeCache} for Deno release objects.
572
+ *
573
+ * @example
574
+ * ```typescript
575
+ * import type { DenoRelease } from "runtime-resolver";
576
+ * import { DenoReleaseCache, OfflineDenoCacheLive } from "runtime-resolver";
577
+ * import { Effect } from "effect";
578
+ *
579
+ * const program = Effect.gen(function* () {
580
+ * const cache = yield* DenoReleaseCache;
581
+ * const latest = yield* cache.latest();
582
+ * console.log(latest.version.toString());
583
+ * });
584
+ *
585
+ * Effect.runPromise(program.pipe(Effect.provide(OfflineDenoCacheLive)));
586
+ * ```
587
+ *
588
+ * @see {@link DenoRelease}
589
+ * @see {@link RuntimeCache}
590
+ * @see {@link AutoDenoCacheLive}
591
+ * @see {@link FreshDenoCacheLive}
592
+ * @see {@link OfflineDenoCacheLive}
593
+ *
594
+ * @public
595
+ */
596
+ export declare class DenoReleaseCache extends DenoReleaseCache_base {
597
+ }
598
+
599
+ declare const DenoReleaseCache_base: Context.TagClass<DenoReleaseCache, "runtime-resolver/DenoReleaseCache", RuntimeCache<DenoRelease>>;
600
+
601
+ /**
602
+ * Low-level layer that constructs the {@link DenoReleaseCache} service backed by
603
+ * a `SemVerVersionCache` instance from `semver-effect`.
604
+ *
605
+ * This layer wires up the cache storage but does not populate it with any
606
+ * release data. Callers are responsible for loading releases after construction.
607
+ * In normal usage you should prefer the higher-level cache strategy layers
608
+ * instead of using this layer directly:
609
+ * - {@link AutoDenoCacheLive} — API with fallback to bundled defaults
610
+ * - {@link FreshDenoCacheLive} — API only, fails if unavailable
611
+ * - {@link OfflineDenoCacheLive} — bundled defaults only
612
+ *
613
+ * @see {@link DenoReleaseCache}
614
+ * @see {@link createRuntimeCache}
103
615
  * @internal
104
- * Exported for declaration bundling — see AuthenticationError.ts for details.
105
616
  */
106
- export declare const CacheErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
107
- readonly _tag: "CacheError";
617
+ export declare const DenoReleaseCacheLive: Layer.Layer<DenoReleaseCache, never, VersionCache>;
618
+
619
+ /**
620
+ * Service for resolving Deno runtime versions against the cached
621
+ * release index.
622
+ *
623
+ * For a one-shot Promise-based API see {@link resolveDeno}.
624
+ *
625
+ * @example
626
+ * ```typescript
627
+ * import type { ResolvedVersions } from "runtime-resolver";
628
+ * import { DenoResolver, DenoResolverLive } from "runtime-resolver";
629
+ * import { Effect } from "effect";
630
+ *
631
+ * const program = Effect.gen(function* () {
632
+ * const resolver = yield* DenoResolver;
633
+ * const result = yield* resolver.resolve({ semverRange: "^2.0.0", increments: "minor" });
634
+ * console.log(result.latest);
635
+ * console.log(result.versions);
636
+ * });
637
+ *
638
+ * Effect.runPromise(program.pipe(Effect.provide(DenoResolverLive)));
639
+ * ```
640
+ *
641
+ * @see {@link DenoResolverLive}
642
+ * @see {@link resolveDeno}
643
+ * @see {@link DenoResolverOptions}
644
+ * @see {@link ResolvedVersions}
645
+ *
646
+ * @public
647
+ */
648
+ export declare class DenoResolver extends DenoResolver_base {
649
+ }
650
+
651
+ declare const DenoResolver_base: Context.TagClass<DenoResolver, "runtime-resolver/DenoResolver", {
652
+ /**
653
+ * Resolves Deno versions according to `options` and returns a
654
+ * {@link ResolvedVersions} object containing the matching version list,
655
+ * the latest version string, and the data source indicator.
656
+ *
657
+ * Fails with {@link DenoResolverError} when no version can be resolved and
658
+ * no `defaultVersion` was provided.
659
+ *
660
+ * @param options - Optional resolution constraints.
661
+ *
662
+ * @see {@link DenoResolverOptions}
663
+ */
664
+ readonly resolve: (options?: DenoResolverOptions | undefined) => Effect.Effect<{
665
+ readonly source: "api" | "cache";
666
+ readonly versions: readonly string[];
667
+ readonly latest: string;
668
+ readonly lts?: string | undefined;
669
+ readonly default?: string | undefined;
670
+ }, VersionNotFoundError, never>;
671
+ }>;
672
+
673
+ /**
674
+ * Union of all typed errors that {@link DenoResolver.resolve} can fail with.
675
+ *
676
+ * @see {@link VersionNotFoundError}
677
+ *
678
+ * @public
679
+ */
680
+ export declare type DenoResolverError = VersionNotFoundError;
681
+
682
+ /**
683
+ * Provides the {@link DenoResolver} service backed by a {@link DenoReleaseCache}.
684
+ *
685
+ * This layer composes with any of the three Deno cache strategy layers to form
686
+ * a complete resolver stack. It implements semver range filtering and increment
687
+ * grouping (latest per major, latest per minor, or all patch versions) using
688
+ * the releases already loaded into the cache.
689
+ *
690
+ * @example
691
+ * ```ts
692
+ * import { DenoResolverLive, AutoDenoCacheLive, DenoVersionFetcherLive, GitHubClientLive, GitHubAutoAuth } from "runtime-resolver";
693
+ * import { DenoResolver } from "runtime-resolver";
694
+ * import { Effect, Layer } from "effect";
695
+ *
696
+ * const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));
697
+ * const CacheLayer = AutoDenoCacheLive.pipe(Layer.provide(DenoVersionFetcherLive.pipe(Layer.provide(GitHubLayer))));
698
+ * const ResolverLayer = DenoResolverLive.pipe(Layer.provide(CacheLayer));
699
+ *
700
+ * const program = Effect.gen(function* () {
701
+ * const resolver = yield* DenoResolver;
702
+ * const result = yield* resolver.resolve({ semverRange: "^2.0.0" });
703
+ * console.log(result.latest);
704
+ * });
705
+ *
706
+ * Effect.runPromise(program.pipe(Effect.provide(ResolverLayer)));
707
+ * ```
708
+ *
709
+ * @see {@link DenoResolver}
710
+ * @see {@link DenoReleaseCache}
711
+ * @see {@link AutoDenoCacheLive}
712
+ * @see {@link FreshDenoCacheLive}
713
+ * @see {@link OfflineDenoCacheLive}
714
+ * @public
715
+ */
716
+ export declare const DenoResolverLive: Layer.Layer<DenoResolver, never, DenoReleaseCache>;
717
+
718
+ /**
719
+ * Options accepted by {@link DenoResolver.resolve}.
720
+ *
721
+ * All fields are optional; omitting them causes the resolver to fall back to
722
+ * its built-in defaults (latest stable release, `"latest"` increment
723
+ * granularity).
724
+ *
725
+ * @see {@link DenoResolver}
726
+ * @see {@link Increments}
727
+ * @see {@link ResolvedVersions}
728
+ *
729
+ * @public
730
+ */
731
+ export declare interface DenoResolverOptions {
732
+ /**
733
+ * A semver range string used to constrain which Deno releases are
734
+ * considered (e.g. `"^2.0.0"` or `">=1.40.0 <2.0.0"`).
735
+ *
736
+ * When omitted all cached releases are eligible.
737
+ */
738
+ readonly semverRange?: string;
739
+ /**
740
+ * A fallback version string returned when no release satisfies
741
+ * `semverRange`. Must be a valid semver version (e.g. `"2.0.0"`).
742
+ *
743
+ * When omitted and no match is found the effect fails with
744
+ * {@link VersionNotFoundError}.
745
+ */
746
+ readonly defaultVersion?: string;
747
+ /**
748
+ * Controls the granularity of the version list included in
749
+ * {@link ResolvedVersions.versions}.
750
+ *
751
+ * - `"latest"` — only the single highest matching version.
752
+ * - `"minor"` — the latest patch for every minor line.
753
+ * - `"patch"` — every individual patch release.
754
+ *
755
+ * Defaults to `"latest"` when omitted.
756
+ *
757
+ * @see {@link Increments}
758
+ */
759
+ readonly increments?: Increments;
760
+ }
761
+
762
+ /**
763
+ * Service for fetching Deno release data from the GitHub API.
764
+ *
765
+ * Retrieves both the parsed semver version list and the raw release input
766
+ * records needed to populate a {@link DenoReleaseCache}. The live
767
+ * implementation queries the `denoland/deno` GitHub repository via
768
+ * {@link GitHubClient}.
769
+ *
770
+ * @example
771
+ * ```typescript
772
+ * import type { RuntimeReleaseInput } from "runtime-resolver";
773
+ * import { DenoVersionFetcher, DenoVersionFetcherLive, GitHubAutoAuth } from "runtime-resolver";
774
+ * import { Effect } from "effect";
775
+ *
776
+ * const program = Effect.gen(function* () {
777
+ * const fetcher = yield* DenoVersionFetcher;
778
+ * const { versions, inputs } = yield* fetcher.fetch();
779
+ * console.log(`Fetched ${versions.length} Deno releases`);
780
+ * });
781
+ *
782
+ * Effect.runPromise(
783
+ * program.pipe(Effect.provide(DenoVersionFetcherLive), Effect.provide(GitHubAutoAuth)),
784
+ * );
785
+ * ```
786
+ *
787
+ * @see {@link DenoVersionFetcherLive}
788
+ * @see {@link DenoReleaseCache}
789
+ * @see {@link AuthenticationError}
790
+ * @see {@link NetworkError}
791
+ * @see {@link ParseError}
792
+ * @see {@link RateLimitError}
793
+ *
794
+ * @public
795
+ */
796
+ export declare class DenoVersionFetcher extends DenoVersionFetcher_base {
797
+ }
798
+
799
+ declare const DenoVersionFetcher_base: Context.TagClass<DenoVersionFetcher, "runtime-resolver/DenoVersionFetcher", {
800
+ /**
801
+ * Fetches all available Deno releases from the GitHub API and returns both
802
+ * the parsed semver version objects and the raw release input records.
803
+ *
804
+ * Fails with:
805
+ * - {@link AuthenticationError} — missing or invalid GitHub credentials.
806
+ * - {@link NetworkError} — the upstream request could not be completed.
807
+ * - {@link ParseError} — the response payload could not be decoded.
808
+ * - {@link RateLimitError} — the GitHub API rate limit was exceeded.
809
+ */
810
+ readonly fetch: () => Effect.Effect<{
811
+ readonly versions: readonly SemVer[];
812
+ readonly inputs: readonly {
813
+ readonly version: string;
814
+ readonly date: string;
815
+ }[];
816
+ }, AuthenticationError | NetworkError | ParseError | RateLimitError, never>;
817
+ }>;
818
+
819
+ /**
820
+ * Provides the {@link DenoVersionFetcher} service using the live GitHub API.
821
+ *
822
+ * Fetches Deno releases from the `denoland/deno` GitHub repository using the
823
+ * provided {@link GitHubClient}. Draft releases and pre-releases are excluded.
824
+ * Tag names are normalized by stripping any leading `v` before parsing as
825
+ * semver.
826
+ *
827
+ * This layer is a required dependency of {@link AutoDenoCacheLive} and
828
+ * {@link FreshDenoCacheLive}. It is not needed by {@link OfflineDenoCacheLive}.
829
+ *
830
+ * @see {@link DenoVersionFetcher}
831
+ * @see {@link GitHubClient}
832
+ * @see {@link AutoDenoCacheLive}
833
+ * @see {@link FreshDenoCacheLive}
834
+ * @public
835
+ */
836
+ export declare const DenoVersionFetcherLive: Layer.Layer<DenoVersionFetcher, never, GitHubClient>;
837
+
838
+ /**
839
+ * Provides the {@link BunReleaseCache} service using the "Fresh" cache strategy.
840
+ *
841
+ * Requires live release data from the GitHub API via {@link BunVersionFetcher}.
842
+ * If the fetch fails for any reason — authentication errors, network errors,
843
+ * parse errors, or rate-limit errors — the layer fails with a `FreshnessError`
844
+ * rather than falling back to stale data.
845
+ *
846
+ * Use this strategy in contexts where working with outdated version data would
847
+ * be incorrect or misleading, such as CI pipelines or tooling that must reflect
848
+ * the true current state of Bun releases.
849
+ *
850
+ * The three Bun cache strategy layers are:
851
+ * - {@link AutoBunCacheLive} — tries API first, falls back to bundled defaults on any error
852
+ * - `FreshBunCacheLive` — requires live API data, fails with `FreshnessError` if unavailable
853
+ * - {@link OfflineBunCacheLive} — uses only bundled defaults, no network I/O
854
+ *
855
+ * @see {@link BunReleaseCache}
856
+ * @see {@link AutoBunCacheLive}
857
+ * @see {@link OfflineBunCacheLive}
858
+ * @public
859
+ */
860
+ export declare const FreshBunCacheLive: Layer.Layer<BunReleaseCache, FreshnessError, BunVersionFetcher>;
861
+
862
+ /**
863
+ * Provides the {@link DenoReleaseCache} service using the "Fresh" cache strategy.
864
+ *
865
+ * Requires live release data from the GitHub API via {@link DenoVersionFetcher}.
866
+ * If the fetch fails for any reason — authentication errors, network errors,
867
+ * parse errors, or rate-limit errors — the layer fails with a `FreshnessError`
868
+ * rather than falling back to stale data.
869
+ *
870
+ * Use this strategy in contexts where working with outdated version data would
871
+ * be incorrect or misleading, such as CI pipelines or tooling that must reflect
872
+ * the true current state of Deno releases.
873
+ *
874
+ * The three Deno cache strategy layers are:
875
+ * - {@link AutoDenoCacheLive} — tries API first, falls back to bundled defaults on any error
876
+ * - `FreshDenoCacheLive` — requires live API data, fails with `FreshnessError` if unavailable
877
+ * - {@link OfflineDenoCacheLive} — uses only bundled defaults, no network I/O
878
+ *
879
+ * @see {@link DenoReleaseCache}
880
+ * @see {@link AutoDenoCacheLive}
881
+ * @see {@link OfflineDenoCacheLive}
882
+ * @public
883
+ */
884
+ export declare const FreshDenoCacheLive: Layer.Layer<DenoReleaseCache, FreshnessError, DenoVersionFetcher>;
885
+
886
+ /**
887
+ * Raised when a cache freshness check cannot be completed.
888
+ *
889
+ * Each runtime cache layer ({@link FreshBunCacheLive}, {@link FreshDenoCacheLive},
890
+ * {@link FreshNodeCacheLive}) validates whether locally stored version data is
891
+ * still current before returning it. This error is produced when that
892
+ * validation fails — for example, when the upstream API is unreachable during
893
+ * an `"api"` freshness check, or when the cached timestamp is corrupt during
894
+ * an `"auto"` or `"cache"` check.
895
+ *
896
+ * @see {@link FreshBunCacheLive}
897
+ * @see {@link FreshDenoCacheLive}
898
+ * @see {@link FreshNodeCacheLive}
899
+ *
900
+ * @example
901
+ * ```typescript
902
+ * import { Effect } from "effect";
903
+ * import type { FreshnessError } from "./FreshnessError.js";
904
+ * import { resolveNode } from "../resolvers/node.js";
905
+ *
906
+ * const program = resolveNode(">=20").pipe(
907
+ * Effect.catchTag("FreshnessError", (err: FreshnessError) =>
908
+ * Effect.logWarning(
909
+ * `Freshness check (${err.strategy}) failed: ${err.message}`
910
+ * ).pipe(Effect.andThen(Effect.fail(err)))
911
+ * )
912
+ * );
913
+ * ```
914
+ *
915
+ * @public
916
+ */
917
+ export declare class FreshnessError extends FreshnessError_base<{
918
+ readonly strategy: "auto" | "api" | "cache";
919
+ readonly message: string;
920
+ }> {
921
+ }
922
+
923
+ declare const FreshnessError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
924
+ readonly _tag: "FreshnessError";
925
+ } & Readonly<A>;
926
+
927
+ /**
928
+ * Provides the {@link NodeReleaseCache} service using the "Fresh" cache strategy.
929
+ *
930
+ * Requires live release data from `nodejs.org/dist/index.json` via
931
+ * {@link NodeVersionFetcher} and the release schedule from the `nodejs/Release`
932
+ * repository via {@link NodeScheduleFetcher}. If either fetch fails — due to
933
+ * network errors or parse errors — the layer fails with a `FreshnessError`
934
+ * rather than falling back to stale data.
935
+ *
936
+ * Use this strategy in contexts where working with outdated version data would
937
+ * be incorrect or misleading, such as CI pipelines or tooling that must reflect
938
+ * the true current state of Node.js releases and LTS schedule.
939
+ *
940
+ * The three Node cache strategy layers are:
941
+ * - {@link AutoNodeCacheLive} — tries API first, falls back to bundled defaults on any error
942
+ * - `FreshNodeCacheLive` — requires live API data, fails with `FreshnessError` if unavailable
943
+ * - {@link OfflineNodeCacheLive} — uses only bundled defaults, no network I/O
944
+ *
945
+ * @see {@link NodeReleaseCache}
946
+ * @see {@link AutoNodeCacheLive}
947
+ * @see {@link OfflineNodeCacheLive}
948
+ * @public
949
+ */
950
+ export declare const FreshNodeCacheLive: Layer.Layer<NodeReleaseCache, FreshnessError, NodeVersionFetcher | NodeScheduleFetcher>;
951
+
952
+ /**
953
+ * Provides an {@link OctokitInstance} authenticated as a GitHub App installation.
954
+ *
955
+ * Accepts a {@link GitHubAppAuthConfig} and uses `@octokit/auth-app` to obtain
956
+ * an installation access token. If `installationId` is omitted, the first
957
+ * installation returned by the GitHub API is used automatically (suitable for
958
+ * apps with a single installation). If authentication fails for any reason the
959
+ * layer fails with an `AuthenticationError`.
960
+ *
961
+ * Use this auth method when your integration runs as a GitHub App rather than
962
+ * as an individual user or a CI token.
963
+ *
964
+ * @param config - App credentials and optional installation ID.
965
+ *
966
+ * @see {@link GitHubAppAuthConfig}
967
+ * @see {@link OctokitInstance}
968
+ * @see {@link GitHubAutoAuth}
969
+ * @see {@link GitHubTokenAuth}
970
+ * @public
971
+ */
972
+ export declare const GitHubAppAuth: (config: GitHubAppAuthConfig) => Layer.Layer<OctokitInstance, AuthenticationError, never>;
973
+
974
+ /**
975
+ * Configuration required to authenticate as a GitHub App installation.
976
+ *
977
+ * @see {@link GitHubAppAuth}
978
+ * @public
979
+ */
980
+ export declare interface GitHubAppAuthConfig {
981
+ readonly appId: string;
982
+ readonly privateKey: string;
983
+ readonly installationId?: number;
984
+ }
985
+
986
+ /**
987
+ * Provides an {@link OctokitInstance} by auto-detecting the available
988
+ * authentication credentials from environment variables at layer construction
989
+ * time.
990
+ *
991
+ * Credentials are evaluated in the following priority order:
992
+ * 1. **GitHub App** — when both `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY`
993
+ * are set, authenticates as a GitHub App installation via {@link GitHubAppAuth}.
994
+ * `GITHUB_APP_INSTALLATION_ID` is optional; if absent the first installation
995
+ * is used automatically.
996
+ * 2. **Personal Access Token** — when `GITHUB_PERSONAL_ACCESS_TOKEN` is set,
997
+ * authenticates with that token.
998
+ * 3. **`GITHUB_TOKEN`** — when `GITHUB_TOKEN` is set (e.g., the default token
999
+ * injected in GitHub Actions), authenticates with that token.
1000
+ * 4. **Unauthenticated** — falls back to an unauthenticated Octokit instance
1001
+ * when no credentials are found (lower rate limits apply).
1002
+ *
1003
+ * If both App and token credentials are present, App auth takes precedence and
1004
+ * a warning is logged. This layer fails with `AuthenticationError` only when
1005
+ * App credentials are present but the App authentication itself fails.
1006
+ *
1007
+ * Use this layer as the default auth strategy in most applications. Switch to
1008
+ * {@link GitHubTokenAuth} or {@link GitHubAppAuth} when you need explicit control
1009
+ * over which credential source is used.
1010
+ *
1011
+ * @example
1012
+ * ```ts
1013
+ * import { GitHubClientLive, GitHubAutoAuth } from "runtime-resolver";
1014
+ * import { Layer } from "effect";
1015
+ *
1016
+ * const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));
1017
+ * ```
1018
+ *
1019
+ * @see {@link OctokitInstance}
1020
+ * @see {@link GitHubTokenAuth}
1021
+ * @see {@link GitHubAppAuth}
1022
+ * @public
1023
+ */
1024
+ export declare const GitHubAutoAuth: Layer.Layer<OctokitInstance, AuthenticationError>;
1025
+
1026
+ /**
1027
+ * Service for GitHub REST API operations used by the version
1028
+ * fetcher services.
1029
+ *
1030
+ * Abstracts over pagination and authentication so that higher-level services
1031
+ * such as {@link BunVersionFetcher}, {@link DenoVersionFetcher}, and
1032
+ * {@link NodeScheduleFetcher} remain independent of the underlying HTTP
1033
+ * transport. The live implementation is provided by {@link GitHubClientLive}
1034
+ * and requires an {@link OctokitInstance} in its dependency graph.
1035
+ *
1036
+ * @example
1037
+ * ```typescript
1038
+ * import type { GitHubTag } from "runtime-resolver";
1039
+ * import { GitHubClient, GitHubClientLive, GitHubAutoAuth } from "runtime-resolver";
1040
+ * import { Effect } from "effect";
1041
+ *
1042
+ * const program = Effect.gen(function* () {
1043
+ * const client = yield* GitHubClient;
1044
+ * const tags = yield* client.listTags("oven-sh", "bun", { perPage: 100 });
1045
+ * console.log(`Found ${tags.length} tags`);
1046
+ * });
1047
+ *
1048
+ * Effect.runPromise(
1049
+ * program.pipe(Effect.provide(GitHubClientLive), Effect.provide(GitHubAutoAuth)),
1050
+ * );
1051
+ * ```
1052
+ *
1053
+ * @see {@link GitHubClientLive}
1054
+ * @see {@link OctokitInstance}
1055
+ * @see {@link ListOptions}
1056
+ *
1057
+ * @public
1058
+ */
1059
+ export declare class GitHubClient extends GitHubClient_base {
1060
+ }
1061
+
1062
+ declare const GitHubClient_base: Context.TagClass<GitHubClient, "runtime-resolver/GitHubClient", {
1063
+ /**
1064
+ * Lists all git tags for the given repository, automatically paginating
1065
+ * through results according to `options`.
1066
+ *
1067
+ * Fails with:
1068
+ * - {@link AuthenticationError} — missing or invalid GitHub credentials.
1069
+ * - {@link NetworkError} — the upstream request could not be completed.
1070
+ * - {@link RateLimitError} — the GitHub API rate limit was exceeded.
1071
+ * - {@link ParseError} — a response page could not be decoded.
1072
+ *
1073
+ * @param owner - GitHub organisation or user name (e.g. `"oven-sh"`).
1074
+ * @param repo - Repository name (e.g. `"bun"`).
1075
+ * @param options - Optional pagination controls.
1076
+ */
1077
+ readonly listTags: (owner: string, repo: string, options?: ListOptions | undefined) => Effect.Effect<readonly {
1078
+ readonly name: string;
1079
+ readonly zipball_url: string;
1080
+ readonly tarball_url: string;
1081
+ readonly commit: {
1082
+ readonly sha: string;
1083
+ readonly url: string;
1084
+ };
1085
+ readonly node_id: string;
1086
+ }[], AuthenticationError | NetworkError | ParseError | RateLimitError, never>;
1087
+ /**
1088
+ * Lists all GitHub Releases for the given repository, automatically
1089
+ * paginating through results according to `options`.
1090
+ *
1091
+ * Fails with:
1092
+ * - {@link AuthenticationError} — missing or invalid GitHub credentials.
1093
+ * - {@link NetworkError} — the upstream request could not be completed.
1094
+ * - {@link RateLimitError} — the GitHub API rate limit was exceeded.
1095
+ * - {@link ParseError} — a response page could not be decoded.
1096
+ *
1097
+ * @param owner - GitHub organisation or user name (e.g. `"denoland"`).
1098
+ * @param repo - Repository name (e.g. `"deno"`).
1099
+ * @param options - Optional pagination controls.
1100
+ */
1101
+ readonly listReleases: (owner: string, repo: string, options?: ListOptions | undefined) => Effect.Effect<readonly {
1102
+ readonly tag_name: string;
1103
+ readonly name: string | null;
1104
+ readonly draft: boolean;
1105
+ readonly prerelease: boolean;
1106
+ readonly published_at: string | null;
1107
+ }[], AuthenticationError | NetworkError | ParseError | RateLimitError, never>;
1108
+ /**
1109
+ * Fetches arbitrary JSON from `url` and decodes it with the provided
1110
+ * schema, returning the typed result.
1111
+ *
1112
+ * Useful for endpoints outside the standard Octokit REST methods (e.g.
1113
+ * raw GitHub content URLs).
1114
+ *
1115
+ * Fails with:
1116
+ * - {@link NetworkError} — the upstream request could not be completed.
1117
+ * - {@link ParseError} — the response body could not be decoded by `schema`.
1118
+ *
1119
+ * @typeParam A - The decoded output type produced by `schema`.
1120
+ * @param url - Fully qualified URL to fetch.
1121
+ * @param schema - An object with a `decode` method compatible with
1122
+ * Effect Schema's `decodeUnknown`.
1123
+ */
1124
+ readonly getJson: <A>(url: string, schema: {
1125
+ readonly decode: (input: unknown) => Effect.Effect<A, ParseError, never>;
1126
+ }) => Effect.Effect<A, NetworkError | ParseError, never>;
1127
+ }>;
1128
+
1129
+ /**
1130
+ * Provides the {@link GitHubClient} service backed by an {@link OctokitInstance}.
1131
+ *
1132
+ * This layer wraps an Octokit instance and exposes a typed, Effect-native API
1133
+ * for listing GitHub releases, listing tags, and fetching arbitrary JSON from
1134
+ * URLs. HTTP errors are mapped to typed domain errors (`NetworkError`,
1135
+ * `RateLimitError`, `AuthenticationError`) so they can be handled precisely in
1136
+ * calling code.
1137
+ *
1138
+ * Pair this layer with one of the auth layers to form a complete GitHub
1139
+ * integration stack:
1140
+ * - {@link GitHubAutoAuth} — auto-detects auth from environment variables
1141
+ * - {@link GitHubTokenAuth} — authenticates using a personal access token
1142
+ * - {@link GitHubAppAuth} — authenticates as a GitHub App installation
1143
+ *
1144
+ * @see {@link GitHubClient}
1145
+ * @see {@link OctokitInstance}
1146
+ * @see {@link GitHubAutoAuth}
1147
+ * @public
1148
+ */
1149
+ export declare const GitHubClientLive: Layer.Layer<GitHubClient, never, OctokitInstance>;
1150
+
1151
+ /**
1152
+ * A single release entry as returned by the GitHub REST API
1153
+ * (`GET /repos/{owner}/{repo}/releases`).
1154
+ *
1155
+ * Consumed by {@link GitHubClient} when fetching Deno releases that carry
1156
+ * publication timestamps.
1157
+ *
1158
+ * @see {@link GitHubClient}
1159
+ * @see {@link GitHubReleaseList}
1160
+ *
1161
+ * @public
1162
+ */
1163
+ export declare const GitHubRelease: Schema.Struct<{
1164
+ tag_name: typeof Schema.String;
1165
+ name: Schema.NullOr<typeof Schema.String>;
1166
+ draft: typeof Schema.Boolean;
1167
+ prerelease: typeof Schema.Boolean;
1168
+ published_at: Schema.NullOr<typeof Schema.String>;
1169
+ }>;
1170
+
1171
+ /**
1172
+ * A single release entry as returned by the GitHub REST API.
1173
+ *
1174
+ * @see {@link GitHubClient}
1175
+ *
1176
+ * @public
1177
+ */
1178
+ export declare type GitHubRelease = typeof GitHubRelease.Type;
1179
+
1180
+ /**
1181
+ * A single tag entry as returned by the GitHub REST API
1182
+ * (`GET /repos/{owner}/{repo}/tags`).
1183
+ *
1184
+ * Consumed by {@link GitHubClient} when fetching Bun and Deno release tags.
1185
+ *
1186
+ * @see {@link GitHubClient}
1187
+ * @see {@link GitHubTagList}
1188
+ *
1189
+ * @public
1190
+ */
1191
+ export declare const GitHubTag: Schema.Struct<{
1192
+ name: typeof Schema.String;
1193
+ zipball_url: typeof Schema.String;
1194
+ tarball_url: typeof Schema.String;
1195
+ commit: Schema.Struct<{
1196
+ sha: typeof Schema.String;
1197
+ url: typeof Schema.String;
1198
+ }>;
1199
+ node_id: typeof Schema.String;
1200
+ }>;
1201
+
1202
+ /**
1203
+ * A single tag entry as returned by the GitHub REST API.
1204
+ *
1205
+ * @see {@link GitHubClient}
1206
+ *
1207
+ * @public
1208
+ */
1209
+ export declare type GitHubTag = typeof GitHubTag.Type;
1210
+
1211
+ /**
1212
+ * Provides an {@link OctokitInstance} authenticated with a GitHub personal
1213
+ * access token read from the environment at layer construction time.
1214
+ *
1215
+ * The token is resolved by checking, in order:
1216
+ * 1. `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable
1217
+ * 2. `GITHUB_TOKEN` environment variable
1218
+ *
1219
+ * If neither variable is set the layer constructs an unauthenticated Octokit
1220
+ * instance (lower rate limits apply). This layer never fails.
1221
+ *
1222
+ * Use {@link GitHubTokenAuthFromToken} when you have the token value available
1223
+ * programmatically at construction time.
1224
+ *
1225
+ * @see {@link OctokitInstance}
1226
+ * @see {@link GitHubTokenAuthFromToken}
1227
+ * @see {@link GitHubAutoAuth}
1228
+ * @see {@link GitHubAppAuth}
1229
+ * @public
1230
+ */
1231
+ export declare const GitHubTokenAuth: Layer.Layer<OctokitInstance>;
1232
+
1233
+ /**
1234
+ * Provides an {@link OctokitInstance} authenticated with the given personal
1235
+ * access token string.
1236
+ *
1237
+ * Use this variant when the token is available as a value in your program
1238
+ * rather than read from an environment variable. The resulting layer never
1239
+ * fails.
1240
+ *
1241
+ * @param token - A GitHub personal access token string.
1242
+ *
1243
+ * @see {@link OctokitInstance}
1244
+ * @see {@link GitHubTokenAuth}
1245
+ * @see {@link GitHubAutoAuth}
1246
+ * @see {@link GitHubAppAuth}
1247
+ * @public
1248
+ */
1249
+ export declare const GitHubTokenAuthFromToken: (token: string) => Layer.Layer<OctokitInstance, never, never>;
1250
+
1251
+ /**
1252
+ * Controls the granularity at which matching versions are grouped when a
1253
+ * resolver returns multiple results.
1254
+ *
1255
+ * - `"latest"` — return only the single highest matching version.
1256
+ * - `"minor"` — return the latest patch for every minor line.
1257
+ * - `"patch"` — return every individual patch release.
1258
+ *
1259
+ * @public
1260
+ */
1261
+ export declare const Increments: Schema.Literal<["latest", "minor", "patch"]>;
1262
+
1263
+ /**
1264
+ * Controls the granularity at which matching versions are grouped.
1265
+ *
1266
+ * @public
1267
+ */
1268
+ export declare type Increments = typeof Increments.Type;
1269
+
1270
+ /**
1271
+ * Raised when a caller-supplied value fails validation.
1272
+ *
1273
+ * This error is produced at the boundary where user-provided inputs (such as
1274
+ * version constraint strings or runtime identifiers) are decoded against their
1275
+ * expected schemas. The `field` identifies which input was invalid, `value`
1276
+ * carries the raw string that was rejected, and `message` explains the
1277
+ * specific violation.
1278
+ *
1279
+ * @example
1280
+ * ```typescript
1281
+ * import { Effect } from "effect";
1282
+ * import type { InvalidInputError } from "./InvalidInputError.js";
1283
+ * import { resolveNode } from "../resolvers/node.js";
1284
+ *
1285
+ * const program = resolveNode("not-a-semver-range").pipe(
1286
+ * Effect.catchTag("InvalidInputError", (err: InvalidInputError) =>
1287
+ * Effect.logError(
1288
+ * `Invalid value for "${err.field}": ${err.value} — ${err.message}`
1289
+ * ).pipe(Effect.andThen(Effect.fail(err)))
1290
+ * )
1291
+ * );
1292
+ * ```
1293
+ *
1294
+ * @public
1295
+ */
1296
+ export declare class InvalidInputError extends InvalidInputError_base<{
1297
+ readonly field: string;
1298
+ readonly value: string;
1299
+ readonly message: string;
1300
+ }> {
1301
+ }
1302
+
1303
+ declare const InvalidInputError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
1304
+ readonly _tag: "InvalidInputError";
108
1305
  } & Readonly<A>;
109
1306
 
110
1307
  /**
111
- * Service interface for resolving Deno runtime versions.
1308
+ * Pagination options shared by all list operations on {@link GitHubClient}.
1309
+ *
1310
+ * @see {@link GitHubClient}
1311
+ *
1312
+ * @public
112
1313
  */
113
- export declare interface DenoResolver {
114
- readonly resolve: (options?: DenoResolverOptions) => Effect.Effect<ResolvedVersions, DenoResolverError>;
115
- readonly resolveVersion: (versionOrRange: string) => Effect.Effect<string, DenoResolverError>;
1314
+ export declare interface ListOptions {
1315
+ /**
1316
+ * Number of items to request per page. Defaults to the GitHub API maximum
1317
+ * of `100` when omitted.
1318
+ */
1319
+ readonly perPage?: number;
1320
+ /**
1321
+ * Maximum number of pages to fetch. When omitted all pages are fetched
1322
+ * until the API returns an empty result set.
1323
+ */
1324
+ readonly pages?: number;
1325
+ }
1326
+
1327
+ /**
1328
+ * Raised when an HTTP request to an upstream API fails.
1329
+ *
1330
+ * This error is produced when a fetch to a runtime release API (e.g. the
1331
+ * Node.js release index, the Deno release feed, or the Bun GitHub releases
1332
+ * endpoint) either does not receive a response or receives a non-successful
1333
+ * HTTP status code. When a status code is available it is included in the
1334
+ * `status` field; for connection-level failures (timeouts, DNS errors) that
1335
+ * field is absent.
1336
+ *
1337
+ * @example
1338
+ * ```typescript
1339
+ * import { Effect } from "effect";
1340
+ * import type { NetworkError } from "./NetworkError.js";
1341
+ * import { resolveNode } from "../resolvers/node.js";
1342
+ *
1343
+ * const program = resolveNode(">=20").pipe(
1344
+ * Effect.catchTag("NetworkError", (err: NetworkError) =>
1345
+ * Effect.logError(
1346
+ * `Request to ${err.url} failed${err.status != null ? ` with status ${err.status}` : ""}: ${err.message}`
1347
+ * ).pipe(Effect.andThen(Effect.fail(err)))
1348
+ * )
1349
+ * );
1350
+ * ```
1351
+ *
1352
+ * @public
1353
+ */
1354
+ export declare class NetworkError extends NetworkError_base<{
1355
+ readonly url: string;
1356
+ readonly status?: number;
1357
+ readonly message: string;
1358
+ }> {
116
1359
  }
117
1360
 
1361
+ declare const NetworkError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
1362
+ readonly _tag: "NetworkError";
1363
+ } & Readonly<A>;
1364
+
118
1365
  /**
119
- * @internal Uses GenericTag — see BunResolver.ts for rationale.
1366
+ * Lifecycle phase of a Node.js major release line, derived from the official
1367
+ * Node.js release schedule.
1368
+ *
1369
+ * - `"current"` — actively receiving features and bug fixes.
1370
+ * - `"active-lts"` — Long-Term Support; stable, receiving only bug and
1371
+ * security fixes.
1372
+ * - `"maintenance-lts"` — critical security fixes only.
1373
+ * - `"end-of-life"` — no longer maintained.
1374
+ *
1375
+ * @see {@link NodeRelease.phase}
1376
+ *
1377
+ * @public
120
1378
  */
121
- export declare const DenoResolver: Context.Tag<DenoResolver, DenoResolver>;
1379
+ export declare const NodePhase: Schema.Literal<["current", "active-lts", "maintenance-lts", "end-of-life"]>;
122
1380
 
123
- export declare type DenoResolverError = AuthenticationError | NetworkError | ParseError | RateLimitError | VersionNotFoundError | InvalidInputError | FreshnessError | CacheError;
1381
+ /**
1382
+ * Lifecycle phase of a Node.js major release line.
1383
+ *
1384
+ * @see {@link NodeRelease.phase}
1385
+ *
1386
+ * @public
1387
+ */
1388
+ export declare type NodePhase = typeof NodePhase.Type;
124
1389
 
125
- export declare const DenoResolverLive: Layer.Layer<DenoResolver, never, GitHubClient | VersionCache>;
1390
+ /**
1391
+ * A Node.js release entry with parsed version, bundled npm version,
1392
+ * publication date, and a shared reference to the Node.js release schedule.
1393
+ *
1394
+ * `NodeRelease` is a plain class rather than a `Data.TaggedClass` because it
1395
+ * holds a `Ref<NodeSchedule>` — mutable Effect references break structural
1396
+ * equality semantics, making `Data.TaggedClass` unsuitable here. The
1397
+ * `_tag` field is set manually to preserve tagged-union compatibility.
1398
+ *
1399
+ * Use {@link NodeRelease.fromInput} to construct instances; call
1400
+ * {@link NodeRelease.phase} or {@link NodeRelease.lts} to query the lifecycle
1401
+ * state of the release at a given point in time.
1402
+ *
1403
+ * @see {@link NodeReleaseCache}
1404
+ * @see {@link NodeResolver}
1405
+ * @see {@link NodeSchedule}
1406
+ *
1407
+ * @example
1408
+ * ```typescript
1409
+ * import { Effect, Ref } from "effect";
1410
+ * import type { NodePhase } from "./common.js";
1411
+ * import type { NodeRelease } from "./node-release.js";
1412
+ * import { NodeRelease as NodeReleaseClass } from "./node-release.js";
1413
+ * import { NodeSchedule } from "./node-schedule.js";
1414
+ *
1415
+ * const program = Effect.gen(function* () {
1416
+ * const schedule = NodeSchedule.fromData({
1417
+ * v20: { start: "2023-04-18", lts: "2023-10-24", end: "2026-04-30" },
1418
+ * });
1419
+ * const scheduleRef = yield* Ref.make(schedule);
1420
+ *
1421
+ * const release: NodeRelease = yield* NodeReleaseClass.fromInput(
1422
+ * { version: "v20.0.0", npm: "9.6.4", date: "2023-04-18" },
1423
+ * scheduleRef,
1424
+ * );
1425
+ *
1426
+ * const phase: NodePhase | null = yield* release.phase();
1427
+ * const isLts: boolean = yield* release.lts();
1428
+ * console.log(phase); // e.g. "active-lts"
1429
+ * console.log(isLts); // true
1430
+ * });
1431
+ *
1432
+ * Effect.runPromise(program);
1433
+ * ```
1434
+ *
1435
+ * @public
1436
+ */
1437
+ export declare class NodeRelease {
1438
+ readonly version: SemVer;
1439
+ readonly npm: SemVer;
1440
+ readonly date: DateTime.DateTime;
1441
+ readonly scheduleRef: Ref.Ref<NodeSchedule>;
1442
+ readonly _tag: "NodeRelease";
1443
+ constructor(version: SemVer, npm: SemVer, date: DateTime.DateTime, scheduleRef: Ref.Ref<NodeSchedule>);
1444
+ /**
1445
+ * Determine this release's lifecycle phase at the given point in time.
1446
+ *
1447
+ * Reads the current value of the shared `scheduleRef` and delegates to
1448
+ * {@link NodeSchedule.phaseFor}. Defaults to the current wall-clock time
1449
+ * when `now` is omitted.
1450
+ *
1451
+ * @param now - Optional reference time; defaults to `new Date()`.
1452
+ * @returns An `Effect` that resolves to a {@link NodePhase} string, or
1453
+ * `null` when the major version is not present in the schedule or has
1454
+ * not yet been released.
1455
+ */
1456
+ phase(now?: DateTime.DateTime): Effect.Effect<NodePhase | null>;
1457
+ /**
1458
+ * Whether this release is currently in Long-Term Support.
1459
+ *
1460
+ * Returns `true` for both `"active-lts"` and `"maintenance-lts"` phases.
1461
+ * Defaults to the current wall-clock time when `now` is omitted.
1462
+ *
1463
+ * @param now - Optional reference time; defaults to `new Date()`.
1464
+ */
1465
+ lts(now?: DateTime.DateTime): Effect.Effect<boolean>;
1466
+ /**
1467
+ * Create a {@link NodeRelease} from lean string inputs.
1468
+ *
1469
+ * Parses `input.version` and `input.npm` via `SemVer.parse` and
1470
+ * constructs the publication date with `DateTime.unsafeMake`.
1471
+ *
1472
+ * @param input - A {@link NodeReleaseInput} object with raw string fields.
1473
+ * @param scheduleRef - A shared `Ref<NodeSchedule>` injected by the cache layer.
1474
+ * @returns An `Effect` that succeeds with a `NodeRelease` or fails with an
1475
+ * `InvalidVersionError` when either version string cannot be parsed.
1476
+ */
1477
+ static fromInput(input: NodeReleaseInput, scheduleRef: Ref.Ref<NodeSchedule>): Effect.Effect<NodeRelease, InvalidVersionError>;
1478
+ }
126
1479
 
127
- export declare interface DenoResolverOptions {
1480
+ /**
1481
+ * Node.js-specific cache service that extends {@link RuntimeCache} with
1482
+ * additional operations for lifecycle schedule data and LTS / current
1483
+ * release filtering.
1484
+ *
1485
+ * Unlike the Bun and Deno equivalents this service is not a plain type alias
1486
+ * — it adds four Node-specific methods on top of the generic cache contract.
1487
+ *
1488
+ * @example
1489
+ * ```typescript
1490
+ * import type { NodeRelease } from "runtime-resolver";
1491
+ * import { NodeReleaseCache, OfflineNodeCacheLive } from "runtime-resolver";
1492
+ * import { Effect } from "effect";
1493
+ *
1494
+ * const program = Effect.gen(function* () {
1495
+ * const cache = yield* NodeReleaseCache;
1496
+ * const lts = yield* cache.ltsReleases();
1497
+ * for (const release of lts) {
1498
+ * console.log(release.version.toString(), release.phase);
1499
+ * }
1500
+ * });
1501
+ *
1502
+ * Effect.runPromise(program.pipe(Effect.provide(OfflineNodeCacheLive)));
1503
+ * ```
1504
+ *
1505
+ * @see {@link NodeRelease}
1506
+ * @see {@link NodeScheduleData}
1507
+ * @see {@link RuntimeCache}
1508
+ * @see {@link AutoNodeCacheLive}
1509
+ * @see {@link FreshNodeCacheLive}
1510
+ * @see {@link OfflineNodeCacheLive}
1511
+ *
1512
+ * @public
1513
+ */
1514
+ export declare class NodeReleaseCache extends NodeReleaseCache_base {
1515
+ }
1516
+
1517
+ declare const NodeReleaseCache_base: Context.TagClass<NodeReleaseCache, "runtime-resolver/NodeReleaseCache", RuntimeCache<NodeRelease> & {
1518
+ /**
1519
+ * Merges Node.js release-schedule data into the cache, updating lifecycle
1520
+ * phase information (LTS dates, end-of-life dates, etc.) for every major
1521
+ * release line.
1522
+ *
1523
+ * @param data - Parsed schedule payload, typically fetched via
1524
+ * {@link NodeScheduleFetcher}.
1525
+ *
1526
+ * @see {@link NodeScheduleData}
1527
+ */
1528
+ readonly updateSchedule: (data: NodeScheduleData) => Effect.Effect<void, never, never>;
1529
+ /**
1530
+ * Populates the cache from raw {@link NodeReleaseInput} records (as
1531
+ * returned by the Node.js release index JSON) rather than from already-
1532
+ * decoded {@link NodeRelease} objects.
1533
+ *
1534
+ * Prefer this over `load` when consuming data directly from the Node.js
1535
+ * distribution server.
1536
+ *
1537
+ * @param inputs - An array of un-decoded release index entries.
1538
+ *
1539
+ * @see {@link NodeReleaseInput}
1540
+ */
1541
+ readonly loadFromInputs: (inputs: readonly {
1542
+ readonly version: string;
1543
+ readonly npm: string;
1544
+ readonly date: string;
1545
+ }[]) => Effect.Effect<void, never, never>;
1546
+ /**
1547
+ * Returns all cached releases whose lifecycle phase is `"active-lts"` or
1548
+ * `"maintenance-lts"` as of the given point in time.
1549
+ *
1550
+ * When `now` is omitted the current wall-clock time is used.
1551
+ *
1552
+ * @param now - Optional reference timestamp for phase evaluation.
1553
+ *
1554
+ * @see {@link NodeRelease}
1555
+ */
1556
+ readonly ltsReleases: (now?: DateTime.DateTime | undefined) => Effect.Effect<readonly NodeRelease[], never, never>;
1557
+ /**
1558
+ * Returns all cached releases whose lifecycle phase is `"current"` as of
1559
+ * the given point in time.
1560
+ *
1561
+ * When `now` is omitted the current wall-clock time is used.
1562
+ *
1563
+ * @param now - Optional reference timestamp for phase evaluation.
1564
+ *
1565
+ * @see {@link NodeRelease}
1566
+ */
1567
+ readonly currentReleases: (now?: DateTime.DateTime | undefined) => Effect.Effect<readonly NodeRelease[], never, never>;
1568
+ }>;
1569
+
1570
+ /**
1571
+ * Low-level layer that constructs the {@link NodeReleaseCache} service backed by
1572
+ * a `SemVerVersionCache` instance from `semver-effect`.
1573
+ *
1574
+ * This layer is more complex than the Bun and Deno equivalents because it manages
1575
+ * additional LTS schedule state via an internal `Ref`. The schedule is stored
1576
+ * separately from the version list so that calling `updateSchedule` can
1577
+ * transparently rebuild all release objects with the latest phase information
1578
+ * without reloading data from the network.
1579
+ *
1580
+ * This layer wires up the cache storage but does not populate it with any
1581
+ * release data. Callers are responsible for calling `updateSchedule` and
1582
+ * `loadFromInputs` after construction. In normal usage you should prefer the
1583
+ * higher-level cache strategy layers instead of using this layer directly:
1584
+ * - {@link AutoNodeCacheLive} — API with fallback to bundled defaults
1585
+ * - {@link FreshNodeCacheLive} — API only, fails if unavailable
1586
+ * - {@link OfflineNodeCacheLive} — bundled defaults only
1587
+ *
1588
+ * @see {@link NodeReleaseCache}
1589
+ * @see {@link createRuntimeCache}
1590
+ * @internal
1591
+ */
1592
+ export declare const NodeReleaseCacheLive: Layer.Layer<NodeReleaseCache, never, VersionCache>;
1593
+
1594
+ /**
1595
+ * Lean input schema used to construct {@link NodeRelease} instances from
1596
+ * bundled default data files and the Node.js dist API response.
1597
+ *
1598
+ * The `version` and `npm` fields are raw version strings validated by
1599
+ * `SemVer.parse`; `date` is a date string accepted by
1600
+ * `DateTime.unsafeMake`.
1601
+ *
1602
+ * @see {@link NodeRelease.fromInput}
1603
+ * @see {@link NodeVersionFetcher}
1604
+ *
1605
+ * @public
1606
+ */
1607
+ export declare const NodeReleaseInput: Schema.Struct<{
1608
+ version: typeof Schema.String;
1609
+ npm: typeof Schema.String;
1610
+ date: typeof Schema.String;
1611
+ }>;
1612
+
1613
+ /**
1614
+ * Lean input type used to construct {@link NodeRelease} instances.
1615
+ *
1616
+ * @see {@link NodeRelease.fromInput}
1617
+ *
1618
+ * @public
1619
+ */
1620
+ export declare type NodeReleaseInput = typeof NodeReleaseInput.Type;
1621
+
1622
+ /**
1623
+ * Service for resolving Node.js runtime versions against the cached
1624
+ * release index.
1625
+ *
1626
+ * Unlike {@link BunResolver} and {@link DenoResolver}, this service is
1627
+ * lifecycle-aware: results can be filtered by {@link NodePhase} and evaluated
1628
+ * against a specific reference date.
1629
+ *
1630
+ * For a one-shot Promise-based API see {@link resolveNode}.
1631
+ *
1632
+ * @example
1633
+ * ```typescript
1634
+ * import type { ResolvedVersions } from "runtime-resolver";
1635
+ * import { NodeResolver, NodeResolverLive } from "runtime-resolver";
1636
+ * import { Effect } from "effect";
1637
+ *
1638
+ * const program = Effect.gen(function* () {
1639
+ * const resolver = yield* NodeResolver;
1640
+ * const result = yield* resolver.resolve({
1641
+ * semverRange: "^20.0.0",
1642
+ * phases: ["active-lts"],
1643
+ * increments: "minor",
1644
+ * });
1645
+ * console.log(result.latest);
1646
+ * console.log(result.lts);
1647
+ * });
1648
+ *
1649
+ * Effect.runPromise(program.pipe(Effect.provide(NodeResolverLive)));
1650
+ * ```
1651
+ *
1652
+ * @see {@link NodeResolverLive}
1653
+ * @see {@link resolveNode}
1654
+ * @see {@link NodeResolverOptions}
1655
+ * @see {@link ResolvedVersions}
1656
+ *
1657
+ * @public
1658
+ */
1659
+ export declare class NodeResolver extends NodeResolver_base {
1660
+ }
1661
+
1662
+ declare const NodeResolver_base: Context.TagClass<NodeResolver, "runtime-resolver/NodeResolver", {
1663
+ /**
1664
+ * Resolves Node.js versions according to `options` and returns a
1665
+ * {@link ResolvedVersions} object containing the matching version list,
1666
+ * the latest version string, optional LTS version string, and the data
1667
+ * source indicator.
1668
+ *
1669
+ * Fails with {@link NodeResolverError} when no version can be resolved and
1670
+ * no `defaultVersion` was provided.
1671
+ *
1672
+ * @param options - Optional resolution constraints.
1673
+ *
1674
+ * @see {@link NodeResolverOptions}
1675
+ */
1676
+ readonly resolve: (options?: NodeResolverOptions | undefined) => Effect.Effect<{
1677
+ readonly source: "api" | "cache";
1678
+ readonly versions: readonly string[];
1679
+ readonly latest: string;
1680
+ readonly lts?: string | undefined;
1681
+ readonly default?: string | undefined;
1682
+ }, VersionNotFoundError, never>;
1683
+ }>;
1684
+
1685
+ /**
1686
+ * Union of all typed errors that {@link NodeResolver.resolve} can fail with.
1687
+ *
1688
+ * @see {@link VersionNotFoundError}
1689
+ *
1690
+ * @public
1691
+ */
1692
+ export declare type NodeResolverError = VersionNotFoundError;
1693
+
1694
+ /**
1695
+ * Provides the {@link NodeResolver} service backed by a {@link NodeReleaseCache}.
1696
+ *
1697
+ * This layer composes with any of the three Node cache strategy layers to form
1698
+ * a complete resolver stack. In addition to semver range filtering and increment
1699
+ * grouping, it supports filtering by Node.js release phase (e.g., `"current"`,
1700
+ * `"active-lts"`, `"maintenance-lts"`, `"end-of-life"`) and automatically
1701
+ * determines the current LTS version from the loaded schedule data.
1702
+ *
1703
+ * @example
1704
+ * ```ts
1705
+ * import {
1706
+ * NodeResolverLive, AutoNodeCacheLive,
1707
+ * NodeVersionFetcherLive, NodeScheduleFetcherLive,
1708
+ * GitHubClientLive, GitHubAutoAuth,
1709
+ * } from "runtime-resolver";
1710
+ * import { NodeResolver } from "runtime-resolver";
1711
+ * import { Effect, Layer } from "effect";
1712
+ *
1713
+ * const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));
1714
+ * const FetchersLayer = Layer.merge(
1715
+ * NodeVersionFetcherLive.pipe(Layer.provide(GitHubLayer)),
1716
+ * NodeScheduleFetcherLive.pipe(Layer.provide(GitHubLayer)),
1717
+ * );
1718
+ * const CacheLayer = AutoNodeCacheLive.pipe(Layer.provide(FetchersLayer));
1719
+ * const ResolverLayer = NodeResolverLive.pipe(Layer.provide(CacheLayer));
1720
+ *
1721
+ * const program = Effect.gen(function* () {
1722
+ * const resolver = yield* NodeResolver;
1723
+ * // Resolve only active-LTS and maintenance-LTS releases in the ^20 range
1724
+ * const result = yield* resolver.resolve({
1725
+ * semverRange: "^20.0.0",
1726
+ * phases: ["active-lts", "maintenance-lts"],
1727
+ * });
1728
+ * console.log(result.lts);
1729
+ * });
1730
+ *
1731
+ * Effect.runPromise(program.pipe(Effect.provide(ResolverLayer)));
1732
+ * ```
1733
+ *
1734
+ * @see {@link NodeResolver}
1735
+ * @see {@link NodeReleaseCache}
1736
+ * @see {@link AutoNodeCacheLive}
1737
+ * @see {@link FreshNodeCacheLive}
1738
+ * @see {@link OfflineNodeCacheLive}
1739
+ * @public
1740
+ */
1741
+ export declare const NodeResolverLive: Layer.Layer<NodeResolver, never, NodeReleaseCache>;
1742
+
1743
+ /**
1744
+ * Options accepted by {@link NodeResolver.resolve}.
1745
+ *
1746
+ * All fields are optional; omitting them causes the resolver to fall back to
1747
+ * its built-in defaults (all lifecycle phases, latest stable release,
1748
+ * `"latest"` increment granularity, current wall-clock date).
1749
+ *
1750
+ * @see {@link NodeResolver}
1751
+ * @see {@link NodePhase}
1752
+ * @see {@link Increments}
1753
+ * @see {@link ResolvedVersions}
1754
+ *
1755
+ * @public
1756
+ */
1757
+ export declare interface NodeResolverOptions {
1758
+ /**
1759
+ * A semver range string used to constrain which Node.js releases are
1760
+ * considered (e.g. `"^20.0.0"` or `">=18.0.0 <22.0.0"`).
1761
+ *
1762
+ * When omitted all cached releases are eligible.
1763
+ */
128
1764
  readonly semverRange?: string;
1765
+ /**
1766
+ * A fallback version string returned when no release satisfies
1767
+ * `semverRange`. Must be a valid semver version (e.g. `"20.0.0"`).
1768
+ *
1769
+ * When omitted and no match is found the effect fails with
1770
+ * {@link VersionNotFoundError}.
1771
+ */
129
1772
  readonly defaultVersion?: string;
1773
+ /**
1774
+ * Restricts resolution to releases whose lifecycle phase is included in
1775
+ * this list. For example, passing `["active-lts"]` excludes `"current"`
1776
+ * and `"maintenance-lts"` releases from consideration.
1777
+ *
1778
+ * When omitted all phases are eligible.
1779
+ *
1780
+ * @see {@link NodePhase}
1781
+ */
1782
+ readonly phases?: ReadonlyArray<NodePhase>;
1783
+ /**
1784
+ * Controls the granularity of the version list included in
1785
+ * {@link ResolvedVersions.versions}.
1786
+ *
1787
+ * - `"latest"` — only the single highest matching version.
1788
+ * - `"minor"` — the latest patch for every minor line.
1789
+ * - `"patch"` — every individual patch release.
1790
+ *
1791
+ * Defaults to `"latest"` when omitted.
1792
+ *
1793
+ * @see {@link Increments}
1794
+ */
130
1795
  readonly increments?: Increments;
131
- readonly freshness?: Freshness;
132
- }
133
-
134
- export declare const Freshness: Schema.Literal<["auto", "api", "cache"]>;
135
-
136
- export declare type Freshness = typeof Freshness.Type;
137
-
138
- export declare class FreshnessError extends FreshnessErrorBase<{
139
- readonly strategy: Freshness;
140
- readonly message: string;
141
- }> {
1796
+ /**
1797
+ * Reference date used when evaluating Node.js lifecycle phases. Releases
1798
+ * whose LTS or end-of-life boundaries fall relative to this date are
1799
+ * classified accordingly.
1800
+ *
1801
+ * When omitted the current wall-clock date is used.
1802
+ */
1803
+ readonly date?: Date;
142
1804
  }
143
1805
 
144
1806
  /**
145
- * @internal
146
- * Exported for declaration bundling — see AuthenticationError.ts for details.
1807
+ * Immutable snapshot of the Node.js release schedule.
1808
+ *
1809
+ * Holds a `ReadonlyArray<NodeScheduleEntry>` parsed from the raw
1810
+ * {@link NodeScheduleData} fetched by {@link NodeScheduleFetcher}. A single
1811
+ * `NodeSchedule` instance lives inside a `Ref<NodeSchedule>` that is shared
1812
+ * across all {@link NodeRelease} instances so that schedule updates propagate
1813
+ * without rebuilding the release cache.
1814
+ *
1815
+ * `NodeSchedule` extends `Data.TaggedClass` and therefore supports structural
1816
+ * equality — two schedules built from identical data compare as equal.
1817
+ *
1818
+ * @see {@link NodeRelease}
1819
+ * @see {@link NodeScheduleFetcher}
1820
+ *
1821
+ * @example
1822
+ * ```typescript
1823
+ * import { Effect } from "effect";
1824
+ * import type { NodePhase } from "./common.js";
1825
+ * import type { NodeScheduleData } from "./node-schedule.js";
1826
+ * import { NodeSchedule } from "./node-schedule.js";
1827
+ *
1828
+ * const data: NodeScheduleData = {
1829
+ * v20: {
1830
+ * start: "2023-04-18",
1831
+ * lts: "2023-10-24",
1832
+ * maintenance: "2024-10-22",
1833
+ * end: "2026-04-30",
1834
+ * codename: "Iron",
1835
+ * },
1836
+ * };
1837
+ *
1838
+ * const schedule = NodeSchedule.fromData(data);
1839
+ *
1840
+ * const program = schedule.phaseFor(20, new Date("2024-01-01") as any).pipe(
1841
+ * Effect.map((phase: NodePhase | null) => {
1842
+ * console.log(phase); // "active-lts"
1843
+ * }),
1844
+ * );
1845
+ *
1846
+ * Effect.runPromise(program);
1847
+ * ```
1848
+ *
1849
+ * @public
147
1850
  */
148
- export declare const FreshnessErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
149
- readonly _tag: "FreshnessError";
150
- } & Readonly<A>;
151
-
152
- export declare const GitHubAppAuth: (config: GitHubAppAuthConfig) => Layer.Layer<OctokitInstance, AuthenticationError, never>;
153
-
154
- export declare interface GitHubAppAuthConfig {
155
- readonly appId: string;
156
- readonly privateKey: string;
157
- readonly installationId?: number;
1851
+ export declare class NodeSchedule extends NodeSchedule_base<{
1852
+ readonly entries: ReadonlyArray<NodeScheduleEntry>;
1853
+ }> {
1854
+ /**
1855
+ * Parse raw {@link NodeScheduleData} into a `NodeSchedule` instance.
1856
+ *
1857
+ * Iterates over every key of the form `"vNN"`, converts ISO date strings
1858
+ * to `DateTime.DateTime`, and normalises missing codenames to `""`.
1859
+ *
1860
+ * @param data - The raw JSON object fetched from the Node.js release repo.
1861
+ * @returns A fully constructed, immutable `NodeSchedule`.
1862
+ */
1863
+ static fromData(data: NodeScheduleData): NodeSchedule;
1864
+ /**
1865
+ * Determine the current {@link NodePhase} for a given major version number
1866
+ * at the specified point in time.
1867
+ *
1868
+ * Returns `null` when the major is not present in the schedule or when
1869
+ * `now` is before the release's start date.
1870
+ *
1871
+ * @param major - The Node.js major version number (e.g. `20`).
1872
+ * @param now - The reference `DateTime` against which phases are evaluated.
1873
+ */
1874
+ phaseFor(major: number, now: DateTime.DateTime): Effect.Effect<NodePhase | null>;
1875
+ /**
1876
+ * Look up the {@link NodeScheduleEntry} for a given major version number.
1877
+ *
1878
+ * Returns `Option.none()` when the major is not present in the schedule.
1879
+ *
1880
+ * @param major - The Node.js major version number (e.g. `20`).
1881
+ */
1882
+ entryFor(major: number): Option.Option<NodeScheduleEntry>;
158
1883
  }
159
1884
 
160
- export declare const GitHubAutoAuth: Layer.Layer<OctokitInstance, AuthenticationError>;
1885
+ declare const NodeSchedule_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
1886
+ readonly _tag: "NodeSchedule";
1887
+ };
161
1888
 
162
1889
  /**
163
- * Service interface for GitHub API operations.
1890
+ * Raw schedule format as fetched from the Node.js Release GitHub repository
1891
+ * (`nodejs/Release`).
1892
+ *
1893
+ * This is the intermediate type between the {@link NodeScheduleFetcher} layer
1894
+ * and the {@link NodeSchedule} class. Date values are plain ISO strings; the
1895
+ * `NodeSchedule.fromData` factory converts them to `DateTime`.
1896
+ *
1897
+ * @see {@link NodeSchedule.fromData}
1898
+ * @see {@link NodeScheduleFetcher}
1899
+ *
1900
+ * @public
164
1901
  */
165
- export declare interface GitHubClient {
166
- readonly listTags: (owner: string, repo: string, options?: ListOptions) => Effect.Effect<ReadonlyArray<GitHubTag>, AuthenticationError | NetworkError | RateLimitError | ParseError>;
167
- readonly listReleases: (owner: string, repo: string, options?: ListOptions) => Effect.Effect<ReadonlyArray<GitHubRelease>, AuthenticationError | NetworkError | RateLimitError | ParseError>;
168
- readonly getJson: <A>(url: string, schema: {
169
- readonly decode: (input: unknown) => Effect.Effect<A, ParseError>;
170
- }) => Effect.Effect<A, NetworkError | ParseError>;
171
- }
1902
+ export declare type NodeScheduleData = Record<string, {
1903
+ readonly start: string;
1904
+ readonly lts?: string;
1905
+ readonly maintenance?: string;
1906
+ readonly end: string;
1907
+ readonly codename?: string;
1908
+ }>;
172
1909
 
173
1910
  /**
174
- * @internal Uses GenericTag — see BunResolver.ts for rationale.
1911
+ * A single entry in the Node.js release schedule, representing one major
1912
+ * release line's lifecycle dates and optional LTS codename.
1913
+ *
1914
+ * All date fields have already been converted from ISO strings to Effect
1915
+ * `DateTime.DateTime` values by {@link NodeSchedule.fromData}.
1916
+ *
1917
+ * @see {@link NodeSchedule}
1918
+ * @see {@link NodeSchedule.phaseFor}
1919
+ *
1920
+ * @public
175
1921
  */
176
- export declare const GitHubClient: Context.Tag<GitHubClient, GitHubClient>;
177
-
178
- export declare const GitHubClientLive: Layer.Layer<GitHubClient, never, OctokitInstance>;
179
-
180
- export declare const GitHubRelease: Schema.Struct<{
181
- tag_name: typeof Schema.String;
182
- name: Schema.NullOr<typeof Schema.String>;
183
- draft: typeof Schema.Boolean;
184
- prerelease: typeof Schema.Boolean;
185
- published_at: Schema.NullOr<typeof Schema.String>;
186
- }>;
187
-
188
- export declare type GitHubRelease = typeof GitHubRelease.Type;
189
-
190
- export declare const GitHubTag: Schema.Struct<{
191
- name: typeof Schema.String;
192
- zipball_url: typeof Schema.String;
193
- tarball_url: typeof Schema.String;
194
- commit: Schema.Struct<{
195
- sha: typeof Schema.String;
196
- url: typeof Schema.String;
197
- }>;
198
- node_id: typeof Schema.String;
199
- }>;
200
-
201
- export declare type GitHubTag = typeof GitHubTag.Type;
202
-
203
- export declare const GitHubTokenAuth: Layer.Layer<OctokitInstance>;
204
-
205
- export declare const GitHubTokenAuthFromToken: (token: string) => Layer.Layer<OctokitInstance, never, never>;
206
-
207
- export declare const Increments: Schema.Literal<["latest", "minor", "patch"]>;
208
-
209
- export declare type Increments = typeof Increments.Type;
210
-
211
- export declare class InvalidInputError extends InvalidInputErrorBase<{
212
- readonly field: string;
213
- readonly value: string;
214
- readonly message: string;
215
- }> {
1922
+ export declare interface NodeScheduleEntry {
1923
+ readonly major: number;
1924
+ readonly start: DateTime.DateTime;
1925
+ readonly lts: DateTime.DateTime | null;
1926
+ readonly maintenance: DateTime.DateTime | null;
1927
+ readonly end: DateTime.DateTime;
1928
+ readonly codename: string;
216
1929
  }
217
1930
 
218
1931
  /**
219
- * @internal
220
- * Exported for declaration bundling — see AuthenticationError.ts for details.
1932
+ * Service for fetching the official Node.js release schedule.
1933
+ *
1934
+ * The schedule maps each major release line to its LTS start date and
1935
+ * end-of-life date, and is used by {@link NodeReleaseCache} to assign
1936
+ * lifecycle phases to individual releases. The live implementation fetches
1937
+ * from the `nodejs/Release` GitHub repository via a plain HTTP request.
1938
+ *
1939
+ * @example
1940
+ * ```typescript
1941
+ * import type { NodeScheduleData } from "runtime-resolver";
1942
+ * import { NodeScheduleFetcher, NodeScheduleFetcherLive } from "runtime-resolver";
1943
+ * import { Effect } from "effect";
1944
+ *
1945
+ * const program = Effect.gen(function* () {
1946
+ * const fetcher = yield* NodeScheduleFetcher;
1947
+ * const schedule = yield* fetcher.fetch();
1948
+ * console.log(Object.keys(schedule).length, "major release lines");
1949
+ * });
1950
+ *
1951
+ * Effect.runPromise(program.pipe(Effect.provide(NodeScheduleFetcherLive)));
1952
+ * ```
1953
+ *
1954
+ * @see {@link NodeScheduleFetcherLive}
1955
+ * @see {@link NodeReleaseCache}
1956
+ * @see {@link NodeScheduleData}
1957
+ * @see {@link NetworkError}
1958
+ * @see {@link ParseError}
1959
+ *
1960
+ * @public
221
1961
  */
222
- export declare const InvalidInputErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
223
- readonly _tag: "InvalidInputError";
224
- } & Readonly<A>;
225
-
226
- export declare interface ListOptions {
227
- readonly perPage?: number;
228
- readonly pages?: number;
1962
+ export declare class NodeScheduleFetcher extends NodeScheduleFetcher_base {
229
1963
  }
230
1964
 
231
- export declare class NetworkError extends NetworkErrorBase<{
232
- readonly url: string;
233
- readonly status?: number;
234
- readonly message: string;
235
- }> {
236
- }
1965
+ declare const NodeScheduleFetcher_base: Context.TagClass<NodeScheduleFetcher, "runtime-resolver/NodeScheduleFetcher", {
1966
+ /**
1967
+ * Fetches and decodes the Node.js release schedule JSON, returning a
1968
+ * typed {@link NodeScheduleData} map keyed by major release line.
1969
+ *
1970
+ * Fails with:
1971
+ * - {@link NetworkError} — the upstream request could not be completed.
1972
+ * - {@link ParseError} — the response payload could not be decoded.
1973
+ */
1974
+ readonly fetch: () => Effect.Effect<NodeScheduleData, NetworkError | ParseError, never>;
1975
+ }>;
237
1976
 
238
1977
  /**
239
- * @internal
240
- * Exported for declaration bundling — see AuthenticationError.ts for details.
1978
+ * Provides the {@link NodeScheduleFetcher} service using the live Node.js release schedule.
1979
+ *
1980
+ * Fetches the Node.js release schedule from the raw GitHub content URL for
1981
+ * `nodejs/Release` (`schedule.json` on the `main` branch) using the HTTP client
1982
+ * on the provided {@link GitHubClient} service. The schedule data maps major
1983
+ * version codenames to their start, LTS, and end-of-life dates and is used by
1984
+ * {@link NodeReleaseCache} to compute per-release lifecycle phases.
1985
+ *
1986
+ * This layer is a required dependency of {@link AutoNodeCacheLive} and
1987
+ * {@link FreshNodeCacheLive}. It is not needed by {@link OfflineNodeCacheLive}.
1988
+ *
1989
+ * @see {@link NodeScheduleFetcher}
1990
+ * @see {@link GitHubClient}
1991
+ * @see {@link AutoNodeCacheLive}
1992
+ * @see {@link FreshNodeCacheLive}
1993
+ * @public
241
1994
  */
242
- export declare const NetworkErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
243
- readonly _tag: "NetworkError";
244
- } & Readonly<A>;
245
-
246
- export declare const NodePhase: Schema.Literal<["current", "active-lts", "maintenance-lts", "end-of-life"]>;
247
-
248
- export declare type NodePhase = typeof NodePhase.Type;
1995
+ export declare const NodeScheduleFetcherLive: Layer.Layer<NodeScheduleFetcher, never, GitHubClient>;
249
1996
 
250
1997
  /**
251
- * Service interface for resolving Node.js runtime versions.
1998
+ * Service for fetching Node.js release data from the official
1999
+ * Node.js distribution index.
2000
+ *
2001
+ * Retrieves both the parsed semver version list and the raw
2002
+ * {@link NodeReleaseInput} records needed to populate a
2003
+ * {@link NodeReleaseCache}. The live implementation fetches from
2004
+ * `https://nodejs.org/dist/index.json` via a plain HTTP request — no GitHub
2005
+ * credentials are required.
2006
+ *
2007
+ * @example
2008
+ * ```typescript
2009
+ * import type { NodeReleaseInput } from "runtime-resolver";
2010
+ * import { NodeVersionFetcher, NodeVersionFetcherLive } from "runtime-resolver";
2011
+ * import { Effect } from "effect";
2012
+ *
2013
+ * const program = Effect.gen(function* () {
2014
+ * const fetcher = yield* NodeVersionFetcher;
2015
+ * const { versions, inputs } = yield* fetcher.fetch();
2016
+ * console.log(`Fetched ${versions.length} Node.js releases`);
2017
+ * });
2018
+ *
2019
+ * Effect.runPromise(program.pipe(Effect.provide(NodeVersionFetcherLive)));
2020
+ * ```
2021
+ *
2022
+ * @see {@link NodeVersionFetcherLive}
2023
+ * @see {@link NodeReleaseCache}
2024
+ * @see {@link NetworkError}
2025
+ * @see {@link ParseError}
2026
+ *
2027
+ * @public
252
2028
  */
253
- export declare interface NodeResolver {
254
- readonly resolve: (options?: NodeResolverOptions) => Effect.Effect<ResolvedVersions, NodeResolverError>;
255
- readonly resolveVersion: (versionOrRange: string) => Effect.Effect<string, NodeResolverError>;
2029
+ export declare class NodeVersionFetcher extends NodeVersionFetcher_base {
256
2030
  }
257
2031
 
2032
+ declare const NodeVersionFetcher_base: Context.TagClass<NodeVersionFetcher, "runtime-resolver/NodeVersionFetcher", {
2033
+ /**
2034
+ * Fetches all available Node.js releases from the distribution index and
2035
+ * returns both the parsed semver version objects and the raw release input
2036
+ * records.
2037
+ *
2038
+ * Fails with:
2039
+ * - {@link NetworkError} — the upstream request could not be completed.
2040
+ * - {@link ParseError} — the response payload could not be decoded.
2041
+ */
2042
+ readonly fetch: () => Effect.Effect<{
2043
+ readonly versions: readonly SemVer[];
2044
+ readonly inputs: readonly {
2045
+ readonly version: string;
2046
+ readonly npm: string;
2047
+ readonly date: string;
2048
+ }[];
2049
+ }, NetworkError | ParseError, never>;
2050
+ }>;
2051
+
258
2052
  /**
259
- * @internal Uses GenericTag — see BunResolver.ts for rationale.
2053
+ * Provides the {@link NodeVersionFetcher} service using the live Node.js dist index.
2054
+ *
2055
+ * Fetches the full Node.js version list from `https://nodejs.org/dist/index.json`
2056
+ * using the HTTP client on the provided {@link GitHubClient} service. Version
2057
+ * strings are normalized by stripping the leading `v` before parsing as semver.
2058
+ * Entries with non-string `npm` fields (present in very old releases prior to
2059
+ * v0.6.3) are substituted with the sentinel version `"0.0.0"`.
2060
+ *
2061
+ * This layer is a required dependency of {@link AutoNodeCacheLive} and
2062
+ * {@link FreshNodeCacheLive}. It is not needed by {@link OfflineNodeCacheLive}.
2063
+ *
2064
+ * @see {@link NodeVersionFetcher}
2065
+ * @see {@link GitHubClient}
2066
+ * @see {@link AutoNodeCacheLive}
2067
+ * @see {@link FreshNodeCacheLive}
2068
+ * @public
260
2069
  */
261
- export declare const NodeResolver: Context.Tag<NodeResolver, NodeResolver>;
262
-
263
- export declare type NodeResolverError = AuthenticationError | NetworkError | ParseError | RateLimitError | VersionNotFoundError | InvalidInputError | FreshnessError | CacheError;
264
-
265
- export declare const NodeResolverLive: Layer.Layer<NodeResolver, never, GitHubClient | VersionCache>;
266
-
267
- export declare interface NodeResolverOptions {
268
- readonly semverRange?: string;
269
- readonly defaultVersion?: string;
270
- readonly phases?: ReadonlyArray<NodePhase>;
271
- readonly increments?: Increments;
272
- readonly date?: Date;
273
- readonly freshness?: Freshness;
274
- }
2070
+ export declare const NodeVersionFetcherLive: Layer.Layer<NodeVersionFetcher, never, GitHubClient>;
275
2071
 
276
2072
  /**
277
- * Service interface for Octokit REST API client.
2073
+ * Thin wrapper around the subset of Octokit's REST client used by
2074
+ * {@link GitHubClient}.
2075
+ *
2076
+ * Exposing only the repository methods actually consumed by this library
2077
+ * keeps the surface area narrow and makes the interface straightforward to
2078
+ * stub in tests without pulling in the full Octokit type.
2079
+ *
2080
+ * An `OctokitInstance` is provided to the dependency graph by one of the
2081
+ * three authentication layers: {@link GitHubTokenAuth}, {@link GitHubAppAuth},
2082
+ * or {@link GitHubAutoAuth}.
2083
+ *
2084
+ * @example
2085
+ * ```typescript
2086
+ * import { OctokitInstance, GitHubClientLive, GitHubAutoAuth } from "runtime-resolver";
2087
+ * import { Effect } from "effect";
2088
+ *
2089
+ * const program = Effect.gen(function* () {
2090
+ * const octokit = yield* OctokitInstance;
2091
+ * const response = await octokit.rest.repos.listTags({
2092
+ * owner: "oven-sh",
2093
+ * repo: "bun",
2094
+ * per_page: 10,
2095
+ * });
2096
+ * console.log(response.data.length);
2097
+ * });
2098
+ *
2099
+ * Effect.runPromise(program.pipe(Effect.provide(GitHubAutoAuth)));
2100
+ * ```
2101
+ *
2102
+ * @see {@link GitHubClientLive}
2103
+ * @see {@link GitHubTokenAuth}
2104
+ * @see {@link GitHubAppAuth}
2105
+ * @see {@link GitHubAutoAuth}
2106
+ *
2107
+ * @public
278
2108
  */
279
- export declare interface OctokitInstance {
2109
+ export declare class OctokitInstance extends OctokitInstance_base {
2110
+ }
2111
+
2112
+ declare const OctokitInstance_base: Context.TagClass<OctokitInstance, "runtime-resolver/OctokitInstance", {
280
2113
  readonly rest: {
281
2114
  readonly repos: {
2115
+ /**
2116
+ * Lists git tags for a repository, corresponding to
2117
+ * `GET /repos/{owner}/{repo}/tags`.
2118
+ *
2119
+ * @param params - Owner, repo, and optional pagination parameters.
2120
+ */
282
2121
  readonly listTags: (params: {
283
2122
  owner: string;
284
2123
  repo: string;
285
2124
  per_page?: number;
286
2125
  page?: number;
287
2126
  }) => Promise<{
288
- data: Array<unknown>;
2127
+ data: unknown[];
289
2128
  }>;
2129
+ /**
2130
+ * Lists GitHub Releases for a repository, corresponding to
2131
+ * `GET /repos/{owner}/{repo}/releases`.
2132
+ *
2133
+ * @param params - Owner, repo, and optional pagination parameters.
2134
+ */
290
2135
  readonly listReleases: (params: {
291
2136
  owner: string;
292
2137
  repo: string;
293
2138
  per_page?: number;
294
2139
  page?: number;
295
2140
  }) => Promise<{
296
- data: Array<unknown>;
2141
+ data: unknown[];
297
2142
  }>;
298
2143
  };
299
2144
  };
300
- }
2145
+ }>;
2146
+
2147
+ /**
2148
+ * Provides the {@link BunReleaseCache} service using the "Offline" cache strategy.
2149
+ *
2150
+ * Uses only the bundled default Bun release data that was baked in at build time.
2151
+ * Makes no network requests and has no runtime dependencies. This layer always
2152
+ * succeeds immediately.
2153
+ *
2154
+ * Use this strategy in environments where network access is unavailable or
2155
+ * undesirable (e.g., air-gapped systems, hermetic test environments, or any
2156
+ * context where version data from the bundle is sufficient).
2157
+ *
2158
+ * The three Bun cache strategy layers are:
2159
+ * - {@link AutoBunCacheLive} — tries API first, falls back to bundled defaults on any error
2160
+ * - {@link FreshBunCacheLive} — requires live API data, fails with `FreshnessError` if unavailable
2161
+ * - `OfflineBunCacheLive` — uses only bundled defaults, no network I/O
2162
+ *
2163
+ * @see {@link BunReleaseCache}
2164
+ * @see {@link AutoBunCacheLive}
2165
+ * @see {@link FreshBunCacheLive}
2166
+ * @public
2167
+ */
2168
+ export declare const OfflineBunCacheLive: Layer.Layer<BunReleaseCache>;
2169
+
2170
+ /**
2171
+ * Provides the {@link DenoReleaseCache} service using the "Offline" cache strategy.
2172
+ *
2173
+ * Uses only the bundled default Deno release data that was baked in at build time.
2174
+ * Makes no network requests and has no runtime dependencies. This layer always
2175
+ * succeeds immediately.
2176
+ *
2177
+ * Use this strategy in environments where network access is unavailable or
2178
+ * undesirable (e.g., air-gapped systems, hermetic test environments, or any
2179
+ * context where version data from the bundle is sufficient).
2180
+ *
2181
+ * The three Deno cache strategy layers are:
2182
+ * - {@link AutoDenoCacheLive} — tries API first, falls back to bundled defaults on any error
2183
+ * - {@link FreshDenoCacheLive} — requires live API data, fails with `FreshnessError` if unavailable
2184
+ * - `OfflineDenoCacheLive` — uses only bundled defaults, no network I/O
2185
+ *
2186
+ * @see {@link DenoReleaseCache}
2187
+ * @see {@link AutoDenoCacheLive}
2188
+ * @see {@link FreshDenoCacheLive}
2189
+ * @public
2190
+ */
2191
+ export declare const OfflineDenoCacheLive: Layer.Layer<DenoReleaseCache>;
301
2192
 
302
2193
  /**
303
- * @internal Uses GenericTag — see BunResolver.ts for rationale.
2194
+ * Provides the {@link NodeReleaseCache} service using the "Offline" cache strategy.
2195
+ *
2196
+ * Uses only the bundled default Node.js release data and schedule data that were
2197
+ * baked in at build time. Makes no network requests and has no runtime
2198
+ * dependencies. This layer always succeeds immediately.
2199
+ *
2200
+ * Use this strategy in environments where network access is unavailable or
2201
+ * undesirable (e.g., air-gapped systems, hermetic test environments, or any
2202
+ * context where version data from the bundle is sufficient).
2203
+ *
2204
+ * The three Node cache strategy layers are:
2205
+ * - {@link AutoNodeCacheLive} — tries API first, falls back to bundled defaults on any error
2206
+ * - {@link FreshNodeCacheLive} — requires live API data, fails with `FreshnessError` if unavailable
2207
+ * - `OfflineNodeCacheLive` — uses only bundled defaults, no network I/O
2208
+ *
2209
+ * @see {@link NodeReleaseCache}
2210
+ * @see {@link AutoNodeCacheLive}
2211
+ * @see {@link FreshNodeCacheLive}
2212
+ * @public
304
2213
  */
305
- export declare const OctokitInstance: Context.Tag<OctokitInstance, OctokitInstance>;
2214
+ export declare const OfflineNodeCacheLive: Layer.Layer<NodeReleaseCache>;
306
2215
 
307
- export declare class ParseError extends ParseErrorBase<{
2216
+ /**
2217
+ * Raised when the body of an upstream API response cannot be decoded.
2218
+ *
2219
+ * This error is produced after a successful HTTP response is received but
2220
+ * its payload does not match the expected shape — for example, when a release
2221
+ * index returns malformed JSON or when a required field is missing from the
2222
+ * response structure. The `source` field identifies the URL or data source
2223
+ * that produced the unparseable content.
2224
+ *
2225
+ * @example
2226
+ * ```typescript
2227
+ * import { Effect } from "effect";
2228
+ * import type { ParseError } from "./ParseError.js";
2229
+ * import { resolveNode } from "../resolvers/node.js";
2230
+ *
2231
+ * const program = resolveNode(">=20").pipe(
2232
+ * Effect.catchTag("ParseError", (err: ParseError) =>
2233
+ * Effect.logError(
2234
+ * `Failed to parse response from ${err.source}: ${err.message}`
2235
+ * ).pipe(Effect.andThen(Effect.fail(err)))
2236
+ * )
2237
+ * );
2238
+ * ```
2239
+ *
2240
+ * @public
2241
+ */
2242
+ export declare class ParseError extends ParseError_base<{
308
2243
  readonly source: string;
309
2244
  readonly message: string;
310
2245
  }> {
311
2246
  }
312
2247
 
313
- /**
314
- * @internal
315
- * Exported for declaration bundling — see AuthenticationError.ts for details.
316
- */
317
- export declare const ParseErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
2248
+ declare const ParseError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
318
2249
  readonly _tag: "ParseError";
319
2250
  } & Readonly<A>;
320
2251
 
321
- export declare class RateLimitError extends RateLimitErrorBase<{
2252
+ /**
2253
+ * Raised when a GitHub API rate limit is exceeded.
2254
+ *
2255
+ * The `limit` and `remaining` fields reflect the values returned in the
2256
+ * `X-RateLimit-Limit` and `X-RateLimit-Remaining` response headers. When the
2257
+ * API indicates how long to wait before the quota resets, that duration (in
2258
+ * seconds) is available via `retryAfter`.
2259
+ *
2260
+ * Use {@link retryOnRateLimit} to automatically retry an effect with
2261
+ * exponential backoff whenever this error is encountered.
2262
+ *
2263
+ * @see {@link retryOnRateLimit}
2264
+ *
2265
+ * @example
2266
+ * ```typescript
2267
+ * import { Effect } from "effect";
2268
+ * import type { RateLimitError } from "./RateLimitError.js";
2269
+ * import { resolveNode } from "../resolvers/node.js";
2270
+ *
2271
+ * const program = resolveNode(">=20").pipe(
2272
+ * Effect.catchTag("RateLimitError", (err: RateLimitError) =>
2273
+ * Effect.logWarning(
2274
+ * `Rate limit hit (${err.remaining}/${err.limit} remaining)` +
2275
+ * (err.retryAfter != null ? `, retry after ${err.retryAfter}s` : "") +
2276
+ * `: ${err.message}`
2277
+ * ).pipe(Effect.andThen(Effect.fail(err)))
2278
+ * )
2279
+ * );
2280
+ * ```
2281
+ *
2282
+ * @public
2283
+ */
2284
+ export declare class RateLimitError extends RateLimitError_base<{
322
2285
  readonly retryAfter?: number;
323
2286
  readonly limit: number;
324
2287
  readonly remaining: number;
@@ -326,19 +2289,36 @@ export declare class RateLimitError extends RateLimitErrorBase<{
326
2289
  }> {
327
2290
  }
328
2291
 
329
- /**
330
- * @internal
331
- * Exported for declaration bundling — see AuthenticationError.ts for details.
332
- */
333
- export declare const RateLimitErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
2292
+ declare const RateLimitError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
334
2293
  readonly _tag: "RateLimitError";
335
2294
  } & Readonly<A>;
336
2295
 
337
2296
  /**
338
2297
  * Resolve Bun versions matching the given options.
339
2298
  *
340
- * Uses GITHUB_PERSONAL_ACCESS_TOKEN or GITHUB_TOKEN for authentication.
341
- * Falls back to build-time cache when the network is unavailable.
2299
+ * Fetches release data from the Bun GitHub repository, then filters and ranks
2300
+ * versions according to `options`. Authentication is read from
2301
+ * `GITHUB_PERSONAL_ACCESS_TOKEN` or `GITHUB_TOKEN` environment variables.
2302
+ * Falls back to a build-time offline cache when the network is unavailable.
2303
+ *
2304
+ * @param options - Filtering and ranking options. See {@link BunResolverOptions}.
2305
+ * @returns A promise that resolves to a {@link ResolvedVersions} object
2306
+ * containing the matched version list and the highest matching version as
2307
+ * `latest`.
2308
+ *
2309
+ * @example
2310
+ * ```ts
2311
+ * import type { ResolvedVersions } from "runtime-resolver";
2312
+ * import { resolveBun } from "runtime-resolver";
2313
+ *
2314
+ * const result: ResolvedVersions = await resolveBun({ semverRange: "^1.0.0" });
2315
+ * console.log(result.latest);
2316
+ * ```
2317
+ *
2318
+ * @see {@link BunResolver} for the Effect-based API
2319
+ * @see {@link BunResolverOptions} for available options
2320
+ *
2321
+ * @public
342
2322
  */
343
2323
  export declare const resolveBun: (options?: BunResolverOptions | undefined) => Promise<{
344
2324
  readonly source: "api" | "cache";
@@ -351,8 +2331,29 @@ export declare const resolveBun: (options?: BunResolverOptions | undefined) => P
351
2331
  /**
352
2332
  * Resolve Deno versions matching the given options.
353
2333
  *
354
- * Uses GITHUB_PERSONAL_ACCESS_TOKEN or GITHUB_TOKEN for authentication.
355
- * Falls back to build-time cache when the network is unavailable.
2334
+ * Fetches release data from the Deno GitHub repository, then filters and ranks
2335
+ * versions according to `options`. Authentication is read from
2336
+ * `GITHUB_PERSONAL_ACCESS_TOKEN` or `GITHUB_TOKEN` environment variables.
2337
+ * Falls back to a build-time offline cache when the network is unavailable.
2338
+ *
2339
+ * @param options - Filtering and ranking options. See {@link DenoResolverOptions}.
2340
+ * @returns A promise that resolves to a {@link ResolvedVersions} object
2341
+ * containing the matched version list and the highest matching version as
2342
+ * `latest`.
2343
+ *
2344
+ * @example
2345
+ * ```ts
2346
+ * import type { ResolvedVersions } from "runtime-resolver";
2347
+ * import { resolveDeno } from "runtime-resolver";
2348
+ *
2349
+ * const result: ResolvedVersions = await resolveDeno({ semverRange: "^2.0.0" });
2350
+ * console.log(result.latest);
2351
+ * ```
2352
+ *
2353
+ * @see {@link DenoResolver} for the Effect-based API
2354
+ * @see {@link DenoResolverOptions} for available options
2355
+ *
2356
+ * @public
356
2357
  */
357
2358
  export declare const resolveDeno: (options?: DenoResolverOptions | undefined) => Promise<{
358
2359
  readonly source: "api" | "cache";
@@ -362,6 +2363,35 @@ export declare const resolveDeno: (options?: DenoResolverOptions | undefined) =>
362
2363
  readonly default?: string | undefined;
363
2364
  }>;
364
2365
 
2366
+ /**
2367
+ * The primary return type produced by every runtime resolver.
2368
+ *
2369
+ * Contains the full list of matching versions, the latest version string, and
2370
+ * optional `lts` / `default` version strings depending on the runtime.
2371
+ * The `source` field indicates whether the data came from a live API call or
2372
+ * a cached snapshot.
2373
+ *
2374
+ * @see {@link resolveNode}
2375
+ * @see {@link resolveBun}
2376
+ * @see {@link resolveDeno}
2377
+ *
2378
+ * @example
2379
+ * ```typescript
2380
+ * import { Effect } from "effect";
2381
+ * import type { ResolvedVersions } from "./common.js";
2382
+ * import { resolveNode } from "../resolvers/node.js";
2383
+ *
2384
+ * const program: Effect.Effect<ResolvedVersions> = resolveNode(">=20");
2385
+ *
2386
+ * Effect.runPromise(program).then((result: ResolvedVersions) => {
2387
+ * console.log(result.latest); // e.g. "22.3.0"
2388
+ * console.log(result.source); // "api" | "cache"
2389
+ * console.log(result.versions); // ["22.3.0", "22.2.0", ...]
2390
+ * });
2391
+ * ```
2392
+ *
2393
+ * @public
2394
+ */
365
2395
  export declare const ResolvedVersions: Schema.Struct<{
366
2396
  source: Schema.Literal<["api", "cache"]>;
367
2397
  versions: Schema.Array$<typeof Schema.String>;
@@ -370,13 +2400,48 @@ export declare const ResolvedVersions: Schema.Struct<{
370
2400
  default: Schema.optional<typeof Schema.String>;
371
2401
  }>;
372
2402
 
2403
+ /**
2404
+ * The primary return type produced by every runtime resolver.
2405
+ *
2406
+ * @see {@link resolveNode}
2407
+ * @see {@link resolveBun}
2408
+ * @see {@link resolveDeno}
2409
+ *
2410
+ * @public
2411
+ */
373
2412
  export declare type ResolvedVersions = typeof ResolvedVersions.Type;
374
2413
 
375
2414
  /**
376
2415
  * Resolve Node.js versions matching the given options.
377
2416
  *
378
- * Uses GITHUB_PERSONAL_ACCESS_TOKEN or GITHUB_TOKEN for authentication.
379
- * Falls back to build-time cache when the network is unavailable.
2417
+ * Fetches release data from the Node.js GitHub repository and the official
2418
+ * release schedule API, then filters and ranks versions according to
2419
+ * `options`. Authentication is read from `GITHUB_PERSONAL_ACCESS_TOKEN` or
2420
+ * `GITHUB_TOKEN` environment variables. Falls back to a build-time offline
2421
+ * cache when the network is unavailable.
2422
+ *
2423
+ * @param options - Filtering and ranking options. See {@link NodeResolverOptions}.
2424
+ * @returns A promise that resolves to a {@link ResolvedVersions} object
2425
+ * containing the matched version list, the highest matching version as
2426
+ * `latest`, and optional `lts` / `default` strings.
2427
+ *
2428
+ * @example
2429
+ * ```ts
2430
+ * import type { ResolvedVersions } from "runtime-resolver";
2431
+ * import { resolveNode } from "runtime-resolver";
2432
+ *
2433
+ * const result: ResolvedVersions = await resolveNode({
2434
+ * semverRange: ">=20.0.0",
2435
+ * phases: ["active-lts"],
2436
+ * });
2437
+ * console.log(result.latest); // e.g. "22.11.0"
2438
+ * console.log(result.versions); // all matching versions
2439
+ * ```
2440
+ *
2441
+ * @see {@link NodeResolver} for the Effect-based API
2442
+ * @see {@link NodeResolverOptions} for available options
2443
+ *
2444
+ * @public
380
2445
  */
381
2446
  export declare const resolveNode: (options?: NodeResolverOptions | undefined) => Promise<{
382
2447
  readonly source: "api" | "cache";
@@ -386,44 +2451,222 @@ export declare const resolveNode: (options?: NodeResolverOptions | undefined) =>
386
2451
  readonly default?: string | undefined;
387
2452
  }>;
388
2453
 
2454
+ /**
2455
+ * Discriminates which JavaScript runtime a resolver targets.
2456
+ *
2457
+ * @see {@link resolveNode}
2458
+ * @see {@link resolveBun}
2459
+ * @see {@link resolveDeno}
2460
+ *
2461
+ * @public
2462
+ */
389
2463
  export declare const Runtime: Schema.Literal<["node", "bun", "deno"]>;
390
2464
 
2465
+ /**
2466
+ * Discriminates which JavaScript runtime a resolver targets.
2467
+ *
2468
+ * @see {@link resolveNode}
2469
+ * @see {@link resolveBun}
2470
+ * @see {@link resolveDeno}
2471
+ *
2472
+ * @public
2473
+ */
391
2474
  export declare type Runtime = typeof Runtime.Type;
392
2475
 
393
- export declare const Source: Schema.Literal<["api", "cache"]>;
394
-
395
- export declare type Source = typeof Source.Type;
2476
+ /**
2477
+ * Generic in-memory cache service for a typed runtime release collection,
2478
+ * built on top of semver-effect's version cache primitives.
2479
+ *
2480
+ * Concrete instantiations are {@link BunReleaseCache}, {@link DenoReleaseCache},
2481
+ * and {@link NodeReleaseCache}. The canonical factory function for obtaining a
2482
+ * `RuntimeCache` implementation is {@link createRuntimeCache}.
2483
+ *
2484
+ * @typeParam R - A {@link RuntimeRelease} subtype specific to the runtime
2485
+ * (e.g. `BunRelease`, `DenoRelease`, `NodeRelease`).
2486
+ *
2487
+ * @example
2488
+ * ```typescript
2489
+ * import type { BunRelease } from "runtime-resolver";
2490
+ * import { BunReleaseCache, OfflineBunCacheLive } from "runtime-resolver";
2491
+ * import { Effect } from "effect";
2492
+ *
2493
+ * const program = Effect.gen(function* () {
2494
+ * const cache = yield* BunReleaseCache;
2495
+ * const latest = yield* cache.latest();
2496
+ * console.log(latest.version.toString());
2497
+ * });
2498
+ *
2499
+ * Effect.runPromise(program.pipe(Effect.provide(OfflineBunCacheLive)));
2500
+ * ```
2501
+ *
2502
+ * @see {@link BunReleaseCache}
2503
+ * @see {@link DenoReleaseCache}
2504
+ * @see {@link NodeReleaseCache}
2505
+ * @see {@link createRuntimeCache}
2506
+ *
2507
+ * @public
2508
+ */
2509
+ export declare interface RuntimeCache<R extends RuntimeRelease> {
2510
+ /**
2511
+ * Populates the cache with the given array of typed release objects,
2512
+ * replacing any previously loaded entries.
2513
+ *
2514
+ * @param releases - The full set of releases to store.
2515
+ */
2516
+ readonly load: (releases: ReadonlyArray<R>) => Effect.Effect<void>;
2517
+ /**
2518
+ * Finds the single best-matching release for the given semver range string,
2519
+ * preferring the highest satisfying version.
2520
+ *
2521
+ * Fails with {@link InvalidRangeError} when `range` is not a valid semver
2522
+ * range expression, or with {@link UnsatisfiedRangeError} when no cached
2523
+ * release satisfies the range.
2524
+ *
2525
+ * @param range - A semver range string (e.g. `">=1.0.0 <2.0.0"`).
2526
+ */
2527
+ readonly resolve: (range: string) => Effect.Effect<R, InvalidRangeError | UnsatisfiedRangeError>;
2528
+ /**
2529
+ * Returns the complete list of all releases currently held in the cache,
2530
+ * in the order they were loaded.
2531
+ */
2532
+ readonly releases: () => Effect.Effect<ReadonlyArray<R>>;
2533
+ /**
2534
+ * Returns all cached releases whose version satisfies the given semver
2535
+ * range string.
2536
+ *
2537
+ * Fails with {@link InvalidRangeError} when `range` is not a valid semver
2538
+ * range expression.
2539
+ *
2540
+ * @param range - A semver range string (e.g. `"^20.0.0"`).
2541
+ */
2542
+ readonly filter: (range: string) => Effect.Effect<ReadonlyArray<R>, InvalidRangeError>;
2543
+ /**
2544
+ * Returns the release with the highest version among all entries in the
2545
+ * cache.
2546
+ *
2547
+ * Fails with {@link EmptyCacheError} when the cache contains no releases.
2548
+ */
2549
+ readonly latest: () => Effect.Effect<R, EmptyCacheError>;
2550
+ /**
2551
+ * Returns one release per major version line, each being the highest
2552
+ * available version within that major line.
2553
+ *
2554
+ * Returns an empty array when the cache is empty.
2555
+ */
2556
+ readonly latestByMajor: () => Effect.Effect<ReadonlyArray<R>>;
2557
+ /**
2558
+ * Returns one release per minor version line, each being the highest
2559
+ * available patch version within that minor line.
2560
+ *
2561
+ * Returns an empty array when the cache is empty.
2562
+ */
2563
+ readonly latestByMinor: () => Effect.Effect<ReadonlyArray<R>>;
2564
+ }
396
2565
 
397
2566
  /**
398
- * Service interface for version data caching.
2567
+ * Structural base constraint shared by {@link BunRelease}, {@link DenoRelease},
2568
+ * and {@link NodeRelease}.
2569
+ *
2570
+ * Enables the generic `RuntimeCache<R extends RuntimeRelease>` service to
2571
+ * operate over any runtime release type without coupling to a concrete class.
2572
+ *
2573
+ * @see {@link RuntimeCache}
2574
+ * @see {@link BunRelease}
2575
+ * @see {@link DenoRelease}
2576
+ * @see {@link NodeRelease}
2577
+ *
2578
+ * @public
399
2579
  */
400
- export declare interface VersionCache {
401
- readonly get: (runtime: Runtime) => Effect.Effect<{
402
- data: CachedData;
403
- source: Source;
404
- }, CacheError>;
405
- readonly set: (runtime: Runtime, data: CachedData) => Effect.Effect<void, CacheError>;
2580
+ export declare interface RuntimeRelease {
2581
+ readonly _tag: string;
2582
+ readonly version: SemVer;
2583
+ readonly date: DateTime.DateTime;
406
2584
  }
407
2585
 
408
2586
  /**
409
- * @internal Uses GenericTag — see BunResolver.ts for rationale.
2587
+ * Lean input schema used to construct {@link BunRelease} and
2588
+ * {@link DenoRelease} instances from bundled default data files.
2589
+ *
2590
+ * Both Bun and Deno source their offline snapshots from JSON/TS data files
2591
+ * whose entries conform to this shape. The `version` and `date` fields are
2592
+ * plain strings that are subsequently parsed by `SemVer.parse` and
2593
+ * `DateTime.unsafeMake` respectively.
2594
+ *
2595
+ * @see {@link BunRelease.fromInput}
2596
+ * @see {@link DenoRelease.fromInput}
2597
+ *
2598
+ * @public
2599
+ */
2600
+ export declare const RuntimeReleaseInput: Schema.Struct<{
2601
+ version: typeof Schema.String;
2602
+ date: typeof Schema.String;
2603
+ }>;
2604
+
2605
+ /**
2606
+ * Lean input type used to construct {@link BunRelease} and {@link DenoRelease}
2607
+ * instances from bundled default data files.
2608
+ *
2609
+ * @see {@link BunRelease.fromInput}
2610
+ * @see {@link DenoRelease.fromInput}
2611
+ *
2612
+ * @public
2613
+ */
2614
+ export declare type RuntimeReleaseInput = typeof RuntimeReleaseInput.Type;
2615
+
2616
+ /**
2617
+ * Indicates whether a {@link ResolvedVersions} result was served from
2618
+ * a live API call or a local cache.
2619
+ *
2620
+ * @public
410
2621
  */
411
- export declare const VersionCache: Context.Tag<VersionCache, VersionCache>;
2622
+ export declare const Source: Schema.Literal<["api", "cache"]>;
412
2623
 
413
- export declare const VersionCacheLive: Layer.Layer<VersionCache>;
2624
+ /**
2625
+ * Indicates whether a {@link ResolvedVersions} result was served from
2626
+ * a live API call or a local cache.
2627
+ *
2628
+ * @public
2629
+ */
2630
+ export declare type Source = typeof Source.Type;
414
2631
 
415
- export declare class VersionNotFoundError extends VersionNotFoundErrorBase<{
2632
+ /**
2633
+ * Raised when no published version satisfies the requested constraint.
2634
+ *
2635
+ * This error is produced by {@link NodeResolver}, {@link BunResolver}, and
2636
+ * {@link DenoResolver} after the full list of available versions has been
2637
+ * fetched and filtered but no entry matches the caller-supplied semver
2638
+ * constraint. The `runtime` field identifies which runtime was queried and
2639
+ * `constraint` echoes the range string that was provided.
2640
+ *
2641
+ * @see {@link NodeResolver}
2642
+ * @see {@link BunResolver}
2643
+ * @see {@link DenoResolver}
2644
+ *
2645
+ * @example
2646
+ * ```typescript
2647
+ * import { Effect } from "effect";
2648
+ * import type { VersionNotFoundError } from "./VersionNotFoundError.js";
2649
+ * import { resolveNode } from "../resolvers/node.js";
2650
+ *
2651
+ * const program = resolveNode(">=999").pipe(
2652
+ * Effect.catchTag("VersionNotFoundError", (err: VersionNotFoundError) =>
2653
+ * Effect.logError(
2654
+ * `No ${err.runtime} version matched "${err.constraint}": ${err.message}`
2655
+ * ).pipe(Effect.andThen(Effect.fail(err)))
2656
+ * )
2657
+ * );
2658
+ * ```
2659
+ *
2660
+ * @public
2661
+ */
2662
+ export declare class VersionNotFoundError extends VersionNotFoundError_base<{
416
2663
  readonly runtime: Runtime;
417
2664
  readonly constraint: string;
418
2665
  readonly message: string;
419
2666
  }> {
420
2667
  }
421
2668
 
422
- /**
423
- * @internal
424
- * Exported for declaration bundling — see AuthenticationError.ts for details.
425
- */
426
- export declare const VersionNotFoundErrorBase: new <A extends Record<string, any> = {}>(args: Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => YieldableError & {
2669
+ declare const VersionNotFoundError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty< { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
427
2670
  readonly _tag: "VersionNotFoundError";
428
2671
  } & Readonly<A>;
429
2672