openapi-ff 0.1.3 → 0.2.2
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 +28 -48
- package/dist/openapi-ff.cjs +1 -1
- package/dist/openapi-ff.d.cts +96 -24
- package/dist/openapi-ff.d.ts +96 -24
- package/dist/openapi-ff.js +59 -50
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
openapi-ff is a type-safe tiny wrapper around [effector](https://effector.dev/) and [farfetched](https://ff.effector.dev/) to work with OpenAPI schema.
|
|
4
4
|
|
|
5
|
-
It works by using [openapi-fetch](
|
|
5
|
+
It works by using [openapi-fetch](https://openapi-ts.dev/openapi-fetch/) and [openapi-typescript](https://openapi-ts.dev/).
|
|
6
6
|
|
|
7
7
|
## Setup
|
|
8
8
|
|
|
@@ -21,17 +21,17 @@ npx openapi-typescript ./path/to/api/v1.yaml -o ./src/shared/api/schema.d.ts
|
|
|
21
21
|
|
|
22
22
|
```ts
|
|
23
23
|
import createFetchClient from "openapi-fetch";
|
|
24
|
-
import {
|
|
24
|
+
import { createEffectorClient } from "openapi-ff";
|
|
25
25
|
import { createQuery } from "@farfetched/core";
|
|
26
26
|
import type { paths } from "./schema"; // generated by openapi-typescript
|
|
27
27
|
|
|
28
28
|
export const client = createFetchClient<paths>({
|
|
29
29
|
baseUrl: "https://myapi.dev/v1/",
|
|
30
30
|
});
|
|
31
|
-
export const { createApiEffect } =
|
|
31
|
+
export const { createApiEffect } = createEffectorClient(client);
|
|
32
32
|
|
|
33
33
|
const blogpostQuery = createQuery({
|
|
34
|
-
|
|
34
|
+
...createApiEffect("get", "/blogposts/{post_id}"),
|
|
35
35
|
});
|
|
36
36
|
```
|
|
37
37
|
|
|
@@ -60,11 +60,10 @@ import { chainRoute } from "atomic-router";
|
|
|
60
60
|
import { startChain } from "@farfetched/atomic-router";
|
|
61
61
|
import { isApiError } from "openapi-ff";
|
|
62
62
|
|
|
63
|
-
const getBlogpostFx = createApiEffect("get", "/blogposts/{post_id}", {
|
|
64
|
-
mapParams: (args: { postId: string }) => ({ params: { path: { post_id: args.postId } } }),
|
|
65
|
-
});
|
|
66
63
|
const blogpostQuery = createQuery({
|
|
67
|
-
|
|
64
|
+
...createApiEffect("get", "/blogposts/{post_id}", {
|
|
65
|
+
mapParams: (args: { postId: string }) => ({ params: { path: { post_id: args.postId } } }),
|
|
66
|
+
}),
|
|
68
67
|
mapData: ({ result, params }) => ({ ...result, ...params }),
|
|
69
68
|
initialData: { body: "-", title: "-", postId: "0" },
|
|
70
69
|
});
|
|
@@ -80,16 +79,31 @@ const apiError = sample({
|
|
|
80
79
|
});
|
|
81
80
|
```
|
|
82
81
|
|
|
82
|
+
Map with source:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { mergeInitHeaders } from 'openapi-ff';
|
|
86
|
+
|
|
87
|
+
const blogpostQuery = createQuery({
|
|
88
|
+
...createApiEffect("get", "/blogposts/{post_id}", {
|
|
89
|
+
mapParams: {
|
|
90
|
+
source: $token,
|
|
91
|
+
fn: (token, init) => mergeInitHeaders(init, { Authorization: token }),
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
83
97
|
## Runtime Validation
|
|
84
98
|
|
|
85
99
|
`openapi-ff` does not handle runtime validation, as `openapi-typescript` [does not support it](https://github.com/openapi-ts/openapi-typescript/issues/1420#issuecomment-1792909086).
|
|
86
100
|
|
|
87
101
|
> openapi-typescript by its design generates runtime-free static types, and only static types.
|
|
88
102
|
|
|
89
|
-
However, `openapi-ff` allows adding a contract factory when creating a client
|
|
103
|
+
However, `openapi-ff` allows adding a contract factory when creating a client:
|
|
90
104
|
|
|
91
105
|
```ts
|
|
92
|
-
const {
|
|
106
|
+
const { createApiEffect } = createEffectorClient(fetchClient, {
|
|
93
107
|
createContract(method, path) {
|
|
94
108
|
// ... create your own contract
|
|
95
109
|
return contract; // Contract<unknown, unknown>
|
|
@@ -97,7 +111,7 @@ const { createApiEffectWithContract } = createClient(fetchClient, {
|
|
|
97
111
|
});
|
|
98
112
|
|
|
99
113
|
const query = createQuery({
|
|
100
|
-
...
|
|
114
|
+
...createApiEffect("get", "/blogposts"),
|
|
101
115
|
});
|
|
102
116
|
```
|
|
103
117
|
|
|
@@ -112,10 +126,9 @@ pnpm install zod @farfetched/zod
|
|
|
112
126
|
import { EndpointByMethod } from "./zod";
|
|
113
127
|
import { zodContract } from "@farfetched/zod";
|
|
114
128
|
|
|
115
|
-
const {
|
|
129
|
+
const { createApiEffect } = createEffectorClient(fetchClient, {
|
|
116
130
|
createContract(method, path) {
|
|
117
|
-
const
|
|
118
|
-
const response = endpoints[path]?.response;
|
|
131
|
+
const response = (EndpointByMethod as any)[method][path]?.response;
|
|
119
132
|
if (!response) {
|
|
120
133
|
throw new Error(`Response schema for route "${method} ${path}" doesn't exist`);
|
|
121
134
|
}
|
|
@@ -124,39 +137,6 @@ const { createApiEffectWithContract } = createClient(fetchClient, {
|
|
|
124
137
|
});
|
|
125
138
|
|
|
126
139
|
const query = createQuery({
|
|
127
|
-
...
|
|
128
|
-
});
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### [orval](https://orval.dev/) example
|
|
132
|
-
|
|
133
|
-
Alternatively, you can simply add any contract to a query:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
pnpm install zod @farfetched/zod
|
|
137
|
-
npx orval --input path/to/api.yaml --output src/zod.ts --client zod --mode single
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
import { zodContract } from "@farfetched/zod";
|
|
142
|
-
import { getBlogpostsResponseItem } from "./zod";
|
|
143
|
-
|
|
144
|
-
const blogpostQuery = createQuery({
|
|
145
|
-
effect: createApiEffect("get", "/blogposts/{post_id}"),
|
|
146
|
-
contract: zodContract(getBlogpostsResponseItem),
|
|
147
|
-
});
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## TODO
|
|
151
|
-
|
|
152
|
-
Add `createApiQuery`:
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
createApiQuery({
|
|
156
|
-
method: "get",
|
|
157
|
-
path: "/blogposts/{post_id}",
|
|
158
|
-
mapParams: (args: { postId: string }) => ({ params: { path: { post_id: args.postId } } }),
|
|
159
|
-
mapData: ({ result, params }) => ({ ...result, ...params }),
|
|
160
|
-
initialData: { body: "-", title: "-", postId: "0" },
|
|
140
|
+
...createApiEffect("get", "/blogposts"),
|
|
161
141
|
});
|
|
162
142
|
```
|
package/dist/openapi-ff.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const w=require("@farfetched/core"),q=require("effector"),i=require("openapi-fetch"),x="API";function p(e){var a;return((a=e.error)==null?void 0:a.errorType)===x}function S(e,a){let s,t;return e===void 0?(s=i(),t=void 0):typeof e=="object"&&"request"in e?(s=e,t=a):(s=i(),t=e),{createApiEffect:(l,m,r)=>{var P,g;const A=l.toUpperCase(),T=s[A],y=((P=r==null?void 0:r.mapParams)==null?void 0:P.source)??q.createStore(null),d=!!(r!=null&&r.mapParams),h=!!(r!=null&&r.mapParams.fn),E=d?h?r.mapParams.fn:r.mapParams:null;return{effect:q.attach({source:y,effect:async(C,u)=>{let o=u;d&&(h?o=E(C,u):o=E(u));const{data:I,error:f,response:n}=await T(m,o).catch(c=>{throw w.networkError({reason:(c==null?void 0:c.message)??null,cause:c})});if(f!=null&&f!=="")throw b({status:n.status,statusText:n.statusText,response:f});if(!n.ok)throw w.httpError({status:n.status,statusText:n.statusText,response:null});return I}}),contract:((g=t==null?void 0:t.createContract)==null?void 0:g.call(t,l,m))??H()}}}}function b(e){return{...e,errorType:x,explanation:"Request was finished with unsuccessful API response"}}function v(e,a){return{...e,headers:i.mergeHeaders(e.headers,a)}}function H(){return{isData:e=>!0,getErrorMessages:()=>[]}}exports.createEffectorClient=S;exports.isApiError=p;exports.mergeInitHeaders=v;
|
package/dist/openapi-ff.d.cts
CHANGED
|
@@ -1,50 +1,122 @@
|
|
|
1
|
+
import { BodySerializer } from 'openapi-fetch';
|
|
1
2
|
import { Client } from 'openapi-fetch';
|
|
3
|
+
import { ClientOptions } from 'openapi-fetch';
|
|
2
4
|
import { Contract } from '@farfetched/core';
|
|
3
5
|
import { Effect } from 'effector';
|
|
4
6
|
import { FarfetchedError } from '@farfetched/core';
|
|
7
|
+
import { FetchOptions } from 'openapi-fetch';
|
|
5
8
|
import { FetchResponse } from 'openapi-fetch';
|
|
9
|
+
import { HeadersOptions } from 'openapi-fetch';
|
|
6
10
|
import { HttpError } from '@farfetched/core';
|
|
7
|
-
import { HttpMethod } from 'openapi-typescript-helpers';
|
|
8
11
|
import { MaybeOptionalInit } from 'openapi-fetch';
|
|
9
|
-
import { MediaType } from 'openapi-typescript-helpers';
|
|
10
12
|
import { NetworkError } from '@farfetched/core';
|
|
11
|
-
import {
|
|
13
|
+
import { ParamsOption } from 'openapi-fetch';
|
|
14
|
+
import { ParseAs } from 'openapi-fetch';
|
|
15
|
+
import { QuerySerializer } from 'openapi-fetch';
|
|
16
|
+
import { QuerySerializerOptions } from 'openapi-fetch';
|
|
17
|
+
import { RequestBodyOption } from 'openapi-fetch';
|
|
18
|
+
import { Store } from 'effector';
|
|
12
19
|
|
|
13
20
|
declare const API = "API";
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
status:
|
|
22
|
+
declare type ApiError<S extends ErrorStatus, T> = {
|
|
23
|
+
status: S;
|
|
17
24
|
statusText: string;
|
|
18
|
-
response:
|
|
19
|
-
}
|
|
25
|
+
response: T;
|
|
26
|
+
} & FarfetchedError<typeof API>;
|
|
20
27
|
|
|
21
|
-
declare type
|
|
28
|
+
declare type ApiErrors<Responses extends Record<string | number, any>> = {
|
|
29
|
+
[K in keyof Responses]: K extends ErrorStatus ? Responses[K] extends {
|
|
30
|
+
content: Record<string, any>;
|
|
31
|
+
} ? ApiError<K, FilterKeys<Responses[K]['content'], `${string}/json`>> : never : never;
|
|
32
|
+
}[keyof Responses & ErrorStatus];
|
|
22
33
|
|
|
23
|
-
declare type
|
|
24
|
-
|
|
34
|
+
declare type CreateApiEffect<Paths extends Record<string, Record<HttpMethod, {}>>> = {
|
|
35
|
+
<Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Operation extends Paths[Path][Method], Responses extends ResponseObjectMap<Operation>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, `${string}/json`>>>(method: Method, path: Path): {
|
|
36
|
+
effect: Effect<OptionalParams<Init>, Response['data'], ApiErrors<Responses> | HttpError | NetworkError>;
|
|
37
|
+
contract: Contract<unknown, Response['data']>;
|
|
38
|
+
};
|
|
39
|
+
<Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Operation extends Paths[Path][Method], Responses extends ResponseObjectMap<Operation>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, `${string}/json`>>, Source, NewInit = Init>(method: Method, path: Path, options: {
|
|
40
|
+
mapParams: {
|
|
41
|
+
source: Store<Source>;
|
|
42
|
+
fn: (source: Source, params: NewInit) => Init;
|
|
43
|
+
};
|
|
44
|
+
}): {
|
|
45
|
+
effect: Effect<OptionalParams<NewInit>, Response['data'], ApiErrors<Responses> | HttpError | NetworkError>;
|
|
46
|
+
contract: Contract<unknown, Response['data']>;
|
|
47
|
+
};
|
|
48
|
+
<Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Operation extends Paths[Path][Method], Responses extends ResponseObjectMap<Operation>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, `${string}/json`>>, NewInit>(method: Method, path: Path, options: {
|
|
49
|
+
mapParams: (params: NewInit) => Init;
|
|
50
|
+
}): {
|
|
51
|
+
effect: Effect<OptionalParams<NewInit>, Response['data'], ApiErrors<Responses> | HttpError | NetworkError>;
|
|
52
|
+
contract: Contract<unknown, Response['data']>;
|
|
53
|
+
};
|
|
25
54
|
};
|
|
26
55
|
|
|
27
|
-
declare
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
56
|
+
export declare function createEffectorClient<Paths extends {}>(): OpenapiEffectorClient<Paths>;
|
|
57
|
+
|
|
58
|
+
export declare function createEffectorClient<Paths extends {}>(client: Client<Paths>): OpenapiEffectorClient<Paths>;
|
|
59
|
+
|
|
60
|
+
export declare function createEffectorClient<Paths extends {}>(client: Client<Paths>, options: EffectorClientOptions<Paths>): OpenapiEffectorClient<Paths>;
|
|
31
61
|
|
|
32
|
-
export declare function
|
|
62
|
+
export declare function createEffectorClient<Paths extends {}>(options: EffectorClientOptions<Paths>): OpenapiEffectorClient<Paths>;
|
|
33
63
|
|
|
34
|
-
declare type EffectorClientOptions<Paths extends
|
|
64
|
+
declare type EffectorClientOptions<Paths extends {}> = {
|
|
35
65
|
createContract?: <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>(method: Method, path: Path) => Contract<unknown, unknown>;
|
|
36
66
|
};
|
|
37
67
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
68
|
+
/** 2XX statuses */
|
|
69
|
+
declare type ErrorStatus = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | '5XX' | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 444 | 450 | 451 | 497 | 498 | 499 | '4XX' | "default";
|
|
70
|
+
|
|
71
|
+
/** Find first match of multiple keys */
|
|
72
|
+
declare type FilterKeys<Obj, Matchers> = Obj[keyof Obj & Matchers];
|
|
73
|
+
|
|
74
|
+
declare type HasRequiredKeys<T> = {} extends T ? true : false;
|
|
75
|
+
|
|
76
|
+
declare type HttpMethod = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace';
|
|
77
|
+
|
|
78
|
+
export declare function isApiError<S extends ErrorStatus, R>(args: WithError): args is WithError<ApiError<S, R>>;
|
|
79
|
+
|
|
80
|
+
export declare function mergeInitHeaders<T>(init: FetchOptions<T>, headers: Record<string, any>): ParamsOption<T> & RequestBodyOption<T> & {
|
|
81
|
+
baseUrl?: string;
|
|
82
|
+
querySerializer?: QuerySerializerOptions | QuerySerializer<T> | undefined;
|
|
83
|
+
bodySerializer?: BodySerializer<T> | undefined;
|
|
84
|
+
parseAs?: ParseAs;
|
|
85
|
+
fetch?: ClientOptions["fetch"];
|
|
86
|
+
headers?: HeadersOptions;
|
|
87
|
+
} & {
|
|
88
|
+
headers: Headers;
|
|
89
|
+
cache?: RequestCache | undefined;
|
|
90
|
+
credentials?: RequestCredentials | undefined;
|
|
91
|
+
integrity?: string | undefined;
|
|
92
|
+
keepalive?: boolean | undefined;
|
|
93
|
+
method?: string | undefined;
|
|
94
|
+
mode?: RequestMode | undefined;
|
|
95
|
+
priority?: RequestPriority | undefined;
|
|
96
|
+
redirect?: RequestRedirect | undefined;
|
|
97
|
+
referrer?: string | undefined;
|
|
98
|
+
referrerPolicy?: ReferrerPolicy | undefined;
|
|
99
|
+
signal?: (AbortSignal | null) | undefined;
|
|
100
|
+
window?: null | undefined;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
declare type OpenapiEffectorClient<Paths extends {}> = {
|
|
104
|
+
createApiEffect: CreateApiEffect<Paths>;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
declare type OptionalParams<T> = HasRequiredKeys<T> extends true ? T | void : T;
|
|
41
108
|
|
|
42
|
-
|
|
109
|
+
/** Given an OpenAPI **Paths Object**, find all paths that have the given method */
|
|
110
|
+
declare type PathsWithMethod<Paths extends {}, PathnameMethod extends HttpMethod> = {
|
|
111
|
+
[Pathname in keyof Paths]: Paths[Pathname] extends {
|
|
112
|
+
[K in PathnameMethod]: any;
|
|
113
|
+
} ? Pathname : never;
|
|
114
|
+
}[keyof Paths];
|
|
43
115
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
116
|
+
/** Return `responses` for an Operation Object */
|
|
117
|
+
declare type ResponseObjectMap<T> = T extends {
|
|
118
|
+
responses: any;
|
|
119
|
+
} ? T['responses'] : unknown;
|
|
48
120
|
|
|
49
121
|
declare type WithError<T = any, P = Record<string, unknown>> = P & {
|
|
50
122
|
error: T;
|
package/dist/openapi-ff.d.ts
CHANGED
|
@@ -1,50 +1,122 @@
|
|
|
1
|
+
import { BodySerializer } from 'openapi-fetch';
|
|
1
2
|
import { Client } from 'openapi-fetch';
|
|
3
|
+
import { ClientOptions } from 'openapi-fetch';
|
|
2
4
|
import { Contract } from '@farfetched/core';
|
|
3
5
|
import { Effect } from 'effector';
|
|
4
6
|
import { FarfetchedError } from '@farfetched/core';
|
|
7
|
+
import { FetchOptions } from 'openapi-fetch';
|
|
5
8
|
import { FetchResponse } from 'openapi-fetch';
|
|
9
|
+
import { HeadersOptions } from 'openapi-fetch';
|
|
6
10
|
import { HttpError } from '@farfetched/core';
|
|
7
|
-
import { HttpMethod } from 'openapi-typescript-helpers';
|
|
8
11
|
import { MaybeOptionalInit } from 'openapi-fetch';
|
|
9
|
-
import { MediaType } from 'openapi-typescript-helpers';
|
|
10
12
|
import { NetworkError } from '@farfetched/core';
|
|
11
|
-
import {
|
|
13
|
+
import { ParamsOption } from 'openapi-fetch';
|
|
14
|
+
import { ParseAs } from 'openapi-fetch';
|
|
15
|
+
import { QuerySerializer } from 'openapi-fetch';
|
|
16
|
+
import { QuerySerializerOptions } from 'openapi-fetch';
|
|
17
|
+
import { RequestBodyOption } from 'openapi-fetch';
|
|
18
|
+
import { Store } from 'effector';
|
|
12
19
|
|
|
13
20
|
declare const API = "API";
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
status:
|
|
22
|
+
declare type ApiError<S extends ErrorStatus, T> = {
|
|
23
|
+
status: S;
|
|
17
24
|
statusText: string;
|
|
18
|
-
response:
|
|
19
|
-
}
|
|
25
|
+
response: T;
|
|
26
|
+
} & FarfetchedError<typeof API>;
|
|
20
27
|
|
|
21
|
-
declare type
|
|
28
|
+
declare type ApiErrors<Responses extends Record<string | number, any>> = {
|
|
29
|
+
[K in keyof Responses]: K extends ErrorStatus ? Responses[K] extends {
|
|
30
|
+
content: Record<string, any>;
|
|
31
|
+
} ? ApiError<K, FilterKeys<Responses[K]['content'], `${string}/json`>> : never : never;
|
|
32
|
+
}[keyof Responses & ErrorStatus];
|
|
22
33
|
|
|
23
|
-
declare type
|
|
24
|
-
|
|
34
|
+
declare type CreateApiEffect<Paths extends Record<string, Record<HttpMethod, {}>>> = {
|
|
35
|
+
<Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Operation extends Paths[Path][Method], Responses extends ResponseObjectMap<Operation>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, `${string}/json`>>>(method: Method, path: Path): {
|
|
36
|
+
effect: Effect<OptionalParams<Init>, Response['data'], ApiErrors<Responses> | HttpError | NetworkError>;
|
|
37
|
+
contract: Contract<unknown, Response['data']>;
|
|
38
|
+
};
|
|
39
|
+
<Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Operation extends Paths[Path][Method], Responses extends ResponseObjectMap<Operation>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, `${string}/json`>>, Source, NewInit = Init>(method: Method, path: Path, options: {
|
|
40
|
+
mapParams: {
|
|
41
|
+
source: Store<Source>;
|
|
42
|
+
fn: (source: Source, params: NewInit) => Init;
|
|
43
|
+
};
|
|
44
|
+
}): {
|
|
45
|
+
effect: Effect<OptionalParams<NewInit>, Response['data'], ApiErrors<Responses> | HttpError | NetworkError>;
|
|
46
|
+
contract: Contract<unknown, Response['data']>;
|
|
47
|
+
};
|
|
48
|
+
<Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Operation extends Paths[Path][Method], Responses extends ResponseObjectMap<Operation>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, `${string}/json`>>, NewInit>(method: Method, path: Path, options: {
|
|
49
|
+
mapParams: (params: NewInit) => Init;
|
|
50
|
+
}): {
|
|
51
|
+
effect: Effect<OptionalParams<NewInit>, Response['data'], ApiErrors<Responses> | HttpError | NetworkError>;
|
|
52
|
+
contract: Contract<unknown, Response['data']>;
|
|
53
|
+
};
|
|
25
54
|
};
|
|
26
55
|
|
|
27
|
-
declare
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
56
|
+
export declare function createEffectorClient<Paths extends {}>(): OpenapiEffectorClient<Paths>;
|
|
57
|
+
|
|
58
|
+
export declare function createEffectorClient<Paths extends {}>(client: Client<Paths>): OpenapiEffectorClient<Paths>;
|
|
59
|
+
|
|
60
|
+
export declare function createEffectorClient<Paths extends {}>(client: Client<Paths>, options: EffectorClientOptions<Paths>): OpenapiEffectorClient<Paths>;
|
|
31
61
|
|
|
32
|
-
export declare function
|
|
62
|
+
export declare function createEffectorClient<Paths extends {}>(options: EffectorClientOptions<Paths>): OpenapiEffectorClient<Paths>;
|
|
33
63
|
|
|
34
|
-
declare type EffectorClientOptions<Paths extends
|
|
64
|
+
declare type EffectorClientOptions<Paths extends {}> = {
|
|
35
65
|
createContract?: <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>(method: Method, path: Path) => Contract<unknown, unknown>;
|
|
36
66
|
};
|
|
37
67
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
68
|
+
/** 2XX statuses */
|
|
69
|
+
declare type ErrorStatus = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | '5XX' | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 444 | 450 | 451 | 497 | 498 | 499 | '4XX' | "default";
|
|
70
|
+
|
|
71
|
+
/** Find first match of multiple keys */
|
|
72
|
+
declare type FilterKeys<Obj, Matchers> = Obj[keyof Obj & Matchers];
|
|
73
|
+
|
|
74
|
+
declare type HasRequiredKeys<T> = {} extends T ? true : false;
|
|
75
|
+
|
|
76
|
+
declare type HttpMethod = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace';
|
|
77
|
+
|
|
78
|
+
export declare function isApiError<S extends ErrorStatus, R>(args: WithError): args is WithError<ApiError<S, R>>;
|
|
79
|
+
|
|
80
|
+
export declare function mergeInitHeaders<T>(init: FetchOptions<T>, headers: Record<string, any>): ParamsOption<T> & RequestBodyOption<T> & {
|
|
81
|
+
baseUrl?: string;
|
|
82
|
+
querySerializer?: QuerySerializerOptions | QuerySerializer<T> | undefined;
|
|
83
|
+
bodySerializer?: BodySerializer<T> | undefined;
|
|
84
|
+
parseAs?: ParseAs;
|
|
85
|
+
fetch?: ClientOptions["fetch"];
|
|
86
|
+
headers?: HeadersOptions;
|
|
87
|
+
} & {
|
|
88
|
+
headers: Headers;
|
|
89
|
+
cache?: RequestCache | undefined;
|
|
90
|
+
credentials?: RequestCredentials | undefined;
|
|
91
|
+
integrity?: string | undefined;
|
|
92
|
+
keepalive?: boolean | undefined;
|
|
93
|
+
method?: string | undefined;
|
|
94
|
+
mode?: RequestMode | undefined;
|
|
95
|
+
priority?: RequestPriority | undefined;
|
|
96
|
+
redirect?: RequestRedirect | undefined;
|
|
97
|
+
referrer?: string | undefined;
|
|
98
|
+
referrerPolicy?: ReferrerPolicy | undefined;
|
|
99
|
+
signal?: (AbortSignal | null) | undefined;
|
|
100
|
+
window?: null | undefined;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
declare type OpenapiEffectorClient<Paths extends {}> = {
|
|
104
|
+
createApiEffect: CreateApiEffect<Paths>;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
declare type OptionalParams<T> = HasRequiredKeys<T> extends true ? T | void : T;
|
|
41
108
|
|
|
42
|
-
|
|
109
|
+
/** Given an OpenAPI **Paths Object**, find all paths that have the given method */
|
|
110
|
+
declare type PathsWithMethod<Paths extends {}, PathnameMethod extends HttpMethod> = {
|
|
111
|
+
[Pathname in keyof Paths]: Paths[Pathname] extends {
|
|
112
|
+
[K in PathnameMethod]: any;
|
|
113
|
+
} ? Pathname : never;
|
|
114
|
+
}[keyof Paths];
|
|
43
115
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
116
|
+
/** Return `responses` for an Operation Object */
|
|
117
|
+
declare type ResponseObjectMap<T> = T extends {
|
|
118
|
+
responses: any;
|
|
119
|
+
} ? T['responses'] : unknown;
|
|
48
120
|
|
|
49
121
|
declare type WithError<T = any, P = Record<string, unknown>> = P & {
|
|
50
122
|
error: T;
|
package/dist/openapi-ff.js
CHANGED
|
@@ -1,59 +1,68 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { networkError as I, httpError as q } from "@farfetched/core";
|
|
2
|
+
import { createStore as y, attach as k } from "effector";
|
|
3
|
+
import E, { mergeHeaders as v } from "openapi-fetch";
|
|
4
|
+
const P = "API";
|
|
5
|
+
function R(e) {
|
|
6
|
+
var a;
|
|
7
|
+
return ((a = e.error) == null ? void 0 : a.errorType) === P;
|
|
8
|
+
}
|
|
9
|
+
function U(e, a) {
|
|
10
|
+
let s, t;
|
|
11
|
+
return e === void 0 ? (s = E(), t = void 0) : typeof e == "object" && "request" in e ? (s = e, t = a) : (s = E(), t = e), { createApiEffect: (m, i, r) => {
|
|
12
|
+
var w, x;
|
|
13
|
+
const p = m.toUpperCase(), A = s[p], T = ((w = r == null ? void 0 : r.mapParams) == null ? void 0 : w.source) ?? y(null), l = !!(r != null && r.mapParams), h = !!(r != null && r.mapParams.fn), d = l ? h ? r.mapParams.fn : r.mapParams : null;
|
|
14
|
+
return {
|
|
15
|
+
effect: k({
|
|
16
|
+
source: T,
|
|
17
|
+
effect: async (g, u) => {
|
|
18
|
+
let f = u;
|
|
19
|
+
l && (h ? f = d(g, u) : f = d(u));
|
|
20
|
+
const { data: C, error: o, response: n } = await A(i, f).catch((c) => {
|
|
21
|
+
throw I({
|
|
22
|
+
reason: (c == null ? void 0 : c.message) ?? null,
|
|
23
|
+
cause: c
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
if (o != null && o !== "")
|
|
27
|
+
throw F({
|
|
28
|
+
status: n.status,
|
|
29
|
+
statusText: n.statusText,
|
|
30
|
+
response: o
|
|
31
|
+
});
|
|
32
|
+
if (!n.ok)
|
|
33
|
+
throw q({
|
|
34
|
+
status: n.status,
|
|
35
|
+
statusText: n.statusText,
|
|
36
|
+
response: null
|
|
37
|
+
});
|
|
38
|
+
return C;
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
contract: ((x = t == null ? void 0 : t.createContract) == null ? void 0 : x.call(t, m, i)) ?? H()
|
|
42
|
+
};
|
|
43
|
+
} };
|
|
44
|
+
}
|
|
45
|
+
function F(e) {
|
|
5
46
|
return {
|
|
6
|
-
...
|
|
7
|
-
errorType:
|
|
8
|
-
explanation: "Request was finished with unsuccessful
|
|
47
|
+
...e,
|
|
48
|
+
errorType: P,
|
|
49
|
+
explanation: "Request was finished with unsuccessful API response"
|
|
9
50
|
};
|
|
10
51
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function y(t, e = {}) {
|
|
16
|
-
const f = (a, s, r) => {
|
|
17
|
-
const h = a.toUpperCase(), p = t[h];
|
|
18
|
-
return m(async (u) => {
|
|
19
|
-
var i;
|
|
20
|
-
const { data: w, error: o, response: n } = await p(
|
|
21
|
-
s,
|
|
22
|
-
((i = r == null ? void 0 : r.mapParams) == null ? void 0 : i.call(r, u)) ?? u
|
|
23
|
-
).catch((c) => {
|
|
24
|
-
throw E({
|
|
25
|
-
reason: (c == null ? void 0 : c.message) ?? null,
|
|
26
|
-
cause: c
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
if (o != null && o !== "")
|
|
30
|
-
throw x({
|
|
31
|
-
status: n.status,
|
|
32
|
-
statusText: n.statusText,
|
|
33
|
-
response: o
|
|
34
|
-
});
|
|
35
|
-
if (!n.ok)
|
|
36
|
-
throw T({
|
|
37
|
-
status: n.status,
|
|
38
|
-
statusText: n.statusText,
|
|
39
|
-
response: null
|
|
40
|
-
});
|
|
41
|
-
return w;
|
|
42
|
-
});
|
|
52
|
+
function _(e, a) {
|
|
53
|
+
return {
|
|
54
|
+
...e,
|
|
55
|
+
headers: v(e.headers, a)
|
|
43
56
|
};
|
|
57
|
+
}
|
|
58
|
+
function H() {
|
|
44
59
|
return {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (e.createContract == null)
|
|
48
|
-
throw new Error("'createContract' is missing in config");
|
|
49
|
-
return {
|
|
50
|
-
effect: f(a, s, r),
|
|
51
|
-
contract: e.createContract(a, s)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
60
|
+
isData: (e) => !0,
|
|
61
|
+
getErrorMessages: () => []
|
|
54
62
|
};
|
|
55
63
|
}
|
|
56
64
|
export {
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
U as createEffectorClient,
|
|
66
|
+
R as isApiError,
|
|
67
|
+
_ as mergeInitHeaders
|
|
59
68
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openapi-ff",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/borolgs/openapi-ff.git"
|
|
8
|
+
},
|
|
5
9
|
"scripts": {
|
|
6
10
|
"test": "vitest run --typecheck",
|
|
7
11
|
"build": "vite build",
|
|
@@ -31,7 +35,7 @@
|
|
|
31
35
|
"@biomejs/biome": "^1.9.4",
|
|
32
36
|
"@farfetched/zod": "^0.12.8",
|
|
33
37
|
"@types/node": "^20.12.7",
|
|
34
|
-
"typescript": "5.
|
|
38
|
+
"typescript": "5.8.3",
|
|
35
39
|
"undici": "^6.21.0",
|
|
36
40
|
"vite": "5",
|
|
37
41
|
"vite-plugin-dts": "^3.8.3",
|
|
@@ -44,7 +48,6 @@
|
|
|
44
48
|
"effector": "^23.2.3"
|
|
45
49
|
},
|
|
46
50
|
"dependencies": {
|
|
47
|
-
"openapi-fetch": "^0.
|
|
48
|
-
"openapi-typescript-helpers": "^0.0.15"
|
|
51
|
+
"openapi-fetch": "^0.14.0"
|
|
49
52
|
}
|
|
50
53
|
}
|