@effected/github 0.14.0 → 0.15.1

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.
@@ -7,10 +7,7 @@ import { Context, Effect, Layer, Schema } from "effect";
7
7
  * What to record about a published artifact.
8
8
  *
9
9
  * @remarks
10
- * These are the fields the endpoint actually accepts. The version this replaces
11
- * declared a `version` field the endpoint has no notion of — a fabricated key
12
- * that a `Record<string, unknown>` body accepted silently and the generated
13
- * types reject outright.
10
+ * These are the fields the storage-record endpoint accepts.
14
11
  *
15
12
  * @public
16
13
  */
@@ -29,11 +26,16 @@ var StorageRecordInput = class extends Schema.Class("StorageRecordInput")({
29
26
  path: Schema.optionalKey(Schema.String)
30
27
  }) {};
31
28
  /**
32
- * Artifact metadata.
29
+ * Records where published artifacts are stored, at the organization level.
30
+ *
31
+ * @remarks
32
+ * Provide it with {@link ArtifactMetadata.layer}, which needs a `GitHubClient`;
33
+ * `createStorageRecord` also needs a `Repo` in `R`.
33
34
  *
34
35
  * @public
35
36
  */
36
37
  var ArtifactMetadata = class ArtifactMetadata extends Context.Service()("@effected/github/ArtifactMetadata") {
38
+ /** The live service, built over a `GitHubClient`. */
37
39
  static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
38
40
  /** An in-memory double; unstubbed members die naming themselves. */
39
41
  static makeTest = (overrides = {}) => ({ createStorageRecord: overrides.createStorageRecord ?? (() => unstubbed("createStorageRecord")) });
package/Attestation.js CHANGED
@@ -42,11 +42,17 @@ const ListResponse = Schema.Struct({ attestations: Schema.optionalKey(Schema.Arr
42
42
  predicate_type: Schema.optionalKey(Schema.String)
43
43
  }))) });
44
44
  /**
45
- * Attestations.
45
+ * Uploads signed attestation bundles to a repository and lists what is attested
46
+ * about a subject digest.
47
+ *
48
+ * @remarks
49
+ * Provide it with {@link Attestation.layer}, which needs a `GitHubClient`; each
50
+ * method also needs a `Repo` in `R`.
46
51
  *
47
52
  * @public
48
53
  */
49
54
  var Attestation = class Attestation extends Context.Service()("@effected/github/Attestation") {
55
+ /** The live service, built over a `GitHubClient`. */
50
56
  static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
51
57
  /** An in-memory double; unstubbed members die naming themselves. */
52
58
  static makeTest = (overrides = {}) => ({
package/CheckRun.js CHANGED
@@ -43,8 +43,8 @@ var Annotation = class extends Schema.Class("Annotation")({
43
43
  * GitHub's limits are **byte** limits, and that distinction is the whole reason
44
44
  * this class exists rather than a struct: `✅`, `❌`, `🦋` and `│` cost several
45
45
  * bytes each, so a character-count check passes while the request comes back
46
- * 422 saying *"summary exceeds a maximum bytesize of 65535"*. One consumer hit
47
- * exactly that and wrote the truncation by hand.
46
+ * 422 saying *"summary exceeds a maximum bytesize of 65535"*. Use
47
+ * {@link CheckRunOutput.truncated} to cut an output to fit.
48
48
  *
49
49
  * @public
50
50
  */
@@ -87,8 +87,7 @@ var CheckRunOutput = class CheckRunOutput extends Schema.Class("CheckRunOutput")
87
87
  * @remarks
88
88
  * Slicing a UTF-8 buffer mid-character decodes to U+FFFD. Splitting a four-byte
89
89
  * code point can produce **more than one** replacement character, so the trim
90
- * loops rather than dropping a single one — which is the hardening this needed
91
- * over the hand-written version it replaces.
90
+ * loops rather than dropping a single one.
92
91
  */
93
92
  const capBytes = (value) => {
94
93
  if (Buffer.byteLength(value, "utf8") <= CheckRunOutput.LIMIT_BYTES) return value;
@@ -110,11 +109,17 @@ var CheckRunRef = class extends Schema.Class("CheckRunRef")({
110
109
  status: Schema.String
111
110
  }) {};
112
111
  /**
113
- * Check runs.
112
+ * Create, update and conclude GitHub check runs, including the
113
+ * {@link CheckRunShape.withCheckRun} bracket that always reaches a terminal state.
114
+ *
115
+ * @remarks
116
+ * Provide it with {@link CheckRun.layer}, which needs a `GitHubClient`; each
117
+ * method also needs a `Repo` in `R`.
114
118
  *
115
119
  * @public
116
120
  */
117
121
  var CheckRun = class CheckRun extends Context.Service()("@effected/github/CheckRun") {
122
+ /** The live service, built over a `GitHubClient`. */
118
123
  static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
119
124
  /** An in-memory double; unstubbed members die naming themselves. */
120
125
  static makeTest = (overrides = {}) => ({
package/CodeScanning.js CHANGED
@@ -4,12 +4,19 @@ import { Context, Effect, Layer } from "effect";
4
4
 
5
5
  //#region src/CodeScanning.ts
6
6
  /**
7
- * CodeQL default setup.
7
+ * Configure CodeQL default setup and read the languages GitHub detects in a
8
+ * repository.
9
+ *
10
+ * @remarks
11
+ * Provide it with {@link CodeScanning.layer}, which needs a `GitHubClient`; each
12
+ * method also needs a `Repo` in `R`.
8
13
  *
9
14
  * @public
10
15
  */
11
16
  var CodeScanning = class CodeScanning extends Context.Service()("@effected/github/CodeScanning") {
12
17
  /**
18
+ * The live service, built over a `GitHubClient`.
19
+ *
13
20
  * @remarks
14
21
  * The callback is written `(client) => make(client)` rather than passed as
15
22
  * `make` directly, and that is load-bearing: a static initializer runs while
@@ -4,12 +4,30 @@ import { Context, Effect, Layer } from "effect";
4
4
 
5
5
  //#region src/DeploymentEnvironment.ts
6
6
  /**
7
- * Deployment environments.
7
+ * Create or update, list and delete a repository's deployment environments.
8
+ *
9
+ * @remarks
10
+ * Provide it with {@link DeploymentEnvironment.layer}, which needs a
11
+ * `GitHubClient`; each method also needs a `Repo` in `R`.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { DeploymentEnvironment } from "@effected/github";
16
+ * import { Effect } from "effect";
17
+ *
18
+ * const program = Effect.gen(function* () {
19
+ * const environments = yield* DeploymentEnvironment;
20
+ * yield* environments.upsert("production", { wait_timer: 10 });
21
+ * return yield* environments.list();
22
+ * });
23
+ * ```
8
24
  *
9
25
  * @public
10
26
  */
11
27
  var DeploymentEnvironment = class DeploymentEnvironment extends Context.Service()("@effected/github/DeploymentEnvironment") {
12
28
  /**
29
+ * The live service, built over a `GitHubClient`.
30
+ *
13
31
  * @remarks
14
32
  * `(client) => make(client)` rather than `make`: a static initializer runs
15
33
  * while the module body is still evaluating, so naming a `const` declared
package/GitBranch.js CHANGED
@@ -15,12 +15,32 @@ const CreateLinkedBranch = GraphQLDocument.make({
15
15
  response: Schema.Struct({})
16
16
  })();
17
17
  /**
18
- * Branches, as refs.
18
+ * Create, move, read and delete branch refs through GitHub's Git Database API,
19
+ * with an `upsert` that needs no existence check.
20
+ *
21
+ * @remarks
22
+ * Provide it with {@link GitBranch.layer}, which needs a `GitHubClient`; each
23
+ * method also needs a `Repo` in `R`. Branch names are accepted as `main`,
24
+ * `heads/main` or `refs/heads/main`.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { GitBranch } from "@effected/github";
29
+ * import { Effect } from "effect";
30
+ *
31
+ * const ensureBranch = (name: string, sha: string) =>
32
+ * Effect.gen(function* () {
33
+ * const branches = yield* GitBranch;
34
+ * return yield* branches.upsert(name, sha); // "created" | "reset"
35
+ * });
36
+ * ```
19
37
  *
20
38
  * @public
21
39
  */
22
40
  var GitBranch = class GitBranch extends Context.Service()("@effected/github/GitBranch") {
23
41
  /**
42
+ * The live service, built over a `GitHubClient`.
43
+ *
24
44
  * @remarks
25
45
  * The callback is written `(client) => make(client)` rather than passed as
26
46
  * `make` directly, and that is load-bearing: a static initializer runs while
@@ -71,8 +91,7 @@ const rejectEmpty = (operation, name) => {
71
91
  * house pattern for a stable dependency — but it would also make a scoped
72
92
  * override silently do nothing, because the resource would already hold the
73
93
  * repository it was built with. The repository is precisely the dependency that
74
- * is *not* stable: silk-sync-action loops one program over many target
75
- * repositories.
94
+ * is *not* stable: one program may loop over many target repositories.
76
95
  *
77
96
  * The client stays resolved at construction, so this costs one context read per
78
97
  * call and nothing else.
package/GitCommit.js CHANGED
@@ -44,10 +44,8 @@ const FileChange = Schema.Union([FileContent, FileDeletion]);
44
44
  * A commit, projected to the three fields callers actually use.
45
45
  *
46
46
  * @remarks
47
- * `treeSha` is here because two surveyed call sites dropped to a raw octokit
48
- * cast for it alone, both with the comment *"the Git Data API's `base_tree`
49
- * wants a tree SHA, not a commit SHA"* — the same eight lines written twice, in
50
- * two files, for one string.
47
+ * `treeSha` is here because the Git Data API's `base_tree` wants a tree SHA,
48
+ * not a commit SHA.
51
49
  *
52
50
  * @public
53
51
  */
@@ -60,11 +58,33 @@ var CommitRef = class extends Schema.Class("CommitRef")({
60
58
  parents: Schema.Array(Schema.String)
61
59
  }) {};
62
60
  /**
63
- * Commits, trees and blobs.
61
+ * Read commits and build trees and commits through GitHub's Git Database API,
62
+ * including a one-call "commit these files onto a branch".
63
+ *
64
+ * @remarks
65
+ * Provide it with {@link GitCommit.layer}, which needs a `GitHubClient`; each
66
+ * method also needs a `Repo` in `R`. No local git runs: this is the REST API,
67
+ * not `@effected/git`.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * import { FileContent, GitCommit } from "@effected/github";
72
+ * import { Effect } from "effect";
73
+ *
74
+ * const writeNotes = Effect.gen(function* () {
75
+ * const commits = yield* GitCommit;
76
+ * return yield* commits.commitFiles({
77
+ * branch: "docs/notes",
78
+ * message: "docs: add notes",
79
+ * changes: [FileContent.make({ path: "NOTES.md", content: "# Notes\n" })],
80
+ * }); // the new commit's sha
81
+ * });
82
+ * ```
64
83
  *
65
84
  * @public
66
85
  */
67
86
  var GitCommit = class GitCommit extends Context.Service()("@effected/github/GitCommit") {
87
+ /** The live service, built over a `GitHubClient`. */
68
88
  static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
69
89
  /** An in-memory double; unstubbed members die naming themselves. */
70
90
  static makeTest = (overrides = {}) => ({
package/GitHubApp.js CHANGED
@@ -76,9 +76,8 @@ var InstallationToken = class extends Schema.Class("InstallationToken")({
76
76
  * Whether this token is spent, `skew` before its stated expiry.
77
77
  *
78
78
  * @remarks
79
- * Modelled **and enforced**. The package this replaces persisted `expiresAt`
80
- * and read it nowhere, so a long `main` phase that outlived the hour simply
81
- * started answering 401 with no explanation.
79
+ * `skew` defaults to one minute, so a token is treated as spent slightly early
80
+ * rather than answering 401 mid-request.
82
81
  */
83
82
  isExpired(nowMillis, skew = DEFAULT_SKEW) {
84
83
  return DateTime.toEpochMillis(this.expiresAt) - Duration.toMillis(skew) <= nowMillis;
@@ -97,10 +96,10 @@ const DEFAULT_SKEW = Duration.seconds(60);
97
96
  * Who a bot commits as.
98
97
  *
99
98
  * @remarks
100
- * A **pure class**, not a service member. The package this replaces put
101
- * `botIdentity(source?)` on the `GitHubApp` service shape as a plain synchronous
102
- * method, which makes it required in every `Layer.mock` and silently degrades
103
- * every partial double to a full implementation.
99
+ * A **pure class**, not a `GitHubApp` service member: a synchronous method on
100
+ * the service shape would be required in every `Layer.mock` and would silently
101
+ * degrade every partial double to a full implementation. Get one from
102
+ * `InstallationToken.botIdentity` or `AppIdentity.botIdentity`.
104
103
  *
105
104
  * @public
106
105
  */
@@ -189,9 +188,28 @@ var Installation = class extends Schema.Class("Installation")({
189
188
  * `@effected/commands`' service, for the same reason.
190
189
  *
191
190
  * The JWT signer is `universal-github-app-jwt` — zero dependencies, and
192
- * `@octokit/auth-app`'s own JWT dependency. Taking it directly rather than
193
- * taking `auth-app` leaves behind roughly half a megabyte of OAuth app, user and
194
- * device-flow machinery that this package never calls.
191
+ * `@octokit/auth-app`'s own JWT dependency.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * import { GitHubApp, GitHubClient } from "@effected/github";
196
+ * import { Effect, Redacted } from "effect";
197
+ *
198
+ * const program = Effect.gen(function* () {
199
+ * const client = yield* GitHubClient;
200
+ * const accessible = yield* client.request("GET /installation/repositories", { per_page: 1 });
201
+ * return accessible.total_count;
202
+ * });
203
+ *
204
+ * // Mints an installation token on build, re-mints before expiry, revokes on release.
205
+ * const layer = GitHubApp.clientLayer({
206
+ * appId: "12345",
207
+ * privateKey: Redacted.make("-----BEGIN PRIVATE KEY-----\n..."),
208
+ * owner: "my-org",
209
+ * });
210
+ *
211
+ * Effect.runPromise(Effect.provide(program, layer));
212
+ * ```
195
213
  *
196
214
  * @public
197
215
  */
@@ -214,11 +232,12 @@ var GitHubApp = class GitHubApp extends Context.Service()("@effected/github/GitH
214
232
  * The token's lifetime is the layer's scope: it is minted on build and
215
233
  * **revoked on release**, best-effort, so a workflow does not leave live
216
234
  * credentials behind. It is also **re-minted automatically** a minute before
217
- * it expires, which the package this replaces did not do — it persisted
218
- * `expiresAt` and read it nowhere, so a `main` phase outliving the hour
219
- * started answering 401 with nothing explaining why.
235
+ * it expires, so a long-running program does not start answering 401 when
236
+ * the hour ends.
220
237
  *
221
- * A failure to obtain credentials surfaces to the caller as a
238
+ * Building the layer mints the first token, so a misconfigured app fails
239
+ * construction with `GitHubAppError`. After that, a failure to obtain
240
+ * credentials surfaces to the caller as a
222
241
  * `GitHubError { kind: "unauthorized" }` carrying the `GitHubAppError` as its
223
242
  * cause: from a request's point of view, "could not authenticate" is an
224
243
  * authorization failure, and widening every method's error channel to say so
package/GitHubClient.js CHANGED
@@ -23,12 +23,27 @@ const unstubbed = (member) => {
23
23
  * literal and gets both sides typed, with no callback, no type parameter to
24
24
  * invent, and no cast.
25
25
  *
26
- * That is the whole point of this package. The surface it replaces was
27
- * `rest<T>(operation: string, fn: (octokit: any) => Promise<{ data: T }>)`,
28
- * where `T` was whatever the caller wrote and nothing connected it to the
29
- * endpoint. Four consumer repos paid for that with sixteen cast sites and three
30
- * hand-written octokit interfaces, one of which gave up and typed its methods as
31
- * `Record<string, (p: unknown) => Promise<{ data: unknown }>>`.
26
+ * Nothing here asks the caller to invent a response type: the route literal is
27
+ * the only thing written, and a mismatch between a route and its parameters is
28
+ * a compile error.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { GitHubClient } from "@effected/github";
33
+ * import { Effect, Redacted } from "effect";
34
+ *
35
+ * const program = Effect.gen(function* () {
36
+ * const client = yield* GitHubClient;
37
+ * const release = yield* client.request("GET /repos/{owner}/{repo}/releases/latest", {
38
+ * owner: "effect-ts",
39
+ * repo: "effect",
40
+ * });
41
+ * return release.tag_name;
42
+ * });
43
+ *
44
+ * const layer = GitHubClient.layerFromToken({ token: Redacted.make("ghp_example") });
45
+ * Effect.runPromise(program.pipe(Effect.provide(layer)));
46
+ * ```
32
47
  *
33
48
  * @public
34
49
  */
@@ -51,10 +66,7 @@ var GitHubClient = class GitHubClient extends Context.Service()("@effected/githu
51
66
  * provides a provider instead of mutating the environment and a non-Actions
52
67
  * consumer can source the token however it likes.
53
68
  *
54
- * Construction fails with core's `ConfigError` — an honest "no token is
55
- * configured". The layer this replaces failed with a **wire-failure** error
56
- * type instead, which is why one consumer wrapped it in `Layer.orDie` under a
57
- * five-line comment explaining that the error did not mean what it said.
69
+ * Construction fails with core's `ConfigError` when no token is configured.
58
70
  */
59
71
  static layerFromConfig = (options = {}) => Layer.effect(this, Effect.gen(function* () {
60
72
  const token = yield* Config.Redacted(options.name ?? "GITHUB_TOKEN");
@@ -90,17 +102,11 @@ var GitHubClient = class GitHubClient extends Context.Service()("@effected/githu
90
102
  * A double over recorded responses that **pages them for real**.
91
103
  *
92
104
  * @remarks
93
- * The one recorded-response double in this package, and the single narrow
94
- * exception to the no-behavior-reimplementing-doubles rule — safe precisely
95
- * because it reimplements nothing: it builds a `PageSource` over the recorded
105
+ * The recorded-response double in this package. It reimplements no behavior:
106
+ * it builds a `PageSource` over the recorded
96
107
  * array and hands it to the same `paginate` engine the live client uses, so
97
108
  * `perPage` and `maxPages` cannot behave differently here than in production.
98
109
  *
99
- * The double it replaces named its pagination parameters `_options` and
100
- * ignored them, returning every recorded page regardless of what the caller
101
- * asked for — which made every truncation path in every consumer
102
- * structurally untestable.
103
- *
104
110
  * `fixtures.requested` is appended to as the test runs, so a suite can assert
105
111
  * which routes were walked and at what page size.
106
112
  */
package/GitHubCommit.js CHANGED
@@ -89,11 +89,30 @@ var CommitComparison = class extends Schema.Class("CommitComparison")({
89
89
  files: Schema.Array(CommitFile)
90
90
  }) {};
91
91
  /**
92
- * Commits, as GitHub reports them.
92
+ * Read commits, compare refs and list changed files through GitHub's commits
93
+ * API.
94
+ *
95
+ * @remarks
96
+ * Provide it with {@link GitHubCommit.layer}, which needs a `GitHubClient`; each
97
+ * method also needs a `Repo` in `R`. For commits as Git Database objects (trees,
98
+ * parents), use `GitCommit` instead.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * import { GitHubCommit } from "@effected/github";
103
+ * import { Effect } from "effect";
104
+ *
105
+ * const changedSinceMain = Effect.gen(function* () {
106
+ * const commits = yield* GitHubCommit;
107
+ * const comparison = yield* commits.compare("main", "feature");
108
+ * return comparison.files.map((file) => file.path);
109
+ * });
110
+ * ```
93
111
  *
94
112
  * @public
95
113
  */
96
114
  var GitHubCommit = class GitHubCommit extends Context.Service()("@effected/github/GitHubCommit") {
115
+ /** The live service, built over a `GitHubClient`. */
97
116
  static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
98
117
  /** An in-memory double; unstubbed members die naming themselves. */
99
118
  static makeTest = (overrides = {}) => ({
package/GitHubContent.js CHANGED
@@ -5,11 +5,29 @@ import { Context, Effect, Layer, Option } from "effect";
5
5
 
6
6
  //#region src/GitHubContent.ts
7
7
  /**
8
- * Repository file contents.
8
+ * Read a text file out of a repository at a ref, with absence as an `Option`
9
+ * when you want it.
10
+ *
11
+ * @remarks
12
+ * Provide it with {@link GitHubContent.layer}, which needs a `GitHubClient`;
13
+ * each method also needs a `Repo` in `R`.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { GitHubContent } from "@effected/github";
18
+ * import { Effect, Option } from "effect";
19
+ *
20
+ * const readme = Effect.gen(function* () {
21
+ * const content = yield* GitHubContent;
22
+ * const file = yield* content.getFileOption("README.md", { ref: "main" });
23
+ * return Option.getOrElse(file, () => "");
24
+ * });
25
+ * ```
9
26
  *
10
27
  * @public
11
28
  */
12
29
  var GitHubContent = class GitHubContent extends Context.Service()("@effected/github/GitHubContent") {
30
+ /** The live service, built over a `GitHubClient`. */
13
31
  static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
14
32
  /** An in-memory double; unstubbed members die naming themselves. */
15
33
  static makeTest = (overrides = {}) => ({
package/GitHubError.js CHANGED
@@ -6,12 +6,9 @@ import { Schema } from "effect";
6
6
  * Why a GitHub call failed, as a value you can branch on.
7
7
  *
8
8
  * @remarks
9
- * This discriminant is what replaces string-matching an error message. The
10
- * package this replaces carried a free-form `reason` string and nothing else
11
- * structural, so consumers grepped it: one repo lowercased a GraphQL message and
12
- * tested `includes("already") || includes("exists")`, and another re-issued an
13
- * existence check after every failed branch creation because it could not tell
14
- * "someone else created it" from "that failed".
9
+ * Branch on this discriminant instead of string-matching an error message:
10
+ * `alreadyExists` tells "someone else created it" apart from "that failed", so
11
+ * an upsert needs no follow-up existence check.
15
12
  *
16
13
  * @public
17
14
  */
@@ -75,16 +72,9 @@ var GitHubValidationEntry = class extends Schema.Class("GitHubValidationEntry")(
75
72
  * Every REST failure this package produces, from every resource.
76
73
  *
77
74
  * @remarks
78
- * One error class, not one per resource. Across the six repos surveyed for this
79
- * port, consumers read `reason` about forty times, `status` twice, `operation`
80
- * twice, and matched a resource-specific `_tag` exactly **once** — at a call
81
- * site that disappears entirely now that `GitBranch.upsert` exists. Eighteen
82
- * near-identical error classes and thirteen near-identical mapper closures
83
- * bought that one match.
84
- *
85
- * What replaces them is {@link GitHubErrorKind} for routing and `operation` for
86
- * identification. This mirrors `@effected/git`, where the rule is that no
87
- * consumer ever string-matches stderr because classification happens once.
75
+ * One error class, not one per resource: {@link GitHubErrorKind} routes and
76
+ * `operation` identifies what was attempted. This mirrors `@effected/git`,
77
+ * where classification happens once so no consumer string-matches stderr.
88
78
  *
89
79
  * @public
90
80
  */
@@ -103,8 +93,8 @@ var GitHubError = class GitHubError extends Schema.TaggedError()("GitHubError",
103
93
  * @remarks
104
94
  * Written by the client from `retry-after` or the rate-limit reset, and read
105
95
  * by exactly one thing: the retry `Schedule`. It is a policy input, not
106
- * information for a caller — which is why it is optional and why the
107
- * `retryable` boolean it used to travel with is now a derived getter.
96
+ * information for a caller. Whether a failure is worth retrying at all is the
97
+ * derived `retryable` getter.
108
98
  */
109
99
  retryAfterMillis: Schema.optionalKey(Schema.Int),
110
100
  /**
@@ -176,7 +166,7 @@ var GitHubError = class GitHubError extends Schema.TaggedError()("GitHubError",
176
166
  * @remarks
177
167
  * The single classification step for the whole package — every resource
178
168
  * method's failures come through here, so the taxonomy cannot drift between
179
- * resources the way thirteen hand-written mapper closures did.
169
+ * resources.
180
170
  *
181
171
  * `nowMillis` is passed in rather than read from the wall clock so the
182
172
  * function stays pure and total: the rate-limit reset header is an absolute
@@ -276,9 +266,8 @@ const asRecord = (value) => typeof value === "object" && value !== null ? value
276
266
  * multi-kilobyte "message".
277
267
  *
278
268
  * @remarks
279
- * The package this replaces detected the "Unicorn" page specifically; this is
280
- * the general form — an HTML body is never a useful reason string, and letting
281
- * one through means a log line with a whole web page in it.
269
+ * An HTML body is never a useful reason string, and letting one through means
270
+ * a log line with a whole web page in it.
282
271
  */
283
272
  const sanitizeReason = (message) => {
284
273
  const trimmed = message.trim();
package/GitHubIssue.js CHANGED
@@ -37,10 +37,8 @@ var LinkedIssue = class extends Schema.Class("LinkedIssue")({
37
37
  * branch or commit messages.
38
38
  *
39
39
  * @remarks
40
- * This is the field the whole document exists for. The version this replaces
41
- * could not express `userLinkedOnly`, so one consumer re-declared the query
42
- * with the field aliased twice to get it — the single largest duplicated
43
- * document in the survey.
40
+ * Resolved by querying the pull request's closing references twice, once with
41
+ * `userLinkedOnly`, and marking the issues present in the second result.
44
42
  */
45
43
  userLinked: Schema.Boolean
46
44
  }) {};
@@ -104,11 +102,30 @@ const CrossReferencedDocument = GraphQLDocument.make({
104
102
  response: CrossReferencedResponse
105
103
  })();
106
104
  /**
107
- * Issues.
105
+ * Read, list, close and comment on issues, and resolve the issues a pull
106
+ * request closes.
107
+ *
108
+ * @remarks
109
+ * Provide it with {@link GitHubIssue.layer}, which needs a `GitHubClient`; each
110
+ * method also needs a `Repo` in `R`.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * import { GitHubIssue } from "@effected/github";
115
+ * import { Effect } from "effect";
116
+ *
117
+ * const closeWontFix = (number: number) =>
118
+ * Effect.gen(function* () {
119
+ * const issues = yield* GitHubIssue;
120
+ * yield* issues.comment(number, "Closing as not planned.");
121
+ * yield* issues.close(number, "not_planned");
122
+ * });
123
+ * ```
108
124
  *
109
125
  * @public
110
126
  */
111
127
  var GitHubIssue = class GitHubIssue extends Context.Service()("@effected/github/GitHubIssue") {
128
+ /** The live service, built over a `GitHubClient`. */
112
129
  static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
113
130
  /** An in-memory double; unstubbed members die naming themselves. */
114
131
  static makeTest = (overrides = {}) => ({
@@ -139,15 +156,14 @@ const unstubbed = (member) => {
139
156
  * silence it, because the fetch wrapper resolves `request.log ?? console`.
140
157
  * `PATCH /repos/{owner}/{repo}/issues/{issue_number}` is on that list (the
141
158
  * singular `assignee` parameter was removed), so every `close` warned once per
142
- * issue in consumer workflow logs (effected#189).
159
+ * issue in consumer workflow logs.
143
160
  *
144
161
  * Nothing this module sends or reads changed in 2026-03-10: `state` and
145
162
  * `state_reason` are untouched, and the projections read only fields both
146
163
  * versions carry. Pinning the current version is therefore the whole fix, and
147
164
  * it deliberately does **not** hide future deprecations — when GitHub
148
165
  * deprecates 2026-03-10 in turn, the header comes back and octokit warns
149
- * again. Verified live 2026-08-14: the pinned request answers with no
150
- * `Deprecation`/`Sunset` headers.
166
+ * again.
151
167
  */
152
168
  const API_VERSION_HEADERS = { "x-github-api-version": "2026-03-10" };
153
169
  /** GitHub sends a label as either a string or an object; callers want the name. */
package/GitHubRelease.js CHANGED
@@ -5,7 +5,7 @@ import { Context, Effect, Layer, Option, Schema } from "effect";
5
5
 
6
6
  //#region src/GitHubRelease.ts
7
7
  /**
8
- * A release.
8
+ * A release, projected to the fields callers read.
9
9
  *
10
10
  * @public
11
11
  */
@@ -35,11 +35,29 @@ var ReleaseAsset = class extends Schema.Class("ReleaseAsset")({
35
35
  size: Schema.Int
36
36
  }) {};
37
37
  /**
38
- * Releases.
38
+ * Create, read, list and update GitHub releases, and upload and list their
39
+ * assets.
40
+ *
41
+ * @remarks
42
+ * Provide it with {@link GitHubRelease.layer}, which needs a `GitHubClient`;
43
+ * each method also needs a `Repo` in `R`.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { GitHubRelease } from "@effected/github";
48
+ * import { Effect } from "effect";
49
+ *
50
+ * const publish = Effect.gen(function* () {
51
+ * const releases = yield* GitHubRelease;
52
+ * const release = yield* releases.create({ tag: "v1.2.3", generateReleaseNotes: true });
53
+ * return release.url;
54
+ * });
55
+ * ```
39
56
  *
40
57
  * @public
41
58
  */
42
59
  var GitHubRelease = class GitHubRelease extends Context.Service()("@effected/github/GitHubRelease") {
60
+ /** The live service, built over a `GitHubClient`. */
43
61
  static layer = Layer.effect(this, Effect.map(GitHubClient, (client) => make(client)));
44
62
  /** An in-memory double; unstubbed members die naming themselves. */
45
63
  static makeTest = (overrides = {}) => ({