{"id":26663918,"url":"https://github.com/remscodes/drino","last_synced_at":"2025-04-11T19:41:52.173Z","repository":{"id":184893274,"uuid":"672637609","full_name":"remscodes/drino","owner":"remscodes","description":"🌐 Flexible and Reactive HTTP Client","archived":false,"fork":false,"pushed_at":"2025-03-17T17:07:20.000Z","size":5383,"stargazers_count":4,"open_issues_count":5,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-06T11:02:15.955Z","etag":null,"topics":["drino","fetch","http-client","interceptors","javascript","react-native","request","typescript"],"latest_commit_sha":null,"homepage":"https://npm.im/drino","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/remscodes.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-30T18:52:18.000Z","updated_at":"2024-12-13T16:59:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"61fbb8d3-e493-499c-b6ce-8d5a5c63b6c4","html_url":"https://github.com/remscodes/drino","commit_stats":null,"previous_names":["remscodes/drino"],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remscodes%2Fdrino","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remscodes%2Fdrino/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remscodes%2Fdrino/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/remscodes%2Fdrino/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/remscodes","download_url":"https://codeload.github.com/remscodes/drino/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248468422,"owners_count":21108810,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["drino","fetch","http-client","interceptors","javascript","react-native","request","typescript"],"created_at":"2025-03-25T15:33:32.681Z","updated_at":"2025-04-11T19:41:52.151Z","avatar_url":"https://github.com/remscodes.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n    \u003ch1\u003eDrino\u003c/h1\u003e\n    \u003cp\u003eFlexible and Reactive HTTP Client\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![github ci](https://img.shields.io/github/actions/workflow/status/remscodes/drino/npm-ci.yml.svg?\u0026logo=github\u0026label=CI\u0026style=for-the-badge)](https://github.com/remscodes/drino/actions/workflows/npm-ci.yml)\n[![codecov coverage](https://img.shields.io/codecov/c/github/remscodes/drino/main.svg?style=for-the-badge\u0026logo=codecov)](https://codecov.io/gh/remscodes/drino)\n[![npm version](https://img.shields.io/npm/v/drino.svg?\u0026style=for-the-badge\u0026logo=npm)](https://www.npmjs.org/package/drino)\n[![bundle size](https://img.shields.io/bundlephobia/minzip/drino.svg?style=for-the-badge)](https://bundlephobia.com/package/drino)\n[![license](https://img.shields.io/github/license/remscodes/drino.svg?style=for-the-badge)](LICENSE)\n\n\u003c/div\u003e\n\n## Installation\n\n```shell\nnpm install drino\n```\n\n## Table of contents\n\n- [Basic Usage](#basic-usage)\n    - [Example](#example)\n    - [Request Methods](#request-methods)\n    - [Request Config](#request-config)\n    - [Instance](#instance)\n    - [Plugin](#plugin)\n- [Advanced Usage](#advanced-usage)\n    - [Interceptors](#interceptors)\n    - [Progress Capturing](#progress-capturing)\n    - [Pipe Methods](#pipe-methods)\n    - [Request Annulation](#request-annulation)\n    - [Request Retry](#request-retry)\n- [React Native support](#react-native-support)\n\n## Basic Usage\n\n### Example\n\n```ts\nimport drino from 'drino';\n\n// With Observer's callbacks\ndrino.get('/cat/meow').consume({\n  result: (res) =\u003e {\n    // handle result \n  },\n  error: (err) =\u003e {\n    // handle error\n  },\n  finish: () =\u003e {\n    // after result or error\n  }\n});\n\n// With Promise async/await\nasync function getCatInfo() {\n  try {\n    const res = await drino.get('/cat/meow').consume();\n    // handle result \n  }\n  catch (err) {\n    // handle error\n  }\n  finally {\n    // after result or error\n  }\n}\n```\n\n### Request Methods\n\ndrino.get(url, config?)\n\ndrino.head(url, config?)\n\ndrino.delete(url, config?)\n\ndrino.post(url, body, config?)\n\ndrino.put(url, body, config?)\n\ndrino.patch(url, body, config?)\n\n### Request Config\n\n```ts\ninterface RequestConfig {\n  // Prefix URL\n  // Example : 'https://example.com' OR '/api'\n  prefix?: string;\n\n  // HTTP Headers\n  headers?: Headers | Record\u003cstring, any\u003e;\n\n  // HTTP Parameters\n  queryParams?: URLSearchParams | Record\u003cstring, any\u003e;\n\n  // Response type that will be passed into :\n  // - `result` callback when using Observer\n  // - `then` callback when using Promise\n  // \n  // If 'auto' is specified, the response type will be inferred from \"content-type\" response header.\n  // \n  // default: 'auto'\n  read?: 'auto' | 'object' | 'string' | 'blob' | 'arrayBuffer' | 'formData' | 'none';\n\n  // Wrap response body into a specific Object.\n  // - 'response' : HttpResponse\n  // - 'none' : nothing\n  //\n  // default: 'none'\n  wrapper?: 'none' | 'response';\n\n  // AbortSignal to cancel HTTP Request with an AbortController.\n  // See below in section 'Abort Request'.\n  signal?: AbortSignal;\n\n  // Time limit from which the request is aborted.\n  //\n  // default: 0 (= meaning disabled)\n  //\n  // See below in section 'Timeout'.\n  timeoutMs?: number;\n\n  // Retry a failed request a certain number of times on a specific http status.\n  // See below in section 'Request retry'.\n  retry?: RetryConfig;\n\n  // Config to inspect download progress.\n  // See below in section 'Progress capturing'.\n  progress?: ProgressConfig;\n}\n```\n\n### Instance\n\nInstance can be created to embded common configuration to all requests produced from this instance.\n\n```ts\nimport drino from 'drino';\n\nconst instance = drino.create({\n  baseUrl: 'http://localhost:8080'\n});\n\ninstance.get('/cat/meow').consume() // GET -\u003e http://localhost:8080/cat/meow\n```\n\nYou can create another instance from a parent instance to inherit its config by using `child` method :\n\n```ts\nconst child = instance.child({\n  requestsConfig: {\n    prefix: '/cat'\n  }\n});\n\nchild.get('/meow').consume() // GET -\u003e http://localhost:8080/cat/meow\n```\n\n#### Instance Config\n\n```ts\ninterface DrinoDefaultConfig {\n  // Base URL\n  // Example : 'https://example.com/v1/api'\n  //\n  // default: 'http://localhost'\n  baseUrl?: string | URL;\n\n  // Interceptors in order to take action during http request lifecyle.\n  //\n  // See below in section 'Interceptors'\n  interceptors?: {\n    beforeConsume?: (req: HttpRequest) =\u003e void;\n    afterConsume?: (req: HttpRequest) =\u003e void;\n    beforeResult?: (res: any) =\u003e void;\n    beforeError?: (errRes: HttpErrorResponse) =\u003e void;\n    beforeFinish?: () =\u003e void;\n  };\n\n  // Default requestConfig applied to all requests hosted by the instance\n  // See above in section 'Request Config'\n  requestsConfig?: RequestConfig;\n}\n```\n\nYou can override config applied to a `drino` instance (default import or created instance).\n\n```ts\ndrino.default.baseUrl = 'https://example.com';\ndrino.default.requestsConfig.headers.set('Custom-Header', 'Cat');\n\ndrino.get('/cat/meow').consume(); // GET -\u003e https://example.com/cat/meow (headers = { \"Custom-Header\", \"Cat\" })\n```\n\n### Plugin\n\nYou can use third-party plugin to add more features.\n\n```ts\ndrino.use(myPlugin);\n```\n\nPlugin example : [drino-rx](https://github.com/remscodes/drino-rx)\n\n## Advanced Usage\n\n### Interceptors\n\nYou can intercept request, result or error throughout the http request lifecycle.\n\nInterceptors can be passed into instance config .\n\n```ts\nconst instance = drino.create({\n  interceptors: {\n    // ...\n  }\n});\n```\n\n#### Before consume\n\nIntercept a `HttpRequest` before the request is launched.\n\nExample :\n\n```ts\nconst instance = drino.create({\n  interceptors: {\n    beforeConsume: (req) =\u003e {\n      const token = myService.getToken();\n      req.headers.set('Authorization', `Bearer ${token}`);\n    }\n  }\n});\n```\n\n#### After consume\n\nIntercept a `HttpRequest` just after the response has been received.\n\nExample :\n\n```ts\nconst instance = drino.create({\n  interceptors: {\n    afterConsume: (req) =\u003e {\n      console.info(`Response received from ${req.url}`);\n    }\n  }\n});\n```\n\n#### Before result\n\nIntercept a result before being passed into `result` callback (Observer) or into `then()` arg callback (Promise).\n\nExample :\n\n```ts\nconst instance = drino.create({\n  interceptors: {\n    beforeResult: (res) =\u003e {\n      console.info(`Result : ${res}`);\n    }\n  }\n});\n```\n\n#### Before error\n\nIntercept an error before being passed into `error` callback (Observer) or into `catch()` arg callback (Promise).\n\nExample :\n\n```ts\nconst instance = drino.create({\n  interceptors: {\n    beforeError: (errorResponse) =\u003e {\n      if (errorResponse.status === 401) {\n        myService.clearToken();\n        myService.navigateToLogin();\n      }\n      else {\n        console.error(`Error ${errorResponse.status} from ${errorResponse.url} : ${errorResponse.error}`);\n      }\n    }\n  }\n});\n```\n\n#### Before finish\n\nIntercept before being passed into `finish` callback (Observer) or into `finally()` arg callback (Promise).\n\nExample :\n\n```ts\nconst instance = drino.create({\n  interceptors: {\n    beforeFinish: () =\u003e {\n      console.info('Finished');\n    }\n  }\n});\n```\n\n### Progress Capturing\n\n#### Download\n\nYou can inspect download progress with `downloadProgress` observer's callback.\n\nProgress capturing can be disabled for the instance or for the request by set `inspect: false` into ProgressConfig in\nRequestConfig.\n\n```ts\ninterface ProgressConfig {\n  download?: {\n    // Enable download progress.\n    //\n    // default : true\n    inspect?: boolean;\n  };\n}\n```\n\nA `StreamProgressEvent` is passed to `downloadProgress` callback for each progress iteration.\n\n```ts\nexport interface StreamProgressEvent {\n  // Total bytes to be received or to be sent;\n  total: number;\n\n  // Total bytes received or sent.\n  loaded: number;\n\n  // Current percentage received or sent.\n  // Between 0 and 1.\n  percent: number;\n\n  // Current speed in bytes/ms.\n  // Equals to `0` for the first `iteration`.\n  speed: number;\n\n  // Estimated remaining time in milliseconds to complete the progress.\n  // Equals to `0` for the first `iteration`.\n  remainingMs: number;\n\n  // Current chunk received or sent.\n  chunk: Uint8Array;\n\n  // Current iteration number of the progress.\n  iteration: number;\n}\n```\n\nExample :\n\n```ts\ndrino.get('/cat/image').consume({\n  download: ({ loaded, total, percent, speed, remainingTimeMs }) =\u003e {\n    const remainingSeconds = remainingTimeMs / 1000;\n    const speedKBs = speed / 1024 * 1000;\n\n    console.info(`Received ${loaded} of ${total} bytes (${Math.floor(percent * 100)} %).`);\n    console.info(`Speed ${speedKBs.toFixed(1)} KB/s | ${remainingSeconds.toFixed(2)} seconds remaining.`);\n\n    if (loaded === total) console.info('Download completed.');\n  },\n  result: (res) =\u003e {\n    // handle result\n  }\n});\n```\n\n### Pipe Methods\n\nBefore calling `consume()` method, you can chain call methods to modify or inspect the current value before being passed\ninto final callbacks.\n\n#### Transform\n\nChange the result value.\n\nExample :\n\n```ts\ndrino.get('/cat/meow')\n  .transform((res) =\u003e res.name)\n  .consume({\n    result: (name) =\u003e {\n      // handle value\n    },\n  });\n```\n\n#### Check\n\nRead the result value without changing it.\n\nExample :\n\n```ts\ndrino.get('/cat/meow')\n  .check((res) =\u003e console.log(res)) // { name: \"Gaïa\" }\n  .consume({\n    result: (res) =\u003e {\n      // handle value\n    }\n  });\n```\n\n#### Report\n\nRead the error value without changing it.\n\nExample :\n\n```ts\ndrino.get('/cat/meow')\n  .report((err) =\u003e console.error(err.name)) // \"ErrorName\" \n  .consume({\n    result: (res) =\u003e {\n      // handle value\n    }\n  });\n```\n\n#### Finalize\n\nFinalize when controller finished.\n\nExample :\n\n```ts\ndrino.get('/cat/meow')\n  .finalize(() =\u003e console.log('Finished')) // \"Finished\"\n  .consume({\n    result: (res) =\u003e {\n      // handle value\n    }\n  });\n```\n\n#### Follow\n\nMake another http request sequentially that depends on previous one.\n\nExample :\n\n```ts\ndrino.get('/cat/meow')\n  .follow((cat) =\u003e drino.get(`/dog/wouaf/cat-friend/${cat.name}`))\n  .consume({\n    result: (res) =\u003e {\n      // handle value\n    }\n  });\n```\n\n#### Methods combination\n\nPipe methods can be combined.\n\nExample :\n\n```ts\ndrino.get('/cat/meow')\n  .check((cat) =\u003e console.log(cat)) // { name: \"Gaïa\" }\n  .transform((cat) =\u003e cat.name)\n  .check((name) =\u003e console.log(name)) // \"Gaïa\"\n  .consume({\n    result: (name) =\u003e {\n      // handle value\n    }\n  });\n```\n\n### Request Annulation\n\n#### AbortController\n\nYou can cancel a send request (before receive response) by using `AbortSignal` and `AbortController`.\n\nExample :\n\n```ts\nconst controller = new AbortController();\nconst signal = controller.signal;\n\nsetTimeout(() =\u003e controller.abort('Too Long'), 2_000);\n\n// With Observer\ndrino.get('/cat/meow', { signal }).consume({\n  result: (res) =\u003e {\n    // handle result\n  },\n  abort: (reason) =\u003e {\n    console.error(reason); // \"Too Long\"\n    // handle abort reason\n  }\n});\n\n// With Promise async/await\nasync function getCatInfo() {\n  try {\n    const result = await drino.get('/cat/meow', { signal }).consume();\n    // handle result\n  }\n  catch (err) {\n    if (signal.aborted) {\n      const reason = signal.reason;\n      console.error(reason); // \"Too Long\"\n      // handle abort reason\n    }\n  }\n}\n```\n\n#### Timeout\n\nYou can cancel a send request after a certain time using a `timeoutMs` (timeout in milliseconds).\n\nExample :\n\n```ts\n// With Observer\ndrino.get('/cat/meow', { timeoutMs: 2_000 }).consume({\n  result: (res) =\u003e {\n    // handle result\n  },\n  error: (err) =\u003e {\n    console.error(err.message); // \"The operation timed out.\"\n    // handle timeout error\n  },\n});\n\n// With Promise async/await\nasync function getCatInfo() {\n  try {\n    const res = await drino.get('/cat/meow', { timeoutMs: 2_000 }).consume();\n    // handle result\n  }\n  catch (err) {\n    const message = err.message;\n    console.error(message); // \"The operation timed out.\"\n    // handle timeout error\n  }\n}\n```\n\n### Request Retry\n\nYou can automatically retry failed request on conditions.\n\n```ts\ninterface RetryConfig {\n  // Maximum retries to do on failed request.\n  //\n  // default: 0\n  max: number;\n\n  // Use the \"Retry-After\" response Header to know how much time it waits before retry.\n  //\n  // default: true\n  withRetryAfter?: boolean;\n\n  // Specify the time in millisecond to wait before retry.\n  //\n  // Work only if `withRetryAfter` is `false` or if \"Retry-After\" response header is not present.\n  //\n  // default: 0\n  withDelayMs?: number;\n\n  // HTTP response status code to filter which request should be retried on failure.\n  //\n  // default: [408, 429, 503, 504]\n  onStatus?: number[] | { start: number, end: number } | { start: number, end: number }[];\n\n  // Http method to filter which request should be retried on failure.\n  // Can only be used for instance configuration.\n  // \n  // \"*\" means all methods.\n  //\n  // Example: [\"GET\", \"POST\"]\n  // \n  // default: \"*\"\n  onMethods?: '*' | string[];\n}\n```\n\nExample :\n\n```ts\nconst instance = drino.create({\n  requestsConfig: {\n    retry: { max: 2, onMethods: ['GET'] }\n  }\n});\n\ninstance.get('/my-failed-api', {\n  retry: { max: 1 }\n});\n```\n\nWhen using Observer you can use the `retry` callback to get info about current retry via `RetryEvent`.\n\n```ts\nexport interface RetryEvent {\n  // Current retry count.\n  count: number;\n\n  // Error that causes the retry.\n  error: any;\n\n  // Function to abort retrying.\n  abort: (reason?: any) =\u003e void;\n\n  // Current retry delay.\n  delay: number;\n}\n```\n\nExample :\n\n```ts\ninstance.get('/my-failed-api').consume({\n  retry: ({ count, error, abort }) =\u003e {\n    console.log(`Will retry for the ${count} time caused by the error : ${error}.`);\n    if (count \u003e 2) abort('Too many retries.');\n  },\n  abort: (reason) =\u003e {\n    console.log(reason); // \"Too many retries.\"\n  }\n});\n```\n\n## React Native support\n\nInstall `react-native-url-polyfill` and add the following line at the top of your `index.js` file :\n\n```js\nimport 'react-native-url-polyfill/auto';\n```\n\n## License\n\n[MIT](LICENSE) © Rémy Abitbol.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fremscodes%2Fdrino","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fremscodes%2Fdrino","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fremscodes%2Fdrino/lists"}