Coder Social home page Coder Social logo

ecyrbe / zodios Goto Github PK

View Code? Open in Web Editor NEW
1.6K 7.0 45.0 7.43 MB

typescript http client and server with zod validation

Home Page: https://www.zodios.org/

License: MIT License

TypeScript 96.24% JavaScript 3.21% CSS 0.55%
zod http typescript api nodejs openapi rest

zodios's Introduction

Zodios

Zodios logo

Zodios is a typescript api client and an optional api server with auto-completion features backed by axios and zod and express
Documentation

langue typescript npm GitHub GitHub Workflow Status

Bundle Size Bundle Size Bundle Size Bundle Size Bundle Size Bundle Size Bundle Size

zodios.mp4

What is it ?

It's an axios compatible API client and an optional expressJS compatible API server with the following features:

  • really simple centralized API declaration
  • typescript autocompletion in your favorite IDE for URL and parameters
  • typescript response types
  • parameters and responses schema thanks to zod
  • response schema validation
  • powerfull plugins like fetch adapter or auth automatic injection
  • all axios features available
  • @tanstack/query wrappers for react and solid (vue, svelte, etc, soon)
  • all expressJS features available (middlewares, etc.)

Table of contents:

Install

Client and api definitions :

> npm install @zodios/core

or

> yarn add @zodios/core

Server :

> npm install @zodios/core @zodios/express

or

> yarn add @zodios/core @zodios/express

How to use it on client side ?

For an almost complete example on how to use zodios and how to split your APIs declarations, take a look at dev.to example.

Declare your API with zodios

Here is an example of API declaration with Zodios.

import { Zodios } from "@zodios/core";
import { z } from "zod";

const apiClient = new Zodios(
  "https://jsonplaceholder.typicode.com",
  // API definition
  [
    {
      method: "get",
      path: "/users/:id", // auto detect :id and ask for it in apiClient get params
      alias: "getUser", // optional alias to call this endpoint with it
      description: "Get a user",
      response: z.object({
        id: z.number(),
        name: z.string(),
      }),
    },
  ],
);

Calling this API is now easy and has builtin autocomplete features :

//   typed                     auto-complete path   auto-complete params
//     ▼                               ▼                   ▼
const user = await apiClient.get("/users/:id", { params: { id: 7 } });
console.log(user);

It should output

{ id: 7, name: 'Kurtis Weissnat' }

You can also use aliases :

//   typed                     alias   auto-complete params
//     ▼                        ▼                ▼
const user = await apiClient.getUser({ params: { id: 7 } });
console.log(user);

API definition format

type ZodiosEndpointDescriptions = Array<{
  method: 'get'|'post'|'put'|'patch'|'delete';
  path: string; // example: /posts/:postId/comments/:commentId
  alias?: string; // example: getPostComments
  immutable?: boolean; // flag a post request as immutable to allow it to be cached with react-query
  description?: string;
  requestFormat?: 'json'|'form-data'|'form-url'|'binary'|'text'; // default to json if not set
  parameters?: Array<{
    name: string;
    description?: string;
    type: 'Path'|'Query'|'Body'|'Header';
    schema: ZodSchema; // you can use zod `transform` to transform the value of the parameter before sending it to the server
  }>;
  response: ZodSchema; // you can use zod `transform` to transform the value of the response before returning it
  status?: number; // default to 200, you can use this to override the sucess status code of the response (only usefull for openapi and express)
  responseDescription?: string; // optional response description of the endpoint
  errors?: Array<{
    status: number | 'default';
    description?: string;
    schema: ZodSchema; // transformations are not supported on error schemas
  }>;
}>;

Full documentation

Check out the full documentation or following shortcuts.

Ecosystem

Roadmap for v11

for Zod/Io-Ts` :

  • By using the TypeProvider pattern we can now make zodios validation agnostic.

  • Implement at least ZodTypeProvider and IoTsTypeProvider since they both support input and output type inferrence

  • openapi generation will only be compatible with zod though

  • Not a breaking change so no codemod needed

  • MonoRepo:

    • Zodios will become a really large project so maybe migrate to turbo repo + pnpm

    • not a breaking change

  • Transform:

    • By default, activate transforms on backend and disable on frontend (today it's the opposite), would make server transform code simpler since with this option we could make any transforms activated not just zod defaults.

    • Rationale being that transformation can be viewed as business code that should be kept on backend

    • breaking change => codemod to keep current defaults by setting them explicitly

  • Axios:

    • Move Axios client to it's own package @zodios/axios and keep @zodios/core with only common types and helpers

    • Move plugins to @zodios/axios-plugins

    • breaking change => easy to do a codemod for this

  • Fetch:

    • Create a new Fetch client with almost the same features as axios, but without axios dependency @zodios/fetch

    • Today we have fetch support with a plugin for axios instance (zodios maintains it's own axios network adapter for fetch). But since axios interceptors are not used by zodios plugins, we can make fetch implementation lighter than axios instance.

    • Create plugins package @zodios/fetch-plugins

    • Not sure it's doable without a lot of effort to keep it in sync/compatible with axios client

    • new feature, so no codemod needed

  • React/Solid:

    • make ZodiosHooks independant of Zodios client instance (axios, fetch)

    • not a breaking change, so no codemod needed

  • Client Request Config

    • uniform Query/Mutation with body sent on the config and not as a standalone object. This would allow to not do client.deleteUser(undefined, { params: { id: 1 } }) but simply client.deleteUser({ params: { id: 1 } })

    • breaking change, so a codemod would be needed, but might be difficult to implement

  • Mock/Tests:

    • if we implement an abstraction layer for client instance, relying on moxios to mock APIs response will likely not work for fetch implementation.

    • create a @zodios/testing package that work for both axios/fetch clients

    • new feature, so no breaking change (no codemod needed)

You have other ideas ? Let me know !

Dependencies

Zodios even when working in pure Javascript is better suited to be working with Typescript Language Server to handle autocompletion. So you should at least use the one provided by your IDE (vscode integrates a typescript language server) However, we will only support fixing bugs related to typings for versions of Typescript Language v4.5 Earlier versions should work, but do not have TS tail recusion optimisation that impact the size of the API you can declare.

Also note that Zodios do not embed any dependency. It's your Job to install the peer dependencies you need.

Internally Zodios uses these libraries on all platforms :

  • zod
  • axios

zodios's People

Contributors

adipol1359 avatar astahmer avatar ayoubqrt avatar dbanck avatar dependabot[bot] avatar ecyrbe avatar emilpaw avatar g3root avatar ghoullier avatar h-sigma avatar itacirgabral avatar jamesgelok avatar maantje avatar maurer2 avatar pkoo avatar pkrinesh avatar qzcurious avatar renovate-bot avatar renovate[bot] avatar rjwilsondev avatar rmvn27 avatar secondthunder avatar simonflk avatar sunakan avatar thelinuxlich avatar yassh avatar zehir avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

zodios's Issues

Subscriptions through SSE

To keep things simple, as of today most browser support HTTP 2, so SSE doesn't have those disadvantages of the past and can replace Websockets anywhere you aren't building a FPS-shooter on the browser :)

So it's a simpler architecture and built upon HTTP already. Some GraphQL projects are already adopting SSE as the standard for subscriptions, like graphql-yoga for example.

What do you think?

Ability to provide default values for params/queries

This is something I have wanted to be able to do a few times now and doesn't seem possible. Could be solved via the Zod default function if all the params/query fields were parsed with the schema (I don't see that happening anywhere but maybe I missed it), that's what I have tried so far.

[Bug] `data` for POST request is optional

I've define a body parameter for my post request. But I can ignore data without ts showing error.

codesandbox

import { makeApi, Zodios } from "@zodios/core";
import { z } from "zod";

export const Request = z.object({
  page: z.number().int().min(1),
  perpage: z.number().int().max(20)
});

const Response = z.object({});

const userApi = makeApi([
  {
    method: "post",
    path: "user/list",
    alias: "userList",
    immutable: true,
    parameters: [
      {
        name: "body",
        type: "Body",
        schema: Request
      }
    ],
    response: Response
  }
]);

const apiClient = new Zodios(userApi);
apiClient.userList(); // <-- data should be required
apiClient.post("user/list"); // <-- data should be required

[Feature Request] - Improve Error messages

I'm really liking this library so far. For context I'm creating a web app which acts essentially as an aggregator for other API's and Zodios is doing a lot of the heavy lifting for me, so firstly thank you! 😍

I've noticed that the error's that are reported from a failed response parsing are just throwing the ZodError, without any further context.

ZodError: [
  {
    "code": "invalid_type",
    "expected": "array",
    "received": "undefined",
    "path": [
      "value"
    ],
    "message": "Required"
  }
]

My ideal solution would give additional context, such as the URL called with an option to enable the original response content to be shown. It may be possible/more suitable for this functionality to live inside a plugin, which I'll start to investigate if it's possible to do so now. Thought I would just get the ball rolling, in the case where this is actually a lot simpler than it seems.

bug: makeApi not working as intended?

I'm not sure what I'm doing wrong, but I'm only getting types coming through when I use the apiBuilder helper, but if I use makeApi the types look messed up, see the screenshot below. Is this a bug or am I not using makeApi as intended?

image

Ability to have multiple aliases with different schemas for the same endpoint

Hi! 👋

Firstly, amazing project - one of the best typed libraries I've seen.

I have a small improvement suggestion - would be great, if the Zod validation utility searching for schema to apply to the response took into account also the alias name, not just the method with the path. This would allow forming multiple endpoint descriptions for the same endpoint, for which developer expects to receive different results (e.g. depending on type of entity returned).

Sample use case
The GET /vehicle/:id/state endpoint returns a vehicle that can be of different types (either a car, or a ship, or a bike) and so the responses would be different too (1st would return fields like consumedGas, 2nd: isSailFolded, etc.). While it's possible to create a schema with discriminated union for those, it might end up to be a big union. Also, in some cases, knowing the context of the query, it might make sense for it to only return one of the vehicle types (e.g. a dashboard panel displaying tire pressure wouldn't be satisfied with ship vehicle being returned). For such situations, a developer might create endpoint descriptions for the same endpoint, but with different response validation schemas and of course different aliases: getCarState, getShipState, etc.


I used patch-package to fast-forward this functionality locally, and it works perfectly - here's the code for the core and react packages:

diff --git a/node_modules/@zodios/core/lib/plugins/zod-validation.plugin.js b/node_modules/@zodios/core/lib/plugins/zod-validation.plugin.js
index 2578894..c10620e 100644
--- a/node_modules/@zodios/core/lib/plugins/zod-validation.plugin.js
+++ b/node_modules/@zodios/core/lib/plugins/zod-validation.plugin.js
@@ -48,7 +48,7 @@ const plugin = {
         return conf;
     }),
     response: (api, config, response) => __awaiter(void 0, void 0, void 0, function* () {
-        const endpoint = (0, zodios_plugins_utils_1.findEndpoint)(api, config.method, config.url);
+        const endpoint = (0, zodios_plugins_utils_1.findEndpoint)(api, config.method, config.url, config.fromAlias);
         /* istanbul ignore next */
         if (!endpoint) {
             throw new Error(`No endpoint found for ${config.method} ${config.url}`);
diff --git a/node_modules/@zodios/core/lib/plugins/zodios-plugins.utils.js b/node_modules/@zodios/core/lib/plugins/zodios-plugins.utils.js
index ba5828e..478af9c 100644
--- a/node_modules/@zodios/core/lib/plugins/zodios-plugins.utils.js
+++ b/node_modules/@zodios/core/lib/plugins/zodios-plugins.utils.js
@@ -1,7 +1,7 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
 exports.findEndpoint = void 0;
-function findEndpoint(api, method, path) {
-    return api.find((e) => e.method === method && e.path === path);
+function findEndpoint(api, method, path, alias) {
+    return api.find((e) => e.method === method && e.path === path && (alias === undefined || e.alias === alias));
 }
 exports.findEndpoint = findEndpoint;
diff --git a/node_modules/@zodios/core/lib/zodios.js b/node_modules/@zodios/core/lib/zodios.js
index 4190f45..5784163 100644
--- a/node_modules/@zodios/core/lib/zodios.js
+++ b/node_modules/@zodios/core/lib/zodios.js
@@ -137,10 +137,10 @@ class ZodiosClass {
         this.api.forEach((endpoint) => {
             if (endpoint.alias) {
                 if (["post", "put", "patch", "delete"].includes(endpoint.method)) {
-                    this[endpoint.alias] = (data, config) => this.request(Object.assign(Object.assign({}, config), { method: endpoint.method, url: endpoint.path, data }));
+                    this[endpoint.alias] = (data, config) => this.request(Object.assign(Object.assign({}, config), { method: endpoint.method, url: endpoint.path, data }, { fromAlias: endpoint.alias }));
                 }
                 else {
-                    this[endpoint.alias] = (config) => this.request(Object.assign(Object.assign({}, config), { method: endpoint.method, url: endpoint.path }));
+                    this[endpoint.alias] = (config) => this.request(Object.assign(Object.assign({}, config), { method: endpoint.method, url: endpoint.path }, { fromAlias: endpoint.alias }));
                 }
             }
         });
diff --git a/node_modules/@zodios/react/lib/hooks.js b/node_modules/@zodios/react/lib/hooks.js
index 8959b82..ceb8f0b 100644
--- a/node_modules/@zodios/react/lib/hooks.js
+++ b/node_modules/@zodios/react/lib/hooks.js
@@ -26,11 +26,11 @@ var ZodiosHooksClass = /** @class */ (function () {
             if (endpoint.alias) {
                 if (["post", "put", "patch", "delete"].includes(endpoint.method)) {
                     _this["use".concat((0, utils_1.capitalize)(endpoint.alias))] = function (config, mutationOptions) {
-                        return _this.useMutation(endpoint.method, endpoint.path, config, mutationOptions);
+                        return _this.useMutation(endpoint.method, endpoint.path, { ...config, fromAlias: endpoint.alias }, mutationOptions);
                     };
                 }
                 else {
-                    _this["use".concat((0, utils_1.capitalize)(endpoint.alias))] = function (config, queryOptions) { return _this.useQuery(endpoint.path, config, queryOptions); };
+                    _this["use".concat((0, utils_1.capitalize)(endpoint.alias))] = function (config, queryOptions) { return _this.useQuery(endpoint.path, { ...config, fromAlias: endpoint.alias }, queryOptions); };
                 }
             }
         });

React hook can not be auto-completed

Problem

A sample codesandbox for demonstrating the bug
image

And note that, following the doc, I installed @tanstack/react instead of react-query.

Possible Solution

A simple solution is to install react-query@3.

But maybe this library should use @tanstack/react instead.
image

Sorry I can't locate the right spot for the breaking code. And also, I can't figure out how this library were importing react-query. It's even not marked as a dependency in package.json. Hope the provided information is enough.

Cryptic error message

When the error sent by a API request doesn't conform to the defined schema, Zodios replies just with Zodios: Invalid response, there should be a description of what's missing and what it's receiving

Plugin application order inconsistent

I'm seeing an issue where the plugins are applied in one order for 2 requests and then in a different order after that, leading to problems with a plugin that modifies the API response before it reaches the zod validation plugin. So the first 2 requests fail and then the 3rd succeeds because the response transform plugin is now processed before the zod validation plugin.

It seems to be because of this in-place reverse happening here https://github.com/ecyrbe/zodios/blob/main/src/plugins/zodios-plugins.ts#L111

If I locally change that to [...this.plugins].reverse() the problem goes away. Using v9.3.1 of @zodios/core.

P.S. Love the library so far, building an Observable variant of the zodios hooks package to use with XState ontop of @tanstack/query-core

Usage without Axios

Hey @ecyrbe, this is fantastic!

In my org, we recently moved off axios and started using a small custom fetch-wrapper. We really want to use this but we don't want to go back to axios. It would be awesome if we could provide a custom fetcher to this lib. Is this something you may consider?

I understand if this is out-of-scope for this lib.

Thanks!!

Feature Request: Allow BaseUrl to be optional

This is a feature request to allow the BaseUrl to be optional when initialising Zodios.

The reason for this is that I want to have use a relative path, instead of an absolute path to fetch data from the web server the app is running under (As I'll be running zodios in the browser).

Make zodiosApp and ZodiosRouter context aware

Express does not have a context per say.
Users usually use req object and inject their context in there.
Since zodios is aimed at typesafety and discoverability, we should add one.

const context = z.object({
   user: z.object({
     name: z.string(),
     email: z.string().email(),
   })
});

const app = zodiosApp(userApi, { context });

app.get('/users', (req, res) => {
  // user is typed and req has autocompletion
  const user = req.user;
  if (isAdmin(user)) {
    res.status(403).json (...);
  }
  ...
});

missing TS error when a required query param is not passed

idk if that is even possible but I expected this error at compile-time

const apiEndpoints = makeApi([
  {
    method: 'delete',
    path: '/cart/clean',
    requestFormat: 'json',
    parameters: [
      {
        name: 'storeId',
        type: 'Query',
        schema: z.number(),
      },
    ],
    response: z.string(),
  },
]);
const client = new Zodios(apiEndpoints);

// no error, should have error
client.delete('/cart/clean');

// no error, should have error
client.delete('/cart/clean', undefined);

// expects error, is fine
client.delete('/cart/clean', undefined, {});

// expects error, is fine
client.delete('/cart/clean', undefined, { queries: {} });

I think there is the same behaviour with header params

headers object doesn't accept the defined schema

I have a parameter with the Header type defined as:

{
  type: 'Header',
  name: 'token',
  schema: z.object({
    Authorization: z.string().transform(s => {
      return `Basic ${Buffer.from(s + ':').toString('base64')}`
    }),
  }),
}

But when I try to use it like this:

zoop.getMarketplaceTransactions({
  params: { id: marketplace_id },
  headers: {
    token: {
      Authorization: key,
    },
  },
})

The typechecker complains about the object shape:
image

Transforming parameters causes incorrect type to be inferred

It's me again!

I've got a small issue which I'm hoping isn't a pain to resolve.

Essentially when performing a transform on a schema for a parameter, it seemingly infers the incorrect type. I've added an example below:

const refreshClient = new Zodios(url, [
  {
    method: "post",
    path: "/oauth_token.do",
    alias: "getRefreshTokenWithCode",
    parameters: [
      {
        name: "body",
        type: "Body",
        schema: z
          .object({
            code: z.string(),
            client_id: z.string(),
            client_secret: z.string(),
            redirect_uri: z.string().url(),
          })
          .transform(
            (value) =>
          ),
      },
    ],
    response: z.object({
      access_token: z.string(),
      refresh_token: z.string(),
    }),
  },
] as const);

const refreshResponse = await refreshClient.getRefreshTokenWithCode(
      {
      code,
      client_id: process.env.SERVICENOW__CLIENT_ID,
      client_secret: process.env.SERVICENOW__CLIENT_SECRET,
      redirect_uri: process.env.SERVICENOW__REDIRECT_URI,
    }
  );

image

What I'd like to be able to do is to effectively simplify the inputs via a transform?

Some strict TS narrowing is limiting parameter reuse

If we define the parameters array outside of the parameter definition like this:

apiBuilder({
  path: '/marketplaces/:id/transactions',
  method: 'get',
  alias: 'getTransactions',
  parameters: queryStringStandardParameters,
  response: transactionsSchema,
})

The typechecker complains with:
image

zodios cause big bundle size (on nextjs)

Discussed in #197

Originally posted by QzCurious October 21, 2022
I'm not familiar how bundler works and how to trace it down. I am willing to inspect it more, please give me some instructions.

The problem

I'm using nextjs. When integrate with zodios, a simple request can cause build bundle up to 197KB. Although @zodios/core is only of size 3.7 kb. I don't know if it's me importing thing wrong. Please explain to me what is causing this to be happend.
image

Here is a repo to showcase. Each commit take a small step for reviewing how code and bundle size changes. I've also configured it with @next/bundle-analyzer so you can ANALYZE=true yarn build to inspect the bundle (but I can't tell how and what is wrong, sadly).

In summary:

clean page with axios with zod with zodios
77.9 kB 95.3 kB 106 kB 197 kb

One wierd thing I noticed. bundle-analyzer list out a few packages that is not mean to be ship to production are included in bundle.
image

API Client-wide variable in method path

Hi there! First, I'd like to thank you for this library. I have been using it for some time and I love it!

I have been working with API, which requires passing an Organization ID within the endpoint path, for example:

/v1/organizations/**organization_id**/shipments

Is it possible to set that ID globally for API client instance? I would prefer to avoid passing it in every operation as variable.

Thanks!

Typescript-json > zod ?

There is a new player in the field of TS runtime validation libraries. typescript-json adds a plugin transform to tsconfig.json and can work with existing TS types.

Pros

  • No need to add a extra DSL for runtime, just use the TS interfaces/types
  • Supports anything fancy in TS level
  • Much faster than zod
  • Has a "comment tag" API for richer validation
  • Might simplify zodios internals

Cons

  • Doesn't have transforms
  • Zod has better error output

I understand the library is deeply committed to zod but anyway I thought it would be interesting to share this, might be gamechanging.

Query string as an array

Thanks for a great library! I really like it.
I've been working with API which requires passing an array of IDs.
for example:

  {
    method: 'get',
    path: '/users',
    alias: 'getUsers',
    parameters: [
      {
        type: 'Query',
        name: 'id',
        schema: z.array(z.string()),
      },
    ],
    response: z.array(z.object(...)),
  })

currently, zodios generates following URL:

http://localhost/users?id=1,2,3

however, the standard URL API does not recognize it as an array

> new URL('http://localhost/users?id=1,2,3').searchParams.getAll('id')
['1,2,3']

I think the standard representation of an array of searchparams would be:

http://localhost/users?id=1&id=2&id=3
> new URL('http://localhost/users?id=1&id=2&id=3').searchParams.getAll('id')
['1', '2', '3']

can I change the encoding of an array?

Improve API definition creation experience

One of the good ideas of zodios is to use plain objects to declare APIs.
This make api definition easier to read, faster to write. But it comes at a cost :

  • A lot of typescript utility types had to be made to parse the definition and transform it.
  • When you have made a mistake typescript create cryptic messages for your whole definition instead of highlighting where the error is.
  • Autocompletion is somewhat broken sometimes (all optional properties don't seem to have autocompletion once one endpoint has been written)

Needs investigation to improve this and maybe simplify the whole thing if possible

Path parameters with explicit types are not correct at runtime

If I have the following API and endpoint:

const userApi = makeApi([
  {
    method: 'get',
    path: '/users/:id',
    alias: 'getUser',
    description: 'Get a user',
    parameters: [
      {
        name: 'id',
        type: 'Path',
        schema: z.number(),
      },
    ],
    response: z.object({
      id: z.number(),
      name: z.string().nullable(),
    }),
    errors,
  },
]);

const userRouter = ctx.router(userApi);

userRouter.get('/users/:id', async (req, res) => {
  console.log(typeof req.params.id);

  return res.json({
    id: req.params.id,
    name: "example",
  });
});

The output of the console.log() is string, even though the type is inferred as number.

[improvement] add Alias endpoints

We can improve zodios with named calls in addition to endpoints calls.
Examples:

const client = new Zodios (BASE_URL, [
  {
    method : 'get',
    path: '/users',
    alias: 'getUsers',
    parameters: [...]
  }
  ]);

Then we could call it like that:

  client.get('/users');

Or with it's alias :

  client.getUsers();

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • actions/checkout v4
  • actions/setup-node v4
  • actions/checkout v4
  • github/codeql-action v2
  • github/codeql-action v2
.github/workflows/publish.yml
  • actions/checkout v4
  • actions/setup-node v4
  • orhun/git-cliff-action v2
  • actions/create-release v1
npm
package.json
  • @jest/types 29.6.3
  • @types/express 4.17.19
  • @types/jest 29.5.5
  • @types/multer 1.4.8
  • @types/node 20.8.9
  • axios 1.5.1
  • express 4.18.2
  • form-data 4.0.0
  • jest 29.7.0
  • multer 1.4.5-lts.1
  • rimraf 5.0.5
  • ts-jest 29.1.1
  • ts-node 10.9.1
  • typescript 5.2.2
  • zod 3.22.4
  • axios ^0.x || ^1.0.0
  • zod ^3.x

  • Check this box to trigger a request for Renovate to run again on this repository

make config parameter read-only

Hi, query string as an array again!
I'm useing Zodios with a GraphQL DataLoader.

example:

const userResponse = z.array(
  z.object({
    id: z.number(),
    name: z.string(),
  })
}),

type UserResponse = z.infer<typeof userResponse>

const apiClient = new Zodios(
  "https://example.com",
  [
    {
      method: "get",
      path: "/users",
      alias: "getUsers",
      parameters: [
        {
          type: 'Query',
          name: 'keys',
          schema: z.array(z.string()).min(1),
        },
      ],
      response: userResponse,
    },
  ],
);

const loader = new DataLoader<string, UserResponse>(
  async function batchFunction(keys) { // keys: readonly string[]
    const response = await apiClient.getUsers({ queries: { keys: keys as string[]} })
    ...
  }
)

It's works very well but unfortunately it needs cast because a DataLoader passes a readonly array.
I don't think Zodios will change the passed config (In fact, Zodios internally casts them as read-only.), so could you concider adding readonly to the config parameter deeply?
(Probably the same for the body parameter of the post method, etc.)

[Bug] select for QueryObserverOptions is typed incorrectly

The typing of select was introduced due to #158

For convenience, a codesandbox to demo the bug here.

The bug is described by comments of following code block

const { data } = apiHook.useUser(undefined, {
  //    ^^^^
  //    data is NOT reflecting to return type of select
  select:
    // ^^ zodios is forcing me follow the type Response
    () => ({
      first_name: "first"
    })
});

function useUser() {
  const { data } = useQuery(["key"], () => ({ name: "name" }), {
    // ^^^^
    // data is reflected to return type of select
    // typed as {
    //   first_name: string;
    // } | undefined
    select: () => ({ first_name: "first" })
  });
}

API shorthand definition

I feel like the API for declaring parameters could be simplified, it seems a bit verbose:

apiBuilder({
  path: '/marketplaces/:id/transactions',
  method: 'get',
  alias: 'getMarketplaceTransactions',
  // if it were a POST
  body: {
    .....
  },
  queries: {
      limit: z.number().optional()
      offset: z.number().optional()
      'date_range[gte]': z.string().optional()
      'date_range[lte]': z.string().optional()
  },
  headers: {
    Authorization: z.string()
  },
  // if the user really wants description for openAPI:
  description: {
     queries: {
        limit: 'A parameter to limit the number of rows returned'
     }
  },
  response: transactionsSchema,
})

Besides being less verbose, the parameter types have the same name as the objects in the API client (queries, headers) so it's intuitive

If all fields in a schema are optional, the parameter should be too

I have a parameter defined as

{
      name: 'querystring',
      type: 'Query',
      schema: z.object({
        limit: z.number().optional(),
        offset: z.number().optional(),
        'date_range[gte]': z.string().optional(),
        'date_range[lte]': z.string().optional(),
      }),
    }

If I don't pass { queries: {querystring: {}} to the API call the typechecker will complain.

Inject default params in Zodios client

Suppose I have a url like https://endpoint.com/:id/transactions and I will always pass the same id. Is it possible to inject this parameter on the client so I don't need to add it to the call everytime and make the typechecker happy?

[Feature Request] - Infer from Alias/Path

👋 Hello, it's me again!

What do you think on adding a helper function which can infer the response shape of an alias?

For example I have the following client configured:

export const client = new Zodios("https://example.com", [
  {
    method: 'get',
    path: '/api/get_thing',
    alias: 'getThings',
    response: z.object({
      result: z.string()
    })
  },
] as const);

It would be really cool if we could do something like the following:

import { InferResponse } from "@Zodios/core";

type getThingsResponse = InferResponse<"getThings">
// { result: string; }

This is very similar to the following: https://trpc.io/docs/infer-types

The reason I ask for this is because I'm using SvelteKit and right now the typing between the backend and frontend isn't that strong, so I have to extract my types/schemas into a separate file and import into both places which works but isn't as smooth as I'd like 😄

some utilities are no longer visible

Hi, I've created a Zodios client plugin which uses the following utilities:

import { findEndpoint } from '@zodios/core/lib/plugins/zodios-plugins.utils'
import { replacePathParams } from '@zodios/core/lib/utils'

It worked fine until v10.2.0, however since v10.3.0 these utilities are no longer visible.
These may not have been intended to be public APIs, but I think they are very useful.

Is it possible to use these utilities in a custom plugins? Or is there an alternative?

Using multiple query parameters in an endpoint

Hello and thanks for this great library!

I have a question that I haven't been able to resolve, maybe there is a solution but I missed it in the docs and couldn't find an answer through Google/Stackoverflow either.

Let's say I have a GET endpoint that requires multiple query parameters in the request. How can I implement this with zodios?

{
  method: "get",
  path: "/users/:id", // id here is a single optional parameter
  alias: "getUser",
  description: "Get a user",
  response: z.object({
    id: z.number(),
    name: z.string(),
  })
}

But my endpoint is using multiple parameters like so: /users?page=1&pageSize=10&name=test

How can I add these parameters and type them as I want? I fiddled around and couldn't make it work with multiple parameters like /users?page=:page&pageSize=:pageSize&name=:name. I also want to type these input parameters.

[Feature Request] Allow GET request with body

The data parameter of axios is omitted from get method by ZodiosRequestOptionsByPath.

Note: Sending body/payload in a GET request may cause some existing implementations to reject the request — while not prohibited by the specification, the semantics are undefined. It is better to just avoid sending payloads in GET requests.
MDN

Could zodios be less opinionated about it?

[Feature Request] Validation on path parameters

Discussed in #176

Originally posted by epotter2297 October 11, 2022
It would be really cool if there was a way to use zod to validate my path parameters. For example, if I have an endpoint like so:

{
    method: 'get',
    path: '/users/:id',
    alias: 'getUser',
    description: 'Get a user',
    response: z.object({
      id: z.number(),
      name: z.string().nullable(),
    }),
},

And I want to make sure that id is a valid UUID, I could do something like:

{
    method: 'get',
    path: '/users/:id',
    alias: 'getUser',
    description: 'Get a user',
    parameters: [
      {
        name: 'id',
        description: 'The user id',
        type: 'Path',
        schema: z.string().uuid(),
      },
    ],
    response: z.object({
      id: z.number(),
      name: z.string().nullable(),
    }),
},
```</div>

[zodios-openapi] multi security schemes

Zodios should allow to declare multiple security schemes for openapi

We could add something like this :

const document = toMultiSchemeOpenApi({
  info: {
    title: 'Project A API',
    version: '0.0.0',
    description: 'A simple example API',
  },
  servers: [
    {
      url: '/',
    },
  ],
  securitySchemeAPIs: [
    {
      name: 'basic',
      securityScheme: basicAuthScheme(),
      api: basicAuthAPI,
    },
    {
      name: 'bearer',
      securityScheme: bearerAuthScheme(),
      api: bearerAuthAPI,
    },
    {
        api: notSecuredAPI, // for health check, etc
    }
  ] 
});

[Improvement] add CRUD generation helper

We could improve zodios with CRUD generation feature to remove unnecessary duplicated declarations :

client = new Zodios(BASE_URL, 
  CRUD('users', { schema: zodUserSchema }));

[Feature Request] useQuery for POST request

Senario

For my experience, it quite often to use a POST request for searching resource or pagination.
For example:

POST /users/search HTTP/1.1
Accept: application/json
Content-Type: application/json

{
  "gender": "female",
  "limit": 20
}

Proposal

Maybe it could be

export const Request = z.object({
  gender: z.enum(["male", "female"]),
  limit: z.number().min(0).max(200),
});

export const Response = z.object({});

export type User = z.infer<typeof Request>;

export const userApi = makeApi([
  {
    method: "post",
    mutation: false, // <-- explicitly make it useQuery-able
    path: "users/search",
    parameters: [
      {
        name: "body",
        type: "Body",
        schema: Request,
      },
    ],
    response: Response,
  },
]);

Function to create a single endpoint definition

We currently have an file with a bunch of route definitions. The readability really starts to get worse as the file grow, having an makeEndpoint method would be specially useful when we want one definition per file, e.g:

// getUser.ts

export default makeEndpoint({
  method: "get",
  path: "/users/:id",
  alias: "getUser",
  description: "Get a user",
  response: z.object({
    id: z.number(),
    name: z.string(),
  }),
})
// createUser.ts

export default makeEndpoint({
  method: "post",
  path: "/users",
  alias: "createUser",
  description: "Create a user",
  parameters: [{
    name: 'body',
    type: 'Body',
    schema: z.object({
      name: z.string(),
    })
  }],
  response: z.object({
    id: z.number(),
    name: z.string(),
  }),
})
// api.ts
import getUser from './getUser';
import createUser from './createUser';

// It could also be compatible `makeApi` as well.
const api = new Zodios([getUser, createUser])

Validating request but not response ?

Currently we can validate both the request payload sent and the response content using validate or none

Continuing my reasoning (and use-cases for openapi-zod-client) that not all APIs are in our own control, and therefore that we can only control what we send in the request payload,

would you consider having a validate mode (as a string enum ?) with the current boolean ?

something like:
validate: "request" | "response" | boolean

tbh I don't know if a response mode would really be useful but I added it in the example above so that every options are available

Dynamic base URL using plugin

Hey, I have a use-case where I need to inject a custom base URL depending on the request config, to do so i'm currently using a plugin creator (the baseUrl is an env var fetched at runtime and dynamic by country to be specific..)

it looks kind of like this:

const endpoints = makeApi([
  {
    method: 'get',
    path: '/v1/stores/:store_id',
    requestFormat: 'json',
    response: variables.d,
  },
  {
    method: 'get',
    path: '/v2/customer/:customer_id',
    requestFormat: 'json',
    response: variables.d,
  },
]);
// no baseUrl provided since it's only known at runtime
export const api = new Zodios(endpoints);

// ...
const createApiPlugin = (apiId: ApiId) => {
  return {
    name: 'apiPlugin',
    request: async (_endpoint, config) => {
      const state = xxx;
      const baseUrl = xxx[apiId];
      return { ...config, url: `${baseUrl}${config.url}` };
    }
  }
}

but then I get an error because the endpoint is not found in the response

would it be ok to allow overriding a baseUrl in the return of the plugin.request rather than overriding url completely ? i think this would solve my use-case and would not break anything

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.