runtime-resolver 0.3.12 → 0.3.13
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/bin/runtime-resolver.d.ts +1 -0
- package/bin/runtime-resolver.js +30 -261
- package/bin/runtime-resolver.js.map +1 -0
- package/cli/commands/resolve.js +240 -0
- package/cli/commands/resolve.js.map +1 -0
- package/cli/schemas/json-schema.js +13 -0
- package/cli/schemas/json-schema.js.map +1 -0
- package/cli/schemas/response.js +87 -0
- package/cli/schemas/response.js.map +1 -0
- package/data/bun-defaults.js +851 -0
- package/data/bun-defaults.js.map +1 -0
- package/data/deno-defaults.js +1523 -0
- package/data/deno-defaults.js.map +1 -0
- package/data/node-defaults.js +4401 -0
- package/data/node-defaults.js.map +1 -0
- package/errors/AuthenticationError.js +36 -0
- package/errors/AuthenticationError.js.map +1 -0
- package/errors/CacheError.js +33 -0
- package/errors/CacheError.js.map +1 -0
- package/errors/FreshnessError.js +39 -0
- package/errors/FreshnessError.js.map +1 -0
- package/errors/InvalidInputError.js +34 -0
- package/errors/InvalidInputError.js.map +1 -0
- package/errors/NetworkError.js +35 -0
- package/errors/NetworkError.js.map +1 -0
- package/errors/ParseError.js +34 -0
- package/errors/ParseError.js.map +1 -0
- package/errors/RateLimitError.js +40 -0
- package/errors/RateLimitError.js.map +1 -0
- package/errors/VersionNotFoundError.js +38 -0
- package/errors/VersionNotFoundError.js.map +1 -0
- package/index.d.ts +2577 -2608
- package/index.d.ts.map +1 -0
- package/index.js +159 -132
- package/index.js.map +1 -0
- package/layers/AutoBunCacheLive.js +48 -0
- package/layers/AutoBunCacheLive.js.map +1 -0
- package/layers/AutoDenoCacheLive.js +48 -0
- package/layers/AutoDenoCacheLive.js.map +1 -0
- package/layers/AutoNodeCacheLive.js +59 -0
- package/layers/AutoNodeCacheLive.js.map +1 -0
- package/layers/BunReleaseCacheLive.js +26 -0
- package/layers/BunReleaseCacheLive.js.map +1 -0
- package/layers/BunResolverLive.js +86 -0
- package/layers/BunResolverLive.js.map +1 -0
- package/layers/BunVersionFetcherLive.js +56 -0
- package/layers/BunVersionFetcherLive.js.map +1 -0
- package/layers/DenoReleaseCacheLive.js +26 -0
- package/layers/DenoReleaseCacheLive.js.map +1 -0
- package/layers/DenoResolverLive.js +85 -0
- package/layers/DenoResolverLive.js.map +1 -0
- package/layers/DenoVersionFetcherLive.js +52 -0
- package/layers/DenoVersionFetcherLive.js.map +1 -0
- package/layers/FreshBunCacheLive.js +61 -0
- package/layers/FreshBunCacheLive.js.map +1 -0
- package/layers/FreshDenoCacheLive.js +61 -0
- package/layers/FreshDenoCacheLive.js.map +1 -0
- package/layers/FreshNodeCacheLive.js +56 -0
- package/layers/FreshNodeCacheLive.js.map +1 -0
- package/layers/GitHubAppAuth.js +59 -0
- package/layers/GitHubAppAuth.js.map +1 -0
- package/layers/GitHubAutoAuth.js +71 -0
- package/layers/GitHubAutoAuth.js.map +1 -0
- package/layers/GitHubClientLive.js +131 -0
- package/layers/GitHubClientLive.js.map +1 -0
- package/layers/GitHubTokenAuth.js +51 -0
- package/layers/GitHubTokenAuth.js.map +1 -0
- package/layers/NodeReleaseCacheLive.js +78 -0
- package/layers/NodeReleaseCacheLive.js.map +1 -0
- package/layers/NodeResolverLive.js +108 -0
- package/layers/NodeResolverLive.js.map +1 -0
- package/layers/NodeScheduleFetcherLive.js +38 -0
- package/layers/NodeScheduleFetcherLive.js.map +1 -0
- package/layers/NodeVersionFetcherLive.js +56 -0
- package/layers/NodeVersionFetcherLive.js.map +1 -0
- package/layers/OfflineBunCacheLive.js +41 -0
- package/layers/OfflineBunCacheLive.js.map +1 -0
- package/layers/OfflineDenoCacheLive.js +41 -0
- package/layers/OfflineDenoCacheLive.js.map +1 -0
- package/layers/OfflineNodeCacheLive.js +40 -0
- package/layers/OfflineNodeCacheLive.js.map +1 -0
- package/layers/RuntimeCacheLive.js +79 -0
- package/layers/RuntimeCacheLive.js.map +1 -0
- package/lib/retry.js +28 -0
- package/lib/retry.js.map +1 -0
- package/lib/semver.js +22 -0
- package/lib/semver.js.map +1 -0
- package/package.json +57 -68
- package/schemas/bun-release.js +62 -0
- package/schemas/bun-release.js.map +1 -0
- package/schemas/deno-release.js +62 -0
- package/schemas/deno-release.js.map +1 -0
- package/schemas/github.js +75 -0
- package/schemas/github.js.map +1 -0
- package/schemas/node-release.js +131 -0
- package/schemas/node-release.js.map +1 -0
- package/schemas/node-schedule.js +111 -0
- package/schemas/node-schedule.js.map +1 -0
- package/schemas/node.js +69 -0
- package/schemas/node.js.map +1 -0
- package/services/BunReleaseCache.js +34 -0
- package/services/BunReleaseCache.js.map +1 -0
- package/services/BunResolver.js +37 -0
- package/services/BunResolver.js.map +1 -0
- package/services/BunVersionFetcher.js +42 -0
- package/services/BunVersionFetcher.js.map +1 -0
- package/services/DenoReleaseCache.js +34 -0
- package/services/DenoReleaseCache.js.map +1 -0
- package/services/DenoResolver.js +37 -0
- package/services/DenoResolver.js.map +1 -0
- package/services/DenoVersionFetcher.js +42 -0
- package/services/DenoVersionFetcher.js.map +1 -0
- package/services/GitHubClient.js +41 -0
- package/services/GitHubClient.js.map +1 -0
- package/services/NodeReleaseCache.js +42 -0
- package/services/NodeReleaseCache.js.map +1 -0
- package/services/NodeResolver.js +45 -0
- package/services/NodeResolver.js.map +1 -0
- package/services/NodeScheduleFetcher.js +39 -0
- package/services/NodeScheduleFetcher.js.map +1 -0
- package/services/NodeVersionFetcher.js +40 -0
- package/services/NodeVersionFetcher.js.map +1 -0
- package/services/OctokitInstance.js +45 -0
- package/services/OctokitInstance.js.map +1 -0
- package/tsdoc-metadata.json +11 -11
- package/612.js +0 -23
- package/711.js +0 -7554
- package/json-schema.js +0 -95
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { BunReleaseCache } from "../services/BunReleaseCache.js";
|
|
2
|
+
import { VersionNotFoundError } from "../errors/VersionNotFoundError.js";
|
|
3
|
+
import { BunResolver } from "../services/BunResolver.js";
|
|
4
|
+
import { Effect, Layer } from "effect";
|
|
5
|
+
import { SemVer } from "semver-effect";
|
|
6
|
+
|
|
7
|
+
//#region src/layers/BunResolverLive.ts
|
|
8
|
+
/**
|
|
9
|
+
* Provides the {@link BunResolver} service backed by a {@link BunReleaseCache}.
|
|
10
|
+
*
|
|
11
|
+
* This layer composes with any of the three Bun cache strategy layers to form
|
|
12
|
+
* a complete resolver stack. It implements semver range filtering and increment
|
|
13
|
+
* grouping (latest per major, latest per minor, or all patch versions) using
|
|
14
|
+
* the releases already loaded into the cache.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { BunResolverLive, AutoBunCacheLive, BunVersionFetcherLive, GitHubClientLive, GitHubAutoAuth } from "runtime-resolver";
|
|
19
|
+
* import type { ResolvedVersions } from "runtime-resolver";
|
|
20
|
+
* import { BunResolver } from "runtime-resolver";
|
|
21
|
+
* import { Effect, Layer } from "effect";
|
|
22
|
+
*
|
|
23
|
+
* const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));
|
|
24
|
+
* const CacheLayer = AutoBunCacheLive.pipe(Layer.provide(BunVersionFetcherLive.pipe(Layer.provide(GitHubLayer))));
|
|
25
|
+
* const ResolverLayer = BunResolverLive.pipe(Layer.provide(CacheLayer));
|
|
26
|
+
*
|
|
27
|
+
* const program = Effect.gen(function* () {
|
|
28
|
+
* const resolver = yield* BunResolver;
|
|
29
|
+
* const result = yield* resolver.resolve({ semverRange: "^1.0.0" });
|
|
30
|
+
* console.log(result.latest);
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* Effect.runPromise(program.pipe(Effect.provide(ResolverLayer)));
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @see {@link BunResolver}
|
|
37
|
+
* @see {@link BunReleaseCache}
|
|
38
|
+
* @see {@link AutoBunCacheLive}
|
|
39
|
+
* @see {@link FreshBunCacheLive}
|
|
40
|
+
* @see {@link OfflineBunCacheLive}
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
const BunResolverLive = Layer.effect(BunResolver, Effect.gen(function* () {
|
|
44
|
+
const cache = yield* BunReleaseCache;
|
|
45
|
+
return { resolve: (options) => Effect.gen(function* () {
|
|
46
|
+
const semverRange = options?.semverRange ?? "*";
|
|
47
|
+
const increments = options?.increments ?? "latest";
|
|
48
|
+
const matching = yield* cache.filter(semverRange).pipe(Effect.catchAll(() => Effect.succeed([])));
|
|
49
|
+
let resultReleases;
|
|
50
|
+
if (increments === "latest") {
|
|
51
|
+
const groups = /* @__PURE__ */ new Map();
|
|
52
|
+
for (const r of matching) {
|
|
53
|
+
const existing = groups.get(r.version.major);
|
|
54
|
+
if (!existing || SemVer.gt(r.version, existing.version)) groups.set(r.version.major, r);
|
|
55
|
+
}
|
|
56
|
+
resultReleases = [...groups.values()];
|
|
57
|
+
} else if (increments === "minor") {
|
|
58
|
+
const groups = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const r of matching) {
|
|
60
|
+
const key = `${r.version.major}.${r.version.minor}`;
|
|
61
|
+
const existing = groups.get(key);
|
|
62
|
+
if (!existing || SemVer.gt(r.version, existing.version)) groups.set(key, r);
|
|
63
|
+
}
|
|
64
|
+
resultReleases = [...groups.values()];
|
|
65
|
+
} else resultReleases = [...matching];
|
|
66
|
+
if (resultReleases.length === 0) return yield* Effect.fail(new VersionNotFoundError({
|
|
67
|
+
runtime: "bun",
|
|
68
|
+
constraint: semverRange,
|
|
69
|
+
message: `No Bun versions found matching "${semverRange}"`
|
|
70
|
+
}));
|
|
71
|
+
const versions = SemVer.rsort(resultReleases.map((r) => r.version)).map((v) => v.toString());
|
|
72
|
+
const latest = versions[0];
|
|
73
|
+
let resolvedDefault;
|
|
74
|
+
if (options?.defaultVersion) resolvedDefault = yield* cache.resolve(options.defaultVersion).pipe(Effect.map((r) => r.version.toString()), Effect.catchAll(() => Effect.succeed(void 0)));
|
|
75
|
+
return {
|
|
76
|
+
source: "api",
|
|
77
|
+
versions,
|
|
78
|
+
latest,
|
|
79
|
+
...resolvedDefault ? { default: resolvedDefault } : {}
|
|
80
|
+
};
|
|
81
|
+
}) };
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
export { BunResolverLive };
|
|
86
|
+
//# sourceMappingURL=BunResolverLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BunResolverLive.js","names":[],"sources":["../../../../src/layers/BunResolverLive.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\nimport { SemVer } from \"semver-effect\";\nimport { VersionNotFoundError } from \"../errors/VersionNotFoundError.js\";\nimport type { BunRelease } from \"../schemas/bun-release.js\";\nimport type { Increments } from \"../schemas/common.js\";\nimport { BunReleaseCache } from \"../services/BunReleaseCache.js\";\nimport type { BunResolverOptions } from \"../services/BunResolver.js\";\nimport { BunResolver } from \"../services/BunResolver.js\";\n\n/**\n * Provides the {@link BunResolver} service backed by a {@link BunReleaseCache}.\n *\n * This layer composes with any of the three Bun cache strategy layers to form\n * a complete resolver stack. It implements semver range filtering and increment\n * grouping (latest per major, latest per minor, or all patch versions) using\n * the releases already loaded into the cache.\n *\n * @example\n * ```ts\n * import { BunResolverLive, AutoBunCacheLive, BunVersionFetcherLive, GitHubClientLive, GitHubAutoAuth } from \"runtime-resolver\";\n * import type { ResolvedVersions } from \"runtime-resolver\";\n * import { BunResolver } from \"runtime-resolver\";\n * import { Effect, Layer } from \"effect\";\n *\n * const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));\n * const CacheLayer = AutoBunCacheLive.pipe(Layer.provide(BunVersionFetcherLive.pipe(Layer.provide(GitHubLayer))));\n * const ResolverLayer = BunResolverLive.pipe(Layer.provide(CacheLayer));\n *\n * const program = Effect.gen(function* () {\n * const resolver = yield* BunResolver;\n * const result = yield* resolver.resolve({ semverRange: \"^1.0.0\" });\n * console.log(result.latest);\n * });\n *\n * Effect.runPromise(program.pipe(Effect.provide(ResolverLayer)));\n * ```\n *\n * @see {@link BunResolver}\n * @see {@link BunReleaseCache}\n * @see {@link AutoBunCacheLive}\n * @see {@link FreshBunCacheLive}\n * @see {@link OfflineBunCacheLive}\n * @public\n */\nexport const BunResolverLive: Layer.Layer<BunResolver, never, BunReleaseCache> = Layer.effect(\n\tBunResolver,\n\tEffect.gen(function* () {\n\t\tconst cache = yield* BunReleaseCache;\n\n\t\treturn {\n\t\t\tresolve: (options?: BunResolverOptions) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst semverRange = options?.semverRange ?? \"*\";\n\t\t\t\t\tconst increments: Increments = options?.increments ?? \"latest\";\n\n\t\t\t\t\tconst matching = yield* cache\n\t\t\t\t\t\t.filter(semverRange)\n\t\t\t\t\t\t.pipe(Effect.catchAll(() => Effect.succeed([] as ReadonlyArray<BunRelease>)));\n\n\t\t\t\t\tlet resultReleases: BunRelease[];\n\t\t\t\t\tif (increments === \"latest\") {\n\t\t\t\t\t\tconst groups = new Map<number, BunRelease>();\n\t\t\t\t\t\tfor (const r of matching) {\n\t\t\t\t\t\t\tconst existing = groups.get(r.version.major);\n\t\t\t\t\t\t\tif (!existing || SemVer.gt(r.version, existing.version)) {\n\t\t\t\t\t\t\t\tgroups.set(r.version.major, r);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresultReleases = [...groups.values()];\n\t\t\t\t\t} else if (increments === \"minor\") {\n\t\t\t\t\t\tconst groups = new Map<string, BunRelease>();\n\t\t\t\t\t\tfor (const r of matching) {\n\t\t\t\t\t\t\tconst key = `${r.version.major}.${r.version.minor}`;\n\t\t\t\t\t\t\tconst existing = groups.get(key);\n\t\t\t\t\t\t\tif (!existing || SemVer.gt(r.version, existing.version)) {\n\t\t\t\t\t\t\t\tgroups.set(key, r);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresultReleases = [...groups.values()];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresultReleases = [...matching];\n\t\t\t\t\t}\n\n\t\t\t\t\tif (resultReleases.length === 0) {\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew VersionNotFoundError({\n\t\t\t\t\t\t\t\truntime: \"bun\",\n\t\t\t\t\t\t\t\tconstraint: semverRange,\n\t\t\t\t\t\t\t\tmessage: `No Bun versions found matching \"${semverRange}\"`,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst sorted = SemVer.rsort(resultReleases.map((r) => r.version));\n\t\t\t\t\tconst versions = sorted.map((v) => v.toString());\n\t\t\t\t\tconst latest = versions[0];\n\n\t\t\t\t\tlet resolvedDefault: string | undefined;\n\t\t\t\t\tif (options?.defaultVersion) {\n\t\t\t\t\t\tresolvedDefault = yield* cache.resolve(options.defaultVersion).pipe(\n\t\t\t\t\t\t\tEffect.map((r) => r.version.toString()),\n\t\t\t\t\t\t\tEffect.catchAll(() => Effect.succeed(undefined)),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsource: \"api\" as const,\n\t\t\t\t\t\tversions,\n\t\t\t\t\t\tlatest,\n\t\t\t\t\t\t...(resolvedDefault ? { default: resolvedDefault } : {}),\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t};\n\t}),\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,MAAa,kBAAoE,MAAM,OACtF,aACA,OAAO,IAAI,aAAa;CACvB,MAAM,QAAQ,OAAO;CAErB,OAAO,EACN,UAAU,YACT,OAAO,IAAI,aAAa;EACvB,MAAM,cAAc,SAAS,eAAe;EAC5C,MAAM,aAAyB,SAAS,cAAc;EAEtD,MAAM,WAAW,OAAO,MACtB,OAAO,WAAW,CAAC,CACnB,KAAK,OAAO,eAAe,OAAO,QAAQ,CAAC,CAA8B,CAAC,CAAC;EAE7E,IAAI;EACJ,IAAI,eAAe,UAAU;GAC5B,MAAM,yBAAS,IAAI,IAAwB;GAC3C,KAAK,MAAM,KAAK,UAAU;IACzB,MAAM,WAAW,OAAO,IAAI,EAAE,QAAQ,KAAK;IAC3C,IAAI,CAAC,YAAY,OAAO,GAAG,EAAE,SAAS,SAAS,OAAO,GACrD,OAAO,IAAI,EAAE,QAAQ,OAAO,CAAC;GAE/B;GACA,iBAAiB,CAAC,GAAG,OAAO,OAAO,CAAC;EACrC,OAAO,IAAI,eAAe,SAAS;GAClC,MAAM,yBAAS,IAAI,IAAwB;GAC3C,KAAK,MAAM,KAAK,UAAU;IACzB,MAAM,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,EAAE,QAAQ;IAC5C,MAAM,WAAW,OAAO,IAAI,GAAG;IAC/B,IAAI,CAAC,YAAY,OAAO,GAAG,EAAE,SAAS,SAAS,OAAO,GACrD,OAAO,IAAI,KAAK,CAAC;GAEnB;GACA,iBAAiB,CAAC,GAAG,OAAO,OAAO,CAAC;EACrC,OACC,iBAAiB,CAAC,GAAG,QAAQ;EAG9B,IAAI,eAAe,WAAW,GAC7B,OAAO,OAAO,OAAO,KACpB,IAAI,qBAAqB;GACxB,SAAS;GACT,YAAY;GACZ,SAAS,mCAAmC,YAAY;EACzD,CAAC,CACF;EAID,MAAM,WADS,OAAO,MAAM,eAAe,KAAK,MAAM,EAAE,OAAO,CACzC,CAAC,CAAC,KAAK,MAAM,EAAE,SAAS,CAAC;EAC/C,MAAM,SAAS,SAAS;EAExB,IAAI;EACJ,IAAI,SAAS,gBACZ,kBAAkB,OAAO,MAAM,QAAQ,QAAQ,cAAc,CAAC,CAAC,KAC9D,OAAO,KAAK,MAAM,EAAE,QAAQ,SAAS,CAAC,GACtC,OAAO,eAAe,OAAO,QAAQ,MAAS,CAAC,CAChD;EAGD,OAAO;GACN,QAAQ;GACR;GACA;GACA,GAAI,kBAAkB,EAAE,SAAS,gBAAgB,IAAI,CAAC;EACvD;CACD,CAAC,EACH;AACD,CAAC,CACF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { BunVersionFetcher } from "../services/BunVersionFetcher.js";
|
|
2
|
+
import { retryOnRateLimit } from "../lib/retry.js";
|
|
3
|
+
import { tryParseSemVer } from "../lib/semver.js";
|
|
4
|
+
import { GitHubClient } from "../services/GitHubClient.js";
|
|
5
|
+
import { Effect, Layer, Option } from "effect";
|
|
6
|
+
|
|
7
|
+
//#region src/layers/BunVersionFetcherLive.ts
|
|
8
|
+
/**
|
|
9
|
+
* Strip bun- prefix and v/V prefix, parse to SemVer.
|
|
10
|
+
*/
|
|
11
|
+
const normalizeBunTag = (tagName) => {
|
|
12
|
+
const version = tagName.startsWith("bun-") ? tagName.slice(4) : tagName;
|
|
13
|
+
return tryParseSemVer(version.startsWith("v") || version.startsWith("V") ? version.slice(1) : version);
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Provides the {@link BunVersionFetcher} service using the live GitHub API.
|
|
17
|
+
*
|
|
18
|
+
* Fetches Bun releases from the `oven-sh/bun` GitHub repository using the
|
|
19
|
+
* provided {@link GitHubClient}. Draft releases and pre-releases are excluded.
|
|
20
|
+
* Tag names are normalized by stripping the `bun-` prefix and any leading `v`
|
|
21
|
+
* or `V` before parsing as semver.
|
|
22
|
+
*
|
|
23
|
+
* This layer is a required dependency of {@link AutoBunCacheLive} and
|
|
24
|
+
* {@link FreshBunCacheLive}. It is not needed by {@link OfflineBunCacheLive}.
|
|
25
|
+
*
|
|
26
|
+
* @see {@link BunVersionFetcher}
|
|
27
|
+
* @see {@link GitHubClient}
|
|
28
|
+
* @see {@link AutoBunCacheLive}
|
|
29
|
+
* @see {@link FreshBunCacheLive}
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
const BunVersionFetcherLive = Layer.effect(BunVersionFetcher, Effect.gen(function* () {
|
|
33
|
+
const client = yield* GitHubClient;
|
|
34
|
+
return { fetch: () => Effect.gen(function* () {
|
|
35
|
+
const releases = yield* retryOnRateLimit(client.listReleases("oven-sh", "bun", {
|
|
36
|
+
perPage: 100,
|
|
37
|
+
pages: 3
|
|
38
|
+
}));
|
|
39
|
+
const fallbackDate = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
40
|
+
const parsed = yield* Effect.forEach(releases.filter((r) => !r.draft && !r.prerelease), (release) => normalizeBunTag(release.tag_name).pipe(Effect.map((opt) => Option.isSome(opt) ? Option.some({
|
|
41
|
+
version: opt.value,
|
|
42
|
+
input: {
|
|
43
|
+
version: opt.value.toString(),
|
|
44
|
+
date: release.published_at ?? fallbackDate
|
|
45
|
+
}
|
|
46
|
+
}) : Option.none())), { concurrency: "unbounded" });
|
|
47
|
+
return {
|
|
48
|
+
versions: parsed.flatMap(Option.toArray).map(({ version }) => version),
|
|
49
|
+
inputs: parsed.flatMap(Option.toArray).map(({ input }) => input)
|
|
50
|
+
};
|
|
51
|
+
}) };
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
export { BunVersionFetcherLive };
|
|
56
|
+
//# sourceMappingURL=BunVersionFetcherLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BunVersionFetcherLive.js","names":[],"sources":["../../../../src/layers/BunVersionFetcherLive.ts"],"sourcesContent":["import { Effect, Layer, Option } from \"effect\";\nimport { retryOnRateLimit } from \"../lib/retry.js\";\nimport { tryParseSemVer } from \"../lib/semver.js\";\nimport type { RuntimeReleaseInput } from \"../schemas/runtime-release.js\";\nimport { BunVersionFetcher } from \"../services/BunVersionFetcher.js\";\nimport { GitHubClient } from \"../services/GitHubClient.js\";\n\n/**\n * Strip bun- prefix and v/V prefix, parse to SemVer.\n */\nconst normalizeBunTag = (tagName: string): Effect.Effect<Option.Option<import(\"semver-effect\").SemVer>> => {\n\tconst version = tagName.startsWith(\"bun-\") ? tagName.slice(4) : tagName;\n\tconst stripped = version.startsWith(\"v\") || version.startsWith(\"V\") ? version.slice(1) : version;\n\treturn tryParseSemVer(stripped);\n};\n\n/**\n * Provides the {@link BunVersionFetcher} service using the live GitHub API.\n *\n * Fetches Bun releases from the `oven-sh/bun` GitHub repository using the\n * provided {@link GitHubClient}. Draft releases and pre-releases are excluded.\n * Tag names are normalized by stripping the `bun-` prefix and any leading `v`\n * or `V` before parsing as semver.\n *\n * This layer is a required dependency of {@link AutoBunCacheLive} and\n * {@link FreshBunCacheLive}. It is not needed by {@link OfflineBunCacheLive}.\n *\n * @see {@link BunVersionFetcher}\n * @see {@link GitHubClient}\n * @see {@link AutoBunCacheLive}\n * @see {@link FreshBunCacheLive}\n * @public\n */\nexport const BunVersionFetcherLive: Layer.Layer<BunVersionFetcher, never, GitHubClient> = Layer.effect(\n\tBunVersionFetcher,\n\tEffect.gen(function* () {\n\t\tconst client = yield* GitHubClient;\n\n\t\treturn {\n\t\t\tfetch: () =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst releases = yield* retryOnRateLimit(client.listReleases(\"oven-sh\", \"bun\", { perPage: 100, pages: 3 }));\n\t\t\t\t\tconst fallbackDate = new Date().toISOString().slice(0, 10);\n\n\t\t\t\t\tconst parsed = yield* Effect.forEach(\n\t\t\t\t\t\treleases.filter((r) => !r.draft && !r.prerelease),\n\t\t\t\t\t\t(release) =>\n\t\t\t\t\t\t\tnormalizeBunTag(release.tag_name).pipe(\n\t\t\t\t\t\t\t\tEffect.map((opt) =>\n\t\t\t\t\t\t\t\t\tOption.isSome(opt)\n\t\t\t\t\t\t\t\t\t\t? Option.some({\n\t\t\t\t\t\t\t\t\t\t\t\tversion: opt.value,\n\t\t\t\t\t\t\t\t\t\t\t\tinput: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tversion: opt.value.toString(),\n\t\t\t\t\t\t\t\t\t\t\t\t\tdate: release.published_at ?? fallbackDate,\n\t\t\t\t\t\t\t\t\t\t\t\t} satisfies RuntimeReleaseInput,\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t{ concurrency: \"unbounded\" },\n\t\t\t\t\t);\n\n\t\t\t\t\tconst versions = parsed.flatMap(Option.toArray).map(({ version }) => version);\n\t\t\t\t\tconst inputs = parsed.flatMap(Option.toArray).map(({ input }) => input);\n\n\t\t\t\t\treturn { versions, inputs };\n\t\t\t\t}),\n\t\t};\n\t}),\n);\n"],"mappings":";;;;;;;;;;AAUA,MAAM,mBAAmB,YAAkF;CAC1G,MAAM,UAAU,QAAQ,WAAW,MAAM,IAAI,QAAQ,MAAM,CAAC,IAAI;CAEhE,OAAO,eADU,QAAQ,WAAW,GAAG,KAAK,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI,OAC3D;AAC/B;;;;;;;;;;;;;;;;;;AAmBA,MAAa,wBAA6E,MAAM,OAC/F,mBACA,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CAEtB,OAAO,EACN,aACC,OAAO,IAAI,aAAa;EACvB,MAAM,WAAW,OAAO,iBAAiB,OAAO,aAAa,WAAW,OAAO;GAAE,SAAS;GAAK,OAAO;EAAE,CAAC,CAAC;EAC1G,MAAM,gCAAe,IAAI,KAAK,EAAC,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE;EAEzD,MAAM,SAAS,OAAO,OAAO,QAC5B,SAAS,QAAQ,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,UAAU,IAC/C,YACA,gBAAgB,QAAQ,QAAQ,CAAC,CAAC,KACjC,OAAO,KAAK,QACX,OAAO,OAAO,GAAG,IACd,OAAO,KAAK;GACZ,SAAS,IAAI;GACb,OAAO;IACN,SAAS,IAAI,MAAM,SAAS;IAC5B,MAAM,QAAQ,gBAAgB;GAC/B;EACD,CAAC,IACA,OAAO,KAAK,CAChB,CACD,GACD,EAAE,aAAa,YAAY,CAC5B;EAKA,OAAO;GAAE,UAHQ,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,EAAE,cAAc,OAGrD;GAAG,QAFJ,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,EAAE,YAAY,KAEzC;EAAE;CAC3B,CAAC,EACH;AACD,CAAC,CACF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createRuntimeCache } from "./RuntimeCacheLive.js";
|
|
2
|
+
import { DenoReleaseCache } from "../services/DenoReleaseCache.js";
|
|
3
|
+
import { Layer } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/layers/DenoReleaseCacheLive.ts
|
|
6
|
+
/**
|
|
7
|
+
* Low-level layer that constructs the {@link DenoReleaseCache} service backed by
|
|
8
|
+
* a `SemVerVersionCache` instance from `semver-effect`.
|
|
9
|
+
*
|
|
10
|
+
* This layer wires up the cache storage but does not populate it with any
|
|
11
|
+
* release data. Callers are responsible for loading releases after construction.
|
|
12
|
+
* In normal usage you should prefer the higher-level cache strategy layers
|
|
13
|
+
* instead of using this layer directly:
|
|
14
|
+
* - {@link AutoDenoCacheLive} — API with fallback to bundled defaults
|
|
15
|
+
* - {@link FreshDenoCacheLive} — API only, fails if unavailable
|
|
16
|
+
* - {@link OfflineDenoCacheLive} — bundled defaults only
|
|
17
|
+
*
|
|
18
|
+
* @see {@link DenoReleaseCache}
|
|
19
|
+
* @see {@link createRuntimeCache}
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
const DenoReleaseCacheLive = Layer.effect(DenoReleaseCache, createRuntimeCache());
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { DenoReleaseCacheLive };
|
|
26
|
+
//# sourceMappingURL=DenoReleaseCacheLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DenoReleaseCacheLive.js","names":[],"sources":["../../../../src/layers/DenoReleaseCacheLive.ts"],"sourcesContent":["import { Layer } from \"effect\";\nimport type { VersionCache as SemVerVersionCache } from \"semver-effect\";\nimport type { DenoRelease } from \"../schemas/deno-release.js\";\nimport { DenoReleaseCache } from \"../services/DenoReleaseCache.js\";\nimport { createRuntimeCache } from \"./RuntimeCacheLive.js\";\n\n/**\n * Low-level layer that constructs the {@link DenoReleaseCache} service backed by\n * a `SemVerVersionCache` instance from `semver-effect`.\n *\n * This layer wires up the cache storage but does not populate it with any\n * release data. Callers are responsible for loading releases after construction.\n * In normal usage you should prefer the higher-level cache strategy layers\n * instead of using this layer directly:\n * - {@link AutoDenoCacheLive} — API with fallback to bundled defaults\n * - {@link FreshDenoCacheLive} — API only, fails if unavailable\n * - {@link OfflineDenoCacheLive} — bundled defaults only\n *\n * @see {@link DenoReleaseCache}\n * @see {@link createRuntimeCache}\n * @internal\n */\nexport const DenoReleaseCacheLive: Layer.Layer<DenoReleaseCache, never, SemVerVersionCache> = Layer.effect(\n\tDenoReleaseCache,\n\tcreateRuntimeCache<DenoRelease>(),\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,uBAAiF,MAAM,OACnG,kBACA,mBAAgC,CACjC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { DenoReleaseCache } from "../services/DenoReleaseCache.js";
|
|
2
|
+
import { VersionNotFoundError } from "../errors/VersionNotFoundError.js";
|
|
3
|
+
import { DenoResolver } from "../services/DenoResolver.js";
|
|
4
|
+
import { Effect, Layer } from "effect";
|
|
5
|
+
import { SemVer } from "semver-effect";
|
|
6
|
+
|
|
7
|
+
//#region src/layers/DenoResolverLive.ts
|
|
8
|
+
/**
|
|
9
|
+
* Provides the {@link DenoResolver} service backed by a {@link DenoReleaseCache}.
|
|
10
|
+
*
|
|
11
|
+
* This layer composes with any of the three Deno cache strategy layers to form
|
|
12
|
+
* a complete resolver stack. It implements semver range filtering and increment
|
|
13
|
+
* grouping (latest per major, latest per minor, or all patch versions) using
|
|
14
|
+
* the releases already loaded into the cache.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { DenoResolverLive, AutoDenoCacheLive, DenoVersionFetcherLive, GitHubClientLive, GitHubAutoAuth } from "runtime-resolver";
|
|
19
|
+
* import { DenoResolver } from "runtime-resolver";
|
|
20
|
+
* import { Effect, Layer } from "effect";
|
|
21
|
+
*
|
|
22
|
+
* const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));
|
|
23
|
+
* const CacheLayer = AutoDenoCacheLive.pipe(Layer.provide(DenoVersionFetcherLive.pipe(Layer.provide(GitHubLayer))));
|
|
24
|
+
* const ResolverLayer = DenoResolverLive.pipe(Layer.provide(CacheLayer));
|
|
25
|
+
*
|
|
26
|
+
* const program = Effect.gen(function* () {
|
|
27
|
+
* const resolver = yield* DenoResolver;
|
|
28
|
+
* const result = yield* resolver.resolve({ semverRange: "^2.0.0" });
|
|
29
|
+
* console.log(result.latest);
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* Effect.runPromise(program.pipe(Effect.provide(ResolverLayer)));
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @see {@link DenoResolver}
|
|
36
|
+
* @see {@link DenoReleaseCache}
|
|
37
|
+
* @see {@link AutoDenoCacheLive}
|
|
38
|
+
* @see {@link FreshDenoCacheLive}
|
|
39
|
+
* @see {@link OfflineDenoCacheLive}
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
const DenoResolverLive = Layer.effect(DenoResolver, Effect.gen(function* () {
|
|
43
|
+
const cache = yield* DenoReleaseCache;
|
|
44
|
+
return { resolve: (options) => Effect.gen(function* () {
|
|
45
|
+
const semverRange = options?.semverRange ?? "*";
|
|
46
|
+
const increments = options?.increments ?? "latest";
|
|
47
|
+
const matching = yield* cache.filter(semverRange).pipe(Effect.catchAll(() => Effect.succeed([])));
|
|
48
|
+
let resultReleases;
|
|
49
|
+
if (increments === "latest") {
|
|
50
|
+
const groups = /* @__PURE__ */ new Map();
|
|
51
|
+
for (const r of matching) {
|
|
52
|
+
const existing = groups.get(r.version.major);
|
|
53
|
+
if (!existing || SemVer.gt(r.version, existing.version)) groups.set(r.version.major, r);
|
|
54
|
+
}
|
|
55
|
+
resultReleases = [...groups.values()];
|
|
56
|
+
} else if (increments === "minor") {
|
|
57
|
+
const groups = /* @__PURE__ */ new Map();
|
|
58
|
+
for (const r of matching) {
|
|
59
|
+
const key = `${r.version.major}.${r.version.minor}`;
|
|
60
|
+
const existing = groups.get(key);
|
|
61
|
+
if (!existing || SemVer.gt(r.version, existing.version)) groups.set(key, r);
|
|
62
|
+
}
|
|
63
|
+
resultReleases = [...groups.values()];
|
|
64
|
+
} else resultReleases = [...matching];
|
|
65
|
+
if (resultReleases.length === 0) return yield* Effect.fail(new VersionNotFoundError({
|
|
66
|
+
runtime: "deno",
|
|
67
|
+
constraint: semverRange,
|
|
68
|
+
message: `No Deno versions found matching "${semverRange}"`
|
|
69
|
+
}));
|
|
70
|
+
const versions = SemVer.rsort(resultReleases.map((r) => r.version)).map((v) => v.toString());
|
|
71
|
+
const latest = versions[0];
|
|
72
|
+
let resolvedDefault;
|
|
73
|
+
if (options?.defaultVersion) resolvedDefault = yield* cache.resolve(options.defaultVersion).pipe(Effect.map((r) => r.version.toString()), Effect.catchAll(() => Effect.succeed(void 0)));
|
|
74
|
+
return {
|
|
75
|
+
source: "api",
|
|
76
|
+
versions,
|
|
77
|
+
latest,
|
|
78
|
+
...resolvedDefault ? { default: resolvedDefault } : {}
|
|
79
|
+
};
|
|
80
|
+
}) };
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
export { DenoResolverLive };
|
|
85
|
+
//# sourceMappingURL=DenoResolverLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DenoResolverLive.js","names":[],"sources":["../../../../src/layers/DenoResolverLive.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\nimport { SemVer } from \"semver-effect\";\nimport { VersionNotFoundError } from \"../errors/VersionNotFoundError.js\";\nimport type { Increments } from \"../schemas/common.js\";\nimport type { DenoRelease } from \"../schemas/deno-release.js\";\nimport { DenoReleaseCache } from \"../services/DenoReleaseCache.js\";\nimport type { DenoResolverOptions } from \"../services/DenoResolver.js\";\nimport { DenoResolver } from \"../services/DenoResolver.js\";\n\n/**\n * Provides the {@link DenoResolver} service backed by a {@link DenoReleaseCache}.\n *\n * This layer composes with any of the three Deno cache strategy layers to form\n * a complete resolver stack. It implements semver range filtering and increment\n * grouping (latest per major, latest per minor, or all patch versions) using\n * the releases already loaded into the cache.\n *\n * @example\n * ```ts\n * import { DenoResolverLive, AutoDenoCacheLive, DenoVersionFetcherLive, GitHubClientLive, GitHubAutoAuth } from \"runtime-resolver\";\n * import { DenoResolver } from \"runtime-resolver\";\n * import { Effect, Layer } from \"effect\";\n *\n * const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));\n * const CacheLayer = AutoDenoCacheLive.pipe(Layer.provide(DenoVersionFetcherLive.pipe(Layer.provide(GitHubLayer))));\n * const ResolverLayer = DenoResolverLive.pipe(Layer.provide(CacheLayer));\n *\n * const program = Effect.gen(function* () {\n * const resolver = yield* DenoResolver;\n * const result = yield* resolver.resolve({ semverRange: \"^2.0.0\" });\n * console.log(result.latest);\n * });\n *\n * Effect.runPromise(program.pipe(Effect.provide(ResolverLayer)));\n * ```\n *\n * @see {@link DenoResolver}\n * @see {@link DenoReleaseCache}\n * @see {@link AutoDenoCacheLive}\n * @see {@link FreshDenoCacheLive}\n * @see {@link OfflineDenoCacheLive}\n * @public\n */\nexport const DenoResolverLive: Layer.Layer<DenoResolver, never, DenoReleaseCache> = Layer.effect(\n\tDenoResolver,\n\tEffect.gen(function* () {\n\t\tconst cache = yield* DenoReleaseCache;\n\n\t\treturn {\n\t\t\tresolve: (options?: DenoResolverOptions) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst semverRange = options?.semverRange ?? \"*\";\n\t\t\t\t\tconst increments: Increments = options?.increments ?? \"latest\";\n\n\t\t\t\t\tconst matching = yield* cache\n\t\t\t\t\t\t.filter(semverRange)\n\t\t\t\t\t\t.pipe(Effect.catchAll(() => Effect.succeed([] as ReadonlyArray<DenoRelease>)));\n\n\t\t\t\t\tlet resultReleases: DenoRelease[];\n\t\t\t\t\tif (increments === \"latest\") {\n\t\t\t\t\t\tconst groups = new Map<number, DenoRelease>();\n\t\t\t\t\t\tfor (const r of matching) {\n\t\t\t\t\t\t\tconst existing = groups.get(r.version.major);\n\t\t\t\t\t\t\tif (!existing || SemVer.gt(r.version, existing.version)) {\n\t\t\t\t\t\t\t\tgroups.set(r.version.major, r);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresultReleases = [...groups.values()];\n\t\t\t\t\t} else if (increments === \"minor\") {\n\t\t\t\t\t\tconst groups = new Map<string, DenoRelease>();\n\t\t\t\t\t\tfor (const r of matching) {\n\t\t\t\t\t\t\tconst key = `${r.version.major}.${r.version.minor}`;\n\t\t\t\t\t\t\tconst existing = groups.get(key);\n\t\t\t\t\t\t\tif (!existing || SemVer.gt(r.version, existing.version)) {\n\t\t\t\t\t\t\t\tgroups.set(key, r);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresultReleases = [...groups.values()];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresultReleases = [...matching];\n\t\t\t\t\t}\n\n\t\t\t\t\tif (resultReleases.length === 0) {\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew VersionNotFoundError({\n\t\t\t\t\t\t\t\truntime: \"deno\",\n\t\t\t\t\t\t\t\tconstraint: semverRange,\n\t\t\t\t\t\t\t\tmessage: `No Deno versions found matching \"${semverRange}\"`,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst sorted = SemVer.rsort(resultReleases.map((r) => r.version));\n\t\t\t\t\tconst versions = sorted.map((v) => v.toString());\n\t\t\t\t\tconst latest = versions[0];\n\n\t\t\t\t\tlet resolvedDefault: string | undefined;\n\t\t\t\t\tif (options?.defaultVersion) {\n\t\t\t\t\t\tresolvedDefault = yield* cache.resolve(options.defaultVersion).pipe(\n\t\t\t\t\t\t\tEffect.map((r) => r.version.toString()),\n\t\t\t\t\t\t\tEffect.catchAll(() => Effect.succeed(undefined)),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsource: \"api\" as const,\n\t\t\t\t\t\tversions,\n\t\t\t\t\t\tlatest,\n\t\t\t\t\t\t...(resolvedDefault ? { default: resolvedDefault } : {}),\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t};\n\t}),\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,mBAAuE,MAAM,OACzF,cACA,OAAO,IAAI,aAAa;CACvB,MAAM,QAAQ,OAAO;CAErB,OAAO,EACN,UAAU,YACT,OAAO,IAAI,aAAa;EACvB,MAAM,cAAc,SAAS,eAAe;EAC5C,MAAM,aAAyB,SAAS,cAAc;EAEtD,MAAM,WAAW,OAAO,MACtB,OAAO,WAAW,CAAC,CACnB,KAAK,OAAO,eAAe,OAAO,QAAQ,CAAC,CAA+B,CAAC,CAAC;EAE9E,IAAI;EACJ,IAAI,eAAe,UAAU;GAC5B,MAAM,yBAAS,IAAI,IAAyB;GAC5C,KAAK,MAAM,KAAK,UAAU;IACzB,MAAM,WAAW,OAAO,IAAI,EAAE,QAAQ,KAAK;IAC3C,IAAI,CAAC,YAAY,OAAO,GAAG,EAAE,SAAS,SAAS,OAAO,GACrD,OAAO,IAAI,EAAE,QAAQ,OAAO,CAAC;GAE/B;GACA,iBAAiB,CAAC,GAAG,OAAO,OAAO,CAAC;EACrC,OAAO,IAAI,eAAe,SAAS;GAClC,MAAM,yBAAS,IAAI,IAAyB;GAC5C,KAAK,MAAM,KAAK,UAAU;IACzB,MAAM,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,EAAE,QAAQ;IAC5C,MAAM,WAAW,OAAO,IAAI,GAAG;IAC/B,IAAI,CAAC,YAAY,OAAO,GAAG,EAAE,SAAS,SAAS,OAAO,GACrD,OAAO,IAAI,KAAK,CAAC;GAEnB;GACA,iBAAiB,CAAC,GAAG,OAAO,OAAO,CAAC;EACrC,OACC,iBAAiB,CAAC,GAAG,QAAQ;EAG9B,IAAI,eAAe,WAAW,GAC7B,OAAO,OAAO,OAAO,KACpB,IAAI,qBAAqB;GACxB,SAAS;GACT,YAAY;GACZ,SAAS,oCAAoC,YAAY;EAC1D,CAAC,CACF;EAID,MAAM,WADS,OAAO,MAAM,eAAe,KAAK,MAAM,EAAE,OAAO,CACzC,CAAC,CAAC,KAAK,MAAM,EAAE,SAAS,CAAC;EAC/C,MAAM,SAAS,SAAS;EAExB,IAAI;EACJ,IAAI,SAAS,gBACZ,kBAAkB,OAAO,MAAM,QAAQ,QAAQ,cAAc,CAAC,CAAC,KAC9D,OAAO,KAAK,MAAM,EAAE,QAAQ,SAAS,CAAC,GACtC,OAAO,eAAe,OAAO,QAAQ,MAAS,CAAC,CAChD;EAGD,OAAO;GACN,QAAQ;GACR;GACA;GACA,GAAI,kBAAkB,EAAE,SAAS,gBAAgB,IAAI,CAAC;EACvD;CACD,CAAC,EACH;AACD,CAAC,CACF"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DenoVersionFetcher } from "../services/DenoVersionFetcher.js";
|
|
2
|
+
import { retryOnRateLimit } from "../lib/retry.js";
|
|
3
|
+
import { tryParseSemVer } from "../lib/semver.js";
|
|
4
|
+
import { GitHubClient } from "../services/GitHubClient.js";
|
|
5
|
+
import { Effect, Layer, Option } from "effect";
|
|
6
|
+
|
|
7
|
+
//#region src/layers/DenoVersionFetcherLive.ts
|
|
8
|
+
/**
|
|
9
|
+
* Provides the {@link DenoVersionFetcher} service using the live GitHub API.
|
|
10
|
+
*
|
|
11
|
+
* Fetches Deno releases from the `denoland/deno` GitHub repository using the
|
|
12
|
+
* provided {@link GitHubClient}. Draft releases and pre-releases are excluded.
|
|
13
|
+
* Tag names are normalized by stripping any leading `v` before parsing as
|
|
14
|
+
* semver.
|
|
15
|
+
*
|
|
16
|
+
* This layer is a required dependency of {@link AutoDenoCacheLive} and
|
|
17
|
+
* {@link FreshDenoCacheLive}. It is not needed by {@link OfflineDenoCacheLive}.
|
|
18
|
+
*
|
|
19
|
+
* @see {@link DenoVersionFetcher}
|
|
20
|
+
* @see {@link GitHubClient}
|
|
21
|
+
* @see {@link AutoDenoCacheLive}
|
|
22
|
+
* @see {@link FreshDenoCacheLive}
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
const DenoVersionFetcherLive = Layer.effect(DenoVersionFetcher, Effect.gen(function* () {
|
|
26
|
+
const client = yield* GitHubClient;
|
|
27
|
+
return { fetch: () => Effect.gen(function* () {
|
|
28
|
+
const releases = yield* retryOnRateLimit(client.listReleases("denoland", "deno", {
|
|
29
|
+
perPage: 100,
|
|
30
|
+
pages: 3
|
|
31
|
+
}));
|
|
32
|
+
const fallbackDate = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
33
|
+
const parsed = yield* Effect.forEach(releases.filter((r) => !r.draft && !r.prerelease), (release) => {
|
|
34
|
+
const stripped = release.tag_name.startsWith("v") ? release.tag_name.slice(1) : release.tag_name;
|
|
35
|
+
return tryParseSemVer(stripped).pipe(Effect.map((opt) => Option.isSome(opt) ? Option.some({
|
|
36
|
+
version: opt.value,
|
|
37
|
+
input: {
|
|
38
|
+
version: stripped,
|
|
39
|
+
date: release.published_at ?? fallbackDate
|
|
40
|
+
}
|
|
41
|
+
}) : Option.none()));
|
|
42
|
+
}, { concurrency: "unbounded" });
|
|
43
|
+
return {
|
|
44
|
+
versions: parsed.flatMap(Option.toArray).map(({ version }) => version),
|
|
45
|
+
inputs: parsed.flatMap(Option.toArray).map(({ input }) => input)
|
|
46
|
+
};
|
|
47
|
+
}) };
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
export { DenoVersionFetcherLive };
|
|
52
|
+
//# sourceMappingURL=DenoVersionFetcherLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DenoVersionFetcherLive.js","names":[],"sources":["../../../../src/layers/DenoVersionFetcherLive.ts"],"sourcesContent":["import { Effect, Layer, Option } from \"effect\";\nimport { retryOnRateLimit } from \"../lib/retry.js\";\nimport { tryParseSemVer } from \"../lib/semver.js\";\nimport type { RuntimeReleaseInput } from \"../schemas/runtime-release.js\";\nimport { DenoVersionFetcher } from \"../services/DenoVersionFetcher.js\";\nimport { GitHubClient } from \"../services/GitHubClient.js\";\n\n/**\n * Provides the {@link DenoVersionFetcher} service using the live GitHub API.\n *\n * Fetches Deno releases from the `denoland/deno` GitHub repository using the\n * provided {@link GitHubClient}. Draft releases and pre-releases are excluded.\n * Tag names are normalized by stripping any leading `v` before parsing as\n * semver.\n *\n * This layer is a required dependency of {@link AutoDenoCacheLive} and\n * {@link FreshDenoCacheLive}. It is not needed by {@link OfflineDenoCacheLive}.\n *\n * @see {@link DenoVersionFetcher}\n * @see {@link GitHubClient}\n * @see {@link AutoDenoCacheLive}\n * @see {@link FreshDenoCacheLive}\n * @public\n */\nexport const DenoVersionFetcherLive: Layer.Layer<DenoVersionFetcher, never, GitHubClient> = Layer.effect(\n\tDenoVersionFetcher,\n\tEffect.gen(function* () {\n\t\tconst client = yield* GitHubClient;\n\n\t\treturn {\n\t\t\tfetch: () =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst releases = yield* retryOnRateLimit(client.listReleases(\"denoland\", \"deno\", { perPage: 100, pages: 3 }));\n\t\t\t\t\tconst fallbackDate = new Date().toISOString().slice(0, 10);\n\n\t\t\t\t\tconst parsed = yield* Effect.forEach(\n\t\t\t\t\t\treleases.filter((r) => !r.draft && !r.prerelease),\n\t\t\t\t\t\t(release) => {\n\t\t\t\t\t\t\tconst stripped = release.tag_name.startsWith(\"v\") ? release.tag_name.slice(1) : release.tag_name;\n\t\t\t\t\t\t\treturn tryParseSemVer(stripped).pipe(\n\t\t\t\t\t\t\t\tEffect.map((opt) =>\n\t\t\t\t\t\t\t\t\tOption.isSome(opt)\n\t\t\t\t\t\t\t\t\t\t? Option.some({\n\t\t\t\t\t\t\t\t\t\t\t\tversion: opt.value,\n\t\t\t\t\t\t\t\t\t\t\t\tinput: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tversion: stripped,\n\t\t\t\t\t\t\t\t\t\t\t\t\tdate: release.published_at ?? fallbackDate,\n\t\t\t\t\t\t\t\t\t\t\t\t} satisfies RuntimeReleaseInput,\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ concurrency: \"unbounded\" },\n\t\t\t\t\t);\n\n\t\t\t\t\tconst versions = parsed.flatMap(Option.toArray).map(({ version }) => version);\n\t\t\t\t\tconst inputs = parsed.flatMap(Option.toArray).map(({ input }) => input);\n\n\t\t\t\t\treturn { versions, inputs };\n\t\t\t\t}),\n\t\t};\n\t}),\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,yBAA+E,MAAM,OACjG,oBACA,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CAEtB,OAAO,EACN,aACC,OAAO,IAAI,aAAa;EACvB,MAAM,WAAW,OAAO,iBAAiB,OAAO,aAAa,YAAY,QAAQ;GAAE,SAAS;GAAK,OAAO;EAAE,CAAC,CAAC;EAC5G,MAAM,gCAAe,IAAI,KAAK,EAAC,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE;EAEzD,MAAM,SAAS,OAAO,OAAO,QAC5B,SAAS,QAAQ,MAAM,CAAC,EAAE,SAAS,CAAC,EAAE,UAAU,IAC/C,YAAY;GACZ,MAAM,WAAW,QAAQ,SAAS,WAAW,GAAG,IAAI,QAAQ,SAAS,MAAM,CAAC,IAAI,QAAQ;GACxF,OAAO,eAAe,QAAQ,CAAC,CAAC,KAC/B,OAAO,KAAK,QACX,OAAO,OAAO,GAAG,IACd,OAAO,KAAK;IACZ,SAAS,IAAI;IACb,OAAO;KACN,SAAS;KACT,MAAM,QAAQ,gBAAgB;IAC/B;GACD,CAAC,IACA,OAAO,KAAK,CAChB,CACD;EACD,GACA,EAAE,aAAa,YAAY,CAC5B;EAKA,OAAO;GAAE,UAHQ,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,EAAE,cAAc,OAGrD;GAAG,QAFJ,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,EAAE,YAAY,KAEzC;EAAE;CAC3B,CAAC,EACH;AACD,CAAC,CACF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { BunRelease } from "../schemas/bun-release.js";
|
|
2
|
+
import { BunReleaseCache } from "../services/BunReleaseCache.js";
|
|
3
|
+
import { BunVersionFetcher } from "../services/BunVersionFetcher.js";
|
|
4
|
+
import { BunReleaseCacheLive } from "./BunReleaseCacheLive.js";
|
|
5
|
+
import { FreshnessError } from "../errors/FreshnessError.js";
|
|
6
|
+
import { Effect, Layer } from "effect";
|
|
7
|
+
import { SemVerParserLive, VersionCacheLive } from "semver-effect";
|
|
8
|
+
|
|
9
|
+
//#region src/layers/FreshBunCacheLive.ts
|
|
10
|
+
const freshSetup = Effect.gen(function* () {
|
|
11
|
+
const cache = yield* BunReleaseCache;
|
|
12
|
+
const { inputs } = yield* (yield* BunVersionFetcher).fetch().pipe(Effect.catchTags({
|
|
13
|
+
AuthenticationError: (err) => Effect.fail(new FreshnessError({
|
|
14
|
+
strategy: "api",
|
|
15
|
+
message: `Fresh data required but authentication failed: ${err.message}`
|
|
16
|
+
})),
|
|
17
|
+
NetworkError: (err) => Effect.fail(new FreshnessError({
|
|
18
|
+
strategy: "api",
|
|
19
|
+
message: `Fresh data required but network unavailable: ${err.message}`
|
|
20
|
+
})),
|
|
21
|
+
ParseError: (err) => Effect.fail(new FreshnessError({
|
|
22
|
+
strategy: "api",
|
|
23
|
+
message: `Fresh data required but parse failed: ${err.message}`
|
|
24
|
+
})),
|
|
25
|
+
RateLimitError: (err) => Effect.fail(new FreshnessError({
|
|
26
|
+
strategy: "api",
|
|
27
|
+
message: `Fresh data required but rate limit exceeded: ${err.message}`
|
|
28
|
+
}))
|
|
29
|
+
}));
|
|
30
|
+
const releases = yield* Effect.all(inputs.map((input) => BunRelease.fromInput(input)), { concurrency: "unbounded" }).pipe(Effect.orElseSucceed(() => []));
|
|
31
|
+
yield* cache.load(releases);
|
|
32
|
+
});
|
|
33
|
+
const SemVerLayer = VersionCacheLive.pipe(Layer.provide(SemVerParserLive));
|
|
34
|
+
const BaseCacheLayer = BunReleaseCacheLive.pipe(Layer.provide(SemVerLayer));
|
|
35
|
+
/**
|
|
36
|
+
* Provides the {@link BunReleaseCache} service using the "Fresh" cache strategy.
|
|
37
|
+
*
|
|
38
|
+
* Requires live release data from the GitHub API via {@link BunVersionFetcher}.
|
|
39
|
+
* If the fetch fails for any reason — authentication errors, network errors,
|
|
40
|
+
* parse errors, or rate-limit errors — the layer fails with a `FreshnessError`
|
|
41
|
+
* rather than falling back to stale data.
|
|
42
|
+
*
|
|
43
|
+
* Use this strategy in contexts where working with outdated version data would
|
|
44
|
+
* be incorrect or misleading, such as CI pipelines or tooling that must reflect
|
|
45
|
+
* the true current state of Bun releases.
|
|
46
|
+
*
|
|
47
|
+
* The three Bun cache strategy layers are:
|
|
48
|
+
* - {@link AutoBunCacheLive} — tries API first, falls back to bundled defaults on any error
|
|
49
|
+
* - `FreshBunCacheLive` — requires live API data, fails with `FreshnessError` if unavailable
|
|
50
|
+
* - {@link OfflineBunCacheLive} — uses only bundled defaults, no network I/O
|
|
51
|
+
*
|
|
52
|
+
* @see {@link BunReleaseCache}
|
|
53
|
+
* @see {@link AutoBunCacheLive}
|
|
54
|
+
* @see {@link OfflineBunCacheLive}
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
const FreshBunCacheLive = BaseCacheLayer.pipe(Layer.tap((ctx) => freshSetup.pipe(Effect.provide(ctx))));
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
export { FreshBunCacheLive };
|
|
61
|
+
//# sourceMappingURL=FreshBunCacheLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FreshBunCacheLive.js","names":["SemVerVersionCacheLive"],"sources":["../../../../src/layers/FreshBunCacheLive.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\nimport { SemVerParserLive, VersionCacheLive as SemVerVersionCacheLive } from \"semver-effect\";\nimport { FreshnessError } from \"../errors/FreshnessError.js\";\nimport { BunRelease } from \"../schemas/bun-release.js\";\nimport { BunReleaseCache } from \"../services/BunReleaseCache.js\";\nimport { BunVersionFetcher } from \"../services/BunVersionFetcher.js\";\nimport { BunReleaseCacheLive } from \"./BunReleaseCacheLive.js\";\n\nconst freshSetup = Effect.gen(function* () {\n\tconst cache = yield* BunReleaseCache;\n\tconst fetcher = yield* BunVersionFetcher;\n\n\tconst { inputs } = yield* fetcher.fetch().pipe(\n\t\tEffect.catchTags({\n\t\t\tAuthenticationError: (err) =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\tnew FreshnessError({\n\t\t\t\t\t\tstrategy: \"api\",\n\t\t\t\t\t\tmessage: `Fresh data required but authentication failed: ${err.message}`,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\tNetworkError: (err) =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\tnew FreshnessError({\n\t\t\t\t\t\tstrategy: \"api\",\n\t\t\t\t\t\tmessage: `Fresh data required but network unavailable: ${err.message}`,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\tParseError: (err) =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\tnew FreshnessError({ strategy: \"api\", message: `Fresh data required but parse failed: ${err.message}` }),\n\t\t\t\t),\n\t\t\tRateLimitError: (err) =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\tnew FreshnessError({\n\t\t\t\t\t\tstrategy: \"api\",\n\t\t\t\t\t\tmessage: `Fresh data required but rate limit exceeded: ${err.message}`,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t}),\n\t);\n\n\tconst releases = yield* Effect.all(\n\t\tinputs.map((input) => BunRelease.fromInput(input)),\n\t\t{ concurrency: \"unbounded\" },\n\t).pipe(Effect.orElseSucceed(() => [] as BunRelease[]));\n\tyield* cache.load(releases);\n});\n\nconst SemVerLayer = SemVerVersionCacheLive.pipe(Layer.provide(SemVerParserLive));\nconst BaseCacheLayer = BunReleaseCacheLive.pipe(Layer.provide(SemVerLayer));\n\n/**\n * Provides the {@link BunReleaseCache} service using the \"Fresh\" cache strategy.\n *\n * Requires live release data from the GitHub API via {@link BunVersionFetcher}.\n * If the fetch fails for any reason — authentication errors, network errors,\n * parse errors, or rate-limit errors — the layer fails with a `FreshnessError`\n * rather than falling back to stale data.\n *\n * Use this strategy in contexts where working with outdated version data would\n * be incorrect or misleading, such as CI pipelines or tooling that must reflect\n * the true current state of Bun releases.\n *\n * The three Bun cache strategy layers are:\n * - {@link AutoBunCacheLive} — tries API first, falls back to bundled defaults on any error\n * - `FreshBunCacheLive` — requires live API data, fails with `FreshnessError` if unavailable\n * - {@link OfflineBunCacheLive} — uses only bundled defaults, no network I/O\n *\n * @see {@link BunReleaseCache}\n * @see {@link AutoBunCacheLive}\n * @see {@link OfflineBunCacheLive}\n * @public\n */\nexport const FreshBunCacheLive: Layer.Layer<BunReleaseCache, FreshnessError, BunVersionFetcher> = BaseCacheLayer.pipe(\n\tLayer.tap((ctx) => freshSetup.pipe(Effect.provide(ctx))),\n);\n"],"mappings":";;;;;;;;;AAQA,MAAM,aAAa,OAAO,IAAI,aAAa;CAC1C,MAAM,QAAQ,OAAO;CAGrB,MAAM,EAAE,WAAW,QAAO,OAFH,kBAEU,CAAC,MAAM,CAAC,CAAC,KACzC,OAAO,UAAU;EAChB,sBAAsB,QACrB,OAAO,KACN,IAAI,eAAe;GAClB,UAAU;GACV,SAAS,kDAAkD,IAAI;EAChE,CAAC,CACF;EACD,eAAe,QACd,OAAO,KACN,IAAI,eAAe;GAClB,UAAU;GACV,SAAS,gDAAgD,IAAI;EAC9D,CAAC,CACF;EACD,aAAa,QACZ,OAAO,KACN,IAAI,eAAe;GAAE,UAAU;GAAO,SAAS,yCAAyC,IAAI;EAAU,CAAC,CACxG;EACD,iBAAiB,QAChB,OAAO,KACN,IAAI,eAAe;GAClB,UAAU;GACV,SAAS,gDAAgD,IAAI;EAC9D,CAAC,CACF;CACF,CAAC,CACF;CAEA,MAAM,WAAW,OAAO,OAAO,IAC9B,OAAO,KAAK,UAAU,WAAW,UAAU,KAAK,CAAC,GACjD,EAAE,aAAa,YAAY,CAC5B,CAAC,CAAC,KAAK,OAAO,oBAAoB,CAAC,CAAiB,CAAC;CACrD,OAAO,MAAM,KAAK,QAAQ;AAC3B,CAAC;AAED,MAAM,cAAcA,iBAAuB,KAAK,MAAM,QAAQ,gBAAgB,CAAC;AAC/E,MAAM,iBAAiB,oBAAoB,KAAK,MAAM,QAAQ,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAwB1E,MAAa,oBAAqF,eAAe,KAChH,MAAM,KAAK,QAAQ,WAAW,KAAK,OAAO,QAAQ,GAAG,CAAC,CAAC,CACxD"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { DenoRelease } from "../schemas/deno-release.js";
|
|
2
|
+
import { DenoReleaseCache } from "../services/DenoReleaseCache.js";
|
|
3
|
+
import { DenoVersionFetcher } from "../services/DenoVersionFetcher.js";
|
|
4
|
+
import { DenoReleaseCacheLive } from "./DenoReleaseCacheLive.js";
|
|
5
|
+
import { FreshnessError } from "../errors/FreshnessError.js";
|
|
6
|
+
import { Effect, Layer } from "effect";
|
|
7
|
+
import { SemVerParserLive, VersionCacheLive } from "semver-effect";
|
|
8
|
+
|
|
9
|
+
//#region src/layers/FreshDenoCacheLive.ts
|
|
10
|
+
const freshSetup = Effect.gen(function* () {
|
|
11
|
+
const cache = yield* DenoReleaseCache;
|
|
12
|
+
const { inputs } = yield* (yield* DenoVersionFetcher).fetch().pipe(Effect.catchTags({
|
|
13
|
+
AuthenticationError: (err) => Effect.fail(new FreshnessError({
|
|
14
|
+
strategy: "api",
|
|
15
|
+
message: `Fresh data required but authentication failed: ${err.message}`
|
|
16
|
+
})),
|
|
17
|
+
NetworkError: (err) => Effect.fail(new FreshnessError({
|
|
18
|
+
strategy: "api",
|
|
19
|
+
message: `Fresh data required but network unavailable: ${err.message}`
|
|
20
|
+
})),
|
|
21
|
+
ParseError: (err) => Effect.fail(new FreshnessError({
|
|
22
|
+
strategy: "api",
|
|
23
|
+
message: `Fresh data required but parse failed: ${err.message}`
|
|
24
|
+
})),
|
|
25
|
+
RateLimitError: (err) => Effect.fail(new FreshnessError({
|
|
26
|
+
strategy: "api",
|
|
27
|
+
message: `Fresh data required but rate limit exceeded: ${err.message}`
|
|
28
|
+
}))
|
|
29
|
+
}));
|
|
30
|
+
const releases = yield* Effect.all(inputs.map((input) => DenoRelease.fromInput(input)), { concurrency: "unbounded" }).pipe(Effect.orElseSucceed(() => []));
|
|
31
|
+
yield* cache.load(releases);
|
|
32
|
+
});
|
|
33
|
+
const SemVerLayer = VersionCacheLive.pipe(Layer.provide(SemVerParserLive));
|
|
34
|
+
const BaseCacheLayer = DenoReleaseCacheLive.pipe(Layer.provide(SemVerLayer));
|
|
35
|
+
/**
|
|
36
|
+
* Provides the {@link DenoReleaseCache} service using the "Fresh" cache strategy.
|
|
37
|
+
*
|
|
38
|
+
* Requires live release data from the GitHub API via {@link DenoVersionFetcher}.
|
|
39
|
+
* If the fetch fails for any reason — authentication errors, network errors,
|
|
40
|
+
* parse errors, or rate-limit errors — the layer fails with a `FreshnessError`
|
|
41
|
+
* rather than falling back to stale data.
|
|
42
|
+
*
|
|
43
|
+
* Use this strategy in contexts where working with outdated version data would
|
|
44
|
+
* be incorrect or misleading, such as CI pipelines or tooling that must reflect
|
|
45
|
+
* the true current state of Deno releases.
|
|
46
|
+
*
|
|
47
|
+
* The three Deno cache strategy layers are:
|
|
48
|
+
* - {@link AutoDenoCacheLive} — tries API first, falls back to bundled defaults on any error
|
|
49
|
+
* - `FreshDenoCacheLive` — requires live API data, fails with `FreshnessError` if unavailable
|
|
50
|
+
* - {@link OfflineDenoCacheLive} — uses only bundled defaults, no network I/O
|
|
51
|
+
*
|
|
52
|
+
* @see {@link DenoReleaseCache}
|
|
53
|
+
* @see {@link AutoDenoCacheLive}
|
|
54
|
+
* @see {@link OfflineDenoCacheLive}
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
const FreshDenoCacheLive = BaseCacheLayer.pipe(Layer.tap((ctx) => freshSetup.pipe(Effect.provide(ctx))));
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
export { FreshDenoCacheLive };
|
|
61
|
+
//# sourceMappingURL=FreshDenoCacheLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FreshDenoCacheLive.js","names":["SemVerVersionCacheLive"],"sources":["../../../../src/layers/FreshDenoCacheLive.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\nimport { SemVerParserLive, VersionCacheLive as SemVerVersionCacheLive } from \"semver-effect\";\nimport { FreshnessError } from \"../errors/FreshnessError.js\";\nimport { DenoRelease } from \"../schemas/deno-release.js\";\nimport { DenoReleaseCache } from \"../services/DenoReleaseCache.js\";\nimport { DenoVersionFetcher } from \"../services/DenoVersionFetcher.js\";\nimport { DenoReleaseCacheLive } from \"./DenoReleaseCacheLive.js\";\n\nconst freshSetup = Effect.gen(function* () {\n\tconst cache = yield* DenoReleaseCache;\n\tconst fetcher = yield* DenoVersionFetcher;\n\n\tconst { inputs } = yield* fetcher.fetch().pipe(\n\t\tEffect.catchTags({\n\t\t\tAuthenticationError: (err) =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\tnew FreshnessError({\n\t\t\t\t\t\tstrategy: \"api\",\n\t\t\t\t\t\tmessage: `Fresh data required but authentication failed: ${err.message}`,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\tNetworkError: (err) =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\tnew FreshnessError({\n\t\t\t\t\t\tstrategy: \"api\",\n\t\t\t\t\t\tmessage: `Fresh data required but network unavailable: ${err.message}`,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\tParseError: (err) =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\tnew FreshnessError({ strategy: \"api\", message: `Fresh data required but parse failed: ${err.message}` }),\n\t\t\t\t),\n\t\t\tRateLimitError: (err) =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\tnew FreshnessError({\n\t\t\t\t\t\tstrategy: \"api\",\n\t\t\t\t\t\tmessage: `Fresh data required but rate limit exceeded: ${err.message}`,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t}),\n\t);\n\n\tconst releases = yield* Effect.all(\n\t\tinputs.map((input) => DenoRelease.fromInput(input)),\n\t\t{ concurrency: \"unbounded\" },\n\t).pipe(Effect.orElseSucceed(() => [] as DenoRelease[]));\n\tyield* cache.load(releases);\n});\n\nconst SemVerLayer = SemVerVersionCacheLive.pipe(Layer.provide(SemVerParserLive));\nconst BaseCacheLayer = DenoReleaseCacheLive.pipe(Layer.provide(SemVerLayer));\n\n/**\n * Provides the {@link DenoReleaseCache} service using the \"Fresh\" cache strategy.\n *\n * Requires live release data from the GitHub API via {@link DenoVersionFetcher}.\n * If the fetch fails for any reason — authentication errors, network errors,\n * parse errors, or rate-limit errors — the layer fails with a `FreshnessError`\n * rather than falling back to stale data.\n *\n * Use this strategy in contexts where working with outdated version data would\n * be incorrect or misleading, such as CI pipelines or tooling that must reflect\n * the true current state of Deno releases.\n *\n * The three Deno cache strategy layers are:\n * - {@link AutoDenoCacheLive} — tries API first, falls back to bundled defaults on any error\n * - `FreshDenoCacheLive` — requires live API data, fails with `FreshnessError` if unavailable\n * - {@link OfflineDenoCacheLive} — uses only bundled defaults, no network I/O\n *\n * @see {@link DenoReleaseCache}\n * @see {@link AutoDenoCacheLive}\n * @see {@link OfflineDenoCacheLive}\n * @public\n */\nexport const FreshDenoCacheLive: Layer.Layer<DenoReleaseCache, FreshnessError, DenoVersionFetcher> =\n\tBaseCacheLayer.pipe(Layer.tap((ctx) => freshSetup.pipe(Effect.provide(ctx))));\n"],"mappings":";;;;;;;;;AAQA,MAAM,aAAa,OAAO,IAAI,aAAa;CAC1C,MAAM,QAAQ,OAAO;CAGrB,MAAM,EAAE,WAAW,QAAO,OAFH,mBAEU,CAAC,MAAM,CAAC,CAAC,KACzC,OAAO,UAAU;EAChB,sBAAsB,QACrB,OAAO,KACN,IAAI,eAAe;GAClB,UAAU;GACV,SAAS,kDAAkD,IAAI;EAChE,CAAC,CACF;EACD,eAAe,QACd,OAAO,KACN,IAAI,eAAe;GAClB,UAAU;GACV,SAAS,gDAAgD,IAAI;EAC9D,CAAC,CACF;EACD,aAAa,QACZ,OAAO,KACN,IAAI,eAAe;GAAE,UAAU;GAAO,SAAS,yCAAyC,IAAI;EAAU,CAAC,CACxG;EACD,iBAAiB,QAChB,OAAO,KACN,IAAI,eAAe;GAClB,UAAU;GACV,SAAS,gDAAgD,IAAI;EAC9D,CAAC,CACF;CACF,CAAC,CACF;CAEA,MAAM,WAAW,OAAO,OAAO,IAC9B,OAAO,KAAK,UAAU,YAAY,UAAU,KAAK,CAAC,GAClD,EAAE,aAAa,YAAY,CAC5B,CAAC,CAAC,KAAK,OAAO,oBAAoB,CAAC,CAAkB,CAAC;CACtD,OAAO,MAAM,KAAK,QAAQ;AAC3B,CAAC;AAED,MAAM,cAAcA,iBAAuB,KAAK,MAAM,QAAQ,gBAAgB,CAAC;AAC/E,MAAM,iBAAiB,qBAAqB,KAAK,MAAM,QAAQ,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAwB3E,MAAa,qBACZ,eAAe,KAAK,MAAM,KAAK,QAAQ,WAAW,KAAK,OAAO,QAAQ,GAAG,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NodeReleaseCache } from "../services/NodeReleaseCache.js";
|
|
2
|
+
import { NodeScheduleFetcher } from "../services/NodeScheduleFetcher.js";
|
|
3
|
+
import { NodeVersionFetcher } from "../services/NodeVersionFetcher.js";
|
|
4
|
+
import { NodeReleaseCacheLive } from "./NodeReleaseCacheLive.js";
|
|
5
|
+
import { FreshnessError } from "../errors/FreshnessError.js";
|
|
6
|
+
import { Effect, Layer } from "effect";
|
|
7
|
+
import { SemVerParserLive, VersionCacheLive } from "semver-effect";
|
|
8
|
+
|
|
9
|
+
//#region src/layers/FreshNodeCacheLive.ts
|
|
10
|
+
const freshSetup = Effect.gen(function* () {
|
|
11
|
+
const cache = yield* NodeReleaseCache;
|
|
12
|
+
const versionFetcher = yield* NodeVersionFetcher;
|
|
13
|
+
const scheduleFetcher = yield* NodeScheduleFetcher;
|
|
14
|
+
const [fetchResult, scheduleData] = yield* Effect.all([versionFetcher.fetch(), scheduleFetcher.fetch()]).pipe(Effect.catchTags({
|
|
15
|
+
NetworkError: (err) => Effect.fail(new FreshnessError({
|
|
16
|
+
strategy: "api",
|
|
17
|
+
message: `Fresh data required but network unavailable: ${err.message}`
|
|
18
|
+
})),
|
|
19
|
+
ParseError: (err) => Effect.fail(new FreshnessError({
|
|
20
|
+
strategy: "api",
|
|
21
|
+
message: `Fresh data required but response could not be parsed: ${err.message}`
|
|
22
|
+
}))
|
|
23
|
+
}));
|
|
24
|
+
yield* cache.updateSchedule(scheduleData);
|
|
25
|
+
yield* cache.loadFromInputs(fetchResult.inputs);
|
|
26
|
+
});
|
|
27
|
+
const SemVerLayer = VersionCacheLive.pipe(Layer.provide(SemVerParserLive));
|
|
28
|
+
const BaseCacheLayer = NodeReleaseCacheLive.pipe(Layer.provide(SemVerLayer));
|
|
29
|
+
/**
|
|
30
|
+
* Provides the {@link NodeReleaseCache} service using the "Fresh" cache strategy.
|
|
31
|
+
*
|
|
32
|
+
* Requires live release data from `nodejs.org/dist/index.json` via
|
|
33
|
+
* {@link NodeVersionFetcher} and the release schedule from the `nodejs/Release`
|
|
34
|
+
* repository via {@link NodeScheduleFetcher}. If either fetch fails — due to
|
|
35
|
+
* network errors or parse errors — the layer fails with a `FreshnessError`
|
|
36
|
+
* rather than falling back to stale data.
|
|
37
|
+
*
|
|
38
|
+
* Use this strategy in contexts where working with outdated version data would
|
|
39
|
+
* be incorrect or misleading, such as CI pipelines or tooling that must reflect
|
|
40
|
+
* the true current state of Node.js releases and LTS schedule.
|
|
41
|
+
*
|
|
42
|
+
* The three Node cache strategy layers are:
|
|
43
|
+
* - {@link AutoNodeCacheLive} — tries API first, falls back to bundled defaults on any error
|
|
44
|
+
* - `FreshNodeCacheLive` — requires live API data, fails with `FreshnessError` if unavailable
|
|
45
|
+
* - {@link OfflineNodeCacheLive} — uses only bundled defaults, no network I/O
|
|
46
|
+
*
|
|
47
|
+
* @see {@link NodeReleaseCache}
|
|
48
|
+
* @see {@link AutoNodeCacheLive}
|
|
49
|
+
* @see {@link OfflineNodeCacheLive}
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
const FreshNodeCacheLive = BaseCacheLayer.pipe(Layer.tap((ctx) => freshSetup.pipe(Effect.provide(ctx))));
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
export { FreshNodeCacheLive };
|
|
56
|
+
//# sourceMappingURL=FreshNodeCacheLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FreshNodeCacheLive.js","names":["SemVerVersionCacheLive"],"sources":["../../../../src/layers/FreshNodeCacheLive.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\nimport { SemVerParserLive, VersionCacheLive as SemVerVersionCacheLive } from \"semver-effect\";\nimport { FreshnessError } from \"../errors/FreshnessError.js\";\nimport { NodeReleaseCache } from \"../services/NodeReleaseCache.js\";\nimport { NodeScheduleFetcher } from \"../services/NodeScheduleFetcher.js\";\nimport { NodeVersionFetcher } from \"../services/NodeVersionFetcher.js\";\nimport { NodeReleaseCacheLive } from \"./NodeReleaseCacheLive.js\";\n\nconst freshSetup = Effect.gen(function* () {\n\tconst cache = yield* NodeReleaseCache;\n\tconst versionFetcher = yield* NodeVersionFetcher;\n\tconst scheduleFetcher = yield* NodeScheduleFetcher;\n\n\tconst [fetchResult, scheduleData] = yield* Effect.all([versionFetcher.fetch(), scheduleFetcher.fetch()]).pipe(\n\t\tEffect.catchTags({\n\t\t\tNetworkError: (err) =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\tnew FreshnessError({\n\t\t\t\t\t\tstrategy: \"api\",\n\t\t\t\t\t\tmessage: `Fresh data required but network unavailable: ${err.message}`,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\tParseError: (err) =>\n\t\t\t\tEffect.fail(\n\t\t\t\t\tnew FreshnessError({\n\t\t\t\t\t\tstrategy: \"api\",\n\t\t\t\t\t\tmessage: `Fresh data required but response could not be parsed: ${err.message}`,\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t}),\n\t);\n\n\tyield* cache.updateSchedule(scheduleData);\n\tyield* cache.loadFromInputs(fetchResult.inputs);\n});\n\nconst SemVerLayer = SemVerVersionCacheLive.pipe(Layer.provide(SemVerParserLive));\nconst BaseCacheLayer = NodeReleaseCacheLive.pipe(Layer.provide(SemVerLayer));\n\n/**\n * Provides the {@link NodeReleaseCache} service using the \"Fresh\" cache strategy.\n *\n * Requires live release data from `nodejs.org/dist/index.json` via\n * {@link NodeVersionFetcher} and the release schedule from the `nodejs/Release`\n * repository via {@link NodeScheduleFetcher}. If either fetch fails — due to\n * network errors or parse errors — the layer fails with a `FreshnessError`\n * rather than falling back to stale data.\n *\n * Use this strategy in contexts where working with outdated version data would\n * be incorrect or misleading, such as CI pipelines or tooling that must reflect\n * the true current state of Node.js releases and LTS schedule.\n *\n * The three Node cache strategy layers are:\n * - {@link AutoNodeCacheLive} — tries API first, falls back to bundled defaults on any error\n * - `FreshNodeCacheLive` — requires live API data, fails with `FreshnessError` if unavailable\n * - {@link OfflineNodeCacheLive} — uses only bundled defaults, no network I/O\n *\n * @see {@link NodeReleaseCache}\n * @see {@link AutoNodeCacheLive}\n * @see {@link OfflineNodeCacheLive}\n * @public\n */\nexport const FreshNodeCacheLive: Layer.Layer<\n\tNodeReleaseCache,\n\tFreshnessError,\n\tNodeVersionFetcher | NodeScheduleFetcher\n> = BaseCacheLayer.pipe(Layer.tap((ctx) => freshSetup.pipe(Effect.provide(ctx))));\n"],"mappings":";;;;;;;;;AAQA,MAAM,aAAa,OAAO,IAAI,aAAa;CAC1C,MAAM,QAAQ,OAAO;CACrB,MAAM,iBAAiB,OAAO;CAC9B,MAAM,kBAAkB,OAAO;CAE/B,MAAM,CAAC,aAAa,gBAAgB,OAAO,OAAO,IAAI,CAAC,eAAe,MAAM,GAAG,gBAAgB,MAAM,CAAC,CAAC,CAAC,CAAC,KACxG,OAAO,UAAU;EAChB,eAAe,QACd,OAAO,KACN,IAAI,eAAe;GAClB,UAAU;GACV,SAAS,gDAAgD,IAAI;EAC9D,CAAC,CACF;EACD,aAAa,QACZ,OAAO,KACN,IAAI,eAAe;GAClB,UAAU;GACV,SAAS,yDAAyD,IAAI;EACvE,CAAC,CACF;CACF,CAAC,CACF;CAEA,OAAO,MAAM,eAAe,YAAY;CACxC,OAAO,MAAM,eAAe,YAAY,MAAM;AAC/C,CAAC;AAED,MAAM,cAAcA,iBAAuB,KAAK,MAAM,QAAQ,gBAAgB,CAAC;AAC/E,MAAM,iBAAiB,qBAAqB,KAAK,MAAM,QAAQ,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyB3E,MAAa,qBAIT,eAAe,KAAK,MAAM,KAAK,QAAQ,WAAW,KAAK,OAAO,QAAQ,GAAG,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { OctokitInstance } from "../services/OctokitInstance.js";
|
|
2
|
+
import { AuthenticationError } from "../errors/AuthenticationError.js";
|
|
3
|
+
import { Effect, Layer } from "effect";
|
|
4
|
+
import { Octokit } from "octokit";
|
|
5
|
+
import { createAppAuth } from "@octokit/auth-app";
|
|
6
|
+
|
|
7
|
+
//#region src/layers/GitHubAppAuth.ts
|
|
8
|
+
const resolveInstallationId = async (auth, installationId) => {
|
|
9
|
+
if (installationId) return installationId;
|
|
10
|
+
const { token: jwt } = await auth({ type: "app" });
|
|
11
|
+
const response = await fetch("https://api.github.com/app/installations?per_page=100", { headers: {
|
|
12
|
+
Authorization: `Bearer ${jwt}`,
|
|
13
|
+
Accept: "application/vnd.github+json"
|
|
14
|
+
} });
|
|
15
|
+
if (!response.ok) throw new Error(`Failed to list installations: ${response.status}`);
|
|
16
|
+
const installations = await response.json();
|
|
17
|
+
if (installations.length === 0) throw new Error("No installations found for this GitHub App");
|
|
18
|
+
return installations[0].id;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Provides an {@link OctokitInstance} authenticated as a GitHub App installation.
|
|
22
|
+
*
|
|
23
|
+
* Accepts a {@link GitHubAppAuthConfig} and uses `@octokit/auth-app` to obtain
|
|
24
|
+
* an installation access token. If `installationId` is omitted, the first
|
|
25
|
+
* installation returned by the GitHub API is used automatically (suitable for
|
|
26
|
+
* apps with a single installation). If authentication fails for any reason the
|
|
27
|
+
* layer fails with an `AuthenticationError`.
|
|
28
|
+
*
|
|
29
|
+
* Use this auth method when your integration runs as a GitHub App rather than
|
|
30
|
+
* as an individual user or a CI token.
|
|
31
|
+
*
|
|
32
|
+
* @param config - App credentials and optional installation ID.
|
|
33
|
+
*
|
|
34
|
+
* @see {@link GitHubAppAuthConfig}
|
|
35
|
+
* @see {@link OctokitInstance}
|
|
36
|
+
* @see {@link GitHubAutoAuth}
|
|
37
|
+
* @see {@link GitHubTokenAuth}
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
const GitHubAppAuth = (config) => Layer.effect(OctokitInstance, Effect.tryPromise({
|
|
41
|
+
try: async () => {
|
|
42
|
+
const auth = createAppAuth({
|
|
43
|
+
appId: config.appId,
|
|
44
|
+
privateKey: config.privateKey
|
|
45
|
+
});
|
|
46
|
+
return new Octokit({ auth: (await auth({
|
|
47
|
+
type: "installation",
|
|
48
|
+
installationId: await resolveInstallationId(auth, config.installationId)
|
|
49
|
+
})).token });
|
|
50
|
+
},
|
|
51
|
+
catch: (error) => new AuthenticationError({
|
|
52
|
+
method: "app",
|
|
53
|
+
message: error instanceof Error ? error.message : String(error)
|
|
54
|
+
})
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
//#endregion
|
|
58
|
+
export { GitHubAppAuth };
|
|
59
|
+
//# sourceMappingURL=GitHubAppAuth.js.map
|