@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.
- package/README.md +36 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -0
- package/package.json +58 -0
- package/src/app/main.ts +18 -0
- package/src/app/program.ts +33 -0
- package/src/core/api/openapi.d.ts +445 -0
- package/src/core/api-client/index.ts +32 -0
- package/src/core/api-client/strict-types.ts +349 -0
- package/src/core/axioms.ts +143 -0
- package/src/core/greeting.ts +22 -0
- package/src/generated/decoders.ts +318 -0
- package/src/generated/dispatch.ts +172 -0
- package/src/generated/dispatchers-by-path.ts +40 -0
- package/src/generated/index.ts +9 -0
- package/src/index.ts +42 -0
- package/src/shell/api-client/create-client-types.ts +310 -0
- package/src/shell/api-client/create-client.ts +517 -0
- package/src/shell/api-client/index.ts +37 -0
- package/src/shell/api-client/strict-client.ts +471 -0
- package/src/shell/cli.ts +22 -0
|
@@ -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"
|