diff --git a/.prettierrc.json b/.prettierrc.json deleted file mode 100644 index 18edf11..0000000 --- a/.prettierrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "arrowParens": "avoid", - "bracketSpacing": true, - "bracketSameLine": false, - "jsxSingleQuote": true, - "printWidth": 100, - "semi": true, - "singleQuote": true, - "tabWidth": 2, - "trailingComma": "all" -} diff --git a/README.md b/README.md index 59cca7d..b1f5014 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Fetch tools -Set of utilities for JS [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) function. +Set of utilities for JS +[fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) function. ## Goals @@ -26,21 +27,19 @@ bun add @krutoo/fetch-tools Creating fetch with some extra features. ```ts -import { configureFetch, applyMiddleware } from '@krutoo/fetch-tools'; -import { validateStatus, defaultHeaders, log } from '@krutoo/fetch-tools/middleware'; +import { applyMiddleware, configureFetch } from '@krutoo/fetch-tools'; +import { defaultHeaders, log, validateStatus } from '@krutoo/fetch-tools/middleware'; // configure your own fetch... const myFetch = configureFetch( fetch, applyMiddleware( // validate status (like in axios) - validateStatus(status => status >= 200 && status < 300), - + validateStatus((status) => status >= 200 && status < 300), // add default headers defaultHeaders({ 'user-agent': 'test', }), - // log request stages (before request, after response, on catch) log({ onCatch: ({ error }) => console.error(error), @@ -50,8 +49,8 @@ const myFetch = configureFetch( // ...and using it like normal fetch myFetch('posts/1') - .then(res => res.json()) - .then(data => console.log(data)); + .then((res) => res.json()) + .then((data) => console.log(data)); ``` ## Middleware @@ -83,14 +82,14 @@ async function myMiddleware(request, next) { Returns a middleware that will validate status. ```ts -import { configureFetch, applyMiddleware } from '@krutoo/fetch-tools'; +import { applyMiddleware, configureFetch } from '@krutoo/fetch-tools'; import { validateStatus } from '@krutoo/fetch-tools/middleware'; const myFetch = configureFetch( fetch, applyMiddleware( // fetch promise will be rejected when status is not valid - validateStatus(status => status >= 200 && status < 300), + validateStatus((status) => status >= 200 && status < 300), ), ); ``` @@ -100,7 +99,7 @@ const myFetch = configureFetch( Returns a middleware that will set default headers to request. ```ts -import { configureFetch, applyMiddleware } from '@krutoo/fetch-tools'; +import { applyMiddleware, configureFetch } from '@krutoo/fetch-tools'; import { defaultHeaders } from '@krutoo/fetch-tools/middleware'; const myFetch = configureFetch( @@ -117,7 +116,7 @@ const myFetch = configureFetch( Returns a middleware that will log phases by handler. ```ts -import { configureFetch, applyMiddleware } from '@krutoo/fetch-tools'; +import { applyMiddleware, configureFetch } from '@krutoo/fetch-tools'; import { log } from '@krutoo/fetch-tools/middleware'; const myFetch = configureFetch( @@ -160,7 +159,7 @@ const enhance = applyMiddleware( ); Deno.serve( - enhance(req => { + enhance((req) => { return new Response('

Main page

'); }), ); @@ -212,7 +211,8 @@ Bun.serve({ Currently there is no builtin server implementation based on Fetch API in Node.js. -It is possible to use _adapter_ for `node:http` or `express` from [@whatwg-node/server](https://www.npmjs.com/package/@whatwg-node/server). +It is possible to use _adapter_ for `node:http` or `express` from +[@whatwg-node/server](https://www.npmjs.com/package/@whatwg-node/server). ```ts import { router } from '@krutoo/fetch-tools'; @@ -237,7 +237,7 @@ server.listen(8080); You can use middleware for server handlers too: ```ts -import { router, applyMiddleware } from '@krutoo/fetch-tools'; +import { applyMiddleware, router } from '@krutoo/fetch-tools'; import { log } from '@krutoo/fetch-tools/middleware'; const enhance = applyMiddleware( @@ -266,13 +266,17 @@ Cookies can be used in different ways on the server. ### Browser like behavior -If you want to imitate browser behavior as much as possible in terms of working with cookies, you can use `@krutoo/fetch-tools` together with `fetch-cookie`. +If you want to imitate browser behavior as much as possible in terms of working with cookies, you +can use `@krutoo/fetch-tools` together with `fetch-cookie`. -To use **fetch-cookie** as an middleware, follow [these](https://github.com/valeriangalliat/fetch-cookie/issues/79#issuecomment-1672188226) instructions. +To use **fetch-cookie** as an middleware, follow +[these](https://github.com/valeriangalliat/fetch-cookie/issues/79#issuecomment-1672188226) +instructions. ### Microfrontends -Server part of the microfrontend can make requests to some HTTP API on behalf of the user, sending his cookies in requests. +Server part of the microfrontend can make requests to some HTTP API on behalf of the user, sending +his cookies in requests. In this case you can use just `defaultHeaders` middleware: diff --git a/deno.json b/deno.json index d99b3d2..efc1274 100644 --- a/deno.json +++ b/deno.json @@ -2,7 +2,7 @@ "name": "@krutoo/fetch-tools", "version": "0.0.0", "tasks": { - "lint": "deno lint && deno check src/**/*.ts", + "lint": "deno check src/**/*.ts && deno lint && deno fmt --check", "build-npm": "deno run -A scripts/build-npm.ts" }, "imports": { @@ -21,7 +21,7 @@ "indentWidth": 2, "semiColons": true, "singleQuote": true, - "exclude": ["npm/**/*", "example/**/*"] + "exclude": ["npm/**/*"] }, "lint": { "exclude": ["npm/**/*"] diff --git a/src/configure.ts b/src/configure.ts index 13a51ba..dc1c7f4 100644 --- a/src/configure.ts +++ b/src/configure.ts @@ -1,4 +1,4 @@ -import type { Handler, Enhancer, Middleware } from './types.ts'; +import type { Enhancer, Handler, Middleware } from './types.ts'; /** * Enhance fetch function by provided enhancer. @@ -12,7 +12,7 @@ export function configureFetch( fetchFn: T, enhance?: Enhancer, ): typeof fetch { - let inner: Handler = request => fetchFn(request); + let inner: Handler = (request) => fetchFn(request); if (enhance) { inner = enhance(inner); @@ -28,15 +28,15 @@ export function configureFetch( */ export function applyMiddleware(...list: Array): Enhancer { if (list.length === 0) { - return handler => handler; + return (handler) => handler; } - return handler => { + return (handler) => { let result = handler; for (const item of list.reverse()) { const next = result; - result = request => Promise.resolve(item(request, next)); + result = (request) => Promise.resolve(item(request, next)); } return result; diff --git a/src/middleware/default-headers.ts b/src/middleware/default-headers.ts index df40f5d..0b733e6 100644 --- a/src/middleware/default-headers.ts +++ b/src/middleware/default-headers.ts @@ -1,18 +1,31 @@ import type { Middleware } from '../types.ts'; +/** Options of default headers middleware. */ +export interface DefaultHeadersOptions { + /** + * How to add header to request headers. + * - "set" - headers will be added using "set" method + * - "append" - headers will be added using "append" method + */ + strategy?: 'set' | 'append'; +} + /** * Returns a middleware that will set default headers to request. * @param defaults Default headers. * @return Middleware. */ -export function defaultHeaders(defaults: HeadersInit): Middleware { +export function defaultHeaders( + defaults: HeadersInit, + { strategy = 'append' }: DefaultHeadersOptions = {}, +): Middleware { return (request, next) => { // IMPORTANT: for avoid mutate request, just create new Headers and Request here const headers = new Headers(defaults); if (request.headers) { new Headers(request.headers).forEach((value, key) => { - headers.append(key, value); + headers[strategy](key, value); }); } diff --git a/src/middleware/jwt.ts b/src/middleware/jwt.ts new file mode 100644 index 0000000..0711bb7 --- /dev/null +++ b/src/middleware/jwt.ts @@ -0,0 +1,35 @@ +import type { Middleware } from '../types.ts'; + +/** Options of JWT middleware. */ +export interface JwtMiddlewareOptions { + /** JWT Token. */ + token: string | (() => string | Promise); + + /** Filter. Takes request, should return boolean. When returns false, JWT payload will not be added to request. */ + filter?: (request: Request) => boolean; +} + +/** + * Simple JWT middleware. Will add "Authorization" header with JWT token to request. + * @param options Options. + * @returns Middleware. + */ +export function jwt({ + token, + filter = () => true, +}: JwtMiddlewareOptions): Middleware { + const getToken = typeof token === 'function' ? token : () => token; + + return async (request, next) => { + if (!filter(request)) { + return next(request); + } + + // IMPORTANT: for avoid mutate request, just create new Headers and Request here + const headers = new Headers(request.headers); + + headers.set('Authorization', `Bearer ${await getToken()}`); + + return next(new Request(request, { headers })); + }; +} diff --git a/src/middleware/mod.ts b/src/middleware/mod.ts index 10cc4df..35445b9 100644 --- a/src/middleware/mod.ts +++ b/src/middleware/mod.ts @@ -1,12 +1,18 @@ -export { defaultHeaders } from './default-headers.ts'; +export { defaultHeaders, type DefaultHeadersOptions } from './default-headers.ts'; + export { - log, - type LogData, type DoneLogData, type FailLogData, + log, + type LogData, type LogHandler, type LogHandlerFactory, } from './log.ts'; + export { retry } from './retry.ts'; + export { validateStatus, type ValidateStatusOptions } from './validate-status.ts'; + export { proxy, type ProxyOptions, type ProxyRequestFilter } from './proxy.ts'; + +export { jwt, type JwtMiddlewareOptions } from './jwt.ts'; diff --git a/src/middleware/proxy.ts b/src/middleware/proxy.ts index 6ed7e85..2afd4f8 100644 --- a/src/middleware/proxy.ts +++ b/src/middleware/proxy.ts @@ -19,7 +19,7 @@ export interface ProxyOptions { * Simple proxy middleware for servers based on Web Fetch API. * Based on good article: https://blog.r0b.io/post/creating-a-proxy-with-deno/ */ -export function proxy({ filter, target, pathRewrite = p => p }: ProxyOptions): Middleware { +export function proxy({ filter, target, pathRewrite = (p) => p }: ProxyOptions): Middleware { const matches = createMatches(filter); const createRequest = (url: URL, request: Request) => { @@ -51,10 +51,10 @@ export function proxy({ filter, target, pathRewrite = p => p }: ProxyOptions): M function createMatches(filter: ProxyOptions['filter']): ProxyRequestFilter { switch (true) { case Array.isArray(filter): - return url => filter.some(item => url.pathname.startsWith(item)); + return (url) => filter.some((item) => url.pathname.startsWith(item)); case typeof filter === 'string': - return url => url.pathname.startsWith(filter); + return (url) => url.pathname.startsWith(filter); case typeof filter === 'function': return filter; diff --git a/src/middleware/validate-status.ts b/src/middleware/validate-status.ts index 3f04c57..2099fc7 100644 --- a/src/middleware/validate-status.ts +++ b/src/middleware/validate-status.ts @@ -18,7 +18,7 @@ export interface ValidateStatusOptions { export function validateStatus( validate: (status: number, request: Request, response: Response) => boolean, { - getThrowable = response => new Error(`Request failed with status ${response.status}`), + getThrowable = (response) => new Error(`Request failed with status ${response.status}`), needDump = true, }: ValidateStatusOptions = {}, ): Middleware { diff --git a/src/mod.ts b/src/mod.ts index 0106f04..1f4d8d0 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -1,5 +1,5 @@ -export type { Handler, Enhancer, Middleware } from './types.ts'; -export { configureFetch, applyMiddleware } from './configure.ts'; +export type { Enhancer, Handler, Middleware } from './types.ts'; +export { applyMiddleware, configureFetch } from './configure.ts'; export { html, json } from './response.ts'; -export { router, route } from './server.ts'; +export { route, router } from './server.ts'; export { dump } from './utils/dump.ts';