@prover-coder-ai/openapi-effect 1.0.19

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.
@@ -0,0 +1,318 @@
1
+ // CHANGE: Auto-generated decoder stubs for all operations
2
+ // WHY: Provide type-safe runtime validation entry points
3
+ // QUOTE(ТЗ): "при изменении схемы сборка обязана падать, пока декодеры не обновлены"
4
+ // REF: issue-2, section 5.2
5
+ // SOURCE: Generated from tests/fixtures/petstore.openapi.json
6
+ // FORMAT THEOREM: ∀ op, status: decoder(op, status) → Effect<T, DecodeError, never>
7
+ // PURITY: SHELL
8
+ // EFFECT: Effect<T, DecodeError, never>
9
+ // INVARIANT: All decoders return typed DecodeError on failure
10
+ // COMPLEXITY: O(n) where n = size of parsed object
11
+
12
+ import { Effect } from "effect"
13
+ import type { DecodeError } from "../core/api-client/strict-types.js"
14
+
15
+ /**
16
+ * JSON value type - result of JSON.parse()
17
+ */
18
+ type Json = null | boolean | number | string | ReadonlyArray<Json> | { readonly [k: string]: Json }
19
+
20
+ /**
21
+ * Decoder for listPets status 200 (application/json)
22
+ * STUB: Replace with real schema decoder when needed
23
+ *
24
+ * @pure false - performs validation
25
+ * @effect Effect<T, DecodeError, never>
26
+ */
27
+ export const decodelistPets_200 = (
28
+ _status: number,
29
+ _contentType: string,
30
+ _body: string,
31
+ parsed: Json
32
+ ): Effect.Effect<Json, DecodeError> => {
33
+ // STUB: Always succeeds with parsed value
34
+ // Replace with: Schema.decodeUnknown(YourSchema)(parsed)
35
+ return Effect.succeed(parsed)
36
+
37
+ // Example of real decoder:
38
+ // return Effect.mapError(
39
+ // Schema.decodeUnknown(YourSchema)(parsed),
40
+ // (error): DecodeError => ({
41
+ // _tag: "DecodeError",
42
+ // status,
43
+ // contentType,
44
+ // error,
45
+ // body
46
+ // })
47
+ // )
48
+ }
49
+
50
+ /**
51
+ * Decoder for listPets status 500 (application/json)
52
+ * STUB: Replace with real schema decoder when needed
53
+ *
54
+ * @pure false - performs validation
55
+ * @effect Effect<T, DecodeError, never>
56
+ */
57
+ export const decodelistPets_500 = (
58
+ _status: number,
59
+ _contentType: string,
60
+ _body: string,
61
+ parsed: Json
62
+ ): Effect.Effect<Json, DecodeError> => {
63
+ // STUB: Always succeeds with parsed value
64
+ // Replace with: Schema.decodeUnknown(YourSchema)(parsed)
65
+ return Effect.succeed(parsed)
66
+
67
+ // Example of real decoder:
68
+ // return Effect.mapError(
69
+ // Schema.decodeUnknown(YourSchema)(parsed),
70
+ // (error): DecodeError => ({
71
+ // _tag: "DecodeError",
72
+ // status,
73
+ // contentType,
74
+ // error,
75
+ // body
76
+ // })
77
+ // )
78
+ }
79
+
80
+ /**
81
+ * Decoder for createPet status 201 (application/json)
82
+ * STUB: Replace with real schema decoder when needed
83
+ *
84
+ * @pure false - performs validation
85
+ * @effect Effect<T, DecodeError, never>
86
+ */
87
+ export const decodecreatePet_201 = (
88
+ _status: number,
89
+ _contentType: string,
90
+ _body: string,
91
+ parsed: Json
92
+ ): Effect.Effect<Json, DecodeError> => {
93
+ // STUB: Always succeeds with parsed value
94
+ // Replace with: Schema.decodeUnknown(YourSchema)(parsed)
95
+ return Effect.succeed(parsed)
96
+
97
+ // Example of real decoder:
98
+ // return Effect.mapError(
99
+ // Schema.decodeUnknown(YourSchema)(parsed),
100
+ // (error): DecodeError => ({
101
+ // _tag: "DecodeError",
102
+ // status,
103
+ // contentType,
104
+ // error,
105
+ // body
106
+ // })
107
+ // )
108
+ }
109
+
110
+ /**
111
+ * Decoder for createPet status 400 (application/json)
112
+ * STUB: Replace with real schema decoder when needed
113
+ *
114
+ * @pure false - performs validation
115
+ * @effect Effect<T, DecodeError, never>
116
+ */
117
+ export const decodecreatePet_400 = (
118
+ _status: number,
119
+ _contentType: string,
120
+ _body: string,
121
+ parsed: Json
122
+ ): Effect.Effect<Json, DecodeError> => {
123
+ // STUB: Always succeeds with parsed value
124
+ // Replace with: Schema.decodeUnknown(YourSchema)(parsed)
125
+ return Effect.succeed(parsed)
126
+
127
+ // Example of real decoder:
128
+ // return Effect.mapError(
129
+ // Schema.decodeUnknown(YourSchema)(parsed),
130
+ // (error): DecodeError => ({
131
+ // _tag: "DecodeError",
132
+ // status,
133
+ // contentType,
134
+ // error,
135
+ // body
136
+ // })
137
+ // )
138
+ }
139
+
140
+ /**
141
+ * Decoder for createPet status 500 (application/json)
142
+ * STUB: Replace with real schema decoder when needed
143
+ *
144
+ * @pure false - performs validation
145
+ * @effect Effect<T, DecodeError, never>
146
+ */
147
+ export const decodecreatePet_500 = (
148
+ _status: number,
149
+ _contentType: string,
150
+ _body: string,
151
+ parsed: Json
152
+ ): Effect.Effect<Json, DecodeError> => {
153
+ // STUB: Always succeeds with parsed value
154
+ // Replace with: Schema.decodeUnknown(YourSchema)(parsed)
155
+ return Effect.succeed(parsed)
156
+
157
+ // Example of real decoder:
158
+ // return Effect.mapError(
159
+ // Schema.decodeUnknown(YourSchema)(parsed),
160
+ // (error): DecodeError => ({
161
+ // _tag: "DecodeError",
162
+ // status,
163
+ // contentType,
164
+ // error,
165
+ // body
166
+ // })
167
+ // )
168
+ }
169
+
170
+ /**
171
+ * Decoder for getPet status 200 (application/json)
172
+ * STUB: Replace with real schema decoder when needed
173
+ *
174
+ * @pure false - performs validation
175
+ * @effect Effect<T, DecodeError, never>
176
+ */
177
+ export const decodegetPet_200 = (
178
+ _status: number,
179
+ _contentType: string,
180
+ _body: string,
181
+ parsed: Json
182
+ ): Effect.Effect<Json, DecodeError> => {
183
+ // STUB: Always succeeds with parsed value
184
+ // Replace with: Schema.decodeUnknown(YourSchema)(parsed)
185
+ return Effect.succeed(parsed)
186
+
187
+ // Example of real decoder:
188
+ // return Effect.mapError(
189
+ // Schema.decodeUnknown(YourSchema)(parsed),
190
+ // (error): DecodeError => ({
191
+ // _tag: "DecodeError",
192
+ // status,
193
+ // contentType,
194
+ // error,
195
+ // body
196
+ // })
197
+ // )
198
+ }
199
+
200
+ /**
201
+ * Decoder for getPet status 404 (application/json)
202
+ * STUB: Replace with real schema decoder when needed
203
+ *
204
+ * @pure false - performs validation
205
+ * @effect Effect<T, DecodeError, never>
206
+ */
207
+ export const decodegetPet_404 = (
208
+ _status: number,
209
+ _contentType: string,
210
+ _body: string,
211
+ parsed: Json
212
+ ): Effect.Effect<Json, DecodeError> => {
213
+ // STUB: Always succeeds with parsed value
214
+ // Replace with: Schema.decodeUnknown(YourSchema)(parsed)
215
+ return Effect.succeed(parsed)
216
+
217
+ // Example of real decoder:
218
+ // return Effect.mapError(
219
+ // Schema.decodeUnknown(YourSchema)(parsed),
220
+ // (error): DecodeError => ({
221
+ // _tag: "DecodeError",
222
+ // status,
223
+ // contentType,
224
+ // error,
225
+ // body
226
+ // })
227
+ // )
228
+ }
229
+
230
+ /**
231
+ * Decoder for getPet status 500 (application/json)
232
+ * STUB: Replace with real schema decoder when needed
233
+ *
234
+ * @pure false - performs validation
235
+ * @effect Effect<T, DecodeError, never>
236
+ */
237
+ export const decodegetPet_500 = (
238
+ _status: number,
239
+ _contentType: string,
240
+ _body: string,
241
+ parsed: Json
242
+ ): Effect.Effect<Json, DecodeError> => {
243
+ // STUB: Always succeeds with parsed value
244
+ // Replace with: Schema.decodeUnknown(YourSchema)(parsed)
245
+ return Effect.succeed(parsed)
246
+
247
+ // Example of real decoder:
248
+ // return Effect.mapError(
249
+ // Schema.decodeUnknown(YourSchema)(parsed),
250
+ // (error): DecodeError => ({
251
+ // _tag: "DecodeError",
252
+ // status,
253
+ // contentType,
254
+ // error,
255
+ // body
256
+ // })
257
+ // )
258
+ }
259
+
260
+ /**
261
+ * Decoder for deletePet status 404 (application/json)
262
+ * STUB: Replace with real schema decoder when needed
263
+ *
264
+ * @pure false - performs validation
265
+ * @effect Effect<T, DecodeError, never>
266
+ */
267
+ export const decodedeletePet_404 = (
268
+ _status: number,
269
+ _contentType: string,
270
+ _body: string,
271
+ parsed: Json
272
+ ): Effect.Effect<Json, DecodeError> => {
273
+ // STUB: Always succeeds with parsed value
274
+ // Replace with: Schema.decodeUnknown(YourSchema)(parsed)
275
+ return Effect.succeed(parsed)
276
+
277
+ // Example of real decoder:
278
+ // return Effect.mapError(
279
+ // Schema.decodeUnknown(YourSchema)(parsed),
280
+ // (error): DecodeError => ({
281
+ // _tag: "DecodeError",
282
+ // status,
283
+ // contentType,
284
+ // error,
285
+ // body
286
+ // })
287
+ // )
288
+ }
289
+
290
+ /**
291
+ * Decoder for deletePet status 500 (application/json)
292
+ * STUB: Replace with real schema decoder when needed
293
+ *
294
+ * @pure false - performs validation
295
+ * @effect Effect<T, DecodeError, never>
296
+ */
297
+ export const decodedeletePet_500 = (
298
+ _status: number,
299
+ _contentType: string,
300
+ _body: string,
301
+ parsed: Json
302
+ ): Effect.Effect<Json, DecodeError> => {
303
+ // STUB: Always succeeds with parsed value
304
+ // Replace with: Schema.decodeUnknown(YourSchema)(parsed)
305
+ return Effect.succeed(parsed)
306
+
307
+ // Example of real decoder:
308
+ // return Effect.mapError(
309
+ // Schema.decodeUnknown(YourSchema)(parsed),
310
+ // (error): DecodeError => ({
311
+ // _tag: "DecodeError",
312
+ // status,
313
+ // contentType,
314
+ // error,
315
+ // body
316
+ // })
317
+ // )
318
+ }
@@ -0,0 +1,172 @@
1
+ // CHANGE: Auto-generated dispatchers for all operations with Effect-native error handling
2
+ // WHY: Maintain compile-time correlation between status codes and body types
3
+ // QUOTE(ТЗ): "реализует switch(status) по всем статусам схемы; Failure включает все инварианты протокола и схемы"
4
+ // REF: issue-2, section 5.2, 4.1-4.3
5
+ // SOURCE: Generated from tests/fixtures/petstore.openapi.json
6
+ // FORMAT THEOREM: ∀ op ∈ Operations: dispatcher(op) → Effect<ApiSuccess, HttpError | BoundaryError>
7
+ // PURITY: SHELL
8
+ // EFFECT: Effect<ApiSuccess<Responses>, HttpError<Responses> | BoundaryError, never>
9
+ // INVARIANT: 2xx → success channel, non-2xx → error channel (forced handling)
10
+ // COMPLEXITY: O(1) per dispatch (Match lookup)
11
+
12
+ import { Effect, Match } from "effect"
13
+ import type { Operations } from "../../tests/fixtures/petstore.openapi.js"
14
+ import type { DecodeError, ResponsesFor } from "../core/api-client/strict-types.js"
15
+ import { asConst, type Json } from "../core/axioms.js"
16
+ import {
17
+ createDispatcher,
18
+ parseJSON,
19
+ unexpectedContentType,
20
+ unexpectedStatus
21
+ } from "../shell/api-client/strict-client.js"
22
+ import * as Decoders from "./decoders.js"
23
+
24
+ // Response types for each operation - used for type inference
25
+ type ListPetsResponses = ResponsesFor<Operations["listPets"]>
26
+ type CreatePetResponses = ResponsesFor<Operations["createPet"]>
27
+ type GetPetResponses = ResponsesFor<Operations["getPet"]>
28
+ type DeletePetResponses = ResponsesFor<Operations["deletePet"]>
29
+
30
+ /**
31
+ * Helper: process JSON content type for a given status - returns SUCCESS variant
32
+ * Used for 2xx responses that go to the success channel
33
+ */
34
+ const processJsonContentSuccess = <S extends number, D>(
35
+ status: S,
36
+ contentType: string | undefined,
37
+ text: string,
38
+ decoder: (
39
+ s: number,
40
+ ct: string,
41
+ body: string,
42
+ parsed: Json
43
+ ) => Effect.Effect<D, DecodeError>
44
+ ) =>
45
+ contentType?.includes("application/json")
46
+ ? Effect.gen(function*() {
47
+ const parsed = yield* parseJSON(status, "application/json", text)
48
+ const decoded = yield* decoder(status, "application/json", text, parsed)
49
+ return asConst({
50
+ status,
51
+ contentType: "application/json" as const,
52
+ body: decoded
53
+ })
54
+ })
55
+ : Effect.fail(unexpectedContentType(status, ["application/json"], contentType, text))
56
+
57
+ /**
58
+ * Helper: process JSON content type for a given status - returns HTTP ERROR variant
59
+ * Used for non-2xx responses (4xx, 5xx) that go to the error channel.
60
+ *
61
+ * Adds `_tag: "HttpError"` discriminator to distinguish from BoundaryError.
62
+ */
63
+ const processJsonContentError = <S extends number, D>(
64
+ status: S,
65
+ contentType: string | undefined,
66
+ text: string,
67
+ decoder: (
68
+ s: number,
69
+ ct: string,
70
+ body: string,
71
+ parsed: Json
72
+ ) => Effect.Effect<D, DecodeError>
73
+ ) =>
74
+ contentType?.includes("application/json")
75
+ ? Effect.gen(function*() {
76
+ const parsed = yield* parseJSON(status, "application/json", text)
77
+ const decoded = yield* decoder(status, "application/json", text, parsed)
78
+ // Non-2xx: Return as FAILURE with _tag discriminator (goes to error channel)
79
+ return yield* Effect.fail(asConst({
80
+ _tag: "HttpError" as const,
81
+ status,
82
+ contentType: "application/json" as const,
83
+ body: decoded
84
+ }))
85
+ })
86
+ : Effect.fail(unexpectedContentType(status, ["application/json"], contentType, text))
87
+
88
+ /**
89
+ * Dispatcher for listPets
90
+ * Handles statuses: 200 (success), 500 (error)
91
+ *
92
+ * Effect channel mapping:
93
+ * - 200: success channel → ApiSuccess
94
+ * - 500: error channel → HttpError (forces explicit handling)
95
+ *
96
+ * @pure false - applies decoders
97
+ * @invariant Exhaustive coverage of all schema statuses
98
+ */
99
+ export const dispatcherlistPets = createDispatcher<ListPetsResponses>((status, contentType, text) =>
100
+ Match.value(status).pipe(
101
+ Match.when(200, () => processJsonContentSuccess(200, contentType, text, Decoders.decodelistPets_200)),
102
+ Match.when(500, () => processJsonContentError(500, contentType, text, Decoders.decodelistPets_500)),
103
+ Match.orElse(() => Effect.fail(unexpectedStatus(status, text)))
104
+ )
105
+ )
106
+
107
+ /**
108
+ * Dispatcher for createPet
109
+ * Handles statuses: 201 (success), 400 (error), 500 (error)
110
+ *
111
+ * Effect channel mapping:
112
+ * - 201: success channel → ApiSuccess
113
+ * - 400, 500: error channel → HttpError (forces explicit handling)
114
+ *
115
+ * @pure false - applies decoders
116
+ * @invariant Exhaustive coverage of all schema statuses
117
+ */
118
+ export const dispatchercreatePet = createDispatcher<CreatePetResponses>((status, contentType, text) =>
119
+ Match.value(status).pipe(
120
+ Match.when(201, () => processJsonContentSuccess(201, contentType, text, Decoders.decodecreatePet_201)),
121
+ Match.when(400, () => processJsonContentError(400, contentType, text, Decoders.decodecreatePet_400)),
122
+ Match.when(500, () => processJsonContentError(500, contentType, text, Decoders.decodecreatePet_500)),
123
+ Match.orElse(() => Effect.fail(unexpectedStatus(status, text)))
124
+ )
125
+ )
126
+
127
+ /**
128
+ * Dispatcher for getPet
129
+ * Handles statuses: 200 (success), 404 (error), 500 (error)
130
+ *
131
+ * Effect channel mapping:
132
+ * - 200: success channel → ApiSuccess
133
+ * - 404, 500: error channel → HttpError (forces explicit handling)
134
+ *
135
+ * @pure false - applies decoders
136
+ * @invariant Exhaustive coverage of all schema statuses
137
+ */
138
+ export const dispatchergetPet = createDispatcher<GetPetResponses>((status, contentType, text) =>
139
+ Match.value(status).pipe(
140
+ Match.when(200, () => processJsonContentSuccess(200, contentType, text, Decoders.decodegetPet_200)),
141
+ Match.when(404, () => processJsonContentError(404, contentType, text, Decoders.decodegetPet_404)),
142
+ Match.when(500, () => processJsonContentError(500, contentType, text, Decoders.decodegetPet_500)),
143
+ Match.orElse(() => Effect.fail(unexpectedStatus(status, text)))
144
+ )
145
+ )
146
+
147
+ /**
148
+ * Dispatcher for deletePet
149
+ * Handles statuses: 204 (success), 404 (error), 500 (error)
150
+ *
151
+ * Effect channel mapping:
152
+ * - 204: success channel → ApiSuccess (no content)
153
+ * - 404, 500: error channel → HttpError (forces explicit handling)
154
+ *
155
+ * @pure false - applies decoders
156
+ * @invariant Exhaustive coverage of all schema statuses
157
+ */
158
+ export const dispatcherdeletePet = createDispatcher<DeletePetResponses>((status, contentType, text) =>
159
+ Match.value(status).pipe(
160
+ Match.when(204, () =>
161
+ Effect.succeed(
162
+ asConst({
163
+ status: 204,
164
+ contentType: "none" as const,
165
+ body: undefined
166
+ })
167
+ )),
168
+ Match.when(404, () => processJsonContentError(404, contentType, text, Decoders.decodedeletePet_404)),
169
+ Match.when(500, () => processJsonContentError(500, contentType, text, Decoders.decodedeletePet_500)),
170
+ Match.orElse(() => Effect.fail(unexpectedStatus(status, text)))
171
+ )
172
+ )
@@ -0,0 +1,40 @@
1
+ // CHANGE: Auto-generated dispatcher map by path+method
2
+ // WHY: Provide a single dispatcher registry without manual wiring in examples
3
+ // QUOTE(ТЗ): "Этого в плане вообще не должно быть"
4
+ // REF: user-msg-3
5
+ // SOURCE: Generated from tests/fixtures/petstore.openapi.json
6
+ // FORMAT THEOREM: ∀ path, method: dispatchersByPath[path][method] = dispatcher(op)
7
+ // PURITY: SHELL
8
+ // EFFECT: none
9
+ // INVARIANT: dispatcher map is total for all operations in Paths
10
+ // COMPLEXITY: O(1)
11
+
12
+ import type { Paths } from "../../tests/fixtures/petstore.openapi.js"
13
+ import { type DispatchersFor, registerDefaultDispatchers } from "../shell/api-client/create-client.js"
14
+ import { dispatchercreatePet, dispatcherdeletePet, dispatchergetPet, dispatcherlistPets } from "./dispatch.js"
15
+
16
+ /**
17
+ * Dispatcher map keyed by OpenAPI path and HTTP method
18
+ */
19
+ export const dispatchersByPath: DispatchersFor<Paths> = {
20
+ "/pets": {
21
+ get: dispatcherlistPets,
22
+ post: dispatchercreatePet
23
+ },
24
+ "/pets/{petId}": {
25
+ get: dispatchergetPet,
26
+ delete: dispatcherdeletePet
27
+ }
28
+ }
29
+
30
+ // CHANGE: Register default dispatchers at module load
31
+ // WHY: Enable createClient(options) without passing dispatcher map
32
+ // QUOTE(ТЗ): "const apiClient = createClient<Paths>(clientOptions)"
33
+ // REF: user-msg-4
34
+ // SOURCE: n/a
35
+ // FORMAT THEOREM: ∀ call: createClient(options) uses dispatchersByPath
36
+ // PURITY: SHELL
37
+ // EFFECT: none
38
+ // INVARIANT: registerDefaultDispatchers is called exactly once per module load
39
+ // COMPLEXITY: O(1)
40
+ registerDefaultDispatchers(dispatchersByPath)
@@ -0,0 +1,9 @@
1
+ // CHANGE: Export all generated dispatchers and decoders
2
+ // WHY: Single entry point for generated code
3
+ // REF: issue-2
4
+ // PURITY: CORE
5
+ // COMPLEXITY: O(1)
6
+
7
+ export * from "./decoders.js"
8
+ export * from "./dispatch.js"
9
+ export * from "./dispatchers-by-path.js"
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ // CHANGE: Make openapi-effect a drop-in replacement for openapi-fetch (Promise API), with an opt-in Effect API.
2
+ // WHY: Consumer projects must be able to swap openapi-fetch -> openapi-effect with near-zero code changes.
3
+ // QUOTE(ТЗ): "openapi-effect должен почти 1 в 1 заменяться с openapi-fetch" / "Просто добавлять effect поведение"
4
+ // SOURCE: n/a
5
+ // PURITY: SHELL (re-exports)
6
+ // COMPLEXITY: O(1)
7
+
8
+ // Promise-based client (openapi-fetch compatible)
9
+ export { default } from "openapi-fetch"
10
+ export { default as createClient } from "openapi-fetch"
11
+ export * from "openapi-fetch"
12
+
13
+ // Effect-based client (opt-in)
14
+ export * as FetchHttpClient from "@effect/platform/FetchHttpClient"
15
+
16
+ // Strict Effect client (advanced)
17
+ export type * from "./core/api-client/index.js"
18
+ export { assertNever } from "./core/api-client/index.js"
19
+
20
+ export type {
21
+ DispatchersFor,
22
+ StrictApiClient,
23
+ StrictApiClientWithDispatchers
24
+ } from "./shell/api-client/create-client.js"
25
+
26
+ export type { Decoder, Dispatcher, RawResponse, StrictClient, StrictRequestInit } from "./shell/api-client/index.js"
27
+
28
+ export {
29
+ createClient as createClientStrict,
30
+ createClientEffect,
31
+ createDispatcher,
32
+ createStrictClient,
33
+ createUniversalDispatcher,
34
+ executeRequest,
35
+ parseJSON,
36
+ registerDefaultDispatchers,
37
+ unexpectedContentType,
38
+ unexpectedStatus
39
+ } from "./shell/api-client/index.js"
40
+
41
+ // Generated dispatchers (auto-generated from OpenAPI schema)
42
+ export * from "./generated/index.js"