@alchemy.run/pkg 2.0.0-beta.80 → 2.0.0-beta.81

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.
Files changed (51) hide show
  1. package/dist/Manifest.d.ts.map +1 -1
  2. package/dist/Manifest.js.map +1 -1
  3. package/dist/Protocol.d.ts +1 -1
  4. package/dist/Protocol.d.ts.map +1 -1
  5. package/dist/Protocol.js +3 -12
  6. package/dist/Protocol.js.map +1 -1
  7. package/dist/Registry/Bindings.d.ts.map +1 -1
  8. package/dist/Registry/Bindings.js.map +1 -1
  9. package/dist/Registry/GitHub.d.ts +2 -2
  10. package/dist/Registry/GitHub.d.ts.map +1 -1
  11. package/dist/Registry/GitHub.js +9 -36
  12. package/dist/Registry/GitHub.js.map +1 -1
  13. package/dist/Registry/Handler.d.ts +1 -1
  14. package/dist/Registry/Handler.d.ts.map +1 -1
  15. package/dist/Registry/Handler.js +10 -18
  16. package/dist/Registry/Handler.js.map +1 -1
  17. package/dist/Registry/KVCache.d.ts.map +1 -1
  18. package/dist/Registry/KVCache.js +1 -1
  19. package/dist/Registry/KVCache.js.map +1 -1
  20. package/dist/Registry/Sweep.d.ts.map +1 -1
  21. package/dist/Registry/Sweep.js +3 -8
  22. package/dist/Registry/Sweep.js.map +1 -1
  23. package/dist/Registry/Tags.d.ts.map +1 -1
  24. package/dist/Registry/Tags.js.map +1 -1
  25. package/dist/Registry.d.ts +1 -1
  26. package/dist/Registry.d.ts.map +1 -1
  27. package/dist/Registry.js +1 -1
  28. package/dist/Registry.js.map +1 -1
  29. package/dist/cli/bin.d.ts +1 -1
  30. package/dist/cli/bin.d.ts.map +1 -1
  31. package/dist/cli/bin.js +5 -9
  32. package/dist/cli/bin.js.map +1 -1
  33. package/dist/cli/pack.d.ts.map +1 -1
  34. package/dist/cli/pack.js +11 -23
  35. package/dist/cli/pack.js.map +1 -1
  36. package/dist/cli/publish.d.ts.map +1 -1
  37. package/dist/cli/publish.js +2 -2
  38. package/dist/cli/publish.js.map +1 -1
  39. package/package.json +19 -19
  40. package/src/Manifest.ts +1 -3
  41. package/src/Protocol.ts +12 -28
  42. package/src/Registry/Bindings.ts +1 -4
  43. package/src/Registry/GitHub.ts +24 -83
  44. package/src/Registry/Handler.ts +31 -87
  45. package/src/Registry/KVCache.ts +6 -17
  46. package/src/Registry/Sweep.ts +13 -26
  47. package/src/Registry/Tags.ts +7 -15
  48. package/src/Registry.ts +5 -15
  49. package/src/cli/bin.ts +8 -18
  50. package/src/cli/pack.ts +27 -73
  51. package/src/cli/publish.ts +11 -23
@@ -1,3 +1,4 @@
1
+ import * as crypto from "node:crypto";
1
2
  import * as Github from "@distilled.cloud/github";
2
3
  import * as Actions from "@distilled.cloud/github/actions";
3
4
  import * as Apps from "@distilled.cloud/github/apps";
@@ -12,19 +13,16 @@ import * as DateTime from "effect/DateTime";
12
13
  import * as Duration from "effect/Duration";
13
14
  import * as Effect from "effect/Effect";
14
15
  import { Base64Url } from "effect/encoding";
16
+ import * as HttpClient from "effect/http/HttpClient";
15
17
  import * as Layer from "effect/Layer";
16
18
  import * as Option from "effect/Option";
17
19
  import * as Redacted from "effect/Redacted";
18
20
  import * as Schema from "effect/Schema";
19
21
  import * as Stream from "effect/Stream";
20
- import * as HttpClient from "effect/http/HttpClient";
21
- import * as crypto from "node:crypto";
22
22
  import { COMMENT_MARKER, RegistryConfig } from "./Bindings.ts";
23
23
  import * as KVCache from "./KVCache.ts";
24
24
 
25
- export class CryptoError extends Data.TaggedError("CryptoError")<{
26
- readonly message: string;
27
- }> {}
25
+ export class CryptoError extends Data.TaggedError("CryptoError")<{ readonly message: string }> {}
28
26
 
29
27
  export class AppNotInstalled extends Data.TaggedError("AppNotInstalled")<{
30
28
  readonly repo: string;
@@ -38,20 +36,14 @@ export class AppNotInstalled extends Data.TaggedError("AppNotInstalled")<{
38
36
  export const importPrivateKey = (pem: string) =>
39
37
  Effect.try({
40
38
  try: () => crypto.createPrivateKey({ key: pem }),
41
- catch: (cause) =>
42
- new CryptoError({ message: `invalid private key: ${cause}` }),
39
+ catch: (cause) => new CryptoError({ message: `invalid private key: ${cause}` }),
43
40
  });
44
41
 
45
42
  /** Sign a compact RS256 JWT, used to authenticate as the GitHub App. */
46
- export const signJwt = (
47
- claims: Record<string, unknown>,
48
- key: crypto.KeyObject,
49
- ) =>
43
+ export const signJwt = (claims: Record<string, unknown>, key: crypto.KeyObject) =>
50
44
  Effect.try({
51
45
  try: () => {
52
- const header = Base64Url.encode(
53
- JSON.stringify({ alg: "RS256", typ: "JWT" }),
54
- );
46
+ const header = Base64Url.encode(JSON.stringify({ alg: "RS256", typ: "JWT" }));
55
47
  const payload = Base64Url.encode(JSON.stringify(claims));
56
48
  const input = `${header}.${payload}`;
57
49
  const signature = crypto.sign("sha256", Buffer.from(input), key);
@@ -69,21 +61,14 @@ export const renderInstalls = (
69
61
  packages: ReadonlyArray<{ name: string; group: string; url: string }>,
70
62
  groups: ReadonlyArray<{ name: string; collapsed: boolean }>,
71
63
  ) => {
72
- const collapsed = new Set(
73
- groups.filter((g) => g.collapsed).map((g) => g.name),
74
- );
64
+ const collapsed = new Set(groups.filter((g) => g.collapsed).map((g) => g.name));
75
65
  // One code block per package so each command has its own copy button, in
76
66
  // the order the manifest lists them, which is the order they were given
77
67
  // to `pkg pack`. Blank lines around the markdown inside `<details>` are
78
68
  // what make GitHub render it.
79
69
  return Object.entries(Arr.groupBy(packages, (pkg) => pkg.group))
80
70
  .flatMap(([group, members]) => {
81
- const installs = members.flatMap(({ url }) => [
82
- "```sh",
83
- `pnpm install ${url}`,
84
- "```",
85
- "",
86
- ]);
71
+ const installs = members.flatMap(({ url }) => ["```sh", `pnpm install ${url}`, "```", ""]);
87
72
  return collapsed.has(group)
88
73
  ? [
89
74
  "<details>",
@@ -177,10 +162,7 @@ const make = Effect.gen(function* () {
177
162
  const pem = yield* config.github.privateKey;
178
163
  const key = yield* importPrivateKey(Redacted.value(pem));
179
164
  const now = Math.floor((yield* Clock.currentTimeMillis) / 1000);
180
- return yield* signJwt(
181
- { iat: now - 60, exp: now + 540, iss: yield* config.github.appId },
182
- key,
183
- );
165
+ return yield* signJwt({ iat: now - 60, exp: now + 540, iss: yield* config.github.appId }, key);
184
166
  });
185
167
 
186
168
  // Failures are not kept: the next call retries the mint.
@@ -190,17 +172,12 @@ const make = Effect.gen(function* () {
190
172
  (repo) =>
191
173
  Effect.gen(function* () {
192
174
  const jwt = yield* appJwt;
193
- const installation = yield* as(
194
- jwt,
195
- Apps.getRepoInstallation(split(repo)),
196
- ).pipe(
175
+ const installation = yield* as(jwt, Apps.getRepoInstallation(split(repo))).pipe(
197
176
  Effect.catchTag("NotFound", () => new AppNotInstalled({ repo })),
198
177
  );
199
178
  const access = yield* as(
200
179
  jwt,
201
- Apps.createInstallationAccessToken({
202
- installation_id: installation.id,
203
- }),
180
+ Apps.createInstallationAccessToken({ installation_id: installation.id }),
204
181
  );
205
182
  const now = yield* Clock.currentTimeMillis;
206
183
  return {
@@ -214,17 +191,12 @@ const make = Effect.gen(function* () {
214
191
  );
215
192
 
216
193
  /** A GitHub operation run as the App's installation on `repo`. */
217
- const asInstallation = <A, E, R>(
218
- repo: string,
219
- operation: Effect.Effect<A, E, R>,
220
- ) => Effect.flatMap(tokens(repo), (token) => as(token, operation));
194
+ const asInstallation = <A, E, R>(repo: string, operation: Effect.Effect<A, E, R>) =>
195
+ Effect.flatMap(tokens(repo), (token) => as(token, operation));
221
196
 
222
197
  return {
223
198
  getRun: (repo: string, runId: number) =>
224
- asInstallation(
225
- repo,
226
- Actions.getWorkflowRun({ ...split(repo), run_id: runId }),
227
- ),
199
+ asInstallation(repo, Actions.getWorkflowRun({ ...split(repo), run_id: runId })),
228
200
 
229
201
  /**
230
202
  * Artifacts named `name` uploaded to a run so far, including by jobs
@@ -233,12 +205,7 @@ const make = Effect.gen(function* () {
233
205
  listRunArtifacts: (repo: string, runId: number, name: string) =>
234
206
  asInstallation(
235
207
  repo,
236
- Actions.listWorkflowRunArtifacts({
237
- ...split(repo),
238
- run_id: runId,
239
- name,
240
- per_page: 100,
241
- }),
208
+ Actions.listWorkflowRunArtifacts({ ...split(repo), run_id: runId, name, per_page: 100 }),
242
209
  ).pipe(Effect.map((page) => page.artifacts)),
243
210
 
244
211
  /**
@@ -246,12 +213,7 @@ const make = Effect.gen(function* () {
246
213
  * repository the commit was pushed to. Commit-associated PR lookups can
247
214
  * return no results for forks, so discover candidates by owner and branch.
248
215
  */
249
- pullRequestsForCommit: (
250
- repo: string,
251
- headRepo: string,
252
- headBranch: string,
253
- sha: string,
254
- ) =>
216
+ pullRequestsForCommit: (repo: string, headRepo: string, headBranch: string, sha: string) =>
255
217
  asInstallation(
256
218
  repo,
257
219
  Pulls.list({
@@ -309,28 +271,16 @@ const make = Effect.gen(function* () {
309
271
  * Create or update the comment on `issue` whose body starts with
310
272
  * `marker`, looking through the first five pages of comments.
311
273
  */
312
- upsertComment: (
313
- repo: string,
314
- issue: number,
315
- marker: string,
316
- body: string,
317
- ) =>
274
+ upsertComment: (repo: string, issue: number, marker: string, body: string) =>
318
275
  Effect.gen(function* () {
319
276
  const comments = Stream.paginate(1, (page) =>
320
277
  asInstallation(
321
278
  repo,
322
- Issues.listComments({
323
- ...split(repo),
324
- issue_number: issue,
325
- per_page: 100,
326
- page,
327
- }),
279
+ Issues.listComments({ ...split(repo), issue_number: issue, per_page: 100, page }),
328
280
  ).pipe(
329
281
  Effect.map((comments) => [
330
282
  comments,
331
- comments.length < 100 || page >= 5
332
- ? Option.none()
333
- : Option.some(page + 1),
283
+ comments.length < 100 || page >= 5 ? Option.none() : Option.some(page + 1),
334
284
  ]),
335
285
  ),
336
286
  );
@@ -344,29 +294,20 @@ const make = Effect.gen(function* () {
344
294
  onNone: () =>
345
295
  asInstallation(
346
296
  repo,
347
- Issues.createComment({
348
- ...split(repo),
349
- issue_number: issue,
350
- body,
351
- }),
297
+ Issues.createComment({ ...split(repo), issue_number: issue, body }),
352
298
  ),
353
299
  onSome: (comment) =>
354
300
  asInstallation(
355
301
  repo,
356
- Issues.updateComment({
357
- ...split(repo),
358
- comment_id: comment.id,
359
- body,
360
- }),
302
+ Issues.updateComment({ ...split(repo), comment_id: comment.id, body }),
361
303
  ),
362
304
  });
363
305
  }),
364
306
  };
365
307
  });
366
308
 
367
- export class GitHubApp extends Context.Service<
368
- GitHubApp,
369
- Effect.Success<typeof make>
370
- >()("@alchemy.run/pkg/GitHubApp") {}
309
+ export class GitHubApp extends Context.Service<GitHubApp, Effect.Success<typeof make>>()(
310
+ "@alchemy.run/pkg/GitHubApp",
311
+ ) {}
371
312
 
372
313
  export const GitHubAppLive = Layer.effect(GitHubApp, make);
@@ -5,17 +5,17 @@ import * as Clock from "effect/Clock";
5
5
  import * as Duration from "effect/Duration";
6
6
  import * as Effect from "effect/Effect";
7
7
  import { Hex } from "effect/encoding";
8
- import * as Layer from "effect/Layer";
9
- import * as Path from "effect/Path";
10
- import * as Result from "effect/Result";
11
- import * as Schema from "effect/Schema";
8
+ import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
12
9
  import * as Etag from "effect/http/Etag";
13
10
  import * as FetchHttpClient from "effect/http/FetchHttpClient";
14
11
  import * as HttpPlatform from "effect/http/HttpPlatform";
15
12
  import * as HttpRouter from "effect/http/HttpRouter";
16
13
  import { HttpServerRequest } from "effect/http/HttpServerRequest";
17
14
  import * as HttpServerResponse from "effect/http/HttpServerResponse";
18
- import * as HttpApiBuilder from "effect/http-api/HttpApiBuilder";
15
+ import * as Layer from "effect/Layer";
16
+ import * as Path from "effect/Path";
17
+ import * as Result from "effect/Result";
18
+ import * as Schema from "effect/Schema";
19
19
  import { ManifestJson, type ManifestPackage } from "../Manifest.ts";
20
20
  import {
21
21
  BadRequest,
@@ -28,14 +28,7 @@ import {
28
28
  RunRef,
29
29
  Upstream,
30
30
  } from "../Protocol.ts";
31
- import {
32
- Bucket,
33
- Cache,
34
- COMMENT_MARKER,
35
- Database,
36
- RegistryConfig,
37
- tarballKey,
38
- } from "./Bindings.ts";
31
+ import { Bucket, Cache, COMMENT_MARKER, Database, RegistryConfig, tarballKey } from "./Bindings.ts";
39
32
  import * as GitHub from "./GitHub.ts";
40
33
  import * as KVCache from "./KVCache.ts";
41
34
  import { sweep } from "./Sweep.ts";
@@ -98,9 +91,7 @@ export const tagsFor = (run: Run): string[] => {
98
91
 
99
92
  /** The tag install commands are written against. */
100
93
  export const installTag = (run: Run) =>
101
- run.pr !== null
102
- ? `pr:${run.pr}:${run.headSha.slice(0, SHORT)}`
103
- : run.headSha.slice(0, SHORT);
94
+ run.pr !== null ? `pr:${run.pr}:${run.headSha.slice(0, SHORT)}` : run.headSha.slice(0, SHORT);
104
95
 
105
96
  const upstream = (e: { readonly _tag: string; readonly message?: string }) =>
106
97
  new Upstream({ message: GitHub.describe(e) });
@@ -123,9 +114,7 @@ const lookupRun = Effect.fn("lookupRun")(function* (ref: RunRef) {
123
114
  if (!policy.repos.includes(ref.repo)) {
124
115
  return yield* new Forbidden({ message: `${ref.repo} may not publish` });
125
116
  }
126
- const data = yield* github
127
- .getRun(ref.repo, ref.runId)
128
- .pipe(Effect.mapError(upstream));
117
+ const data = yield* github.getRun(ref.repo, ref.runId).pipe(Effect.mapError(upstream));
129
118
  if (
130
119
  data.status !== "in_progress" ||
131
120
  (data.run_attempt !== undefined && data.run_attempt !== ref.attempt)
@@ -151,12 +140,7 @@ const lookupRun = Effect.fn("lookupRun")(function* (ref: RunRef) {
151
140
  // repository already published; matching the head repository keeps a
152
141
  // run's publication on its own pull request.
153
142
  const pulls = yield* github
154
- .pullRequestsForCommit(
155
- ref.repo,
156
- headRepo,
157
- data.head_branch,
158
- data.head_sha,
159
- )
143
+ .pullRequestsForCommit(ref.repo, headRepo, data.head_branch, data.head_sha)
160
144
  .pipe(Effect.mapError(upstream));
161
145
  const first = pulls[0];
162
146
  if (first === undefined) {
@@ -185,10 +169,7 @@ const lookupRun = Effect.fn("lookupRun")(function* (ref: RunRef) {
185
169
  * run, and GitHub reports the run's artifacts to the App, so a matching
186
170
  * name means this run vouched for exactly this manifest text.
187
171
  */
188
- const requireVouched = Effect.fn("requireVouched")(function* (
189
- run: Run,
190
- manifestText: string,
191
- ) {
172
+ const requireVouched = Effect.fn("requireVouched")(function* (run: Run, manifestText: string) {
192
173
  const github = yield* GitHub.GitHubApp;
193
174
  const expected = manifestArtifactName(yield* sha256(manifestText));
194
175
  const artifacts = yield* github
@@ -201,22 +182,15 @@ const requireVouched = Effect.fn("requireVouched")(function* (
201
182
  }
202
183
  });
203
184
 
204
- const publish = Effect.fn("publish")(function* (
205
- run: Run,
206
- manifestText: string,
207
- ) {
185
+ const publish = Effect.fn("publish")(function* (run: Run, manifestText: string) {
208
186
  const { policy } = yield* RegistryConfig;
209
187
  const r2 = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
210
188
  const github = yield* GitHub.GitHubApp;
211
189
  const base = yield* origin;
212
190
 
213
191
  yield* requireVouched(run, manifestText);
214
- const manifest = yield* Schema.decodeUnknownEffect(ManifestJson)(
215
- manifestText,
216
- ).pipe(
217
- Effect.mapError(
218
- (e) => new BadRequest({ message: `invalid manifest: ${String(e)}` }),
219
- ),
192
+ const manifest = yield* Schema.decodeUnknownEffect(ManifestJson)(manifestText).pipe(
193
+ Effect.mapError((e) => new BadRequest({ message: `invalid manifest: ${String(e)}` })),
220
194
  );
221
195
  // The run vouched for the manifest, but the tags come from the run's
222
196
  // head, so the packed checkout has to be that commit: a `pull_request`
@@ -230,9 +204,7 @@ const publish = Effect.fn("publish")(function* (
230
204
  const packages = manifest.packages;
231
205
  const maxSize = policy.maxPackageSize;
232
206
  const tooLarge =
233
- maxSize === undefined
234
- ? undefined
235
- : packages.find((pkg) => BigInt(pkg.size) > maxSize);
207
+ maxSize === undefined ? undefined : packages.find((pkg) => BigInt(pkg.size) > maxSize);
236
208
  if (tooLarge !== undefined) {
237
209
  return yield* new PackageTooLarge({
238
210
  message: `${tooLarge.name} exceeds ${maxSize} bytes`,
@@ -242,15 +214,9 @@ const publish = Effect.fn("publish")(function* (
242
214
  const missing = yield* Effect.filter(
243
215
  packages,
244
216
  (pkg) =>
245
- r2
246
- .head(tarballKey(pkg.name, pkg.sha256))
247
- .pipe(Effect.map((object) => object === null)),
217
+ r2.head(tarballKey(pkg.name, pkg.sha256)).pipe(Effect.map((object) => object === null)),
248
218
  { concurrency: 8 },
249
- ).pipe(
250
- Effect.map((packages) =>
251
- packages.map(({ name, sha256 }) => ({ name, sha256 })),
252
- ),
253
- );
219
+ ).pipe(Effect.map((packages) => packages.map(({ name, sha256 }) => ({ name, sha256 }))));
254
220
  if (missing.length > 0) {
255
221
  return yield* new MissingTarballs({ missing });
256
222
  }
@@ -297,9 +263,7 @@ const publish = Effect.fn("publish")(function* (
297
263
  })
298
264
  .pipe(
299
265
  Effect.catch((e) =>
300
- Effect.logWarning(
301
- `check run on ${run.repo}@${run.headSha} failed: ${GitHub.describe(e)}`,
302
- ),
266
+ Effect.logWarning(`check run on ${run.repo}@${run.headSha} failed: ${GitHub.describe(e)}`),
303
267
  ),
304
268
  );
305
269
  if (run.pr !== null) {
@@ -311,9 +275,7 @@ const publish = Effect.fn("publish")(function* (
311
275
  .upsertComment(run.repo, run.pr, COMMENT_MARKER, body)
312
276
  .pipe(
313
277
  Effect.catch((e) =>
314
- Effect.logWarning(
315
- `comment on ${run.repo}#${run.pr} failed: ${GitHub.describe(e)}`,
316
- ),
278
+ Effect.logWarning(`comment on ${run.repo}#${run.pr} failed: ${GitHub.describe(e)}`),
317
279
  ),
318
280
  );
319
281
  }
@@ -353,11 +315,7 @@ const uploadTarball = Effect.fn("uploadTarball")(function* (
353
315
  // The params schema already constrained this to 64 hex chars.
354
316
  sha256: Result.getOrThrow(Hex.decode(sha256)),
355
317
  })
356
- .pipe(
357
- Effect.mapError(
358
- (e) => new BadRequest({ message: `upload rejected: ${String(e)}` }),
359
- ),
360
- );
318
+ .pipe(Effect.mapError((e) => new BadRequest({ message: `upload rejected: ${String(e)}` })));
361
319
  return { name, sha256, size: contentLength, uploaded: true };
362
320
  });
363
321
 
@@ -380,9 +338,7 @@ export const parseInstallPath = (
380
338
  // Malformed percent-encoding is a path nothing can be published under.
381
339
  return undefined;
382
340
  }
383
- const tarball = path.match(
384
- /^(@[^/]+\/[^/@]+|[^/@]+)\/-\/([a-f0-9]{64})\.tgz$/,
385
- );
341
+ const tarball = path.match(/^(@[^/]+\/[^/@]+|[^/@]+)\/-\/([a-f0-9]{64})\.tgz$/);
386
342
  if (tarball) {
387
343
  return {
388
344
  kind: "tarball",
@@ -403,8 +359,7 @@ export const parseInstallPath = (
403
359
  const qualify = (name: string, scope: string | undefined) =>
404
360
  scope !== undefined && !name.startsWith("@") ? `${scope}/${name}` : name;
405
361
 
406
- const notFound = (message: string) =>
407
- HttpServerResponse.json({ error: message }, { status: 404 });
362
+ const notFound = (message: string) => HttpServerResponse.json({ error: message }, { status: 404 });
408
363
 
409
364
  /**
410
365
  * Install URLs: `/<name>/<tag>` redirects to the immutable tarball URL,
@@ -417,16 +372,10 @@ const install = Effect.gen(function* () {
417
372
  const r2 = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
418
373
 
419
374
  if (request.method !== "GET" && request.method !== "HEAD") {
420
- return yield* HttpServerResponse.json(
421
- { error: "method not allowed" },
422
- { status: 405 },
423
- );
375
+ return yield* HttpServerResponse.json({ error: "method not allowed" }, { status: 405 });
424
376
  }
425
377
  const host = request.headers.host ?? "localhost";
426
- const target = parseInstallPath(
427
- new URL(request.url, `https://${host}`).pathname,
428
- aliases[host],
429
- );
378
+ const target = parseInstallPath(new URL(request.url, `https://${host}`).pathname, aliases[host]);
430
379
  if (target === undefined) {
431
380
  return yield* notFound("not found");
432
381
  }
@@ -490,9 +439,7 @@ export const handler = Effect.gen(function* () {
490
439
 
491
440
  // Built once per isolate. The D1 client memoizes its connection on each
492
441
  // event's scope, so its layer is safe to share.
493
- const github = yield* GitHub.GitHubApp.pipe(
494
- Effect.provide(GitHub.GitHubAppLive),
495
- );
442
+ const github = yield* GitHub.GitHubApp.pipe(Effect.provide(GitHub.GitHubAppLive));
496
443
  const services = Layer.mergeAll(
497
444
  Cloudflare.R2.ReadWriteBucketBinding,
498
445
  Cloudflare.KV.ReadWriteNamespaceBinding,
@@ -509,23 +456,20 @@ export const handler = Effect.gen(function* () {
509
456
  (ref) => Effect.map(lookupRun(ref), (value) => ({ value, ttl: RUN_CACHE })),
510
457
  );
511
458
 
512
- yield* Cloudflare.Workers.cron(config.cron, () =>
513
- sweep.pipe(Effect.provide(services)),
514
- );
459
+ yield* Cloudflare.Workers.cron(config.cron, () => sweep.pipe(Effect.provide(services)));
515
460
 
516
461
  const registry = HttpApiBuilder.group(PkgApi, "Registry", (handlers) =>
517
462
  handlers
518
463
  .handle("publish", ({ payload }) =>
519
- Effect.flatMap(runs(payload.run), (run) =>
520
- publish(run, payload.manifest),
521
- ).pipe(Effect.catchTag(storageFailures, (e) => Effect.die(e))),
464
+ Effect.flatMap(runs(payload.run), (run) => publish(run, payload.manifest)).pipe(
465
+ Effect.catchTag(storageFailures, (e) => Effect.die(e)),
466
+ ),
522
467
  )
523
468
  // Raw so the body streams into R2 instead of being buffered.
524
469
  .handleRaw("uploadTarball", ({ query, params, request }) =>
525
- Effect.andThen(
526
- runs(query),
527
- uploadTarball(request, params.name, params.sha256),
528
- ).pipe(Effect.catchTag("R2Error", (e) => Effect.die(e))),
470
+ Effect.andThen(runs(query), uploadTarball(request, params.name, params.sha256)).pipe(
471
+ Effect.catchTag("R2Error", (e) => Effect.die(e)),
472
+ ),
529
473
  ),
530
474
  );
531
475
 
@@ -7,8 +7,7 @@ import { Cache } from "./Bindings.ts";
7
7
  /** KV's floor for both `expirationTtl` and `cacheTtl`. */
8
8
  const MIN_TTL = Duration.minutes(1);
9
9
 
10
- const seconds = (duration: Duration.Duration) =>
11
- Math.floor(Duration.toSeconds(duration));
10
+ const seconds = (duration: Duration.Duration) => Math.floor(Duration.toSeconds(duration));
12
11
 
13
12
  /**
14
13
  * A lookup-through cache on the registry's KV namespace, shared by every
@@ -28,9 +27,7 @@ const seconds = (duration: Duration.Duration) =>
28
27
  export const make = <K, A, I, E, R>(
29
28
  key: (input: K) => string,
30
29
  value: Schema.Codec<A, I>,
31
- lookup: (
32
- input: K,
33
- ) => Effect.Effect<{ value: A; ttl: Duration.Duration }, E, R>,
30
+ lookup: (input: K) => Effect.Effect<{ value: A; ttl: Duration.Duration }, E, R>,
34
31
  ) => {
35
32
  const json = Schema.fromJsonString(value);
36
33
  const decode = Schema.decodeUnknownEffect(json);
@@ -40,13 +37,9 @@ export const make = <K, A, I, E, R>(
40
37
  const kv = yield* Cloudflare.KV.ReadWriteNamespace(Cache);
41
38
  const id = key(input);
42
39
  const hit = yield* kv.get(id, { cacheTtl: seconds(MIN_TTL) }).pipe(
43
- Effect.flatMap((text) =>
44
- text === null ? Effect.succeed(undefined) : decode(text),
45
- ),
40
+ Effect.flatMap((text) => (text === null ? Effect.succeed(undefined) : decode(text))),
46
41
  Effect.catch((e) =>
47
- Effect.logWarning(`cache read of ${id} failed: ${e}`).pipe(
48
- Effect.as(undefined),
49
- ),
42
+ Effect.logWarning(`cache read of ${id} failed: ${e}`).pipe(Effect.as(undefined)),
50
43
  ),
51
44
  );
52
45
  if (hit !== undefined) {
@@ -55,12 +48,8 @@ export const make = <K, A, I, E, R>(
55
48
  const fresh = yield* lookup(input);
56
49
  if (Duration.isGreaterThanOrEqualTo(fresh.ttl, MIN_TTL)) {
57
50
  yield* encode(fresh.value).pipe(
58
- Effect.flatMap((text) =>
59
- kv.put(id, text, { expirationTtl: seconds(fresh.ttl) }),
60
- ),
61
- Effect.catch((e) =>
62
- Effect.logWarning(`cache write of ${id} failed: ${e}`),
63
- ),
51
+ Effect.flatMap((text) => kv.put(id, text, { expirationTtl: seconds(fresh.ttl) })),
52
+ Effect.catch((e) => Effect.logWarning(`cache write of ${id} failed: ${e}`)),
64
53
  );
65
54
  }
66
55
  return fresh.value;
@@ -4,13 +4,7 @@ import * as Duration from "effect/Duration";
4
4
  import * as Effect from "effect/Effect";
5
5
  import * as Option from "effect/Option";
6
6
  import * as Stream from "effect/Stream";
7
- import {
8
- Bucket,
9
- ORPHAN_GRACE,
10
- RegistryConfig,
11
- SWEEP_LOOKAHEAD,
12
- tarballKey,
13
- } from "./Bindings.ts";
7
+ import { Bucket, ORPHAN_GRACE, RegistryConfig, SWEEP_LOOKAHEAD, tarballKey } from "./Bindings.ts";
14
8
  import * as GitHub from "./GitHub.ts";
15
9
  import * as Tags from "./Tags.ts";
16
10
 
@@ -37,15 +31,13 @@ export const sweep = Effect.gen(function* () {
37
31
  const [repo, number] = ref.split("#");
38
32
  return github.getPullRequest(repo!, Number(number)).pipe(
39
33
  Effect.map((pr) =>
40
- pr.state === "open"
41
- ? undefined
42
- : Date.parse(pr.merged_at ?? pr.closed_at ?? "") || now,
34
+ pr.state === "open" ? undefined : Date.parse(pr.merged_at ?? pr.closed_at ?? "") || now,
43
35
  ),
44
36
  Effect.map((closedAt) => [ref, { closedAt }] as const),
45
37
  Effect.catch((e) =>
46
- Effect.logWarning(
47
- `pull request ${ref} lookup failed: ${GitHub.describe(e)}`,
48
- ).pipe(Effect.as([ref, undefined] as const)),
38
+ Effect.logWarning(`pull request ${ref} lookup failed: ${GitHub.describe(e)}`).pipe(
39
+ Effect.as([ref, undefined] as const),
40
+ ),
49
41
  ),
50
42
  );
51
43
  },
@@ -85,20 +77,15 @@ export const sweep = Effect.gen(function* () {
85
77
  );
86
78
 
87
79
  // Uploads that never got tagged.
88
- const orphans = yield* Stream.paginate(
89
- undefined as string | undefined,
90
- (cursor) =>
91
- r2
92
- .list({ cursor, limit: 500 })
93
- .pipe(
94
- Effect.map(
95
- (page) =>
96
- [
97
- page.objects,
98
- page.truncated ? Option.some(page.cursor) : Option.none(),
99
- ] as const,
100
- ),
80
+ const orphans = yield* Stream.paginate(undefined as string | undefined, (cursor) =>
81
+ r2
82
+ .list({ cursor, limit: 500 })
83
+ .pipe(
84
+ Effect.map(
85
+ (page) =>
86
+ [page.objects, page.truncated ? Option.some(page.cursor) : Option.none()] as const,
101
87
  ),
88
+ ),
102
89
  ).pipe(
103
90
  Stream.filter((object) => {
104
91
  const match = object.key.match(/^(.+)\/([a-f0-9]{64})\.tgz$/);
@@ -71,9 +71,7 @@ export const resolve = (pkg: string, tag: string) =>
71
71
  Effect.map(get(pkg, tag), (row) => row?.sha256);
72
72
 
73
73
  /** Rows tied to pull requests whose expiry falls before `before`. */
74
- export const dueLinked = Effect.fn("Tags.dueLinked")(function* (
75
- before: number,
76
- ) {
74
+ export const dueLinked = Effect.fn("Tags.dueLinked")(function* (before: number) {
77
75
  const sql = yield* SqlClient.SqlClient;
78
76
  return yield* decodeTags(
79
77
  yield* sql`
@@ -94,9 +92,7 @@ export const setExpiry = Effect.fn("Tags.setExpiry")(function* (
94
92
  });
95
93
 
96
94
  /** Delete every expired row and return the tarballs those rows pointed at. */
97
- export const deleteExpired = Effect.fn("Tags.deleteExpired")(function* (
98
- now: number,
99
- ) {
95
+ export const deleteExpired = Effect.fn("Tags.deleteExpired")(function* (now: number) {
100
96
  const sql = yield* SqlClient.SqlClient;
101
97
  return yield* decodeTarballs(
102
98
  yield* sql`
@@ -106,12 +102,8 @@ export const deleteExpired = Effect.fn("Tags.deleteExpired")(function* (
106
102
  });
107
103
 
108
104
  /** Every tarball some tag still points at, as `<package>/<sha256>`. */
109
- export const referencedTarballs = Effect.fn("Tags.referencedTarballs")(
110
- function* () {
111
- const sql = yield* SqlClient.SqlClient;
112
- const rows = yield* decodeTarballs(
113
- yield* sql`SELECT DISTINCT package, sha256 FROM tags`,
114
- );
115
- return new Set(rows.map((row) => `${row.package}/${row.sha256}`));
116
- },
117
- );
105
+ export const referencedTarballs = Effect.fn("Tags.referencedTarballs")(function* () {
106
+ const sql = yield* SqlClient.SqlClient;
107
+ const rows = yield* decodeTarballs(yield* sql`SELECT DISTINCT package, sha256 FROM tags`);
108
+ return new Set(rows.map((row) => `${row.package}/${row.sha256}`));
109
+ });
package/src/Registry.ts CHANGED
@@ -20,14 +20,7 @@ import {
20
20
  } from "./Registry/Bindings.ts";
21
21
  import { handler } from "./Registry/Handler.ts";
22
22
 
23
- export {
24
- Bucket,
25
- Cache,
26
- Database,
27
- Policy,
28
- RegistryConfig,
29
- Settings,
30
- } from "./Registry/Bindings.ts";
23
+ export { Bucket, Cache, Database, Policy, RegistryConfig, Settings } from "./Registry/Bindings.ts";
31
24
 
32
25
  export interface RegistryOptions {
33
26
  /**
@@ -99,9 +92,9 @@ const RegistryConfigFromEnv = Layer.effect(
99
92
  RegistryConfig,
100
93
  Effect.gen(function* () {
101
94
  const env = yield* Cloudflare.Workers.WorkerEnvironment;
102
- const settings = yield* Schema.decodeUnknownEffect(Settings)(
103
- env[SETTINGS_ENV],
104
- ).pipe(Effect.orDie);
95
+ const settings = yield* Schema.decodeUnknownEffect(Settings)(env[SETTINGS_ENV]).pipe(
96
+ Effect.orDie,
97
+ );
105
98
  return {
106
99
  ...settings,
107
100
  worker: {},
@@ -165,10 +158,7 @@ export default RegistryLive.pipe(Layer.provide(RegistryConfigFromEnv));
165
158
  * @product Workers
166
159
  * @category Workers & Compute
167
160
  */
168
- export const PkgRegistry = <const Id extends string>(
169
- id: Id,
170
- options: RegistryOptions,
171
- ) =>
161
+ export const PkgRegistry = <const Id extends string>(id: Id, options: RegistryOptions) =>
172
162
  Registry.pipe(
173
163
  Effect.provide(
174
164
  RegistryLive.pipe(