├── .gitignore ├── LICENSE ├── README.md ├── build.config.ts ├── package.json ├── playground ├── app.vue └── nuxt.config.ts ├── src ├── module.ts ├── runtime │ ├── composables.ts │ ├── http-plugin.nitro.ts │ └── http-plugin.nuxt.ts └── types │ └── index.d.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_STORE 8 | coverage 9 | dist 10 | package-lock.json 11 | temp 12 | tsdoc-metadata.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 nuxt-alt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Information 2 | 3 | This serves as an extension to ohmyfetch for nuxt. This works similar to `nuxt/http` and `nuxtjs-alt/axios` except it utilizes ohmyfetch. All property options will be under `http`. This module is required in order for `@nuxt-alt/auth` to function. 4 | 5 | ## Setup 6 | 7 | 1. Add `@nuxt-alt/http` dependency to your project 8 | 9 | ```bash 10 | yarn add @nuxt-alt/http 11 | ``` 12 | 13 | 2. Add `@nuxt-alt/http` to the `modules` section of `nuxt.config.ts` 14 | 15 | ```ts 16 | export default defineNuxtConfig({ 17 | modules: [ 18 | '@nuxt-alt/http' 19 | ], 20 | http: { 21 | /* module options */ 22 | } 23 | }); 24 | 25 | ``` 26 | 27 | ## Example 28 | 29 | Remember this is a mix of `ofetch` and `nuxt/http` so to use methods you would use as an example: 30 | 31 | ```ts 32 | // Available methods: 'get', 'head', 'delete', 'post', 'put', 'patch', 'options' 33 | 34 | // $http.$get('/api', options) and $http.$get({ url: '/api' }) is the same as $fetch('/api', { method: 'get' }) 35 | await $http.$get('/api', options) 36 | await $http.$get({ url: '/api', ...options }) 37 | 38 | // Access Raw Response 39 | // $http.get('/api', options) and $http.get({ url: '/api' }) is the same as $fetch.raw('/api', { method: 'get' }) 40 | await $http.get('/api', options) 41 | await $http.get({ url: '/api', ...options }) 42 | 43 | // $http.request('/api', options) and $http.request({ url: '/api' }) is the same as $fetch('/api', options) 44 | await $http.request({ url: '/api', ...options }) 45 | await $http.request('/api', options) 46 | 47 | // Access Raw Response 48 | // $http.raw('/api', options) and $http.raw({ url: '/api' }) is the same as $fetch.raw('/api', options) 49 | await $http.raw({ url: '/api', ...options }) 50 | await $http.raw('/api', options) 51 | 52 | // Access Fetch Native Response 53 | // $http.natvie('/api', options) and $http.native({ url: '/api' }) is the same as $fetch.native('/api', options) or fetch('/api', options) 54 | await $http.native({ url: '/api', ...options }) 55 | await $http.native('/api', options) 56 | ``` 57 | 58 | ## Composable 59 | 60 | A `useHttp` composable is avaialble, it works like `useFetch` except uses this module under the hood. 61 | 62 | ## Interceptors 63 | 64 | The interceptors should work exactly like how axios has it so to access them you would use: 65 | 66 | ```ts 67 | $http.interceptors.request.use(config) 68 | $http.interceptors.response.use(response) 69 | 70 | ``` 71 | 72 | A `interceptorPlugin` property has been added. This relies on the proxy module being present and will proxy urls based on the target for the client. 73 | 74 | @nuxtjs-axios based functions have also been added: 75 | 76 | ```ts 77 | $http.onRequest(config) 78 | $http.onResponse(response) 79 | $http.onRequestError(err) 80 | $http.onResponseError(err) 81 | $http.onError(err) 82 | ``` 83 | 84 | ## Options 85 | 86 | Config options are taken from the [http module](https://http.nuxtjs.org/). In addition an additional properyul has been added. -------------------------------------------------------------------------------- /build.config.ts: -------------------------------------------------------------------------------- 1 | import type { NuxtModule } from '@nuxt/schema' 2 | import { existsSync, promises as fsp } from 'node:fs' 3 | import { defineBuildConfig } from "unbuild" 4 | import { pathToFileURL } from 'node:url' 5 | import { resolve } from 'pathe' 6 | import mri from 'mri' 7 | 8 | const args = mri(process.argv.slice(2)) 9 | 10 | export default defineBuildConfig({ 11 | failOnWarn: false, 12 | declaration: true, 13 | stub: args.stub, 14 | entries: [ 15 | 'src/module', 16 | // @ts-ignore 17 | { input: 'src/types/', outDir: 'dist/types', ext: 'd.ts' }, 18 | { input: 'src/runtime/', outDir: 'dist/runtime', ext: 'mjs' }, 19 | ], 20 | rollup: { 21 | emitCJS: false, 22 | cjsBridge: true, 23 | }, 24 | externals: [ 25 | '@nuxt/schema', 26 | '@nuxt/schema-edge', 27 | '@nuxt/kit', 28 | '@nuxt/kit-edge', 29 | 'nuxt', 30 | 'nuxt-edge', 31 | 'nuxt3', 32 | 'vue', 33 | 'vue-demi', 34 | '@nuxtjs-alt/proxy' 35 | ], 36 | hooks: { 37 | async 'rollup:done' (ctx) { 38 | // Generate CommonJS stup 39 | await writeCJSStub(ctx.options.outDir) 40 | 41 | // Load module meta 42 | const moduleEntryPath = resolve(ctx.options.outDir, 'module.mjs') 43 | const moduleFn: NuxtModule = await import( 44 | pathToFileURL(moduleEntryPath).toString() 45 | ).then(r => r.default || r).catch((err) => { 46 | console.error(err) 47 | console.error('Cannot load module. Please check dist:', moduleEntryPath) 48 | return null 49 | }) 50 | if (!moduleFn) { 51 | return 52 | } 53 | const moduleMeta = await moduleFn.getMeta!() 54 | 55 | // Enhance meta using package.json 56 | if (ctx.pkg) { 57 | if (!moduleMeta.name) { 58 | moduleMeta.name = ctx.pkg.name 59 | } 60 | if (!moduleMeta.version) { 61 | moduleMeta.version = ctx.pkg.version 62 | } 63 | } 64 | 65 | // Write meta 66 | const metaFile = resolve(ctx.options.outDir, 'module.json') 67 | await fsp.writeFile(metaFile, JSON.stringify(moduleMeta, null, 2), 'utf8') 68 | } 69 | } 70 | }); 71 | 72 | async function writeCJSStub (distDir: string) { 73 | const cjsStubFile = resolve(distDir, 'module.cjs') 74 | if (existsSync(cjsStubFile)) { 75 | return 76 | } 77 | 78 | const cjsStub = 79 | `module.exports = function(...args) { 80 | return import('./module.mjs').then(m => m.default.call(this, ...args)) 81 | } 82 | const _meta = module.exports.meta = require('./module.json') 83 | module.exports.getMeta = () => Promise.resolve(_meta)` 84 | 85 | await fsp.writeFile(cjsStubFile, cjsStub, 'utf8') 86 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxt-alt/http", 3 | "version": "1.7.8", 4 | "description": "An extended module to ohmyfetch", 5 | "homepage": "https://github.com/nuxt-alt/http", 6 | "author": "Teranode", 7 | "keywords": [ 8 | "nuxt", 9 | "nuxtjs", 10 | "nuxt-module", 11 | "nuxt-plugin", 12 | "nuxt-module-alternatives", 13 | "ohmyfetch", 14 | "@nuxtjs/http" 15 | ], 16 | "license": "MIT", 17 | "type": "module", 18 | "sideEffects": false, 19 | "main": "./dist/module.cjs", 20 | "module": "./dist/module.mjs", 21 | "types": "./dist/types/index.d.ts", 22 | "files": [ 23 | "dist" 24 | ], 25 | "scripts": { 26 | "dev": "nuxi dev playground", 27 | "dev:build": "nuxi build playground", 28 | "dev:prepare": "unbuild --stub && nuxi prepare playground", 29 | "prepack": "unbuild" 30 | }, 31 | "dependencies": { 32 | "@nuxt/kit": "^3.8.1", 33 | "@nuxt/module-builder": "^0.5.4", 34 | "@refactorjs/ofetch": "latest", 35 | "defu": "^6.1.3", 36 | "ufo": "^1.3.2" 37 | }, 38 | "devDependencies": { 39 | "@types/node": "^18", 40 | "nuxt": "^3.9.1", 41 | "typescript": "^5.2.2", 42 | "unbuild": "^2.0.0" 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "git+https://github.com/nuxt-alt/http.git", 47 | "directory": "@nuxt-alt/http" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/nuxt-alt/http/issues" 51 | }, 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "packageManager": "yarn@3.3.1" 56 | } 57 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import httpModule from '..' 2 | import { defineNuxtConfig } from 'nuxt/config' 3 | 4 | export default defineNuxtConfig({ 5 | modules: [ 6 | httpModule, 7 | ], 8 | http: { 9 | interceptorPlugin: true 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleOptions } from './types' 2 | import { addTemplate, defineNuxtModule, addPlugin, createResolver, addImports } from '@nuxt/kit' 3 | import { name, version } from '../package.json' 4 | import { withHttps } from 'ufo' 5 | import { defu } from 'defu' 6 | 7 | const CONFIG_KEY = 'http' 8 | 9 | export default defineNuxtModule({ 10 | meta: { 11 | name, 12 | version, 13 | configKey: CONFIG_KEY, 14 | }, 15 | defaults: {} as ModuleOptions, 16 | async setup(opts, nuxt) { 17 | // Combine options with runtime config 18 | const moduleOptions: ModuleOptions = opts 19 | 20 | // Default host 21 | const defaultHost = moduleOptions.host || process.env.NITRO_HOST || process.env.HOST || 'localhost' 22 | 23 | // Default port 24 | const defaultPort = moduleOptions.port || process.env.NITRO_PORT || process.env.PORT || 3000 25 | 26 | // Default prefix 27 | const prefix = moduleOptions.prefix || process.env.PREFIX || '/' 28 | 29 | // Apply defaults 30 | const options = defu(moduleOptions, { 31 | baseURL: `http://${defaultHost}:${defaultPort}${prefix}`, 32 | browserBaseURL: undefined, 33 | proxyHeaders: true, 34 | proxyHeadersIgnore: [ 35 | 'accept', 36 | 'connection', 37 | 'cf-connecting-ip', 38 | 'cf-ray', 39 | 'content-length', 40 | 'content-md5', 41 | 'content-type', 42 | 'host', 43 | 'if-modified-since', 44 | 'if-none-match', 45 | 'x-forwarded-host', 46 | 'x-forwarded-port', 47 | 'x-forwarded-proto' 48 | ], 49 | serverTimeout: 10000, 50 | clientTimeout: 25000, 51 | https: false, 52 | retry: 1, 53 | headers: { 54 | accept: 'application/json, text/plain, */*' 55 | }, 56 | credentials: 'same-origin', 57 | debug: false 58 | }) 59 | 60 | if (typeof options.browserBaseURL === 'undefined') { 61 | options.browserBaseURL = nuxt.options.app.baseURL 62 | } 63 | 64 | // Convert http:// to https:// if https option is on 65 | if (options.https === true) { 66 | options.baseURL = withHttps(options.baseURL as string) 67 | options.browserBaseURL = withHttps(options.browserBaseURL) 68 | } 69 | 70 | // resolver 71 | const resolver = createResolver(import.meta.url) 72 | 73 | const runtimeDir = resolver.resolve('./runtime') 74 | nuxt.options.build.transpile.push(runtimeDir) 75 | 76 | // Inject options via virtual template 77 | nuxt.options.alias['#nuxt-http-options'] = addTemplate({ 78 | filename: 'nuxt-http-options.mjs', 79 | write: true, 80 | getContents: () => `export const options = ${JSON.stringify(options, null, 2)}` 81 | }).dst 82 | 83 | nuxt.hook('nitro:config', (config) => { 84 | config.externals = config.externals || {} 85 | config.externals.inline = config.externals.inline || [] 86 | config.externals.inline.push(runtimeDir) 87 | 88 | config.virtual = config.virtual || {} 89 | config.virtual['#nuxt-http-options'] = `export const options = ${JSON.stringify(options, null, 2)}` 90 | config.plugins = config.plugins || [] 91 | config.plugins.push(resolver.resolve(runtimeDir, 'http-plugin.nitro')) 92 | }) 93 | 94 | // Register plugin 95 | addPlugin({ 96 | src: resolver.resolve(resolver.resolve(runtimeDir, 'http-plugin.nuxt')), 97 | }) 98 | 99 | // Add auto imports 100 | const composables = resolver.resolve('runtime/composables') 101 | 102 | addImports([ 103 | { from: composables, name: 'useHttp' }, 104 | { from: composables, name: 'useLazyHttp' }, 105 | { from: composables, name: 'useRequestHttp' } 106 | ]) 107 | } 108 | }) -------------------------------------------------------------------------------- /src/runtime/composables.ts: -------------------------------------------------------------------------------- 1 | import type { FetchConfig } from '@refactorjs/ofetch' 2 | import type { FetchError } from 'ofetch' 3 | import type { TypedInternalResponse, NitroFetchRequest, AvailableRouterMethod as _AvailableRouterMethod } from 'nitropack' 4 | import type { AsyncDataOptions, AsyncData } from '#app' 5 | import { computed, unref, reactive, toValue, type MaybeRef, type WatchSource, type Ref } from 'vue' 6 | import { useAsyncData, useRequestEvent } from '#imports' 7 | import { hash } from 'ohash' 8 | 9 | type PickFrom> = T extends Array ? T : T extends Record ? keyof T extends K[number] ? T : K[number] extends never ? T : Pick : T; 10 | type KeysOf = Array; 11 | 12 | type AvailableRouterMethod = _AvailableRouterMethod | Uppercase<_AvailableRouterMethod> 13 | type MultiWatchSources = (WatchSource | object)[]; 14 | 15 | interface NitroFetchOptions = AvailableRouterMethod> extends Omit { 16 | method?: Uppercase | M; 17 | } 18 | 19 | type ComputedOptions> = { 20 | [K in keyof T]: T[K] extends Function ? T[K] : T[K] extends Record ? ComputedOptions | Ref | T[K] : Ref | T[K] 21 | } 22 | 23 | export type FetchResult> = TypedInternalResponse> 24 | 25 | type ComputedFetchOptions> = ComputedOptions> 26 | 27 | export interface UseHttpOptions< 28 | ResT, 29 | DataT = ResT, 30 | PickKeys extends KeysOf = KeysOf, 31 | DefaultT = null, 32 | R extends NitroFetchRequest = string & {}, 33 | M extends AvailableRouterMethod = AvailableRouterMethod 34 | > extends Omit, 'watch'>, ComputedFetchOptions { 35 | key?: string 36 | $http?: typeof globalThis.$http 37 | watch?: MultiWatchSources | false 38 | } 39 | 40 | export function useHttp< 41 | ResT = void, 42 | ErrorT = FetchError, 43 | ReqT extends NitroFetchRequest = NitroFetchRequest, 44 | Method extends AvailableRouterMethod = ResT extends void ? 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod : AvailableRouterMethod, 45 | _ResT = ResT extends void ? FetchResult : ResT, 46 | DataT = _ResT, 47 | PickKeys extends KeysOf = KeysOf, 48 | DefaultT = null, 49 | >( 50 | request: Ref | ReqT | (() => ReqT), 51 | opts?: UseHttpOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method> 52 | ): AsyncData | DefaultT, ErrorT | null> 53 | export function useHttp< 54 | ResT = void, 55 | ErrorT = FetchError, 56 | ReqT extends NitroFetchRequest = NitroFetchRequest, 57 | Method extends AvailableRouterMethod = ResT extends void ? 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod : AvailableRouterMethod, 58 | _ResT = ResT extends void ? FetchResult : ResT, 59 | DataT = _ResT, 60 | PickKeys extends KeysOf = KeysOf, 61 | DefaultT = DataT, 62 | >( 63 | request: Ref | ReqT | (() => ReqT), 64 | opts?: UseHttpOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method> 65 | ): AsyncData | DefaultT, ErrorT | null> 66 | export function useHttp< 67 | ResT = void, 68 | ErrorT = FetchError, 69 | ReqT extends NitroFetchRequest = NitroFetchRequest, 70 | Method extends AvailableRouterMethod = ResT extends void ? 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod : AvailableRouterMethod, 71 | _ResT = ResT extends void ? FetchResult : ResT, 72 | DataT = _ResT, 73 | PickKeys extends KeysOf = KeysOf, 74 | DefaultT = null, 75 | >( 76 | request: Ref | ReqT | (() => ReqT), 77 | arg1?: string | UseHttpOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, 78 | arg2?: string 79 | ) { 80 | const [opts = {}, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2] 81 | 82 | const _request = computed(() => { 83 | let r = request 84 | if (typeof r === 'function') { 85 | r = r() 86 | } 87 | return unref(r) 88 | }) 89 | 90 | const _key = opts.key || hash([autoKey, typeof _request.value === 'string' ? _request.value : '', ...generateOptionSegments(opts)]) 91 | 92 | if (!_key || typeof _key !== 'string') { 93 | throw new TypeError('[nuxt] [useHttp] key must be a string: ' + _key) 94 | } 95 | 96 | if (!request) { 97 | throw new Error('[nuxt] [useHttp] request is missing.') 98 | } 99 | 100 | const key = _key === autoKey ? '$h' + _key : _key 101 | 102 | if (!opts.baseURL && typeof _request.value === 'string' && (_request.value[0] === '/' && _request.value[1] === '/')) { 103 | throw new Error('[nuxt] [useHttp] the request URL must not start with "//".') 104 | } 105 | 106 | const { 107 | server, 108 | lazy, 109 | default: defaultFn, 110 | transform, 111 | pick, 112 | watch, 113 | immediate, 114 | ...fetchOptions 115 | } = opts 116 | 117 | const _fetchOptions = reactive({ 118 | ...fetchOptions, 119 | cache: typeof opts.cache === 'boolean' ? undefined : opts.cache 120 | }) 121 | 122 | const _asyncDataOptions: AsyncDataOptions<_ResT, DataT, PickKeys, DefaultT> = { 123 | server, 124 | lazy, 125 | default: defaultFn, 126 | transform, 127 | pick, 128 | immediate, 129 | watch: watch === false ? [] : [_fetchOptions, _request, ...(watch || [])] 130 | } 131 | 132 | let _$http = opts.$http || globalThis.$http 133 | 134 | if (import.meta.server && !opts.$http) { 135 | const isLocalFetch = typeof _request.value === 'string' && _request.value[0] === '/' && (!toValue(opts.baseURL) || toValue(opts.baseURL)![0] === '/') 136 | if (isLocalFetch) { 137 | _$http = useRequestEvent()?.$http 138 | } 139 | } 140 | 141 | const asyncData = useAsyncData<_ResT, ErrorT, DataT, PickKeys, DefaultT>(key, () => { 142 | return _$http.request(_request.value, _fetchOptions as FetchConfig) as Promise<_ResT> 143 | }, _asyncDataOptions) 144 | 145 | return asyncData 146 | } 147 | 148 | export function useLazyHttp< 149 | ResT = void, 150 | ErrorT = FetchError, 151 | ReqT extends NitroFetchRequest = NitroFetchRequest, 152 | Method extends AvailableRouterMethod = ResT extends void ? 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod : AvailableRouterMethod, 153 | _ResT = ResT extends void ? FetchResult : ResT, 154 | DataT = _ResT, 155 | PickKeys extends KeysOf = KeysOf, 156 | DefaultT = null, 157 | >( 158 | request: Ref | ReqT | (() => ReqT), 159 | opts?: Omit, 'lazy'> 160 | ): AsyncData | DefaultT, ErrorT | null> 161 | export function useLazyHttp< 162 | ResT = void, 163 | ErrorT = FetchError, 164 | ReqT extends NitroFetchRequest = NitroFetchRequest, 165 | Method extends AvailableRouterMethod = ResT extends void ? 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod : AvailableRouterMethod, 166 | _ResT = ResT extends void ? FetchResult : ResT, 167 | DataT = _ResT, 168 | PickKeys extends KeysOf = KeysOf, 169 | DefaultT = DataT, 170 | >( 171 | request: Ref | ReqT | (() => ReqT), 172 | opts?: Omit, 'lazy'> 173 | ): AsyncData | DefaultT, ErrorT | null> 174 | export function useLazyHttp< 175 | ResT = void, 176 | ErrorT = FetchError, 177 | ReqT extends NitroFetchRequest = NitroFetchRequest, 178 | Method extends AvailableRouterMethod = ResT extends void ? 'get' extends AvailableRouterMethod ? 'get' : AvailableRouterMethod : AvailableRouterMethod, 179 | _ResT = ResT extends void ? FetchResult : ResT, 180 | DataT = _ResT, 181 | PickKeys extends KeysOf = KeysOf, 182 | DefaultT = null, 183 | >( 184 | request: Ref | ReqT | (() => ReqT), 185 | arg1?: string | Omit, 'lazy'>, 186 | arg2?: string 187 | ) { 188 | 189 | const [opts, autoKey] = typeof arg1 === 'string' ? [{}, arg1] : [arg1, arg2] 190 | 191 | return useHttp(request, { 192 | ...opts, 193 | lazy: true 194 | // @ts-expect-error we pass an extra argument with the resolved auto-key to prevent another from being injected 195 | }, autoKey) 196 | } 197 | 198 | function generateOptionSegments<_ResT, DataT, DefaultT>(opts: UseHttpOptions<_ResT, DataT, any, DefaultT, any, any>) { 199 | const segments: Array> = [ 200 | toValue(opts.method as MaybeRef | undefined)?.toUpperCase() || 'GET', 201 | toValue(opts.baseURL), 202 | ] 203 | for (const _obj of [opts.params || opts.query]) { 204 | const obj = toValue(_obj) 205 | if (!obj) { continue } 206 | 207 | const unwrapped: Record = {} 208 | for (const [key, value] of Object.entries(obj)) { 209 | unwrapped[toValue(key)] = toValue(value) 210 | } 211 | segments.push(unwrapped) 212 | } 213 | return segments 214 | } 215 | 216 | export function useRequestHttp(): typeof global.$http { 217 | if (import.meta.client) { 218 | return globalThis.$http 219 | } 220 | 221 | return useRequestEvent()?.$http as typeof globalThis.$http || globalThis.$http 222 | } -------------------------------------------------------------------------------- /src/runtime/http-plugin.nitro.ts: -------------------------------------------------------------------------------- 1 | import type { NitroAppPlugin } from 'nitropack' 2 | import { createInstance, type FetchConfig } from '@refactorjs/ofetch' 3 | import { eventHandler } from 'h3' 4 | // @ts-ignore: virtual file 5 | import { options } from '#nuxt-http-options' 6 | 7 | const httpInstance = (opts: FetchConfig) => { 8 | // Create new Fetch instance 9 | let instance = createInstance(opts) 10 | 11 | if (options.debug) { 12 | type ConsoleKeys = 'log' | 'info' | 'warn' | 'error'; 13 | const log = (level: ConsoleKeys, ...messages: any) => console[level]('[nitro-http]', ...messages); 14 | 15 | // request 16 | instance.onRequest(config => { 17 | log('info', 'Request:', config) 18 | return config 19 | }) 20 | 21 | instance.onRequestError(error => { 22 | log('error', 'Request error:', error) 23 | }) 24 | 25 | // response 26 | instance.onResponse(res => { 27 | log('info', 'Response:', res) 28 | return res 29 | }) 30 | 31 | instance.onResponseError(error => { 32 | log('error', 'Response error:', error) 33 | }) 34 | } 35 | 36 | return instance 37 | } 38 | 39 | export default function (nitroApp) { 40 | // baseURL 41 | const baseURL = options.baseURL 42 | 43 | // Defaults 44 | const defaults = { 45 | baseURL, 46 | retry: options.retry, 47 | timeout: options.serverTimeout, 48 | credentials: options.credentials, 49 | headers: {}, 50 | } 51 | 52 | // @ts-ignore 53 | globalThis.$http = httpInstance(defaults) 54 | 55 | nitroApp.h3App.stack.unshift({ 56 | route: '/', 57 | handler: eventHandler((event) => { 58 | defaults.headers = options.headers 59 | if (options.proxyHeaders) { 60 | // Proxy SSR request headers 61 | if (event.node.req.headers) { 62 | const reqHeaders = { ...event.node.req.headers } 63 | 64 | for (const h of options.proxyHeadersIgnore) { 65 | delete reqHeaders[h] 66 | } 67 | 68 | defaults.headers = { ...reqHeaders, ...defaults.headers } 69 | } 70 | } 71 | 72 | // Assign bound http to context 73 | // @ts-ignore 74 | event.$http = httpInstance(defaults); 75 | }) 76 | }) 77 | } -------------------------------------------------------------------------------- /src/runtime/http-plugin.nuxt.ts: -------------------------------------------------------------------------------- 1 | import { createInstance, type FetchConfig } from '@refactorjs/ofetch'; 2 | import { defineNuxtPlugin, useRuntimeConfig } from '#imports'; 3 | // @ts-expect-error: virtual file 4 | import { options } from '#nuxt-http-options'; 5 | import { defu } from "defu"; 6 | 7 | const httpInstance = (opts: FetchConfig) => { 8 | // Create new Fetch instance 9 | let instance = createInstance(opts) 10 | 11 | if (options.debug) { 12 | type ConsoleKeys = 'log' | 'info' | 'warn' | 'error'; 13 | const log = (level: ConsoleKeys, ...messages: any) => console[level]('[http]', ...messages); 14 | 15 | // request 16 | instance.onRequest(config => { 17 | log('info', 'Request:', config) 18 | return config 19 | }) 20 | 21 | instance.onRequestError(error => { 22 | log('error', 'Request error:', error) 23 | }) 24 | 25 | // response 26 | instance.onResponse(res => { 27 | log('info', 'Response:', res) 28 | return res 29 | }) 30 | 31 | instance.onResponseError(error => { 32 | log('error', 'Response error:', error) 33 | }) 34 | } 35 | 36 | return instance 37 | } 38 | 39 | export default defineNuxtPlugin(ctx => { 40 | const runtimeConfig = useRuntimeConfig() 41 | 42 | // Use runtime config to configure options, with module options as the fallback 43 | const config = process.server ? defu({}, runtimeConfig.http, runtimeConfig.public.http, options) : defu({}, runtimeConfig.public.http, options) as any 44 | 45 | // baseURL 46 | const baseURL = process.client ? config.browserBaseURL : config.baseURL 47 | 48 | // Defaults 49 | const defaults = { 50 | baseURL, 51 | retry: config.retry, 52 | timeout: process.server ? config.serverTimeout : config.clientTimeout, 53 | credentials: config.credentials, 54 | headers: config.headers, 55 | } 56 | 57 | if (config.proxyHeaders) { 58 | // Proxy SSR request headers 59 | if (import.meta.server && ctx.ssrContext?.event.node.req.headers) { 60 | const reqHeaders = { ...ctx.ssrContext.event.node.req.headers } 61 | for (const h of config.proxyHeadersIgnore) { 62 | delete reqHeaders[h] 63 | } 64 | 65 | defaults.headers = { ...reqHeaders, ...defaults.headers } 66 | } 67 | } 68 | 69 | const http = httpInstance(defaults) 70 | 71 | if (!globalThis.$http) { 72 | globalThis.$http = http 73 | } 74 | 75 | return { 76 | provide: { 77 | http: $http 78 | } 79 | } 80 | }) -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { FetchConfig, FetchInstance } from '@refactorjs/ofetch' 2 | import * as NuxtSchema from '@nuxt/schema'; 3 | 4 | export interface ModuleOptions extends Omit { 5 | baseURL?: string; 6 | browserBaseURL?: string; 7 | host?: string; 8 | prefix?: string; 9 | proxyHeaders?: boolean; 10 | proxyHeadersIgnore?: string[]; 11 | serverTimeout?: number, 12 | clientTimeout?: number, 13 | port?: string | number; 14 | https?: boolean; 15 | retry?: number; 16 | credentials?: 'same-origin' | 'omit' | 'include'; 17 | headers?: any; 18 | debug?: boolean; 19 | } 20 | 21 | declare global { 22 | var $http: FetchInstance; 23 | namespace NodeJS { 24 | interface Global { 25 | $http: FetchInstance; 26 | } 27 | } 28 | } 29 | 30 | declare module '@nuxt/schema' { 31 | interface NuxtConfig { 32 | ['http']?: Partial 33 | } 34 | interface NuxtOptions { 35 | ['http']?: ModuleOptions 36 | } 37 | } 38 | 39 | declare module "h3" { 40 | interface H3Event { 41 | /** @experimental Calls fetch with same context and request headers */ 42 | $http: typeof $http; 43 | } 44 | } 45 | 46 | declare module '#app' { 47 | interface NuxtApp extends HttpPluginInjection {} 48 | } 49 | 50 | interface HttpPluginInjection { 51 | $http: FetchInstance; 52 | } 53 | 54 | declare const NuxtHttp: NuxtSchema.NuxtModule 55 | 56 | export default NuxtHttp -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json", 3 | } 4 | --------------------------------------------------------------------------------