@knpkv/jira-api-client 0.4.0 → 1.0.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +88 -65
  3. package/dist/JiraApiClient.d.ts +20 -86
  4. package/dist/JiraApiClient.d.ts.map +1 -1
  5. package/dist/JiraApiClient.js +26 -75
  6. package/dist/JiraApiClient.js.map +1 -1
  7. package/dist/generated/JiraApi.d.ts +103977 -0
  8. package/dist/generated/JiraApi.d.ts.map +1 -0
  9. package/dist/generated/JiraApi.js +7608 -0
  10. package/dist/generated/JiraApi.js.map +1 -0
  11. package/dist/index.d.ts +3 -12
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +3 -11
  14. package/dist/index.js.map +1 -1
  15. package/package.json +13 -16
  16. package/scripts/regenerate.ts +209 -201
  17. package/scripts/tsconfig.json +13 -0
  18. package/src/JiraApiClient.ts +74 -104
  19. package/src/generated/JiraApi.ts +19596 -0
  20. package/src/index.ts +3 -14
  21. package/test/JiraApiClient.test.ts +154 -153
  22. package/.specs/VERSION_V3 +0 -1
  23. package/.specs/jira-v3-1001.0.0-SNAPSHOT-05f20e18d9bde3de68f911b4915970482f536d4e.json +0 -56275
  24. package/.specs/jira-v3-1001.0.0-SNAPSHOT-d7cd03e966bb408ad32ffe870114289c528f56fd.json +0 -56129
  25. package/.specs/jira-v3-1001.0.0-SNAPSHOT-ea9913a8c4412d2bfb1911480b023a04544e19d8.json +0 -55618
  26. package/dist/OpenApiFetchClient.d.ts +0 -55
  27. package/dist/OpenApiFetchClient.d.ts.map +0 -1
  28. package/dist/OpenApiFetchClient.js +0 -68
  29. package/dist/OpenApiFetchClient.js.map +0 -1
  30. package/dist/generated/v3/index.d.ts +0 -7
  31. package/dist/generated/v3/index.d.ts.map +0 -1
  32. package/dist/generated/v3/index.js +0 -2
  33. package/dist/generated/v3/index.js.map +0 -1
  34. package/dist/generated/v3/schema.d.ts +0 -64454
  35. package/src/OpenApiFetchClient.ts +0 -102
  36. package/src/generated/v3/index.ts +0 -6
  37. package/src/generated/v3/schema.d.ts +0 -64454
  38. package/tsconfig.tsbuildinfo +0 -1
@@ -1,128 +1,98 @@
1
1
  /**
2
- * Effect Layer wrapping openapi-fetch Jira V3 client with auth and base URL.
2
+ * Schema-validated Jira Cloud REST API client.
3
3
  *
4
- * **Mental model**
5
- *
6
- * - **Auth-polymorphic**: Supports both Basic (email + API token) and OAuth2 (access token +
7
- * cloud ID) auth. The layer reads {@link JiraApiConfig} to build the Authorization header
8
- * and derive the correct base URL.
9
- * - **openapi-fetch wrapper**: Uses {@link OpenApiFetchClient} for Effect-based methods.
10
- *
11
- * **Common tasks**
12
- *
13
- * - Use the client: `const jira = yield* JiraApiClient; jira.v3.GET(...)`
14
- * - Provide the layer: `Effect.provide(JiraApiClient.layer)`
4
+ * Generated code owns request construction and response decoding. This module
5
+ * applies Jira authentication and contains the one multipart boundary that the
6
+ * upstream specification cannot model as a native FormData value.
15
7
  *
16
8
  * @module
17
9
  */
18
10
  import * as Context from "effect/Context"
19
11
  import * as Effect from "effect/Effect"
20
12
  import * as Encoding from "effect/Encoding"
13
+ import { flow } from "effect/Function"
21
14
  import * as Layer from "effect/Layer"
22
15
  import * as Redacted from "effect/Redacted"
23
- import type { paths as V3Paths } from "./generated/v3/schema.js"
24
- import { JiraApiConfig } from "./JiraApiConfig.js"
25
- import { makeOpenApiFetchClient, type OpenApiFetchClient } from "./OpenApiFetchClient.js"
16
+ import type { SchemaError } from "effect/Schema"
17
+ import * as HttpClient from "effect/unstable/http/HttpClient"
18
+ import type * as HttpClientError from "effect/unstable/http/HttpClientError"
19
+ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest"
20
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"
21
+ import * as Generated from "./generated/JiraApi.js"
22
+ import { JiraApiConfig, type JiraApiConfigShape } from "./JiraApiConfig.js"
26
23
 
27
- /**
28
- * Jira API client shape (v3 only).
29
- *
30
- * @example
31
- * ```typescript
32
- * import { toEffect } from "@knpkv/jira-api-client"
33
- *
34
- * // Get issue by key
35
- * toEffect(client.v3.client.GET("/rest/api/3/issue/{issueIdOrKey}", {
36
- * params: { path: { issueIdOrKey: "PROJ-123" } }
37
- * }))
38
- *
39
- * // Search issues with JQL
40
- * toEffect(client.v3.client.POST("/rest/api/3/search/jql", {
41
- * body: { jql: "project = PROJ", maxResults: 50 }
42
- * }))
43
- * ```
44
- *
45
- * @category Client
46
- */
47
- export interface JiraApiClientShape {
48
- readonly v3: OpenApiFetchClient<V3Paths>
24
+ export interface UploadAttachmentInput {
25
+ readonly bytes: Uint8Array
26
+ readonly filename: string
27
+ readonly mediaType?: string | undefined
49
28
  }
50
29
 
51
- /**
52
- * Jira API client service.
53
- *
54
- * @example
55
- * ```typescript
56
- * import { JiraApiClient, JiraApiConfig } from "@knpkv/jira-api-client"
57
- * import * as Redacted from "effect/Redacted"
58
- * import * as Effect from "effect/Effect"
59
- * import * as Layer from "effect/Layer"
60
- *
61
- * const program = Effect.gen(function* () {
62
- * const client = yield* JiraApiClient
63
- * const issue = yield* toEffect(client.v3.client.GET("/rest/api/3/issue/{issueIdOrKey}", {
64
- * params: { path: { issueIdOrKey: "PROJ-123" } }
65
- * }))
66
- * console.log(issue.fields?.summary)
67
- * })
68
- *
69
- * const configLayer = Layer.succeed(JiraApiConfig, {
70
- * baseUrl: "https://mysite.atlassian.net",
71
- * auth: {
72
- * type: "basic",
73
- * email: "user@example.com",
74
- * apiToken: Redacted.make("token")
75
- * }
76
- * })
77
- *
78
- * Effect.runPromise(
79
- * program.pipe(
80
- * Effect.provide(JiraApiClient.layer),
81
- * Effect.provide(configLayer)
82
- * )
83
- * )
84
- * ```
85
- *
86
- * @category Client
87
- */
30
+ export interface JiraApiClientShape extends Generated.JiraApi {
31
+ readonly uploadAttachment: (
32
+ issueIdOrKey: string,
33
+ input: UploadAttachmentInput
34
+ ) => Effect.Effect<Generated.AddAttachment200, HttpClientError.HttpClientError | SchemaError>
35
+ }
36
+
37
+ const authorizationHeader = (config: JiraApiConfigShape): string =>
38
+ config.auth.type === "basic"
39
+ ? `Basic ${Encoding.encodeBase64(`${config.auth.email}:${Redacted.value(config.auth.apiToken)}`)}`
40
+ : `Bearer ${Redacted.value(config.auth.accessToken)}`
41
+
42
+ const apiBaseUrl = (config: JiraApiConfigShape): string =>
43
+ config.auth.type === "oauth2"
44
+ ? `https://api.atlassian.com/ex/jira/${config.auth.cloudId}`
45
+ : config.baseUrl
46
+
47
+ export const make = (
48
+ httpClient: HttpClient.HttpClient,
49
+ config: JiraApiConfigShape
50
+ ): Generated.JiraApi =>
51
+ Generated.make(httpClient.pipe(
52
+ HttpClient.mapRequest(flow(
53
+ HttpClientRequest.prependUrl(apiBaseUrl(config)),
54
+ HttpClientRequest.setHeader("Authorization", authorizationHeader(config)),
55
+ HttpClientRequest.setHeader("Accept", "application/json")
56
+ ))
57
+ ))
58
+
59
+ const makeUploadAttachment =
60
+ (api: Generated.JiraApi): JiraApiClientShape["uploadAttachment"] => (issueIdOrKey, input) => {
61
+ const buffer = new ArrayBuffer(input.bytes.byteLength)
62
+ new Uint8Array(buffer).set(input.bytes)
63
+ const form = new FormData()
64
+ form.append(
65
+ "file",
66
+ new Blob([buffer], input.mediaType === undefined ? undefined : { type: input.mediaType }),
67
+ input.filename
68
+ )
69
+ return api.httpClient.execute(
70
+ HttpClientRequest.post(`/rest/api/3/issue/${encodeURIComponent(issueIdOrKey)}/attachments`).pipe(
71
+ HttpClientRequest.setHeader("X-Atlassian-Token", "no-check"),
72
+ HttpClientRequest.bodyFormData(form)
73
+ )
74
+ ).pipe(
75
+ Effect.flatMap(HttpClientResponse.filterStatusOk),
76
+ Effect.flatMap(HttpClientResponse.schemaBodyJson(Generated.AddAttachment200))
77
+ )
78
+ }
79
+
88
80
  export class JiraApiClient extends Context.Service<JiraApiClient, JiraApiClientShape>()(
89
81
  "@knpkv/jira-api-client/JiraApiClient"
90
82
  ) {
91
- /**
92
- * Layer that provides JiraApiClient.
93
- *
94
- * Requires: JiraApiConfig
95
- */
96
- static readonly layer: Layer.Layer<JiraApiClient, never, JiraApiConfig> = Layer.effect(
83
+ static readonly layer: Layer.Layer<
84
+ JiraApiClient,
85
+ never,
86
+ JiraApiConfig | HttpClient.HttpClient
87
+ > = Layer.effect(
97
88
  JiraApiClient,
98
89
  Effect.gen(function*() {
99
90
  const config = yield* JiraApiConfig
100
-
101
- // Build auth header
102
- const authHeader = config.auth.type === "basic"
103
- ? `Basic ${Encoding.encodeBase64(`${config.auth.email}:${Redacted.value(config.auth.apiToken)}`)}`
104
- : `Bearer ${Redacted.value(config.auth.accessToken)}`
105
-
106
- // Base URL differs by auth type
107
- const baseUrl = config.auth.type === "oauth2"
108
- ? `https://api.atlassian.com/ex/jira/${config.auth.cloudId}`
109
- : config.baseUrl
110
-
111
- const headers = {
112
- Authorization: authHeader,
113
- Accept: "application/json"
114
- }
115
-
116
- return {
117
- v3: makeOpenApiFetchClient<V3Paths>(baseUrl, headers)
118
- }
91
+ const httpClient = yield* HttpClient.HttpClient
92
+ const api = make(httpClient, config)
93
+ return JiraApiClient.of({ ...api, uploadAttachment: makeUploadAttachment(api) })
119
94
  })
120
95
  )
121
96
  }
122
97
 
123
- /**
124
- * Layer that provides JiraApiClient.
125
- *
126
- * @category Layers
127
- */
128
98
  export const layer = JiraApiClient.layer