@knpkv/jira-api-client 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.specs/VERSION_V3 +1 -0
  2. package/.specs/jira-v3-1001.0.0-SNAPSHOT-ea9913a8c4412d2bfb1911480b023a04544e19d8.json +55618 -0
  3. package/CHANGELOG.md +11 -0
  4. package/LICENSE +21 -0
  5. package/README.md +94 -0
  6. package/dist/JiraApiClient.d.ts +99 -0
  7. package/dist/JiraApiClient.d.ts.map +1 -0
  8. package/dist/JiraApiClient.js +94 -0
  9. package/dist/JiraApiClient.js.map +1 -0
  10. package/dist/JiraApiConfig.d.ts +57 -0
  11. package/dist/JiraApiConfig.d.ts.map +1 -0
  12. package/dist/JiraApiConfig.js +37 -0
  13. package/dist/JiraApiConfig.js.map +1 -0
  14. package/dist/OpenApiFetchClient.d.ts +55 -0
  15. package/dist/OpenApiFetchClient.d.ts.map +1 -0
  16. package/dist/OpenApiFetchClient.js +63 -0
  17. package/dist/OpenApiFetchClient.js.map +1 -0
  18. package/dist/generated/v3/index.d.ts +7 -0
  19. package/dist/generated/v3/index.d.ts.map +1 -0
  20. package/dist/generated/v3/index.js +2 -0
  21. package/dist/generated/v3/index.js.map +1 -0
  22. package/dist/generated/v3/schema.d.ts +64353 -0
  23. package/dist/index.d.ts +18 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +17 -0
  26. package/dist/index.js.map +1 -0
  27. package/package.json +78 -0
  28. package/scripts/regenerate.ts +141 -0
  29. package/src/JiraApiClient.ts +129 -0
  30. package/src/JiraApiConfig.ts +51 -0
  31. package/src/OpenApiFetchClient.ts +92 -0
  32. package/src/generated/v3/index.ts +6 -0
  33. package/src/generated/v3/schema.d.ts +64353 -0
  34. package/src/index.ts +22 -0
  35. package/test/JiraApiClient.test.ts +145 -0
  36. package/tsconfig.json +11 -0
  37. package/tsconfig.tsbuildinfo +1 -0
  38. package/vitest.config.ts +7 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Root barrel export for `@knpkv/jira-api-client` -- openapi-fetch + Effect Jira REST client.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Namespaced version**: `V3` re-exports the generated OpenAPI types (paths, components, operations).
7
+ * - **Config + client split**: {@link JiraApiConfig} is provided separately from the
8
+ * generated client to support auth-polymorphic usage.
9
+ * - **openapi-fetch wrapper**: {@link OpenApiFetchClient} provides Effect-based `execute` method
10
+ * around raw openapi-fetch clients.
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ export { JiraApiClient, type JiraApiClientShape, layer } from "./JiraApiClient.js";
15
+ export { JiraApiConfig, type JiraApiConfigShape } from "./JiraApiConfig.js";
16
+ export { FetchClientError, type OpenApiFetchClient, type SuccessData, toEffect } from "./OpenApiFetchClient.js";
17
+ export type * as V3 from "./generated/v3/index.js";
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAElF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAE3E,OAAO,EAAE,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAG/G,YAAY,KAAK,EAAE,MAAM,yBAAyB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Root barrel export for `@knpkv/jira-api-client` -- openapi-fetch + Effect Jira REST client.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Namespaced version**: `V3` re-exports the generated OpenAPI types (paths, components, operations).
7
+ * - **Config + client split**: {@link JiraApiConfig} is provided separately from the
8
+ * generated client to support auth-polymorphic usage.
9
+ * - **openapi-fetch wrapper**: {@link OpenApiFetchClient} provides Effect-based `execute` method
10
+ * around raw openapi-fetch clients.
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ export { JiraApiClient, layer } from "./JiraApiClient.js";
15
+ export { JiraApiConfig } from "./JiraApiConfig.js";
16
+ export { FetchClientError, toEffect } from "./OpenApiFetchClient.js";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAA2B,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAElF,OAAO,EAAE,aAAa,EAA2B,MAAM,oBAAoB,CAAA;AAE3E,OAAO,EAAE,gBAAgB,EAA6C,QAAQ,EAAE,MAAM,yBAAyB,CAAA"}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@knpkv/jira-api-client",
3
+ "version": "0.2.0",
4
+ "description": "Effect-based Jira Cloud REST API v3 client",
5
+ "license": "MIT",
6
+ "author": "knpkv",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/knpkv/npm.git",
10
+ "directory": "packages/jira-api-client"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/knpkv/npm/issues"
14
+ },
15
+ "homepage": "https://github.com/knpkv/npm/tree/main/packages/jira-api-client#readme",
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "import": "./dist/index.js",
23
+ "types": "./dist/index.d.ts"
24
+ },
25
+ "./JiraApiClient": {
26
+ "import": "./dist/JiraApiClient.js",
27
+ "types": "./dist/JiraApiClient.d.ts"
28
+ },
29
+ "./JiraApiConfig": {
30
+ "import": "./dist/JiraApiConfig.js",
31
+ "types": "./dist/JiraApiConfig.d.ts"
32
+ },
33
+ "./v3": {
34
+ "import": "./dist/generated/v3/index.js",
35
+ "types": "./dist/generated/v3/index.d.ts"
36
+ }
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "peerDependencies": {
42
+ "effect": "^3.19.3"
43
+ },
44
+ "devDependencies": {
45
+ "@effect/cli": "latest",
46
+ "@effect/platform-node": "latest",
47
+ "@effect/vitest": "latest",
48
+ "@types/node": "latest",
49
+ "effect": "latest",
50
+ "openapi-typescript": "^7.13.0",
51
+ "openapi-typescript-helpers": "^0.1.0",
52
+ "tsx": "^4.19.4",
53
+ "typescript": "~5.9.0",
54
+ "vitest": "^4.0.13"
55
+ },
56
+ "keywords": [
57
+ "effect",
58
+ "effect-ts",
59
+ "jira",
60
+ "atlassian",
61
+ "api-client",
62
+ "openapi"
63
+ ],
64
+ "dependencies": {
65
+ "openapi-fetch": "^0.17.0"
66
+ },
67
+ "scripts": {
68
+ "build": "tsc && cp src/generated/v3/schema.d.ts dist/generated/v3/schema.d.ts",
69
+ "clean": "rimraf dist dist-test .tsbuildinfo",
70
+ "test": "vitest run",
71
+ "test:watch": "vitest",
72
+ "lint": "eslint src",
73
+ "lint:fix": "eslint src --fix",
74
+ "check": "tsc --noEmit",
75
+ "regenerate": "tsx scripts/regenerate.ts",
76
+ "regenerate:check": "tsx scripts/regenerate.ts --check"
77
+ }
78
+ }
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Regeneration script for Jira API types.
4
+ *
5
+ * Fetches OpenAPI specs from Atlassian and regenerates openapi-typescript types.
6
+ */
7
+ import { Command, Options } from "@effect/cli"
8
+ import { NodeContext, NodeRuntime } from "@effect/platform-node"
9
+ import * as Console from "effect/Console"
10
+ import * as Effect from "effect/Effect"
11
+ import { execSync } from "node:child_process"
12
+ import * as fs from "node:fs"
13
+ import * as path from "node:path"
14
+ import { fileURLToPath } from "node:url"
15
+ import pkg from "../package.json" with { type: "json" }
16
+
17
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
18
+ const SPECS_DIR = path.join(__dirname, "..", ".specs")
19
+ const VERSION_V3_FILE = path.join(SPECS_DIR, "VERSION_V3")
20
+ const GENERATED_DIR = path.join(__dirname, "..", "src", "generated", "v3")
21
+
22
+ const SPEC_URLS = {
23
+ v3: "https://dac-static.atlassian.com/cloud/jira/platform/swagger-v3.v3.json"
24
+ } as const
25
+
26
+ interface SpecInfo {
27
+ version: string
28
+ title: string
29
+ }
30
+
31
+ const fetchSpecInfo = (url: string): Effect.Effect<SpecInfo, Error> =>
32
+ Effect.tryPromise({
33
+ try: async () => {
34
+ const response = await fetch(url)
35
+ if (!response.ok) {
36
+ throw new Error(`Failed to fetch ${url}: ${response.status}`)
37
+ }
38
+ const spec = await response.json() as { info: { version: string; title: string } }
39
+ return { version: spec.info.version, title: spec.info.title }
40
+ },
41
+ catch: (e) => new Error(`Fetch failed: ${e}`)
42
+ })
43
+
44
+ const fetchAndSaveSpec = (url: string, outputPath: string): Effect.Effect<void, Error> =>
45
+ Effect.tryPromise({
46
+ try: async () => {
47
+ const response = await fetch(url)
48
+ if (!response.ok) {
49
+ throw new Error(`Failed to fetch ${url}: ${response.status}`)
50
+ }
51
+ const spec = await response.text()
52
+ fs.writeFileSync(outputPath, spec, "utf-8")
53
+ },
54
+ catch: (e) => new Error(`Fetch/save failed: ${e}`)
55
+ })
56
+
57
+ const readVersion = (file: string): Effect.Effect<string | null> =>
58
+ Effect.sync(() => {
59
+ try {
60
+ return fs.readFileSync(file, "utf-8").trim()
61
+ } catch {
62
+ return null
63
+ }
64
+ })
65
+
66
+ const writeVersion = (file: string, version: string): Effect.Effect<void> =>
67
+ Effect.sync(() => fs.writeFileSync(file, version, "utf-8"))
68
+
69
+ const ensureDir = (dir: string): Effect.Effect<void> =>
70
+ Effect.sync(() => {
71
+ if (!fs.existsSync(dir)) {
72
+ fs.mkdirSync(dir, { recursive: true })
73
+ }
74
+ })
75
+
76
+ const generateTypes = (specPath: string): Effect.Effect<void, Error> =>
77
+ Effect.tryPromise({
78
+ try: async () => {
79
+ const outputFile = path.join(GENERATED_DIR, "schema.d.ts")
80
+ execSync(
81
+ `pnpm exec openapi-typescript "${specPath}" -o "${outputFile}"`,
82
+ { cwd: path.join(__dirname, "..") }
83
+ )
84
+ },
85
+ catch: (e) => new Error(`Type generation failed: ${e}`)
86
+ })
87
+
88
+ const checkOnly = Options.boolean("check").pipe(
89
+ Options.withDescription("Check only, exit 1 if outdated"),
90
+ Options.withDefault(false)
91
+ )
92
+
93
+ const regenerate = Command.make("regenerate", { checkOnly }, ({ checkOnly }) =>
94
+ Effect.gen(function*() {
95
+ yield* Console.log("Fetching Jira API spec version...")
96
+
97
+ const v3Info = yield* fetchSpecInfo(SPEC_URLS.v3)
98
+ const currentV3 = yield* readVersion(VERSION_V3_FILE)
99
+
100
+ yield* Console.log(`V3: current=${currentV3 ?? "none"}, remote=${v3Info.version}`)
101
+
102
+ const v3Changed = currentV3 !== v3Info.version
103
+
104
+ if (!v3Changed) {
105
+ yield* Console.log("Spec is up to date.")
106
+ return
107
+ }
108
+
109
+ if (checkOnly) {
110
+ yield* Console.log("Spec is outdated!")
111
+ yield* Console.log(` V3: ${currentV3} -> ${v3Info.version}`)
112
+ return yield* Effect.fail(new Error("Spec outdated"))
113
+ }
114
+
115
+ yield* ensureDir(SPECS_DIR)
116
+ yield* ensureDir(GENERATED_DIR)
117
+
118
+ const specPath = path.join(SPECS_DIR, `jira-v3-${v3Info.version}.json`)
119
+
120
+ yield* Console.log(`Fetching V3 spec (${v3Info.version})...`)
121
+ yield* fetchAndSaveSpec(SPEC_URLS.v3, specPath)
122
+ yield* Console.log(`Saved: ${specPath}`)
123
+
124
+ yield* Console.log("Generating types...")
125
+ yield* generateTypes(specPath)
126
+ yield* Console.log(`Generated: src/generated/v3/schema.d.ts`)
127
+
128
+ yield* writeVersion(VERSION_V3_FILE, v3Info.version)
129
+ yield* Console.log("Done!")
130
+ })
131
+ ).pipe(Command.withDescription("Regenerate Jira API types from OpenAPI specs"))
132
+
133
+ const cli = Command.run(regenerate, {
134
+ name: pkg.name,
135
+ version: pkg.version
136
+ })
137
+
138
+ Effect.suspend(() => cli(process.argv)).pipe(
139
+ Effect.provide(NodeContext.layer),
140
+ NodeRuntime.runMain
141
+ )
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Effect Layer wrapping openapi-fetch Jira V3 client with auth and base URL.
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)`
15
+ *
16
+ * @module
17
+ */
18
+ import * as Context from "effect/Context"
19
+ import * as Effect from "effect/Effect"
20
+ import * as Encoding from "effect/Encoding"
21
+ import * as Layer from "effect/Layer"
22
+ 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"
26
+
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>
49
+ }
50
+
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
+ */
88
+ export class JiraApiClient extends Context.Tag(
89
+ "@knpkv/jira-api-client/JiraApiClient"
90
+ )<JiraApiClient, JiraApiClientShape>() {
91
+ /**
92
+ * Layer that provides JiraApiClient.
93
+ *
94
+ * Requires: JiraApiConfig
95
+ */
96
+ static readonly layer: Layer.Layer<JiraApiClient, never, JiraApiConfig> = Layer.effect(
97
+ JiraApiClient,
98
+ Effect.gen(function*() {
99
+ 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
+ "Content-Type": "application/json"
115
+ }
116
+
117
+ return {
118
+ v3: makeOpenApiFetchClient<V3Paths>(baseUrl, headers)
119
+ }
120
+ })
121
+ )
122
+ }
123
+
124
+ /**
125
+ * Layer that provides JiraApiClient.
126
+ *
127
+ * @category Layers
128
+ */
129
+ export const layer = JiraApiClient.layer
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Configuration service tag for the Jira API client (basic or OAuth2 auth).
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Discriminated auth union**: The `auth` field is `{ type: "basic", ... } | { type: "oauth2", ... }`.
7
+ * Basic auth uses email + Redacted API token; OAuth2 uses Redacted access token + cloud ID.
8
+ * - **Base URL routing**: For OAuth2, the base URL is derived from cloud ID (`api.atlassian.com/ex/jira/{cloudId}`);
9
+ * for basic auth, `baseUrl` is used directly.
10
+ *
11
+ * @module
12
+ */
13
+ import * as Context from "effect/Context"
14
+ import type * as Redacted from "effect/Redacted"
15
+
16
+ /**
17
+ * Configuration shape for Jira API client.
18
+ *
19
+ * @category Config
20
+ */
21
+ export interface JiraApiConfigShape {
22
+ readonly baseUrl: string
23
+ readonly auth:
24
+ | { readonly type: "basic"; readonly email: string; readonly apiToken: Redacted.Redacted<string> }
25
+ | { readonly type: "oauth2"; readonly accessToken: Redacted.Redacted<string>; readonly cloudId: string }
26
+ }
27
+
28
+ /**
29
+ * Jira API configuration service.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { JiraApiConfig } from "@knpkv/jira-api-client"
34
+ * import * as Redacted from "effect/Redacted"
35
+ * import * as Layer from "effect/Layer"
36
+ *
37
+ * const configLayer = Layer.succeed(JiraApiConfig, {
38
+ * baseUrl: "https://mysite.atlassian.net",
39
+ * auth: {
40
+ * type: "basic",
41
+ * email: "user@example.com",
42
+ * apiToken: Redacted.make("token")
43
+ * }
44
+ * })
45
+ * ```
46
+ *
47
+ * @category Config
48
+ */
49
+ export class JiraApiConfig extends Context.Tag(
50
+ "@knpkv/jira-api-client/JiraApiConfig"
51
+ )<JiraApiConfig, JiraApiConfigShape>() {}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Generic Effect wrapper for openapi-fetch clients.
3
+ *
4
+ * Exposes a type-safe `Client<Paths>` and a `toEffect` helper that wraps
5
+ * any `Promise<FetchResponse>` in Effect with error mapping.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const page = yield* toEffect(client.GET("/pages/{id}", {
10
+ * params: { path: { id: 123 } }
11
+ * }))
12
+ * ```
13
+ *
14
+ * @module
15
+ */
16
+ import * as Data from "effect/Data"
17
+ import * as Effect from "effect/Effect"
18
+ import createClient, { type Client, type FetchResponse } from "openapi-fetch"
19
+ import type { MediaType } from "openapi-typescript-helpers"
20
+
21
+ /**
22
+ * Error from openapi-fetch operations.
23
+ *
24
+ * @category Errors
25
+ */
26
+ export class FetchClientError extends Data.TaggedError("FetchClientError")<{
27
+ readonly error: unknown
28
+ readonly status: number
29
+ readonly message: string
30
+ }> {}
31
+
32
+ /** Extract success `data` from a FetchResponse discriminated union. */
33
+ export type SuccessData<T> = T extends { data: infer D; error?: undefined } ? D
34
+ : T extends { data?: infer D } ? NonNullable<D>
35
+ : never
36
+
37
+ /**
38
+ * Wrap an openapi-fetch `Promise<FetchResponse>` in Effect.
39
+ *
40
+ * Extracts `data` on success, maps errors to `FetchClientError`.
41
+ * Fully type-safe — path/body constraints come from the `Client<Paths>` call site.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const page = yield* toEffect(client.GET("/pages/{id}", { params: { path: { id: 123 } } }))
46
+ * ```
47
+ *
48
+ * @category Utilities
49
+ */
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- matches FetchResponse generic constraint
51
+ export const toEffect = <T extends Record<string | number, any>, O, M extends MediaType>(
52
+ promise: Promise<FetchResponse<T, O, M>>
53
+ ): Effect.Effect<SuccessData<FetchResponse<T, O, M>>, FetchClientError> =>
54
+ Effect.tryPromise({
55
+ try: () =>
56
+ promise.then(({ data, error, response }) => {
57
+ if (error !== undefined || !response.ok) {
58
+ throw { error, status: response.status }
59
+ }
60
+ return data as SuccessData<FetchResponse<T, O, M>>
61
+ }),
62
+ catch: (e) =>
63
+ new FetchClientError({
64
+ error: (e as Record<string, unknown>).error ?? e,
65
+ status: ((e as Record<string, unknown>).status as number | undefined) ?? 0,
66
+ message: typeof (e as Record<string, unknown>).error === "string"
67
+ ? (e as Record<string, unknown>).error as string
68
+ : JSON.stringify((e as Record<string, unknown>).error ?? e)
69
+ })
70
+ })
71
+
72
+ /**
73
+ * openapi-fetch client paired with the `toEffect` helper.
74
+ *
75
+ * @category Client
76
+ */
77
+ export interface OpenApiFetchClient<Paths extends {}> {
78
+ /** Type-safe openapi-fetch client. Use with `toEffect()` to get Effect. */
79
+ readonly client: Client<Paths, "application/json">
80
+ }
81
+
82
+ /**
83
+ * Create an openapi-fetch client with auth headers pre-configured.
84
+ *
85
+ * @category Constructors
86
+ */
87
+ export const makeOpenApiFetchClient = <Paths extends {}>(
88
+ baseUrl: string,
89
+ headers: Record<string, string>
90
+ ): OpenApiFetchClient<Paths> => ({
91
+ client: createClient<Paths, "application/json">({ baseUrl, headers })
92
+ })
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Generated Jira Cloud REST API v3 types.
3
+ *
4
+ * @module
5
+ */
6
+ export type { components, operations, paths } from "./schema.js"