Skip to content

Commit 8dafeb6

Browse files
authored
fix: implement openapi-fetch compat without openapi-fetch dependency (#13)
* fix: remove openapi-fetch dependency * refactor(shell): move openapi-fetch compat to api-client boundary * refactor(app): remove openapi-fetch compat folder and lint-clean promise client * refactor(app): enforce effect-only analyzer and remove promise client * fix(app): make openapi-fetch input compat and return Effect * fix(shell): avoid Effect.catchAll in api client
1 parent a50367a commit 8dafeb6

23 files changed

+2085
-1246
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pnpm add @prover-coder-ai/openapi-effect
1010

1111
## Usage (Promise API)
1212

13-
This package re-exports `openapi-fetch`, so most code can be migrated by changing only the import.
13+
This package implements an `openapi-fetch` compatible API, so most code can be migrated by changing only the import.
1414

1515
```ts
1616
import createClient from "@prover-coder-ai/openapi-effect"

packages/app/eslint.config.mts

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,26 @@ export default defineConfig(
209209
message:
210210
"Запрещены Promise.* — используй комбинаторы Effect (all, forEach, etc.).",
211211
},
212+
{
213+
selector: "CallExpression[callee.object.object.name='globalThis'][callee.object.property.name='Promise']",
214+
message:
215+
"Запрещены globalThis.Promise.* — используй комбинаторы Effect.",
216+
},
217+
{
218+
selector: "NewExpression[callee.object.name='globalThis'][callee.property.name='Promise']",
219+
message:
220+
"Запрещён globalThis.Promise — используй Effect.async / Effect.tryPromise.",
221+
},
222+
{
223+
selector: "TSTypeReference[typeName.name='Promise'], TSTypeReference[typeName.name='PromiseLike']",
224+
message:
225+
"Запрещены Promise/PromiseLike в типах — используй Effect.Effect<A, E, R>.",
226+
},
227+
{
228+
selector: "TSTypeReference[typeName.type='TSQualifiedName'][typeName.left.name='globalThis'][typeName.right.name='Promise']",
229+
message:
230+
"Запрещён globalThis.Promise в типах — используй Effect.Effect<A, E, R>.",
231+
},
212232
{
213233
selector: "CallExpression[callee.property.name='push'] > SpreadElement.arguments",
214234
message: "Do not use spread arguments in Array.push",
@@ -232,6 +252,18 @@ export default defineConfig(
232252
"Запрещён Promise<T> — используй Effect.Effect<T, E, R>.",
233253
suggest: ["Effect.Effect<T, E, R>"],
234254
},
255+
PromiseLike: {
256+
message:
257+
"Запрещён PromiseLike<T> — используй Effect.Effect<T, E, R>.",
258+
},
259+
"PromiseLike<*>": {
260+
message:
261+
"Запрещён PromiseLike<T> — используй Effect.Effect<T, E, R>.",
262+
},
263+
"globalThis.Promise": {
264+
message:
265+
"Запрещён globalThis.Promise<T> — используй Effect.Effect<T, E, R>.",
266+
},
235267
},
236268
},
237269
],
@@ -312,6 +344,30 @@ export default defineConfig(
312344
selector: 'CallExpression[callee.name="require"]',
313345
message: "Avoid using require(). Use ES6 imports instead.",
314346
},
347+
{
348+
selector: "NewExpression[callee.name='Promise']",
349+
message: "Запрещён new Promise — используй Effect.async / Effect.tryPromise.",
350+
},
351+
{
352+
selector: "CallExpression[callee.object.name='Promise']",
353+
message: "Запрещены Promise.* — используй комбинаторы Effect.",
354+
},
355+
{
356+
selector: "CallExpression[callee.object.object.name='globalThis'][callee.object.property.name='Promise']",
357+
message: "Запрещены globalThis.Promise.* — используй комбинаторы Effect.",
358+
},
359+
{
360+
selector: "NewExpression[callee.object.name='globalThis'][callee.property.name='Promise']",
361+
message: "Запрещён globalThis.Promise — используй Effect.async / Effect.tryPromise.",
362+
},
363+
{
364+
selector: "TSTypeReference[typeName.name='Promise'], TSTypeReference[typeName.name='PromiseLike']",
365+
message: "Запрещены Promise/PromiseLike в типах — используй Effect.Effect<A, E, R>.",
366+
},
367+
{
368+
selector: "TSTypeReference[typeName.type='TSQualifiedName'][typeName.left.name='globalThis'][typeName.right.name='Promise']",
369+
message: "Запрещён globalThis.Promise в типах — используй Effect.Effect<A, E, R>.",
370+
},
315371
],
316372
'@typescript-eslint/no-restricted-types': 'off',
317373
// Axiom type casting functions intentionally use single-use type parameters
@@ -336,6 +392,30 @@ export default defineConfig(
336392
selector: 'CallExpression[callee.name="require"]',
337393
message: "Avoid using require(). Use ES6 imports instead.",
338394
},
395+
{
396+
selector: "NewExpression[callee.name='Promise']",
397+
message: "Запрещён new Promise — используй Effect.async / Effect.tryPromise.",
398+
},
399+
{
400+
selector: "CallExpression[callee.object.name='Promise']",
401+
message: "Запрещены Promise.* — используй комбинаторы Effect.",
402+
},
403+
{
404+
selector: "CallExpression[callee.object.object.name='globalThis'][callee.object.property.name='Promise']",
405+
message: "Запрещены globalThis.Promise.* — используй комбинаторы Effect.",
406+
},
407+
{
408+
selector: "NewExpression[callee.object.name='globalThis'][callee.property.name='Promise']",
409+
message: "Запрещён globalThis.Promise — используй Effect.async / Effect.tryPromise.",
410+
},
411+
{
412+
selector: "TSTypeReference[typeName.name='Promise'], TSTypeReference[typeName.name='PromiseLike']",
413+
message: "Запрещены Promise/PromiseLike в типах — используй Effect.Effect<A, E, R>.",
414+
},
415+
{
416+
selector: "TSTypeReference[typeName.type='TSQualifiedName'][typeName.left.name='globalThis'][typeName.right.name='Promise']",
417+
message: "Запрещён globalThis.Promise в типах — используй Effect.Effect<A, E, R>.",
418+
},
339419
],
340420
'@typescript-eslint/no-restricted-types': 'off',
341421
},
@@ -358,6 +438,6 @@ export default defineConfig(
358438
extends: [tseslint.configs.disableTypeChecked],
359439
},
360440

361-
// 6) Глобальные игноры
441+
// 7) Глобальные игноры
362442
{ ignores: ['dist/**', 'build/**', 'coverage/**', '**/dist/**'] },
363443
);

packages/app/eslint.effect-ts-check.config.mjs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ const restrictedSyntaxBase = [
7979
selector: "CallExpression[callee.object.name='Promise']",
8080
message: "Avoid Promise.*. Use Effect combinators."
8181
},
82+
{
83+
selector: "CallExpression[callee.object.object.name='globalThis'][callee.object.property.name='Promise']",
84+
message: "Avoid globalThis.Promise.*. Use Effect combinators."
85+
},
86+
{
87+
selector: "NewExpression[callee.object.name='globalThis'][callee.property.name='Promise']",
88+
message: "Avoid globalThis.Promise. Use Effect.async / Effect.tryPromise."
89+
},
90+
{
91+
selector: "TSTypeReference[typeName.name='Promise'], TSTypeReference[typeName.name='PromiseLike']",
92+
message: "Avoid Promise/PromiseLike types. Use Effect.Effect<A, E, R>."
93+
},
94+
{
95+
selector: "TSTypeReference[typeName.type='TSQualifiedName'][typeName.left.name='globalThis'][typeName.right.name='Promise']",
96+
message: "Avoid globalThis.Promise type. Use Effect.Effect<A, E, R>."
97+
},
8298
{
8399
selector: "CallExpression[callee.name='require']",
84100
message: "Avoid require(). Use ES module imports."
@@ -178,6 +194,15 @@ export default tseslint.config(
178194
},
179195
"Promise<*>": {
180196
message: "Avoid Promise<T>. Use Effect.Effect<T, E, R>."
197+
},
198+
PromiseLike: {
199+
message: "Avoid PromiseLike<T>. Use Effect.Effect<T, E, R>."
200+
},
201+
"PromiseLike<*>": {
202+
message: "Avoid PromiseLike<T>. Use Effect.Effect<T, E, R>."
203+
},
204+
"globalThis.Promise": {
205+
message: "Avoid globalThis.Promise<T>. Use Effect.Effect<T, E, R>."
181206
}
182207
}
183208
}],

packages/app/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
"@effect/typeclass": "^0.38.0",
6464
"@effect/workflow": "^0.16.0",
6565
"effect": "^3.19.16",
66-
"openapi-fetch": "^0.15.2",
6766
"openapi-typescript-helpers": "^0.1.0",
6867
"ts-morph": "^27.0.2"
6968
},

packages/app/src/index.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
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 поведение"
1+
// CHANGE: Expose Effect-only public API
2+
// WHY: Enforce Effect-first paradigm and remove Promise-based client surface
43
// SOURCE: n/a
54
// PURITY: SHELL (re-exports)
65
// COMPLEXITY: O(1)
76

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)
147
export * as FetchHttpClient from "@effect/platform/FetchHttpClient"
158

16-
// Strict Effect client (advanced)
179
export type * from "./core/api-client/index.js"
1810
export { assertNever } from "./core/api-client/index.js"
1911

2012
export type {
13+
ClientOptions,
2114
DispatchersFor,
2215
StrictApiClient,
2316
StrictApiClientWithDispatchers
@@ -26,17 +19,29 @@ export type {
2619
export type { Decoder, Dispatcher, RawResponse, StrictClient, StrictRequestInit } from "./shell/api-client/index.js"
2720

2821
export {
29-
createClient as createClientStrict,
22+
createClient,
3023
createClientEffect,
3124
createDispatcher,
25+
createFinalURL,
26+
createPathBasedClient,
27+
createQuerySerializer,
3228
createStrictClient,
3329
createUniversalDispatcher,
30+
defaultBodySerializer,
31+
defaultPathSerializer,
3432
executeRequest,
33+
mergeHeaders,
3534
parseJSON,
3635
registerDefaultDispatchers,
36+
removeTrailingSlash,
37+
serializeArrayParam,
38+
serializeObjectParam,
39+
serializePrimitiveParam,
3740
unexpectedContentType,
3841
unexpectedStatus
3942
} from "./shell/api-client/index.js"
4043

44+
export { createClient as default } from "./shell/api-client/index.js"
45+
4146
// Generated dispatchers (auto-generated from OpenAPI schema)
4247
export * from "./generated/index.js"

0 commit comments

Comments
 (0)