openapi-ff 0.1.0 → 0.1.1

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 CHANGED
@@ -58,6 +58,7 @@ Advanced Usage:
58
58
  ```ts
59
59
  import { chainRoute } from "atomic-router";
60
60
  import { startChain } from "@farfetched/atomic-router";
61
+ import { isApiError } from "openapi-ff";
61
62
 
62
63
  const getBlogpostFx = createApiEffect("get", "/blogposts/{post_id}", {
63
64
  mapParams: (args: { postId: string }) => ({ params: { path: { post_id: args.postId } } }),
@@ -85,14 +86,52 @@ const apiError = sample({
85
86
 
86
87
  > openapi-typescript by its design generates runtime-free static types, and only static types.
87
88
 
88
- To enable runtime validation, you can use third-party solutions, such as **[orval](https://orval.dev/)**:
89
+ However, `openapi-ff` allows adding a contract factory when creating a client and provides a corresponding method, `createApiEffectWithContract`:
90
+
91
+ ```ts
92
+ const { createApiEffectWithContract } = createClient(fetchClient, {
93
+ createContract(method, path) {
94
+ // ... create your own contract
95
+ return contract; // Contract<unknown, unknown>
96
+ },
97
+ });
98
+
99
+ const query = createQuery({
100
+ ...createApiEffectWithContract("get", "/blogposts"),
101
+ });
102
+ ```
103
+
104
+ ### [typed-openapi](https://github.com/astahmer/typed-openapi) example
89
105
 
90
106
  ```bash
107
+ npx typed-openapi path/to/api.yaml -o src/zod.ts -r zod # Generate zod schemas
91
108
  pnpm install zod @farfetched/zod
92
- npx orval --input path/to/api.yaml --output src/zod.ts --client zod --mode single
93
109
  ```
94
110
 
95
- Usage:
111
+ ```ts
112
+ import { EndpointByMethod } from "./zod";
113
+ import { zodContract } from "@farfetched/zod";
114
+
115
+ const { createApiEffectWithContract } = createClient(fetchClient, {
116
+ createContract(method, path) {
117
+ const { response } = (EndpointByMethod as any)[method][path];
118
+ return zodContract(response);
119
+ },
120
+ });
121
+
122
+ const query = createQuery({
123
+ ...createApiEffectWithContract("get", "/blogposts"),
124
+ });
125
+ ```
126
+
127
+ ### [orval](https://orval.dev/) example
128
+
129
+ Alternatively, you can simply add any contract to a query:
130
+
131
+ ```bash
132
+ pnpm install zod @farfetched/zod
133
+ npx orval --input path/to/api.yaml --output src/zod.ts --client zod --mode single
134
+ ```
96
135
 
97
136
  ```ts
98
137
  import { zodContract } from "@farfetched/zod";
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("effector"),o=require("@farfetched/core"),f="API";function w(r){return{...r,errorType:f,explanation:"Request was finished with unsuccessful HTTP code"}}function E(r){var s;return((s=r.error)==null?void 0:s.errorType)===f}function d(r){return{createApiEffect:(s,i,e)=>{const l=s.toUpperCase(),T=r[l];return p.createEffect(async u=>{var c;const{data:h,error:a,response:t}=await T(i,((c=e==null?void 0:e.mapParams)==null?void 0:c.call(e,u))??u).catch(n=>{throw o.networkError({reason:(n==null?void 0:n.message)??null,cause:n})});if(a!=null&&a!=="")throw w({status:t.status,statusText:t.statusText,response:a});if(!t.ok)throw o.httpError({status:t.status,statusText:t.statusText,response:null});return h})}}}exports.createClient=d;exports.isApiError=E;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("effector"),l=require("@farfetched/core"),h="API";function C(t){return{...t,errorType:h,explanation:"Request was finished with unsuccessful HTTP code"}}function A(t){var e;return((e=t.error)==null?void 0:e.errorType)===h}function g(t,e={}){const u=(s,a,r)=>{const w=s.toUpperCase(),E=t[w];return p.createEffect(async i=>{var f;const{data:T,error:o,response:n}=await E(a,((f=r==null?void 0:r.mapParams)==null?void 0:f.call(r,i))??i).catch(c=>{throw l.networkError({reason:(c==null?void 0:c.message)??null,cause:c})});if(o!=null&&o!=="")throw C({status:n.status,statusText:n.statusText,response:o});if(!n.ok)throw l.httpError({status:n.status,statusText:n.statusText,response:null});return T})};return{createApiEffect:u,createApiEffectWithContract:(s,a,r)=>{if(e.createContract==null)throw new Error("'createContract' is missing in config");return{effect:u(s,a,r),contract:e.createContract(s,a.toString())}}}}exports.createClient=g;exports.isApiError=A;
@@ -1,4 +1,5 @@
1
1
  import { Client } from 'openapi-fetch';
2
+ import { Contract } from '@farfetched/core';
2
3
  import { Effect } from 'effector';
3
4
  import { FarfetchedError } from '@farfetched/core';
4
5
  import { FetchResponse } from 'openapi-fetch';
@@ -23,7 +24,16 @@ declare type CreateApiEffectOptions<Init> = {
23
24
  mapParams?: (init: any) => Init;
24
25
  };
25
26
 
26
- export declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>): OpenapiEffectorClient<Paths, Media>;
27
+ declare type CreateApiEffectWithContract<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends CreateApiEffectOptions<Init> = {}>(method: Method, path: Path, options?: Options) => {
28
+ effect: Effect<Init extends undefined ? void : InitOrPrependInit<Init, Parameters<NonNullable<Options["mapParams"]>>[0], Options>, Response["data"], ApiError<number, Response["error"]> | HttpError | NetworkError>;
29
+ contract: Contract<unknown, Response["data"]>;
30
+ };
31
+
32
+ export declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>, config?: EffectorClientOptions): OpenapiEffectorClient<Paths, Media>;
33
+
34
+ declare type EffectorClientOptions = {
35
+ createContract?: (method: HttpMethod, path: string) => Contract<unknown, unknown>;
36
+ };
27
37
 
28
38
  declare type InitOrPrependInit<Init, PrependInit, Options extends CreateApiEffectOptions<Init>> = Options extends {
29
39
  mapParams: (init: any) => any;
@@ -33,6 +43,7 @@ export declare function isApiError(args: WithError): args is WithError<ApiError>
33
43
 
34
44
  declare interface OpenapiEffectorClient<Paths extends {}, Media extends MediaType = MediaType> {
35
45
  createApiEffect: CreateApiEffect<Paths, Media>;
46
+ createApiEffectWithContract: CreateApiEffectWithContract<Paths, Media>;
36
47
  }
37
48
 
38
49
  declare type WithError<T = any, P = Record<string, unknown>> = P & {
@@ -1,4 +1,5 @@
1
1
  import { Client } from 'openapi-fetch';
2
+ import { Contract } from '@farfetched/core';
2
3
  import { Effect } from 'effector';
3
4
  import { FarfetchedError } from '@farfetched/core';
4
5
  import { FetchResponse } from 'openapi-fetch';
@@ -23,7 +24,16 @@ declare type CreateApiEffectOptions<Init> = {
23
24
  mapParams?: (init: any) => Init;
24
25
  };
25
26
 
26
- export declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>): OpenapiEffectorClient<Paths, Media>;
27
+ declare type CreateApiEffectWithContract<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>, Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, Options extends CreateApiEffectOptions<Init> = {}>(method: Method, path: Path, options?: Options) => {
28
+ effect: Effect<Init extends undefined ? void : InitOrPrependInit<Init, Parameters<NonNullable<Options["mapParams"]>>[0], Options>, Response["data"], ApiError<number, Response["error"]> | HttpError | NetworkError>;
29
+ contract: Contract<unknown, Response["data"]>;
30
+ };
31
+
32
+ export declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(client: Client<Paths, Media>, config?: EffectorClientOptions): OpenapiEffectorClient<Paths, Media>;
33
+
34
+ declare type EffectorClientOptions = {
35
+ createContract?: (method: HttpMethod, path: string) => Contract<unknown, unknown>;
36
+ };
27
37
 
28
38
  declare type InitOrPrependInit<Init, PrependInit, Options extends CreateApiEffectOptions<Init>> = Options extends {
29
39
  mapParams: (init: any) => any;
@@ -33,6 +43,7 @@ export declare function isApiError(args: WithError): args is WithError<ApiError>
33
43
 
34
44
  declare interface OpenapiEffectorClient<Paths extends {}, Media extends MediaType = MediaType> {
35
45
  createApiEffect: CreateApiEffect<Paths, Media>;
46
+ createApiEffectWithContract: CreateApiEffectWithContract<Paths, Media>;
36
47
  }
37
48
 
38
49
  declare type WithError<T = any, P = Record<string, unknown>> = P & {
@@ -1,50 +1,59 @@
1
- import { createEffect as p } from "effector";
2
- import { networkError as T, httpError as i } from "@farfetched/core";
3
- const f = "API";
4
- function w(r) {
1
+ import { createEffect as m } from "effector";
2
+ import { networkError as E, httpError as T } from "@farfetched/core";
3
+ const l = "API";
4
+ function x(t) {
5
5
  return {
6
- ...r,
7
- errorType: f,
6
+ ...t,
7
+ errorType: l,
8
8
  explanation: "Request was finished with unsuccessful HTTP code"
9
9
  };
10
10
  }
11
- function A(r) {
12
- var s;
13
- return ((s = r.error) == null ? void 0 : s.errorType) === f;
11
+ function P(t) {
12
+ var e;
13
+ return ((e = t.error) == null ? void 0 : e.errorType) === l;
14
14
  }
15
- function P(r) {
16
- return {
17
- createApiEffect: (s, c, t) => {
18
- const l = s.toUpperCase(), h = r[l];
19
- return p(async (u) => {
20
- var o;
21
- const { data: m, error: n, response: e } = await h(
22
- c,
23
- ((o = t == null ? void 0 : t.mapParams) == null ? void 0 : o.call(t, u)) ?? u
24
- ).catch((a) => {
25
- throw T({
26
- reason: (a == null ? void 0 : a.message) ?? null,
27
- cause: a
28
- });
15
+ function g(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
29
27
  });
30
- if (n != null && n !== "")
31
- throw w({
32
- status: e.status,
33
- statusText: e.statusText,
34
- response: n
35
- });
36
- if (!e.ok)
37
- throw i({
38
- status: e.status,
39
- statusText: e.statusText,
40
- response: null
41
- });
42
- return m;
43
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
+ });
43
+ };
44
+ return {
45
+ createApiEffect: f,
46
+ createApiEffectWithContract: (a, s, r) => {
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.toString())
52
+ };
44
53
  }
45
54
  };
46
55
  }
47
56
  export {
48
- P as createClient,
49
- A as isApiError
57
+ g as createClient,
58
+ P as isApiError
50
59
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openapi-ff",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "test": "vitest run --typecheck",
@@ -29,13 +29,15 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@biomejs/biome": "^1.9.4",
32
+ "@farfetched/zod": "^0.12.8",
32
33
  "@types/node": "^20.12.7",
33
34
  "typescript": "5.1.6",
34
35
  "undici": "^6.21.0",
35
36
  "vite": "5",
36
37
  "vite-plugin-dts": "^3.8.3",
37
38
  "vite-tsconfig-paths": "4.2.1",
38
- "vitest": "2.0.4"
39
+ "vitest": "2.0.4",
40
+ "zod": "^3.23.8"
39
41
  },
40
42
  "peerDependencies": {
41
43
  "@farfetched/core": "^0.12.8",