@shivaedev/pr-watch 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +2 -2
- package/dist/GitHub.d.ts.map +1 -1
- package/dist/GitHub.js +5 -19
- package/dist/GitHub.js.map +1 -1
- package/dist/GitHubHttp.d.ts +4 -5
- package/dist/GitHubHttp.d.ts.map +1 -1
- package/dist/GitHubHttp.js +26 -29
- package/dist/GitHubHttp.js.map +1 -1
- package/dist/arguments.d.ts +1 -2
- package/dist/arguments.d.ts.map +1 -1
- package/dist/arguments.js +1 -2
- package/dist/arguments.js.map +1 -1
- package/dist/cli.js +7 -6
- package/dist/cli.js.map +1 -1
- package/dist/confirmQueue.d.ts +3 -2
- package/dist/confirmQueue.d.ts.map +1 -1
- package/dist/confirmQueue.js +2 -2
- package/dist/confirmQueue.js.map +1 -1
- package/dist/model.d.ts +0 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/program.d.ts +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +2 -2
- package/dist/program.js.map +1 -1
- package/dist/quota.d.ts +3 -9
- package/dist/quota.d.ts.map +1 -1
- package/dist/quota.js +10 -30
- package/dist/quota.js.map +1 -1
- package/package.json +5 -4
- package/src/GitHub.ts +5 -19
- package/src/GitHubHttp.ts +38 -30
- package/src/arguments.ts +1 -2
- package/src/cli.ts +12 -12
- package/src/confirmQueue.ts +2 -2
- package/src/model.ts +0 -1
- package/src/program.ts +1 -2
- package/src/quota.ts +16 -37
- package/dist/httpResponse.d.ts +0 -15
- package/dist/httpResponse.d.ts.map +0 -1
- package/dist/httpResponse.js +0 -31
- package/dist/httpResponse.js.map +0 -1
- package/src/httpResponse.ts +0 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 - 2026-10-25
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Send GitHub reads, queue-confirmation writes, conditional requests and Link pagination through effect-github. Keep polling slowdown and cooldown scheduling in the watcher, using shared resource observations and rate-limit retry instants. Permission failures retain the saved snapshot without imposing a quota cooldown.
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- Provide effect-github’s `GitHub.layer` and `GitHubToken.fromEnv` when composing `GitHubHttp.layer`. Remove `Options.token` and the token arguments to `argumentsFor`, `program` and `GitHubHttp` methods; configure authentication through `GitHubToken` instead. `GitHubHttp.get` and `post` return decoded bodies, and `list` collects paginated bodies. Replace `httpResponse` helpers and `quota.observeBudget`/`Budget` with effect-github; `pollDelay` now accepts its rate-limit observations and an optional retry instant.
|
|
12
|
+
|
|
3
13
|
## 0.1.2 - 2026-10-24
|
|
4
14
|
|
|
5
15
|
### Changed
|
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ At a 60-second interval, for N open PRs, one page of checks per PR and one page
|
|
|
59
59
|
|
|
60
60
|
These are request counts, not necessarily charged quota. With authentication, a successful ETag conditional request returning 304 costs zero primary rate-limit quota. Every REST endpoint sends `If-None-Match` after receiving an ETag and reuses its successful cached body on 304. Initial requests, changed responses and endpoints without ETags consume quota normally. Caches are in memory, so a restart makes fresh requests. Secondary limits still apply.
|
|
61
61
|
|
|
62
|
-
The minimum interval doubles below 50% remaining quota, increases fivefold below 20% and tenfold below 10%. At low quota, the delay also spreads the observed round's request count over the remaining reset window.
|
|
62
|
+
The minimum interval doubles below 50% remaining quota, increases fivefold below 20% and tenfold below 10%. At low quota, the delay also spreads the observed round's request count over the remaining reset window. Rate-limited requests defer polling until effect-github’s `retryAt`, derived from `Retry-After` or the primary reset instant; exhausted primary quota waits until reset. Permission failures remain failed observations without a quota cooldown. No further request starts during a known cooldown.
|
|
63
63
|
|
|
64
64
|
### Restart and delivery limits
|
|
65
65
|
|
|
@@ -67,4 +67,4 @@ The state file records discovered open PR numbers and the last successfully comp
|
|
|
67
67
|
|
|
68
68
|
Transitions that reverse between observations, including while the watcher is down, are not recovered. For example, leaving and rejoining the merge queue before the next observation produces no `dequeued` event. Stdout delivery is at-least-once across a crash: a crash after output but before the state replacement can repeat a line. There is no event history or acknowledged output protocol.
|
|
69
69
|
|
|
70
|
-
The exported `StateFile.layer` requires Effect `FileSystem
|
|
70
|
+
The exported `StateFile.layer` requires Effect `FileSystem`. `GitHubHttp.layer` requires effect-github’s `GitHub` service, which owns authentication, conditional representations, Link pagination and rate-limit observations. Provide `GitHub.layer` with Effect `HttpClient` and `GitHubToken.fromEnv`; the token Layer reads `GH_TOKEN`, then `GITHUB_TOKEN`, and allows unauthenticated access when neither exists. Watcher `Options` contain polling and repository settings; authentication belongs to the Layer. The CLI supplies these Layers, `NodeFileSystem.layer` and a `FetchHttpClient` layer that refuses redirects. HTTP and file failures remain typed `WatchError` values.
|
package/dist/GitHub.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHub.d.ts","sourceRoot":"","sources":["../src/GitHub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAIxC,OAAO,EAAE,KAAK,OAAO,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AAkBrE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"GitHub.d.ts","sourceRoot":"","sources":["../src/GitHub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAIxC,OAAO,EAAE,KAAK,OAAO,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AAkBrE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiEjB,CAAC"}
|
package/dist/GitHub.js
CHANGED
|
@@ -27,21 +27,7 @@ export const GitHub = defineService({
|
|
|
27
27
|
return Schema.decodeUnknownEffect(schema)(body).pipe(Effect.mapError(() => new WatchError({ message: "Invalid GitHub response" })));
|
|
28
28
|
}
|
|
29
29
|
function pages(options, path) {
|
|
30
|
-
return
|
|
31
|
-
const bodies = [];
|
|
32
|
-
const visited = new Set();
|
|
33
|
-
let url = `${options.apiUrl}/repos/${options.repository}${path}`;
|
|
34
|
-
while (url !== undefined) {
|
|
35
|
-
if (visited.has(url)) {
|
|
36
|
-
return yield* Effect.fail(new WatchError({ message: "Repeated pagination URL" }));
|
|
37
|
-
}
|
|
38
|
-
visited.add(url);
|
|
39
|
-
const response = yield* http.get(url, options.token);
|
|
40
|
-
bodies.push(response.body);
|
|
41
|
-
url = response.next;
|
|
42
|
-
}
|
|
43
|
-
return bodies;
|
|
44
|
-
});
|
|
30
|
+
return http.list(`${options.apiUrl}/repos/${options.repository}${path}`);
|
|
45
31
|
}
|
|
46
32
|
return {
|
|
47
33
|
confirmQueue: (options, snapshot) => confirmQueue(http, options, snapshot),
|
|
@@ -60,8 +46,8 @@ export const GitHub = defineService({
|
|
|
60
46
|
}));
|
|
61
47
|
}),
|
|
62
48
|
snapshot: (options, number, queued) => Effect.gen(function* () {
|
|
63
|
-
const response = yield* http.get(`${options.apiUrl}/repos/${options.repository}/pulls/${number}
|
|
64
|
-
const pr = yield* decode(Pull, response
|
|
49
|
+
const response = yield* http.get(`${options.apiUrl}/repos/${options.repository}/pulls/${number}`);
|
|
50
|
+
const pr = yield* decode(Pull, response);
|
|
65
51
|
if (pr.number !== number) {
|
|
66
52
|
return yield* Effect.fail(new WatchError({ message: "GitHub returned the wrong PR" }));
|
|
67
53
|
}
|
|
@@ -72,8 +58,8 @@ export const GitHub = defineService({
|
|
|
72
58
|
if (pr.state === "open") {
|
|
73
59
|
const bodies = yield* pages(options, `/commits/${pr.head.sha}/check-runs?per_page=100&filter=latest`);
|
|
74
60
|
const checks = yield* Effect.forEach(bodies, (body) => decode(Checks, body));
|
|
75
|
-
const statusResponse = yield* http.get(`${options.apiUrl}/repos/${options.repository}/commits/${pr.head.sha}/status
|
|
76
|
-
const status = yield* decode(Status, statusResponse
|
|
61
|
+
const statusResponse = yield* http.get(`${options.apiUrl}/repos/${options.repository}/commits/${pr.head.sha}/status`);
|
|
62
|
+
const status = yield* decode(Status, statusResponse);
|
|
77
63
|
failed =
|
|
78
64
|
checks.some((page) => page.checkRuns.some((check) => check.status === "completed" && failures.has(check.conclusion ?? "")))
|
|
79
65
|
|| ["failure", "error"].includes(status.state);
|
package/dist/GitHub.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHub.js","sourceRoot":"","sources":["../src/GitHub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,4CAA4C,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAA+B,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;IAChI,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAC/D,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;IACxC,cAAc,EAAE,MAAM,CAAC,MAAM;IAC7B,MAAM,EAAE,MAAM,CAAC,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC,GAAG;IAClB,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC,MAAM;CACpB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;AAC3F,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAClD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;AACzD,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AACjG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;AACtH,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AACvD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,OAAO,CAAC,CAAC,CAAC;AAC/G,MAAM,CAAC,MAAM,MAAM,GAAG,aAAa,CAAC;IACnC,EAAE,EAAE,4BAA4B;IAChC,UAAU,EAAE,UAAU;IACtB,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;QACjB,SAAS,MAAM,CAA6B,MAAe,EAAE,IAAa;YACzE,OAAO,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC,CAAC,CAAC;QACrI,CAAC;QACD,SAAS,KAAK,CAAC,OAAgB,EAAE,IAAY;YAC5C,OAAO,
|
|
1
|
+
{"version":3,"file":"GitHub.js","sourceRoot":"","sources":["../src/GitHub.ts"],"names":[],"mappings":"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"}
|
package/dist/GitHubHttp.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import { HttpClient } from "effect/http";
|
|
3
|
-
import { type CachedResponse } from "./httpResponse.ts";
|
|
4
2
|
import { WatchError } from "./model.ts";
|
|
5
3
|
export declare const GitHubHttp: import("effect/Context").Service<"@shivaedev/pr-watch/GitHubHttp", Readonly<{
|
|
6
4
|
readonly delay: (interval: number) => Effect.Effect<number, never, never>;
|
|
7
|
-
readonly get: (url: string
|
|
8
|
-
readonly
|
|
5
|
+
readonly get: (url: string) => Effect.Effect<unknown, WatchError, never>;
|
|
6
|
+
readonly list: (url: string) => Effect.Effect<unknown[], WatchError, never>;
|
|
7
|
+
readonly post: (url: string, body: unknown) => Effect.Effect<unknown, WatchError, never>;
|
|
9
8
|
}>> & {
|
|
10
|
-
readonly layer: import("effect/Layer").Layer<"@shivaedev/pr-watch/GitHubHttp", never,
|
|
9
|
+
readonly layer: import("effect/Layer").Layer<"@shivaedev/pr-watch/GitHubHttp", never, "@shivaedev/effect-github/GitHub">;
|
|
11
10
|
};
|
|
12
11
|
//# sourceMappingURL=GitHubHttp.d.ts.map
|
package/dist/GitHubHttp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHubHttp.d.ts","sourceRoot":"","sources":["../src/GitHubHttp.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"GitHubHttp.d.ts","sourceRoot":"","sources":["../src/GitHubHttp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,MAAM,EAAkB,MAAM,QAAQ,CAAC;AAIjE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGxC,eAAO,MAAM,UAAU;;;;;;;CAuDrB,CAAC"}
|
package/dist/GitHubHttp.js
CHANGED
|
@@ -1,51 +1,48 @@
|
|
|
1
|
-
import { Clock, Effect } from "effect";
|
|
2
|
-
import {
|
|
1
|
+
import { Clock, DateTime, Effect, Schema, Stream } from "effect";
|
|
2
|
+
import { GitHubRateLimited } from "@shivaedev/effect-github/errors.ts";
|
|
3
|
+
import { GitHub } from "@shivaedev/effect-github/GitHub.ts";
|
|
3
4
|
import { defineService } from "@shivaedev/effect-service/defineService.ts";
|
|
4
|
-
import { decodeResponse, fetchResponse } from "./httpResponse.js";
|
|
5
5
|
import { WatchError } from "./model.js";
|
|
6
|
-
import {
|
|
6
|
+
import { pollDelay, quotaRetryAt } from "./quota.js";
|
|
7
7
|
export const GitHubHttp = defineService({
|
|
8
8
|
id: "@shivaedev/pr-watch/GitHubHttp",
|
|
9
9
|
initialize: Effect.gen(function* () {
|
|
10
|
-
return {
|
|
11
|
-
budget: { blockedUntil: 0, limit: 5000, remaining: 5000, reset: 0 },
|
|
12
|
-
cache: new Map(),
|
|
13
|
-
client: yield* HttpClient.HttpClient,
|
|
14
|
-
requests: 0,
|
|
15
|
-
};
|
|
10
|
+
return { github: yield* GitHub, requests: 0, retryAt: 0 };
|
|
16
11
|
}),
|
|
17
12
|
methods: (state) => {
|
|
18
|
-
function request(
|
|
13
|
+
function request(operation) {
|
|
19
14
|
return Effect.gen(function* () {
|
|
20
15
|
const now = yield* Clock.currentTimeMillis;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const cached = body === undefined ? state.cache.get(url) : undefined;
|
|
25
|
-
state.requests += 1;
|
|
26
|
-
const response = yield* fetchResponse(state.client, url, token, cached, body);
|
|
27
|
-
observeBudget(state.budget, response, yield* Clock.currentTimeMillis);
|
|
28
|
-
if (response.status === 304 && cached !== undefined) {
|
|
29
|
-
return cached;
|
|
16
|
+
const observations = yield* state.github.observations();
|
|
17
|
+
if (quotaRetryAt(observations, state.retryAt) > now) {
|
|
18
|
+
return yield* new WatchError({ message: "GitHub quota cooldown" });
|
|
30
19
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
return yield* operation;
|
|
21
|
+
}).pipe(Effect.scoped, Effect.timeout("30 seconds"), Effect.tapError((cause) => Effect.sync(() => {
|
|
22
|
+
if (cause instanceof GitHubRateLimited) {
|
|
23
|
+
state.retryAt = DateTime.toEpochMillis(cause.retryAt);
|
|
34
24
|
}
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
})), Effect.mapError(() => new WatchError({ message: "GitHub request failed" })));
|
|
26
|
+
}
|
|
27
|
+
function counted(operation) {
|
|
28
|
+
return Effect.suspend(() => {
|
|
29
|
+
state.requests += 1;
|
|
30
|
+
return operation;
|
|
31
|
+
});
|
|
37
32
|
}
|
|
38
33
|
return {
|
|
39
34
|
delay: (interval) => Effect.gen(function* () {
|
|
40
35
|
const now = yield* Clock.currentTimeMillis;
|
|
41
|
-
const
|
|
36
|
+
const observations = yield* state.github.observations();
|
|
37
|
+
const delay = pollDelay(observations, interval, Math.max(1, state.requests), now, state.retryAt);
|
|
42
38
|
state.requests = 0;
|
|
43
39
|
return delay;
|
|
44
40
|
}),
|
|
45
|
-
get: (url
|
|
46
|
-
|
|
41
|
+
get: (url) => request(counted(state.github.read(url)).pipe(Effect.flatMap((response) => response.decode(Schema.Unknown)))),
|
|
42
|
+
list: (url) => request(Stream.unwrap(state.github.list(url)).pipe(Stream.mapEffect((response) => counted(response.decode(Schema.Unknown))), Stream.runCollect)),
|
|
43
|
+
post: (url, body) => request(counted(state.github.write("POST", url, body)).pipe(Effect.flatMap((response) => response.decode(Schema.Unknown)))),
|
|
47
44
|
};
|
|
48
45
|
},
|
|
49
|
-
requires: [
|
|
46
|
+
requires: [GitHub],
|
|
50
47
|
});
|
|
51
48
|
//# sourceMappingURL=GitHubHttp.js.map
|
package/dist/GitHubHttp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHubHttp.js","sourceRoot":"","sources":["../src/GitHubHttp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"GitHubHttp.js","sourceRoot":"","sources":["../src/GitHubHttp.ts"],"names":[],"mappings":"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"}
|
package/dist/arguments.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { WatchError } from "./model.ts";
|
|
3
3
|
export declare const help = "pr-watch --repo OWNER/REPO [--fleet | PR_NUMBER ...]\n --state PATH Snapshot file (default .pr-watch/OWNER-REPO.json)\n --interval SECONDS Poll interval (default 60; slows as quota falls)\n --once Poll once and exit\n --api-url URL GitHub REST base URL (default https://api.github.com)\n --help Show this help\nAuthentication: GH_TOKEN or GITHUB_TOKEN";
|
|
4
|
-
export declare function argumentsFor(args: readonly string[]
|
|
4
|
+
export declare function argumentsFor(args: readonly string[]): Effect.Effect<{
|
|
5
5
|
help: true;
|
|
6
6
|
once?: never;
|
|
7
7
|
options?: never;
|
|
@@ -15,7 +15,6 @@ export declare function argumentsFor(args: readonly string[], token: string): Ef
|
|
|
15
15
|
numbers: readonly number[];
|
|
16
16
|
repository: string;
|
|
17
17
|
stateFile: string;
|
|
18
|
-
token: string;
|
|
19
18
|
};
|
|
20
19
|
}, WatchError, never>;
|
|
21
20
|
//# sourceMappingURL=arguments.d.ts.map
|
package/dist/arguments.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arguments.d.ts","sourceRoot":"","sources":["../src/arguments.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAC;AACtD,eAAO,MAAM,IAAI,4YAMwB,CAAC;AAC1C,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"arguments.d.ts","sourceRoot":"","sources":["../src/arguments.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAC;AACtD,eAAO,MAAM,IAAI,4YAMwB,CAAC;AAC1C,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE;;;;;;;;;;;;;;;sBAyCnD"}
|
package/dist/arguments.js
CHANGED
|
@@ -8,7 +8,7 @@ export const help = `pr-watch --repo OWNER/REPO [--fleet | PR_NUMBER ...]
|
|
|
8
8
|
--api-url URL GitHub REST base URL (default https://api.github.com)
|
|
9
9
|
--help Show this help
|
|
10
10
|
Authentication: GH_TOKEN or GITHUB_TOKEN`;
|
|
11
|
-
export function argumentsFor(args
|
|
11
|
+
export function argumentsFor(args) {
|
|
12
12
|
return Effect.try({
|
|
13
13
|
catch: (cause) => new WatchError({ message: cause instanceof Error ? cause.message : "Invalid arguments" }),
|
|
14
14
|
try: () => {
|
|
@@ -45,7 +45,6 @@ export function argumentsFor(args, token) {
|
|
|
45
45
|
numbers,
|
|
46
46
|
repository,
|
|
47
47
|
stateFile: values.state ?? `.pr-watch/${repository.replace("/", "-")}.json`,
|
|
48
|
-
token,
|
|
49
48
|
},
|
|
50
49
|
};
|
|
51
50
|
},
|
package/dist/arguments.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arguments.js","sourceRoot":"","sources":["../src/arguments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAC;AACtD,MAAM,CAAC,MAAM,IAAI,GAAG;;;;;;yCAMqB,CAAC;AAC1C,MAAM,UAAU,YAAY,CAAC,IAAuB
|
|
1
|
+
{"version":3,"file":"arguments.js","sourceRoot":"","sources":["../src/arguments.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { NodeFileSystem } from "@effect/platform-node";
|
|
4
|
-
import {
|
|
4
|
+
import { Effect, Layer } from "effect";
|
|
5
5
|
import { FetchHttpClient } from "effect/http";
|
|
6
|
+
import { GitHub as GitHubClient } from "@shivaedev/effect-github/GitHub.ts";
|
|
7
|
+
import { GitHubToken } from "@shivaedev/effect-github/GitHubToken.ts";
|
|
6
8
|
import { GitHub } from "./GitHub.js";
|
|
7
9
|
import { GitHubHttp } from "./GitHubHttp.js";
|
|
8
10
|
import { WatchError } from "./model.js";
|
|
9
11
|
import { program } from "./program.js";
|
|
10
12
|
import { StateFile } from "./StateFile.js";
|
|
11
13
|
import { Watcher } from "./Watcher.js";
|
|
12
|
-
const services = Watcher.layer.pipe(Layer.provide(Layer.mergeAll(StateFile.layer, GitHub.layer.pipe(Layer.provide(GitHubHttp.layer)))), Layer.provide(Layer.mergeAll(NodeFileSystem.layer, FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.RequestInit)({ redirect: "error" }))))));
|
|
14
|
+
const services = Watcher.layer.pipe(Layer.provide(Layer.mergeAll(StateFile.layer, GitHub.layer.pipe(Layer.provide(GitHubHttp.layer.pipe(Layer.provide(GitHubClient.layer), Layer.provide(GitHubToken.fromEnv)))))), Layer.provide(Layer.mergeAll(NodeFileSystem.layer, FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.RequestInit)({ redirect: "error" }))))));
|
|
13
15
|
function write(stream) {
|
|
14
16
|
return (line) => Effect.tryPromise({
|
|
15
17
|
catch: () => new WatchError({ message: "Output stream failed" }),
|
|
@@ -20,10 +22,9 @@ const abort = new AbortController();
|
|
|
20
22
|
process.once("SIGINT", () => abort.abort());
|
|
21
23
|
process.once("SIGTERM", () => abort.abort());
|
|
22
24
|
try {
|
|
23
|
-
await Effect.runPromise(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}).pipe(Effect.provide(services)), { signal: abort.signal });
|
|
25
|
+
await Effect.runPromise(program(process.argv.slice(2), write(process.stdout), write(process.stderr)).pipe(Effect.provide(services)), {
|
|
26
|
+
signal: abort.signal,
|
|
27
|
+
});
|
|
27
28
|
}
|
|
28
29
|
catch (cause) {
|
|
29
30
|
if (!abort.signal.aborted) {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/confirmQueue.d.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { Effect } from "effect";
|
|
|
2
2
|
import { type Options, WatchError } from "./model.ts";
|
|
3
3
|
export declare const confirmQueue: (http: Readonly<{
|
|
4
4
|
readonly delay: (interval: number) => Effect.Effect<number, never, never>;
|
|
5
|
-
readonly get: (url: string
|
|
6
|
-
readonly
|
|
5
|
+
readonly get: (url: string) => Effect.Effect<unknown, WatchError, never>;
|
|
6
|
+
readonly list: (url: string) => Effect.Effect<unknown[], WatchError, never>;
|
|
7
|
+
readonly post: (url: string, body: unknown) => Effect.Effect<unknown, WatchError, never>;
|
|
7
8
|
}>, options: Options, snapshot: {
|
|
8
9
|
readonly autoMerge: boolean;
|
|
9
10
|
readonly branch: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirmQueue.d.ts","sourceRoot":"","sources":["../src/confirmQueue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAU,MAAM,QAAQ,CAAC;AAEtD,OAAO,EAAE,KAAK,OAAO,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AA6BrE,eAAO,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"confirmQueue.d.ts","sourceRoot":"","sources":["../src/confirmQueue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAU,MAAM,QAAQ,CAAC;AAEtD,OAAO,EAAE,KAAK,OAAO,EAAiB,UAAU,EAAE,MAAM,YAAY,CAAC;AA6BrE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;qBAoBvB,CAAC"}
|
package/dist/confirmQueue.js
CHANGED
|
@@ -28,8 +28,8 @@ export const confirmQueue = Effect.fn("GitHub.confirmQueue")(function* (http, op
|
|
|
28
28
|
url.pathname = `${url.pathname.replace(/\/$/u, "")}/graphql`;
|
|
29
29
|
}
|
|
30
30
|
const [owner, name] = options.repository.split("/");
|
|
31
|
-
const response = yield* http.post(url.href,
|
|
32
|
-
const body = yield* Schema.decodeUnknownEffect(QueueResponse)(response
|
|
31
|
+
const response = yield* http.post(url.href, { query, variables: { name, number: snapshot.number, owner } });
|
|
32
|
+
const body = yield* Schema.decodeUnknownEffect(QueueResponse)(response).pipe(Effect.mapError(() => new WatchError({ message: "Invalid GitHub queue confirmation" })));
|
|
33
33
|
const pr = body.data?.repository?.pullRequest;
|
|
34
34
|
if ((body.errors?.length ?? 0) > 0 || pr === undefined || pr === null || pr.state !== "OPEN") {
|
|
35
35
|
return yield* Effect.fail(new WatchError({ message: `Queue confirmation unavailable for PR #${snapshot.number}` }));
|
package/dist/confirmQueue.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"confirmQueue.js","sourceRoot":"","sources":["../src/confirmQueue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEtD,OAAO,EAA+B,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC,MAAM,CAClB,MAAM,CAAC,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC,MAAM,CACxB,MAAM,CAAC,MAAM,CAAC;YACb,WAAW,EAAE,MAAM,CAAC,MAAM,CACzB,MAAM,CAAC,MAAM,CAAC;gBACb,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC5E,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBACpE,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;aACpD,CAAC,CACF;SACD,CAAC,CACF;KACD,CAAC,CACF;IACD,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;CACxD,CAAC,CAAC;AACH,MAAM,KAAK,GAAG;;;;;;;;EAQZ,CAAC;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,EAAE,CAAC,qBAAqB,CAAC,CAAC,QAAQ,CAAC,EACrE,IAA8C,EAC9C,OAAgB,EAChB,QAAkB;IAElB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpC,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;IACtE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,QAAQ,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC;IAC9D,CAAC;IACD,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"confirmQueue.js","sourceRoot":"","sources":["../src/confirmQueue.ts"],"names":[],"mappings":"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"}
|
package/dist/model.d.ts
CHANGED
package/dist/model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;AAChC,qBAAa,UAAW,SAAQ,eAE9B;CAAG;AACL,eAAO,MAAM,QAAQ;;;;;;;;;;EAUnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAC5C,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;EAKhB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC;AACtC,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;;;;AAChC,qBAAa,UAAW,SAAQ,eAE9B;CAAG;AACL,eAAO,MAAM,QAAQ;;;;;;;;;;EAUnB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAC5C,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;EAKhB,CAAC;AACH,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC;AACtC,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B"}
|
package/dist/program.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import type { WatchError } from "./model.ts";
|
|
3
|
-
export declare const program: (args: readonly string[],
|
|
3
|
+
export declare const program: (args: readonly string[], emit: (line: string) => Effect.Effect<void, WatchError>, diagnostic: (message: string) => Effect.Effect<void, WatchError>) => Effect.Effect<undefined, WatchError, "@shivaedev/pr-watch/Watcher">;
|
|
4
4
|
//# sourceMappingURL=program.d.ts.map
|
package/dist/program.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,OAAO,yCAEN,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,wBACjC,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,wEAyB/D,CAAC"}
|
package/dist/program.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { argumentsFor, help } from "./arguments.js";
|
|
3
3
|
import { Watcher } from "./Watcher.js";
|
|
4
|
-
export const program = Effect.fn("PrWatch.program")(function* (args,
|
|
5
|
-
const parsed = yield* argumentsFor(args
|
|
4
|
+
export const program = Effect.fn("PrWatch.program")(function* (args, emit, diagnostic) {
|
|
5
|
+
const parsed = yield* argumentsFor(args);
|
|
6
6
|
if (parsed.help) {
|
|
7
7
|
yield* emit(help);
|
|
8
8
|
return;
|
package/dist/program.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.js","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,EAC5D,IAAuB,EACvB,
|
|
1
|
+
{"version":3,"file":"program.js","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,EAC5D,IAAuB,EACvB,IAAuD,EACvD,UAAgE;IAEhE,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO;IACR,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC;IAC/B,IAAI,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChD,OAAO,IAAI,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;QAC3F,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC;QACxB,CAAC;aAAM,CAAC;YACP,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBACjB,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3C,CAAC;YACD,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC1C,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,OAAO;QACR,CAAC;QACD,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpE,CAAC;AACF,CAAC,CAAC,CAAC"}
|
package/dist/quota.d.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
limit: number;
|
|
5
|
-
remaining: number;
|
|
6
|
-
reset: number;
|
|
7
|
-
}
|
|
8
|
-
export declare function observeBudget(budget: Budget, response: Pick<HttpClientResponse.HttpClientResponse, "headers" | "status">, now: number): void;
|
|
9
|
-
export declare function pollDelay(budget: Budget, interval: number, requests: number, now: number): number;
|
|
1
|
+
import type { RateLimitObservation } from "@shivaedev/effect-github/rateLimits.ts";
|
|
2
|
+
export declare function quotaRetryAt(observations: readonly RateLimitObservation[], retryAt?: number): number;
|
|
3
|
+
export declare function pollDelay(observations: readonly RateLimitObservation[], interval: number, requests: number, now: number, retryAt?: number): number;
|
|
10
4
|
//# sourceMappingURL=quota.d.ts.map
|
package/dist/quota.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quota.d.ts","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"quota.d.ts","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAEnF,wBAAgB,YAAY,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAAE,OAAO,SAAI,GAAG,MAAM,CAE/F;AAED,wBAAgB,SAAS,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,SAAI,GAAG,MAAM,CAW7I"}
|
package/dist/quota.js
CHANGED
|
@@ -1,34 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return
|
|
1
|
+
import { DateTime } from "effect";
|
|
2
|
+
export function quotaRetryAt(observations, retryAt = 0) {
|
|
3
|
+
return Math.max(retryAt, ...observations.filter((budget) => budget.remaining === 0).map((budget) => DateTime.toEpochMillis(budget.reset)));
|
|
4
4
|
}
|
|
5
|
-
export function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
budget.blockedUntil = Math.max(budget.blockedUntil, Number.isFinite(until) ? until : now + 60_000);
|
|
13
|
-
}
|
|
14
|
-
if (budget.remaining === 0) {
|
|
15
|
-
budget.blockedUntil = Math.max(budget.blockedUntil, budget.reset);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export function pollDelay(budget, interval, requests, now) {
|
|
19
|
-
const ratio = budget.remaining / Math.max(1, budget.limit);
|
|
20
|
-
const multiplier = slowdown(ratio);
|
|
21
|
-
const paced = budget.remaining > 0 && ratio < 0.5 ? Math.ceil((Math.max(0, budget.reset - now) * requests) / budget.remaining) : 0;
|
|
22
|
-
return Math.max(interval * multiplier, paced, budget.blockedUntil - now);
|
|
23
|
-
}
|
|
24
|
-
function retryUntil(retry, now) {
|
|
25
|
-
if (retry === null) {
|
|
26
|
-
return now + 60_000;
|
|
27
|
-
}
|
|
28
|
-
if (Number.isFinite(Number(retry))) {
|
|
29
|
-
return now + Number(retry) * 1000;
|
|
30
|
-
}
|
|
31
|
-
return Date.parse(retry);
|
|
5
|
+
export function pollDelay(observations, interval, requests, now, retryAt = 0) {
|
|
6
|
+
return Math.max(interval, quotaRetryAt(observations, retryAt) - now, ...observations.map((budget) => {
|
|
7
|
+
const ratio = budget.remaining / Math.max(1, budget.limit);
|
|
8
|
+
const reset = DateTime.toEpochMillis(budget.reset);
|
|
9
|
+
const paced = budget.remaining > 0 && ratio < 0.5 ? Math.ceil((Math.max(0, reset - now) * requests) / budget.remaining) : 0;
|
|
10
|
+
return Math.max(interval * slowdown(ratio), paced);
|
|
11
|
+
}));
|
|
32
12
|
}
|
|
33
13
|
function slowdown(ratio) {
|
|
34
14
|
if (ratio < 0.1) {
|
package/dist/quota.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quota.js","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"quota.js","sourceRoot":"","sources":["../src/quota.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAGlC,MAAM,UAAU,YAAY,CAAC,YAA6C,EAAE,OAAO,GAAG,CAAC;IACtF,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC5I,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,YAA6C,EAAE,QAAgB,EAAE,QAAgB,EAAE,GAAW,EAAE,OAAO,GAAG,CAAC;IACpI,OAAO,IAAI,CAAC,GAAG,CACd,QAAQ,EACR,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG,GAAG,EACzC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;QAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,GAAG,CAAC,IAAI,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5H,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IACpD,CAAC,CAAC,CACF,CAAC;AACH,CAAC;AACD,SAAS,QAAQ,CAAC,KAAa;IAC9B,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QACjB,OAAO,EAAE,CAAC;IACX,CAAC;IACD,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QACjB,OAAO,CAAC,CAAC;IACV,CAAC;IACD,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;QACjB,OAAO,CAAC,CAAC;IACV,CAAC;IACD,OAAO,CAAC,CAAC;AACV,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/pr-watch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "One REST watcher for pull request transitions across a repository",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/pr-watch#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -43,15 +43,16 @@
|
|
|
43
43
|
"README.md"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"@shivaedev/effect-github": "0.2.0",
|
|
46
47
|
"@shivaedev/effect-service": "0.4.1"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"@effect/platform-node": "4.0.5",
|
|
50
51
|
"@effect/platform-node-shared": "4.0.5",
|
|
51
52
|
"@effect/vitest": "4.0.5",
|
|
52
|
-
"@shivaedev/effect-test": "0.
|
|
53
|
-
"@shivaedev/quality": "0.
|
|
54
|
-
"@shivaedev/test-story": "0.
|
|
53
|
+
"@shivaedev/effect-test": "0.8.1",
|
|
54
|
+
"@shivaedev/quality": "0.38.0",
|
|
55
|
+
"@shivaedev/test-story": "0.19.0",
|
|
55
56
|
"@types/node": "24.19.2",
|
|
56
57
|
"effect": "4.0.5",
|
|
57
58
|
"typescript": "7.0.2",
|
package/src/GitHub.ts
CHANGED
|
@@ -28,21 +28,7 @@ export const GitHub = defineService({
|
|
|
28
28
|
return Schema.decodeUnknownEffect(schema)(body).pipe(Effect.mapError(() => new WatchError({ message: "Invalid GitHub response" })));
|
|
29
29
|
}
|
|
30
30
|
function pages(options: Options, path: string) {
|
|
31
|
-
return
|
|
32
|
-
const bodies: unknown[] = [];
|
|
33
|
-
const visited = new Set<string>();
|
|
34
|
-
let url: string | undefined = `${options.apiUrl}/repos/${options.repository}${path}`;
|
|
35
|
-
while (url !== undefined) {
|
|
36
|
-
if (visited.has(url)) {
|
|
37
|
-
return yield* Effect.fail(new WatchError({ message: "Repeated pagination URL" }));
|
|
38
|
-
}
|
|
39
|
-
visited.add(url);
|
|
40
|
-
const response: Effect.Success<ReturnType<typeof http.get>> = yield* http.get(url, options.token);
|
|
41
|
-
bodies.push(response.body);
|
|
42
|
-
url = response.next;
|
|
43
|
-
}
|
|
44
|
-
return bodies;
|
|
45
|
-
});
|
|
31
|
+
return http.list(`${options.apiUrl}/repos/${options.repository}${path}`);
|
|
46
32
|
}
|
|
47
33
|
return {
|
|
48
34
|
confirmQueue: (options: Options, snapshot: Snapshot) => confirmQueue(http, options, snapshot),
|
|
@@ -66,8 +52,8 @@ export const GitHub = defineService({
|
|
|
66
52
|
}),
|
|
67
53
|
snapshot: (options: Options, number: number, queued: ReadonlySet<number>) =>
|
|
68
54
|
Effect.gen(function* () {
|
|
69
|
-
const response = yield* http.get(`${options.apiUrl}/repos/${options.repository}/pulls/${number}
|
|
70
|
-
const pr = yield* decode(Pull, response
|
|
55
|
+
const response = yield* http.get(`${options.apiUrl}/repos/${options.repository}/pulls/${number}`);
|
|
56
|
+
const pr = yield* decode(Pull, response);
|
|
71
57
|
if (pr.number !== number) {
|
|
72
58
|
return yield* Effect.fail(new WatchError({ message: "GitHub returned the wrong PR" }));
|
|
73
59
|
}
|
|
@@ -78,8 +64,8 @@ export const GitHub = defineService({
|
|
|
78
64
|
if (pr.state === "open") {
|
|
79
65
|
const bodies = yield* pages(options, `/commits/${pr.head.sha}/check-runs?per_page=100&filter=latest`);
|
|
80
66
|
const checks = yield* Effect.forEach(bodies, (body) => decode(Checks, body));
|
|
81
|
-
const statusResponse = yield* http.get(`${options.apiUrl}/repos/${options.repository}/commits/${pr.head.sha}/status
|
|
82
|
-
const status = yield* decode(Status, statusResponse
|
|
67
|
+
const statusResponse = yield* http.get(`${options.apiUrl}/repos/${options.repository}/commits/${pr.head.sha}/status`);
|
|
68
|
+
const status = yield* decode(Status, statusResponse);
|
|
83
69
|
failed =
|
|
84
70
|
checks.some((page) => page.checkRuns.some((check) => check.status === "completed" && failures.has(check.conclusion ?? "")))
|
|
85
71
|
|| ["failure", "error"].includes(status.state);
|
package/src/GitHubHttp.ts
CHANGED
|
@@ -1,55 +1,63 @@
|
|
|
1
|
-
import { Clock, Effect } from "effect";
|
|
2
|
-
import {
|
|
1
|
+
import { Clock, DateTime, Effect, Schema, Stream } from "effect";
|
|
2
|
+
import { GitHubRateLimited } from "@shivaedev/effect-github/errors.ts";
|
|
3
|
+
import { GitHub } from "@shivaedev/effect-github/GitHub.ts";
|
|
3
4
|
import { defineService } from "@shivaedev/effect-service/defineService.ts";
|
|
4
|
-
import { type CachedResponse, decodeResponse, fetchResponse } from "./httpResponse.ts";
|
|
5
5
|
import { WatchError } from "./model.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { pollDelay, quotaRetryAt } from "./quota.ts";
|
|
7
|
+
|
|
7
8
|
export const GitHubHttp = defineService({
|
|
8
9
|
id: "@shivaedev/pr-watch/GitHubHttp",
|
|
9
10
|
initialize: Effect.gen(function* () {
|
|
10
|
-
return {
|
|
11
|
-
budget: { blockedUntil: 0, limit: 5000, remaining: 5000, reset: 0 } satisfies Budget,
|
|
12
|
-
cache: new Map<string, CachedResponse>(),
|
|
13
|
-
client: yield* HttpClient.HttpClient,
|
|
14
|
-
requests: 0,
|
|
15
|
-
};
|
|
11
|
+
return { github: yield* GitHub, requests: 0, retryAt: 0 };
|
|
16
12
|
}),
|
|
17
13
|
methods: (state) => {
|
|
18
|
-
function request
|
|
14
|
+
function request<T, E, R>(operation: Effect.Effect<T, E, R>) {
|
|
19
15
|
return Effect.gen(function* () {
|
|
20
16
|
const now = yield* Clock.currentTimeMillis;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const cached = body === undefined ? state.cache.get(url) : undefined;
|
|
25
|
-
state.requests += 1;
|
|
26
|
-
const response = yield* fetchResponse(state.client, url, token, cached, body);
|
|
27
|
-
observeBudget(state.budget, response, yield* Clock.currentTimeMillis);
|
|
28
|
-
if (response.status === 304 && cached !== undefined) {
|
|
29
|
-
return cached;
|
|
17
|
+
const observations = yield* state.github.observations();
|
|
18
|
+
if (quotaRetryAt(observations, state.retryAt) > now) {
|
|
19
|
+
return yield* new WatchError({ message: "GitHub quota cooldown" });
|
|
30
20
|
}
|
|
31
|
-
|
|
32
|
-
if (body === undefined) {
|
|
33
|
-
state.cache.set(url, result);
|
|
34
|
-
}
|
|
35
|
-
return result;
|
|
21
|
+
return yield* operation;
|
|
36
22
|
}).pipe(
|
|
37
23
|
Effect.scoped,
|
|
38
24
|
Effect.timeout("30 seconds"),
|
|
39
|
-
Effect.
|
|
25
|
+
Effect.tapError((cause) =>
|
|
26
|
+
Effect.sync(() => {
|
|
27
|
+
if (cause instanceof GitHubRateLimited) {
|
|
28
|
+
state.retryAt = DateTime.toEpochMillis(cause.retryAt);
|
|
29
|
+
}
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
32
|
+
Effect.mapError(() => new WatchError({ message: "GitHub request failed" })),
|
|
40
33
|
);
|
|
41
34
|
}
|
|
35
|
+
function counted<T, E, R>(operation: Effect.Effect<T, E, R>) {
|
|
36
|
+
return Effect.suspend(() => {
|
|
37
|
+
state.requests += 1;
|
|
38
|
+
return operation;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
42
41
|
return {
|
|
43
42
|
delay: (interval: number) =>
|
|
44
43
|
Effect.gen(function* () {
|
|
45
44
|
const now = yield* Clock.currentTimeMillis;
|
|
46
|
-
const
|
|
45
|
+
const observations = yield* state.github.observations();
|
|
46
|
+
const delay = pollDelay(observations, interval, Math.max(1, state.requests), now, state.retryAt);
|
|
47
47
|
state.requests = 0;
|
|
48
48
|
return delay;
|
|
49
49
|
}),
|
|
50
|
-
get: (url: string
|
|
51
|
-
|
|
50
|
+
get: (url: string) => request(counted(state.github.read(url)).pipe(Effect.flatMap((response) => response.decode(Schema.Unknown)))),
|
|
51
|
+
list: (url: string) =>
|
|
52
|
+
request(
|
|
53
|
+
Stream.unwrap(state.github.list(url)).pipe(
|
|
54
|
+
Stream.mapEffect((response) => counted(response.decode(Schema.Unknown))),
|
|
55
|
+
Stream.runCollect,
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
post: (url: string, body: unknown) =>
|
|
59
|
+
request(counted(state.github.write("POST", url, body)).pipe(Effect.flatMap((response) => response.decode(Schema.Unknown)))),
|
|
52
60
|
};
|
|
53
61
|
},
|
|
54
|
-
requires: [
|
|
62
|
+
requires: [GitHub],
|
|
55
63
|
});
|
package/src/arguments.ts
CHANGED
|
@@ -8,7 +8,7 @@ export const help = `pr-watch --repo OWNER/REPO [--fleet | PR_NUMBER ...]
|
|
|
8
8
|
--api-url URL GitHub REST base URL (default https://api.github.com)
|
|
9
9
|
--help Show this help
|
|
10
10
|
Authentication: GH_TOKEN or GITHUB_TOKEN`;
|
|
11
|
-
export function argumentsFor(args: readonly string[]
|
|
11
|
+
export function argumentsFor(args: readonly string[]) {
|
|
12
12
|
return Effect.try({
|
|
13
13
|
catch: (cause) => new WatchError({ message: cause instanceof Error ? cause.message : "Invalid arguments" }),
|
|
14
14
|
try: () => {
|
|
@@ -45,7 +45,6 @@ export function argumentsFor(args: readonly string[], token: string) {
|
|
|
45
45
|
numbers,
|
|
46
46
|
repository,
|
|
47
47
|
stateFile: values.state ?? `.pr-watch/${repository.replace("/", "-")}.json`,
|
|
48
|
-
token,
|
|
49
48
|
} satisfies Options,
|
|
50
49
|
};
|
|
51
50
|
},
|
package/src/cli.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { NodeFileSystem } from "@effect/platform-node";
|
|
4
|
-
import {
|
|
4
|
+
import { Effect, Layer } from "effect";
|
|
5
5
|
import { FetchHttpClient } from "effect/http";
|
|
6
|
+
import { GitHub as GitHubClient } from "@shivaedev/effect-github/GitHub.ts";
|
|
7
|
+
import { GitHubToken } from "@shivaedev/effect-github/GitHubToken.ts";
|
|
6
8
|
import { GitHub } from "./GitHub.ts";
|
|
7
9
|
import { GitHubHttp } from "./GitHubHttp.ts";
|
|
8
10
|
import { WatchError } from "./model.ts";
|
|
@@ -11,7 +13,12 @@ import { StateFile } from "./StateFile.ts";
|
|
|
11
13
|
import { Watcher } from "./Watcher.ts";
|
|
12
14
|
|
|
13
15
|
const services = Watcher.layer.pipe(
|
|
14
|
-
Layer.provide(
|
|
16
|
+
Layer.provide(
|
|
17
|
+
Layer.mergeAll(
|
|
18
|
+
StateFile.layer,
|
|
19
|
+
GitHub.layer.pipe(Layer.provide(GitHubHttp.layer.pipe(Layer.provide(GitHubClient.layer), Layer.provide(GitHubToken.fromEnv)))),
|
|
20
|
+
),
|
|
21
|
+
),
|
|
15
22
|
Layer.provide(
|
|
16
23
|
Layer.mergeAll(
|
|
17
24
|
NodeFileSystem.layer,
|
|
@@ -30,16 +37,9 @@ const abort = new AbortController();
|
|
|
30
37
|
process.once("SIGINT", () => abort.abort());
|
|
31
38
|
process.once("SIGTERM", () => abort.abort());
|
|
32
39
|
try {
|
|
33
|
-
await Effect.runPromise(
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Config.orElse(() => Config.String("GITHUB_TOKEN")),
|
|
37
|
-
Config.withDefault(""),
|
|
38
|
-
);
|
|
39
|
-
yield* program(process.argv.slice(2), token, write(process.stdout), write(process.stderr));
|
|
40
|
-
}).pipe(Effect.provide(services)),
|
|
41
|
-
{ signal: abort.signal },
|
|
42
|
-
);
|
|
40
|
+
await Effect.runPromise(program(process.argv.slice(2), write(process.stdout), write(process.stderr)).pipe(Effect.provide(services)), {
|
|
41
|
+
signal: abort.signal,
|
|
42
|
+
});
|
|
43
43
|
} catch (cause) {
|
|
44
44
|
if (!abort.signal.aborted) {
|
|
45
45
|
process.stderr.write(`${cause instanceof Error ? cause.message : "Watcher failed"}\n`);
|
package/src/confirmQueue.ts
CHANGED
|
@@ -40,8 +40,8 @@ export const confirmQueue = Effect.fn("GitHub.confirmQueue")(function* (
|
|
|
40
40
|
url.pathname = `${url.pathname.replace(/\/$/u, "")}/graphql`;
|
|
41
41
|
}
|
|
42
42
|
const [owner, name] = options.repository.split("/");
|
|
43
|
-
const response = yield* http.post(url.href,
|
|
44
|
-
const body = yield* Schema.decodeUnknownEffect(QueueResponse)(response
|
|
43
|
+
const response = yield* http.post(url.href, { query, variables: { name, number: snapshot.number, owner } });
|
|
44
|
+
const body = yield* Schema.decodeUnknownEffect(QueueResponse)(response).pipe(
|
|
45
45
|
Effect.mapError(() => new WatchError({ message: "Invalid GitHub queue confirmation" })),
|
|
46
46
|
);
|
|
47
47
|
const pr = body.data?.repository?.pullRequest;
|
package/src/model.ts
CHANGED
package/src/program.ts
CHANGED
|
@@ -4,11 +4,10 @@ import type { WatchError } from "./model.ts";
|
|
|
4
4
|
import { Watcher } from "./Watcher.ts";
|
|
5
5
|
export const program = Effect.fn("PrWatch.program")(function* (
|
|
6
6
|
args: readonly string[],
|
|
7
|
-
token: string,
|
|
8
7
|
emit: (line: string) => Effect.Effect<void, WatchError>,
|
|
9
8
|
diagnostic: (message: string) => Effect.Effect<void, WatchError>,
|
|
10
9
|
) {
|
|
11
|
-
const parsed = yield* argumentsFor(args
|
|
10
|
+
const parsed = yield* argumentsFor(args);
|
|
12
11
|
if (parsed.help) {
|
|
13
12
|
yield* emit(help);
|
|
14
13
|
return;
|
package/src/quota.ts
CHANGED
|
@@ -1,42 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { DateTime } from "effect";
|
|
2
|
+
import type { RateLimitObservation } from "@shivaedev/effect-github/rateLimits.ts";
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
limit: number;
|
|
6
|
-
remaining: number;
|
|
7
|
-
reset: number;
|
|
4
|
+
export function quotaRetryAt(observations: readonly RateLimitObservation[], retryAt = 0): number {
|
|
5
|
+
return Math.max(retryAt, ...observations.filter((budget) => budget.remaining === 0).map((budget) => DateTime.toEpochMillis(budget.reset)));
|
|
8
6
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
if (budget.remaining === 0) {
|
|
23
|
-
budget.blockedUntil = Math.max(budget.blockedUntil, budget.reset);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
export function pollDelay(budget: Budget, interval: number, requests: number, now: number): number {
|
|
27
|
-
const ratio = budget.remaining / Math.max(1, budget.limit);
|
|
28
|
-
const multiplier = slowdown(ratio);
|
|
29
|
-
const paced = budget.remaining > 0 && ratio < 0.5 ? Math.ceil((Math.max(0, budget.reset - now) * requests) / budget.remaining) : 0;
|
|
30
|
-
return Math.max(interval * multiplier, paced, budget.blockedUntil - now);
|
|
31
|
-
}
|
|
32
|
-
function retryUntil(retry: string | null, now: number): number {
|
|
33
|
-
if (retry === null) {
|
|
34
|
-
return now + 60_000;
|
|
35
|
-
}
|
|
36
|
-
if (Number.isFinite(Number(retry))) {
|
|
37
|
-
return now + Number(retry) * 1000;
|
|
38
|
-
}
|
|
39
|
-
return Date.parse(retry);
|
|
7
|
+
|
|
8
|
+
export function pollDelay(observations: readonly RateLimitObservation[], interval: number, requests: number, now: number, retryAt = 0): number {
|
|
9
|
+
return Math.max(
|
|
10
|
+
interval,
|
|
11
|
+
quotaRetryAt(observations, retryAt) - now,
|
|
12
|
+
...observations.map((budget) => {
|
|
13
|
+
const ratio = budget.remaining / Math.max(1, budget.limit);
|
|
14
|
+
const reset = DateTime.toEpochMillis(budget.reset);
|
|
15
|
+
const paced = budget.remaining > 0 && ratio < 0.5 ? Math.ceil((Math.max(0, reset - now) * requests) / budget.remaining) : 0;
|
|
16
|
+
return Math.max(interval * slowdown(ratio), paced);
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
40
19
|
}
|
|
41
20
|
function slowdown(ratio: number): number {
|
|
42
21
|
if (ratio < 0.1) {
|
package/dist/httpResponse.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { type HttpClient, type HttpClientResponse } from "effect/http";
|
|
3
|
-
import { WatchError } from "./model.ts";
|
|
4
|
-
export interface CachedResponse {
|
|
5
|
-
readonly body: unknown;
|
|
6
|
-
readonly etag: string | null;
|
|
7
|
-
readonly next: string | undefined;
|
|
8
|
-
}
|
|
9
|
-
export declare function fetchResponse(client: HttpClient.HttpClient, url: string, token: string, cached: CachedResponse | undefined, body?: unknown): Effect.Effect<HttpClientResponse.HttpClientResponse, import("effect/http/HttpBody").HttpBodyError | import("effect/http/HttpClientError").HttpClientError, never>;
|
|
10
|
-
export declare function decodeResponse(url: string, response: HttpClientResponse.HttpClientResponse): Effect.Effect<{
|
|
11
|
-
body: import("effect/Schema").Json;
|
|
12
|
-
etag: string | null;
|
|
13
|
-
next: string | undefined;
|
|
14
|
-
}, WatchError, never>;
|
|
15
|
-
//# sourceMappingURL=httpResponse.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"httpResponse.d.ts","sourceRoot":"","sources":["../src/httpResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,KAAK,UAAU,EAAqB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AACD,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,CAAC,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,qKAW1I;AACD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,CAAC,kBAAkB;;;;sBAgB1F"}
|
package/dist/httpResponse.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { HttpClientRequest } from "effect/http";
|
|
3
|
-
import { WatchError } from "./model.js";
|
|
4
|
-
export function fetchResponse(client, url, token, cached, body) {
|
|
5
|
-
const headers = { Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" };
|
|
6
|
-
if (token) {
|
|
7
|
-
headers.Authorization = `Bearer ${token}`;
|
|
8
|
-
}
|
|
9
|
-
if (cached?.etag) {
|
|
10
|
-
headers["If-None-Match"] = cached.etag;
|
|
11
|
-
}
|
|
12
|
-
return body === undefined
|
|
13
|
-
? client.get(url, { headers })
|
|
14
|
-
: HttpClientRequest.post(url, { headers }).pipe(HttpClientRequest.bodyJson(body), Effect.flatMap(client.execute));
|
|
15
|
-
}
|
|
16
|
-
export function decodeResponse(url, response) {
|
|
17
|
-
return Effect.gen(function* () {
|
|
18
|
-
if (response.status < 200 || response.status >= 300) {
|
|
19
|
-
return yield* Effect.fail(new WatchError({ message: `GitHub HTTP ${response.status} for ${new URL(url).pathname}` }));
|
|
20
|
-
}
|
|
21
|
-
const body = yield* response.json;
|
|
22
|
-
const next = response.headers.link?.match(/<(?<url>[^>]+)>; rel="next"/u)?.groups?.url;
|
|
23
|
-
yield* Effect.try(() => {
|
|
24
|
-
if (next !== undefined && new URL(next).origin !== new URL(url).origin) {
|
|
25
|
-
throw new Error("Cross-origin pagination refused");
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
return { body, etag: response.headers.etag ?? null, next };
|
|
29
|
-
}).pipe(Effect.mapError((cause) => (cause instanceof WatchError ? cause : new WatchError({ message: "Invalid GitHub response or pagination URL" }))));
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=httpResponse.js.map
|
package/dist/httpResponse.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"httpResponse.js","sourceRoot":"","sources":["../src/httpResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAmB,iBAAiB,EAA2B,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAMxC,MAAM,UAAU,aAAa,CAAC,MAA6B,EAAE,GAAW,EAAE,KAAa,EAAE,MAAkC,EAAE,IAAc;IAC1I,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,6BAA6B,EAAE,sBAAsB,EAAE,YAAY,EAAE,CAAC;IACxH,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,aAAa,GAAG,UAAU,KAAK,EAAE,CAAC;IAC3C,CAAC;IACD,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC;QAClB,OAAO,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,KAAK,SAAS;QACxB,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC;QAC9B,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;AACpH,CAAC;AACD,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,QAA+C;IAC1F,OAAO,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YACrD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,eAAe,QAAQ,CAAC,MAAM,QAAQ,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;QACvH,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,8BAA8B,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC;QACvF,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE;YACtB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;gBACxE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;YACpD,CAAC;QACF,CAAC,CAAC,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE,IAAI,EAA2B,CAAC;IACrF,CAAC,CAAC,CAAC,IAAI,CACN,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,EAAE,OAAO,EAAE,2CAA2C,EAAE,CAAC,CAAC,CAAC,CAC5I,CAAC;AACH,CAAC"}
|
package/src/httpResponse.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import { type HttpClient, HttpClientRequest, type HttpClientResponse } from "effect/http";
|
|
3
|
-
import { WatchError } from "./model.ts";
|
|
4
|
-
export interface CachedResponse {
|
|
5
|
-
readonly body: unknown;
|
|
6
|
-
readonly etag: string | null;
|
|
7
|
-
readonly next: string | undefined;
|
|
8
|
-
}
|
|
9
|
-
export function fetchResponse(client: HttpClient.HttpClient, url: string, token: string, cached: CachedResponse | undefined, body?: unknown) {
|
|
10
|
-
const headers: Record<string, string> = { Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" };
|
|
11
|
-
if (token) {
|
|
12
|
-
headers.Authorization = `Bearer ${token}`;
|
|
13
|
-
}
|
|
14
|
-
if (cached?.etag) {
|
|
15
|
-
headers["If-None-Match"] = cached.etag;
|
|
16
|
-
}
|
|
17
|
-
return body === undefined
|
|
18
|
-
? client.get(url, { headers })
|
|
19
|
-
: HttpClientRequest.post(url, { headers }).pipe(HttpClientRequest.bodyJson(body), Effect.flatMap(client.execute));
|
|
20
|
-
}
|
|
21
|
-
export function decodeResponse(url: string, response: HttpClientResponse.HttpClientResponse) {
|
|
22
|
-
return Effect.gen(function* () {
|
|
23
|
-
if (response.status < 200 || response.status >= 300) {
|
|
24
|
-
return yield* Effect.fail(new WatchError({ message: `GitHub HTTP ${response.status} for ${new URL(url).pathname}` }));
|
|
25
|
-
}
|
|
26
|
-
const body = yield* response.json;
|
|
27
|
-
const next = response.headers.link?.match(/<(?<url>[^>]+)>; rel="next"/u)?.groups?.url;
|
|
28
|
-
yield* Effect.try(() => {
|
|
29
|
-
if (next !== undefined && new URL(next).origin !== new URL(url).origin) {
|
|
30
|
-
throw new Error("Cross-origin pagination refused");
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
return { body, etag: response.headers.etag ?? null, next } satisfies CachedResponse;
|
|
34
|
-
}).pipe(
|
|
35
|
-
Effect.mapError((cause) => (cause instanceof WatchError ? cause : new WatchError({ message: "Invalid GitHub response or pagination URL" }))),
|
|
36
|
-
);
|
|
37
|
-
}
|