@shivaedev/github 0.8.6 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +142 -8
  3. package/dist/GitHub.d.ts +360 -18
  4. package/dist/GitHub.d.ts.map +1 -1
  5. package/dist/GitHub.js +19 -6
  6. package/dist/GitHub.js.map +1 -1
  7. package/dist/GitHubToken.d.ts +2 -2
  8. package/dist/GitHubToken.d.ts.map +1 -1
  9. package/dist/GitHubToken.js +1 -1
  10. package/dist/GitHubToken.js.map +1 -1
  11. package/dist/adapters/resolveApiUrl.d.ts +2 -0
  12. package/dist/adapters/resolveApiUrl.d.ts.map +1 -0
  13. package/dist/adapters/resolveApiUrl.js +6 -0
  14. package/dist/adapters/resolveApiUrl.js.map +1 -0
  15. package/dist/adapters/zipEntries.d.ts +17 -0
  16. package/dist/adapters/zipEntries.d.ts.map +1 -0
  17. package/dist/adapters/zipEntries.js +57 -0
  18. package/dist/adapters/zipEntries.js.map +1 -0
  19. package/dist/appTokens.d.ts +6 -0
  20. package/dist/appTokens.d.ts.map +1 -0
  21. package/dist/appTokens.js +65 -0
  22. package/dist/appTokens.js.map +1 -0
  23. package/dist/artifactErrors.d.ts +34 -0
  24. package/dist/artifactErrors.d.ts.map +1 -0
  25. package/dist/artifactErrors.js +27 -0
  26. package/dist/artifactErrors.js.map +1 -0
  27. package/dist/artifactExtraction.d.ts +12 -0
  28. package/dist/artifactExtraction.d.ts.map +1 -0
  29. package/dist/artifactExtraction.js +40 -0
  30. package/dist/artifactExtraction.js.map +1 -0
  31. package/dist/artifactPaths.d.ts +4 -0
  32. package/dist/artifactPaths.d.ts.map +1 -0
  33. package/dist/artifactPaths.js +35 -0
  34. package/dist/artifactPaths.js.map +1 -0
  35. package/dist/artifacts.d.ts +13 -8
  36. package/dist/artifacts.d.ts.map +1 -1
  37. package/dist/artifacts.js +15 -8
  38. package/dist/artifacts.js.map +1 -1
  39. package/dist/authenticatedRequest.d.ts +1 -1
  40. package/dist/authenticatedRequest.d.ts.map +1 -1
  41. package/dist/authenticatedRequest.js +3 -2
  42. package/dist/authenticatedRequest.js.map +1 -1
  43. package/dist/cache.d.ts +2 -0
  44. package/dist/cache.d.ts.map +1 -1
  45. package/dist/cache.js +3 -0
  46. package/dist/cache.js.map +1 -1
  47. package/dist/checkRuns.d.ts +116 -11
  48. package/dist/checkRuns.d.ts.map +1 -1
  49. package/dist/checkRuns.js +41 -18
  50. package/dist/checkRuns.js.map +1 -1
  51. package/dist/gitSchemas.d.ts +195 -0
  52. package/dist/gitSchemas.d.ts.map +1 -0
  53. package/dist/gitSchemas.js +45 -0
  54. package/dist/gitSchemas.js.map +1 -0
  55. package/dist/githubApp.d.ts +2 -4
  56. package/dist/githubApp.d.ts.map +1 -1
  57. package/dist/githubApp.js +5 -55
  58. package/dist/githubApp.js.map +1 -1
  59. package/dist/installationClients.d.ts +461 -0
  60. package/dist/installationClients.d.ts.map +1 -0
  61. package/dist/installationClients.js +37 -0
  62. package/dist/installationClients.js.map +1 -0
  63. package/dist/json.d.ts.map +1 -1
  64. package/dist/json.js +4 -0
  65. package/dist/json.js.map +1 -1
  66. package/dist/pagedLists.d.ts +15 -0
  67. package/dist/pagedLists.d.ts.map +1 -0
  68. package/dist/pagedLists.js +16 -0
  69. package/dist/pagedLists.js.map +1 -0
  70. package/dist/repositoryReads.d.ts +261 -0
  71. package/dist/repositoryReads.d.ts.map +1 -0
  72. package/dist/repositoryReads.js +30 -0
  73. package/dist/repositoryReads.js.map +1 -0
  74. package/dist/repositorySchemas.d.ts +73 -0
  75. package/dist/repositorySchemas.d.ts.map +1 -0
  76. package/dist/repositorySchemas.js +38 -0
  77. package/dist/repositorySchemas.js.map +1 -0
  78. package/dist/representations.d.ts +4 -4
  79. package/dist/representations.d.ts.map +1 -1
  80. package/dist/representations.js +3 -2
  81. package/dist/representations.js.map +1 -1
  82. package/dist/requests.d.ts +2 -2
  83. package/dist/requests.d.ts.map +1 -1
  84. package/dist/requests.js +11 -9
  85. package/dist/requests.js.map +1 -1
  86. package/dist/status.d.ts +1 -1
  87. package/dist/status.d.ts.map +1 -1
  88. package/dist/stickyComments.d.ts +1 -1
  89. package/dist/viewerRepositoryAccess.d.ts +46 -0
  90. package/dist/viewerRepositoryAccess.d.ts.map +1 -0
  91. package/dist/viewerRepositoryAccess.js +54 -0
  92. package/dist/viewerRepositoryAccess.js.map +1 -0
  93. package/package.json +14 -7
  94. package/src/GitHub.ts +32 -11
  95. package/src/GitHubToken.ts +4 -2
  96. package/src/adapters/resolveApiUrl.ts +5 -0
  97. package/src/adapters/zipEntries.ts +69 -0
  98. package/src/appTokens.ts +93 -0
  99. package/src/artifactErrors.ts +26 -0
  100. package/src/artifactExtraction.ts +62 -0
  101. package/src/artifactPaths.ts +36 -0
  102. package/src/artifacts.ts +36 -39
  103. package/src/authenticatedRequest.ts +3 -1
  104. package/src/cache.ts +5 -0
  105. package/src/checkRuns.ts +58 -18
  106. package/src/gitSchemas.ts +52 -0
  107. package/src/githubApp.ts +6 -80
  108. package/src/installationClients.ts +50 -0
  109. package/src/json.ts +4 -0
  110. package/src/pagedLists.ts +40 -0
  111. package/src/repositoryReads.ts +61 -0
  112. package/src/repositorySchemas.ts +43 -0
  113. package/src/representations.ts +5 -4
  114. package/src/requests.ts +11 -9
  115. package/src/viewerRepositoryAccess.ts +66 -0
@@ -1 +1 @@
1
- {"version":3,"file":"requests.js","sourceRoot":"","sources":["../src/requests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAmB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAuB,aAAa,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,MAAM,UAAU,QAAQ,CACvB,MAA6B,EAC7B,KAA6B,EAC7B,KAAiB,EACjB,OAAO,GAAgC,SAAS;IAEhD,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,oBAAoB,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACjE,OAAO,MAAM,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,QAAQ,CAAC,EAAE,KAA0C,EAAE,KAAa;QACtG,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACnE,IAAI,OAAO,GAAG,iBAAiB,CAAC,YAAY,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;QACrE,IAAI,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACjD,OAAO,GAAG,iBAAiB,CAAC,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/F,CAAC;QACD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACvF,IAAI,WAAW,CAAC,QAAQ,CAAC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACnD,IAAI,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,KAAK,EAAE,CAAC;gBACrC,OAAO,KAAK,CAAC,CAAC,IAAI,oBAAoB,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACnD,CAAC;YACD,OAAO,MAAM,CAAC;QACf,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,IAAI,CACzD,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CACtI,CAAC;QACF,MAAM,KAAK,GAAmB,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,EAAE,QAAQ,EAAE,CAAC;QACtE,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACnE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"requests.js","sourceRoot":"","sources":["../src/requests.ts"],"names":[],"mappings":"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"}
package/dist/status.d.ts CHANGED
@@ -3,5 +3,5 @@ import { type HttpClientResponse } from "effect/http";
3
3
  import { GitHubConversationLocked, GitHubPermissionDenied, GitHubRateLimited } from "#errors.ts";
4
4
  export declare function isUnauthorized(response: HttpClientResponse.HttpClientResponse): boolean;
5
5
  export declare function isUnchanged(response: HttpClientResponse.HttpClientResponse): boolean;
6
- export declare const checkStatus: (response: HttpClientResponse.HttpClientResponse, resource: string, failures?: any) => Effect.Effect<HttpClientResponse.HttpClientResponse, GitHubConversationLocked | GitHubPermissionDenied | GitHubRateLimited | import("effect/http/HttpClientError").HttpClientError | import("@shivaedev/http/errors.ts").ResponseBodyTooLarge | import("@shivaedev/http/errors.ts").RemoteStatusError, never>;
6
+ export declare const checkStatus: (response: HttpClientResponse.HttpClientResponse, resource: string, failures?: any) => Effect.Effect<HttpClientResponse.HttpClientResponse, GitHubConversationLocked | GitHubPermissionDenied | GitHubRateLimited | import("effect/http/HttpClientError").HttpClientError | import("@shivaedev/http/errors.ts").InvalidBodyLimit | import("@shivaedev/http/errors.ts").ResponseBodyTooLarge | import("@shivaedev/http/errors.ts").RemoteStatusError, never>;
7
7
  //# sourceMappingURL=status.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAkB,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAc,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAKlE,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEjG,wBAAgB,cAAc,CAAC,QAAQ,EAAE,kBAAkB,CAAC,kBAAkB,WAE7E;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,kBAAkB,CAAC,kBAAkB,WAE1E;AAED,eAAO,MAAM,WAAW,sYAgBtB,CAAC"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAkB,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAc,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAKlE,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEjG,wBAAgB,cAAc,CAAC,QAAQ,EAAE,kBAAkB,CAAC,kBAAkB,WAE7E;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,kBAAkB,CAAC,kBAAkB,WAE1E;AAED,eAAO,MAAM,WAAW,6bAgBtB,CAAC"}
@@ -41,6 +41,6 @@ export declare function stickyComments(client: Representations): {
41
41
  readonly login: string;
42
42
  readonly type?: string | undefined;
43
43
  } | null;
44
- }, import("./errors.ts").GitHubAppError | import("./errors.ts").GitHubConversationLocked | import("./errors.ts").GitHubPermissionDenied | import("./errors.ts").GitHubRateLimited | import("./errors.ts").GitHubTransportError | import("effect/http/HttpBody").HttpBodyError | import("effect/Cause").NoSuchElementError | import("@shivaedev/http/errors.ts").PaginationLinkError | import("@shivaedev/http/errors.ts").ResponseBodyTooLarge | Schema.SchemaError | import("@shivaedev/errors/taxonomy.ts").ServiceUnavailable | import("@shivaedev/http/errors.ts").RemoteStatusError, never>;
44
+ }, import("./errors.ts").GitHubAppError | import("./errors.ts").GitHubConversationLocked | import("./errors.ts").GitHubPermissionDenied | import("./errors.ts").GitHubRateLimited | import("./errors.ts").GitHubTransportError | import("effect/http/HttpBody").HttpBodyError | import("@shivaedev/http/errors.ts").InvalidBodyLimit | import("effect/Cause").NoSuchElementError | import("@shivaedev/http/errors.ts").PaginationLinkError | import("@shivaedev/http/errors.ts").ResponseBodyTooLarge | Schema.SchemaError | import("@shivaedev/errors/taxonomy.ts").ServiceUnavailable | import("@shivaedev/http/errors.ts").RemoteStatusError, never>;
45
45
  };
46
46
  //# sourceMappingURL=stickyComments.d.ts.map
@@ -0,0 +1,46 @@
1
+ import { Cache, Effect, Schema } from "effect";
2
+ import type { Representations } from "#representations.ts";
3
+ export declare const InstalledRepository: Schema.encodeKeys<Schema.Struct<{
4
+ readonly htmlUrl: Schema.String;
5
+ readonly id: Schema.Number;
6
+ readonly name: Schema.String;
7
+ readonly owner: Schema.Struct<{
8
+ readonly login: Schema.String;
9
+ }>;
10
+ readonly private: Schema.Boolean;
11
+ }>, {
12
+ readonly htmlUrl: "html_url";
13
+ }>;
14
+ export interface ViewerRepositoryAccess {
15
+ readonly administrable: readonly (typeof InstalledRepository.Type & {
16
+ readonly installationId: number;
17
+ })[];
18
+ readonly readable: readonly (typeof InstalledRepository.Type & {
19
+ readonly installationId: number;
20
+ })[];
21
+ }
22
+ export declare function viewerRepositoryAccess(app: Representations, installation: (id: number) => Effect.Effect<Representations>): Effect.Effect<Cache.Cache<string, {
23
+ administrable: ({
24
+ readonly htmlUrl: string;
25
+ readonly id: number;
26
+ readonly name: string;
27
+ readonly owner: {
28
+ readonly login: string;
29
+ };
30
+ readonly private: boolean;
31
+ } & {
32
+ readonly installationId: number;
33
+ })[];
34
+ readable: ({
35
+ readonly htmlUrl: string;
36
+ readonly id: number;
37
+ readonly name: string;
38
+ readonly owner: {
39
+ readonly login: string;
40
+ };
41
+ readonly private: boolean;
42
+ } & {
43
+ readonly installationId: number;
44
+ })[];
45
+ }, import("./errors.ts").GitHubAppError | import("./errors.ts").GitHubConversationLocked | import("./errors.ts").GitHubPermissionDenied | import("./errors.ts").GitHubRateLimited | import("./errors.ts").GitHubTransportError | import("@shivaedev/http/errors.ts").InvalidBodyLimit | import("effect/Cause").NoSuchElementError | import("@shivaedev/http/errors.ts").PaginationLinkError | import("@shivaedev/http/errors.ts").ResponseBodyTooLarge | Schema.SchemaError | import("@shivaedev/errors/taxonomy.ts").ServiceUnavailable | import("@shivaedev/http/errors.ts").RemoteStatusError, never>, never, never>;
46
+ //# sourceMappingURL=viewerRepositoryAccess.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewerRepositoryAccess.d.ts","sourceRoot":"","sources":["../src/viewerRepositoryAccess.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAQ,MAAM,EAAU,MAAM,QAAQ,CAAC;AAE7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,eAAO,MAAM,mBAAmB;;;;;;;;;;EAMmB,CAAC;AAEpD,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,aAAa,EAAE,SAAS,CAAC,OAAO,mBAAmB,CAAC,IAAI,GAAG;QAAE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;IAC3G,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,OAAO,mBAAmB,CAAC,IAAI,GAAG;QAAE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE,CAAC;CACtG;AA6BD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC;;;;;;;;;;iCA/BzB,MAAM;;;;;;;;;;;iCACX,MAAM;;wlBAmDhG"}
@@ -0,0 +1,54 @@
1
+ import { Cache, Effect, Exit, Schema, Stream } from "effect";
2
+ import { decodedPages } from "#pagedLists.ts";
3
+ export const InstalledRepository = Schema.Struct({
4
+ htmlUrl: Schema.String,
5
+ id: Schema.Number,
6
+ name: Schema.String,
7
+ owner: Schema.Struct({ login: Schema.String }),
8
+ private: Schema.Boolean,
9
+ }).pipe(Schema.encodeKeys({ htmlUrl: "html_url" }));
10
+ const Account = Schema.Struct({ login: Schema.String });
11
+ const Installation = Schema.Struct({ id: Schema.Number });
12
+ const Permission = Schema.Struct({ permission: Schema.String });
13
+ const installationAccess = Effect.fnUntraced(function* (client, installationId, login) {
14
+ const pages = yield* decodedPages(client, "/installation/repositories?per_page=100", InstalledRepository, {
15
+ arrayKey: "repositories",
16
+ }).pipe(Stream.runCollect);
17
+ const readable = [];
18
+ const administrable = [];
19
+ for (const repository of pages.flatMap((page) => page.items)) {
20
+ const permissionPath = `/repos/${encodeURIComponent(repository.owner.login)}/${encodeURIComponent(repository.name)}/collaborators/${encodeURIComponent(login)}/permission`;
21
+ const permission = yield* Effect.flatMap(client.read(permissionPath), (response) => response.decode(Permission)).pipe(Effect.map((grant) => grant.permission), Effect.catchTag("RemoteNotFound", () => Effect.succeed("none")));
22
+ const installed = { ...repository, installationId };
23
+ if (!repository.private || ["read", "triage", "write", "maintain", "admin"].includes(permission)) {
24
+ readable.push(installed);
25
+ }
26
+ if (permission === "admin") {
27
+ administrable.push(installed);
28
+ }
29
+ }
30
+ return { administrable, readable };
31
+ });
32
+ export function viewerRepositoryAccess(app, installation) {
33
+ const lookup = Effect.fnUntraced(function* (account) {
34
+ const path = account.startsWith("id:") ? `/user/${account.slice(3)}` : `/users/${encodeURIComponent(account.slice(6))}`;
35
+ const installations = yield* decodedPages(app, "/app/installations?per_page=100", Installation).pipe(Stream.runCollect);
36
+ const first = installations.flatMap((page) => page.items)[0];
37
+ if (first === undefined) {
38
+ return { administrable: [], readable: [] };
39
+ }
40
+ const resolver = yield* installation(first.id);
41
+ const { login } = yield* Effect.flatMap(resolver.read(path), (response) => response.decode(Account));
42
+ const readable = [];
43
+ const administrable = [];
44
+ for (const { id } of installations.flatMap((page) => page.items)) {
45
+ const client = yield* installation(id);
46
+ const access = yield* installationAccess(client, id, login);
47
+ readable.push(...access.readable);
48
+ administrable.push(...access.administrable);
49
+ }
50
+ return { administrable, readable };
51
+ });
52
+ return Cache.makeWith(lookup, { capacity: 100, timeToLive: (exit) => (Exit.isSuccess(exit) ? "60 seconds" : 0) });
53
+ }
54
+ //# sourceMappingURL=viewerRepositoryAccess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viewerRepositoryAccess.js","sourceRoot":"","sources":["../src/viewerRepositoryAccess.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/github",
3
- "version": "0.8.6",
3
+ "version": "0.10.0",
4
4
  "description": "Typed GitHub REST requests, conditional reads and resource budgets for Effect applications",
5
5
  "keywords": [
6
6
  "effect"
@@ -43,25 +43,32 @@
43
43
  "README.md"
44
44
  ],
45
45
  "dependencies": {
46
- "@shivaedev/errors": "0.4.1",
47
- "@shivaedev/http": "0.3.1",
48
- "@shivaedev/service": "0.6.0"
46
+ "@shivaedev/errors": "0.5.0",
47
+ "@shivaedev/http": "0.4.0",
48
+ "@shivaedev/service": "0.6.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@effect/platform-node": "4.0.6",
52
52
  "@effect/platform-node-shared": "4.0.6",
53
53
  "@effect/vitest": "4.0.6",
54
- "@shivaedev/quality": "0.54.0",
55
- "@shivaedev/test": "0.12.0",
56
- "@shivaedev/test-story": "0.32.0",
54
+ "@shivaedev/quality": "0.56.0",
55
+ "@shivaedev/test": "0.13.0",
56
+ "@shivaedev/test-story": "0.34.0",
57
57
  "@types/node": "24.19.2",
58
+ "@zip.js/zip.js": "2.24.0",
58
59
  "effect": "4.0.6",
59
60
  "typescript": "7.0.2",
60
61
  "vitest": "5.0.5"
61
62
  },
62
63
  "peerDependencies": {
64
+ "@zip.js/zip.js": "2.24.0",
63
65
  "effect": "4.0.6"
64
66
  },
67
+ "peerDependenciesMeta": {
68
+ "@zip.js/zip.js": {
69
+ "optional": true
70
+ }
71
+ },
65
72
  "engines": {
66
73
  "node": ">=24"
67
74
  },
package/src/GitHub.ts CHANGED
@@ -5,7 +5,9 @@ import { artifacts } from "#artifacts.ts";
5
5
  import { checkRuns } from "#checkRuns.ts";
6
6
  import { GitHubConfig } from "#GitHubConfig.ts";
7
7
  import { GitHubToken } from "#GitHubToken.ts";
8
+ import { decodedPages, type ListOptions } from "#pagedLists.ts";
8
9
  import { rateLimits } from "#rateLimits.ts";
10
+ import { repositoryReads } from "#repositoryReads.ts";
9
11
  import { representations } from "#representations.ts";
10
12
  import { requests } from "#requests.ts";
11
13
  import { stickyComments } from "#stickyComments.ts";
@@ -21,7 +23,7 @@ export const GitHub = defineService({
21
23
  const refresh = yield* TokenRefresh;
22
24
  return {
23
25
  apiUrl: config.apiUrl,
24
- archive: artifacts(config.apiUrl, client, token, rates, refresh),
26
+ archive: yield* artifacts(config.apiUrl, rates),
25
27
  rates,
26
28
  request: requests(client, token, rates, refresh),
27
29
  responseByteLimit: config.responseByteLimit,
@@ -35,6 +37,7 @@ export const GitHub = defineService({
35
37
  ...archive,
36
38
  ...stickyComments(client),
37
39
  ...checkRuns(client),
40
+ ...repositoryReads(client),
38
41
  observations: () => Effect.sync(() => [...rates.observations.values()]),
39
42
  resource: (path: string) => Effect.sync(() => rates.resource(new URL(path, apiUrl).pathname)),
40
43
  text: (path: string, accept: string, limit: number) =>
@@ -48,24 +51,42 @@ export function read<TSchema extends Schema.Constraint>(path: string, schema: TS
48
51
  return Effect.flatMap(GitHub, (github) => Effect.flatMap(github.read(path, responseByteLimit), (response) => response.decode(schema)));
49
52
  }
50
53
 
51
- export function list<TSchema extends Schema.Constraint>(path: string, schema: TSchema, responseByteLimit?: number) {
52
- return Stream.unwrap(Effect.flatMap(GitHub, (github) => github.list(path, undefined, responseByteLimit))).pipe(
53
- Stream.mapEffect((response) => response.decode(Schema.Array(schema))),
54
- Stream.flatMap(Stream.fromIterable),
55
- );
54
+ export function listPages<TSchema extends Schema.Constraint>(path: string, schema: TSchema, options?: ListOptions | number) {
55
+ return Stream.unwrap(Effect.map(GitHub, (github) => decodedPages(github, path, schema, options)));
56
+ }
57
+
58
+ export function list<TSchema extends Schema.Constraint>(path: string, schema: TSchema, options?: ListOptions | number) {
59
+ return listPages(path, schema, options).pipe(Stream.flatMap((page) => Stream.fromIterable(page.items)));
56
60
  }
57
61
 
58
- export function listBounded<TSchema extends Schema.Constraint>(path: string, schema: TSchema, pageCap: number, responseByteLimit?: number) {
59
- return Stream.unwrap(Effect.flatMap(GitHub, (github) => github.list(path, undefined, responseByteLimit))).pipe(
62
+ export function listBounded<TSchema extends Schema.Constraint>(path: string, schema: TSchema, pageCap: number, options?: ListOptions | number) {
63
+ return listPages(path, schema, options).pipe(
60
64
  Stream.take(pageCap),
61
- Stream.mapEffect((page) => Effect.map(page.decode(Schema.Array(schema)), (items) => ({ items, truncated: page.hasNext }))),
62
65
  Stream.runCollect,
63
- Effect.map((pages) => ({ items: pages.flatMap((page) => page.items), truncated: pages.at(-1)?.truncated ?? false })),
66
+ Effect.map((pages) => ({
67
+ items: pages.flatMap((page) => page.items),
68
+ skipped: pages.reduce((count, page) => count + page.skipped, 0),
69
+ truncated: pages.at(-1)?.hasNext ?? false,
70
+ })),
64
71
  );
65
72
  }
66
73
 
74
+ type WriteMethod = "POST" | "PATCH" | "PUT" | "DELETE";
75
+
67
76
  export function write<TSchema extends Schema.Constraint>(
68
- method: "POST" | "PATCH",
77
+ method: WriteMethod,
78
+ path: string,
79
+ body: unknown,
80
+ schema: TSchema,
81
+ responseByteLimit?: number,
82
+ ): ReturnType<typeof writeDecoded<TSchema>>;
83
+ export function write(method: WriteMethod, path: string, body?: unknown): ReturnType<typeof writeDecoded<typeof Schema.Void>>;
84
+ export function write(method: WriteMethod, path: string, body?: unknown, schema: Schema.Constraint = Schema.Void, responseByteLimit?: number) {
85
+ return writeDecoded(method, path, body, schema, responseByteLimit);
86
+ }
87
+
88
+ function writeDecoded<TSchema extends Schema.Constraint>(
89
+ method: WriteMethod,
69
90
  path: string,
70
91
  body: unknown,
71
92
  schema: TSchema,
@@ -1,6 +1,6 @@
1
1
  import { Config, Context, Effect, Layer, Option, Redacted, Stream } from "effect";
2
2
  import { ChildProcess, ChildProcessSpawner } from "effect/process";
3
- import { type GitHubAppError, GitHubTokenError } from "./errors.ts";
3
+ import { type GitHubAppError, type GitHubRateLimited, GitHubTokenError } from "#errors.ts";
4
4
 
5
5
  type Token = Option.Option<Redacted.Redacted<string>>;
6
6
  const environmentToken = Config.Redacted("GH_TOKEN").pipe(
@@ -8,7 +8,9 @@ const environmentToken = Config.Redacted("GH_TOKEN").pipe(
8
8
  Config.option,
9
9
  );
10
10
 
11
- export class GitHubToken extends Context.Service<GitHubToken, Token | Effect.Effect<Token, GitHubAppError>>()("@shivaedev/github/GitHubToken") {
11
+ export class GitHubToken extends Context.Service<GitHubToken, Token | Effect.Effect<Token, GitHubAppError | GitHubRateLimited>>()(
12
+ "@shivaedev/github/GitHubToken",
13
+ ) {
12
14
  static readonly fromEnv = Layer.effect(GitHubToken)(environmentToken);
13
15
 
14
16
  static readonly fromEnvOrCli = Layer.effect(GitHubToken)(
@@ -0,0 +1,5 @@
1
+ export function resolveApiUrl(apiUrl: string, path: string) {
2
+ const base = new URL(apiUrl);
3
+ base.pathname = `${base.pathname.replace(/\/$/u, "")}/`;
4
+ return new URL(path.replace(/^\/(?!\/)/u, ""), base).href;
5
+ }
@@ -0,0 +1,69 @@
1
+ import type { Entry } from "@zip.js/zip.js";
2
+ import { Effect, Stream } from "effect";
3
+ import { GitHubArtifactCompressionRatio, GitHubArtifactExtractedTooLarge, GitHubArtifactExtractionError } from "#artifactErrors.ts";
4
+
5
+ interface ReadLimits {
6
+ readonly compressionRatioLimit: number;
7
+ readonly remaining: number;
8
+ readonly totalSizeLimit: number;
9
+ }
10
+
11
+ function invalidArchive() {
12
+ return new GitHubArtifactExtractionError({ reason: "invalid-archive" });
13
+ }
14
+
15
+ function readEntry(entry: Entry, limits: ReadLimits) {
16
+ return Effect.tryPromise({
17
+ catch: (error) =>
18
+ error instanceof GitHubArtifactExtractedTooLarge || error instanceof GitHubArtifactCompressionRatio ? error : invalidArchive(),
19
+ try: async (signal) => {
20
+ let size = 0;
21
+ const chunks: Uint8Array[] = [];
22
+ const output = new WritableStream<Uint8Array>({
23
+ write(chunk) {
24
+ size += chunk.byteLength;
25
+ if (size > limits.remaining) {
26
+ throw new GitHubArtifactExtractedTooLarge({ limit: limits.totalSizeLimit });
27
+ }
28
+ if (size > entry.compressedSize * limits.compressionRatioLimit) {
29
+ throw new GitHubArtifactCompressionRatio({ limit: limits.compressionRatioLimit, path: entry.filename });
30
+ }
31
+ chunks.push(chunk);
32
+ },
33
+ });
34
+ if (!entry.directory) {
35
+ await entry.getData(output, { signal });
36
+ }
37
+ const result = new Uint8Array(size);
38
+ let offset = 0;
39
+ for (const chunk of chunks) {
40
+ result.set(chunk, offset);
41
+ offset += chunk.byteLength;
42
+ }
43
+ return result;
44
+ },
45
+ });
46
+ }
47
+
48
+ export const zipEntries = Effect.fn("GitHub.zipEntries")(function* (bytes: Uint8Array) {
49
+ const { ZipReader, Uint8ArrayReader } = yield* Effect.tryPromise({
50
+ catch: () => new GitHubArtifactExtractionError({ reason: "reader-unavailable" }),
51
+ try: () => import("@zip.js/zip.js"),
52
+ });
53
+ const reader = new ZipReader(new Uint8ArrayReader(bytes), {
54
+ checkCrc32: true,
55
+ // Core validates paths so refusals retain their dedicated typed error.
56
+ filenameValidation: "tolerant",
57
+ useWebWorkers: false,
58
+ });
59
+ return Stream.fromAsyncIterable(reader.getEntriesGenerator(), invalidArchive).pipe(
60
+ Stream.map((entry) => ({
61
+ compressedSize: entry.compressedSize,
62
+ directory: entry.directory,
63
+ path: entry.filename,
64
+ read: (limits: ReadLimits) => readEntry(entry, limits),
65
+ symlink: entry.symlink,
66
+ uncompressedSize: entry.uncompressedSize,
67
+ })),
68
+ );
69
+ });
@@ -0,0 +1,93 @@
1
+ import { Cache, Clock, Context, Effect, Exit, Layer, Option, type Redacted, Schema } from "effect";
2
+ import { HttpClient, HttpClientRequest } from "effect/http";
3
+ import { readBoundedJson } from "@shivaedev/http/body.ts";
4
+ import { resolveApiUrl } from "#adapters/resolveApiUrl.ts";
5
+ import { appJwt } from "#appJwt.ts";
6
+ import { authenticatedRequest } from "#authenticatedRequest.ts";
7
+ import { GitHubAppError } from "#errors.ts";
8
+ import { GitHubConfig } from "#GitHubConfig.ts";
9
+ import { GitHubToken } from "#GitHubToken.ts";
10
+ import type { GitHubAppOptions } from "#githubApp.ts";
11
+ import { rateLimits } from "#rateLimits.ts";
12
+ import { TokenRefresh } from "#TokenRefresh.ts";
13
+
14
+ const Installation = Schema.Struct({ id: Schema.Number });
15
+ const AccessToken = Schema.Struct({ expiresAt: Schema.DateFromString, token: Schema.RedactedFromValue(Schema.String) }).pipe(
16
+ Schema.encodeKeys({ expiresAt: "expires_at" }),
17
+ );
18
+
19
+ export function appTokens(options: GitHubAppOptions) {
20
+ return Layer.effectContext(
21
+ Effect.gen(function* () {
22
+ if (!("installationId" in options || "owner" in options)) {
23
+ return Context.make(GitHubToken, Effect.map(appJwt(options), Option.some)).pipe(Context.add(TokenRefresh, undefined));
24
+ }
25
+ const client = yield* HttpClient.HttpClient;
26
+ const { apiUrl } = yield* GitHubConfig;
27
+ let installationId = "installationId" in options ? options.installationId : undefined;
28
+ const request = appRequests(client, apiUrl);
29
+ const exchange = Effect.gen(function* () {
30
+ const jwt = yield* appJwt(options);
31
+ if (installationId === undefined && "owner" in options && options.owner !== undefined) {
32
+ const path = `/repos/${encodeURIComponent(options.owner)}/${encodeURIComponent(options.repository)}/installation`;
33
+ installationId = (yield* request("GET", path, jwt, Installation)).id;
34
+ }
35
+ const access = yield* request("POST", `/app/installations/${installationId}/access_tokens`, jwt, AccessToken, {
36
+ permissions: options.scope?.permissions,
37
+ repositories: options.scope?.repositories,
38
+ "repository_ids": options.scope?.repositoryIds,
39
+ });
40
+ const now = yield* Clock.currentTimeMillis;
41
+ return { ...access, timeToLive: Math.max(0, access.expiresAt.getTime() - now - 300_000) };
42
+ });
43
+ const cache = yield* Cache.makeWith((_: undefined) => exchange, {
44
+ capacity: 1,
45
+ timeToLive: (exit) => (Exit.isSuccess(exit) ? exit.value.timeToLive : 0),
46
+ });
47
+ return Context.make(
48
+ GitHubToken,
49
+ Effect.map(Cache.get(cache, undefined), ({ token }) => Option.some(token)),
50
+ ).pipe(
51
+ Context.add(TokenRefresh, (rejected) =>
52
+ Cache.invalidateWhen(cache, undefined, ({ token }) => Option.isSome(rejected) && token === rejected.value),
53
+ ),
54
+ );
55
+ }),
56
+ );
57
+ }
58
+
59
+ function appRequests(client: HttpClient.HttpClient, apiUrl: string) {
60
+ const scoped = HttpClient.withScope(client);
61
+ const rates = rateLimits();
62
+ return Effect.fnUntraced(function* <TSchema extends Schema.Constraint>(
63
+ method: "GET" | "POST",
64
+ path: string,
65
+ jwt: Redacted.Redacted<string>,
66
+ schema: TSchema,
67
+ body?: unknown,
68
+ ) {
69
+ let input = HttpClientRequest.make(method)(resolveApiUrl(apiUrl, path), {
70
+ headers: { Accept: "application/vnd.github+json" },
71
+ });
72
+ if (body !== undefined) {
73
+ input = yield* HttpClientRequest.bodyJson(input, body).pipe(Effect.mapError((cause) => new GitHubAppError({ reason: "encode" }, { cause })));
74
+ }
75
+ const response = yield* authenticatedRequest(
76
+ scoped,
77
+ Option.some(jwt),
78
+ rates,
79
+ )(input).pipe(
80
+ Effect.mapError((cause) => {
81
+ if (cause._tag === "GitHubRateLimited") {
82
+ return cause;
83
+ }
84
+ return cause._tag === "GitHubTransportError"
85
+ ? new GitHubAppError({ reason: "transport" }, { cause: cause.cause })
86
+ : new GitHubAppError({ reason: "status", status: "status" in cause ? cause.status : 403 });
87
+ }),
88
+ );
89
+ return yield* readBoundedJson(response, 64 * 1024, schema).pipe(
90
+ Effect.mapError((cause) => new GitHubAppError({ reason: cause._tag === "HttpClientError" ? "transport" : "response" }, { cause })),
91
+ );
92
+ }, Effect.scoped);
93
+ }
@@ -0,0 +1,26 @@
1
+ import { Schema } from "effect";
2
+
3
+ export class GitHubArtifactPathTraversal extends Schema.TaggedError<GitHubArtifactPathTraversal>()("GitHubArtifactPathTraversal", {
4
+ path: Schema.String,
5
+ }) {}
6
+
7
+ export class GitHubArtifactSymlink extends Schema.TaggedError<GitHubArtifactSymlink>()("GitHubArtifactSymlink", {
8
+ path: Schema.String,
9
+ }) {}
10
+
11
+ export class GitHubArtifactDuplicateEntry extends Schema.TaggedError<GitHubArtifactDuplicateEntry>()("GitHubArtifactDuplicateEntry", {
12
+ path: Schema.String,
13
+ }) {}
14
+
15
+ export class GitHubArtifactCompressionRatio extends Schema.TaggedError<GitHubArtifactCompressionRatio>()("GitHubArtifactCompressionRatio", {
16
+ limit: Schema.Number,
17
+ path: Schema.String,
18
+ }) {}
19
+
20
+ export class GitHubArtifactExtractedTooLarge extends Schema.TaggedError<GitHubArtifactExtractedTooLarge>()("GitHubArtifactExtractedTooLarge", {
21
+ limit: Schema.Number,
22
+ }) {}
23
+
24
+ export class GitHubArtifactExtractionError extends Schema.TaggedError<GitHubArtifactExtractionError>()("GitHubArtifactExtractionError", {
25
+ reason: Schema.Literals(["reader-unavailable", "invalid-archive", "invalid-limits"]),
26
+ }) {}
@@ -0,0 +1,62 @@
1
+ import { Effect, Stream } from "effect";
2
+ import { zipEntries } from "#adapters/zipEntries.ts";
3
+ import {
4
+ GitHubArtifactCompressionRatio,
5
+ GitHubArtifactExtractedTooLarge,
6
+ GitHubArtifactExtractionError,
7
+ GitHubArtifactSymlink,
8
+ } from "#artifactErrors.ts";
9
+ import { artifactPaths } from "#artifactPaths.ts";
10
+
11
+ export interface ArtifactExtractionOptions {
12
+ readonly compressionRatioLimit?: number;
13
+ readonly totalSizeLimit?: number;
14
+ }
15
+
16
+ export interface ArtifactFile {
17
+ readonly bytes: Uint8Array;
18
+ readonly path: string;
19
+ }
20
+
21
+ export const extractArtifactArchive = Effect.fn("GitHub.extractArtifactArchive")(function* (bytes: Uint8Array, options: ArtifactExtractionOptions) {
22
+ const totalSizeLimit = options.totalSizeLimit ?? 100 * 1024 * 1024;
23
+ const compressionRatioLimit = options.compressionRatioLimit ?? 100;
24
+ if (!Number.isSafeInteger(totalSizeLimit) || totalSizeLimit < 0 || !Number.isFinite(compressionRatioLimit) || compressionRatioLimit <= 0) {
25
+ return yield* new GitHubArtifactExtractionError({ reason: "invalid-limits" });
26
+ }
27
+ const entries = yield* zipEntries(bytes);
28
+ const pathOf = artifactPaths();
29
+ const files: ArtifactFile[] = [];
30
+ let total = 0;
31
+ yield* entries.pipe(
32
+ Stream.runForEach((entry) =>
33
+ Effect.gen(function* () {
34
+ const path = yield* pathOf(entry.path, entry.directory);
35
+ yield* validateEntry(entry, total, { compressionRatioLimit, totalSizeLimit });
36
+ const content = yield* entry.read({ compressionRatioLimit, remaining: totalSizeLimit - total, totalSizeLimit });
37
+ total += content.byteLength;
38
+ if (!entry.directory) {
39
+ files.push({ bytes: content, path });
40
+ }
41
+ }),
42
+ ),
43
+ );
44
+ return files;
45
+ });
46
+
47
+ type ZipEntry = Stream.Success<Effect.Success<ReturnType<typeof zipEntries>>>;
48
+
49
+ const validateEntry = Effect.fnUntraced(function* (entry: ZipEntry, total: number, options: Required<ArtifactExtractionOptions>) {
50
+ if (entry.symlink) {
51
+ return yield* new GitHubArtifactSymlink({ path: entry.path });
52
+ }
53
+ if (entry.directory && entry.uncompressedSize !== 0) {
54
+ return yield* new GitHubArtifactExtractionError({ reason: "invalid-archive" });
55
+ }
56
+ if (total + entry.uncompressedSize > options.totalSizeLimit) {
57
+ return yield* new GitHubArtifactExtractedTooLarge({ limit: options.totalSizeLimit });
58
+ }
59
+ if (entry.uncompressedSize > entry.compressedSize * options.compressionRatioLimit) {
60
+ return yield* new GitHubArtifactCompressionRatio({ limit: options.compressionRatioLimit, path: entry.path });
61
+ }
62
+ });
@@ -0,0 +1,36 @@
1
+ import { Effect } from "effect";
2
+ import { GitHubArtifactDuplicateEntry, GitHubArtifactPathTraversal } from "#artifactErrors.ts";
3
+
4
+ const safeName = Effect.fnUntraced(function* (path: string, directory: boolean) {
5
+ const name = directory && path.endsWith("/") ? path.slice(0, -1) : path;
6
+ const segments = name.split("/");
7
+ if (/[\\:\0]/u.test(name) || segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
8
+ return yield* new GitHubArtifactPathTraversal({ path });
9
+ }
10
+ return { name, segments };
11
+ });
12
+
13
+ export function artifactPaths() {
14
+ const entries = new Set<string>();
15
+ const files = new Set<string>();
16
+ const directories = new Set<string>();
17
+ const registerParents = Effect.fnUntraced(function* (path: string, segments: readonly string[]) {
18
+ for (let index = 1; index < segments.length; index += 1) {
19
+ const parent = segments.slice(0, index).join("/");
20
+ if (files.has(parent)) {
21
+ return yield* new GitHubArtifactDuplicateEntry({ path });
22
+ }
23
+ directories.add(parent);
24
+ }
25
+ });
26
+ return Effect.fnUntraced(function* (path: string, directory: boolean) {
27
+ const { name, segments } = yield* safeName(path, directory);
28
+ if (entries.has(name) || (!directory && directories.has(name))) {
29
+ return yield* new GitHubArtifactDuplicateEntry({ path });
30
+ }
31
+ yield* registerParents(path, segments);
32
+ entries.add(name);
33
+ (directory ? directories : files).add(name);
34
+ return name;
35
+ });
36
+ }