@useairfoil/producer-github 0.0.1 → 0.0.3

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 (60) hide show
  1. package/LICENSE.txt +201 -0
  2. package/README.md +116 -0
  3. package/dist/index.d.mts +1939 -0
  4. package/dist/index.mjs +3 -0
  5. package/dist/main.d.mts +1 -0
  6. package/dist/main.mjs +700 -0
  7. package/dist/main.mjs.map +1 -0
  8. package/dist/manifest.d.mts +56 -0
  9. package/dist/manifest.mjs +137 -0
  10. package/dist/manifest.mjs.map +1 -0
  11. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  12. package/dist/src-DKDUc00e.mjs +1563 -0
  13. package/dist/src-DKDUc00e.mjs.map +1 -0
  14. package/package.json +46 -8
  15. package/src/client/auth.ts +113 -0
  16. package/src/client/client.ts +192 -0
  17. package/src/client/constants.ts +4 -0
  18. package/src/connector.ts +97 -0
  19. package/src/create-table.ts +10 -0
  20. package/src/demo/changes.ts +420 -0
  21. package/src/demo/index.ts +59 -0
  22. package/src/demo/setup.ts +34 -0
  23. package/src/demo/shared.ts +101 -0
  24. package/src/index.ts +13 -0
  25. package/src/main.ts +28 -0
  26. package/src/manifest.ts +45 -0
  27. package/src/resources/check-runs.ts +72 -0
  28. package/src/resources/commit-statuses.ts +84 -0
  29. package/src/resources/commits.ts +101 -0
  30. package/src/resources/issue-comments.ts +75 -0
  31. package/src/resources/issues.ts +78 -0
  32. package/src/resources/pull-request-commits.ts +91 -0
  33. package/src/resources/pull-request-review-comments.ts +78 -0
  34. package/src/resources/pull-request-reviews.ts +76 -0
  35. package/src/resources/pull-requests.ts +79 -0
  36. package/src/resources/releases.ts +74 -0
  37. package/src/resources/repositories.ts +101 -0
  38. package/src/resources/shared.ts +480 -0
  39. package/src/resources/workflow-jobs.ts +73 -0
  40. package/src/resources/workflow-runs.ts +70 -0
  41. package/src/sandbox.ts +35 -0
  42. package/src/schemas/check-runs.ts +72 -0
  43. package/src/schemas/commit-statuses.ts +51 -0
  44. package/src/schemas/commits.ts +80 -0
  45. package/src/schemas/events.ts +139 -0
  46. package/src/schemas/issue-comments.ts +48 -0
  47. package/src/schemas/issues.ts +76 -0
  48. package/src/schemas/pull-request-commits.ts +42 -0
  49. package/src/schemas/pull-request-review-comments.ts +85 -0
  50. package/src/schemas/pull-request-reviews.ts +53 -0
  51. package/src/schemas/pull-requests.ts +95 -0
  52. package/src/schemas/releases.ts +70 -0
  53. package/src/schemas/repositories.ts +73 -0
  54. package/src/schemas/shared.ts +76 -0
  55. package/src/schemas/workflow-jobs.ts +78 -0
  56. package/src/schemas/workflow-runs.ts +72 -0
  57. package/src/start.ts +47 -0
  58. package/src/tables.ts +31 -0
  59. package/src/webhook/route.ts +46 -0
  60. package/src/webhook/signature.ts +20 -0
@@ -0,0 +1,1563 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { GitHubConfigDef, manifest } from "./manifest.mjs";
3
+ import { Auth, Connector, ConnectorError, Cursor, Fetch, Iceberg, Metrics, Resource, Telemetry, Webhook } from "@useairfoil/connector-kit";
4
+ import { Array, Clock, Config, Context, DateTime, Duration, Effect, Exit, Layer, Option, Order, Redacted, Result, Schedule, Schema, SchemaTransformation, Stream, Struct } from "effect";
5
+ import { HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, HttpServerResponse } from "effect/http";
6
+ import { createHmac, createPrivateKey, sign, timingSafeEqual } from "node:crypto";
7
+ //#region src/client/constants.ts
8
+ const GITHUB_API_VERSION = "2026-03-10";
9
+ const baseUrl = "https://api.github.com";
10
+ //#endregion
11
+ //#region src/client/auth.ts
12
+ var auth_exports = /* @__PURE__ */ __exportAll({
13
+ GitHubAuth: () => GitHubAuth,
14
+ layer: () => layer$2,
15
+ make: () => make$2,
16
+ makeAppJwt: () => makeAppJwt
17
+ });
18
+ var GitHubAuth = class extends Context.Service()("@useairfoil/producer-github/GitHubAuth") {};
19
+ const TokenResponseSchema = Schema.Struct({
20
+ token: Schema.String,
21
+ expires_at: Schema.DateFromString
22
+ });
23
+ const base64Url = (value) => Buffer.from(value).toString("base64url");
24
+ /**
25
+ * Signs the app JWT with RS256. GitHub allows at most 10 minutes, and asks for
26
+ * `iat` 60 seconds in the past to allow for clock drift.
27
+ */
28
+ const makeAppJwt = (options) => Effect.try({
29
+ try: () => {
30
+ const now = Math.floor(options.nowMillis / 1e3);
31
+ const signingInput = `${base64Url(JSON.stringify({
32
+ alg: "RS256",
33
+ typ: "JWT"
34
+ }))}.${base64Url(JSON.stringify({
35
+ iat: now - 60,
36
+ exp: now + 540,
37
+ iss: options.clientId
38
+ }))}`;
39
+ const key = createPrivateKey(options.privateKey.replace(/\\n/g, "\n"));
40
+ return `${signingInput}.${base64Url(sign("sha256", Buffer.from(signingInput), key))}`;
41
+ },
42
+ catch: (cause) => new ConnectorError({
43
+ message: "Invalid GitHub App private key",
44
+ cause
45
+ })
46
+ });
47
+ const make$2 = Effect.fnUntraced(function* (config) {
48
+ const client = (yield* HttpClient.HttpClient).pipe(HttpClient.retryTransient({
49
+ schedule: Schedule.exponential("250 millis").pipe(Schedule.jittered),
50
+ times: 3
51
+ }));
52
+ const path = `/app/installations/${config.installationId}/access_tokens`;
53
+ return yield* Auth.makeTokenCache({ acquire: Effect.gen(function* () {
54
+ const jwt = yield* makeAppJwt({
55
+ clientId: config.appClientId,
56
+ privateKey: Redacted.value(config.appPrivateKey),
57
+ nowMillis: yield* Clock.currentTimeMillis
58
+ });
59
+ const request = HttpClientRequest.post(`${baseUrl}${path}`).pipe(HttpClientRequest.bearerToken(jwt), HttpClientRequest.acceptJson, HttpClientRequest.setHeader("X-GitHub-Api-Version", GITHUB_API_VERSION));
60
+ const response = yield* client.execute(request).pipe(Effect.mapError((cause) => new ConnectorError({
61
+ message: "GitHub installation token request failed",
62
+ cause
63
+ })));
64
+ const body = yield* HttpClientResponse.matchStatus(response, {
65
+ "2xx": (ok) => HttpClientResponse.schemaBodyJson(TokenResponseSchema)(ok).pipe(Effect.mapError((cause) => new ConnectorError({
66
+ message: "Invalid GitHub installation token response",
67
+ cause
68
+ }))),
69
+ orElse: (failed) => Effect.fail(new ConnectorError({ message: `GitHub returned ${failed.status} for the installation token` }))
70
+ });
71
+ const now = yield* Clock.currentTimeMillis;
72
+ return {
73
+ value: Redacted.make(body.token),
74
+ expiresIn: Duration.millis(Math.max(body.expires_at.getTime() - now, 0))
75
+ };
76
+ }).pipe(Effect.tapError((error) => Telemetry.annotateError("api_auth", error)), Effect.withSpan(Telemetry.SpanName.apiFetch, {
77
+ kind: "client",
78
+ attributes: { [Telemetry.Attr.apiPath]: path }
79
+ })) });
80
+ });
81
+ const layer$2 = (config) => Layer.effect(GitHubAuth)(make$2(config));
82
+ //#endregion
83
+ //#region src/client/client.ts
84
+ var client_exports = /* @__PURE__ */ __exportAll({
85
+ GitHubClient: () => GitHubClient,
86
+ layer: () => layer$1,
87
+ layerConfig: () => layerConfig$1,
88
+ make: () => make$1,
89
+ nextLink: () => nextLink,
90
+ withParams: () => withParams
91
+ });
92
+ const maxRetries = 5;
93
+ const retryBaseDelay = Duration.millis(500);
94
+ const requestTimeout = Duration.minutes(2);
95
+ var GitHubClient = class extends Context.Service()("@useairfoil/producer-github/GitHubClient") {};
96
+ const withParams = (path, params) => `${path}?${new URLSearchParams(params).toString()}`;
97
+ const nextLink = (header) => Option.fromNullishOr(/<([^>]+)>;\s*rel="next"/.exec(header ?? "")?.[1]);
98
+ const ErrorBodySchema = Schema.Struct({ message: Schema.optional(Schema.String) });
99
+ const statusError = (response, path) => HttpClientResponse.schemaBodyJson(ErrorBodySchema)(response).pipe(Effect.map((body) => body.message), Effect.orElseSucceed(() => void 0), Effect.flatMap((detail) => Effect.fail(new ConnectorError({ message: `GitHub API returned ${response.status} for ${path}${detail ? `: ${detail}` : ""}` }))), Effect.tapError((error) => Telemetry.annotateError("api_status", error)));
100
+ /**
101
+ * GitHub answers rate limits with `403` or `429`. A `403` is a rate limit when
102
+ * it has `retry-after`, `x-ratelimit-remaining: 0`, or a rate limit message;
103
+ * otherwise it's a permission error.
104
+ */
105
+ const isRateLimited = (response, body) => response.status === 429 || response.status === 403 && (response.headers["retry-after"] !== void 0 || response.headers["x-ratelimit-remaining"] === "0" || /rate limit/i.test(body));
106
+ const rateLimitedResponse = (input) => HttpClientError.isHttpClientError(input) && input.reason._tag === "StatusCodeError" ? input.reason.response : void 0;
107
+ const retrySchedule = Metrics.retrySchedule({
108
+ connector: manifest.name,
109
+ baseDelay: retryBaseDelay,
110
+ times: maxRetries
111
+ }).pipe(Schedule.modifyDelay(({ input, duration, now, attempt }) => {
112
+ const response = rateLimitedResponse(input);
113
+ if (response === void 0) return Effect.succeed(duration);
114
+ const retryAfter = response.headers["retry-after"];
115
+ if (retryAfter !== void 0) return Effect.succeed(Duration.seconds(Number(retryAfter)));
116
+ if (response.headers["x-ratelimit-remaining"] === "0") return Effect.succeed(Duration.millis(Math.max(Number(response.headers["x-ratelimit-reset"]) * 1e3 - now, 0) + 1e3));
117
+ return Effect.succeed(Duration.minutes(2 ** (attempt - 1)));
118
+ }));
119
+ const make$1 = Effect.fnUntraced(function* () {
120
+ const auth = yield* GitHubAuth;
121
+ const client = (yield* HttpClient.HttpClient).pipe(HttpClient.mapRequest((request) => request.pipe(HttpClientRequest.acceptJson, HttpClientRequest.setHeader("X-GitHub-Api-Version", GITHUB_API_VERSION))), HttpClient.mapRequestEffect((request) => auth.get.pipe(Effect.map((token) => HttpClientRequest.bearerToken(request, token)))), HttpClient.transformResponse(Effect.flatMap((response) => Effect.flatMap(response.text, (body) => isRateLimited(response, body) ? Effect.fail(new HttpClientError.HttpClientError({ reason: new HttpClientError.StatusCodeError({
122
+ request: response.request,
123
+ response
124
+ }) })) : Effect.succeed(response)))), HttpClient.transform((effect) => Effect.timeout(effect, requestTimeout)), HttpClient.retryTransient({
125
+ schedule: retrySchedule,
126
+ while: (error) => rateLimitedResponse(error) !== void 0
127
+ }));
128
+ const get = (schema, url) => {
129
+ const fullUrl = url.startsWith("/") ? `${baseUrl}${url}` : url;
130
+ if (!fullUrl.startsWith(`https://api.github.com/`)) return Effect.fail(new ConnectorError({ message: `Not a GitHub API URL: ${url}` }));
131
+ const path = fullUrl.slice(22).split("?")[0] ?? fullUrl;
132
+ return client.execute(HttpClientRequest.get(fullUrl)).pipe(Effect.catchTag(["HttpClientError", "TimeoutError"], (error) => {
133
+ const response = rateLimitedResponse(error);
134
+ if (response !== void 0) return Effect.succeed(response);
135
+ return Telemetry.annotateError("api_http", error).pipe(Effect.andThen(Effect.fail(new ConnectorError({
136
+ message: `GitHub API request ${error._tag === "TimeoutError" ? "timed out" : "failed"} for ${path}`,
137
+ cause: error
138
+ }))));
139
+ }), Effect.flatMap((response) => HttpClientResponse.matchStatus(response, {
140
+ "2xx": (ok) => HttpClientResponse.schemaBodyJson(schema)(ok).pipe(Effect.tapError((error) => Telemetry.annotateError("api_decode", error)), Effect.mapError((cause) => new ConnectorError({
141
+ message: `Invalid GitHub API response for ${path}`,
142
+ cause
143
+ })), Effect.map((body) => ({
144
+ body,
145
+ next: nextLink(ok.headers.link)
146
+ }))),
147
+ orElse: (failed) => statusError(failed, path)
148
+ })), Effect.withSpan(Telemetry.SpanName.apiFetch, {
149
+ kind: "client",
150
+ attributes: {
151
+ [Telemetry.Attr.apiPath]: path,
152
+ "github.api.version": GITHUB_API_VERSION
153
+ }
154
+ }));
155
+ };
156
+ return GitHubClient.of({ get });
157
+ });
158
+ const layer$1 = (config) => Layer.effect(GitHubClient)(make$1()).pipe(Layer.provide(layer$2(config)));
159
+ const layerConfig$1 = (config) => Layer.unwrap(Config.unwrap(config).pipe(Effect.map(layer$1)));
160
+ //#endregion
161
+ //#region src/schemas/shared.ts
162
+ /** IDs and counts. Connector Kit maps whole numbers to Iceberg longs. */
163
+ const Long = Schema.Int.pipe(Schema.decodeTo(Schema.BigInt, SchemaTransformation.transform({
164
+ decode: (value) => BigInt(value),
165
+ encode: (value) => Number(value)
166
+ })));
167
+ const Timestamp = Schema.DateFromString;
168
+ /** Text GitHub can clear, such as a body. */
169
+ const Text = Schema.NullOr(Schema.String).pipe(Schema.decodeTo(Schema.String, SchemaTransformation.transform({
170
+ decode: (value) => value ?? "",
171
+ encode: (value) => value
172
+ })));
173
+ /** `null` becomes an empty list. A webhook can leave the list out. */
174
+ const optionalList = (item) => Schema.optionalKey(Schema.NullOr(Schema.Array(item)).pipe(Schema.decodeTo(Schema.Array(Schema.toType(item)), SchemaTransformation.transform({
175
+ decode: (value) => value ?? [],
176
+ encode: (value) => value
177
+ }))));
178
+ const user = Schema.Struct({
179
+ id: Long.pipe(Schema.annotateKey({ description: "GitHub user ID." })),
180
+ login: Schema.String.pipe(Schema.annotateKey({ description: "GitHub username." })),
181
+ type: Schema.String.pipe(Schema.annotateKey({ description: "Account type, such as User, Bot, or Organization." }))
182
+ });
183
+ const label = Schema.Struct({
184
+ id: Long.pipe(Schema.annotateKey({ description: "Label ID." })),
185
+ name: Schema.String.pipe(Schema.annotateKey({ description: "Label name." }))
186
+ });
187
+ const milestone = Schema.Struct({
188
+ id: Long.pipe(Schema.annotateKey({ description: "Milestone ID." })),
189
+ number: Long.pipe(Schema.annotateKey({ description: "Milestone number in the repository." })),
190
+ title: Schema.String.pipe(Schema.annotateKey({ description: "Milestone title." }))
191
+ });
192
+ const version = (object) => Schema.Date.pipe(Schema.annotateKey({ description: `Time GitHub last updated the ${object}.` }));
193
+ /** For objects without an update time. A newer read wins. */
194
+ const readVersion = (object) => Schema.Date.pipe(Schema.annotateKey({ description: `Time the connector read the ${object}. GitHub has no update time for it.` }));
195
+ const deleted = (object) => Schema.optional(Schema.Boolean).pipe(Schema.annotateKey({ description: `Whether the ${object} was deleted in GitHub.` }));
196
+ const ObjectRefSchema = Schema.Struct({ id: Long });
197
+ //#endregion
198
+ //#region src/schemas/check-runs.ts
199
+ const CheckRunSchema = Schema.Struct({
200
+ id: Long.pipe(Schema.annotateKey({ description: "Unique check run ID." })),
201
+ version: readVersion("check run"),
202
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the check run." })),
203
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the check run belongs to." })),
204
+ head_sha: Schema.String.pipe(Schema.annotateKey({ description: "SHA of the commit the check ran on." })),
205
+ name: Schema.String.pipe(Schema.annotateKey({ description: "Check name." })),
206
+ status: Schema.String.pipe(Schema.annotateKey({ description: "Status, such as queued, in_progress, or completed." })),
207
+ conclusion: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Result of a completed check, such as success or failure." })),
208
+ started_at: Schema.NullOr(Timestamp).pipe(Schema.annotateKey({ description: "Time the check started." })),
209
+ completed_at: Schema.NullOr(Timestamp).pipe(Schema.annotateKey({ description: "Time the check completed." })),
210
+ app: Schema.NullOr(Schema.Struct({
211
+ id: Long.pipe(Schema.annotateKey({ description: "GitHub App ID." })),
212
+ name: Schema.String.pipe(Schema.annotateKey({ description: "GitHub App name." }))
213
+ })).pipe(Schema.annotateKey({ description: "GitHub App that created the check, such as GitHub Actions." })),
214
+ check_suite: Schema.NullOr(Schema.Struct({ id: Long.pipe(Schema.annotateKey({ description: "Check suite ID." })) })).pipe(Schema.annotateKey({ description: "Check suite the check run belongs to." })),
215
+ output: Schema.Struct({
216
+ title: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Output title." })),
217
+ summary: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Output summary in Markdown." }))
218
+ }).pipe(Schema.annotateKey({ description: "Output the check reported." })),
219
+ external_id: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "ID the app gave the check." })),
220
+ details_url: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Details page from the app." })),
221
+ html_url: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Check run page on GitHub." }))
222
+ }).annotate({ description: "Check runs, such as GitHub Actions jobs. Polled for default-branch commits; webhooks cover all branches." }).pipe(Iceberg.identifier("id"));
223
+ const CheckRunObjectSchema = CheckRunSchema.mapFields((fields) => Struct.omit(fields, ["version", "repository_id"]));
224
+ //#endregion
225
+ //#region src/schemas/issue-comments.ts
226
+ const IssueCommentSchema = Schema.Struct({
227
+ id: Long.pipe(Schema.annotateKey({ description: "Unique comment ID." })),
228
+ version: version("comment"),
229
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the comment." })),
230
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the comment belongs to." })),
231
+ issue_number: Long.pipe(Schema.annotateKey({ description: "Number of the issue or pull request the comment is on, in its repository." })),
232
+ user: Schema.NullOr(user).pipe(Schema.annotateKey({ description: "Account that wrote the comment." })),
233
+ author_association: Schema.String.pipe(Schema.annotateKey({ description: "Author's relation to the repository, such as OWNER or CONTRIBUTOR." })),
234
+ body: Text.pipe(Schema.annotateKey({ description: "Comment in Markdown. Empty when not set." })),
235
+ created_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the comment was written." })),
236
+ html_url: Schema.String.pipe(Schema.annotateKey({ description: "Comment on GitHub." })),
237
+ _deleted: deleted("comment")
238
+ }).annotate({ description: "Comments on issues and pull requests. Pull request review comments are not included." }).pipe(Iceberg.identifier("id"));
239
+ const IssueCommentObjectSchema = IssueCommentSchema.mapFields((fields) => ({
240
+ ...Struct.omit(fields, [
241
+ "version",
242
+ "repository_id",
243
+ "issue_number",
244
+ "_deleted"
245
+ ]),
246
+ updated_at: Timestamp,
247
+ issue_url: Schema.String.check(Schema.isPattern(/\/issues\/\d+$/))
248
+ }));
249
+ //#endregion
250
+ //#region src/schemas/issues.ts
251
+ const IssueSchema = Schema.Struct({
252
+ id: Long.pipe(Schema.annotateKey({ description: "Unique issue ID." })),
253
+ version: version("issue"),
254
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the issue." })),
255
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the issue belongs to." })),
256
+ number: Long.pipe(Schema.annotateKey({ description: "Issue number in the repository." })),
257
+ title: Schema.String.pipe(Schema.annotateKey({ description: "Issue title." })),
258
+ body: Text.pipe(Schema.annotateKey({ description: "Issue description in Markdown. Empty when not set." })),
259
+ state: Schema.String.pipe(Schema.annotateKey({ description: "State: open or closed." })),
260
+ state_reason: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Why the issue was closed or reopened, such as completed or not_planned." })),
261
+ locked: Schema.Boolean.pipe(Schema.annotateKey({ description: "Whether the conversation is locked." })),
262
+ user: Schema.NullOr(user).pipe(Schema.annotateKey({ description: "Account that opened the issue." })),
263
+ author_association: Schema.String.pipe(Schema.annotateKey({ description: "Author's relation to the repository, such as OWNER or CONTRIBUTOR." })),
264
+ labels: Schema.Array(label).pipe(Schema.annotateKey({ description: "Labels on the issue." })),
265
+ assignees: Schema.Array(user).pipe(Schema.annotateKey({ description: "Accounts assigned to the issue." })),
266
+ milestone: Schema.NullOr(milestone).pipe(Schema.annotateKey({ description: "Milestone of the issue." })),
267
+ comments: Long.pipe(Schema.annotateKey({ description: "Number of comments." })),
268
+ created_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the issue was opened." })),
269
+ closed_at: Schema.NullOr(Timestamp).pipe(Schema.annotateKey({ description: "Time the issue was closed. Null while it is open." })),
270
+ html_url: Schema.String.pipe(Schema.annotateKey({ description: "Issue page on GitHub." })),
271
+ _deleted: deleted("issue")
272
+ }).annotate({ description: "Issues in the installation's repositories. Pull requests are not included." }).pipe(Iceberg.identifier("id"));
273
+ const IssueObjectSchema = IssueSchema.mapFields((fields) => ({
274
+ ...Struct.omit(fields, [
275
+ "version",
276
+ "repository_id",
277
+ "labels",
278
+ "assignees",
279
+ "_deleted"
280
+ ]),
281
+ updated_at: Timestamp,
282
+ labels: optionalList(label),
283
+ assignees: optionalList(user),
284
+ pull_request: Schema.optionalKey(Schema.Unknown)
285
+ }));
286
+ //#endregion
287
+ //#region src/schemas/pull-request-review-comments.ts
288
+ const PullRequestReviewCommentSchema = Schema.Struct({
289
+ id: Long.pipe(Schema.annotateKey({ description: "Unique review comment ID." })),
290
+ version: version("review comment"),
291
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the review comment." })),
292
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the comment belongs to." })),
293
+ pull_request_number: Long.pipe(Schema.annotateKey({ description: "Number of the pull request the comment is on, in its repository." })),
294
+ pull_request_review_id: Schema.NullOr(Long).pipe(Schema.annotateKey({ description: "ID of the review the comment belongs to." })),
295
+ in_reply_to_id: Schema.optionalKey(Long).pipe(Schema.annotateKey({ description: "ID of the comment this one replies to." })),
296
+ user: Schema.NullOr(user).pipe(Schema.annotateKey({ description: "Account that wrote the comment." })),
297
+ author_association: Schema.String.pipe(Schema.annotateKey({ description: "Author's relation to the repository, such as OWNER or CONTRIBUTOR." })),
298
+ body: Schema.String.pipe(Schema.annotateKey({ description: "Comment in Markdown." })),
299
+ path: Schema.String.pipe(Schema.annotateKey({ description: "Path of the file the comment is on." })),
300
+ commit_id: Schema.String.pipe(Schema.annotateKey({ description: "SHA of the commit the comment is on now." })),
301
+ original_commit_id: Schema.String.pipe(Schema.annotateKey({ description: "SHA of the commit the comment was written on." })),
302
+ diff_hunk: Schema.String.pipe(Schema.annotateKey({ description: "Part of the diff the comment is on." })),
303
+ line: Schema.optionalKey(Schema.NullOr(Long)).pipe(Schema.annotateKey({ description: "Last line of the commented range in the current diff." })),
304
+ original_line: Schema.optionalKey(Schema.NullOr(Long)).pipe(Schema.annotateKey({ description: "Last line of the commented range in the original diff." })),
305
+ start_line: Schema.optionalKey(Schema.NullOr(Long)).pipe(Schema.annotateKey({ description: "First line of a multi-line comment." })),
306
+ side: Schema.optionalKey(Schema.NullOr(Schema.String)).pipe(Schema.annotateKey({ description: "Side of the diff: LEFT or RIGHT." })),
307
+ subject_type: Schema.optionalKey(Schema.String).pipe(Schema.annotateKey({ description: "What the comment is on: line or file." })),
308
+ created_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the comment was written." })),
309
+ html_url: Schema.String.pipe(Schema.annotateKey({ description: "Comment on GitHub." })),
310
+ _deleted: deleted("review comment")
311
+ }).annotate({ description: "Review comments on pull request diffs." }).pipe(Iceberg.identifier("id"));
312
+ const PullRequestReviewCommentObjectSchema = PullRequestReviewCommentSchema.mapFields((fields) => ({
313
+ ...Struct.omit(fields, [
314
+ "version",
315
+ "repository_id",
316
+ "pull_request_number",
317
+ "_deleted"
318
+ ]),
319
+ updated_at: Timestamp,
320
+ pull_request_url: Schema.String.check(Schema.isPattern(/\/pulls\/\d+$/))
321
+ }));
322
+ //#endregion
323
+ //#region src/schemas/pull-requests.ts
324
+ const branch = Schema.Struct({
325
+ ref: Schema.String.pipe(Schema.annotateKey({ description: "Branch name." })),
326
+ sha: Schema.String.pipe(Schema.annotateKey({ description: "Commit SHA." })),
327
+ label: Schema.String.pipe(Schema.annotateKey({ description: "Owner and branch, such as octocat:main." }))
328
+ });
329
+ const PullRequestSchema = Schema.Struct({
330
+ id: Long.pipe(Schema.annotateKey({ description: "Unique pull request ID. Not the ID of its issue." })),
331
+ version: version("pull request"),
332
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the pull request." })),
333
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the pull request belongs to." })),
334
+ number: Long.pipe(Schema.annotateKey({ description: "Pull request number in the repository." })),
335
+ title: Schema.String.pipe(Schema.annotateKey({ description: "Pull request title." })),
336
+ body: Text.pipe(Schema.annotateKey({ description: "Pull request description in Markdown. Empty when not set." })),
337
+ state: Schema.String.pipe(Schema.annotateKey({ description: "State: open or closed. Merged pull requests are closed." })),
338
+ draft: Schema.optionalKey(Schema.Boolean).pipe(Schema.annotateKey({ description: "Whether the pull request is a draft." })),
339
+ locked: Schema.Boolean.pipe(Schema.annotateKey({ description: "Whether the conversation is locked." })),
340
+ user: Schema.NullOr(user).pipe(Schema.annotateKey({ description: "Account that opened the pull request." })),
341
+ author_association: Schema.String.pipe(Schema.annotateKey({ description: "Author's relation to the repository, such as OWNER or CONTRIBUTOR." })),
342
+ labels: Schema.Array(label).pipe(Schema.annotateKey({ description: "Labels on the pull request." })),
343
+ assignees: Schema.Array(user).pipe(Schema.annotateKey({ description: "Accounts assigned to the pull request." })),
344
+ requested_reviewers: Schema.Array(user).pipe(Schema.annotateKey({ description: "Accounts asked to review that have not reviewed yet." })),
345
+ milestone: Schema.NullOr(milestone).pipe(Schema.annotateKey({ description: "Milestone of the pull request." })),
346
+ head: branch.pipe(Schema.annotateKey({ description: "Branch the changes come from." })),
347
+ base: branch.pipe(Schema.annotateKey({ description: "Branch the changes merge into." })),
348
+ created_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the pull request was opened." })),
349
+ closed_at: Schema.NullOr(Timestamp).pipe(Schema.annotateKey({ description: "Time the pull request was closed. Null while it is open." })),
350
+ merged_at: Schema.NullOr(Timestamp).pipe(Schema.annotateKey({ description: "Time the pull request was merged." })),
351
+ html_url: Schema.String.pipe(Schema.annotateKey({ description: "Pull request page on GitHub." }))
352
+ }).annotate({ description: "Pull requests in the installation's repositories." }).pipe(Iceberg.identifier("id"));
353
+ const PullRequestObjectSchema = PullRequestSchema.mapFields((fields) => ({
354
+ ...Struct.omit(fields, [
355
+ "version",
356
+ "repository_id",
357
+ "labels",
358
+ "assignees",
359
+ "requested_reviewers"
360
+ ]),
361
+ updated_at: Timestamp,
362
+ labels: optionalList(label),
363
+ assignees: optionalList(user),
364
+ requested_reviewers: optionalList(user)
365
+ }));
366
+ //#endregion
367
+ //#region src/schemas/releases.ts
368
+ const asset = Schema.Struct({
369
+ id: Long.pipe(Schema.annotateKey({ description: "Asset ID." })),
370
+ name: Schema.String.pipe(Schema.annotateKey({ description: "File name." })),
371
+ label: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Label shown instead of the name." })),
372
+ state: Schema.String.pipe(Schema.annotateKey({ description: "State: uploaded or open." })),
373
+ content_type: Schema.String.pipe(Schema.annotateKey({ description: "Media type of the file." })),
374
+ size: Long.pipe(Schema.annotateKey({ description: "Size in bytes." })),
375
+ download_count: Long.pipe(Schema.annotateKey({ description: "Number of downloads when the release last changed." })),
376
+ created_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the asset was uploaded." })),
377
+ updated_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the asset last changed." })),
378
+ browser_download_url: Schema.String.pipe(Schema.annotateKey({ description: "Download link." }))
379
+ });
380
+ const ReleaseSchema = Schema.Struct({
381
+ id: Long.pipe(Schema.annotateKey({ description: "Unique release ID." })),
382
+ version: version("release"),
383
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the release." })),
384
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the release belongs to." })),
385
+ tag_name: Schema.String.pipe(Schema.annotateKey({ description: "Tag of the release." })),
386
+ target_commitish: Schema.String.pipe(Schema.annotateKey({ description: "Branch or commit the tag was created from." })),
387
+ name: Text.pipe(Schema.annotateKey({ description: "Release title. Empty when not set." })),
388
+ body: Text.pipe(Schema.annotateKey({ description: "Release notes in Markdown. Empty when not set." })),
389
+ draft: Schema.Boolean.pipe(Schema.annotateKey({ description: "Whether the release is a draft." })),
390
+ prerelease: Schema.Boolean.pipe(Schema.annotateKey({ description: "Whether the release is a pre-release." })),
391
+ author: user.pipe(Schema.annotateKey({ description: "Account that created the release." })),
392
+ assets: Schema.Array(asset).pipe(Schema.annotateKey({ description: "Files attached to the release." })),
393
+ created_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the release was created." })),
394
+ published_at: Schema.NullOr(Timestamp).pipe(Schema.annotateKey({ description: "Time the release was published." })),
395
+ html_url: Schema.String.pipe(Schema.annotateKey({ description: "Release page on GitHub." })),
396
+ _deleted: deleted("release")
397
+ }).annotate({ description: "Releases in the installation's repositories, with their assets." }).pipe(Iceberg.identifier("id"));
398
+ const ReleaseObjectSchema = ReleaseSchema.mapFields((fields) => ({
399
+ ...Struct.omit(fields, [
400
+ "version",
401
+ "repository_id",
402
+ "_deleted"
403
+ ]),
404
+ updated_at: Schema.optionalKey(Schema.NullOr(Timestamp))
405
+ }));
406
+ //#endregion
407
+ //#region src/schemas/repositories.ts
408
+ const RepositorySchema = Schema.Struct({
409
+ id: Long.pipe(Schema.annotateKey({ description: "Unique repository ID." })),
410
+ version: version("repository"),
411
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the repository." })),
412
+ name: Schema.String.pipe(Schema.annotateKey({ description: "Repository name." })),
413
+ full_name: Schema.String.pipe(Schema.annotateKey({ description: "Owner and name, such as octo-org/octo-repo." })),
414
+ owner: user.pipe(Schema.annotateKey({ description: "Account that owns the repository." })),
415
+ private: Schema.Boolean.pipe(Schema.annotateKey({ description: "Whether the repository is private." })),
416
+ visibility: Schema.optionalKey(Schema.String).pipe(Schema.annotateKey({ description: "Visibility: public, private, or internal." })),
417
+ description: Text.pipe(Schema.annotateKey({ description: "Repository description. Empty when not set." })),
418
+ fork: Schema.Boolean.pipe(Schema.annotateKey({ description: "Whether the repository is a fork." })),
419
+ archived: Schema.Boolean.pipe(Schema.annotateKey({ description: "Whether the repository is archived." })),
420
+ disabled: Schema.Boolean.pipe(Schema.annotateKey({ description: "Whether the repository is disabled." })),
421
+ default_branch: Schema.String.pipe(Schema.annotateKey({ description: "Default branch name." })),
422
+ language: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Main language GitHub detected." })),
423
+ topics: Schema.Array(Schema.String).pipe(Schema.annotateKey({ description: "Repository topics." })),
424
+ html_url: Schema.String.pipe(Schema.annotateKey({ description: "Repository page on GitHub." })),
425
+ created_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the repository was created." })),
426
+ _deleted: deleted("repository")
427
+ }).annotate({ description: "Repositories the GitHub App installation can read." }).pipe(Iceberg.identifier("id"));
428
+ const RepositoryObjectSchema = RepositorySchema.mapFields((fields) => ({
429
+ ...Struct.omit(fields, [
430
+ "version",
431
+ "topics",
432
+ "_deleted"
433
+ ]),
434
+ updated_at: Timestamp,
435
+ topics: optionalList(Schema.String)
436
+ }));
437
+ const RepositoryRefSchema = Schema.Struct({
438
+ id: Schema.Number,
439
+ full_name: Schema.String,
440
+ has_issues: Schema.Boolean,
441
+ has_pull_requests: Schema.Boolean.pipe(Schema.withDecodingDefaultKey(Effect.succeed(true))),
442
+ size: Schema.Number
443
+ });
444
+ //#endregion
445
+ //#region src/schemas/workflow-runs.ts
446
+ const WorkflowRunSchema = Schema.Struct({
447
+ id: Long.pipe(Schema.annotateKey({ description: "Unique workflow run ID." })),
448
+ version: version("workflow run"),
449
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the run." })),
450
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the run belongs to." })),
451
+ workflow_id: Long.pipe(Schema.annotateKey({ description: "ID of the workflow." })),
452
+ name: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Workflow name." })),
453
+ display_title: Schema.String.pipe(Schema.annotateKey({ description: "Title of the run, such as the commit message." })),
454
+ path: Schema.String.pipe(Schema.annotateKey({ description: "Path of the workflow file." })),
455
+ event: Schema.String.pipe(Schema.annotateKey({ description: "Event that started the run, such as push." })),
456
+ status: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Status, such as queued, in_progress, or completed." })),
457
+ conclusion: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Result of a completed run, such as success or failure." })),
458
+ head_branch: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Branch the run is for." })),
459
+ head_sha: Schema.String.pipe(Schema.annotateKey({ description: "SHA of the commit the run is for." })),
460
+ run_number: Long.pipe(Schema.annotateKey({ description: "Run number of the workflow." })),
461
+ run_attempt: Schema.optionalKey(Long).pipe(Schema.annotateKey({ description: "Attempt number. Re-runs count up." })),
462
+ check_suite_id: Schema.optionalKey(Long).pipe(Schema.annotateKey({ description: "ID of the run's check suite." })),
463
+ actor: Schema.optionalKey(user).pipe(Schema.annotateKey({ description: "Account that started the first attempt." })),
464
+ triggering_actor: Schema.optionalKey(user).pipe(Schema.annotateKey({ description: "Account that started the latest attempt." })),
465
+ pull_requests: Schema.Array(Schema.Struct({
466
+ id: Long.pipe(Schema.annotateKey({ description: "Pull request ID." })),
467
+ number: Long.pipe(Schema.annotateKey({ description: "Pull request number." }))
468
+ })).pipe(Schema.annotateKey({ description: "Pull requests the run is for. Empty for forks." })),
469
+ created_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the run was created." })),
470
+ run_started_at: Schema.optionalKey(Timestamp).pipe(Schema.annotateKey({ description: "Time the latest attempt started." })),
471
+ html_url: Schema.String.pipe(Schema.annotateKey({ description: "Run page on GitHub." }))
472
+ }).annotate({ description: "GitHub Actions workflow runs in the installation's repositories." }).pipe(Iceberg.identifier("id"));
473
+ const WorkflowRunObjectSchema = WorkflowRunSchema.mapFields((fields) => ({
474
+ ...Struct.omit(fields, [
475
+ "version",
476
+ "repository_id",
477
+ "pull_requests"
478
+ ]),
479
+ updated_at: Timestamp,
480
+ pull_requests: Schema.NullOr(fields.pull_requests)
481
+ }));
482
+ //#endregion
483
+ //#region src/schemas/events.ts
484
+ /**
485
+ * The webhook route passes the payload on as it is. GitHub puts the event name
486
+ * in a header, so each resource decodes its own event.
487
+ */
488
+ const WebhookPayloadSchema = Schema.Record(Schema.String, Schema.Unknown);
489
+ const InstallationEventSchema = Schema.Struct({ installation: Schema.optional(Schema.Struct({ id: Schema.Number })) });
490
+ /** For handlers that read the API again. */
491
+ const RepositoryNameSchema = Schema.Struct({
492
+ id: Long,
493
+ full_name: Schema.String
494
+ });
495
+ /** A transferred issue no longer exists in its old repository. */
496
+ const IssueRemovedEventSchema = Schema.Struct({
497
+ action: Schema.Literals(["deleted", "transferred"]),
498
+ issue: ObjectRefSchema
499
+ });
500
+ const IssuesEventSchema = Schema.Union([IssueRemovedEventSchema, Schema.Struct({
501
+ action: Schema.String,
502
+ repository: RepositoryNameSchema,
503
+ issue: IssueObjectSchema
504
+ })]);
505
+ const IssueCommentDeletedEventSchema = Schema.Struct({
506
+ action: Schema.Literal("deleted"),
507
+ comment: ObjectRefSchema
508
+ });
509
+ const IssueCommentEventSchema = Schema.Union([IssueCommentDeletedEventSchema, Schema.Struct({
510
+ action: Schema.String,
511
+ repository: ObjectRefSchema,
512
+ comment: IssueCommentObjectSchema
513
+ })]);
514
+ const PullRequestReviewCommentDeletedEventSchema = Schema.Struct({
515
+ action: Schema.Literal("deleted"),
516
+ comment: ObjectRefSchema
517
+ });
518
+ const PullRequestReviewCommentEventSchema = Schema.Union([PullRequestReviewCommentDeletedEventSchema, Schema.Struct({
519
+ action: Schema.String,
520
+ repository: ObjectRefSchema,
521
+ comment: PullRequestReviewCommentObjectSchema
522
+ })]);
523
+ const PullRequestEventSchema = Schema.Struct({
524
+ repository: RepositoryNameSchema,
525
+ pull_request: PullRequestObjectSchema
526
+ });
527
+ const RepositoryDeletedEventSchema = Schema.Struct({
528
+ action: Schema.Literal("deleted"),
529
+ repository: ObjectRefSchema
530
+ });
531
+ const RepositoryEventSchema = Schema.Union([RepositoryDeletedEventSchema, Schema.Struct({
532
+ action: Schema.String,
533
+ repository: RepositoryObjectSchema
534
+ })]);
535
+ const ReleaseDeletedEventSchema = Schema.Struct({
536
+ action: Schema.Literal("deleted"),
537
+ release: ObjectRefSchema
538
+ });
539
+ const ReleaseEventSchema = Schema.Union([ReleaseDeletedEventSchema, Schema.Struct({
540
+ action: Schema.String,
541
+ repository: ObjectRefSchema,
542
+ release: ReleaseObjectSchema
543
+ })]);
544
+ const WorkflowRunEventSchema = Schema.Struct({
545
+ repository: ObjectRefSchema,
546
+ workflow_run: WorkflowRunObjectSchema
547
+ });
548
+ const PushEventSchema = Schema.Struct({
549
+ ref: Schema.String,
550
+ before: Schema.String,
551
+ after: Schema.String,
552
+ repository: RepositoryNameSchema.mapFields(Struct.assign({ default_branch: Schema.String }))
553
+ });
554
+ const PullRequestReviewEventSchema = Schema.Struct({
555
+ repository: RepositoryNameSchema,
556
+ pull_request: Schema.Struct({ number: Long }),
557
+ review: ObjectRefSchema
558
+ });
559
+ const PullRequestCommitsEventSchema = Schema.Struct({
560
+ action: Schema.String,
561
+ changes: Schema.optionalKey(Schema.Struct({ base: Schema.optionalKey(Schema.Unknown) })),
562
+ repository: RepositoryNameSchema,
563
+ pull_request: Schema.Struct({
564
+ number: Long,
565
+ updated_at: Timestamp
566
+ })
567
+ });
568
+ const CheckRunEventSchema = Schema.Struct({
569
+ repository: RepositoryNameSchema,
570
+ check_run: ObjectRefSchema
571
+ });
572
+ const WorkflowJobEventSchema = Schema.Struct({
573
+ repository: RepositoryNameSchema,
574
+ workflow_job: ObjectRefSchema
575
+ });
576
+ /** The status payload is flat. */
577
+ const StatusEventSchema = Schema.Struct({
578
+ id: Long,
579
+ sha: Schema.String,
580
+ repository: RepositoryNameSchema
581
+ });
582
+ //#endregion
583
+ //#region src/schemas/commits.ts
584
+ const gitActor = Schema.Struct({
585
+ name: Schema.String.pipe(Schema.annotateKey({ description: "Name in the commit." })),
586
+ date: Timestamp.pipe(Schema.annotateKey({ description: "Time in the commit." }))
587
+ });
588
+ /**
589
+ * The GitHub account of a commit's author or committer. GitHub sends `null` or
590
+ * `{}` when the email matches no account, and both become `null`.
591
+ */
592
+ const account = Schema.NullOr(Schema.Union([user, Schema.Struct({})])).pipe(Schema.decodeTo(Schema.NullOr(Schema.toType(user)), SchemaTransformation.transform({
593
+ decode: (value) => value !== null && "id" in value ? value : null,
594
+ encode: (value) => value
595
+ })));
596
+ const CommitSchema = Schema.Struct({
597
+ key: Schema.String.pipe(Schema.annotateKey({ description: "Repository ID and SHA, such as `1406396726:9eb3a7d`." })),
598
+ version: Schema.Date.pipe(Schema.annotateKey({ description: "Commit time. A commit never changes." })),
599
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the commit is in." })),
600
+ sha: Schema.String.pipe(Schema.annotateKey({ description: "Commit SHA." })),
601
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the commit." })),
602
+ message: Schema.String.pipe(Schema.annotateKey({ description: "Commit message." })),
603
+ author: gitActor.pipe(Schema.annotateKey({ description: "Author name and time from the commit." })),
604
+ committer: gitActor.pipe(Schema.annotateKey({ description: "Committer name and time from the commit." })),
605
+ author_user: account.pipe(Schema.annotateKey({ description: "GitHub account of the author." })),
606
+ committer_user: account.pipe(Schema.annotateKey({ description: "GitHub account of the committer." })),
607
+ parents: Schema.Array(Schema.String).pipe(Schema.annotateKey({ description: "SHAs of the parent commits." })),
608
+ verified: Schema.Boolean.pipe(Schema.annotateKey({ description: "Whether GitHub verified the commit signature." })),
609
+ html_url: Schema.String.pipe(Schema.annotateKey({ description: "Commit page on GitHub." }))
610
+ }).annotate({ description: "Commits on the default branch of the installation's repositories." }).pipe(Iceberg.identifier("key"));
611
+ const CommitObjectSchema = Schema.Struct({
612
+ sha: Schema.String,
613
+ node_id: Schema.String,
614
+ commit: Schema.Struct({
615
+ author: gitActor,
616
+ committer: gitActor,
617
+ message: Schema.String,
618
+ verification: Schema.optionalKey(Schema.Struct({ verified: Schema.Boolean }))
619
+ }),
620
+ author: account,
621
+ committer: account,
622
+ parents: Schema.Array(Schema.Struct({ sha: Schema.String })),
623
+ html_url: Schema.String
624
+ });
625
+ //#endregion
626
+ //#region src/resources/shared.ts
627
+ const timestamps = {
628
+ createdAt: (item) => item.created_at,
629
+ updatedAt: (item) => item.updated_at
630
+ };
631
+ const changesInterval = Duration.minutes(15);
632
+ const maxChangesPagesPerRun = 20;
633
+ const changesOverlap = Duration.minutes(5);
634
+ const repositoryListTtl = Duration.minutes(5);
635
+ const installationRepositoriesPath = "/installation/repositories";
636
+ const repositoryPage = (item) => Schema.Struct({ repositories: Schema.Array(item) });
637
+ /** Some lists wrap their items, such as `{ total_count, workflow_runs: [...] }`. */
638
+ const wrappedList = (key, item) => Schema.Struct({ [key]: Schema.Array(item) }).pipe(Schema.decodeTo(Schema.Array(Schema.toType(item)), SchemaTransformation.transform({
639
+ decode: (page) => page[key],
640
+ encode: (items) => ({ [key]: items })
641
+ })));
642
+ const readPages = (client, pageSchema, url, keepGoing = () => true) => Stream.paginate(url, (current) => client.get(pageSchema, current).pipe(Effect.map((page) => [page.body, keepGoing(page.body) ? page.next : Option.none()]))).pipe(Stream.runCollect);
643
+ /** Every repository the installation can read, ordered by ID. */
644
+ const listRepositories = (client, schema) => readPages(client, wrappedList("repositories", schema), withParams(installationRepositoriesPath, { per_page: "100" })).pipe(Effect.map(Array.sort(Order.mapInput(Order.Number, (repository) => Number(repository.id)))));
645
+ const cachedRepositoryRefs = (client) => listRepositories(client, RepositoryRefSchema).pipe(Effect.cachedWithTTL((exit) => Exit.isSuccess(exit) ? repositoryListTtl : 0));
646
+ /**
647
+ * `since` is when the last run started, and `repositoryIds` are the
648
+ * repositories it read. Any other repository is new and is read from the start.
649
+ * `pending` is set while a run is split.
650
+ */
651
+ const ChangesCursorSchema = Schema.Struct({
652
+ since: Schema.DateTimeUtcFromString,
653
+ repositoryIds: Schema.optional(Schema.Array(Schema.Number)),
654
+ pending: Schema.optional(Schema.Struct({
655
+ until: Schema.DateTimeUtcFromString,
656
+ repositoryIds: Schema.Array(Schema.Number),
657
+ repositoryId: Schema.Number,
658
+ url: Schema.optional(Schema.String)
659
+ }))
660
+ });
661
+ const ChangesCursorCodec = Schema.Union([Schema.fromJsonString(ChangesCursorSchema), Schema.DateTimeUtcFromString.pipe(Schema.decodeTo(Schema.toType(ChangesCursorSchema), SchemaTransformation.transform({
662
+ decode: (since) => ({ since }),
663
+ encode: (cursor) => cursor.since
664
+ })))]);
665
+ const cursorError = (cause) => new ConnectorError({
666
+ message: "Invalid GitHub cursor",
667
+ cause
668
+ });
669
+ const decodeChangesCursor = (value) => Schema.decodeUnknownEffect(ChangesCursorCodec)(value).pipe(Effect.mapError(cursorError));
670
+ const encodeChangesCursor = (cursor) => Schema.encodeEffect(Schema.fromJsonString(ChangesCursorSchema))(cursor).pipe(Effect.mapError(cursorError));
671
+ /** When to read a repository's changes from. None means from the start. */
672
+ const readFrom = (state, repositoryId) => state.repositoryIds === void 0 || state.repositoryIds.includes(repositoryId) ? Option.some(DateTime.toDateUtc(DateTime.subtractDuration(state.since, changesOverlap))) : Option.none();
673
+ const isSince = (time, date) => Option.match(time, {
674
+ onNone: () => true,
675
+ onSome: (since) => date.getTime() >= since.getTime()
676
+ });
677
+ const readChanges = (cursor, repositories, toRows) => Effect.gen(function* () {
678
+ const state = yield* decodeChangesCursor(cursor);
679
+ const runStart = yield* DateTime.now;
680
+ const listed = yield* repositories;
681
+ return {
682
+ rows: toRows(listed.map((repository) => ({
683
+ repository,
684
+ since: readFrom(state, Number(repository.id))
685
+ }))),
686
+ cursor: yield* encodeChangesCursor({
687
+ since: runStart,
688
+ repositoryIds: listed.map((repository) => Number(repository.id))
689
+ })
690
+ };
691
+ });
692
+ const rowsOf = (toRow) => (items, repository) => Effect.succeed(items.map((item) => toRow(item, BigInt(repository.id))));
693
+ const repoPath = (repository, path) => `/repos/${repository.full_name}/${path}`;
694
+ /**
695
+ * `rows` for a child resource, such as reviews of pull requests. Parents are
696
+ * read one at a time, as GitHub asks. `readAt` is the version for children
697
+ * without an update time.
698
+ */
699
+ const childRows = (client, child) => (parents, repository) => Effect.gen(function* () {
700
+ const readAt = yield* DateTime.nowAsDate;
701
+ return (yield* Effect.forEach(parents, (parent) => readPages(client, child.pageSchema, withParams(repoPath(repository, child.path(parent)), {
702
+ ...child.params,
703
+ per_page: "100"
704
+ })).pipe(Effect.map((children) => children.map((item) => child.toRow(item, parent, BigInt(repository.id), readAt)))))).flat();
705
+ });
706
+ /** Lists one item, then reads its rows, so a child resource checks its own call too. */
707
+ const checkRepoResource = (client, spec) => Effect.gen(function* () {
708
+ const page = yield* client.get(repositoryPage(RepositoryRefSchema), withParams(installationRepositoriesPath, { per_page: "100" }));
709
+ const repository = Array.findFirst(page.body.repositories, spec.enabled);
710
+ if (Option.isSome(repository)) {
711
+ const items = yield* client.get(spec.pageSchema, withParams(repoPath(repository.value, spec.path), {
712
+ ...spec.params,
713
+ per_page: "1"
714
+ }));
715
+ yield* spec.rows(items.body.filter(spec.keep), repository.value);
716
+ }
717
+ });
718
+ const BackfillCursorSchema = Schema.fromJsonString(Schema.Struct({
719
+ repositoryId: Schema.Number,
720
+ next: Schema.optional(Schema.String)
721
+ }));
722
+ const decodeBackfillCursor = (value) => Schema.decodeUnknownEffect(BackfillCursorSchema)(value).pipe(Effect.mapError(cursorError));
723
+ const encodeBackfillCursor = (cursor) => Schema.encodeEffect(BackfillCursorSchema)(cursor).pipe(Effect.mapError(cursorError));
724
+ const parseCutoff = (value) => DateTime.toDateUtc(DateTime.makeUnsafe(value));
725
+ /**
726
+ * Reads one repository at a time up to the cutoff, oldest first when the list
727
+ * can sort. The cursor keeps the `next` URL, because the issues list pages
728
+ * with a cursor.
729
+ */
730
+ const backfillRepoResource = (client, repositories, spec) => Fetch.page({
731
+ pageCursor: Cursor.string(),
732
+ cutoff: Cursor.isoDateTime(),
733
+ fetch: ({ pageCursor, cutoff }) => Effect.gen(function* () {
734
+ const cutoffDate = parseCutoff(String(cutoff));
735
+ const listed = (yield* repositories).filter(spec.enabled);
736
+ const position = typeof pageCursor === "string" ? Option.some(yield* decodeBackfillCursor(pageCursor)) : Option.none();
737
+ const repository = Option.match(position, {
738
+ onNone: () => listed[0],
739
+ onSome: ({ repositoryId }) => listed.find((item) => item.id >= repositoryId)
740
+ });
741
+ if (repository === void 0) return {
742
+ rows: [],
743
+ hasMore: false
744
+ };
745
+ const url = Option.flatMap(position, ({ repositoryId, next }) => repositoryId === repository.id ? Option.fromNullishOr(next) : Option.none()).pipe(Option.getOrElse(() => withParams(repoPath(repository, spec.path), {
746
+ ...spec.params,
747
+ ...spec.order === "sorted" ? {
748
+ sort: "created",
749
+ direction: "asc"
750
+ } : {},
751
+ per_page: "100"
752
+ })));
753
+ const page = yield* client.get(spec.pageSchema, url);
754
+ const beforeCutoff = page.body.filter((item) => spec.createdAt(item).getTime() <= cutoffDate.getTime());
755
+ const rows = yield* spec.rows(beforeCutoff.filter(spec.keep), repository);
756
+ if (!(spec.order === "sorted" && beforeCutoff.length < page.body.length) && Option.isSome(page.next)) return {
757
+ rows,
758
+ nextPageCursor: yield* encodeBackfillCursor({
759
+ repositoryId: repository.id,
760
+ next: page.next.value
761
+ }),
762
+ hasMore: true
763
+ };
764
+ const following = listed.find((item) => item.id > repository.id);
765
+ if (following === void 0) return {
766
+ rows,
767
+ hasMore: false
768
+ };
769
+ return {
770
+ rows,
771
+ nextPageCursor: yield* encodeBackfillCursor({ repositoryId: following.id }),
772
+ hasMore: true
773
+ };
774
+ })
775
+ });
776
+ /**
777
+ * One repository's changes, newest first, so an edit during the read repeats a
778
+ * row instead of skipping one.
779
+ */
780
+ const changesPages = (client, spec, repository, since, url) => {
781
+ const first = withParams(repoPath(repository, spec.path), {
782
+ ...spec.params,
783
+ ...spec.order === "sorted" ? {
784
+ sort: "updated",
785
+ direction: "desc"
786
+ } : {},
787
+ per_page: "100",
788
+ ...spec.since && Option.isSome(since) ? { since: since.value.toISOString() } : {}
789
+ });
790
+ const isRecent = (item) => isSince(since, spec.updatedAt(item));
791
+ const { order, recentFor } = spec;
792
+ const createdFrom = Option.map(since, (date) => DateTime.toDateUtc(DateTime.subtractDuration(DateTime.fromDateUnsafe(date), recentFor ?? Duration.zero)));
793
+ const goOn = (items) => {
794
+ if (order === "sorted") return items.every(isRecent);
795
+ if (recentFor === void 0) return true;
796
+ return items.every((item) => isSince(createdFrom, spec.createdAt(item)));
797
+ };
798
+ return Stream.paginate(Option.getOrElse(url, () => first), (current) => Effect.gen(function* () {
799
+ const page = yield* client.get(spec.pageSchema, current);
800
+ const next = goOn(page.body) ? page.next : Option.none();
801
+ return [[{
802
+ repository,
803
+ rows: yield* spec.rows(page.body.filter((item) => isRecent(item) && spec.keep(item)), repository),
804
+ next
805
+ }], next];
806
+ }));
807
+ };
808
+ const changesRepoResource = (client, spec) => Fetch.changes({
809
+ cursor: Cursor.string(),
810
+ interval: changesInterval,
811
+ fetch: ({ cursor }) => Effect.gen(function* () {
812
+ const state = yield* decodeChangesCursor(String(cursor));
813
+ const listed = yield* listRepositories(client, RepositoryRefSchema);
814
+ const pending = state.pending;
815
+ const until = pending?.until ?? (yield* DateTime.now);
816
+ const repositoryIds = pending?.repositoryIds ?? listed.filter(spec.enabled).map((repository) => repository.id);
817
+ const repositories = listed.filter((repository) => spec.enabled(repository) && repositoryIds.includes(repository.id) && repository.id >= (pending?.repositoryId ?? 0));
818
+ const pages = yield* Stream.fromIterable(repositories).pipe(Stream.flatMap((repository) => changesPages(client, spec, repository, readFrom(state, repository.id), Option.fromNullishOr(pending?.repositoryId === repository.id ? pending.url : void 0))), Stream.take(maxChangesPagesPerRun), Stream.runCollect);
819
+ const rows = pages.flatMap((page) => page.rows);
820
+ const position = Option.flatMap(Array.last(pages), ({ repository, next }) => Option.match(next, {
821
+ onSome: (url) => Option.some({
822
+ repositoryId: repository.id,
823
+ url
824
+ }),
825
+ onNone: () => Array.findFirst(repositories, (item) => item.id > repository.id).pipe(Option.map((item) => ({ repositoryId: item.id })))
826
+ }));
827
+ if (pages.length === maxChangesPagesPerRun && Option.isSome(position)) return {
828
+ rows,
829
+ cursor: yield* encodeChangesCursor({
830
+ ...state,
831
+ pending: {
832
+ until,
833
+ repositoryIds,
834
+ ...position.value
835
+ }
836
+ }),
837
+ hasMore: true
838
+ };
839
+ return {
840
+ rows,
841
+ cursor: yield* encodeChangesCursor({
842
+ since: until,
843
+ repositoryIds
844
+ }),
845
+ hasMore: false
846
+ };
847
+ })
848
+ });
849
+ /** Schemas check that the URL ends in a number. */
850
+ const numberAtEnd = (url) => BigInt(url.slice(url.lastIndexOf("/") + 1));
851
+ const deleteRow = (id) => DateTime.nowAsDate.pipe(Effect.map((version) => ({
852
+ id,
853
+ version,
854
+ _deleted: true
855
+ })));
856
+ const decodeEvent = (schema, payload) => Schema.decodeUnknownEffect(schema)(payload).pipe(Effect.mapError((cause) => new ConnectorError({
857
+ message: "GitHub webhook payload does not decode",
858
+ cause
859
+ })));
860
+ //#endregion
861
+ //#region src/resources/commits.ts
862
+ const toRow$12 = ({ sha, node_id, commit, author, committer, parents, html_url }, repository_id) => ({
863
+ key: `${repository_id}:${sha}`,
864
+ version: commit.committer.date,
865
+ repository_id,
866
+ sha,
867
+ node_id,
868
+ message: commit.message,
869
+ author: commit.author,
870
+ committer: commit.committer,
871
+ author_user: author,
872
+ committer_user: committer,
873
+ parents: parents.map((parent) => parent.sha),
874
+ verified: commit.verification?.verified ?? false,
875
+ html_url
876
+ });
877
+ const committedAt = (item) => item.commit.committer.date;
878
+ /** The list only has the default branch. */
879
+ const commitSpec = {
880
+ path: "commits",
881
+ pageSchema: Schema.Array(CommitObjectSchema),
882
+ params: {},
883
+ since: true,
884
+ order: "newest",
885
+ createdAt: committedAt,
886
+ updatedAt: committedAt,
887
+ keep: () => true,
888
+ enabled: (repository) => repository.size > 0,
889
+ rows: rowsOf(toRow$12)
890
+ };
891
+ const noCommit = /^0+$/;
892
+ const makeCommits = (client, repositories) => Resource.entity({
893
+ name: "commits",
894
+ rowSchema: CommitSchema,
895
+ version: "version",
896
+ check: checkRepoResource(client, commitSpec),
897
+ backfill: backfillRepoResource(client, repositories, commitSpec),
898
+ changes: changesRepoResource(client, commitSpec),
899
+ webhook: {
900
+ schema: WebhookPayloadSchema,
901
+ handler: ({ payload }) => Effect.gen(function* () {
902
+ const { ref, before, after, repository } = yield* decodeEvent(PushEventSchema, payload);
903
+ if (ref !== `refs/heads/${repository.default_branch}` || noCommit.test(before) || noCommit.test(after)) return [];
904
+ return (yield* readPages(client, wrappedList("commits", CommitObjectSchema), withParams(`/repos/${repository.full_name}/compare/${before}...${after}`, { per_page: "100" }))).map((commit) => toRow$12(commit, repository.id));
905
+ })
906
+ }
907
+ });
908
+ //#endregion
909
+ //#region src/resources/check-runs.ts
910
+ const toRow$11 = (checkRun, repository_id, version) => ({
911
+ ...checkRun,
912
+ version,
913
+ repository_id
914
+ });
915
+ /** Check runs of default-branch commits. Webhooks also bring other branches. */
916
+ const checkRunSpec = (client) => ({
917
+ ...commitSpec,
918
+ rows: childRows(client, {
919
+ path: (commit) => `commits/${commit.sha}/check-runs`,
920
+ pageSchema: wrappedList("check_runs", CheckRunObjectSchema),
921
+ params: { filter: "all" },
922
+ toRow: (checkRun, _commit, repositoryId, readAt) => toRow$11(checkRun, repositoryId, readAt)
923
+ })
924
+ });
925
+ const makeCheckRuns = (client, repositories) => {
926
+ const spec = checkRunSpec(client);
927
+ return Resource.entity({
928
+ name: "check_runs",
929
+ rowSchema: CheckRunSchema,
930
+ version: "version",
931
+ check: checkRepoResource(client, spec),
932
+ backfill: backfillRepoResource(client, repositories, spec),
933
+ changes: changesRepoResource(client, spec),
934
+ webhook: {
935
+ schema: WebhookPayloadSchema,
936
+ handler: ({ payload }) => Effect.gen(function* () {
937
+ const { repository, check_run } = yield* decodeEvent(CheckRunEventSchema, payload);
938
+ const readAt = yield* DateTime.nowAsDate;
939
+ const page = yield* client.get(CheckRunObjectSchema, `/repos/${repository.full_name}/check-runs/${check_run.id}`);
940
+ return [toRow$11(page.body, repository.id, readAt)];
941
+ })
942
+ }
943
+ });
944
+ };
945
+ //#endregion
946
+ //#region src/schemas/commit-statuses.ts
947
+ const CommitStatusSchema = Schema.Struct({
948
+ id: Long.pipe(Schema.annotateKey({ description: "Unique status ID. Each new status of a context gets a new ID." })),
949
+ version: version("status"),
950
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the status." })),
951
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the status belongs to." })),
952
+ sha: Schema.String.pipe(Schema.annotateKey({ description: "SHA of the commit the status is on." })),
953
+ state: Schema.String.pipe(Schema.annotateKey({ description: "State: error, failure, pending, or success." })),
954
+ context: Schema.String.pipe(Schema.annotateKey({ description: "Label that tells the status apart, such as ci/build." })),
955
+ description: Text.pipe(Schema.annotateKey({ description: "Short description. Empty when not set." })),
956
+ target_url: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Link the status points to." })),
957
+ creator: Schema.NullOr(user).pipe(Schema.annotateKey({ description: "Account that created the status." })),
958
+ created_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the status was created." }))
959
+ }).annotate({ description: "Commit statuses. Polled for default-branch commits; webhooks cover all branches." }).pipe(Iceberg.identifier("id"));
960
+ const CommitStatusObjectSchema = CommitStatusSchema.mapFields((fields) => ({
961
+ ...Struct.omit(fields, [
962
+ "version",
963
+ "repository_id",
964
+ "sha"
965
+ ]),
966
+ updated_at: Timestamp
967
+ }));
968
+ //#endregion
969
+ //#region src/resources/commit-statuses.ts
970
+ const toRow$10 = ({ updated_at, ...rest }, sha, repository_id) => ({
971
+ ...rest,
972
+ version: updated_at,
973
+ repository_id,
974
+ sha
975
+ });
976
+ /** Statuses of default-branch commits. Webhooks also bring other branches. */
977
+ const commitStatusSpec = (client) => ({
978
+ ...commitSpec,
979
+ rows: childRows(client, {
980
+ path: (commit) => `commits/${commit.sha}/statuses`,
981
+ pageSchema: Schema.Array(CommitStatusObjectSchema),
982
+ toRow: (status, commit, repositoryId) => toRow$10(status, commit.sha, repositoryId)
983
+ })
984
+ });
985
+ const makeCommitStatuses = (client, repositories) => {
986
+ const spec = commitStatusSpec(client);
987
+ return Resource.entity({
988
+ name: "commit_statuses",
989
+ rowSchema: CommitStatusSchema,
990
+ version: "version",
991
+ check: checkRepoResource(client, spec),
992
+ backfill: backfillRepoResource(client, repositories, spec),
993
+ changes: changesRepoResource(client, spec),
994
+ webhook: {
995
+ schema: WebhookPayloadSchema,
996
+ handler: ({ payload }) => Effect.gen(function* () {
997
+ const { id, sha, repository } = yield* decodeEvent(StatusEventSchema, payload);
998
+ const isStatus = (status) => status.id === id;
999
+ const status = yield* readPages(client, Schema.Array(CommitStatusObjectSchema), withParams(`/repos/${repository.full_name}/commits/${sha}/statuses`, { per_page: "100" }), (page) => !page.some(isStatus)).pipe(Effect.flatMap((statuses) => Effect.fromOption(Array.findFirst(statuses, isStatus), () => new ConnectorError({ message: `Commit status ${id} is not in the list` }))));
1000
+ return [toRow$10(status, sha, repository.id)];
1001
+ })
1002
+ }
1003
+ });
1004
+ };
1005
+ //#endregion
1006
+ //#region src/resources/issue-comments.ts
1007
+ const toRow$9 = ({ updated_at, issue_url, ...rest }, repository_id) => ({
1008
+ ...rest,
1009
+ version: updated_at,
1010
+ repository_id,
1011
+ issue_number: numberAtEnd(issue_url)
1012
+ });
1013
+ const issueCommentSpec = {
1014
+ path: "issues/comments",
1015
+ pageSchema: Schema.Array(IssueCommentObjectSchema),
1016
+ params: {},
1017
+ since: true,
1018
+ order: "sorted",
1019
+ ...timestamps,
1020
+ keep: () => true,
1021
+ enabled: (repository) => repository.has_issues || repository.has_pull_requests,
1022
+ rows: rowsOf(toRow$9)
1023
+ };
1024
+ const makeIssueComments = (client, repositories) => Resource.entity({
1025
+ name: "issue_comments",
1026
+ rowSchema: IssueCommentSchema,
1027
+ version: "version",
1028
+ check: checkRepoResource(client, issueCommentSpec),
1029
+ backfill: backfillRepoResource(client, repositories, issueCommentSpec),
1030
+ changes: changesRepoResource(client, issueCommentSpec),
1031
+ webhook: {
1032
+ schema: WebhookPayloadSchema,
1033
+ handler: ({ payload }) => Effect.gen(function* () {
1034
+ const event = yield* decodeEvent(IssueCommentEventSchema, payload);
1035
+ if (Schema.is(IssueCommentDeletedEventSchema)(event)) return [yield* deleteRow(event.comment.id)];
1036
+ return [toRow$9(event.comment, event.repository.id)];
1037
+ })
1038
+ }
1039
+ });
1040
+ //#endregion
1041
+ //#region src/resources/issues.ts
1042
+ const toRow$8 = ({ updated_at, labels, assignees, pull_request: _pullRequest, ...rest }, repository_id) => ({
1043
+ ...rest,
1044
+ version: updated_at,
1045
+ repository_id,
1046
+ labels: labels ?? [],
1047
+ assignees: assignees ?? []
1048
+ });
1049
+ const hasLists$1 = ({ labels }) => labels !== void 0;
1050
+ const issueSpec = {
1051
+ path: "issues",
1052
+ pageSchema: Schema.Array(IssueObjectSchema),
1053
+ params: { state: "all" },
1054
+ since: true,
1055
+ order: "sorted",
1056
+ ...timestamps,
1057
+ keep: (item) => item.pull_request === void 0,
1058
+ enabled: (repository) => repository.has_issues,
1059
+ rows: rowsOf(toRow$8)
1060
+ };
1061
+ const makeIssues = (client, repositories) => Resource.entity({
1062
+ name: "issues",
1063
+ rowSchema: IssueSchema,
1064
+ version: "version",
1065
+ check: checkRepoResource(client, issueSpec),
1066
+ backfill: backfillRepoResource(client, repositories, issueSpec),
1067
+ changes: changesRepoResource(client, issueSpec),
1068
+ webhook: {
1069
+ schema: WebhookPayloadSchema,
1070
+ handler: ({ payload }) => Effect.gen(function* () {
1071
+ const event = yield* decodeEvent(IssuesEventSchema, payload);
1072
+ if (Schema.is(IssueRemovedEventSchema)(event)) return [yield* deleteRow(event.issue.id)];
1073
+ const { issue, repository } = event;
1074
+ const full = hasLists$1(issue) ? issue : (yield* client.get(IssueObjectSchema, `/repos/${repository.full_name}/issues/${issue.number}`)).body;
1075
+ return [toRow$8(full, repository.id)];
1076
+ })
1077
+ }
1078
+ });
1079
+ //#endregion
1080
+ //#region src/schemas/pull-request-commits.ts
1081
+ const PullRequestCommitSchema = Schema.Struct({
1082
+ key: Schema.String.pipe(Schema.annotateKey({ description: "Repository ID, pull request number, and SHA, such as `1406396726:3:9eb3a7d`." })),
1083
+ version: Schema.Date.pipe(Schema.annotateKey({ description: "Time GitHub last updated the pull request." })),
1084
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the pull request belongs to." })),
1085
+ pull_request_number: Long.pipe(Schema.annotateKey({ description: "Pull request number in the repository." })),
1086
+ sha: Schema.String.pipe(Schema.annotateKey({ description: "Commit SHA." })),
1087
+ message: Schema.String.pipe(Schema.annotateKey({ description: "Commit message." })),
1088
+ author: gitActor.pipe(Schema.annotateKey({ description: "Author name and time from the commit." })),
1089
+ committer: gitActor.pipe(Schema.annotateKey({ description: "Committer name and time from the commit." })),
1090
+ author_user: account.pipe(Schema.annotateKey({ description: "GitHub account of the author." })),
1091
+ committer_user: account.pipe(Schema.annotateKey({ description: "GitHub account of the committer." })),
1092
+ html_url: Schema.String.pipe(Schema.annotateKey({ description: "Commit page on GitHub." }))
1093
+ }).annotate({ description: "Commits in pull requests, up to 250 per pull request. Commits removed by a force push stay." }).pipe(Iceberg.identifier("key"));
1094
+ //#endregion
1095
+ //#region src/resources/pull-requests.ts
1096
+ const toRow$7 = ({ updated_at, labels, assignees, requested_reviewers, ...rest }, repository_id) => ({
1097
+ ...rest,
1098
+ version: updated_at,
1099
+ repository_id,
1100
+ labels: labels ?? [],
1101
+ assignees: assignees ?? [],
1102
+ requested_reviewers: requested_reviewers ?? []
1103
+ });
1104
+ const hasLists = ({ assignees, requested_reviewers }) => assignees !== void 0 && requested_reviewers !== void 0;
1105
+ const pullRequestSpec = {
1106
+ path: "pulls",
1107
+ pageSchema: Schema.Array(PullRequestObjectSchema),
1108
+ params: { state: "all" },
1109
+ since: false,
1110
+ order: "sorted",
1111
+ ...timestamps,
1112
+ keep: () => true,
1113
+ enabled: (repository) => repository.has_pull_requests,
1114
+ rows: rowsOf(toRow$7)
1115
+ };
1116
+ const makePullRequests = (client, repositories) => Resource.entity({
1117
+ name: "pull_requests",
1118
+ rowSchema: PullRequestSchema,
1119
+ version: "version",
1120
+ check: checkRepoResource(client, pullRequestSpec),
1121
+ backfill: backfillRepoResource(client, repositories, pullRequestSpec),
1122
+ changes: changesRepoResource(client, pullRequestSpec),
1123
+ webhook: {
1124
+ schema: WebhookPayloadSchema,
1125
+ handler: ({ payload }) => Effect.gen(function* () {
1126
+ const { pull_request, repository } = yield* decodeEvent(PullRequestEventSchema, payload);
1127
+ const full = hasLists(pull_request) ? pull_request : (yield* client.get(PullRequestObjectSchema, `/repos/${repository.full_name}/pulls/${pull_request.number}`)).body;
1128
+ return [toRow$7(full, repository.id)];
1129
+ })
1130
+ }
1131
+ });
1132
+ //#endregion
1133
+ //#region src/resources/pull-request-commits.ts
1134
+ const toRow$6 = ({ sha, commit, author, committer, html_url }, pullRequest, repository_id) => ({
1135
+ key: `${repository_id}:${pullRequest.number}:${sha}`,
1136
+ version: pullRequest.updated_at,
1137
+ repository_id,
1138
+ pull_request_number: pullRequest.number,
1139
+ sha,
1140
+ message: commit.message,
1141
+ author: commit.author,
1142
+ committer: commit.committer,
1143
+ author_user: author,
1144
+ committer_user: committer,
1145
+ html_url
1146
+ });
1147
+ const commitsPath = (pullRequest) => `pulls/${pullRequest.number}/commits`;
1148
+ const pullRequestCommitSpec = (client) => ({
1149
+ ...pullRequestSpec,
1150
+ rows: childRows(client, {
1151
+ path: (pullRequest) => commitsPath(pullRequest),
1152
+ pageSchema: Schema.Array(CommitObjectSchema),
1153
+ toRow: toRow$6
1154
+ })
1155
+ });
1156
+ const commitActions = /* @__PURE__ */ new Set([
1157
+ "opened",
1158
+ "reopened",
1159
+ "synchronize"
1160
+ ]);
1161
+ const makePullRequestCommits = (client, repositories) => {
1162
+ const spec = pullRequestCommitSpec(client);
1163
+ return Resource.entity({
1164
+ name: "pull_request_commits",
1165
+ rowSchema: PullRequestCommitSchema,
1166
+ version: "version",
1167
+ check: checkRepoResource(client, spec),
1168
+ backfill: backfillRepoResource(client, repositories, spec),
1169
+ changes: changesRepoResource(client, spec),
1170
+ webhook: {
1171
+ schema: WebhookPayloadSchema,
1172
+ handler: ({ payload }) => Effect.gen(function* () {
1173
+ const { action, changes, repository, pull_request } = yield* decodeEvent(PullRequestCommitsEventSchema, payload);
1174
+ if (!commitActions.has(action) && changes?.base === void 0) return [];
1175
+ return (yield* readPages(client, Schema.Array(CommitObjectSchema), withParams(`/repos/${repository.full_name}/${commitsPath(pull_request)}`, { per_page: "100" }))).map((commit) => toRow$6(commit, pull_request, repository.id));
1176
+ })
1177
+ }
1178
+ });
1179
+ };
1180
+ //#endregion
1181
+ //#region src/resources/pull-request-review-comments.ts
1182
+ const toRow$5 = ({ updated_at, pull_request_url, ...rest }, repository_id) => ({
1183
+ ...rest,
1184
+ version: updated_at,
1185
+ repository_id,
1186
+ pull_request_number: numberAtEnd(pull_request_url)
1187
+ });
1188
+ const pullRequestReviewCommentSpec = {
1189
+ path: "pulls/comments",
1190
+ pageSchema: Schema.Array(PullRequestReviewCommentObjectSchema),
1191
+ params: {},
1192
+ since: true,
1193
+ order: "sorted",
1194
+ ...timestamps,
1195
+ keep: () => true,
1196
+ enabled: (repository) => repository.has_pull_requests,
1197
+ rows: rowsOf(toRow$5)
1198
+ };
1199
+ const makePullRequestReviewComments = (client, repositories) => Resource.entity({
1200
+ name: "pull_request_review_comments",
1201
+ rowSchema: PullRequestReviewCommentSchema,
1202
+ version: "version",
1203
+ check: checkRepoResource(client, pullRequestReviewCommentSpec),
1204
+ backfill: backfillRepoResource(client, repositories, pullRequestReviewCommentSpec),
1205
+ changes: changesRepoResource(client, pullRequestReviewCommentSpec),
1206
+ webhook: {
1207
+ schema: WebhookPayloadSchema,
1208
+ handler: ({ payload }) => Effect.gen(function* () {
1209
+ const event = yield* decodeEvent(PullRequestReviewCommentEventSchema, payload);
1210
+ if (Schema.is(PullRequestReviewCommentDeletedEventSchema)(event)) return [yield* deleteRow(event.comment.id)];
1211
+ return [toRow$5(event.comment, event.repository.id)];
1212
+ })
1213
+ }
1214
+ });
1215
+ //#endregion
1216
+ //#region src/schemas/pull-request-reviews.ts
1217
+ const PullRequestReviewSchema = Schema.Struct({
1218
+ id: Long.pipe(Schema.annotateKey({ description: "Unique review ID." })),
1219
+ version: readVersion("review"),
1220
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the review." })),
1221
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the review belongs to." })),
1222
+ pull_request_number: Long.pipe(Schema.annotateKey({ description: "Number of the pull request the review is on, in its repository." })),
1223
+ user: Schema.NullOr(user).pipe(Schema.annotateKey({ description: "Account that wrote the review." })),
1224
+ author_association: Schema.String.pipe(Schema.annotateKey({ description: "Author's relation to the repository, such as OWNER or CONTRIBUTOR." })),
1225
+ state: Schema.String.pipe(Schema.annotateKey({ description: "State, such as APPROVED, CHANGES_REQUESTED, COMMENTED, or DISMISSED." })),
1226
+ body: Schema.String.pipe(Schema.annotateKey({ description: "Review summary in Markdown." })),
1227
+ commit_id: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "SHA of the commit the review is on." })),
1228
+ submitted_at: Schema.optionalKey(Timestamp).pipe(Schema.annotateKey({ description: "Time the review was submitted." })),
1229
+ html_url: Schema.String.pipe(Schema.annotateKey({ description: "Review on GitHub." }))
1230
+ }).annotate({ description: "Reviews on pull requests. Pending reviews are only visible to their author." }).pipe(Iceberg.identifier("id"));
1231
+ const PullRequestReviewObjectSchema = PullRequestReviewSchema.mapFields((fields) => Struct.omit(fields, [
1232
+ "version",
1233
+ "repository_id",
1234
+ "pull_request_number"
1235
+ ]));
1236
+ //#endregion
1237
+ //#region src/resources/pull-request-reviews.ts
1238
+ const toRow$4 = (review, pull_request_number, repository_id, version) => ({
1239
+ ...review,
1240
+ version,
1241
+ repository_id,
1242
+ pull_request_number
1243
+ });
1244
+ /** A review bumps its pull request's update time, so changes follow pull requests. */
1245
+ const reviewSpec = (client) => ({
1246
+ ...pullRequestSpec,
1247
+ rows: childRows(client, {
1248
+ path: (pullRequest) => `pulls/${pullRequest.number}/reviews`,
1249
+ pageSchema: Schema.Array(PullRequestReviewObjectSchema),
1250
+ toRow: (review, pullRequest, repositoryId, readAt) => toRow$4(review, pullRequest.number, repositoryId, readAt)
1251
+ })
1252
+ });
1253
+ const makePullRequestReviews = (client, repositories) => {
1254
+ const spec = reviewSpec(client);
1255
+ return Resource.entity({
1256
+ name: "pull_request_reviews",
1257
+ rowSchema: PullRequestReviewSchema,
1258
+ version: "version",
1259
+ check: checkRepoResource(client, spec),
1260
+ backfill: backfillRepoResource(client, repositories, spec),
1261
+ changes: changesRepoResource(client, spec),
1262
+ webhook: {
1263
+ schema: WebhookPayloadSchema,
1264
+ handler: ({ payload }) => Effect.gen(function* () {
1265
+ const { repository, pull_request, review } = yield* decodeEvent(PullRequestReviewEventSchema, payload);
1266
+ const readAt = yield* DateTime.nowAsDate;
1267
+ const page = yield* client.get(PullRequestReviewObjectSchema, `/repos/${repository.full_name}/pulls/${pull_request.number}/reviews/${review.id}`);
1268
+ return [toRow$4(page.body, pull_request.number, repository.id, readAt)];
1269
+ })
1270
+ }
1271
+ });
1272
+ };
1273
+ //#endregion
1274
+ //#region src/resources/releases.ts
1275
+ const updatedAt = (release) => release.updated_at ?? release.created_at;
1276
+ const toRow$3 = (release, repository_id) => ({
1277
+ ...Struct.omit(release, ["updated_at"]),
1278
+ version: updatedAt(release),
1279
+ repository_id
1280
+ });
1281
+ const releaseSpec = {
1282
+ path: "releases",
1283
+ pageSchema: Schema.Array(ReleaseObjectSchema),
1284
+ params: {},
1285
+ since: false,
1286
+ order: "newest",
1287
+ createdAt: (release) => release.created_at,
1288
+ updatedAt,
1289
+ keep: () => true,
1290
+ enabled: () => true,
1291
+ rows: rowsOf(toRow$3)
1292
+ };
1293
+ const makeReleases = (client, repositories) => Resource.entity({
1294
+ name: "releases",
1295
+ rowSchema: ReleaseSchema,
1296
+ version: "version",
1297
+ check: checkRepoResource(client, releaseSpec),
1298
+ backfill: backfillRepoResource(client, repositories, releaseSpec),
1299
+ changes: changesRepoResource(client, releaseSpec),
1300
+ webhook: {
1301
+ schema: WebhookPayloadSchema,
1302
+ handler: ({ payload }) => Effect.gen(function* () {
1303
+ const event = yield* decodeEvent(ReleaseEventSchema, payload);
1304
+ if (Schema.is(ReleaseDeletedEventSchema)(event)) return [yield* deleteRow(event.release.id)];
1305
+ return [toRow$3(event.release, event.repository.id)];
1306
+ })
1307
+ }
1308
+ });
1309
+ //#endregion
1310
+ //#region src/resources/repositories.ts
1311
+ const toRow$2 = ({ updated_at, topics, ...rest }) => ({
1312
+ ...rest,
1313
+ version: updated_at,
1314
+ topics: topics ?? [],
1315
+ _deleted: false
1316
+ });
1317
+ const makeRepositories = (client) => Resource.entity({
1318
+ name: "repositories",
1319
+ rowSchema: RepositorySchema,
1320
+ version: "version",
1321
+ check: client.get(repositoryPage(RepositoryObjectSchema), withParams(installationRepositoriesPath, { per_page: "1" })).pipe(Effect.asVoid),
1322
+ backfill: Fetch.page({
1323
+ pageCursor: Cursor.string(),
1324
+ cutoff: Cursor.isoDateTime(),
1325
+ fetch: ({ pageCursor, cutoff }) => Effect.gen(function* () {
1326
+ const cutoffDate = parseCutoff(String(cutoff));
1327
+ const page = yield* client.get(repositoryPage(RepositoryObjectSchema), typeof pageCursor === "string" ? pageCursor : withParams(installationRepositoriesPath, { per_page: "100" }));
1328
+ return {
1329
+ rows: page.body.repositories.filter((repository) => repository.created_at.getTime() <= cutoffDate.getTime()).map(toRow$2),
1330
+ nextPageCursor: Option.getOrUndefined(page.next),
1331
+ hasMore: Option.isSome(page.next)
1332
+ };
1333
+ })
1334
+ }),
1335
+ changes: Fetch.changes({
1336
+ cursor: Cursor.string(),
1337
+ interval: changesInterval,
1338
+ fetch: ({ cursor }) => readChanges(String(cursor), listRepositories(client, RepositoryObjectSchema), (repositories) => repositories.filter(({ repository, since }) => isSince(since, repository.updated_at)).map(({ repository }) => toRow$2(repository)))
1339
+ }),
1340
+ webhook: {
1341
+ schema: WebhookPayloadSchema,
1342
+ handler: ({ payload }) => Effect.gen(function* () {
1343
+ const event = yield* decodeEvent(RepositoryEventSchema, payload);
1344
+ if (Schema.is(RepositoryDeletedEventSchema)(event)) return [yield* deleteRow(event.repository.id)];
1345
+ const { repository } = event;
1346
+ const full = repository.topics !== void 0 ? repository : (yield* client.get(RepositoryObjectSchema, `/repos/${repository.full_name}`)).body;
1347
+ return [toRow$2(full)];
1348
+ })
1349
+ }
1350
+ });
1351
+ //#endregion
1352
+ //#region src/schemas/workflow-jobs.ts
1353
+ const step = Schema.Struct({
1354
+ number: Long.pipe(Schema.annotateKey({ description: "Step number." })),
1355
+ name: Schema.String.pipe(Schema.annotateKey({ description: "Step name." })),
1356
+ status: Schema.String.pipe(Schema.annotateKey({ description: "Status, such as queued, in_progress, or completed." })),
1357
+ conclusion: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Result of a completed step." })),
1358
+ started_at: Schema.optionalKey(Schema.NullOr(Timestamp)).pipe(Schema.annotateKey({ description: "Time the step started." })),
1359
+ completed_at: Schema.optionalKey(Schema.NullOr(Timestamp)).pipe(Schema.annotateKey({ description: "Time the step completed." }))
1360
+ });
1361
+ const WorkflowJobSchema = Schema.Struct({
1362
+ id: Long.pipe(Schema.annotateKey({ description: "Unique job ID." })),
1363
+ version: readVersion("job"),
1364
+ node_id: Schema.String.pipe(Schema.annotateKey({ description: "GraphQL node ID of the job." })),
1365
+ repository_id: Long.pipe(Schema.annotateKey({ description: "ID of the repository the job belongs to." })),
1366
+ run_id: Long.pipe(Schema.annotateKey({ description: "ID of the workflow run." })),
1367
+ run_attempt: Schema.optionalKey(Long).pipe(Schema.annotateKey({ description: "Attempt of the run the job belongs to." })),
1368
+ workflow_name: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Workflow name." })),
1369
+ name: Schema.String.pipe(Schema.annotateKey({ description: "Job name." })),
1370
+ head_sha: Schema.String.pipe(Schema.annotateKey({ description: "SHA of the commit the job ran on." })),
1371
+ head_branch: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Branch the job ran on." })),
1372
+ status: Schema.String.pipe(Schema.annotateKey({ description: "Status, such as queued, in_progress, or completed." })),
1373
+ conclusion: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Result of a completed job, such as success or failure." })),
1374
+ created_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the job was queued." })),
1375
+ started_at: Timestamp.pipe(Schema.annotateKey({ description: "Time the job started." })),
1376
+ completed_at: Schema.NullOr(Timestamp).pipe(Schema.annotateKey({ description: "Time the job completed." })),
1377
+ runner_name: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Name of the runner." })),
1378
+ labels: Schema.Array(Schema.String).pipe(Schema.annotateKey({ description: "Runner labels the job asked for." })),
1379
+ steps: Schema.Array(step).pipe(Schema.annotateKey({ description: "Steps of the job." })),
1380
+ html_url: Schema.NullOr(Schema.String).pipe(Schema.annotateKey({ description: "Job page on GitHub." }))
1381
+ }).annotate({ description: "Jobs of GitHub Actions workflow runs. Each run attempt has its own jobs." }).pipe(Iceberg.identifier("id"));
1382
+ const WorkflowJobObjectSchema = WorkflowJobSchema.mapFields((fields) => ({
1383
+ ...Struct.omit(fields, [
1384
+ "version",
1385
+ "repository_id",
1386
+ "steps"
1387
+ ]),
1388
+ steps: Schema.optionalKey(Schema.Array(step))
1389
+ }));
1390
+ //#endregion
1391
+ //#region src/resources/workflow-runs.ts
1392
+ const toRow$1 = ({ updated_at, pull_requests, ...rest }, repository_id) => ({
1393
+ ...rest,
1394
+ version: updated_at,
1395
+ repository_id,
1396
+ pull_requests: pull_requests ?? []
1397
+ });
1398
+ const workflowRunSpec = {
1399
+ path: "actions/runs",
1400
+ pageSchema: wrappedList("workflow_runs", WorkflowRunObjectSchema),
1401
+ params: {},
1402
+ since: false,
1403
+ order: "newest",
1404
+ recentFor: Duration.days(1),
1405
+ ...timestamps,
1406
+ keep: () => true,
1407
+ enabled: () => true,
1408
+ rows: rowsOf(toRow$1)
1409
+ };
1410
+ const makeWorkflowRuns = (client, repositories) => Resource.entity({
1411
+ name: "workflow_runs",
1412
+ rowSchema: WorkflowRunSchema,
1413
+ version: "version",
1414
+ check: checkRepoResource(client, workflowRunSpec),
1415
+ backfill: backfillRepoResource(client, repositories, workflowRunSpec),
1416
+ changes: changesRepoResource(client, workflowRunSpec),
1417
+ webhook: {
1418
+ schema: WebhookPayloadSchema,
1419
+ handler: ({ payload }) => decodeEvent(WorkflowRunEventSchema, payload).pipe(Effect.map((event) => [toRow$1(event.workflow_run, event.repository.id)]))
1420
+ }
1421
+ });
1422
+ //#endregion
1423
+ //#region src/resources/workflow-jobs.ts
1424
+ const toRow = ({ steps, ...rest }, repository_id, version) => ({
1425
+ ...rest,
1426
+ version,
1427
+ repository_id,
1428
+ steps: steps ?? []
1429
+ });
1430
+ const workflowJobSpec = (client) => ({
1431
+ ...workflowRunSpec,
1432
+ rows: childRows(client, {
1433
+ path: (run) => `actions/runs/${run.id}/jobs`,
1434
+ pageSchema: wrappedList("jobs", WorkflowJobObjectSchema),
1435
+ params: { filter: "all" },
1436
+ toRow: (job, _run, repositoryId, readAt) => toRow(job, repositoryId, readAt)
1437
+ })
1438
+ });
1439
+ const makeWorkflowJobs = (client, repositories) => {
1440
+ const spec = workflowJobSpec(client);
1441
+ return Resource.entity({
1442
+ name: "workflow_jobs",
1443
+ rowSchema: WorkflowJobSchema,
1444
+ version: "version",
1445
+ check: checkRepoResource(client, spec),
1446
+ backfill: backfillRepoResource(client, repositories, spec),
1447
+ changes: changesRepoResource(client, spec),
1448
+ webhook: {
1449
+ schema: WebhookPayloadSchema,
1450
+ handler: ({ payload }) => Effect.gen(function* () {
1451
+ const { repository, workflow_job } = yield* decodeEvent(WorkflowJobEventSchema, payload);
1452
+ const readAt = yield* DateTime.nowAsDate;
1453
+ const page = yield* client.get(WorkflowJobObjectSchema, `/repos/${repository.full_name}/actions/jobs/${workflow_job.id}`);
1454
+ return [toRow(page.body, repository.id, readAt)];
1455
+ })
1456
+ }
1457
+ });
1458
+ };
1459
+ //#endregion
1460
+ //#region src/webhook/signature.ts
1461
+ const prefix = "sha256=";
1462
+ const verifySignature = (options) => {
1463
+ const header = options.header ?? "";
1464
+ const expected = createHmac("sha256", options.secret).update(options.rawBody).digest();
1465
+ const provided = Buffer.from(header.slice(7), "hex");
1466
+ return header.startsWith(prefix) && provided.length === expected.length && timingSafeEqual(provided, expected) ? Effect.void : Effect.fail(new ConnectorError({ message: "Invalid GitHub webhook signature" }));
1467
+ };
1468
+ //#endregion
1469
+ //#region src/webhook/route.ts
1470
+ const webhookPath = "/webhooks/github";
1471
+ const makeWebhookRoute = (options) => Webhook.route({
1472
+ path: webhookPath,
1473
+ ackMode: "after-enqueue",
1474
+ schema: WebhookPayloadSchema,
1475
+ handler: ({ request, rawBody, payload, to }) => Effect.gen(function* () {
1476
+ const verified = yield* verifySignature({
1477
+ rawBody,
1478
+ header: request.headers["x-hub-signature-256"],
1479
+ secret: options.secret
1480
+ }).pipe(Effect.result);
1481
+ if (Result.isFailure(verified)) return HttpServerResponse.jsonUnsafe({
1482
+ ok: false,
1483
+ error: verified.failure.message
1484
+ }, { status: 401 });
1485
+ const installationId = Schema.decodeUnknownOption(InstallationEventSchema)(payload).pipe(Option.flatMap(({ installation }) => Option.fromNullishOr(installation?.id)));
1486
+ if (Option.contains(installationId, options.installationId)) {
1487
+ const resources = options.targets.get(request.headers["x-github-event"] ?? "") ?? [];
1488
+ yield* Effect.forEach(resources, (resource) => to(resource, payload), { discard: true });
1489
+ }
1490
+ return HttpServerResponse.jsonUnsafe({ ok: true });
1491
+ })
1492
+ });
1493
+ //#endregion
1494
+ //#region src/connector.ts
1495
+ var connector_exports = /* @__PURE__ */ __exportAll({
1496
+ GitHubConfigDef: () => GitHubConfigDef,
1497
+ GitHubConnector: () => GitHubConnector,
1498
+ layer: () => layer,
1499
+ layerConfig: () => layerConfig,
1500
+ make: () => make,
1501
+ manifest: () => manifest
1502
+ });
1503
+ const make = Effect.fnUntraced(function* (config) {
1504
+ const client = yield* GitHubClient;
1505
+ const repositories = yield* cachedRepositoryRefs(client);
1506
+ const Repositories = makeRepositories(client);
1507
+ const Issues = makeIssues(client, repositories);
1508
+ const PullRequests = makePullRequests(client, repositories);
1509
+ const IssueComments = makeIssueComments(client, repositories);
1510
+ const PullRequestReviews = makePullRequestReviews(client, repositories);
1511
+ const PullRequestReviewComments = makePullRequestReviewComments(client, repositories);
1512
+ const PullRequestCommits = makePullRequestCommits(client, repositories);
1513
+ const Commits = makeCommits(client, repositories);
1514
+ const CheckRuns = makeCheckRuns(client, repositories);
1515
+ const CommitStatuses = makeCommitStatuses(client, repositories);
1516
+ const WorkflowRuns = makeWorkflowRuns(client, repositories);
1517
+ const WorkflowJobs = makeWorkflowJobs(client, repositories);
1518
+ const Releases = makeReleases(client, repositories);
1519
+ return Connector.define({
1520
+ name: "producer-github",
1521
+ title: "GitHub",
1522
+ resources: [
1523
+ Repositories,
1524
+ Issues,
1525
+ PullRequests,
1526
+ IssueComments,
1527
+ PullRequestReviews,
1528
+ PullRequestReviewComments,
1529
+ PullRequestCommits,
1530
+ Commits,
1531
+ CheckRuns,
1532
+ CommitStatuses,
1533
+ WorkflowRuns,
1534
+ WorkflowJobs,
1535
+ Releases
1536
+ ],
1537
+ webhooks: [makeWebhookRoute({
1538
+ secret: Redacted.value(config.webhookSecret),
1539
+ installationId: config.installationId,
1540
+ targets: /* @__PURE__ */ new Map([
1541
+ ["repository", [Repositories]],
1542
+ ["issues", [Issues]],
1543
+ ["pull_request", [PullRequests, PullRequestCommits]],
1544
+ ["issue_comment", [IssueComments]],
1545
+ ["pull_request_review", [PullRequestReviews]],
1546
+ ["pull_request_review_comment", [PullRequestReviewComments]],
1547
+ ["push", [Commits]],
1548
+ ["check_run", [CheckRuns]],
1549
+ ["status", [CommitStatuses]],
1550
+ ["workflow_run", [WorkflowRuns]],
1551
+ ["workflow_job", [WorkflowJobs]],
1552
+ ["release", [Releases]]
1553
+ ])
1554
+ })]
1555
+ });
1556
+ });
1557
+ var GitHubConnector = class extends Context.Service()("@useairfoil/producer-github/GitHubConnector") {};
1558
+ const layer = (config) => Layer.effect(GitHubConnector)(make(config).pipe(Effect.annotateLogs({ component: "github" }))).pipe(Layer.provide(layer$1(config)));
1559
+ const layerConfig = (config) => Layer.unwrap(Config.unwrap(config).pipe(Effect.map(layer)));
1560
+ //#endregion
1561
+ export { auth_exports as C, client_exports as S, IssueObjectSchema as _, WorkflowJobSchema as a, IssueCommentSchema as b, CommitStatusSchema as c, RepositoryObjectSchema as d, RepositorySchema as f, PullRequestReviewCommentSchema as g, PullRequestSchema as h, webhookPath as i, CommitSchema as l, PullRequestObjectSchema as m, connector_exports as n, PullRequestReviewSchema as o, ReleaseSchema as p, layerConfig as r, PullRequestCommitSchema as s, GitHubConnector as t, WorkflowRunSchema as u, IssueSchema as v, CheckRunSchema as x, IssueCommentObjectSchema as y };
1562
+
1563
+ //# sourceMappingURL=src-DKDUc00e.mjs.map